SwiftUI 导出或共享文件

Posted

技术标签:

【中文标题】SwiftUI 导出或共享文件【英文标题】:SwiftUI exporting or sharing files 【发布时间】:2019-06-29 17:07:51 【问题描述】:

我想知道是否有通过 的好方法。似乎没有办法包装 UIActivityViewController 并直接呈现它。我使用 UIViewControllerRepresentable 来包装 UIActivityViewController,如果我将它呈现在 SwiftUI 模态中,它会崩溃。

我能够创建一个通用的 UIViewController,然后从那里调用一个呈现 UIActivityViewController 的方法,但这需要大量的包装。

如果我们想使用 SwiftUI 从 Mac 共享,有没有办法包装 NSSharingServicePicker?

无论如何,如果有人有他们如何做到这一点的例子,将不胜感激。

【问题讨论】:

这个周末我将尝试做同样的事情。有一个关于使用 UIImagePickerController ***.com/questions/56515871/… 的问题,我正在工作并希望首先集成到我的应用程序中。看看那个答案——尤其是ContentView——看看它对你有用。我担心的是让 iPad “弹出窗口”而不是全屏模式。如果我能在接下来的一两天内完成工作,我会发布我的代码。 我有 something 作为UIViewControllerRepresentable 工作。我唯一的问题是它需要View - 我收到关于呈现不在视图层次结构中的视图的控制台警告。我想我应该使用Coordinator 来解决这个问题。今天晚些时候我会发布一些代码。 【参考方案1】:

你可以在任何地方定义这个函数(最好在全局范围内):

@discardableResult
func share(
    items: [Any],
    excludedActivityTypes: [UIActivity.ActivityType]? = nil
) -> Bool 
    guard let source = UIApplication.shared.windows.last?.rootViewController else 
        return false
    
    let vc = UIActivityViewController(
        activityItems: items,
        applicationActivities: nil
    )
    vc.excludedActivityTypes = excludedActivityTypes
    vc.popoverPresentationController?.sourceView = source.view
    source.present(vc, animated: true)
    return true

您可以在按钮操作中或其他任何需要的地方使用此功能:

Button(action: 
    share(items: ["This is some text"])
) 
    Text("Share")

【讨论】:

虽然这是我希望 ti 工作的方式;它对我不起作用。它只是冻结了 UI。 对于我正在使用的图像共享(项目:[UIImage(命名:“图像”)]) 什么也没帮我。 对我来说,这适用于 Xcode 12.4 和 ios 14.4。只需确保从主线程调用该函数即可。【参考方案2】:

编辑:删除所有代码和对UIButton 的引用。

感谢@Matteo_Pacini 对this question 的回复,向我们展示了这项技术。与他的回答(和评论)一样,(1)这很粗糙,(2)我不确定苹果希望我们如何使用UIViewControllerRepresentable,我真的希望他们提供更好的SwiftUI( “SwiftierUI”?)在未来的测试版中替换。

我在UIKit 中做了很多工作,因为我希望它在 iPad 上看起来不错,而弹出窗口需要sourceView。真正的技巧是显示一个(SwiftUI)View,它在视图层次结构中获取UIActivityViewController,并从UIKit 触发present

我的需要是展示一个单一的图像来分享,所以事情就是朝着这个方向发展的。假设您有一个图像,存储为 @State 变量 - 在我的示例中,图像被称为 vermont.jpg 是的,事情是硬编码的。

首先,创建一个 `UIViewController 类型的 UIKit 类来显示共享弹出框:

class ActivityViewController : UIViewController 

    var uiImage:UIImage!

    @objc func shareImage() 
        let vc = UIActivityViewController(activityItems: [uiImage!], applicationActivities: [])
        vc.excludedActivityTypes =  [
            UIActivity.ActivityType.postToWeibo,
            UIActivity.ActivityType.assignToContact,
            UIActivity.ActivityType.addToReadingList,
            UIActivity.ActivityType.postToVimeo,
            UIActivity.ActivityType.postToTencentWeibo
        ]
        present(vc,
                animated: true,
                completion: nil)
        vc.popoverPresentationController?.sourceView = self.view
    

主要是:

您需要一个“包装器”UIViewController 才能present 事物。 您需要var uiImage:UIImage! 来设置activityItems

接下来,将其包装成UIViewControllerRepresentable

struct SwiftUIActivityViewController : UIViewControllerRepresentable 

    let activityViewController = ActivityViewController()

    func makeUIViewController(context: Context) -> ActivityViewController 
        activityViewController
    
    func updateUIViewController(_ uiViewController: ActivityViewController, context: Context) 
        //
    
    func shareImage(uiImage: UIImage) 
        activityViewController.uiImage = uiImage
        activityViewController.shareImage()
    

唯一需要注意的两件事是:

实例化ActivityViewController 以将其返回到ContentView 创建shareImage(uiImage:UIImage) 来调用它。

最后,你有ContentView

struct ContentView : View 
    let activityViewController = SwiftUIActivityViewController()
    @State var uiImage = UIImage(named: "vermont.jpg")
    var body: some View 
        VStack 
            Button(action: 
                self.activityViewController.shareImage(uiImage: self.uiImage!)
            ) 
                ZStack 
                    Image(systemName:"square.and.arrow.up").renderingMode(.original).font(Font.title.weight(.regular))
                    activityViewController
                
        .frame(width: 60, height: 60).border(Color.black, width: 2, cornerRadius: 2)
            Divider()
            Image(uiImage: uiImage!)
        
    

请注意,uiImage 有一些硬编码和(呃)强制解包,以及对@State 的不必要使用。这些是因为我打算在旁边使用 `UIImagePickerController 将它们联系在一起。

注意事项:

实例化SwiftUIActivityViewController,并使用shareImage 作为按钮操作。 用它来是按钮显示。别忘了,即使是 UIViewControllerRepresentable 也只是被视为 SwiftUI View

将图像的名称更改为您项目中的名称,这应该可以。您将获得一个居中的 60x60 按钮,其下方有图片。

【讨论】:

感谢@dfd。自从我发布问题以来,我最终做了类似的事情。我特别喜欢使用 activityViewController 作为按钮的标签,这样 iPad 弹出框就可以正常工作了。 很高兴我能帮助@MScottWaller。关于sourceView 的问题?回到过去(iOS 9 和UIKit)我记得设置它的一些“怪癖”,即使在今天我的应用程序也使用根 VC 视图。我的示例在工作时实际上覆盖了 按钮的大部分。这是您知道如何解决的问题吗?是“有意行为”吗?或者它与 SwiftUI 和/或 iOS 13 相关(这真的改变了它的外观)? 我不能说。到目前为止,我还没有实现 iPad 弹出框,但如果我发现任何东西,我会告诉你。【参考方案3】:

我们可以直接从 View (SwiftUI) 调用 UIActivityViewController 而无需使用UIViewControllerRepresentable

import SwiftUI
enum Coordinator 
  static func topViewController(_ viewController: UIViewController? = nil) -> UIViewController? 
    let vc = viewController ?? UIApplication.shared.windows.first(where:  $0.isKeyWindow )?.rootViewController
    if let navigationController = vc as? UINavigationController 
      return topViewController(navigationController.topViewController)
     else if let tabBarController = vc as? UITabBarController 
      return tabBarController.presentedViewController != nil ? topViewController(tabBarController.presentedViewController) : topViewController(tabBarController.selectedViewController)
      
     else if let presentedViewController = vc?.presentedViewController 
      return topViewController(presentedViewController)
    
    return vc
  


struct ActivityView: View 
    var body: some View 
      Button(action: 
        self.shareApp()
      ) 
        Text("Share")
      
    


extension ActivityView 
  func shareApp() 
    let textToShare = "something..."
    let activityViewController = UIActivityViewController(activityItems: [textToShare], applicationActivities: nil)
    
    let viewController = Coordinator.topViewController()
    activityViewController.popoverPresentationController?.sourceView = viewController?.view
    viewController?.present(activityViewController, animated: true, completion: nil)
  


struct ActivityView_Previews: PreviewProvider 
    static var previews: some View 
        ActivityView()
    

这是一个预览:

希望能帮助到别人!

【讨论】:

【参考方案4】:

看看AlanQuatermain -s SwiftUIShareSheetDemo

简而言之,它看起来像这样:

@State private var showShareSheet = false
@State public var sharedItems : [Any] = []

Button(action: 
    self.sharedItems = [UIImage(systemName: "house")!]
    self.showShareSheet = true
) 
    Text("Share")
.sheet(isPresented: $showShareSheet) 
    ShareSheet(activityItems: self.sharedItems)

struct ShareSheet: UIViewControllerRepresentable 
    typealias Callback = (_ activityType: UIActivity.ActivityType?, _ completed: Bool, _ returnedItems: [Any]?, _ error: Error?) -> Void

    let activityItems: [Any]
    let applicationActivities: [UIActivity]? = nil
    let excludedActivityTypes: [UIActivity.ActivityType]? = nil
    let callback: Callback? = nil

    func makeUIViewController(context: Context) -> UIActivityViewController 
        let controller = UIActivityViewController(
            activityItems: activityItems,
            applicationActivities: applicationActivities)
        controller.excludedActivityTypes = excludedActivityTypes
        controller.completionWithItemsHandler = callback
        return controller
    

    func updateUIViewController(_ uiViewController: UIActivityViewController, context: Context) 
        // nothing to do here
    

【讨论】:

【参考方案5】:

这里的大多数解决方案都忘记在 iPad 上填充共享表。

因此,如果您打算让应用程序在此设备上不崩溃,您可以使用 此方法使用popoverController 并添加您想要的activityItems 作为参数。

import SwiftUI

/// Share button to populate on any SwiftUI view.
///
struct ShareButton: View 

  /// Your items you want to share to the world.
  ///
  let itemsToShare = ["https://itunes.apple.com/app/id1234"]

  var body: some View 
    Button(action:  showShareSheet(with: itemsToShare) ) 
      Image(systemName: "square.and.arrow.up")
        .font(.title2)
        .foregroundColor(.blue)
    
  


extension View 
  /// Show the classic Apple share sheet on iPhone and iPad.
  ///
  func showShareSheet(with activityItems: [Any]) 
    guard let source = UIApplication.shared.windows.last?.rootViewController else 
      return
    

    let activityVC = UIActivityViewController(
      activityItems: activityItems,
      applicationActivities: nil)

    if let popoverController = activityVC.popoverPresentationController 
      popoverController.sourceView = source.view
      popoverController.sourceRect = CGRect(x: source.view.bounds.midX,
                                            y: source.view.bounds.midY,
                                            width: .zero, height: .zero)
      popoverController.permittedArrowDirections = []
    
    source.present(activityVC, animated: true)
  

【讨论】:

以上是关于SwiftUI 导出或共享文件的主要内容,如果未能解决你的问题,请参考以下文章

如何导出iPhone手机中app共享文件夹的文件与闪退日志的收集

在 SwiftUI 中使用 NSPersistentCloudKitContainer 实现共享

虚拟机怎么导出文件

SQL Server批量数据导出导入Bulk Insert使用

如何将绘图仪表板应用程序导出到 html 独立文件以与其他人共享?

如何使用React Native接收在iOS上导出/共享的文件