SwiftUI 导出或共享文件

MSc*_*ler 9 uiactivityviewcontroller swift nssharingservice swiftui

我想知道是否有通过 SwiftUI 导出或共享文件的好方法。似乎没有一种方法可以包装 UIActivityViewController 并直接呈现它。我已经使用 UIViewControllerRepresentable 来包装 UIActivityViewController,如果我将它呈现在 SwiftUI Modal 中,它就会崩溃。

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

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

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

Ken*_*ler 15

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

@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
}
Run Code Online (Sandbox Code Playgroud)

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

Button(action: {
    share(items: ["This is some text"])
}) {
    Text("Share")
}
Run Code Online (Sandbox Code Playgroud)

  • 虽然这就是我想要的工作方式;它对我不起作用。它只是冻结了用户界面。 (2认同)

Tua*_*yen 8

我们可以直接从 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()
    }
}
Run Code Online (Sandbox Code Playgroud)

这是预览:

在此输入图像描述

希望能帮助别人!


Rol*_*tte 7

这里的大多数解决方案都忘记填充 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)
  }
}
Run Code Online (Sandbox Code Playgroud)


dfd*_*dfd 5

编辑:删除了所有代码和对UIButton.

感谢@Matteo_Pacini 对这个问题的回答,他向我们展示了这项技术。与他的回答(和评论)一样,(1)这很粗糙,(2)我不确定这是 Apple 希望我们使用的方式UIViewControllerRepresentable,我真的希望他们提供更好的SwiftUI(“SwiftierUI”?)替代品在未来的测试版中。

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

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

首先,创建一个UIKit类型为 UIViewController的类来呈现共享弹出窗口:

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
    }
}
Run Code Online (Sandbox Code Playgroud)

主要的事情是;

  • 你需要一个“包装器”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()
    }
}
Run Code Online (Sandbox Code Playgroud)

唯一需要注意的有两点:

  • 实例化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!)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,有一些硬编码和(呃)强制解包uiImage,以及不必要的@State. 之所以有这些,是因为我计划接下来使用`UIImagePickerController 将它们联系在一起。

这里的注意事项:

  • 实例化SwiftUIActivityViewController,并shareImage用作 Button 动作。
  • 使用它可以是按钮显示。不要忘记,即使 aUIViewControllerRepresentable真的只是被认为是一个 SwiftUI View

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


lor*_*nzo 3

从 iOS 16 开始,您可以使用 ShareLink:

ShareLink(item: "http://www.myappurl.com") {
    Label("Share app", systemImage: "square.and.arrow.up")
}
Run Code Online (Sandbox Code Playgroud)

要不就:

ShareLink(item: "http://www.myappurl.com")
Run Code Online (Sandbox Code Playgroud)