├── .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 | WWLayout 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 --------------------------------------------------------------------------------