ConstraintMaker for Autolayout

in #utopian-io7 years ago

Repository

https://github.com/hitenkmr/ConstraintMaker

What is ConstraintMaker?

ConstraintMaker allows you to bind views programmatically. It's a light-weight class which wraps AutoLayout with nice and clean syntax. ConstraintMaker provides its own NSLayoutConstraint resulting in code that is more concise and readable.

For Example clone the repo or just download and go to DemoProject and run it, After running the project you will find how easy it is to manage views programmatically with ConstraintMaker.swift class.

Difficulty with Autolayout

Suppose that we have to bind a view from the 4 ends to the superview with 50 margins, then we have to write a huge line of code.

let view1 = UIView.init()
   view1.translatesAutoresizingMaskIntoConstraints = false
   view1.backgroundColor = UIColor.red
   superView.addSubview(view1)
   superView.addConstraints([
   NSLayoutConstraint.init(item: view1, 
            attribute: NSLayoutAttribute.leading, 
            relatedBy: .equal, 
            toItem: self,
            attribute: NSLayoutAttribute.leading, 
            multiplier: 1.0, 
            constant: 20),
            NSLayoutConstraint.init(item: view1, 
            attribute: NSLayoutAttribute.trailing, 
            relatedBy: .equal, 
            toItem: self, 
            attribute: NSLayoutAttribute.trailing,
            multiplier: 1.0, 
            constant: 20),
            NSLayoutConstraint.init(item: view1, 
            attribute: NSLayoutAttribute.top, 
            relatedBy: .equal, 
            toItem: self, 
            attribute: NSLayoutAttribute.top, 
            multiplier: 1.0, 
            constant: 20),
            NSLayoutConstraint.init(item: view1, 
            attribute: NSLayoutAttribute.bottom, 
            relatedBy: .equal, 
            toItem: self, 
            attribute: NSLayoutAttribute.bottom,
            multiplier: 1.0, 
            constant: 20)
            ])

But using ConstraintMaker with just few lines of code you can have a workaround to make it possible.

view1.prepareForNewConstraints { (v) in
            //give leading space from superview
            v?.setLeadingSpaceFromSuperView(leadingSpace: 50)
            //give trailing space from superview
            v?.setTrailingSpaceFromSuperView(trailingSpace: -50)
            //give top space from superview
            v?.setTopSpaceFromSuperView(topSpace: 50)
            //give bottom space from superview
            v?.setBottomSpaceFromSuperView(bottomSpace: -50)
        }

What's in ConstraintMaker.swift class

We have an extension to the UIView which contains functions that can be very useful while wrapping the view in another view. Autolayout Engine is smart and does all the work for you but adding a view on its superview programmatically takes huge lines of code that we have mentioned above, but using this class one can easily make use of it with few lines of code.

  func prepareForNewConstraints(block : ((UIView?) -> ())!)
    {
        self.translatesAutoresizingMaskIntoConstraints = false
        block(self)
    }

If you want to use Auto Layout to dynamically calculate the size and position of your view, you must set 'translatesAutoresizingMaskIntoConstraints' property to false. This method is used to bind the view and with a callback it prepares view magically.

