Reputation: 39
I am trying to make a UIscrollview that can zoom out beyond the original frames of the view that it hosts. Here is my code:
import SwiftUI
import UIKit
struct MapWrapperView<Content: View>: UIViewRepresentable {
private var content: Content
init(@ViewBuilder content: () -> Content) {
self.content = content()
}
func makeUIView(context: Context) -> UIScrollView {
let scrollView = UIScrollView()
scrollView.delegate = context.coordinator
scrollView.minimumZoomScale = 0
scrollView.maximumZoomScale = 100
scrollView.bouncesZoom = false
scrollView.bounces = false
scrollView.showsVerticalScrollIndicator = false
scrollView.showsHorizontalScrollIndicator = 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: self.content))
}
func updateUIView(_ uiView: UIScrollView, context: Context) {
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 scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) {
if(scale < scrollView.minimumZoomScale){
scrollView.minimumZoomScale = scale
}
if(scale > scrollView.maximumZoomScale){
scrollView.maximumZoomScale = scale
}
}
}
}
I can successfully zoom out farther than the bounds of the subview, but when this occurs the subview zooms out from the top left of the screen and begins to act strangely. Is there any way to either a) make the subview zoom out from the center of the screen and then extend to the new bounds or b) perhaps set the scale of the subview to a smaller value in the beginning and then set a reasonable minimum zoom?
Upvotes: 0
Views: 620
Reputation: 39
Ok I have figured out an answer. Basically, according to other members of stackoverflow, the contentOffset and contentInset variable have to be changed dynamically with every pinch in order to manage where the content anchors the zoom. Here is the changed code to make this work:
//
// MapWrapperView.swift
// Saturday
//
// Created by Bruce Jagid on 6/22/21.
//
import SwiftUI
import UIKit
struct MapWrapperView<Content: View>: UIViewRepresentable {
private var content: Content
init(@ViewBuilder content: () -> Content) {
self.content = content()
}
func makeUIView(context: Context) -> UIScrollView {
let scrollView = UIScrollView()
scrollView.delegate = context.coordinator
scrollView.minimumZoomScale = 0
scrollView.maximumZoomScale = 100
scrollView.bouncesZoom = false
scrollView.bounces = false
scrollView.showsVerticalScrollIndicator = false
scrollView.showsHorizontalScrollIndicator = false
scrollView.clipsToBounds = false
let hostedView = context.coordinator.hostingController.view!
hostedView.translatesAutoresizingMaskIntoConstraints = true
hostedView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
scrollView.bounds = hostedView.frame
scrollView.addSubview(hostedView)
let leftMargin: CGFloat = (scrollView.frame.size.width - hostedView.bounds.width)*0.5
let topMargin: CGFloat = (scrollView.frame.size.height - hostedView.bounds.height)*0.5
scrollView.contentOffset = CGPoint(x: max(0,-leftMargin), y: max(0,-topMargin));
scrollView.contentSize = CGSize(width: max(hostedView.bounds.width, hostedView.bounds.width+1), height: max(hostedView.bounds.height, hostedView.bounds.height+1))
return scrollView
}
func makeCoordinator() -> Coordinator {
return Coordinator(hostingController: UIHostingController(rootView: self.content))
}
func updateUIView(_ uiView: UIScrollView, context: Context) {
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 scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) {
if(scale < scrollView.minimumZoomScale){
scrollView.minimumZoomScale = scale
}
if(scale > scrollView.maximumZoomScale){
scrollView.maximumZoomScale = scale
}
}
func scrollViewDidZoom(_ scrollView: UIScrollView) {
if(scrollView.zoomScale < 1){
let leftMargin: CGFloat = (scrollView.frame.size.width - hostingController.view!.frame.width)*0.5
let topMargin: CGFloat = (scrollView.frame.size.height - hostingController.view!.frame.height)*0.5
scrollView.contentInset = UIEdgeInsets(top: max(0, topMargin), left: max(0,leftMargin), bottom: 0, right: 0)
}
}
}
}
Whenever the scroll view zooms out past the content view bounds, the zoom will be centered, as per the logic within the scrollViewDidZoom delegate function.
Upvotes: 1