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