【问题标题】:SwiftUI - Animate view onto screen from button tap anchored to button frameSwiftUI - 从锚定到按钮框架的按钮点击动画到屏幕上
【发布时间】:2019-07-31 23:58:01
【问题描述】:

我创建了一个示例项目来使用颜色显示我的视图布局。这是它的样子:

这是生成它的代码:

struct ContentView: View {
    @State private var isShowingLeftPopup: Bool = false
    @State private var isShowingRightPopup: Bool = false

    var body: some View {
        TabView {
            VStack {
                Spacer()
                ZStack {
                    Color.red
                        .frame(height: 200)
                    HStack(spacing: 15) {
                        Color.accentColor
                            .disabled(self.isShowingRightPopup)
                            .onTapGesture {
                                self.isShowingLeftPopup.toggle()
                            }
                        Color.accentColor
                            .disabled(self.isShowingLeftPopup)
                            .onTapGesture {
                                self.isShowingRightPopup.toggle()
                            }
                    }
                    .frame(height: 70)
                    .padding(.horizontal)
                }
                Color.purple
                    .frame(height: 300)
            }
        }
    }
}

当点击两个蓝色矩形中的任何一个时,我想在蓝色矩形正下方的屏幕上制作一个动画视图,填充蓝色矩形和底部标签栏之间的垂直空间。动画目前并不那么重要 - 我不知道如何将条件视图锚定到蓝色矩形的底部并调整其大小以适应下面的剩余空间。

我制作了一个模型,展示了点击左侧蓝色矩形时的样子:

我在本例中使用了固定高度,但我正在寻找一种不依赖于固定值的解决方案。有谁知道如何将绿色矩形锚定到蓝色矩形的底部并动态调整其大小以填充一直到标签栏的垂直空间?

【问题讨论】:

    标签: swiftui


    【解决方案1】:

    您可以从 GeometryReader、Preferences 和 AnchorPreferences 中受益。我写了很多关于他们的文章。要详细了解它们的工作原理,请参考它们:

    GeometryReader 文章: https://swiftui-lab.com/geometryreader-to-the-rescue/

    偏好文章: https://swiftui-lab.com/communicating-with-the-view-tree-part-1/

    具体来说,对于您想要完成的任务,您需要知道蓝色视图和紫色视图(表示绿色视图的下限)的大小和位置。一旦你得到这些信息,剩下的就很容易了。下面的代码就是这样做的:

    import SwiftUI
    
    struct MyData {
        let viewName: String
        let bounds: Anchor<CGRect>
    }
    
    struct MyPreferenceKey: PreferenceKey {
        static var defaultValue: [MyData] = []
    
        static func reduce(value: inout [MyData], nextValue: () -> [MyData]) {
            value.append(contentsOf: nextValue())
        }
    
        typealias Value = [MyData]
    }
    
    struct ContentView: View {
        @State private var isShowingLeftPopup: Bool = false
        @State private var isShowingRightPopup: Bool = false
    
        var body: some View {
            TabView {
                VStack {
                    Spacer()
                    ZStack {
                        Color.red
                            .frame(height: 200)
                        HStack(spacing: 15) {
                            Color.accentColor
                                .disabled(self.isShowingRightPopup)
                                .onTapGesture {
                                    self.isShowingLeftPopup.toggle()
                                }
                                .anchorPreference(key: MyPreferenceKey.self, value: .bounds) {
                                    return [MyData(viewName: "leftView", bounds: $0)]
                                }
    
                            Color.accentColor
                                .disabled(self.isShowingLeftPopup)
                                .onTapGesture { self.isShowingRightPopup.toggle() }
                                .anchorPreference(key: MyPreferenceKey.self, value: .bounds) {
                                    return [MyData(viewName: "rightView", bounds: $0)]
                                }
                        }
                        .frame(height: 70)
                        .padding(.horizontal)
                    }
    
                    Color.purple
                        .frame(height: 300)
                        .anchorPreference(key: MyPreferenceKey.self, value: .bounds) {
                            return [MyData(viewName: "purpleView", bounds: $0)]
                        }
                }.overlayPreferenceValue(MyPreferenceKey.self) { preferences in
                    GeometryReader { proxy in
                        Group {
                            if self.isShowingLeftPopup {
                                ZStack(alignment: .topLeading) {
                                    self.createRectangle(proxy, preferences)
    
                                    HStack { Spacer() } // makes the ZStack to expand horizontally
                                    VStack { Spacer() } // makes the ZStack to expand vertically
                                }.frame(alignment: .topLeading)
                            } else {
                                EmptyView()
                            }
                        }
                    }
                }
            }
        }
    
        func createRectangle(_ geometry: GeometryProxy, _ preferences: [MyData]) -> some View {
    
            let l = preferences.first(where: { $0.viewName == "leftView" })
            let r = preferences.first(where: { $0.viewName == "rightView" })
            let p = preferences.first(where: { $0.viewName == "purpleView" })
    
            let bounds_l = l != nil ? geometry[l!.bounds] : .zero
            let bounds_r = r != nil ? geometry[r!.bounds] : .zero
            let bounds_p = p != nil ? geometry[p!.bounds] : .zero
    
            return RoundedRectangle(cornerRadius: 15)
                .fill(Color.green)
                .frame(width: bounds_r.maxX - bounds_l.minX, height: bounds_p.maxY - bounds_l.maxY)
                .fixedSize()
                .offset(x: bounds_l.minX, y: bounds_l.maxY)
        }
    }
    

    【讨论】:

    • 正是我想要的——非常感谢!我从您那里学到的关于 SwiftUI 的知识比其他任何来源都多,因此非常感谢您的帮助。干杯!
    猜你喜欢
    • 2020-09-06
    • 1970-01-01
    • 2014-09-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-08
    相关资源
    最近更新 更多