I extended another user’s posted solution for this ZoomableContainer to enable a pinch and scroll for my SwiftUI App. I’m able to programmatically set the zoom level, but can’t figure out the coordinates for the drag to scroll behavior so that I can modify them externally, like a bookmark for a specific zoom level centered on a particular coordinate within the Container. Would love any pointers on a good way to do this, especially one that is bindable to other views for flexibility.
struct ZoomableContainer<Content: View>: View {
let content: Content
@Binding private var currentScale: CGFloat
@State private var tapLocation: CGPoint = .zero
@Binding var maxAllowedScale: CGFloat
@Binding var mapViewCenter: CGPoint?
@Binding var mapViewZoom: CGFloat?
init(currentScale: Binding<CGFloat>, maxAllowedScale: Binding<CGFloat>, mapViewCenter: Binding<CGPoint?>, mapViewZoom: Binding<CGFloat?>, @ViewBuilder content: () -> Content) {
print ("ZoomableContainer Init")
_currentScale = currentScale
self.content = content()
_maxAllowedScale = maxAllowedScale
_mapViewCenter = mapViewCenter
_mapViewZoom = mapViewZoom
}
func setCurrentCenterAndZoom(center: CGPoint, zoom: CGFloat) {
mapViewCenter = center
mapViewZoom = zoom
}
func doubleTapAction(location: CGPoint) {
tapLocation = location
currentScale = currentScale == 1.0 ? maxAllowedScale : 1.0
}
var body: some View {
ZoomableScrollView(scale: $currentScale, tapLocation: $tapLocation, maxScale: maxAllowedScale, mapViewCenter: $mapViewCenter, mapViewZoom: $mapViewZoom) {
content
}
.onTapGesture(count: 2, perform: doubleTapAction)
}
fileprivate struct ZoomableScrollView<Content: View>: UIViewRepresentable {
private var content: Content
@Binding private var currentScale: CGFloat
@Binding private var tapLocation: CGPoint
private let maxScale: CGFloat
@Binding var mapViewCenter: CGPoint?
@Binding var mapViewZoom: CGFloat?
init(scale: Binding<CGFloat>, tapLocation: Binding<CGPoint>, maxScale: CGFloat, mapViewCenter: Binding<CGPoint?>, mapViewZoom: Binding<CGFloat?>, @ViewBuilder content: () -> Content) {
_currentScale = scale
_tapLocation = tapLocation
self.maxScale = maxScale
_mapViewCenter = mapViewCenter
_mapViewZoom = mapViewZoom
self.content = content()
}
func makeUIView(context: Context) -> UIScrollView {
let scrollView = UIScrollView()
scrollView.delegate = context.coordinator
scrollView.maximumZoomScale = maxScale
scrollView.minimumZoomScale = 1.0
scrollView.bouncesZoom = true
scrollView.showsHorizontalScrollIndicator = false
scrollView.showsVerticalScrollIndicator = false
scrollView.clipsToBounds = false
let hostedView = context.coordinator.hostingController.view!
hostedView.translatesAutoresizingMaskIntoConstraints = true
hostedView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
hostedView.frame = scrollView.bounds
scrollView.addSubview(hostedView)
return scrollView
}
func makeCoordinator() -> Coordinator {
return Coordinator(hostingController: UIHostingController(rootView: content), scale: $currentScale)
}
func updateUIView(_ uiView: UIScrollView, context: Context) {
print ("running updateUIView")
context.coordinator.hostingController.rootView = content
if uiView.zoomScale > uiView.minimumZoomScale {
uiView.setZoomScale(currentScale, animated: true)
mapViewZoom = currentScale
print ("setting Zoom Scale")
} else if tapLocation != .zero {
uiView.zoom(to: zoomRect(for: uiView, scale: uiView.maximumZoomScale, center: tapLocation), animated: true)
mapViewCenter?.x = tapLocation.x
mapViewCenter?.y = tapLocation.y
mapViewZoom = currentScale
DispatchQueue.main.async { tapLocation = .zero }
print("tapLocation")
} else if let center = mapViewCenter, let zoom = mapViewZoom {
uiView.zoom(to: zoomRect(for: uiView, scale: zoom, center: center), animated: true)
print ("coords and zoom")
}
assert(context.coordinator.hostingController.view.superview == uiView)
}
func zoomRect(for scrollView: UIScrollView, scale: CGFloat, center: CGPoint) -> CGRect {
print ("Running zoomRect")
let scrollViewSize = scrollView.bounds.size
let width = scrollViewSize.width / scale
let height = scrollViewSize.height / scale
let x = center.x - (width / 2.0)
let y = center.y - (height / 2.0)
return CGRect(x: x, y: y, width: width, height: height)
}
class Coordinator: NSObject, UIScrollViewDelegate {
var hostingController: UIHostingController<Content>
@Binding var currentScale: CGFloat
init(hostingController: UIHostingController<Content>, scale: Binding<CGFloat>) {
self.hostingController = hostingController
_currentScale = scale
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return hostingController.view
}
func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) {
currentScale = scale
}
}
}
}```
I've added some various print statements to see where this code executes when I drag to scroll around my map, but haven't figured out a place to extract coordinates from.
Rhodizzle is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.