0

Can anyone provide insight on why my code isn't working?

I'm trying to add horizontal spacing between the 2 views in the picture (Red and Black one) but for some reason I keep getting that error and I'm not sure how to fix it.

I'm assuming the other constraints are working correctly since I'm not seeing any issues, nor in the simulation or in the console.

enter image description here

Code as requested by user: https://gist.github.com/sungkim23/66ecd9ce71d0480f083bcd05caf0a58c

import UIKit

class BuyButton: UIView {

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)

        //MARK: - View
        backgroundColor = UIColor(red: 180/255, green: 35/255, blue: 115/255, alpha: 1)
        layer.cornerRadius = 3

        //MARK: - Price View
        let priceView = UIView(frame: CGRect(x: 0, y: 0, width: 80, height: bounds.size.height))
        priceView.backgroundColor = UIColor.black
        addSubview(priceView)

        //MARK: - Purchase View
        let purchaseView = UIView(frame: CGRect(x: 80, y: 0, width: bounds.size.width - 80, height: bounds.size.height))
        purchaseView.backgroundColor = UIColor.red
        addSubview(purchaseView)

        //MARK: - Price Label
        let priceLabel = UILabel(frame: CGRect.zero)
        priceLabel.text = "$ 50.00"
        priceLabel.textAlignment = .center
        priceLabel.backgroundColor = .red
        priceLabel.translatesAutoresizingMaskIntoConstraints = false
        priceView.addSubview(priceLabel)

        //MARK: - Purchase Label
        let purchaseLabel = UILabel(frame: CGRect.zero)
        purchaseLabel.text = "Purchase"
        purchaseLabel.textAlignment = .center
        purchaseLabel.backgroundColor = .green
        purchaseLabel.translatesAutoresizingMaskIntoConstraints = false
        purchaseView.addSubview(purchaseLabel)

        //MARK: - View Constraints
        priceView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
        priceView.topAnchor.constraint(equalTo: topAnchor).isActive = true
        priceView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true

        purchaseView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
        purchaseView.topAnchor.constraint(equalTo: topAnchor).isActive = true
        purchaseView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true

        priceView.centerXAnchor.constraint(equalTo: purchaseView.centerXAnchor).isActive = true

        //MARK: - Label Constraints
        priceLabel.centerXAnchor.constraint(equalTo: priceView.centerXAnchor).isActive = true
        priceLabel.centerYAnchor.constraint(equalTo: priceView.centerYAnchor).isActive = true
        purchaseLabel.centerXAnchor.constraint(equalTo: purchaseView.centerXAnchor).isActive = true
        purchaseLabel.centerYAnchor.constraint(equalTo: purchaseView.centerYAnchor).isActive = true

        //MARK: - Constraint priorities

    }
}

UPDATE 1 - Added error to question as requested

2017-03-13 11:02:38.440892 ButtonTest[20954:1645749] [LayoutConstraints] Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want. 
Try this: 
    (1) look at each constraint and try to figure out which you don't expect; 
    (2) find the code that added the unwanted constraint or constraints and fix it. 
(Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints) 
(
"<NSAutoresizingMaskLayoutConstraint:0x60000009a400 h=--& v=--& UIView:0x7fe55be017b0.midX == 40   (active)>",
"<NSAutoresizingMaskLayoutConstraint:0x600000099ff0 h=--& v=--& UIView:0x7fe55bc09310.midX == 232   (active)>",
"<NSLayoutConstraint:0x600000099c30 UIView:0x7fe55be017b0.centerX == UIView:0x7fe55bc09310.centerX   (active)>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x600000099c30 UIView:0x7fe55be017b0.centerX ==    UIView:0x7fe55bc09310.centerX   (active)>

Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to   catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.

UPDATE 2 - tl;dr

Use
yourView.leadingAnchor.constraint(equalTo: yourOtherView.trailingAnchor).isActive = true

instead of

yourView.centerXAnchor.constraint(equalTo: yourOtherView.centerXAnchor).isActive = true

Sung
  • 434
  • 5
  • 17
  • @EricAya added GIST – Sung Mar 13 '17 at 15:45
  • 1
    You forgot to add `.translatesAutoresizingMaskIntoConstraints = false` to some of your views. This is a very common problem and googling the error message will show you many many SO questions asking the same thing. – Fogmeister Mar 13 '17 at 15:49
  • @Fogmeister even to the views themselves? I have been searching for this answer for a while but found no actual support for my particular problem, I did add it however to the labels as shown above. – Sung Mar 13 '17 at 15:50
  • 1
    Every view that you create in code will then create AutoLayout constraints from the resizing mask. If you then add layout constraints to them and it tries to have a different frame then you will get this issue. The easiest (I find this the easiest way, personally) thing to do when using constraints is to ignore that `frame` exists at all. Instead of `UIView(frame: CGRect(x: 0, y: 0, width: 80, height: bounds.size.height))` just use `UIView()`. – Fogmeister Mar 13 '17 at 15:52
  • @matt good spot. i assumed this was in the ViewController. there is also `priceView` and `purchaseView` which are subviews of `self` and haven't had `translates...` turned off. – Fogmeister Mar 13 '17 at 15:54
  • @Fogmeister thanks for the explanation, I read into it further and realized you are correct, however I tried your method and still doesn't work, the black part actually is breaking constraint and is pushing the right bounds picture for reference http://imgur.com/a/ppmiE – Sung Mar 13 '17 at 15:57
  • I tested your code. You have much more constraint issues than your screen shot shows! Copy the _entire_ constraint issues log message and put it into the question! (Do not use Gist. Do not use screen shot of code or console. Paste everything right into the question.) – matt Mar 13 '17 at 16:00
  • @matt hmm? Before I tried Fogs method I only had 1 issue, which is what the picture shows. After I tried his method I get no errors, just the view going crazy :P – Sung Mar 13 '17 at 16:01
  • Okay, got it. See my answer below. – matt Mar 13 '17 at 16:08
  • You are also not setting the horizontal constraints between priveView and purchaseView. – Fogmeister Mar 13 '17 at 16:09
  • 2
    *cough* UIStackView *cough*. Set the width constraint for the price view and let the stack view worry about filling the rest with the purchase view. – Abizern Mar 13 '17 at 16:38

2 Answers2

4

OK, from your comment on Matt's question. You're still not thinking in constraints :D

You don't need a bounds.size.width - 80 that is a frame and rect thing to think about. It is not an AutoLayout thing to think about.

In order to think about layouts in AutoLayout you need to be describing the layout to the app.

This is what I think you want...

  • The width of price view should be fixed at 80.
  • The leading, top, and bottom edges of price view will have 0 space to the leading, top, and bottom edges of the super view respectively.
  • The leading edge of purchase view should have 0 space to the trailing edge of price view.
  • The trailing, top, and bottom edges of purchase view should have 0 space to the trailing, top, and bottom edges of the super view.

From this list there is only one possible layout for the price view and purchase view. Any other layout would contradict the constraints. Also, I haven't added anything I don't need. For instance, the centerYAnchor of both views will be the same by following these instructions. I don't need to explicitly add that.

So now you have this very simple description... just add it.

// set the width of price view
priceView.widthAnchor.constraint(equalToConstant: 80).isActive = true
// now add the other edges to super view
priceView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
priceView.topAnchor.constraint(equalTo: topAnchor).isActive = true
priceView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true

// set the spacing between price view and purchase view
purchaseView.leadingAnchor.constraint(equalTo: priceView.trailingAnchor).isActive = true
// add the other edges to the super view
purchaseView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
purchaseView.topAnchor.constraint(equalTo: topAnchor).isActive = true
purchaseView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true

That should be it (for the price view and purchase view).

Don't try to overcomplicate your task when using AutoLayout. A lot of the work is done for you by AutoLayout.

Edit - UIStackView

As per @abizern's comment on your question. You should really check out UIStackView. It takes away a lot of this pain and makes layouts using auto layout much easier.

Fogmeister
  • 76,236
  • 42
  • 207
  • 306
  • You're a genius, thanks a lot. My mistake was in the way I was implementing the horizontal constraint, I read up only that I had to use the centerXAnchor, while you are using the more "obvious" way. also can you elaborate on why your widthAnchor uses the equalToConstant value directly instead of equalTo: constant:? (This was also wrong in my code) Both of your implementations and initial guidance helped a lot, thanks Fog! – Sung Mar 13 '17 at 16:37
  • @Velix007 the `equalTo:constant:` is equivalent to saying (for example). "I want the width of price view to be 20 points *bigger* than the width of purchase view." In which case you would use `priceView.widthAnchor.constraint(equalTo: purchaseView.widthAnchor constant: 20)`. But because you just want a *fixed* width you are not comparing to anything else. So just use constant. – Fogmeister Mar 13 '17 at 16:41
  • Thanks a lot Fog, really appreciated, not sure why various guides online said centerXAnchor was the way to set horizontal constraints, but yours works wonderfully. – Sung Mar 13 '17 at 16:43
2

Problem 1: You never said

priceView.translatesAutoresizingMaskIntoConstraints = false

Problem 2: You never said

purchaseView.translatesAutoresizingMaskIntoConstraints = false

Problem 3: You need a complete set of constraints on priceView and purchaseView.

In other words, you cannot mix and match like this, using frame for some views and autolayout for others. Once a view is involved in autolayout, it is completely involved in autolayout and must be entirely positioned and sized by autolayout.

So, if you fix problem 1 and problem 2, no more console errors — but then you will find that the positions / sizes of the views go crazy, because you have gone from overdetermined layout to underdetermined layout. Now you need to fill in the missing constraints to determine that layout. In particular:

  • The black view's width will need to be determined (priceView).

  • The red view's width and x-position will need to be determined (purchaseView).

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • Damn, you got there just as I was about to add an answer :D – Fogmeister Mar 13 '17 at 16:10
  • 2
    @Fogmeister While you were talking, I was testing. :) – matt Mar 13 '17 at 16:14
  • Okay, with both of you guys providing feedback I've gone ahead and changed the views to = UIView() Also went ahead and created - let priceViewWidth = 80 and let purchaseViewWidth = bounds.size.width - 80 And in the constraints I did -priceVi ew.widthAnchor.constraint(equalTo: priceView.widthAnchor, constant: CGFloat(priceViewWidth)).isActive = true and priceView.centerXAnchor.constraint(equalTo: purchaseView.centerXAnchor).isActive = true Still getting issues, working to fix while I answer and read feedback from you 2. – Sung Mar 13 '17 at 16:16
  • 1
    @Velix007 The thing is, Xcode tells you exactly what to do. Just follow its instructions. If you're overdetermined, it shows you the conflicting constants. If you're underdetermined, you get Runtime errors describing the underdetermination in the View Debugger. Just keep saying to yourself "x-position, y-position, width, height" for every view. Every view needs all four to be determined _by constraints_, and _no more_ and _no less_. – matt Mar 13 '17 at 16:21