我无法修复这个复杂视图中的滚动视图约束

问题描述 投票:0回答:1

我的代码中存在约束问题。我看到以下类型的错误:

会尝试通过打破约束来恢复

在 UIViewAlertForUnsatisfiableConstraints 处创建一个符号断点以在调试器中捕获它。 中列出的 UIView 的 UIConstraintBasedLayoutDebugging 类别中的方法也可能有帮助。

我认为限制是问题所在。

我遇到的另一个问题是按钮视图中的按钮。它们有一点偏移,有时当 numN 改变时它不适合屏幕。

import UIKit
import Foundation

class PianoRollView: UIView {
    var numN = 127
    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)
        
        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: rect.width, y: y))
        }
        
        let timeSlotWidth = rect.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: rect.height))
        }
        
        // Add line to right of grid
       context?.move(to: CGPoint(x: rect.width, y: 0))
      context?.addLine(to: CGPoint(x: rect.width, y: rect.height))
        // Add line to left of grid
        context?.move(to: CGPoint(x: 0, y: 0))
       context?.addLine(to: CGPoint(x: 0, y: rect.height))
        
        
        // Add line to top of grid
        context?.move(to: CGPoint(x: 0, y: 0))
       context?.addLine(to: CGPoint(x: rect.width, y: 0))
        
        // Draw bottom line
        context?.move(to: CGPoint(x: 0, y: rect.height))
       context?.addLine(to: CGPoint(x: rect.width, y: rect.height))
        
        context?.strokePath()
    }
}

class ViewController: UIViewController, UIScrollViewDelegate {
    let scrollView = UIScrollView()
    let pianoRollView = PianoRollView()
    
    // Create a container view to hold both the PianoRollView and the ButtonView
    let containerView = UIView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Add the UIScrollView to the view controller's view
        view.addSubview(scrollView)
        // Set the container view as the content view of the scroll view
        scrollView.addSubview(containerView)
        
        // Disable the autoresizing mask for both the UIScrollView and the PianoRollView
        scrollView.translatesAutoresizingMaskIntoConstraints = false
        pianoRollView.translatesAutoresizingMaskIntoConstraints = false
        
        // Set the constraints for the UIScrollView to fill the view controller's view
        scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
        scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
        scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
        scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
        
        // Initialize the piano roll view
        pianoRollView.frame = CGRect(x: 0, y: 0, width: 2000, height: 2000)
        pianoRollView.layer.zPosition = 1
        
       containerView.frame = pianoRollView.frame
       
        containerView.addSubview(pianoRollView)
       
        
        // Set the constraints for the PianoRollView to fill the container view
        pianoRollView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor).isActive = true
        pianoRollView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor).isActive = true
        pianoRollView.topAnchor.constraint(equalTo: containerView.topAnchor).isActive = true
        pianoRollView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor).isActive = true
        pianoRollView.widthAnchor.constraint(equalTo: containerView.widthAnchor).isActive = true
        
        // Set the constraints for the container view to fill the scroll view
        containerView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
        containerView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
        containerView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
        containerView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
        containerView.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true
        containerView.heightAnchor.constraint(equalTo: scrollView.heightAnchor).isActive = true
        
        // Add a UIView under the PianoRollView
        let buttonView = UIView()
              buttonView.translatesAutoresizingMaskIntoConstraints = false
        buttonView.frame =  pianoRollView.frame
        containerView.addSubview(buttonView)
        
        // Set the constraints for the ButtonView to align with the PianoRollView
        buttonView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor).isActive = true
        buttonView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor).isActive = true
        buttonView.topAnchor.constraint(equalTo: containerView.topAnchor).isActive = true
        buttonView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor).isActive = true
        buttonView.heightAnchor.constraint(equalTo: containerView.heightAnchor).isActive = true
        
// calculate size of notes
        let noteHeight = Int(round(pianoRollView.frame.height / CGFloat(pianoRollView.numN)) )
        
        // Add buttons to the buttonView with the same height as the space between the grids
        for i in 0..<pianoRollView.numN {
            let button = UIButton()
            button.translatesAutoresizingMaskIntoConstraints = false
            buttonView.addSubview(button)
            
            button.leadingAnchor.constraint(equalTo: buttonView.leadingAnchor).isActive = true
            button.trailingAnchor.constraint(equalTo: buttonView.trailingAnchor).isActive = true
            button.topAnchor.constraint(equalTo: buttonView.topAnchor, constant: CGFloat(i) * CGFloat(noteHeight)).isActive = true
            button.bottomAnchor.constraint(equalTo: buttonView.topAnchor, constant: CGFloat(i+1) * CGFloat(noteHeight)).isActive = true

            button.setTitle("Button \(i)", for: .normal)
            button.setTitleColor(.black, for: .normal)
            button.backgroundColor = .red
        }

        // Set the content size of the scroll view to match the size of the piano roll view
        scrollView.contentSize = containerView.bounds.size
        
        // Enable scrolling in both directions
        scrollView.isScrollEnabled = true
        scrollView.showsVerticalScrollIndicator = true
        scrollView.showsHorizontalScrollIndicator = true
        
        // Set the background color of the piano roll view to white
        pianoRollView.backgroundColor = UIColor.clear

     
        // Set the delegate of the scroll view to self
        scrollView.delegate = self
        
        // Set the minimum zoom scale
       let minZoomScale = max(view.bounds.width / pianoRollView.bounds.width, view.bounds.height / pianoRollView.bounds.height)
       scrollView.minimumZoomScale = minZoomScale
       // scrollView.minimumZoomScale = 1
        scrollView.maximumZoomScale = 5
    }
    
    // Return the container view in the viewForZooming method
    func viewForZooming(in scrollView: UIScrollView) -> UIView? {
        return containerView
    }
    func scrollViewDidZoom(_ scrollView: UIScrollView) {
        // Adjust the content offset so that the content stays centered when zooming
        let horizontalInset = max(0, (scrollView.bounds.width - scrollView.contentSize.width) / 2)
        let verticalInset = max(0, (scrollView.bounds.height - scrollView.contentSize.height) / 2)
        scrollView.contentInset = UIEdgeInsets(top: verticalInset, left: horizontalInset, bottom: verticalInset, right: horizontalInset)
    }
}
ios swift uiscrollview constraints
1个回答
1
投票

部分问题是您将显式框架设置与约束混合在一起。

通常在使用

UIScrollView
时,我们要么设置框架,要么
.contentSize
OR 我们使用约束并让自动布局为我们完成所有工作。

将代码组织成“相关”任务会很有帮助。例如:

  • 创建视图和设置属性
  • 将所有视图添加到层次结构
  • 设置约束
  • 做任何额外的任务

对于您的项目,

scrollView
/
pianoRollView
/
containerView
被创建为类属性,因此
viewDidLoad()
中的第一件事将是:

    // buttons will go under the PianoRollView
    let buttonView = UIView()

接下来,将它们添加到视图层次结构中:

    // Add the UIScrollView to the view controller's view
    view.addSubview(scrollView)

    // Set the container view as the content view of the scroll view
    scrollView.addSubview(containerView)

    // add buttonView to containerView
    containerView.addSubview(buttonView)

    // add pianoRollView to containerView
    containerView.addSubview(pianoRollView)

我们想使用自动布局,所以跟着:

    // we will use auto-layout on all views
    scrollView.translatesAutoresizingMaskIntoConstraints = false
    containerView.translatesAutoresizingMaskIntoConstraints = false
    buttonView.translatesAutoresizingMaskIntoConstraints = false
    pianoRollView.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 = scrollView.contentLayoutGuide
    
    NSLayoutConstraint.activate([
        
        // Set the constraints for the UIScrollView to fill the view controller's view (safe area)
        scrollView.leadingAnchor.constraint(equalTo: safeG.leadingAnchor),
        scrollView.trailingAnchor.constraint(equalTo: safeG.trailingAnchor),
        scrollView.topAnchor.constraint(equalTo: safeG.topAnchor),
        scrollView.bottomAnchor.constraint(equalTo: safeG.bottomAnchor),

        // constrain containerView to Content Layout Guide
        //  this will define the "scrollable area"
        //  so we won't be setting .contentSize anywhere
        containerView.leadingAnchor.constraint(equalTo: contentG.leadingAnchor),
        containerView.trailingAnchor.constraint(equalTo: contentG.trailingAnchor),
        containerView.topAnchor.constraint(equalTo: contentG.topAnchor),
        containerView.bottomAnchor.constraint(equalTo: contentG.bottomAnchor),
        
        // constrain all 4 sides of buttonView to containerView
        buttonView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
        buttonView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
        buttonView.topAnchor.constraint(equalTo: containerView.topAnchor),
        buttonView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
        
        // constrain all 4 sides of pianoRollView to containerView
        pianoRollView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
        pianoRollView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
        pianoRollView.topAnchor.constraint(equalTo: containerView.topAnchor),
        pianoRollView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
        
        // pianoRollView width
        pianoRollView.widthAnchor.constraint(equalToConstant: 2000.0),
        
    ])

请注意,我们确实NOT

heightAnchor
上设置了
pianoRollView
。你会明白为什么......

您还向

numN
添加了
buttonView
按钮...在对上述视图设置约束后 更有意义。

