// the main change to fix your issue... // // on zoom, we change the Width constraint of the Left Container View - based on the zoomScale // so it always matches the Width of the Left Scroll View frame // // made some other changes to try and help... // - changed object / var names for consistency (makes it easier to keep track of things) // - modified a few constraints - some were not needed // - re-organized some of the code for readability // - disabled .bouncesZoom for a better visual import UIKit class PianoViewController: UIViewController, UIScrollViewDelegate { let rightScrollView = UIScrollView() let rightPianoRollView = PianoRollView() let leftScrollView = UIScrollView() let leftPianoRollView = PianoRollViewl() let rightContainerView = UIView() let leftContainerView = UIView() // we will modify the left container width constraint when zooming // so it always fills the left scroll view width // let's define the left container / subviews / scroll view width here let leftContainerWidth: CGFloat = 50 var leftContainerWidthConstraint: NSLayoutConstraint! override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = UIColor.black // buttons will go under the PianoRollView let rightButtonsView = UIView() let leftButtonsView = UIView() // Add the UIScrollView to the view controller's view view.addSubview(rightScrollView) view.addSubview(leftScrollView) // Set the container view as the content view of the scroll view rightScrollView.addSubview(rightContainerView) leftScrollView.addSubview(leftContainerView) // add buttons views amd pianoRoll views to container views rightContainerView.addSubview(rightButtonsView) rightContainerView.addSubview(rightPianoRollView) leftContainerView.addSubview(leftButtonsView) leftContainerView.addSubview(leftPianoRollView) // we will use auto-layout on all views rightScrollView.translatesAutoresizingMaskIntoConstraints = false leftScrollView.translatesAutoresizingMaskIntoConstraints = false leftContainerView.translatesAutoresizingMaskIntoConstraints = false leftButtonsView.translatesAutoresizingMaskIntoConstraints = false leftPianoRollView.translatesAutoresizingMaskIntoConstraints = false rightContainerView.translatesAutoresizingMaskIntoConstraints = false rightButtonsView.translatesAutoresizingMaskIntoConstraints = false rightPianoRollView.translatesAutoresizingMaskIntoConstraints = false // we (almost) always want to respect the safe area let safeG = view.safeAreaLayoutGuide // we want to constrain scrollView subviews to the scrollView's Content Layout Guide let contentG = rightScrollView.contentLayoutGuide let contentgleft = leftScrollView.contentLayoutGuide // left pianoRollView width // auto-layout often complains (generates "breaking constraints" messages to the debug console) // when views get sized dynamically - this will avoid those warning messages // we will activate it below let leftPianoRollWidthConstraint = leftPianoRollView.widthAnchor.constraint(equalToConstant: leftContainerWidth) leftPianoRollWidthConstraint.priority = .required - 1 // we will be modifying this constraint when zooming leftContainerWidthConstraint = leftContainerView.widthAnchor.constraint(equalToConstant: leftContainerWidth) NSLayoutConstraint.activate([ leftScrollView.leadingAnchor.constraint(equalTo: safeG.leadingAnchor), leftScrollView.topAnchor.constraint(equalTo: safeG.topAnchor), leftScrollView.bottomAnchor.constraint(equalTo: safeG.bottomAnchor), leftScrollView.widthAnchor.constraint(equalToConstant: leftContainerWidth), rightScrollView.leadingAnchor.constraint(equalTo: leftScrollView.trailingAnchor), rightScrollView.trailingAnchor.constraint(equalTo: safeG.trailingAnchor), rightScrollView.topAnchor.constraint(equalTo: safeG.topAnchor), rightScrollView.bottomAnchor.constraint(equalTo: safeG.bottomAnchor), leftContainerView.leadingAnchor.constraint(equalTo: contentgleft.leadingAnchor), leftContainerView.topAnchor.constraint(equalTo: contentgleft.topAnchor), leftContainerView.bottomAnchor.constraint(equalTo: contentgleft.bottomAnchor), // leftContainerView does NOT need a trailing anchor // activate leftContainerWidthConstraint leftContainerWidthConstraint, leftPianoRollView.leadingAnchor.constraint(equalTo: leftContainerView.leadingAnchor), leftPianoRollView.trailingAnchor.constraint(equalTo: leftContainerView.trailingAnchor), leftPianoRollView.topAnchor.constraint(equalTo: leftContainerView.topAnchor), leftPianoRollView.bottomAnchor.constraint(equalTo: leftContainerView.bottomAnchor), // activate the leftPianoRollView width constraint we created above leftPianoRollWidthConstraint, leftButtonsView.leadingAnchor.constraint(equalTo: leftContainerView.leadingAnchor), leftButtonsView.trailingAnchor.constraint(equalTo: leftContainerView.trailingAnchor), leftButtonsView.topAnchor.constraint(equalTo: leftContainerView.topAnchor), leftButtonsView.bottomAnchor.constraint(equalTo: leftContainerView.bottomAnchor), // constrain containerView to Content Layout Guide // this will define the "scrollable area" // so we won't be setting .contentSize anywhere rightContainerView.leadingAnchor.constraint(equalTo: contentG.leadingAnchor), rightContainerView.trailingAnchor.constraint(equalTo: contentG.trailingAnchor), rightContainerView.topAnchor.constraint(equalTo: contentG.topAnchor), rightContainerView.bottomAnchor.constraint(equalTo: contentG.bottomAnchor), // constrain all 4 sides of buttonView to containerView rightButtonsView.leadingAnchor.constraint(equalTo: rightContainerView.leadingAnchor), rightButtonsView.trailingAnchor.constraint(equalTo: rightContainerView.trailingAnchor), rightButtonsView.topAnchor.constraint(equalTo: rightContainerView.topAnchor), rightButtonsView.bottomAnchor.constraint(equalTo: rightContainerView.bottomAnchor), // constrain all 4 sides of pianoRollView to containerView rightPianoRollView.leadingAnchor.constraint(equalTo: rightContainerView.leadingAnchor), rightPianoRollView.trailingAnchor.constraint(equalTo: rightContainerView.trailingAnchor), rightPianoRollView.topAnchor.constraint(equalTo: rightContainerView.topAnchor), rightPianoRollView.bottomAnchor.constraint(equalTo: rightContainerView.bottomAnchor), // pianoRollView width rightPianoRollView.widthAnchor.constraint(equalToConstant: 2000.0), ]) // calculate size of notes // let's get close to 2000 // in this example, pianoRollView.numN is 127 let targetHeight: CGFloat = 2000.0 let floatNumN: CGFloat = CGFloat(rightPianoRollView.numN) let noteH1: CGFloat = floor(targetHeight / floatNumN) // == 15.0 let noteH2: CGFloat = ceil(targetHeight / floatNumN) // == 16.0 let totalHeight1: CGFloat = noteH1 * floatNumN // == 1905 let totalHeight2: CGFloat = noteH2 * floatNumN // == 2032 let diff1: CGFloat = abs(targetHeight - totalHeight1) // == 95 let diff2: CGFloat = abs(targetHeight - totalHeight2) // == 32 // if diff1 is less than diff2, use noteH1 else noteH2 let noteHeight: CGFloat = diff1 < diff2 ? noteH1 : noteH2 // noteHeight now equals 16 // Add buttons to the leftButtonsView // we can constrain them vertically to each other var leftpreviousButton: UIButton! for i in 0.. UIView? { return scrollView.subviews.first } // implement the UIScrollViewDelegate method for tracking the zoom scale func scrollViewDidZoom(_ scrollView: UIScrollView) { if scrollView == rightScrollView { // Adjust the content offset so that the content stays centered when zooming let horizontalInset = max(0, (scrollView.bounds.width - scrollView.contentSize.width) ) let verticalInset = max(0, (scrollView.bounds.height - scrollView.contentSize.height) ) scrollView.contentInset = UIEdgeInsets(top: verticalInset, left: horizontalInset, bottom: verticalInset, right: horizontalInset) leftScrollView.zoomScale = rightScrollView.zoomScale } else if scrollView == leftScrollView { rightScrollView.zoomScale = leftScrollView.zoomScale } // set leftContainer Width based on zoomScale so it always // fits the leftScrollView Width leftContainerWidthConstraint.constant = leftContainerWidth / leftScrollView.zoomScale } // implement UIScrollViewDelegate method func scrollViewDidScroll(_ scrollView: UIScrollView) { if scrollView == self.rightScrollView { self.syncScrollView(self.leftScrollView, toScrollView: self.rightScrollView) } else if scrollView == self.leftScrollView { self.syncScrollView(self.rightScrollView, toScrollView: leftScrollView) } } func syncScrollView(_ scrollViewToScroll: UIScrollView, toScrollView scrolledView: UIScrollView) { scrollViewToScroll.contentOffset.y = scrolledView.contentOffset.y } } class PianoRollView: UIView { var noteHeight: CGFloat = 0 var notes: [(note: Int, startTime: Double, duration: Double)] = [] var numN = 22 var numT = 4 override func draw(_ rect: CGRect) { super.draw(rect) // Draw the grid let context = UIGraphicsGetCurrentContext() context?.setStrokeColor(UIColor.black.cgColor) context?.setLineWidth(1.0) for i in 1..