从SwiftUI视图中呈现UIActivityViewController

21

我正在尝试从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")!])
}

出现故障的控制器展示截图

有没有办法像通常展示一样,即覆盖半个屏幕?

4个回答

11
希望这可以帮到您,
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)
    }
}

这里输入图片描述


抱歉回复晚了,但是非常感谢您! - Kilian
11
这个解决方案很差,不应被标记为已接受。在许多情况下,UIApplication.shared.windows.first?.rootViewController?.present(av, animated: true, completion: nil)将无法正确获取vc。 - Kirill
我看到尝试使用windows.first.present时出现了问题。根据OP的版本,我改用了.sheet。需要注意的是,在共享表中键盘无法正常工作。如果你的表格不是作为父视图的一部分包含的话,响应链会很脆弱,我转而使用最外层的父视图。看起来这是iOS15带来的新问题。 - unB

5
在至少iOS 14、Swift 5、Xcode 12.5版本中,我可以通过将UIActivityViewController包装在另一个视图控制器中来轻松完成此操作。这不需要检查视图层次结构或使用任何第三方库。唯一的hackish部分是异步呈现视图控制器,这可能甚至不是必要的。拥有更多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

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)

原始回答(已弃用代码):

虽然不够优雅,但您可以直接这样调用它(考虑到您的应用程序只有一个窗口):

UIApplication.shared.windows.first?.rootViewController?.present(activityViewController, animated: true, completion: nil)

如果你遇到以下警告信息:

警告: 尝试呈现 ...,但它已经在呈现了...

你可以参考这篇文章获取最上层的视图控制器并在其上调用 present 方法。


2

有一个 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。如果这些内容不能如您所预期的那样工作,我还没有测试过所有内容,所以提前道歉。或者,您可以参考这个答案,其中提到了一个第三方库,它将允许您以通常呈现的方式创建半模态。请注意保留HTML标签。

谢谢你的提示,但我不确定在SwiftUI中是否可以指定UIModalPresentationStyle :/ - Kilian
@KilianKoeltzsch 我已经更新了答案,包括如何应用 modalPresentationStyle。不过说实话,你最好修改第三方库以适应你的需求。这样你会有更多的控制权。 - fulvio
3
啊,有趣,我甚至没有考虑在创建视图控制器时设置它。然而,当通过 .sheet() 显示它时,似乎这个设置没有被尊重。我也会查看相关选项。 - Kilian
4
即使您将SwiftUI视图包装在UIHostingController中,并将包装器的modalPresentationStyle设置为“pageSheet”,然后使用顶部控制器呈现上述内容,它仍将几乎全屏幕显示(就像问题中的图片)。但是,“pageSheet”确实受到尊重,因为将其设置为其他值会更改外观。遗憾的是,我无法获得屏幕一半被覆盖的表单。 - NeverwinterMoon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接