为什么这个带有过渡的简单SwiftUI视图修饰符会导致崩溃?

时间:2020-07-02 14:36:49

标签: swiftui

我在SwiftUI中编写了一个非常简单的.modal View修饰符。

当它打开时,我按close,然后在转换再次完成open之前,出现以下崩溃:

 Gesture: Failed to receive system gesture state notification before next touch

示例代码:

import SwiftUI

struct ContentView: View {
    @State var show = false
    
    var body: some View {
        VStack {
            Button("Open") {
                withAnimation {
                    self.show.toggle()
                }
            }.disabled(self.show) // doesn't help
        }.modal(isShowing: self.$show) {
            Button("Close") {
                withAnimation {
                    self.show.toggle()
                }
            }
        }
    }
}

extension View {
    func modal<C>(isShowing: Binding<Bool>, @ViewBuilder content: @escaping () -> C) -> some View where C: View {
        self.modifier(ModalView(isShowing: isShowing, content: content))
    }
}

struct ModalView<C>: ViewModifier where C: View {
    @Binding var isShowing: Bool
    let content: () -> C
    
    func body(content: Content) -> some View {
        ZStack {
            content.zIndex(0)
            if self.isShowing {
                self.content()
                    .background(Color.primary.colorInvert())
                    .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
                    .transition(.move(edge: .bottom))
                    .zIndex(1)
            }
        }
    }
    
}

有人可以解释我如何防止这种情况发生吗?似乎负责切换的Bool是在动画完成之前设置的。这是设计使然吗?

1 个答案:

答案 0 :(得分:1)

是的,您需要禁用下面的内容(在介绍模态时),但要在其他地方有所不同

此处是固定变体。经过Xcode 12 / iOS 14的测试

struct ModalView<C>: ViewModifier where C: View {
    @Binding var isShowing: Bool
    let content: () -> C

    @State private var interactive: Bool   // track state

    init(isShowing: Binding<Bool>, @ViewBuilder content: @escaping () -> C) {
        self._isShowing = isShowing
        self._interactive = State(initialValue: !isShowing.wrappedValue)
        self.content = content
    }

    func body(content: Content) -> some View {
        ZStack {
            content.zIndex(0).disabled(!interactive)   // disable here !!
            if self.isShowing {
                self.content()
                    .background(Color.primary.colorInvert())
                    .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
                    .transition(.move(edge: .bottom))
                    .zIndex(1)
                    .onAppear { self.interactive = false }     // << !!
                    .onDisappear { self.interactive = true }   // << !!
            }
        }
    }
}