Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // 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..<rightPianoRollView.numN {
- let button = UIButton()
- button.translatesAutoresizingMaskIntoConstraints = false
- leftButtonsView.addSubview(button)
- button.leadingAnchor.constraint(equalTo: leftButtonsView.leadingAnchor).isActive = true
- button.trailingAnchor.constraint(equalTo: leftButtonsView.trailingAnchor).isActive = true
- button.heightAnchor.constraint(equalToConstant: noteHeight).isActive = true
- if leftpreviousButton == nil {
- // constrain FIRST button to Top of buttonView
- button.topAnchor.constraint(equalTo: leftButtonsView.topAnchor).isActive = true
- } else {
- // constrain other buttons to Bottom of Previous Button
- button.topAnchor.constraint(equalTo: leftpreviousButton.bottomAnchor).isActive = true
- }
- // update previousButton to the current button
- leftpreviousButton = button
- button.setTitle("B\(i)", for: .normal)
- button.setTitleColor(.black, for: .normal)
- button.backgroundColor = .green
- }
- // constrain bottom of LAST button to bottom of buttonsView
- leftpreviousButton.bottomAnchor.constraint(equalTo: leftButtonsView.bottomAnchor).isActive = true
- // Add buttons to the buttonView
- // we can constrain them vertically to each other
- var previousButton: UIButton!
- for i in 0..<rightPianoRollView.numN {
- let button = UIButton()
- button.translatesAutoresizingMaskIntoConstraints = false
- rightButtonsView.addSubview(button)
- button.leadingAnchor.constraint(equalTo: rightButtonsView.leadingAnchor).isActive = true
- button.trailingAnchor.constraint(equalTo: rightButtonsView.trailingAnchor).isActive = true
- button.heightAnchor.constraint(equalToConstant: noteHeight).isActive = true
- if previousButton == nil {
- // constrain FIRST button to Top of buttonView
- button.topAnchor.constraint(equalTo: rightButtonsView.topAnchor).isActive = true
- } else {
- // constrain other buttons to Bottom of Previous Button
- button.topAnchor.constraint(equalTo: previousButton.bottomAnchor).isActive = true
- }
- // update previousButton to the current button
- previousButton = button
- button.setTitle("Button \(i)", for: .normal)
- button.setTitleColor(.black, for: .normal)
- button.backgroundColor = .red
- }
- // constrain bottom of LAST button to bottom of buttonsView
- previousButton.bottomAnchor.constraint(equalTo: rightButtonsView.bottomAnchor).isActive = true
- rightPianoRollView.noteHeight = noteHeight
- leftPianoRollView.noteHeight = noteHeight
- // make sure left PianoRoll has the same .numN
- leftPianoRollView.numN = rightPianoRollView.numN
- // Set the background color of the piano roll view to clear
- leftPianoRollView.backgroundColor = UIColor.clear
- rightPianoRollView.backgroundColor = UIColor.clear
- leftButtonsView.backgroundColor = UIColor.purple
- // Set the delegate of the scroll view to self
- rightScrollView.delegate = self
- leftScrollView.delegate = self
- rightScrollView.maximumZoomScale = 5
- leftScrollView.maximumZoomScale = 5
- leftScrollView.showsHorizontalScrollIndicator = false
- // probably also want to not-show the vertical indicator
- // because it just looks odd
- leftScrollView.showsVerticalScrollIndicator = false
- // for better visual result, disable zoom bouncing
- rightScrollView.bouncesZoom = false
- leftScrollView.bouncesZoom = false
- // during development, so we can see the scrollView framing
- rightScrollView.backgroundColor = UIColor.blue
- leftScrollView.backgroundColor = UIColor.orange
- leftContainerView.backgroundColor = UIColor.yellow
- }
- override func viewDidAppear(_ animated: Bool) {
- super.viewDidAppear(animated)
- let minZoomScale = rightScrollView.frame.height / rightPianoRollView.bounds.height
- rightScrollView.minimumZoomScale = minZoomScale
- leftScrollView.minimumZoomScale = minZoomScale
- }
- // Return the container view in the viewForZooming method
- func viewForZooming(in scrollView: UIScrollView) -> 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..<numN {
- let y = CGFloat(i) * noteHeight
- context?.move(to: CGPoint(x: 0, y: y))
- context?.addLine(to: CGPoint(x: bounds.width, y: y))
- }
- let timeSlotWidth = bounds.width / CGFloat(numT)
- for i in 1..<numT {
- let x = CGFloat(i) * timeSlotWidth
- context?.move(to: CGPoint(x: x, y: 0))
- context?.addLine(to: CGPoint(x: x, y: bounds.height))
- }
- // Add line to right of grid
- context?.move(to: CGPoint(x: bounds.width, y: 0))
- context?.addLine(to: CGPoint(x: bounds.width, y: bounds.height))
- // Add line to left of grid
- context?.move(to: CGPoint(x: 0, y: 0))
- context?.addLine(to: CGPoint(x: 0, y: bounds.height))
- // Add line to top of grid
- context?.move(to: CGPoint(x: 0, y: 0))
- context?.addLine(to: CGPoint(x: bounds.width, y: 0))
- // Draw bottom line
- context?.move(to: CGPoint(x: 0, y: bounds.height))
- context?.addLine(to: CGPoint(x: bounds.width, y: bounds.height))
- context?.strokePath()
- }
- }
- class PianoRollViewl: UIView {
- var noteHeight: CGFloat = 0
- 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)
- // UIKit *may* tell us to draw only PART of the view
- // (the rect), so use bounds instead of rect
- // noteHeight will be set by the controller
- // when it calculates the button heights
- // let noteHeight = rect.height / CGFloat(numN)
- for i in 1..<numN {
- let y = CGFloat(i) * noteHeight
- context?.move(to: CGPoint(x: 0, y: y))
- context?.addLine(to: CGPoint(x: bounds.width, y: y))
- }
- context?.strokePath()
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement