I'm trying to render the SwiftUI View as an UIImage then let the user choose to save to the camera roll or send to others via email.
As an example, I want to render a list of 50 rows into a UIImage.
struct MyList: View {
var body: some View {
List {
ForEach(0 ..< 50, id:\.self) {
Text("row \($0)")
}
}
}
}
Searched over the internet for the past few weeks still with no luck. I have tried 2 different approaches.
1. UIHostingController (source here)
let hosting = UIHostingController(rootView: Text("TEST"))
hosting.view.frame = // Calculate the content size here //
let snapshot = hosting.view.snapshot // output: an empty snapshot of the size
print(hosting.view.subviews.count) // output: 0
I've tried layoutSubviews()
, setNeedsLayout()
, layoutIfNeeded()
, loadView()
, but all still resulted 0 subviews.
2. UIWindow.rootViewController (source here)
var vc = UIApplication.shared.windows[0].rootViewController
vc = vc.visibleController // loop through the view controller stack to find the top most view controller
let snapshot = vc.view.snapshot // output: a snapshot of the top most view controller
This almost yields the output I have wanted. However, the snapshot I get is literally a screenshot, i.e. with a Navigation bar, Tab bar and fixed size (same as screen size). What I need to capture is simply the view's content without those bars, and may sometimes be larger than the screen (my view is a long list in this example).
Tried to query vc.view.subviews
to look for the table view I want, but returning an unhelpful [<_TtGC7SwiftUI16PlatformViewHostGVS_42PlatformViewControllerRepresentableAdaptorGVS_16BridgedSplitViewVVS_22_VariadicView_Children7ElementGVS_5GroupGVS_19_ConditionalContentS4_GVS_17_UnaryViewAdaptorVS_9EmptyView______: 0x7fb061cc4770; frame = (0 0; 414 842); anchorPoint = (0, 0); tintColor = UIExtendedSRGBColorSpace 0 0.478431 1 1; layer = <CALayer: 0x600002d8caa0>>]
.
Any help is much appreciated.
I am wondering why people say your question is not clear. It actually makes perfect sense and it's one of the things I've been looking to do myself.
I finally found a way to do things the same way you wish them to be done, it's convoluted but it does work.
The idea is to abuse your second possibility (UIWindow.rootViewController
). That one is fun because you take your window, you figure out what you wish to draw, and you draw it.
But the problem is this is your main window, and you ask to draw your main window.
The way I finally got it working is by doing a UIViewControllerRepresentable
, which gives me a UIViewController
in my SwiftUI application. Then, inside it, I put a single UIHostingController
, which gives me a SwiftUI view inside the UIView. In other words, I do a bridge.
In the update
function, I can then call a dispatch async to view.layer.render(context)
to draw the image.
Of interest, the UIViewControllerRepresentable
will actually be full screen. So I recommend doing a .scale(...)
so your entire list fits in the screen (the render command will not mind it being smaller) and it will be centered. So if you know the size of your image in advance, you can do a HStack { VStack { YourStuff Spacer() } Spacer() }
so it's rendered top-left.
Good luck!
Would something like this work for you?
import SwiftUI
extension UIView {
func takeScreenshot() -> UIImage {
// Begin context
UIGraphicsBeginImageContextWithOptions(self.bounds.size, false, UIScreen.main.scale)
// Draw view in that context
drawHierarchy(in: self.bounds, afterScreenUpdates: true)
// And finally, get image
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
if (image != nil) {
UIImageWriteToSavedPhotosAlbum(image!, nil, nil, nil);
return image!
}
return UIImage()
}
}
struct ContentView: UIViewRepresentable {
func makeUIView(context: Context) -> UIView {
let someView = UIView(frame: UIScreen.main.bounds)
_ = someView.takeScreenshot()
return someView
}
func updateUIView(_ view: UIView, context: Context) {
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With