// // ContentView.swift // Grabber // // SwiftUI-Lab // // For more information on the techniques used in this example, check // The Power of the Hosting+Representable Combo (https://swiftui-lab.com/a-powerful-combo/) import SwiftUI struct ContentView: View { @State private var beginCapture = false @State private var image: UIImage? = nil var body: some View { VStack(spacing: 40) { HStack(spacing: 5) { ZStack { RotatingSpheres(clockwise: false) .frame(width: 300, height: 300) RotatingSpheres(clockwise: true) .onGrab(trigger: self.$beginCapture) { img in DispatchQueue.main.async { self.image = img } } .frame(width: 300, height: 300) .border(Color.black, width: 3) RotatingSpheres(clockwise: false) .frame(width: 300, height: 300) } // Captured Image Image(uiImage: self.image ?? UIImage()) .resizable() .frame(width: 300, height: 300) .border(Color.black, width: 3) } Button("Capture") { self.beginCapture = true } } } } struct RotatingSpheres: View { @State private var flag = false let clockwise: Bool var body: some View { VStack { HStack(spacing: 20) { Circle() .fill(self.clockwise ? Color.orange : Color.red) .frame(width: 80, height: 80) Circle() .fill(self.clockwise ? Color.green : Color.blue) .frame(width: 80, height: 80) } Circle() .fill(self.clockwise ? Color.yellow : Color.purple) .frame(width: 80, height: 80) } .onAppear { withAnimation(Animation.linear(duration: 5.0).repeatForever(autoreverses: false)) { self.flag = true } } .rotationEffect(self.flag ? (self.clockwise ? Angle.degrees(360) : Angle.degrees(-360)) : Angle.degrees(0)) } } extension View { func onGrab(rect: CGRect? = nil, trigger: Binding, onCapture: @escaping (UIImage) -> Void) -> some View { return CaptureWrapper(rect: rect, trigger: trigger, onCapture: onCapture) { self } } } struct CaptureWrapper: View where Content : View { let rect: CGRect? let trigger: Binding let onCapture: (UIImage) -> Void let content: () -> Content init(rect: CGRect? = nil, trigger: Binding, onCapture: @escaping (UIImage) -> Void, @ViewBuilder content: @escaping () -> Content) { self.rect = rect self.trigger = trigger self.onCapture = onCapture self.content = content } var body: some View { CaptureRepresentable(rect: rect, trigger: self.trigger, onCapture: self.onCapture, content: self.content()) } } struct CaptureRepresentable: UIViewControllerRepresentable where Content: View { typealias UIViewControllerType = CaptureNSHostingController @Binding var trigger: Bool let rect: CGRect? let content: Content let onCapture: (UIImage) -> Void init(rect: CGRect?, trigger: Binding, onCapture: @escaping (UIImage) -> Void, content: Content) { self.rect = rect self._trigger = trigger self.onCapture = onCapture self.content = content } func makeUIViewController(context: UIViewControllerRepresentableContext>) -> CaptureNSHostingController { return CaptureNSHostingController(rootView: self.content) } func updateUIViewController(_ uiViewController: CaptureNSHostingController, context: UIViewControllerRepresentableContext>) { if self.trigger { self.onCapture(uiViewController.getAsImage(rect: rect)) DispatchQueue.main.async { self.trigger = false } } } } class CaptureNSHostingController: UIHostingController where Content : View { override func preferredContentSizeDidChange(forChildContentContainer container: UIContentContainer) { } func getAsImage(rect: CGRect? = nil) -> UIImage { if let rect = rect { return self.view.asImage(rect: rect) } else { return self.view.asImage(rect: self.view.bounds) } } } extension UIView { func asImage(rect: CGRect) -> UIImage { let renderer = UIGraphicsImageRenderer(bounds: rect) return renderer.image { rendererContext in layer.render(in: rendererContext.cgContext) } } }