从 SwiftUI 视图呈现 UIActivityViewController
Posted
技术标签:
【中文标题】从 SwiftUI 视图呈现 UIActivityViewController【英文标题】:Presenting UIActivityViewController from SwiftUI View 【发布时间】:2019-10-08 12:26:03 【问题描述】:我正在尝试从 SwiftUI 视图中呈现 UIActivityViewController
(共享表)。我创建了一个名为ShareSheet
的视图符合UIViewControllerRepresentable
来配置UIActivityViewController
,但实际呈现它并不是那么简单。
struct ShareSheet: UIViewControllerRepresentable
typealias UIViewControllerType = UIActivityViewController
var sharing: [Any]
func makeUIViewController(context: UIViewControllerRepresentableContext<ShareSheet>) -> UIActivityViewController
UIActivityViewController(activityItems: sharing, applicationActivities: nil)
func updateUIViewController(_ uiViewController: UIActivityViewController, context: UIViewControllerRepresentableContext<ShareSheet>)
通过.sheet
天真地这样做会导致以下结果。
.sheet(isPresented: $showShareSheet)
ShareSheet(sharing: [URL(string: "https://example.com")!])
有没有办法像通常呈现的那样呈现这个?就像覆盖半个屏幕一样?
【问题讨论】:
【参考方案1】:希望对你有帮助,
struct ShareSheetView: View
var body: some View
Button(action: actionSheet)
Image(systemName: "square.and.arrow.up")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 36, height: 36)
func actionSheet()
guard let data = URL(string: "https://www.apple.com") else return
let av = UIActivityViewController(activityItems: [data], applicationActivities: nil)
UIApplication.shared.windows.first?.rootViewController?.present(av, animated: true, completion: nil)
【讨论】:
抱歉回复晚了,谢谢! 这是一个糟糕的解决方案,不应标记为已接受。 ` UIApplication.shared.windows.first?.rootViewController?.present(av, animated: true, completion: nil)` 在很多场景下会无法获取到正确的vc。 我在尝试使用 windows first.present 时看到了这个问题。我根据 OP 的版本使用了 .sheet 。需要注意的是共享表中的键盘无法正常工作。如果您的工作表不包含在父视图中,则响应者链很脆弱,我转而使用最外层的父视图。看起来像是 ios15 自带的东西。【参考方案2】:至少在 iOS 14、Swift 5、Xcode 12.5 中,我只需将 UIActivityViewController
包装在另一个视图控制器中即可相当轻松地完成此操作。它不需要检查视图层次结构或使用任何 3rd 方库。唯一骇人听闻的部分是异步呈现视图控制器,这甚至可能不是必需的。有更多 SwiftUI 经验的人或许可以提供改进建议。
import Foundation
import SwiftUI
import UIKit
struct ActivityViewController: UIViewControllerRepresentable
@Binding var shareURL: URL?
func makeCoordinator() -> Coordinator
Coordinator(self)
func makeUIViewController(context: Context) -> some UIViewController
let containerViewController = UIViewController()
return containerViewController
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context)
guard let shareURL = shareURL, context.coordinator.presented == false else return
context.coordinator.presented = true
let activityViewController = UIActivityViewController(activityItems: [shareURL], applicationActivities: nil)
activityViewController.completionWithItemsHandler = activity, completed, returnedItems, activityError in
self.shareURL = nil
context.coordinator.presented = false
if completed
// ...
else
// ...
// Executing this asynchronously might not be necessary but some of my tests
// failed because the view wasn't yet in the view hierarchy on the first pass of updateUIViewController
//
// There might be a better way to test for that condition in the guard statement and execute this
// synchronously if we can be be sure updateUIViewController is invoked at least once after the view is added
DispatchQueue.main.asyncAfter(deadline: .now())
uiViewController.present(activityViewController, animated: true)
class Coordinator: NSObject
let parent: ActivityViewController
var presented: Bool = false
init(_ parent: ActivityViewController)
self.parent = parent
struct ContentView: View
@State var shareURL: URL? = nil
var body: some View
ZStack
Button(action: shareURL = URL(string: "https://apple.com") )
Text("Share")
.foregroundColor(.white)
.padding()
.background(Color.blue)
if shareURL != nil
ActivityViewController(shareURL: $shareURL)
.frame(width: 375, height: 812)
【讨论】:
【参考方案3】:有一个UIModalPresentationStyle
可用于显示某些演示文稿:
case pageSheet
一种部分覆盖底层内容的展示风格。
你应用演示风格的方式:
func makeUIViewController(context: UIViewControllerRepresentableContext<ActivityView>) -> UIActivityViewController
let v = UIActivityViewController(activityItems: sharing, applicationActivities: nil)
v.modalPresentationStyle = .pageSheet
return v
可在此处找到演示文稿列表:
https://developer.apple.com/documentation/uikit/uimodalpresentationstyle
我还没有亲自测试过它们,所以如果最终没有像您预期的那样工作,我提前道歉。
或者,您可以查看this answer,他们在其中提到了第三方库,这将允许您以通常呈现的方式创建半模态。
【讨论】:
感谢您的提示,但我不确定是否可以使用 SwiftUI 指定 UIModalPresentationStyle :/ @KilianKoeltzsch 我已经更新了答案,包括您如何应用modalPresentationStyle
。说您最好修改第三方库以满足您的需求。您也将拥有更多控制权。
啊,有意思,我在创建视图控制器的时候都没想过要设置它。虽然通过.sheet()
显示它时似乎没有得到尊重。我也会研究链接选项。
即使您将 SwiftUI 视图包装在 UIHostingController
中并将包装器的 modalPresentationStyle
设置为 pageSheet
,然后使用顶部控制器呈现上述内容,仍然几乎是全屏的 (就像问题中的图片一样)。 pageSheet
当然受到尊重,因为设置为其他内容确实会改变外观。唉,我无法让纸张半覆盖屏幕。【参考方案4】:
iOS 15 / Swift 5 / Xcode 13
获取顶部呈现的 UIViewController 的扩展:
import UIKit
extension UIApplication
// MARK: No shame!
static func TopPresentedViewController() -> UIViewController?
guard let rootViewController = UIApplication.shared
.connectedScenes.lazy
.compactMap( $0.activationState == .foregroundActive ? ($0 as? UIWindowScene) : nil )
.first(where: $0.keyWindow != nil )?
.keyWindow?
.rootViewController
else
return nil
var topController = rootViewController
while let presentedViewController = topController.presentedViewController
topController = presentedViewController
return topController
然后用它来展示你的 UIActivityViewController:
UIApplication.TopPresentedViewController?.present(activityViewController, animated: true, completion: nil)
原始答案(已弃用的代码):
它并不漂亮,但你可以直接这样调用它(考虑到你的应用只有 1 个窗口):
UIApplication.shared.windows.first?.rootViewController?.present(activityViewController, animated: true, completion: nil)
如果你得到一些警告blablabla:
警告:尝试展示...已经在展示...
您可以执行this to get the top most view controller 之类的操作并调用present。
【讨论】:
以上是关于从 SwiftUI 视图呈现 UIActivityViewController的主要内容,如果未能解决你的问题,请参考以下文章
从 SwiftUI 视图呈现 UIActivityViewController
从公共 API 获取 JSON 以在 SwiftUI 的列表视图中呈现
如何将关闭按钮从 swiftui 视图的主结构(由 uihostingviewcontroller 呈现)分离到它自己的结构?
从 NavigationView 呈现的 SwiftUI 关闭模式表(Xcode Beta 5)