Home > OS >  NSPanGestureRecognizer Autolayout Constriants
NSPanGestureRecognizer Autolayout Constriants

Time:02-25

I am using below code to move a view inside boundaries of superview, it works fine but I want to use auto layout instead. Below are the initial constraints moveable view should have and when I move it using gesture I want to update trailingConstraints & bottomConstraints.

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

Is is possible to use NSPanGestureRecognizer using auto layout?

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)
            }
        }
    }
}

CodePudding user response:

Fairly straightforward to do this with auto-layout / constraints...

We'll add var properties for Leading and Top constraints:

var leadingC: NSLayoutConstraint!
var topC: NSLayoutConstraint!

Inside our Pan Gesture handler, we'll update those constraint constants:

    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

As an added benefit, we can avoid all of the if/else checking by giving the movable view Required constraint limits and giving the "adjustable" Leading and Top constraints less-than-Required priorities.

Here's a complete example implementation (assumes you have @IBOutlet var panView already setup):

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.
        }
    }

}
  • Related