Reputation: 427
In my iOS App I've got the problem that the App crashes on iPad when I want to show a UIActivityViewController
. I've found out that this happens because of the PopoverPresentationController
and that I should set it to my view or button.
My code looks like this:
@State var alert = ActionSheet(title: Text("Error"))
var body: some View {
VStack() {
Button(action: {
self.showSheet.toggle()
}) {
Image(systemName: "ellipsis")
}
.actionSheet(isPresented: $showSheet) {
self.alert
}
}.onAppear(){
alert = ActionSheet(
title: Text("Auftrag"),
buttons: [
.cancel(Text("Close")),
.default(Text("Share")) { openActionSheet() },
)
}
}
func openActionSheet() {
let contextString = "TEST"
let print = UIMarkupTextPrintFormatter(markupText: contextString)
let render = UIPrintPageRenderer()
render.addPrintFormatter(print, startingAtPageAt: 0)
let page = CGRect(x: 0, y: 0, width: 595.2, height: 841.8) // A4, 72 dpi
render.setValue(page, forKey: "paperRect")
render.setValue(page, forKey: "printableRect")
let pdfData = NSMutableData()
UIGraphicsBeginPDFContextToData(pdfData, .zero, nil)
for i in 0..<render.numberOfPages {
UIGraphicsBeginPDFPage();
render.drawPage(at: i, in: UIGraphicsGetPDFContextBounds())
}
UIGraphicsEndPDFContext();
let av = UIActivityViewController(activityItems: [pdfData], applicationActivities: nil)
av.popoverPresentationController?.sourceView = view
UIApplication.shared.windows.first?.rootViewController?.present(av, animated: true, completion: nil)
}
The line av.popoverPresentationController?.sourceView = view
is what prevents the crash in all the other questions I've found.
In my case I get the error Cannot find 'view' in scope
.
Can someone please explain me why?
Upvotes: 3
Views: 1500
Reputation: 2345
Your own answer has a problem where you need to calculate from the screen size and key window, which can vary depending on the environment.
Instead, I suggest placing a hidden UIView
onto the button for sourceView
using this:
struct SourceViewModifier: ViewModifier {
var sourceView: UIView
init(_ sourceView: UIView) {
self.sourceView = sourceView
}
func body(content: Content) -> some View {
content
.background {
GeometryReader {
sourceView.frame = $0.frame(in: CoordinateSpace.global)
sourceView.bounds = $0.frame(in: CoordinateSpace.local)
return HiddenView(view: sourceView)
}
}
}
private struct HiddenView: UIViewRepresentable {
var view: UIView
func makeUIView(context: Context) -> some UIView {
view
}
func updateUIView(_ uiView: UIViewType, context: Context) {
view.isHidden = true
}
}
}
extension View {
func sourceView(_ sourceView: UIView) -> some View {
self.modifier(SourceViewModifier(sourceView))
}
}
Usage:
@State private var buttonSourceView = UIView()
var body: some View {
VStack {
Button { ... } label: { ... }
...
.sourceView(buttonSourceView) // ← Add this
}.onAppear {
alert = ActionSheet(
...
buttons: [
...,
.default(Text("Share")) {
openActionSheet(sender: buttonSourceView)
},
]
)
}
}
private func openActionSheet(sender: UIView) {
...
let av = UIActivityViewController(...)
av.popoverPresentationController?.sourceView = sender
av.popoverPresentationController?.sourceRect = sender.bounds
...
}
Upvotes: 0
Reputation: 427
You can specify the av.popoverPresentationController.sourceView like this:
if UIDevice.current.userInterfaceIdiom == .pad {
av.popoverPresentationController?.sourceView = UIApplication.shared.windows.first
av.popoverPresentationController?.sourceRect = CGRect(x: UIScreen.main.bounds.width / 2.1, y: UIScreen.main.bounds.height / 2.3, width: 200, height: 200)
}
Upvotes: 2