Skip to content

Instantly share code, notes, and snippets.

@samrshi
Created July 1, 2022 05:38
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save samrshi/9befeb91e187dae2c9629fac41452e9b to your computer and use it in GitHub Desktop.
Save samrshi/9befeb91e187dae2c9629fac41452e9b to your computer and use it in GitHub Desktop.
SwiftUI - Snapshot the view inside a UIViewRepresentable (barely) using Combine
import SwiftUI
import Combine
struct ContentView: View {
@State private var text = "A\nB\nC\nD\nE\nF\nG\nH"
@State private var image: Image?
private let snapshotRequester = PassthroughSubject<Void, Never>()
var body: some View {
VStack {
GroupBox {
image
} label: {
Label("Resuting Image", systemImage: "photo")
}
Button {
snapshotRequester.send()
} label: {
Label("Take Snapshot", systemImage: "camera")
}
GroupBox {
TextView(text: $text, snapshotRequester: snapshotRequester, didTakeSnapshot: didTakeSnapshot)
.frame(height: 88)
} label: {
Label("Text View", systemImage: "square.and.pencil")
}
Spacer()
}
.padding()
}
func didTakeSnapshot(snapshot: UIImage) {
self.image = Image(uiImage: snapshot)
}
}
import SwiftUI
import Combine
struct TextView: UIViewRepresentable {
@Binding var text: String
let snapshotRequester: PassthroughSubject<Void, Never>
let didTakeSnapshot: (UIImage) -> Void
func makeCoordinator() -> Coordinator {
return Coordinator(text: $text, snapshotRequester: snapshotRequester, didTakeSnapshot: didTakeSnapshot)
}
func makeUIView(context: Context) -> UITextView {
let textView = UITextView()
textView.text = text
textView.delegate = context.coordinator
context.coordinator.textView = textView
return textView
}
func updateUIView(_ textView: UITextView, context: Context) {
textView.text = text
}
}
extension TextView {
class Coordinator: NSObject, UITextViewDelegate {
@Binding private var text: String
var cancellable: AnyCancellable?
var textView: UITextView!
init(text: Binding<String>, snapshotRequester: PassthroughSubject<Void, Never>, didTakeSnapshot: @escaping (UIImage) -> Void) {
_text = text
super.init()
cancellable = snapshotRequester
.sink { [weak self] _ in
guard let snapshot = self?.takeSnapshot() else { return }
didTakeSnapshot(snapshot)
}
}
func textViewDidChange(_ textView: UITextView) {
text = textView.text
}
func takeSnapshot() -> UIImage {
// replace this with whatever snapshotting implementation works best for you
let renderer = UIGraphicsImageRenderer(size: textView.contentSize)
let rect = CGRect(origin: .zero, size: textView.contentSize)
return renderer.image { _ in
textView.attributedText.draw(in: rect)
}
}
}
}
@samrshi
Copy link
Author

samrshi commented Jul 1, 2022

If I were doing this in a real application, I would likely create a separate object to communicate between the Coordinator and parent view that encapsulates the bridging logic (containing all the parameters I pass in, @Published properties, etc)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment