diff --git a/Cartography.xcodeproj/project.pbxproj b/Cartography.xcodeproj/project.pbxproj index a5875ff4..b82fb643 100644 --- a/Cartography.xcodeproj/project.pbxproj +++ b/Cartography.xcodeproj/project.pbxproj @@ -144,6 +144,13 @@ 97F50E551F9633AA00C6DCF5 /* ViewLayoutGuideSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97F50E531F9633AA00C6DCF5 /* ViewLayoutGuideSpec.swift */; }; 97F50E561F96401200C6DCF5 /* LayoutProxy+TypeErasure.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97F50E511F962CF300C6DCF5 /* LayoutProxy+TypeErasure.swift */; }; 97F50E571F96401300C6DCF5 /* LayoutProxy+TypeErasure.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97F50E511F962CF300C6DCF5 /* LayoutProxy+TypeErasure.swift */; }; + 9AE90A0429FAF2D700944F10 /* Edge+OffsetOperator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AE90A0329FAF2D700944F10 /* Edge+OffsetOperator.swift */; }; + 9AE90A0529FAF2D700944F10 /* Edge+OffsetOperator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AE90A0329FAF2D700944F10 /* Edge+OffsetOperator.swift */; }; + 9AE90A0629FAF2D700944F10 /* Edge+OffsetOperator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AE90A0329FAF2D700944F10 /* Edge+OffsetOperator.swift */; }; + 9AE90A0829FAF36E00944F10 /* AnchorSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AE90A0729FAF36E00944F10 /* AnchorSupport.swift */; }; + 9AE90A0929FAF36E00944F10 /* AnchorSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AE90A0729FAF36E00944F10 /* AnchorSupport.swift */; }; + 9AE90A0A29FAF36E00944F10 /* AnchorSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AE90A0729FAF36E00944F10 /* AnchorSupport.swift */; }; + 9AE90A0C29FB03DC00944F10 /* OffsetDimensionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AE90A0B29FB03DC00944F10 /* OffsetDimensionSpec.swift */; }; A75B6143FF12C54FF3223B47 /* Pods_TestPods_Cartography_tvOS_tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0827A83361EACF1E6062607E /* Pods_TestPods_Cartography_tvOS_tests.framework */; }; D63BD7B82089B6FA00061239 /* LayoutItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE85314F1F9363DC003EC021 /* LayoutItem.swift */; }; EE8531501F936462003EC021 /* LayoutItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE85314F1F9363DC003EC021 /* LayoutItem.swift */; }; @@ -272,6 +279,9 @@ 97E7F0A81F8D598A004857CE /* ViewProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewProxy.swift; sourceTree = ""; }; 97F50E511F962CF300C6DCF5 /* LayoutProxy+TypeErasure.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LayoutProxy+TypeErasure.swift"; sourceTree = ""; }; 97F50E531F9633AA00C6DCF5 /* ViewLayoutGuideSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewLayoutGuideSpec.swift; sourceTree = ""; }; + 9AE90A0329FAF2D700944F10 /* Edge+OffsetOperator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Edge+OffsetOperator.swift"; sourceTree = ""; }; + 9AE90A0729FAF36E00944F10 /* AnchorSupport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnchorSupport.swift; sourceTree = ""; }; + 9AE90A0B29FB03DC00944F10 /* OffsetDimensionSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OffsetDimensionSpec.swift; sourceTree = ""; }; EE85314F1F9363DC003EC021 /* LayoutItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutItem.swift; sourceTree = ""; }; EEDD4098FF7503B1F9188F10 /* Pods_Cartography_iOS_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Cartography_iOS_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ @@ -418,6 +428,7 @@ 546E9E8C1950A31100B16707 /* Dimension.swift */, 5467916E1A93962000DC9BF7 /* Distribute.swift */, 546E9E901950A76C00B16707 /* Edge.swift */, + 9AE90A0329FAF2D700944F10 /* Edge+OffsetOperator.swift */, 545F858C195322EA00791F75 /* Edges.swift */, 546E9E941950A97F00B16707 /* Expression.swift */, 54B093961A7165F2008A1102 /* Extensions.swift */, @@ -437,6 +448,7 @@ 97D17CA91F8E779300C57CE1 /* LayoutGuideProxy.swift */, 97D17C991F8E774700C57CE1 /* CartographyTests */, 54C96A14195063CD000CDD27 /* Supporting Files */, + 9AE90A0729FAF36E00944F10 /* AnchorSupport.swift */, ); path = Cartography; sourceTree = ""; @@ -466,6 +478,7 @@ 979F29C91F94DC6B00257363 /* LayoutGuideSpec.swift */, 97D17C9D1F8E774700C57CE1 /* PrioritySpec.swift */, 54C96A23195063CD000CDD27 /* DimensionSpec.swift */, + 9AE90A0B29FB03DC00944F10 /* OffsetDimensionSpec.swift */, 97D17C9E1F8E774700C57CE1 /* TestView.swift */, 97D17C9F1F8E774700C57CE1 /* Matchers.swift */, 97D17CA01F8E774700C57CE1 /* DistributeSpec.swift */, @@ -863,6 +876,7 @@ 97D17C961F8E774400C57CE1 /* LayoutGuide.swift in Sources */, 547BC85719E2EB9B007BEE9E /* Constraint.swift in Sources */, 547BC85519E2DD06007BEE9E /* Context.swift in Sources */, + 9AE90A0429FAF2D700944F10 /* Edge+OffsetOperator.swift in Sources */, 97F50E521F962CF300C6DCF5 /* LayoutProxy+TypeErasure.swift in Sources */, EE8531501F936462003EC021 /* LayoutItem.swift in Sources */, 977C36821F9FAC890057A057 /* AutoresizingMaskLayoutProxy.swift in Sources */, @@ -883,6 +897,7 @@ 54BF29B01A9348B30066ED10 /* Align.swift in Sources */, 546E9E951950A97F00B16707 /* Expression.swift in Sources */, 97D17CAA1F8E779300C57CE1 /* LayoutGuideProxy.swift in Sources */, + 9AE90A0829FAF36E00944F10 /* AnchorSupport.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -894,6 +909,7 @@ 97F50E541F9633AA00C6DCF5 /* ViewLayoutGuideSpec.swift in Sources */, 1B319C622162740100DD91D4 /* ViewProxyTests.swift in Sources */, 9795590B1F9701CD0096BBEA /* ConstraintGroupSpec.swift in Sources */, + 9AE90A0C29FB03DC00944F10 /* OffsetDimensionSpec.swift in Sources */, 979559081F9701C90096BBEA /* MemoryLeakSpec.swift in Sources */, 979558FC1F97019E0096BBEA /* Matchers.swift in Sources */, 979559021F9701B10096BBEA /* PointSpec.swift in Sources */, @@ -921,6 +937,7 @@ 54F6A856195C213A00313D24 /* Edges.swift in Sources */, 54F6A858195C213A00313D24 /* LayoutProxy.swift in Sources */, 54BF29B51A9350170066ED10 /* Align.swift in Sources */, + 9AE90A0529FAF2D700944F10 /* Edge+OffsetOperator.swift in Sources */, 97F50E561F96401200C6DCF5 /* LayoutProxy+TypeErasure.swift in Sources */, 9722953C1F950A1E00FA4AF9 /* ViewProxy.swift in Sources */, 977C36831F9FC2900057A057 /* AutoresizingMaskLayoutProxy.swift in Sources */, @@ -941,6 +958,7 @@ 549B47B61A58198B002498C7 /* Context.swift in Sources */, 54F6A854195C213A00313D24 /* Dimension.swift in Sources */, 54F6A857195C213A00313D24 /* Expression.swift in Sources */, + 9AE90A0929FAF36E00944F10 /* AnchorSupport.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -976,6 +994,7 @@ 632F09371BF1F127002431A3 /* Compound.swift in Sources */, 632F09381BF1F127002431A3 /* Constrain.swift in Sources */, 632F09391BF1F127002431A3 /* Constraint.swift in Sources */, + 9AE90A0629FAF2D700944F10 /* Edge+OffsetOperator.swift in Sources */, 632F093A1BF1F127002431A3 /* ConstraintGroup.swift in Sources */, 97D17C921F8E740B00C57CE1 /* LayoutSupportProxy.swift in Sources */, 97F50E571F96401300C6DCF5 /* LayoutProxy+TypeErasure.swift in Sources */, @@ -996,6 +1015,7 @@ 632F09461BF1F127002431A3 /* Size.swift in Sources */, 4A3756C91D67445F0036190E /* LayoutSupport.swift in Sources */, 632F09471BF1F127002431A3 /* View.swift in Sources */, + 9AE90A0A29FAF36E00944F10 /* AnchorSupport.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Cartography/AnchorSupport.swift b/Cartography/AnchorSupport.swift new file mode 100644 index 00000000..7825dafa --- /dev/null +++ b/Cartography/AnchorSupport.swift @@ -0,0 +1,87 @@ +// +// AnchorSupport.swift +// Cartography +// +// Created by Jayson Rhynas on 2023-04-27. +// Copyright © 2023 Robert Böhnke. All rights reserved. +// + +import Foundation + +#if os(iOS) || os(tvOS) +import UIKit +#elseif os(OSX) +import AppKit +#endif + +@available(iOS 10.0, macOS 10.12, tvOS 10.0, *) +public protocol AnchorSupport: AnyObject {} + +@available(iOS 10.0, macOS 10.12, tvOS 10.0, *) +public protocol SupportsTopAnchor: AnchorSupport { + var topAnchor: NSLayoutYAxisAnchor { get } +} + +@available(iOS 10.0, macOS 10.12, tvOS 10.0, *) +public protocol SupportsBottomAnchor: AnchorSupport { + var bottomAnchor: NSLayoutYAxisAnchor { get } +} + +@available(iOS 10.0, macOS 10.12, tvOS 10.0, *) +public protocol SupportsRightAnchor: AnchorSupport { + var rightAnchor: NSLayoutXAxisAnchor { get } +} + +@available(iOS 10.0, macOS 10.12, tvOS 10.0, *) +public protocol SupportsLeftAnchor: AnchorSupport { + var leftAnchor: NSLayoutXAxisAnchor { get } +} + +@available(iOS 10.0, macOS 10.12, tvOS 10.0, *) +public protocol SupportsLeadingAnchor: AnchorSupport { + var leadingAnchor: NSLayoutXAxisAnchor { get } +} + +@available(iOS 10.0, macOS 10.12, tvOS 10.0, *) +public protocol SupportsTrailingAnchor: AnchorSupport { + var trailingAnchor: NSLayoutXAxisAnchor { get } +} + +@available(iOS 10.0, macOS 10.12, tvOS 10.0, *) +public protocol SupportsEdgeAnchors: SupportsTopAnchor, SupportsBottomAnchor, SupportsLeadingAnchor, SupportsTrailingAnchor, SupportsLeftAnchor, SupportsRightAnchor {} + +@available(iOS 10.0, macOS 10.12, tvOS 10.0, *) +public protocol SupportsCenterXAnchor: AnchorSupport { + var centerXAnchor: NSLayoutXAxisAnchor { get } +} + +@available(iOS 10.0, macOS 10.12, tvOS 10.0, *) +public protocol SupportsCenterYAnchor: AnchorSupport { + var centerYAnchor: NSLayoutYAxisAnchor { get } +} + +@available(iOS 10.0, macOS 10.12, tvOS 10.0, *) +public protocol SupportsCenteringAnchors: SupportsCenterXAnchor, SupportsCenterYAnchor {} + +@available(iOS 10.0, macOS 10.12, tvOS 10.0, *) +public protocol SupportsWidthAnchor: AnchorSupport { + var widthAnchor: NSLayoutDimension { get } +} + +@available(iOS 10.0, macOS 10.12, tvOS 10.0, *) +public protocol SupportsHeightAnchor: AnchorSupport { + var heightAnchor: NSLayoutDimension { get } +} + +@available(iOS 10.0, macOS 10.12, tvOS 10.0, *) +public protocol SupportsSizeAnchors: SupportsWidthAnchor, SupportsHeightAnchor {} + +@available(iOS 10.0, macOS 10.12, tvOS 10.0, *) +public protocol SupportsBaselineAnchors: AnchorSupport { + var firstBaselineAnchor: NSLayoutYAxisAnchor { get } + + var lastBaselineAnchor: NSLayoutYAxisAnchor { get } +} + +@available(iOS 10.0, macOS 10.12, tvOS 10.0, *) +public protocol SupportsPositioningAnchors: SupportsEdgeAnchors, SupportsSizeAnchors, SupportsCenteringAnchors {} diff --git a/Cartography/Context.swift b/Cartography/Context.swift index 06bdbe38..66729896 100644 --- a/Cartography/Context.swift +++ b/Cartography/Context.swift @@ -24,7 +24,32 @@ public class Context { fromItem.translatesAutoresizingMaskIntoConstraints = false } - let layoutConstraint = NSLayoutConstraint(item: from.item, + func constrainDimensionAnchors() -> NSLayoutConstraint? { + guard #available(iOS 10.0, macOS 10.12, tvOS 10.0, *) else { + return nil + } + + guard from.attribute == .notAnAttribute || to?.attribute == .notAnAttribute else { + return nil + } + + guard let fromAnchor = (from as? Dimension)?.anchor else { + return nil + } + + if let to = to { + guard let toAnchor = (to as? Dimension)?.anchor else { + return nil + } + + return addConstraint(from: fromAnchor, to: toAnchor, coefficients: coefficients, relation: relation) + } else { + return addConstraint(from: fromAnchor, to: coefficients.constant, relation: relation) + } + } + + let layoutConstraint = constrainDimensionAnchors() + ?? NSLayoutConstraint(item: from.item, attribute: from.attribute, relatedBy: relation, toItem: to?.item, @@ -48,4 +73,24 @@ public class Context { return results } + + @available(iOS 10.0, macOS 10.12, tvOS 10.0, *) + private func addConstraint(from dimension: NSLayoutDimension, to constant: CGFloat, relation: LayoutRelation) -> NSLayoutConstraint { + switch relation { + case .lessThanOrEqual: return dimension.constraint(lessThanOrEqualToConstant: constant) + case.greaterThanOrEqual: return dimension.constraint(greaterThanOrEqualToConstant: constant) + case .equal: return dimension.constraint(equalToConstant: constant) + @unknown default: fatalError("Unknown relation \(relation)") + } + } + + @available(iOS 10.0, macOS 10.12, tvOS 10.0, *) + private func addConstraint(from dimension: NSLayoutDimension, to other: NSLayoutDimension, coefficients: Coefficients, relation: LayoutRelation) -> NSLayoutConstraint { + switch relation { + case .lessThanOrEqual: return dimension.constraint(lessThanOrEqualTo: other, multiplier: coefficients.multiplier, constant: coefficients.constant) + case.greaterThanOrEqual: return dimension.constraint(greaterThanOrEqualTo: other, multiplier: coefficients.multiplier, constant: coefficients.constant) + case .equal: return dimension.constraint(equalTo: other, multiplier: coefficients.multiplier, constant: coefficients.constant) + @unknown default: fatalError("Unknown relation \(relation)") + } + } } diff --git a/Cartography/Dimension.swift b/Cartography/Dimension.swift index cf96c886..5d119f4a 100644 --- a/Cartography/Dimension.swift +++ b/Cartography/Dimension.swift @@ -16,10 +16,36 @@ public struct Dimension : Property, NumericalEquality, RelativeEquality, Numeric public let attribute: LayoutAttribute public let context: Context public let item: AnyObject - + + private let _anchor: AnyObject? + internal init(_ context: Context, _ item: AnyObject, _ attribute: LayoutAttribute) { self.attribute = attribute self.context = context self.item = item + self._anchor = nil + } + + @available(iOS 10.0, macOS 10.12, tvOS 10.0, *) + internal init(_ context: Context, _ anchor: NSLayoutDimension) { + self.attribute = .notAnAttribute + self.context = context + self.item = NSNull() + self._anchor = anchor + } + + @available(iOS 10.0, macOS 10.12, tvOS 10.0, *) + internal var anchor: NSLayoutDimension? { + if let anchor = self._anchor as? NSLayoutDimension { return anchor } + + if attribute == .width, let item = item as? SupportsWidthAnchor { + return item.widthAnchor + } + else if attribute == .height, let item = item as? SupportsHeightAnchor { + return item.heightAnchor + } + else { + return nil + } } } diff --git a/Cartography/Edge+OffsetOperator.swift b/Cartography/Edge+OffsetOperator.swift new file mode 100644 index 00000000..fb5bdc2f --- /dev/null +++ b/Cartography/Edge+OffsetOperator.swift @@ -0,0 +1,77 @@ +// +// Edge+OffsetOperator.swift +// Cartography +// +// Created by Jayson Rhynas on 2023-04-27. +// Copyright © 2023 Robert Böhnke. All rights reserved. +// + +import Foundation + +#if os(iOS) || os(tvOS) +import UIKit +#else +import AppKit +#endif + +precedencegroup OffsetPrecedence { + higherThan: MultiplicationPrecedence + associativity: none +} + +infix operator |-|: OffsetPrecedence + +@available(iOS 10.0, macOS 10.12, tvOS 10.0, *) +extension Edge { + public static func |-|(lhs: Edge, rhs: Edge) -> Dimension { + if let first = lhs.xAnchor, let second = rhs.xAnchor { + return Dimension(lhs.context, first.anchorWithOffset(to: second)) + } else if let first = lhs.yAnchor, let second = rhs.yAnchor { + return Dimension(lhs.context, first.anchorWithOffset(to: second)) + } else { + return Dimension(lhs.context, lhs.item, .notAnAttribute) + } + } + + internal var xAnchor: NSLayoutXAxisAnchor? { + if attribute == .left, let item = item as? SupportsLeftAnchor { + return item.leftAnchor + } + else if attribute == .right, let item = item as? SupportsRightAnchor { + return item.rightAnchor + } + else if attribute == .leading, let item = item as? SupportsLeadingAnchor { + return item.leadingAnchor + } + else if attribute == .trailing, let item = item as? SupportsTrailingAnchor { + return item.trailingAnchor + } + else if attribute == .centerX, let item = item as? SupportsCenterXAnchor { + return item.centerXAnchor + } + else { + return nil + } + } + + internal var yAnchor: NSLayoutYAxisAnchor? { + if attribute == .top, let item = item as? SupportsTopAnchor { + return item.topAnchor + } + else if attribute == .bottom, let item = item as? SupportsBottomAnchor { + return item.bottomAnchor + } + else if attribute == .centerY, let item = item as? SupportsCenterYAnchor { + return item.centerYAnchor + } + else if attribute == .firstBaseline, let item = item as? SupportsBaselineAnchors { + return item.firstBaselineAnchor + } + else if attribute == .lastBaseline, let item = item as? SupportsBaselineAnchors { + return item.lastBaselineAnchor + } + else { + return nil + } + } +} diff --git a/Cartography/LayoutGuide.swift b/Cartography/LayoutGuide.swift index a78a6f7f..486eee26 100644 --- a/Cartography/LayoutGuide.swift +++ b/Cartography/LayoutGuide.swift @@ -15,7 +15,7 @@ public typealias LayoutGuide = UILayoutGuide @available(iOS, introduced: 9.0) @available(tvOS, introduced: 9.0) -extension UILayoutGuide: LayoutItem { +extension UILayoutGuide: LayoutItem, SupportsPositioningAnchors { @available(iOS, introduced: 9.0) @available(tvOS, introduced: 9.0) @@ -30,7 +30,7 @@ import AppKit public typealias LayoutGuide = NSLayoutGuide @available(OSX, introduced: 10.11) -extension NSLayoutGuide: LayoutItem { +extension NSLayoutGuide: LayoutItem, SupportsPositioningAnchors { @available(OSX, introduced: 10.11) public func asProxy(context: Context) -> LayoutGuideProxy { diff --git a/Cartography/LayoutSupport.swift b/Cartography/LayoutSupport.swift index 281a99b8..2e2c9688 100644 --- a/Cartography/LayoutSupport.swift +++ b/Cartography/LayoutSupport.swift @@ -11,7 +11,7 @@ import Foundation #if os(iOS) || os(tvOS) import UIKit - public final class LayoutSupport: LayoutItem { + public final class LayoutSupport: LayoutItem, SupportsHeightAnchor, SupportsTopAnchor, SupportsBottomAnchor { let layoutGuide : UILayoutSupport init(layoutGuide: UILayoutSupport) { @@ -23,6 +23,21 @@ import Foundation } } + @available(iOS 10.0, macOS 10.12, tvOS 10.0, *) + extension LayoutSupport { + public var heightAnchor: NSLayoutDimension { + self.layoutGuide.heightAnchor + } + + public var topAnchor: NSLayoutYAxisAnchor { + self.layoutGuide.topAnchor + } + + public var bottomAnchor: NSLayoutYAxisAnchor { + self.layoutGuide.bottomAnchor + } + } + public extension UIViewController { var car_topLayoutGuide : LayoutSupport { get { diff --git a/Cartography/View.swift b/Cartography/View.swift index 5ac40ef4..62c51c08 100644 --- a/Cartography/View.swift +++ b/Cartography/View.swift @@ -12,7 +12,7 @@ import Foundation import UIKit public typealias View = UIView - extension UIView: LayoutItem { + extension UIView: LayoutItem, SupportsPositioningAnchors, SupportsBaselineAnchors { public func asProxy(context: Context) -> ViewProxy { return ViewProxy(context: context, view: self) } @@ -21,7 +21,7 @@ import Foundation import AppKit public typealias View = NSView - extension NSView: LayoutItem { + extension NSView: LayoutItem, SupportsPositioningAnchors, SupportsBaselineAnchors { public func asProxy(context: Context) -> ViewProxy { return ViewProxy(context: context, view: self) } diff --git a/CartographyTests/OffsetDimensionSpec.swift b/CartographyTests/OffsetDimensionSpec.swift new file mode 100644 index 00000000..f6481193 --- /dev/null +++ b/CartographyTests/OffsetDimensionSpec.swift @@ -0,0 +1,212 @@ +import Cartography + +import Nimble +import Quick + +class OffsetDimensionSpec: QuickSpec { + override func spec() { + var window: TestWindow! + var view: TestView! + + beforeEach { + window = TestWindow(frame: CGRect(x: 0, y: 0, width: 600, height: 600)) + + view = TestView(frame: CGRect.zero) + + constrain(view) { view in + view.height == 200 + view.width == 200 + } + + window.addSubview(view) + } + + describe("X-Axis Offset") { + it("should support relative equalities to other offsets") { + constrain(view) { view in + view.superview!.left |-| view.left == view.right |-| view.superview!.right + } + + window.layoutIfNeeded() + + expect(view.frame.minX).to(equal(200)) + } + + it("should support relative equalities to real dimensions") { + constrain(view) { view in + view.superview!.left |-| view.left == view.width + view.width == view.right |-| view.superview!.right + } + + window.layoutIfNeeded() + + expect(view.frame.minX).to(equal(200)) + } + + it("should support relative inequalities") { + constrain(view) { view in + view.superview!.left |-| view.left >= view.width + view.superview!.left |-| view.left <= view.width + } + + window.layoutIfNeeded() + + expect(view.frame.minX).to(equal(200)) + } + + it("should support addition") { + constrain(view) { view in + view.superview!.left |-| view.left == view.width + 100 + } + + window.layoutIfNeeded() + + expect(view.frame.minX).to(equal(300)) + } + + it("should support subtraction") { + constrain(view) { view in + view.superview!.left |-| view.left == view.width - 100 + } + + window.layoutIfNeeded() + + expect(view.frame.minX).to(equal(100)) + } + + it("should support multiplication") { + constrain(view) { view in + view.superview!.left |-| view.left == view.width * 2 + } + + window.layoutIfNeeded() + + expect(view.frame.minX).to(equal(400)) + } + + it("should support division") { + constrain(view) { view in + view.superview!.left |-| view.left == view.width / 2 + } + + window.layoutIfNeeded() + + expect(view.frame.minX).to(equal(100)) + } + + it("should support complex expressions") { + constrain(view) { view in + view.superview!.left |-| view.left == view.width / 2 + 100 + } + + window.layoutIfNeeded() + + expect(view.frame.minX).to(equal(200)) + } + + it("should support numerical equalities") { + constrain(view) { view in + view.superview!.left |-| view.left == 200 + } + + window.layoutIfNeeded() + + expect(view.frame.minX).to(equal(200)) + } + } + + describe("Y-Axis Offset") { + it("should support relative equalities to other offsets") { + constrain(view) { view in + view.superview!.top |-| view.top == view.bottom |-| view.superview!.bottom + } + + window.layoutIfNeeded() + + expect(view.frame.minY).to(equal(200)) + } + + it("should support relative equalities to real dimensions") { + constrain(view) { view in + view.superview!.top |-| view.top == view.height + view.height == view.bottom |-| view.superview!.bottom + } + + window.layoutIfNeeded() + + expect(view.frame.minY).to(equal(200)) + } + + it("should support relative inequalities") { + constrain(view) { view in + view.superview!.top |-| view.top >= view.width + view.superview!.top |-| view.top <= view.width + } + + window.layoutIfNeeded() + + expect(view.frame.minY).to(equal(200)) + } + + it("should support addition") { + constrain(view) { view in + view.superview!.top |-| view.top == view.height + 100 + } + + window.layoutIfNeeded() + + expect(view.frame.minY).to(equal(300)) + } + + it("should support subtraction") { + constrain(view) { view in + view.superview!.top |-| view.top == view.height - 100 + } + + window.layoutIfNeeded() + + expect(view.frame.minY).to(equal(100)) + } + + it("should support multiplication") { + constrain(view) { view in + view.superview!.top |-| view.top == view.height * 2 + } + + window.layoutIfNeeded() + + expect(view.frame.minY).to(equal(400)) + } + + it("should support division") { + constrain(view) { view in + view.superview!.top |-| view.top == view.height / 2 + } + + window.layoutIfNeeded() + + expect(view.frame.minY).to(equal(100)) + } + + it("should support complex expressions") { + constrain(view) { view in + view.superview!.top |-| view.top == view.height / 2 + 100 + } + + window.layoutIfNeeded() + + expect(view.frame.minY).to(equal(200)) + } + + it("should support numerical equalities") { + constrain(view) { view in + view.superview!.top |-| view.top == 200 + } + + window.layoutIfNeeded() + + expect(view.frame.minY).to(equal(200)) + } + } + } +}