【问题标题】:Presenting UIActivityViewController from SwiftUI View从 SwiftUI 视图呈现 UIActivityViewController
【发布时间】:2021-07-29 03:46:48
【问题描述】:

我正在尝试从 SwiftUI 视图中呈现 UIActivityViewController(共享表)。我创建了一个名为ShareSheet 的视图符合UIViewControllerRepresentable 来配置UIActivityViewController,但实际呈现它并不是那么简单。

struct ShareSheet: UIViewControllerRepresentable {
    typealias UIViewControllerType = UIActivityViewController

    var sharing: [Any]

    func makeUIViewController(context: UIViewControllerRepresentableContext<ShareSheet>) -> UIActivityViewController {
        UIActivityViewController(activityItems: sharing, applicationActivities: nil)
    }

    func updateUIViewController(_ uiViewController: UIActivityViewController, context: UIViewControllerRepresentableContext<ShareSheet>) {

    }
}

通过.sheet 天真地这样做会导致以下结果。

.sheet(isPresented: $showShareSheet) {
    ShareSheet(sharing: [URL(string: "https://example.com")!])
}

有没有办法像通常呈现的那样呈现这个?就像覆盖半个屏幕一样?

【问题讨论】:

    标签: ios swift swiftui uiactivityviewcontroller


    【解决方案1】:

    希望对你有帮助,

    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.apple.com") else { return }
            let av = UIActivityViewController(activityItems: [data], applicationActivities: nil)
            UIApplication.shared.windows.first?.rootViewController?.present(av, animated: true, completion: nil)
        }
    }
    

    【讨论】:

    • 抱歉回复晚了,谢谢!
    • 这是一个糟糕的解决方案,不应标记为已接受。 ` UIApplication.shared.windows.first?.rootViewController?.present(av, animated: true, completion: nil)` 在很多情况下会无法获取到正确的vc。
    • 我在尝试使用 windows first.present 时看到了这个问题。我根据 OP 的版本使用了 .sheet 。需要注意的是共享表中的键盘无法正常工作。如果您的工作表不包含在父视图中,则响应者链很脆弱,我转而使用最外层的父视图。看起来像是 iOS15 自带的东西。
    【解决方案2】:

    有一个UIModalPresentationStyle 可用于显示某些演示文稿:

    case pageSheet

    一种部分覆盖底层内容的展示风格。

    你应用演示风格的方式:

    func makeUIViewController(context: UIViewControllerRepresentableContext<ActivityView>) -> UIActivityViewController {
        let v = UIActivityViewController(activityItems: sharing, applicationActivities: nil)
        v.modalPresentationStyle = .pageSheet
        return v
    }
    

    可在此处找到演示文稿列表:

    https://developer.apple.com/documentation/uikit/uimodalpresentationstyle

    我自己还没有对它们进行全部测试,所以如果最终没有像您预期的那样工作,我提前道歉。

    或者,您可以查看this answer,他们在其中提到了第三方库,这将允许您以通常呈现的方式创建半模态。

    【讨论】:

    • 感谢您的提示,但我不确定是否可以使用 SwiftUI 指定 UIModalPresentationStyle :/
    • @KilianKoeltzsch 我已经更新了答案,包括您如何应用modalPresentationStyle。说您最好修改第三方库以满足您的需求。您也将拥有更多控制权。
    • 啊,有意思,我在创建视图控制器的时候都没有想过要设置它。虽然通过.sheet() 显示它时似乎没有得到尊重。我也会研究链接选项。
    • 即使您将 SwiftUI 视图包装在 UIHostingController 中并将包装器的 modalPresentationStyle 设置为 pageSheet,然后使用顶部控制器呈现上述内容,仍然几乎是全屏的 (就像问题中的图片一样)。 pageSheet 当然受到尊重,因为设置为其他内容确实会改变外观。唉,我无法让纸张半覆盖屏幕。
    【解决方案3】:

    它并不漂亮,但你可以直接这样调用它(考虑到你的应用只有 1 个窗口):

    UIApplication.shared.windows.first?.rootViewController?.present(activityViewController, animated: true, completion: nil)
    

    如果你得到一些警告blablabla:

    警告:尝试呈现……已经在呈现……

    您可以执行this to get the top most view controller 之类的操作并调用present。

    【讨论】:

      【解决方案4】:

      至少在 iOS 14、Swift 5、Xcode 12.5 中,我只需将 UIActivityViewController 包装在另一个视图控制器中即可相当轻松地完成此操作。它不需要检查视图层次结构或使用任何 3rd 方库。唯一骇人听闻的部分是异步呈现视图控制器,这甚至可能不是必需的。有更多 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)
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-03-16
        • 2021-01-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-01-25
        • 1970-01-01
        相关资源
        最近更新 更多