SwiftUI:自定义模态动画

6

我使用SwiftUI制作了一个自定义modal。它能正常工作,但动画效果不佳。

如果以慢动作播放,你会发现当触发ModalOverlay的点击操作后,ModalContent的背景立即消失。然而,ModalContent中的Text视图保持可见状态。

请问有人知道如何防止ModalContent的背景过早消失吗?

以下是慢动作视频和代码:

enter image description here

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)
            }
        }
    }
}
1个回答

0
解决方案(感谢 @JWK):
可能是一个错误。在过渡动画期间(当视图消失时),涉及到的两个视图(ModalContentModalOverlay)的zIndex没有得到尊重。 ModalContent(应该在ModalOverlay之前)实际上被移动到动画开始时在ModalOverlay下面。为了解决这个问题,我们可以在ModalContent视图上手动设置zIndex为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):

enter image description here

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

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

enter image description here

3) 但是,如果你将ModalContent的背景颜色改为绿色(这样你就同时拥有了ContentViewModalContent都是绿色),问题会再次出现(我不会再发布另一个GIF,但你可以轻松地自己尝试)。

4) 另一个例子:如果你将iPhone的外观更改为Dark Appearance(iOS 13的新功能),你的ContentView将自动变成黑色,而由于你的ModalView是白色的,问题就不会发生,一切都很顺利。


感谢您的调查工作!我认为您说得对,这是一个 bug。这种行为似乎完全出乎意料。而且不清楚为什么 ModalContent 的背景在某些情况下会在只指定 .move(edge: .bottom) 的情况下进行��渡时具有不透明度。我将使用 Feedback Assistant 提交一份报告。 - JWK
1
原來這裡實際上有個解決方法!它也考慮了我之前評論中感知的不透明度變化。只需在 ModalContent 上設置 zIndex: ModalContent().transition(.move(edge: .bottom)).zIndex(1)。由於某些原因,當狀態改變時,ModalContent 會落在 ModalOverlay 的後面。我仍然同意這是個問題,但這似乎可以解決問題。 - JWK
哦,现在我们明白了为什么问题似乎只在ContentView和子视图具有相同的背景颜色时出现。无论如何,是一个bug。现在我会修改我的答案来解释你发现的内容。如果你愿意,甚至可以回答自己的问题,然后接受你的答案。 - superpuccio

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接