46

我想让用户能够共享位置,但我不知道如何UIActivityViewController在 SwiftUI 中显示。

4

15 回答 15

59

UIActivityViewControllerin的基本实现SwiftUI

import UIKit
import SwiftUI

struct ActivityViewController: UIViewControllerRepresentable {

    var activityItems: [Any]
    var applicationActivities: [UIActivity]? = nil

    func makeUIViewController(context: UIViewControllerRepresentableContext<ActivityViewController>) -> UIActivityViewController {
        let controller = UIActivityViewController(activityItems: activityItems, applicationActivities: applicationActivities)
        return controller
    }

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

}

这里是如何使用它。

struct MyView: View {

    @State private var isSharePresented: Bool = false

    var body: some View {
        Button("Share app") {
            self.isSharePresented = true
        }
        .sheet(isPresented: $isSharePresented, onDismiss: {
            print("Dismiss")
        }, content: {
            ActivityViewController(activityItems: [URL(string: "https://www.apple.com")!])
        })
    }
}
于 2019-10-11T13:08:00.970 回答
15

基于Tikhonov 的,以下代码添加了一个修复程序,以确保活动表被正确关闭(如果不是随后将不会显示工作表)。

struct ActivityViewController: UIViewControllerRepresentable {

    var activityItems: [Any]
    var applicationActivities: [UIActivity]? = nil
    @Environment(\.presentationMode) var presentationMode

    func makeUIViewController(context: UIViewControllerRepresentableContext<ActivityViewController>) -> UIActivityViewController {
        let controller = UIActivityViewController(activityItems: activityItems, applicationActivities: applicationActivities)
        controller.completionWithItemsHandler = { (activityType, completed, returnedItems, error) in
            self.presentationMode.wrappedValue.dismiss()
        }
        return controller
    }

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

}
于 2020-02-09T15:04:14.160 回答
11

目前是一次性的。.sheet 会将其显示为工作表,但从同一视图再次显示它会有陈旧的数据。随后的工作表显示也不会触发任何完成处理程序。基本上,makeUIViewController 只被调用一次,这是将数据共享到 UIActivityViewController 中的唯一方法。updateUIViewController 无法更新您的 activityItems 中的数据或重置控制器,因为这些在 UIActivityViewController 的实例中不可见。

请注意,它也不适用于 UIActivityItemSource 或 UIActivityItemProvider 。使用这些更糟糕。占位符值不显示。

我又摸索了一些,并决定也许我的解决方案的问题是一张纸正在呈现另一张纸,当一张纸离开时,另一张就留下了。

这种让 ViewController 在出现时进行演示的间接方式使它对我有用。

class UIActivityViewControllerHost: UIViewController {
    var message = ""
    var completionWithItemsHandler: UIActivityViewController.CompletionWithItemsHandler? = nil

    override func viewDidAppear(_ animated: Bool) {
        share()
    }

    func share() {
        // set up activity view controller
        let textToShare = [ message ]
        let activityViewController = UIActivityViewController(activityItems: textToShare, applicationActivities: nil)

        activityViewController.completionWithItemsHandler = completionWithItemsHandler
        activityViewController.popoverPresentationController?.sourceView = self.view // so that iPads won't crash

        // present the view controller
        self.present(activityViewController, animated: true, completion: nil)
    }
}

struct ActivityViewController: UIViewControllerRepresentable {
    @Binding var text: String
    @Binding var showing: Bool

    func makeUIViewController(context: Context) -> UIActivityViewControllerHost {
        // Create the host and setup the conditions for destroying it
        let result = UIActivityViewControllerHost()

        result.completionWithItemsHandler = { (activityType, completed, returnedItems, error) in
            // To indicate to the hosting view this should be "dismissed"
            self.showing = false
        }

        return result
    }

    func updateUIViewController(_ uiViewController: UIActivityViewControllerHost, context: Context) {
        // Update the text in the hosting controller
        uiViewController.message = text
    }

}

struct ContentView: View {
    @State private var showSheet = false
    @State private var message = "a message"

