PanGestureRecognizer 自动布局约束

NSPanGestureRecognizer Autolayout Constriants

我正在使用下面的代码在超级视图的边界内移动一个视图,它工作正常但我想改用自动布局。以下是可移动视图应具有的初始约束,当我使用手势移动它时,我想更新 trailingConstraints 和 bottomConstraints。

width - super view width / 4 height = 9/16 of width trailingConstraints = 0 bottomConstraints = 0

是否可以通过自动布局使用 NSPanGestureRecognizer?

import Cocoa

class ViewController: NSViewController {
    
    var movableView = NSView()
    // MARK: - IBOutlet
    @IBOutlet weak var panView: NSView!
    
    // MARK: - Life cycle
    override func viewDidLoad() {
        super.viewDidLoad()
        panView.wantsLayer = true
        panView.layer?.backgroundColor = NSColor.white.cgColor
        movableView.setFrameSize(NSSize(width: 100, height: 100))
        movableView.setFrameOrigin(NSPoint(x: self.panView.frame.size.width - movableView.frame.size.width, y:  0))
        movableView.wantsLayer = true
        if let myLayer1 = movableView.layer {
            myLayer1.backgroundColor = NSColor.red.cgColor
        }
        panView.addSubview(movableView)

        let panRecognizer1 = NSPanGestureRecognizer.init(target: self, action: #selector(panPictureView(_:)))
        movableView.addGestureRecognizer(panRecognizer1)
    }
    
    // MARK: - Pan gesture
    @objc func panPictureView(_ sender: NSPanGestureRecognizer) {
        
        if let movingObject = sender.view {
            let translation = sender.translation(in: self.panView)
            sender.setTranslation(CGPoint.zero, in: self.panView)
            
            let newX = movingObject.frame.origin.x + translation.x
            let newY = movingObject.frame.origin.y + translation.y
            
            let maxX = panView.frame.width - movingObject.frame.width
            let maxY = panView.frame.height - movingObject.frame.height
            
            if newX <= 0 && newY <= 0 {
                let newPosition = CGPoint(x: 0, y: 0)
                movingObject.setFrameOrigin(newPosition)
            } else if newX <= 0  {
                let newPosition = CGPoint(x: 0, y: newY < maxY ? newY : maxY)
                movingObject.setFrameOrigin(newPosition)
            } else if newY <= 0 {
                let newPosition = CGPoint(x: newX < maxX ? newX : maxX, y: 0)
                movingObject.setFrameOrigin(newPosition)
            } else if newX >= maxX && newY >= maxY {
                let newPosition = CGPoint(x: maxX, y: maxY)
                movingObject.setFrameOrigin(newPosition)
            } else if newX >= maxX  {
                let newPosition = CGPoint(x: maxX, y: newY)
                movingObject.setFrameOrigin(newPosition)
            } else if newY >= maxY  {
                let newPosition = CGPoint(x: newX, y: maxY)
                movingObject.setFrameOrigin(newPosition)
            } else {
                let newPosition = CGPoint(x: newX, y: newY)
                movingObject.setFrameOrigin(newPosition)
            }
        }
    }
}

使用 auto-layout / 约束条件很简单...

我们将为 Leading 和 Top 约束添加 var 属性:

var leadingC: NSLayoutConstraint!
var topC: NSLayoutConstraint!

在我们的平移手势处理程序中,我们将更新这些约束常量:

    let translation = sender.translation(in: self.panView)
    sender.setTranslation(CGPoint.zero, in: self.panView)
    
    // update movable view Leading and Top constraints
    leadingC.constant += translation.x
    topC.constant -= translation.y

作为一个额外的好处,我们可以通过给可移动视图 Required 约束限制并给出“可调整的”Leading 和 Top 约束来避免所有 if/else 检查less-than-Required 个优先级。

这是一个完整的示例实现(假设您已经 @IBOutlet var panView 设置):

import Cocoa

class ViewController: NSViewController {

    @IBOutlet var panView: NSView!
    
    var movableView = NSView()

    var leadingC: NSLayoutConstraint!
    var topC: NSLayoutConstraint!
    
    override func viewDidLoad() {
        super.viewDidLoad()

        super.viewDidLoad()
        panView.wantsLayer = true
        panView.layer?.backgroundColor = NSColor.blue.cgColor

        // we don't need these, since we'll be using constraints
        //movableView.setFrameSize(NSSize(width: 100, height: 100))
        //movableView.setFrameOrigin(NSPoint(x: self.panView.frame.size.width - movableView.frame.size.width, y:  0))
        
        movableView.wantsLayer = true
        if let myLayer1 = movableView.layer {
            myLayer1.backgroundColor = NSColor.red.cgColor
        }
        panView.addSubview(movableView)
        
        // let's use constraints
        movableView.translatesAutoresizingMaskIntoConstraints = false
        
        // initialize the constraints we'll use to
        //  move the subview
        leadingC = movableView.leadingAnchor.constraint(equalTo: panView.leadingAnchor)
        topC = movableView.topAnchor.constraint(equalTo: panView.topAnchor)

        // give them less-than-required priority
        //  we'll use stronger constraints to prevent dragging
        //  outside the frame of panView
        leadingC.priority = .required - 1
        topC.priority = .required - 1

        NSLayoutConstraint.activate([
            // width and height
            movableView.widthAnchor.constraint(equalToConstant: 100.0),
            movableView.heightAnchor.constraint(equalTo: movableView.widthAnchor),
            
            // these will prevent dragging outside the panView frame
            movableView.topAnchor.constraint(greaterThanOrEqualTo: panView.topAnchor),
            movableView.leadingAnchor.constraint(greaterThanOrEqualTo: panView.leadingAnchor),
            movableView.trailingAnchor.constraint(lessThanOrEqualTo: panView.trailingAnchor),
            movableView.bottomAnchor.constraint(lessThanOrEqualTo: panView.bottomAnchor),
        ])
        
        let panRecognizer1 = NSPanGestureRecognizer.init(target: self, action: #selector(panPictureView(_:)))
        movableView.addGestureRecognizer(panRecognizer1)
    }
    
    override func viewDidLayout() {
        super.viewDidLayout()

        // start movable view at bottom-right corner
        //  need to wait until auto-layout has setup the frames
        // and, to make sure this only happens once,
        //  only execute if our "movable" constraints are not yet activated
        if movableView.frame.size.width != 0 && !leadingC.isActive {
            leadingC.constant = self.panView.frame.size.width - movableView.frame.size.width
            topC.constant = self.panView.frame.size.height - movableView.frame.size.height
            leadingC.isActive = true
            topC.isActive = true
        }
        
    }
    
    // MARK: - Pan gesture
    @objc func panPictureView(_ sender: NSPanGestureRecognizer) {
    
        let translation = sender.translation(in: self.panView)
        sender.setTranslation(CGPoint.zero, in: self.panView)
        
        // update movable view Leading and Top constraints
        leadingC.constant += translation.x
        topC.constant -= translation.y
        
    }
    
    override var representedObject: Any? {
        didSet {
        // Update the view, if already loaded.
        }
    }

}