Search code examples
swiftconstraintsgesture

Swift: Enable or disable constraint bug


I'm creating a gesture on a view that I called separatorView (in the photo it's the middle black view).

I use the constraint method and the priorities to do it:

-centerConstraint = This is the constraint that allows to center the view horizontally in X, priority = 1000

-rightConstant = This constraint links the view to the edge of the right screen at zero pt, priority = 999

-GaucheConstant = This constraint links the view to the edge of the left screen at zero pt, priority = 998

When I drag this view to the left, the animation continues to drag.

The problem is that after the animation is finished, and I want to resume the view and drag it to the left the cash application centerConstraint is nil!

I can not find the solution to make it all generic and I can slide the view to the right or left without any problem

I know the problem comes from the fact that I disable the constraint

The photo

here my code:

@IBOutlet weak var sep: UIView!

@IBOutlet weak var constantGauche: NSLayoutConstraint!

@IBOutlet weak var constantDroite: NSLayoutConstraint!
@IBOutlet weak var centerConstraint: NSLayoutConstraint!

var startingConstant: CGFloat  = 0.0

var panGesture: UIPanGestureRecognizer?

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

@objc func detectPan(recognizer: UIPanGestureRecognizer) {

    let translation = recognizer.translation(in: self.view)

    switch recognizer.state {
    case .began:
        self.startingConstant = self.centerConstraint.constant
    case .changed:
        self.centerConstraint.constant = self.startingConstant + translation.x
    case .ended:
        if translation.x > 0 {
            centerConstraint.isActive = false
            constantGauche.isActive = false
            constantDroite.isActive = true
            UIView.animate(withDuration: 0.5) {
                self.view.layoutIfNeeded()
            }
        }else{
            centerConstraint.isActive = false
            constantGauche.isActive = true
            constantDroite.isActive = false
            UIView.animate(withDuration: 0.5) {
                self.view.layoutIfNeeded()
            }
        }
    default:
        break
    }
}

Solution

  • Yes, when you disable the center constraint, iOS removes it from the view's constraints and frees it because there is no longer a strong reference to it. The @IBOutlet is a weak reference which gets set to nil as soon as the strong references are all gone. So the next time you access the center constraint, it crashes because it is nil and centerConstraint is implicitly unwrapped with !.

    Instead of disabling constraints, modify their priorities:

    • Change the center constraint priority to 999 as a starting value. You'll get an error if you try to modify a required constraint's priority in code.
    • Lowering the priority of the left and center constraints will cause sep to move to the right. Once it gets there, you can calculate the new constant for the center constraint and then set their priorities back to the starting values.
    • Lowering the priority of the right and center constraints will cause sep to move to the left. Once it gets there, you can calculate the new constant for the center constraint and then set their priorities back to the starting values.

    @objc func detectPan(recognizer: UIPanGestureRecognizer) {
    
        let translation = recognizer.translation(in: self.view)
    
        switch recognizer.state {
        case .began:
            self.startingConstant = self.centerConstraint.constant
        case .changed:
            self.centerConstraint.constant = self.startingConstant + translation.x
        case .ended:
            if translation.x > 0 {
                centerConstraint.priority = UILayoutPriority(rawValue: 750)
                constantGauche.priority = UILayoutPriority(rawValue: 750)
            } else {
                centerConstraint.priority = UILayoutPriority(rawValue: 750)
                constantDroite.priority = UILayoutPriority(rawValue: 750)
            }
            UIView.animate(withDuration: 0.5) {
                self.view.layoutIfNeeded()
            }
            print(sep.center.x - view.center.x)
            centerConstraint.constant = sep.center.x - view.center.x
            centerConstraint.priority = UILayoutPriority(rawValue: 999)
            constantGauche.priority = UILayoutPriority(rawValue: 998)
            constantDroite.priority = UILayoutPriority(rawValue: 998)
        default:
            break
        }
    }
    

    Here are the changes running in the simulator:

    Image of separator in the simulator