    var body: some View {
        VStack {
            TextField("what to share", text: $message)

            Button("Hello World") {
                self.showSheet = true
            }

            if showSheet {
                ActivityViewController(text: $message, showing: $showSheet)
                    .frame(width: 0, height: 0)
            }

            Spacer()
        }
        .padding()
    }
}
于 2019-08-27T18:27:29.903 回答
9

可能不推荐,但它真的很容易和两行代码(用于 iPhone)共享文本

Button(action: {
      let shareActivity = UIActivityViewController(activityItems: ["Text To Share"], applicationActivities: nil)
      if let vc = UIApplication.shared.windows.first?.rootViewController{
          shareActivity.popoverPresentationController?.sourceView = vc.view
         //Setup share activity position on screen on bottom center
          shareActivity.popoverPresentationController?.sourceRect = CGRect(x: UIScreen.main.bounds.width / 2, y: UIScreen.main.bounds.height, width: 0, height: 0)
          shareActivity.popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection.down
         vc.present(shareActivity, animated: true, completion: nil)
      }
}) {
    Text("Share")
}

编辑:现在在 iPad 上工作正常(在 iPad Pro(9.7 英寸)模拟器上测试)

于 2020-11-23T05:28:32.220 回答
6

我想建议另一种看起来更原生的实现(没有白色间隙底部的半屏幕高度)。

import SwiftUI

struct ActivityView: UIViewControllerRepresentable {
    var activityItems: [Any]
    var applicationActivities: [UIActivity]? = nil
    @Binding var isPresented: Bool

    func makeUIViewController(context: Context) -> ActivityViewWrapper {
        ActivityViewWrapper(activityItems: activityItems, applicationActivities: applicationActivities, isPresented: $isPresented)
    }

    func updateUIViewController(_ uiViewController: ActivityViewWrapper, context: Context) {
        uiViewController.isPresented = $isPresented
        uiViewController.updateState()
    }
}

class ActivityViewWrapper: UIViewController {
    var activityItems: [Any]
    var applicationActivities: [UIActivity]?

    var isPresented: Binding<Bool>

    init(activityItems: [Any], applicationActivities: [UIActivity]? = nil, isPresented: Binding<Bool>) {
        self.activityItems = activityItems
        self.applicationActivities = applicationActivities
        self.isPresented = isPresented
        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func didMove(toParent parent: UIViewController?) {
        super.didMove(toParent: parent)
        updateState()
    }

    fileprivate func updateState() {
        guard parent != nil else {return}
        let isActivityPresented = presentedViewController != nil
        if isActivityPresented != isPresented.wrappedValue {
            if !isActivityPresented {
                let controller = UIActivityViewController(activityItems: activityItems, applicationActivities: applicationActivities)
                controller.completionWithItemsHandler = { (activityType, completed, _, _) in
                    self.isPresented.wrappedValue = false
                }
                present(controller, animated: true, completion: nil)
            }
            else {
                self.presentedViewController?.dismiss(animated: true, completion: nil)
            }
        }
    }
}

struct ActivityViewTest: View {
    @State private var isActivityPresented = false
    var body: some View {
        Button("Preset") {
            self.isActivityPresented = true
        }.background(ActivityView(activityItems: ["Hello, World"], isPresented: $isActivityPresented))
    }
}

struct ActivityView_Previews: PreviewProvider {
    static var previews: some View {
        ActivityViewTest()
    }
}
于 2020-04-08T14:18:47.880 回答
3

您可以尝试移植UIActivityViewControllerSwiftUI如下:

struct ActivityView: UIViewControllerRepresentable {

    let activityItems: [Any]
    let applicationActivities: [UIActivity]?

    func makeUIViewController(context: UIViewControllerRepresentableContext<ActivityView>) -> UIActivityViewController {
        return UIActivityViewController(activityItems: activityItems,
                                        applicationActivities: applicationActivities)
    }

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

    }
}

但是当您尝试显示它时,该应用程序会崩溃。

我试过ModalPopoverNavigationButton

要测试它:

struct ContentView: View {
    var body: some Body {
        EmptyView
        .presentation(Modal(ActivityView()))
    }
}

它似乎无法从SwiftUI.

于 2019-06-11T04:50:41.897 回答
3

我现在使用它来工作

.sheet(isPresented: $isSheet, content: { ActivityViewController() }

.presentation 已弃用

它占据了整个屏幕的iOS 13风格。

于 2019-08-10T19:05:40.683 回答
3

FWIW - 对答案进行轻微改进,其中包括UIActivityItemSource. 为简洁起见简化了代码,特别是围绕默认返回itemForActivityTypeactivityViewControllerPlaceholderItem,它们必须始终返回相同的类型。

ActivityViewController

struct ActivityViewController: UIViewControllerRepresentable {

    var activityItems: [Any]
    var shareable : ActivityShareable?
    var applicationActivities: [UIActivity]? = nil

    func makeUIViewController(context: UIViewControllerRepresentableContext<ActivityViewController>) -> UIActivityViewController {
        let controller = UIActivityViewController(activityItems: activityItems, applicationActivities: applicationActivities)
        controller.modalPresentationStyle = .automatic
        return controller
    }

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

    func makeCoordinator() -> ActivityViewController.Coordinator {
        Coordinator(self.shareable)
    }

    class Coordinator : NSObject, UIActivityItemSource {

        private let shareable : ActivityShareable?

        init(_ shareable: ActivityShareable?) {
            self.shareable = shareable
            super.init()
        }

        func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
            guard let share = self.shareable else { return "" }
            return share.getPlaceholderItem()
        }

        func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
            guard let share = self.shareable else { return "" }
            return share.itemForActivityType(activityType: activityType)
        }

        func activityViewController(_ activityViewController: UIActivityViewController, subjectForActivityType activityType: UIActivity.ActivityType?) -> String {
            guard let share = self.shareable else { return "" }
            return share.subjectForActivityType(activityType: activityType)
        }
    }
}

ActivityShareable

protocol ActivityShareable {

    func getPlaceholderItem() -> Any
    func itemForActivityType(activityType: UIActivity.ActivityType?) -> Any?

    /// Optional
    func subjectForActivityType(activityType: UIActivity.ActivityType?) -> String
}

extension ActivityShareable {

    func subjectForActivityType(activityType: UIActivity.ActivityType?) -> String {
        return ""
    }
}

您可以传递参考ActivityViewController或基础UIActivityViewController,但感觉没有必要。

于 2020-01-09T04:02:43.267 回答
2

扩展@Shimanski Artem 解决方案。我认为我们可以更简洁地编写代码。所以我基本上把我ActivityViewController的嵌入到一个空白中UIViewController并从那里呈现出来。这样我们就不会得到完整的“覆盖”表,而你会得到本机行为。就像@Shimanski Artem 所做的那样。

struct UIKitActivityView: UIViewControllerRepresentable {
    @Binding var isPresented: Bool

    let data: [Any]

    func makeUIViewController(context: Context) -> UIViewController {
        UIViewController()
    }

    func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
        let activityViewController = UIActivityViewController(
            activityItems: data,
            applicationActivities: nil
        )

        if isPresented && uiViewController.presentedViewController == nil {
            uiViewController.present(activityViewController, animated: true)
        }

        activityViewController.completionWithItemsHandler = { (_, _, _, _) in
            isPresented = false
        }
    }
}

用法

struct ActivityViewTest: View {
    @State private var isActivityPresented = false

    var body: some View {
        Button("Preset") {
           self.isActivityPresented = true
        }
        .background(
            UIKitActivityView(
                isPresented: $viewModel.showShareSheet,
                data: ["String"]
            )
        )
    }
}
于 2021-07-31T19:49:24.463 回答
2

使用SwiftUIX 的示例

有一个名为 SwiftUIX 的库已经有一个UIActivityViewController. 请参阅如何呈现它的快速框架,.sheet()它应该放置在var body: some View {}.

import SwiftUIX

