简体   繁体   中英

Out of bounds UIView constraints programmatically

I have a cake of 3 UIView layers with programmatically constraints.

Constructed function for programmatically set up constraints:

func setupViewConstraints(item:UIView, leadingTo:NSLayoutXAxisAnchor, leadingCon:CGFloat, 
trailingTo:NSLayoutXAxisAnchor, trailingCon:CGFloat, topTo:NSLayoutYAxisAnchor, 
topCon:CGFloat, bottomTo:NSLayoutYAxisAnchor, bottomCon:CGFloat) {
    item.translatesAutoresizingMaskIntoConstraints = false
    item.leadingAnchor.constraint(equalTo: leadingTo, constant: leadingCon).isActive = true
    item.trailingAnchor.constraint(equalTo: trailingTo, constant: trailingCon).isActive = true
    item.topAnchor.constraint(equalTo: topTo, constant:topCon).isActive = true
    item.bottomAnchor.constraint(equalTo: bottomTo, constant:bottomCon).isActive = true
}

The lowest base layer is lightGray.

view = UIView()
view.backgroundColor = .lightGray

The 2nd layer contains 2 UIView (red and blue) with constraints .

let red = UIView()
red.backgroundColor = .red
view.addSubview(red)
setupViewConstraints(item: red, leadingTo: view.leadingAnchor, leadingCon: 0, trailingTo: view.trailingAnchor, trailingCon: -(view.frame.width)*0.2), topTo: view.topAnchor, topCon: 0, bottomTo: view.bottomAnchor, bottomCon: -(view.frame.width)*0.8)

let blue = UIView()
blue.backgroundColor = .blue
view.addSubview(blue)
setupViewConstraints(item: blue, leadingTo: view.leadingAnchor, leadingCon: 0, trailingTo: view.trailingAnchor, trailingCon: -(view.frame.width)*0.2), topTo: red.bottomAnchor, topCon: 0, bottomTo: view.bottomAnchor, bottomCon: 0)

3 UIView

And on top i have yellow UIView layer, which overlaps all the lower layers.

let yellow = UIView()
yellow.backgroundColor = .yellow
view.addSubview(yellow)
setupViewConstraints(item: yellow, leadingTo: view.leadingAnchor, leadingCon: 0, trailingTo: view.trailingAnchor, trailingCon: 0, topTo: view.topAnchor, topCon: 0, bottomTo: view.bottomAnchor, bottomCon: 0)

Also, i have UINavigationBar with UINavigationItem inside the yellow UIView .

