Search code examples
swiftsliderconstantsnslayoutconstraint

Constraint objects to prevent them from moving when object changes size


As you can see in the gif below the light green objects moves south when the size of the dark green object is increased on the slide. What I want to do is when the dark green object size is increased the light green object does not move it all it remains at is original position. Even if the image does not move the same thing happens.

enter image description here

import UIKit

class ViewController: UIViewController {



    var picWidth: NSLayoutConstraint!
    var picHeight: NSLayoutConstraint!
    var pic = UIImageView()
    var slider = UISlider()
    var pic2 = UIImageView()
    var existingTransition : CGAffineTransform?

    var currentView: UIView?

    var g2 = UIPanGestureRecognizer()



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

        g2 = UIPanGestureRecognizer(target: self, action: #selector(ViewController.g1Method))
        pic.addGestureRecognizer(g2)

        pic.backgroundColor = .systemGreen
        pic2.backgroundColor = .green

        picWidth =  pic.widthAnchor.constraint(equalTo:  view.widthAnchor ,multiplier:  0.2)
        picHeight =  pic.heightAnchor.constraint(equalTo:  view.heightAnchor ,multiplier:  0.20)
        [pic,pic2,slider].forEach {

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

        }
        NSLayoutConstraint.activate ([
            pic.topAnchor.constraint(equalTo: view.topAnchor, constant : 0),
            picWidth,
            picHeight,
            pic.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant :0),

            pic2.topAnchor.constraint(equalTo: pic.bottomAnchor, constant : 0),
            pic2.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5, constant: 0),
            pic2.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.4, constant: 0),
            pic2.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant : 0),


            slider.topAnchor.constraint(equalTo: pic2.bottomAnchor, constant : 0),
            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),


        ])

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


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

    @objc func g1Method(_ sender: UIPanGestureRecognizer){

        let subview = pic2
        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() {


        picWidth.constant = CGFloat(slider.value) * view.frame.size.width * 0.25
        picHeight.constant = CGFloat(slider.value) * view.frame.size.height * 0.25

    }

}

Solution

  • Your problem lies here:

    pic2.topAnchor.constraint(equalTo: pic.bottomAnchor, constant : 0)

    You are telling the layout to keep the top of your pic2 shape equivalent to the bottom of your pic shape.

    To fix this, constrain pic2 to the top safe space, then the height of pic as the constant to put the top of pic2 directly under pic.

    pic2.topAnchor.constraint(equalTo: view.topAnchor, constant : pic.bounds.height)

    Edit: pic.bounds.height won't load because you are loading the constraints in the viewDidLoad all at the same time and the ViewController hasn't laid subviews yet, thus your pic is null when pic.bounds.height is called. In order to fix this issue, you need to do the following:

    •Isolate the pic initialization in viewDidLoad:

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        pic.isUserInteractionEnabled = true
    
        g2 = UIPanGestureRecognizer(target: self, action: #selector(ViewController.g1Method))
        pic.addGestureRecognizer(g2)
    
        pic.backgroundColor = .systemGreen
        pic2.backgroundColor = .green
    
        picWidth =  pic.widthAnchor.constraint(equalTo:  view.widthAnchor ,multiplier:  0.2)
        picHeight =  pic.heightAnchor.constraint(equalTo:  view.heightAnchor ,multiplier:  0.20)
        [pic,pic2,slider].forEach {
    
            view.addSubview($0)
            $0.translatesAutoresizingMaskIntoConstraints = false
    
        }
        NSLayoutConstraint.activate([pic.topAnchor.constraint(equalTo: view.topAnchor, constant : 0),
        picWidth,
        picHeight,
        pic.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant :0)])
        slider.addTarget(self, action: #selector(hhh), for: .allEvents)
    
    }
    

    •Throw the rest of the inits into viewDidLayoutSubviews:

    override func viewDidLayoutSubviews() {
        NSLayoutConstraint.activate ([
    
            pic2.topAnchor.constraint(equalTo: view.topAnchor, constant : pic.bounds.height),
            pic2.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5, constant: 0),
            pic2.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.4, constant: 0),
            pic2.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant : 0),
    
    
            slider.topAnchor.constraint(equalTo: pic2.bottomAnchor, constant : 0),
            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),
    
    
        ])
    }
    

    As a side note, the slider would still be messed up if you were to make the light green square move because you are constraining it to the square. Try to get in the pattern of making your constraints for object located on the outer parts of the screen based off of the view's anchors, not other UI elements.