Skip to content

Instantly share code, notes, and snippets.

@tadija
Last active February 27, 2021 00:43
Show Gist options
  • Save tadija/9ea941c3055281a2a349072cf0453f50 to your computer and use it in GitHub Desktop.
Save tadija/9ea941c3055281a2a349072cf0453f50 to your computer and use it in GitHub Desktop.
AESnapshot
/**
* https://gist.github.com/tadija/9ea941c3055281a2a349072cf0453f50
* Copyright © 2021 Marko Tadić
* Licensed under the MIT license
*/
import SwiftUI
public struct SnapshotTrigger {
public var trigger: Bool = false
public var image: UIImage?
public init() {}
}
public struct SnapshotMake<Content: View>: View {
@Binding var image: UIImage?
@Binding var trigger: Bool
var size: CGSize
var content: () -> Content
public init(into image: Binding<UIImage?>,
trigger: Binding<Bool>,
size: CGSize = .init(width: 1024, height: 1024),
@ViewBuilder content: @escaping () -> Content) {
self._image = image
self._trigger = trigger
self.size = size
self.content = content
}
public var body: some View {
ScrollView(.vertical, showsIndicators: false) {
ScrollView(.horizontal, showsIndicators: false) {
content()
.snapshot(into: $image, trigger: $trigger, scale: 1)
.frame(width: size.width, height: size.height)
}
}
}
}
public extension View {
func snapshot(
into image: Binding<UIImage?>,
trigger: Binding<Bool>,
scale: CGFloat = UIScreen.main.scale,
opaque: Bool = false,
edgesIgnoringSafeArea: Edge.Set = .all
) -> some View {
self.modifier(
SnapshotIntoModifier(
image: image,
trigger: trigger,
scale: scale,
opaque: opaque,
edgesIgnoringSafeArea: edgesIgnoringSafeArea
)
)
}
}
public struct SnapshotIntoModifier: ViewModifier {
@Binding var image: UIImage?
@Binding var trigger: Bool
var scale: CGFloat
var opaque: Bool
var edgesIgnoringSafeArea: Edge.Set
public func body(content: Content) -> some View {
SnapshotInto($image, trigger: $trigger, scale: scale, opaque: opaque) {
content
.edgesIgnoringSafeArea(edgesIgnoringSafeArea)
}
}
}
public struct SnapshotInto<Content: View>: View {
@Binding var image: UIImage?
@Binding var trigger: Bool
var scale: CGFloat
var opaque: Bool
var content: () -> Content
public init(_ image: Binding<UIImage?>,
trigger: Binding<Bool>,
scale: CGFloat = UIScreen.main.scale,
opaque: Bool = false,
@ViewBuilder content: @escaping () -> Content) {
self._image = image
self._trigger = trigger
self.scale = scale
self.opaque = opaque
self.content = content
}
public var body: some View {
SnapshotHost(
image: $image,
trigger: readyTrigger,
format: format,
content: content()
)
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.isReady = true
}
}
}
@State private var isReady = false
private var readyTrigger: Binding<Bool> {
.init(get: {
trigger && isReady
}, set: { value in
trigger = value
})
}
private var format: UIGraphicsImageRendererFormat {
let format = UIGraphicsImageRendererFormat()
format.scale = scale
format.opaque = opaque
return format
}
}
struct SnapshotHost<Content: View>: UIViewControllerRepresentable {
var image: Binding<UIImage?>
var trigger: Binding<Bool>
var format: UIGraphicsImageRendererFormat
var content: Content
func makeUIViewController(context: Context) -> UIViewController {
let vc = UIHostingController(rootView: content)
vc.view.backgroundColor = .clear
return vc
}
func updateUIViewController(_ viewController: UIViewController, context: Context) {
guard trigger.wrappedValue, viewController.view.bounds != .zero else {
return
}
image.wrappedValue = renderLayer(of: viewController)
trigger.wrappedValue = false
}
func renderLayer(of viewController: UIViewController) -> UIImage {
let renderer = UIGraphicsImageRenderer(bounds: viewController.view.bounds, format: format)
let image = renderer.image { rendererContext in
viewController.view.layer.render(in: rendererContext.cgContext)
}
return image
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment