├── .github
├── FUNDING.yml
└── workflows
│ └── swift.yml
├── .gitignore
├── .swiftlint.yml
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ └── contents.xcworkspacedata
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── LICENSE
├── LoginExample
├── LoginStevia.xcodeproj
│ ├── project.pbxproj
│ └── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── LoginStevia
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ └── AppIcon.appiconset
│ │ └── Contents.json
│ ├── Base.lproj
│ └── LaunchScreen.storyboard
│ ├── Info.plist
│ ├── LoginVC.swift
│ ├── LoginViewNative.swift
│ └── LoginViewStevia.swift
├── Package.resolved
├── Package.swift
├── README.md
├── Sources
├── PrivacyInfo.xcprivacy
└── Stevia
│ ├── Stevia+Alignment.swift
│ ├── Stevia+Baselines.swift
│ ├── Stevia+Center.swift
│ ├── Stevia+Constraints.swift
│ ├── Stevia+Content.swift
│ ├── Stevia+DoubleDash.swift
│ ├── Stevia+Equation.swift
│ ├── Stevia+Fill.swift
│ ├── Stevia+FlexibleMargin.swift
│ ├── Stevia+GetConstraint.swift
│ ├── Stevia+Hierarchy.swift
│ ├── Stevia+LayoutAnchors.swift
│ ├── Stevia+Notifications.swift
│ ├── Stevia+Operators.swift
│ ├── Stevia+Percentage.swift
│ ├── Stevia+Position.swift
│ ├── Stevia+Size.swift
│ ├── Stevia+Stacks.swift
│ └── Stevia+Style.swift
├── SteviaLayout.podspec
├── Tests
└── SteviaTests
│ ├── BaselineTests.swift
│ ├── CenterTests.swift
│ ├── ContentTests.swift
│ ├── EquationTests.swift
│ ├── FillTests.swift
│ ├── FlexibleMarginTests.swift
│ ├── FullLayoutTests.swift
│ ├── GetConstraintsTests.swift
│ ├── HierarchyTests.swift
│ ├── LayoutTests.swift
│ ├── PositionTests.swift
│ ├── SizeTests.swift
│ └── StyleTests.swift
└── banner.png
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | open_collective: freshos
2 | github: s4cha
3 |
4 |
--------------------------------------------------------------------------------
/.github/workflows/swift.yml:
--------------------------------------------------------------------------------
1 | # This workflow will build a Swift project
2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-swift
3 |
4 | name: Swift
5 |
6 | on:
7 | push:
8 | branches: [ "master" ]
9 | pull_request:
10 | branches: [ "master" ]
11 |
12 | jobs:
13 | build:
14 |
15 | runs-on: macos-latest
16 |
17 | steps:
18 | - uses: actions/checkout@v4
19 | - name: Build
20 | run: swift build -v
21 | - name: Run tests
22 | run: swift test -v
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | #build/
4 | *.pbxuser
5 | !default.pbxuser
6 | *.mode1v3
7 | !default.mode1v3
8 | *.mode2v3
9 | !default.mode2v3
10 | *.perspectivev3
11 | !default.perspectivev3
12 | xcuserdata
13 | *.xccheckout
14 | *.moved-aside
15 | DerivedData
16 | *.hmap
17 | *.ipa
18 | *.xcuserstate
19 |
20 | # CocoaPods
21 | #
22 | # We recommend against adding the Pods directory to your .gitignore. However
23 | # you should judge for yourself, the pros and cons are mentioned at:
24 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control
25 | #
26 | # Pods/
27 |
28 | # Carthage
29 | #
30 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
31 | # Carthage/Checkouts
32 | Stevia/LoginExample/Carthage/Checkouts
33 | Stevia/LoginExample/iOSInjectionProject/
34 |
35 | .DS_Store
36 |
--------------------------------------------------------------------------------
/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | disabled_rules:
2 | - trailing_whitespace
3 | - cyclomatic_complexity
4 | - variable_name
5 | - function_body_length
6 | - type_body_length
7 | - valid_docs
8 | opt_in_rules:
9 | - empty_count
10 | # - missing_docs
11 | # - force_unwrapping
12 |
13 | excluded:
14 | - LoginExample
15 | - SteviaTests
16 | - Playground
17 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | All notable changes to this project will be documented in this file.
3 |
4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6 |
7 | ## [Unreleased]
8 | Put unreleased changes here
9 | - Preprocessor flag `#if canImport(UIKit)` moved to wrap all UIKit related types.
10 |
11 | ## [4.6.0] - 2019-09-30
12 | ### Changed
13 | - Adds support for Xcode11 & Swift 5.1
14 |
15 | ## [4.4.4] - 2019-01-03
16 | ### Changed
17 | `fillContainer` now returns self to make it chainable
18 |
19 | ## [4.4.3] - 2019-01-03
20 | ### Changed
21 | Fixes Equation api >= broken operator
22 |
23 | ## [4.4.2] - 2019-01-03
24 | ### Changed
25 | Fixes view.bottom/top/right/leftConstraint possibly returning wrong constraint if the one you want is not there yet. (looking for constraint in the view itself after looking for it in the superview)
26 | The fix makes sure it only looks for the constraint in the view itself for width and height constraints, that corresponds to constraints added via width/heightAnchors api.
27 |
28 | ## [4.4.1] - 2018-12-28
29 | ### Changed
30 | - Make sure userAddedConstraints doesn't return layout margins. Fixes #104 Kudos @mpsnp 👏
31 | - Code clean
32 | - Removing deprecated tap helper (discussion here #42) alternatives : https://github.com/XCEssentials/ViewEvents
33 |
34 | ## [4.4.0] - 2018-09-18
35 | ### Changed
36 | - Add support for Xcode10 & Swift 4.2
37 |
38 | ## [4.3.2] - 2018-09-09
39 | ### Added
40 | - Adds support for baselines, kudos to [@bellebethcooper](https://github.com/bellebethcooper) for bringing this up 🚀
41 |
42 | ```swift
43 | align(lastBaselines: label, label2, label3)
44 | label.FirstBaseline == label.LastBaseline
45 | ```
46 |
47 | ### Changed
48 | - Fixes "view.heightConstraint being nil" when set from IB or anchors. Thanks [@leidi0129](https://github.com/leidi0129) for spotting the issue 👏
49 |
50 | ## [4.3.0] - 2018-04-04
51 | ### Changed
52 | - Supports Xcode 9.3 & Swift 4.1
53 |
54 | ## [4.2.0] - 2018-01-09
55 |
56 | ### Changed
57 | - `==` operator now works even if views are not at the same level in the view hierarchy
58 | - `==` operator is now reflexive
59 | - Re-opening Layout([array]) overload Some needed it for dynamic layout so it's back :)
60 |
61 |
62 |
63 | ## [4.1.0] - 2017-11-20
64 |
65 | ### Added
66 | - UILayoutSupport & UILayoutGuide are now supported in Equation base api.
67 |
68 | ## [4.1.0] - 2017-09-29
69 |
70 | ### Changed
71 | - Supports Xcode 9 & Swift 4
72 |
73 | ## [3.2.0] - 2017-06-14
74 |
75 | ### Changed
76 | - Size constraints such as width and height can now be added without the need for a superview.
77 |
78 | ### Added
79 | - New leadingConstraint & trailingConstraint property
80 |
81 | - Kudos to [@trupin](https://github.com/trupin) and [@cowgp](https://github.com/cowgp) for this release 🎉
82 |
83 | ## [3.1.4] - 2017-06-14
84 |
85 | ### Added
86 | - tvOS Support
87 |
88 | ## [3.1.4] - 2017-06-14
89 |
90 | ### Changed
91 | - Built with Xcode 8.3.1
92 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
6 |
7 | ## Our Standards
8 |
9 | Examples of behavior that contributes to creating a positive environment include:
10 |
11 | * Using welcoming and inclusive language
12 | * Being respectful of differing viewpoints and experiences
13 | * Gracefully accepting constructive criticism
14 | * Focusing on what is best for the community
15 | * Showing empathy towards other community members
16 |
17 | Examples of unacceptable behavior by participants include:
18 |
19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances
20 | * Trolling, insulting/derogatory comments, and personal or political attacks
21 | * Public or private harassment
22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission
23 | * Other conduct which could reasonably be considered inappropriate in a professional setting
24 |
25 | ## Our Responsibilities
26 |
27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
28 |
29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
30 |
31 | ## Scope
32 |
33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
34 |
35 | ## Enforcement
36 |
37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at sachadso@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
38 |
39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
40 |
41 | ## Attribution
42 |
43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
44 |
45 | [homepage]: http://contributor-covenant.org
46 | [version]: http://contributor-covenant.org/version/1/4/
47 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/LoginExample/LoginStevia.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 52;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 99E2AA8D23BF7C4E00CBD63C /* Stevia in Frameworks */ = {isa = PBXBuildFile; productRef = 99E2AA8C23BF7C4E00CBD63C /* Stevia */; };
11 | 99FC80DF1BBD147B0068503C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99FC80DE1BBD147B0068503C /* AppDelegate.swift */; };
12 | 99FC80E11BBD147B0068503C /* LoginVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99FC80E01BBD147B0068503C /* LoginVC.swift */; };
13 | 99FC80E61BBD147B0068503C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 99FC80E51BBD147B0068503C /* Assets.xcassets */; };
14 | 99FC80E91BBD147B0068503C /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 99FC80E71BBD147B0068503C /* LaunchScreen.storyboard */; };
15 | 99FC80F31BBD14BB0068503C /* LoginViewNative.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99FC80F21BBD14BB0068503C /* LoginViewNative.swift */; };
16 | 99FC80FA1BBD16840068503C /* LoginViewStevia.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99FC80F91BBD16840068503C /* LoginViewStevia.swift */; };
17 | /* End PBXBuildFile section */
18 |
19 | /* Begin PBXCopyFilesBuildPhase section */
20 | E27AF8B22247476800BD88F8 /* Embed Frameworks */ = {
21 | isa = PBXCopyFilesBuildPhase;
22 | buildActionMask = 2147483647;
23 | dstPath = "";
24 | dstSubfolderSpec = 10;
25 | files = (
26 | );
27 | name = "Embed Frameworks";
28 | runOnlyForDeploymentPostprocessing = 0;
29 | };
30 | /* End PBXCopyFilesBuildPhase section */
31 |
32 | /* Begin PBXFileReference section */
33 | 99E2AA8A23BF7C3E00CBD63C /* Stevia */ = {isa = PBXFileReference; lastKnownFileType = folder; name = Stevia; path = ..; sourceTree = ""; };
34 | 99FC80DB1BBD147B0068503C /* LoginStevia.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LoginStevia.app; sourceTree = BUILT_PRODUCTS_DIR; };
35 | 99FC80DE1BBD147B0068503C /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
36 | 99FC80E01BBD147B0068503C /* LoginVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginVC.swift; sourceTree = ""; };
37 | 99FC80E51BBD147B0068503C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
38 | 99FC80E81BBD147B0068503C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
39 | 99FC80EA1BBD147B0068503C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
40 | 99FC80F21BBD14BB0068503C /* LoginViewNative.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginViewNative.swift; sourceTree = ""; };
41 | 99FC80F91BBD16840068503C /* LoginViewStevia.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginViewStevia.swift; sourceTree = ""; };
42 | /* End PBXFileReference section */
43 |
44 | /* Begin PBXFrameworksBuildPhase section */
45 | 99FC80D81BBD147B0068503C /* Frameworks */ = {
46 | isa = PBXFrameworksBuildPhase;
47 | buildActionMask = 2147483647;
48 | files = (
49 | 99E2AA8D23BF7C4E00CBD63C /* Stevia in Frameworks */,
50 | );
51 | runOnlyForDeploymentPostprocessing = 0;
52 | };
53 | /* End PBXFrameworksBuildPhase section */
54 |
55 | /* Begin PBXGroup section */
56 | 99E2AA8B23BF7C4E00CBD63C /* Frameworks */ = {
57 | isa = PBXGroup;
58 | children = (
59 | );
60 | name = Frameworks;
61 | sourceTree = "";
62 | };
63 | 99FC80D21BBD147B0068503C = {
64 | isa = PBXGroup;
65 | children = (
66 | 99E2AA8A23BF7C3E00CBD63C /* Stevia */,
67 | 99FC80DD1BBD147B0068503C /* LoginStevia */,
68 | 99FC80DC1BBD147B0068503C /* Products */,
69 | 99E2AA8B23BF7C4E00CBD63C /* Frameworks */,
70 | );
71 | sourceTree = "";
72 | };
73 | 99FC80DC1BBD147B0068503C /* Products */ = {
74 | isa = PBXGroup;
75 | children = (
76 | 99FC80DB1BBD147B0068503C /* LoginStevia.app */,
77 | );
78 | name = Products;
79 | sourceTree = "";
80 | };
81 | 99FC80DD1BBD147B0068503C /* LoginStevia */ = {
82 | isa = PBXGroup;
83 | children = (
84 | 99FC80DE1BBD147B0068503C /* AppDelegate.swift */,
85 | 99FC80E01BBD147B0068503C /* LoginVC.swift */,
86 | 99FC80F21BBD14BB0068503C /* LoginViewNative.swift */,
87 | 99FC80F91BBD16840068503C /* LoginViewStevia.swift */,
88 | 99FC80E51BBD147B0068503C /* Assets.xcassets */,
89 | 99FC80E71BBD147B0068503C /* LaunchScreen.storyboard */,
90 | 99FC80EA1BBD147B0068503C /* Info.plist */,
91 | );
92 | path = LoginStevia;
93 | sourceTree = "";
94 | };
95 | /* End PBXGroup section */
96 |
97 | /* Begin PBXNativeTarget section */
98 | 99FC80DA1BBD147B0068503C /* LoginStevia */ = {
99 | isa = PBXNativeTarget;
100 | buildConfigurationList = 99FC80ED1BBD147B0068503C /* Build configuration list for PBXNativeTarget "LoginStevia" */;
101 | buildPhases = (
102 | 99FC80D71BBD147B0068503C /* Sources */,
103 | 99FC80D81BBD147B0068503C /* Frameworks */,
104 | 99FC80D91BBD147B0068503C /* Resources */,
105 | E27AF8B22247476800BD88F8 /* Embed Frameworks */,
106 | );
107 | buildRules = (
108 | );
109 | dependencies = (
110 | );
111 | name = LoginStevia;
112 | packageProductDependencies = (
113 | 99E2AA8C23BF7C4E00CBD63C /* Stevia */,
114 | );
115 | productName = LoginNadir;
116 | productReference = 99FC80DB1BBD147B0068503C /* LoginStevia.app */;
117 | productType = "com.apple.product-type.application";
118 | };
119 | /* End PBXNativeTarget section */
120 |
121 | /* Begin PBXProject section */
122 | 99FC80D31BBD147B0068503C /* Project object */ = {
123 | isa = PBXProject;
124 | attributes = {
125 | LastUpgradeCheck = 1100;
126 | ORGANIZATIONNAME = "Sacha Durand Saint Omer";
127 | TargetAttributes = {
128 | 99FC80DA1BBD147B0068503C = {
129 | CreatedOnToolsVersion = 7.0;
130 | LastSwiftMigration = 1100;
131 | };
132 | };
133 | };
134 | buildConfigurationList = 99FC80D61BBD147B0068503C /* Build configuration list for PBXProject "LoginStevia" */;
135 | compatibilityVersion = "Xcode 3.2";
136 | developmentRegion = en;
137 | hasScannedForEncodings = 0;
138 | knownRegions = (
139 | en,
140 | Base,
141 | );
142 | mainGroup = 99FC80D21BBD147B0068503C;
143 | productRefGroup = 99FC80DC1BBD147B0068503C /* Products */;
144 | projectDirPath = "";
145 | projectRoot = "";
146 | targets = (
147 | 99FC80DA1BBD147B0068503C /* LoginStevia */,
148 | );
149 | };
150 | /* End PBXProject section */
151 |
152 | /* Begin PBXResourcesBuildPhase section */
153 | 99FC80D91BBD147B0068503C /* Resources */ = {
154 | isa = PBXResourcesBuildPhase;
155 | buildActionMask = 2147483647;
156 | files = (
157 | 99FC80E91BBD147B0068503C /* LaunchScreen.storyboard in Resources */,
158 | 99FC80E61BBD147B0068503C /* Assets.xcassets in Resources */,
159 | );
160 | runOnlyForDeploymentPostprocessing = 0;
161 | };
162 | /* End PBXResourcesBuildPhase section */
163 |
164 | /* Begin PBXSourcesBuildPhase section */
165 | 99FC80D71BBD147B0068503C /* Sources */ = {
166 | isa = PBXSourcesBuildPhase;
167 | buildActionMask = 2147483647;
168 | files = (
169 | 99FC80F31BBD14BB0068503C /* LoginViewNative.swift in Sources */,
170 | 99FC80FA1BBD16840068503C /* LoginViewStevia.swift in Sources */,
171 | 99FC80E11BBD147B0068503C /* LoginVC.swift in Sources */,
172 | 99FC80DF1BBD147B0068503C /* AppDelegate.swift in Sources */,
173 | );
174 | runOnlyForDeploymentPostprocessing = 0;
175 | };
176 | /* End PBXSourcesBuildPhase section */
177 |
178 | /* Begin PBXVariantGroup section */
179 | 99FC80E71BBD147B0068503C /* LaunchScreen.storyboard */ = {
180 | isa = PBXVariantGroup;
181 | children = (
182 | 99FC80E81BBD147B0068503C /* Base */,
183 | );
184 | name = LaunchScreen.storyboard;
185 | sourceTree = "";
186 | };
187 | /* End PBXVariantGroup section */
188 |
189 | /* Begin XCBuildConfiguration section */
190 | 99FC80EB1BBD147B0068503C /* Debug */ = {
191 | isa = XCBuildConfiguration;
192 | buildSettings = {
193 | ALWAYS_SEARCH_USER_PATHS = NO;
194 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
195 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
196 | CLANG_CXX_LIBRARY = "libc++";
197 | CLANG_ENABLE_MODULES = YES;
198 | CLANG_ENABLE_OBJC_ARC = YES;
199 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
200 | CLANG_WARN_BOOL_CONVERSION = YES;
201 | CLANG_WARN_COMMA = YES;
202 | CLANG_WARN_CONSTANT_CONVERSION = YES;
203 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
204 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
205 | CLANG_WARN_EMPTY_BODY = YES;
206 | CLANG_WARN_ENUM_CONVERSION = YES;
207 | CLANG_WARN_INFINITE_RECURSION = YES;
208 | CLANG_WARN_INT_CONVERSION = YES;
209 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
210 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
211 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
212 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
213 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
214 | CLANG_WARN_STRICT_PROTOTYPES = YES;
215 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
216 | CLANG_WARN_UNREACHABLE_CODE = YES;
217 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
218 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
219 | COPY_PHASE_STRIP = NO;
220 | DEBUG_INFORMATION_FORMAT = dwarf;
221 | ENABLE_STRICT_OBJC_MSGSEND = YES;
222 | ENABLE_TESTABILITY = YES;
223 | GCC_C_LANGUAGE_STANDARD = gnu99;
224 | GCC_DYNAMIC_NO_PIC = NO;
225 | GCC_NO_COMMON_BLOCKS = YES;
226 | GCC_OPTIMIZATION_LEVEL = 0;
227 | GCC_PREPROCESSOR_DEFINITIONS = (
228 | "DEBUG=1",
229 | "$(inherited)",
230 | );
231 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
232 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
233 | GCC_WARN_UNDECLARED_SELECTOR = YES;
234 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
235 | GCC_WARN_UNUSED_FUNCTION = YES;
236 | GCC_WARN_UNUSED_VARIABLE = YES;
237 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
238 | MTL_ENABLE_DEBUG_INFO = YES;
239 | ONLY_ACTIVE_ARCH = YES;
240 | SDKROOT = iphoneos;
241 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
242 | };
243 | name = Debug;
244 | };
245 | 99FC80EC1BBD147B0068503C /* Release */ = {
246 | isa = XCBuildConfiguration;
247 | buildSettings = {
248 | ALWAYS_SEARCH_USER_PATHS = NO;
249 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
250 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
251 | CLANG_CXX_LIBRARY = "libc++";
252 | CLANG_ENABLE_MODULES = YES;
253 | CLANG_ENABLE_OBJC_ARC = YES;
254 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
255 | CLANG_WARN_BOOL_CONVERSION = YES;
256 | CLANG_WARN_COMMA = YES;
257 | CLANG_WARN_CONSTANT_CONVERSION = YES;
258 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
259 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
260 | CLANG_WARN_EMPTY_BODY = YES;
261 | CLANG_WARN_ENUM_CONVERSION = YES;
262 | CLANG_WARN_INFINITE_RECURSION = YES;
263 | CLANG_WARN_INT_CONVERSION = YES;
264 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
265 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
266 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
267 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
268 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
269 | CLANG_WARN_STRICT_PROTOTYPES = YES;
270 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
271 | CLANG_WARN_UNREACHABLE_CODE = YES;
272 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
273 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
274 | COPY_PHASE_STRIP = NO;
275 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
276 | ENABLE_NS_ASSERTIONS = NO;
277 | ENABLE_STRICT_OBJC_MSGSEND = YES;
278 | GCC_C_LANGUAGE_STANDARD = gnu99;
279 | GCC_NO_COMMON_BLOCKS = YES;
280 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
281 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
282 | GCC_WARN_UNDECLARED_SELECTOR = YES;
283 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
284 | GCC_WARN_UNUSED_FUNCTION = YES;
285 | GCC_WARN_UNUSED_VARIABLE = YES;
286 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
287 | MTL_ENABLE_DEBUG_INFO = NO;
288 | SDKROOT = iphoneos;
289 | SWIFT_COMPILATION_MODE = wholemodule;
290 | SWIFT_OPTIMIZATION_LEVEL = "-O";
291 | VALIDATE_PRODUCT = YES;
292 | };
293 | name = Release;
294 | };
295 | 99FC80EE1BBD147B0068503C /* Debug */ = {
296 | isa = XCBuildConfiguration;
297 | buildSettings = {
298 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
299 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
300 | FRAMEWORK_SEARCH_PATHS = "$(inherited)";
301 | INFOPLIST_FILE = LoginStevia/Info.plist;
302 | LD_RUNPATH_SEARCH_PATHS = (
303 | "$(inherited)",
304 | "@executable_path/Frameworks",
305 | );
306 | PRODUCT_BUNDLE_IDENTIFIER = com.octopepper.LoginNadir;
307 | PRODUCT_NAME = LoginStevia;
308 | SWIFT_VERSION = 5.0;
309 | };
310 | name = Debug;
311 | };
312 | 99FC80EF1BBD147B0068503C /* Release */ = {
313 | isa = XCBuildConfiguration;
314 | buildSettings = {
315 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
316 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
317 | FRAMEWORK_SEARCH_PATHS = "$(inherited)";
318 | INFOPLIST_FILE = LoginStevia/Info.plist;
319 | LD_RUNPATH_SEARCH_PATHS = (
320 | "$(inherited)",
321 | "@executable_path/Frameworks",
322 | );
323 | PRODUCT_BUNDLE_IDENTIFIER = com.octopepper.LoginNadir;
324 | PRODUCT_NAME = LoginStevia;
325 | SWIFT_VERSION = 5.0;
326 | };
327 | name = Release;
328 | };
329 | /* End XCBuildConfiguration section */
330 |
331 | /* Begin XCConfigurationList section */
332 | 99FC80D61BBD147B0068503C /* Build configuration list for PBXProject "LoginStevia" */ = {
333 | isa = XCConfigurationList;
334 | buildConfigurations = (
335 | 99FC80EB1BBD147B0068503C /* Debug */,
336 | 99FC80EC1BBD147B0068503C /* Release */,
337 | );
338 | defaultConfigurationIsVisible = 0;
339 | defaultConfigurationName = Release;
340 | };
341 | 99FC80ED1BBD147B0068503C /* Build configuration list for PBXNativeTarget "LoginStevia" */ = {
342 | isa = XCConfigurationList;
343 | buildConfigurations = (
344 | 99FC80EE1BBD147B0068503C /* Debug */,
345 | 99FC80EF1BBD147B0068503C /* Release */,
346 | );
347 | defaultConfigurationIsVisible = 0;
348 | defaultConfigurationName = Release;
349 | };
350 | /* End XCConfigurationList section */
351 |
352 | /* Begin XCSwiftPackageProductDependency section */
353 | 99E2AA8C23BF7C4E00CBD63C /* Stevia */ = {
354 | isa = XCSwiftPackageProductDependency;
355 | productName = Stevia;
356 | };
357 | /* End XCSwiftPackageProductDependency section */
358 | };
359 | rootObject = 99FC80D31BBD147B0068503C /* Project object */;
360 | }
361 |
--------------------------------------------------------------------------------
/LoginExample/LoginStevia.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/LoginExample/LoginStevia.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/LoginExample/LoginStevia/AppDelegate.swift:
--------------------------------------------------------------------------------
1 |
2 | // AppDelegate.swift
3 | // LoginStevia
4 | //
5 | // Created by Sacha Durand Saint Omer on 01/10/15.
6 | // Copyright © 2015 Sacha Durand Saint Omer. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | var window: UIWindow?
15 |
16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
17 | Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/iOSInjection10.bundle")?.load()
18 | window = UIWindow(frame: UIScreen.main.bounds)
19 | window?.rootViewController = LoginVC()
20 | window?.makeKeyAndVisible()
21 | return true
22 | }
23 | }
24 |
25 |
--------------------------------------------------------------------------------
/LoginExample/LoginStevia/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "29x29",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "29x29",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "40x40",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "40x40",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "60x60",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "60x60",
31 | "scale" : "3x"
32 | }
33 | ],
34 | "info" : {
35 | "version" : 1,
36 | "author" : "xcode"
37 | }
38 | }
--------------------------------------------------------------------------------
/LoginExample/LoginStevia/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 |
--------------------------------------------------------------------------------
/LoginExample/LoginStevia/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
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 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/LoginExample/LoginStevia/LoginVC.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // LoginStevia
4 | //
5 | // Created by Sacha Durand Saint Omer on 01/10/15.
6 | // Copyright © 2015 Sacha Durand Saint Omer. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class LoginVC: UIViewController {
12 |
13 | var v: LoginViewStevia!
14 | override func loadView() {
15 | v = LoginViewStevia()
16 | view = v
17 | }
18 |
19 | override func viewDidLoad() {
20 | super.viewDidLoad()
21 |
22 | // Link actions
23 | v.login.addTarget(self, action: #selector(login), for: .touchUpInside)
24 |
25 | // Here we want to reload the view after injection
26 | // this is only needed for live reload
27 | on("INJECTION_BUNDLE_NOTIFICATION") {
28 | self.loadView()
29 | }
30 | }
31 |
32 | @objc
33 | func login() {
34 | print("login tapped")
35 | }
36 | }
37 |
38 |
--------------------------------------------------------------------------------
/LoginExample/LoginStevia/LoginViewNative.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LoginViewNative.swift
3 | // LoginStevia
4 | //
5 | // Created by Sacha Durand Saint Omer on 01/10/15.
6 | // Copyright © 2015 Sacha Durand Saint Omer. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class LoginViewNative: UIView {
12 |
13 | let email = UITextField()
14 | let password = UITextField()
15 | let login = UIButton()
16 |
17 | convenience init() {
18 | self.init(frame:CGRect.zero)
19 |
20 | // 01 - View Hieararchy
21 | email.translatesAutoresizingMaskIntoConstraints = false
22 | password.translatesAutoresizingMaskIntoConstraints = false
23 | login.translatesAutoresizingMaskIntoConstraints = false
24 | addSubview(email)
25 | addSubview(password)
26 | addSubview(login)
27 |
28 | // 02 - Layout (using latest layoutAnchors)
29 | email.topAnchor.constraint(equalTo: topAnchor, constant: 100).isActive = true
30 | email.leftAnchor.constraint(equalTo: leftAnchor, constant: 8).isActive = true
31 | email.rightAnchor.constraint(equalTo: rightAnchor, constant: -8).isActive = true
32 | email.heightAnchor.constraint(equalToConstant: 80).isActive = true
33 |
34 | password.topAnchor.constraint(equalTo: email.bottomAnchor, constant: 8).isActive = true
35 | password.leftAnchor.constraint(equalTo: leftAnchor, constant: 8).isActive = true
36 | password.rightAnchor.constraint(equalTo: rightAnchor, constant: -8).isActive = true
37 | password.heightAnchor.constraint(equalToConstant: 80).isActive = true
38 |
39 |
40 | login.topAnchor.constraint(lessThanOrEqualTo: password.bottomAnchor, constant: 20).isActive = true
41 | login.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0).isActive = true
42 | login.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
43 | login.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
44 | login.heightAnchor.constraint(equalToConstant: 80).isActive = true
45 |
46 | // 03 - Styling
47 | backgroundColor = .gray
48 | email.borderStyle = .roundedRect
49 | email.autocorrectionType = .no
50 | email.keyboardType = .emailAddress
51 | email.font = UIFont(name: "HelveticaNeue-Light", size: 26)
52 | email.returnKeyType = .next
53 | password.borderStyle = .roundedRect
54 | password.font = UIFont(name: "HelveticaNeue-Light", size: 26)
55 | password.isSecureTextEntry = true
56 | password.returnKeyType = .done
57 | login.backgroundColor = .lightGray
58 |
59 | // 04 - Content
60 | email.placeholder = "Email"
61 | password.placeholder = "Password"
62 | login.setTitle("Login", for: .normal)
63 | }
64 |
65 | func loginTapped() {
66 | //Do something
67 | }
68 | }
69 |
70 |
--------------------------------------------------------------------------------
/LoginExample/LoginStevia/LoginViewStevia.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LoginViewStevia.swift
3 | // LoginStevia
4 | //
5 | // Created by Sacha Durand Saint Omer on 01/10/15.
6 | // Copyright © 2015 Sacha Durand Saint Omer. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Stevia
11 |
12 | class LoginViewStevia: UIView {
13 |
14 | let email = UITextField()
15 | let password = UITextField()
16 | let login = UIButton()
17 |
18 | convenience init() {
19 | self.init(frame:CGRect.zero)
20 | // Get injectionForXcode here : http://johnholdsworth.com/injection.html
21 |
22 | // 01 -View Hierarchy
23 | // This essentially does `translatesAutoresizingMaskIntoConstraints = false`
24 | // and `addSubsview()`. The neat benefit is that
25 | // (`Subviews` calls can be nested which will visually show hierarchy ! )
26 | subviews {
27 | email
28 | password
29 | login
30 | }
31 |
32 | // 02 - Vertical + Horizontal Layout in one pass
33 | // With type-safe visual format
34 | layout {
35 | 100
36 | |-email-| ~ 80
37 | 8
38 | |-password-| ~ 80
39 | >=20
40 | |login| ~ 80
41 | 0
42 | }
43 |
44 | // ⛓ Chainable api
45 | // email.top(100).fillHorizontally(m: 8).height(80)
46 | // password.Top == email.Bottom + 8
47 | // password.fillHorizontally(m: 8).height(80)
48 | // login.bottom(0).fillHorizontally().height(80)
49 |
50 | // 📐 Equation based layout (Try it out!)
51 | //This comes in handy to cover tricky layout cases
52 | // email.Top == Top + 100
53 | // email.Left == Left + 8
54 | // email.Right == Right - 8
55 | // email.Height == 80
56 | //
57 | // password.Top == email.Bottom + 8
58 | // password.Left == Left + 8
59 | // password.Right == Right - 8
60 | // password.Height == 80
61 | //
62 | // password.Top == email.Bottom + 8
63 | // password.Left == Left + 8
64 | // password.Right == Right - 8
65 | // password.Height == 80
66 | //
67 | // login.Left == Left
68 | // login.Right == Right
69 | // login.Bottom == Bottom
70 | // login.Height == 80
71 |
72 |
73 | // 03 - Styling 🎨
74 | backgroundColor = .gray
75 | email.style(commonFieldStyle)
76 | password.style(commonFieldStyle).style { f in
77 | f.isSecureTextEntry = true
78 | f.returnKeyType = .done
79 | }
80 | login.backgroundColor = .lightGray
81 |
82 | // 04 - Content 🖋
83 | email.placeholder = "Email"
84 | password.placeholder = "Password"
85 | login.setTitle("Login", for: .normal)
86 | }
87 |
88 | // Style can be extracted and applied kind of like css \o/
89 | // but in pure Swift though!
90 | func commonFieldStyle(_ f:UITextField) {
91 | f.borderStyle = .roundedRect
92 | f.font = UIFont(name: "HelveticaNeue-Light", size: 26)
93 | f.returnKeyType = .next
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "originHash" : "0d1e08a917a619df6510ae568b5ab623f1fc22dffa644b8c590c04c4a5b086bc",
3 | "pins" : [
4 | {
5 | "identity" : "swift-numerics",
6 | "kind" : "remoteSourceControl",
7 | "location" : "https://github.com/apple/swift-numerics",
8 | "state" : {
9 | "revision" : "0a5bc04095a675662cf24757cc0640aa2204253b",
10 | "version" : "1.0.2"
11 | }
12 | }
13 | ],
14 | "version" : 3
15 | }
16 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:6.0
2 |
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "Stevia",
7 | platforms: [
8 | .iOS(.v12),
9 | .tvOS(.v12)
10 | ],
11 | products: [.library(name: "Stevia", targets: ["Stevia"])],
12 | dependencies: [
13 | .package(url: "https://github.com/apple/swift-numerics", from: "1.0.2"),
14 | ],
15 | targets: [
16 | .target(name: "Stevia", path: "Sources", resources: [.copy("PrivacyInfo.xcprivacy")]),
17 | .testTarget(name: "SteviaTests", dependencies: [
18 | "Stevia",
19 | .product(name: "Numerics", package: "swift-numerics"),
20 | ]),
21 | ]
22 | )
23 |
--------------------------------------------------------------------------------
/Sources/PrivacyInfo.xcprivacy:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NSPrivacyTracking
6 |
7 | NSPrivacyTrackingDomains
8 |
9 | NSPrivacyCollectedDataTypes
10 |
11 | NSPrivacyAccessedAPITypes
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/Sources/Stevia/Stevia+Alignment.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Stevia+Alignment.swift
3 | // Stevia
4 | //
5 | // Created by Sacha Durand Saint Omer on 10/02/16.
6 | // Copyright © 2016 Sacha Durand Saint Omer. All rights reserved.
7 | //
8 |
9 | #if canImport(UIKit)
10 | import UIKit
11 |
12 | /** Aligns an array of views Horizontally (on the X Axis)
13 |
14 | Example Usage:
15 | ```
16 | align(horizontally: label,button,arrow)
17 | ```
18 |
19 | Can also be used directly on horizontal layouts since they return the array of views :
20 | ```
21 | align(horizontally: |-image1-image2-image3-|)
22 | ```
23 |
24 | - Returns: The array of views, enabling chaining,
25 |
26 | */
27 | @discardableResult
28 | @MainActor public func align(horizontally views: UIView...) -> [UIView] {
29 | return align(horizontally: views)
30 | }
31 |
32 | @available(*, deprecated, renamed: "align(horizontally:)")
33 | @discardableResult
34 | @MainActor public func alignHorizontally(_ views: UIView...) -> [UIView] {
35 | return align(horizontally: views)
36 | }
37 |
38 | /** Aligns an array of views Horizontally (on the X Axis)
39 |
40 | Example Usage:
41 | ```
42 | align(horizontally: label,button,arrow)
43 | ```
44 |
45 | Ca also be used directly on horizontal layouts since they return the array of views :
46 | ```
47 | align(horizontally: |-image1-image2-image3-|)
48 | ```
49 |
50 | - Returns: The array of views, enabling chaining,
51 |
52 | */
53 | @discardableResult
54 | @MainActor public func align(horizontally views: [UIView]) -> [UIView] {
55 | align(.horizontal, views: views)
56 | return views
57 | }
58 |
59 | @available(*, deprecated, renamed: "align(horizontally:)")
60 | @discardableResult
61 | @MainActor public func alignHorizontally(_ views: [UIView]) -> [UIView] {
62 | align(.horizontal, views: views)
63 | return views
64 | }
65 |
66 | /** Aligns an array of views Vertically (on the Y Axis)
67 |
68 | Example Usage:
69 | ```
70 | align(vertically: label,field,button)
71 | ```
72 |
73 | - Returns: The array of views, enabling chaining,
74 |
75 | */
76 | @MainActor public func align(vertically views: UIView...) {
77 | align(vertically: views)
78 | }
79 |
80 | @available(*, deprecated, renamed: "align(vertically:)")
81 | @MainActor public func alignVertically(_ views: UIView...) {
82 | align(vertically: views)
83 | }
84 |
85 | /** Aligns an array of views Vertically (on the Y Axis)
86 |
87 | Example Usage:
88 | ```
89 | align(vertically: label,field,button)
90 | ```
91 |
92 | - Returns: The array of views, enabling chaining,
93 |
94 | */
95 | @MainActor public func align(vertically views: [UIView]) {
96 | align(.vertical, views: views)
97 | }
98 |
99 | @available(*, deprecated, renamed: "align(vertically:)")
100 | @MainActor public func alignVertically(_ views: [UIView]) {
101 | align(.vertical, views: views)
102 | }
103 |
104 | /** Aligns the center of two views
105 |
106 | Example Usage:
107 | ```
108 | alignCenter(button, with:image)
109 | ```
110 | */
111 | @MainActor public func alignCenter(_ v1: UIView, with v2: UIView) {
112 | alignHorizontally(v1, with: v2)
113 | alignVertically(v1, with: v2)
114 | }
115 |
116 | /** Aligns two views Horizontall (on the X Axis)
117 |
118 | Example Usage:
119 | ```
120 | alignHorizontally(label, with:field)
121 | ```
122 |
123 | */
124 | @MainActor public func alignHorizontally(_ v1: UIView, with v2: UIView, offset: Double = 0) {
125 | align(.horizontal, v1: v1, with: v2, offset: offset)
126 | }
127 |
128 | /** Aligns two views Vertically (on the Y Axis)
129 |
130 | Example Usage:
131 | ```
132 | alignVertically(label, with:field)
133 | ```
134 |
135 | */
136 | @MainActor public func alignVertically(_ v1: UIView, with v2: UIView, offset: Double = 0) {
137 | align(.vertical, v1: v1, with: v2, offset: offset)
138 | }
139 |
140 | @MainActor private func align(_ axis: NSLayoutConstraint.Axis, views: [UIView]) {
141 | for (i, v) in views.enumerated() where views.count > i+1 {
142 | let v2 = views[i+1]
143 | if axis == .horizontal {
144 | alignHorizontally(v, with: v2)
145 | } else {
146 | alignVertically(v, with: v2)
147 | }
148 | }
149 | }
150 |
151 | @MainActor func align(_ axis: NSLayoutConstraint.Axis, v1: UIView, with v2: UIView, offset: Double) {
152 | if let spv = v1.superview {
153 | let center: NSLayoutConstraint.Attribute = axis == .horizontal ? .centerY : .centerX
154 | let c = constraint(item: v1, attribute: center, toItem: v2, constant: offset)
155 | spv.addConstraint(c)
156 | }
157 | }
158 |
159 | // MARK: Align sides
160 |
161 | /** Aligns tops of an array of views
162 |
163 | Example Usage:
164 | ```
165 | align(tops: label,button,arrow)
166 | ```
167 |
168 | Ca also be used directly on horizontal layouts since they return the array of views :
169 | ```
170 | align(tops: |-image1-image2-image3-|)
171 | ```
172 |
173 | - Returns: The array of views, enabling chaining,
174 |
175 | */
176 | @discardableResult
177 | @MainActor public func align(tops views: UIView...) -> [UIView] {
178 | return align(tops: views)
179 | }
180 |
181 | @available(*, deprecated, renamed: "align(tops:)")
182 | @discardableResult
183 | @MainActor public func alignTops(_ views: UIView...) -> [UIView] {
184 | return align(tops: views)
185 | }
186 |
187 | /** Aligns tops of an array of views
188 |
189 | Example Usage:
190 | ```
191 | align(tops: label,button,arrow)
192 | ```
193 |
194 | Ca also be used directly on horizontal layouts since they return the array of views :
195 | ```
196 | align(tops: |-image1-image2-image3-|)
197 | ```
198 |
199 | - Returns: The array of views, enabling chaining,
200 |
201 | */
202 | @discardableResult
203 | @MainActor public func align(tops views: [UIView]) -> [UIView] {
204 | align(.top, views: views)
205 | return views
206 | }
207 |
208 | @available(*, deprecated, renamed: "align(tops:)")
209 | @discardableResult
210 | @MainActor public func alignTops(_ views: [UIView]) -> [UIView] {
211 | align(.top, views: views)
212 | return views
213 | }
214 |
215 | /** Aligns bottoms of an array of views
216 |
217 | Example Usage:
218 | ```
219 | align(bottoms: label,button,arrow)
220 | ```
221 |
222 | Ca also be used directly on horizontal layouts since they return the array of views :
223 | ```
224 | align(bottoms: |-image1-image2-image3-|)
225 | ```
226 |
227 | - Returns: The array of views, enabling chaining,
228 |
229 | */
230 | @discardableResult
231 | @MainActor public func align(bottoms views: UIView...) -> [UIView] {
232 | return align(bottoms: views)
233 | }
234 |
235 | @available(*, deprecated, renamed: "align(bottoms:)")
236 | @discardableResult
237 | @MainActor public func alignBottoms(_ views: UIView...) -> [UIView] {
238 | return align(bottoms: views)
239 | }
240 |
241 | /** Aligns bottoms of an array of views
242 |
243 | Example Usage:
244 | ```
245 | align(bottoms: label,button,arrow)
246 | ```
247 |
248 | Ca also be used directly on horizontal layouts since they return the array of views :
249 | ```
250 | align(bottoms: |-image1-image2-image3-|)
251 | ```
252 |
253 | - Returns: The array of views, enabling chaining,
254 |
255 | */
256 | @discardableResult
257 | @MainActor public func align(bottoms views: [UIView]) -> [UIView] {
258 | align(.bottom, views: views)
259 | return views
260 | }
261 |
262 | @available(*, deprecated, renamed: "align(bottoms:)")
263 | @discardableResult
264 | @MainActor public func alignBottoms(_ views: [UIView]) -> [UIView] {
265 | align(.bottom, views: views)
266 | return views
267 | }
268 |
269 | /** Aligns left sides of an array of views
270 |
271 | Example Usage:
272 | ```
273 | align(lefts: label,field,button)
274 | ```
275 |
276 | - Returns: The array of views, enabling chaining,
277 |
278 | */
279 | @discardableResult
280 | @MainActor public func align(lefts views: UIView...) -> [UIView] {
281 | return align(lefts: views)
282 | }
283 |
284 | @available(*, deprecated, renamed: "align(lefts:)")
285 | @discardableResult
286 | @MainActor public func alignLefts(_ views: UIView...) -> [UIView] {
287 | return align(lefts: views)
288 | }
289 |
290 | /** Aligns left sides of an array of views
291 |
292 | Example Usage:
293 | ```
294 | align(lefts: label,field,button)
295 | ```
296 |
297 | - Returns: The array of views, enabling chaining,
298 |
299 | */
300 | @discardableResult
301 | @MainActor public func align(lefts views: [UIView]) -> [UIView] {
302 | align(.left, views: views)
303 | return views
304 | }
305 |
306 | @available(*, deprecated, renamed: "align(lefts:)")
307 | @discardableResult
308 | @MainActor public func alignLefts(_ views: [UIView]) -> [UIView] {
309 | align(.left, views: views)
310 | return views
311 | }
312 |
313 | /** Aligns right sides of an array of views
314 |
315 | Example Usage:
316 | ```
317 | align(rights: label,field,button)
318 | ```
319 |
320 | - Returns: The array of views, enabling chaining,
321 |
322 | */
323 | @discardableResult
324 | @MainActor public func align(rights views: UIView...) -> [UIView] {
325 | return align(rights: views)
326 | }
327 |
328 | @available(*, deprecated, renamed: "align(rights:)")
329 | @discardableResult
330 | @MainActor public func alignRights(_ views: UIView...) -> [UIView] {
331 | return align(rights: views)
332 | }
333 |
334 | /** Aligns right sides of an array of views
335 |
336 | Example Usage:
337 | ```
338 | align(rights: label,field,button)
339 | ```
340 |
341 | - Returns: The array of views, enabling chaining,
342 |
343 | */
344 | @discardableResult
345 | @MainActor public func align(rights views: [UIView]) -> [UIView] {
346 | align(.right, views: views)
347 | return views
348 | }
349 |
350 | @available(*, deprecated, renamed: "align(rights:)")
351 | @discardableResult
352 | @MainActor public func alignRights(_ views: [UIView]) -> [UIView] {
353 | align(.right, views: views)
354 | return views
355 | }
356 |
357 | /** Aligns leading sides of an array of views
358 |
359 | Example Usage:
360 | ```
361 | align(leadings: [label,field,button])
362 | ```
363 |
364 | - Returns: The array of views, enabling chaining,
365 |
366 | */
367 | @discardableResult
368 | @MainActor public func align(leadings views: [UIView]) -> [UIView] {
369 | align(.leading, views: views)
370 | return views
371 | }
372 |
373 | /** Aligns leading sides of an array of views
374 |
375 | Example Usage:
376 | ```
377 | align(leadings: label,field,button)
378 | ```
379 |
380 | - Returns: The array of views, enabling chaining,
381 |
382 | */
383 | @discardableResult
384 | @MainActor public func align(leadings views: UIView...) -> [UIView] {
385 | align(.leading, views: views)
386 | return views
387 | }
388 |
389 | /** Aligns trailing sides of an array of views
390 |
391 | Example Usage:
392 | ```
393 | align(trailing: [label,field,button])
394 | ```
395 |
396 | - Returns: The array of views, enabling chaining,
397 |
398 | */
399 | @discardableResult
400 | @MainActor public func align(trailings views: [UIView]) -> [UIView] {
401 | align(.trailing, views: views)
402 | return views
403 | }
404 |
405 | /** Aligns trailing sides of an array of views
406 |
407 | Example Usage:
408 | ```
409 | align(trailing: label,field,button)
410 | ```
411 |
412 | - Returns: The array of views, enabling chaining,
413 |
414 | */
415 | @discardableResult
416 | @MainActor public func align(trailings views: UIView...) -> [UIView] {
417 | align(.trailing, views: views)
418 | return views
419 | }
420 |
421 | @discardableResult
422 | @MainActor public func align(_ attribute: NSLayoutConstraint.Attribute, views: [UIView]) -> [UIView] {
423 | for (i, v) in views.enumerated() where views.count > i+1 {
424 | let v2 = views[i+1]
425 | if let spv = v.superview {
426 | let c = constraint(item: v, attribute: attribute, toItem: v2)
427 | spv.addConstraint(c)
428 | }
429 | }
430 | return views
431 | }
432 | #endif
433 |
--------------------------------------------------------------------------------
/Sources/Stevia/Stevia+Baselines.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Stevia+Baselines.swift
3 | // Stevia
4 | //
5 | // Created by Sacha on 09/09/2018.
6 | // Copyright © 2018 Sacha Durand Saint Omer. All rights reserved.
7 | //
8 |
9 | #if canImport(UIKit)
10 | import UIKit
11 |
12 | /** Aligns an array of views by their lastBaselines (on the Y Axis)
13 |
14 | Example Usage:
15 | ```
16 | align(lastBaselines: label1, label2, label3)
17 | ```
18 |
19 | Can also be used directly on horizontal layouts since they return the array of views :
20 | ```
21 | align(lastBaselines: |-label1-label2-label3-|)
22 | ```
23 |
24 | - Returns: The array of views, enabling chaining,
25 |
26 | */
27 | @discardableResult
28 | @MainActor public func align(lastBaselines views: UIView...) -> [UIView] {
29 | return align(lastBaselines: views)
30 | }
31 |
32 | @discardableResult
33 | @MainActor public func align(lastBaselines views: [UIView]) -> [UIView] {
34 | for (i, v) in views.enumerated() where views.count > i+1 {
35 | let v2 = views[i+1]
36 | if #available(iOS 9.0, *) {
37 | v.lastBaselineAnchor.constraint(equalTo: v2.lastBaselineAnchor).isActive = true
38 | } else if let spv = v.superview {
39 | let c = constraint(item: v, attribute: .lastBaseline, toItem: v2)
40 | spv.addConstraint(c)
41 | }
42 | }
43 | return views
44 | }
45 |
46 | /** Aligns an array of views by their firstBaselines (on the Y Axis)
47 |
48 | Example Usage:
49 | ```
50 | align(firstBaselines: label1, label2, label3)
51 | ```
52 |
53 | Can also be used directly on horizontal layouts since they return the array of views :
54 | ```
55 | align(firstBaselines: |-label1-label2-label3-|)
56 | ```
57 |
58 | - Returns: The array of views, enabling chaining,
59 |
60 | */
61 | @discardableResult
62 | @MainActor public func align(firstBaselines views: UIView...) -> [UIView] {
63 | return align(firstBaselines: views)
64 | }
65 |
66 | @discardableResult
67 | @MainActor public func align(firstBaselines views: [UIView]) -> [UIView] {
68 | for (i, v) in views.enumerated() where views.count > i+1 {
69 | let v2 = views[i+1]
70 | if #available(iOS 9.0, *) {
71 | v.firstBaselineAnchor.constraint(equalTo: v2.firstBaselineAnchor).isActive = true
72 | } else if let spv = v.superview {
73 | let c = constraint(item: v, attribute: .firstBaseline, toItem: v2)
74 | spv.addConstraint(c)
75 | }
76 | }
77 | return views
78 | }
79 | #endif
80 |
--------------------------------------------------------------------------------
/Sources/Stevia/Stevia+Center.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Stevia+Center.swift
3 | // Stevia
4 | //
5 | // Created by Sacha Durand Saint Omer on 10/02/16.
6 | // Copyright © 2016 Sacha Durand Saint Omer. All rights reserved.
7 | //
8 |
9 | #if canImport(UIKit)
10 | import UIKit
11 |
12 | public extension UIView {
13 |
14 | /**
15 | Centers the view in its container.
16 |
17 | ```
18 | button.centerInContainer()
19 | ```
20 |
21 | - Returns: Itself, enabling chaining,
22 |
23 | */
24 | @discardableResult
25 | func centerInContainer() -> Self {
26 | if let spv = superview {
27 | alignCenter(self, with: spv)
28 | }
29 | return self
30 | }
31 |
32 | /**
33 | Centers the view horizontally (X axis) in its container.
34 |
35 | ```
36 | button.centerHorizontally()
37 | button.centerHorizontally(offset: 40)
38 | ```
39 |
40 | - Returns: Itself, enabling chaining,
41 |
42 | */
43 | @discardableResult
44 | func centerHorizontally(offset: Double = 0) -> Self {
45 | if let spv = superview {
46 | align(.vertical, v1: self, with: spv, offset: offset)
47 | }
48 | return self
49 | }
50 |
51 | /**
52 | Centers the view horizontally (X axis) in its container.
53 |
54 | ```
55 | button.centerHorizontally()
56 | button.centerHorizontally(offset: 40)
57 | ```
58 |
59 | - Returns: Itself, enabling chaining,
60 |
61 | */
62 | @discardableResult
63 | func centerHorizontally(offset: CGFloat) -> Self {
64 | centerHorizontally(offset: Double(offset))
65 | }
66 |
67 | /**
68 | Centers the view horizontally (X axis) in its container.
69 |
70 | ```
71 | button.centerHorizontally()
72 | button.centerHorizontally(offset: 40)
73 | ```
74 |
75 | - Returns: Itself, enabling chaining,
76 |
77 | */
78 | @discardableResult
79 | func centerHorizontally(offset: Int) -> Self {
80 | centerHorizontally(offset: Double(offset))
81 | }
82 |
83 | /**
84 | Centers the view vertically (Y axis) in its container.
85 |
86 | ```
87 | button.centerVertically()
88 | button.centerVertically(offset: 40)
89 | ```
90 |
91 | - Returns: Itself, enabling chaining,
92 |
93 | */
94 | @discardableResult
95 | func centerVertically(offset: Double = 0) -> Self {
96 | if let spv = superview {
97 | align(.horizontal, v1: self, with: spv, offset: offset)
98 | }
99 | return self
100 | }
101 |
102 | /**
103 | Centers the view vertically (Y axis) in its container.
104 |
105 | ```
106 | button.centerVertically()
107 | button.centerVertically(offset: 40)
108 | ```
109 |
110 | - Returns: Itself, enabling chaining,
111 |
112 | */
113 | @discardableResult
114 | func centerVertically(offset: CGFloat) -> Self {
115 | centerVertically(offset: Double(offset))
116 | }
117 |
118 | /**
119 | Centers the view vertically (Y axis) in its container.
120 |
121 | ```
122 | button.centerVertically()
123 | button.centerVertically(offset: 40)
124 | ```
125 |
126 | - Returns: Itself, enabling chaining,
127 |
128 | */
129 | @discardableResult
130 | func centerVertically(offset: Int) -> Self {
131 | centerVertically(offset: Double(offset))
132 | }
133 | }
134 | #endif
135 |
--------------------------------------------------------------------------------
/Sources/Stevia/Stevia+Constraints.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Stevia+Constraints.swift
3 | // LoginNadir
4 | //
5 | // Created by Sacha Durand Saint Omer on 01/10/15.
6 | // Copyright © 2015 Sacha Durand Saint Omer. All rights reserved.
7 | //
8 |
9 | #if canImport(UIKit)
10 | import UIKit
11 |
12 | public class SteviaLayoutConstraint: NSLayoutConstraint {
13 | public static var defaultPriority: Float = UILayoutPriority.defaultHigh + 1
14 | }
15 |
16 |
17 | // MARK: - Shortcut
18 |
19 | public extension UIView {
20 |
21 | /**
22 | Helper for creating and adding NSLayoutConstraint but with default values provided.
23 |
24 | For instance
25 |
26 | addConstraint(item: view1, attribute: .CenterX, toItem: view2)
27 |
28 | is equivalent to
29 |
30 | addConstraint(
31 | NSLayoutConstraint(item: view1,
32 | attribute: .CenterX,
33 | relatedBy: .Equal,
34 | toItem: view2,
35 | attribute: .CenterX,
36 | multiplier: 1,
37 | constant: 0
38 | )
39 | )
40 |
41 | - Returns: The NSLayoutConstraint created.
42 | */
43 | @discardableResult
44 | func addConstraint(item view1: AnyObject,
45 | attribute attr1: NSLayoutConstraint.Attribute,
46 | relatedBy: NSLayoutConstraint.Relation = .equal,
47 | toItem view2: AnyObject? = nil,
48 | attribute attr2: NSLayoutConstraint.Attribute? = nil,
49 | multiplier: Double = 1,
50 | constant: Double = 0) -> NSLayoutConstraint {
51 | let c = constraint(
52 | item: view1, attribute: attr1,
53 | relatedBy: relatedBy,
54 | toItem: view2, attribute: attr2,
55 | multiplier: multiplier, constant: constant)
56 | addConstraint(c)
57 | return c
58 | }
59 | }
60 |
61 | /**
62 | Helper for creating a NSLayoutConstraint but with default values provided.
63 |
64 | For instance
65 |
66 | constraint(item: view1, attribute: .CenterX, toItem: view2)
67 |
68 | is equivalent to
69 |
70 | NSLayoutConstraint(item: view1, attribute: .CenterX,
71 | relatedBy: .Equal,
72 | toItem: view2, attribute: .CenterX,
73 | multiplier: 1, constant: 0)
74 |
75 | - Returns: The NSLayoutConstraint created.
76 | */
77 | @MainActor func constraint(item view1: AnyObject,
78 | attribute attr1: NSLayoutConstraint.Attribute,
79 | relatedBy: NSLayoutConstraint.Relation = .equal,
80 | toItem view2: AnyObject? = nil,
81 | attribute attr2: NSLayoutConstraint.Attribute? = nil, // Not an attribute??
82 | multiplier: Double = 1,
83 | constant: Double = 0) -> NSLayoutConstraint {
84 | let c = SteviaLayoutConstraint(item: view1, attribute: attr1,
85 | relatedBy: relatedBy,
86 | toItem: view2, attribute: ((attr2 == nil) ? attr1 : attr2! ),
87 | multiplier: CGFloat(multiplier), constant: CGFloat(constant))
88 | c.priority = UILayoutPriority(rawValue: SteviaLayoutConstraint.defaultPriority)
89 | return c
90 | }
91 |
92 | public extension UIView {
93 |
94 | /**
95 | Get User added constraints. For making complex changes on layout, we need to remove user added constraints.
96 |
97 | If we remove all constraints, it may return broken layout.
98 |
99 | Use this method as:
100 |
101 | removeConstraints(userAddedConstraints)
102 |
103 | */
104 | var userAddedConstraints: [NSLayoutConstraint] {
105 | return constraints.filter { c in
106 | c is SteviaLayoutConstraint
107 | }
108 | }
109 | }
110 |
111 | // MARK: - Other
112 |
113 | public extension UIView {
114 |
115 | /**
116 | Makes a view follow another view's frame.
117 | For instance if we want a button to be on top of an image :
118 | You can also set how much you want to cover the image, like set the leading, trailing, top and bottom alignment.
119 |
120 | ```
121 | button.followEdges(image)
122 | ```
123 | */
124 | func followEdges(_ otherView: UIView, top : Double = 0.0, bottom : Double = 0.0, leading : Double = 0.0, trailing : Double = 0.0) {
125 | if let spv = superview {
126 | let cs = [
127 | constraint(item: self, attribute: .top, toItem: otherView, constant: top),
128 | constraint(item: self, attribute: .trailing, toItem: otherView, constant: trailing),
129 | constraint(item: self, attribute: .bottom, toItem: otherView, constant: bottom),
130 | constraint(item: self, attribute: .leading, toItem: otherView, constant: leading)
131 | ]
132 | spv.addConstraints(cs)
133 | }
134 | }
135 |
136 | /**
137 | Enforce a view to keep height and width equal at all times, essentially
138 | forcing it to be a square.
139 |
140 | ```
141 | image.heightEqualsWidth()
142 | ```
143 |
144 | - Returns: Itself, enabling chaining,
145 |
146 | */
147 | @discardableResult
148 | func heightEqualsWidth() -> Self {
149 | if let spv = superview {
150 | let c = constraint(item: self, attribute: .height, toItem: self, attribute: .width)
151 | spv.addConstraint(c)
152 | }
153 | return self
154 | }
155 |
156 | }
157 | #endif
158 |
--------------------------------------------------------------------------------
/Sources/Stevia/Stevia+Content.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Stevia+Content.swift
3 | // LoginStevia
4 | //
5 | // Created by Sacha Durand Saint Omer on 01/10/15.
6 | // Copyright © 2015 Sacha Durand Saint Omer. All rights reserved.
7 | //
8 |
9 | #if canImport(UIKit)
10 | import UIKit
11 |
12 | public extension UIButton {
13 |
14 | /**
15 | Sets the title of the button for normal State
16 |
17 | Essentially a shortcut for `setTitle("MyText", forState: .Normal)`
18 |
19 | - Returns: Itself for chaining purposes
20 | */
21 | @discardableResult
22 | func text(_ t: String) -> Self {
23 | setTitle(t, for: .normal)
24 | return self
25 | }
26 |
27 | /**
28 | Sets the localized key for the button's title in normal State
29 |
30 | Essentially a shortcut for `setTitle(NSLocalizedString("MyText", comment: "")
31 | , forState: .Normal)`
32 |
33 | - Returns: Itself for chaining purposes
34 | */
35 | @discardableResult
36 | func textKey(_ t: String) -> Self {
37 | text(NSLocalizedString(t, comment: ""))
38 | return self
39 | }
40 |
41 | /**
42 | Sets the image of the button in normal State
43 |
44 | Essentially a shortcut for `setImage(UIImage(named:"X"), forState: .Normal)`
45 |
46 | - Returns: Itself for chaining purposes
47 | */
48 | @discardableResult
49 | func image(_ s: String) -> Self {
50 | setImage(UIImage(named: s), for: .normal)
51 | return self
52 | }
53 | }
54 |
55 | public extension UITextField {
56 | /**
57 | Sets the textfield placeholder but in a chainable fashion
58 | - Returns: Itself for chaining purposes
59 | */
60 | @discardableResult
61 | func placeholder(_ t: String) -> Self {
62 | placeholder = t
63 | return self
64 | }
65 | }
66 |
67 | public extension UILabel {
68 | /**
69 | Sets the label text but in a chainable fashion
70 | - Returns: Itself for chaining purposes
71 | */
72 | @discardableResult
73 | func text(_ t: String) -> Self {
74 | text = t
75 | return self
76 | }
77 |
78 | /**
79 | Sets the label localization key but in a chainable fashion
80 | Essentially a shortcut for `text = NSLocalizedString("X", comment: "")`
81 | - Returns: Itself for chaining purposes
82 | */
83 | @discardableResult
84 | func textKey(_ t: String) -> Self {
85 | text(NSLocalizedString(t, comment: ""))
86 | return self
87 | }
88 | }
89 |
90 | extension UIImageView {
91 | /**
92 | Sets the image of the imageView but in a chainable fashion
93 |
94 | Essentially a shortcut for `image = UIImage(named: "X")`
95 |
96 | - Returns: Itself for chaining purposes
97 | */
98 | @discardableResult
99 | public func image(_ t: String) -> Self {
100 | image = UIImage(named: t)
101 | return self
102 | }
103 | }
104 | #endif
105 |
--------------------------------------------------------------------------------
/Sources/Stevia/Stevia+DoubleDash.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Stevia+DoubleDash.swift
3 | // Stevia
4 | //
5 | // Created by Sacha Durand Saint Omer on 03/05/16.
6 | // Copyright © 2016 Sacha Durand Saint Omer. All rights reserved.
7 | //
8 |
9 | #if canImport(UIKit)
10 | import UIKit
11 |
12 | infix operator -- :AdditionPrecedence
13 |
14 |
15 | @discardableResult
16 | public func -- (left: UIView, right: Double) -> PartialConstraint {
17 | return left-right
18 | }
19 |
20 | public func -- (left: UIView, right: CGFloat) -> PartialConstraint {
21 | return left--Double(right)
22 | }
23 |
24 | public func -- (left: UIView, right: Int) -> PartialConstraint {
25 | return left--Double(right)
26 | }
27 |
28 | @discardableResult
29 | @MainActor public func -- (left: SideConstraint, right: UIView) -> UIView {
30 | return left-right
31 | }
32 |
33 | @discardableResult
34 | @MainActor public func -- (left: [UIView], right: SideConstraint) -> [UIView] {
35 | return left-right
36 | }
37 |
38 | @discardableResult
39 | @MainActor public func -- (left: UIView, right: SideConstraint) -> UIView {
40 | return left-right
41 | }
42 |
43 | @discardableResult
44 | @MainActor public func -- (left: PartialConstraint, right: UIView) -> [UIView] {
45 | return left-right
46 | }
47 |
48 | @discardableResult
49 | @MainActor public func -- (left: UIView, right: UIView) -> [UIView] {
50 | return left-right
51 | }
52 |
53 | @discardableResult
54 | public func -- (left: [UIView], right: Double) -> PartialConstraint {
55 | return left-right
56 | }
57 |
58 | @discardableResult
59 | public func -- (left: [UIView], right: CGFloat) -> PartialConstraint {
60 | return left--Double(right)
61 | }
62 |
63 | @discardableResult
64 | public func -- (left: [UIView], right: Int) -> PartialConstraint {
65 | return left--Double(right)
66 | }
67 |
68 | @discardableResult
69 | @MainActor public func -- (left: [UIView], right: UIView) -> [UIView] {
70 | return left-right
71 | }
72 |
73 | @discardableResult
74 | public func -- (left: UIView, right: String) -> Space {
75 | return left-right
76 | }
77 |
78 | @discardableResult
79 | public func -- (left: [UIView], right: String) -> Space {
80 | return left-right
81 | }
82 |
83 | @discardableResult
84 | public func -- (left: Space, right: UIView) -> [UIView] {
85 | return left-right
86 | }
87 |
88 | @discardableResult
89 | public func -- (left: UIView,
90 | right: SteviaFlexibleMargin) -> PartialFlexibleConstraint {
91 | return left-right
92 | }
93 |
94 | @discardableResult
95 | public func -- (left: [UIView],
96 | right: SteviaFlexibleMargin) -> PartialFlexibleConstraint {
97 | return left-right
98 | }
99 |
100 | @discardableResult
101 | @MainActor public func -- (left: PartialFlexibleConstraint, right: UIView) -> [UIView] {
102 | return left-right
103 | }
104 |
105 | @discardableResult
106 | @MainActor public func -- (left: SteviaLeftFlexibleMargin, right: UIView) -> UIView {
107 | return left-right
108 | }
109 |
110 | @discardableResult
111 | @MainActor public func -- (left: UIView, right: SteviaRightFlexibleMargin) -> UIView {
112 | return left-right
113 | }
114 |
115 | @discardableResult
116 | @MainActor public func -- (left: [UIView], right: SteviaRightFlexibleMargin) -> [UIView] {
117 | return left-right
118 | }
119 | #endif
120 |
121 | /// Hyphen Bullet operator is introduced to remove the ambiguity with the language "-" (minus) sign operator.
122 | /// this ambiguity can make tu build times exponentially longer in big layout blocks.
123 |
124 |
125 | #if canImport(UIKit)
126 | import UIKit
127 |
128 | infix operator ⁃ :AdditionPrecedence
129 |
130 | @discardableResult
131 | public func ⁃ (left: UIView, right: Double) -> PartialConstraint {
132 | return left-right
133 | }
134 |
135 | public func ⁃ (left: UIView, right: CGFloat) -> PartialConstraint {
136 | return left⁃Double(right)
137 | }
138 |
139 | public func ⁃ (left: UIView, right: Int) -> PartialConstraint {
140 | return left⁃Double(right)
141 | }
142 |
143 | @discardableResult
144 | @MainActor public func ⁃ (left: SideConstraint, right: UIView) -> UIView {
145 | return left-right
146 | }
147 |
148 | @discardableResult
149 | @MainActor public func ⁃ (left: [UIView], right: SideConstraint) -> [UIView] {
150 | return left-right
151 | }
152 |
153 | @discardableResult
154 | @MainActor public func ⁃ (left: UIView, right: SideConstraint) -> UIView {
155 | return left-right
156 | }
157 |
158 | @discardableResult
159 | @MainActor public func ⁃ (left: PartialConstraint, right: UIView) -> [UIView] {
160 | return left-right
161 | }
162 |
163 | @discardableResult
164 | @MainActor public func ⁃ (left: UIView, right: UIView) -> [UIView] {
165 | return left-right
166 | }
167 |
168 | @discardableResult
169 | public func ⁃ (left: [UIView], right: Double) -> PartialConstraint {
170 | return left-right
171 | }
172 |
173 | @discardableResult
174 | public func ⁃ (left: [UIView], right: CGFloat) -> PartialConstraint {
175 | return left⁃Double(right)
176 | }
177 |
178 | @discardableResult
179 | public func ⁃ (left: [UIView], right: Int) -> PartialConstraint {
180 | return left⁃Double(right)
181 | }
182 |
183 | @discardableResult
184 | @MainActor public func ⁃ (left: [UIView], right: UIView) -> [UIView] {
185 | return left-right
186 | }
187 |
188 | @discardableResult
189 | public func ⁃ (left: UIView, right: String) -> Space {
190 | return left-right
191 | }
192 |
193 | @discardableResult
194 | public func ⁃ (left: [UIView], right: String) -> Space {
195 | return left-right
196 | }
197 |
198 | @discardableResult
199 | public func ⁃ (left: Space, right: UIView) -> [UIView] {
200 | return left-right
201 | }
202 |
203 | @discardableResult
204 | public func ⁃ (left: UIView,
205 | right: SteviaFlexibleMargin) -> PartialFlexibleConstraint {
206 | return left-right
207 | }
208 |
209 | @discardableResult
210 | public func ⁃ (left: [UIView],
211 | right: SteviaFlexibleMargin) -> PartialFlexibleConstraint {
212 | return left-right
213 | }
214 |
215 | @discardableResult
216 | @MainActor public func ⁃ (left: PartialFlexibleConstraint, right: UIView) -> [UIView] {
217 | return left-right
218 | }
219 |
220 | @discardableResult
221 | @MainActor public func ⁃ (left: SteviaLeftFlexibleMargin, right: UIView) -> UIView {
222 | return left-right
223 | }
224 |
225 | @discardableResult
226 | @MainActor public func ⁃ (left: UIView, right: SteviaRightFlexibleMargin) -> UIView {
227 | return left-right
228 | }
229 |
230 | @discardableResult
231 | @MainActor public func ⁃ (left: [UIView], right: SteviaRightFlexibleMargin) -> [UIView] {
232 | return left-right
233 | }
234 | #endif
235 |
--------------------------------------------------------------------------------
/Sources/Stevia/Stevia+Equation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Stevia+Equation.swift
3 | // Stevia
4 | //
5 | // Created by Sacha Durand Saint Omer on 21/01/2017.
6 | // Copyright © 2017 Sacha Durand Saint Omer. All rights reserved.
7 | //
8 |
9 | #if canImport(UIKit)
10 | import UIKit
11 |
12 | public struct SteviaAttribute {
13 | let view: UIView
14 | let attribute: NSLayoutConstraint.Attribute
15 | let constant: Double?
16 | let multiplier: Double?
17 |
18 | init(view: UIView, attribute: NSLayoutConstraint.Attribute) {
19 | self.view = view
20 | self.attribute = attribute
21 | self.constant = nil
22 | self.multiplier = nil
23 | }
24 |
25 | init(view: UIView, attribute: NSLayoutConstraint.Attribute, constant: Double?, multiplier: Double?) {
26 | self.view = view
27 | self.attribute = attribute
28 | self.constant = constant
29 | self.multiplier = multiplier
30 | }
31 | }
32 |
33 | public extension UIView {
34 |
35 | var Width: SteviaAttribute {
36 | return SteviaAttribute(view: self, attribute: .width)
37 | }
38 |
39 | var Height: SteviaAttribute {
40 | return SteviaAttribute(view: self, attribute: .height)
41 | }
42 |
43 | var Top: SteviaAttribute {
44 | return SteviaAttribute(view: self, attribute: .top)
45 | }
46 |
47 | var Bottom: SteviaAttribute {
48 | return SteviaAttribute(view: self, attribute: .bottom)
49 | }
50 |
51 | var Left: SteviaAttribute {
52 | return SteviaAttribute(view: self, attribute: .left)
53 | }
54 |
55 | var Right: SteviaAttribute {
56 | return SteviaAttribute(view: self, attribute: .right)
57 | }
58 |
59 | var Leading: SteviaAttribute {
60 | return SteviaAttribute(view: self, attribute: .leading)
61 | }
62 |
63 | var Trailing: SteviaAttribute {
64 | return SteviaAttribute(view: self, attribute: .trailing)
65 | }
66 |
67 | var CenterX: SteviaAttribute {
68 | return SteviaAttribute(view: self, attribute: .centerX)
69 | }
70 |
71 | var CenterY: SteviaAttribute {
72 | return SteviaAttribute(view: self, attribute: .centerY)
73 | }
74 |
75 | var FirstBaseline: SteviaAttribute {
76 | return SteviaAttribute(view: self, attribute: .firstBaseline)
77 | }
78 |
79 | var LastBaseline: SteviaAttribute {
80 | return SteviaAttribute(view: self, attribute: .lastBaseline)
81 | }
82 | }
83 |
84 | // MARK: - Equations of type v.P == v'.P' + X
85 |
86 | @discardableResult
87 | @MainActor public func == (left: SteviaAttribute, right: SteviaAttribute) -> NSLayoutConstraint {
88 | let constant = right.constant ?? left.constant ?? 0
89 | let multiplier = right.multiplier ?? left.multiplier ?? 1
90 |
91 | if left.view.superview == right.view.superview { // A and B are at the same level
92 | // Old code
93 | if let spv = left.view.superview {
94 | return spv.addConstraint(item: left.view,
95 | attribute: left.attribute,
96 | toItem: right.view,
97 | attribute: right.attribute,
98 | multiplier: multiplier,
99 | constant: constant)
100 | }
101 | } else if left.view.superview == right.view { // A is in B (first level)
102 | return right.view.addConstraint(item: left.view,
103 | attribute: left.attribute,
104 | toItem: right.view,
105 | attribute: right.attribute,
106 | multiplier: multiplier,
107 | constant: constant)
108 | } else if right.view.superview == left.view { // B is in A (first level)
109 | return left.view.addConstraint(item: right.view,
110 | attribute: right.attribute,
111 | toItem: left.view,
112 | attribute: left.attribute,
113 | multiplier: multiplier,
114 | constant: constant)
115 | } else if left.view.isDescendant(of: right.view) { // A is in B (LOW level)
116 | return right.view.addConstraint(item: left.view,
117 | attribute: left.attribute,
118 | toItem: right.view,
119 | attribute: right.attribute,
120 | multiplier: multiplier,
121 | constant: constant)
122 | } else if right.view.isDescendant(of: left.view) { // B is in A (LOW level)
123 | return left.view.addConstraint(item: left.view,
124 | attribute: left.attribute,
125 | toItem: right.view,
126 | attribute: right.attribute,
127 | multiplier: multiplier,
128 | constant: constant)
129 | } else if let commonParent = commonParent(with: left.view, and: right.view) { // Look for common ancestor
130 | return commonParent.addConstraint(item: left.view,
131 | attribute: left.attribute,
132 | toItem: right.view,
133 | attribute: right.attribute,
134 | multiplier: multiplier,
135 | constant: constant)
136 | }
137 |
138 | return NSLayoutConstraint()
139 | }
140 |
141 | @MainActor func commonParent(with viewA: UIView, and viewB: UIView) -> UIView? {
142 |
143 | // Both views should have a superview
144 | guard viewA.superview != nil && viewB.superview != nil else {
145 | return nil
146 | }
147 |
148 | // Find the common parent
149 | var spv = viewA.superview
150 | while spv != nil {
151 | if viewA.isDescendant(of: spv!) && viewB.isDescendant(of: spv!) {
152 | return spv
153 | } else {
154 | spv = spv?.superview
155 | }
156 | }
157 | return nil
158 | }
159 |
160 | @discardableResult
161 | @MainActor public func >= (left: SteviaAttribute, right: SteviaAttribute) -> NSLayoutConstraint {
162 | return applyRelation(left: left, right: right, relateBy: .greaterThanOrEqual)
163 | }
164 |
165 | @discardableResult
166 | @MainActor public func <= (left: SteviaAttribute, right: SteviaAttribute) -> NSLayoutConstraint {
167 | return applyRelation(left: left, right: right, relateBy: .lessThanOrEqual)
168 | }
169 |
170 | @MainActor private func applyRelation(left: SteviaAttribute, right: SteviaAttribute, relateBy: NSLayoutConstraint.Relation) -> NSLayoutConstraint {
171 | let constant = right.constant ?? 0
172 | let multiplier = right.multiplier ?? 1
173 | if let spv = left.view.superview {
174 | return spv.addConstraint(item: left.view,
175 | attribute: left.attribute,
176 | relatedBy: relateBy,
177 | toItem: right.view,
178 | attribute: right.attribute,
179 | multiplier: multiplier,
180 | constant: constant)
181 | }
182 | return NSLayoutConstraint()
183 | }
184 |
185 | @discardableResult
186 | public func + (left: SteviaAttribute, right: Double) -> SteviaAttribute {
187 | SteviaAttribute(view: left.view, attribute: left.attribute, constant: right, multiplier: left.multiplier)
188 | }
189 |
190 | @discardableResult
191 | public func + (left: SteviaAttribute, right: CGFloat) -> SteviaAttribute {
192 | left + Double(right)
193 | }
194 |
195 | @discardableResult
196 | public func + (left: SteviaAttribute, right: Int) -> SteviaAttribute {
197 | left + Double(right)
198 | }
199 |
200 | @discardableResult
201 | public func - (left: SteviaAttribute, right: Double) -> SteviaAttribute {
202 | SteviaAttribute(view: left.view, attribute: left.attribute, constant: -right, multiplier: left.multiplier)
203 | }
204 |
205 | @discardableResult
206 | public func - (left: SteviaAttribute, right: CGFloat) -> SteviaAttribute {
207 | left - Double(right)
208 | }
209 |
210 | @discardableResult
211 | public func - (left: SteviaAttribute, right: Int) -> SteviaAttribute {
212 | left - Double(right)
213 | }
214 |
215 | @discardableResult
216 | public func * (left: SteviaAttribute, right: Double) -> SteviaAttribute {
217 | SteviaAttribute(view: left.view, attribute: left.attribute, constant: left.constant, multiplier: right)
218 | }
219 |
220 | @discardableResult
221 | public func * (left: SteviaAttribute, right: CGFloat) -> SteviaAttribute {
222 | left * Double(right)
223 | }
224 |
225 | @discardableResult
226 | public func * (left: SteviaAttribute, right: Int) -> SteviaAttribute {
227 | left * Double(right)
228 | }
229 |
230 | @discardableResult
231 | public func / (left: SteviaAttribute, right: Double) -> SteviaAttribute {
232 | left * (1/right)
233 | }
234 |
235 | @discardableResult
236 | public func / (left: SteviaAttribute, right: CGFloat) -> SteviaAttribute {
237 | left / Double(right)
238 | }
239 |
240 | @discardableResult
241 | public func / (left: SteviaAttribute, right: Int) -> SteviaAttribute {
242 | left / Double(right)
243 | }
244 |
245 |
246 | @discardableResult
247 | public func % (left: Double, right: SteviaAttribute) -> SteviaAttribute {
248 | right * (left/100)
249 | }
250 |
251 | @discardableResult
252 | public func % (left: CGFloat, right: SteviaAttribute) -> SteviaAttribute {
253 | Double(left) % right
254 | }
255 |
256 | @discardableResult
257 | public func % (left: Int, right: SteviaAttribute) -> SteviaAttribute {
258 | Double(left) % right
259 | }
260 |
261 |
262 | // MARK: - Equations of type v.P == X
263 |
264 | @discardableResult
265 | @MainActor public func == (left: SteviaAttribute, right: Double) -> NSLayoutConstraint {
266 | if let spv = left.view.superview {
267 | var toItem: UIView? = spv
268 | var constant = right
269 | if left.attribute == .width || left.attribute == .height {
270 | toItem = nil
271 | }
272 | if left.attribute == .bottom || left.attribute == .right {
273 | constant = -constant
274 | }
275 | return spv.addConstraint(item: left.view,
276 | attribute: left.attribute,
277 | toItem: toItem,
278 | constant: Double(constant))
279 | }
280 | return NSLayoutConstraint()
281 | }
282 |
283 | @discardableResult
284 | @MainActor public func == (left: SteviaAttribute, right: CGFloat) -> NSLayoutConstraint {
285 | left == Double(right)
286 | }
287 |
288 | @discardableResult
289 | @MainActor public func == (left: SteviaAttribute, right: Int) -> NSLayoutConstraint {
290 | left == Double(right)
291 | }
292 |
293 | @discardableResult
294 | @MainActor public func >= (left: SteviaAttribute, right: Double) -> NSLayoutConstraint {
295 | if let spv = left.view.superview {
296 | var toItem: UIView? = spv
297 | var constant = right
298 | if left.attribute == .width || left.attribute == .height {
299 | toItem = nil
300 | }
301 | if left.attribute == .bottom || left.attribute == .right {
302 | constant = -constant
303 | }
304 | return spv.addConstraint(item: left.view,
305 | attribute: left.attribute,
306 | relatedBy: .greaterThanOrEqual,
307 | toItem: toItem,
308 | constant: Double(constant))
309 | }
310 | return NSLayoutConstraint()
311 | }
312 |
313 | @discardableResult
314 | @MainActor public func >= (left: SteviaAttribute, right: CGFloat) -> NSLayoutConstraint {
315 | left >= Double(right)
316 | }
317 |
318 | @discardableResult
319 | @MainActor public func >= (left: SteviaAttribute, right: Int) -> NSLayoutConstraint {
320 | left >= Double(right)
321 | }
322 |
323 | @discardableResult
324 | @MainActor public func <= (left: SteviaAttribute, right: Double) -> NSLayoutConstraint {
325 | if let spv = left.view.superview {
326 | var toItem: UIView? = spv
327 | var constant = right
328 | if left.attribute == .width || left.attribute == .height {
329 | toItem = nil
330 | }
331 | if left.attribute == .bottom || left.attribute == .right {
332 | constant = -constant
333 | }
334 | return spv.addConstraint(item: left.view,
335 | attribute: left.attribute,
336 | relatedBy: .lessThanOrEqual,
337 | toItem: toItem,
338 | constant: Double(constant))
339 | }
340 | return NSLayoutConstraint()
341 | }
342 |
343 | @discardableResult
344 | @MainActor public func <= (left: SteviaAttribute, right: CGFloat) -> NSLayoutConstraint {
345 | left <= Double(right)
346 | }
347 |
348 | @discardableResult
349 | @MainActor public func <= (left: SteviaAttribute, right: Int) -> NSLayoutConstraint {
350 | left <= Double(right)
351 | }
352 | #endif
353 |
--------------------------------------------------------------------------------
/Sources/Stevia/Stevia+Fill.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Stevia+Fill.swift
3 | // Stevia
4 | //
5 | // Created by Sacha Durand Saint Omer on 10/02/16.
6 | // Copyright © 2016 Sacha Durand Saint Omer. All rights reserved.
7 | //
8 |
9 | #if canImport(UIKit)
10 | import UIKit
11 |
12 | public extension UIView {
13 |
14 | /**
15 | Adds the constraints needed for the view to fill its `superview`.
16 | A padding can be used to apply equal spaces between the view and its superview
17 | */
18 | @discardableResult
19 | func fillContainer(padding: Double = 0) -> Self {
20 | fillHorizontally(padding: padding)
21 | fillVertically(padding: padding)
22 | return self
23 | }
24 |
25 | /**
26 | Adds the constraints needed for the view to fill its `superview`.
27 | A padding can be used to apply equal spaces between the view and its superview
28 | */
29 | @discardableResult
30 | func fillContainer(padding: CGFloat) -> Self {
31 | fillContainer(padding: Double(padding))
32 | }
33 |
34 | /**
35 | Adds the constraints needed for the view to fill its `superview`.
36 | A padding can be used to apply equal spaces between the view and its superview
37 | */
38 | @discardableResult
39 | func fillContainer(padding: Int) -> Self {
40 | fillContainer(padding: Double(padding))
41 | }
42 |
43 | /**
44 | Adds the constraints needed for the view to fill its `superview` Vertically.
45 | A padding can be used to apply equal spaces between the view and its superview
46 | */
47 | @discardableResult
48 | func fillVertically(padding: Double = 0) -> Self {
49 | fill(.vertical, points: padding)
50 | }
51 |
52 | /**
53 | Adds the constraints needed for the view to fill its `superview` Vertically.
54 | A padding can be used to apply equal spaces between the view and its superview
55 | */
56 | @discardableResult
57 | func fillVertically(padding: CGFloat) -> Self {
58 | fillVertically(padding: Double(padding))
59 | }
60 |
61 | /**
62 | Adds the constraints needed for the view to fill its `superview` Vertically.
63 | A padding can be used to apply equal spaces between the view and its superview
64 | */
65 | @discardableResult
66 | func fillVertically(padding: Int) -> Self {
67 | fillVertically(padding: Double(padding))
68 | }
69 |
70 | /**
71 | Adds the constraints needed for the view to fill its `superview` Horizontally.
72 | A padding can be used to apply equal spaces between the view and its superview
73 | */
74 | @discardableResult
75 | func fillHorizontally(padding: Double = 0) -> Self {
76 | fill(.horizontal, points: padding)
77 | }
78 |
79 | /**
80 | Adds the constraints needed for the view to fill its `superview` Horizontally.
81 | A padding can be used to apply equal spaces between the view and its superview
82 | */
83 | @discardableResult
84 | func fillHorizontally(padding: CGFloat) -> Self {
85 | fillHorizontally(padding: Double(padding))
86 | }
87 |
88 | /**
89 | Adds the constraints needed for the view to fill its `superview` Horizontally.
90 | A padding can be used to apply equal spaces between the view and its superview
91 | */
92 | @discardableResult
93 | func fillHorizontally(padding: Int) -> Self {
94 | fillHorizontally(padding: Double(padding))
95 | }
96 |
97 | fileprivate func fill(_ axis: NSLayoutConstraint.Axis, points: Double = 0) -> Self {
98 | let a: NSLayoutConstraint.Attribute = axis == .vertical ? .top : .leading
99 | let b: NSLayoutConstraint.Attribute = axis == .vertical ? .bottom : .trailing
100 | if let spv = superview {
101 | let c1 = constraint(item: self, attribute: a, toItem: spv, constant: points)
102 | let c2 = constraint(item: self, attribute: b, toItem: spv, constant: -points)
103 | spv.addConstraints([c1, c2])
104 | }
105 | return self
106 | }
107 | }
108 | #endif
109 |
--------------------------------------------------------------------------------
/Sources/Stevia/Stevia+FlexibleMargin.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Stevia+FlexibleMargin.swift
3 | // Stevia
4 | //
5 | // Created by Sacha Durand Saint Omer on 10/07/16.
6 | // Copyright © 2016 Sacha Durand Saint Omer. All rights reserved.
7 | //
8 |
9 | #if canImport(UIKit)
10 | import UIKit
11 |
12 | prefix operator >=
13 | @discardableResult
14 | public prefix func >= (p: Double) -> SteviaFlexibleMargin {
15 | SteviaFlexibleMargin(points: p, relation: .greaterThanOrEqual)
16 | }
17 |
18 | @discardableResult
19 | public prefix func >= (p: CGFloat) -> SteviaFlexibleMargin {
20 | >=Double(p)
21 | }
22 |
23 | @discardableResult
24 | public prefix func >= (p: Int) -> SteviaFlexibleMargin {
25 | >=Double(p)
26 | }
27 |
28 | prefix operator <=
29 | @discardableResult
30 | public prefix func <= (p: Double) -> SteviaFlexibleMargin {
31 | SteviaFlexibleMargin(points: p, relation: .lessThanOrEqual)
32 | }
33 |
34 | @discardableResult
35 | public prefix func <= (p: CGFloat) -> SteviaFlexibleMargin {
36 | <=Double(p)
37 | }
38 |
39 | @discardableResult
40 | public prefix func <= (p: Int) -> SteviaFlexibleMargin {
41 | <=Double(p)
42 | }
43 |
44 | public struct SteviaFlexibleMargin {
45 | var points: Double!
46 | var relation: NSLayoutConstraint.Relation!
47 | }
48 |
49 | public struct PartialFlexibleConstraint {
50 | var fm: SteviaFlexibleMargin!
51 | var view1: UIView?
52 | var views: [UIView]?
53 | }
54 |
55 | @discardableResult
56 | public func - (left: UIView,
57 | right: SteviaFlexibleMargin) -> PartialFlexibleConstraint {
58 | return PartialFlexibleConstraint(fm: right, view1: left, views: nil)
59 | }
60 |
61 | @discardableResult
62 | public func - (left: [UIView],
63 | right: SteviaFlexibleMargin) -> PartialFlexibleConstraint {
64 | return PartialFlexibleConstraint(fm: right, view1: nil, views: left)
65 | }
66 |
67 | @discardableResult
68 | @MainActor public func - (left: PartialFlexibleConstraint, right: UIView) -> [UIView] {
69 | if let views = left.views {
70 | if let spv = right.superview {
71 | let c = constraint(item: right, attribute: .leading,
72 | relatedBy: left.fm.relation, toItem: views.last,
73 | attribute: .trailing,
74 | constant: left.fm.points)
75 | spv.addConstraint(c)
76 | }
77 | return views + [right]
78 | } else {
79 | if let spv = right.superview {
80 | let c = constraint(item: right, attribute: .leading,
81 | relatedBy: left.fm.relation, toItem: left.view1!,
82 | attribute: .trailing,
83 | constant: left.fm.points)
84 | spv.addConstraint(c)
85 | }
86 | return [left.view1!, right]
87 | }
88 | }
89 |
90 | // Left Flexible margin
91 |
92 | public struct SteviaLeftFlexibleMargin {
93 | let fm: SteviaFlexibleMargin
94 | }
95 |
96 | @discardableResult
97 | public prefix func |- (fm: SteviaFlexibleMargin) -> SteviaLeftFlexibleMargin {
98 | return SteviaLeftFlexibleMargin(fm: fm)
99 | }
100 |
101 | @discardableResult
102 | @MainActor public func - (left: SteviaLeftFlexibleMargin, right: UIView) -> UIView {
103 | if let spv = right.superview {
104 | let c = constraint(item: right, attribute: .leading,
105 | relatedBy: left.fm.relation, toItem: spv,
106 | attribute: .leading,
107 | constant: left.fm.points)
108 | spv.addConstraint(c)
109 | }
110 | return right
111 | }
112 |
113 | // Right Flexible margin
114 |
115 | public struct SteviaRightFlexibleMargin {
116 | let fm: SteviaFlexibleMargin
117 | }
118 |
119 | @discardableResult
120 | public postfix func -| (fm: SteviaFlexibleMargin) -> SteviaRightFlexibleMargin {
121 | return SteviaRightFlexibleMargin(fm: fm)
122 | }
123 |
124 | @discardableResult
125 | @MainActor public func - (left: UIView, right: SteviaRightFlexibleMargin) -> UIView {
126 | if let spv = left.superview {
127 | let c = constraint(item: spv, attribute: .trailing,
128 | relatedBy: right.fm.relation, toItem: left,
129 | attribute: .trailing,
130 | constant: right.fm.points)
131 | spv.addConstraint(c)
132 | }
133 | return left
134 | }
135 |
136 | @discardableResult
137 | @MainActor public func - (left: [UIView], right: SteviaRightFlexibleMargin) -> [UIView] {
138 | if let spv = left.last!.superview {
139 | let c = constraint(item: spv, attribute: .trailing,
140 | relatedBy: right.fm.relation,
141 | toItem: left.last!,
142 | attribute: .trailing,
143 | constant: right.fm.points)
144 | spv.addConstraint(c)
145 | }
146 | return left
147 | }
148 | #endif
149 |
--------------------------------------------------------------------------------
/Sources/Stevia/Stevia+GetConstraint.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Stevia+GetConstraint.swift
3 | // Stevia
4 | //
5 | // Created by Sacha Durand Saint Omer on 12/03/16.
6 | // Copyright © 2016 Sacha Durand Saint Omer. All rights reserved.
7 | //
8 |
9 | #if canImport(UIKit)
10 | import UIKit
11 |
12 | public extension UIView {
13 |
14 | /** Gets the left constraint if found.
15 |
16 | Example Usage for changing left margin of a label :
17 | ```
18 | label.leftConstraint?.constant = 10
19 |
20 | // Animate if needed
21 | UIView.animateWithDuration(0.3, animations:layoutIfNeeded)
22 | ```
23 | - Returns: The left NSLayoutConstraint if found.
24 | */
25 | var leftConstraint: NSLayoutConstraint? {
26 | return constraintForView(self, attribute: .left)
27 | }
28 |
29 | /** Gets the right constraint if found.
30 |
31 | Example Usage for changing right margin of a label :
32 |
33 | ```
34 | label.rightConstraint?.constant = 10
35 |
36 | // Animate if needed
37 | UIView.animateWithDuration(0.3, animations:layoutIfNeeded)
38 | ```
39 | - Returns: The right NSLayoutConstraint if found.
40 | */
41 | var rightConstraint: NSLayoutConstraint? {
42 | return constraintForView(self, attribute: .right)
43 | }
44 |
45 | /** Gets the top constraint if found.
46 |
47 | Example Usage for changing top margin of a label :
48 |
49 | ```
50 | label.topConstraint?.constant = 10
51 |
52 | // Animate if needed
53 | UIView.animateWithDuration(0.3, animations:layoutIfNeeded)
54 | ```
55 | - Returns: The top NSLayoutConstraint if found.
56 | */
57 | var topConstraint: NSLayoutConstraint? {
58 | return constraintForView(self, attribute: .top)
59 | }
60 |
61 | /** Gets the bottom constraint if found.
62 |
63 | Example Usage for changing bottom margin of a label :
64 |
65 | ```
66 | label.bottomConstraint?.constant = 10
67 |
68 | // Animate if needed
69 | UIView.animateWithDuration(0.3, animations:layoutIfNeeded)
70 | ```
71 | - Returns: The bottom NSLayoutConstraint if found.
72 | */
73 | var bottomConstraint: NSLayoutConstraint? {
74 | return constraintForView(self, attribute: .bottom)
75 | }
76 |
77 | /** Gets the height constraint if found.
78 |
79 | Example Usage for changing height property of a label :
80 |
81 | ```
82 | label.heightConstraint?.constant = 10
83 |
84 | // Animate if needed
85 | UIView.animateWithDuration(0.3, animations:layoutIfNeeded)
86 | ```
87 | - Returns: The height NSLayoutConstraint if found.
88 | */
89 | var heightConstraint: NSLayoutConstraint? {
90 | return constraintForView(self, attribute: .height)
91 | }
92 |
93 | /** Gets the width constraint if found.
94 |
95 | Example Usage for changing width property of a label :
96 |
97 | ```
98 | label.widthConstraint?.constant = 10
99 |
100 | // Animate if needed
101 | UIView.animateWithDuration(0.3, animations:layoutIfNeeded)
102 | ```
103 | - Returns: The width NSLayoutConstraint if found.
104 | */
105 | var widthConstraint: NSLayoutConstraint? {
106 | return constraintForView(self, attribute: .width)
107 | }
108 |
109 | /** Gets the trailing constraint if found.
110 |
111 | Example Usage for changing width property of a label :
112 |
113 | ```
114 | label.trailingConstraint?.constant = 10
115 |
116 | // Animate if needed
117 | UIView.animateWithDuration(0.3, animations:layoutIfNeeded)
118 | ```
119 | - Returns: The trailing NSLayoutConstraint if found.
120 | */
121 | var trailingConstraint: NSLayoutConstraint? {
122 | return constraintForView(self, attribute: .trailing)
123 | }
124 |
125 | /** Gets the leading constraint if found.
126 |
127 | Example Usage for changing width property of a label :
128 |
129 | ```
130 | label.leadingConstraint?.constant = 10
131 |
132 | // Animate if needed
133 | UIView.animateWithDuration(0.3, animations:layoutIfNeeded)
134 | ```
135 | - Returns: The leading NSLayoutConstraint if found.
136 | */
137 | var leadingConstraint: NSLayoutConstraint? {
138 | return constraintForView(self, attribute: .leading)
139 | }
140 |
141 | /** Gets the centerX constraint if found.
142 |
143 | Example Usage for changing width property of a label :
144 |
145 | ```
146 | label.centerXConstraint?.constant = 10
147 |
148 | // Animate if needed
149 | UIView.animateWithDuration(0.3, animations:layoutIfNeeded)
150 | ```
151 | - Returns: The width NSLayoutConstraint if found.
152 | */
153 | var centerXConstraint: NSLayoutConstraint? {
154 | return constraintForView(self, attribute: .centerX)
155 | }
156 |
157 | /** Gets the centerY constraint if found.
158 |
159 | Example Usage for changing width property of a label :
160 |
161 | ```
162 | label.centerYConstraint?.constant = 10
163 |
164 | // Animate if needed
165 | UIView.animateWithDuration(0.3, animations:layoutIfNeeded)
166 | ```
167 | - Returns: The width NSLayoutConstraint if found.
168 | */
169 | var centerYConstraint: NSLayoutConstraint? {
170 | return constraintForView(self, attribute: .centerY)
171 | }
172 | }
173 |
174 | @MainActor func constraintForView(_ v: UIView, attribute: NSLayoutConstraint.Attribute) -> NSLayoutConstraint? {
175 |
176 | func lookForConstraint(in view: UIView?) -> NSLayoutConstraint? {
177 | guard let constraints = view?.constraints else {
178 | return nil
179 | }
180 | for c in constraints {
181 | if let fi = c.firstItem as? NSObject, fi == v && c.firstAttribute == attribute {
182 | return c
183 | } else if let si = c.secondItem as? NSObject, si == v && c.secondAttribute == attribute {
184 | return c
185 | }
186 | }
187 | return nil
188 | }
189 |
190 | // Width and height constraints added via widthAnchor/heightAnchors are
191 | // added on the view itself.
192 | if (attribute == .width || attribute == .height) {
193 | return lookForConstraint(in: v.superview) ?? lookForConstraint(in: v)
194 | }
195 |
196 | // Look for constraint on superview.
197 | return lookForConstraint(in: v.superview)
198 | }
199 | #endif
200 |
--------------------------------------------------------------------------------
/Sources/Stevia/Stevia+Hierarchy.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Stevia+Hierarchy.swift
3 | // LoginStevia
4 | //
5 | // Created by Sacha Durand Saint Omer on 01/10/15.
6 | // Copyright © 2015 Sacha Durand Saint Omer. All rights reserved.
7 | //
8 |
9 | #if canImport(UIKit)
10 | import UIKit
11 |
12 | @resultBuilder public struct SubviewsBuilder {
13 | public static func buildBlock(_ content: UIView...) -> [UIView] {
14 | return content
15 | }
16 | }
17 |
18 | public extension UIView {
19 |
20 | @available(*, deprecated, renamed: "subviews")
21 | @discardableResult
22 | func sv(_ subViews: UIView...) -> UIView {
23 | subviews(subViews)
24 | }
25 |
26 | /**
27 | Defines the view hierachy for the view.
28 |
29 | Esentially, this is just a shortcut to `addSubview`
30 | and 'translatesAutoresizingMaskIntoConstraints = false'
31 |
32 |
33 |
34 | ```
35 | class MyView: UIView {
36 |
37 | let email = UITextField()
38 | let password = UITextField()
39 | let login = UIButton()
40 |
41 | convenience init() {
42 | self.init(frame: CGRect.zero)
43 |
44 | subviews(
45 | email,
46 | password,
47 | login
48 | )
49 | ...
50 |
51 | }
52 | }
53 |
54 | ```
55 |
56 | - Returns: Itself to enable nested layouts.
57 | */
58 | @discardableResult
59 | func subviews(_ subViews: UIView...) -> UIView {
60 | subviews(subViews)
61 | }
62 |
63 | /**
64 | Defines the view hierachy for the view.
65 |
66 | Esentially, this is just a shortcut to `addSubview`
67 | and 'translatesAutoresizingMaskIntoConstraints = false'
68 |
69 |
70 |
71 | ```
72 | class MyView: UIView {
73 |
74 | let email = UITextField()
75 | let password = UITextField()
76 | let login = UIButton()
77 |
78 | convenience init() {
79 | self.init(frame: CGRect.zero)
80 |
81 | subviews {
82 | email
83 | password
84 | login
85 | }
86 | ...
87 |
88 | }
89 | }
90 |
91 | ```
92 |
93 | - Returns: Itself to enable nested layouts.
94 | */
95 | @discardableResult
96 | func subviews(@SubviewsBuilder content: () -> [UIView]) -> UIView {
97 | subviews(content())
98 | }
99 |
100 | /**
101 | Defines the view hierachy for the view.
102 |
103 | Esentially, this is just a shortcut to `addSubview`
104 | and 'translatesAutoresizingMaskIntoConstraints = false'
105 |
106 |
107 |
108 | ```
109 | class MyView: UIView {
110 |
111 | let email = UITextField()
112 | let password = UITextField()
113 | let login = UIButton()
114 |
115 | convenience init() {
116 | self.init(frame: CGRect.zero)
117 |
118 | subviews {
119 | email
120 | password
121 | login
122 | }
123 | ...
124 |
125 | }
126 | }
127 |
128 | ```
129 |
130 | - Returns: Itself to enable nested layouts.
131 | */
132 | @discardableResult
133 | func subviews(@SubviewsBuilder content: () -> UIView) -> UIView {
134 | let subview = content()
135 | subviews(subview)
136 | return self
137 | }
138 |
139 | /**
140 | Defines the view hierachy for the view.
141 |
142 | Esentially, this is just a shortcut to `addSubview`
143 | and 'translatesAutoresizingMaskIntoConstraints = false'
144 |
145 |
146 | ```
147 | class MyView: UIView {
148 |
149 | let email = UITextField()
150 | let password = UITextField()
151 | let login = UIButton()
152 |
153 | convenience init() {
154 | self.init(frame: CGRect.zero)
155 |
156 | sv(
157 | email,
158 | password,
159 | login
160 | )
161 | ...
162 |
163 | }
164 | }
165 |
166 | ```
167 |
168 | - Returns: Itself to enable nested layouts.
169 | */
170 | @objc
171 | @available(*, deprecated, renamed: "subviews")
172 | @discardableResult
173 | func sv(_ subViews: [UIView]) -> UIView {
174 | subviews(subViews)
175 | }
176 |
177 |
178 | @discardableResult
179 | @objc
180 | func subviews(_ subViews: [UIView]) -> UIView {
181 | for sv in subViews {
182 | addSubview(sv)
183 | sv.translatesAutoresizingMaskIntoConstraints = false
184 | }
185 | return self
186 | }
187 | }
188 |
189 | public extension UITableViewCell {
190 |
191 | /**
192 | Defines the view hierachy for the view.
193 |
194 | Esentially, this is just a shortcut to `contentView.addSubview`
195 | and 'translatesAutoresizingMaskIntoConstraints = false'
196 |
197 | ```
198 | class NotificationCell: UITableViewCell {
199 |
200 | var avatar = UIImageView()
201 | var name = UILabel()
202 | var followButton = UIButton()
203 |
204 | required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) }
205 | override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
206 | super.init(style: style, reuseIdentifier: reuseIdentifier) {
207 |
208 | sv(
209 | avatar,
210 | name,
211 | followButton
212 | )
213 | ...
214 |
215 | }
216 | }
217 | ```
218 |
219 | - Returns: Itself to enable nested layouts.
220 | */
221 | @available(*, deprecated, renamed: "subviews")
222 | @discardableResult
223 | override func sv(_ subViews: [UIView]) -> UIView {
224 | contentView.subviews(subViews)
225 | }
226 |
227 | /**
228 | Defines the view hierachy for the view.
229 |
230 | Esentially, this is just a shortcut to `contentView.addSubview`
231 | and 'translatesAutoresizingMaskIntoConstraints = false'
232 |
233 | ```
234 | class NotificationCell: UITableViewCell {
235 |
236 | var avatar = UIImageView()
237 | var name = UILabel()
238 | var followButton = UIButton()
239 |
240 | required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) }
241 | override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
242 | super.init(style: style, reuseIdentifier: reuseIdentifier) {
243 |
244 | subviews(
245 | avatar,
246 | name,
247 | followButton
248 | )
249 | ...
250 |
251 | }
252 | }
253 | ```
254 |
255 | - Returns: Itself to enable nested layouts.
256 | */
257 | @discardableResult
258 | override func subviews(_ subViews: [UIView]) -> UIView {
259 | contentView.subviews(subViews)
260 | }
261 | }
262 |
263 | public extension UICollectionViewCell {
264 | /**
265 | Defines the view hierachy for the view.
266 |
267 | Esentially, this is just a shortcut to `contentView.addSubview`
268 | and 'translatesAutoresizingMaskIntoConstraints = false'
269 |
270 | ```
271 | class PhotoCollectionViewCell: UICollectionViewCell {
272 |
273 | var avatar = UIImageView()
274 | var name = UILabel()
275 | var followButton = UIButton()
276 |
277 |
278 | required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") }
279 | override init(frame: CGRect) {
280 | super.init(frame: frame)
281 |
282 | subviews(
283 | avatar,
284 | name,
285 | followButton
286 | )
287 | ...
288 |
289 | }
290 | }
291 | ```
292 |
293 | - Returns: Itself to enable nested layouts.
294 | */
295 | @available(*, deprecated, renamed: "subviews")
296 | @discardableResult
297 | override func sv(_ subViews: [UIView]) -> UIView {
298 | contentView.subviews(subViews)
299 | }
300 |
301 | @discardableResult
302 | override func subviews(_ subViews: [UIView]) -> UIView {
303 | contentView.subviews(subViews)
304 | }
305 | }
306 |
307 |
308 | public extension UIStackView {
309 |
310 | @discardableResult
311 | func arrangedSubviews(@SubviewsBuilder content: () -> [UIView]) -> UIView {
312 | arrangedSubviews(content())
313 | }
314 |
315 | @discardableResult
316 | func arrangedSubviews(@SubviewsBuilder content: () -> UIView) -> UIView {
317 | arrangedSubviews([content()])
318 | }
319 |
320 | @discardableResult
321 | private func arrangedSubviews(_ subViews: UIView...) -> UIView {
322 | arrangedSubviews(subViews)
323 | }
324 |
325 | @discardableResult
326 | func arrangedSubviews(_ subViews: [UIView]) -> UIView {
327 | subViews.forEach { addArrangedSubview($0) }
328 | return self
329 | }
330 | }
331 |
332 | #endif
333 |
--------------------------------------------------------------------------------
/Sources/Stevia/Stevia+LayoutAnchors.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Stevia+LayoutAnchors.swift
3 | // Stevia
4 | //
5 | // Created by Sacha DSO on 09/10/2017.
6 | // Copyright © 2017 Sacha Durand Saint Omer. All rights reserved.
7 | //
8 |
9 | #if canImport(UIKit)
10 | import UIKit
11 |
12 | @available(iOS 9.0, *)
13 | public struct SteviaLayoutYAxisAnchor {
14 | let anchor: NSLayoutYAxisAnchor
15 | let constant: Double
16 |
17 | init(anchor: NSLayoutYAxisAnchor, constant: Double = 0) {
18 | self.anchor = anchor
19 | self.constant = constant
20 | }
21 | }
22 |
23 | @available(iOS 9.0, *)
24 | public struct SteviaLayoutXAxisAnchor {
25 | let anchor: NSLayoutXAxisAnchor
26 | let constant: Double
27 |
28 | init(anchor: NSLayoutXAxisAnchor, constant: Double = 0) {
29 | self.anchor = anchor
30 | self.constant = constant
31 | }
32 | }
33 |
34 | @available(iOS 9.0, *)
35 | public extension UILayoutGuide {
36 |
37 | var Top: SteviaLayoutYAxisAnchor {
38 | return SteviaLayoutYAxisAnchor(anchor: topAnchor)
39 | }
40 |
41 | var Bottom: SteviaLayoutYAxisAnchor {
42 | return SteviaLayoutYAxisAnchor(anchor: bottomAnchor)
43 | }
44 |
45 | var Left: SteviaLayoutXAxisAnchor {
46 | return SteviaLayoutXAxisAnchor(anchor: leftAnchor)
47 | }
48 |
49 | var Right: SteviaLayoutXAxisAnchor {
50 | return SteviaLayoutXAxisAnchor(anchor: rightAnchor)
51 | }
52 |
53 | var Leading: SteviaLayoutXAxisAnchor {
54 | return SteviaLayoutXAxisAnchor(anchor: leadingAnchor)
55 | }
56 |
57 | var Trailing: SteviaLayoutXAxisAnchor {
58 | return SteviaLayoutXAxisAnchor(anchor: trailingAnchor)
59 | }
60 |
61 | var CenterX: SteviaLayoutXAxisAnchor {
62 | return SteviaLayoutXAxisAnchor(anchor: centerXAnchor)
63 | }
64 |
65 | var CenterY: SteviaLayoutYAxisAnchor {
66 | return SteviaLayoutYAxisAnchor(anchor: centerYAnchor)
67 | }
68 | }
69 |
70 | @available(iOS 9.0, *)
71 | @discardableResult
72 | @MainActor public func == (left: SteviaAttribute, right: SteviaLayoutYAxisAnchor) -> NSLayoutConstraint {
73 |
74 | var constraint = NSLayoutConstraint()
75 |
76 | if left.attribute == .top {
77 | constraint = left.view.topAnchor.constraint(equalTo: right.anchor, constant: CGFloat(right.constant))
78 | }
79 |
80 | if left.attribute == .bottom {
81 | constraint = left.view.bottomAnchor.constraint(equalTo: right.anchor, constant: CGFloat(right.constant))
82 | }
83 |
84 | if left.attribute == .centerY {
85 | constraint = left.view.centerYAnchor.constraint(equalTo: right.anchor, constant: CGFloat(right.constant))
86 | }
87 |
88 | constraint.isActive = true
89 | return constraint
90 | }
91 |
92 | @available(iOS 9.0, *)
93 | @discardableResult
94 | @MainActor public func == (left: SteviaAttribute, right: SteviaLayoutXAxisAnchor) -> NSLayoutConstraint {
95 |
96 | var constraint = NSLayoutConstraint()
97 |
98 | if left.attribute == .left {
99 | constraint = left.view.leftAnchor.constraint(equalTo: right.anchor, constant: CGFloat(right.constant))
100 | }
101 |
102 | if left.attribute == .right {
103 | constraint = left.view.rightAnchor.constraint(equalTo: right.anchor, constant: CGFloat(right.constant))
104 | }
105 |
106 | if left.attribute == .leading {
107 | constraint = left.view.leadingAnchor.constraint(equalTo: right.anchor, constant: CGFloat(right.constant))
108 | }
109 |
110 | if left.attribute == .trailing {
111 | constraint = left.view.trailingAnchor.constraint(equalTo: right.anchor, constant: CGFloat(right.constant))
112 | }
113 |
114 | if left.attribute == .centerX {
115 | constraint = left.view.centerXAnchor.constraint(equalTo: right.anchor, constant: CGFloat(right.constant))
116 | }
117 |
118 | constraint.isActive = true
119 | return constraint
120 | }
121 |
122 | // SteviaLayoutYAxisAnchor
123 |
124 | @available(iOS 9.0, *)
125 | @discardableResult
126 | public func + (left: SteviaLayoutYAxisAnchor, right: Double) -> SteviaLayoutYAxisAnchor {
127 | return SteviaLayoutYAxisAnchor(anchor: left.anchor, constant: right)
128 | }
129 |
130 | @available(iOS 9.0, *)
131 | @discardableResult
132 | public func - (left: SteviaLayoutYAxisAnchor, right: Double) -> SteviaLayoutYAxisAnchor {
133 | return SteviaLayoutYAxisAnchor(anchor: left.anchor, constant: -right)
134 | }
135 |
136 | @available(iOS 9.0, *)
137 | @discardableResult
138 | public func + (left: SteviaLayoutXAxisAnchor, right: Double) -> SteviaLayoutXAxisAnchor {
139 | return SteviaLayoutXAxisAnchor(anchor: left.anchor, constant: right)
140 | }
141 |
142 | @available(iOS 9.0, *)
143 | @discardableResult
144 | public func - (left: SteviaLayoutXAxisAnchor, right: Double) -> SteviaLayoutXAxisAnchor {
145 | return SteviaLayoutXAxisAnchor(anchor: left.anchor, constant: -right)
146 | }
147 |
148 | @available(iOS 9.0, *)
149 | @discardableResult
150 | public func + (left: SteviaLayoutYAxisAnchor, right: CGFloat) -> SteviaLayoutYAxisAnchor {
151 | return SteviaLayoutYAxisAnchor(anchor: left.anchor, constant: Double(right))
152 | }
153 |
154 | @available(iOS 9.0, *)
155 | @discardableResult
156 | public func - (left: SteviaLayoutYAxisAnchor, right: CGFloat) -> SteviaLayoutYAxisAnchor {
157 | return SteviaLayoutYAxisAnchor(anchor: left.anchor, constant: Double(-right))
158 | }
159 |
160 | @available(iOS 9.0, *)
161 | @discardableResult
162 | public func + (left: SteviaLayoutXAxisAnchor, right: CGFloat) -> SteviaLayoutXAxisAnchor {
163 | return SteviaLayoutXAxisAnchor(anchor: left.anchor, constant: Double(right))
164 | }
165 |
166 | @available(iOS 9.0, *)
167 | @discardableResult
168 | public func - (left: SteviaLayoutXAxisAnchor, right: CGFloat) -> SteviaLayoutXAxisAnchor {
169 | return SteviaLayoutXAxisAnchor(anchor: left.anchor, constant: Double(-right))
170 | }
171 |
172 | @available(iOS 9.0, *)
173 | @discardableResult
174 | public func + (left: SteviaLayoutYAxisAnchor, right: Int) -> SteviaLayoutYAxisAnchor {
175 | return SteviaLayoutYAxisAnchor(anchor: left.anchor, constant: Double(right))
176 | }
177 |
178 | @available(iOS 9.0, *)
179 | @discardableResult
180 | public func - (left: SteviaLayoutYAxisAnchor, right: Int) -> SteviaLayoutYAxisAnchor {
181 | return SteviaLayoutYAxisAnchor(anchor: left.anchor, constant: Double(-right))
182 | }
183 |
184 | @available(iOS 9.0, *)
185 | @discardableResult
186 | public func + (left: SteviaLayoutXAxisAnchor, right: Int) -> SteviaLayoutXAxisAnchor {
187 | return SteviaLayoutXAxisAnchor(anchor: left.anchor, constant: Double(right))
188 | }
189 |
190 | @available(iOS 9.0, *)
191 | @discardableResult
192 | public func - (left: SteviaLayoutXAxisAnchor, right: Int) -> SteviaLayoutXAxisAnchor {
193 | return SteviaLayoutXAxisAnchor(anchor: left.anchor, constant: Double(-right))
194 | }
195 |
196 | // UILayoutSupport
197 |
198 | @available(iOS 9.0, *)
199 | public extension UILayoutSupport {
200 |
201 | var Top: SteviaLayoutYAxisAnchor {
202 | return SteviaLayoutYAxisAnchor(anchor: topAnchor)
203 | }
204 |
205 | var Bottom: SteviaLayoutYAxisAnchor {
206 | return SteviaLayoutYAxisAnchor(anchor: bottomAnchor)
207 | }
208 | }
209 | #endif
210 |
--------------------------------------------------------------------------------
/Sources/Stevia/Stevia+Notifications.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Stevia+Notifications.swift
3 | // LoginStevia
4 | //
5 | // Created by Sacha Durand Saint Omer on 12/10/15.
6 | // Copyright © 2015 Sacha Durand Saint Omer. All rights reserved.
7 | //
8 |
9 | #if canImport(UIKit)
10 | import UIKit
11 |
12 | public extension NSObject {
13 |
14 | func on(_ event: String, _ callback:@escaping @Sendable() -> Void) {
15 | _ = NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: event),
16 | object: nil,
17 | queue: nil) { _ in
18 | callback()
19 | }
20 | }
21 | }
22 | #endif
23 |
--------------------------------------------------------------------------------
/Sources/Stevia/Stevia+Operators.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Stevia+Operators.swift
3 | // Stevia
4 | //
5 | // Created by Sacha Durand Saint Omer on 10/02/16.
6 | // Copyright © 2016 Sacha Durand Saint Omer. All rights reserved.
7 | //
8 |
9 | #if canImport(UIKit)
10 | import UIKit
11 |
12 | prefix operator |
13 | @discardableResult
14 | @MainActor public prefix func | (p: UIView) -> UIView {
15 | p.leading(0)
16 | }
17 |
18 | postfix operator |
19 | @discardableResult
20 | @MainActor public postfix func | (p: UIView) -> UIView {
21 | p.trailing(0)
22 | }
23 |
24 | infix operator ~ : HeightPrecedence
25 |
26 | precedencegroup HeightPrecedence {
27 | lowerThan: AdditionPrecedence
28 | }
29 |
30 | @discardableResult
31 | @MainActor public func ~ (left: UIView, right: Double) -> UIView {
32 | left.height(right)
33 | }
34 |
35 | @discardableResult
36 | @MainActor public func ~ (left: UIView, right: CGFloat) -> UIView {
37 | left ~ Double(right)
38 | }
39 |
40 | @discardableResult
41 | @MainActor public func ~ (left: UIView, right: Int) -> UIView {
42 | left ~ Double(right)
43 | }
44 |
45 | @discardableResult
46 | @MainActor public func ~ (left: UIView, right: SteviaPercentage) -> UIView {
47 | left.height(right)
48 | }
49 |
50 | @discardableResult
51 | @MainActor public func ~ (left: UIView, right: SteviaFlexibleMargin) -> UIView {
52 | left.height(right)
53 | }
54 |
55 | @discardableResult
56 | @MainActor public func ~ (left: [UIView], right: Double) -> [UIView] {
57 | for l in left { l.height(right) }
58 | return left
59 | }
60 |
61 | @discardableResult
62 | @MainActor public func ~ (left: [UIView], right: CGFloat) -> [UIView] {
63 | left ~ Double(right)
64 | }
65 |
66 | @discardableResult
67 | @MainActor public func ~ (left: [UIView], right: Int) -> [UIView] {
68 | left ~ Double(right)
69 | }
70 |
71 | @discardableResult
72 | @MainActor public func ~ (left: [UIView], right: SteviaFlexibleMargin) -> [UIView] {
73 | for l in left { l.height(right) }
74 | return left
75 | }
76 |
77 | prefix operator |-
78 | @discardableResult
79 | public prefix func |- (p: Double) -> SideConstraint {
80 | var s = SideConstraint()
81 | s.constant = p
82 | return s
83 | }
84 |
85 | @discardableResult
86 | public prefix func |- (p: CGFloat) -> SideConstraint {
87 | |-Double(p)
88 | }
89 |
90 | @discardableResult
91 | public prefix func |- (p: Int) -> SideConstraint {
92 | |-Double(p)
93 | }
94 |
95 | @discardableResult
96 | @MainActor public prefix func |- (v: UIView) -> UIView {
97 | v.leading(8)
98 | }
99 |
100 | postfix operator -|
101 | @discardableResult
102 | public postfix func -| (p: Double) -> SideConstraint {
103 | var s = SideConstraint()
104 | s.constant = p
105 | return s
106 | }
107 |
108 | @discardableResult
109 | public postfix func -| (p: CGFloat) -> SideConstraint {
110 | Double(p)-|
111 | }
112 |
113 | @discardableResult
114 | public postfix func -| (p: Int) -> SideConstraint {
115 | Double(p)-|
116 | }
117 |
118 | @discardableResult
119 | @MainActor public postfix func -| (v: UIView) -> UIView {
120 | v.trailing(8)
121 | }
122 |
123 | public struct SideConstraint {
124 | var constant: Double!
125 | }
126 |
127 | public struct PartialConstraint {
128 | var view1: UIView!
129 | var constant: Double!
130 | var views: [UIView]?
131 | }
132 |
133 | @discardableResult
134 | public func - (left: UIView, right: Double) -> PartialConstraint {
135 | var p = PartialConstraint()
136 | p.view1 = left
137 | p.constant = right
138 | return p
139 | }
140 |
141 | @discardableResult
142 | public func - (left: UIView, right: CGFloat) -> PartialConstraint {
143 | left-Double(right)
144 | }
145 |
146 | @discardableResult
147 | public func - (left: UIView, right: Int) -> PartialConstraint {
148 | left-Double(right)
149 | }
150 |
151 | // Side Constraints
152 |
153 | @discardableResult
154 | @MainActor public func - (left: SideConstraint, right: UIView) -> UIView {
155 | if let spv = right.superview {
156 | let c = constraint(item: right, attribute: .leading,
157 | toItem: spv, attribute: .leading,
158 | constant: left.constant)
159 | spv.addConstraint(c)
160 | }
161 | return right
162 | }
163 |
164 | @discardableResult
165 | @MainActor public func - (left: [UIView], right: SideConstraint) -> [UIView] {
166 | let lastView = left[left.count-1]
167 | if let spv = lastView.superview {
168 | let c = constraint(item: lastView, attribute: .trailing,
169 | toItem: spv, attribute: .trailing,
170 | constant: -right.constant)
171 | spv.addConstraint(c)
172 | }
173 | return left
174 | }
175 |
176 | @discardableResult
177 | @MainActor public func - (left: UIView, right: SideConstraint) -> UIView {
178 | if let spv = left.superview {
179 | let c = constraint(item: left, attribute: .trailing,
180 | toItem: spv, attribute: .trailing,
181 | constant: -right.constant)
182 | spv.addConstraint(c)
183 | }
184 | return left
185 | }
186 |
187 | @discardableResult
188 | @MainActor public func - (left: PartialConstraint, right: UIView) -> [UIView] {
189 | if let views = left.views {
190 | if let spv = right.superview {
191 | let lastView = views[views.count-1]
192 | let c = constraint(item: lastView, attribute: .trailing,
193 | toItem: right, attribute: .leading,
194 | constant: -left.constant)
195 | spv.addConstraint(c)
196 | }
197 |
198 | return views + [right]
199 | } else {
200 | // were at the end?? nooope?/?
201 | if let spv = right.superview {
202 | let c = constraint(item: left.view1, attribute: .trailing,
203 | toItem: right, attribute: .leading,
204 | constant: -left.constant)
205 | spv.addConstraint(c)
206 | }
207 | return [left.view1, right]
208 | }
209 | }
210 |
211 | @discardableResult
212 | @MainActor public func - (left: UIView, right: UIView) -> [UIView] {
213 | if let spv = left.superview {
214 | let c = constraint(item: right, attribute: .leading,
215 | toItem: left, attribute: .trailing,
216 | constant: 8)
217 | spv.addConstraint(c)
218 | }
219 | return [left, right]
220 | }
221 |
222 | @discardableResult
223 | public func - (left: [UIView], right: Double) -> PartialConstraint {
224 | var p = PartialConstraint()
225 | p.constant = right
226 | p.views = left
227 | return p
228 | }
229 |
230 | @discardableResult
231 | public func - (left: [UIView], right: CGFloat) -> PartialConstraint {
232 | left-Double(right)
233 | }
234 |
235 | @discardableResult
236 | public func - (left: [UIView], right: Int) -> PartialConstraint {
237 | left-Double(right)
238 | }
239 |
240 |
241 | @discardableResult
242 | @MainActor public func - (left: [UIView], right: UIView) -> [UIView] {
243 | let lastView = left[left.count-1]
244 | if let spv = lastView.superview {
245 | let c = constraint(item: lastView, attribute: .trailing,
246 | toItem: right, attribute: .leading,
247 | constant: -8)
248 | spv.addConstraint(c)
249 | }
250 | return left + [right]
251 | }
252 |
253 | //// Test space in Horizointal layout ""
254 | public struct Space {
255 | var previousViews: [UIView]!
256 | }
257 |
258 | @discardableResult
259 | public func - (left: UIView, right: String) -> Space {
260 | Space(previousViews: [left])
261 | }
262 |
263 | @discardableResult
264 | public func - (left: [UIView], right: String) -> Space {
265 | Space(previousViews: left)
266 | }
267 |
268 | @discardableResult
269 | public func - (left: Space, right: UIView) -> [UIView] {
270 | var va = left.previousViews
271 | va?.append(right)
272 | return va!
273 | }
274 | #endif
275 |
--------------------------------------------------------------------------------
/Sources/Stevia/Stevia+Percentage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Stevia+Percentage.swift
3 | // Stevia
4 | //
5 | // Created by Sacha Durand Saint Omer on 21/01/2017.
6 | // Copyright © 2017 Sacha Durand Saint Omer. All rights reserved.
7 | //
8 |
9 | #if canImport(UIKit)
10 | import UIKit
11 |
12 | public struct SteviaPercentage {
13 | let value: Double
14 | }
15 |
16 | postfix operator %
17 | public postfix func % (v: Double) -> SteviaPercentage {
18 | SteviaPercentage(value: Double(v))
19 | }
20 |
21 | public postfix func % (v: CGFloat) -> SteviaPercentage {
22 | Double(v)%
23 | }
24 |
25 | public postfix func % (v: Int) -> SteviaPercentage {
26 | Double(v)%
27 | }
28 |
29 | public extension UIView {
30 |
31 | /**
32 | Adds an Autolayout constraint to provide the aspect ratio for the view.
33 |
34 | ```
35 | image.aspectRatio(3/2)
36 | image.aspectRatio(150%)
37 |
38 | // is equivalent to
39 |
40 | image.Width == image.Height * 1.5
41 | image.Width == 150 % image.Height
42 | ```
43 |
44 | - Returns: Itself, enabling chaining,
45 |
46 | */
47 | @discardableResult
48 | func aspectRatio(_ p: SteviaPercentage) -> Self {
49 | Width == p.value % Height
50 | return self
51 | }
52 |
53 | /**
54 | Adds an Autolayout constraint for sizing the view.
55 |
56 | ```
57 | image.size(100)
58 | image.size(100%)
59 |
60 | // is equivalent to
61 |
62 | image.width(100).height(100)
63 | ```
64 |
65 | - Returns: Itself, enabling chaining,
66 |
67 | */
68 | @discardableResult
69 | func size(_ p: SteviaPercentage) -> Self {
70 | width(p)
71 | height(p)
72 | return self
73 | }
74 |
75 | /**
76 | Adds an Autolayout constraint for setting the view's width.
77 |
78 | ```
79 | image.width(100)
80 | image.width(<=100)
81 | image.width(>=100)
82 | image.width(100%)
83 | ```
84 |
85 | - Returns: Itself, enabling chaining,
86 |
87 | */
88 | @discardableResult
89 | func width(_ p: SteviaPercentage) -> Self {
90 | if let spv = superview {
91 | Width == p.value % spv.Width
92 | }
93 | return self
94 | }
95 |
96 | /**
97 | Adds an Autolayout constraint for setting the view's height.
98 |
99 | ```
100 | image.height(100)
101 |
102 | // is equivalent to
103 |
104 | image ~ 100
105 |
106 | // Flexible margins
107 | image.height(<=100)
108 | image.height(>=100)
109 | image.height(100%)
110 | ```
111 |
112 | - Returns: Itself, enabling chaining,
113 |
114 | */
115 | @discardableResult
116 | func height(_ p: SteviaPercentage) -> Self {
117 | if let spv = superview {
118 | Height == p.value % spv.Height
119 | }
120 | return self
121 | }
122 |
123 | /** Sets the top margin for a view.
124 |
125 | Example Usage :
126 |
127 | label.top(20)
128 | label.top(<=20)
129 | label.top(>=20)
130 | label.top(20%)
131 |
132 | - Returns: Itself for chaining purposes
133 | */
134 | @discardableResult
135 | func top(_ p: SteviaPercentage) -> Self {
136 | if let spv = superview {
137 | Top == p.value % spv.Bottom
138 | }
139 | return self
140 | }
141 |
142 | /** Sets the left margin for a view.
143 |
144 | Example Usage :
145 |
146 | label.left(20)
147 | label.left(<=20)
148 | label.left(>=20)
149 | label.left(20%)
150 |
151 | - Returns: Itself for chaining purposes
152 | */
153 | @discardableResult
154 | func left(_ p: SteviaPercentage) -> Self {
155 | if let spv = superview {
156 | Left == p.value % spv.Right
157 | }
158 | return self
159 | }
160 |
161 | /** Sets the right margin for a view.
162 |
163 | Example Usage :
164 |
165 | label.right(20)
166 | label.right(<=20)
167 | label.right(>=20)
168 | label.right(20%)
169 |
170 | - Returns: Itself for chaining purposes
171 | */
172 | @discardableResult
173 | func right(_ p: SteviaPercentage) -> Self {
174 | if let spv = superview {
175 | if p.value == 100 {
176 | Right == spv.Left
177 | } else {
178 | Right == (100 - p.value) % spv.Right
179 | }
180 | }
181 | return self
182 | }
183 |
184 | /** Sets the bottom margin for a view.
185 |
186 | Example Usage :
187 |
188 | label.bottom(20)
189 | label.bottom(<=20)
190 | label.bottom(>=20)
191 | label.bottom(20%)
192 |
193 | - Returns: Itself for chaining purposes
194 | */
195 | @discardableResult
196 | func bottom(_ p: SteviaPercentage) -> Self {
197 | if let spv = superview {
198 | if p.value == 100 {
199 | Bottom == spv.Top
200 | } else {
201 | Bottom == (100 - p.value) % spv.Bottom
202 | }
203 | }
204 | return self
205 | }
206 |
207 | /** Sets the leading margin for a view.
208 |
209 | Example Usage :
210 |
211 | label.leading(20)
212 | label.leading(<=20)
213 | label.leading(>=20)
214 | label.leading(20%)
215 |
216 | - Returns: itself for chaining purposes
217 | */
218 | @discardableResult
219 | func leading(_ p: SteviaPercentage) -> UIView {
220 | // Percent based (multipliers) with leading or trailing attributes
221 | // are not available so we introduce an intermediary layout guide.
222 | // |-[layoutGuide(== x % width)-[view]
223 | // RTL version: [view]-[layoutGuide(== x % width)-|
224 | if let spv = superview {
225 | let lg = UILayoutGuide()
226 | spv.addLayoutGuide(lg)
227 | let constraints = [
228 | lg.widthAnchor.constraint(equalTo: spv.widthAnchor, multiplier: CGFloat(p.value / 100.0)),
229 | lg.leadingAnchor.constraint(equalTo: spv.leadingAnchor),
230 | leadingAnchor.constraint(equalTo: lg.trailingAnchor)
231 | ]
232 | constraints.forEach { $0.isActive = true }
233 | }
234 | return self
235 | }
236 |
237 | /** Sets the trailing margin for a view.
238 |
239 | Example Usage :
240 |
241 | label.trailing(20)
242 | label.trailing(<=20)
243 | label.trailing(>=20)
244 | label.trailing(20%)
245 |
246 | - Returns: itself for chaining purposes
247 | */
248 | @discardableResult
249 | func trailing(_ p: SteviaPercentage) -> UIView {
250 |
251 | // Percent based (multipliers) with leading or trailing attributes
252 | // are not available so we introduce an intermediary layout guide.
253 | // |-[layoutGuide(== x % width)-[view]
254 | // RTL version: [view]-[layoutGuide(== x % width)-|
255 | if let spv = superview {
256 | let lg = UILayoutGuide()
257 | spv.addLayoutGuide(lg)
258 | let constraints = [
259 | lg.widthAnchor.constraint(equalTo: spv.widthAnchor, multiplier: CGFloat(p.value / Double(100))),
260 | lg.trailingAnchor.constraint(equalTo: spv.trailingAnchor),
261 | trailingAnchor.constraint(equalTo: lg.leadingAnchor)
262 | ]
263 | constraints.forEach { $0.isActive = true }
264 | }
265 | return self
266 | }
267 | }
268 | #endif
269 |
--------------------------------------------------------------------------------
/Sources/Stevia/Stevia+Position.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Stevia+Position.swift
3 | // Stevia
4 | //
5 | // Created by Sacha Durand Saint Omer on 10/02/16.
6 | // Copyright © 2016 Sacha Durand Saint Omer. All rights reserved.
7 | //
8 |
9 | #if canImport(UIKit)
10 | import UIKit
11 |
12 | public extension UIView {
13 |
14 | /** Sets the left margin for a view.
15 |
16 | Example Usage :
17 |
18 | label.left(20)
19 | label.left(<=20)
20 | label.left(>=20)
21 | label.left(20%)
22 |
23 | - Returns: Itself for chaining purposes
24 | */
25 | @discardableResult
26 | func left(_ points: Double) -> Self {
27 | position(.left, points: points)
28 | }
29 |
30 | /** Sets the left margin for a view.
31 |
32 | Example Usage :
33 |
34 | label.left(20)
35 | label.left(<=20)
36 | label.left(>=20)
37 | label.left(20%)
38 |
39 | - Returns: Itself for chaining purposes
40 | */
41 | @discardableResult
42 | func left(_ points: CGFloat) -> Self {
43 | left(Double(points))
44 | }
45 |
46 | /** Sets the left margin for a view.
47 |
48 | Example Usage :
49 |
50 | label.left(20)
51 | label.left(<=20)
52 | label.left(>=20)
53 | label.left(20%)
54 |
55 | - Returns: Itself for chaining purposes
56 | */
57 | @discardableResult
58 | func left(_ points: Int) -> Self {
59 | left(Double(points))
60 | }
61 |
62 | /** Sets the right margin for a view.
63 |
64 | Example Usage :
65 |
66 | label.right(20)
67 | label.right(<=20)
68 | label.right(>=20)
69 | label.right(20%)
70 |
71 | - Returns: Itself for chaining purposes
72 | */
73 | @discardableResult
74 | func right(_ points: Double) -> Self {
75 | position(.right, points: -points)
76 | }
77 |
78 | /** Sets the right margin for a view.
79 |
80 | Example Usage :
81 |
82 | label.right(20)
83 | label.right(<=20)
84 | label.right(>=20)
85 | label.right(20%)
86 |
87 | - Returns: Itself for chaining purposes
88 | */
89 | @discardableResult
90 | func right(_ points: CGFloat) -> Self {
91 | right(Double(points))
92 | }
93 |
94 | /** Sets the right margin for a view.
95 |
96 | Example Usage :
97 |
98 | label.right(20)
99 | label.right(<=20)
100 | label.right(>=20)
101 | label.right(20%)
102 |
103 | - Returns: Itself for chaining purposes
104 | */
105 | @discardableResult
106 | func right(_ points: Int) -> Self {
107 | right(Double(points))
108 | }
109 |
110 | /** Sets the top margin for a view.
111 |
112 | Example Usage :
113 |
114 | label.top(20)
115 | label.top(<=20)
116 | label.top(>=20)
117 | label.top(20%)
118 |
119 | - Returns: Itself for chaining purposes
120 | */
121 | @discardableResult
122 | func top(_ points: Double) -> Self {
123 | position(.top, points: points)
124 | }
125 |
126 | /** Sets the top margin for a view.
127 |
128 | Example Usage :
129 |
130 | label.top(20)
131 | label.top(<=20)
132 | label.top(>=20)
133 | label.top(20%)
134 |
135 | - Returns: Itself for chaining purposes
136 | */
137 | @discardableResult
138 | func top(_ points: CGFloat) -> Self {
139 | top(Double(points))
140 | }
141 |
142 | /** Sets the top margin for a view.
143 |
144 | Example Usage :
145 |
146 | label.top(20)
147 | label.top(<=20)
148 | label.top(>=20)
149 | label.top(20%)
150 |
151 | - Returns: Itself for chaining purposes
152 | */
153 | @discardableResult
154 | func top(_ points: Int) -> Self {
155 | top(Double(points))
156 | }
157 |
158 | /** Sets the bottom margin for a view.
159 |
160 | Example Usage :
161 |
162 | label.bottom(20)
163 | label.bottom(<=20)
164 | label.bottom(>=20)
165 | label.bottom(20%)
166 |
167 | - Returns: Itself for chaining purposes
168 | */
169 | @discardableResult
170 | func bottom(_ points: Double) -> Self {
171 | position(.bottom, points: -points)
172 | }
173 |
174 | /** Sets the bottom margin for a view.
175 |
176 | Example Usage :
177 |
178 | label.bottom(20)
179 | label.bottom(<=20)
180 | label.bottom(>=20)
181 | label.bottom(20%)
182 |
183 | - Returns: Itself for chaining purposes
184 | */
185 | @discardableResult
186 | func bottom(_ points: CGFloat) -> Self {
187 | bottom(Double(points))
188 | }
189 |
190 | /** Sets the bottom margin for a view.
191 |
192 | Example Usage :
193 |
194 | label.bottom(20)
195 | label.bottom(<=20)
196 | label.bottom(>=20)
197 | label.bottom(20%)
198 |
199 | - Returns: Itself for chaining purposes
200 | */
201 | @discardableResult
202 | func bottom(_ points: Int) -> Self {
203 | bottom(Double(points))
204 | }
205 |
206 | /** Sets the left margin for a view.
207 |
208 | Example Usage :
209 |
210 | label.left(20)
211 | label.left(<=20)
212 | label.left(>=20)
213 | label.left(20%)
214 |
215 | - Returns: Itself for chaining purposes
216 | */
217 | @discardableResult
218 | func left(_ fm: SteviaFlexibleMargin) -> Self {
219 | position(.left, relatedBy: fm.relation, points: fm.points)
220 | }
221 |
222 | /** Sets the right margin for a view.
223 |
224 | Example Usage :
225 |
226 | label.right(20)
227 | label.right(<=20)
228 | label.right(>=20)
229 | label.right(20%)
230 |
231 | - Returns: Itself for chaining purposes
232 | */
233 | @discardableResult
234 | func right(_ fm: SteviaFlexibleMargin) -> Self {
235 | // For right this should be inverted.
236 | var n = SteviaFlexibleMargin()
237 | n.points = -fm.points
238 | if fm.relation == .greaterThanOrEqual {
239 | n.relation = .lessThanOrEqual
240 | }
241 | if fm.relation == .lessThanOrEqual {
242 | n.relation = .greaterThanOrEqual
243 | }
244 | return position(.right, relatedBy: n.relation, points: n.points)
245 | }
246 |
247 | /** Sets the top margin for a view.
248 |
249 | Example Usage :
250 |
251 | label.top(20)
252 | label.top(<=20)
253 | label.top(>=20)
254 | label.top(20%)
255 |
256 | - Returns: Itself for chaining purposes
257 | */
258 | @discardableResult
259 | func top(_ fm: SteviaFlexibleMargin) -> Self {
260 | position(.top, relatedBy: fm.relation, points: fm.points)
261 | }
262 |
263 | /** Sets the bottom margin for a view.
264 |
265 | Example Usage :
266 |
267 | label.bottom(20)
268 | label.bottom(<=20)
269 | label.bottom(>=20)
270 | label.bottom(20%)
271 |
272 | - Returns: Itself for chaining purposes
273 | */
274 | @discardableResult
275 | func bottom(_ fm: SteviaFlexibleMargin) -> Self {
276 | // For bottom this should be inverted.
277 | var n = SteviaFlexibleMargin()
278 | n.points = -fm.points
279 | if fm.relation == .greaterThanOrEqual {
280 | n.relation = .lessThanOrEqual
281 | }
282 | if fm.relation == .lessThanOrEqual {
283 | n.relation = .greaterThanOrEqual
284 | }
285 | return position(.bottom, relatedBy: n.relation, points: n.points)
286 | }
287 |
288 | /** Sets the leading margin for a view.
289 |
290 | Example Usage :
291 |
292 | label.leading(20)
293 | label.leading(<=20)
294 | label.leading(>=20)
295 | label.leading(20%)
296 |
297 | - Returns: itself for chaining purposes
298 | */
299 |
300 | @discardableResult
301 | func leading(_ points: Double) -> UIView {
302 | position(.leading, points: points)
303 | }
304 |
305 | /** Sets the leading margin for a view.
306 |
307 | Example Usage :
308 |
309 | label.leading(20)
310 | label.leading(<=20)
311 | label.leading(>=20)
312 | label.leading(20%)
313 |
314 | - Returns: itself for chaining purposes
315 | */
316 | @discardableResult
317 | func leading(_ points: CGFloat) -> UIView {
318 | leading(Double(points))
319 | }
320 |
321 | /** Sets the leading margin for a view.
322 |
323 | Example Usage :
324 |
325 | label.leading(20)
326 | label.leading(<=20)
327 | label.leading(>=20)
328 | label.leading(20%)
329 |
330 | - Returns: itself for chaining purposes
331 | */
332 | @discardableResult
333 | func leading(_ points: Int) -> UIView {
334 | leading(Double(points))
335 | }
336 |
337 | @discardableResult
338 | func leading(_ fm: SteviaFlexibleMargin) -> UIView {
339 | position(.leading, relatedBy: fm.relation, points: fm.points)
340 | }
341 |
342 | /** Sets the trailing margin for a view.
343 |
344 | Example Usage :
345 |
346 | label.trailing(20)
347 | label.trailing(<=20)
348 | label.trailing(>=20)
349 | label.trailing(20%)
350 |
351 | - Returns: itself for chaining purposes
352 | */
353 | @discardableResult
354 | func trailing(_ points: Double) -> UIView {
355 | position(.trailing, points: -points)
356 | }
357 |
358 | /** Sets the trailing margin for a view.
359 |
360 | Example Usage :
361 |
362 | label.trailing(20)
363 | label.trailing(<=20)
364 | label.trailing(>=20)
365 | label.trailing(20%)
366 |
367 | - Returns: itself for chaining purposes
368 | */
369 | @discardableResult
370 | func trailing(_ points: CGFloat) -> UIView {
371 | trailing(Double(points))
372 | }
373 |
374 | /** Sets the trailing margin for a view.
375 |
376 | Example Usage :
377 |
378 | label.trailing(20)
379 | label.trailing(<=20)
380 | label.trailing(>=20)
381 | label.trailing(20%)
382 |
383 | - Returns: itself for chaining purposes
384 | */
385 | @discardableResult
386 | func trailing(_ points: Int) -> UIView {
387 | trailing(Double(points))
388 | }
389 |
390 | @discardableResult
391 | func trailing(_ fm: SteviaFlexibleMargin) -> UIView {
392 | var invertedRelation = fm.relation
393 | if invertedRelation == .lessThanOrEqual {
394 | invertedRelation = .greaterThanOrEqual
395 | } else if invertedRelation == .greaterThanOrEqual {
396 | invertedRelation = .lessThanOrEqual
397 | }
398 | return position(.trailing, relatedBy: invertedRelation!, points: -fm.points)
399 | }
400 |
401 | fileprivate func position(_ position: NSLayoutConstraint.Attribute,
402 | relatedBy: NSLayoutConstraint.Relation = .equal,
403 | points: Double) -> Self {
404 | if let spv = superview {
405 | let c = constraint(item: self, attribute: position,
406 | relatedBy: relatedBy,
407 | toItem: spv,
408 | constant: points)
409 | spv.addConstraint(c)
410 | }
411 | return self
412 | }
413 | }
414 | #endif
415 |
--------------------------------------------------------------------------------
/Sources/Stevia/Stevia+Size.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Stevia+Size.swift
3 | // Stevia
4 | //
5 | // Created by Sacha Durand Saint Omer on 10/02/16.
6 | // Copyright © 2016 Sacha Durand Saint Omer. All rights reserved.
7 | //
8 |
9 | #if canImport(UIKit)
10 | import UIKit
11 |
12 | public extension UIView {
13 |
14 | /**
15 | Adds an Autolayout constraint to provide the aspect ratio for the view.
16 |
17 | ```
18 | image.aspectRatio(3.0/2.0)
19 | image.aspectRatio(150%)
20 |
21 | // is equivalent to
22 |
23 | image.Width == image.Height * 1.5
24 | image.Width == 150 % image.Height
25 | ```
26 |
27 | - Returns: Itself, enabling chaining,
28 |
29 | */
30 | @discardableResult
31 | func aspectRatio(_ ratio: Double = 1) -> Self {
32 | Width == Height * ratio
33 | return self
34 | }
35 |
36 | /**
37 | Adds an Autolayout constraint to provide the aspect ratio for the view.
38 |
39 | ```
40 | image.aspectRatio(3.0/2.0)
41 | image.aspectRatio(150%)
42 |
43 | // is equivalent to
44 |
45 | image.Width == image.Height * 1.5
46 | image.Width == 150 % image.Height
47 | ```
48 |
49 | - Returns: Itself, enabling chaining,
50 |
51 | */
52 | @discardableResult
53 | func aspectRatio(_ ratio: CGFloat = 1) -> Self {
54 | aspectRatio(Double(ratio))
55 | return self
56 | }
57 |
58 | /**
59 | Adds an Autolayout constraint to provide the aspect ratio for the view.
60 |
61 | ```
62 | image.aspectRatio(3.0/2.0)
63 | image.aspectRatio(150%)
64 |
65 | // is equivalent to
66 |
67 | image.Width == image.Height * 1.5
68 | image.Width == 150 % image.Height
69 | ```
70 |
71 | - Returns: Itself, enabling chaining,
72 |
73 | */
74 | @discardableResult
75 | func aspectRatio(_ ratio: Int = 1) -> Self {
76 | aspectRatio(Double(ratio))
77 | return self
78 | }
79 |
80 | /**
81 | Adds an Autolayout constraint for sizing the view.
82 |
83 | ```
84 | image.size(100)
85 | image.size(100%)
86 |
87 | // is equivalent to
88 |
89 | image.width(100).height(100)
90 | ```
91 |
92 | - Returns: Itself, enabling chaining,
93 |
94 | */
95 | @discardableResult
96 | func size(_ points: Double) -> Self {
97 | width(points)
98 | height(points)
99 | return self
100 | }
101 |
102 | /**
103 | Adds an Autolayout constraint for sizing the view.
104 |
105 | ```
106 | image.size(100)
107 | image.size(100%)
108 |
109 | // is equivalent to
110 |
111 | image.width(100).height(100)
112 | ```
113 |
114 | - Returns: Itself, enabling chaining,
115 |
116 | */
117 | @discardableResult
118 | func size(_ points: CGFloat) -> Self {
119 | size(Double(points))
120 | }
121 |
122 | /**
123 | Adds an Autolayout constraint for sizing the view.
124 |
125 | ```
126 | image.size(100)
127 | image.size(100%)
128 |
129 | // is equivalent to
130 |
131 | image.width(100).height(100)
132 | ```
133 |
134 | - Returns: Itself, enabling chaining,
135 |
136 | */
137 | @discardableResult
138 | func size(_ points: Int) -> Self {
139 | size(Double(points))
140 | }
141 |
142 | /**
143 | Adds an Autolayout constraint for setting the view's height.
144 |
145 | ```
146 | image.height(100)
147 |
148 | // is equivalent to
149 |
150 | image ~ 100
151 |
152 | // Flexible margins
153 | image.height(<=100)
154 | image.height(>=100)
155 | image.height(100%)
156 | ```
157 |
158 | - Returns: Itself, enabling chaining,
159 |
160 | */
161 | @discardableResult
162 | func height(_ points: Double) -> Self {
163 | size(.height, points: points)
164 | }
165 |
166 | /**
167 | Adds an Autolayout constraint for setting the view's height.
168 |
169 | ```
170 | image.height(100)
171 |
172 | // is equivalent to
173 |
174 | image ~ 100
175 |
176 | // Flexible margins
177 | image.height(<=100)
178 | image.height(>=100)
179 | image.height(100%)
180 | ```
181 |
182 | - Returns: Itself, enabling chaining,
183 |
184 | */
185 | @discardableResult
186 | func height(_ points: CGFloat) -> Self {
187 | height(Double(points))
188 | }
189 |
190 | /**
191 | Adds an Autolayout constraint for setting the view's height.
192 |
193 | ```
194 | image.height(100)
195 |
196 | // is equivalent to
197 |
198 | image ~ 100
199 |
200 | // Flexible margins
201 | image.height(<=100)
202 | image.height(>=100)
203 | image.height(100%)
204 | ```
205 |
206 | - Returns: Itself, enabling chaining,
207 |
208 | */
209 | @discardableResult
210 | func height(_ points: Int) -> Self {
211 | height(Double(points))
212 | }
213 |
214 | /**
215 | Adds an Autolayout constraint for setting the view's width.
216 |
217 | ```
218 | image.width(100)
219 | image.width(<=100)
220 | image.width(>=100)
221 | image.width(100%)
222 | ```
223 |
224 | - Returns: Itself, enabling chaining,
225 |
226 | */
227 | @discardableResult
228 | func width(_ points: Double) -> Self {
229 | size(.width, points: points)
230 | }
231 |
232 | /**
233 | Adds an Autolayout constraint for setting the view's width.
234 |
235 | ```
236 | image.width(100)
237 | image.width(<=100)
238 | image.width(>=100)
239 | image.width(100%)
240 | ```
241 |
242 | - Returns: Itself, enabling chaining,
243 |
244 | */
245 | @discardableResult
246 | func width(_ points: CGFloat) -> Self {
247 | width(Double(points))
248 | }
249 |
250 | /**
251 | Adds an Autolayout constraint for setting the view's width.
252 |
253 | ```
254 | image.width(100)
255 | image.width(<=100)
256 | image.width(>=100)
257 | image.width(100%)
258 | ```
259 |
260 | - Returns: Itself, enabling chaining,
261 |
262 | */
263 | @discardableResult
264 | func width(_ points: Int) -> Self {
265 | width(Double(points))
266 | }
267 |
268 | /**
269 | Adds an Autolayout constraint for setting the view's height.
270 |
271 | ```
272 | image.height(100)
273 |
274 | // is equivalent to
275 |
276 | image ~ 100
277 |
278 | // Flexible margins
279 | image.height(<=100)
280 | image.height(>=100)
281 | image.height(100%)
282 | ```
283 |
284 | - Returns: Itself, enabling chaining,
285 |
286 | */
287 | @discardableResult
288 | func height(_ fm: SteviaFlexibleMargin) -> Self {
289 | return size(.height, relatedBy: fm.relation, points: fm.points)
290 | }
291 |
292 | /**
293 | Adds an Autolayout constraint for setting the view's width.
294 |
295 | ```
296 | image.width(100)
297 | image.width(<=100)
298 | image.width(>=100)
299 | image.width(100%)
300 | ```
301 |
302 | - Returns: Itself, enabling chaining,
303 |
304 | */
305 | @discardableResult
306 | func width(_ fm: SteviaFlexibleMargin) -> Self {
307 | return size(.width, relatedBy: fm.relation, points: fm.points)
308 | }
309 |
310 | fileprivate func size(_ attribute: NSLayoutConstraint.Attribute,
311 | relatedBy: NSLayoutConstraint.Relation = .equal,
312 | points: Double) -> Self {
313 | let c = constraint(item: self,
314 | attribute: attribute,
315 | relatedBy: relatedBy,
316 | constant: points)
317 | if let spv = superview {
318 | spv.addConstraint(c)
319 | } else {
320 | addConstraint(c)
321 | }
322 | return self
323 | }
324 | }
325 |
326 | /**
327 | Enforces an array of views to keep the same size.
328 |
329 | ```
330 | equal(sizes: image1, image2, image3)
331 | ```
332 |
333 | - Returns: The views enabling chaining.
334 |
335 | */
336 | @discardableResult
337 | @MainActor public func equal(sizes views: UIView...) -> [UIView] {
338 | return equal(sizes: views)
339 | }
340 |
341 | @available(*, deprecated, renamed:"equal(sizes:)")
342 | @discardableResult
343 | @MainActor public func equalSizes(_ views: UIView...) -> [UIView] {
344 | return equal(sizes: views)
345 | }
346 |
347 | /**
348 | Enforces an array of views to keep the same size.
349 |
350 | ```
351 | equal(sizes: image1, image2, image3)
352 | ```
353 |
354 | - Returns: The views enabling chaining.
355 |
356 | */
357 | @discardableResult
358 | @MainActor public func equal(sizes views: [UIView]) -> [UIView] {
359 | equal(heights: views)
360 | equal(widths: views)
361 | return views
362 | }
363 |
364 | @available(*, deprecated, renamed:"equal(sizes:)")
365 | @discardableResult
366 | @MainActor public func equalSizes(_ views: [UIView]) -> [UIView] {
367 | equal(heights: views)
368 | equal(widths: views)
369 | return views
370 | }
371 |
372 | /**
373 | Enforces an array of views to keep the same widths.
374 |
375 | ```
376 | equal(widths: image1, image2, image3)
377 | ```
378 |
379 | - Returns: The views enabling chaining.
380 |
381 | */
382 | @discardableResult
383 | @MainActor public func equal(widths views: UIView...) -> [UIView] {
384 | return equal(widths: views)
385 | }
386 |
387 | @available(*, deprecated, renamed:"equal(widths:)")
388 | @discardableResult
389 | @MainActor public func equalWidths(_ views: UIView...) -> [UIView] {
390 | return equal(widths: views)
391 | }
392 |
393 | /**
394 | Enforces an array of views to keep the same widths.
395 |
396 | ```
397 | equal(widths: image1, image2, image3)
398 | ```
399 |
400 | - Returns: The views enabling chaining.
401 |
402 | */
403 | @discardableResult
404 | @MainActor public func equal(widths views: [UIView]) -> [UIView] {
405 | equal(.width, views: views)
406 | return views
407 | }
408 |
409 | @available(*, deprecated, renamed:"equal(widths:)")
410 | @discardableResult
411 | @MainActor public func equalWidths(_ views: [UIView]) -> [UIView] {
412 | equal(.width, views: views)
413 | return views
414 | }
415 |
416 | /**
417 | Enforces an array of views to keep the same heights.
418 |
419 | ```
420 | equal(heights: image1, image2, image3)
421 | ```
422 |
423 | - Returns: The views enabling chaining.
424 |
425 | */
426 | @discardableResult
427 | @MainActor public func equal(heights views: UIView...) -> [UIView] {
428 | return equal(heights: views)
429 | }
430 |
431 | @available(*, deprecated, renamed:"equal(heights:)")
432 | @discardableResult
433 | @MainActor public func equalHeights(_ views: UIView...) -> [UIView] {
434 | return equal(heights: views)
435 | }
436 |
437 | /**
438 | Enforces an array of views to keep the same heights.
439 |
440 | ```
441 | equal(heights: image1, image2, image3)
442 | ```
443 |
444 | - Returns: The views enabling chaining.
445 |
446 | */
447 | @discardableResult
448 | @MainActor public func equal(heights views: [UIView]) -> [UIView] {
449 | equal(.height, views: views)
450 | return views
451 | }
452 |
453 | @available(*, deprecated, renamed:"equal(heights:)")
454 | @discardableResult
455 | @MainActor public func equalHeights(_ views: [UIView]) -> [UIView] {
456 | equal(.height, views: views)
457 | return views
458 | }
459 |
460 | @MainActor private func equal(_ attribute: NSLayoutConstraint.Attribute, views: [UIView]) {
461 | var previousView: UIView?
462 | for v in views {
463 | if let pv = previousView {
464 | if let spv = v.superview {
465 | spv.addConstraint(item: v, attribute: attribute, toItem: pv)
466 | }
467 | }
468 | previousView = v
469 | }
470 | }
471 | #endif
472 |
--------------------------------------------------------------------------------
/Sources/Stevia/Stevia+Stacks.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Stevia+Stacks.swift
3 | // Stevia
4 | //
5 | // Created by Sacha Durand Saint Omer on 10/02/16.
6 | // Copyright © 2016 Sacha Durand Saint Omer. All rights reserved.
7 | //
8 |
9 | #if canImport(UIKit)
10 | import UIKit
11 |
12 | //enum SteviaLayoutItemType {
13 | //
14 | //}
15 |
16 | public protocol SteviaLayoutItem {
17 | var any: Any {get}
18 | }
19 |
20 | extension SteviaLayoutItem {
21 | public var any: Any { self }
22 | }
23 | extension UIView: SteviaLayoutItem {}
24 | extension Int: SteviaLayoutItem {}
25 | extension Double: SteviaLayoutItem {}
26 | extension CGFloat: SteviaLayoutItem {}
27 | extension String: SteviaLayoutItem {}
28 |
29 | extension FlexibleSpace: SteviaLayoutItem {}
30 | extension SteviaFlexibleMargin: SteviaLayoutItem {}
31 | extension SteviaPercentage: SteviaLayoutItem {}
32 | extension Array: SteviaLayoutItem where Element: UIView {}
33 |
34 | public struct FlexibleSpace {
35 | public init() {}
36 | }
37 |
38 | @resultBuilder public struct SteviaLayoutBuilder {
39 | public static func buildBlock(_ content: SteviaLayoutItem...) -> [SteviaLayoutItem] {
40 | return content
41 | }
42 | }
43 |
44 | public extension UIView {
45 | @discardableResult
46 | func layout(@SteviaLayoutBuilder content: () -> [SteviaLayoutItem]) -> UIView {
47 | let subviews = content()
48 | let anys = subviews.map { $0.any }
49 | layout(anys)
50 | return self
51 | }
52 | }
53 |
54 | public extension UIView {
55 |
56 | /**
57 |
58 | Lays out the views on both axis.
59 |
60 | Note that this is not needed for Horizontal only layouts.
61 |
62 | `layout` is primarily for laying out views vertically but horizontal statements
63 | are supported, making it perfect for describing a layout in one single statement.
64 |
65 | ```
66 | layout(
67 | 100,
68 | |-email-| ~ 80,
69 | 8,
70 | |-password-forgot-| ~ 80,
71 | >=20,
72 | |login| ~ 80,
73 | 0
74 | )
75 | ```
76 | */
77 | // @available(*, deprecated, message: "Use Layout { } function builder instead.")
78 | @discardableResult
79 | func layout(_ objects: Any...) -> [UIView] {
80 | return layout(objects)
81 | }
82 |
83 | @discardableResult
84 | func layout(_ objects: [Any]) -> [UIView] {
85 | var previousMargin: Double?
86 | var previousFlexibleMargin: SteviaFlexibleMargin?
87 | var previousPercentMargin: SteviaPercentage?
88 |
89 | for (i, o) in objects.enumerated() {
90 |
91 | func viewLogic(_ v: UIView) {
92 | if let pm = previousMargin {
93 | if i == 1 {
94 | v.top(pm) // only if first view
95 | } else {
96 | if let vx = objects[i-2] as? UIView {
97 | vx.stackV(m: pm, v: v)
98 | } else if let va = objects[i-2] as? [UIView] {
99 | va.first!.stackV(m: pm, v: v)
100 | }
101 | }
102 | previousMargin = nil
103 | } else if let pfm = previousFlexibleMargin {
104 | if i == 1 {
105 | v.top(pfm) // only if first view
106 | } else {
107 | if let vx = objects[i-2] as? UIView {
108 | addConstraint(
109 | item: v, attribute: .top,
110 | relatedBy: pfm.relation,
111 | toItem: vx, attribute: .bottom,
112 | multiplier: 1, constant: pfm.points
113 | )
114 | } else if let va = objects[i-2] as? [UIView] {
115 | addConstraint(
116 | item: v, attribute: .top,
117 | relatedBy: pfm.relation,
118 | toItem: va.first!, attribute: .bottom,
119 | multiplier: 1, constant: pfm.points
120 | )
121 | }
122 | }
123 | previousFlexibleMargin = nil
124 | } else if let ppm = previousPercentMargin {
125 | if i == 1 {
126 | v.top(ppm) // only if first view
127 | } else {
128 | if let vx = objects[i-2] as? UIView {
129 | // Add layout guide to suport %-based spaces.
130 | let percent = ppm.value / 100
131 |
132 | let lg = UILayoutGuide()
133 | addLayoutGuide(lg)
134 | NSLayoutConstraint.activate([
135 | lg.topAnchor.constraint(equalTo: vx.bottomAnchor),
136 | lg.heightAnchor.constraint(equalTo: heightAnchor, multiplier: CGFloat(percent)),
137 | v.topAnchor.constraint(equalTo: lg.bottomAnchor)
138 | ])
139 | }
140 | }
141 | previousPercentMargin = nil
142 | } else {
143 | tryStackViewVerticallyWithPreviousView(v, index: i, objects: objects)
144 | }
145 | }
146 |
147 | switch o {
148 | case let v as UIView:
149 | viewLogic(v)
150 | case is Int, is Double, is CGFloat:
151 | let m = doubleMarginFromObject(o)
152 | previousMargin = m // Store margin for next pass
153 | if i != 0 && i == (objects.count - 1) {
154 | //Last Margin, Bottom
155 | if let previousView = objects[i-1] as? UIView {
156 | previousView.bottom(m)
157 | } else if let va = objects[i-1] as? [UIView] {
158 | va.first!.bottom(m)
159 | }
160 | }
161 | case let fm as SteviaFlexibleMargin:
162 | previousFlexibleMargin = fm // Store margin for next pass
163 | if i != 0 && i == (objects.count - 1) {
164 | //Last Margin, Bottom
165 | if let previousView = objects[i-1] as? UIView {
166 | previousView.bottom(fm)
167 | } else if let va = objects[i-1] as? [UIView] {
168 | va.first!.bottom(fm)
169 | }
170 | }
171 | case let pm as SteviaPercentage:
172 | previousPercentMargin = pm // Store margin for next pass
173 | if i != 0 && i == (objects.count - 1) {
174 | //Last Margin, Bottom
175 | if let previousView = objects[i-1] as? UIView {
176 | previousView.bottom(pm)
177 | } else if let va = objects[i-1] as? [UIView] {
178 | va.first!.bottom(pm)
179 | }
180 | }
181 | case _ as String:() //Do nothin' !
182 | case let a as [UIView]:
183 | align(horizontally: a)
184 | let v = a.first!
185 | viewLogic(v)
186 | default: ()
187 | }
188 | }
189 | return objects.map {$0 as? UIView }.compactMap {$0}
190 | }
191 |
192 | fileprivate func doubleMarginFromObject(_ o: Any) -> Double {
193 | var m: Double = 0
194 | if let i = o as? Int {
195 | m = Double(i)
196 | } else if let d = o as? Double {
197 | m = d
198 | } else if let cg = o as? CGFloat {
199 | m = Double(cg)
200 | }
201 | return m
202 | }
203 |
204 | fileprivate func tryStackViewVerticallyWithPreviousView(_ view: UIView,
205 | index: Int, objects: [Any]) {
206 | if let pv = previousViewFromIndex(index, objects: objects) {
207 | pv.stackV(v: view)
208 | }
209 | }
210 |
211 | fileprivate func previousViewFromIndex(_ index: Int, objects: [Any]) -> UIView? {
212 | if index != 0 {
213 | if let previousView = objects[index-1] as? UIView {
214 | return previousView
215 | }
216 | }
217 | return nil
218 | }
219 |
220 | @discardableResult
221 | fileprivate func stackV(m points: Double = 0, v: UIView) -> UIView {
222 | return stack(.vertical, points: points, v: v)
223 | }
224 |
225 | fileprivate func stack(_ axis: NSLayoutConstraint.Axis,
226 | points: Double = 0, v: UIView) -> UIView {
227 | let a: NSLayoutConstraint.Attribute = axis == .vertical ? .top : .left
228 | let b: NSLayoutConstraint.Attribute = axis == .vertical ? .bottom : .right
229 | if let spv = superview {
230 | let c = constraint(item: v, attribute: a, toItem: self, attribute: b, constant: points)
231 | spv.addConstraint(c)
232 | }
233 | return v
234 | }
235 | }
236 | #endif
237 |
--------------------------------------------------------------------------------
/Sources/Stevia/Stevia+Style.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Stevia+Style.swift
3 | // LoginStevia
4 | //
5 | // Created by Sacha Durand Saint Omer on 01/10/15.
6 | // Copyright © 2015 Sacha Durand Saint Omer. All rights reserved.
7 | //
8 |
9 | #if canImport(UIKit)
10 | import UIKit
11 |
12 | public extension UIAppearance {
13 |
14 | /** Applies a styling block on an element.
15 |
16 | Example Usage:
17 |
18 | ```
19 | button.style { b in
20 | b.A = X
21 | b.B = Y
22 | b.C = Z
23 | }
24 | ```
25 |
26 | Handy for reusing styles :
27 | ```
28 | button.style(buttonStyle)
29 |
30 | // later
31 | func buttonStyle(b: UIButton) {
32 | ..styling code
33 | }
34 | ```
35 |
36 | - Returns: Itself for chaining purposes
37 |
38 | */
39 | @discardableResult
40 | func style(_ styleClosure: (Self) -> Void) -> Self {
41 | styleClosure(self)
42 | return self
43 | }
44 | }
45 | #endif
46 |
--------------------------------------------------------------------------------
/SteviaLayout.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = 'SteviaLayout'
3 | s.version = "5.1.2"
4 | s.summary = "Elegant view layout for iOS"
5 | s.homepage = "https://github.com/freshOS/Stevia"
6 | s.license = { :type => "MIT", :file => "LICENSE" }
7 | s.author = 'S4cha'
8 | s.source = { :git => "https://github.com/freshOS/Stevia.git",
9 | :tag => s.version.to_s }
10 | s.social_media_url = 'https://twitter.com/sachadso'
11 | s.source_files = "Sources/Stevia/*.swift"
12 | s.ios.deployment_target = "9"
13 | s.tvos.deployment_target = "10.2"
14 | s.description = "Elegant view layout for iOS :leaves: - Auto layout code finally readable by a human being"
15 | s.module_name = 'Stevia'
16 | end
17 |
--------------------------------------------------------------------------------
/Tests/SteviaTests/BaselineTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BaselineTests.swift
3 | // SteviaTests
4 | //
5 | // Created by Sacha on 09/09/2018.
6 | // Copyright © 2018 Sacha Durand Saint Omer. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Stevia
11 | import Testing
12 |
13 | @Suite
14 | @MainActor struct BaselineTests {
15 |
16 | let win = UIWindow(frame: UIScreen.main.bounds)
17 | let ctrler = UIViewController()
18 | let label1 = UILabel()
19 | let label2 = UILabel()
20 |
21 | init() {
22 | win.rootViewController = ctrler
23 | ctrler.view.subviews {
24 | label1
25 | label2
26 | }
27 | }
28 |
29 | @Test
30 | func alignLastBaselines() {
31 | label1.top(100)
32 | align(lastBaselines: label1, label2)
33 | ctrler.view.layoutIfNeeded()
34 | #expect(label1.forLastBaselineLayout.frame.minY == label2.forLastBaselineLayout.frame.minY)
35 | }
36 |
37 | @Test
38 | func alignFirstBaselines() {
39 | label1.top(100)
40 | align(firstBaselines: label1, label2)
41 | ctrler.view.layoutIfNeeded()
42 | #expect(label1.forLastBaselineLayout.frame.minY == label2.forLastBaselineLayout.frame.minY)
43 | }
44 | }
45 |
46 |
--------------------------------------------------------------------------------
/Tests/SteviaTests/CenterTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CenterTests.swift
3 | // Stevia
4 | //
5 | // Created by Sacha Durand Saint Omer on 21/02/16.
6 | // Copyright © 2016 Sacha Durand Saint Omer. All rights reserved.
7 | //
8 |
9 | import Testing
10 | import Numerics
11 | import UIKit
12 | import Stevia
13 |
14 |
15 | @Suite
16 | @MainActor struct CenterTests {
17 |
18 | let win = UIWindow(frame: UIScreen.main.bounds)
19 | var ctrler = UIViewController()
20 | var v = UIView()
21 |
22 | init() {
23 | win.rootViewController = ctrler
24 | ctrler.view.subviews { v }
25 | v.size(100)
26 | ctrler.view.layoutIfNeeded()
27 | }
28 |
29 | @Test
30 | func centerHorizontally() {
31 | v.centerHorizontally()
32 | ctrler.view.setNeedsLayout()
33 | ctrler.view.layoutIfNeeded()
34 | #expect(v.frame.origin.y == 0)
35 | #expect(isApproximatelyEqual(v.frame.origin.x, ctrler.view.frame.width/2.0 - (v.frame.width/2.0)))
36 | #expect(v.frame.width == 100)
37 | #expect(v.frame.height == 100)
38 | }
39 |
40 | @Test
41 | func centerHorizontallyWithOffsetDouble() {
42 | v.centerHorizontally(offset: Double(50))
43 | ctrler.view.setNeedsLayout()
44 | ctrler.view.layoutIfNeeded()
45 | #expect(v.frame.origin.y == 0)
46 | let val = v.frame.origin.x - 50
47 | #expect(isApproximatelyEqual(val, ctrler.view.frame.width/2.0 - (v.frame.width/2.0)))
48 | #expect(v.frame.width == 100)
49 | #expect(v.frame.height == 100)
50 | }
51 |
52 | @Test
53 | func centerHorizontallyWithOffsetCGFloat() {
54 | v.centerHorizontally(offset: CGFloat(50))
55 | ctrler.view.setNeedsLayout()
56 | ctrler.view.layoutIfNeeded()
57 | #expect(v.frame.origin.y == 0)
58 | let val = v.frame.origin.x - 50
59 | #expect(isApproximatelyEqual(val, (ctrler.view.frame.width/2.0 - (v.frame.width/2.0))))
60 | #expect(v.frame.width == 100)
61 | #expect(v.frame.height == 100)
62 | }
63 |
64 | @Test
65 | func centerHorizontallyWithOffsetInt() {
66 | v.centerHorizontally(offset: Int(50))
67 | ctrler.view.setNeedsLayout()
68 | ctrler.view.layoutIfNeeded()
69 | #expect(v.frame.origin.y == 0)
70 | let val = v.frame.origin.x - 50
71 | #expect(isApproximatelyEqual(val, (ctrler.view.frame.width/2.0 - (v.frame.width/2.0))))
72 | #expect(v.frame.width == 100)
73 | #expect(v.frame.height == 100)
74 | }
75 |
76 | @Test
77 | func centerVertically() {
78 | v.centerVertically()
79 | ctrler.view.setNeedsLayout()
80 | ctrler.view.layoutIfNeeded()
81 | #expect(v.frame.origin.y == ctrler.view.frame.height/2.0 - (v.frame.height/2.0))
82 | #expect(v.frame.origin.x == 0)
83 | #expect(v.frame.width == 100)
84 | #expect(v.frame.height == 100)
85 | }
86 |
87 | @Test
88 | func centerVerticallyWithOffsetDouble() {
89 | v.centerVertically(offset: Double(50))
90 | ctrler.view.setNeedsLayout()
91 | ctrler.view.layoutIfNeeded()
92 | let val = v.frame.origin.y - 50
93 | #expect(isApproximatelyEqual(val, (ctrler.view.frame.height/2.0 - (v.frame.height/2.0))))
94 | #expect(v.frame.origin.x == 0)
95 | #expect(v.frame.width == 100)
96 | #expect(v.frame.height == 100)
97 | }
98 |
99 | @Test
100 | func centerVerticallyWithOffsetCGFloat() {
101 | v.centerVertically(offset: CGFloat(50))
102 | ctrler.view.setNeedsLayout()
103 | ctrler.view.layoutIfNeeded()
104 | let val = v.frame.origin.y - 50
105 | #expect(isApproximatelyEqual(val, (ctrler.view.frame.height/2.0 - (v.frame.height/2.0))))
106 | #expect(v.frame.origin.x == 0)
107 | #expect(v.frame.width == 100)
108 | #expect(v.frame.height == 100)
109 | }
110 |
111 | @Test
112 | func centerVerticallyWithOffsetInt() {
113 | v.centerVertically(offset: Int(50))
114 | ctrler.view.setNeedsLayout()
115 | ctrler.view.layoutIfNeeded()
116 |
117 | let val = v.frame.origin.y - 50
118 | #expect(isApproximatelyEqual(val, (ctrler.view.frame.height/2.0 - (v.frame.height/2.0))))
119 | #expect(v.frame.origin.x == 0)
120 | #expect(v.frame.width == 100)
121 | #expect(v.frame.height == 100)
122 | }
123 |
124 | @Test
125 | func centerInContainer() {
126 | v.centerInContainer()
127 | ctrler.view.setNeedsLayout()
128 | ctrler.view.layoutIfNeeded()
129 |
130 | #expect(v.frame.origin.y == ctrler.view.frame.height/2.0 - (v.frame.height/2.0))
131 | #expect(isApproximatelyEqual(v.frame.origin.x, ctrler.view.frame.width/2.0 - (v.frame.width/2.0)))
132 | #expect(v.frame.width == 100)
133 | #expect(v.frame.height == 100)
134 | #expect(isApproximatelyEqual(v.frame.origin.y, ctrler.view.frame.height/2.0 - (v.frame.height/2.0)))
135 | #expect(isApproximatelyEqual(v.frame.origin.x, ctrler.view.frame.width/2.0 - (v.frame.width/2.0)))
136 | #expect(v.frame.height == 100)
137 | }
138 | }
139 |
140 | func isApproximatelyEqual(_ a: Double, _ b: Double) -> Bool {
141 | return a.isApproximatelyEqual(to: b, absoluteTolerance: 0.5)
142 | }
143 |
--------------------------------------------------------------------------------
/Tests/SteviaTests/ContentTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContentTests.swift
3 | // Stevia
4 | //
5 | // Created by Naabed on 12/02/16.
6 | // Copyright © 2016 Sacha Durand Saint Omer. All rights reserved.
7 | //
8 |
9 | import Testing
10 | import UIKit
11 | import Stevia
12 |
13 | let title = "TitleTest"
14 |
15 | @Suite
16 | @MainActor struct UIButtonContentTests {
17 |
18 | var button = UIButton()
19 |
20 | init() {
21 | button = UIButton()
22 | }
23 |
24 | @Test
25 | func testText() {
26 | button.text(title)
27 | #expect(button.currentTitle == title)
28 | #expect(button.state == .normal)
29 | }
30 |
31 | @Test
32 | func testTextKey() {
33 | button.textKey(title)
34 | #expect(button.currentTitle == title)
35 | }
36 | }
37 |
38 |
39 | @Suite
40 | @MainActor struct UILabelContentTests {
41 |
42 | let label = UILabel()
43 |
44 | @Test
45 | func testText() {
46 | label.text(title)
47 | #expect(label.text == title)
48 | }
49 |
50 | @Test
51 | func testTextKey() {
52 | label.textKey(title)
53 | #expect(label.text == title)
54 | }
55 | }
56 |
57 | @Suite
58 | @MainActor struct UITextFieldContentTests {
59 |
60 | let textField = UITextField()
61 |
62 | @Test
63 | func testPlaceholder() {
64 | textField.placeholder(title)
65 | #expect(textField.placeholder == title)
66 | }
67 | }
68 |
69 | @Suite
70 | @MainActor struct UIImageViewContentTests {
71 |
72 | let imageView = UIImageView()
73 |
74 | @Test
75 | func testImage() {
76 | imageView.image("foo")
77 | //XCTAssertEqual(button.currentImage, title)
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/Tests/SteviaTests/FillTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FillTests.swift
3 | // Stevia
4 | //
5 | // Created by Naabed on 12/02/16.
6 | // Copyright © 2016 Sacha Durand Saint Omer. All rights reserved.
7 | //
8 |
9 | import Testing
10 | import UIKit
11 | import Stevia
12 |
13 | @Suite
14 | @MainActor class FillTests {
15 |
16 | let win = UIWindow(frame: UIScreen.main.bounds)
17 | let ctrler = UIViewController()
18 |
19 | init() {
20 | win.rootViewController = ctrler
21 | }
22 |
23 | @Test
24 | func testFillContainer() {
25 | let b = UIButton()
26 | ctrler.view.subviews { b }
27 | b.fillContainer()
28 | ctrler.view.layoutIfNeeded() // This is needed to force auto-layout to kick-in
29 | #expect(ctrler.view.frame == b.frame)
30 | }
31 |
32 | @Test
33 | func testFillContainerWithPaddingDouble() {
34 | let padding: Double = 10.0
35 | let b = UIButton()
36 | ctrler.view.subviews { b }
37 | b.fillContainer(padding: padding)
38 | ctrler.view.layoutIfNeeded() // This is needed to force auto-layout to kick-in
39 | #expect(ctrler.view.frame.height == b.frame.height + CGFloat(padding) * 2)
40 | #expect(ctrler.view.frame.width == b.frame.width + CGFloat(padding) * 2)
41 | }
42 |
43 | @Test
44 | func testFillContainerWithPaddingCGFloat() {
45 | let padding: CGFloat = 10.0
46 | let b = UIButton()
47 | ctrler.view.subviews { b }
48 | b.fillContainer(padding: padding)
49 | ctrler.view.layoutIfNeeded() // This is needed to force auto-layout to kick-in
50 | #expect(ctrler.view.frame.height == b.frame.height + CGFloat(padding) * 2)
51 | #expect(ctrler.view.frame.width == b.frame.width + CGFloat(padding) * 2)
52 | }
53 |
54 | @Test
55 | func testFillContainerWithPaddingInt() {
56 | let padding: Int = 10
57 | let b = UIButton()
58 | ctrler.view.subviews { b }
59 | b.fillContainer(padding: padding)
60 | ctrler.view.layoutIfNeeded() // This is needed to force auto-layout to kick-in
61 | #expect(ctrler.view.frame.height == b.frame.height + CGFloat(padding) * 2)
62 | #expect(ctrler.view.frame.width == b.frame.width + CGFloat(padding) * 2)
63 | }
64 |
65 | @Test
66 | func testFillVertically() {
67 | let b = UIButton()
68 | ctrler.view.subviews { b }
69 | b.width(10)
70 | b.fillVertically()
71 | ctrler.view.layoutIfNeeded() // This is needed to force auto-layout to kick-in
72 | #expect(ctrler.view.frame.height == b.frame.height)
73 | #expect(10 == b.frame.width)
74 | }
75 |
76 | @Test
77 | func testFillVerticallyWithPaddingDouble() {
78 | let padding: Double = 40.0
79 | let b = UIButton()
80 | ctrler.view.subviews { b }
81 | b.fillVertically(padding: padding)
82 | ctrler.view.layoutIfNeeded() // This is needed to force auto-layout to kick-in
83 | #expect(ctrler.view.frame.height == b.frame.height + CGFloat(padding) * 2)
84 | }
85 |
86 | @Test
87 | func testFillVerticallyWithPaddingCGFloat() {
88 | let padding: CGFloat = 30.0
89 | let b = UIButton()
90 | ctrler.view.subviews { b }
91 | b.fillVertically(padding: padding)
92 | ctrler.view.layoutIfNeeded() // This is needed to force auto-layout to kick-in
93 | #expect(ctrler.view.frame.height == b.frame.height + CGFloat(padding) * 2)
94 | }
95 |
96 | @Test
97 | func testFillVerticallyWithPaddingInt() {
98 | let padding: Int = 14
99 | let b = UIButton()
100 | ctrler.view.subviews { b }
101 | b.fillVertically(padding: padding)
102 | ctrler.view.layoutIfNeeded() // This is needed to force auto-layout to kick-in
103 | #expect(ctrler.view.frame.height == b.frame.height + CGFloat(padding) * 2)
104 | }
105 |
106 | @Test
107 | func testFillHorizontallyWithPaddingDouble() {
108 | let padding: Double = 40.0
109 | let b = UIButton()
110 | ctrler.view.subviews { b }
111 | b.fillHorizontally(padding: padding)
112 | ctrler.view.layoutIfNeeded() // This is needed to force auto-layout to kick-in
113 | #expect(ctrler.view.frame.width == b.frame.width + CGFloat(padding) * 2)
114 | }
115 |
116 | @Test
117 | func testFillHorizontallyWithPaddingCGFloat() {
118 | let padding: CGFloat = 30.0
119 | let b = UIButton()
120 | ctrler.view.subviews { b }
121 | b.fillHorizontally(padding: padding)
122 | ctrler.view.layoutIfNeeded() // This is needed to force auto-layout to kick-in
123 | #expect(ctrler.view.frame.width == b.frame.width + CGFloat(padding) * 2)
124 | }
125 |
126 | @Test
127 | func testFillHorizontallyWithPaddingInt() {
128 | let padding: Int = 14
129 | let b = UIButton()
130 | ctrler.view.subviews { b }
131 | b.fillHorizontally(padding: padding)
132 | ctrler.view.layoutIfNeeded() // This is needed to force auto-layout to kick-in
133 | #expect(ctrler.view.frame.width == b.frame.width + CGFloat(padding) * 2)
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/Tests/SteviaTests/FlexibleMarginTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FlexibleMarginTests.swift
3 | // Stevia
4 | //
5 | // Created by Sacha Durand Saint Omer on 21/02/16.
6 | // Copyright © 2016 Sacha Durand Saint Omer. All rights reserved.
7 | //
8 |
9 | import Testing
10 | import UIKit
11 | import Stevia
12 |
13 | @Suite
14 | @MainActor struct FlexibleMarginTests {
15 |
16 | let win = UIWindow(frame: UIScreen.main.bounds)
17 | let ctrler = UIViewController()
18 | let v = UIView()
19 |
20 | init() {
21 | win.rootViewController = ctrler
22 | ctrler.view.subviews { v }
23 | v.size(100.0)
24 | }
25 |
26 | @Test
27 | func testGreaterTopDouble() {
28 | v.top(>=Double(23))
29 | ctrler.view.layoutIfNeeded()
30 | #expect(v.frame.origin.y == 23)
31 | #expect(v.frame.origin.x == 0)
32 | #expect(v.frame.width == 100)
33 | #expect(v.frame.height == 100)
34 | }
35 |
36 | @Test
37 | func testGreaterTopCGFloat() {
38 | v.top(>=CGFloat(23))
39 | ctrler.view.layoutIfNeeded()
40 | #expect(v.frame.origin.y == 23)
41 | #expect(v.frame.origin.x == 0)
42 | #expect(v.frame.width == 100)
43 | #expect(v.frame.height == 100)
44 | }
45 |
46 | @Test
47 | func testGreaterTopInt() {
48 | v.top(>=Int(23))
49 | ctrler.view.layoutIfNeeded()
50 | #expect(v.frame.origin.y == 23)
51 | #expect(v.frame.origin.x == 0)
52 | #expect(v.frame.width == 100)
53 | #expect(v.frame.height == 100)
54 | }
55 |
56 | @Test
57 | func testGreaterBottom() {
58 | v.bottom(>=45)
59 | ctrler.view.layoutIfNeeded()
60 | #expect(v.frame.origin.y == ctrler.view.frame.height - v.frame.height - 45)
61 | #expect(v.frame.origin.x == 0)
62 | #expect(v.frame.width == 100)
63 | #expect(v.frame.height == 100)
64 | }
65 |
66 | @Test
67 | func testGreaterLeft() {
68 | v.left(>=23)
69 | ctrler.view.layoutIfNeeded()
70 | #expect(v.frame.origin.y == 0)
71 | #expect(v.frame.origin.x == 23)
72 | #expect(v.frame.width == 100)
73 | #expect(v.frame.height == 100)
74 | }
75 |
76 | @Test
77 | func testGreaterLeading() {
78 | v.leading(>=23)
79 | ctrler.view.layoutIfNeeded()
80 | #expect(v.frame.origin.y == 0)
81 | #expect(v.frame.origin.x == 23)
82 | #expect(v.frame.width == 100)
83 | #expect(v.frame.height == 100)
84 | }
85 |
86 | @Test
87 | func testGreaterLeadingRTL() {
88 | ctrler.view.semanticContentAttribute = .forceRightToLeft
89 | v.leading(>=23)
90 | ctrler.view.layoutIfNeeded()
91 | #expect(v.frame.origin.y == 0)
92 | #expect(v.frame.origin.x == ctrler.view.frame.width - v.frame.width - 23)
93 | #expect(v.frame.width == 100)
94 | #expect(v.frame.height == 100)
95 | }
96 |
97 | @Test
98 | func testGreaterRight() {
99 | v.right(>=74)
100 | ctrler.view.layoutIfNeeded()
101 | #expect(v.frame.origin.y == 0)
102 | #expect(v.frame.origin.x == ctrler.view.frame.width - v.frame.width - 74)
103 | #expect(v.frame.width == 100)
104 | #expect(v.frame.height == 100)
105 | }
106 |
107 | @Test
108 | func testGreaterTrailing() {
109 | v.trailing(>=74)
110 | ctrler.view.layoutIfNeeded()
111 | #expect(v.frame.origin.y == 0)
112 | #expect(v.frame.origin.x == ctrler.view.frame.width - v.frame.width - 74)
113 | #expect(v.frame.width == 100)
114 | #expect(v.frame.height == 100)
115 | }
116 |
117 | @Test
118 | func testGreaterTrailingRTL() {
119 | ctrler.view.semanticContentAttribute = .forceRightToLeft
120 | v.trailing(>=74)
121 | ctrler.view.layoutIfNeeded()
122 | #expect(v.frame.origin.y == 0)
123 | #expect(v.frame.origin.x == 74)
124 | #expect(v.frame.width == 100)
125 | #expect(v.frame.height == 100)
126 | }
127 |
128 | @Test
129 | func testLessTopDouble() {
130 | v.top(<=Double(23))
131 | ctrler.view.layoutIfNeeded()
132 | #expect(v.frame.origin.y == 23)
133 | #expect(v.frame.origin.x == 0)
134 | #expect(v.frame.width == 100)
135 | #expect(v.frame.height == 100)
136 | }
137 |
138 | @Test
139 | func testLessTopCGFloat() {
140 | v.top(<=CGFloat(23))
141 | ctrler.view.layoutIfNeeded()
142 | #expect(v.frame.origin.y == 23)
143 | #expect(v.frame.origin.x == 0)
144 | #expect(v.frame.width == 100)
145 | #expect(v.frame.height == 100)
146 | }
147 |
148 | @Test
149 | func testLessTopInt() {
150 | v.top(<=Int(23))
151 | ctrler.view.layoutIfNeeded()
152 | #expect(v.frame.origin.y == 23)
153 | #expect(v.frame.origin.x == 0)
154 | #expect(v.frame.width == 100)
155 | #expect(v.frame.height == 100)
156 | }
157 |
158 | @Test
159 | func testLessBottom() {
160 | v.bottom(<=45)
161 | ctrler.view.layoutIfNeeded()
162 | #expect(v.frame.origin.y == ctrler.view.frame.height - v.frame.height - 45)
163 | #expect(v.frame.origin.x == 0)
164 | #expect(v.frame.width == 100)
165 | #expect(v.frame.height == 100)
166 | }
167 |
168 | @Test
169 | func testLessLeft() {
170 | v.left(<=23)
171 | ctrler.view.layoutIfNeeded()
172 | #expect(v.frame.origin.y == 0)
173 | #expect(v.frame.origin.x == 23)
174 | #expect(v.frame.width == 100)
175 | #expect(v.frame.height == 100)
176 | }
177 |
178 | @Test
179 | func testLessLeading() {
180 | v.leading(<=23)
181 | ctrler.view.layoutIfNeeded()
182 | #expect(v.frame.origin.y == 0)
183 | #expect(v.frame.origin.x == 23)
184 | #expect(v.frame.width == 100)
185 | #expect(v.frame.height == 100)
186 | }
187 |
188 | @Test
189 | func testLessLeadingRTL() {
190 | ctrler.view.semanticContentAttribute = .forceRightToLeft
191 | v.leading(<=23)
192 | ctrler.view.layoutIfNeeded()
193 | #expect(v.frame.origin.y == 0)
194 | #expect(v.frame.origin.x == ctrler.view.frame.width - v.frame.width - 23)
195 | #expect(v.frame.width == 100)
196 | #expect(v.frame.height == 100)
197 | }
198 |
199 | @Test
200 | func testLessLeftOperator() {
201 | |-(<=23)-v
202 | ctrler.view.layoutIfNeeded()
203 | #expect(v.frame.origin.y == 0)
204 | #expect(v.frame.origin.x == 23)
205 | #expect(v.frame.width == 100)
206 | #expect(v.frame.height == 100)
207 | }
208 |
209 | @Test
210 | func testLessLeftOperatorRTL() {
211 | ctrler.view.semanticContentAttribute = .forceRightToLeft
212 | |-(<=23)-v
213 | ctrler.view.layoutIfNeeded()
214 | #expect(v.frame.origin.y == 0)
215 | #expect(v.frame.origin.x == ctrler.view.frame.width - v.frame.width - 23)
216 | #expect(v.frame.width == 100)
217 | #expect(v.frame.height == 100)
218 | }
219 |
220 | @Test
221 | func testLessRight() {
222 | v.right(<=74)
223 | ctrler.view.layoutIfNeeded()
224 | #expect(v.frame.origin.y == 0)
225 | #expect(v.frame.origin.x == ctrler.view.frame.width - v.frame.width - 74)
226 | #expect(v.frame.width == 100)
227 | #expect(v.frame.height == 100)
228 | }
229 |
230 | @Test
231 | func testLessTrailing() {
232 | v.trailing(<=74)
233 | ctrler.view.layoutIfNeeded()
234 | #expect(v.frame.origin.y == 0)
235 | #expect(v.frame.origin.x == ctrler.view.frame.width - v.frame.width - 74)
236 | #expect(v.frame.width == 100)
237 | #expect(v.frame.height == 100)
238 | }
239 |
240 | @Test
241 | func testLessTrailingRTL() {
242 | ctrler.view.semanticContentAttribute = .forceRightToLeft
243 | v.trailing(<=74)
244 | ctrler.view.layoutIfNeeded()
245 | #expect(v.frame.origin.y == 0)
246 | #expect(v.frame.origin.x == 74)
247 | #expect(v.frame.width == 100)
248 | #expect(v.frame.height == 100)
249 | }
250 |
251 | @Test
252 | func testLessRightOperator() {
253 | v-(<=74)-|
254 | ctrler.view.layoutIfNeeded()
255 | #expect(v.frame.origin.y == 0)
256 | #expect(v.frame.origin.x == ctrler.view.frame.width - v.frame.width - 74)
257 | #expect(v.frame.width == 100)
258 | #expect(v.frame.height == 100)
259 | }
260 |
261 | @Test
262 | func testLessRightOperatorRTL() {
263 | ctrler.view.semanticContentAttribute = .forceRightToLeft
264 | v-(<=74)-|
265 | ctrler.view.layoutIfNeeded()
266 | #expect(v.frame.origin.y == 0)
267 | #expect(v.frame.origin.x == 74)
268 | #expect(v.frame.width == 100)
269 | #expect(v.frame.height == 100)
270 | }
271 |
272 | @Test
273 | func testMarginGreaterBetweenTwoViews() {
274 | let v1 = UIView()
275 | let v2 = UIView()
276 | v.removeFromSuperview()
277 | ctrler.view.subviews { v1; v2 }
278 | for view in ctrler.view.subviews {
279 | #expect(view.frame.origin.y == 0)
280 | #expect(view.frame.origin.x == 0)
281 | #expect(view.frame.width == 0)
282 | #expect(view.frame.height == 0)
283 | }
284 |
285 | |v1.width(10)-(>=25)-v2
286 | ctrler.view.layoutIfNeeded()
287 | #expect(v1.frame.origin.y == 0)
288 | #expect(v1.frame.origin.x == 0)
289 | #expect(v1.frame.width == 10)
290 | #expect(v1.frame.height == 0)
291 |
292 | #expect(v2.frame.origin.y == 0)
293 | #expect(v2.frame.origin.x == 35)
294 | #expect(v2.frame.width == 0)
295 | #expect(v2.frame.height == 0)
296 | }
297 |
298 | @Test
299 | func testMarginGreaterBetweenTwoViewsRTL() {
300 | ctrler.view.semanticContentAttribute = .forceRightToLeft
301 | let v1 = UIView()
302 | let v2 = UIView()
303 | v.removeFromSuperview()
304 | ctrler.view.subviews {
305 | v1
306 | v2
307 | }
308 | for view in ctrler.view.subviews {
309 | #expect(view.frame.origin.y == 0)
310 | #expect(view.frame.origin.x == 0)
311 | #expect(view.frame.width == 0)
312 | #expect(view.frame.height == 0)
313 | }
314 |
315 | |v1.width(10)-(>=25)-v2
316 | ctrler.view.layoutIfNeeded()
317 | #expect(v1.frame.origin.y == 0)
318 | #expect(v1.frame.origin.x == ctrler.view.frame.width - 10)
319 | #expect(v1.frame.width == 10)
320 | #expect(v1.frame.height == 0)
321 |
322 | #expect(v2.frame.origin.y == 0)
323 | #expect(v2.frame.origin.x == ctrler.view.frame.width - v1.frame.width - 25)
324 | #expect(v2.frame.width == 0)
325 | #expect(v2.frame.height == 0)
326 |
327 | }
328 |
329 | @Test
330 | func testMarginLesserBetweenTwoViews() {
331 | let v1 = UIView()
332 | let v2 = UIView()
333 | v.removeFromSuperview()
334 | ctrler.view.subviews {
335 | v1
336 | v2
337 | }
338 | for view in ctrler.view.subviews {
339 | #expect(view.frame.origin.y == 0)
340 | #expect(view.frame.origin.x == 0)
341 | #expect(view.frame.width == 0)
342 | #expect(view.frame.height == 0)
343 | }
344 |
345 | |v1.width(10)-(<=25)-v2
346 | ctrler.view.layoutIfNeeded()
347 | #expect(v1.frame.origin.y == 0)
348 | #expect(v1.frame.origin.x == 0)
349 | #expect(v1.frame.width == 10)
350 | #expect(v1.frame.height == 0)
351 |
352 | #expect(v2.frame.origin.y == 0)
353 | #expect(v2.frame.origin.x == 35)
354 | #expect(v2.frame.width == 0)
355 | #expect(v2.frame.height == 0)
356 | }
357 |
358 | @Test
359 | func testMarginLesserBetweenTwoViewsRTL() {
360 | ctrler.view.semanticContentAttribute = .forceRightToLeft
361 | let v1 = UIView()
362 | let v2 = UIView()
363 | v.removeFromSuperview()
364 | ctrler.view.subviews {
365 | v1
366 | v2
367 | }
368 | for view in ctrler.view.subviews {
369 | #expect(view.frame.origin.y == 0)
370 | #expect(view.frame.origin.x == 0)
371 | #expect(view.frame.width == 0)
372 | #expect(view.frame.height == 0)
373 | }
374 |
375 | |v1.width(10)-(<=25)-v2
376 | ctrler.view.layoutIfNeeded()
377 | #expect(v1.frame.origin.y == 0)
378 | #expect(v1.frame.origin.x == ctrler.view.frame.width - 10)
379 | #expect(v1.frame.width == 10)
380 | #expect(v1.frame.height == 0)
381 |
382 | #expect(v2.frame.origin.y == 0)
383 | #expect(v2.frame.origin.x == ctrler.view.frame.width - v1.frame.width - 25)
384 | #expect(v2.frame.width == 0)
385 | #expect(v2.frame.height == 0)
386 | }
387 | }
388 |
--------------------------------------------------------------------------------
/Tests/SteviaTests/FullLayoutTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FullLayoutTests.swift
3 | // Stevia
4 | //
5 | // Created by Sacha Durand Saint Omer on 21/02/16.
6 | // Copyright © 2016 Sacha Durand Saint Omer. All rights reserved.
7 | //
8 |
9 | import Testing
10 | import UIKit
11 | import Stevia
12 |
13 | class TestView: UIView {
14 |
15 | let email = UITextField()
16 | let password = UITextField()
17 | let login = UIButton()
18 |
19 | let view1 = UIView()
20 | let view2 = UIView()
21 |
22 | convenience init() {
23 | self.init(frame: CGRect.zero)
24 |
25 | subviews {
26 | email
27 | password
28 | login
29 | }
30 |
31 | layout {
32 | 100.5
33 | |-email-22-| ~ 10%
34 | 20
35 | |password.width(54) ~ 47.0
36 | >=0
37 | login.centerHorizontally() ~ 99
38 | 7
39 | }
40 |
41 | subviews {
42 | view1
43 | view2
44 | }
45 |
46 | layout {
47 | 10%
48 | |view1| ~ 20
49 | 33%
50 | |view2|
51 | 20%
52 | }
53 |
54 | }
55 | }
56 |
57 | @Suite
58 | @MainActor class FullLayoutTests {
59 |
60 | let win = UIWindow(frame: UIScreen.main.bounds)
61 | let vc = UIViewController()
62 | let v = TestView()
63 |
64 | init() {
65 | win.rootViewController = vc
66 | v.frame = vc.view.frame
67 | vc.view = v
68 | }
69 |
70 | @Test
71 | func testFullLayout() {
72 | #expect(vc.view.frame.origin.x == 0)
73 | #expect(vc.view.frame.origin.y == 0)
74 | #expect(vc.view.frame.width == win.frame.width)
75 | #expect(vc.view.frame.height == win.frame.height)
76 |
77 | v.layoutIfNeeded()
78 |
79 | // Email
80 | #expect(isApproximatelyEqual(v.email.frame.origin.y, 100.5))
81 | #expect(v.email.frame.origin.x == 8)
82 | #expect(v.email.frame.width == win.frame.width - 8 - 22)
83 | #expect(isApproximatelyEqual(v.email.frame.height, win.frame.height*0.1))
84 |
85 | // Password
86 | #expect(isApproximatelyEqual(v.password.frame.origin.y, v.email.frame.origin.y+v.email.frame.height + 20))
87 | #expect(v.password.frame.origin.x == 0)
88 | #expect(v.password.frame.width == 54)
89 | #expect(v.password.frame.height == 47)
90 |
91 | // Password
92 | #expect(v.login.frame.origin.y == win.frame.height - v.login.frame.height - 7)
93 | #expect(isApproximatelyEqual(v.login.frame.origin.x, win.frame.width/2.0 - (v.login.frame.width/2.0)))
94 | #expect(v.login.frame.height == 99)
95 | }
96 |
97 | @Test
98 | func testFullLayoutRTL() {
99 | vc.view.semanticContentAttribute = .forceRightToLeft
100 | #expect(vc.view.frame.origin.x == 0)
101 | #expect(vc.view.frame.origin.y == 0)
102 | #expect(vc.view.frame.width == win.frame.width)
103 | #expect(vc.view.frame.height == win.frame.height)
104 |
105 | v.layoutIfNeeded()
106 |
107 | // Email
108 | #expect(isApproximatelyEqual(v.email.frame.origin.y, 100.5))
109 | #expect(v.email.frame.origin.x == 22)
110 | #expect(v.email.frame.width == win.frame.width - 8 - 22)
111 | #expect(isApproximatelyEqual(v.email.frame.height, win.frame.height*0.1))
112 |
113 | // Password
114 | #expect(isApproximatelyEqual(v.password.frame.origin.y, v.email.frame.origin.y+v.email.frame.height + 20))
115 | #expect(v.password.frame.origin.x == vc.view.frame.width - 54)
116 | #expect(v.password.frame.width == 54)
117 | #expect(v.password.frame.height == 47)
118 |
119 | // Password
120 | #expect(v.login.frame.origin.y == win.frame.height - v.login.frame.height - 7)
121 | #expect(isApproximatelyEqual(v.login.frame.origin.x, win.frame.width/2.0 - (v.login.frame.width/2.0)))
122 | #expect(v.login.frame.height == 99)
123 | }
124 |
125 | @Test
126 | func testPercentLayout() {
127 | #expect(vc.view.frame.origin.x == 0)
128 | #expect(vc.view.frame.origin.y == 0)
129 | #expect(vc.view.frame.width == win.frame.width)
130 | #expect(vc.view.frame.height == win.frame.height)
131 |
132 | v.layoutIfNeeded()
133 |
134 | #expect(isApproximatelyEqual(v.view1.frame.origin.y, v.frame.height*0.1))
135 | #expect(v.view1.frame.origin.x == 0)
136 | #expect(v.view1.frame.width == v.frame.width)
137 | #expect(isApproximatelyEqual(v.view2.frame.origin.y, (v.frame.height*0.1) + 20 + (v.frame.height*0.33)))
138 | #expect(v.view2.frame.origin.x == 0)
139 | #expect(isApproximatelyEqual(v.view2.frame.origin.y + v.view2.frame.height, (v.frame.height*0.8)))
140 | #expect(v.view2.frame.width == v.frame.width)
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/Tests/SteviaTests/GetConstraintsTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GetConstraintsTests.swift
3 | // Stevia
4 | //
5 | // Created by Sacha DSO on 14/06/2017.
6 | // Copyright © 2017 Sacha Durand Saint Omer. All rights reserved.
7 | //
8 |
9 | import Testing
10 | import UIKit
11 | import Stevia
12 |
13 | @Suite
14 | @MainActor struct GetConstraintsTests {
15 |
16 | let v = UIView()
17 | let spv = UIView()
18 |
19 | init() {
20 | spv.subviews { v }
21 | }
22 |
23 | @Test
24 | func canGetLeftConstraint() throws {
25 | #expect(v.leftConstraint == nil)
26 | v.left(10)
27 | let c = try #require(v.leftConstraint)
28 | #expect(c.constant == 10)
29 | #expect(c.firstItem as? UIView == v)
30 | #expect(c.secondItem as? UIView == spv)
31 | #expect(c.firstAttribute == .left)
32 | #expect(c.secondAttribute == .left)
33 | #expect(c.multiplier == 1)
34 | #expect(c.relation == .equal)
35 | #expect(c.priority.rawValue == 751)
36 | #expect(c.isActive)
37 | }
38 |
39 | @Test
40 | func canGetRightConstraint() throws {
41 | #expect(v.rightConstraint == nil)
42 | v.right(42)
43 | let c = try #require(v.rightConstraint)
44 | #expect(c.constant == -42)
45 | #expect(c.firstItem as? UIView == v)
46 | #expect(c.secondItem as? UIView == spv)
47 | #expect(c.firstAttribute == .right)
48 | #expect(c.secondAttribute == .right)
49 | #expect(c.multiplier == 1)
50 | #expect(c.relation == .equal)
51 | #expect(c.priority.rawValue == 751)
52 | #expect(c.isActive)
53 | }
54 |
55 | @Test
56 | func canGetTopConstraint() throws {
57 | #expect(v.topConstraint == nil)
58 | v.top(23)
59 | let c = try #require(v.topConstraint)
60 | #expect(c.constant == 23)
61 | #expect(c.firstItem as? UIView == v)
62 | #expect(c.secondItem as? UIView == spv)
63 | #expect(c.firstAttribute == .top)
64 | #expect(c.secondAttribute == .top)
65 | #expect(c.multiplier == 1)
66 | #expect(c.relation == .equal)
67 | #expect(c.priority.rawValue == 751)
68 | #expect(c.isActive)
69 | }
70 |
71 | @Test
72 | func testCanGetBottomConstraint() throws {
73 | #expect(v.bottomConstraint == nil)
74 | v.bottom(145)
75 | let c = try #require(v.bottomConstraint)
76 | #expect(c.constant == -145)
77 | #expect(c.firstItem as? UIView == v)
78 | #expect(c.secondItem as? UIView == spv)
79 | #expect(c.firstAttribute == .bottom)
80 | #expect(c.secondAttribute == .bottom)
81 | #expect(c.multiplier == 1)
82 | #expect(c.relation == .equal)
83 | #expect(c.priority.rawValue == 751)
84 | #expect(c.isActive)
85 | }
86 |
87 | @Test
88 | func testCanGetHeightConstraint() throws {
89 | #expect(v.heightConstraint == nil)
90 | v.height(35)
91 | let c = try #require(v.heightConstraint)
92 | #expect(c.constant == 35)
93 | #expect(c.firstItem as? UIView == v)
94 | #expect(c.secondItem == nil)
95 | #expect(c.firstAttribute == .height)
96 | #expect(c.secondAttribute == .notAnAttribute)
97 | #expect(c.multiplier == 1)
98 | #expect(c.relation == .equal)
99 | #expect(c.priority.rawValue == 751)
100 | #expect(c.isActive)
101 | }
102 |
103 | @Test
104 | func canGetWidthConstraint() throws {
105 | #expect(v.widthConstraint == nil)
106 | v.width(51)
107 | let c = try #require(v.widthConstraint)
108 | #expect(c.constant == 51)
109 | #expect(c.firstItem as? UIView == v)
110 | #expect(c.secondItem == nil)
111 | #expect(c.firstAttribute == .width)
112 | #expect(c.secondAttribute == .notAnAttribute)
113 | #expect(c.multiplier == 1)
114 | #expect(c.relation == .equal)
115 | #expect(c.priority.rawValue == 751)
116 | #expect(c.isActive)
117 | }
118 |
119 | @Test
120 | func canGetTrailingConstraint() throws {
121 | #expect(v.trailingConstraint == nil)
122 | v.trailingAnchor.constraint(equalTo: spv.trailingAnchor, constant: 104).isActive = true
123 | let c = try #require(v.trailingConstraint)
124 | #expect(c.constant == 104)
125 | #expect(c.firstItem as? UIView == v)
126 | #expect(c.secondItem as? UIView == spv)
127 | #expect(c.firstAttribute == .trailing)
128 | #expect(c.secondAttribute == .trailing)
129 | #expect(c.multiplier == 1)
130 | #expect(c.relation == .equal)
131 | #expect(c.priority.rawValue == 1000)
132 | #expect(c.isActive)
133 | }
134 |
135 | @Test
136 | func canGetLeadingonstraint() throws {
137 | #expect(v.leadingConstraint == nil)
138 | v.leadingAnchor.constraint(equalTo: spv.leadingAnchor, constant: 73).isActive = true
139 | let c = try #require(v.leadingConstraint)
140 | #expect(c.constant == 73)
141 | #expect(c.firstItem as? UIView == v)
142 | #expect(c.secondItem as? UIView == spv)
143 | #expect(c.firstAttribute == .leading)
144 | #expect(c.secondAttribute == .leading)
145 | #expect(c.multiplier == 1)
146 | #expect(c.relation == .equal)
147 | #expect(c.priority.rawValue == 1000)
148 | #expect(c.isActive)
149 | }
150 |
151 | @Test
152 | func canGetCenterXConstraint() throws {
153 | #expect(v.centerXConstraint == nil)
154 | v.CenterX == spv.CenterX + 27
155 | let c = try #require(v.centerXConstraint)
156 | #expect(c.constant == 27)
157 | #expect(c.firstItem as? UIView == v)
158 | #expect(c.secondItem as? UIView == spv)
159 | #expect(c.firstAttribute == .centerX)
160 | #expect(c.secondAttribute == .centerX)
161 | #expect(c.multiplier == 1)
162 | #expect(c.relation == .equal)
163 | #expect(c.priority.rawValue == 751)
164 | #expect(c.isActive)
165 | }
166 |
167 | @Test
168 | func canGetCenterYConstraint() throws {
169 | #expect(v.centerYConstraint == nil)
170 | v.CenterY == spv.CenterY - 32
171 | let c = try #require(v.centerYConstraint)
172 | #expect(c.constant == -32)
173 | #expect(c.firstItem as? UIView == v)
174 | #expect(c.secondItem as? UIView == spv)
175 | #expect(c.firstAttribute == .centerY)
176 | #expect(c.secondAttribute == .centerY)
177 | #expect(c.multiplier == 1)
178 | #expect(c.relation == .equal)
179 | #expect(c.priority.rawValue == 751)
180 | #expect(c.isActive)
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/Tests/SteviaTests/HierarchyTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HierarchyTests.swift
3 | // Stevia
4 | //
5 | // Created by Naabed on 12/02/16.
6 | // Copyright © 2016 Sacha Durand Saint Omer. All rights reserved.
7 | //
8 |
9 | import Testing
10 | import UIKit
11 | import Stevia
12 |
13 | @Suite
14 | @MainActor
15 | struct HierarchyTests {
16 |
17 | @Test
18 | func subviews() {
19 | let view = UIView()
20 | let v1 = UIView()
21 | let v2 = UIView()
22 | view.subviews {
23 | v1
24 | v2
25 | }
26 | #expect(view.subviews.count == 2)
27 | #expect(view.subviews.contains(v1))
28 | #expect(view.subviews.contains(v2))
29 | #expect(v1.translatesAutoresizingMaskIntoConstraints == false)
30 | #expect(v2.translatesAutoresizingMaskIntoConstraints == false)
31 | }
32 |
33 | @Test
34 | func tableViewCellSubviews() {
35 | let cell = UITableViewCell()
36 | let v1 = UIView()
37 | let v2 = UIView()
38 | cell.subviews {
39 | v1
40 | v2
41 | }
42 |
43 | #expect(cell.contentView.subviews.count == 2)
44 | #expect(cell.contentView.subviews.contains(v1))
45 | #expect(cell.contentView.subviews.contains(v2))
46 | #expect(v1.translatesAutoresizingMaskIntoConstraints == false)
47 | #expect(v2.translatesAutoresizingMaskIntoConstraints == false)
48 | }
49 |
50 | @Test
51 | func testCollectionViewCellSubviews() {
52 | let cell = UICollectionViewCell()
53 | let v1 = UIView()
54 | let v2 = UIView()
55 | cell.subviews {
56 | v1
57 | v2
58 | }
59 | #expect(cell.contentView.subviews.count == 2)
60 | #expect(cell.contentView.subviews.contains(v1))
61 | #expect(cell.contentView.subviews.contains(v2))
62 | #expect(v1.translatesAutoresizingMaskIntoConstraints == false)
63 | #expect(v2.translatesAutoresizingMaskIntoConstraints == false)
64 | }
65 |
66 | @Test
67 | func testSubviewsCanBeNested() {
68 | let view = UIView()
69 | let v1 = UIView()
70 | let v2 = UIView()
71 | let v3 = UIView()
72 | let v4 = UIView()
73 | view.subviews {
74 | v1.subviews {
75 | v3
76 | v4
77 | }
78 | v2
79 | }
80 | #expect(view.subviews.count == 2)
81 | #expect(view.subviews.contains(v1))
82 | #expect(view.subviews.contains(v2))
83 | #expect(v1.subviews.count == 2)
84 | #expect(v1.subviews.contains(v3))
85 | #expect(v1.subviews.contains(v4))
86 |
87 | #expect(v1.translatesAutoresizingMaskIntoConstraints == false)
88 | #expect(v2.translatesAutoresizingMaskIntoConstraints == false)
89 | #expect(v3.translatesAutoresizingMaskIntoConstraints == false)
90 | #expect(v4.translatesAutoresizingMaskIntoConstraints == false)
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/Tests/SteviaTests/PositionTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CenterTests.swift
3 | // Stevia
4 | //
5 | // Created by Naabed on 13/02/16.
6 | // Copyright © 2016 Sacha Durand Saint Omer. All rights reserved.
7 | //
8 |
9 | import Testing
10 | import UIKit
11 | import Stevia
12 |
13 | @Suite
14 | @MainActor class PositionTests {
15 |
16 | let win = UIWindow(frame: UIScreen.main.bounds)
17 | let ctrler = UIViewController()
18 | let v = UIView()
19 |
20 | init() {
21 | win.rootViewController = ctrler
22 | ctrler.view.subviews { v }
23 | v.size(100.0)
24 | }
25 |
26 | @Test
27 | func testTopDouble() {
28 | v.top(Double(23))
29 | ctrler.view.layoutIfNeeded()
30 | #expect(v.frame.origin.y == 23)
31 | #expect(v.frame.origin.x == 0)
32 | #expect(v.frame.width == 100)
33 | #expect(v.frame.height == 100)
34 | }
35 |
36 | @Test
37 | func testTopCGFloat() {
38 | v.top(CGFloat(23))
39 | ctrler.view.layoutIfNeeded()
40 | #expect(v.frame.origin.y == 23)
41 | #expect(v.frame.origin.x == 0)
42 | #expect(v.frame.width == 100)
43 | #expect(v.frame.height == 100)
44 | }
45 |
46 | @Test
47 | func testTopInt() {
48 | v.top(Int(23))
49 | ctrler.view.layoutIfNeeded()
50 | #expect(v.frame.origin.y == 23)
51 | #expect(v.frame.origin.x == 0)
52 | #expect(v.frame.width == 100)
53 | #expect(v.frame.height == 100)
54 | }
55 |
56 | @Test
57 | func testBottomDouble() {
58 | v.bottom(Double(45))
59 | ctrler.view.layoutIfNeeded()
60 | #expect(v.frame.origin.y == ctrler.view.frame.height - v.frame.height - 45)
61 | #expect(v.frame.origin.x == 0)
62 | #expect(v.frame.width == 100)
63 | #expect(v.frame.height == 100)
64 | }
65 |
66 | @Test
67 | func testBottomCGFloat() {
68 | v.bottom(CGFloat(45))
69 | ctrler.view.layoutIfNeeded()
70 | #expect(v.frame.origin.y == ctrler.view.frame.height - v.frame.height - 45)
71 | #expect(v.frame.origin.x == 0)
72 | #expect(v.frame.width == 100)
73 | #expect(v.frame.height == 100)
74 | }
75 |
76 | @Test
77 | func testBottomInt() {
78 | v.bottom(Int(45))
79 | ctrler.view.layoutIfNeeded()
80 | #expect(v.frame.origin.y == ctrler.view.frame.height - v.frame.height - 45)
81 | #expect(v.frame.origin.x == 0)
82 | #expect(v.frame.width == 100)
83 | #expect(v.frame.height == 100)
84 | }
85 |
86 | @Test
87 | func testLeftDouble() {
88 | v.left(Double(12))
89 | ctrler.view.layoutIfNeeded()
90 | #expect(v.frame.origin.y == 0)
91 | #expect(v.frame.origin.x == 12)
92 | #expect(v.frame.width == 100)
93 | #expect(v.frame.height == 100)
94 | }
95 |
96 | @Test
97 | func testLeftCGFloat() {
98 | v.left(CGFloat(12))
99 | ctrler.view.layoutIfNeeded()
100 | #expect(v.frame.origin.y == 0)
101 | #expect(v.frame.origin.x == 12)
102 | #expect(v.frame.width == 100)
103 | #expect(v.frame.height == 100)
104 | }
105 |
106 | @Test
107 | func testLeftInt() {
108 | v.left(Int(12))
109 | ctrler.view.layoutIfNeeded()
110 | #expect(v.frame.origin.y == 0)
111 | #expect(v.frame.origin.x == 12)
112 | #expect(v.frame.width == 100)
113 | #expect(v.frame.height == 100)
114 | }
115 |
116 | @Test
117 | func testLeadingDouble() {
118 | v.leading(Double(12))
119 | ctrler.view.layoutIfNeeded()
120 | #expect(v.frame.origin.y == 0)
121 | #expect(v.frame.origin.x == 12)
122 | #expect(v.frame.width == 100)
123 | #expect(v.frame.height == 100)
124 | }
125 |
126 | @Test
127 | func testLeadingCGFloat() {
128 | v.leading(CGFloat(12))
129 | ctrler.view.layoutIfNeeded()
130 | #expect(v.frame.origin.y == 0)
131 | #expect(v.frame.origin.x == 12)
132 | #expect(v.frame.width == 100)
133 | #expect(v.frame.height == 100)
134 | }
135 |
136 | @Test
137 | func testLeadingInt() {
138 | v.leading(Int(12))
139 | ctrler.view.layoutIfNeeded()
140 | #expect(v.frame.origin.y == 0)
141 | #expect(v.frame.origin.x == 12)
142 | #expect(v.frame.width == 100)
143 | #expect(v.frame.height == 100)
144 | }
145 |
146 | @Test
147 | func testRightDouble() {
148 | v.right(Double(74))
149 | ctrler.view.layoutIfNeeded()
150 | #expect(v.frame.origin.y == 0)
151 | #expect(v.frame.origin.x == ctrler.view.frame.width - v.frame.width - 74)
152 | #expect(v.frame.width == 100)
153 | #expect(v.frame.height == 100)
154 | }
155 |
156 | @Test
157 | func testRightCGFloat() {
158 | v.right(CGFloat(74))
159 | ctrler.view.layoutIfNeeded()
160 | #expect(v.frame.origin.y == 0)
161 | #expect(v.frame.origin.x == ctrler.view.frame.width - v.frame.width - 74)
162 | #expect(v.frame.width == 100)
163 | #expect(v.frame.height == 100)
164 | }
165 |
166 | @Test
167 | func testRightInt() {
168 | v.right(Int(74))
169 | ctrler.view.layoutIfNeeded()
170 | #expect(v.frame.origin.y == 0)
171 | #expect(v.frame.origin.x == ctrler.view.frame.width - v.frame.width - 74)
172 | #expect(v.frame.width == 100)
173 | #expect(v.frame.height == 100)
174 | }
175 |
176 | @Test
177 | func testTrailingDouble() {
178 | v.trailing(Double(74))
179 | ctrler.view.layoutIfNeeded()
180 | #expect(v.frame.origin.y == 0)
181 | #expect(v.frame.origin.x == ctrler.view.frame.width - v.frame.width - 74)
182 | #expect(v.frame.width == 100)
183 | #expect(v.frame.height == 100)
184 | }
185 |
186 | @Test
187 | func testTrailingCGFloat() {
188 | v.trailing(CGFloat(74))
189 | ctrler.view.layoutIfNeeded()
190 | #expect(v.frame.origin.y == 0)
191 | #expect(v.frame.origin.x == ctrler.view.frame.width - v.frame.width - 74)
192 | #expect(v.frame.width == 100)
193 | #expect(v.frame.height == 100)
194 | }
195 |
196 | @Test
197 | func testTrailingInt() {
198 | v.trailing(Int(74))
199 | ctrler.view.layoutIfNeeded()
200 | #expect(v.frame.origin.y == 0)
201 | #expect(v.frame.origin.x == ctrler.view.frame.width - v.frame.width - 74)
202 | #expect(v.frame.width == 100)
203 | #expect(v.frame.height == 100)
204 | }
205 |
206 | @Test
207 | func testPercentTop() {
208 | v.top(10%)
209 | ctrler.view.layoutIfNeeded()
210 | #expect(isApproximatelyEqual(v.frame.origin.y, ctrler.view.frame.height * 0.1))
211 | #expect(v.frame.origin.x == 0)
212 | #expect(v.frame.width == 100)
213 | #expect(v.frame.height == 100)
214 | }
215 |
216 | @Test
217 | func testPercentBottom() {
218 | v.bottom(10%)
219 | ctrler.view.layoutIfNeeded()
220 | #expect(isApproximatelyEqual(v.frame.origin.y, ctrler.view.frame.height * 0.9 - v.frame.height))
221 | #expect(v.frame.origin.x == 0)
222 | #expect(v.frame.width == 100)
223 | #expect(v.frame.height == 100)
224 | }
225 |
226 | @Test
227 | func testPercentLeft() {
228 | v.left(10%)
229 | ctrler.view.layoutIfNeeded()
230 | #expect(v.frame.origin.y == 0)
231 | #expect(isApproximatelyEqual(v.frame.origin.x, ctrler.view.frame.width * 0.1))
232 | #expect(v.frame.width == 100)
233 | #expect(v.frame.height == 100)
234 | }
235 |
236 | @Test
237 | func testPercentRight() {
238 | v.right(10%)
239 | ctrler.view.layoutIfNeeded()
240 | #expect(v.frame.origin.y == 0)
241 | #expect(isApproximatelyEqual(v.frame.origin.x, ctrler.view.frame.width * 0.9 - v.frame.width))
242 | #expect(v.frame.width == 100)
243 | #expect(v.frame.height == 100)
244 | }
245 |
246 | @Test
247 | func testPercentLeading() {
248 | v.leading(10%)
249 | ctrler.view.layoutIfNeeded()
250 | #expect(v.frame.origin.y == 0)
251 | #expect(isApproximatelyEqual(v.frame.origin.x, ctrler.view.frame.width * 0.1))
252 | #expect(v.frame.width == 100)
253 | #expect(v.frame.height == 100)
254 | }
255 |
256 | @Test
257 | func testPercentTrailing() {
258 | v.trailing(10%)
259 | ctrler.view.layoutIfNeeded()
260 | #expect(v.frame.origin.y == 0)
261 | #expect(isApproximatelyEqual(v.frame.origin.x, ctrler.view.frame.width * 0.9 - v.frame.width))
262 | #expect(v.frame.width == 100)
263 | #expect(v.frame.height == 100)
264 | }
265 |
266 | @Test
267 | func testPercentLeadingRTL() {
268 | ctrler.view.semanticContentAttribute = .forceRightToLeft
269 | v.leading(10%)
270 | ctrler.view.layoutIfNeeded()
271 | #expect(v.frame.origin.y == 0)
272 | #expect(isApproximatelyEqual(v.frame.origin.x, (ctrler.view.frame.width * 0.9 - v.frame.width)))
273 | #expect(v.frame.width == 100)
274 | #expect(v.frame.height == 100)
275 | }
276 |
277 | @Test
278 | func testPercentTrailingRTL() {
279 | ctrler.view.semanticContentAttribute = .forceRightToLeft
280 | v.trailing(10%)
281 | ctrler.view.layoutIfNeeded()
282 | #expect(v.frame.origin.y == 0)
283 | #expect(isApproximatelyEqual(v.frame.origin.x, ctrler.view.frame.width * 0.1))
284 | #expect(v.frame.width == 100)
285 | #expect(v.frame.height == 100)
286 | }
287 | }
288 |
--------------------------------------------------------------------------------
/Tests/SteviaTests/SizeTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SizeTests.swift
3 | // Stevia
4 | //
5 | // Created by Naabed on 12/02/16.
6 | // Copyright © 2016 Sacha Durand Saint Omer. All rights reserved.
7 | //
8 |
9 | import Testing
10 | import UIKit
11 | import Stevia
12 |
13 | @Suite
14 | @MainActor struct SizeTests {
15 |
16 | let win = UIWindow(frame: UIScreen.main.bounds)
17 | let ctrler = UIViewController()
18 | let v = UIView()
19 |
20 | init() {
21 | win.rootViewController = ctrler
22 | ctrler.view.subviews {
23 | v
24 | }
25 | }
26 |
27 | @Test
28 | func testAspectRatioDouble() {
29 | v.width(150).aspectRatio(Double(3.0/2.0))
30 | ctrler.view.layoutIfNeeded()
31 | #expect(v.frame.origin.y == 0)
32 | #expect(v.frame.origin.x == 0)
33 | #expect(v.frame.width == 150)
34 | #expect(v.frame.height == 100)
35 | }
36 |
37 | @Test
38 | func testAspectRatioCGFloat() {
39 | v.width(150).aspectRatio(CGFloat(3.0/2.0))
40 | ctrler.view.layoutIfNeeded()
41 | #expect(v.frame.origin.y == 0)
42 | #expect(v.frame.origin.x == 0)
43 | #expect(v.frame.width == 150)
44 | #expect(v.frame.height == 100)
45 | }
46 |
47 | @Test
48 | func testAspectRatioInt() {
49 | v.width(150).aspectRatio(Int(3))
50 | ctrler.view.layoutIfNeeded()
51 | #expect(v.frame.origin.y == 0)
52 | #expect(v.frame.origin.x == 0)
53 | #expect(v.frame.width == 150)
54 | #expect(v.frame.height == 50)
55 | }
56 |
57 | @Test
58 | func testAspectRatioPercentage() {
59 | v.width(150).aspectRatio(150%)
60 | ctrler.view.layoutIfNeeded()
61 | #expect(v.frame.origin.y == 0)
62 | #expect(v.frame.origin.x == 0)
63 | #expect(v.frame.width == 150)
64 | #expect(v.frame.height == 100)
65 | }
66 |
67 | @Test
68 | func testSizeDouble() {
69 | v.size(Double(57))
70 | ctrler.view.layoutIfNeeded()
71 | #expect(v.frame.origin.y == 0)
72 | #expect(v.frame.origin.x == 0)
73 | #expect(v.frame.width == 57)
74 | #expect(v.frame.height == 57)
75 | }
76 |
77 | @Test
78 | func testSizeCGFloat() {
79 | v.size(CGFloat(57))
80 | ctrler.view.layoutIfNeeded()
81 | #expect(v.frame.origin.y == 0)
82 | #expect(v.frame.origin.x == 0)
83 | #expect(v.frame.width == 57)
84 | #expect(v.frame.height == 57)
85 | }
86 |
87 | @Test
88 | func testSizeInt() {
89 | v.size(Int(57))
90 | ctrler.view.layoutIfNeeded()
91 | #expect(v.frame.origin.y == 0)
92 | #expect(v.frame.origin.x == 0)
93 | #expect(v.frame.width == 57)
94 | #expect(v.frame.height == 57)
95 | }
96 |
97 | @Test
98 | func testWidthAndHeightDouble() {
99 | v.width(Double(36))
100 | v.height(Double(23))
101 | ctrler.view.layoutIfNeeded()
102 | #expect(v.frame.origin.y == 0)
103 | #expect(v.frame.origin.x == 0)
104 | #expect(v.frame.width == 36)
105 | #expect(v.frame.height == 23)
106 | }
107 |
108 | @Test
109 | func testWidthAndHeightCGFloat() {
110 | v.width(CGFloat(36))
111 | v.height(CGFloat(23))
112 | ctrler.view.layoutIfNeeded()
113 | #expect(v.frame.origin.y == 0)
114 | #expect(v.frame.origin.x == 0)
115 | #expect(v.frame.width == 36)
116 | #expect(v.frame.height == 23)
117 | }
118 |
119 | @Test
120 | func testWidthAndHeightInt() {
121 | v.width(Int(36))
122 | v.height(Int(23))
123 | ctrler.view.layoutIfNeeded()
124 | #expect(v.frame.origin.y == 0)
125 | #expect(v.frame.origin.x == 0)
126 | #expect(v.frame.width == 36)
127 | #expect(v.frame.height == 23)
128 | }
129 |
130 | @Test
131 | func testHeightPercentage() {
132 | v.width(100)
133 | v.height(40%)
134 | ctrler.view.layoutIfNeeded()
135 | #expect(v.frame.origin.y == 0)
136 | #expect(v.frame.origin.x == 0)
137 | #expect(v.frame.width == 100)
138 | #expect(isApproximatelyEqual(v.frame.height, ctrler.view.frame.height*0.4))
139 | }
140 |
141 | @Test
142 | func testWidthPercentage() {
143 | v.height(100)
144 | v.width(87%)
145 | ctrler.view.layoutIfNeeded()
146 | #expect(v.frame.origin.y == 0)
147 | #expect(v.frame.origin.x == 0)
148 | #expect(v.frame.height == 100)
149 | #expect(isApproximatelyEqual(v.frame.width, ctrler.view.frame.width*0.87))
150 | }
151 |
152 | @Test
153 | func testEqualSizes() {
154 | let width = 24.0
155 | let height = 267.0
156 | let v1 = UIView()
157 | let v2 = UIView()
158 | ctrler.view.subviews {
159 | v1
160 | v2
161 | }
162 | v1.height(height)
163 | v1.width(width)
164 | equal(sizes: [v1, v2])
165 | ctrler.view.layoutIfNeeded()
166 | #expect(v1.frame.width == v2.frame.width)
167 | #expect(v1.frame.height == v2.frame.height)
168 | }
169 |
170 | @Test
171 | func testVariadicEqualSizes() {
172 | let width = 24.0
173 | let height = 267.0
174 | let v1 = UIView()
175 | let v2 = UIView()
176 | ctrler.view.subviews {
177 | v1
178 | v2
179 | }
180 | v1.height(height)
181 | v1.width(width)
182 | equal(sizes: v1, v2)
183 | ctrler.view.layoutIfNeeded()
184 | #expect(v1.frame.width == v2.frame.width)
185 | #expect(v1.frame.height == v2.frame.height)
186 | }
187 |
188 | @Test
189 | func testFollwEdges() {
190 | let v1 = UIView()
191 | let v2 = UIView()
192 | ctrler.view.subviews {
193 | v1
194 | v2
195 | }
196 |
197 | let test = ctrler.view
198 |
199 | test!.layout {
200 | 10
201 | |-20-v1| ~ 32
202 | }
203 |
204 | ctrler.view.layoutIfNeeded()
205 |
206 | #expect(v1.frame.origin.y == 10)
207 | #expect(v1.frame.origin.x == 20)
208 | #expect(v1.frame.width == ctrler.view.frame.width - 20)
209 | #expect(v1.frame.height == 32)
210 |
211 | #expect(v2.frame.origin.y == 0)
212 | #expect(v2.frame.origin.x == 0)
213 | #expect(v2.frame.width == 0)
214 | #expect(v2.frame.height == 0)
215 |
216 | v2.followEdges(v1)
217 | ctrler.view.layoutIfNeeded()
218 | #expect(v2.frame.origin.y == v1.frame.origin.y)
219 | #expect(v2.frame.origin.x == v1.frame.origin.x)
220 | #expect(v2.frame.width == v1.frame.width)
221 | #expect(v2.frame.height == v1.frame.height)
222 | }
223 |
224 | @Test
225 | func testHeightEqualWidth() {
226 | v.heightEqualsWidth().width(85)
227 | ctrler.view.layoutIfNeeded()
228 | #expect(v.frame.origin.y == 0)
229 | #expect(v.frame.origin.x == 0)
230 | #expect(v.frame.width == 85)
231 | #expect(v.frame.height == 85)
232 | }
233 |
234 | @Test
235 | func testWidthEqualHeight() {
236 | v.height(192)
237 | v.heightEqualsWidth()
238 | ctrler.view.layoutIfNeeded()
239 | #expect(v.frame.origin.y == 0)
240 | #expect(v.frame.origin.x == 0)
241 | #expect(v.frame.width == 192)
242 | #expect(v.frame.height == 192)
243 | }
244 |
245 | @Test
246 | func testSizeOnOrphanView() {
247 | v.removeFromSuperview()
248 | v.height(80)
249 | v.width(80)
250 | ctrler.view?.subviews { v }
251 |
252 | let view: UIView = ctrler.view
253 | view.layout {
254 | 0
255 | |v
256 | }
257 |
258 | ctrler.view.layoutIfNeeded()
259 | #expect(v.frame.width == 80)
260 | #expect(v.frame.height == 80)
261 | }
262 | }
263 |
--------------------------------------------------------------------------------
/Tests/SteviaTests/StyleTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StyleTests.swift
3 | // Stevia
4 | //
5 | // Created by krzat on 17/03/16.
6 | // Copyright © 2016 Sacha Durand Saint Omer. All rights reserved.
7 | //
8 |
9 | import Testing
10 | import UIKit
11 | import Stevia
12 |
13 | @Suite
14 | @MainActor struct StyleTests {
15 |
16 | func styleView(_ view: UIView) {
17 | view.backgroundColor = UIColor.yellow
18 | }
19 |
20 | func styleLabel(_ label: UILabel) {
21 | label.textColor = UIColor.yellow
22 | }
23 |
24 | @Test
25 | func canCallStyle() {
26 | let label = UILabel()
27 |
28 | label.style(styleLabel).style(styleView)
29 | label.style(styleView).style(styleLabel)
30 |
31 | let view: UIView = label
32 | view.style(styleView)
33 |
34 | #expect(view.backgroundColor == UIColor.yellow)
35 | #expect(label.textColor == UIColor.yellow)
36 |
37 | //check type deduction
38 | label.style { (label) -> () in
39 | label.textColor = UIColor.blue
40 | }
41 | #expect(label.textColor == UIColor.blue)
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/freshOS/Stevia/7eb652374263868ff61f6ba15e0eb3b830289485/banner.png
--------------------------------------------------------------------------------