I am trying to make buttons grid that will be scale up and down with pinching the scrollview , I am inspired by Scale UIView elements and the answer by donmag. the problem is that the buttons comes out of the scrollview, its showing on the main view as well but I want it inside the scrollview and when I zoom in and out they do not stay together like the grid , they separate or go together. dose anyone knows how to fix these two problem.i know I should ask just one question at time but these are some how in the same category ,sorry and thanks for helping. .you can run the code , just give ComplexVC to a view controller.
import UIKit
class DrawZoomBaseVC: UIViewController {
let scrollView: UIScrollView = UIScrollView()
// this will be a plain, clear UIView that we will use
// as the viewForZooming
let zoomView = UIView()
// this will be placed *behind* the scrollView
// in our subclasses, we'll set it to either
// Simple or Complex
// and we'll set its zoomScale and contentOffset
// to match the scrollView
var drawView: UIView!
// a label to put at the top to show the current zoomScale
let infoLabel: UILabel = {
let v = UILabel()
v.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
v.textAlignment = .center
v.numberOfLines = 0
v.text = "\n\n\n"
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.yellow
[infoLabel, drawView, scrollView].forEach { v in
v!.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(v!)
}
zoomView.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(zoomView)
drawView.backgroundColor = .black
scrollView.backgroundColor = .clear
zoomView.backgroundColor = .clear
let g = view.safeAreaLayoutGuide
let cg = scrollView.contentLayoutGuide
NSLayoutConstraint.activate([
// info label at the top
infoLabel.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
infoLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
infoLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
scrollView.topAnchor.constraint(equalTo: infoLabel.bottomAnchor, constant: 20.0),
scrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
scrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
scrollView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0),
zoomView.topAnchor.constraint(equalTo: cg.topAnchor, constant: 0.0),
zoomView.leadingAnchor.constraint(equalTo: cg.leadingAnchor, constant: 0.0),
zoomView.trailingAnchor.constraint(equalTo: cg.trailingAnchor, constant: 0.0),
zoomView.bottomAnchor.constraint(equalTo: cg.bottomAnchor, constant: 0.0),
drawView.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 0.0),
drawView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor, constant: 0.0),
drawView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor, constant: 0.0),
drawView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: 0.0),
])
scrollView.maximumZoomScale = 60.0
scrollView.minimumZoomScale = 0.1
scrollView.zoomScale = 1.0
scrollView.indicatorStyle = .white
scrollView.delegate = self
infoLabel.isHidden = false
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// if we're using the ComplexDrawScaledView
// we *get* its size that was determined by
// it laying out its elements in its commonInit()
if let dv = drawView as? ComplexDrawScaledView {
zoomView.widthAnchor.constraint(equalToConstant: dv.virtualSize.width).isActive = true
zoomView.heightAnchor.constraint(equalToConstant: dv.virtualSize.height).isActive = true
}
// let auto-layout size the view before we update the info label
DispatchQueue.main.async {
self.updateInfoLabel()
}
}
func updateInfoLabel() {
infoLabel.text = String(format: "\nzoomView size: (%0.0f, %0.0f)\nzoomScale: %0.3f\n", zoomView.frame.width, zoomView.frame.height, scrollView.zoomScale)
}
}
extension DrawZoomBaseVC: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if let dv = drawView as? ComplexDrawScaledView {
dv.contentOffset = scrollView.contentOffset
}
}
func scrollViewDidZoom(_ scrollView: UIScrollView) {
updateInfoLabel()
if let dv = drawView as? ComplexDrawScaledView {
dv.zoomScale = scrollView.zoomScale
}
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return zoomView
}
}
class ComplexVC: DrawZoomBaseVC {
override func viewDidLoad() {
drawView = ComplexDrawScaledView()
super.viewDidLoad()
}
}
class ComplexDrawScaledView: UIView {
public var virtualSize: CGSize = .zero
public var zoomScale: CGFloat = 1.0 { didSet { setNeedsDisplay() } }
public var contentOffset: CGPoint = .zero { didSet { setNeedsDisplay() } }
private let nCols: Int = 32
private let nRows: Int = 128
private let colWidth: CGFloat = 120.0
private let rowHeight: CGFloat = 80.0
private let colSpacing: CGFloat = -2.0
private let rowSpacing: CGFloat = -2.0
private let buttonInset: CGSize = .init(width: 1.0, height: 1.0)
private var buttons: [UIButton] = []
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
var r: CGRect = .init(x: 0.0, y: 0.0, width: colWidth, height: rowHeight)
for _ in 0..<nRows {
for _ in 0..<nCols {
let button = UIButton(type: .system)
button.frame = r.insetBy(dx: buttonInset.width, dy: buttonInset.height)
button.setTitle("Button", for: .normal)
button.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
// Set button border properties
button.layer.borderWidth = 1.0
button.layer.borderColor = UIColor.red.cgColor
buttons.append(button)
addSubview(button)
r.origin.x += colWidth + colSpacing
}
r.origin.x = 0.0
r.origin.y += rowHeight + rowSpacing
}
// Set virtual size based on button layout
let w: CGFloat = buttons.compactMap({ $0.frame.maxX }).max() ?? 0
let h: CGFloat = buttons.compactMap({ $0.frame.maxY }).max() ?? 0
virtualSize = CGSize(width: w, height: h)
}
override func draw(_ rect: CGRect) {
let tr = CGAffineTransform(translationX: -contentOffset.x, y: -contentOffset.y)
.scaledBy(x: zoomScale, y: zoomScale)
buttons.forEach { button in
button.transform = CGAffineTransform.identity
button.transform = tr
}
}
@objc private func buttonTapped(_ sender: UIButton) {
print("Button tapped")
// Handle button tap if needed
}
}


