9

I am trying to programmatically set the constraint for a multiplier in swift, and when I set the value, it just gives me the error, "Cannot assign to the result of this expression"...

I declared the NSLayoutConstraint with an IBOutlet, and then set the multiplier, same as I did with the constant for another, which works fine, but this one won't accept it...

@IBOutlet weak var clockWidthConstraint: NSLayoutConstraint!

override func updateViewConstraints() {
    super.updateViewConstraints()

    confirmTopConstraint.constant = 40.0
    clockWidthConstraint.multiplier = 0.1  // Gives me the error!

    }

Any ideas?

Jake Weso
  • 555
  • 1
  • 6
  • 14
  • You can modify `constant`. If you want to modify `multiplier` (or anything else about constraint, just remove it and add new constraint. – Rob Jan 09 '15 at 23:52
  • Possible duplicate of [Can i change multiplier property for NSLayoutConstraint?](http://stackoverflow.com/questions/19593641/can-i-change-multiplier-property-for-nslayoutconstraint) – Andrew Schreiber Oct 07 '15 at 22:07
  • Check out my answer here to a duplicate question: http://stackoverflow.com/a/33003217/2854041 – Andrew Schreiber Oct 07 '15 at 22:11

1 Answers1

7

Yeah, it's read only:

var multiplier: CGFloat { get } 

You can only specify the multiplier at creation time. In contrast, you can change the non-constant constant property at run-time:

var constant: CGFloat

Edit:

It takes a little effort, but to change immutable properties, you have to create a new constraint and copy all but the property that you want to change. I keep playing around with different techniques for this, but am currently exploring this form:

let constraint = self.aspectRatioConstraint.with() {
    (inout c:NSLayoutConstraint.Model) in
    c.multiplier = self.aspectRatio }

or used in a more realistic context:

var aspectRatio:CGFloat = 1.0 { didSet {

    // remove, update, and add constraint
    self.removeConstraint(self.aspectRatioConstraint)
    self.aspectRatioConstraint = self.aspectRatioConstraint.with() {
        (inout c:NSLayoutConstraint.Model) in
        c.multiplier = self.aspectRatio }
    self.addConstraint(self.aspectRatioConstraint)
    self.setNeedsLayout()
}}

Where the backing code is:

extension NSLayoutConstraint {

    class Model {

        init(item view1: UIView, attribute attr1: NSLayoutAttribute,
            relatedBy relation: NSLayoutRelation,
            toItem view2: UIView?, attribute attr2: NSLayoutAttribute,
            multiplier: CGFloat, constant c: CGFloat,
            priority:UILayoutPriority = 1000) {
                self.firstItem = view1
                self.firstAttribute = attr1
                self.relation = relation
                self.secondItem = view2
                self.secondAttribute = attr2
                self.multiplier = multiplier
                self.constant = c
                self.priority = priority
        }

        var firstItem:UIView
        var firstAttribute:NSLayoutAttribute
        var relation:NSLayoutRelation
        var secondItem:UIView?
        var secondAttribute:NSLayoutAttribute
        var multiplier:CGFloat = 1.0
        var constant:CGFloat = 0
        var priority:UILayoutPriority = 1000
    }


    func priority(priority:UILayoutPriority) -> NSLayoutConstraint {
        self.priority = priority;
        return self
    }

    func with(configure:(inout Model)->()) -> NSLayoutConstraint {

        // build and configure model
        var m = Model(
            item: self.firstItem as! UIView, attribute: self.firstAttribute,
            relatedBy: self.relation,
            toItem: self.secondItem as? UIView, attribute: self.secondAttribute,
            multiplier: self.multiplier, constant: self.constant)
        configure(&m)

        // build and return contraint from model
        var constraint = NSLayoutConstraint(
            item: m.firstItem, attribute: m.firstAttribute,
            relatedBy: m.relation,
            toItem: m.secondItem, attribute: m.secondAttribute,
            multiplier: m.multiplier, constant: m.constant)
        return constraint
    }
}
Chris Conover
  • 8,889
  • 5
  • 52
  • 68
  • Hmm, well I am having an issue, im attempting to make the iPhone 4s layout fit everything on screen, so im using a Equal Widths constraint to make the images small enough to fit everything using the multiplier, but I want to make them bigger on the rest of devices, as they can be much larger and fit everything perfectly... is there another way to go about this then? Perhaps can I set two equal width constraints and simply disable one, then enable the other? – Jake Weso Jan 09 '15 at 23:11
  • can you comment back how you were able to achieve this? – Paul Lehn May 22 '15 at 17:07
  • 1
    And for multiple settings, you can set multiple constraints with staggered priorities. Autolayout randomly chooses a constraint to break in a conflict, so I keep my optional ones on their own priority levels, with my desired first, and fallback after that. In general, you also need hard (1000) bounding constraints to keep things in the playing field, so to speak. – Chris Conover May 22 '15 at 17:29
  • your example gives me the error "cannot invoke 'with' with an argument list of type '(()_->_)' on .with() – Paul Lehn May 22 '15 at 20:25
  • @paul: I did miss something in my quick snippet - see above for a real example. Swift forces you to declare the explicit type for inout params, I'm not if I like this form better than passing in a model that is consequently modified locally (var) and returned – Chris Conover May 22 '15 at 23:02