在SwitUI中显示“ UIActivityViewController”

时间:2019-06-10 21:08:41

标签: uiactivityviewcontroller swiftui xcode11

我想让用户能够共享一个位置,但是我不知道如何在Swift UI中显示UIActivityViewController

16 个答案:

答案 0 :(得分:5)

这是一次性的事情。 .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()
    }
}

答案 1 :(得分:5)

基于Tikhonov's,以下代码添加了一个修复程序,以确保正确关闭活动表(如果以后不显示该表)。

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>) {}

}

答案 2 :(得分:3)

我现在开始使用它

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

。不推荐使用演示

它占据了全屏iOS 13样式。

答案 3 :(得分:3)

UIActivityViewControllerSwiftUI的基本实现是

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")!])
        })
    }
}

答案 4 :(得分:2)

您可以尝试将UIActivityViewController移植到SwiftUI,如下所示:

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中似乎无法使用。

答案 5 :(得分:2)

我想建议另一种看起来更原生的实现(半屏高度,无白底)。

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()
    }
}

答案 6 :(得分:1)

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的引用,但这感觉不必要。

答案 7 :(得分:1)

使用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")!])
})

答案 8 :(得分:1)

可能不建议这样做,但这确实很容易,并且两行代码可以共享文本

Button(action: {
     let shareActivity = UIActivityViewController(activityItems: ["Text To Share"], applicationActivities: nil)
      UIApplication.shared.windows.first?.rootViewController?.present(shareActivity, animated: true, completion: nil)
}) {
    Text("Share")
}

答案 9 :(得分:1)

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

答案 10 :(得分:0)

感谢此主题中的有用答案。

我试图解决stale data问题。没有在updateUIViewController中实现UIViewControllerRepresentable的问题。 SwiftUI仅调用一次makeUIViewController来创建视图控制器。方法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
    }
}

答案 11 :(得分:0)

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

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

此外,我希望能够在共享表中设置图标、标题和副标题,因此我在 activityViewControllerLinkMetadata 类中实现了 func Coordinator

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

ActivityViewController

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

答案 12 :(得分: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)
}

答案 13 :(得分:0)

建议另一种解决方法?

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

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

答案 14 :(得分:-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)")
                    
})

答案 15 :(得分:-2)

对于 ios 13 使用自定义视图作为加载指示器,对于 ios 14 使用 ProgressView()

struct LoadingIndicator: View {
    @State private var animating = false
    var body: some View {
        Image(systemName: "rays")
            .rotationEffect(animating ? Angle.degrees(360) : .zero)
            .animation(Animation
                        .linear(duration: 2)
                        .repeatForever(autoreverses: false)
            )
            .onAppear { self.animating = true }
    }
}