Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Update constraints with UIPanGestureRecognizer

I have a black separatorView in the middle of the screen separating a topContainerView (orange) and a bottomContainerView (green). The separatorView can be dragged up and down with a panGesture, but I can't get the top and bottom views to update their constraints and resize. The bottom of the orange view and the top of the green view should always stick with the separatorView.

Here's the code I have (UPDATED to include variable declarations):

let separatorView: UIView = {
    let view = UIView()
    view.backgroundColor = UIColor.black
    view.translatesAutoresizingMaskIntoConstraints = false
    return view
}()

let topContainerView : UIView = {
    let view = UIView()
    view.backgroundColor = UIColor.orange
    view.translatesAutoresizingMaskIntoConstraints = false
    return view
}()

let bottomContainerView : UIView = {
    let view = UIView()
    view.backgroundColor = UIColor.green
    view.translatesAutoresizingMaskIntoConstraints = false
    return view
}()

override func viewDidLoad() {
        super.viewDidLoad()
        self.addViews()
        let panGesture = UIPanGestureRecognizer(target: self, action: #selector(detectPan(recognizer:)))
        panGesture.delaysTouchesBegan = false
        panGesture.delaysTouchesEnded = false
        separatorView.addGestureRecognizer(panGesture)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    func addViews() {
        
        view.addSubview(topContainerView)
        view.addSubview(bottomContainerView)
        view.addSubview(separatorView)
        
        separatorView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
        separatorView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
        separatorView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
        separatorView.heightAnchor.constraint(equalToConstant: 50).isActive = true
        
        topContainerView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
        topContainerView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
        topContainerView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
        topContainerView.bottomAnchor.constraint(equalTo: separatorView.topAnchor).isActive = true
        
        bottomContainerView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
        bottomContainerView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
        bottomContainerView.topAnchor.constraint(equalTo: separatorView.bottomAnchor).isActive = true
        bottomContainerView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
    }
    
    func detectPan(recognizer: UIPanGestureRecognizer) {
        let translation = recognizer.translation(in: self.view)
        separatorView.center = CGPoint(x: view.center.x, y: lastLocation.y + translation.y)
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        self.view.bringSubview(toFront: separatorView)
        lastLocation = separatorView.center
    }

enter image description here

enter image description here

like image 255
kerbelda Avatar asked Dec 03 '22 13:12

kerbelda


1 Answers

When you are using Autolayout you can't simply modify the frame of elements (which is what you are doing implicitly by changing the center property). Well, you can, but it won't affect any other elements (as you have found) and as soon as Autolayout is triggered your changes to the frame will be reset.

You need to manipulate the constraints so that Autolayout produces the result you want.

In this case you need to modify the constant property of your constraint that ties the separator to the center of the view. You can use the translation value of the pan gesture recognizer to do this. The only tricky bit is that this translation is relative to 0 at the start of the pan, so you need to incorporate any offset from any previous pan.

let separatorView: UIView = {
    let view = UIView()
    view.backgroundColor = UIColor.black
    view.translatesAutoresizingMaskIntoConstraints = false
    return view
}()

let topContainerView : UIView = {
    let view = UIView()
    view.backgroundColor = UIColor.orange
    view.translatesAutoresizingMaskIntoConstraints = false
    return view
}()

let bottomContainerView : UIView = {
    let view = UIView()
    view.backgroundColor = UIColor.green
    view.translatesAutoresizingMaskIntoConstraints = false
    return view
}()

var centerConstraint: NSLayoutConstraint!

var startingConstant: CGFloat  = 0.0

override func viewDidLoad() {
    super.viewDidLoad()
    self.addViews()
    let panGesture = UIPanGestureRecognizer(target: self, action: #selector(detectPan(recognizer:)))
    panGesture.delaysTouchesBegan = false
    panGesture.delaysTouchesEnded = false
    separatorView.addGestureRecognizer(panGesture)
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}

func addViews() {

    view.addSubview(topContainerView)
    view.addSubview(bottomContainerView)
    view.addSubview(separatorView)

    separatorView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
    separatorView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true

    self.centerConstraint = separatorView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
    self.centerConstraint.isActive = true

    separatorView.heightAnchor.constraint(equalToConstant: 50).isActive = true

    topContainerView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
    topContainerView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
    topContainerView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
    topContainerView.bottomAnchor.constraint(equalTo: separatorView.topAnchor).isActive = true

    bottomContainerView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
    bottomContainerView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
    bottomContainerView.topAnchor.constraint(equalTo: separatorView.bottomAnchor).isActive = true
    bottomContainerView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}

func detectPan(recognizer: UIPanGestureRecognizer) {

    switch recognizer.state {
    case .began:
        self.startingConstant = self.centerConstraint.constant
    case .changed:
        let translation = recognizer.translation(in: self.view)
        self.centerConstraint.constant = self.startingConstant + translation.y
    default:
        break
    }
}
like image 180
Paulw11 Avatar answered Dec 17 '22 15:12

Paulw11