在 SwiftUI 中显示“UIActivityViewController”

nOk*_*nOk 38 uiactivityviewcontroller swiftui xcode11

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

Tik*_*der 45

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

}
Run Code Online (Sandbox Code Playgroud)

这是如何使用它。

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

  • 由于未设置“popoverPresentationController”,这将在 iPad 上崩溃。 (5认同)
  • @CalebFriden 你能提供一些有关崩溃的详细信息吗?我在 iPadOS 14.2 的模拟器和物理设备(iPad Pro 11" 2018)上进行了尝试 - 它对我来说没有崩溃。 (2认同)
  • 我刚刚在 iPad 模拟器中尝试过,效果非常好。 (2认同)

sam*_*ize 12

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

}
Run Code Online (Sandbox Code Playgroud)


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

  • viewDidAppear必须调用super (2认同)

Ami*_*mir 6

可能不推荐,但它真的很容易和两行代码(用于 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")
}
Run Code Online (Sandbox Code Playgroud)

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

  • @Ali它崩溃了,因为在iPad上它不知道sourceView在哪里/sf/answers/4604980411/ (2认同)

Lob*_*obo 6

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

let controller = UIActivityViewController(activityItems: [context.coordinator], applicationActivities: applicationActivities)
Run Code Online (Sandbox Code Playgroud)

另外,我希望能够在共享表中设置图标、标题和副标题,所以我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
        }
    }
}   
Run Code Online (Sandbox Code Playgroud)

协议 ActivityShareable

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

就我而言,我使用共享表导出文本,因此我创建了一个名为 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
    }
}
Run Code Online (Sandbox Code Playgroud)

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

ActivityViewController(shareable: ActivityShareableText(
    text: myShareText(),
    title: myShareTitle(),
    subTitle: myShareSubTitle(),
    icon: UIImage(named: "myAppLogo")
))
Run Code Online (Sandbox Code Playgroud)


小智 5

我现在使用它来工作

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

.presentation 已弃用

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

  • 也许补充一点,它是一个自定义包装器。 (2认同)

Shi*_*tem 5

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

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