Search code examples
swiftconstraintsnslayoutconstraintuipangesturerecognizerviewdidlayoutsubviews

De activate constraints between 2 objects linking them together


My swift code as you can see in the gif below. Lets the user select one of the image views then use the slider to increase or decrease the size of that image view. The problem is when the image view is moved the other image view follows it that should not happen. So the constraints should be set when the code runs for the first time but after one of the imageviews is selected. The constraints linking them together should become deactivated. A line causing this is

** greenMove.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant :0),**

enter image description here

import UIKit

class ViewController: UIViewController {



    var image1Width: NSLayoutConstraint!
    var image1Height: NSLayoutConstraint!
    var image1Width2: NSLayoutConstraint!
    var image1Height2: NSLayoutConstraint!
    var greenMove = UIImageView()
    var slider = UISlider()
    var blueMove = UIImageView()
    var existingTransition : CGAffineTransform?
    var clock = Int()
    var currentView: UIView?
    var g2 = UIPanGestureRecognizer()
    var g3 = UIPanGestureRecognizer()

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        greenMove.isUserInteractionEnabled = true
        blueMove.isUserInteractionEnabled = true

        g2 = UIPanGestureRecognizer(target: self, action: #selector(ViewController.g1Method))
        greenMove.addGestureRecognizer(g2)
        g3 = UIPanGestureRecognizer(target: self, action: #selector(ViewController.g2Method))
        blueMove.addGestureRecognizer(g3)

        greenMove.backgroundColor = .systemGreen

        blueMove.backgroundColor = .blue


        [greenMove,slider,blueMove].forEach {

            view.addSubview($0)
            $0.translatesAutoresizingMaskIntoConstraints = false

        }


        //image11
        image1Width =  greenMove.widthAnchor.constraint(equalTo:  view.widthAnchor ,multiplier:  0.2)
        image1Height =  greenMove.heightAnchor.constraint(equalTo:  view.heightAnchor ,multiplier:  0.20)

        //image12
        image1Width2 =  blueMove.widthAnchor.constraint(equalTo:  view.widthAnchor ,multiplier:  0.2)
        image1Height2 =  blueMove.heightAnchor.constraint(equalTo:  view.heightAnchor ,multiplier:  0.20)



        NSLayoutConstraint.activate([

            greenMove.topAnchor.constraint(equalTo: view.topAnchor, constant : 0),
            image1Width,
            image1Height,
            greenMove.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant :0),


            blueMove.topAnchor.constraint(equalTo: view.topAnchor, constant : 0),
            image1Width2,
            image1Height2,
            blueMove.leadingAnchor.constraint(equalTo: greenMove.trailingAnchor, constant :0)

        ])








        slider.addTarget(self, action: #selector(hhh), for: .allEvents)

    }

    override func viewDidLayoutSubviews() {
        NSLayoutConstraint.activate ([




            slider.topAnchor.constraint(equalTo: view.topAnchor, constant : greenMove.bounds.height),
            slider.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.20, constant: 0),
            slider.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.20, constant: 0),
            slider.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant : 0),


        ])
    }
    @objc func handleTapGestured(_ gesture: UIPanGestureRecognizer) {
        currentView = gesture.view
    }

    @objc func g1Method(_ sender: UIPanGestureRecognizer){
        clock = 1
        let subview = greenMove
        guard let child = sender.view else{return}
        let transitionPoint = sender.translation(in: self.view)
        let newTransition = CGAffineTransform(translationX: transitionPoint.x, y: transitionPoint.y)
        switch sender.state {

        case .ended,.cancelled:// on End
            if let existing = existingTransition{
                self.existingTransition = newTransition.concatenating(existing)
            }else{
                self.existingTransition = newTransition
            }
        default://on change and other states
            if let existing = existingTransition{
                child.transform = newTransition
                    .concatenating(existing)
            }else{
                child.transform = newTransition
            }
        }
        self.view.layoutIfNeeded()


        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTapGestured(_:)))
        subview.addGestureRecognizer(tapGesture)


    }
    @objc func g2Method(_ sender: UIPanGestureRecognizer){
        clock = 2
        let subview = blueMove
        guard let child = sender.view else{return}
        let transitionPoint = sender.translation(in: self.view)
        let newTransition = CGAffineTransform(translationX: transitionPoint.x, y: transitionPoint.y)
        switch sender.state {

        case .ended,.cancelled:// on End
            if let existing = existingTransition{
                self.existingTransition = newTransition.concatenating(existing)
            }else{
                self.existingTransition = newTransition
            }
        default://on change and other states
            if let existing = existingTransition{
                child.transform = newTransition
                    .concatenating(existing)
            }else{
                child.transform = newTransition
            }
        }
        self.view.layoutIfNeeded()


        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTapGestured(_:)))
        subview.addGestureRecognizer(tapGesture)


    }
    @objc func hhh() {

        if clock ==  1 {
            image1Width.constant = CGFloat(slider.value) * view.frame.size.width * 0.25
            image1Height.constant = CGFloat(slider.value) * view.frame.size.height * 0.25
        }
        if clock  == 2 {

            image1Width2.constant = CGFloat(slider.value) * view.frame.size.width * 0.25
            image1Height2.constant = CGFloat(slider.value) * view.frame.size.height * 0.25
        }




    }

}

Solution

  • The problem you are running into is because you are using a single transform for both views and concatenating new transform information that gets applied to the other view when you try to move it.

    Make these changes to your posted code...

    At the class level, create separate transform "trackers":

    // don't use this
    //var existingTransition : CGAffineTransform?
    
    // separate transforms for each "move view"
    var blueTransition: CGAffineTransform?
    var greenTransition: CGAffineTransform?
    

    Then, in g1Method and g2Method use the associated transform:

    @objc func g1Method(_ sender: UIPanGestureRecognizer){
        clock = 1
        let subview = greenMove
        guard let child = sender.view else{return}
        let transitionPoint = sender.translation(in: self.view)
        let newTransition = CGAffineTransform(translationX: transitionPoint.x, y: transitionPoint.y)
    
        // greenMove view must track its own CGAffineTransform
    
        switch sender.state {
    
        case .ended,.cancelled:// on End
            if let existing = greenTransition {
                greenTransition = newTransition.concatenating(existing)
            } else {
                greenTransition = newTransition
            }
            //if let existing = existingTransition{
            //  self.existingTransition = newTransition.concatenating(existing)
            //}else{
            //  self.existingTransition = newTransition
            //}
        default://on change and other states
            if let existing = greenTransition {
                child.transform = newTransition
                    .concatenating(existing)
            } else {
                child.transform = newTransition
            }
            //if let existing = existingTransition{
            //  child.transform = newTransition
            //      .concatenating(existing)
            //}else{
            //  child.transform = newTransition
            //}
        }
        self.view.layoutIfNeeded()
    
        // move this to viewDidLoad(), otherwise you are adding ANOTHER recognizer each time this method is called
        //let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTapGestured(_:)))
        //subview.addGestureRecognizer(tapGesture)
    
    
    }
    @objc func g2Method(_ sender: UIPanGestureRecognizer){
        clock = 2
        let subview = blueMove
        guard let child = sender.view else{return}
        let transitionPoint = sender.translation(in: self.view)
        let newTransition = CGAffineTransform(translationX: transitionPoint.x, y: transitionPoint.y)
    
        // blueMove view must track its own CGAffineTransform
    
        switch sender.state {
    
        case .ended,.cancelled:// on End
            if let existing = blueTransition {
                blueTransition = newTransition.concatenating(existing)
            } else {
                blueTransition = newTransition
            }
            //if let existing = existingTransition{
            //  self.existingTransition = newTransition.concatenating(existing)
            //}else{
            //  self.existingTransition = newTransition
            //}
        default://on change and other states
            if let existing = blueTransition {
                child.transform = newTransition
                    .concatenating(existing)
            } else {
                child.transform = newTransition
            }
            //if let existing = existingTransition{
            //  child.transform = newTransition
            //      .concatenating(existing)
            //}else{
            //  child.transform = newTransition
            //}
        }
        self.view.layoutIfNeeded()
    
        // move this to viewDidLoad(), otherwise you are adding ANOTHER recognizer each time this method is called
        //let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTapGestured(_:)))
        //subview.addGestureRecognizer(tapGesture)
    
    
    }