SwiftUI-通过将锚定在按钮框架上的按钮点击动画到屏幕上

时间:2019-07-31 23:58:01

标签: swiftui

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

enter image description here

下面是产生它的代码:

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)
            }
        }
    }
}

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

我整理了一下当点击左侧的蓝色矩形时的外观的样机:

enter image description here

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

1 个答案:

答案 0 :(得分:1)

您可以从GeometryReader,首选项和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)
    }
}
相关问题