/// ...

@State private var showSocialsInviteShareSheet: Bool = false

// ...

.sheet(isPresented: $showSocialsInviteShareSheet, onDismiss: {
    print("Dismiss")
}, content: {
    AppActivityView(activityItems: [URL(string: "https://www.apple.com")!])
})
于 2020-06-23T22:58:52.637 回答
2

建议另一种解决方法

您可以创建空视图控制器来呈现工作表

struct ShareSheet: UIViewControllerRepresentable {
  // To setup the share sheet
  struct Config {
    let activityItems: [Any]
    var applicationActivities: [UIActivity]?
    var excludedActivityTypes: [UIActivity.ActivityType]?
  }

  // Result object
  struct Result {
    let error: Error?
    let activityType: UIActivity.ActivityType?
    let completed: Bool
    let returnedItems: [Any]?
  }

  @Binding var isPresented: Bool

  private var handler: ((Result) -> Void)?
  private let shareSheet: UIActivityViewController

  init(
    isPresented: Binding<Bool>,
    config: Config,
    onEnd: ((Result) -> Void)? = nil
  ) {
    self._isPresented = isPresented
    shareSheet = UIActivityViewController(
      activityItems: config.activityItems,
      applicationActivities: config.applicationActivities
    )
    shareSheet.excludedActivityTypes = config.excludedActivityTypes
    shareSheet.completionWithItemsHandler = { activityType, completed, returnedItems, error in
      onEnd?(
        .init(
          error: error,
          activityType: activityType,
          completed: completed,
          returnedItems: returnedItems
        )
      )
      // Set isPresented to false after complete
      isPresented.wrappedValue = false
    }
  }

  func makeUIViewController(context: Context) -> UIViewController {
    UIViewController()
  }

  func updateUIViewController(
    _ uiViewController: UIViewController,
    context: Context
  ) {
    if isPresented, shareSheet.view.window == nil {
      uiViewController.present(shareSheet, animated: true, completion: nil)
    } else if !isPresented, shareSheet.view.window != nil {
      shareSheet.dismiss(animated: true)
    }
  }
}

您还可以在视图扩展中创建运算符

extension View {
  func shareSheet(
    isPresented: Binding<Bool>,
    config: ShareSheet.Config,
    onEnd: ((ShareSheet.Result) -> Void)? = nil
  ) -> some View {
    self.background(
      ShareSheet(isPresented: isPresented, config: config, onEnd: onEnd)
    )
  }
}
于 2021-07-29T13:22:43.930 回答
1

如果您需要对共享表中显示的内容进行更精细的控制,您可能会停止实施UIActivityItemSource. 我尝试使用上面的 Mike W. 的代码,但一开始它不起作用(没有调用委托函数)。UIActivityController该修复正在更改内部的初始化,makeUIViewController如下所示,现在传递[context.coordinator]activityItems

let controller = UIActivityViewController(activityItems: [context.coordinator], applicationActivities: applicationActivities)

另外,我希望能够在共享表中设置图标、标题和副标题,所以我activityViewControllerLinkMetadataCoordinator课堂上实现了 func。

以下是 Mike W. 答案的完整扩展版本。请注意,您将需要添加import LinkPresentation到代码中。

活动视图控制器

import SwiftUI
import LinkPresentation

struct ActivityViewController: UIViewControllerRepresentable {
    var shareable : ActivityShareable?
    var applicationActivities: [UIActivity]? = nil

    func makeUIViewController(context: UIViewControllerRepresentableContext<ActivityViewController>) -> UIActivityViewController {
        let controller = UIActivityViewController(activityItems: [context.coordinator], applicationActivities: applicationActivities)
        controller.modalPresentationStyle = .automatic
        return controller
    }

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

    func makeCoordinator() -> ActivityViewController.Coordinator {
        Coordinator(self.shareable)
    }

    class Coordinator : NSObject, UIActivityItemSource {
        private let shareable : ActivityShareable?

        init(_ shareable: ActivityShareable?) {
            self.shareable = shareable
            super.init()
        }

