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