Initial Commit

This commit is contained in:
relikd
2020-07-02 17:36:08 +02:00
commit 40a52d6966
11 changed files with 623 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "container:../..">
</FileRef>
</Workspace>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1150"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "NO"
buildForArchiving = "NO"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "TextualAutoLayoutTests"
BuildableName = "TextualAutoLayoutTests"
BlueprintName = "TextualAutoLayoutTests"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "TextualAutoLayout"
BuildableName = "TextualAutoLayout"
BlueprintName = "TextualAutoLayout"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "TextualAutoLayoutTests"
BuildableName = "TextualAutoLayoutTests"
BlueprintName = "TextualAutoLayoutTests"
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "TextualAutoLayout"
BuildableName = "TextualAutoLayout"
BlueprintName = "TextualAutoLayout"
ReferencedContainer = "container:">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -0,0 +1,115 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1150"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "TextualAutoLayout"
BuildableName = "TextualAutoLayout"
BlueprintName = "TextualAutoLayout"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "NO"
buildForArchiving = "NO"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "TextualAutoLayoutTests"
BuildableName = "TextualAutoLayoutTests"
BlueprintName = "TextualAutoLayoutTests"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "TextualAutoLayout"
BuildableName = "TextualAutoLayout"
BlueprintName = "TextualAutoLayout"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "TextualAutoLayoutTests"
BuildableName = "TextualAutoLayoutTests"
BlueprintName = "TextualAutoLayoutTests"
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "TextualAutoLayoutTests"
BuildableName = "TextualAutoLayoutTests"
BlueprintName = "TextualAutoLayoutTests"
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "TextualAutoLayout"
BuildableName = "TextualAutoLayout"
BlueprintName = "TextualAutoLayout"
ReferencedContainer = "container:">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

20
LICENSE Normal file
View File

@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2020 Oleg Geier
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

16
Package.swift Normal file
View File

@@ -0,0 +1,16 @@
// swift-tools-version:5.0
import PackageDescription
let package = Package(
name: "TextualAutoLayout",
platforms: [
.macOS(.v10_11), .iOS(.v9), .tvOS(.v9) // watchOS does not support NSLayout
],
products: [
.library(name: "TextualAutoLayout", targets: ["TextualAutoLayout"]),
],
targets: [
.target(name: "TextualAutoLayout", dependencies: []),
.testTarget(name: "TextualAutoLayoutTests", dependencies: ["TextualAutoLayout"]),
]
)

BIN
README-anatomy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

161
README.md Normal file
View File

@@ -0,0 +1,161 @@
# Textual Auto Layout
![macOS 10.11+](https://img.shields.io/badge/macOS-10.12+-888)
![iOS 9.0+](https://img.shields.io/badge/iOS-9.0+-888)
![tvOS 9.0+](https://img.shields.io/badge/tvOS-9.0+-888)
[![GitHub license](https://img.shields.io/github/license/relikd/Textual-Auto-Layout)](LICENSE)
Textual Auto Layout (TAL) helps you create Auto Layout constraints that you can read, understand, and maintain.
In contrast to existing libraries ([SnapKit](https://github.com/SnapKit/SnapKit), [Cartography](https://github.com/robb/Cartography)), TAL follows a minimization approach. The package consists of a single ~120 lines source file, thus TAL won't have every imaginable feature. Instead you get a dependency ideal for security audits: short and tidy.
To install, use Swift Package Manager or simply copy the source file to your project and you're good to go.
## Requirements
- macOS 10.11+ / iOS 9.0+ / tvOS 9.0+
- Swift 5? (probably lower, can't test)
The swift package requires `swift-tools-version:5.0` for the new `platforms` attribute.
## Long story
Lets take a look to Apples documentation archive on Auto Layout Guide, [Anatomy of a Constraint][1]. There is a wonderful picture that illustrates the constraint relation between two views.
> ![anatomy of a constraint](README-anatomy.png)
> Copyright © 2018 Apple Inc ([source][2])
I love the simplicity of the depiction. However, when you start creating constraints programmatically, the same constraint looks more like this:
```swift
let constraint = RedView.leadingAnchor.constraint(equalTo: BlueView.trailingAnchor, constant: 8.0)
constraint.isActive = true
```
Not only is this hard to read but it gets tedious when you have to create a bunch of constraints (and remember to activate all of them).
**Textual Auto Layout** (TAL) to the rescue; This is what the same constraint looks like in TAL:
```swift
RedView.leadingAnchor =&= BlueView.trailingAnchor + 8.0
```
This sets all constraint attributes and activates the constraint too. The only slight deviation to the illustration it uses `=&=` instead of `=`.
There are two reasons for that. The obvious, `=` is already used for assignments. But also, you want to handle the other relations too. You have the `greaterThanOrEqualTo` and `lessThanOrEqualTo` relations at your disposal (`=>=` and `=<=` respectively).
### Going into a different dimension
The example above uses a multiplier of `1.0`. Technically this is correct, though it does not make sense for location attributes. The documentation is clear:
> You cannot use a nonidentity multiplier (a value other than 1.0) with location attributes.
Thats why it is omitted altogether in the `=&=` relation above. But lets take a look where it does make sense, dimensional constraints:
```swift
View.widthAnchor =>= 2 * View.heightAnchor
View.widthAnchor =<= 250
```
These two constraints ensure that the view is at least twice as wide as tall, but at most 250 pt wide. Readability, 100%.
Again, what would you do without TAL?:
```swift
View.widthAnchor.constraint(greaterThanOrEqualTo: View.heightAnchor, multiplier: 2).isActive = true
View.widthAnchor.constraint(lessThanOrEqualToConstant: 250).isActive = true
```
### Prioritize your constraints
Sometimes requiring a specific is just too strong. You can adjust the priority in the same line. Use the pipe symbol (`|`)<a href="#n1" id="n1ref"><sup>1</sup></a> and append the priority as needed:
```swift
View.widthAnchor =&= 250 | .defaultHigh
```
--------------------
<a id="n1" href="#n1ref"><sup>1</sup></a> To be consistent with Apple documentation, I'd like to use `@` for priority assignment (instead of `|`). But sadly you can't use `@` in an operator declaration.
### Doing more things at the same time
Usually you need to set more than a single constraint. For example, you want to set a child view to the same size as the parent view, but inset by 5 px in all directions. You could create these four constraints individually, or use a little helper method:
```swift
Child.anchor([.top, .bottom, .left, .right], to: Parent, padding: 5)
```
The `anchor(_,to:)` method will use the same relation attribute for both views. In this example the code above is equivalent to:
```swift
Child.topAnchor =&= Parent.topAnchor + 5
Child.leftAnchor =&= Parent.leftAnchor + 5
Parent.bottomAnchor =&= Child.bottomAnchor + 5
Parent.rightAnchor =&= Child.rightAnchor + 5
Child.translatesAutoresizingMaskIntoConstraints = false
```
Notice how `bottomAnchor` and `rightAnchor` did change the relation direction instead of using `Child =&= Parent - 5` with a negative constant. This makes it easier when debugging constraints. You can simply look for your chosen `5 px` padding instead of thinking what edges are involved.
Further you should note that `anchor(_,to:)` will set `translatesAutoresizingMaskIntoConstraints` to `false`. Most of the time you'll want that. In fact you'll probably want to set it for single constraints too. But give it some time and you'll see that you often use both constraint types at the same time.
```swift
ChildA.anchor([.leadingMargin, .trailingMargin], to: Parent)
ChildB.anchor([.leadingMargin, .trailingMargin], to: Parent)
ChildB.topAnchor =&= ChildA.bottomAnchor + 8
```
In that case both relevant views (`ChildA` and `ChildB`) have `translatesAutoresizingMaskIntoConstraints` set. Not `Parent` though, which is intended. E.g., `Parent ` may have `autoresizingMask = [.flexibleWidth, .flexibleHeight]`.
#### Limitations
Since the `anchor(_,to:)` reuses the same relation attribute for both sides, you can't connect two different attributes (e.g., `.left =&= .right`). You can resolve this issue with individual constraints.
### Requiring greater control
Even though it wasn't mentioned in the previous example, you can chain `anchor(_,to:)` with a priority, same as with individual constraints. Or more general, the `anchor` method returns a list of constraints. And you can apply functions on constraint lists.
```swift
let x: [NSLayoutConstraint]
x = Child.anchor([.left, .right], to: Parent)
x | .defaultLow
// or short
Child.anchor([.left, .right], to: Parent) | .defaultLow
```
It doesn't stop there. I haven't mention it in the beginning, but every TAL expression has a return type.
```swift
View.widthAnchor =<= 250 // returns NSLayoutConstraint
```
This allows you to chain, reuse or store constraints for later usage.
```swift
let compactConstraints = [
View.widthAnchor =&= 120,
View.heightAnchor =&= 35
].setActive(false)
let regularConstraints = [
View.widthAnchor =<= 250,
View.heightAnchor =<= 50
] // implicitly isActive = true
regularConstraints | .fittingSizeLevel
```
And lastly, though not as helpful as the other features. You can constraint the intrinsic content size.
```swift
Label.constrainHuggingCompression(.vertical, .required)
// which is equivalent to:
Label.setContentHuggingPriority(.vertical, for: .required)
Label.setContentCompressionResistancePriority(.vertical, for: .required)
```
[1]: https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/AnatomyofaConstraint.html
[2]: https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/Art/view_formula_2x.png

View File

@@ -0,0 +1,124 @@
#if os(macOS)
import AppKit
public typealias ConstraintPriority = NSLayoutConstraint.Priority
public typealias ConstraintAxis = NSLayoutConstraint.Orientation
public typealias ConstraintView = NSView
#else
import UIKit
public typealias ConstraintPriority = UILayoutPriority
public typealias ConstraintAxis = NSLayoutConstraint.Axis
public typealias ConstraintView = UIView
#endif
/*
Readable Auto Layout Constraints
Usage:
A.anchor =&= multiplier * B.anchor + constant | priority
*/
precedencegroup ReadableLayoutPrecedence {
higherThan: AdditionPrecedence
lowerThan: MultiplicationPrecedence
}
infix operator =&= : ReadableLayoutPrecedence
infix operator =<= : ReadableLayoutPrecedence
infix operator =>= : ReadableLayoutPrecedence
/// Create and activate an `equal` constraint between left and right anchor. Format: `A.anchor =&= multiplier * B.anchor + constant | priority`
@discardableResult public func =&= <T>(l: NSLayoutAnchor<T>, r: NSLayoutAnchor<T>) -> NSLayoutConstraint { l.constraint(equalTo: r).on() }
/// Create and activate a `lessThan` constraint between left and right anchor. Format: `A.anchor =<= multiplier * B.anchor + constant | priority`
@discardableResult public func =<= <T>(l: NSLayoutAnchor<T>, r: NSLayoutAnchor<T>) -> NSLayoutConstraint { l.constraint(lessThanOrEqualTo: r).on() }
/// Create and activate a `greaterThan` constraint between left and right anchor. Format: `A.anchor =>= multiplier * B.anchor + constant | priority`
@discardableResult public func =>= <T>(l: NSLayoutAnchor<T>, r: NSLayoutAnchor<T>) -> NSLayoutConstraint { l.constraint(greaterThanOrEqualTo: r).on() }
public extension NSLayoutDimension { // higher precedence, so multiply first
/// Create intermediate anchor multiplier result.
static func *(l: CGFloat, r: NSLayoutDimension) -> AnchorMultiplier { .init(anchor: r, m: l) }
/// Create and activate an `equal` constraint with constant value. Format: `A.anchor =&= constant | priority`
@discardableResult static func =&=(l: NSLayoutDimension, r: CGFloat) -> NSLayoutConstraint { l.constraint(equalToConstant: r).on() }
/// Create and activate a `lessThan` constraint with constant value. Format: `A.anchor =<= constant | priority`
@discardableResult static func =<=(l: NSLayoutDimension, r: CGFloat) -> NSLayoutConstraint { l.constraint(lessThanOrEqualToConstant: r).on() }
/// Create and activate a `greaterThan` constraint with constant value. Format: `A.anchor =>= constant | priority`
@discardableResult static func =>=(l: NSLayoutDimension, r: CGFloat) -> NSLayoutConstraint { l.constraint(greaterThanOrEqualToConstant: r).on() }
}
/// Intermediate `NSLayoutConstraint` anchor with multiplier supplement
public struct AnchorMultiplier {
fileprivate let anchor: NSLayoutDimension, m: CGFloat
}
public extension AnchorMultiplier {
/// Create and activate an `equal` constraint between left and right anchor. Format: `A.anchor =&= multiplier * B.anchor + constant | priority`
@discardableResult static func =&=(l: NSLayoutDimension, r: Self) -> NSLayoutConstraint { l.constraint(equalTo: r.anchor, multiplier: r.m).on() }
/// Create and activate a `lessThan` constraint between left and right anchor. Format: `A.anchor =<= multiplier * B.anchor + constant | priority`
@discardableResult static func =<=(l: NSLayoutDimension, r: Self) -> NSLayoutConstraint { l.constraint(lessThanOrEqualTo: r.anchor, multiplier: r.m).on() }
/// Create and activate a `greaterThan` constraint between left and right anchor. Format: `A.anchor =>= multiplier * B.anchor + constant | priority`
@discardableResult static func =>=(l: NSLayoutDimension, r: Self) -> NSLayoutConstraint { l.constraint(greaterThanOrEqualTo: r.anchor, multiplier: r.m).on() }
}
public extension NSLayoutConstraint {
/// Change `isActive`to `true` and return `self`
func on() -> Self { isActive = true; return self }
/// Change `isActive`to `false` and return `self`
func off() -> Self { isActive = false; return self }
/// Change `constant`attribute and return `self`
@discardableResult static func +(l: NSLayoutConstraint, r: CGFloat) -> NSLayoutConstraint { l.constant = r; return l }
/// Change `constant` attribute and return `self`
@discardableResult static func -(l: NSLayoutConstraint, r: CGFloat) -> NSLayoutConstraint { l.constant = -r; return l }
/// Change `priority` attribute and return `self`
@discardableResult static func |(l: NSLayoutConstraint, r: ConstraintPriority) -> NSLayoutConstraint { l.priority = r; return l }
}
/*
UIView extension to generate multiple constraints at once
Usage:
child.anchor([.width, .height], to: parent) | .defaultLow
*/
public extension ConstraintView {
#if os(macOS)
/// Edges that need the relation to flip arguments. For these we need to inverse the constant value and relation.
private static let inverseItem: [NSLayoutConstraint.Attribute] = [.right, .bottom, .trailing, .lastBaseline]
#else
/// Edges that need the relation to flip arguments. For these we need to inverse the constant value and relation.
private static let inverseItem: [NSLayoutConstraint.Attribute] = [.right, .bottom, .trailing, .lastBaseline, .rightMargin, .bottomMargin, .trailingMargin]
#endif
/// Create and active constraints for provided edges. Constraints will anchor the same edge on both `self` and `other`.
/// - Note: Will set `translatesAutoresizingMaskIntoConstraints = false`
/// - Parameters:
/// - edges: List of constraint attributes, e.g. `[.top, .bottom, .left, .right]`
/// - other: Instance to bind to, e.g. `UIView` or `UILayoutGuide`
/// - padding: Used as constant value. Multiplier will always be `1.0`. If you need to change the multiplier, use single constraints instead. (Default: `0`)
/// - rel: Constraint relation. (Default: `.equal`)
/// - Returns: List of created and active constraints
@discardableResult func anchor(_ edges: [NSLayoutConstraint.Attribute], to other: Any, padding: CGFloat = 0, if rel: NSLayoutConstraint.Relation = .equal) -> [NSLayoutConstraint] {
translatesAutoresizingMaskIntoConstraints = false
return edges.map {
let (A, B) = Self.inverseItem.contains($0) ? (other, self) : (self, other)
return NSLayoutConstraint(item: A, attribute: $0, relatedBy: rel, toItem: B, attribute: $0, multiplier: 1, constant: padding).on()
}
}
/// Sets the priority with which a view resists being made smaller and larger than its intrinsic size.
func constrainHuggingCompression(_ axis: ConstraintAxis, _ priotity: ConstraintPriority) {
setContentHuggingPriority(priotity, for: axis)
setContentCompressionResistancePriority(priotity, for: axis)
}
}
public extension Array where Element: NSLayoutConstraint {
/// set `priority` on all elements and return same list
@discardableResult static func |(l: Self, r: ConstraintPriority) -> Self {
for x in l { x.priority = r }
return l
}
/// set `isActive` on all elements and return `self`
@discardableResult func setActive(_ flag: Bool) -> Self {
flag ? NSLayoutConstraint.activate(self) : NSLayoutConstraint.deactivate(self)
return self
}
}

View File

@@ -0,0 +1,76 @@
import XCTest
@testable import TextualAutoLayout
final class TextualAutoLayoutTests: XCTestCase {
var parent, A, B: ConstraintView!
override func setUp() {
parent = ConstraintView()
A = ConstraintView()
B = ConstraintView()
parent.addSubview(A)
parent.addSubview(B)
}
func testBasic() {
let x = A.bottomAnchor =&= B.topAnchor + 20.3 | .defaultLow
XCTAssertEqual(x.relation, NSLayoutConstraint.Relation.equal)
XCTAssertEqual(x.priority, ConstraintPriority.defaultLow)
XCTAssertEqual(x.constant, 20.3)
XCTAssertEqual(x.isActive, true)
}
func testDimensional() {
let x = A.heightAnchor =<= 2 * B.widthAnchor - 4
XCTAssertEqual(x.relation, NSLayoutConstraint.Relation.lessThanOrEqual)
XCTAssertEqual(x.priority, ConstraintPriority.required)
XCTAssertEqual(x.multiplier, 2)
XCTAssertEqual(x.constant, -4)
XCTAssertEqual(x.isActive, true)
XCTAssertEqual((A.widthAnchor =>= 2 * A.heightAnchor).constant, 0)
XCTAssertEqual((A.widthAnchor =<= 250).constant, 250)
}
func testIntrinsicContent() {
A.constrainHuggingCompression(.vertical, .required)
// newly set values
XCTAssertEqual(A.contentCompressionResistancePriority(for: .vertical), ConstraintPriority.required)
XCTAssertEqual(A.contentHuggingPriority(for: .vertical), ConstraintPriority.required)
// default values
XCTAssertEqual(A.contentCompressionResistancePriority(for: .horizontal), ConstraintPriority.defaultHigh)
XCTAssertEqual(A.contentHuggingPriority(for: .horizontal), ConstraintPriority.defaultLow)
}
func testReturnConstraint() {
XCTAssertEqual((A.rightAnchor =&= B.leftAnchor).firstItem as? ConstraintView, A)
}
func testViewMultiConstraint() {
A.anchor([.left, .right, .top], to: parent!, padding: 76) | .defaultHigh
let x = parent.constraints
XCTAssertEqual(x.count, 3)
XCTAssertEqual(A.translatesAutoresizingMaskIntoConstraints, false)
XCTAssertEqual(parent.translatesAutoresizingMaskIntoConstraints, true)
for u in x {
XCTAssertEqual(u.relation, NSLayoutConstraint.Relation.equal)
XCTAssertEqual(u.priority, ConstraintPriority.defaultHigh)
XCTAssertEqual(u.constant, 76)
XCTAssertEqual(u.firstAttribute, u.secondAttribute)
XCTAssertEqual(u.isActive, true)
let first = u.firstItem as? ConstraintView
let second = u.secondItem as? ConstraintView
if u.firstAttribute == .right {
XCTAssertEqual(first, parent)
XCTAssertEqual(second, A)
} else {
XCTAssertEqual(first, A)
XCTAssertEqual(second, parent)
}
}
x.setActive(false)
for u in x { XCTAssertEqual(u.isActive, false) }
}
}