SwiftUI - 从锚定到按钮框架的按钮点击动画到屏幕上

Posted

技术标签:

【中文标题】SwiftUI - 从锚定到按钮框架的按钮点击动画到屏幕上【英文标题】:SwiftUI - Animate view onto screen from button tap anchored to button frame 【发布时间】: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)
            
        
    

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

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

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

【问题讨论】:

【参考方案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 的知识比其他任何来源都多,因此非常感谢您的帮助。干杯!

以上是关于SwiftUI - 从锚定到按钮框架的按钮点击动画到屏幕上的主要内容,如果未能解决你的问题,请参考以下文章

按钮锚定到InputAccessoryView仅在其框架中工作

如何将对话框锚定到listview qt qml中的按钮

如何将浮动操作按钮锚定到App ToolBar,就像在指南图像中一样?

将 Windows 窗体工具提示锚定到鼠标

SwiftUI 按钮点击重置 SpriteKit 动画 - 这是一个错误吗?

如何在 SwiftUI 中点击手势时放大缩小按钮动画?