        func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
            guard let share = self.shareable else { return "" }
            return share.getPlaceholderItem()
        }

        func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
            guard let share = self.shareable else { return "" }
            return share.itemForActivityType(activityType: activityType)
        }

        func activityViewController(_ activityViewController: UIActivityViewController, subjectForActivityType activityType: UIActivity.ActivityType?) -> String {
            guard let share = self.shareable else { return "" }
            return share.subjectForActivityType(activityType: activityType)
        }
        
        func activityViewControllerLinkMetadata(_ activityViewController: UIActivityViewController) -> LPLinkMetadata? {
            guard let share = self.shareable else { return nil }
            
            let metadata = LPLinkMetadata()

            // share sheet preview title
            metadata.title = share.shareSheetTitle()
            // share sheet preview subtitle
            metadata.originalURL = URL(fileURLWithPath: share.shareSheetSubTitle())
            // share sheet preview icon
            if let image = share.shareSheetIcon() {
                let imageProvider = NSItemProvider(object: image)
                metadata.imageProvider = imageProvider
                metadata.iconrovider = imageProvider
            }
            return metadata
        }
    }
}   

协议活动可共享

protocol ActivityShareable {
    func getPlaceholderItem() -> Any
    func itemForActivityType(activityType: UIActivity.ActivityType?) -> Any?
    func subjectForActivityType(activityType: UIActivity.ActivityType?) -> String
    func shareSheetTitle() -> String
    func shareSheetSubTitle() -> String
    func shareSheetIcon() -> UIImage?
}

在我的例子中,我使用共享表来导出文本,所以我创建了一个名为 ActivityShareableText 的结构,它符合 ActivityShareable:

struct ActivityShareableText: ActivityShareable {
    let text: String
    let title: String
    let subTitle: String
    let icon: UIImage?
    
    func getPlaceholderItem() -> Any {
        return text
    }
    
    func itemForActivityType(activityType: UIActivity.ActivityType?) -> Any? {
        return text
    }
    
    func subjectForActivityType(activityType: UIActivity.ActivityType?) -> String {
        return "\(title): \(subTitle)"
    }
    
    func shareSheetTitle() -> String {
        return title
    }
    
    func shareSheetSubTitle() -> String {
        return subTitle
    }
    
    func shareSheetIcon() -> UIImage? {
        return icon
    }
}

在我的代码中,我将共享表称为如下:

ActivityViewController(shareable: ActivityShareableText(
    text: myShareText(),
    title: myShareTitle(),
    subTitle: myShareSubTitle(),
    icon: UIImage(named: "myAppLogo")
))
于 2021-03-06T08:25:06.587 回答
0

感谢此线程中的有用答案。

我试图解决这个stale data问题。未updateUIViewControllerUIViewControllerRepresentable. SwiftUImakeUIViewController只调用一次来创建视图控制器。该方法updateUIViewController负责根据 SwiftUI 视图的变化对视图控制器进行更改。

由于UIActivityViewController不允许更改activityItemsapplicationActivities我使用了包装视图控制器。 UIViewControllerRepresentable将更新包装器,包装器将根据需要创建一个新UIActivityViewController的来执行更新。

在我的代码下方,在我的应用程序中实现“共享”按钮。该代码在 iOS 13.4 beta 上进行了测试,它修复了几个 SwiftUI 错误 - 不确定它是否适用于早期版本。

struct Share: View {
    var document: ReaderDocument   // UIDocument subclass
    @State var showShareSheet = false

    var body: some View {
        Button(action: {
            self.document.save(to: self.document.fileURL, for: .forOverwriting) { success in
                self.showShareSheet = true
            }
        }) {
            Image(systemName: "square.and.arrow.up")
        }.popover(isPresented: $showShareSheet) {
            ActivityViewController(activityItems: [ self.document.text, self.document.fileURL,
                                                    UIPrintInfo.printInfo(), self.printFormatter ])
              .frame(minWidth: 320, minHeight: 500)  // necessary for iPad
        }
    }

