Je souhaite permettre à l'utilisateur de partager un emplacement mais je ne sais pas comment afficher UIActivityViewController dans l'interface utilisateur Swift
10 Réponses :
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
.
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.
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é.
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() } }
viewDidAppear doit appeler super
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>) {} }
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.
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.
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>) {} }
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 } }
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() } }
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")!]) })
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") }