在SwiftUI中显示 "UIActivityViewController"。

问题描述 投票:8回答:8

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

uiactivityviewcontroller swiftui xcode11
8个回答
17
投票

基本落实 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")!])
        })
    }
}

7
投票

目前是一次性的。 .sheet会以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()
    }
}

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

    }
}

但当你试图显示它时,应用程序会崩溃。

我试过了。Modal, PopoverNavigationButton.

为了测试它。

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

它似乎无法使用从 SwiftUI.


3
投票

我得到了它的工作,现在使用

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

.presentation已被废弃

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


2
投票

基于 季洪诺夫,下面的代码添加了一个修复,以确保活动表被正确地驳回(如果不是随后该表将不会被呈现)。

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

}

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 但感觉没必要。


0
投票

谢谢这个帖子里的有益解答。

我试着解决了 stale data 的问题。 的问题,从不执行 updateUIViewControllerUIViewControllerRepresentable. SwiftUI调用 makeUIViewController 只需一次就可以创建视图控制器。方法 updateUIViewController 负责根据SwiftUI视图的变化对视图控制器进行修改。

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

下面是我在应用中实现 "分享 "按钮的代码。 这段代码是在iOS 13.4测试版上测试的,它已经修复了几个SwiftUI的bug--不知道它是否能在早期版本上工作。

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

0
投票

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

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

0
投票

使用示例 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")!])
})
© www.soinside.com 2019 - 2024. All rights reserved.