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 на самом деле просто считается SwiftUI View!

Измените имя изображения на то, которое у вас есть в вашем проекте, и это должно работать. Вы получите центрированную кнопку 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")
Другие вопросы по тегам