您的代码表明您希望 pianoView(以及 buttonView)的高度为 2000。然后您希望用

numN
按钮均匀填充该高度。

在您发布的代码中,您正在像这样计算按钮的高度:

let noteHeight = Int(round(pianoRollView.frame.height / CGFloat(pianoRollView.numN)) )

所以,高度是 2000,numN 是 127:

2000.0 / 127.0 == 15.748031496062993

你正在使用

round()
,这很好,因为不使用部分点是个好主意。

如果我们使用

round()
,我们得到:

round(2000.0 / 127.0) == 16.0

但是,如果我们有 127 个按钮,每个按钮的高度都是 16...

16 * 127 == 2032

所以我们不会看到底部的两个按钮!

我们可以尝试使用

floor()
...我们得到:

floor(2000.0 / 127.0) == 15

但是,如果我们有 127 个按钮,每个按钮的高度都是 15...

15 * 127 == 1905

所以我们在底部有一个95分的“差距”!

您可能想要做的是使用

floor()
ceil()
,然后比较这些值以查看哪个 最接近 2000。

这可以写成更少的行,但为了澄清:

// in this example, pianoRollView.numN is 127
let targetHeight: CGFloat = 2000.0
let floatNumN: CGFloat = CGFloat(pianoRollView.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
现在等于
16
totalHeight
等于
2032
... about 2000.

与其在

buttonView
上设置高度然后循环并设置按钮的框架,不如继续利用自动布局。

我们将创建约束的“垂直链”:

  • first按钮的顶部约束到
    buttonView
  • 的顶部
  • 将每个后续按钮的顶部限制在其上方按钮的底部
  • last按钮的底部约束到
    buttonView
  • 的底部

现在的约束链:

  • 确定buttonView的高度
  • 这也决定了 pianoRollView 的高度
  • 这也决定了 containerView 的高度
  • 定义 .contentSize——“可滚动区域”

您遇到的另一个问题是您对按钮的

noteHeight
使用不同的计算:

let noteHeight = Int(round(pianoRollView.frame.height / CGFloat(pianoRollView.numN)) )
    

对于

pianoRollView
中的水平线:

let noteHeight = rect.height / CGFloat(numN)

所以你的每个按钮都有

16-points
的高度,但是你的线间隔为
15.748031496062993

更好的选择是使

noteHeight
成为pianoRollView
属性......并在您计算
noteHeight
(按钮高度)时设置它
viewDidLoad()
。不需要再计算了。

这是完整的东西,还有一些额外的修改。通读评论,以便您了解发生了什么:

class PianoRollView: UIView {
    
    var noteHeight: CGFloat = 0
    
    var numN = 127
    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))
        }
        
        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 PianoViewController: UIViewController, UIScrollViewDelegate {
    
    let scrollView = UIScrollView()
    let pianoRollView = PianoRollView()
    
    // Create a container view to hold both the PianoRollView and the ButtonView
    let containerView = UIView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = UIColor(white: 0.95, alpha: 1.0)

        // buttons will go under the PianoRollView
        let buttonView = UIView()

        // Add the UIScrollView to the view controller's view
        view.addSubview(scrollView)

        // Set the container view as the content view of the scroll view
        scrollView.addSubview(containerView)

        // add buttonView to containerView
        containerView.addSubview(buttonView)

        // add pianoRollView to containerView
        containerView.addSubview(pianoRollView)

        // we will use auto-layout on all views
        scrollView.translatesAutoresizingMaskIntoConstraints = false
        containerView.translatesAutoresizingMaskIntoConstraints = false
        buttonView.translatesAutoresizingMaskIntoConstraints = false
        pianoRollView.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 = scrollView.contentLayoutGuide
        
        NSLayoutConstraint.activate([
            
            // Set the constraints for the UIScrollView to fill the view controller's view (safe area)
            scrollView.leadingAnchor.constraint(equalTo: safeG.leadingAnchor),
            scrollView.trailingAnchor.constraint(equalTo: safeG.trailingAnchor),
            scrollView.topAnchor.constraint(equalTo: safeG.topAnchor),
            scrollView.bottomAnchor.constraint(equalTo: safeG.bottomAnchor),

            // constrain containerView to Content Layout Guide
            //  this will define the "scrollable area"
            //  so we won't be setting .contentSize anywhere
            containerView.leadingAnchor.constraint(equalTo: contentG.leadingAnchor),
            containerView.trailingAnchor.constraint(equalTo: contentG.trailingAnchor),
            containerView.topAnchor.constraint(equalTo: contentG.topAnchor),
            containerView.bottomAnchor.constraint(equalTo: contentG.bottomAnchor),
            
            // constrain all 4 sides of buttonView to containerView
            buttonView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
            buttonView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
            buttonView.topAnchor.constraint(equalTo: containerView.topAnchor),
            buttonView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
            
            // constrain all 4 sides of pianoRollView to containerView
            pianoRollView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
            pianoRollView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
            pianoRollView.topAnchor.constraint(equalTo: containerView.topAnchor),
            pianoRollView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
            
            // pianoRollView width
            pianoRollView.widthAnchor.constraint(equalToConstant: 2000.0),
            // we don't set height, because that will be controlled by the
            //  number of buttons (pianoRollView.numN) sized to get
            //  as close to 2000 as possible
            //pianoRollView.heightAnchor.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(pianoRollView.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 buttonView
        //  we can constrain them vertically to each other
        var previousButton: UIButton!
        for i in 0..<pianoRollView.numN {
            let button = UIButton()
            button.translatesAutoresizingMaskIntoConstraints = false
            buttonView.addSubview(button)
            
            button.leadingAnchor.constraint(equalTo: buttonView.leadingAnchor).isActive = true
            button.trailingAnchor.constraint(equalTo: buttonView.trailingAnchor).isActive = true
            button.heightAnchor.constraint(equalToConstant: noteHeight).isActive = true
            
            if previousButton == nil {
                // constrain FIRST button to Top of buttonView
                button.topAnchor.constraint(equalTo: buttonView.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: buttonView.bottomAnchor).isActive = true
        
        // so, our buttons are constrained with a "vertical chain" inside buttonView
        //  which determines the height of buttonView
        //  which also determines the height of pianoRollView
        //  which also determines the height of containerView
        //  which defines the .contentSize -- the "scrollable area"
        
        // set the noteHeight in pianoRollView so it will match
        //  pianoRollView no longer needs to re-caluclate the "line spacing"
        pianoRollView.noteHeight = noteHeight
        
        // these all default to True, so not needed
        // Enable scrolling in both directions
        //scrollView.isScrollEnabled = true
        //scrollView.showsVerticalScrollIndicator = true
        //scrollView.showsHorizontalScrollIndicator = true
        
        // Set the background color of the piano roll view to clear
        pianoRollView.backgroundColor = UIColor.clear
        
        // Set the delegate of the scroll view to self
        scrollView.delegate = self
        
        // Set the minimum zoom scale
        // can't do this here... views have not been laid-out yet
        //  so do it in viewDidAppear
        //let minZoomScale = max(view.bounds.width / pianoRollView.bounds.width, view.bounds.height / pianoRollView.bounds.height)
        //scrollView.minimumZoomScale = minZoomScale
        
        scrollView.maximumZoomScale = 5
        
        // during development, so we can see the scrollView framing
        scrollView.backgroundColor = .systemBlue
        
    }
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        // views have been laid-out, so now we can use their frames/bounds to calculate minZoomScale
        let minZoomScale = min(scrollView.frame.width / pianoRollView.bounds.width, scrollView.frame.height / pianoRollView.bounds.height)
        scrollView.minimumZoomScale = minZoomScale
    }
    
    // Return the container view in the viewForZooming method
    func viewForZooming(in scrollView: UIScrollView) -> UIView? {
        return containerView
    }
    func scrollViewDidZoom(_ scrollView: UIScrollView) {
        // Adjust the content offset so that the content stays centered when zooming
        let horizontalInset = max(0, (scrollView.frame.width - scrollView.contentSize.width) / 2)
        let verticalInset = max(0, (scrollView.frame.height - scrollView.contentSize.height) / 2)
        scrollView.contentInset = UIEdgeInsets(top: verticalInset, left: horizontalInset, bottom: verticalInset, right: horizontalInset)
    }
}

如果有任何不清楚的地方——或者即使你现在已经完全理解了——你会通过学习一堆自动布局/

UIScrollView
教程来真正受益...


编辑

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    // views have been laid-out, so now we can use their frames/bounds to calculate minZoomScale

    // if we want min zoom to fit
    //  FULL pianoRollView
    let minZoomScale = min(scrollView.frame.width / pianoRollView.bounds.width, scrollView.frame.height / pianoRollView.bounds.height)
    
    // if we want min zoom to fit
    //  pianoRollView HEIGHT
    //let minZoomScale = scrollView.frame.height / pianoRollView.bounds.height
    
    // if we want min zoom to fit
    //  one "column" of pianoRollView
    //let minZoomScale = scrollView.frame.width / (pianoRollView.bounds.width / CGFloat(pianoRollView.numT))
    
    scrollView.minimumZoomScale = minZoomScale
}
© www.soinside.com 2019 - 2024. All rights reserved.