SwiftUI's ScrollView does not natively support zooming, so I have to use a UIScrollView for zooming in on a canvas. I implemented a UIScrollView, with some minor changes to how bounds work, to this answer found here.(https://stackoverflow.com/a/64110231/14766265) However, when zooming in, the text in the canvas is unsurprisingly blurry. The surprising thing is that I have not been able to find a way to have the canvas upscaled when zooming in. Most answers on stackoverflow use the contentScaleZoom as a fix, but while it clearly *is* rendering at a higher resolution, it screws up the bounds, zooms too far in, and zooms into the top left corner.
Here is the smallest implementation of my problem as an xCode playground. The commented out part is a commonly given answer to this problem.
import SwiftUI
import PlaygroundSupport
struct ZoomableScrollView<Content: View>: UIViewRepresentable {
private var content: Content
init(@ViewBuilder content: () -> Content) {
self.content = content()
}
func makeUIView(context: Context) -> UIScrollView {
// set up the UIScrollView
let scrollView = UIScrollView()
scrollView.delegate = context.coordinator // for viewForZooming(in:)
scrollView.maximumZoomScale = 40
scrollView.minimumZoomScale = 1
scrollView.bouncesZoom = true
scrollView.clipsToBounds = false
scrollView.autoresizesSubviews = false
scrollView.isPagingEnabled = false
// create a UIHostingController to hold our SwiftUI content
let hostedView = context.coordinator.hostingController.view!
hostedView.translatesAutoresizingMaskIntoConstraints = false
hostedView.frame = scrollView.bounds
scrollView.addSubview(hostedView)
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint (equalTo: hostedView.topAnchor),
scrollView.bottomAnchor.constraint (equalTo: hostedView.bottomAnchor),
scrollView.leadingAnchor.constraint (equalTo: hostedView.leadingAnchor),
scrollView.trailingAnchor.constraint (equalTo: hostedView.trailingAnchor),
hostedView.heightAnchor.constraint(equalToConstant: 600),
hostedView.widthAnchor.constraint(equalToConstant: 600),
])
return scrollView
}
func makeCoordinator() -> Coordinator {
return Coordinator(hostingController: UIHostingController(rootView: self.content))
}
func updateUIView(_ uiView: UIScrollView, context: Context) {
// update the hosting controller's SwiftUI content
context.coordinator.hostingController.rootView = self.content
assert(context.coordinator.hostingController.view.superview == uiView)
}
// MARK: - Coordinator
class Coordinator: NSObject, UIScrollViewDelegate {
var hostingController: UIHostingController<Content>
init(hostingController: UIHostingController<Content>) {
self.hostingController = hostingController
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return hostingController.view
}
/*
func scaleView( view: UIView, scale: CGFloat ){
view.contentScaleFactor = scale * UIScreen.main.scale
for vi in view.subviews {
scaleView(view: vi, scale: scale)
}
}
func scaleLayer( layer: CALayer, scale: CGFloat ){
layer.contentsScale = scale * UIScreen.main.scale
if layer.sublayers == nil {
return
}
for la in layer.sublayers! {
scaleLayer(layer: la, scale: scale)
}
}
func scrollViewDidEndZooming(_ scrollView: UIScrollView, with hostedView: UIView?, atScale scale: CGFloat) {
scaleView(view: hostedView!, scale: scale)
scaleLayer(layer: hostedView!.layer, scale: scale)
}*/
}
}
struct ContentView : View {
var body : some View {
ZoomableScrollView{
Canvas(renderer: {context, size in
context.fill(Path(CGRect(origin: .zero, size: size)), with: .linearGradient(Gradient(colors: [.white, .black]), startPoint: CGPoint(x:0,y:0), endPoint: CGPoint(x: size.width, y: size.height)))
context.draw(Text("Just some text"), at: CGPoint(x: 80,y: 80))
})
}
}
}
PlaygroundPage.current.setLiveView(ContentView().frame(width: 500.0, height: 500.0))