【问题标题】:SwiftUI's custom modal logic: how to present view from parentSwiftUI 自定义模态逻辑:如何从父级呈现视图
【发布时间】:2022-07-21 01:34:09
【问题描述】:

来自 UIKit,我正在 SwiftUI 中构建自己的模态导航逻辑,因为我想要自定义布局和动画。在这里,我想要一个像这样的通用底页:

我已经通过以下代码实现了一些接近:

enum ModalType: Equatable {
    case normal // ...
    
    @ViewBuilder
    var screen: some View {
        switch self {
        case .normal: ModalView()
            // ...
        }
    }
}

struct ContentView: View {
    @State var presentedModal: ModalType?
    
    var body: some View {
        VStack {
            Button("Present modal", action: { presentedModal = .normal }).foregroundColor(.black)
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(.gray)
        .modifier(ModalBottomViewModifier(item: $presentedModal) { $0.screen })
    }
}


struct ModalView: View {
    @Environment(\.dismissModal) private var dismissModal

    var body: some View {
        VStack {
            Button("Close", action: { dismissModal() })
        }
        .frame(maxWidth: .infinity)
        .frame(height: 300)
        .background(
            RoundedRectangle(cornerRadius: 32)
                .fill(.black.opacity(0.5))
                .edgesIgnoringSafeArea([.bottom])
        )
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}


// MARK: - Modal logic

struct ModalBottomViewModifier<Item:Equatable, V:View>: ViewModifier {
    @Binding var item: Item?
    @ViewBuilder var view: (Item) -> V
        
    func body(content: Content) -> some View {
        ZStack(alignment: .bottom) {
            content
            
            if let item = item {
                view(item)
                    .environment(\.dismissModal, { self.item = nil })
                    .transition(.move(edge: .bottom))
            }
        }
        .animation(.easeOut, value: item)
    }
}

private struct ModalDismissKey: EnvironmentKey {
    static let defaultValue: () -> Void = {}
}

extension EnvironmentValues {
    var dismissModal: () -> Void {
        get { self[ModalDismissKey.self] }
        set { self[ModalDismissKey.self] = newValue }
    }
}

现在我想让这个系统可重复使用,这样我就不必将ModalBottomViewModifier 添加到我所有的应用程序屏幕上。为此,我希望能够将修饰符应用于按钮而不是屏幕,就像 fullScreenCover 一样:

Button("Present modal", action: { isPresented = true }).foregroundColor(.black)
    .fullScreenCover(isPresented: $isPresented) { ModalView() }

这在我当前的解决方案中是不可能的,因为模态视图将出现在按钮旁边,而不是全屏。

我怎样才能做到这一点?还是我应该做一些不同的事情?

【问题讨论】:

  • 我相信你可以创建一个返回视图的函数。
  • 可以通过在非常根级别应用模态修饰符并通过功能修饰符的首选项键将条件控制转移到那里来实现(就像 fullScreenCover 一样)。

标签: swiftui


【解决方案1】:

这是一个使用UIKit的简单解决方案:

extension View {
    func presentModalView<Content: View, Item: Equatable>(item: Binding<Item?>, @ViewBuilder view: @escaping (Item) -> Content) -> some View {
        func present() {
            guard let itemy = item.wrappedValue else {return}
            DispatchQueue.main.async {
                let topMostController = self.topMostController()
                let someView = VStack {
                    Spacer()
                    view(itemy)
                        .environment(\.dismissModal, {item.wrappedValue = nil})
                }
                let viewController = UIHostingController(rootView: someView)
                viewController.view?.backgroundColor = .clear
                viewController.modalPresentationStyle = .overFullScreen
                topMostController.present(viewController, animated: true)
            }
        }
        return self.onChange(of: item.wrappedValue) { value in
            if value != nil {
                present()
            }else {
                topMostController().dismiss(animated: true)
            }
        }.onAppear {
            if item.wrappedValue != nil {
                present()
            }
        }
    }
    func topMostController() -> UIViewController {
        var topController: UIViewController = UIApplication.shared.windows.first!.rootViewController!
        while (topController.presentedViewController != nil) {
            topController = topController.presentedViewController!
        }
        return topController
    }
}

用法:

struct ContentView: View {
    @State var presentedModal: ModalType?
    var body: some View {
        VStack {
            Button("Present modal", action: { presentedModal = .normal }).foregroundColor(.black)
                .presentModalView(item: $presentedModal, view: {$0.screen})
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(.gray)
    }
}

【讨论】:

    猜你喜欢
    • 2021-09-18
    • 1970-01-01
    • 1970-01-01
    • 2021-07-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多