简体   繁体   English

在 SwiftUI 中定位另一个视图的 position

[英]Locating position of another View in SwiftUI

I am building a card game (Set) and when dealing the cards I am currently using我正在构建一个纸牌游戏(套装),并且在处理我当前使用的纸牌时

withAnimation{} and .transition(.move(edge: .bottom)

to have the cards animate from the bottom edge of the screen when the user taps the deal button.当用户点击发牌按钮时,让卡片从屏幕的底部边缘开始动画。 I want to make it so that the cards 'fly out' of the button and was wondering how I find the location of the button.我想让卡片“飞出”按钮,我想知道如何找到按钮的位置。

Once I find the position of the button I intend to use.offset to have them 'fly' out.一旦我找到按钮的 position 我打算使用.offset 让它们“飞”出来。 Is there a better way?有没有更好的办法?

I know it is possible using geometry reader, but I have not found out how to do so.我知道使用几何阅读器是可能的,但我还没有找到如何做到这一点。

Here is my current code这是我当前的代码

struct SetGameView: View {
    
    @ObservedObject var viewModel: SetViewModel = SetViewModel()
    var location: (CGFloat, CGFloat) = (0.0, 0.0)   

    var body: some View {
        VStack {
            HStack {
                Label("Score: \(viewModel.score)", systemImage: "face.dashed").font(.title).padding(.leading)
                Spacer()
            }
            Grid(viewModel.dealtCards) { card in
                CardView(card: card)
                    .transition(.asymmetric(insertion: .move(edge: .bottom), removal: .move(edge: .leading)))
                    .padding()
                    .onTapGesture { withAnimation { viewModel.choose(card) } }
            } .onAppear { withAnimation(.easeInOut) { deal(12) } }
            Divider()
            HStack {
                CreateNewGameButton(viewModel: viewModel)
                DealNewCardbutton(viewModel: viewModel, deal: deal)
            }.padding(.horizontal).frame(maxHeight: 50)
        }
    }
    
    struct DealNewCardbutton: View {
        
        var viewModel: SetViewModel
        let deal: (Int) -> Void
        
        init(viewModel: SetViewModel, deal: @escaping (Int) -> Void) {
            self.viewModel = viewModel
            self.deal = deal
        }
        
        var body: some View {
            GeometryReader { geo in
                Button(action: {
                    deal(3)
                }){
                    ZStack {
                        RoundedRectangle(cornerRadius: 10.0).foregroundColor(.blue)
                        Text("Deal Three Cards").foregroundColor(.white)
                    }
                }.onAppear {
                    print(geo.frame(in: .global).midX, geo.frame(in: .global).midY)
                }
            }
        }
    }

Heres a video of how it currently works.这是一个关于它当前如何工作的视频。

https://i.imgur.com/NJfOjBP.mp4 https://i.imgur.com/NJfOjBP.mp4

I want the cards to all 'fly out' from the deal button.我希望卡片从交易按钮中全部“飞出”。

Ok.好的。 This is pretty tricky.这很棘手。 You can get the position of the button via GeometryGetter , but I'd recommend using anchorPreference .您可以通过GeometryGetter获得按钮的 position ,但我建议使用anchorPreference The only downside is that you'll have to specify your grid in an overlay or background of the view containing the button.唯一的缺点是您必须在包含按钮的视图的叠加层或背景中指定网格。

import SwiftUI

struct BoundsPreferenceKey: PreferenceKey {

    static var defaultValue: [Int: Anchor<CGRect>] = [:]
    
    static func reduce(value: inout [Int: Anchor<CGRect>], nextValue: () -> [Int: Anchor<CGRect>]) {
        value.merge(nextValue()) { v1, _ in v1 }
    }
}

extension View {
    func reportBounds(id: Int) -> some View {
        self.anchorPreference(key: BoundsPreferenceKey.self, value: .bounds, transform: { [id: $0] })
    }
}

private extension Int {
    static let dealButtonId = 0
}

var body: some View {
    VStack {
        Divider()
        HStack {
            CreateNewGameButton(viewModel: viewModel)
            DealNewCardbutton(viewModel: viewModel, deal: deal)
                .reportBounds(id: .dealButtonId)
        }   .padding(.horizontal).frame(maxHeight: 50)
    }   .overlayPreferenceValue(BoundsPreferenceKey.self) { preferences in
        GeometryReader { geometry in
            self.getOverlay(preferences: preferences, geometry: geometry)           
        }
    }
}

func getOverlay(preferences:(preferences : [Int: Anchor<CGRect>], geometry: GeometryProxy) -> some View {
        
    var center: CGPoint = .zero

    if let anchor = preferences[.dealButtonId] {
        let rect = geometry[anchor]
        center = CGPoint(x: rect.midX, y: rect.midY)
    }
    
    return VStack {
        HStack {
            Label("Score: \(viewModel.score)", systemImage: "face.dashed").font(.title).padding(.leading)
            Spacer()
        }
        Grid(viewModel.dealtCards) { card in
            CardView(card: card)
                // USE CENTER here in your transition and/or offset
                .transition(.asymmetric(insertion: .move(edge: .bottom), removal: .move(edge: .leading)))
                .padding()
                .onTapGesture { withAnimation { viewModel.choose(card) } }
        } .onAppear { withAnimation(.easeInOut) { deal(12) } }
        Spacer()
    }
}

Note that I moved the grid and other views to the overlay, you may need to set explicit frame heights (to the spacer) to make sure the bottom of the grid and the top of the button align.请注意,我将网格和其他视图移动到叠加层,您可能需要设置显式框架高度(到间隔)以确保网格底部和按钮顶部对齐。 I left the actual implementation of the transition for you.我为您留下了过渡的实际实施。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM