SwiftUI - 半模态?

93

我正在尝试在SwiftUI中重新创建类似于iOS13 Safari的Modal:

它看起来像这样:

图片描述

有没有人知道是否可以在SwiftUI中实现这个效果?我想展示一个小的半模态视图,并且可以拖拽到全屏显示,就像共享菜单一样。

非常感谢任何建议!


难道不只是overlay吗? - user28434'mstep
1
我不太确定,我认为这可能是模态框或弹出窗口上的一个选项,但目前文档相当稀少。 - ryannn
1
我发现 SwiftUI 中的模态选项的选项太少了。例如,我找不到一种方法来选择其演示样式为“FormSheet”。这是自 iOS 3.2 以来就存在的非常基本的功能!我不得不使用 UIHostingController 的技巧。 - kontiki
6
使用纯SwiftUI编写。享受吧! https://github.com/cyrilzakka/SwiftUIModal。实现全屏和半屏模态窗口功能。 - cyril
3
@ryannn 看起来 iOS 16 终于支持半页了 - 详情请参见此答案 - pawello2222
显示剩余2条评论
14个回答

1

我的作品:

var body: some View {
    ZStack {
        YOURTOPVIEW()
        VStack {
            Spacer()
                .frame(minWidth: .zero,
                       maxWidth: .infinity,
                       minHeight: .zero,
                       maxHeight: .infinity,
                       alignment: .top)
            YOURBOTTOMVIEW()
                .frame(minWidth: .zero,
                       maxWidth: .infinity,
                       minHeight: .zero,
                       maxHeight: .infinity,
                       alignment: .bottom)
        }

    }
}

1

我试图做与此处要求相同的事情,在SwiftUI中以本地方式显示共享表单,而无需实现/导入组件。 我在 https://jeevatamil.medium.com/how-to-create-share-sheet-uiactivityviewcontroller-in-swiftui-cef64b26f073中找到了这个解决方案。

struct ShareSheetView: View {
    var body: some View {
        Button(action: actionSheet) {
            Image(systemName: "square.and.arrow.up")
                .resizable()
                .aspectRatio(contentMode: .fit)
                .frame(width: 36, height: 36)
        }
    }
    
    func actionSheet() {
        guard let data = URL(string: "https://www.zoho.com") else { return }
        let av = UIActivityViewController(activityItems: [data], applicationActivities: nil)
        UIApplication.shared.windows.first?.rootViewController?.present(av, animated: true, completion: nil)
    }
}

0
在 iOS 14、Swift 5 和 Xcode 12.5 中,我很容易地通过将 UIActivityViewController 包装在另一个视图控制器中来实现这一点。它不需要检查视图层次结构或使用任何第三方库。唯一有些 hackish 的部分是异步呈现视图控制器,这可能甚至不是必要的。有更多 SwiftUI 经验的人可能能够提供改进建议。
import Foundation
import SwiftUI
import UIKit

struct ActivityViewController: UIViewControllerRepresentable {
        
    @Binding var shareURL: URL?
    
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    
    func makeUIViewController(context: Context) -> some UIViewController {
        let containerViewController = UIViewController()
        
        return containerViewController

    }
    
    func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
        guard let shareURL = shareURL, context.coordinator.presented == false else { return }
        
        context.coordinator.presented = true

        let activityViewController = UIActivityViewController(activityItems: [shareURL], applicationActivities: nil)
        activityViewController.completionWithItemsHandler = { activity, completed, returnedItems, activityError in
            self.shareURL = nil
            context.coordinator.presented = false

            if completed {
                // ...
            } else {
                // ...
            }
        }
        
        // Executing this asynchronously might not be necessary but some of my tests
        // failed because the view wasn't yet in the view hierarchy on the first pass of updateUIViewController
        //
        // There might be a better way to test for that condition in the guard statement and execute this
        // synchronously if we can be be sure updateUIViewController is invoked at least once after the view is added
        DispatchQueue.main.asyncAfter(deadline: .now()) {
            uiViewController.present(activityViewController, animated: true)
        }
    }
    
    class Coordinator: NSObject {
        let parent: ActivityViewController
        
        var presented: Bool = false
        
        init(_ parent: ActivityViewController) {
            self.parent = parent
        }
    }
    
}

struct ContentView: View {
    
    @State var shareURL: URL? = nil
    
    var body: some View {
        ZStack {
            Button(action: { shareURL = URL(string: "https://apple.com") }) {
                Text("Share")
                    .foregroundColor(.white)
                    .padding()
            }
            .background(Color.blue)
            if shareURL != nil {
                ActivityViewController(shareURL: $shareURL)
            }
        }
        .frame(width: 375, height: 812)
    }
}

0

为了更通用的解决方案,我想到了以下的方法:
https://github.com/mtzaquia/UIKitPresentationModifier

这是一个通用的修饰符,允许您在SwiftUI视图中使用UIKit呈现。

从那里开始,世界就是你的。唯一的缺点是您可能需要将自定义环境值从呈现视图级联到呈现的视图。

myPresentingView
  .presentation(isPresented: $isPresented) {
    MyPresentedView()
  } controllerProvider: { content in
    let controller = UIHostingController(rootView: content)
    if #available(iOS 15, *) {
      if let sheet = controller.sheetPresentationController {
        sheet.preferredCornerRadius = 12
        sheet.prefersGrabberVisible = true
      }
    }
    
    return controller
  }

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