3

I would like to create a menu like android in iOS. I'm using layout constraints to setup constraints. I'm facing the issue when I'm trying to update the left constraint of an image on button click (It should animate to clicked button's position). Can anyone help me? It should support landscape and portrait.

I don't want to user third party code and don't want to use the NSLayoutconstraint.

Landscape image Potrait Image

Here is my code.

class MenuViewController: UIViewController {
            var buttons: [UIButton] = []
            var imageView : UIImageView!

            override func viewDidLoad() {
                super.viewDidLoad()
            }

            override func viewDidAppear(_ animated: Bool) {
                super.viewDidAppear(animated)
                addMenuItems()
            }

            func addMenuItems() {

                for _ in 0...4 {
                    let button : UIButton = UIButton()
                    self.view.addSubview(button)
                    button.backgroundColor = UIColor.darkGray
                    button.addTarget(self, action: #selector(didSelectedButton(_:)), for: .touchUpInside)
                    self.buttons.append(button)

                }

                for (index, button) in buttons.enumerated() {

                    button.setTitle(" \(index)", for: .normal)
                    if index == 0 {
                        button.translatesAutoresizingMaskIntoConstraints = false
                        button.leftAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leftAnchor, constant: 0).isActive = true
                        button.widthAnchor.constraint(greaterThanOrEqualToConstant: 1).isActive = true
                        button.heightAnchor.constraint(equalToConstant: 50).isActive = true
                        button.topAnchor.constraint(equalTo: self.view.topAnchor, constant:200).isActive = true

                        self.imageView = UIImageView()
                        self.view.addSubview(self.imageView)
                        self.imageView.backgroundColor = UIColor.red

                        self.imageView.translatesAutoresizingMaskIntoConstraints = false
                        self.imageView.leftAnchor.constraint(equalTo: button.leftAnchor, constant: 0).isActive = true
                        self.imageView.widthAnchor.constraint(equalTo: button.widthAnchor, multiplier: 1).isActive = true
                        self.imageView.heightAnchor.constraint(equalToConstant: 4).isActive = true
                        self.imageView.topAnchor.constraint(equalTo: self.view.topAnchor, constant:250).isActive = true

                    }
                    else {

                        let preButton = buttons\[index - 1\]
                        button.translatesAutoresizingMaskIntoConstraints = false
                        button.leftAnchor.constraint(equalTo: preButton.rightAnchor, constant: 0).isActive = true
                        button.widthAnchor.constraint(equalTo: preButton.widthAnchor, multiplier: 1).isActive = true
                        button.heightAnchor.constraint(equalToConstant: 50).isActive = true
                        button.topAnchor.constraint(equalTo: self.view.topAnchor, constant:200).isActive = true
                        if index == self.buttons.count - 1 {
                            button.rightAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.rightAnchor, constant: 0).isActive = true
                        }
                    }

                }

            }

            @objc func didSelectedButton(_ button:UIButton) {
                UIView.animate(withDuration: 1, animations: {
                    self.imageView.leftAnchor.constraint(equalTo: button.leftAnchor).isActive = true
                    self.imageView.layoutIfNeeded()
                }, completion: nil)
            }

        }
Vikash Kumar
  • 642
  • 1
  • 11
  • 25
Damodar
  • 707
  • 2
  • 10
  • 23

1 Answers1

10

Using anchors creates NSLayoutContraints. You need to deactivate the previous ones when you create new ones.

Add this property to your MenuViewController:

var imageLeftConstraint: NSLayoutConstraint?

Set it when you first create the constraint:

imageLeftConstraint = self.imageView.leftAnchor.constraint(equalTo: button.leftAnchor, constant: 0)
imageLeftConstraint?.isActive = true

Then use it to deactivate the previous constraint before adding a new one:

@objc func didSelectedButton(_ button:UIButton) {
    imageLeftConstraint?.isActive = false
    imageLeftConstraint = self.imageView.leftAnchor.constraint(equalTo: button.leftAnchor)
    imageLeftConstraint?.isActive = true
    UIView.animate(withDuration: 1, animations: {
        self.view.layoutIfNeeded()
    }, completion: nil)
}

Note: You need to call layoutIfNeeded() on the superView of imageView (aka self.view) because the constraint between button and imageView will be added to their common ancestor (self.view).

vacawama
  • 150,663
  • 30
  • 266
  • 294
  • I did this one, is there any other way? Thanks to vacawama & vikas – Damodar Jan 08 '19 at 18:14
  • What is your objection to this way? – vacawama Jan 08 '19 at 18:17
  • Nothing, just need to all the solutions. Anyways thanks for your support, got the solution. – Damodar Jan 08 '19 at 18:21
  • What does `.isActive = false` actually do? Does it keep it in a long list or move it to the end of an otherwise ignored queue (like a scheduler in UNIX would)? Because over time these deactivated constraints could add up and slow things down if not garbage collected (and I don't see why they'd be garbage collected since they can be re-activated). I want to change the constraint every time someone edits a text field. Probably wouldn't be a frequent enough thing that it would be a problem and, I guess if you want to tweak the constraint NSConstraint class lets one modify .constant – clearlight May 07 '22 at 20:09
  • Setting a constraint to `false` removes it from the view hierarchy (which means one less object retaining it). If you don't hold another strong pointer to the constraint, then it will be freed. In the case of this example the constraint is held by the `var imageLeftConstraint: NSLayoutConstraint?` property, but when its pointer gets overwritten by the newly created constraint, then there will be no strong references to it and it will be freed. – vacawama May 08 '22 at 15:49