Search code examples
xcodestoryboardxcode-storyboard

Is there a way to update the constant value of a large number of constraints in a storyboard?


Suppose in my storyboard, I have a bunch of controller views that align their content to left and right edges with a certain inset (ex: a padding of 6pts). If I later want to change that padding value, how can I quickly make the change across the entire project? Is there a way to change the constant of all constraints with a certain tag in a Storyboard, or do I have to create a kind of "contentView" class and put it in the base view of every ViewController in my project?


Solution

  • You might be able to get the effect you want using a custom subclass of NSLayoutConstraint:

    class AdjustableConstraint: NSLayoutConstraint {
    
        @IBInspectable var name: String = ""
    
        class func setConstant(ofConstraintsNamed name: String, to value: CGFloat) {
            constantForName[name] = value
            NSNotificationCenter.defaultCenter().postNotificationName(notificationName,
                object: self, userInfo: [nameKey: name])
        }
    
        override func awakeFromNib() {
            super.awakeFromNib()
    
            NSNotificationCenter.defaultCenter().addObserver(self,
                selector: #selector(AdjustableConstraint.observeAdjustmentNotification(_:)),
                name: AdjustableConstraint.notificationName, object: AdjustableConstraint.self)
    
            updateConstant()
        }
    
        deinit {
            NSNotificationCenter.defaultCenter().removeObserver(self,
                name: AdjustableConstraint.notificationName, object: AdjustableConstraint.self)
        }
    
        @objc private func observeAdjustmentNotification(note: NSNotification) {
            guard
                let userInfo = note.userInfo
                where userInfo[AdjustableConstraint.nameKey] as? String == name
            else { return }
    
            updateConstant()
        }
    
        private func updateConstant() {
            if let newConstant = AdjustableConstraint.constantForName[name] {
                self.constant = newConstant
            }
        }
    
        private static var constantForName = [String: CGFloat]()
        private static let notificationName = "UpdateAdjustableConstraintConstant"
        private static let nameKey = "name"
    
    
    }
    

    Here's how to use it. For all the constraints in the storyboard that you want to adjust together, set their custom class to AdjustableConstraint. (You can select multiple constraints in the document outline to set the custom class of all of them simultaneously.)

    Doing this will make Xcode show a new field, “Name”, in the Attributes Inspector for these constraints. Set the name to some string like “customMargin”:

    attributes inspector with name field

    In your code, you can set the constant of every AdjustableConstraint with a specific name like this:

    AdjustableConstraint.setConstant(ofConstraintsNamed: "customMargin", to: 10)
    

    When an AdjustableConstraint is loaded from the storyboard, if you have previously set a constant for its name, it will apply that constant. So this will work for newly-loaded constraints too. You might want to initialize the constant before any any constraint is loaded, by setting it in your app delegate at launch time:

    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        AdjustableConstraint.setConstant(ofConstraintsNamed: "customMargin", to: 30)
        return true
    }