    var printFormatter: UIPrintFormatter {
        let fontNum = Preferences.shared.readerFontSize.value
        let fontSize = ReaderController.readerFontSizes[fontNum < ReaderController.readerFontSizes.count ? fontNum : 1]
        let printFormatter = UISimpleTextPrintFormatter(text: self.document.text)
        printFormatter.startPage = 0
        printFormatter.perPageContentInsets = UIEdgeInsets(top: 72, left: 72, bottom: 72, right: 72)
        return printFormatter
    }
}

struct ActivityViewController: UIViewControllerRepresentable {

    var activityItems: [Any]
    var applicationActivities: [UIActivity]? = nil
    @Environment(\.presentationMode) var presentationMode

    func makeUIViewController(context: UIViewControllerRepresentableContext<ActivityViewController>)
        -> WrappedViewController<UIActivityViewController> {
        let controller = WrappedViewController(wrappedController: activityController)
        return controller
    }

    func updateUIViewController(_ uiViewController: WrappedViewController<UIActivityViewController>,
                                context: UIViewControllerRepresentableContext<ActivityViewController>) {
        uiViewController.wrappedController = activityController
    }

    private var activityController: UIActivityViewController {
        let avc = UIActivityViewController(activityItems: activityItems, applicationActivities: applicationActivities)
        avc.completionWithItemsHandler = { (_, _, _, _) in
            self.presentationMode.wrappedValue.dismiss()
        }
        return avc
    }
}

class WrappedViewController<Controller: UIViewController>: UIViewController {
    var wrappedController: Controller {
        didSet {
            if (wrappedController != oldValue) {
                oldValue.removeFromParent()
                oldValue.view.removeFromSuperview()
                addChild(wrappedController)
                view.addSubview(wrappedController.view)
                wrappedController.view.frame = view.bounds
            }
        }
    }

    init(wrappedController: Controller) {
        self.wrappedController = wrappedController
        super.init(nibName: nil, bundle: nil)
        addChild(wrappedController)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func loadView() {
        super.loadView()
        view.addSubview(wrappedController.view)
        wrappedController.view.frame = view.bounds
    }
}
于 2020-02-22T23:58:40.410 回答
0

只需使用内省。然后您可以轻松编写如下代码:

YourView().introspectViewController { controller in
    guard let items = viewModel.inviteLinkParams, viewModel.isSharePresented else { return }
    let activity = UIActivityViewController(activityItems: items, applicationActivities: nil)
    controller.present(activity, animated: true, completion: nil)
}
于 2021-03-25T14:09:45.190 回答
-1

Swift 5 / SwiftUI 原生

简单,带有完成回调和原生 SwiftUI @Binding

import SwiftUI

struct ShareSheet: UIViewControllerRepresentable {
    typealias Callback = (_ activityType: UIActivity.ActivityType?, _ completed: Bool, _ returnedItems: [Any]?, _ error: Error?) -> Void
    
    @Binding var isPresented: Bool
    @Binding var activityItem: String
    
    let applicationActivities: [UIActivity]? = nil
    let excludedActivityTypes: [UIActivity.ActivityType]? = nil
    let callback: Callback?
    
    func makeUIViewController(context: Context) -> UIActivityViewController {
        
        let controller = UIActivityViewController(
            activityItems: [activityItem],
            applicationActivities: applicationActivities)
        
        controller.excludedActivityTypes = excludedActivityTypes
        controller.completionWithItemsHandler = { (activityType, completed, returnedItems, error) in
            callback?(activityType, completed, returnedItems, error)
            isPresented = false
        }
        
        return controller
    }
    
    func updateUIViewController(_ uiViewController: UIActivityViewController, context: Context) {
        
    }
}

示例用法:

ShareSheet(isPresented: $showShareSheet, activityItem: $sharingUrl, callback: { activityType, completed, returnedItems, error in
    
    print("ShareSheet dismissed: \(activityType) \(completed) \(returnedItems) \(error)")
                    
})
于 2021-03-20T05:56:36.143 回答