var topSpaceConstraint: NSLayoutConstraint? {
        get {
            return objc_getAssociatedObject(self, &Keys.TopSpaceConstraint) as? NSLayoutConstraint
        }
        set (newValue) {
            objc_setAssociatedObject(self, &Keys.TopSpaceConstraint, nil,
                                     objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
            
            objc_setAssociatedObject(self, &Keys.TopSpaceConstraint, newValue,
                                     objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }
    
    var bottomSpaceConstraint: NSLayoutConstraint? {
        get {
            return objc_getAssociatedObject(self, &Keys.BottomSpaceConstraint) as? NSLayoutConstraint
        }
        set (newValue) {
            objc_setAssociatedObject(self, &Keys.BottomSpaceConstraint, nil,
                                     objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
            
            objc_setAssociatedObject(self, &Keys.BottomSpaceConstraint, newValue,
                                     objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }
    
    var leadingSpaceConstraint: NSLayoutConstraint? {
        get {
            return objc_getAssociatedObject(self, &Keys.LeadingSpaceConstraint) as? NSLayoutConstraint
        }
        set (newValue) {
            objc_setAssociatedObject(self, &Keys.LeadingSpaceConstraint, nil,
                                     objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
            
            objc_setAssociatedObject(self, &Keys.LeadingSpaceConstraint, newValue,
                                     objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }
    var trailingSpaceConstraint: NSLayoutConstraint? {
        get {
            return objc_getAssociatedObject(self, &Keys.TrailingSpaceConstraint) as? NSLayoutConstraint
        }
        set (newValue) {
            objc_setAssociatedObject(self, &Keys.TrailingSpaceConstraint, nil,
                                     objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
            
            objc_setAssociatedObject(self, &Keys.TrailingSpaceConstraint, newValue,
                                     objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }

    
    var heightConstraint: NSLayoutConstraint? {
        get {
            return objc_getAssociatedObject(self, &Keys.HeightConstraint) as? NSLayoutConstraint
        }
        set (newValue) {
            objc_setAssociatedObject(self, &Keys.HeightConstraint, nil,
                                     objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
            
            objc_setAssociatedObject(self, &Keys.HeightConstraint, newValue,
                                     objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }
    
    var widthConstraint: NSLayoutConstraint? {
        get {
            return objc_getAssociatedObject(self, &Keys.WidthConstraint) as? NSLayoutConstraint
        }
        set (newValue) {
            objc_setAssociatedObject(self, &Keys.WidthConstraint, nil,
                                     objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
            
            objc_setAssociatedObject(self, &Keys.WidthConstraint, newValue,
                                     objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }
    
    var verticalSpaceConstraint: NSLayoutConstraint? {
        get {
            return objc_getAssociatedObject(self, &Keys.verticalSpaceConstraint) as? NSLayoutConstraint
        }
        set (newValue) {
            objc_setAssociatedObject(self, &Keys.verticalSpaceConstraint, nil,
                                     objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
            
            objc_setAssociatedObject(self, &Keys.verticalSpaceConstraint, newValue,
                                     objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }

While setting the constraints on views you can get them at runtime also with Computed properties containing the getters and setters. While setting the specific constraint constant we have user association policy retain-nonatomic which keeps the strong reference to the object.
OBJC_ASSOCIATION_RETAIN_NONATOMIC Specifies a strong reference to the associated object, and that the association is not made atomically.

How to use the class

Just go to the repository and download the ConstraintMaker.swift class and drag and drop it into the project directory.
Suppose that we have to add a view called redView to the ViewController view programmatically so that it have 50 margin from left, right and top and height of 200. So just with few lines of code, this can be achieved.

  let redView = UIView.init()
        redView.backgroundColor = UIColor.red
        self.view.addSubview(redView)
        
        //If you want to use Auto Layout to dynamically calculate the size and position of your view, you must set 'translatesAutoresizingMaskIntoConstraints' property to false
        redView.prepareForNewConstraints { (v) in
            //give leading space from superview
            v?.setLeadingSpaceFromSuperView(leadingSpace: 50)
            //give trailing space from superview
            v?.setTrailingSpaceFromSuperView(trailingSpace: -50)
            //give top space from superview
            v?.setTopSpaceFromSuperView(topSpace: 50)
            //give bottom space from superview
            v?.pinHeightConstraint(constant: 200)
        }

Now suppose that we have to add another view called greenView, to make it center horizontally to the superview, vertical space from greenView of 20, width 100 and height 100.

let greenView = UIView.init()
        greenView.backgroundColor = UIColor.green
        self.view.addSubview(greenView)
        
        //If you want to use Auto Layout to dynamically calculate the size and position of your view, you must set 'translatesAutoresizingMaskIntoConstraints' property to false
        greenView.prepareForNewConstraints { (v) in
            //center Horizontally view to superview
            v?.centerHorizontallyWithView(view: self.view)
            //give vertical space from nearest neighbour
            v?.setVerticalSpaceFrom(toView: redView, constant: 20)
            //pin width
            v?.pinWidthConstraint(constant: 100)
            //pin height
            v?.pinHeightConstraint(constant: 100)
        }
Sort:  

I was asked by @amosbastian to review the code as I have some knowledge about iOS development.

I've found the following problems with your code:

  • As mentioned by @opus: similar functionality already exists in the SDK.
  • You've kept the default Xcode project name: DemoProject.
  • There are nearly no useful comments in the code itself. Couldn't find any to be exact. I'm not really the person that should judge people for that (I rarely comment my own code), but still this is an issue.
  • You've failed to adhere to a bracing convention. Some function declarations have the opening brace on the same line as the "func", sometimes the opening brace is on the line following the declaration itself. You should create rules for yourself and then follow them. I'd suggest to reformat the code.
  • The code has lots of duplicate logic. While I don't suggest any solutions to this I'd recommend to try and find a way to deduplicate code on your own. Repeating code like this may lead to issues later on when you'll have to change that exact bit of code in multiple lines (due to API changes, for example).

In spite of the issues I've mentioned the library itself seems to work correctly. I don't believe this project will be deemed useful by fellow Swift programmers. I hope to be proven wrong, though :)

Every contribution to free open source software is welcome though, and I myself would love to see more projects from you. Keep coding!

PS. Keep that in mind this is not an official review of your code. It's just my opinion of your code. It may influence the utopian review, but you will still, probably, be rewarded. Thank you for contributing to FOSS.

Hey @nepeta
Here's a tip for your valuable feedback! @Utopian-io loves and incentivises informative comments.

Contributing on Utopian
Learn how to contribute on our website.

Want to chat? Join us on Discord https://discord.gg/h52nFrV.

Vote for Utopian Witness!

Thanks for the appreciation, though there are some issues which I have to focus on. From my next contribution I will try my best to include all things in a proper way as mentioned above and will try to include comments in the code to make the developers know the exact meaning of each bit of code.

Isn't that easier to use VFL

NSLayoutConstraint.constraintsWithVisualFormat("V:|-50-[v1]-50-|", options: [], metrics: nil, v1: "subview")
NSLayoutConstraint.constraintsWithVisualFormat("H:|-50-[v1]-50-|", options: [], metrics: nil, v1: "subview")

So I made this particular class so that i need not write that much of code.

redView.prepareForNewConstraints { (v) in
            v?.setLeadingSpaceFromSuperView(leadingSpace: 50)
            v?.setTrailingSpaceFromSuperView(trailingSpace: -50)
            v?.setTopSpaceFromSuperView(topSpace: 50)
            v?.pinHeightConstraint(constant: 200)
NSLayoutConstraint.constraintsWithVisualFormat("V:|-50-[v1(200)]", options: [], metrics: nil, v1: "redView")
NSLayoutConstraint.constraintsWithVisualFormat("H:|-50-[v1]-50-|", options: [], metrics: nil, v1: "redView")

Ofcourse it's a matter of choice, but you'll need to import that class everytime, you need to keep up with updates. I'm not sure if it's efficiently work on phone rotations. And by you mean lesser code you'll need to memorize.

  • setLeadingSpaceFromSuperView
  • setTrailingSpaceFromSuperView
  • setTopSpaceFromSuperView
  • pinHeightConstraint
  • centerHorizontallyWithView
  • setVerticalSpaceFrom

And if you start to work with others they'll suffer from it IMO.

You just have to drag the class into the project and no need to import the class everytime as swift classes are globally available throughout the project, In swift, you only import module eg. UIKit. This class also has full support for rotation as well. Hope it helps.

Hi, thanks for the contribution! I don't really have much to add to @nepeta's amazing comment, so I hope you take her feedback into account for future contributions.


Need help? Write a ticket on https://support.utopian.io/.
Chat with us on Discord.
[utopian-moderator]

Hey @hitenkmr
Thanks for contributing on Utopian.
We’re already looking forward to your next contribution!

Contributing on Utopian
Learn how to contribute on our website or by watching this tutorial on Youtube.

Want to chat? Join us on Discord https://discord.gg/h52nFrV.

Vote for Utopian Witness!