【问题标题】:SwiftUI: Custom Modal AnimationSwiftUI:自定义模态动画
【发布时间】:2019-09-12 08:24:23
【问题描述】:

我使用 SwiftUI 制作了一个自定义模式。它工作正常,但动画很不稳定。

慢动作播放时,可以看到ModalContent的背景在触发ModalOverlay的点击动作后立即消失。但是,ModalContentText 视图始终可见。

谁能告诉我如何防止ModalContent的背景过早消失?

下面的慢动作视频和代码:

import SwiftUI

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

    var body: some View {
        GeometryReader { geometry in
            ZStack {
                Button(
                    action: { withAnimation { self.isShowingModal = true } },
                    label: { Text("Show Modal") }
                )

                ZStack {
                    if self.isShowingModal {
                        ModalOverlay(tapAction: { withAnimation { self.isShowingModal = false } })
                        ModalContent().transition(.move(edge: .bottom))
                    }
                }.edgesIgnoringSafeArea(.all)
            }
        }
    }
}

struct ModalOverlay: View {
    var color = Color.black.opacity(0.4)
    var tapAction: (() -> Void)? = nil

    var body: some View {
        color.onTapGesture { self.tapAction?() }
    }
}

struct ModalContent: View {
    var body: some View {
        GeometryReader { geometry in
            VStack {
                Spacer()
                VStack(spacing: 16) {
                    Text("Item 1")
                    Text("Item 2")
                    Text("Item 3")
                }
                .frame(width: geometry.size.width)
                .padding(.top, 16)
                .padding(.bottom, geometry.safeAreaInsets.bottom)
                .background(Color.white)
            }
        }
    }
}

【问题讨论】:

    标签: animation modal-dialog swiftui


    【解决方案1】:

    解决方案(感谢@JWK):

    这可能是一个错误。似乎在过渡动画期间(当视图消失时)所涉及的两个视图(ModalContentModalOverlay)中的zIndex 不受尊重。 ModalContent(应该在ModalOverlay前面)实际上在动画开始时移动到ModalOverlay下面。为了解决这个问题,我们可以手动将zIndex 设置为,例如,ModalContent 视图上的 1。

    struct ContentView: View {
        @State private var isShowingModal = false
    
        var body: some View {
            GeometryReader { geometry in
                ZStack {
                    Button(
                        action: { withAnimation { self.isShowingModal = true } },
                        label: { Text("Show Modal") }
                    )
    
                    ZStack {
                        if self.isShowingModal {
                            ModalOverlay(tapAction: { withAnimation(.easeOut(duration: 5)) { self.isShowingModal = false } })
                            ModalContent()
                                .transition(.move(edge: .bottom))
                                .zIndex(1)
                        }
                    }.edgesIgnoringSafeArea(.all)
                }
            }
        }
    }
    

    找到解决方案的调查

    SwiftUI 中的过渡动画仍然存在一些问题。我认为这是一个错误。我很确定,因为:

    1) 您是否尝试将ModalContent 的背景颜色从白色更改为绿色?

    struct ModalContent: View {
        var body: some View {
            GeometryReader { geometry in
                VStack {
                    Spacer()
                    VStack(spacing: 16) {
                        Text("Item 1")
                        Text("Item 2")
                        Text("Item 3")
                    }
                    .frame(width: geometry.size.width)
                    .padding(.top, 16)
                    .padding(.bottom, geometry.safeAreaInsets.bottom)
                    .background(Color.green)
                }
            }
        }
    }
    

    这样工作(见下面的 GIF):

    2) 另一种使错误发生的方法是将ContentView 的背景颜色更改为例如绿色,而将ModalContent 保留为白色:

    struct ContentView: View {
        @State private var isShowingModal = false
    
        var body: some View {
            GeometryReader { geometry in
                ZStack {
                    Button(
                        action: { withAnimation(.easeOut(duration: 5)) { self.isShowingModal = true } },
                        label: { Text("Show Modal") }
                    )
    
                    ZStack {
                        if self.isShowingModal {
                            ModalOverlay(tapAction: { withAnimation(.easeOut(duration: 5)) { self.isShowingModal = false } })
                            ModalContent().transition(.move(edge: .bottom))
                        }
                    }
                }
            }
            .background(Color.green)
            .edgesIgnoringSafeArea(.all)
        }
    }
    
    struct ModalOverlay: View {
        var color = Color.black.opacity(0.4)
        var tapAction: (() -> Void)? = nil
    
        var body: some View {
            color.onTapGesture { self.tapAction?() }
        }
    }
    
    struct ModalContent: View {
        var body: some View {
            GeometryReader { geometry in
                VStack {
                    Spacer()
                    VStack(spacing: 16) {
                        Text("Item 1")
                        Text("Item 2")
                        Text("Item 3")
                    }
                    .frame(width: geometry.size.width)
                    .padding(.top, 16)
                    .padding(.bottom, geometry.safeAreaInsets.bottom)
                    .background(Color.white)
                }
            }
        }
    }
    

    即使在这种情况下,它也能按预期工作:

    3) 但是如果您将ModalContent 背景颜色更改为绿色(因此您同时拥有ContentViewModalContent 绿色),问题再次出现(我不会发布另一个 GIF,但您可以自己轻松尝试)。

    4) 另一个例子:如果你将 iPhone 的外观更改为深色外观(iOS 13 的新功能),你的ContentView 将自动变为黑色,并且由于你的ModalView 是白色的,问题不会发生,一切正常。

    【讨论】:

    • 欣赏调查工作!我认为你是对的,这是一个错误。这种行为似乎完全出乎意料。也不清楚为什么ModalContent 的背景在某些只指定.move(edge: .bottom) 的情况下对过渡有不透明性。我会向反馈助理提交报告。
    • 原来这里实际上有一个解决方法!它也解释了我之前评论中感知到的不透明度变化。只需在ModalContent 上设置zIndexModalContent().transition(.move(edge: .bottom)).zIndex(1)。无论出于何种原因,当状态发生变化时,ModalContent 将落后于ModalOverlay。我仍然同意这是一个错误,但这似乎可以解决问题。
    • 哦,现在我们明白了为什么只有当 ContentView 和子视图具有相同的 bg 颜色时才会出现问题。无论如何,是的,这是一个错误。现在,我将修改我的答案以解释您的发现。如果您愿意,您甚至可以回答自己的问题,然后接受您的回答。
    猜你喜欢
    • 1970-01-01
    • 2019-12-29
    • 2020-04-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-19
    • 2013-04-15
    • 1970-01-01
    相关资源
    最近更新 更多