22
votes

Affichage de 'UIActivityViewController' dans SwiftUI

Je souhaite permettre à l'utilisateur de partager un emplacement mais je ne sais pas comment afficher UIActivityViewController dans l'interface utilisateur Swift


0 commentaires

10 Réponses :


4
votes

Vous pouvez essayer de porter UIActivityViewController vers SwiftUI comme suit:

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

mais l'application se bloque lorsque vous essayez de l'afficher.

J'ai essayé: Modal , Popover et NavigationButton .

Pour le tester:

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

    }
}

Il ne semble pas être utilisable depuis SwiftUI .


0 commentaires

3
votes

Je l'ai fait fonctionner maintenant en utilisant

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

.presentation est obsolète

Il occupe tout le style de l'écran iOS 13.


3 commentaires

Voulez-vous dire UIActivityViewController() dans la fermeture?


@Bill Non, ActivityViewController dans ce cas serait une vue SwiftUI enveloppant un UIActivityViewController normal.


Ajoutez peut-être que c'est un wrapper personnalisé.



9
votes

C'est une chose ponctuelle actuellement. .sheet l'affichera sous forme de feuille, mais le remonter à partir de la même vue aura des données périmées. Ces représentations ultérieures de la feuille ne déclencheront pas non plus de gestionnaires d'achèvement. Fondamentalement, makeUIViewController est appelé une seule fois, ce qui est le seul moyen d'obtenir les données à partager dans UIActivityViewController. updateUIViewController n'a aucun moyen de mettre à jour les données dans vos activityItems ou de réinitialiser le contrôleur car ceux-ci ne sont pas visibles à partir d'une instance de UIActivityViewController.

Notez que cela ne fonctionne pas non plus avec UIActivityItemSource ou UIActivityItemProvider. Les utiliser est encore pire. La valeur de l'espace réservé ne s'affiche pas.

J'ai piraté un peu plus et j'ai décidé que le problème avec ma solution était peut-être une feuille qui présentait une autre feuille, et quand l'une partait, l'autre restait.

Cette façon indirecte de demander à un ViewController de faire la présentation quand il apparaît l'a fait fonctionner pour moi.

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 commentaires

viewDidAppear doit appeler super



26
votes

L'implémentation de base de UIActivityViewController dans SwiftUI est

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

Et voici comment l'utiliser.

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

}


4 commentaires

J'ai le lien de l'image à partager, comment serait-il dans ce cas? Il fonctionne avec UIImage (named:" Product ") mais tel quel avec une Image (" Product ") dans SwiftUI. Pour MONTRER l'image à mon avis, j'utilise ImageViewContainer1 (imageUrl: self.item.image! .Url) , mais cela ne m'accuse pas de mettre cela


Si quelqu'un souhaite partager le lien de l'application, l'URL est "apps.apple.com/us/app/id(votre identifiant d'application)"


Cela plantera sur les iPad car popoverPresentationController n'est pas défini.


@CalebFriden pourriez-vous fournir quelques détails sur le crash? Je l'ai essayé sur iPadOS 14.2 dans un simulateur et sur un appareil physique (iPad Pro 11 "2018) - il n'a pas planté pour moi.



1
votes

FWIW - Fournit une légère amélioration aux réponses qui inclut une implémentation pour UIActivityItemSource . Code simplifié par souci de concision, en particulier autour du retour par défaut sur itemForActivityType et activityViewControllerPlaceholderItem , ils doivent toujours renvoyer le même type.

ActivityViewController

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

ActivityShareable

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

Vous pouvez transmettre la référence pour ActivityViewController ou le UIActivityViewController sous-jacent, mais cela semble inutile.


0 commentaires

5
votes

Basé sur celui de Tikhonov , le code suivant a ajouté un correctif pour s'assurer que la feuille d'activité est correctement rejetée (sinon la feuille ne sera pas présentée ultérieurement).

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

}


0 commentaires

0
votes

Merci pour les réponses utiles dans ce fil.

J'ai essayé de résoudre le problème des stale data . Le problème de ne pas implémenter updateUIViewController dans UIViewControllerRepresentable . SwiftUI appelle makeUIViewController une seule fois pour créer le contrôleur de vue. La méthode updateUIViewController est chargée d'apporter des modifications au contrôleur de vue en fonction des modifications de la vue SwiftUI.

Comme UIActivityViewController ne permet pas de changer activityItems et applicationActivities , j'ai utilisé un contrôleur de vue wrapper. UIViewControllerRepresentable mettra à jour le wrapper et le wrapper créera un nouveau UIActivityViewController selon les besoins pour effectuer la mise à jour.

Ci-dessous mon code pour implémenter un bouton "partager" dans mon application. Le code est testé sur iOS 13.4 beta, qui a corrigé plusieurs bogues de SwiftUI - je ne sais pas si cela fonctionne sur les versions précédentes.

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 commentaires

1
votes

Je veux suggérer une autre implémentation qui semble plus native (demi-hauteur d'écran sans fond blanc).

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 commentaires

1
votes

Exemple utilisant SwiftUIX

Il existe une bibliothèque appelée SwiftUIX qui a déjà un wrapper pour UIActivityViewController . Voir le squelette rapide de la façon de le présenter via .sheet() qui devrait être placé quelque part dans le 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")!])
})


0 commentaires

0
votes

Ce n'est peut-être pas recommandé, mais c'est vraiment facile et deux lignes de code pour partager du texte

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


0 commentaires