//Add navigation item and buttons
        naviItem = UINavigationItem()
        naviItem.setRightBarButton(UIBarButtonItem(barButtonSystemItem:.add, target:self, action:#selector(goToDestVC)), animated: true)
        naviItem.setLeftBarButton(UIBarButtonItem(image: UIImage(named: "hamburger_slim_30"), style: .plain, target: self, action: #selector(hamburgerBtnPressed)), animated: true)

        //Add navigation bar with transparent background
        naviBar = UINavigationBar()
        naviBar.setBackgroundImage(UIImage(), for: .default)
        naviBar.shadowImage = UIImage()
        naviBar.isTranslucent = true

// Assign the navigation item to the navigation bar
        naviBar.items = [naviItem]

        view.addSubview(naviBar)
        setupViewConstraints(item: naviBar, leadingTo: yellow.leadingAnchor, leadingCon: 0, trailingTo: yellow.trailingAnchor, trailingCon: 0, topTo: yellow.topAnchor, topCon: 0, bottomTo: yellow.bottomAnchor, bottomCon: -(view.frame.height)*0.9))

带有UINavigationBar的顶级UIView层

And i have hamburgerBtnPressed function, which should shift the yellow layer to the right by 80% (I change the values of leading and trailing constants by 80%), but this does not work!!!

var hamburgerMenuIsVisible = false

    @objc func hamburgerBtnPressed(_ sender: Any) {

        if !hamburgerMenuIsVisible {

            let menuWidth = (self.view.frame.width)*0.8

              setupViewConstraints(item: layoutView, leadingTo: view.leadingAnchor, leadingCon: menuWidth, trailingTo: view.trailingAnchor, trailingCon: menuWidth, topTo: view.topAnchor, topCon: 0, bottomTo: view.bottomAnchor, bottomCon: 0)

            hamburgerMenuIsVisible = true

        } else {

            setupViewConstraints(item: layoutView, leadingTo: view.leadingAnchor, leadingCon: 0, trailingTo: view.trailingAnchor, trailingCon: 0, topTo: view.topAnchor, topCon: 0, bottomTo: view.bottomAnchor, bottomCon: 0)

            hamburgerMenuIsVisible = false
        }

        // layoutIfNeeded() lays out the subviews immediately and forces the layout before drawing
        UIView.animate(withDuration: 0.2, delay:0.0, options: .curveEaseIn, animations: {

            self.view.layoutIfNeeded()

        }) { (animationComplete) in
            print("Animation is complete!")
        }
    }

But if I change the values of leading and trailing constants to negative, everything will work, and the menu will shift to the left without any problems.

let menuWidth = -(self.view.frame.width)*0.8

向左转

Please explain.. what's the issue? Why the yellow UIView shifted to the left with negative values of constraints, and does not work with positive values of constraints? and gives an error:

Probably at least one of the constraints in the following list is one you don't want. 
(
    "<NSLayoutConstraint:0x6040002853c0 UIView:0x7fa947c35850.trailing == UIView:0x7fa947e1d2d0.trailing   (active)>",
    "<NSLayoutConstraint:0x604000092750 UIView:0x7fa947c35850.trailing == UIView:0x7fa947e1d2d0.trailing + 331.2   (active)>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x604000092750 UIView:0x7fa947c35850.trailing == UIView:0x7fa947e1d2d0.trailing + 331.2   (active)>

Update: I have choose Option 2 : Keep a reference to the constraint that you want to change and just adjust its constant. Need to call setNeedsLayout too before layoutIfNeeded.

Updated code:

var leadingC: NSLayoutConstraint!
var trailingC: NSLayoutConstraint!
var yellow: UIView!

loadView():

    yellow = UIView()
        yellow.backgroundColor = .yellow
        view.addSubview(yellow)

        //Set up leading and trailing constraints for handling yellow view shift
        leadingC = yellow.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0)
        trailingC = yellow.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0)

//Put leadingC.constant and trailingC.constant into the function
        setupViewConstraints(item: yellow, leadingTo: view.leadingAnchor, leadingCon: leadingC!.constant, trailingTo: view.trailingAnchor, trailingCon: trailingC.constant, topTo: view.topAnchor, topCon: 0, bottomTo: view.bottomAnchor, bottomCon: 0)

Updated Hamburger function:

 @objc func hamburgerBtnPressed(_ sender: Any) {

        if !hamburgerMenuIsVisible {

            let menuWidth = (self.view.frame.width)*0.8


            leadingC!.constant = menuWidth
            trailingC!.constant = menuWidth

            print(leadingC.constant, trailingC.constant)

            hamburgerMenuIsVisible = true

        } else {

            leadingC!.constant = 0
            trailingC!.constant = 0


            hamburgerMenuIsVisible = false
        }

        // layoutIfNeeded() lays out the subviews immediately and forces the layout before drawing
        UIView.animate(withDuration: 0.2, delay:0.0, options: .curveEaseIn, animations: {

            self.view.setNeedsLayout()
            self.view.layoutIfNeeded()

        }) { (animationComplete) in
            print("Animation is complete!")
        }
    }
    var hamburgerMenuIsVisible = false

I have no errors and "Animation complete!" was printed too, but nothing happens on the screen, no animation.

产量

Firstly, it needs negative values because the constraints need to be set up in the correct direction. Change this and you can remove all of those negative constants:

item.leadingAnchor.constraint(equalTo: leadingTo, constant: leadingCon).isActive = true
item.trailingAnchor.constraint(equalTo: trailingTo, constant: trailingCon).isActive = true
item.topAnchor.constraint(equalTo: topTo, constant:topCon).isActive = true
item.bottomAnchor.constraint(equalTo: bottomTo, constant:bottomCon).isActive = true

to

item.leadingAnchor.constraint(equalTo: leadingTo, constant: leadingCon).isActive = true
trailingTo.constraint(equalTo: item.trailingAnchor, constant: trailingCon).isActive = true
item.topAnchor.constraint(equalTo: topTo, constant:topCon).isActive = true
bottomTo.constraint(equalTo: item.bottomAnchor, constant:bottomCon).isActive = true

Secondly, every time you call setupViewConstraints you are creating and activating another set of constraints.

Option 1:

Remove all constraints for the yellow view before setting them up again.

Option 2:

Keep a reference to the constraint that you want to change and just adjust its constant. You may need to call setNeedsLayout too before layoutIfNeeded .

Option 3:

Add 2 contraints. The initial leading constraint, and one with the width you desire. Change the priority of the first constraint to 999 (default is 1000 ) and toggle the isActive property of the other when you want to show/hide the menu.

let leading = yellow.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0)
leading.priority = UILayoutPriority(999)
leading.isActive = true

let otherConstraint = yellow.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: (view.frame.width)*0.8)
otherConstraint.isActive = false // toggle this property to show/hide

Option 2 is probably going to be the best for performance. From the apple docs:

Setting the constant on an existing constraint performs much better than removing the constraint and adding a new one that's exactly like the old except that it has a different constant

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM