Last active
May 11, 2016 10:04
-
-
Save justAnotherDev/5b42692f72975c1d0606363d0b11ffd9 to your computer and use it in GitHub Desktop.
A different approach to laying out views. Designed for Swift, works in ObjC
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// ViewPlace.swift | |
// | |
import UIKit | |
extension UIView { | |
/** | |
Add `self` and `otherView` to a new view and align them along the provided `edge`. | |
- Returns: A new view that is now the superview of `self` and `otherView` | |
*/ | |
@objc func place(edge: ViewEdgeRelationship, _ otherView: UIView, offset: CGFloat = 0) -> UIView { | |
// make sure self and otherView don't already have a superview | |
if self.superview != nil || otherView.superview != nil { | |
assertionFailure("views already have superviews. this feature is not handled yet") | |
} | |
// add self and otherView to a container view | |
let containerView = UIView() | |
self.translatesAutoresizingMaskIntoConstraints = false | |
otherView.translatesAutoresizingMaskIntoConstraints = false | |
containerView.addSubview(self) | |
containerView.addSubview(otherView) | |
// pin self to the passed in edge and its perpendicular edges | |
let perpendicular = edge.perpendicular | |
containerView.match([perpendicular.0, perpendicular.1, edge], to: self) | |
// pin otherView to the passed in edge's inverse and perpendicular edges | |
containerView.match([perpendicular.0, perpendicular.1, edge.inverse], to: otherView) | |
// pin self and otherView to each other with the provided offset | |
containerView.align(self, edge: edge.inverse, to: otherView, edge: edge, offset: offset*edge.offsetMultiplier) | |
return containerView | |
} | |
} | |
/** | |
Helpers for adding layout constraints | |
*/ | |
private extension UIView { | |
private func match(edges: [ViewEdgeRelationship], to otherView: UIView) { | |
for edge in edges { | |
align(self, edge: edge, to: otherView, edge: edge) | |
} | |
} | |
private func align(view: UIView, edge: ViewEdgeRelationship, to otherView: UIView, edge otherViewEdge: ViewEdgeRelationship, offset: CGFloat = 0) { | |
addConstraint(NSLayoutConstraint(item: view, attribute: edge.attribute, relatedBy: .Equal, toItem: otherView, attribute: otherViewEdge.attribute, multiplier: 1, constant: offset)) | |
} | |
} | |
@objc enum ViewEdgeRelationship: Int { | |
case LeftOf | |
case RightOf | |
case Above | |
case Below | |
} | |
private extension ViewEdgeRelationship { | |
/** | |
Associated NSLayoutAttribute value. | |
*/ | |
var attribute: NSLayoutAttribute { | |
switch self { | |
case .Above: | |
return .Top | |
case .Below: | |
return .Bottom | |
case .LeftOf: | |
return .Left | |
case .RightOf: | |
return .Right | |
} | |
} | |
/** | |
The opposite edge of self. | |
*/ | |
var inverse: ViewEdgeRelationship { | |
switch self { | |
case .Above: | |
return .Below | |
case .Below: | |
return .Above | |
case .LeftOf: | |
return .RightOf | |
case .RightOf: | |
return .LeftOf | |
} | |
} | |
/** | |
Multiplier to use when applying offsets. | |
- Note: This makes it so provided offset values don't need to be negative | |
*/ | |
var offsetMultiplier: CGFloat { | |
switch self { | |
case .Above: | |
return -1 | |
case .Below: | |
return 1 | |
case .LeftOf: | |
return -1 | |
case .RightOf: | |
return 1 | |
} | |
} | |
/** | |
The two edges that are perpendicular to self. For example: Above and Below are perpendicular to Right (and Left) | |
*/ | |
var perpendicular: (ViewEdgeRelationship, ViewEdgeRelationship) { | |
switch self { | |
case .Above: | |
return (.LeftOf, .RightOf) | |
case .Below: | |
return (.LeftOf, .RightOf) | |
case .LeftOf: | |
return (.Above, .Below) | |
case .RightOf: | |
return (.Above, .Below) | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
UILabel *label = [UILabel new]; | |
label.text = @"hello world"; | |
label.backgroundColor = [UIColor purpleColor]; | |
UIButton *button = [UIButton new]; | |
[button setTitle:@"asdf" forState: UIControlStateNormal]; | |
button.backgroundColor = [UIColor redColor]; | |
UIView *composite1 = [label place:ViewEdgeRelationshipLeftOf :button offset:0]; | |
UILabel *label2 = [UILabel new]; | |
label2.text = @"qwerty"; | |
label2.backgroundColor = [UIColor greenColor]; | |
UIView *composite2 = [composite1 place:ViewEdgeRelationshipRightOf :label2 offset: 4]; | |
UIButton *button2 = [UIButton new]; | |
[button2 setTitle:@"layers" forState: UIControlStateNormal]; | |
button2.backgroundColor = [UIColor orangeColor]; | |
UIView *randomView = [composite2 place:ViewEdgeRelationshipAbove :button2 offset:0]; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let label = UILabel() | |
label.text = "hello world" | |
label.backgroundColor = UIColor.purpleColor() | |
let button = UIButton() | |
button.setTitle("asdf", forState: .Normal) | |
button.backgroundColor = UIColor.redColor() | |
let composite1 = label.place(.Below, button, offset: 0) | |
let label2 = UILabel() | |
label2.text = "qwerty" | |
label2.backgroundColor = UIColor.greenColor() | |
let composite2 = composite1.place(.RightOf, label2, offset: 4) | |
let button2 = UIButton() | |
button2.setTitle("layers", forState: .Normal) | |
button2.backgroundColor = UIColor.orangeColor() | |
return composite2.place(.Above, button2, offset: 0) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment