SwiftUI 导出或共享文件
SwiftUI exporting or sharing files
我想知道是否有通过 SwiftUI 导出或共享文件的好方法。似乎没有办法包装 UIActivityViewController 并直接呈现它。我已经使用 UIViewControllerRepresentable 来包装 UIActivityViewController,如果我在 SwiftUI 模态中呈现它,它就会崩溃。
我能够创建一个通用的 UIViewController,然后从那里调用一个呈现 UIActivityViewController 的方法,但这需要大量包装。
如果我们想使用 SwiftUI 从 Mac 分享,有没有办法包装 NSSharingServicePicker?
无论如何,如果有人能举例说明他们是如何做到这一点的,我们将不胜感激。
编辑:删除了对 UIButton
.
的所有代码和引用
感谢@Matteo_Pacini 对 的回答向我们展示了这项技术。至于他的回答(和评论),(1)这有点粗糙,(2)我不确定苹果希望我们如何使用 UIViewControllerRepresentable
我真的希望他们提供更好的 SwiftUI
("SwiftierUI"?) 在未来的测试版中替换。
我在 UIKit
中做了很多工作,因为我希望它在 iPad 上看起来不错,而弹出窗口需要 sourceView
。真正的诀窍是显示一个 (SwiftUI) View
,它在视图层次结构中获取 UIActivityViewController
并从 UIKit
.
触发 present
我的需求是展示一张图片来分享,所以事情就是朝着这个方向发展的。假设您有一张图像,存储为 @State
变量 - 在我的示例中,图像称为 vermont.jpg 是的,事情是 hard-coded那。
首先,创建一个 UIKit
class 类型的 `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
}
}
主要是;
- 你需要 "wrapper"
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
到 return 直到 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
中有一些 hard-coding 和(呃)force-unwrapping,以及不必要的 @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")
}
看看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
}
}
我们可以直接从视图 (SwiftUI) 调用 UIActivityViewController 而无需使用 UIViewControllerRepresentable
。
import SwiftUI
enum Coordinator {
static func topViewController(_ viewController: UIViewController? = nil) -> UIViewController? {
let vc = viewController ?? UIApplication.shared.windows.first(where: { [=10=].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 上的共享 sheet。
因此,如果您希望应用程序不会在此设备上崩溃,您可以使用
此方法使用 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)
}
}
我想知道是否有通过 SwiftUI 导出或共享文件的好方法。似乎没有办法包装 UIActivityViewController 并直接呈现它。我已经使用 UIViewControllerRepresentable 来包装 UIActivityViewController,如果我在 SwiftUI 模态中呈现它,它就会崩溃。
我能够创建一个通用的 UIViewController,然后从那里调用一个呈现 UIActivityViewController 的方法,但这需要大量包装。
如果我们想使用 SwiftUI 从 Mac 分享,有没有办法包装 NSSharingServicePicker?
无论如何,如果有人能举例说明他们是如何做到这一点的,我们将不胜感激。
编辑:删除了对 UIButton
.
感谢@Matteo_Pacini 对 UIViewControllerRepresentable
我真的希望他们提供更好的 SwiftUI
("SwiftierUI"?) 在未来的测试版中替换。
我在 UIKit
中做了很多工作,因为我希望它在 iPad 上看起来不错,而弹出窗口需要 sourceView
。真正的诀窍是显示一个 (SwiftUI) View
,它在视图层次结构中获取 UIActivityViewController
并从 UIKit
.
present
我的需求是展示一张图片来分享,所以事情就是朝着这个方向发展的。假设您有一张图像,存储为 @State
变量 - 在我的示例中,图像称为 vermont.jpg 是的,事情是 hard-coded那。
首先,创建一个 UIKit
class 类型的 `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
}
}
主要是;
- 你需要 "wrapper"
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
到 return 直到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
中有一些 hard-coding 和(呃)force-unwrapping,以及不必要的 @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")
}
看看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
}
}
我们可以直接从视图 (SwiftUI) 调用 UIActivityViewController 而无需使用 UIViewControllerRepresentable
。
import SwiftUI
enum Coordinator {
static func topViewController(_ viewController: UIViewController? = nil) -> UIViewController? {
let vc = viewController ?? UIApplication.shared.windows.first(where: { [=10=].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 上的共享 sheet。
因此,如果您希望应用程序不会在此设备上崩溃,您可以使用
此方法使用 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)
}
}