如何在 swiftui 中使用 UIscrollview 缩小过去的视图边界
How to zoom out past view bounds with UIscrollview in swiftui
我正在尝试制作一个 UIscrollview,它可以缩小到它承载的视图的原始框架之外。这是我的代码:
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
}
}
}
}
我可以成功地缩小到超出子视图边界的范围,但是当发生这种情况时,子视图从屏幕左上角缩小并开始出现奇怪的行为。有没有办法a)使子视图从屏幕中心缩小然后扩展到新边界或b)也许在开始时将子视图的比例设置为较小的值然后设置合理的最小缩放?
好的,我找到了答案。基本上,根据 Whosebug 的其他成员的说法,每次捏合时都必须动态更改 contentOffset 和 contentInset 变量,以便管理内容锚定缩放的位置。这是使这项工作生效的更改代码:
//
// 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)
}
}
}
}
每当滚动视图缩小超过内容视图边界时,缩放将居中,按照 scrollViewDidZoom 委托函数中的逻辑。
我正在尝试制作一个 UIscrollview,它可以缩小到它承载的视图的原始框架之外。这是我的代码:
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
}
}
}
}
我可以成功地缩小到超出子视图边界的范围,但是当发生这种情况时,子视图从屏幕左上角缩小并开始出现奇怪的行为。有没有办法a)使子视图从屏幕中心缩小然后扩展到新边界或b)也许在开始时将子视图的比例设置为较小的值然后设置合理的最小缩放?
好的,我找到了答案。基本上,根据 Whosebug 的其他成员的说法,每次捏合时都必须动态更改 contentOffset 和 contentInset 变量,以便管理内容锚定缩放的位置。这是使这项工作生效的更改代码:
//
// 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)
}
}
}
}
每当滚动视图缩小超过内容视图边界时,缩放将居中,按照 scrollViewDidZoom 委托函数中的逻辑。