SwiftUI экспорт или обмен файлами
Мне интересно, есть ли хороший способ экспортировать или поделиться файлом через SwiftUI. Кажется, нет способа обернуть UIActivityViewController и представить его напрямую. Я использовал UIViewControllerRepresentable, чтобы обернуть UIActivityViewController, и он аварийно завершает работу, если я, скажем, представляю его в модале SwiftUI.
Мне удалось создать универсальный UIViewController, а затем вызвать метод, который представляет UIActivityViewController, но это много оберток.
И если мы хотим поделиться с Mac с помощью SwiftUI, есть ли способ обернуть NSSharingServicePicker?
Во всяком случае, если у кого-нибудь есть пример того, как они это делают, это будет высоко ценится.
6 ответов
РЕДАКТИРОВАТЬ: Удален весь код и ссылки на UIButton
,
Спасибо @Matteo_Pacini за ответ на этот вопрос, который показал нам эту технику. Как и в случае с его ответом (и комментарием), (1) это грубо по краям и (2) я не уверен, что именно так Apple хочет, чтобы мы использовали UIViewControllerRepresentable
и я очень надеюсь, что они обеспечивают лучшее SwiftUI
("SwiftierUI"?) Замена в будущей бете.
Я вложил много работы в UIKit
потому что я хочу, чтобы это выглядело хорошо на iPad, где sourceView
нужен для поповера. Настоящая хитрость заключается в отображении (SwiftUI) View
который получает UIActivityViewController
в иерархии представления и триггера present
из UIKit
,
Мне нужно было представить одно изображение, чтобы поделиться им, поэтому все нацелено в этом направлении. Допустим, у вас есть изображение, сохраненное в виде @State
переменная - в моем примере изображение называется vermont.jpg и да, для этого все жестко закодировано.
Сначала создайте UIKit
класс типа `UIViewController для представления поповера общего ресурса:
class ActivityViewController : UIViewController {
var uiImage:UIImage!
@objc func shareImage() {
let vc = UIActivityViewController(activityItems: [uiImage!], applicationActivities: [])
vc.excludedActivityTypes = [
UIActivity.ActivityType.postToWeibo,
UIActivity.ActivityType.assignToContact,
UIActivity.ActivityType.addToReadingList,
UIActivity.ActivityType.postToVimeo,
UIActivity.ActivityType.postToTencentWeibo
]
present(vc,
animated: true,
completion: nil)
vc.popoverPresentationController?.sourceView = self.view
}
}
Главные вещи;
- Вам нужна "обертка"
UIViewController
быть способнымpresent
вещи. - Тебе нужно
var uiImage:UIImage!
установитьactivityItems
,
Далее, заверните это в UIViewControllerRepresentable
:
struct SwiftUIActivityViewController : UIViewControllerRepresentable {
let activityViewController = ActivityViewController()
func makeUIViewController(context: Context) -> ActivityViewController {
activityViewController
}
func updateUIViewController(_ uiViewController: ActivityViewController, context: Context) {
//
}
func shareImage(uiImage: UIImage) {
activityViewController.uiImage = uiImage
activityViewController.shareImage()
}
}
Следует отметить только две вещи:
- Инстанцирование
ActivityViewController
вернуть его доContentView
- Создание
shareImage(uiImage:UIImage
называть это.
Наконец, у вас есть ContentView
:
struct ContentView : View {
let activityViewController = SwiftUIActivityViewController()
@State var uiImage = UIImage(named: "vermont.jpg")
var body: some View {
VStack {
Button(action: {
self.activityViewController.shareImage(uiImage: self.uiImage!)
}) {
ZStack {
Image(systemName:"square.and.arrow.up").renderingMode(.original).font(Font.title.weight(.regular))
activityViewController
}
}.frame(width: 60, height: 60).border(Color.black, width: 2, cornerRadius: 2)
Divider()
Image(uiImage: uiImage!)
}
}
}
Обратите внимание, что есть жесткое кодирование и (тьфу) принудительное развертывание uiImage
наряду с ненужным использованием @State
, Они есть, потому что я планирую использовать `UIImagePickerController рядом, чтобы связать все это вместе.
Примечательные вещи здесь:
- Инстанцирование
SwiftUIActivityViewController
и используяshareImage
как действие кнопки. - С его помощью также можно отображать кнопки. Не забывайте, даже
UIViewControllerRepresentable
на самом деле просто считается SwiftUIView
!
Измените имя изображения на то, которое у вас есть в вашем проекте, и это должно работать. Вы получите центрированную кнопку 60x60 с изображением под ней.
Вы можете определить эту функцию где угодно (желательно в глобальной области):
@discardableResult
func share(
items: [Any],
excludedActivityTypes: [UIActivity.ActivityType]? = nil
) -> Bool {
guard let source = UIApplication.shared.windows.last?.rootViewController else {
return false
}
let vc = UIActivityViewController(
activityItems: items,
applicationActivities: nil
)
vc.excludedActivityTypes = excludedActivityTypes
vc.popoverPresentationController?.sourceView = source.view
source.present(vc, animated: true)
return true
}
Вы можете использовать эту функцию в действии кнопки или где угодно еще:
Button(action: {
share(items: ["This is some text"])
}) {
Text("Share")
}
Мы можем вызвать UIActivityViewController прямо из представления (SwiftUI) без использования UIViewControllerRepresentable
.
import SwiftUI
enum Coordinator {
static func topViewController(_ viewController: UIViewController? = nil) -> UIViewController? {
let vc = viewController ?? UIApplication.shared.windows.first(where: { $0.isKeyWindow })?.rootViewController
if let navigationController = vc as? UINavigationController {
return topViewController(navigationController.topViewController)
} else if let tabBarController = vc as? UITabBarController {
return tabBarController.presentedViewController != nil ? topViewController(tabBarController.presentedViewController) : topViewController(tabBarController.selectedViewController)
} else if let presentedViewController = vc?.presentedViewController {
return topViewController(presentedViewController)
}
return vc
}
}
struct ActivityView: View {
var body: some View {
Button(action: {
self.shareApp()
}) {
Text("Share")
}
}
}
extension ActivityView {
func shareApp() {
let textToShare = "something..."
let activityViewController = UIActivityViewController(activityItems: [textToShare], applicationActivities: nil)
let viewController = Coordinator.topViewController()
activityViewController.popoverPresentationController?.sourceView = viewController?.view
viewController?.present(activityViewController, animated: true, completion: nil)
}
}
struct ActivityView_Previews: PreviewProvider {
static var previews: some View {
ActivityView()
}
}
А это превью:
Надеюсь кому-то помочь!
Большинство решений здесь забывают заполнить лист общего доступа на iPad.
Итак, если вы хотите, чтобы приложение не давало сбоев на этом устройстве, вы можете использовать этот метод, когда
popoverController
используется и добавьте желаемый
activityItems
как параметр.
import SwiftUI
/// Share button to populate on any SwiftUI view.
///
struct ShareButton: View {
/// Your items you want to share to the world.
///
let itemsToShare = ["https://itunes.apple.com/app/id1234"]
var body: some View {
Button(action: { showShareSheet(with: itemsToShare) }) {
Image(systemName: "square.and.arrow.up")
.font(.title2)
.foregroundColor(.blue)
}
}
}
extension View {
/// Show the classic Apple share sheet on iPhone and iPad.
///
func showShareSheet(with activityItems: [Any]) {
guard let source = UIApplication.shared.windows.last?.rootViewController else {
return
}
let activityVC = UIActivityViewController(
activityItems: activityItems,
applicationActivities: nil)
if let popoverController = activityVC.popoverPresentationController {
popoverController.sourceView = source.view
popoverController.sourceRect = CGRect(x: source.view.bounds.midX,
y: source.view.bounds.midY,
width: .zero, height: .zero)
popoverController.permittedArrowDirections = []
}
source.present(activityVC, animated: true)
}
}
Взгляните на AlanQuatermain -s SwiftUIShareSheetDemo
В двух словах это выглядит так:
@State private var showShareSheet = false
@State public var sharedItems : [Any] = []
Button(action: {
self.sharedItems = [UIImage(systemName: "house")!]
self.showShareSheet = true
}) {
Text("Share")
}.sheet(isPresented: $showShareSheet) {
ShareSheet(activityItems: self.sharedItems)
}
struct ShareSheet: UIViewControllerRepresentable {
typealias Callback = (_ activityType: UIActivity.ActivityType?, _ completed: Bool, _ returnedItems: [Any]?, _ error: Error?) -> Void
let activityItems: [Any]
let applicationActivities: [UIActivity]? = nil
let excludedActivityTypes: [UIActivity.ActivityType]? = nil
let callback: Callback? = nil
func makeUIViewController(context: Context) -> UIActivityViewController {
let controller = UIActivityViewController(
activityItems: activityItems,
applicationActivities: applicationActivities)
controller.excludedActivityTypes = excludedActivityTypes
controller.completionWithItemsHandler = callback
return controller
}
func updateUIViewController(_ uiViewController: UIActivityViewController, context: Context) {
// nothing to do here
}
}
Начиная с iOS 16, вы можете использовать ShareLink:
ShareLink(item: "http://www.myappurl.com") {
Label("Share app", systemImage: "square.and.arrow.up")
}
или просто:
ShareLink(item: "http://www.myappurl.com")