├── .codeclimate.yml
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
└── workflows
│ └── build-test.yml
├── .gitignore
├── .slather.yml
├── .swift-version
├── .swiftlint.yml
├── CODEOWNERS
├── Gemfile
├── Gemfile.lock
├── LICENSE
├── Package.swift
├── README.md
├── Sources
└── WWLayout
│ ├── Anchorable.swift
│ ├── Info.plist
│ ├── Insets.swift
│ ├── Layout+Activate.swift
│ ├── Layout+Center.swift
│ ├── Layout+Edges.swift
│ ├── Layout+Fill.swift
│ ├── Layout+Size.swift
│ ├── Layout.swift
│ ├── LayoutAnchor.swift
│ ├── LayoutAxis.swift
│ ├── LayoutConstraint.swift
│ ├── LayoutDimension.swift
│ ├── LayoutEdge.swift
│ ├── LayoutPriority.swift
│ ├── LayoutRelation.swift
│ ├── LayoutView.swift
│ ├── SizeClass.swift
│ ├── UITraitCollection+Active.swift
│ ├── UIView+Containment.swift
│ ├── UIView+Layout.swift
│ └── WWLayout.h
├── Tests
└── WWLayoutTests
│ ├── BasicViewToSuperviewTests.swift
│ ├── ConstraintArrayTests.swift
│ ├── FillTests.swift
│ ├── Info.plist
│ ├── InsetTests.swift
│ ├── LayoutViewTests.swift
│ ├── PriorityTests.swift
│ ├── SafeAreaTests.swift
│ ├── SiblingTests.swift
│ ├── SizeClassTests.swift
│ ├── StackingTests.swift
│ ├── SwiftCompatibility.swift
│ ├── TaggingTests.swift
│ └── UIView+Subviews.swift
├── WWLayout.podspec
├── WWLayout.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── xcshareddata
│ ├── IDETemplateMacros.plist
│ └── xcschemes
│ └── WWLayout.xcscheme
├── WWLayoutExample
├── WWLayoutExample.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ └── xcshareddata
│ │ ├── IDETemplateMacros.plist
│ │ └── xcschemes
│ │ └── WWLayoutExample.xcscheme
├── WWLayoutExample
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ │ ├── AppIcon.appiconset
│ │ │ ├── Contents.json
│ │ │ ├── Icon-1024.png
│ │ │ ├── Icon-120.png
│ │ │ ├── Icon-121.png
│ │ │ ├── Icon-152.png
│ │ │ ├── Icon-167.png
│ │ │ ├── Icon-180.png
│ │ │ ├── Icon-20.png
│ │ │ ├── Icon-29.png
│ │ │ ├── Icon-40.png
│ │ │ ├── Icon-41.png
│ │ │ ├── Icon-42.png
│ │ │ ├── Icon-58.png
│ │ │ ├── Icon-59.png
│ │ │ ├── Icon-60.png
│ │ │ ├── Icon-76.png
│ │ │ ├── Icon-80.png
│ │ │ ├── Icon-81.png
│ │ │ └── Icon-87.png
│ │ ├── Contents.json
│ │ └── logo.imageset
│ │ │ ├── Contents.json
│ │ │ └── logo.png
│ ├── Base.lproj
│ │ └── LaunchScreen.storyboard
│ ├── Info.plist
│ ├── SampleListViewController.swift
│ └── Samples
│ │ ├── Baselines.swift
│ │ ├── BasicSample.swift
│ │ ├── CenterEdges.swift
│ │ ├── FillWidthSample.swift
│ │ ├── LayoutMarginsSample.swift
│ │ ├── SafeAreaSample.swift
│ │ ├── SampleViewController.swift
│ │ ├── SizeClassSample.swift
│ │ ├── TaggingSample.swift
│ │ └── ThreeEdges.swift
└── WWLayoutTV
│ ├── WWLayoutTV.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ └── xcshareddata
│ │ ├── IDETemplateMacros.plist
│ │ └── xcschemes
│ │ └── WWLayoutTV.xcscheme
│ ├── WWLayoutTV
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ │ ├── App Icon & Top Shelf Image.brandassets
│ │ │ ├── App Icon - App Store.imagestack
│ │ │ │ ├── Back.imagestacklayer
│ │ │ │ │ ├── Content.imageset
│ │ │ │ │ │ └── Contents.json
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── Contents.json
│ │ │ │ ├── Front.imagestacklayer
│ │ │ │ │ ├── Content.imageset
│ │ │ │ │ │ └── Contents.json
│ │ │ │ │ └── Contents.json
│ │ │ │ └── Middle.imagestacklayer
│ │ │ │ │ ├── Content.imageset
│ │ │ │ │ └── Contents.json
│ │ │ │ │ └── Contents.json
│ │ │ ├── App Icon.imagestack
│ │ │ │ ├── Back.imagestacklayer
│ │ │ │ │ ├── Content.imageset
│ │ │ │ │ │ └── Contents.json
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── Contents.json
│ │ │ │ ├── Front.imagestacklayer
│ │ │ │ │ ├── Content.imageset
│ │ │ │ │ │ └── Contents.json
│ │ │ │ │ └── Contents.json
│ │ │ │ └── Middle.imagestacklayer
│ │ │ │ │ ├── Content.imageset
│ │ │ │ │ └── Contents.json
│ │ │ │ │ └── Contents.json
│ │ │ ├── Contents.json
│ │ │ ├── Top Shelf Image Wide.imageset
│ │ │ │ └── Contents.json
│ │ │ └── Top Shelf Image.imageset
│ │ │ │ └── Contents.json
│ │ └── Contents.json
│ ├── Base.lproj
│ │ └── LaunchScreen.storyboard
│ ├── Info.plist
│ └── ViewController.swift
│ └── WWLayoutTVTests
│ ├── Info.plist
│ └── WWLayoutTVTests.swift
├── docs
├── _config.yml
├── _layouts
│ └── default.html
├── assets
│ ├── css
│ │ └── style.scss
│ └── images
│ │ └── logo.png
├── constraining-targets.md
├── contributing-guidelines.md
├── from-native-constraints.md
├── from-purelayout.md
├── index.md
├── method-reference.md
├── middle-out-parameters.md
└── size-classes.md
└── logo.png
/.codeclimate.yml:
--------------------------------------------------------------------------------
1 | version: "2"
2 | checks: # Defaults: https://docs.codeclimate.com/docs/advanced-configuration#section-default-checks
3 | argument-count:
4 | enabled: true
5 | config:
6 | threshold: 4
7 | complex-logic:
8 | enabled: true
9 | config:
10 | threshold: 4
11 | file-lines:
12 | enabled: true
13 | config:
14 | threshold: 250
15 | method-complexity:
16 | enabled: true
17 | config:
18 | threshold: 5
19 | method-count:
20 | enabled: true
21 | config:
22 | threshold: 20
23 | method-lines:
24 | enabled: true
25 | config:
26 | threshold: 25
27 | nested-control-flow:
28 | enabled: true
29 | config:
30 | threshold: 4
31 | return-statements:
32 | enabled: true
33 | config:
34 | threshold: 4
35 | similar-code:
36 | enabled: false
37 | config:
38 | threshold: #language-specific defaults. overrides affect all languages.
39 | identical-code:
40 | enabled: true
41 | config:
42 | threshold: #language-specific defaults. overrides affect all languages.
43 |
44 | exclude_patterns:
45 | - "bin/"
46 | - "WWLayoutExample/"
47 | - "WWLayoutTests/"
48 | - "**/*.jpg"
49 | - "**/*.json"
50 | - "**/*.plist"
51 | - "**/*.png"
52 | - "**/*.storyboard"
53 | - "**/*.strings"
54 | - "**/*.ttf"
55 | - ".*"
56 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: "[BUG]"
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Please include the code that is causing the issue. If you can boil the issue down to a sample project, that's even better.
15 |
16 | **Expected behavior**
17 | A clear and concise description of what you expected to happen.
18 |
19 | **Screenshots**
20 | If applicable, add screenshots to help explain your problem.
21 |
22 | **Environment:**
23 | - OS: [e.g. iOS 10.3]
24 | - Device [e.g. iPhone X]
25 |
26 | **Additional context**
27 | Add any other context about the problem here.
28 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/workflows/build-test.yml:
--------------------------------------------------------------------------------
1 | name: Build and test
2 |
3 | on:
4 | pull_request:
5 | workflow_dispatch:
6 |
7 | jobs:
8 | build-test-macos12:
9 | runs-on: macos-12
10 | strategy:
11 | matrix:
12 | xcode-version: [13.4.1, 14.0.1]
13 |
14 | steps:
15 | - name: Checkout
16 | uses: actions/checkout@v2
17 |
18 | - name: Install dependencies in Gemfile
19 | run: gem install fastlane
20 |
21 | - name: Unit Tests, using Xcode ${{ matrix.xcode-version }}
22 | run: fastlane scan --devices "iPhone 8,iPhone 11"
23 | env:
24 | DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode-version }}.app
25 |
26 | build-test-macos13:
27 | runs-on: macos-13
28 | strategy:
29 | matrix:
30 | xcode-version: [14.2, 14.3]
31 |
32 | steps:
33 | - name: Checkout
34 | uses: actions/checkout@v2
35 |
36 | - name: Install dependencies in Gemfile
37 | run: gem install fastlane
38 |
39 | - name: Unit Tests, using Xcode ${{ matrix.xcode-version }}
40 | run: fastlane scan --devices "iPhone 8,iPhone 13"
41 | env:
42 | DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode-version }}.app
43 |
44 | build-spm:
45 | runs-on: macos-latest
46 | steps:
47 | - name: Checkout
48 | uses: actions/checkout@v2
49 |
50 | - name: Build spm
51 | working-directory: WWLayoutExample/WWLayoutTV
52 | run: |
53 | xcodebuild build \
54 | -project "WWLayoutTV.xcodeproj" \
55 | -scheme "WWLayoutTV" \
56 | -destination "platform=tvOS Simulator,name=Apple TV"
57 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData/
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata/
19 |
20 | ## Other
21 | *.moved-aside
22 | *.xccheckout
23 | *.xcscmblueprint\
24 | .DS_Store
25 |
26 | ## Obj-C/Swift specific
27 | *.hmap
28 | *.ipa
29 | *.dSYM.zip
30 | *.dSYM
31 |
32 | ## Playgrounds
33 | timeline.xctimeline
34 | playground.xcworkspace
35 |
36 | ## Swift package manager
37 | Packages/
38 | Package.pins
39 | Package.resolved
40 | .swiftpm
41 |
42 | # fastlane
43 | #
44 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
45 | # screenshots whenever they are needed.
46 | # For more information about the recommended setup visit:
47 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
48 |
49 | fastlane/report.xml
50 | fastlane/Preview.html
51 | fastlane/screenshots
52 | fastlane/test_output
53 | test_output
54 |
--------------------------------------------------------------------------------
/.slather.yml:
--------------------------------------------------------------------------------
1 | coverage_service: cobertura_xml
2 | xcodeproj: WWLayout.xcodeproj
3 | scheme: WWLayout
4 | binary_basename: WWLayout
5 | #output_directory: output/slather
6 |
--------------------------------------------------------------------------------
/.swift-version:
--------------------------------------------------------------------------------
1 | 4.0
--------------------------------------------------------------------------------
/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | #---
2 | #--- Rules
3 |
4 | # Find all the available rules by running:
5 | # swiftlint rules
6 |
7 | disabled_rules: # rule identifiers to exclude from running
8 | - identifier_name
9 | - line_length
10 | - no_extension_access_modifier
11 | - statement_position
12 | - shorthand_operator
13 | - multiple_closures_with_trailing_closure
14 |
15 | opt_in_rules: # some rules are only opt-in
16 | - anyobject_protocol
17 | - collection_alignment
18 | - closure_spacing
19 | - closure_parameter_position
20 | - colon
21 | - comma
22 | - compiler_protocol_init
23 | - contains_over_filter_count
24 | - contains_over_filter_is_empty
25 | - contains_over_first_not_nil
26 | - control_statement
27 | - convenience_type
28 | - deployment_target
29 | - discarded_notification_center_observer
30 | - duplicate_enum_cases
31 | - duplicate_imports
32 | - empty_count
33 | - empty_enum_arguments
34 | - empty_string
35 | - empty_xctest_method
36 | - explicit_init
37 | - first_where
38 | - force_cast
39 | - force_try
40 | - force_unwrapping
41 | - inert_defer
42 | - last_where
43 | - legacy_multiple
44 | - legacy_random
45 | - modifier_order
46 | - multiline_arguments
47 | - multiline_function_chains
48 | - multiline_parameters
49 | - no_space_in_method_call
50 | - opening_brace
51 | - operator_usage_whitespace
52 | - overridden_super_call
53 | - prohibited_super_call
54 | - redundant_discardable_let
55 | - redundant_nil_coalescing
56 | - redundant_optional_initialization
57 | - redundant_string_enum_value
58 | - return_arrow_whitespace
59 | - static_operator
60 | - superfluous_disable_command
61 | - toggle_bool
62 | - trailing_comma
63 | - trailing_newline
64 | - trailing_semicolon
65 | - trailing_whitespace
66 | - unused_declaration
67 | - vertical_parameter_alignment_on_call
68 | - yoda_condition
69 |
70 | custom_rules:
71 | no_space_after_opening_parentheses:
72 | name: "No space after opening parentheses"
73 | message: "Please avoid using space after opening parentheses"
74 | regex: '\(\h+'
75 |
76 | anonymous_init:
77 | name: "Anonymous init()"
78 | message: "Prefer explicit type initializer over anonymous calls to init()"
79 | regex: '(\h+|\()\.init\('
80 |
81 | #---
82 | #--- Rule configuration
83 |
84 | # configurable rules can be customized from this configuration file
85 | # binary rules can set their severity level
86 | # rules that have both warning and error levels, can set just the warning level
87 | # or they can set both explicitly
88 |
89 | cyclomatic_complexity:
90 | warning: 10
91 | error: 20
92 | ignores_case_statements: true
93 |
94 | force_cast: error
95 | force_try: error
96 | force_unwrapping: error
97 |
98 | function_body_length:
99 | warning: 50 # default is 40
100 | error: 120 # default is 100
101 |
102 | function_parameter_count:
103 | warning: 6
104 | error: 9
105 |
106 | nesting:
107 | type_level:
108 | warning: 3 # default is 1
109 |
110 | trailing_whitespace:
111 | ignores_empty_lines: true
112 |
113 | type_body_length:
114 | warning: 500 # default is 200
115 | error: 1000 # default is 350
116 |
117 | unused_setter_value: error
118 |
119 |
120 | #---
121 | #--- Paths
122 |
123 | #included: # paths to include during linting. `--path` is ignored if present.
124 | # - ../Pod
125 | # In ios-common-config, the path to the sources will be done via the --path argument
126 |
127 | excluded: # paths to ignore during linting. Takes precedence over `included`.
128 | - ./Example/Pods
129 | - ./Pods
130 | - ./vendor
131 | - ./fastlane
132 |
133 | reporter: "xcode" # reporter type (xcode, json, csv, checkstyle, junit, html, emoji)
134 |
--------------------------------------------------------------------------------
/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # Each line is a file pattern followed by one or more owners.
2 | # See: https://github.com/blog/2392-introducing-code-owners
3 | # See: https://help.github.com/articles/about-codeowners/
4 |
5 | # These owners will be the default owners for everything in
6 | # the repo. Unless a later match takes precedence,
7 | # the following people will be requested for review
8 | # when someone opens a pull request.
9 | * @g-mark @prnewman
10 |
11 | # Order is important; the last matching pattern takes the most
12 | # precedence. When someone opens a pull request that only
13 | # modifies JS files, only @js-owner and not the global
14 | # owner(s) will be requested for a review.
15 | #*.js @js-owner
16 |
17 | # You can also use email addresses if you prefer. They'll be
18 | # used to look up users just like we do for commit author
19 | # emails.
20 | #*.go docs@example.com
21 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | # Gemfile
2 | source 'https://rubygems.org'
3 |
4 | #gem 'slather', '>= 2.4.7'
5 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 |
5 | PLATFORMS
6 | ruby
7 |
8 | DEPENDENCIES
9 |
10 | BUNDLED WITH
11 | 2.1.4
12 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.1
2 |
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "WWLayout",
7 | platforms: [
8 | .iOS(.v10),
9 | .tvOS(.v10)
10 | ],
11 | products: [
12 | .library(
13 | name: "WWLayout",
14 | targets: ["WWLayout"]),
15 | ],
16 | dependencies: [
17 | ],
18 | targets: [
19 | .target(
20 | name: "WWLayout",
21 | dependencies: []),
22 | .testTarget(
23 | name: "WWLayoutTests",
24 | dependencies: ["WWLayout"])
25 | ]
26 | )
27 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | WWLayout
5 |
6 |
11 |
12 |
13 |
14 |
15 | Easy to write auto layout constraints, with minimal extensions to standard namespaces.
16 |
17 | ## Feature Highlights
18 | * Easy to use, readable API
19 | * Backwards-compatible (i.e. pre iOS 11) Safe Area constraints
20 | * Tag constraints to easily switch between different layouts (coming soon)
21 | * Automatic switching of size-class based constraints (coming soon)
22 |
23 | ## Introduction
24 |
25 | Constraints are added to a view using the view's `layout` property, like so:
26 |
27 | ```swift
28 | myView.layout.width(400)
29 | ```
30 |
31 | Multiple constraints are easily added by chaining calls:
32 |
33 | ```swift
34 | myView.layout.width(400).height(200)
35 | ```
36 |
37 | A more complicated example:
38 |
39 | ```swift
40 | let container = UIView()
41 | let child = UIView()
42 |
43 | container.layout.fill(.safeArea)
44 |
45 | child.layout
46 | .fill(container, axis: .x, inset: 20)
47 | .center(in: container, axis: .y, priority: .high)
48 | .top(.lessOrEqual, to: container, offset: 100)
49 | .height(toWidth: 0.5)
50 | ```
51 |
52 | ## Dcumentation
53 |
54 | ### [Documentation can be found here](//ww-tech.github.io/wwlayout/)
55 |
56 |
57 | ## Installation
58 |
59 | ### Swift Package Manager
60 |
61 | The WWLayout Package URL is:
62 |
63 | ```
64 | https://github.com/ww-tech/wwlayout.git
65 | ```
66 |
67 | Add the package dependency to your Xcode project using the `File` -> `Swift Packages` -> `Add Package Dependency...` menu item.
68 |
69 | ### Cocoapods
70 |
71 | Simply add WWLayout to your `Podfile`:
72 |
73 | ```
74 | pod 'WWLayout'
75 | ```
76 |
77 | ## Contributing
78 |
79 | ## Authors
80 | * [Steven Grosmark](https://github.com/g-mark), steven.grosmark@ww.com
81 | * WW iOS Team
82 |
83 | ## License
84 | WWLayout is © copyright by WW International.
85 |
86 | WWLayout is licensed under the [Apache-2.0 Open Source license](http://choosealicense.com/licenses/apache-2.0/).
87 |
--------------------------------------------------------------------------------
/Sources/WWLayout/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 | NSPrincipalClass
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Sources/WWLayout/Insets.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ===----------------------------------------------------------------------===//
3 | //
4 | // Insets.swift
5 | //
6 | // Created by Steven Grosmark on 12/1/17.
7 | // Copyright © 2018 WW International, Inc.
8 | //
9 | //
10 | // This source file is part of the WWLayout open source project
11 | //
12 | // https://github.com/ww-tech/wwlayout
13 | //
14 | // Copyright © 2017-2021 WW International, Inc.
15 | //
16 | // Licensed under the Apache License, Version 2.0 (the "License");
17 | // you may not use this file except in compliance with the License.
18 | // You may obtain a copy of the License at
19 | //
20 | // http://www.apache.org/licenses/LICENSE-2.0
21 | //
22 | // Unless required by applicable law or agreed to in writing, software
23 | // distributed under the License is distributed on an "AS IS" BASIS,
24 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25 | // See the License for the specific language governing permissions and
26 | // limitations under the License.
27 | //
28 | // ===----------------------------------------------------------------------===//
29 | //
30 |
31 | import UIKit
32 |
33 | /// Insets
34 | /// Simple struct for managing insets
35 | /// Positive numbers represent an inset that is inside - or smaller - than a reference object.
36 | /// Negative numbers represent an inset that is outside - or larger - than a reference object.
37 | public struct Insets {
38 | public var top: CGFloat
39 | public var left: CGFloat
40 | public var bottom: CGFloat
41 | public var right: CGFloat
42 |
43 | public init(top: CGFloat, left: CGFloat, bottom: CGFloat, right: CGFloat) {
44 | self.top = top
45 | self.left = left
46 | self.right = right
47 | self.bottom = bottom
48 | }
49 | }
50 |
51 | extension Insets {
52 | public static let zero = Insets(0)
53 | }
54 |
55 | extension Insets {
56 |
57 | /// Set all edges of the inset to the same amount.
58 | public init(_ allEdgesAmount: CGFloat) {
59 | top = allEdgesAmount
60 | left = allEdgesAmount
61 | bottom = allEdgesAmount
62 | right = allEdgesAmount
63 | }
64 |
65 | /// Set horizontal, vertical edges of the inset to a specific amount.
66 | public init(_ leftRightAmount: CGFloat, _ topBottomAmount: CGFloat) {
67 | top = topBottomAmount
68 | left = leftRightAmount
69 | bottom = topBottomAmount
70 | right = leftRightAmount
71 | }
72 |
73 | /// Create an Insets instance from a UIEdgeInsets object
74 | public init(_ edgeInsets: UIEdgeInsets) {
75 | top = edgeInsets.top
76 | left = edgeInsets.left
77 | bottom = edgeInsets.bottom
78 | right = edgeInsets.right
79 | }
80 | }
81 |
82 | extension Insets: ExpressibleByIntegerLiteral, ExpressibleByFloatLiteral {
83 | public init(integerLiteral: Int) {
84 | self.init(CGFloat(integerLiteral))
85 | }
86 | public init(floatLiteral: Float) {
87 | self.init(CGFloat(floatLiteral))
88 | }
89 | }
90 |
91 | extension Insets: Equatable {
92 | public static func == (lhs: Insets, rhs: Insets) -> Bool {
93 | return lhs.top == rhs.top
94 | && lhs.left == rhs.left
95 | && lhs.bottom == rhs.bottom
96 | && lhs.right == rhs.right
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/Sources/WWLayout/Layout+Activate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ===----------------------------------------------------------------------===//
3 | //
4 | // Layout+Activate.swift
5 | //
6 | // Created by Steven Grosmark on 5/4/18.
7 | // Copyright © 2018 WW International, Inc.
8 | //
9 | //
10 | // This source file is part of the WWLayout open source project
11 | //
12 | // https://github.com/ww-tech/wwlayout
13 | //
14 | // Copyright © 2017-2021 WW International, Inc.
15 | //
16 | // Licensed under the Apache License, Version 2.0 (the "License");
17 | // you may not use this file except in compliance with the License.
18 | // You may obtain a copy of the License at
19 | //
20 | // http://www.apache.org/licenses/LICENSE-2.0
21 | //
22 | // Unless required by applicable law or agreed to in writing, software
23 | // distributed under the License is distributed on an "AS IS" BASIS,
24 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25 | // See the License for the specific language governing permissions and
26 | // limitations under the License.
27 | //
28 | // ===----------------------------------------------------------------------===//
29 | //
30 |
31 | import UIKit
32 |
33 | extension Layout {
34 |
35 | /// Find all the constraints matching `tag` in the view hierarchy that contains `view`
36 | /// - Parameters:
37 | /// - view: one of the views in the target view hierarchy
38 | /// - tag: the tag to search for
39 | public static func findConstraints(in view: UIView, tag: Int) -> [NSLayoutConstraint] {
40 | guard tag != 0 && tag != Int.min else { return [] }
41 | let layoutView = LayoutView.layoutView(for: view)
42 | return layoutView.getConstraints(with: tag)
43 | }
44 |
45 | /// Activate all constraints matching `tag` in the view hierarchy that contains `view`
46 | /// - Parameters:
47 | /// - view: one of the views in the target view hierarchy
48 | /// - tag: the tag to search for
49 | public static func activateConstraints(in view: UIView, tag: Int) {
50 | guard tag != 0 && tag != Int.min else { return }
51 | switchActiveConstraints(in: view, activeTag: tag)
52 | }
53 |
54 | /// Deactivate all constraints matching `tag` in the view hierarchy that contains `view`
55 | /// - Parameters:
56 | /// - view: one of the views in the target view hierarchy
57 | /// - tag: the tag to search for
58 | public static func deactivateConstraints(in view: UIView, tag: Int) {
59 | guard tag != 0 && tag != Int.min else { return }
60 | switchActiveConstraints(in: view, deactiveTag: tag)
61 | }
62 |
63 | /// Activate and/or deactivate all constraints matching the specified tags in the view hierarchy that contains `view`
64 | /// - Parameters:
65 | /// - view: one of the views in the target view hierarchy
66 | /// - activeTag: constraints with this tag will be activated (default is nil)
67 | /// - deactiveTag: constraints with this tag will be deactivated (default is nil)
68 | public static func switchActiveConstraints(in view: UIView,
69 | activeTag: Int? = nil,
70 | deactiveTag: Int? = nil) {
71 | guard activeTag != deactiveTag else { return }
72 | let layoutView = LayoutView.layoutView(for: view)
73 | if let deactiveTag = deactiveTag { layoutView.setActive(false, tag: deactiveTag) }
74 | if let activeTag = activeTag { layoutView.setActive(true, tag: activeTag) }
75 | }
76 |
77 | internal static func describeConstraints(in view: UIView) {
78 | var rootView = view
79 | while let superview = rootView.superview { rootView = superview }
80 | describeConstraints(in: rootView, indent: 0)
81 | }
82 |
83 | internal static func describeConstraints(in view: UIView, indent: Int) {
84 | let space = String(repeating: " ", count: indent)
85 | print("\(space)\(view)")
86 | view.constraints.lazy
87 | .compactMap { $0 as? LayoutConstraint }
88 | .forEach { constraint in
89 | print("\(space) tag=\(constraint.tag) \(constraint)")
90 | }
91 | view.subviews.forEach { describeConstraints(in: $0, indent: indent + 1) }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/Sources/WWLayout/Layout+Center.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ===----------------------------------------------------------------------===//
3 | //
4 | // Layout+Center.swift
5 | //
6 | // Created by Steven Grosmark on 12/9/17.
7 | // Copyright © 2018 WW International, Inc.
8 | //
9 | //
10 | // This source file is part of the WWLayout open source project
11 | //
12 | // https://github.com/ww-tech/wwlayout
13 | //
14 | // Copyright © 2017-2021 WW International, Inc.
15 | //
16 | // Licensed under the Apache License, Version 2.0 (the "License");
17 | // you may not use this file except in compliance with the License.
18 | // You may obtain a copy of the License at
19 | //
20 | // http://www.apache.org/licenses/LICENSE-2.0
21 | //
22 | // Unless required by applicable law or agreed to in writing, software
23 | // distributed under the License is distributed on an "AS IS" BASIS,
24 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25 | // See the License for the specific language governing permissions and
26 | // limitations under the License.
27 | //
28 | // ===----------------------------------------------------------------------===//
29 | //
30 |
31 | import UIKit
32 |
33 | extension Layout {
34 |
35 | /// Center the view relative to another
36 | @discardableResult
37 | public func center(in other: Anchorable,
38 | axis: LayoutAxis = .xy,
39 | priority: LayoutPriority? = nil,
40 | tag: Int? = nil,
41 | active: Bool? = nil) -> Layout {
42 | if axis == .x || axis == .xy {
43 | make(LayoutXEdge.center, .equal, toItem: other.anchor(.center), priority: priority, tag: tag, active: active)
44 | }
45 | if axis == .y || axis == .xy {
46 | make(LayoutYEdge.center, .equal, toItem: other.anchor(.center), priority: priority, tag: tag, active: active)
47 | }
48 | return self
49 | }
50 |
51 | /// Center the view relative to something special
52 | @discardableResult
53 | public func center(in special: SpecialAnchorable,
54 | axis: LayoutAxis = .xy,
55 | priority: LayoutPriority? = nil,
56 | tag: Int? = nil,
57 | active: Bool? = nil) -> Layout {
58 | return center(in: special.anchorable(with: view), axis: axis, priority: priority, tag: tag, active: active)
59 | }
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/Sources/WWLayout/LayoutAnchor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ===----------------------------------------------------------------------===//
3 | //
4 | // LayoutAnchor.swift
5 | //
6 | // Created by Steven Grosmark on 2/19/18.
7 | // Copyright © 2018 WW International, Inc.
8 | //
9 | //
10 | // This source file is part of the WWLayout open source project
11 | //
12 | // https://github.com/ww-tech/wwlayout
13 | //
14 | // Copyright © 2017-2021 WW International, Inc.
15 | //
16 | // Licensed under the Apache License, Version 2.0 (the "License");
17 | // you may not use this file except in compliance with the License.
18 | // You may obtain a copy of the License at
19 | //
20 | // http://www.apache.org/licenses/LICENSE-2.0
21 | //
22 | // Unless required by applicable law or agreed to in writing, software
23 | // distributed under the License is distributed on an "AS IS" BASIS,
24 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25 | // See the License for the specific language governing permissions and
26 | // limitations under the License.
27 | //
28 | // ===----------------------------------------------------------------------===//
29 | //
30 |
31 | import UIKit
32 |
33 | /// Something that one side of a constraint relationship can be attached to
34 | public protocol LayoutAnchor {
35 | associatedtype Axis
36 | var item: Any { get }
37 | var attribute: LayoutConstraint.Attribute { get }
38 | }
39 |
40 | public struct LayoutXAnchor: LayoutAnchor {
41 | public typealias Axis = LayoutXEdge
42 | public let item: Any
43 | public let attribute: LayoutConstraint.Attribute
44 | }
45 |
46 | public struct LayoutYAnchor: LayoutAnchor {
47 | public typealias Axis = LayoutYEdge
48 | public let item: Any
49 | public let attribute: LayoutConstraint.Attribute
50 | }
51 |
52 | public struct LayoutDimensionAnchor: LayoutAnchor {
53 | public typealias Axis = LayoutDimensionEdge
54 | public let item: Any
55 | public let attribute: LayoutConstraint.Attribute
56 | }
57 |
--------------------------------------------------------------------------------
/Sources/WWLayout/LayoutAxis.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ===----------------------------------------------------------------------===//
3 | //
4 | // LayoutAxis.swift
5 | //
6 | // Created by Steven Grosmark on 12/1/17.
7 | // Copyright © 2018 WW International, Inc.
8 | //
9 | //
10 | // This source file is part of the WWLayout open source project
11 | //
12 | // https://github.com/ww-tech/wwlayout
13 | //
14 | // Copyright © 2017-2021 WW International, Inc.
15 | //
16 | // Licensed under the Apache License, Version 2.0 (the "License");
17 | // you may not use this file except in compliance with the License.
18 | // You may obtain a copy of the License at
19 | //
20 | // http://www.apache.org/licenses/LICENSE-2.0
21 | //
22 | // Unless required by applicable law or agreed to in writing, software
23 | // distributed under the License is distributed on an "AS IS" BASIS,
24 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25 | // See the License for the specific language governing permissions and
26 | // limitations under the License.
27 | //
28 | // ===----------------------------------------------------------------------===//
29 | //
30 |
31 | import UIKit
32 |
33 | /// The axis along which items are centered or filled
34 | public enum LayoutAxis {
35 | case x
36 | case y
37 | case xy
38 | }
39 |
--------------------------------------------------------------------------------
/Sources/WWLayout/LayoutConstraint.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ===----------------------------------------------------------------------===//
3 | //
4 | // LayoutConstraint.swift
5 | //
6 | // Created by Steven Grosmark on 2/18/18.
7 | // Copyright © 2018 WW International, Inc.
8 | //
9 | //
10 | // This source file is part of the WWLayout open source project
11 | //
12 | // https://github.com/ww-tech/wwlayout
13 | //
14 | // Copyright © 2017-2021 WW International, Inc.
15 | //
16 | // Licensed under the Apache License, Version 2.0 (the "License");
17 | // you may not use this file except in compliance with the License.
18 | // You may obtain a copy of the License at
19 | //
20 | // http://www.apache.org/licenses/LICENSE-2.0
21 | //
22 | // Unless required by applicable law or agreed to in writing, software
23 | // distributed under the License is distributed on an "AS IS" BASIS,
24 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25 | // See the License for the specific language governing permissions and
26 | // limitations under the License.
27 | //
28 | // ===----------------------------------------------------------------------===//
29 | //
30 |
31 | import UIKit
32 |
33 | /// A single constraint relationship
34 | public class LayoutConstraint: NSLayoutConstraint {
35 |
36 | internal var tag: Int = 0
37 |
38 | /// Helper to set priority & activate an NSLayoutConstraint all at once
39 | @discardableResult
40 | internal func activate(with priority: LayoutPriority) -> LayoutConstraint {
41 | self.priority = UILayoutPriority(priority.rawValue)
42 | isActive = true
43 | return self
44 | }
45 |
46 | /// Helper to set priority & activate an NSLayoutConstraint all at once
47 | @discardableResult
48 | internal func set(priority: LayoutPriority, active: Bool) -> LayoutConstraint {
49 | self.priority = UILayoutPriority(rawValue: priority.rawValue)
50 | isActive = active
51 | return self
52 | }
53 |
54 | #if swift(>=4.2)
55 | public typealias Attribute = NSLayoutConstraint.Attribute
56 | public typealias Relation = NSLayoutConstraint.Relation
57 | #else
58 | public typealias Attribute = NSLayoutAttribute
59 | public typealias Relation = NSLayoutRelation
60 | #endif
61 | }
62 |
63 | extension Array where Element == LayoutConstraint {
64 |
65 | internal func activate(with priority: LayoutPriority) {
66 | self.forEach { $0.activate(with: priority) }
67 | }
68 |
69 | internal func activate() { self.setActive(true) }
70 | internal func deactivate() { self.setActive(false) }
71 |
72 | internal func setActive(_ active: Bool) {
73 | self.forEach { $0.isActive = active }
74 | }
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/Sources/WWLayout/LayoutDimension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ===----------------------------------------------------------------------===//
3 | //
4 | // LayoutDimension.swift
5 | //
6 | // Created by Steven Grosmark on 11/23/17.
7 | // Copyright © 2018 WW International, Inc.
8 | //
9 | //
10 | // This source file is part of the WWLayout open source project
11 | //
12 | // https://github.com/ww-tech/wwlayout
13 | //
14 | // Copyright © 2017-2021 WW International, Inc.
15 | //
16 | // Licensed under the Apache License, Version 2.0 (the "License");
17 | // you may not use this file except in compliance with the License.
18 | // You may obtain a copy of the License at
19 | //
20 | // http://www.apache.org/licenses/LICENSE-2.0
21 | //
22 | // Unless required by applicable law or agreed to in writing, software
23 | // distributed under the License is distributed on an "AS IS" BASIS,
24 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25 | // See the License for the specific language governing permissions and
26 | // limitations under the License.
27 | //
28 | // ===----------------------------------------------------------------------===//
29 | //
30 |
31 | import UIKit
32 |
33 | /// A sizing relationship (i.e. a width or height anchor, plus a multiplier & offset)
34 | /// The `item` + `attribute` define the source dimension
35 | /// `multiplier` and `offset` define modifications to the source dimension's value
36 | public protocol LayoutDimension {
37 | var item: Any { get }
38 | var attribute: LayoutConstraint.Attribute { get }
39 | var multiplier: CGFloat { get }
40 | var offset: CGFloat { get }
41 | }
42 |
43 | // Defaults for multiplier & constant
44 | public extension LayoutDimension {
45 | var multiplier: CGFloat { return 1.0 }
46 | var offset: CGFloat { return 0.0 }
47 | }
48 |
49 | /// The initial dimension represents the base dimension, that can be multiplied or added to
50 | public struct InitialLayoutDimension: LayoutDimension {
51 | public let item: Any
52 | public let attribute: LayoutConstraint.Attribute
53 |
54 | init(_ item: Any, _ attribute: LayoutConstraint.Attribute) {
55 | self.item = item
56 | self.attribute = attribute
57 | }
58 |
59 | public static func * (_ lhs: InitialLayoutDimension, _ rhs: CGFloat) -> SpecifiedLayoutDimension {
60 | return SpecifiedLayoutDimension(lhs.item, lhs.attribute, multiplier: rhs)
61 | }
62 |
63 | public static func + (_ lhs: InitialLayoutDimension, _ rhs: CGFloat) -> LayoutDimension {
64 | return SpecifiedLayoutDimension(lhs.item, lhs.attribute, multiplier: lhs.multiplier, offset: lhs.offset + rhs)
65 | }
66 |
67 | public static func - (_ lhs: InitialLayoutDimension, _ rhs: CGFloat) -> LayoutDimension {
68 | return SpecifiedLayoutDimension(lhs.item, lhs.attribute, multiplier: lhs.multiplier, offset: lhs.offset - rhs)
69 | }
70 | }
71 |
72 | /// A specified dimension represents a dimension that has had a multiplier or an offset applied,
73 | /// and can only be further modified by an offset amount
74 | public struct SpecifiedLayoutDimension: LayoutDimension {
75 | public let item: Any
76 | public let attribute: LayoutConstraint.Attribute
77 | public let multiplier: CGFloat
78 | public let offset: CGFloat
79 |
80 | init(_ item: Any, _ attribute: LayoutConstraint.Attribute, multiplier: CGFloat = 1.0, offset: CGFloat = 0) {
81 | self.item = item
82 | self.attribute = attribute
83 | self.multiplier = multiplier
84 | self.offset = offset
85 | }
86 |
87 | public static func + (_ lhs: SpecifiedLayoutDimension, _ rhs: CGFloat) -> LayoutDimension {
88 | return SpecifiedLayoutDimension(lhs.item, lhs.attribute, multiplier: lhs.multiplier, offset: lhs.offset + rhs)
89 | }
90 |
91 | public static func - (_ lhs: SpecifiedLayoutDimension, _ rhs: CGFloat) -> LayoutDimension {
92 | return SpecifiedLayoutDimension(lhs.item, lhs.attribute, multiplier: lhs.multiplier, offset: lhs.offset - rhs)
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/Sources/WWLayout/LayoutEdge.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ===----------------------------------------------------------------------===//
3 | //
4 | // LayoutEdge.swift
5 | //
6 | // Created by Steven Grosmark on 12/1/17.
7 | // Copyright © 2018 WW International, Inc.
8 | //
9 | //
10 | // This source file is part of the WWLayout open source project
11 | //
12 | // https://github.com/ww-tech/wwlayout
13 | //
14 | // Copyright © 2017-2021 WW International, Inc.
15 | //
16 | // Licensed under the Apache License, Version 2.0 (the "License");
17 | // you may not use this file except in compliance with the License.
18 | // You may obtain a copy of the License at
19 | //
20 | // http://www.apache.org/licenses/LICENSE-2.0
21 | //
22 | // Unless required by applicable law or agreed to in writing, software
23 | // distributed under the License is distributed on an "AS IS" BASIS,
24 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25 | // See the License for the specific language governing permissions and
26 | // limitations under the License.
27 | //
28 | // ===----------------------------------------------------------------------===//
29 | //
30 |
31 | import UIKit
32 |
33 | /// An edge of an anchorable item
34 | protocol LayoutEdge {
35 | associatedtype Axis
36 | var attribute: LayoutConstraint.Attribute { get }
37 | }
38 |
39 | /// An edge used for vertical positioning
40 | public enum LayoutYEdge: LayoutEdge {
41 | typealias Axis = LayoutYEdge
42 | case top
43 | case bottom
44 | case center
45 | case firstBaseline
46 | case lastBaseline
47 |
48 | var attribute: LayoutConstraint.Attribute {
49 | switch self {
50 | case .top: return .top
51 | case .bottom: return .bottom
52 | case .center: return .centerY
53 | case .firstBaseline: return .firstBaseline
54 | case .lastBaseline: return .lastBaseline
55 | }
56 | }
57 | }
58 |
59 | /// An edge used for horizontal positioning
60 | public enum LayoutXEdge: LayoutEdge {
61 | typealias Axis = LayoutXEdge
62 | case left
63 | case right
64 | case leading
65 | case trailing
66 | case center
67 |
68 | var attribute: LayoutConstraint.Attribute {
69 | switch self {
70 | case .left: return .left
71 | case .right: return .right
72 | case .leading: return .leading
73 | case .trailing: return .trailing
74 | case .center: return .centerX
75 | }
76 | }
77 | }
78 |
79 | /// A single edge - used to exclude an edge from filling another view
80 | public enum LayoutFillEdge {
81 | case left
82 | case right
83 | case top
84 | case bottom
85 | }
86 |
87 | /// Width or height dimension
88 | public enum LayoutDimensionEdge: LayoutEdge {
89 | typealias Axis = LayoutDimensionEdge
90 | case width
91 | case height
92 |
93 | var attribute: LayoutConstraint.Attribute {
94 | switch self {
95 | case .width: return .width
96 | case .height: return .height
97 | }
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/Sources/WWLayout/LayoutPriority.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ===----------------------------------------------------------------------===//
3 | //
4 | // LayoutPriority.swift
5 | //
6 | // Created by Steven Grosmark on 11/23/17.
7 | // Copyright © 2018 WW International, Inc.
8 | //
9 | //
10 | // This source file is part of the WWLayout open source project
11 | //
12 | // https://github.com/ww-tech/wwlayout
13 | //
14 | // Copyright © 2017-2021 WW International, Inc.
15 | //
16 | // Licensed under the Apache License, Version 2.0 (the "License");
17 | // you may not use this file except in compliance with the License.
18 | // You may obtain a copy of the License at
19 | //
20 | // http://www.apache.org/licenses/LICENSE-2.0
21 | //
22 | // Unless required by applicable law or agreed to in writing, software
23 | // distributed under the License is distributed on an "AS IS" BASIS,
24 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25 | // See the License for the specific language governing permissions and
26 | // limitations under the License.
27 | //
28 | // ===----------------------------------------------------------------------===//
29 | //
30 |
31 | import UIKit
32 |
33 | /*
34 | Using a typealias to Float seems simpler, and avoids some extra wonky compiler fixits when
35 | passing non-Float varibles into the funcs that expect a LayoutPriority.
36 | With the struct, the fixit offers `LayoutPriority(rawValue: Float(myVar))`.
37 | With the typealias, the fixit just offers `LayoutPriority(myVar)`.
38 |
39 | However, using a struct (like UILayoutPriority does as of Swift 4), allows it to enforce
40 | the 0-1000 range of values.
41 | With the typealias, intermediate LayoutPriority variables can hold out-of-range values,
42 | and the range clamping has to be done when creating a UILayoutPriority from a LayoutPriority.
43 | With the struct, the range clamping is done on creation.
44 | */
45 | /*
46 | public typealias LayoutPriority = Float
47 |
48 | public extension LayoutPriority {
49 | public static let required: LayoutPriority = 1000
50 | public static let high: LayoutPriority = 500
51 | public static let low: LayoutPriority = 250
52 | }
53 | */
54 |
55 | /// LayoutConstraint priority, corresponds dirctly to UILayoutPriority
56 | public struct LayoutPriority: RawRepresentable {
57 | public private(set) var rawValue: Float
58 | public init(rawValue: Float) {
59 | self.rawValue = min(1000, max(0, rawValue))
60 | }
61 | public static let required = LayoutPriority(rawValue: 1000)
62 | public static let high = LayoutPriority(rawValue: 750)
63 | public static let low = LayoutPriority(rawValue: 250)
64 | }
65 |
66 | // MARK: - Int and Float literals
67 |
68 | extension LayoutPriority: ExpressibleByIntegerLiteral, ExpressibleByFloatLiteral {
69 |
70 | public init(integerLiteral: Int) {
71 | self.init(rawValue: Float(integerLiteral))
72 | }
73 |
74 | public init(floatLiteral: Float) {
75 | self.init(rawValue: floatLiteral)
76 | }
77 |
78 | }
79 |
80 | // MARK: - Equality
81 |
82 | extension LayoutPriority: Equatable {
83 |
84 | public static func == (_ lhs: LayoutPriority, _ rhs: LayoutPriority) -> Bool {
85 | return lhs.rawValue == rhs.rawValue
86 | }
87 |
88 | }
89 |
90 | // MARK: - Addition and subtraction support
91 |
92 | extension LayoutPriority {
93 |
94 | // swiftlint:disable shorthand_operator
95 | public static func += (_ lhs: inout LayoutPriority, _ integerLiteral: Int) {
96 | lhs = lhs + integerLiteral
97 | }
98 |
99 | public static func += (_ lhs: inout LayoutPriority, _ floatLiteral: Float) {
100 | lhs = lhs + floatLiteral
101 | }
102 |
103 | public static func -= (_ lhs: inout LayoutPriority, _ integerLiteral: Int) {
104 | lhs = lhs - integerLiteral
105 | }
106 |
107 | public static func -= (_ lhs: inout LayoutPriority, _ floatLiteral: Float) {
108 | lhs = lhs - floatLiteral
109 | }
110 |
111 | public static func + (_ lhs: LayoutPriority, _ integerLiteral: Int) -> LayoutPriority {
112 | return LayoutPriority(rawValue: lhs.rawValue + Float(integerLiteral))
113 | }
114 |
115 | public static func + (_ lhs: LayoutPriority, _ floatLiteral: Float) -> LayoutPriority {
116 | return LayoutPriority(rawValue: lhs.rawValue + floatLiteral)
117 | }
118 |
119 | public static func - (_ lhs: LayoutPriority, _ integerLiteral: Int) -> LayoutPriority {
120 | return LayoutPriority(rawValue: lhs.rawValue - Float(integerLiteral))
121 | }
122 |
123 | public static func - (_ lhs: LayoutPriority, _ floatLiteral: Float) -> LayoutPriority {
124 | return LayoutPriority(rawValue: lhs.rawValue - floatLiteral)
125 | }
126 |
127 | }
128 |
--------------------------------------------------------------------------------
/Sources/WWLayout/LayoutRelation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ===----------------------------------------------------------------------===//
3 | //
4 | // LayoutRelation.swift
5 | //
6 | // Created by Steven Grosmark on 12/9/17.
7 | // Copyright © 2018 WW International, Inc.
8 | //
9 | //
10 | // This source file is part of the WWLayout open source project
11 | //
12 | // https://github.com/ww-tech/wwlayout
13 | //
14 | // Copyright © 2017-2021 WW International, Inc.
15 | //
16 | // Licensed under the Apache License, Version 2.0 (the "License");
17 | // you may not use this file except in compliance with the License.
18 | // You may obtain a copy of the License at
19 | //
20 | // http://www.apache.org/licenses/LICENSE-2.0
21 | //
22 | // Unless required by applicable law or agreed to in writing, software
23 | // distributed under the License is distributed on an "AS IS" BASIS,
24 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25 | // See the License for the specific language governing permissions and
26 | // limitations under the License.
27 | //
28 | // ===----------------------------------------------------------------------===//
29 | //
30 |
31 | import UIKit
32 |
33 | /// Constraint equality relationship
34 | public enum LayoutRelation {
35 | case equal
36 | case lessOrEqual
37 | case greaterOrEqual
38 | }
39 |
40 | // MARK: - constraint creation helpers
41 |
42 | extension LayoutRelation {
43 |
44 | /// Get the corresponding NSLayoutRelation
45 | internal var nsRelation: LayoutConstraint.Relation {
46 | switch self {
47 | case .equal: return .equal
48 | case .lessOrEqual: return .lessThanOrEqual
49 | case .greaterOrEqual: return .greaterThanOrEqual
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/Sources/WWLayout/LayoutView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ===----------------------------------------------------------------------===//
3 | //
4 | // LayoutView.swift
5 | // WWLayout
6 | //
7 | // Created by Steven Grosmark on 5/4/18.
8 | // Copyright © 2018 WW International, Inc. All rights reserved.
9 | //
10 | //
11 | // This source file is part of the WWLayout open source project
12 | //
13 | // https://github.com/ww-tech/wwlayout
14 | //
15 | // Copyright © 2017-2021 WW International, Inc.
16 | //
17 | // Licensed under the Apache License, Version 2.0 (the "License");
18 | // you may not use this file except in compliance with the License.
19 | // You may obtain a copy of the License at
20 | //
21 | // http://www.apache.org/licenses/LICENSE-2.0
22 | //
23 | // Unless required by applicable law or agreed to in writing, software
24 | // distributed under the License is distributed on an "AS IS" BASIS,
25 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
26 | // See the License for the specific language governing permissions and
27 | // limitations under the License.
28 | //
29 | // ===----------------------------------------------------------------------===//
30 | //
31 |
32 | import UIKit
33 |
34 | /// Hidden UIView that gets added to a UIViewController's hierarchy,
35 | /// used to keep track of constraints that are tagged.
36 | /// The hidden view is only created when constraints are tagged.
37 | internal final class LayoutView: UIView {
38 |
39 | // MARK: - API
40 |
41 | /// Retrieve the LayoutView used to manage constraints created against a particular UIView
42 | internal static func layoutView(for view: UIView) -> LayoutView {
43 | let rootView = view.owningSuperview()
44 | for child in rootView.subviews {
45 | if let layoutView = child as? LayoutView {
46 | return layoutView
47 | }
48 | }
49 | let layoutView = LayoutView()
50 | rootView.insertSubview(layoutView, at: 0)
51 | return layoutView
52 | }
53 |
54 | /// Add a constraint to the list of managed constraints.
55 | /// A constraint is only added when it is tagged (i.e. constarint.tag != 0)
56 | internal func add(_ constraint: LayoutConstraint) {
57 | guard constraint.tag != 0 else { return }
58 | taggedConstraints[constraint.tag, default: []] += [constraint]
59 | }
60 |
61 | internal func add(_ constraint: LayoutConstraint, sizeClass: SizeClass) {
62 | sizedConstraints[sizeClass, default: []] += [constraint]
63 | }
64 |
65 | /// Get a list of constraints tagged with a specific tag
66 | internal func getConstraints(with tag: Int) -> [LayoutConstraint] {
67 | return taggedConstraints[tag, default: []]
68 | }
69 |
70 | /// Activate / deactivate all constraints with a specific tag
71 | internal func setActive(_ active: Bool, tag: Int) {
72 | getConstraints(with: tag).setActive(active)
73 | }
74 |
75 | /// Activate / deactivate all constraints when switching from one size class to another.
76 | internal func switchSizeClass(from fromSizeClass: SizeClass?, to toSizeClass: SizeClass?) {
77 | let activate = toSizeClass?.matches() ?? []
78 | if let old = fromSizeClass {
79 | let deactivate = old.matches().subtracting(activate)
80 | for sizeClass in deactivate {
81 | sizedConstraints(sizeClass).setActive(false)
82 | }
83 | }
84 | for sizeClass in activate {
85 | sizedConstraints(sizeClass).setActive(true)
86 | }
87 | }
88 |
89 | // MARK: - Private implementation
90 |
91 | private var taggedConstraints = [Int: [LayoutConstraint]]()
92 | private var sizedConstraints = [SizeClass: [LayoutConstraint]]()
93 |
94 | private init() {
95 | super.init(frame: .zero)
96 | isHidden = true
97 | }
98 |
99 | required init?(coder aDecoder: NSCoder) { fatalError("unsupported") }
100 |
101 | private func sizedConstraints(_ sizeClass: SizeClass) -> [LayoutConstraint] {
102 | return sizedConstraints[sizeClass, default: []]
103 | }
104 |
105 | override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
106 | let newSizeClass = SizeClass(horizontal: traitCollection.horizontalSizeClass,
107 | vertical: traitCollection.verticalSizeClass)
108 | let oldSizeClass = SizeClass(horizontal: previousTraitCollection?.horizontalSizeClass,
109 | vertical: previousTraitCollection?.verticalSizeClass)
110 | switchSizeClass(from: oldSizeClass, to: newSizeClass)
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/Sources/WWLayout/SizeClass.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ===----------------------------------------------------------------------===//
3 | //
4 | // SizeClass.swift
5 | //
6 | // Created by Steven Grosmark on 6/27/19
7 | // Copyright © 2019 WW International, Inc.
8 | //
9 | //
10 | // This source file is part of the WWLayout open source project
11 | //
12 | // https://github.com/ww-tech/wwlayout
13 | //
14 | // Copyright © 2017-2021 WW International, Inc.
15 | //
16 | // Licensed under the Apache License, Version 2.0 (the "License");
17 | // you may not use this file except in compliance with the License.
18 | // You may obtain a copy of the License at
19 | //
20 | // http://www.apache.org/licenses/LICENSE-2.0
21 | //
22 | // Unless required by applicable law or agreed to in writing, software
23 | // distributed under the License is distributed on an "AS IS" BASIS,
24 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25 | // See the License for the specific language governing permissions and
26 | // limitations under the License.
27 | //
28 | // ===----------------------------------------------------------------------===//
29 | //
30 |
31 | import UIKit
32 |
33 | /// Internal representation of the combination of horizontal and vertical UIUserInterfaceSizeClass values
34 | internal enum SizeClass {
35 |
36 | case hcompact, hregular
37 | case hcompact_vcompact, hcompact_vregular
38 | case hregular_vcompact, hregular_vregular
39 | case vcompact, vregular
40 |
41 | init?(horizontal: UIUserInterfaceSizeClass?, vertical: UIUserInterfaceSizeClass?) {
42 | switch (horizontal, vertical) {
43 | case (.compact?, .compact?): self = .hcompact_vcompact
44 | case (.compact?, .regular?): self = .hcompact_vregular
45 | case (.regular?, .compact?): self = .hregular_vcompact
46 | case (.regular?, .regular?): self = .hregular_vregular
47 | case (.compact?, _): self = .hcompact
48 | case (.regular?, _): self = .hregular
49 | case (_, .compact?): self = .vcompact
50 | case (_, .regular?): self = .vregular
51 | default: return nil
52 | }
53 | }
54 |
55 | /// Retrieve the set of SizeClass values, that *this SizeClass.
56 | /// E.g., a constraint that is active for `.hcompact_vcompact`, is also active for `.hcompact` or `.vcompact`.
57 | func matches() -> Set {
58 | switch self {
59 | case .hcompact_vcompact: return [.hcompact_vcompact, .hcompact, .vcompact]
60 | case .hcompact_vregular: return [.hcompact_vregular, .hcompact, .vregular]
61 | case .hregular_vcompact: return [.hregular_vcompact, .hregular, .vcompact]
62 | case .hregular_vregular: return [.hregular_vregular, .hregular, .vregular]
63 | case .hcompact, .hregular, .vcompact, .vregular: return [self]
64 | }
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/Sources/WWLayout/UITraitCollection+Active.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ===----------------------------------------------------------------------===//
3 | //
4 | // UITraitCollection+Active.swift
5 | // WWLayout
6 | //
7 | // Created by Steven Grosmark on 5/6/18.
8 | // Copyright © 2018 WW International, Inc. All rights reserved.
9 | //
10 | //
11 | // This source file is part of the WWLayout open source project
12 | //
13 | // https://github.com/ww-tech/wwlayout
14 | //
15 | // Copyright © 2017-2021 WW International, Inc.
16 | //
17 | // Licensed under the Apache License, Version 2.0 (the "License");
18 | // you may not use this file except in compliance with the License.
19 | // You may obtain a copy of the License at
20 | //
21 | // http://www.apache.org/licenses/LICENSE-2.0
22 | //
23 | // Unless required by applicable law or agreed to in writing, software
24 | // distributed under the License is distributed on an "AS IS" BASIS,
25 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
26 | // See the License for the specific language governing permissions and
27 | // limitations under the License.
28 | //
29 | // ===----------------------------------------------------------------------===//
30 | //
31 |
32 | import UIKit
33 |
34 | extension UITraitCollection {
35 |
36 | internal func isActive(horizontalSize: UIUserInterfaceSizeClass?, verticalSize: UIUserInterfaceSizeClass?) -> Bool {
37 | return isActive(horizontalSize: horizontalSize) && isActive(verticalSize: verticalSize)
38 | }
39 |
40 | internal func isActive(horizontalSize: UIUserInterfaceSizeClass?) -> Bool {
41 | guard let hSize = horizontalSize else { return true }
42 | return hSize == .unspecified || horizontalSizeClass == hSize
43 | }
44 |
45 | internal func isActive(verticalSize: UIUserInterfaceSizeClass?) -> Bool {
46 | guard let vSize = verticalSize else { return true }
47 | return vSize == .unspecified || verticalSizeClass == vSize
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Sources/WWLayout/UIView+Containment.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ===----------------------------------------------------------------------===//
3 | //
4 | // UIView+Containment.swift
5 | // WWLayout
6 | //
7 | // Created by Steven Grosmark on 5/4/18.
8 | // Copyright © 2018 WW International, Inc. All rights reserved.
9 | //
10 | //
11 | // This source file is part of the WWLayout open source project
12 | //
13 | // https://github.com/ww-tech/wwlayout
14 | //
15 | // Copyright © 2017-2021 WW International, Inc.
16 | //
17 | // Licensed under the Apache License, Version 2.0 (the "License");
18 | // you may not use this file except in compliance with the License.
19 | // You may obtain a copy of the License at
20 | //
21 | // http://www.apache.org/licenses/LICENSE-2.0
22 | //
23 | // Unless required by applicable law or agreed to in writing, software
24 | // distributed under the License is distributed on an "AS IS" BASIS,
25 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
26 | // See the License for the specific language governing permissions and
27 | // limitations under the License.
28 | //
29 | // ===----------------------------------------------------------------------===//
30 | //
31 |
32 | import UIKit
33 |
34 | extension UIView {
35 |
36 | /// Retrieve the UIViewController whose view hirearchy contains this view
37 | internal func owningViewController() -> UIViewController? {
38 | var responder: UIResponder = self
39 | while let parentResponder = responder.next {
40 | if let vc = parentResponder as? UIViewController {
41 | return vc
42 | }
43 | responder = parentResponder
44 | }
45 | return nil
46 | }
47 |
48 | /// Retrieve the UIView that is at the root of the view hierarchy that contains this view
49 | internal func owningSuperview() -> UIView {
50 | if let owningVC = owningViewController() {
51 | return owningVC.view
52 | }
53 | var rootView = self
54 | while let superview = rootView.superview, !(superview is UIWindow) {
55 | rootView = superview
56 | }
57 | return rootView
58 | }
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/Sources/WWLayout/UIView+Layout.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ===----------------------------------------------------------------------===//
3 | //
4 | // UIView+Layout.swift
5 | //
6 | // Created by Steven Grosmark on 11/23/17.
7 | // Copyright © 2018 WW International, Inc.
8 | //
9 | //
10 | // This source file is part of the WWLayout open source project
11 | //
12 | // https://github.com/ww-tech/wwlayout
13 | //
14 | // Copyright © 2017-2021 WW International, Inc.
15 | //
16 | // Licensed under the Apache License, Version 2.0 (the "License");
17 | // you may not use this file except in compliance with the License.
18 | // You may obtain a copy of the License at
19 | //
20 | // http://www.apache.org/licenses/LICENSE-2.0
21 | //
22 | // Unless required by applicable law or agreed to in writing, software
23 | // distributed under the License is distributed on an "AS IS" BASIS,
24 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25 | // See the License for the specific language governing permissions and
26 | // limitations under the License.
27 | //
28 | // ===----------------------------------------------------------------------===//
29 | //
30 |
31 | import UIKit
32 |
33 | extension UIView {
34 |
35 | /// Access to auto layout constrains for the view
36 | public var layout: Layout { return Layout(self) }
37 |
38 | /// Access to auto layout constraints for the view, at a specific priority.
39 | /// All constraints set up based on this Layout instance will be at the specified priority.
40 | public func layout(priority: LayoutPriority) -> Layout {
41 | return Layout(self, priority: priority)
42 | }
43 |
44 | /// Access to auto layout constraints for the view, at a specific priority and/or using a specific tag.
45 | /// All constraints set up based on this Layout instance will be at the specified priority, and will use the `tag` specified.
46 | public func layout(priority: LayoutPriority = .required,
47 | tag: Int = 0,
48 | active: Bool = true) -> Layout {
49 | return Layout(self, priority: priority, tag: tag, active: active)
50 | }
51 |
52 | public func layout(priority: LayoutPriority = .required,
53 | horizontalSize: UIUserInterfaceSizeClass) -> Layout {
54 | return Layout(self, priority: priority, horizontalSize: horizontalSize, verticalSize: .unspecified)
55 | }
56 |
57 | public func layout(priority: LayoutPriority = .required,
58 | verticalSize: UIUserInterfaceSizeClass) -> Layout {
59 | return Layout(self, priority: priority, horizontalSize: .unspecified, verticalSize: verticalSize)
60 | }
61 |
62 | public func layout(priority: LayoutPriority = .required,
63 | horizontalSize: UIUserInterfaceSizeClass,
64 | verticalSize: UIUserInterfaceSizeClass) -> Layout {
65 | return Layout(self, priority: priority, horizontalSize: horizontalSize, verticalSize: verticalSize)
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/Sources/WWLayout/WWLayout.h:
--------------------------------------------------------------------------------
1 | //
2 | // ===----------------------------------------------------------------------===//
3 | //
4 | // WWLayout.h
5 | //
6 | // Created by Steven Grosmark on 12/1/17.
7 | // Copyright © 2018 WW International, Inc.
8 | //
9 | //
10 | // This source file is part of the WWLayout open source project
11 | //
12 | // https://github.com/ww-tech/wwlayout
13 | //
14 | // Copyright © 2017-2021 WW International, Inc.
15 | //
16 | // Licensed under the Apache License, Version 2.0 (the "License");
17 | // you may not use this file except in compliance with the License.
18 | // You may obtain a copy of the License at
19 | //
20 | // http://www.apache.org/licenses/LICENSE-2.0
21 | //
22 | // Unless required by applicable law or agreed to in writing, software
23 | // distributed under the License is distributed on an "AS IS" BASIS,
24 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25 | // See the License for the specific language governing permissions and
26 | // limitations under the License.
27 | //
28 | // ===----------------------------------------------------------------------===//
29 | //
30 |
31 | #import
32 |
33 | //! Project version number for WWLayout.
34 | FOUNDATION_EXPORT double WWLayoutVersionNumber;
35 |
36 | //! Project version string for WWLayout.
37 | FOUNDATION_EXPORT const unsigned char WWLayoutVersionString[];
38 |
39 | // In this header, you should import all the public headers of your framework using statements like #import
40 |
41 |
42 |
--------------------------------------------------------------------------------
/Tests/WWLayoutTests/ConstraintArrayTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ===----------------------------------------------------------------------===//
3 | //
4 | // ConstraintArrayTests.swift
5 | //
6 | // Created by Steven Grosmark on 7/2/18.
7 | // Copyright © 2018 WW International, Inc.
8 | //
9 | //
10 | // This source file is part of the WWLayout open source project
11 | //
12 | // https://github.com/ww-tech/wwlayout
13 | //
14 | // Copyright © 2017-2021 WW International, Inc.
15 | //
16 | // Licensed under the Apache License, Version 2.0 (the "License");
17 | // you may not use this file except in compliance with the License.
18 | // You may obtain a copy of the License at
19 | //
20 | // http://www.apache.org/licenses/LICENSE-2.0
21 | //
22 | // Unless required by applicable law or agreed to in writing, software
23 | // distributed under the License is distributed on an "AS IS" BASIS,
24 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25 | // See the License for the specific language governing permissions and
26 | // limitations under the License.
27 | //
28 | // ===----------------------------------------------------------------------===//
29 | //
30 |
31 | import XCTest
32 | @testable import WWLayout
33 |
34 | class ConstraintArrayTests: XCTestCase {
35 |
36 | override func setUp() {
37 | super.setUp()
38 | // Put setup code here. This method is called before the invocation of each test method in the class.
39 | }
40 |
41 | override func tearDown() {
42 | // Put teardown code here. This method is called after the invocation of each test method in the class.
43 | super.tearDown()
44 | }
45 |
46 | func test_ConstraintArrayHelpers() {
47 | // given
48 | let view = UIView()
49 | let constraints = view.layout.size(100, 200).newConstraints
50 | for constraint in constraints {
51 | XCTAssertTrue(constraint.isActive)
52 | XCTAssertEqual(constraint.tag, 0)
53 | XCTAssertEqual(constraint.priority, .required)
54 | }
55 |
56 | // when
57 | constraints.deactivate()
58 |
59 | // then
60 | for constraint in constraints {
61 | XCTAssertFalse(constraint.isActive)
62 | }
63 |
64 | // when
65 | constraints.activate()
66 |
67 | // then
68 | for constraint in constraints {
69 | XCTAssertTrue(constraint.isActive)
70 | }
71 |
72 | // when
73 | constraints.activate(with: .high)
74 |
75 | // then
76 | for constraint in constraints {
77 | XCTAssertEqual(constraint.priority, .defaultHigh)
78 | }
79 | }
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/Tests/WWLayoutTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Tests/WWLayoutTests/InsetTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ===----------------------------------------------------------------------===//
3 | //
4 | // InsetTests.swift
5 | //
6 | // Created by Steven Grosmark on 3/26/18.
7 | // Copyright © 2018 WW International, Inc.
8 | //
9 | //
10 | // This source file is part of the WWLayout open source project
11 | //
12 | // https://github.com/ww-tech/wwlayout
13 | //
14 | // Copyright © 2017-2021 WW International, Inc.
15 | //
16 | // Licensed under the Apache License, Version 2.0 (the "License");
17 | // you may not use this file except in compliance with the License.
18 | // You may obtain a copy of the License at
19 | //
20 | // http://www.apache.org/licenses/LICENSE-2.0
21 | //
22 | // Unless required by applicable law or agreed to in writing, software
23 | // distributed under the License is distributed on an "AS IS" BASIS,
24 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25 | // See the License for the specific language governing permissions and
26 | // limitations under the License.
27 | //
28 | // ===----------------------------------------------------------------------===//
29 | //
30 |
31 | import XCTest
32 | @testable import WWLayout
33 |
34 | class InsetTests: XCTestCase {
35 |
36 | private var container: UIView!
37 | private var view: UIView!
38 |
39 | override func setUp() {
40 | super.setUp()
41 | container = UIView(frame: CGRect(x: 0, y: 0, width: 400, height: 400))
42 | if container.constraints.isEmpty {
43 | NSLayoutConstraint.activate([
44 | container.widthAnchor.constraint(equalToConstant: 400),
45 | container.heightAnchor.constraint(equalToConstant: 400)
46 | ])
47 | }
48 |
49 | view = UIView()
50 | container.addSubview(view)
51 | }
52 |
53 | override func tearDown() {
54 | super.tearDown()
55 | container = nil
56 | view = nil
57 | }
58 |
59 | func test_Insets_Construction() {
60 | do {
61 | let i1: Insets = 20
62 | let i2 = Insets(20, 20)
63 | let i3 = Insets(top: 20, left: 20, bottom: 20, right: 20)
64 | let i4 = Insets(UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20))
65 | XCTAssertEqual(i1, i2)
66 | XCTAssertEqual(i2, i3)
67 | XCTAssertEqual(i1, i3)
68 | XCTAssertEqual(i2, i4)
69 | }
70 |
71 | do {
72 | let i1: Insets = 123.5
73 | let i2 = Insets(123.5, 123.5)
74 | let i3 = Insets(top: 123.5, left: 123.5, bottom: 123.5, right: 123.5)
75 | let i4 = Insets(UIEdgeInsets(top: 123.5, left: 123.5, bottom: 123.5, right: 123.5))
76 | XCTAssertEqual(i1, i2)
77 | XCTAssertEqual(i2, i3)
78 | XCTAssertEqual(i1, i3)
79 | XCTAssertEqual(i2, i4)
80 | }
81 |
82 | do {
83 | let i1 = Insets(top: 10, left: 23, bottom: 37, right: 51)
84 | let i2 = Insets(UIEdgeInsets(top: 10, left: 23, bottom: 37, right: 51))
85 | XCTAssertEqual(i1, i2)
86 | }
87 | }
88 |
89 | func test_Insets_AppliedInset() {
90 | view.layout.fill(.superview, inset: 33)
91 |
92 | container.layoutIfNeeded()
93 |
94 | XCTAssertEqual(view.frame, container.frame.insetBy(dx: 33, dy: 33))
95 | }
96 |
97 | func test_Insets_AppliedMixedInset() {
98 | view.layout.fill(.superview, inset: Insets(33, 51))
99 |
100 | container.layoutIfNeeded()
101 |
102 | XCTAssertEqual(view.frame, container.frame.insetBy(dx: 33, dy: 51))
103 | }
104 |
105 | func test_Insets_AppliedOutset() {
106 | view.layout.fill(.superview, inset: -11)
107 |
108 | container.layoutIfNeeded()
109 |
110 | XCTAssertEqual(view.frame, container.frame.insetBy(dx: -11, dy: -11))
111 | }
112 |
113 | func test_Insets_AppliedMixedInOutset() {
114 | let edgeInsets = UIEdgeInsets(top: 11, left: -22, bottom: 33, right: -44)
115 | view.layout.fill(.superview, inset: Insets(edgeInsets))
116 |
117 | container.layoutIfNeeded()
118 |
119 | var frame = container.frame
120 | frame.origin.y += edgeInsets.top
121 | frame.origin.x += edgeInsets.left
122 | frame.size.width -= edgeInsets.left + edgeInsets.right
123 | frame.size.height -= edgeInsets.top + edgeInsets.bottom
124 | XCTAssertEqual(view.frame, frame)
125 | }
126 |
127 | }
128 |
--------------------------------------------------------------------------------
/Tests/WWLayoutTests/LayoutViewTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ===----------------------------------------------------------------------===//
3 | //
4 | // LayoutViewTests.swift
5 | //
6 | // Created by Steven Grosmark on 08/10/2021
7 | // Copyright © 2021 WW International, Inc.
8 | //
9 | //
10 | // This source file is part of the WWLayout open source project
11 | //
12 | // https://github.com/ww-tech/wwlayout
13 | //
14 | // Copyright © 2017-2021 WW International, Inc.
15 | //
16 | // Licensed under the Apache License, Version 2.0 (the "License");
17 | // you may not use this file except in compliance with the License.
18 | // You may obtain a copy of the License at
19 | //
20 | // http://www.apache.org/licenses/LICENSE-2.0
21 | //
22 | // Unless required by applicable law or agreed to in writing, software
23 | // distributed under the License is distributed on an "AS IS" BASIS,
24 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25 | // See the License for the specific language governing permissions and
26 | // limitations under the License.
27 | //
28 | // ===----------------------------------------------------------------------===//
29 | //
30 |
31 | import XCTest
32 | @testable import WWLayout
33 |
34 | class LayoutViewTests: XCTestCase {
35 |
36 | private var window: UIWindow!
37 |
38 | override func setUp() {
39 | super.setUp()
40 |
41 | window = UIWindow()
42 | window.rootViewController = UIViewController()
43 | }
44 |
45 | override func tearDown() {
46 | window = nil
47 |
48 | super.tearDown()
49 | }
50 |
51 | func test_creationInViewController() throws {
52 | // given
53 | let label = UILabel()
54 | window.rootViewController?.view.addSubview(label)
55 |
56 | // when
57 | label.layout(horizontalSize: .compact).fill(.superview)
58 |
59 | // then
60 | let layoutView = findLayoutView()
61 | XCTAssertNotNil(layoutView, "LayoutView should exist")
62 | XCTAssert(layoutView?.superview === window.rootViewController?.view, "LayoutView should be created at root of the view controller" )
63 | }
64 |
65 | func test_creationInViewController_nestedViews() throws {
66 | // given
67 | let label = UILabel()
68 | window.rootViewController?.view.addSubview(UIView(subviews: UIView(subviews: label)))
69 |
70 | // when
71 | label.layout(horizontalSize: .compact).fill(.superview)
72 |
73 | // then
74 | let layoutView = findLayoutView()
75 | XCTAssertNotNil(layoutView, "LayoutView should exist")
76 | XCTAssert(layoutView?.superview === window.rootViewController?.view, "LayoutView should be created at root of the view controller" )
77 | }
78 |
79 | func test_creationInViewController_nestedController() throws {
80 | // given
81 | let childController = UIViewController()
82 | let label = UILabel()
83 |
84 | childController.view.addSubview(UIView(subviews: UIView(subviews: label)))
85 | window.rootViewController?.addChild(childController)
86 | window.rootViewController?.view.addSubview(childController.view)
87 |
88 | // when
89 | label.layout(horizontalSize: .compact).fill(.superview)
90 |
91 | // then
92 | let layoutView = findLayoutView()
93 | XCTAssertNotNil(layoutView, "LayoutView should exist")
94 | XCTAssert(layoutView?.superview === childController.view, "LayoutView should be created at root of the _child_ view controller" )
95 | }
96 |
97 | func test_creationInWindow() throws {
98 | // given
99 | let label = UILabel()
100 | let targetSubview = UIView(subviews: UIView(subviews: label))
101 | window.addSubview(targetSubview)
102 |
103 | // when
104 | label.layout(horizontalSize: .compact).fill(.superview)
105 |
106 | // then
107 | let layoutView = findLayoutView()
108 | XCTAssertNotNil(layoutView, "LayoutView should exist")
109 | XCTAssert(layoutView?.superview === targetSubview, "LayoutView should be created in child of thw window" )
110 | }
111 |
112 | private func findLayoutView() -> LayoutView? {
113 | var toCheck: [UIView] = [window, window.rootViewController?.view].compactMap { return $0 }
114 | while !toCheck.isEmpty {
115 | let candidate = toCheck.removeFirst()
116 | if let layoutView = candidate as? LayoutView {
117 | return layoutView
118 | }
119 | toCheck.append(contentsOf: candidate.subviews)
120 | }
121 | return nil
122 | }
123 |
124 | }
125 |
--------------------------------------------------------------------------------
/Tests/WWLayoutTests/PriorityTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ===----------------------------------------------------------------------===//
3 | //
4 | // PriorityTests.swift
5 | //
6 | // Created by Steven Grosmark on 3/26/18.
7 | // Copyright © 2018 WW International, Inc.
8 | //
9 | //
10 | // This source file is part of the WWLayout open source project
11 | //
12 | // https://github.com/ww-tech/wwlayout
13 | //
14 | // Copyright © 2017-2021 WW International, Inc.
15 | //
16 | // Licensed under the Apache License, Version 2.0 (the "License");
17 | // you may not use this file except in compliance with the License.
18 | // You may obtain a copy of the License at
19 | //
20 | // http://www.apache.org/licenses/LICENSE-2.0
21 | //
22 | // Unless required by applicable law or agreed to in writing, software
23 | // distributed under the License is distributed on an "AS IS" BASIS,
24 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25 | // See the License for the specific language governing permissions and
26 | // limitations under the License.
27 | //
28 | // ===----------------------------------------------------------------------===//
29 | //
30 |
31 | import XCTest
32 | @testable import WWLayout
33 |
34 | class PriorityTests: XCTestCase {
35 |
36 | private var container: UIView!
37 |
38 | override func setUp() {
39 | super.setUp()
40 | container = UIView()
41 | }
42 |
43 | override func tearDown() {
44 | super.tearDown()
45 | container = nil
46 | }
47 |
48 | func testPriorities() {
49 | let subview = UIView()
50 | container.addSubview(subview)
51 |
52 | // default required priority
53 | for constraint in subview.layout.size(200).constraints() {
54 | XCTAssertEqual(constraint.priority, UILayoutPriority.required)
55 | }
56 |
57 | // our version of required
58 | for constraint in subview.layout.size(200, priority: .required).constraints() {
59 | XCTAssertEqual(constraint.priority, UILayoutPriority.required)
60 | }
61 |
62 | // our version of high
63 | for constraint in subview.layout.fill(.superview, priority: .high).constraints() {
64 | XCTAssertEqual(constraint.priority, UILayoutPriority.defaultHigh)
65 | }
66 |
67 | // our version of low, as the default
68 | for constraint in subview.layout(priority: .low).center(in: .superview).constraints() {
69 | XCTAssertEqual(constraint.priority, UILayoutPriority.defaultLow)
70 | }
71 |
72 | // set a different default, override it
73 | let constraints = subview.layout(priority: .low)
74 | .left(to: .superview)
75 | .top(to: .superview, priority: .high)
76 | .right(to: .superview)
77 | .constraints()
78 |
79 | XCTAssertEqual(constraints[0].priority, UILayoutPriority.defaultLow)
80 | XCTAssertEqual(constraints[1].priority, UILayoutPriority.defaultHigh)
81 | XCTAssertEqual(constraints[2].priority, UILayoutPriority.defaultLow)
82 | }
83 |
84 | func testIntLiterals() {
85 | let subview = UIView()
86 | container.addSubview(subview)
87 |
88 | for constraint in subview.layout.fill(.superview, priority: 678).constraints() {
89 | XCTAssertEqual(constraint.priority, UILayoutPriority(678))
90 | }
91 |
92 | for constraint in subview.layout(priority: 789).center(in: .superview).constraints() {
93 | XCTAssertEqual(constraint.priority, UILayoutPriority(789))
94 | }
95 | }
96 |
97 | func testFloatLiterals() {
98 | let subview = UIView()
99 | container.addSubview(subview)
100 |
101 | for constraint in subview.layout.fill(.superview, priority: 123.4).constraints() {
102 | XCTAssertEqual(constraint.priority, UILayoutPriority(123.4))
103 | }
104 |
105 | for constraint in subview.layout(priority: 567.8).center(in: .superview).constraints() {
106 | XCTAssertEqual(constraint.priority, UILayoutPriority(567.8))
107 | }
108 | }
109 |
110 | func testAdditionSubtraction() {
111 | // + operator
112 | XCTAssertEqual((LayoutPriority.high + 2).rawValue, LayoutPriority.high.rawValue + 2)
113 | XCTAssertEqual((LayoutPriority.low + 1.7).rawValue, LayoutPriority.low.rawValue + 1.7)
114 | XCTAssertEqual((LayoutPriority.required + 1).rawValue, LayoutPriority.required.rawValue)
115 |
116 | // - operator
117 | XCTAssertEqual((LayoutPriority.low - 5).rawValue, LayoutPriority.low.rawValue - 5)
118 | XCTAssertEqual((LayoutPriority.required - 3.14).rawValue, LayoutPriority.required.rawValue - 3.14)
119 | XCTAssertEqual((LayoutPriority.low - 999).rawValue, 0)
120 |
121 | // +=
122 | var priority: LayoutPriority = .high
123 | var value = priority.rawValue
124 |
125 | priority += 1
126 | value += 1
127 | XCTAssertEqual(priority.rawValue, value)
128 |
129 | priority += 12.8
130 | value += 12.8
131 | XCTAssertEqual(priority.rawValue, value)
132 |
133 | // -=
134 | priority -= 1
135 | value -= 1
136 | XCTAssertEqual(priority.rawValue, value)
137 |
138 | priority -= 2.5
139 | value -= 2.5
140 | XCTAssertEqual(priority.rawValue, value)
141 |
142 | // test with a view
143 | let subview = UIView()
144 | container.addSubview(subview)
145 |
146 | for constraint in subview.layout.size(200, priority: .required - 1).constraints() {
147 | XCTAssertEqual(constraint.priority.rawValue, UILayoutPriority.required.rawValue - 1)
148 | }
149 | }
150 |
151 | func testEquatable() {
152 | XCTAssertEqual(LayoutPriority.required, 1000.0)
153 | XCTAssertEqual(LayoutPriority.high, 750)
154 | XCTAssertEqual(LayoutPriority.low, 250)
155 |
156 | let priority: LayoutPriority = .low - 1
157 | XCTAssertEqual(priority, LayoutPriority(rawValue: 249))
158 | }
159 |
160 | }
161 |
--------------------------------------------------------------------------------
/Tests/WWLayoutTests/SafeAreaTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ===----------------------------------------------------------------------===//
3 | //
4 | // SafeAreaTests.swift
5 | //
6 | // Created by Steven Grosmark on 3/26/18.
7 | // Copyright © 2018 WW International, Inc.
8 | //
9 | //
10 | // This source file is part of the WWLayout open source project
11 | //
12 | // https://github.com/ww-tech/wwlayout
13 | //
14 | // Copyright © 2017-2021 WW International, Inc.
15 | //
16 | // Licensed under the Apache License, Version 2.0 (the "License");
17 | // you may not use this file except in compliance with the License.
18 | // You may obtain a copy of the License at
19 | //
20 | // http://www.apache.org/licenses/LICENSE-2.0
21 | //
22 | // Unless required by applicable law or agreed to in writing, software
23 | // distributed under the License is distributed on an "AS IS" BASIS,
24 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25 | // See the License for the specific language governing permissions and
26 | // limitations under the License.
27 | //
28 | // ===----------------------------------------------------------------------===//
29 | //
30 |
31 | import XCTest
32 | @testable import WWLayout
33 |
34 | class SafeAreaTests: XCTestCase {
35 |
36 | private var controller: UIViewController!
37 |
38 | override func setUp() {
39 | super.setUp()
40 | controller = UIViewController()
41 | }
42 |
43 | override func tearDown() {
44 | super.tearDown()
45 | controller = nil
46 | }
47 |
48 | func testSafeArea() {
49 | let subview = UIView()
50 | let centered = UIView()
51 | controller.view.addSubview(subview)
52 | controller.view.addSubview(centered)
53 | controller.beginAppearanceTransition(true, animated: true)
54 |
55 | XCTAssertEqual(subview.layout.fill(.safeArea).constraints().count, 4)
56 | XCTAssertEqual(centered.layout.center(in: .safeArea).size(200).constraints().count, 4)
57 | }
58 |
59 | func testEmulatedSafeArea() {
60 | let subview = UIView()
61 | let centered = UIView()
62 | controller.view.addSubview(subview)
63 | controller.view.addSubview(centered)
64 | controller.beginAppearanceTransition(true, animated: true)
65 |
66 | XCTAssertEqual(subview.layout.fill(EmulatedSafeAreaAnchorable(for: controller.view)).constraints().count, 4)
67 |
68 | let special = EmulatedSafeAreaAnchorable(for: controller.view)
69 | XCTAssertEqual(centered.layout.center(in: special).size(200).constraints().count, 4)
70 | }
71 |
72 | }
73 |
--------------------------------------------------------------------------------
/Tests/WWLayoutTests/SiblingTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ===----------------------------------------------------------------------===//
3 | //
4 | // SiblingTests.swift
5 | //
6 | // Created by Steven Grosmark on 1/25/18.
7 | // Copyright © 2018 WW International, Inc.
8 | //
9 | //
10 | // This source file is part of the WWLayout open source project
11 | //
12 | // https://github.com/ww-tech/wwlayout
13 | //
14 | // Copyright © 2017-2021 WW International, Inc.
15 | //
16 | // Licensed under the Apache License, Version 2.0 (the "License");
17 | // you may not use this file except in compliance with the License.
18 | // You may obtain a copy of the License at
19 | //
20 | // http://www.apache.org/licenses/LICENSE-2.0
21 | //
22 | // Unless required by applicable law or agreed to in writing, software
23 | // distributed under the License is distributed on an "AS IS" BASIS,
24 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25 | // See the License for the specific language governing permissions and
26 | // limitations under the License.
27 | //
28 | // ===----------------------------------------------------------------------===//
29 | //
30 |
31 | import XCTest
32 | @testable import WWLayout
33 |
34 | class SiblingTests: XCTestCase {
35 |
36 | private var container: UIView!
37 | private var view1: UIView!
38 | private var view2: UIView!
39 |
40 | override func setUp() {
41 | super.setUp()
42 | container = UIView(frame: CGRect(x: 0, y: 0, width: 400, height: 400))
43 | if container.constraints.isEmpty {
44 | NSLayoutConstraint.activate([
45 | container.widthAnchor.constraint(equalToConstant: 400),
46 | container.heightAnchor.constraint(equalToConstant: 400)
47 | ])
48 | }
49 |
50 | view1 = UIView()
51 | view2 = UIView()
52 | view1.layout.size(200)
53 | view2.layout.size(200)
54 | container.addSubview(view1)
55 | container.addSubview(view2)
56 | }
57 |
58 | override func tearDown() {
59 | super.tearDown()
60 | container = nil
61 | }
62 |
63 | func testDiagonalArrangement() {
64 | view1.layout.top(to: .superview).left(to: .superview)
65 | view2.layout.top(to: view1, edge: .bottom).left(to: view1, edge: .right)
66 |
67 | container.layoutIfNeeded()
68 |
69 | XCTAssertEqual(view1.frame, CGRect(x: 0, y: 0, width: 200, height: 200))
70 | XCTAssertEqual(view2.frame, CGRect(x: 200, y: 200, width: 200, height: 200))
71 | }
72 |
73 | func testCenterX() {
74 | view1.layout.top(to: .superview).center(in: .superview, axis: .x)
75 | view2.layout.top(to: view1, edge: .bottom).center(in: .superview, axis: .x)
76 |
77 | container.layoutIfNeeded()
78 |
79 | XCTAssertEqual(view1.frame, CGRect(x: 100, y: 0, width: 200, height: 200))
80 | XCTAssertEqual(view2.frame, CGRect(x: 100, y: 200, width: 200, height: 200))
81 | }
82 |
83 | func testCenterY() {
84 | view1.layout.left(to: .superview).center(in: .superview, axis: .y)
85 | view2.layout.left(to: view1, edge: .right).center(in: .superview, axis: .y)
86 |
87 | container.layoutIfNeeded()
88 |
89 | XCTAssertEqual(view1.frame, CGRect(x: 0, y: 100, width: 200, height: 200))
90 | XCTAssertEqual(view2.frame, CGRect(x: 200, y: 100, width: 200, height: 200))
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/Tests/WWLayoutTests/StackingTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ===----------------------------------------------------------------------===//
3 | //
4 | // StackingTests.swift
5 | //
6 | // Created by Steven Grosmark on 11/7/19
7 | // Copyright © 2019 WW International, Inc.
8 | //
9 | //
10 | // This source file is part of the WWLayout open source project
11 | //
12 | // https://github.com/ww-tech/wwlayout
13 | //
14 | // Copyright © 2017-2021 WW International, Inc.
15 | //
16 | // Licensed under the Apache License, Version 2.0 (the "License");
17 | // you may not use this file except in compliance with the License.
18 | // You may obtain a copy of the License at
19 | //
20 | // http://www.apache.org/licenses/LICENSE-2.0
21 | //
22 | // Unless required by applicable law or agreed to in writing, software
23 | // distributed under the License is distributed on an "AS IS" BASIS,
24 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25 | // See the License for the specific language governing permissions and
26 | // limitations under the License.
27 | //
28 | // ===----------------------------------------------------------------------===//
29 | //
30 |
31 | import XCTest
32 | @testable import WWLayout
33 |
34 | class StackingBelowTests: XCTestCase {
35 |
36 | private var container: UIView!
37 | private var a: UIView!
38 | private var b: UIView!
39 | private var c: UIView!
40 |
41 | override func setUp() {
42 | super.setUp()
43 | container = UIView()
44 | a = UIView()
45 | b = UIView()
46 | c = UIView()
47 | container.addSubviews(a, b, c)
48 | }
49 |
50 | override func tearDown() {
51 | container = nil
52 | a = nil
53 | b = nil
54 | c = nil
55 | super.tearDown()
56 | }
57 |
58 | func test_below_noOffset() {
59 | // when
60 | a.layout.top(to: .superview).height(20)
61 | b.layout.below(a)
62 | container.layoutIfNeeded()
63 |
64 | // then
65 | XCTAssertEqual(b.frame.origin.y, 20)
66 | }
67 |
68 | func test_below_withOffset() {
69 | // when
70 | a.layout.top(to: .superview).height(20)
71 | b.layout.below(a, offset: 10)
72 | container.layoutIfNeeded()
73 |
74 | // then
75 | XCTAssertEqual(b.frame.origin.y, 30)
76 | }
77 |
78 | func test_followedBy_noOffset() {
79 | // when
80 | a.layout.top(to: .superview).height(20)
81 | b.layout
82 | .below(a)
83 | .height(20)
84 | .followedBy(c)
85 | container.layoutIfNeeded()
86 |
87 | // then
88 | XCTAssertEqual(b.frame.origin.y, 20)
89 | XCTAssertEqual(c.frame.origin.y, 40)
90 | }
91 |
92 | func test_followedBy_withOffset() {
93 | // when
94 | a.layout.top(to: .superview).height(20)
95 | b.layout
96 | .below(a, offset: 10)
97 | .height(20)
98 | .followedBy(c, offset: 10)
99 | container.layoutIfNeeded()
100 |
101 | // then
102 | XCTAssertEqual(b.frame.origin.y, 30)
103 | XCTAssertEqual(c.frame.origin.y, 60)
104 | }
105 |
106 | func test_stack_noSpace() {
107 | // when
108 | a.layout.top(to: .superview)
109 | [a, b, c].forEach { $0.layout.height(20) }
110 | container.layout.stack(a, b, c)
111 | container.layoutIfNeeded()
112 | Layout.describeConstraints(in: container)
113 |
114 | // then
115 | XCTAssertEqual(b.frame.origin.y, 20)
116 | XCTAssertEqual(c.frame.origin.y, 40)
117 | }
118 |
119 | func test_stack_withSpace() {
120 | // when
121 | a.layout.top(to: .superview)
122 | [a, b, c].forEach { $0.layout.height(20) }
123 | container.layout.stack(a, b, c, space: 10)
124 | container.layoutIfNeeded()
125 | Layout.describeConstraints(in: container)
126 |
127 | // then
128 | XCTAssertEqual(b.frame.origin.y, 30)
129 | XCTAssertEqual(c.frame.origin.y, 60)
130 | }
131 |
132 | func test_stack_withOffset() {
133 | // when
134 | a.layout.top(to: .superview)
135 | [a, b, c].forEach { $0.layout.height(20) }
136 | container.layout.stack(b, c, space: 10, below: a, offset: 100)
137 | container.layoutIfNeeded()
138 | Layout.describeConstraints(in: container)
139 |
140 | // then
141 | XCTAssertEqual(b.frame.origin.y, 120)
142 | XCTAssertEqual(c.frame.origin.y, 150)
143 | }
144 |
145 | }
146 |
--------------------------------------------------------------------------------
/Tests/WWLayoutTests/SwiftCompatibility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ===----------------------------------------------------------------------===//
3 | //
4 | // SwiftCompatibility.swift
5 | //
6 | // Created by Steven Grosmark on 12/3/18.
7 | // Copyright © 2018 WW International, Inc.
8 | //
9 | //
10 | // This source file is part of the WWLayout open source project
11 | //
12 | // https://github.com/ww-tech/wwlayout
13 | //
14 | // Copyright © 2017-2021 WW International, Inc.
15 | //
16 | // Licensed under the Apache License, Version 2.0 (the "License");
17 | // you may not use this file except in compliance with the License.
18 | // You may obtain a copy of the License at
19 | //
20 | // http://www.apache.org/licenses/LICENSE-2.0
21 | //
22 | // Unless required by applicable law or agreed to in writing, software
23 | // distributed under the License is distributed on an "AS IS" BASIS,
24 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25 | // See the License for the specific language governing permissions and
26 | // limitations under the License.
27 | //
28 | // ===----------------------------------------------------------------------===//
29 | //
30 |
31 | import UIKit
32 |
33 | #if swift(>=4.0)
34 | #else
35 |
36 | // Make Swift 3 UILayoutPriority surface area look more like Swift 4's:
37 | extension UILayoutPriority {
38 |
39 | static let required: Float = 1000
40 | static let defaultHigh: Float = 750
41 | static let defaultLow: Float = 250
42 |
43 | var rawValue: Float { return self }
44 |
45 | }
46 |
47 | #endif
48 |
--------------------------------------------------------------------------------
/Tests/WWLayoutTests/UIView+Subviews.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ===----------------------------------------------------------------------===//
3 | //
4 | // UIView+Subviews.swift
5 | //
6 | // Created by Steven Grosmark on 08/11/2021
7 | // Copyright © 2021 WW International, Inc.
8 | //
9 | //
10 | // This source file is part of the WWLayout open source project
11 | //
12 | // https://github.com/ww-tech/wwlayout
13 | //
14 | // Copyright © 2017-2021 WW International, Inc.
15 | //
16 | // Licensed under the Apache License, Version 2.0 (the "License");
17 | // you may not use this file except in compliance with the License.
18 | // You may obtain a copy of the License at
19 | //
20 | // http://www.apache.org/licenses/LICENSE-2.0
21 | //
22 | // Unless required by applicable law or agreed to in writing, software
23 | // distributed under the License is distributed on an "AS IS" BASIS,
24 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25 | // See the License for the specific language governing permissions and
26 | // limitations under the License.
27 | //
28 | // ===----------------------------------------------------------------------===//
29 | //
30 |
31 | import UIKit
32 |
33 | extension UIView {
34 |
35 | convenience init(subviews: UIView...) {
36 | self.init()
37 | addSubviews(subviews)
38 | }
39 |
40 | func addSubviews(_ views: UIView...) {
41 | addSubviews(views)
42 | }
43 |
44 | func addSubviews(_ views: [UIView]) {
45 | views.forEach(addSubview)
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/WWLayout.podspec:
--------------------------------------------------------------------------------
1 | #
2 | # Be sure to run `pod spec lint WWLayout.podspec' to ensure this is a
3 | # valid spec and to remove all comments including this before submitting the spec.
4 | #
5 | # To learn more about Podspec attributes see http://docs.cocoapods.org/specification.html
6 | # To see working Podspecs in the CocoaPods repo see https://github.com/CocoaPods/Specs/
7 | #
8 |
9 | Pod::Spec.new do |s|
10 |
11 | s.name = "WWLayout"
12 | s.version = "0.8.2"
13 | s.summary = "Swifty DSL for programmatic Auto Layout in iOS"
14 | s.description = "WWLayout is an elegant way to add auto-layout constraints with code."
15 | s.homepage = "https://ww-tech.github.io/wwlayout/"
16 |
17 | s.license = "Apache-2.0"
18 | s.author = { "Steven Grosmark" => "steven.grosmark@weightwatchers.com" }
19 | s.platform = :ios, "9.0"
20 | s.swift_versions = '4.0', '4.2', '5', '5.1', '5.2', '5.3'
21 |
22 | s.source = { :git => "https://github.com/ww-tech/wwlayout.git", :tag => s.version.to_s }
23 | s.source_files = "Sources/WWLayout", "Sources/WWLayout/**/*.{h,m,swift}"
24 |
25 | end
26 |
--------------------------------------------------------------------------------
/WWLayout.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/WWLayout.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/WWLayout.xcodeproj/xcshareddata/IDETemplateMacros.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | FILEHEADER
6 |
7 | // ===----------------------------------------------------------------------===//
8 | //
9 | // ___FILENAME___
10 | //
11 | // Created by ___FULLUSERNAME___ on ___DATE___
12 | // Copyright © ___YEAR___ WW International, Inc.
13 | //
14 | //
15 | // This source file is part of the WWLayout open source project
16 | //
17 | // https://github.com/ww-tech/wwlayout
18 | //
19 | // Copyright © 2017-___YEAR___ WW International, Inc.
20 | //
21 | // Licensed under the Apache License, Version 2.0 (the "License");
22 | // you may not use this file except in compliance with the License.
23 | // You may obtain a copy of the License at
24 | //
25 | // http://www.apache.org/licenses/LICENSE-2.0
26 | //
27 | // Unless required by applicable law or agreed to in writing, software
28 | // distributed under the License is distributed on an "AS IS" BASIS,
29 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
30 | // See the License for the specific language governing permissions and
31 | // limitations under the License.
32 | //
33 | // ===----------------------------------------------------------------------===//
34 | //
35 |
36 |
37 |
--------------------------------------------------------------------------------
/WWLayout.xcodeproj/xcshareddata/xcschemes/WWLayout.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
38 |
39 |
40 |
41 |
43 |
49 |
50 |
51 |
52 |
53 |
63 |
64 |
70 |
71 |
72 |
73 |
79 |
80 |
86 |
87 |
88 |
89 |
91 |
92 |
95 |
96 |
97 |
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutExample.xcodeproj/xcshareddata/IDETemplateMacros.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | FILEHEADER
6 |
7 | // ===----------------------------------------------------------------------===//
8 | //
9 | // ___FILENAME___
10 | //
11 | // Created by ___FULLUSERNAME___ on ___DATE___
12 | // Copyright © ___YEAR___ WW International, Inc.
13 | //
14 | //
15 | // This source file is part of the WWLayout open source project
16 | //
17 | // https://github.com/ww-tech/wwlayout
18 | //
19 | // Copyright © 2017-___YEAR___ WW International, Inc.
20 | //
21 | // Licensed under the Apache License, Version 2.0 (the "License");
22 | // you may not use this file except in compliance with the License.
23 | // You may obtain a copy of the License at
24 | //
25 | // http://www.apache.org/licenses/LICENSE-2.0
26 | //
27 | // Unless required by applicable law or agreed to in writing, software
28 | // distributed under the License is distributed on an "AS IS" BASIS,
29 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
30 | // See the License for the specific language governing permissions and
31 | // limitations under the License.
32 | //
33 | // ===----------------------------------------------------------------------===//
34 | //
35 |
36 |
37 |
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutExample.xcodeproj/xcshareddata/xcschemes/WWLayoutExample.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
42 |
48 |
49 |
50 |
52 |
58 |
59 |
60 |
61 |
62 |
72 |
74 |
80 |
81 |
82 |
83 |
89 |
91 |
97 |
98 |
99 |
100 |
102 |
103 |
106 |
107 |
108 |
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutExample/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ===----------------------------------------------------------------------===//
3 | //
4 | // AppDelegate.swift
5 | //
6 | // Created by Steven Grosmark on 12/1/17.
7 | // Copyright © 2018 WW International, Inc.
8 | //
9 | //
10 | // This source file is part of the WWLayout open source project
11 | //
12 | // https://github.com/ww-tech/wwlayout
13 | //
14 | // Copyright © 2017-2021 WW International, Inc.
15 | //
16 | // Licensed under the Apache License, Version 2.0 (the "License");
17 | // you may not use this file except in compliance with the License.
18 | // You may obtain a copy of the License at
19 | //
20 | // http://www.apache.org/licenses/LICENSE-2.0
21 | //
22 | // Unless required by applicable law or agreed to in writing, software
23 | // distributed under the License is distributed on an "AS IS" BASIS,
24 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25 | // See the License for the specific language governing permissions and
26 | // limitations under the License.
27 | //
28 | // ===----------------------------------------------------------------------===//
29 | //
30 |
31 | import UIKit
32 |
33 | @UIApplicationMain
34 | class AppDelegate: UIResponder, UIApplicationDelegate {
35 |
36 | var window: UIWindow?
37 |
38 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
39 |
40 | window = UIWindow(frame: UIScreen.main.bounds)
41 | if let window = window {
42 | let firstViewController = SampleListViewController()
43 | let navigationController = UINavigationController(rootViewController: firstViewController)
44 | window.rootViewController = navigationController
45 | window.makeKeyAndVisible()
46 | }
47 |
48 | return true
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutExample/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "Icon-40.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "Icon-60.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "Icon-59.png",
19 | "scale" : "2x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "Icon-87.png",
25 | "scale" : "3x"
26 | },
27 | {
28 | "size" : "40x40",
29 | "idiom" : "iphone",
30 | "filename" : "Icon-80.png",
31 | "scale" : "2x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "Icon-120.png",
37 | "scale" : "3x"
38 | },
39 | {
40 | "size" : "60x60",
41 | "idiom" : "iphone",
42 | "filename" : "Icon-121.png",
43 | "scale" : "2x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "Icon-180.png",
49 | "scale" : "3x"
50 | },
51 | {
52 | "size" : "20x20",
53 | "idiom" : "ipad",
54 | "filename" : "Icon-20.png",
55 | "scale" : "1x"
56 | },
57 | {
58 | "size" : "20x20",
59 | "idiom" : "ipad",
60 | "filename" : "Icon-41.png",
61 | "scale" : "2x"
62 | },
63 | {
64 | "size" : "29x29",
65 | "idiom" : "ipad",
66 | "filename" : "Icon-29.png",
67 | "scale" : "1x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "Icon-58.png",
73 | "scale" : "2x"
74 | },
75 | {
76 | "size" : "40x40",
77 | "idiom" : "ipad",
78 | "filename" : "Icon-42.png",
79 | "scale" : "1x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "Icon-81.png",
85 | "scale" : "2x"
86 | },
87 | {
88 | "size" : "76x76",
89 | "idiom" : "ipad",
90 | "filename" : "Icon-76.png",
91 | "scale" : "1x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "Icon-152.png",
97 | "scale" : "2x"
98 | },
99 | {
100 | "size" : "83.5x83.5",
101 | "idiom" : "ipad",
102 | "filename" : "Icon-167.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "1024x1024",
107 | "idiom" : "ios-marketing",
108 | "filename" : "Icon-1024.png",
109 | "scale" : "1x"
110 | }
111 | ],
112 | "info" : {
113 | "version" : 1,
114 | "author" : "xcode"
115 | }
116 | }
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutExample/Assets.xcassets/AppIcon.appiconset/Icon-1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ww-tech/wwlayout/c8e74ae42ff24311b445032f19f50895d6e295ae/WWLayoutExample/WWLayoutExample/Assets.xcassets/AppIcon.appiconset/Icon-1024.png
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutExample/Assets.xcassets/AppIcon.appiconset/Icon-120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ww-tech/wwlayout/c8e74ae42ff24311b445032f19f50895d6e295ae/WWLayoutExample/WWLayoutExample/Assets.xcassets/AppIcon.appiconset/Icon-120.png
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutExample/Assets.xcassets/AppIcon.appiconset/Icon-121.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ww-tech/wwlayout/c8e74ae42ff24311b445032f19f50895d6e295ae/WWLayoutExample/WWLayoutExample/Assets.xcassets/AppIcon.appiconset/Icon-121.png
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutExample/Assets.xcassets/AppIcon.appiconset/Icon-152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ww-tech/wwlayout/c8e74ae42ff24311b445032f19f50895d6e295ae/WWLayoutExample/WWLayoutExample/Assets.xcassets/AppIcon.appiconset/Icon-152.png
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutExample/Assets.xcassets/AppIcon.appiconset/Icon-167.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ww-tech/wwlayout/c8e74ae42ff24311b445032f19f50895d6e295ae/WWLayoutExample/WWLayoutExample/Assets.xcassets/AppIcon.appiconset/Icon-167.png
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutExample/Assets.xcassets/AppIcon.appiconset/Icon-180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ww-tech/wwlayout/c8e74ae42ff24311b445032f19f50895d6e295ae/WWLayoutExample/WWLayoutExample/Assets.xcassets/AppIcon.appiconset/Icon-180.png
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutExample/Assets.xcassets/AppIcon.appiconset/Icon-20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ww-tech/wwlayout/c8e74ae42ff24311b445032f19f50895d6e295ae/WWLayoutExample/WWLayoutExample/Assets.xcassets/AppIcon.appiconset/Icon-20.png
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutExample/Assets.xcassets/AppIcon.appiconset/Icon-29.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ww-tech/wwlayout/c8e74ae42ff24311b445032f19f50895d6e295ae/WWLayoutExample/WWLayoutExample/Assets.xcassets/AppIcon.appiconset/Icon-29.png
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutExample/Assets.xcassets/AppIcon.appiconset/Icon-40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ww-tech/wwlayout/c8e74ae42ff24311b445032f19f50895d6e295ae/WWLayoutExample/WWLayoutExample/Assets.xcassets/AppIcon.appiconset/Icon-40.png
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutExample/Assets.xcassets/AppIcon.appiconset/Icon-41.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ww-tech/wwlayout/c8e74ae42ff24311b445032f19f50895d6e295ae/WWLayoutExample/WWLayoutExample/Assets.xcassets/AppIcon.appiconset/Icon-41.png
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutExample/Assets.xcassets/AppIcon.appiconset/Icon-42.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ww-tech/wwlayout/c8e74ae42ff24311b445032f19f50895d6e295ae/WWLayoutExample/WWLayoutExample/Assets.xcassets/AppIcon.appiconset/Icon-42.png
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutExample/Assets.xcassets/AppIcon.appiconset/Icon-58.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ww-tech/wwlayout/c8e74ae42ff24311b445032f19f50895d6e295ae/WWLayoutExample/WWLayoutExample/Assets.xcassets/AppIcon.appiconset/Icon-58.png
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutExample/Assets.xcassets/AppIcon.appiconset/Icon-59.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ww-tech/wwlayout/c8e74ae42ff24311b445032f19f50895d6e295ae/WWLayoutExample/WWLayoutExample/Assets.xcassets/AppIcon.appiconset/Icon-59.png
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutExample/Assets.xcassets/AppIcon.appiconset/Icon-60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ww-tech/wwlayout/c8e74ae42ff24311b445032f19f50895d6e295ae/WWLayoutExample/WWLayoutExample/Assets.xcassets/AppIcon.appiconset/Icon-60.png
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutExample/Assets.xcassets/AppIcon.appiconset/Icon-76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ww-tech/wwlayout/c8e74ae42ff24311b445032f19f50895d6e295ae/WWLayoutExample/WWLayoutExample/Assets.xcassets/AppIcon.appiconset/Icon-76.png
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutExample/Assets.xcassets/AppIcon.appiconset/Icon-80.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ww-tech/wwlayout/c8e74ae42ff24311b445032f19f50895d6e295ae/WWLayoutExample/WWLayoutExample/Assets.xcassets/AppIcon.appiconset/Icon-80.png
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutExample/Assets.xcassets/AppIcon.appiconset/Icon-81.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ww-tech/wwlayout/c8e74ae42ff24311b445032f19f50895d6e295ae/WWLayoutExample/WWLayoutExample/Assets.xcassets/AppIcon.appiconset/Icon-81.png
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutExample/Assets.xcassets/AppIcon.appiconset/Icon-87.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ww-tech/wwlayout/c8e74ae42ff24311b445032f19f50895d6e295ae/WWLayoutExample/WWLayoutExample/Assets.xcassets/AppIcon.appiconset/Icon-87.png
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutExample/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutExample/Assets.xcassets/logo.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "logo.png"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | }
12 | }
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutExample/Assets.xcassets/logo.imageset/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ww-tech/wwlayout/c8e74ae42ff24311b445032f19f50895d6e295ae/WWLayoutExample/WWLayoutExample/Assets.xcassets/logo.imageset/logo.png
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutExample/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutExample/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UILaunchStoryboardName
24 | LaunchScreen
25 | UIRequiredDeviceCapabilities
26 |
27 | armv7
28 |
29 | UISupportedInterfaceOrientations
30 |
31 | UIInterfaceOrientationPortrait
32 | UIInterfaceOrientationLandscapeLeft
33 | UIInterfaceOrientationLandscapeRight
34 |
35 | UISupportedInterfaceOrientations~ipad
36 |
37 | UIInterfaceOrientationPortrait
38 | UIInterfaceOrientationPortraitUpsideDown
39 | UIInterfaceOrientationLandscapeLeft
40 | UIInterfaceOrientationLandscapeRight
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutExample/SampleListViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ===----------------------------------------------------------------------===//
3 | //
4 | // SampleListViewController.swift
5 | //
6 | // Created by Steven Grosmark on 12/1/17.
7 | // Copyright © 2018 WW International, Inc.
8 | //
9 | //
10 | // This source file is part of the WWLayout open source project
11 | //
12 | // https://github.com/ww-tech/wwlayout
13 | //
14 | // Copyright © 2017-2021 WW International, Inc.
15 | //
16 | // Licensed under the Apache License, Version 2.0 (the "License");
17 | // you may not use this file except in compliance with the License.
18 | // You may obtain a copy of the License at
19 | //
20 | // http://www.apache.org/licenses/LICENSE-2.0
21 | //
22 | // Unless required by applicable law or agreed to in writing, software
23 | // distributed under the License is distributed on an "AS IS" BASIS,
24 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25 | // See the License for the specific language governing permissions and
26 | // limitations under the License.
27 | //
28 | // ===----------------------------------------------------------------------===//
29 | //
30 |
31 | import UIKit
32 | import WWLayout
33 |
34 | class SampleListViewController: UIViewController {
35 |
36 | fileprivate let cellIdentifier = "Cell"
37 | fileprivate var tableView: UITableView?
38 |
39 | fileprivate struct Item {
40 | let label: String
41 | let controller: () -> UIViewController
42 | }
43 | fileprivate var items = [Item]()
44 |
45 | override func viewDidLoad() {
46 | super.viewDidLoad()
47 |
48 | title = "WW Layout Samples"
49 | navigationItem.backBarButtonItem = UIBarButtonItem(title: "Samples", style: .plain, target: nil, action: nil)
50 |
51 | setupViews()
52 | setupConstraints()
53 | }
54 |
55 | private func setupViews() {
56 | view.backgroundColor = .white
57 |
58 | items = [
59 | Item(label: "Basic Sample", controller: { return BasicSample() }),
60 | Item(label: "Safe Area Sample", controller: { return self.wrapInTabBar(SafeAreaSample()) }),
61 | Item(label: "Layout Margins Sample", controller: { return self.wrapInTabBar(LayoutMarginsSample()) }),
62 | Item(label: "Three Edges", controller: { return ThreeEdges() }),
63 | Item(label: "Fill Width", controller: { return FillWidthSample() }),
64 | Item(label: "Center Edges", controller: { return CenterEdges() }),
65 | Item(label: "Baseline Edges", controller: { return Baselines() }),
66 | Item(label: "Tags", controller: { return TaggingSample() }),
67 | Item(label: "Size Classes", controller: { return SizeClassSample() })
68 | ]
69 |
70 | tableView = UITableView()
71 | if let tableView = tableView {
72 | tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellIdentifier)
73 | tableView.dataSource = self
74 | tableView.delegate = self
75 | view.addSubview(tableView)
76 | }
77 | }
78 |
79 | private func wrapInTabBar(_ controller: UIViewController) -> UIViewController {
80 | controller.tabBarItem = UITabBarItem(tabBarSystemItem: .favorites, tag: 1)
81 | let tabBarController = UITabBarController()
82 | tabBarController.viewControllers = [controller]
83 | return tabBarController
84 | }
85 |
86 | private func setupConstraints() {
87 | tableView?.layout.fill(.superview)
88 | }
89 |
90 | }
91 |
92 | extension SampleListViewController: UITableViewDataSource {
93 |
94 | public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
95 | return items.count
96 | }
97 |
98 | public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
99 | let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath)
100 | cell.textLabel?.text = items[indexPath.row].label
101 | return cell
102 | }
103 |
104 | }
105 |
106 | extension SampleListViewController: UITableViewDelegate {
107 | public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
108 | tableView.deselectRow(at: indexPath, animated: true)
109 | let item = items[indexPath.row]
110 | navigationController?.pushViewController(item.controller(), animated: true)
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutExample/Samples/Baselines.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ===----------------------------------------------------------------------===//
3 | //
4 | // Baselines.swift
5 | //
6 | // Created by Steven Grosmark on 1/23/18.
7 | // Copyright © 2018 WW International, Inc.
8 | //
9 | //
10 | // This source file is part of the WWLayout open source project
11 | //
12 | // https://github.com/ww-tech/wwlayout
13 | //
14 | // Copyright © 2017-2021 WW International, Inc.
15 | //
16 | // Licensed under the Apache License, Version 2.0 (the "License");
17 | // you may not use this file except in compliance with the License.
18 | // You may obtain a copy of the License at
19 | //
20 | // http://www.apache.org/licenses/LICENSE-2.0
21 | //
22 | // Unless required by applicable law or agreed to in writing, software
23 | // distributed under the License is distributed on an "AS IS" BASIS,
24 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25 | // See the License for the specific language governing permissions and
26 | // limitations under the License.
27 | //
28 | // ===----------------------------------------------------------------------===//
29 | //
30 |
31 | import UIKit
32 | import WWLayout
33 |
34 | public class Baselines: SampleViewController {
35 |
36 | override public func viewDidLoad() {
37 | super.viewDidLoad()
38 |
39 | view.backgroundColor = .white
40 |
41 | let insetView = UIView()
42 | view.addSubview(insetView)
43 | insetView.layout.fill(.safeArea, inset: 10)
44 |
45 | let letter = UILabel()
46 | letter.font = UIFont(name: "Arial", size: 64)
47 | letter.text = "L"
48 | insetView.addSubview(letter)
49 |
50 | let paragraph = UILabel()
51 | paragraph.font = UIFont(name: "Arial", size: 16)
52 | paragraph.numberOfLines = 0
53 | paragraph.text = """
54 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec consectetur lacus \
55 | non dapibus lobortis. Nunc at elit a mauris mattis rutrum vel sit amet purus. Morbi \
56 | at est non felis consequat fermentum. Duis accumsan non felis ut iaculis. Orci \
57 | varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. \
58 | Pellentesque ornare velit lectus, id tincidunt lectus sodales nec. Nunc non lorem \
59 | eget justo laoreet ornare quis non mi. Nunc facilisis arcu nec risus dignissim, a \
60 | varius ligula pulvinar. Aliquam leo mauris, accumsan a sapien vel, congue lobortis velit.
61 | """
62 | insetView.addSubview(paragraph)
63 |
64 | let byline = UILabel()
65 | byline.font = UIFont(name: "Arial", size: 32)
66 | byline.text = "- Anon"
67 | insetView.addSubview(byline)
68 |
69 | letter.setContentCompressionResistancePriority(.required, for: .horizontal)
70 | paragraph.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
71 | byline.setContentCompressionResistancePriority(.required, for: .horizontal)
72 |
73 | letter.setContentHuggingPriority(.required, for: .horizontal)
74 | byline.setContentHuggingPriority(.required, for: .horizontal)
75 |
76 | letter.layout
77 | .top(to: .superview)
78 | .left(to: .superview)
79 |
80 | paragraph.layout
81 | .firstBaseline(to: letter)
82 | .left(to: letter, edge: .right)
83 | .right(to: byline, edge: .left, offset: -10)
84 |
85 | byline.layout
86 | .firstBaseline(to: paragraph, edge: .lastBaseline)
87 | .right(to: .superview)
88 | }
89 |
90 | }
91 |
92 | extension UIView {
93 | fileprivate func addNewColorView(_ color: UIColor) -> UIView {
94 | let colorView = UIView()
95 | colorView.backgroundColor = color
96 | addSubview(colorView)
97 | return colorView
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutExample/Samples/BasicSample.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ===----------------------------------------------------------------------===//
3 | //
4 | // BasicSample.swift
5 | //
6 | // Created by Steven Grosmark on 12/1/17.
7 | // Copyright © 2018 WW International, Inc.
8 | //
9 | //
10 | // This source file is part of the WWLayout open source project
11 | //
12 | // https://github.com/ww-tech/wwlayout
13 | //
14 | // Copyright © 2017-2021 WW International, Inc.
15 | //
16 | // Licensed under the Apache License, Version 2.0 (the "License");
17 | // you may not use this file except in compliance with the License.
18 | // You may obtain a copy of the License at
19 | //
20 | // http://www.apache.org/licenses/LICENSE-2.0
21 | //
22 | // Unless required by applicable law or agreed to in writing, software
23 | // distributed under the License is distributed on an "AS IS" BASIS,
24 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25 | // See the License for the specific language governing permissions and
26 | // limitations under the License.
27 | //
28 | // ===----------------------------------------------------------------------===//
29 | //
30 |
31 | import UIKit
32 | import WWLayout
33 |
34 | public class BasicSample: UIViewController {
35 |
36 | let insetView = UIView()
37 | let squareView = UIView()
38 | let circleView = UIView()
39 | let label = UILabel()
40 | let label2 = UILabel()
41 | let slider = UISlider()
42 |
43 | override public func viewDidLoad() {
44 | super.viewDidLoad()
45 | setupViews()
46 | setupConstraints()
47 | }
48 |
49 | private func setupViews() {
50 | view.backgroundColor = .white
51 |
52 | insetView.backgroundColor = UIColor(red: 0.98, green: 0.97, blue: 0.98, alpha: 1.0)
53 | view.addSubview(insetView)
54 |
55 | squareView.backgroundColor = UIColor(red: 0.5, green: 0.5, blue: 0.5, alpha: 0.5)
56 | insetView.addSubview(squareView)
57 |
58 | circleView.backgroundColor = .white
59 | circleView.layer.cornerRadius = 30
60 | squareView.addSubview(circleView)
61 |
62 | label.font = .systemFont(ofSize: 18, weight: .bold)
63 | label.text = "Hello World!"
64 | label.textColor = .black
65 |
66 | insetView.addSubview(label)
67 |
68 | label2.numberOfLines = 0
69 | label2.text = """
70 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor \
71 | incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud \
72 | exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
73 | """
74 | label2.textColor = .black
75 |
76 | insetView.addSubview(label2)
77 |
78 | insetView.addSubview(slider)
79 | }
80 |
81 | private func setupConstraints() {
82 |
83 | // The safe area pre-iOS 11 will fall back to .superview left/right and the controller's top/bottom layout guides
84 | insetView.layout.fill(.safeArea, inset: 20)
85 |
86 | slider.layout
87 | .bottom(to: insetView, edge: .bottom, offset: -20) // sets the bottom of the slider
88 | .fill(insetView, axis: .x, inset: 20) // sets the left & right edges to view's edges, inset by 20pt
89 |
90 | squareView.layout
91 | .below(topOf: insetView, offset: 10)
92 | .center(in: insetView, axis: .x)
93 | .width(.equal, to: insetView.layout.width * 0.5 - 20, priority: .high)
94 | .width(.lessOrEqual, to: 500, priority: .required)
95 | .height(toWidth: 1.0)
96 |
97 | circleView.layout
98 | .center(in: squareView)
99 | .size(60)
100 |
101 | label.layout
102 | .center(in: insetView, axis: .x) // centers label horizontally in insetView
103 | .below(squareView, offset: 20) // sets top edge of label to bottom edge of headerView + 20pt
104 |
105 | label2.layout
106 | .fill(insetView, axis: .x, inset: 40)
107 | .height(180)
108 | .below(label, offset: 20)
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutExample/Samples/CenterEdges.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ===----------------------------------------------------------------------===//
3 | //
4 | // CenterEdges.swift
5 | //
6 | // Created by Steven Grosmark on 1/23/18.
7 | // Copyright © 2018 WW International, Inc.
8 | //
9 | //
10 | // This source file is part of the WWLayout open source project
11 | //
12 | // https://github.com/ww-tech/wwlayout
13 | //
14 | // Copyright © 2017-2021 WW International, Inc.
15 | //
16 | // Licensed under the Apache License, Version 2.0 (the "License");
17 | // you may not use this file except in compliance with the License.
18 | // You may obtain a copy of the License at
19 | //
20 | // http://www.apache.org/licenses/LICENSE-2.0
21 | //
22 | // Unless required by applicable law or agreed to in writing, software
23 | // distributed under the License is distributed on an "AS IS" BASIS,
24 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25 | // See the License for the specific language governing permissions and
26 | // limitations under the License.
27 | //
28 | // ===----------------------------------------------------------------------===//
29 | //
30 |
31 | import UIKit
32 | import WWLayout
33 |
34 | public class CenterEdges: SampleViewController {
35 |
36 | override public func viewDidLoad() {
37 | super.viewDidLoad()
38 |
39 | view.backgroundColor = .white
40 |
41 | // |
42 | // +-[---]
43 | // |
44 | // []-[ ]
45 |
46 | let insetView = view.addNewColorView(.black)
47 | insetView.layout.fill(.safeArea, inset: 10)
48 |
49 | let leftView = insetView.addNewColorView(.yellow)
50 | let dashView1 = insetView.addNewColorView(.white)
51 | let middleView = insetView.addNewColorView(.green)
52 | let dashView2 = insetView.addNewColorView(.white)
53 | let bottomView = insetView.addNewColorView(.blue)
54 | let dashView3 = insetView.addNewColorView(.white)
55 | let appendageView = insetView.addNewColorView(.red)
56 |
57 | leftView.layout
58 | .top(to: .superview, offset: 5)
59 | .left(to: .superview, offset: 5)
60 | .width(120)
61 | .height(to: insetView.layout.height * 0.3)
62 |
63 | dashView1.layout
64 | .size(10, 1)
65 | .centerY(to: leftView, edge: .bottom)
66 | .left(to: leftView, edge: .right, offset: 2)
67 |
68 | middleView.layout
69 | .centerY(to: dashView1)
70 | .left(to: dashView1, edge: .right, offset: 2)
71 | .right(to: dashView2, edge: .center)
72 | .height(80)
73 |
74 | dashView2.layout
75 | .size(1, 10)
76 | .centerX(to: bottomView)
77 | .top(to: middleView, edge: .bottom, offset: 2)
78 |
79 | bottomView.layout
80 | .top(to: dashView2, edge: .bottom, offset: 2)
81 | .bottom(to: .superview, offset: -5)
82 | .right(to: .superview, offset: -5)
83 | .width(120)
84 |
85 | dashView3.layout
86 | .size(10, 1)
87 | .centerY(to: bottomView)
88 | .right(to: bottomView, edge: .left, offset: -2)
89 |
90 | appendageView.layout
91 | .size(60, 60)
92 | .centerY(to: dashView3)
93 | .right(to: dashView3, edge: .left, offset: -2)
94 | }
95 |
96 | }
97 |
98 | extension UIView {
99 | fileprivate func addNewColorView(_ color: UIColor) -> UIView {
100 | let colorView = UIView()
101 | colorView.backgroundColor = color
102 | addSubview(colorView)
103 | return colorView
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutExample/Samples/FillWidthSample.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ===----------------------------------------------------------------------===//
3 | //
4 | // FillWidthSample.swift
5 | //
6 | // Created by Steven Grosmark on 9/7/18.
7 | // Copyright © 2018 WW International, Inc.
8 | //
9 | //
10 | // This source file is part of the WWLayout open source project
11 | //
12 | // https://github.com/ww-tech/wwlayout
13 | //
14 | // Copyright © 2017-2021 WW International, Inc.
15 | //
16 | // Licensed under the Apache License, Version 2.0 (the "License");
17 | // you may not use this file except in compliance with the License.
18 | // You may obtain a copy of the License at
19 | //
20 | // http://www.apache.org/licenses/LICENSE-2.0
21 | //
22 | // Unless required by applicable law or agreed to in writing, software
23 | // distributed under the License is distributed on an "AS IS" BASIS,
24 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25 | // See the License for the specific language governing permissions and
26 | // limitations under the License.
27 | //
28 | // ===----------------------------------------------------------------------===//
29 | //
30 |
31 | import UIKit
32 | import WWLayout
33 |
34 | public class FillWidthSample: SampleViewController {
35 |
36 | override public func viewDidLoad() {
37 | super.viewDidLoad()
38 |
39 | view.backgroundColor = .white
40 |
41 | let insetView = UIView()
42 | insetView.backgroundColor = .white
43 | view.addSubview(insetView)
44 |
45 | insetView.layout.fill(.safeArea)
46 |
47 | let maxScreenWidth = max(UIScreen.main.bounds.width, UIScreen.main.bounds.height)
48 | let minScreenWidth = min(UIScreen.main.bounds.width, UIScreen.main.bounds.height)
49 | let avgScreenWidth = round((minScreenWidth + maxScreenWidth) / 2)
50 |
51 | var previousView: UIView?
52 | func add(_ subview: UIView, maxWidth: CGFloat, alignTo edge: LayoutXEdge = .center) {
53 | insetView.addSubview(subview)
54 | if let previousView = previousView {
55 | subview.layout.top(to: previousView, edge: .bottom, offset: 10)
56 | }
57 | else {
58 | subview.layout.top(to: insetView)
59 | }
60 | subview.layout.fillWidth(of: insetView, inset: 10, maximum: maxWidth, alignTo: edge)
61 | previousView = subview
62 | }
63 |
64 | let label = UILabel()
65 | label.text = "Hint: rotate the device"
66 | label.textAlignment = .center
67 | add(label, maxWidth: minScreenWidth)
68 |
69 | func makeLabel(with text: String) -> UILabel {
70 | let label = UILabel()
71 | label.text = text
72 | label.textAlignment = .center
73 | label.backgroundColor = UIColor.blue.withAlphaComponent(0.2)
74 | label.layer.borderWidth = 1
75 | label.layer.borderColor = UIColor.darkGray.cgColor
76 | return label
77 | }
78 | let edges = [LayoutXEdge.left, .right, .center, .leading, .trailing]
79 | for edge in edges {
80 | let label = makeLabel(with: "Aligned to \(edge)")
81 | add(label, maxWidth: avgScreenWidth, alignTo: edge)
82 | }
83 |
84 | let multiLine = makeLabel(with: "Lorum absurdism ipso facto de jure. Veni vidi gustibus est. Pluribus omnibus tincture non gratis.")
85 | multiLine.numberOfLines = 0
86 | add(multiLine, maxWidth: avgScreenWidth)
87 | }
88 |
89 | }
90 |
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutExample/Samples/LayoutMarginsSample.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ===----------------------------------------------------------------------===//
3 | //
4 | // LayoutMarginsSample.swift
5 | //
6 | // Created by Steven Grosmark on 1/12/18.
7 | // Copyright © 2018 WW International, Inc.
8 | //
9 | //
10 | // This source file is part of the WWLayout open source project
11 | //
12 | // https://github.com/ww-tech/wwlayout
13 | //
14 | // Copyright © 2017-2021 WW International, Inc.
15 | //
16 | // Licensed under the Apache License, Version 2.0 (the "License");
17 | // you may not use this file except in compliance with the License.
18 | // You may obtain a copy of the License at
19 | //
20 | // http://www.apache.org/licenses/LICENSE-2.0
21 | //
22 | // Unless required by applicable law or agreed to in writing, software
23 | // distributed under the License is distributed on an "AS IS" BASIS,
24 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25 | // See the License for the specific language governing permissions and
26 | // limitations under the License.
27 | //
28 | // ===----------------------------------------------------------------------===//
29 | //
30 |
31 | import UIKit
32 | import WWLayout
33 |
34 | public class LayoutMarginsSample: SampleViewController {
35 |
36 | let insetView = UIView()
37 | let label = UILabel()
38 | let backButton = UIButton()
39 | let navigationButton = UIButton()
40 | let tabsButton = UIButton()
41 |
42 | override public func viewDidLoad() {
43 | super.viewDidLoad()
44 | setupViews()
45 | setupConstraints()
46 | }
47 |
48 | private func setupViews() {
49 | view.backgroundColor = .white
50 |
51 | insetView.backgroundColor = .gray
52 | view.addSubview(insetView)
53 |
54 | label.font = .systemFont(ofSize: 18, weight: .bold)
55 | label.text = "Gray rectangle is inside the layout margins"
56 | label.textAlignment = .center
57 | label.numberOfLines = 0
58 | label.textColor = .black
59 |
60 | insetView.addSubview(label)
61 |
62 | navigationButton.setTitle("Toggle Navigation", for: .normal)
63 | navigationButton.addTarget(self, action: #selector(toggleNavigation), for: .touchUpInside)
64 | insetView.addSubview(navigationButton)
65 |
66 | backButton.setTitle("Back to Samples", for: .normal)
67 | backButton.addTarget(self, action: #selector(goBack), for: .touchUpInside)
68 | insetView.addSubview(backButton)
69 |
70 | tabsButton.setTitle("Toggle Tab Bar", for: .normal)
71 | tabsButton.addTarget(self, action: #selector(toggleTabBar), for: .touchUpInside)
72 | insetView.addSubview(tabsButton)
73 | }
74 |
75 | private func setupConstraints() {
76 | insetView.layout.fill(.margins)
77 | navigationButton.layout
78 | .top(to: insetView)
79 | .center(in: insetView, axis: .x)
80 | label.layout
81 | .fill(insetView, axis: .x, inset: 20)
82 | .center(in: insetView, axis: .y)
83 | backButton.layout
84 | .below(label)
85 | .center(in: insetView, axis: .x)
86 | tabsButton.layout
87 | .bottom(to: insetView)
88 | .center(in: insetView, axis: .x)
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutExample/Samples/SafeAreaSample.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ===----------------------------------------------------------------------===//
3 | //
4 | // SafeAreaSample.swift
5 | //
6 | // Created by Steven Grosmark on 12/1/17.
7 | // Copyright © 2018 WW International, Inc.
8 | //
9 | //
10 | // This source file is part of the WWLayout open source project
11 | //
12 | // https://github.com/ww-tech/wwlayout
13 | //
14 | // Copyright © 2017-2021 WW International, Inc.
15 | //
16 | // Licensed under the Apache License, Version 2.0 (the "License");
17 | // you may not use this file except in compliance with the License.
18 | // You may obtain a copy of the License at
19 | //
20 | // http://www.apache.org/licenses/LICENSE-2.0
21 | //
22 | // Unless required by applicable law or agreed to in writing, software
23 | // distributed under the License is distributed on an "AS IS" BASIS,
24 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25 | // See the License for the specific language governing permissions and
26 | // limitations under the License.
27 | //
28 | // ===----------------------------------------------------------------------===//
29 | //
30 |
31 | import UIKit
32 | import WWLayout
33 |
34 | public class SafeAreaSample: SampleViewController {
35 |
36 | let insetView = UIView()
37 | let label = UILabel()
38 | let backButton = UIButton()
39 | let navigationButton = UIButton()
40 | let tabsButton = UIButton()
41 |
42 | override public func viewDidLoad() {
43 | super.viewDidLoad()
44 | setupViews()
45 | setupConstraints()
46 | }
47 |
48 | private func setupViews() {
49 | view.backgroundColor = .white
50 |
51 | insetView.backgroundColor = .gray
52 | view.addSubview(insetView)
53 |
54 | label.font = .systemFont(ofSize: 18, weight: .bold)
55 | label.text = "Gray rectangle is 10pt inside the safe area"
56 | label.textAlignment = .center
57 | label.numberOfLines = 0
58 | label.textColor = .black
59 |
60 | insetView.addSubview(label)
61 |
62 | navigationButton.setTitle("Toggle Navigation", for: .normal)
63 | navigationButton.addTarget(self, action: #selector(toggleNavigation), for: .touchUpInside)
64 | insetView.addSubview(navigationButton)
65 |
66 | backButton.setTitle("Back to Samples", for: .normal)
67 | backButton.addTarget(self, action: #selector(goBack), for: .touchUpInside)
68 | insetView.addSubview(backButton)
69 |
70 | tabsButton.setTitle("Toggle Tab Bar", for: .normal)
71 | tabsButton.addTarget(self, action: #selector(toggleTabBar), for: .touchUpInside)
72 | insetView.addSubview(tabsButton)
73 | }
74 |
75 | private func setupConstraints() {
76 | insetView.layout.fill(.safeArea, inset: 10)
77 | navigationButton.layout
78 | .top(to: insetView)
79 | .center(in: insetView, axis: .x)
80 | label.layout
81 | .fill(insetView, axis: .x, inset: 20)
82 | .center(in: insetView, axis: .y)
83 | backButton.layout
84 | .below(label)
85 | .center(in: insetView, axis: .x)
86 | tabsButton.layout
87 | .bottom(to: insetView)
88 | .center(in: insetView, axis: .x)
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutExample/Samples/SampleViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ===----------------------------------------------------------------------===//
3 | //
4 | // SampleViewController.swift
5 | //
6 | // Created by Steven Grosmark on 1/19/18.
7 | // Copyright © 2018 WW International, Inc.
8 | //
9 | //
10 | // This source file is part of the WWLayout open source project
11 | //
12 | // https://github.com/ww-tech/wwlayout
13 | //
14 | // Copyright © 2017-2021 WW International, Inc.
15 | //
16 | // Licensed under the Apache License, Version 2.0 (the "License");
17 | // you may not use this file except in compliance with the License.
18 | // You may obtain a copy of the License at
19 | //
20 | // http://www.apache.org/licenses/LICENSE-2.0
21 | //
22 | // Unless required by applicable law or agreed to in writing, software
23 | // distributed under the License is distributed on an "AS IS" BASIS,
24 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25 | // See the License for the specific language governing permissions and
26 | // limitations under the License.
27 | //
28 | // ===----------------------------------------------------------------------===//
29 | //
30 |
31 | import UIKit
32 | import WWLayout
33 |
34 | public class SampleViewController: UIViewController {
35 |
36 | override public func viewDidLoad() {
37 | super.viewDidLoad()
38 | tabBarController?.tabBar.isHidden = false
39 | }
40 |
41 | @objc func toggleNavigation() {
42 | if let navigationController = navigationController {
43 | navigationController.setNavigationBarHidden(!navigationController.isNavigationBarHidden, animated: true)
44 | }
45 | }
46 |
47 | @objc func toggleTabBar() {
48 | if let tabBarController = tabBarController {
49 | tabBarController.tabBar.isHidden.toggle()
50 | if tabBarController.tabBar.isHidden {
51 | UIView.animate(withDuration: 0.2, animations: {
52 | self.view.setNeedsLayout()
53 | self.view.layoutIfNeeded()
54 | })
55 | }
56 | else {
57 | self.view.setNeedsLayout()
58 | self.view.layoutIfNeeded()
59 | }
60 | }
61 | }
62 |
63 | @objc func goBack() {
64 | navigationController?.setNavigationBarHidden(false, animated: true)
65 | navigationController?.popViewController(animated: true)
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutExample/Samples/SizeClassSample.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ===----------------------------------------------------------------------===//
3 | //
4 | // SizeClassSample.swift
5 | // WWLayoutExample
6 | //
7 | // Created by Steven Grosmark on 5/4/18.
8 | // Copyright © 2018 WW International, Inc. All rights reserved.
9 | //
10 | //
11 | // This source file is part of the WWLayout open source project
12 | //
13 | // https://github.com/ww-tech/wwlayout
14 | //
15 | // Copyright © 2017-2021 WW International, Inc.
16 | //
17 | // Licensed under the Apache License, Version 2.0 (the "License");
18 | // you may not use this file except in compliance with the License.
19 | // You may obtain a copy of the License at
20 | //
21 | // http://www.apache.org/licenses/LICENSE-2.0
22 | //
23 | // Unless required by applicable law or agreed to in writing, software
24 | // distributed under the License is distributed on an "AS IS" BASIS,
25 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
26 | // See the License for the specific language governing permissions and
27 | // limitations under the License.
28 | //
29 | // ===----------------------------------------------------------------------===//
30 | //
31 |
32 | import UIKit
33 | @testable import WWLayout
34 |
35 | /**
36 | Sample that shows the use of size class to activate and deactivate constraints
37 |
38 | In portrait, there will be a square above some text.
39 | In landscape, there will be a square with text to the right.
40 | */
41 | public class SizeClassSample: UIViewController {
42 |
43 | let insetView = UIView()
44 | let squareView = UIView()
45 | let label = UILabel()
46 | let hintLabel = UILabel()
47 |
48 | override public func viewDidLoad() {
49 | super.viewDidLoad()
50 | setupViews()
51 | setupConstraints()
52 | }
53 |
54 | private func setupViews() {
55 | view.backgroundColor = .white
56 |
57 | insetView.backgroundColor = UIColor(red: 0.98, green: 0.97, blue: 0.98, alpha: 1.0)
58 | view.addSubview(insetView)
59 |
60 | squareView.backgroundColor = UIColor(red: 0.5, green: 0.5, blue: 0.5, alpha: 0.5)
61 | insetView.addSubview(squareView)
62 |
63 | label.numberOfLines = 0
64 | label.text = """
65 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor \
66 | incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud \
67 | exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure \
68 | dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. \
69 | Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt \
70 | mollit anim id est laborum.
71 | """
72 | label.textColor = .black
73 |
74 | insetView.addSubview(label)
75 |
76 | hintLabel.numberOfLines = 0
77 | hintLabel.text = "rotate the device"
78 | hintLabel.textColor = .black
79 | hintLabel.textAlignment = .center
80 | squareView.addSubview(hintLabel)
81 | }
82 |
83 | private func setupConstraints() {
84 |
85 | // The safe area pre-iOS 11 will fall back to .superview left/right and the controller's top/bottom layout guides
86 | insetView.layout.fill(.safeArea, inset: 20)
87 |
88 | squareView.layout
89 | .below(topOf: insetView, offset: 10)
90 | .height(.lessOrEqual, to: insetView.layout.height - 20)
91 | .width(toHeight: 1.0)
92 | .size(260, priority: .required - 1)
93 |
94 | hintLabel.layout
95 | .fill(.superview, inset: 10)
96 |
97 | // phone portrait, all tablet
98 | squareView.layout(verticalSize: .regular)
99 | .center(in: insetView, axis: .x)
100 |
101 | label.layout(verticalSize: .regular)
102 | .fill(insetView, axis: .x, inset: 20)
103 | .below(squareView, offset: 20)
104 |
105 | // phone landscape
106 | squareView.layout(verticalSize: .compact)
107 | .leading(to: insetView, offset: 10)
108 |
109 | label.layout(verticalSize: .compact)
110 | .top(to: insetView, offset: 20)
111 | .leading(to: squareView, edge: .trailing, offset: 20)
112 | .trailing(to: insetView, offset: -20)
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutExample/Samples/TaggingSample.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ===----------------------------------------------------------------------===//
3 | //
4 | // TaggingSample.swift
5 | // WWLayoutExample
6 | //
7 | // Created by Steven Grosmark on 5/4/18.
8 | // Copyright © 2018 WW International, Inc. All rights reserved.
9 | //
10 | //
11 | // This source file is part of the WWLayout open source project
12 | //
13 | // https://github.com/ww-tech/wwlayout
14 | //
15 | // Copyright © 2017-2021 WW International, Inc.
16 | //
17 | // Licensed under the Apache License, Version 2.0 (the "License");
18 | // you may not use this file except in compliance with the License.
19 | // You may obtain a copy of the License at
20 | //
21 | // http://www.apache.org/licenses/LICENSE-2.0
22 | //
23 | // Unless required by applicable law or agreed to in writing, software
24 | // distributed under the License is distributed on an "AS IS" BASIS,
25 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
26 | // See the License for the specific language governing permissions and
27 | // limitations under the License.
28 | //
29 | // ===----------------------------------------------------------------------===//
30 | //
31 |
32 | import UIKit
33 | @testable import WWLayout
34 |
35 | /**
36 | Sample that shows the use of tags to activate and deactivate constraints
37 |
38 | In portrait, there will be a square above some text.
39 | In landscape, there will be a square with text to the right.
40 | */
41 | public class TaggingSample: UIViewController {
42 |
43 | let insetView = UIView()
44 | let boxView1 = UIView()
45 | let boxView2 = UIView()
46 | let button = UIButton()
47 |
48 | var toggle = true
49 |
50 | override public func viewDidLoad() {
51 | super.viewDidLoad()
52 | setupViews()
53 | setupConstraints()
54 | }
55 |
56 | private func setupViews() {
57 | view.backgroundColor = .white
58 |
59 | insetView.backgroundColor = UIColor(red: 0.98, green: 0.97, blue: 0.98, alpha: 1.0)
60 | view.addSubview(insetView)
61 |
62 | button.setTitleColor(.blue, for: .normal)
63 | button.setTitle("Change", for: .normal)
64 | button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
65 | insetView.addSubview(button)
66 |
67 | boxView1.backgroundColor = UIColor(red: 0.5, green: 0.9, blue: 0.9, alpha: 0.25)
68 | insetView.addSubview(boxView1)
69 |
70 | boxView2.backgroundColor = UIColor(red: 0.9, green: 0.9, blue: 0.5, alpha: 0.25)
71 | insetView.addSubview(boxView2)
72 | }
73 |
74 | private func setupConstraints() {
75 |
76 | // The safe area pre-iOS 11 will fall back to .superview left/right and the controller's top/bottom layout guides
77 | insetView.layout.fill(.safeArea, inset: 20)
78 |
79 | button.layout
80 | .top(to: .superview)
81 | .fill(.superview, axis: .x)
82 |
83 | // common constraints
84 | boxView1.layout
85 | .left(to: .superview)
86 | .below(button, offset: 10)
87 | boxView2.layout
88 | .right(to: .superview)
89 | .bottom(to: .superview)
90 |
91 | // columns
92 | boxView1.layout(tag: 1, active: toggle)
93 | .left(to: .superview)
94 | .width(to: insetView.layout.width * 0.5)
95 | .bottom(to: .superview)
96 | boxView2.layout(tag: 1, active: toggle)
97 | .below(button, offset: 10)
98 | .width(to: insetView.layout.width * 0.5)
99 |
100 | // rows
101 | boxView1.layout(tag: 2, active: !toggle)
102 | .right(to: .superview)
103 | boxView2.layout(tag: 2, active: !toggle)
104 | .left(to: .superview)
105 | .top(to: boxView1, edge: .bottom)
106 | .height(to: boxView1)
107 | }
108 |
109 | @objc private func buttonTapped() {
110 | toggle.toggle()
111 | UIView.animate(withDuration: 0.3) {
112 | Layout.switchActiveConstraints(in: self.view, activeTag: self.toggle ? 1 : 2, deactiveTag: self.toggle ? 2 : 1)
113 | self.view.layoutIfNeeded()
114 | }
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutExample/Samples/ThreeEdges.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ===----------------------------------------------------------------------===//
3 | //
4 | // ThreeEdges.swift
5 | //
6 | // Created by Steven Grosmark on 1/19/18.
7 | // Copyright © 2018 WW International, Inc.
8 | //
9 | //
10 | // This source file is part of the WWLayout open source project
11 | //
12 | // https://github.com/ww-tech/wwlayout
13 | //
14 | // Copyright © 2017-2021 WW International, Inc.
15 | //
16 | // Licensed under the Apache License, Version 2.0 (the "License");
17 | // you may not use this file except in compliance with the License.
18 | // You may obtain a copy of the License at
19 | //
20 | // http://www.apache.org/licenses/LICENSE-2.0
21 | //
22 | // Unless required by applicable law or agreed to in writing, software
23 | // distributed under the License is distributed on an "AS IS" BASIS,
24 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25 | // See the License for the specific language governing permissions and
26 | // limitations under the License.
27 | //
28 | // ===----------------------------------------------------------------------===//
29 | //
30 |
31 | import UIKit
32 | import WWLayout
33 |
34 | public class ThreeEdges: SampleViewController {
35 |
36 | override public func viewDidLoad() {
37 | super.viewDidLoad()
38 |
39 | view.backgroundColor = .white
40 |
41 | let insetView = UIView()
42 | insetView.backgroundColor = .black
43 | view.addSubview(insetView)
44 |
45 | insetView.layout.fill(.safeArea)
46 |
47 | let exclusions: [LayoutFillEdge] = [.left, .top, .right, .bottom]
48 | var lastView: UIView = insetView
49 | exclusions.forEach { edge in
50 | let subview = UIView()
51 | subview.backgroundColor = UIColor(red: .random(min: 0.5), green: .random(min: 0.5), blue: .random(min: 0.5), alpha: 0.5)
52 | insetView.addSubview(subview)
53 | subview.layout.fill(lastView, except: edge, inset: 10)
54 | switch edge {
55 | case .left, .right: subview.layout.width(to: lastView.layout.width * 0.75)
56 | case .top, .bottom: subview.layout.height(to: lastView.layout.height * 0.75)
57 | }
58 | lastView = subview
59 | }
60 | }
61 |
62 | }
63 |
64 | extension CGFloat {
65 | static func random(min: CGFloat = 0.0, max: CGFloat = 1.0) -> CGFloat {
66 | #if swift(>=5)
67 | return .random(in: min..
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutTV/WWLayoutTV.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutTV/WWLayoutTV.xcodeproj/xcshareddata/IDETemplateMacros.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | FILEHEADER
6 |
7 | // ===----------------------------------------------------------------------===//
8 | //
9 | // ___FILENAME___
10 | //
11 | // Created by ___FULLUSERNAME___ on ___DATE___
12 | // Copyright © ___YEAR___ WW International, Inc.
13 | //
14 | //
15 | // This source file is part of the WWLayout open source project
16 | //
17 | // https://github.com/ww-tech/wwlayout
18 | //
19 | // Copyright © 2017-___YEAR___ WW International, Inc.
20 | //
21 | // Licensed under the Apache License, Version 2.0 (the "License");
22 | // you may not use this file except in compliance with the License.
23 | // You may obtain a copy of the License at
24 | //
25 | // http://www.apache.org/licenses/LICENSE-2.0
26 | //
27 | // Unless required by applicable law or agreed to in writing, software
28 | // distributed under the License is distributed on an "AS IS" BASIS,
29 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
30 | // See the License for the specific language governing permissions and
31 | // limitations under the License.
32 | //
33 | // ===----------------------------------------------------------------------===//
34 | //
35 |
36 |
37 |
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutTV/WWLayoutTV.xcodeproj/xcshareddata/xcschemes/WWLayoutTV.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
53 |
55 |
61 |
62 |
63 |
64 |
70 |
72 |
78 |
79 |
80 |
81 |
83 |
84 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutTV/WWLayoutTV/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ===----------------------------------------------------------------------===//
3 | //
4 | // AppDelegate.swift
5 | // WWLayoutTV
6 | //
7 | // Created by Steven Grosmark on 1/7/20.
8 | // Copyright © 2020 WW International. All rights reserved.
9 | //
10 | //
11 | // This source file is part of the WWLayout open source project
12 | //
13 | // https://github.com/ww-tech/wwlayout
14 | //
15 | // Copyright © 2017-2021 WW International, Inc.
16 | //
17 | // Licensed under the Apache License, Version 2.0 (the "License");
18 | // you may not use this file except in compliance with the License.
19 | // You may obtain a copy of the License at
20 | //
21 | // http://www.apache.org/licenses/LICENSE-2.0
22 | //
23 | // Unless required by applicable law or agreed to in writing, software
24 | // distributed under the License is distributed on an "AS IS" BASIS,
25 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
26 | // See the License for the specific language governing permissions and
27 | // limitations under the License.
28 | //
29 | // ===----------------------------------------------------------------------===//
30 | //
31 |
32 | import UIKit
33 |
34 | @UIApplicationMain
35 | class AppDelegate: UIResponder, UIApplicationDelegate {
36 |
37 | var window: UIWindow?
38 |
39 | func application(_ application: UIApplication,
40 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
41 |
42 | let window = UIWindow(frame: UIScreen.main.bounds)
43 | self.window = window
44 |
45 | window.rootViewController = ViewController()
46 | window.makeKeyAndVisible()
47 |
48 | return true
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutTV/WWLayoutTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv"
5 | }
6 | ],
7 | "info" : {
8 | "version" : 1,
9 | "author" : "xcode"
10 | }
11 | }
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutTV/WWLayoutTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutTV/WWLayoutTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "layers" : [
3 | {
4 | "filename" : "Front.imagestacklayer"
5 | },
6 | {
7 | "filename" : "Middle.imagestacklayer"
8 | },
9 | {
10 | "filename" : "Back.imagestacklayer"
11 | }
12 | ],
13 | "info" : {
14 | "version" : 1,
15 | "author" : "xcode"
16 | }
17 | }
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutTV/WWLayoutTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv"
5 | }
6 | ],
7 | "info" : {
8 | "version" : 1,
9 | "author" : "xcode"
10 | }
11 | }
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutTV/WWLayoutTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutTV/WWLayoutTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv"
5 | }
6 | ],
7 | "info" : {
8 | "version" : 1,
9 | "author" : "xcode"
10 | }
11 | }
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutTV/WWLayoutTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutTV/WWLayoutTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | }
11 | ],
12 | "info" : {
13 | "version" : 1,
14 | "author" : "xcode"
15 | }
16 | }
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutTV/WWLayoutTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutTV/WWLayoutTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "layers" : [
3 | {
4 | "filename" : "Front.imagestacklayer"
5 | },
6 | {
7 | "filename" : "Middle.imagestacklayer"
8 | },
9 | {
10 | "filename" : "Back.imagestacklayer"
11 | }
12 | ],
13 | "info" : {
14 | "version" : 1,
15 | "author" : "xcode"
16 | }
17 | }
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutTV/WWLayoutTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | }
11 | ],
12 | "info" : {
13 | "version" : 1,
14 | "author" : "xcode"
15 | }
16 | }
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutTV/WWLayoutTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutTV/WWLayoutTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | }
11 | ],
12 | "info" : {
13 | "version" : 1,
14 | "author" : "xcode"
15 | }
16 | }
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutTV/WWLayoutTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutTV/WWLayoutTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "assets" : [
3 | {
4 | "size" : "1280x768",
5 | "idiom" : "tv",
6 | "filename" : "App Icon - App Store.imagestack",
7 | "role" : "primary-app-icon"
8 | },
9 | {
10 | "size" : "400x240",
11 | "idiom" : "tv",
12 | "filename" : "App Icon.imagestack",
13 | "role" : "primary-app-icon"
14 | },
15 | {
16 | "size" : "2320x720",
17 | "idiom" : "tv",
18 | "filename" : "Top Shelf Image Wide.imageset",
19 | "role" : "top-shelf-image-wide"
20 | },
21 | {
22 | "size" : "1920x720",
23 | "idiom" : "tv",
24 | "filename" : "Top Shelf Image.imageset",
25 | "role" : "top-shelf-image"
26 | }
27 | ],
28 | "info" : {
29 | "version" : 1,
30 | "author" : "xcode"
31 | }
32 | }
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutTV/WWLayoutTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | },
11 | {
12 | "idiom" : "tv-marketing",
13 | "scale" : "1x"
14 | },
15 | {
16 | "idiom" : "tv-marketing",
17 | "scale" : "2x"
18 | }
19 | ],
20 | "info" : {
21 | "version" : 1,
22 | "author" : "xcode"
23 | }
24 | }
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutTV/WWLayoutTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | },
11 | {
12 | "idiom" : "tv-marketing",
13 | "scale" : "1x"
14 | },
15 | {
16 | "idiom" : "tv-marketing",
17 | "scale" : "2x"
18 | }
19 | ],
20 | "info" : {
21 | "version" : 1,
22 | "author" : "xcode"
23 | }
24 | }
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutTV/WWLayoutTV/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutTV/WWLayoutTV/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutTV/WWLayoutTV/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UILaunchStoryboardName
24 | LaunchScreen
25 | UIRequiredDeviceCapabilities
26 |
27 | arm64
28 |
29 | UIUserInterfaceStyle
30 | Automatic
31 |
32 |
33 |
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutTV/WWLayoutTV/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ===----------------------------------------------------------------------===//
3 | //
4 | // ViewController.swift
5 | // WWLayoutTV
6 | //
7 | // Created by Steven Grosmark on 1/7/20.
8 | // Copyright © 2020 WW International. All rights reserved.
9 | //
10 | //
11 | // This source file is part of the WWLayout open source project
12 | //
13 | // https://github.com/ww-tech/wwlayout
14 | //
15 | // Copyright © 2017-2021 WW International, Inc.
16 | //
17 | // Licensed under the Apache License, Version 2.0 (the "License");
18 | // you may not use this file except in compliance with the License.
19 | // You may obtain a copy of the License at
20 | //
21 | // http://www.apache.org/licenses/LICENSE-2.0
22 | //
23 | // Unless required by applicable law or agreed to in writing, software
24 | // distributed under the License is distributed on an "AS IS" BASIS,
25 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
26 | // See the License for the specific language governing permissions and
27 | // limitations under the License.
28 | //
29 | // ===----------------------------------------------------------------------===//
30 | //
31 |
32 | import UIKit
33 | import WWLayout
34 |
35 | class ViewController: UIViewController {
36 |
37 | override func viewDidLoad() {
38 | super.viewDidLoad()
39 |
40 | view.backgroundColor = .black
41 | let matrixGreen = UIColor(red: 0, green: 0.7, blue: 0, alpha: 1)
42 |
43 | let label = UILabel()
44 | label.text = "Bonsoir, Elliot"
45 | label.font = UIFont.monospacedSystemFont(ofSize: 48, weight: .medium)
46 | label.textColor = matrixGreen
47 |
48 | view.addSubview(label)
49 | label.layout.center(in: .superview)
50 |
51 | let button = UIButton()
52 | button.backgroundColor = .darkGray
53 | button.setTitleColor(matrixGreen, for: .normal)
54 | button.setTitle("ⓘ", for: .normal)
55 | button.layer.cornerRadius = 11
56 | button.layer.masksToBounds = true
57 |
58 | view.addSubview(button)
59 | button.layout
60 | .centerX(to: .superview)
61 | .size(80)
62 | .bottom(to: .safeArea, offset: -30)
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutTV/WWLayoutTVTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/WWLayoutExample/WWLayoutTV/WWLayoutTVTests/WWLayoutTVTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ===----------------------------------------------------------------------===//
3 | //
4 | // WWLayoutTVTests.swift
5 | // WWLayoutTVTests
6 | //
7 | // Created by Steven Grosmark on 1/7/20.
8 | // Copyright © 2020 WW International. All rights reserved.
9 | //
10 | //
11 | // This source file is part of the WWLayout open source project
12 | //
13 | // https://github.com/ww-tech/wwlayout
14 | //
15 | // Copyright © 2017-2021 WW International, Inc.
16 | //
17 | // Licensed under the Apache License, Version 2.0 (the "License");
18 | // you may not use this file except in compliance with the License.
19 | // You may obtain a copy of the License at
20 | //
21 | // http://www.apache.org/licenses/LICENSE-2.0
22 | //
23 | // Unless required by applicable law or agreed to in writing, software
24 | // distributed under the License is distributed on an "AS IS" BASIS,
25 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
26 | // See the License for the specific language governing permissions and
27 | // limitations under the License.
28 | //
29 | // ===----------------------------------------------------------------------===//
30 | //
31 |
32 | import XCTest
33 | import WWLayout
34 | @testable import WWLayoutTV
35 |
36 | class WWLayoutTVTests: XCTestCase {
37 |
38 | /// Simple test to make sure WWLayout is properly added to project
39 | func testExample() {
40 | // given
41 | let parent = UIView(frame: CGRect(x: 0, y: 0, width: 400, height: 400))
42 | let child = UIView()
43 |
44 | parent.addSubview(child)
45 |
46 | // when
47 | child.layout
48 | .size(200, 100)
49 | .center(in: .superview)
50 | parent.layoutIfNeeded()
51 |
52 | // then
53 | XCTAssertEqual(child.frame, CGRect(x: 100, y: 150, width: 200, height: 100))
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/docs/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-cayman
2 | title: WWLayout
3 | description: A Swifty DSL for programmatic Auto Layout in iOS
4 |
--------------------------------------------------------------------------------
/docs/_layouts/default.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {% if site.google_analytics %}
6 |
7 |
13 | {% endif %}
14 |
15 |
16 | {% seo %}
17 |
18 |
19 |
20 |
21 |
22 |
23 |
34 |
35 |
36 | {{ content }}
37 |
38 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/docs/assets/css/style.scss:
--------------------------------------------------------------------------------
1 | ---
2 | ---
3 |
4 | $header-bg-color: #000586;
5 | $header-bg-color-secondary: #672566;
6 |
7 | $section-headings-color: #000586;
8 | $body-text-color: #000000;
9 | $body-link-color: #0C6CCE;
10 |
11 | @import "{{ site.theme }}";
12 |
13 | .page-header {
14 | padding-bottom: 0;
15 | }
16 |
--------------------------------------------------------------------------------
/docs/assets/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ww-tech/wwlayout/c8e74ae42ff24311b445032f19f50895d6e295ae/docs/assets/images/logo.png
--------------------------------------------------------------------------------
/docs/constraining-targets.md:
--------------------------------------------------------------------------------
1 | # Constraining Targets
2 | Constraints are created as a relationship between two items. Strictly speaking, constraints are from anchor to anchor - e.g., the topAnchor of one view, to the bottomAnchor of another - but it's convenient to think in terms of constraining views to views - e.g., position view A below view B - letting WWLayout create the individual constraints as needed.
3 |
4 | In WWLayout, you will start out with the *from* view by accessing the view's `layout` property, and then specify the *to* view within the individual layout methods.
5 |
6 | ```swift
7 | theView.layout.fill(anotherView)
8 | ```
9 |
10 | There are a number of special targets that you can constrain *to*, like the view's parent, or the safe area. Wherever you can use a UIView as the target, you can also use one of the special targets.
11 |
12 | This will make theView fill its superview:
13 |
14 | ```swift
15 | theView.layout.fill(.superview)
16 | ```
17 |
18 | This will make theView fill the safe area:
19 |
20 | ```swift
21 | theView.layout.fill(.safeArea)
22 | ```
23 |
24 | Note that .safeArea is backwards compatible, so that if your app is running pre-iOS 11, the left and right edges of theView will be constrained to theView's superview, while theView's top and bottom edges will be constrained to theView's containing UIVieController's topLayoutGuide and bottomLayoutGuide, respectively.
25 |
26 | ***
27 |
28 | [<- Home](./) | [Middle out parameters ->](middle-out-parameters)
29 |
--------------------------------------------------------------------------------
/docs/contributing-guidelines.md:
--------------------------------------------------------------------------------
1 | # Contributing to WWLayout
2 | We love your input! We want to make contributing to this project as easy and transparent as possible, whether it's:
3 |
4 | - Reporting a bug
5 | - Discussing the current state of the code
6 | - Submitting a fix
7 | - Proposing new features
8 | - Becoming a maintainer
9 |
10 | ## We Develop with Github
11 | We use github to host code, to track issues and feature requests, as well as accept pull requests.
12 |
13 | ### All Code Changes Happen Through Pull Requests
14 | Pull requests are the best way to propose changes to the codebase. We actively welcome your pull requests:
15 |
16 | 1. Fork the repo and create your branch from `develop`.
17 | 2. If you've added code that should be tested, add tests.
18 | 3. If you've changed APIs, update the documentation.
19 | 4. Ensure the test suite passes.
20 | 5. Issue that pull request!
21 |
22 | ## Any contributions you make will be under the Apache-2.0 Software License
23 | In short, when you submit code changes, your submissions are understood to be under the same [Apache-2.0 License](http://choosealicense.com/licenses/apache-2.0/) that covers the project. Feel free to contact the maintainers if that's a concern.
24 |
25 | ## Report bugs using Github's [issues](https://github.com/WW-Tech/wwlayout/issues)
26 | We use GitHub issues to track public bugs. Report a bug by [opening a new issue](https://github.com/WW-Tech/wwlayout/issues/new); it's that easy!
27 |
28 | ## Write bug reports with detail, background, and sample code
29 |
30 | **Great Bug Reports** tend to have:
31 |
32 | - A quick summary and/or background
33 | - Steps to reproduce
34 | - Be specific!
35 | - Give sample code if you can.
36 | - What you expected would happen
37 | - What actually happens
38 | - Notes (possibly including why you think this might be happening, or stuff you tried that didn't work)
39 |
40 | People *love* thorough bug reports. I'm not even kidding.
41 |
42 | ## Use a Consistent Coding Style
43 | Please try to follow the style already established.
44 |
45 | * 4 spaces for indentation rather than tabs
46 | * Add comments to your code: [self-documenting code is a fallacy](https://www.sicpers.info/2018/04/lets-talk-about-self-documenting-code/)
47 |
48 | ## License
49 | By contributing, you agree that your contributions will be licensed under WWLayout's Apache-2.0 License.
50 |
51 |
--------------------------------------------------------------------------------
/docs/from-native-constraints.md:
--------------------------------------------------------------------------------
1 | # Migrating from native constraints
2 | If you're used to working with `NSLayoutConstraint`s and all their verbose glory, here's what you need to know...
3 |
--------------------------------------------------------------------------------
/docs/from-purelayout.md:
--------------------------------------------------------------------------------
1 | # Migrating from PureLayout
2 | If you are coming from PureLayout, here's what you need to know...
3 |
4 | ## General guidelines
5 | In PureLayout, you call individual methods on the views that you want to set constraints for. In WWLayout, you will instead access the view's `layout` property, and call the constraint methods on that. Furthermore, these methods are chainable, so you only need to access the `layout` property once.
6 |
7 | For example, in PureLayout you would do this:
8 | ```swift
9 | backButton.autoPinEdge(toSuperviewEdge: .top, withInset: 32)
10 | backButton.autoPinEdge(toSuperviewEdge: .left, withInset: 15)
11 | backButton.autoSetDimension(.height, toSize: 21)
12 | backButton.autoSetDimension(.width, toSize: 12.5)
13 | ```
14 |
15 | In WWLayout, you would do this instead:
16 | ```swift
17 | backButton.layout
18 | .top(to: .superview, offset: 32)
19 | .left(to: .superview, offset: 15)
20 | .size(12.5, 21)
21 | ```
22 |
23 | ### Dealing with edges
24 |
25 | Instead of `autoPinEdge`, use the various edge methods (e.g. `top(to:)`, `.left(to:)`) - there's a method for each layout anchor available: `top`, `centerY`, `bottom`, `firstBaseline`, `lastBaseline`, `left`, `right`, `leading`, `training`, `centerX`.
26 |
27 | | PureLayout | WWLayout |
28 | | --- | --- |
29 | | `autoPinEdge(toSuperviewEdge: .top, withInset: 32)` | `.top(to: .superview, offset: 32)` |
30 | | `autoPinEdge(.top, to: .bottom, of: otherView, withOffset: 12)` | `.top(to: otherView, edge: .bottom, offset: 12)` or `.below(otherView, offset: 12)` |
31 |
32 | For setting up a vertical 'stack' of views, you can use the `.stack` helper, like this:
33 | ```swift
34 | containerView.layout.stack([view1, view2, view3, etc], space: 20)
35 | ```
36 | Or, if you need different spacing between the views, use the `.below` and `.followedBy` methods:
37 | ```swift
38 | view1.layout
39 | .below(topView, offset: 10)
40 | .followedBy(view2, offset: 20)
41 | .followedBy(view3)
42 | .followedBy(view4, offset: 15)
43 | ```
44 |
45 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | Easy to write auto layout constraints, with minimal extensions to standard namespaces.
2 |
3 | ## Feature Highlights
4 | * Easy to use, concise, readable API
5 | * Backwards-compatible (i.e. pre iOS 11) Safe Area constraints
6 | * Tag constraints to easily switch between different layouts (coming soon)
7 | * Automatic switching of size-class based constraints (coming soon)
8 |
9 | ## Introduction
10 |
11 | Constraints are added to a view using the view's `layout` property, like so:
12 |
13 | ```swift
14 | myView.layout.width(400)
15 | ```
16 |
17 | Multiple constraints are easily added by chaining calls:
18 |
19 | ```swift
20 | myView.layout.width(400).height(200)
21 | ```
22 |
23 | A more complicated example:
24 |
25 | ```swift
26 | let container = UIView()
27 | let child = UIView()
28 |
29 | child.layout
30 | .fill(container, axis: .x, inset: 20)
31 | .center(in: container, axis: .y, priority: .high)
32 | .top(.lessOrEqual, to: container, offset: 100)
33 | .height(toWidth: 0.5)
34 | ```
35 |
36 | ## Documentation
37 |
38 | [Constraining targets](constraining-targets)
39 |
40 | [Middle out parameters](middle-out-parameters)
41 |
42 | [Method Reference](method-reference)
43 |
44 | [Size classes](size-classes)
45 |
46 | ### Migrating from other layout methods
47 |
48 | [If you're coming from PureLayout...](from-purelayout)
49 |
50 | [If you're coming from Apple's native constraints...](from-native-constraints)
51 |
52 | ## Contributing
53 |
54 | Please read the [contributing guidelines](contributing-guidelines)
55 |
56 | ## Authors
57 | * [Steven Grosmark](https://github.com/g-mark), steven.grosmark@weightwatchers.com
58 | * WW iOS Team
59 |
60 | ## License
61 | WWLayout is © copyright by WW International.
62 |
63 | WWLayout is licensed under the [Apache-2.0 Open Source license](http://choosealicense.com/licenses/apache-2.0/).
64 |
--------------------------------------------------------------------------------
/docs/method-reference.md:
--------------------------------------------------------------------------------
1 | # Method Reference
2 |
3 | ### Size
4 |
5 | #### `size()`
6 | ```swift
7 | .size(100)
8 | .size(200, 150)
9 | ```
10 |
11 | #### `size(to:)`
12 | ```swift
13 | .size(to: .superview)
14 | .size(to: otherView)
15 | ```
16 |
17 | #### `width()`
18 | ```swift
19 | .width(100)
20 | .width(.greaterOrEqual, to: 100)
21 | ```
22 |
23 | #### `width(to:)`
24 | ```swift
25 | .width(to: otherView) // constrain to width of another view
26 | .width(to: .safeArea) // constrain to width of the safe area
27 | .width(to: otherView.layout.width * 0.5 + 20) // mix-in a multiplier and/or constant
28 | ```
29 |
30 | #### `width(toHeight:)`
31 | ```swift
32 | .width(toHeight: 2) // width = height * 2.0 (of itself)
33 | .width(.greaterOrEqual, toHeight: 0.5) // width >= height * 0.5 (of itself)
34 | ```
35 |
36 | #### `height()`
37 | ```swift
38 | .height(250)
39 | .height(.lessOrEqual, to: 320)
40 | ```
41 |
42 | #### `height(to:)`
43 | ```swift
44 | .height(to: otherView) // constrain to height of another view
45 | .height(to: .safeArea) // constrain to height of the safe area
46 | .height(to: otherView.layout.height * 0.5 + 20) // mix-in a multiplier and/or constant
47 | ```
48 |
49 | #### `height(toWidth:)`
50 | ```swift
51 | .height(toWidth: 2) // height = width * 2.0 (of itself)
52 | .height(.lessOrEqual, toWidth: 2) // height <= width * 2.0 (of itself)
53 | ```
54 |
55 | ### Center
56 | #### `center(in:)`
57 | ```swift
58 | oneView.layout.center(in: .safeArea)
59 | oneView.layout.center(in: anotherView, axis: .x)
60 | ```
61 | You can also constrain from a view's center using the `.centerX(to:)` and `.centerY(to:)` methods, as well as constrain to it's center edges, like: `.top(to: otherView, edge: .center)` (it's just `.center` in this context since the compiler already knows it's a Y constraint).
62 |
63 | ### Fill
64 |
65 | #### `fill()`
66 | ```swift
67 | oneView.layout.fill(anotherView)
68 | oneView.layout.fill(anotherView, axis: .x)
69 | oneView.layout.fill(anotherView, except: .bottom)
70 | ```
71 |
72 | #### `fillWidth()`
73 | ```swift
74 | oneView.layout.fillWidth(anotherView, maximum: 400)
75 | oneView.layout.fillWidth(anotherView, inset: 20, maximum: 400)
76 | oneView.layout.fillWidth(anotherView, inset: UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20), maximum: 400)
77 | oneView.layout.fillWidth(anotherView, inset: 20, maximum: 400, alignTo: .leading)
78 | ```
79 |
80 | ### Set Edges
81 |
82 | #### `left(to:)`
83 | ```swift
84 | .left(to: anotherView)
85 | .left(to: anotherView, edge: .center)
86 | .left(to: anotherView, offset: 20)
87 | ```
88 |
89 | #### `centerX(to:)`
90 | ```swift
91 | .centerX(to: anotherView) // Note: same as .center(in: anotherView, axis: .x)
92 | .centerX(to: anotherView, edge: .right)
93 | .centerX(to: anotherView, offset: 20)
94 | ```
95 |
96 | #### `right(to:)`
97 | ```swift
98 | .right(to: anotherView)
99 | .right(to: anotherView, edge: .center)
100 | .right(to: anotherView, offset: -20)
101 | ```
102 |
103 | #### `leading(to:)`
104 | ```swift
105 | .leading(to: anotherView)
106 | .leading(to: anotherView, edge: .trailing)
107 | .leading(to: anotherView, offset: 20)
108 | ```
109 |
110 | #### `trailing(to:)`
111 | ```swift
112 | .trailing(to: anotherView)
113 | .trailing(to: anotherView, edge: .leading)
114 | .trailing(to: anotherView, offset: 20)
115 | ```
116 |
117 | #### `top(to:)`
118 | ```swift
119 | .top(to: anotherView)
120 | .top(to: .safeArea, edge: center)
121 | .top(to: anotherView, edge: .bottom, offset: 20)
122 | ```
123 |
124 | #### `centerY(to:)`
125 | ```swift
126 | .centerY(to: anotherView) // Note: same as .center(in: anotherView, axis: .y)
127 | .centerY(to: anotherView, edge: .bottom)
128 | .centerY(to: anotherView, offset: 20)
129 | ```
130 |
131 | #### `bottom(to:)`
132 | ```swift
133 | .bottom(to: anotherView)
134 | .bottom(to: .safeArea, edge: center)
135 | .bottom(to: anotherView, edge: .top, offset: -20)
136 | ```
137 |
138 | #### `firstBaseline()`
139 | ```swift
140 | oneView.layout.firstBaseline(to: anotherView)
141 | oneView.layout.firstBaseline(to: anotherView, edge: .lastBaseline)
142 | ```
143 |
144 | #### `lastBaseline()`
145 | ```swift
146 | oneView.layout.lastBaseline(to: anotherView)
147 | oneView.layout.lastBaseline(to: anotherView, edge: .firstBaseline)
148 | ```
149 |
150 | #### `below()`
151 | ```swift
152 | oneView.layout.below(anotherView)
153 | oneView.layout.below(anotherView, offset: 10)
154 | ```
155 |
156 | ### Helpers
157 | #### `stack`
158 | ```swift
159 | parentView.layout
160 | .stack([view1, view2, view3 view4], space: 10)
161 |
162 | parentView.layout
163 | .stack([view1, view2, view3 view4], space: 10, below: headerView, offset20)
164 | ```
165 |
166 | #### `followedBy()`
167 | ```swift
168 | view1.layout
169 | .top(to: .safeArea) // view1.topAnchor = safeArea.topAnchor
170 | .followedBy(view2) // view2.topAnchor = view1.bottomAnchor
171 | .followedBy(view3, offset: 20) // view3.topAnchor = view2.bottomAnchor + 20
172 | .followedBy(view4, offset: 10) // view4.topAnchor = view3.bottomAnchor + 10
173 | ```
174 |
175 | ***
176 |
177 | [<- Middle out parameters](middle-out-parameters)
178 |
--------------------------------------------------------------------------------
/docs/middle-out-parameters.md:
--------------------------------------------------------------------------------
1 | # Middle-out parameters
2 | Each method for creating a constraint has a core set of parameters - e.g.:
3 |
4 | ```swift
5 | .size(100, 200) // width, height
6 | .left(to: otherView)
7 | .center(in: .safeArea)
8 | .fill(.superview)
9 | ```
10 |
11 | ### `priority`
12 |
13 | All methods have a final optional `priority` parameter, which allows for setting the priority of a single constraint. Priorities by default are set to `.required`, but can be specified per constraint, like this:
14 |
15 | ```swift
16 | childView.layout
17 | .center(in: containerView, priority: .high)
18 | .below(headerView, prioriyty: .high)
19 | .size(200, 150, priority: .low - 1)
20 | ```
21 |
22 | If a number of constraints need to be created with the same priority, then tell the layout instance to default to the desired priority, like this:
23 |
24 | ```swift
25 | childView.layout(priority: .high)
26 | .center(in: containerView)
27 | .below(headerView)
28 | .size(200, 150, priority: .low)
29 | ```
30 |
31 | ### `inset`, `offset`
32 |
33 | For many constraints, you can also specify an `inset`, or an `offset`:
34 |
35 | ```swift
36 | .left(to: otherView, offset: 20)
37 | .fill(.superview, inset: 20)
38 | ```
39 |
40 | The `inset` parameter can either be a single constant, a pair of width, height constants, or all four edges:
41 |
42 | ```swift
43 | // left, top, right, bottom all inset by 20pt
44 | .fill(.superview, inset: 20)
45 |
46 | // left & right inset by 20pt, top & bottom inset by 10pt
47 | .fill(.superview, inset: Insets(20, 10))
48 |
49 | // each edge explicitly specified
50 | .fill(.superview, inset: Insets(left: 10, top: 5, right: 20, bottom: 0))
51 |
52 | // each edge explicitly specified with UIEdgeInsets
53 | fill(.superview, inset: UIEdgeInsets(top: 5, left: 10, bottom: 0, right: 20))
54 | ```
55 |
56 | ### `edge`
57 |
58 | When constraining a single edge, the constraint (by default) is made with the same edge of the other view:
59 |
60 | ```swift
61 | // constrains leftAnchor of oneView to leftAnchor of anotherView
62 | oneView.layout.left(to: anotherView)
63 | ```
64 |
65 | You can alternatively specify a different edge of the other view - so long as the edge is of the same axis:
66 |
67 | ```swift
68 | // constrains leftAnchor of oneView to rightAnchor of anotherView
69 | oneView.layout.left(to: anotherView, edge: .right)
70 |
71 | // constrains leftAnchor of oneView to centerXAnchor of anotherView
72 | oneView.layout.left(to: anotherView, edge: .center, offset: 10)
73 | ```
74 |
75 | ### `relation`
76 | Most methods accept an initial relation parameter, which allows for <= and >= constraints:
77 |
78 | ```swift
79 | oneView.layout
80 | .width(.greaterOrEqual, to: anotherView)
81 | .width(.lessOrEqual, to: 500)
82 | .left(.greaterOrEqual, to: 20)
83 | ```
84 |
85 | The `relation` optional parameter is always first.
86 | Methods that can have an unlabelled constant as their first parameter, will add a `to:` label to their `constant` parameter when preceeded by a `relation`:
87 |
88 | ```swift
89 | .width(100)
90 | .width(.lessOrEqual, to: 100)
91 | ```
92 |
93 | ***
94 |
95 | [<- Constraining targets](constraining-targets) | [Method reference ->](method-reference)
96 |
--------------------------------------------------------------------------------
/docs/size-classes.md:
--------------------------------------------------------------------------------
1 | # Size Classes in Swift
2 | Size classes help you change the layout of views to adapt to various screen sizes, ranging from a tiny iPhone SE to a massive 12.9" iPad Pro. There are 3 different cases for a size class: `regular`, `compact`, and `unspecified`. Size classes are split up into horizontal and vertical classes, allowing you to adapt based on the size of each dimension. In the horizontal case, `regular` applies to all iPads in full screen as well as some iPhones in landscape, `compact` applies to all iPhones in portrait and most iPhones in landscape along with some iPad multitasking configurations, and `unspecified` is the case where it is not yet determined. For a better and more detailed explanation of size classes, check out this great article: https://useyourloaf.com/blog/size-classes/.
3 |
4 | ## Using Size Classes
5 | Using size classes is pretty simple. Every `UIView` and `UIViewController` has access to a `traitCollection` property, which contains both the horizontal and vertical size classes. You can use these when setting up your views to properly lay out for the current display. Typically, you'll want to just check the horizontal size class, as this is the one that will change when split view is activated on iPad.
6 |
7 | ## Adapting to Changes in Size Classes
8 | It's also important to properly adjust views when the size class changes. This occurs when your app is used in multitasking split view on iPad or when the device is rotated. This can cause the size class to change from `regular` to `compact`.
9 |
10 | In order to respond to these changes, your `UIView` or `UIViewController` needs to override `traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?)`, which is called every time the trait collection changes. Inside this method, you want to write something like this:
11 |
12 | ```swift
13 | public override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
14 | super.traitCollectionDidChange(previousTraitCollection)
15 | guard previousTraitCollection.horizontalSizeClass != traitCollection.horizontalSizeClass else { return }
16 |
17 | // Your layout code here
18 | }
19 | ```
20 | Note that changes in sizes classes aren't the _only_ reason why this method is called. Whenever any trait changes (such as from dark mode to light mode), this method is called. It's important to check that the size class actually changed in order to avoid any erroneous layout code that isn't needed.
21 |
22 | Also note that as of iOS 13, `traitCollectionDidChange` is _not_ always called when a view is loaded. It's important that you also consider the size class in your initial layout code, if necessary. If you want to avoid duplicate code, you can create a helper function that performs all size class dependent layout code, which you can call in both your initial setup and in the `traitCollectionDidChange` method.
23 |
24 | ## Auto Layout Constraints
25 | In order to change constraints based on size classes, you would typically have to maintain 2 separate collections of constraints and manually activate and deactivate them in your layout code. With the help of WWLayout though, you don't have to do that!
26 |
27 | By specifying the horizontal or vertical size class that a set of constraints applies to, WWLayout will automatically respond to changes in size classes and activate the correct constraints. Your code will look something like this:
28 | ```swift
29 | insetView.layout.fill(.safeArea, inset: 20)
30 |
31 | squareView.layout
32 | .below(topOf: insetView, offset: 10)
33 | .height(.lessOrEqual, to: insetView.layout.height - 20)
34 | .width(toHeight: 1.0)
35 | .size(260, priority: .required - 1)
36 |
37 | // portrait
38 | squareView.layout(verticalSize: .compact)
39 | .center(in: insetView, axis: .x)
40 |
41 | label.layout(verticalSize: .regular)
42 | .fill(insetView, axis: .x, inset: 20)
43 | .below(squareView, offset: 20)
44 |
45 | // landscape
46 | squareView.layout(verticalSize: .compact)
47 | .leading(to: insetView, offset: 10)
48 |
49 | label.layout(verticalSize: .regular)
50 | .top(to: insetView, offset: 20)
51 | .leading(to: squareView, edge: .trailing, offset: 20)
52 | .trailing(to: insetView, offset: -20)
53 | ```
54 |
55 | ## Accessing Size Class Outside of `UIView` and `UIViewController`
56 | There might be some cases where you want to access the current size class outside of the classes where `traitCollection` is defined, such as in a model object. While iOS 13 introduced the `UITraitCollection.current` property, you might need a solution that works on all supported OS versions.
57 |
58 | There are 2 ways to go about this. You can either pass along the trait collection from the `UIView` or you can access it directly by calling `UIScreen.main.traitCollection`. Either way will get you the same results, so choose whichever way works best with your code.
59 |
60 | ## Moratorium on Device Idioms
61 | Some developers currently use `UIDevice.current.userInterfaceIdiom` to layout their views based on device. While this might work fine if your app is always fullscreen, it is *not* compatible with iPad multitasking. The reason is that this variable simply checks the device idiom and returns either `phone` for iPhones or `pad` for iPads. It does not tell you the current size class or anything about the actual size of the display, nor does it respond to changes in size. Therefore, it should be avoided unless there is something you specifically need to vary between devices.
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ww-tech/wwlayout/c8e74ae42ff24311b445032f19f50895d6e295ae/logo.png
--------------------------------------------------------------------------------