【问题标题】:How can you transition the size of a view back and forth in SwiftUI?如何在 SwiftUI 中来回转换视图的大小?
【发布时间】:2020-08-11 01:45:57
【问题描述】:

编辑:在 Asperi 的帮助下,我决定重写描述,以便通过简单的复制+粘贴代码更好地阐明问题。

所有测试的预期行为:当点击右上角的Present 按钮时,红色矩形会将其大小从零设置为父视图的大小。当再次点击Present 时,红色矩形将从父视图的大小缩小到零。


测试 #1 属性状态更改

实际行为:

按预期工作。

代码:

struct ContentView: View {  
    @State private var presentRedBox = false  

    var body: some View {  
        NavigationView {  
            GeometryReader { proxy in  
                ZStack {  
                    // ------  
                    Rectangle().fill(Color.red)  
                        .frame(  
                            width: self.presentRedBox ? proxy.size.width : 0.0,  
                            height: self.presentRedBox ? proxy.size.height : 0.0  
                        )  
                    // ------  
                }  
            }.animation(.default)  
            .navigationBarItems(trailing: Button("Present") { self.presentRedBox.toggle() })  
            .navigationBarTitle(Text(""), displayMode: .inline)  
        }  
    }  
}  

使用属性状态更改测试 #2 动画/视图修改器

实际行为:

按预期工作。

代码:

extension AnyTransition {  
    static func sizeTransition(from: CGSize, to: CGSize) -> AnyTransition {  
        .modifier(  
            active: SizeTransition(size: from),  
            identity: SizeTransition(size: to)  
        )  
    }  
}  

struct SizeTransition: AnimatableModifier {  
    var size: CGSize  

    var animatableData: AnimatablePair<cgfloat, cgfloat=""> {  
        get { AnimatablePair(size.width, size.height) }  
        set {  
            size.width = newValue.first  
            size.height = newValue.second  
        }  
    }  

    func body(content: Content) -> some View {  
        print(size)  
        return content.frame(  
            width: size.width,  
            height: size.height  
        )  
    }  
}  

struct ContentView: View {  
    @State private var presentRedBox = false  

    var body: some View {  
        NavigationView {  
            GeometryReader { proxy in  
                ZStack {  
                    // ------  
                    Rectangle().fill(Color.red)  
                        .modifier(  
                            SizeTransition(  
                                size: self.presentRedBox ? proxy.size : .zero  
                            )  
                        )  
                    // ------  
                }  
            }.animation(.default)  
            .navigationBarItems(trailing: Button("Present") { self.presentRedBox.toggle() })  
            .navigationBarTitle(Text(""), displayMode: .inline)  
        }  
    }  
} 

测试#3 带有过渡的动画/视图修改器

实际行为:

红色矩形将按预期动画。 但是(!)它会动画出来,但会立即消失,尽管日志显示了正确的值。

登录动画

(0.0, 0.0)  
(1.8118343353271484, 3.3873424530029297)  
(7.392631530761719, 13.821006774902344)  
(16.9350643157959, 31.66120719909668)  
(30.5800838470459, 57.17146110534668)  
(48.38059616088867, 90.45067977905273)  
(70.25803184509277, 131.35197257995605)  
(95.95654678344727, 179.39702224731445)  
(124.99998664855957, 233.6956272125244)  
(156.67254066467285, 292.90953254699707)  
(190.03098106384277, 355.27531242370605)  
(223.97296714782715, 418.73206901550293)  
(257.33140754699707, 481.0978488922119)  
(289.00356674194336, 540.3110160827637)  
(318.04700660705566, 594.6096210479736)  
(343.7447319030762, 642.6531944274902)  
(365.6217727661133, 683.5537490844727)  
(383.42189025878906, 716.8322296142578)  
(397.06651496887207, 742.3417453765869)  
(406.60855293273926, 760.1812076568604)  
(412.18856048583984, 770.613395690918)  
(414.0, 774.0)  

记录动画

(413.61268043518066, 773.2758808135986)  
(410.07547760009766, 766.6628494262695)  
(402.6749496459961, 752.8270797729492)  
(391.2381649017334, 731.4452648162842)  
(375.6612854003906, 702.3232727050781)  
(355.94628524780273, 665.4647941589355)  
(332.24832916259766, 621.1599197387695)  
(304.9215717315674, 570.070764541626)  
(274.5523223876953, 513.2934722900391)  
(241.9665470123291, 452.3722400665283)  
(208.19354438781738, 389.231409072876)  
(174.37908554077148, 326.0130729675293)  
(141.67486381530762, 264.870397567749)  
(111.12004852294922, 207.74617767333984)  
(83.55758285522461, 156.21635055541992)  
(59.59075355529785, 111.40880012512207)  
(39.58871841430664, 74.01369094848633)  
(23.71967124938965, 44.34547233581543)  
(11.994667053222656, 22.42481231689453)  
(4.315790176391602, 8.06865119934082)  
(0.5136623382568359, 0.9603252410888672)  
(0.0, 0.0)  

代码:

extension AnyTransition {  
    static func sizeTransition(from: CGSize, to: CGSize) -> AnyTransition {  
        .modifier(  
            active: SizeTransition(size: from),  
            identity: SizeTransition(size: to)  
        )  
    }  
}  

struct SizeTransition: AnimatableModifier {  
    var size: CGSize  

    var animatableData: AnimatablePair<cgfloat, cgfloat=""> {  
        get { AnimatablePair(size.width, size.height) }  
        set {  
            size.width = newValue.first  
            size.height = newValue.second  
        }  
    }  

