【问题标题】:SwiftUI: Half screen sheet with Binging<Item>SwiftUI:带有 Binging<Item> 的半屏表单
【发布时间】:2022-11-14 17:07:53
【问题描述】:

我的目标是在 SwiftUI 环境中制作半屏。我知道仅在 iOS 16 中使用 SwiftUI 很容易,但我需要支持 iOS 15。我发现 article 很好,其中非常清楚地描述了如何在 UIKit 中进行操作,但只能使用 isPresented 修饰符。根据我的需要重构这个代码库是没有问题的,但是我面临的问题让我卡住了。如何将Binding&lt;Item&gt; 传递到工作表中以显示数据并进行更改?想要得到相同的functionality

我不确定,但我猜这部分代码打开了可用性:

@ViewBuilder content: @escaping (Item) -> Content

我一直没有成功地尝试实施它并寻求帮助

代码库:

import SwiftUI

// 1 - Create a UISheetPresentationController that can be used in a SwiftUI interface
struct SheetPresentationForSwiftUI<Item, Content>: UIViewRepresentable where Item: Identifiable, Content: View {
    
    @Binding var item: Item?
    let onDismiss: (() -> Void)?
    let detents: [UISheetPresentationController.Detent]
    let content: Content
    
    
    init(
        _ item: Binding<Item?>,
        onDismiss: (() -> Void)? = nil,
        detents: [UISheetPresentationController.Detent] = [.medium()],
        @ViewBuilder content: () -> Content
    ) {
        self._item = item
        self.onDismiss = onDismiss
        self.detents = detents
        self.content = content()
    }
    
    func makeUIView(context: Context) -> UIView {
        let view = UIView()
        return view
    }
    
    func updateUIView(_ uiView: UIView, context: Context) {
        
        // Create the UIViewController that will be presented by the UIButton
        let viewController = UIViewController()
        
        // Create the UIHostingController that will embed the SwiftUI View
        let hostingController = UIHostingController(rootView: content)
        
        // Add the UIHostingController to the UIViewController
        viewController.addChild(hostingController)
        viewController.view.addSubview(hostingController.view)
        
        // Set constraints
        hostingController.view.translatesAutoresizingMaskIntoConstraints = false
        hostingController.view.leftAnchor.constraint(equalTo: viewController.view.leftAnchor).isActive = true
        hostingController.view.topAnchor.constraint(equalTo: viewController.view.topAnchor).isActive = true
        hostingController.view.rightAnchor.constraint(equalTo: viewController.view.rightAnchor).isActive = true
        hostingController.view.bottomAnchor.constraint(equalTo: viewController.view.bottomAnchor).isActive = true
        hostingController.didMove(toParent: viewController)
        
        // Set the presentationController as a UISheetPresentationController
        if let sheetController = viewController.presentationController as? UISheetPresentationController {
            sheetController.detents = detents
            sheetController.prefersGrabberVisible = true
            sheetController.prefersScrollingExpandsWhenScrolledToEdge = false
            sheetController.largestUndimmedDetentIdentifier = .medium
        }
        
        // Set the coordinator (delegate)
        // We need the delegate to use the presentationControllerDidDismiss function
        viewController.presentationController?.delegate = context.coordinator
        
        
        if item != nil {
            // Present the viewController
            uiView.window?.rootViewController?.present(viewController, animated: true)
        } else {
            // Dismiss the viewController
            uiView.window?.rootViewController?.dismiss(animated: true)
        }
        
    }
    
    /* Creates the custom instance that you use to communicate changes
    from your view controller to other parts of your SwiftUI interface.
     */
    func makeCoordinator() -> Coordinator {
        Coordinator(item: $item, onDismiss: onDismiss)
    }
    
    class Coordinator: NSObject, UISheetPresentationControllerDelegate {
        @Binding var item: Item?
        let onDismiss: (() -> Void)?
        
        init(item: Binding<Item?>, onDismiss: (() -> Void)? = nil) {
            self._item = item
            self.onDismiss = onDismiss
        }
        
        func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
            item = nil
            if let onDismiss = onDismiss {
                onDismiss()
            }
        }
        
    }
    
}

// 2 - Create the SwiftUI modifier conforming to the ViewModifier protocol
struct sheetWithDetentsViewModifier<Item, SwiftUIContent>: ViewModifier where Item: Identifiable, SwiftUIContent: View {
    
    @Binding var item: Item?
    let onDismiss: (() -> Void)?
    let detents: [UISheetPresentationController.Detent]
    let swiftUIContent: SwiftUIContent
    
    init(
        _ item: Binding<Item?>,
        detents: [UISheetPresentationController.Detent] = [.medium()] ,
        onDismiss: (() -> Void)? = nil,
        content: () -> SwiftUIContent
    ) {
        self._item = item
        self.onDismiss = onDismiss
        self.swiftUIContent = content()
        self.detents = detents
    }
    
    func body(content: Content) -> some View {
        ZStack {
            SheetPresentationForSwiftUI($item, onDismiss: onDismiss, detents: detents) {
                swiftUIContent
            }
            .fixedSize()
            content
        }
    }
}

// 3 - Create extension on View that makes it easier to use the custom modifier
extension View {
    
    func sheetWithDetents<Item: Identifiable, Content: View>(
        item: Binding<Item?>,
        detents: [UISheetPresentationController.Detent],
        onDismiss: (() -> Void)?,
        content: @escaping () -> Content) -> some View where Content : View {
            modifier(
                sheetWithDetentsViewModifier(
                    item,
                    detents: detents,
                    onDismiss: onDismiss,
                    content: content)
            )
        }
}

【问题讨论】:

    标签: swift swiftui binding uikit


    【解决方案1】:

    您可以尝试使用带有自定义 UIViewControllerUIViewControllerRepresentable 来呈现 sheetController。这样你就不需要依赖找到窗口的 rootViewController。

    此外,make 用于创建所有 UI 对象,update 用于使用可表示结构的 let 和 vars 的值更新它们的属性,例如你需要一个 @Binding var item 并且当它不是 nil 时显示工作表并 nil 隐藏它(来自update func)。您可以在自定义 UIViewController 上调用一个方法,以便您可以根据需要将 sheetController 保留在该对象中,这样更容易更新它。

    【讨论】:

    • 对不起,但我真的不明白为什么需要添加isPresented,我试图避免Binding&lt;Bool&gt; 逻辑并改用Binding&lt;Item&gt;,我做错了什么吗?这是我想重新创建的示例,但只有半屏 developer.apple.com/documentation/swiftui/view/…
    猜你喜欢
    • 2021-09-23
    • 2022-07-27
    • 2020-02-08
    • 2020-04-28
    • 1970-01-01
    • 2017-04-04
    • 2012-10-16
    • 1970-01-01
    相关资源
    最近更新 更多