Skip to content

Commit

Permalink
Merge pull request #203 from TimothyChilvers/master
Browse files Browse the repository at this point in the history
Add UILayoutSupport support
  • Loading branch information
orta committed Sep 1, 2016
2 parents 435a0fc + a28cd12 commit 0bd4be0
Show file tree
Hide file tree
Showing 6 changed files with 294 additions and 1 deletion.
12 changes: 12 additions & 0 deletions Cartography.xcodeproj/project.pbxproj
Expand Up @@ -7,6 +7,9 @@
objects = {

/* Begin PBXBuildFile section */
4A3756C91D67445F0036190E /* LayoutSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66AF7EED1CABEABC00249E27 /* LayoutSupport.swift */; };
4A3756CB1D6749190036190E /* LayoutSupportSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A3756CA1D6749180036190E /* LayoutSupportSpec.swift */; };
4A3756CC1D6749190036190E /* LayoutSupportSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A3756CA1D6749180036190E /* LayoutSupportSpec.swift */; };
541305B91B49598E003F1935 /* Matchers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 541305B71B495986003F1935 /* Matchers.swift */; };
541305BA1B49598F003F1935 /* Matchers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 541305B71B495986003F1935 /* Matchers.swift */; };
54143E4E1A93991D00208182 /* Distribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5467916E1A93962000DC9BF7 /* Distribute.swift */; };
Expand Down Expand Up @@ -115,6 +118,7 @@
632F09531BF1F146002431A3 /* ViewHierarchySpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB41A06E1A229BF7007142FE /* ViewHierarchySpec.swift */; };
632F09541BF1F15C002431A3 /* Matchers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 541305B71B495986003F1935 /* Matchers.swift */; };
632F09551BF1F15C002431A3 /* TestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54DE2B9E1ABE2BC5004D62D8 /* TestView.swift */; };
66AF7EEE1CABEABC00249E27 /* LayoutSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66AF7EED1CABEABC00249E27 /* LayoutSupport.swift */; };
BBAC6D131A22A27900E8A3E2 /* ViewUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBAC6D121A22A27900E8A3E2 /* ViewUtils.swift */; };
BBAC6D141A22A3AC00E8A3E2 /* ViewUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBAC6D121A22A27900E8A3E2 /* ViewUtils.swift */; };
BBAC6D1B1A22A8F300E8A3E2 /* ViewHierarchySpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB41A06E1A229BF7007142FE /* ViewHierarchySpec.swift */; };
Expand Down Expand Up @@ -180,6 +184,7 @@
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
4A3756CA1D6749180036190E /* LayoutSupportSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LayoutSupportSpec.swift; sourceTree = "<group>"; };
541305B71B495986003F1935 /* Matchers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Matchers.swift; sourceTree = "<group>"; };
54143E4F1A939D6000208182 /* DistributeSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = DistributeSpec.swift; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
5435B8B41AD8610300B805F1 /* Nimble.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Nimble.framework; sourceTree = "<group>"; };
Expand Down Expand Up @@ -224,6 +229,7 @@
54FA3A431951A9730094B82A /* SizeSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SizeSpec.swift; sourceTree = "<group>"; };
632F090A1BF1E7AA002431A3 /* Cartography.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Cartography.framework; sourceTree = BUILT_PRODUCTS_DIR; };
632F09221BF1E7FC002431A3 /* Cartography-tvOS-tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Cartography-tvOS-tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
66AF7EED1CABEABC00249E27 /* LayoutSupport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LayoutSupport.swift; sourceTree = "<group>"; };
BB41A06E1A229BF7007142FE /* ViewHierarchySpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewHierarchySpec.swift; sourceTree = "<group>"; };
BBAC6D121A22A27900E8A3E2 /* ViewUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewUtils.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
Expand Down Expand Up @@ -352,6 +358,7 @@
546E9E941950A97F00B16707 /* Expression.swift */,
54B093961A7165F2008A1102 /* Extensions.swift */,
546E9E881950A29300B16707 /* LayoutProxy.swift */,
66AF7EED1CABEABC00249E27 /* LayoutSupport.swift */,
54FA3A3B1951A2B60094B82A /* Point.swift */,
54FA3A351950FD320094B82A /* Priority.swift */,
54C96A2F195064A6000CDD27 /* Property.swift */,
Expand Down Expand Up @@ -380,6 +387,7 @@
54143E4F1A939D6000208182 /* DistributeSpec.swift */,
546E9E921950A87600B16707 /* EdgeSpec.swift */,
545F858E1953235F00791F75 /* EdgesSpec.swift */,
4A3756CA1D6749180036190E /* LayoutSupportSpec.swift */,
545F35831ACDFF6C00B57E98 /* MemoryLeakSpec.swift */,
54FA3A3D1951A3750094B82A /* PointSpec.swift */,
54FA3A371950FD8E0094B82A /* PrioritySpec.swift */,
Expand Down Expand Up @@ -671,6 +679,7 @@
546E9E8F1950A33700B16707 /* Coefficients.swift in Sources */,
54FA3A401951A41B0094B82A /* Compound.swift in Sources */,
54B093951A715E52008A1102 /* ConstraintGroup.swift in Sources */,
66AF7EEE1CABEABC00249E27 /* LayoutSupport.swift in Sources */,
54BF29B01A9348B30066ED10 /* Align.swift in Sources */,
546E9E951950A97F00B16707 /* Expression.swift in Sources */,
);
Expand All @@ -684,6 +693,7 @@
54FA3A441951A9730094B82A /* SizeSpec.swift in Sources */,
54BF29B31A934F240066ED10 /* AlignSpec.swift in Sources */,
54143E511A939D7000208182 /* DistributeSpec.swift in Sources */,
4A3756CB1D6749190036190E /* LayoutSupportSpec.swift in Sources */,
54FA3A3E1951A3750094B82A /* PointSpec.swift in Sources */,
541305B91B49598E003F1935 /* Matchers.swift in Sources */,
545F858F1953235F00791F75 /* EdgesSpec.swift in Sources */,
Expand Down Expand Up @@ -764,6 +774,7 @@
632F09441BF1F127002431A3 /* Priority.swift in Sources */,
632F09451BF1F127002431A3 /* Property.swift in Sources */,
632F09461BF1F127002431A3 /* Size.swift in Sources */,
4A3756C91D67445F0036190E /* LayoutSupport.swift in Sources */,
632F09471BF1F127002431A3 /* View.swift in Sources */,
632F09481BF1F127002431A3 /* ViewUtils.swift in Sources */,
);
Expand All @@ -777,6 +788,7 @@
632F09551BF1F15C002431A3 /* TestView.swift in Sources */,
632F09491BF1F146002431A3 /* AlignSpec.swift in Sources */,
632F094A1BF1F146002431A3 /* ConstraintGroupSpec.swift in Sources */,
4A3756CC1D6749190036190E /* LayoutSupportSpec.swift in Sources */,
632F094B1BF1F146002431A3 /* DimensionSpec.swift in Sources */,
632F094C1BF1F146002431A3 /* DistributeSpec.swift in Sources */,
632F094D1BF1F146002431A3 /* EdgeSpec.swift in Sources */,
Expand Down
82 changes: 82 additions & 0 deletions Cartography/Compound.swift
Expand Up @@ -94,3 +94,85 @@ public func <= <P: RelativeCompoundInequality>(lhs: P, rhs: Expression<P>) -> [N
public func >= <P: RelativeCompoundInequality>(lhs: P, rhs: Expression<P>) -> [NSLayoutConstraint] {
return lhs.context.addConstraint(lhs, coefficients: rhs.coefficients, to: rhs.value, relation: NSLayoutRelation.GreaterThanOrEqual)
}

#if os(iOS) || os(tvOS)

/// Declares a property equal to a layout support.
///
/// - parameter lhs: The affected property. The associated view will have
/// `translatesAutoresizingMaskIntoConstraints` set to `false`.
/// - parameter rhs: The layout support.
///
/// - returns: An `NSLayoutConstraint`.
///

public func == <P: RelativeEquality>(lhs: P, rhs: LayoutSupport) -> NSLayoutConstraint {
return lhs.context.addConstraint(lhs, to: rhs)
}

/// Declares a property equal to the result of a layout support expression.
///
/// - parameter lhs: The affected property. The associated view will have
/// `translatesAutoresizingMaskIntoConstraints` set to `false`.
/// - parameter rhs: The layout support expression.
///
/// - returns: An `NSLayoutConstraint`.
///

public func == <P: RelativeEquality>(lhs: P, rhs: Expression<LayoutSupport>) -> NSLayoutConstraint {
return lhs.context.addConstraint(lhs, to: rhs.value, coefficients: rhs.coefficients[0])
}

/// Declares a property greater than or equal to a layout support.
///
/// - parameter lhs: The affected property. The associated view will have
/// `translatesAutoresizingMaskIntoConstraints` set to `false`.
/// - parameter rhs: The layout support.
///
/// - returns: An `NSLayoutConstraint`.
///

public func >= <P: RelativeEquality>(lhs: P, rhs: LayoutSupport) -> NSLayoutConstraint {
return lhs.context.addConstraint(lhs, to: rhs, relation: NSLayoutRelation.GreaterThanOrEqual)
}

/// Declares a property less than or equal to a layout support.
///
/// - parameter lhs: The affected property. The associated view will have
/// `translatesAutoresizingMaskIntoConstraints` set to `false`.
/// - parameter rhs: The layout support.
///
/// - returns: An `NSLayoutConstraint`.
///

public func <= <P: RelativeEquality>(lhs: P, rhs: LayoutSupport) -> NSLayoutConstraint {
return lhs.context.addConstraint(lhs, to: rhs, relation: NSLayoutRelation.LessThanOrEqual)
}

/// Declares a property greater than or equal to the result of a layout support expression.
///
/// - parameter lhs: The affected property. The associated view will have
/// `translatesAutoresizingMaskIntoConstraints` set to `false`.
/// - parameter rhs: The layout support.
///
/// - returns: An `NSLayoutConstraint`.
///

public func >= <P: RelativeEquality>(lhs: P, rhs: Expression<LayoutSupport>) -> NSLayoutConstraint {
return lhs.context.addConstraint(lhs, to: rhs.value, coefficients: rhs.coefficients[0], relation: NSLayoutRelation.GreaterThanOrEqual)
}

/// Declares a property less than or equal to the result of a layout support expression.
///
/// - parameter lhs: The affected property. The associated view will have
/// `translatesAutoresizingMaskIntoConstraints` set to `false`.
/// - parameter rhs: The layout support.
///
/// - returns: An `NSLayoutConstraint`.
///

public func <= <P: RelativeEquality>(lhs: P, rhs: Expression<LayoutSupport>) -> NSLayoutConstraint {
return lhs.context.addConstraint(lhs, to: rhs.value, coefficients: rhs.coefficients[0], relation: NSLayoutRelation.LessThanOrEqual)
}

#endif
24 changes: 24 additions & 0 deletions Cartography/Context.swift
Expand Up @@ -15,6 +15,30 @@ import AppKit
public class Context {
internal var constraints: [Constraint] = []

#if os(iOS) || os(tvOS)

internal func addConstraint(from: Property, to: LayoutSupport, coefficients: Coefficients = Coefficients(), relation: NSLayoutRelation = .Equal) -> NSLayoutConstraint {
from.view.car_translatesAutoresizingMaskIntoConstraints = false

let layoutConstraint = NSLayoutConstraint(item: from.view,
attribute: from.attribute,
relatedBy: relation,
toItem: to.layoutGuide,
attribute: to.attribute,
multiplier: CGFloat(coefficients.multiplier),
constant: CGFloat(coefficients.constant))

var view = from.view
while let superview = view.superview {
view = superview
}
constraints.append(Constraint(view: view, layoutConstraint: layoutConstraint))

return layoutConstraint
}

#endif

internal func addConstraint(from: Property, to: Property? = nil, coefficients: Coefficients = Coefficients(), relation: NSLayoutRelation = .Equal) -> NSLayoutConstraint {
from.view.car_translatesAutoresizingMaskIntoConstraints = false

Expand Down
34 changes: 34 additions & 0 deletions Cartography/LayoutSupport.swift
@@ -0,0 +1,34 @@
//
// LayoutSupport.swift
// Cartography
//
// Created by Timothy Chilvers on 30/03/2016.
// Copyright © 2016 Robert Böhnke. All rights reserved.
//

import Foundation

#if os(iOS) || os(tvOS)
import UIKit

public struct LayoutSupport {
let layoutGuide : UILayoutSupport
let attribute : NSLayoutAttribute
}

public extension UIViewController {

public var topLayoutGuideCartography : LayoutSupport {
get {
return LayoutSupport(layoutGuide: self.topLayoutGuide, attribute: .Bottom)
}
}

public var bottomLayoutGuideCartography : LayoutSupport {
get {
return LayoutSupport(layoutGuide: self.bottomLayoutGuide, attribute: .Top)
}
}
}

#endif
13 changes: 12 additions & 1 deletion Cartography/Property.swift
Expand Up @@ -144,7 +144,7 @@ public func >= <P: RelativeInequality>(lhs: P, rhs: Expression<P>) -> NSLayoutCo
return lhs.context.addConstraint(lhs, coefficients: rhs.coefficients[0], to: rhs.value, relation: NSLayoutRelation.GreaterThanOrEqual)
}

// Mark: Addition
// MARK: Addition

public protocol Addition : Property { }

Expand Down Expand Up @@ -180,6 +180,17 @@ public func - <P: Addition>(lhs: Expression<P>, rhs: CGFloat) -> Expression<P> {
return rhs - lhs
}

#if os(iOS) || os(tvOS)

public func + (lhs: LayoutSupport, c : CGFloat) -> Expression<LayoutSupport> {
return Expression<LayoutSupport>(lhs, [Coefficients(1, c)])
}

public func - (lhs: LayoutSupport, c : CGFloat) -> Expression<LayoutSupport> {
return lhs + (-c)
}

#endif
// MARK: Multiplication

public protocol Multiplication : Property { }
Expand Down
130 changes: 130 additions & 0 deletions CartographyTests/LayoutSupportSpec.swift
@@ -0,0 +1,130 @@
import Cartography

import Nimble
import Quick
import UIKit

class LayoutSupportSpec: QuickSpec {
override func spec() {
var window: TestWindow!
var view: TestView!
var viewController: UIViewController!
var navigationController: UINavigationController!
var tabBarController: UITabBarController!

beforeEach {
window = TestWindow(frame: CGRectMake(0,0,400,400))

view = TestView(frame: CGRectZero)

viewController = UIViewController()
viewController.view.addSubview(view)

constrain(view) { view in
view.height == 200
view.width == 200
}

navigationController = UINavigationController(rootViewController: viewController)
tabBarController = UITabBarController()
tabBarController.viewControllers = [navigationController]
tabBarController.view.frame = window.bounds
tabBarController.view.layoutIfNeeded()
window.rootViewController = tabBarController

window.setNeedsLayout()
window.layoutIfNeeded()

print(viewController.topLayoutGuide.debugDescription)
}

describe("LayoutSupport.top") {
it("should support relative equalities") {

viewController.view.layoutIfNeeded()

constrain(view) { view in
view.top == viewController.topLayoutGuideCartography
}
viewController.view.layoutIfNeeded()

expect(view.convertRect(view.bounds, toView: window).minY).to(equal(viewController.topLayoutGuide.length))
}

it("should support relative inequalities") {
constrain(view) { view in
view.top <= viewController.topLayoutGuideCartography
view.top >= viewController.topLayoutGuideCartography
}

viewController.view.layoutIfNeeded()

expect(view.convertRect(view.bounds, toView: window).minY).to(equal(viewController.topLayoutGuide.length))
}

it("should support addition") {
constrain(view) { view in
view.top == viewController.topLayoutGuideCartography + 100
}

viewController.view.layoutIfNeeded()

expect(view.convertRect(view.bounds, toView: window).minY).to(equal(100 + viewController.topLayoutGuide.length))
}

it("should support subtraction") {
constrain(view) { view in
view.top == viewController.topLayoutGuideCartography - 100
}

viewController.view.layoutIfNeeded()

expect(view.convertRect(view.bounds, toView: window).minY).to(equal(-100 - viewController.topLayoutGuide.length))
}
}

describe("LayoutSupport.bottom") {
it("should support relative equalities") {
constrain(view) { view in
view.bottom == viewController.bottomLayoutGuideCartography
}
viewController.view.layoutIfNeeded()

expect(view.convertRect(view.bounds, toView: window).maxY).to(equal(window.bounds.maxY - viewController.bottomLayoutGuide.length))
}

it("should support relative inequalities") {
constrain(view) { view in
view.bottom <= viewController.bottomLayoutGuideCartography
view.bottom >= viewController.bottomLayoutGuideCartography
}

viewController.view.layoutIfNeeded()

expect(view.convertRect(view.bounds, toView: window).maxY).to(equal(window.bounds.maxY - viewController.bottomLayoutGuide.length))
}

it("should support addition") {
constrain(view) { view in
view.bottom == viewController.bottomLayoutGuideCartography + 100
}

viewController.view.layoutIfNeeded()

expect(view.convertRect(view.bounds, toView: window).maxY).to(equal(100 + window.bounds.maxY - viewController.bottomLayoutGuide.length))
}

it("should support subtraction") {
constrain(view) { view in
view.bottom == viewController.bottomLayoutGuideCartography - 100
}

viewController.view.layoutIfNeeded()

expect(view.convertRect(view.bounds, toView: window).maxY).to(equal((window.bounds.maxY - 100) - viewController.bottomLayoutGuide.length))
}

}

}
}

0 comments on commit 0bd4be0

Please sign in to comment.