SwiftUI导出或共享文件

时间:2019-06-29 17:07:51

标签: swift uiactivityviewcontroller swiftui nssharingservice

我想知道是否可以通过SwiftUI导出或共享文件。似乎没有办法包装UIActivityViewController并直接呈现它。我已经使用UIViewControllerRepresentable包装了一个UIActivityViewController,如果我将它显示在SwiftUI模式中,它将崩溃。

我能够创建一个通用的UIViewController,然后从那里调用一个呈现UIActivityViewController的方法,但这很麻烦。

如果我们想使用SwiftUI从Mac共享,是否可以包装NSSharingServicePicker?

无论如何,如果有人举了一个例子说明他们如何做到这一点,将不胜感激。

5 个答案:

答案 0 :(得分:2)

看看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
    }
}

答案 1 :(得分:2)

我们可以直接从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()
    }
}

这是预览:

enter image description here

希望能帮助别人!

答案 2 :(得分:1)

这里的大多数解决方案忘记在 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)
  }
}

答案 3 :(得分:0)

编辑:删除了所有代码和对android:scaleType="fitXY" 的引用。

感谢@Matteo_Pacini对this question的回答,向我们展示了此技术。与他的回答(和评论)一样,(1)这有点粗糙;(2)我不确定这是苹果公司希望我们使用UIButton的方式,我真的希望他们提供更好的{{ 1}}(“ SwiftierUI”?)在将来的Beta中替换。

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

我的需求是展示一个共享的图像,所以事情就朝那个方向发展。假设您有一个图像,该图像存储为UIKit变量-在我的示例中,该图像称为 vermont.jpg ,是的,事情为此进行了硬编码。

首先,创建一个类型为UIViewController的@State类,以显示共享弹出窗口:

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!) } } } 进行了一些硬编码和(ugh)强制解包,以及对uiImage的不必要使用。这些是存在的,因为我计划在旁边使用`UIImagePickerController将所有这些联系在一起。

这里的注意事项:

  • 实例化@State,并将SwiftUIActivityViewController用作Button动作。
  • 使用它来是按钮显示。别忘了,甚至shareImage实际上也只是一个SwiftUI UIViewControllerRepresentable

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

答案 4 :(得分:0)

您可以在任何地方(最好是在全局范围内)定义此函数:

@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")
}