    func body(content: Content) -> some View {  
        print(size)  
        return content.frame(  
            width: size.width,  
            height: size.height  
        )  
    }  
}  

struct ContentView: View {  
    @State private var presentRedBox = false  

    var body: some View {  
        NavigationView {  
            GeometryReader { proxy in  
                ZStack {  
                    // ------  
                    if self.presentRedBox {  
                        Rectangle().fill(Color.red)  
                            .transition(  
                                .modifier(  
                                    active: SizeTransition(size: .zero),  
                                    identity: SizeTransition(size: proxy.size)  
                                )  
                            )  
                    }  
                    // ------  
                }  
            }.animation(.default)  
            .navigationBarItems(trailing: Button("Present") { self.presentRedBox.toggle() })  
            .navigationBarTitle(Text(""), displayMode: .inline)  
        }  
    }  
}  

测试 #4 动画/视图修改器与不透明度过渡

预期行为:

当点击右上角的Present 按钮时,红色矩形会将其不透明度从零(隐藏)变为一(可见)。当再次点击Present 时,红色矩形将从一(可见)隐藏到零(隐藏)。

实际行为:

按预期工作。

代码:

extension AnyTransition {
    static func sizeTransition(from: CGSize, to: CGSize) -> AnyTransition {
        .modifier(
            active: SizeTransition(size: from),
            identity: SizeTransition(size: to)
        )
    }
}

struct SizeTransition: AnimatableModifier {
    var size: CGSize

    var animatableData: AnimatablePair<CGFloat, CGFloat> {
        get { AnimatablePair(size.width, size.height) }
        set {
            size.width = newValue.first
            size.height = newValue.second
        }
    }

    func body(content: Content) -> some View {
        print(size)
        return content.opacity(Double(size.width))
    }
}

struct ContentView: View {
    @State private var presentRedBox = false

    var body: some View {
        NavigationView {
            GeometryReader { proxy in
                ZStack {
                    // ------
                    if self.presentRedBox {
                        Rectangle().fill(Color.red)
                            .transition(
                                .modifier(
                                    active: SizeTransition(size: .zero),
                                    identity: SizeTransition(size: CGSize(width: 1.0, height: 1.0))
                                )
                            )
                    }
                    // ------
                }
            }.animation(.default)
            .navigationBarItems(trailing: Button("Present") { self.presentRedBox.toggle() })
            .navigationBarTitle(Text(""), displayMode: .inline)
        }
    }
}

【问题讨论】:

    标签: xcode animation swiftui transition


    【解决方案1】:

    我知道,这不是您所要求的完全解决方案,但在某些情况下可能有用,所以我决定发布它。

    注意:我是从你之前的帖子代码开始的,所以可能与这篇文章有点不一致。

    最初提出的替代方案 - 仅基于动画。

    struct TestReverseTransitions: View {
        @State private var showRedBox = false
        var body: some View {
            VStack {
               Button("Tap") { self.showRedBox.toggle() }
                  RedBox()
                     .modifier(SizeAnimation(size: showRedBox ? 
                               CGSize(width: 200.0, height: 200.0) : .zero))
            }.animation(.default)
        }
    }
    

    动画修饰符的使用与下面提供的调查相同。

    过渡调查:

    实际上我认为这可能是一个错误,但可能是过渡引擎的限制,因为基于效果的过渡,但这里只是物理视图框架的更改,而实际上视图已被删除.所以 50/50...也许值得向 Apple 报告反馈。

    这就是为什么...

    我使用 animatable 修饰符通过可动画数据显式更改帧,正如在 Demo2 调试日志中看到的那样,框架在移除框时确实是可动画的,但外观不是,但是按钮被移动了。漏洞?也许吧。

    本案例代码:

    extension AnyTransition {
        static func size(from: CGSize, to: CGSize) -> AnyTransition {
            AnyTransition.modifier(
                active: SizeAnimation(size: from),
                identity: SizeAnimation(size: to)
            )
        }
    }
    
    struct SizeAnimation: AnimatableModifier {
        var size: CGSize
        var animatableData: AnimatablePair<CGFloat, CGFloat> {
            get { AnimatablePair(size.width, size.height) }
            set {
                size.width = newValue.first
                size.height = newValue.second
            }
        }
    
        func body(content: Content) -> some View {
            //    print(size) // << uncomment for log sizes !!!
            return content.frame(width: size.width, height: size.height)
        }
    }
    
    struct TestReverseTransitions: View {
        @State private var showRedBox = false
        var body: some View {
            VStack {
                Button("Tap") { self.showRedBox.toggle() }
                if self.showRedBox {
                    RedBox()
                        .transition(
                            .size(from: CGSize.zero, to: CGSize(width: 200.0, height: 200.0))
                        )
                }
            }.animation(.default)
        }
    }
    
    struct RedBox: View {
        var body: some View {
            Rectangle().fill(Color.red)
        }
    }
    

    【讨论】:

    • 非常感谢您在答案中投入的时间!感谢您发布。至少它教会了我如何使用 AnimatableModifier,我将尝试理解你现在写的内容 =)
    • 好的,我知道了。感觉就像一个错误,没有理由为什么它不应该以一种方式工作,而不能以另一种方式工作。过渡应该完全用于此目的。
    • 我重写了问题的描述以更好地概述问题。
    猜你喜欢
    • 1970-01-01
    • 2019-10-23
    • 1970-01-01
    • 1970-01-01
    • 2020-02-19
    • 1970-01-01
    • 2022-10-05
    • 2021-11-09
    • 2019-10-29
    相关资源
    最近更新 更多