├── .DS_Store
├── .gitignore
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ └── contents.xcworkspacedata
├── CODE_OF_CONDUCT.md
├── Example
├── .DS_Store
└── HypeUI-Example
│ ├── .DS_Store
│ ├── HypeUI-Example.xcodeproj
│ ├── project.pbxproj
│ └── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── swiftpm
│ │ └── Package.resolved
│ └── HypeUI-Example
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ └── Contents.json
│ ├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
│ ├── Info.plist
│ ├── SceneDelegate.swift
│ └── ViewController.swift
├── HypeUI.podspec
├── LICENSE
├── Package.resolved
├── Package.swift
├── README.md
├── Sources
├── .DS_Store
└── HypeUI
│ ├── Alignment.swift
│ ├── AlignmentView.swift
│ ├── Behavior.swift
│ ├── Button.swift
│ ├── DynamicLinkable.swift
│ ├── Edge.swift
│ ├── Gradient.swift
│ ├── GradientView.swift
│ ├── HStack.swift
│ ├── Image.swift
│ ├── LinearGradient.swift
│ ├── NSObject+Retain.swift
│ ├── PassthroughStackView.swift
│ ├── PassthroughView.swift
│ ├── ScrollView.swift
│ ├── Spacer.swift
│ ├── Text.swift
│ ├── UIView+Passthrough.swift
│ ├── UIView+Search.swift
│ ├── UIView+ViewBuildable.swift
│ ├── UnitPoint.swift
│ ├── VStack.swift
│ ├── View.swift
│ ├── ViewArrayBuilder.swift
│ ├── ViewBuildable.swift
│ └── ZStack.swift
└── Tests
└── HypeUITests
├── AlignmentTests.swift
├── HStackTests.swift
├── ScrollViewTests.swift
├── SpacerTests.swift
├── TextTests.swift
├── UIView+PassthroughTests.swift
├── ViewArrayBuilderTests.swift
├── ViewBuildableTests.swift
├── ViewTests.swift
├── XCLayoutTestCase.swift
└── ZStackTests.swift
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyperconnect/HypeUI/8e1cd9a00cd615eb5283d29fcf656ebf1022d1ee/.DS_Store
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## User settings
6 | xcuserdata/
7 |
8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
9 | *.xcscmblueprint
10 | *.xccheckout
11 |
12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
13 | build/
14 | DerivedData/
15 | *.moved-aside
16 | *.pbxuser
17 | !default.pbxuser
18 | *.mode1v3
19 | !default.mode1v3
20 | *.mode2v3
21 | !default.mode2v3
22 | *.perspectivev3
23 | !default.perspectivev3
24 |
25 | ## AppCode project setting
26 | .idea/
27 |
28 | ## Obj-C/Swift specific
29 | *.hmap
30 |
31 | ## App packaging
32 | *.ipa
33 | *.dSYM.zip
34 | *.dSYM
35 |
36 | ## Playgrounds
37 | timeline.xctimeline
38 | playground.xcworkspace
39 |
40 | # Swift Package Manager
41 | #
42 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
43 | # Packages/
44 | # Package.pins
45 | # Package.resolved
46 | # *.xcodeproj
47 | #
48 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
49 | # hence it is not needed unless you have added a package configuration file to your project
50 | # .swiftpm
51 |
52 | .build/
53 |
54 | # CocoaPods
55 | #
56 | # We recommend against adding the Pods directory to your .gitignore. However
57 | # you should judge for yourself, the pros and cons are mentioned at:
58 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
59 | #
60 | # Pods/
61 | #
62 | # Add this line if you want to avoid checking in source code from the Xcode workspace
63 | # *.xcworkspace
64 |
65 | # Carthage
66 | #
67 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
68 | # Carthage/Checkouts
69 |
70 | Carthage/Build/
71 |
72 | # Accio dependency management
73 | Dependencies/
74 | .accio/
75 |
76 | # fastlane
77 | #
78 | # It is recommended to not store the screenshots in the git repo.
79 | # Instead, use fastlane to re-generate the screenshots whenever they are needed.
80 | # For more information about the recommended setup visit:
81 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
82 |
83 | fastlane/report.xml
84 | fastlane/Preview.html
85 | fastlane/screenshots/**/*.png
86 | fastlane/test_output
87 |
88 | # Code Injection
89 | #
90 | # After new code Injection tools there's a generated folder /iOSInjectionProject
91 | # https://github.com/johnno1962/injectionforxcode
92 |
93 | iOSInjectionProject/
94 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | * Demonstrating empathy and kindness toward other people
21 | * Being respectful of differing opinions, viewpoints, and experiences
22 | * Giving and gracefully accepting constructive feedback
23 | * Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | * Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | * The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | * Trolling, insulting or derogatory comments, and personal or political attacks
33 | * Public or private harassment
34 | * Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | * Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | @hyperconnect.
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series
86 | of actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or
93 | permanent ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within
113 | the community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
129 |
--------------------------------------------------------------------------------
/Example/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyperconnect/HypeUI/8e1cd9a00cd615eb5283d29fcf656ebf1022d1ee/Example/.DS_Store
--------------------------------------------------------------------------------
/Example/HypeUI-Example/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyperconnect/HypeUI/8e1cd9a00cd615eb5283d29fcf656ebf1022d1ee/Example/HypeUI-Example/.DS_Store
--------------------------------------------------------------------------------
/Example/HypeUI-Example/HypeUI-Example.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 56;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | FC8A9F09291D33C100022ED3 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC8A9F08291D33C100022ED3 /* AppDelegate.swift */; };
11 | FC8A9F0B291D33C100022ED3 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC8A9F0A291D33C100022ED3 /* SceneDelegate.swift */; };
12 | FC8A9F0D291D33C100022ED3 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC8A9F0C291D33C100022ED3 /* ViewController.swift */; };
13 | FC8A9F10291D33C100022ED3 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FC8A9F0E291D33C100022ED3 /* Main.storyboard */; };
14 | FC8A9F12291D33C200022ED3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FC8A9F11291D33C200022ED3 /* Assets.xcassets */; };
15 | FC8A9F15291D33C200022ED3 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FC8A9F13291D33C200022ED3 /* LaunchScreen.storyboard */; };
16 | FC8A9F1E291D33FC00022ED3 /* HypeUI in Frameworks */ = {isa = PBXBuildFile; productRef = FC8A9F1D291D33FC00022ED3 /* HypeUI */; };
17 | /* End PBXBuildFile section */
18 |
19 | /* Begin PBXFileReference section */
20 | FC8A9F05291D33C100022ED3 /* HypeUI-Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "HypeUI-Example.app"; sourceTree = BUILT_PRODUCTS_DIR; };
21 | FC8A9F08291D33C100022ED3 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
22 | FC8A9F0A291D33C100022ED3 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; };
23 | FC8A9F0C291D33C100022ED3 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
24 | FC8A9F0F291D33C100022ED3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
25 | FC8A9F11291D33C200022ED3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
26 | FC8A9F14291D33C200022ED3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
27 | FC8A9F16291D33C300022ED3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
28 | /* End PBXFileReference section */
29 |
30 | /* Begin PBXFrameworksBuildPhase section */
31 | FC8A9F02291D33C100022ED3 /* Frameworks */ = {
32 | isa = PBXFrameworksBuildPhase;
33 | buildActionMask = 2147483647;
34 | files = (
35 | FC8A9F1E291D33FC00022ED3 /* HypeUI in Frameworks */,
36 | );
37 | runOnlyForDeploymentPostprocessing = 0;
38 | };
39 | /* End PBXFrameworksBuildPhase section */
40 |
41 | /* Begin PBXGroup section */
42 | FC8A9EFC291D33C100022ED3 = {
43 | isa = PBXGroup;
44 | children = (
45 | FC8A9F07291D33C100022ED3 /* HypeUI-Example */,
46 | FC8A9F06291D33C100022ED3 /* Products */,
47 | );
48 | sourceTree = "";
49 | };
50 | FC8A9F06291D33C100022ED3 /* Products */ = {
51 | isa = PBXGroup;
52 | children = (
53 | FC8A9F05291D33C100022ED3 /* HypeUI-Example.app */,
54 | );
55 | name = Products;
56 | sourceTree = "";
57 | };
58 | FC8A9F07291D33C100022ED3 /* HypeUI-Example */ = {
59 | isa = PBXGroup;
60 | children = (
61 | FC8A9F08291D33C100022ED3 /* AppDelegate.swift */,
62 | FC8A9F0A291D33C100022ED3 /* SceneDelegate.swift */,
63 | FC8A9F0C291D33C100022ED3 /* ViewController.swift */,
64 | FC8A9F0E291D33C100022ED3 /* Main.storyboard */,
65 | FC8A9F11291D33C200022ED3 /* Assets.xcassets */,
66 | FC8A9F13291D33C200022ED3 /* LaunchScreen.storyboard */,
67 | FC8A9F16291D33C300022ED3 /* Info.plist */,
68 | );
69 | path = "HypeUI-Example";
70 | sourceTree = "";
71 | };
72 | /* End PBXGroup section */
73 |
74 | /* Begin PBXNativeTarget section */
75 | FC8A9F04291D33C100022ED3 /* HypeUI-Example */ = {
76 | isa = PBXNativeTarget;
77 | buildConfigurationList = FC8A9F19291D33C300022ED3 /* Build configuration list for PBXNativeTarget "HypeUI-Example" */;
78 | buildPhases = (
79 | FC8A9F01291D33C100022ED3 /* Sources */,
80 | FC8A9F02291D33C100022ED3 /* Frameworks */,
81 | FC8A9F03291D33C100022ED3 /* Resources */,
82 | );
83 | buildRules = (
84 | );
85 | dependencies = (
86 | );
87 | name = "HypeUI-Example";
88 | packageProductDependencies = (
89 | FC8A9F1D291D33FC00022ED3 /* HypeUI */,
90 | );
91 | productName = "HypeUI-Example";
92 | productReference = FC8A9F05291D33C100022ED3 /* HypeUI-Example.app */;
93 | productType = "com.apple.product-type.application";
94 | };
95 | /* End PBXNativeTarget section */
96 |
97 | /* Begin PBXProject section */
98 | FC8A9EFD291D33C100022ED3 /* Project object */ = {
99 | isa = PBXProject;
100 | attributes = {
101 | BuildIndependentTargetsInParallel = 1;
102 | LastSwiftUpdateCheck = 1410;
103 | LastUpgradeCheck = 1410;
104 | TargetAttributes = {
105 | FC8A9F04291D33C100022ED3 = {
106 | CreatedOnToolsVersion = 14.1;
107 | };
108 | };
109 | };
110 | buildConfigurationList = FC8A9F00291D33C100022ED3 /* Build configuration list for PBXProject "HypeUI-Example" */;
111 | compatibilityVersion = "Xcode 14.0";
112 | developmentRegion = en;
113 | hasScannedForEncodings = 0;
114 | knownRegions = (
115 | en,
116 | Base,
117 | );
118 | mainGroup = FC8A9EFC291D33C100022ED3;
119 | packageReferences = (
120 | FC8A9F1C291D33FC00022ED3 /* XCRemoteSwiftPackageReference "HypeUI" */,
121 | );
122 | productRefGroup = FC8A9F06291D33C100022ED3 /* Products */;
123 | projectDirPath = "";
124 | projectRoot = "";
125 | targets = (
126 | FC8A9F04291D33C100022ED3 /* HypeUI-Example */,
127 | );
128 | };
129 | /* End PBXProject section */
130 |
131 | /* Begin PBXResourcesBuildPhase section */
132 | FC8A9F03291D33C100022ED3 /* Resources */ = {
133 | isa = PBXResourcesBuildPhase;
134 | buildActionMask = 2147483647;
135 | files = (
136 | FC8A9F15291D33C200022ED3 /* LaunchScreen.storyboard in Resources */,
137 | FC8A9F12291D33C200022ED3 /* Assets.xcassets in Resources */,
138 | FC8A9F10291D33C100022ED3 /* Main.storyboard in Resources */,
139 | );
140 | runOnlyForDeploymentPostprocessing = 0;
141 | };
142 | /* End PBXResourcesBuildPhase section */
143 |
144 | /* Begin PBXSourcesBuildPhase section */
145 | FC8A9F01291D33C100022ED3 /* Sources */ = {
146 | isa = PBXSourcesBuildPhase;
147 | buildActionMask = 2147483647;
148 | files = (
149 | FC8A9F0D291D33C100022ED3 /* ViewController.swift in Sources */,
150 | FC8A9F09291D33C100022ED3 /* AppDelegate.swift in Sources */,
151 | FC8A9F0B291D33C100022ED3 /* SceneDelegate.swift in Sources */,
152 | );
153 | runOnlyForDeploymentPostprocessing = 0;
154 | };
155 | /* End PBXSourcesBuildPhase section */
156 |
157 | /* Begin PBXVariantGroup section */
158 | FC8A9F0E291D33C100022ED3 /* Main.storyboard */ = {
159 | isa = PBXVariantGroup;
160 | children = (
161 | FC8A9F0F291D33C100022ED3 /* Base */,
162 | );
163 | name = Main.storyboard;
164 | sourceTree = "";
165 | };
166 | FC8A9F13291D33C200022ED3 /* LaunchScreen.storyboard */ = {
167 | isa = PBXVariantGroup;
168 | children = (
169 | FC8A9F14291D33C200022ED3 /* Base */,
170 | );
171 | name = LaunchScreen.storyboard;
172 | sourceTree = "";
173 | };
174 | /* End PBXVariantGroup section */
175 |
176 | /* Begin XCBuildConfiguration section */
177 | FC8A9F17291D33C300022ED3 /* Debug */ = {
178 | isa = XCBuildConfiguration;
179 | buildSettings = {
180 | ALWAYS_SEARCH_USER_PATHS = NO;
181 | CLANG_ANALYZER_NONNULL = YES;
182 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
183 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
184 | CLANG_ENABLE_MODULES = YES;
185 | CLANG_ENABLE_OBJC_ARC = YES;
186 | CLANG_ENABLE_OBJC_WEAK = YES;
187 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
188 | CLANG_WARN_BOOL_CONVERSION = YES;
189 | CLANG_WARN_COMMA = YES;
190 | CLANG_WARN_CONSTANT_CONVERSION = YES;
191 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
192 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
193 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
194 | CLANG_WARN_EMPTY_BODY = YES;
195 | CLANG_WARN_ENUM_CONVERSION = YES;
196 | CLANG_WARN_INFINITE_RECURSION = YES;
197 | CLANG_WARN_INT_CONVERSION = YES;
198 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
199 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
200 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
201 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
202 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
203 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
204 | CLANG_WARN_STRICT_PROTOTYPES = YES;
205 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
206 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
207 | CLANG_WARN_UNREACHABLE_CODE = YES;
208 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
209 | COPY_PHASE_STRIP = NO;
210 | DEBUG_INFORMATION_FORMAT = dwarf;
211 | ENABLE_STRICT_OBJC_MSGSEND = YES;
212 | ENABLE_TESTABILITY = YES;
213 | GCC_C_LANGUAGE_STANDARD = gnu11;
214 | GCC_DYNAMIC_NO_PIC = NO;
215 | GCC_NO_COMMON_BLOCKS = YES;
216 | GCC_OPTIMIZATION_LEVEL = 0;
217 | GCC_PREPROCESSOR_DEFINITIONS = (
218 | "DEBUG=1",
219 | "$(inherited)",
220 | );
221 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
222 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
223 | GCC_WARN_UNDECLARED_SELECTOR = YES;
224 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
225 | GCC_WARN_UNUSED_FUNCTION = YES;
226 | GCC_WARN_UNUSED_VARIABLE = YES;
227 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
228 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
229 | MTL_FAST_MATH = YES;
230 | ONLY_ACTIVE_ARCH = YES;
231 | SDKROOT = iphoneos;
232 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
233 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
234 | };
235 | name = Debug;
236 | };
237 | FC8A9F18291D33C300022ED3 /* Release */ = {
238 | isa = XCBuildConfiguration;
239 | buildSettings = {
240 | ALWAYS_SEARCH_USER_PATHS = NO;
241 | CLANG_ANALYZER_NONNULL = YES;
242 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
243 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
244 | CLANG_ENABLE_MODULES = YES;
245 | CLANG_ENABLE_OBJC_ARC = YES;
246 | CLANG_ENABLE_OBJC_WEAK = YES;
247 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
248 | CLANG_WARN_BOOL_CONVERSION = YES;
249 | CLANG_WARN_COMMA = YES;
250 | CLANG_WARN_CONSTANT_CONVERSION = YES;
251 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
252 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
253 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
254 | CLANG_WARN_EMPTY_BODY = YES;
255 | CLANG_WARN_ENUM_CONVERSION = YES;
256 | CLANG_WARN_INFINITE_RECURSION = YES;
257 | CLANG_WARN_INT_CONVERSION = YES;
258 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
259 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
260 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
261 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
262 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
263 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
264 | CLANG_WARN_STRICT_PROTOTYPES = YES;
265 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
266 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
267 | CLANG_WARN_UNREACHABLE_CODE = YES;
268 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
269 | COPY_PHASE_STRIP = NO;
270 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
271 | ENABLE_NS_ASSERTIONS = NO;
272 | ENABLE_STRICT_OBJC_MSGSEND = YES;
273 | GCC_C_LANGUAGE_STANDARD = gnu11;
274 | GCC_NO_COMMON_BLOCKS = YES;
275 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
276 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
277 | GCC_WARN_UNDECLARED_SELECTOR = YES;
278 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
279 | GCC_WARN_UNUSED_FUNCTION = YES;
280 | GCC_WARN_UNUSED_VARIABLE = YES;
281 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
282 | MTL_ENABLE_DEBUG_INFO = NO;
283 | MTL_FAST_MATH = YES;
284 | SDKROOT = iphoneos;
285 | SWIFT_COMPILATION_MODE = wholemodule;
286 | SWIFT_OPTIMIZATION_LEVEL = "-O";
287 | VALIDATE_PRODUCT = YES;
288 | };
289 | name = Release;
290 | };
291 | FC8A9F1A291D33C300022ED3 /* Debug */ = {
292 | isa = XCBuildConfiguration;
293 | buildSettings = {
294 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
295 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
296 | CODE_SIGN_STYLE = Automatic;
297 | CURRENT_PROJECT_VERSION = 1;
298 | GENERATE_INFOPLIST_FILE = YES;
299 | INFOPLIST_FILE = "HypeUI-Example/Info.plist";
300 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
301 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
302 | INFOPLIST_KEY_UIMainStoryboardFile = Main;
303 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
304 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
305 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
306 | LD_RUNPATH_SEARCH_PATHS = (
307 | "$(inherited)",
308 | "@executable_path/Frameworks",
309 | );
310 | MARKETING_VERSION = 1.0;
311 | PRODUCT_BUNDLE_IDENTIFIER = "com.hpcnt.hypeui-example.HypeUI-Example";
312 | PRODUCT_NAME = "$(TARGET_NAME)";
313 | SWIFT_EMIT_LOC_STRINGS = YES;
314 | SWIFT_VERSION = 5.0;
315 | TARGETED_DEVICE_FAMILY = "1,2";
316 | };
317 | name = Debug;
318 | };
319 | FC8A9F1B291D33C300022ED3 /* Release */ = {
320 | isa = XCBuildConfiguration;
321 | buildSettings = {
322 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
323 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
324 | CODE_SIGN_STYLE = Automatic;
325 | CURRENT_PROJECT_VERSION = 1;
326 | GENERATE_INFOPLIST_FILE = YES;
327 | INFOPLIST_FILE = "HypeUI-Example/Info.plist";
328 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
329 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
330 | INFOPLIST_KEY_UIMainStoryboardFile = Main;
331 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
332 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
333 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
334 | LD_RUNPATH_SEARCH_PATHS = (
335 | "$(inherited)",
336 | "@executable_path/Frameworks",
337 | );
338 | MARKETING_VERSION = 1.0;
339 | PRODUCT_BUNDLE_IDENTIFIER = "com.hpcnt.hypeui-example.HypeUI-Example";
340 | PRODUCT_NAME = "$(TARGET_NAME)";
341 | SWIFT_EMIT_LOC_STRINGS = YES;
342 | SWIFT_VERSION = 5.0;
343 | TARGETED_DEVICE_FAMILY = "1,2";
344 | };
345 | name = Release;
346 | };
347 | /* End XCBuildConfiguration section */
348 |
349 | /* Begin XCConfigurationList section */
350 | FC8A9F00291D33C100022ED3 /* Build configuration list for PBXProject "HypeUI-Example" */ = {
351 | isa = XCConfigurationList;
352 | buildConfigurations = (
353 | FC8A9F17291D33C300022ED3 /* Debug */,
354 | FC8A9F18291D33C300022ED3 /* Release */,
355 | );
356 | defaultConfigurationIsVisible = 0;
357 | defaultConfigurationName = Release;
358 | };
359 | FC8A9F19291D33C300022ED3 /* Build configuration list for PBXNativeTarget "HypeUI-Example" */ = {
360 | isa = XCConfigurationList;
361 | buildConfigurations = (
362 | FC8A9F1A291D33C300022ED3 /* Debug */,
363 | FC8A9F1B291D33C300022ED3 /* Release */,
364 | );
365 | defaultConfigurationIsVisible = 0;
366 | defaultConfigurationName = Release;
367 | };
368 | /* End XCConfigurationList section */
369 |
370 | /* Begin XCRemoteSwiftPackageReference section */
371 | FC8A9F1C291D33FC00022ED3 /* XCRemoteSwiftPackageReference "HypeUI" */ = {
372 | isa = XCRemoteSwiftPackageReference;
373 | repositoryURL = "https://github.com/hyperconnect/HypeUI";
374 | requirement = {
375 | kind = upToNextMajorVersion;
376 | minimumVersion = 0.1.0;
377 | };
378 | };
379 | /* End XCRemoteSwiftPackageReference section */
380 |
381 | /* Begin XCSwiftPackageProductDependency section */
382 | FC8A9F1D291D33FC00022ED3 /* HypeUI */ = {
383 | isa = XCSwiftPackageProductDependency;
384 | package = FC8A9F1C291D33FC00022ED3 /* XCRemoteSwiftPackageReference "HypeUI" */;
385 | productName = HypeUI;
386 | };
387 | /* End XCSwiftPackageProductDependency section */
388 | };
389 | rootObject = FC8A9EFD291D33C100022ED3 /* Project object */;
390 | }
391 |
--------------------------------------------------------------------------------
/Example/HypeUI-Example/HypeUI-Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Example/HypeUI-Example/HypeUI-Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Example/HypeUI-Example/HypeUI-Example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "pins" : [
3 | {
4 | "identity" : "hypeui",
5 | "kind" : "remoteSourceControl",
6 | "location" : "https://github.com/hyperconnect/HypeUI",
7 | "state" : {
8 | "revision" : "f485ca4ffd96df345c5ecf6864d0ca8a172dea00",
9 | "version" : "0.3.0"
10 | }
11 | },
12 | {
13 | "identity" : "rxswift",
14 | "kind" : "remoteSourceControl",
15 | "location" : "https://github.com/ReactiveX/RxSwift.git",
16 | "state" : {
17 | "revision" : "b4307ba0b6425c0ba4178e138799946c3da594f8",
18 | "version" : "6.5.0"
19 | }
20 | },
21 | {
22 | "identity" : "snapkit",
23 | "kind" : "remoteSourceControl",
24 | "location" : "https://github.com/SnapKit/SnapKit.git",
25 | "state" : {
26 | "revision" : "f222cbdf325885926566172f6f5f06af95473158",
27 | "version" : "5.6.0"
28 | }
29 | }
30 | ],
31 | "version" : 2
32 | }
33 |
--------------------------------------------------------------------------------
/Example/HypeUI-Example/HypeUI-Example/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Hyperconnect Inc.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | import UIKit
18 |
19 | @main
20 | class AppDelegate: UIResponder, UIApplicationDelegate {
21 |
22 |
23 |
24 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
25 | // Override point for customization after application launch.
26 | return true
27 | }
28 |
29 | // MARK: UISceneSession Lifecycle
30 |
31 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
32 | // Called when a new scene session is being created.
33 | // Use this method to select a configuration to create the new scene with.
34 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
35 | }
36 |
37 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) {
38 | // Called when the user discards a scene session.
39 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
40 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
41 | }
42 |
43 |
44 | }
45 |
46 |
--------------------------------------------------------------------------------
/Example/HypeUI-Example/HypeUI-Example/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Example/HypeUI-Example/HypeUI-Example/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "platform" : "ios",
6 | "size" : "1024x1024"
7 | }
8 | ],
9 | "info" : {
10 | "author" : "xcode",
11 | "version" : 1
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Example/HypeUI-Example/HypeUI-Example/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Example/HypeUI-Example/HypeUI-Example/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 |
--------------------------------------------------------------------------------
/Example/HypeUI-Example/HypeUI-Example/Base.lproj/Main.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 |
--------------------------------------------------------------------------------
/Example/HypeUI-Example/HypeUI-Example/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | UIApplicationSceneManifest
6 |
7 | UIApplicationSupportsMultipleScenes
8 |
9 | UISceneConfigurations
10 |
11 | UIWindowSceneSessionRoleApplication
12 |
13 |
14 | UISceneConfigurationName
15 | Default Configuration
16 | UISceneDelegateClassName
17 | $(PRODUCT_MODULE_NAME).SceneDelegate
18 | UISceneStoryboardFile
19 | Main
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/Example/HypeUI-Example/HypeUI-Example/SceneDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Hyperconnect Inc.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | import UIKit
18 |
19 | class SceneDelegate: UIResponder, UIWindowSceneDelegate {
20 |
21 | var window: UIWindow?
22 |
23 |
24 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
25 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
26 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
27 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
28 | guard let _ = (scene as? UIWindowScene) else { return }
29 | }
30 |
31 | func sceneDidDisconnect(_ scene: UIScene) {
32 | // Called as the scene is being released by the system.
33 | // This occurs shortly after the scene enters the background, or when its session is discarded.
34 | // Release any resources associated with this scene that can be re-created the next time the scene connects.
35 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead).
36 | }
37 |
38 | func sceneDidBecomeActive(_ scene: UIScene) {
39 | // Called when the scene has moved from an inactive state to an active state.
40 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
41 | }
42 |
43 | func sceneWillResignActive(_ scene: UIScene) {
44 | // Called when the scene will move from an active state to an inactive state.
45 | // This may occur due to temporary interruptions (ex. an incoming phone call).
46 | }
47 |
48 | func sceneWillEnterForeground(_ scene: UIScene) {
49 | // Called as the scene transitions from the background to the foreground.
50 | // Use this method to undo the changes made on entering the background.
51 | }
52 |
53 | func sceneDidEnterBackground(_ scene: UIScene) {
54 | // Called as the scene transitions from the foreground to the background.
55 | // Use this method to save data, release shared resources, and store enough scene-specific state information
56 | // to restore the scene back to its current state.
57 | }
58 |
59 |
60 | }
61 |
62 |
--------------------------------------------------------------------------------
/Example/HypeUI-Example/HypeUI-Example/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Hyperconnect Inc.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | import UIKit
18 | import HypeUI
19 |
20 | class ViewController: UIViewController {
21 |
22 | override func viewDidLoad() {
23 | super.viewDidLoad()
24 |
25 | view.addSubviewWithFit(
26 | ScrollView(.vertical, showsIndicators: false) {
27 | VStack(spacing: 10) {
28 | Text("Text")
29 | .font(UIFont.systemFont(ofSize: 32, weight: .heavy))
30 | Spacer()
31 | .background(.systemGray5)
32 | .frame(height: 1)
33 | Text("Heading 1")
34 | .font(UIFont.systemFont(ofSize: 32, weight: .bold))
35 | .foregroundColor(UIColor.black)
36 | Text("Heading 2")
37 | .font(UIFont.systemFont(ofSize: 24, weight: .bold))
38 | Text("Heading 3")
39 | .font(UIFont.systemFont(ofSize: 16, weight: .bold))
40 | Text("Heavy")
41 | .font(UIFont.systemFont(ofSize: 16, weight: .heavy))
42 | Text("Bold")
43 | .font(UIFont.systemFont(ofSize: 16, weight: .bold))
44 | Text("Regular")
45 | .font(UIFont.systemFont(ofSize: 16, weight: .regular))
46 | Text("Light")
47 | .font(UIFont.systemFont(ofSize: 16, weight: .light))
48 | Text("Alignement Center")
49 | .font(UIFont.systemFont(ofSize: 16, weight: .light))
50 | .textAligned(.center)
51 | Text("🌺 HypeUI is a implementation of Apple's SwiftUI DSL style based on UIKit")
52 | .font(UIFont.systemFont(ofSize: 16, weight: .regular))
53 | .foregroundColor(UIColor.systemGray)
54 | .lineLimit(3)
55 | Spacer()
56 | .frame(height: 64)
57 | Text("Button")
58 | .font(UIFont.systemFont(ofSize: 32, weight: .heavy))
59 | Spacer()
60 | .background(.systemGray5)
61 | .frame(height: 1)
62 | Button(action: { print("🐠 Click Me!!") }) {
63 | Text("🐠 Click Me!!")
64 | .font(UIFont.systemFont(ofSize: 16, weight: .bold ))
65 | .foregroundColor(UIColor.white)
66 | .textAligned(.center)
67 | .padding(.horizontal, 24)
68 | .frame(height: 48)
69 | .background(.systemYellow)
70 | .cornerRadius(24)
71 | }.padding(.horizontal, 64)
72 | Button(action: { print("Border Button") }) {
73 | Text("Border Button")
74 | .font(UIFont.systemFont(ofSize: 16, weight: .bold ))
75 | .foregroundColor(UIColor.black)
76 | .textAligned(.center)
77 | .padding(.horizontal, 24)
78 | .frame(height: 48)
79 | .background(UIColor.white)
80 | .border(UIColor.black, width: 2)
81 | .cornerRadius(24)
82 | }.padding(.horizontal, 64)
83 | Button(action: { print("Stack Button")}) {
84 | ZStack {
85 | UIView()
86 | .background(.systemBlue)
87 | .frame(height: 48)
88 | .cornerRadius(24)
89 | HStack(alignment: .center) {
90 | VStack(alignment: .center, spacing: 1) {
91 | Text("Stack Button")
92 | .font(UIFont.systemFont(ofSize: 16, weight: .bold))
93 | .foregroundColor(UIColor.white)
94 | HStack(alignment: .center, spacing: 2) {
95 | Text("🌎")
96 | .font(UIFont.systemFont(ofSize: 12, weight: .medium))
97 | Text("Earth")
98 | .font(UIFont.systemFont(ofSize: 14, weight: .medium))
99 | .foregroundColor(UIColor.white)
100 | }
101 | }
102 | }
103 | }
104 | }.padding(.horizontal, 64)
105 | Spacer()
106 | .frame(height: 64)
107 | Text("ScrollView")
108 | .font(UIFont.systemFont(ofSize: 32, weight: .heavy))
109 | Spacer()
110 | .background(.systemGray5)
111 | .frame(height: 1)
112 | ScrollView(.horizontal, showsIndicators: false) {
113 | HStack(alignment: .center, spacing: 8) {
114 | ["Proactive", "One Team", "Aim High", "Priortize", "Move Fast", "Logical", "Open"].map {
115 | Text($0)
116 | .font(UIFont.systemFont(ofSize: 18, weight: .bold))
117 | .foregroundColor(UIColor.black)
118 | .textAligned(.center)
119 | .background(.systemGray6)
120 | .frame(width: 140, height: 64)
121 | .cornerRadius(32)
122 | }
123 | }
124 | }
125 | Spacer()
126 | .frame(height: 64)
127 | Text("View Modifier")
128 | .font(UIFont.systemFont(ofSize: 32, weight: .heavy))
129 | Spacer()
130 | .background(.systemGray5)
131 | .frame(height: 1)
132 | Text("Padding")
133 | .foregroundColor(UIColor.white)
134 | .textAligned(.center)
135 | .font(UIFont.systemFont(ofSize: 14, weight: .bold))
136 | .padding(.all, 12)
137 | .background(UIColor.systemRed)
138 | .padding(.all, 12)
139 | .background(UIColor.systemOrange)
140 | .padding(.all, 12)
141 | .background(UIColor.systemYellow)
142 | .padding(.all, 12)
143 | .background(UIColor.systemGreen)
144 | .padding(.all, 12)
145 | .background(UIColor.systemBlue)
146 | .padding(.all, 12)
147 | .background(UIColor.systemIndigo)
148 | .padding(.all, 12)
149 | .background(UIColor.systemPurple)
150 | Text("Border")
151 | .foregroundColor(UIColor.white)
152 | .textAligned(.center)
153 | .font(UIFont.systemFont(ofSize: 14, weight: .bold))
154 | .padding(.all, 12)
155 | .background(UIColor.systemGray)
156 | .border(UIColor.black, width: 2)
157 | }
158 | .padding(.all, 24)
159 | }
160 | )
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/HypeUI.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = "HypeUI"
3 | s.version = "0.3.0"
4 | s.summary = "HypeUI is a implementation of Apple's SwiftUI DSL style based on UIKit"
5 | s.homepage = "https://github.com/hyperconnect/HypeUI"
6 | s.license = { :type => 'Apache 2.0', :file => "LICENSE" }
7 | s.author = { 'Cruz' => 'cruz@hpcnt.com', 'Xeon' => 'xeon@hpcnt.com' }
8 | s.source = { :git => 'https://github.com/hyperconnect/HypeUI.git', :tag => s.version.to_s }
9 | s.ios.deployment_target = '12.0'
10 | s.source_files = 'Sources/**/*.swift'
11 | s.swift_version = '5.0'
12 | s.dependency "RxSwift", '~> 6.0'
13 | s.dependency "RxCocoa", '~> 6.0'
14 | s.dependency "SnapKit", '~> 5.0.0'
15 | end
16 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "RxSwift",
6 | "repositoryURL": "https://github.com/ReactiveX/RxSwift.git",
7 | "state": {
8 | "branch": null,
9 | "revision": "b4307ba0b6425c0ba4178e138799946c3da594f8",
10 | "version": "6.5.0"
11 | }
12 | },
13 | {
14 | "package": "SnapKit",
15 | "repositoryURL": "https://github.com/SnapKit/SnapKit.git",
16 | "state": {
17 | "branch": null,
18 | "revision": "f222cbdf325885926566172f6f5f06af95473158",
19 | "version": "5.6.0"
20 | }
21 | }
22 | ]
23 | },
24 | "version": 1
25 | }
26 |
27 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.1
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "HypeUI",
7 | platforms: [
8 | .iOS(.v12)
9 | ],
10 | products: [
11 | .library(name: "HypeUI", targets: ["HypeUI"]),
12 | ],
13 | dependencies: [
14 | .package(url: "https://github.com/ReactiveX/RxSwift.git", .upToNextMajor(from: "6.2.0")),
15 | .package(url: "https://github.com/SnapKit/SnapKit.git", .upToNextMajor(from: "5.6.0")),
16 | ],
17 | targets: [
18 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
19 | // Targets can depend on other targets in this package, and on products in packages which this package depends on.
20 | .target(
21 | name: "HypeUI",
22 | dependencies: ["RxSwift", "RxCocoa", "SnapKit"]),
23 | .testTarget(
24 | name: "HypeUITests",
25 | dependencies: ["HypeUI"]),
26 | ]
27 | )
28 |
29 |
30 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # HypeUI
2 | 🚀 HypeUI is a implementation of Apple's SwiftUI DSL style based on UIKit
3 | Want to enjoy SwiftUI syntax with UIKit? It's time to use HypeUI 😊
4 |
5 | [](https://github.com/sindresorhus/awesome)
6 | 
7 |
8 | | | Why to use HypeUI? |
9 | ---|-----------------
10 | 📱 | Support iOS 12+
11 | ✨ | HypeUI is compatible with UIKit based project using SwiftUI style syntax
12 | 🏄♂️ | Reduce UI & Autolayout codes more than 30%
13 | ⛳️ | Provide UI binding extension with RxSwift and RxCocoa
14 | 🦄 | Easy to use!
15 | ✈️ | Improve readability and intuitiveness of complex layouts
16 | 🤩 | Have a blast
17 | ⛷ | Customize reusable component, design system
18 | ❄️ | Test with accessibility Identifier
19 |
20 | ## Contents
21 |
22 | - [Requirements](#requirements)
23 | - [Installation](#installation)
24 | - [Supported Features](#supported_features)
25 | * [View Modifier](#view_modifier)
26 | * [Text Modifier](#text_modifier)
27 | * [Stack Modifier](#stack_modifier)
28 | * [ScrollView Modifier](#scrollview_modifier)
29 | - [Usage](#usage)
30 | * [HStack](#hstack)
31 | * [VStack](#vstack)
32 | * [ZStack](#zstack)
33 | * [Button](#button)
34 | * [Text](#text)
35 | * [Image](#image)
36 | * [ScrollView](#scrollview)
37 | * [Behavior](#behavior)
38 | * [Spacer](#spacer)
39 | * [LinearGradient](#lineargradient)
40 | * [ViewBuildable](#viewbuildable)
41 | - [Dependencies](#dependencies)
42 |
43 | ## Requirements
44 |
45 | - iOS 12.0+
46 | - XCode 13.0+
47 | - Swift 5.0+
48 |
49 | ## Installation
50 |
51 | ### Swift Package Manager
52 |
53 | [Swift Package Manager](https://swift.org/package-manager/) is a tool for managing the distribution of Swift code. It’s integrated with the Swift build system to automate the process of downloading, compiling, and linking dependencies.
54 |
55 | > Xcode 13+ is required to build HypeUI using Swift Package Manager.
56 |
57 | To integrate HypeUI into your Xcode project using Swift Package Manager, add it to the dependencies value of your `Package.swift`:
58 |
59 | ```swift
60 | dependencies: [
61 | .package(url: "https://github.com/hyperconnect/HypeUI", .upToNextMajor(from: "0.3.0"))
62 | ]
63 | ```
64 |
65 | ### CocoaPod
66 |
67 | The preferred installation method is with [CocoaPods](https://cocoapods.org). Add the following to your `Podfile`:
68 |
69 | ```ruby
70 | pod 'HypeUI'
71 | ```
72 |
73 | ## Supported Features
74 |
75 | | | Supported Features |
76 | ----------------|----------------
77 | HStack | ✅
78 | VStack | ✅
79 | ZStack | ✅
80 | Button | ✅
81 | Text | ✅
82 | Image | ✅
83 | ScrollView | ✅
84 | Behavior | ✅
85 | Spacer | ✅
86 | LinearGradient | ✅
87 | AlignmentView | ✅
88 | ViewBuildable | ✅
89 | View Modifier | ✅
90 | Text Modifier | ✅
91 | Stack Modifier | ✅
92 | ScrollView Modifier | ✅
93 |
94 |
95 | ### View Modifier
96 |
97 | | name | Description |
98 | ---------------------------------|----------------
99 | setHContentHugging | Adjusts the priority for a view to resist growing beyond its intrinsic size horizontally.
100 | setVContentHugging | Adjusts the priority for a view to resist growing beyond its intrinsic size vertically.
101 | setHContentCompressionResistance | Adjusts the priority for a view to resist shrinking below its intrinsic size horizontally.
102 | setVContentCompressionResistance | Adjusts the priority for a view to resist shrinking below its intrinsic size vertically.
103 | makeRatio | Sets the aspect ratio constraint for the view's size.
104 | cornerRadius | Applies a corner radius to the view to create rounded corners.
105 | border | Adds a border with specified color and width to the view.
106 | background | Sets the background color of the view.
107 | makeContentMode | Sets the content mode of the view.
108 | frame | Positions the view within a specified frame size.
109 | padding | Adds padding around specific edges of the view.
110 | allowsHitTesting | Enables or disables the view's interaction with touch events.
111 | masksToBounds | Clips the view's sublayers to its boundaries.
112 | accessibilityIdentifier | Assigns an identifier used to find this view in tests.
113 | overlay | Places specified views in front of the view.
114 | background | Layers the views that you specify behind this view.
115 | center | Centers the view within a new parent view.
116 | tint | Applies a tint color to the view.
117 | opacity | Sets the transparency level of the view.
118 | scaleEffect | Scales the view by specified factors along the x and y axes.
119 | rotationEffect | Rotates the view by a specified angle around a given anchor point.
120 |
121 |
122 | ### Text Modifier
123 |
124 | | name | Description |
125 | ---------------------------------|----------------
126 | font | Sets the font of the text.
127 | foregroundColor | Applies a foreground color to the text.
128 | textAligned | Sets the alignment of the text within its container.
129 | lineLimit | Specifies the maximum number of lines the text can span.
130 | lineBreakMode | Defines how text wraps when it reaches the edge of its container.
131 | adjustFontSize | Adjusts the font size of the text to fit its width.
132 | minimumScaleFactor | Sets the smallest multiplier for text size reduction to fit the width.
133 | preferredMaxLayoutWidth | Sets the preferred maximum width for the Text object and enables method chaining.
134 | baselineAdjusted | Applies a baseline adjustment to the Text object and enables method chaining.
135 |
136 |
137 | ### Stack Modifier
138 |
139 | | name | Description |
140 | ---------------------------------|----------------
141 | distributed | Modify stack's distribution layout.
142 |
143 |
144 | ### ScrollView Modifier
145 |
146 | | name | Description |
147 | ---------------------------------|----------------
148 | bounces | Modify scroll view bounces.
149 | isPagingEnabled | Modify scroll view paging enabled.
150 | isScrollEnabled | Modify scroll view enabled.
151 |
152 | ## Usage
153 |
154 | ### HStack
155 |
156 | ```swift
157 | HStack(alinement: .center, spacing: 4) {
158 | Image(Asset.icStar.image)
159 | .frame(width: 12, height: 12)
160 | Text()
161 | .foregroundColor(UIColor.black)
162 | .font(UIFont.systemFont(ofSize: 14, weight: .regular))
163 | Spacer()
164 | }
165 | ```
166 |
167 | ### VStack
168 |
169 | ```swift
170 | VStack(spacing: 8) {
171 | Text()
172 | .foregroundColor(UIColor.black)
173 | .font(UIFont.systemFont(ofSize: 14, weight: .regular))
174 | Spacer()
175 | }
176 | ```
177 |
178 | ### ZStack
179 |
180 | ```swift
181 | ZStack {
182 | HStack(alinement: .center, spacing: 4) {
183 | Image(Asset.icStar.image)
184 | .frame(width: 12, height: 12)
185 | Text()
186 | .foregroundColor(UIColor.black)
187 | .font(UIFont.systemFont(ofSize: 14, weight: .regular))
188 | Spacer()
189 | }
190 | VStack {
191 | Text()
192 | .foregroundColor(UIColor.black)
193 | .font(UIFont.systemFont(ofSize: 14, weight: .regular))
194 | Spacer()
195 | }
196 | }
197 | ```
198 |
199 | ### Button
200 |
201 | ```swift
202 | Button(action: { // DO SOMETHING ex) reactor action, closure }) {
203 | HStack(alignment: .center, spacing: 5.0) {
204 | Image("cart")
205 | .padding(.leading, 10.0)
206 | Text("Add to Cart")
207 | .foregroundColor(.black)
208 | .padding(.all, 10.0)
209 | }
210 | }
211 | .background(Color.gray)
212 | .cornerRadius(5)
213 | ```
214 |
215 | ### Text
216 |
217 | ```swift
218 | Text("✨")
219 | .foregroundColor(UIColor.black)
220 | .font(UIFont.systemFont(ofSize: 14, weight: .regular))
221 | .textAligned(.center)
222 | .background(.white)
223 | .cornerRadius(16)
224 | ```
225 |
226 | ### Image
227 |
228 | ```swift
229 | Image(Resource.Profile.placeholderImage)
230 | .frame(width: 48, height: 48)
231 | .cornerRadius(24)
232 | ```
233 |
234 | ### ScrollView
235 |
236 | ```swift
237 | // MARK: Example
238 | ScrollView(.vertical, showsIndicators: false) {
239 | VStack(alignment: .fill) {
240 | Image(image: Asset.imgPopupPrivateCall.image)
241 | .makeRatio(0.46106)
242 | Spacer()
243 | .frame(height: 24)
244 | VStack {
245 | viewModel.messages.map { message in
246 | HStack(alignment: .top, spacing: 8) {
247 | Text("•")
248 | .font(UIFont.systemFont(ofSize: 14, weight: .regular))
249 | .foregroundColor(.Palette.gray04)
250 | .frame(width: 6)
251 | Text(message)
252 | .font(UIFont.systemFont(ofSize: 14, weight: .regular))
253 | .foregroundColor(.Palette.gray04)
254 | .lineLimit(2)
255 | .lineBreakMode(.byCharWrapping)
256 | Spacer()
257 | .frame(width: 5)
258 | }
259 | .padding(.vertical, 8)
260 | }
261 | }
262 | Spacer()
263 | .frame(height: 16)
264 | }
265 | }
266 | ```
267 |
268 | ### @Behavior - It's seems like SwiftUI's @State using DynamicLinkable 😎
269 |
270 | ```swift
271 | @Behavior var isLive: Bool = false
272 | @Behavior var username: String? = nil
273 | @Behavior var profileImageURL: URL? = nil
274 |
275 | // MARK Example
276 | final class SearchHostHistoryViewCell: UICollectionViewCell {
277 | @Behavior var isLive: Bool = false
278 | @Behavior var username: String? = nil
279 | @Behavior var profileImageURL: URL? = nil
280 |
281 | override init(frame: CGRect) {
282 | super.init(frame: frame)
283 | contentView.addSubviewWithFit(
284 | ZStack {
285 | VStack(alignment: .center, spacing: 8) {
286 | Image(Resource.Profile.placeholderImage)
287 | .linked($profileImageURL.flatMapLatest { $0?.getImage(failover: Resource.Profile.placeholderImage) ?? .just(Resource.Profile.placeholderImage) }, keyPath: \.image)
288 | .makeContentMode(.scaleAspectFill)
289 | .frame(width: 48, height: 48)
290 | .cornerRadius(24)
291 | .background(.Palette.gray05)
292 | Text("")
293 | .linked($username, keyPath: \.text)
294 | .textAligned(.center)
295 | .foregroundColor(.darkModeSupporting(.Palette.gray01, .Palette.dkGray01))
296 | .font(UIFont.systemFont(ofSize: 10, weight: .regular))
297 | .frame(height: 12)
298 | }.padding(UIEdgeInsets(top: 12, left: 0, bottom: 4, right: 0))
299 | VStack(alignment: .center) {
300 | Spacer()
301 | Text("LIVE")
302 | .foregroundColor(.Palette.white)
303 | .font(UIFont.systemFont(ofSize: 8, weight: .bold))
304 | .padding(UIEdgeInsets(top: 2, left: 4, bottom: 2, right: 4))
305 | .background(.Palette.red)
306 | .cornerRadius(4)
307 | .linked($isLive.not(), keyPath: \.isHidden)
308 | Spacer()
309 | .frame(height: 20)
310 | }
311 | }
312 | )
313 | }
314 |
315 | required init?(coder: NSCoder) {
316 | fatalError("init(coder:) has not been implemented")
317 | }
318 | }
319 | ```
320 |
321 | ### Spacer
322 | ```swift
323 | Spacer()
324 | .frame(width: 10)
325 | Spacer()
326 | .frame(height: 20)
327 | ```
328 |
329 | ### LinearGradient
330 | ```
331 | ProfileView()
332 | .background(
333 | LinearGradient(
334 | gradient: Gradient(
335 | stops: [
336 | Stop(color: UIColor.black, location: 1.0),
337 | Stop(color: UIColor.black, location: 0.2),
338 | Stop(color: UIColor.black, location: 0.0)
339 | ]),
340 | startPoint: .top,
341 | endPoint: .bottom
342 | )
343 | )
344 | ```
345 |
346 | ### ViewBuildable - Customize UI, Make reusable component and Design System by confirming ViewBuildable protocol.
347 |
348 | ```swift
349 | struct ProfileView: ViewBuildable {
350 | @Behavior var country: String
351 | @Behavior var name: String
352 |
353 | func build() -> UIView {
354 | VStack {
355 | HStack(alignment: .center, spacing: 12) {
356 | Text("")
357 | .linked($country, keyPath: \.text)
358 | .font(UIFont.systemFont(ofSize: 20, weight: .regular))
359 | .accessibilityIdentifier("country")
360 | Text("")
361 | .linked($name, keyPath: \.text)
362 | .font(UIFont.systemFont(ofSize: 20, weight: .regular))
363 | .accessibilityIdentifier("name")
364 | }
365 | }
366 | }
367 | }
368 | ```
369 |
370 | ## Main Contributors
371 | cruz@hpcnt.com
372 | xeon@hpcnt.com
373 | owen.j@hpcnt.com
374 | dough@hpcnt.com
375 |
376 | ## Dependencies
377 |
378 | - [RxSwift](https://github.com/ReactiveX/RxSwift)
379 | - [RxCocoa](https://github.com/ReactiveX/RxSwift/tree/main/RxCocoa)
380 | - [SnapKit](https://github.com/SnapKit/SnapKit)
381 |
--------------------------------------------------------------------------------
/Sources/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyperconnect/HypeUI/8e1cd9a00cd615eb5283d29fcf656ebf1022d1ee/Sources/.DS_Store
--------------------------------------------------------------------------------
/Sources/HypeUI/Alignment.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Hyperconnect Inc.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | import UIKit
18 |
19 | // MARK: - Alignment
20 |
21 | public enum Alignment {
22 | case topLeading
23 | case top
24 | case topTrailing
25 | case leading
26 | case center
27 | case trailing
28 | case bottomLeading
29 | case bottom
30 | case bottomTrailing
31 |
32 | var isLeading: Bool {
33 | switch self {
34 | case .topLeading, .leading, .bottomLeading:
35 | return true
36 | default:
37 | return false
38 | }
39 | }
40 |
41 | var isTop: Bool {
42 | switch self {
43 | case .topLeading, .top, .topTrailing:
44 | return true
45 | default:
46 | return false
47 | }
48 | }
49 |
50 | var isTrailing: Bool {
51 | switch self {
52 | case .topTrailing, .trailing, .bottomTrailing:
53 | return true
54 | default:
55 | return false
56 | }
57 | }
58 |
59 | var isBottom: Bool {
60 | switch self {
61 | case .bottomLeading, .bottom, .bottomTrailing:
62 | return true
63 | default:
64 | return false
65 | }
66 | }
67 |
68 | var horizontalAlignment: UIStackView.Alignment {
69 | if isTop {
70 | return .top
71 | } else if isBottom {
72 | return .bottom
73 | } else {
74 | return .center
75 | }
76 | }
77 |
78 | var verticalAlignment: UIStackView.Alignment {
79 | if isLeading {
80 | return .leading
81 | } else if isTrailing {
82 | return .trailing
83 | } else {
84 | return .center
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/Sources/HypeUI/AlignmentView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Hyperconnect Inc.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | import UIKit
18 |
19 | // MARK: - AlignmentView
20 |
21 | public struct AlignmentView {
22 | private let alignment: Alignment
23 | private let view: ViewBuildable
24 |
25 | init(alignment: Alignment, view: ViewBuildable) {
26 | self.alignment = alignment
27 | self.view = view
28 | }
29 | }
30 |
31 | // MARK: - AlignmentView (ViewBuildable)
32 |
33 | extension AlignmentView: ViewBuildable {
34 | public func build() -> UIView {
35 | HStack(alignment: alignment.horizontalAlignment) {
36 | Spacer()
37 | .linked(.just(!alignment.isBottom), keyPath: \.isHidden)
38 | VStack(alignment: alignment.verticalAlignment) {
39 | Spacer()
40 | .linked(.just(!alignment.isTrailing), keyPath: \.isHidden)
41 | view
42 | Spacer()
43 | .linked(.just(!alignment.isLeading), keyPath: \.isHidden)
44 | }
45 | Spacer()
46 | .linked(.just(!alignment.isTop), keyPath: \.isHidden)
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Sources/HypeUI/Behavior.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Hyperconnect Inc.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | import Foundation
18 | import RxSwift
19 | import RxCocoa
20 |
21 | // MARK: - Behavior
22 |
23 | @propertyWrapper
24 | public final class Behavior {
25 |
26 | private let internalStore: BehaviorRelay
27 |
28 | public init(wrappedValue initialValue: Value) {
29 | self.internalStore = .init(value: initialValue)
30 | }
31 |
32 | public var projectedValue: Observable {
33 | internalStore.asObservable()
34 | }
35 |
36 | public var wrappedValue: Value {
37 | set {
38 | internalStore.accept(newValue)
39 | }
40 |
41 | get {
42 | internalStore.value
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Sources/HypeUI/Button.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Hyperconnect Inc.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | import UIKit
18 | import RxSwift
19 | import RxCocoa
20 |
21 | public typealias Button = UIButton
22 |
23 | // MARK: - Button
24 |
25 | public extension Button {
26 |
27 | /// Button with action closure.
28 | /// - Parameter action: Escaping action closure.
29 | convenience init(action: @escaping () -> Void) {
30 | self.init()
31 | _ = rx.tap.subscribe(onNext: action)
32 | }
33 |
34 | /// Button with action closure.
35 | /// - Parameters:
36 | /// - action: Escaping action closure.
37 | /// - view: Buildable view for button.
38 | convenience init(action: @escaping () -> Void, _ view: () -> ViewBuildable) {
39 | self.init()
40 | let buttonView = view().build()
41 | _ = rx.tap
42 | .map { Void() }
43 | .subscribe(onNext: action)
44 | self.addSubviewWithFit(buttonView.allowsHitTesting(false))
45 |
46 | _ = Observable
47 | .merge(
48 | rx.controlEvent(.touchDown).map { 0.5 },
49 | rx.controlEvent(.touchUpInside).map { 1.0 },
50 | rx.controlEvent(.touchUpOutside).map { 1.0 },
51 | rx.controlEvent(.touchDragInside).map { 0.5 },
52 | rx.controlEvent(.touchDragOutside).map { 1.0 }
53 | )
54 | .bind(to: buttonView.rx.alpha)
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/Sources/HypeUI/DynamicLinkable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Hyperconnect Inc.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | import UIKit
18 | import RxSwift
19 | import RxCocoa
20 |
21 | // MARK: - DynamicLinkable (protocol)
22 |
23 | public protocol DynamicLinkable: NSObject {}
24 |
25 | // MARK: DynamicLinkable
26 |
27 | public extension DynamicLinkable {
28 | /// Bind observable to dynamic member of key path.
29 | /// - Parameters:
30 | /// - stream: Observable for binding.
31 | /// - keyPath: A key path that supports reading from and writing to the resulting value with reference semantics.
32 | /// - Returns: Binded view.
33 | func linked(_ stream: Observable, keyPath: ReferenceWritableKeyPath) -> Self {
34 | let disposeBag = DisposeBag()
35 |
36 | stream.bind(to: self.rx[dynamicMember: keyPath])
37 | .disposed(by: disposeBag)
38 |
39 | retain(disposeBag)
40 |
41 | return self
42 | }
43 |
44 | /// Bind observable to dynamic member of key path.
45 | /// - Parameters:
46 | /// - stream: Observable for binding.
47 | /// - keyPath: A key path that supports reading from and writing to the resulting value with reference semantics for optional.
48 | /// - Returns: Binded view.
49 | func linked(_ stream: Observable, keyPath: ReferenceWritableKeyPath) -> Self {
50 | let disposeBag = DisposeBag()
51 |
52 | stream.bind(to: self.rx[dynamicMember: keyPath])
53 | .disposed(by: disposeBag)
54 | retain(disposeBag)
55 |
56 | return self
57 | }
58 |
59 | /// Bind observable to dynamic member of key path.
60 | /// - Parameters:
61 | /// - stream: Observable for binding.
62 | /// - binder: RxCocoa Binder.
63 | /// - Returns: Binded view.
64 | func linked(_ stream: Observable, binder: (Self) -> Binder) -> Self {
65 | let disposeBag = DisposeBag()
66 |
67 | stream.bind(to: binder(self))
68 | .disposed(by: disposeBag)
69 |
70 | retain(disposeBag)
71 |
72 | return self
73 | }
74 | }
75 |
76 | // MARK: UIView (DynamicLinkable)
77 |
78 | extension UIView: DynamicLinkable {}
79 |
80 | // MARK: CALayer (DynamicLinkable)
81 |
82 | extension CALayer: DynamicLinkable {}
83 |
--------------------------------------------------------------------------------
/Sources/HypeUI/Edge.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Hyperconnect Inc.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | import UIKit
18 |
19 | // MARK: - Edge
20 |
21 | public enum Edge: Int8 {
22 | case top
23 | case leading
24 | case bottom
25 | case trailing
26 |
27 | // MARK: Set
28 |
29 | public struct Set: OptionSet {
30 | public let rawValue: Int8
31 |
32 | public static let top: Set = .init(rawValue: 1 << 0)
33 |
34 | public static let leading: Set = .init(rawValue: 1 << 1)
35 |
36 | public static let bottom: Set = .init(rawValue: 1 << 2)
37 |
38 | public static let trailing: Set = .init(rawValue: 1 << 3)
39 |
40 | public static let all: Set = [.top, .leading, .bottom, .trailing]
41 |
42 | public static let horizontal: Set = [.leading, .trailing]
43 |
44 | public static let vertical: Set = [.top, .bottom]
45 |
46 | public init(rawValue: Int8) {
47 | self.rawValue = rawValue
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Sources/HypeUI/Gradient.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Hyperconnect Inc.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | import UIKit
18 |
19 | // MARK: - Gradient
20 |
21 | public struct Gradient {
22 |
23 | /// One color stop in the gradient
24 |
25 | public struct Stop {
26 | var color: UIColor
27 | var location: CGFloat
28 | }
29 |
30 | public let stops: [Stop]
31 |
32 | public init(stops: [Stop]) {
33 | self.stops = stops
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Sources/HypeUI/GradientView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Hyperconnect Inc.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | import UIKit
18 |
19 | // MARK: - GradientView
20 |
21 | public class GradientView: UIView {
22 | public override class var layerClass: AnyClass {
23 | return CAGradientLayer.self
24 | }
25 |
26 | internal var gradientLayer: CAGradientLayer {
27 | // swiftlint:disable force_cast
28 | return layer as! CAGradientLayer
29 | // swiftlint:enable force_cast
30 | }
31 |
32 | private func update() {
33 | gradientLayer.colors = gradient.stops.map { $0.color.cgColor }
34 | gradientLayer.locations = gradient.stops.map { $0.location as NSNumber }
35 | }
36 |
37 | /// Support dark mode for gradient layer
38 | /// - Parameter previousTraitCollection: A trait collection encapsulates the system traits of an interface's environment.
39 | override public func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
40 | super.traitCollectionDidChange(previousTraitCollection)
41 | update()
42 | }
43 |
44 | // MARK: Privates
45 |
46 | private let gradient: Gradient
47 |
48 | public init(gradient: Gradient) {
49 | self.gradient = gradient
50 | super.init(frame: .zero)
51 | update()
52 | }
53 |
54 | required init?(coder: NSCoder) {
55 | fatalError("init(coder:) has not been implemented")
56 | }
57 | }
58 |
59 | // MARK: - GradientView (Modifier)
60 |
61 | public extension GradientView {
62 | func startPoint(_ point: UnitPoint) -> Self {
63 | self.gradientLayer.startPoint = point
64 | return self
65 | }
66 |
67 | func endPoint(_ point: UnitPoint) -> Self {
68 | self.gradientLayer.endPoint = point
69 | return self
70 | }
71 |
72 | func type(_ type: CAGradientLayerType) -> Self {
73 | self.gradientLayer.type = type
74 | return self
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/Sources/HypeUI/HStack.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Hyperconnect Inc.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | import UIKit
18 |
19 | /// A view that arranges its subviews in a horizontal line.
20 | /// - Parameters:
21 | /// - alignment: The layout of arranged views perpendicular to the stack view’s axis.
22 | /// - spacing: The distance in points between the adjacent edges of the stack view’s arranged views.
23 | /// - content: Build a views from variadic child views
24 | /// - Returns: horizontal stack view
25 | public func HStack(alignment: UIStackView.Alignment = .fill, spacing: CGFloat = 0.0, @ViewArrayBuilder _ content: () -> [UIView]) -> UIStackView {
26 | let stackView = PassthroughStackView(arrangedSubviews: content())
27 | stackView.axis = .horizontal
28 | stackView.alignment = alignment
29 | stackView.spacing = spacing
30 | return stackView
31 | }
32 |
--------------------------------------------------------------------------------
/Sources/HypeUI/Image.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Hyperconnect Inc.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | import UIKit
18 |
19 | public typealias Image = UIImageView
20 |
21 | // MARK: - Image
22 |
23 | public extension Image {
24 |
25 | /// A view that displays an image.
26 | /// - Parameter image: (optional) An object that manages image data in your app.
27 | convenience init(_ image: UIImage?) {
28 | self.init()
29 | self.image = image
30 | }
31 |
32 | /// Modify image.
33 | /// - Parameter image: An object that manages image data in your app.
34 | /// - Returns: Modified image.
35 | func imaged(_ image: UIImage) -> Self {
36 | self.image = image
37 | return self
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Sources/HypeUI/LinearGradient.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Hyperconnect Inc.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | import UIKit
18 |
19 | // MARK: - LinearGradient (ViewBuildable)
20 |
21 | public struct LinearGradient: ViewBuildable {
22 | public let gradient: Gradient
23 | public let startPoint: UnitPoint
24 | public let endPoint: UnitPoint
25 |
26 | /// A linear gradient.
27 | /// - Parameters:
28 | /// - gradient: A color gradient represented as an array of color stops, each having a parametric location value.
29 | /// - startPoint: Start point of gradient.
30 | /// - endPoint: End point of gradient.
31 | public init(gradient: Gradient, startPoint: UnitPoint, endPoint: UnitPoint) {
32 | self.gradient = gradient
33 | self.startPoint = startPoint
34 | self.endPoint = endPoint
35 | }
36 |
37 | public func build() -> UIView {
38 | GradientView(gradient: gradient)
39 | .startPoint(startPoint)
40 | .endPoint(endPoint)
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Sources/HypeUI/NSObject+Retain.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Hyperconnect Inc.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | import Foundation
18 |
19 | private var associatedObjectHandle: UInt8 = 0
20 |
21 | // MARK: - NSObject (retain)
22 |
23 | public extension NSObject {
24 |
25 | /// Increments the receiver’s reference count.
26 | /// - Parameter object: receiver
27 | func retain(_ object: Any) {
28 | let bag: NSMutableArray
29 | if let associated = objc_getAssociatedObject(self, &associatedObjectHandle) as? NSMutableArray {
30 | bag = associated
31 | } else {
32 | bag = NSMutableArray()
33 | objc_setAssociatedObject(self, &associatedObjectHandle, bag, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
34 | }
35 |
36 | bag.add(object)
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Sources/HypeUI/PassthroughStackView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Hyperconnect Inc.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | import UIKit
18 |
19 | // MARK: - PassthroughStackView
20 |
21 | public final class PassthroughStackView: UIStackView {
22 | public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
23 | return passthrough(point, with: event)
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Sources/HypeUI/PassthroughView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Hyperconnect Inc.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | import UIKit
18 |
19 | // MARK: - PassthroughView
20 |
21 | public class PassthroughView: UIView {
22 | public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
23 | return passthrough(point, with: event)
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Sources/HypeUI/ScrollView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Hyperconnect Inc.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | import UIKit
18 |
19 | // MARK: - Axis
20 |
21 | public enum Axis {
22 | case vertical
23 | case horizontal
24 | }
25 |
26 | // MARK: - ScrollView
27 |
28 | // swiftlint:disable identifier_name
29 |
30 | /// A scrollable view.
31 | /// - Parameters:
32 | /// - axis: The scrollable axes of the scroll view.
33 | /// - showsIndicators: A value that indicates whether the scroll view displays the scrollable component of the content offset, in a way that’s suitable for the platform.
34 | /// - content: The buildable content and behavior of the scroll view.
35 | /// - Returns: A scrollable view.
36 | public func ScrollView(_ axis: Axis, showsIndicators: Bool = true, content: () -> ViewBuildable) -> UIScrollView {
37 | let scrollView = UIScrollView()
38 | scrollView.showsVerticalScrollIndicator = showsIndicators
39 | scrollView.showsHorizontalScrollIndicator = showsIndicators
40 |
41 | let contentView = content().build()
42 | scrollView.addSubview(contentView)
43 | contentView.snp.makeConstraints { maker in
44 | maker.directionalEdges.equalToSuperview()
45 | switch axis {
46 | case .horizontal:
47 | maker.height.equalToSuperview()
48 | maker.width.equalToSuperview().priority(.low)
49 | maker.width.greaterThanOrEqualToSuperview()
50 | case .vertical:
51 | maker.width.equalToSuperview()
52 | maker.height.equalToSuperview().priority(.low)
53 | maker.height.greaterThanOrEqualToSuperview()
54 | }
55 | }
56 | return scrollView
57 | }
58 | // swiftlint:enable identifier_name
59 |
60 | // MARK: ScrollView
61 |
62 | public extension UIScrollView {
63 | /// Modify scroll view bounces.
64 | /// - Parameter bounces: A Boolean value that controls whether the scroll view bounces past the edge of content and back again.
65 | /// - Returns: Modified scroll view.
66 | func bounces(_ bounces: Bool) -> Self {
67 | self.bounces = bounces
68 | return self
69 | }
70 |
71 | /// Modify scroll view paging enabled.
72 | /// - Parameter isPagingEnabled: A Boolean value that determines whether paging is enabled for the scroll view.
73 | /// - Returns: Modified scroll view.
74 | func isPagingEnabled(_ isPagingEnabled: Bool) -> Self {
75 | self.isPagingEnabled = isPagingEnabled
76 | return self
77 | }
78 |
79 | /// Modify scroll view enabled.
80 | /// - Parameter isScrollEnabled: A Boolean value that determines whether scrolling is enabled.
81 | /// - Returns: Modified scroll view.
82 | func isScrollEnabled(_ isScrollEnabled: Bool) -> Self {
83 | self.isScrollEnabled = isScrollEnabled
84 | return self
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/Sources/HypeUI/Spacer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Hyperconnect Inc.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | import UIKit
18 |
19 | // MARK: - Spacer
20 |
21 | public final class Spacer: PassthroughView {
22 | public init() {
23 | super.init(frame: .zero)
24 |
25 | setContentHuggingPriority(.fittingSizeLevel, for: .vertical)
26 | setContentHuggingPriority(.fittingSizeLevel, for: .horizontal)
27 | setContentCompressionResistancePriority(.defaultLow, for: .vertical)
28 | setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
29 | }
30 |
31 | @available(*, deprecated)
32 | public required init?(coder: NSCoder) {
33 | fatalError("init(coder:) has not been implemented")
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Sources/HypeUI/Text.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Hyperconnect Inc.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | import UIKit
18 |
19 | public typealias Text = UILabel
20 |
21 | // MARK: - Text
22 |
23 | public extension Text {
24 |
25 | /// A view that displays one or more lines of read-only text.
26 | /// - Parameter text: (optional) text.
27 | convenience init(_ text: String?) {
28 | self.init()
29 | self.text = text
30 | }
31 |
32 | /// Modify text font.
33 | /// - Parameter font: An object that provides access to the font’s characteristics.
34 | /// - Returns: Modified text.
35 | func font(_ font: UIFont) -> Text {
36 | self.font = font
37 | return self
38 | }
39 |
40 | /// Modify foreground color.
41 | /// - Parameter color: An object that stores color data and sometimes opacity.
42 | /// - Returns: Modified text.
43 | func foregroundColor(_ color: UIColor) -> Text {
44 | self.textColor = color
45 | return self
46 | }
47 |
48 | /// Modify text alignment
49 | /// - Parameter textAlignment: Constants that specify text alignment.
50 | /// - Returns: Modified text.
51 | func textAligned(_ textAlignment: NSTextAlignment) -> Text {
52 | self.textAlignment = textAlignment
53 | return self
54 | }
55 |
56 | /// The maximum number of lines that text can occupy in a view.
57 | /// - Parameter numberOfLines: Sets the maximum number of lines that text can occupy in this view.
58 | /// - Returns: Modified text.
59 | func lineLimit(_ numberOfLines: Int) -> Text {
60 | self.numberOfLines = numberOfLines
61 | return self
62 | }
63 |
64 | /// Modify line break mode.
65 | /// - Parameter lineBreakMode: The technique for wrapping and truncating the label’s text.
66 | /// - Returns: Modified text.
67 | func lineBreakMode(_ lineBreakMode: NSLineBreakMode) -> Text {
68 | self.lineBreakMode = lineBreakMode
69 | return self
70 | }
71 |
72 | /// Modify adjustsfontsize to fit width.
73 | /// - Parameter adjustsFontSizeToFit: A Boolean value that determines whether the label reduces the text’s font size to fit the title string into the label’s bounding rectangle.
74 | /// - Returns: Modified text.
75 | func adjustedFontSize(_ adjustsFontSizeToFit: Bool) -> Text {
76 | self.adjustsFontSizeToFitWidth = adjustsFontSizeToFit
77 | return self
78 | }
79 |
80 | /// Modify minimum scale factor.
81 | /// - Parameter factor: The minimum scale factor for the label’s text.
82 | /// - Returns: Modified text.
83 | func minimumScaleFactor(_ factor: CGFloat) -> Text {
84 | self.minimumScaleFactor = factor
85 | return self
86 | }
87 |
88 | /// Sets the preferred maximum layout width for the Text object
89 | /// This function allows for chaining by returning the modified Text instance.
90 | ///
91 | /// - Parameter width: The maximum width that the Text object should try to adhere to in its layout.
92 | /// - Returns: The Text instance with the updated preferred maximum layout width, allowing for method chaining.
93 | func preferredMaxLayoutWidth(_ width: CGFloat) -> Text {
94 | preferredMaxLayoutWidth = width
95 | return self
96 | }
97 |
98 | /// Sets the baseline adjustment and returns the Text for method chaining.
99 | ///
100 | /// - Parameter adjustment: The baseline adjustment to apply to the Text.
101 | /// - Returns: The Text with the updated baseline adjustment.
102 | func baselineAdjusted(_ adjustment: UIBaselineAdjustment) -> Text {
103 | baselineAdjustment = adjustment
104 | return self
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/Sources/HypeUI/UIView+Passthrough.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Hyperconnect Inc.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | import UIKit
18 |
19 | // MARK: - UIView (Passthrought)
20 |
21 | extension UIView {
22 |
23 | /// [Hit-Testing in iOS](http://smnh.me/hit-testing-in-ios/)
24 | func passthrough(_ point: CGPoint, with event: UIEvent?) -> UIView? {
25 | guard isUserInteractionEnabled else { return nil }
26 | guard isHidden == false else { return nil }
27 | guard alpha > 0.01 else { return nil }
28 | guard self.point(inside: point, with: event) else { return nil }
29 |
30 | for subview in subviews.reversed() {
31 | if let view = subview.hitTest(subview.convert(point, from: self), with: event) {
32 | return view
33 | }
34 | }
35 | return nil
36 | }
37 | }
38 |
39 |
--------------------------------------------------------------------------------
/Sources/HypeUI/UIView+Search.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Hyperconnect Inc.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | import UIKit
18 |
19 | // MARK: - UIView (Search)
20 |
21 | public extension UIView {
22 |
23 | /// DFS search for finding specific view by identifier.
24 | /// - Parameter identifier: A string that identifies the element.
25 | /// - Returns: Found view.
26 | func findView(identifier: String) -> UIView? {
27 | if accessibilityIdentifier == identifier {
28 | return self
29 | }
30 | for subview in subviews {
31 | if let found = subview.findView(identifier: identifier) {
32 | return found
33 | }
34 | }
35 | return nil
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Sources/HypeUI/UIView+ViewBuildable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Hyperconnect Inc.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | import UIKit
18 |
19 | // MARK: - UIView (ViewBuildable)
20 |
21 | extension UIView: ViewBuildable {
22 | public func build() -> UIView {
23 | return self
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Sources/HypeUI/UnitPoint.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Hyperconnect Inc.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | import Foundation
18 |
19 | public typealias UnitPoint = CGPoint
20 |
21 | // MARK: - UnitPoint
22 |
23 | public extension UnitPoint {
24 | static let center = UnitPoint(x: 0.5, y: 0.5)
25 | static let left = UnitPoint(x: 0.0, y: 0.5)
26 | static let right = UnitPoint(x: 1.0, y: 0.5)
27 | static let top = UnitPoint(x: 0.5, y: 0.0)
28 | static let bottom = UnitPoint(x: 0.5, y: 1.0)
29 | }
30 |
--------------------------------------------------------------------------------
/Sources/HypeUI/VStack.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Hyperconnect Inc.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | import UIKit
18 |
19 | /// A view that arranges its subviews in a vertical line.
20 | /// - Parameters:
21 | /// - alignment: The layout of arranged views perpendicular to the stack view’s axis.
22 | /// - spacing: The distance in points between the adjacent edges of the stack view’s arranged views.
23 | /// - content: Build a views from variadic child views
24 | /// - Returns: vertical stack view
25 | public func VStack(alignment: UIStackView.Alignment = .fill, spacing: CGFloat = 0.0, @ViewArrayBuilder _ content: () -> [UIView]) -> UIStackView {
26 | let stackView = PassthroughStackView(arrangedSubviews: content())
27 | stackView.axis = .vertical
28 | stackView.alignment = alignment
29 | stackView.spacing = spacing
30 | return stackView
31 | }
32 |
--------------------------------------------------------------------------------
/Sources/HypeUI/View.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Hyperconnect Inc.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | import UIKit
18 | import SnapKit
19 |
20 | // MARK: - UIView
21 |
22 | public extension UIView {
23 |
24 | /// Sets the priority with which a view resists being made larger than its intrinsic size.
25 | /// - Parameter priority: The new priority.
26 | /// - Returns: Modified view.
27 | func setHContentHugging(priority: UILayoutPriority) -> Self {
28 | self.setContentHuggingPriority(priority, for: .horizontal)
29 | return self
30 | }
31 |
32 | /// Sets the priority with which a view resists being made larger than its intrinsic size.
33 | /// - Parameter priority: The new priority.
34 | /// - Returns: Modified view.
35 | func setVContentHugging(priority: UILayoutPriority) -> Self {
36 | self.setContentHuggingPriority(priority, for: .vertical)
37 | return self
38 | }
39 |
40 | /// Sets the priority with which a view resists being made smaller than its intrinsic size.
41 | /// - Parameter priority: The new priority.
42 | /// - Returns: Modified view.
43 | func setHContentCompressionResistance(priority: UILayoutPriority) -> Self {
44 | self.setContentCompressionResistancePriority(priority, for: .horizontal)
45 | return self
46 | }
47 |
48 | /// Sets the priority with which a view resists being made smaller than its intrinsic size.
49 | /// - Parameter priority: The new priority.
50 | /// - Returns: modified view
51 | func setVContentCompressionResistance(priority: UILayoutPriority) -> Self {
52 | self.setContentCompressionResistancePriority(priority, for: .vertical)
53 | return self
54 | }
55 |
56 | /// Modify the ratio of the size of the view.
57 | /// - Parameter ratio: Size ratio.
58 | /// - Returns: Modified view.
59 | func makeRatio(_ ratio: CGFloat) -> Self {
60 | self.snp.makeConstraints { maker in
61 | maker.height.equalTo(self.snp.width).multipliedBy(ratio)
62 | }
63 | return self
64 | }
65 |
66 | /// Modify corner radius.
67 | /// - Parameter radius: The radius to use when drawing rounded corners for the layer’s background.
68 | /// - Returns: Modified view.
69 | func cornerRadius(_ radius: CGFloat) -> Self {
70 | self.applyRound(radius)
71 | return self
72 | }
73 |
74 | /// Modify corner radius.
75 | /// - Parameter radius: The radius to use when drawing rounded corners for the layer’s background.
76 | /// - Parameter corners: A mask used to apply a radius to a specific corner.
77 | /// - Returns: Modified view.
78 | func cornerRadius(_ radius: CGFloat, corners: CACornerMask? = nil) -> Self {
79 | self.applyRound(radius, corners: corners)
80 | return self
81 | }
82 |
83 | /// Modify corner radius.
84 | /// - Parameter radius: The radius to use when drawing rounded corners for the layer’s background.
85 | /// - Parameter corners: A mask used to apply a radius to a specific corner.
86 | private func applyRound(_ radius: CGFloat, corners: CACornerMask? = nil) {
87 | if let corners = corners {
88 | self.layer.maskedCorners = corners
89 | }
90 | self.layer.cornerRadius = radius
91 | self.layer.masksToBounds = true
92 | }
93 |
94 | /// Modify border style.
95 | /// - Parameters:
96 | /// - color: The color of the layer’s border.
97 | /// - width: The width of the layer’s border.
98 | /// - Returns: Modified view
99 | func border(_ color: UIColor, width: CGFloat) -> Self {
100 | self.layer.borderColor = color.cgColor
101 | self.layer.borderWidth = width
102 | return self
103 | }
104 |
105 | /// Modifiy background color.
106 | /// - Parameter color: An object that stores color data and sometimes opacity.
107 | /// - Returns: Modified view
108 | func background(_ color: UIColor) -> Self {
109 | self.backgroundColor = color
110 | return self
111 | }
112 |
113 | /// Modify content mode.
114 | /// - Parameter contentMode: Options to specify how a view adjusts its content when its size changes.
115 | /// - Returns: Modified view.
116 | func makeContentMode(_ contentMode: ContentMode) -> Self {
117 | self.contentMode = contentMode
118 | return self
119 | }
120 |
121 | /// Positions this view within an invisible frame with the specified size.
122 | /// - Parameters:
123 | /// - width: A fixed width for the resulting view. If width is nil, the resulting view assumes this view’s sizing behavior.
124 | /// - height: A fixed height for the resulting view. If height is nil, the resulting view assumes this view’s sizing behavior.
125 | /// - Returns: Modified view.
126 | func frame(width: CGFloat? = nil, height: CGFloat? = nil) -> Self {
127 | if let width = width {
128 | widthAnchor.constraint(equalToConstant: width).isActive = true
129 | }
130 |
131 | if let height = height {
132 | heightAnchor.constraint(equalToConstant: height).isActive = true
133 | }
134 |
135 | return self
136 | }
137 |
138 | /// Adds an equal padding amount to specific edges of this view.
139 | /// - Parameter inset: The inset distances for views.
140 | /// - Returns: Modified view.
141 | func padding(_ inset: UIEdgeInsets) -> UIView {
142 | let view = UIView()
143 | view.addSubview(self)
144 | self.snp.makeConstraints { maker in
145 | maker.directionalEdges.equalTo(inset)
146 | }
147 | return view
148 | }
149 |
150 | /// Adds an equal padding amount to specific edges of this view.
151 | /// - Parameters:
152 | /// - edges: The set of edges to pad for this view.
153 | /// - length: An amount, given in points, to pad this view on the specified edges.
154 | /// - Returns: Modified view.
155 | func padding(_ edges: Edge.Set, _ length: CGFloat) -> UIView {
156 | let paddingView = UIView()
157 | self.translatesAutoresizingMaskIntoConstraints = false
158 | paddingView.addSubview(self)
159 |
160 | NSLayoutConstraint.activate([
161 | topAnchor.constraint(equalTo: paddingView.topAnchor, constant: edges.contains(.top) ? length : 0),
162 | leadingAnchor.constraint(equalTo: paddingView.leadingAnchor, constant: edges.contains(.leading) ? length : 0),
163 | bottomAnchor.constraint(equalTo: paddingView.bottomAnchor, constant: edges.contains(.bottom) ? -length : 0),
164 | trailingAnchor.constraint(equalTo: paddingView.trailingAnchor, constant: edges.contains(.trailing) ? -length : 0),
165 | ])
166 |
167 | return paddingView
168 | }
169 |
170 | /// Configures whether this view participates in hit test operations.
171 | /// - Parameter allows: A value type whose instances are either true or false.
172 | /// - Returns: Modified view.
173 | func allowsHitTesting(_ allows: Bool) -> Self {
174 | self.isUserInteractionEnabled = allows
175 | return self
176 | }
177 |
178 | /// Adds a view to the end of the receiver’s list of subviews with fit
179 | /// - Parameter view: Modified view.
180 | func addSubviewWithFit(_ view: UIView) {
181 | addSubview(view)
182 | view.snp.makeConstraints { $0.directionalEdges.equalToSuperview() }
183 | }
184 |
185 | /// Setting sublayers are clipped to the layer’s bounds.
186 | /// - Parameter masksToBounds: A Boolean indicating whether sublayers are clipped to the layer’s bounds.
187 | /// - Returns: Modified view.
188 | func masksToBounds(_ masksToBounds: Bool) -> Self {
189 | layer.masksToBounds = masksToBounds
190 | return self
191 | }
192 |
193 | /// Uses the string you specify to identify the view.
194 | /// - Parameter identifier: A string that identifies the element.
195 | /// - Returns: Modified view.
196 | func accessibilityIdentifier(_ identifier: String) -> Self {
197 | self.accessibilityIdentifier = identifier
198 | return self
199 | }
200 |
201 | /// Layers the views that you specify in front of this view.
202 | /// - Parameters:
203 | /// - alignment: An alignment in both axes.
204 | /// - view: Buildable view content.
205 | /// - Returns: Modified view.
206 | func overlay(alignment: Alignment = .center, view: ViewBuildable) -> UIView {
207 | ZStack {
208 | self
209 | AlignmentView(alignment: alignment, view: view)
210 | }
211 | }
212 |
213 | /// Layers the views that you specify behind this view.
214 | /// - Parameters:
215 | /// - alignment: An alignment in both axes.
216 | /// - view: Buildable view content.
217 | /// - Returns: Modified view.
218 | func background(alignment: Alignment = .center, view: ViewBuildable) -> UIView {
219 | ZStack {
220 | AlignmentView(alignment: alignment, view: view)
221 | self
222 | }
223 | }
224 |
225 | /// Wraps and centers the current view in a new parent View.
226 | ///
227 | /// - Returns: The new parent View with this view centered inside.
228 | func center() -> UIView {
229 | let view = UIView()
230 | view.addSubview(self)
231 | snp.makeConstraints { maker in
232 | maker.center.equalToSuperview()
233 | }
234 | return view
235 | }
236 |
237 | /// Applies a tint color to the view and returns the modified view for chaining.
238 | ///
239 | /// This extension function sets the `tintColor` property of the view to a specified color, allowing for easy modification of the view's appearance in a method chaining style.
240 | ///
241 | /// - Parameter tintColor: The color to set as the tint color of the view.
242 | /// - Returns: The view instance with the updated tint color.
243 | func tint(_ tintColor: UIColor) -> Self {
244 | self.tintColor = tintColor
245 | return self
246 | }
247 |
248 | /// Sets the opacity of the view and returns the modified view for chaining.
249 | ///
250 | /// This function adjusts the `alpha` property of the view to control its opacity, allowing for fluent style coding.
251 | /// The return type of 'Self' ensures that the function can be used with any subclass of UIView that inherits this method.
252 | ///
253 | /// - Parameter opacity: The opacity level to set, where 1 is fully opaque and 0 is completely transparent.
254 | /// - Returns: The view instance with the updated opacity.
255 | func opacity(_ opacity: CGFloat) -> Self {
256 | alpha = opacity
257 | return self
258 | }
259 |
260 | /// Applies a scale transformation to the view.
261 | ///
262 | /// - Parameters:
263 | /// - scaleX: The scale factor in the x-axis.
264 | /// - scaleY: The scale factor in the y-axis.
265 | /// - Returns: The view with the scaling applied, allowing for chaining.
266 | func scaleEffect(scaleX: CGFloat, scaleY: CGFloat) -> Self {
267 | self.transform = CGAffineTransform(scaleX: scaleX, y: scaleY)
268 | return self
269 | }
270 |
271 | /// Applies a rotation transformation to the view.
272 | ///
273 | /// - Parameters:
274 | /// - angle: The rotation angle in degrees.
275 | /// - anchor: The anchor point of the rotation, default is center.
276 | /// - Returns: The view with the rotation applied, allowing for chaining.
277 | func rotationEffect(_ angle: CGFloat, anchor: CGPoint = CGPoint(x: 0.5, y: 0.5)) -> Self {
278 | var transform = CGAffineTransform.identity
279 | transform = transform.translatedBy(x: anchor.x, y: anchor.y)
280 | transform = transform.rotated(by: angle * CGFloat.pi / 180)
281 | transform = transform.translatedBy(x: -anchor.x, y: -anchor.y)
282 | self.transform = transform
283 | return self
284 | }
285 | }
286 |
287 | // MARK: - UIStackView
288 |
289 | public extension UIStackView {
290 |
291 | /// Modify stack's distribution layout.
292 | /// - Parameter distribution: The layout that defines the size and position of the arranged views along the stack view’s axis.
293 | /// - Returns: Modified stack view.
294 | func distributed(_ distribution: UIStackView.Distribution) -> Self {
295 | self.distribution = distribution
296 | return self
297 | }
298 | }
299 |
--------------------------------------------------------------------------------
/Sources/HypeUI/ViewArrayBuilder.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Hyperconnect Inc.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | import UIKit
18 |
19 | // MARK: - ViewArrayBuilder
20 |
21 | @resultBuilder
22 | public struct ViewArrayBuilder {
23 |
24 | /// Build a views from variadic child views
25 | /// - Parameter views: An buildable variadic child views
26 | /// - Returns: An array of views that are composed of variadic child views
27 | public static func buildBlock(_ views: ViewBuildable...) -> [UIView] {
28 | views.map { $0.build() }
29 | }
30 |
31 |
32 | /// Build a views from array of views
33 | /// - Parameter views: An array of buildable views
34 | /// - Returns: An array of views
35 | public static func buildBlock(_ views: [ViewBuildable]) -> [UIView] {
36 | views.map { $0.build() }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Sources/HypeUI/ViewBuildable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Hyperconnect Inc.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | import UIKit
18 |
19 | // MARK: - ViewBuildable (protocol)
20 |
21 | public protocol ViewBuildable {
22 | func build() -> UIView
23 | }
24 |
--------------------------------------------------------------------------------
/Sources/HypeUI/ZStack.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Hyperconnect Inc.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | import UIKit
18 |
19 | /// A view that overlays its subviews, aligning them in both axes.
20 | /// - Parameter content: Build a views from variadic child views.
21 | /// - Returns: A view that overlays content views.
22 | public func ZStack(@ViewArrayBuilder _ content: () -> [UIView]) -> UIView {
23 | let view = UIView()
24 | content().forEach(view.addSubviewWithFit)
25 | return view
26 | }
27 |
--------------------------------------------------------------------------------
/Tests/HypeUITests/AlignmentTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Hyperconnect Inc.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | import XCTest
18 |
19 | // MARK: - AlignmentTests
20 |
21 | @testable import HypeUI
22 | final class AlignmentTests: XCTestCase {
23 |
24 | func testTopLeadingAlignment() {
25 | let sut = Alignment.topLeading
26 |
27 | XCTAssertTrue(sut.isTop)
28 | XCTAssertTrue(sut.isLeading)
29 |
30 | XCTAssertFalse(sut.isBottom)
31 | XCTAssertFalse(sut.isTrailing)
32 |
33 | XCTAssertEqual(sut.horizontalAlignment, .top)
34 | XCTAssertEqual(sut.verticalAlignment, .leading)
35 | }
36 |
37 | func testTopAlignment() {
38 | let sut = Alignment.top
39 |
40 | XCTAssertTrue(sut.isTop)
41 | XCTAssertFalse(sut.isLeading)
42 |
43 | XCTAssertFalse(sut.isBottom)
44 | XCTAssertFalse(sut.isTrailing)
45 |
46 | XCTAssertEqual(sut.horizontalAlignment, .top)
47 | XCTAssertEqual(sut.verticalAlignment, .center)
48 | }
49 |
50 | func testTopTrailingAlignment() {
51 | let sut = Alignment.topTrailing
52 |
53 | XCTAssertTrue(sut.isTop)
54 | XCTAssertFalse(sut.isLeading)
55 |
56 | XCTAssertFalse(sut.isBottom)
57 | XCTAssertTrue(sut.isTrailing)
58 |
59 | XCTAssertEqual(sut.horizontalAlignment, .top)
60 | XCTAssertEqual(sut.verticalAlignment, .trailing)
61 | }
62 |
63 | func testLeadingAlignment() {
64 | let sut = Alignment.leading
65 |
66 | XCTAssertFalse(sut.isTop)
67 | XCTAssertTrue(sut.isLeading)
68 |
69 | XCTAssertFalse(sut.isBottom)
70 | XCTAssertFalse(sut.isTrailing)
71 |
72 | XCTAssertEqual(sut.horizontalAlignment, .center)
73 | XCTAssertEqual(sut.verticalAlignment, .leading)
74 | }
75 |
76 | func testCenterAlignment() {
77 | let sut = Alignment.center
78 |
79 | XCTAssertFalse(sut.isTop)
80 | XCTAssertFalse(sut.isLeading)
81 |
82 | XCTAssertFalse(sut.isBottom)
83 | XCTAssertFalse(sut.isTrailing)
84 |
85 | XCTAssertEqual(sut.horizontalAlignment, .center)
86 | XCTAssertEqual(sut.verticalAlignment, .center)
87 | }
88 |
89 | func testTrailingAlignment() {
90 | let sut = Alignment.trailing
91 |
92 | XCTAssertFalse(sut.isTop)
93 | XCTAssertFalse(sut.isLeading)
94 |
95 | XCTAssertFalse(sut.isBottom)
96 | XCTAssertTrue(sut.isTrailing)
97 |
98 | XCTAssertEqual(sut.horizontalAlignment, .center)
99 | XCTAssertEqual(sut.verticalAlignment, .trailing)
100 | }
101 |
102 | func testBottomLeadingAlignment() {
103 | let sut = Alignment.bottomLeading
104 |
105 | XCTAssertFalse(sut.isTop)
106 | XCTAssertTrue(sut.isLeading)
107 |
108 | XCTAssertTrue(sut.isBottom)
109 | XCTAssertFalse(sut.isTrailing)
110 |
111 | XCTAssertEqual(sut.horizontalAlignment, .bottom)
112 | XCTAssertEqual(sut.verticalAlignment, .leading)
113 | }
114 |
115 | func testBottomAlignment() {
116 | let sut = Alignment.bottom
117 |
118 | XCTAssertFalse(sut.isTop)
119 | XCTAssertFalse(sut.isLeading)
120 |
121 | XCTAssertTrue(sut.isBottom)
122 | XCTAssertFalse(sut.isTrailing)
123 |
124 | XCTAssertEqual(sut.horizontalAlignment, .bottom)
125 | XCTAssertEqual(sut.verticalAlignment, .center)
126 | }
127 |
128 | func testBottomTrailingAlignment() {
129 | let sut = Alignment.bottomTrailing
130 |
131 | XCTAssertFalse(sut.isTop)
132 | XCTAssertFalse(sut.isLeading)
133 |
134 | XCTAssertTrue(sut.isBottom)
135 | XCTAssertTrue(sut.isTrailing)
136 |
137 | XCTAssertEqual(sut.horizontalAlignment, .bottom)
138 | XCTAssertEqual(sut.verticalAlignment, .trailing)
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/Tests/HypeUITests/HStackTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Hyperconnect Inc.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | import XCTest
18 |
19 | // MARK: - HStackTests
20 |
21 | @testable import HypeUI
22 | final class HStackTests: XCLayoutTestCase {
23 |
24 | func testHStackImageWithText() {
25 | // given
26 | let spacing: CGFloat = 5
27 | let image = Image()
28 | let text = Text("*****")
29 | let spacer = Spacer()
30 |
31 | // when
32 | let sut = HStack(alignment: .center, spacing: spacing) {
33 | image
34 | .frame(width: 24, height: 24)
35 | text
36 | .font(UIFont.systemFont(ofSize: 20, weight: .bold))
37 | spacer
38 | }
39 | contentView.addSubviewWithFit(sut)
40 | contentView.layoutIfNeeded()
41 |
42 | // then
43 | XCTAssertEqual(image.frame.width, 24)
44 | XCTAssertEqual(image.frame.height, 24)
45 | XCTAssertLessThan(image.frame.width, spacer.frame.width)
46 | XCTAssertLessThan(text.frame.width, spacer.frame.width)
47 | }
48 |
49 | func testHStackCenterAlignmentWithSpacing() {
50 | // given
51 | let spacing: CGFloat = 10
52 | let title = Text("Title")
53 | .font(UIFont.systemFont(ofSize: 10, weight: .bold))
54 | let spacer = Spacer()
55 |
56 | // when
57 | let sut = HStack(alignment: .center, spacing: spacing) {
58 | title
59 | spacer
60 | }
61 | contentView.addSubviewWithFit(sut)
62 | contentView.layoutIfNeeded()
63 |
64 | // then
65 | XCTAssertEqual(title.frame.maxX.rounded() + spacing, spacer.frame.minX.rounded())
66 | XCTAssertEqual(title.frame.midY, spacer.frame.midY)
67 | XCTAssertLessThan(title.frame.width, spacer.frame.width)
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/Tests/HypeUITests/ScrollViewTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Hyperconnect Inc.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | import XCTest
18 |
19 | // MARK: - ScrollViewTests
20 |
21 | @testable import HypeUI
22 | final class ScrollViewTests: XCLayoutTestCase {
23 | func testHorizontalScrollViewSmallContentSize() {
24 | // given
25 | let scrollView = ScrollView(.horizontal, showsIndicators: false) {
26 | HStack(alignment: .center, spacing: 20) {
27 | Text()
28 | .frame(width: 100)
29 | Image()
30 | .frame(width: 150)
31 | }
32 | }
33 |
34 | // when
35 | contentView.addSubviewWithFit(
36 | ZStack {
37 | scrollView
38 | }
39 | )
40 | contentView.layoutIfNeeded()
41 |
42 | // then
43 | XCTAssertEqual(scrollView.frame, contentView.frame)
44 | XCTAssertEqual(scrollView.contentSize, contentView.bounds.size)
45 | }
46 |
47 | func testHorizontalScrollViewLargeContentSize() {
48 | // given
49 | let scrollView = ScrollView(.horizontal, showsIndicators: false) {
50 | HStack(alignment: .center, spacing: 20) {
51 | Text()
52 | .frame(width: 400)
53 | Image()
54 | .frame(width: 650)
55 | }
56 | }
57 |
58 | // when
59 | contentView.addSubviewWithFit(
60 | ZStack {
61 | scrollView
62 | }
63 | )
64 | contentView.layoutIfNeeded()
65 |
66 | // then
67 | XCTAssertEqual(scrollView.contentSize.height, contentView.bounds.size.height)
68 | XCTAssertEqual(scrollView.frame, contentView.frame)
69 | XCTAssertGreaterThan(scrollView.contentSize.width, contentView.bounds.size.width)
70 | }
71 |
72 |
73 | func testVerticalScrollViewSmallContentSize() {
74 | // given
75 | let scrollView = ScrollView(.vertical, showsIndicators: false) {
76 | VStack(alignment: .center, spacing: 20) {
77 | Text()
78 | .frame(height: 100)
79 | Image()
80 | .frame(height: 150)
81 | }
82 | }
83 |
84 | // when
85 | contentView.addSubviewWithFit(
86 | ZStack {
87 | scrollView
88 | }
89 | )
90 | contentView.layoutIfNeeded()
91 |
92 | // then
93 | XCTAssertEqual(scrollView.frame, contentView.frame)
94 | XCTAssertEqual(scrollView.contentSize, contentView.bounds.size)
95 | }
96 |
97 | func testVerticalScrollViewLargeContentSize() {
98 | // given
99 | let scrollView = ScrollView(.vertical, showsIndicators: false) {
100 | VStack(alignment: .center, spacing: 20) {
101 | (1...5).map { _ in
102 | HStack(alignment: .center) {
103 | Text()
104 | .frame(height: 100)
105 | Image()
106 | .frame(height: 200)
107 | }
108 | }
109 | }
110 | }
111 |
112 | // when
113 | contentView.addSubviewWithFit(
114 | ZStack {
115 | scrollView
116 | }
117 | )
118 | contentView.layoutIfNeeded()
119 |
120 | // then
121 | XCTAssertEqual(scrollView.contentSize.width, contentView.bounds.size.width)
122 | XCTAssertEqual(scrollView.frame, contentView.frame)
123 | XCTAssertEqual(scrollView.contentSize.height, 200 * 5 + 20 * 4)
124 | XCTAssertGreaterThan(scrollView.contentSize.height, contentView.bounds.size.height)
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/Tests/HypeUITests/SpacerTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Hyperconnect Inc.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | import XCTest
18 |
19 | // MARK: - SpacerTests
20 |
21 | @testable import HypeUI
22 | final class SpacerTests: XCTestCase {
23 |
24 | func testSpacerUILayoutPriority() {
25 | let sut = Spacer()
26 |
27 | testPriority(sut: sut, target: UIView())
28 | testPriority(sut: sut, target: UILabel())
29 | testPriority(sut: sut, target: UIButton())
30 | testPriority(sut: sut, target: UITextView())
31 | testPriority(sut: sut, target: UITextField())
32 | testPriority(sut: sut, target: UIStackView())
33 | testPriority(sut: sut, target: PassthroughView())
34 | testPriority(sut: sut, target: PassthroughStackView())
35 | }
36 |
37 | private func testPriority(sut: UIView, target: UIView) {
38 | XCTAssertLessThan(sut.contentHuggingPriority(for: .vertical), target.contentHuggingPriority(for: .vertical))
39 | XCTAssertLessThan(sut.contentHuggingPriority(for: .horizontal), target.contentHuggingPriority(for: .horizontal))
40 | XCTAssertLessThan(sut.contentCompressionResistancePriority(for: .vertical), target.contentCompressionResistancePriority(for: .vertical))
41 | XCTAssertLessThan(sut.contentCompressionResistancePriority(for: .horizontal), target.contentCompressionResistancePriority(for: .horizontal))
42 | }
43 | }
44 |
45 |
46 |
--------------------------------------------------------------------------------
/Tests/HypeUITests/TextTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Hyperconnect Inc.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | import XCTest
18 |
19 | // MARK: - TextTests
20 |
21 | @testable import HypeUI
22 | final class TextTests: XCLayoutTestCase {
23 |
24 | func testOptionalNilTextModifyFont() {
25 | // Given
26 | let sut = Text(nil)
27 |
28 | // When
29 | let output = sut.font(UIFont.systemFont(ofSize: 12, weight: .regular))
30 |
31 | // Then
32 | XCTAssertNil(sut.text)
33 | XCTAssertEqual(sut, output)
34 | }
35 |
36 | func testNormalTextModifyFont() {
37 | // Given
38 | let title = "🔥 Hello HypeUI"
39 | let sut = Text(title)
40 |
41 | // When
42 | let output = sut.font(UIFont.systemFont(ofSize: 12, weight: .regular))
43 |
44 | // Then
45 | XCTAssertEqual(sut.text, title)
46 | XCTAssertEqual(sut, output)
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Tests/HypeUITests/UIView+PassthroughTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Hyperconnect Inc.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | import XCTest
18 |
19 | // MARK: - UIView_PassthroughTests
20 |
21 | @testable import HypeUI
22 | final class UIView_PassthroughTests: XCTestCase {
23 | enum Constant {
24 | static let defaultFrame = CGRect(origin: .zero, size: CGSize(width: 100, height: 100))
25 | }
26 |
27 | func testPassThroughView() {
28 | // given
29 | let sut = UIView(frame: Constant.defaultFrame)
30 | sut.isHidden = false
31 |
32 | // when
33 | let output = sut.passthrough(CGPoint(x: 10, y: 10), with: nil)
34 |
35 | // then
36 | XCTAssertNil(output)
37 | }
38 |
39 | func testPassThroughHiddenView() {
40 | // given
41 | let sut = UIView(frame: Constant.defaultFrame)
42 | sut.isHidden = true
43 |
44 | // when
45 | let output = sut.passthrough(CGPoint(x: 10, y: 10), with: nil)
46 |
47 | // then
48 | XCTAssertNil(output)
49 | }
50 |
51 | func testPassThroughViewWithSubView() {
52 | // given
53 | let sut = UIView(frame: Constant.defaultFrame)
54 | let target = UIView(frame: Constant.defaultFrame)
55 | sut.isHidden = false
56 | sut.addSubview(target)
57 |
58 | // when
59 | let output = sut.passthrough(CGPoint(x: 10, y: 10), with: nil)
60 |
61 | // then
62 | XCTAssertNotNil(output)
63 | XCTAssertEqual(output, target)
64 | }
65 |
66 | func testPassThroughHiddenViewWithSubview() {
67 | // given
68 | let sut = UIView(frame: Constant.defaultFrame)
69 | let target = UIView(frame: Constant.defaultFrame)
70 | sut.isHidden = true
71 | sut.addSubview(target)
72 |
73 | // when
74 | let output = sut.passthrough(CGPoint(x: 10, y: 10), with: nil)
75 |
76 | // then
77 | XCTAssertNil(output)
78 | }
79 |
80 | func testPassThroughUserInteractionEnabledViewWithSubview() {
81 | // given
82 | let sut = UIView(frame: Constant.defaultFrame)
83 | let target = UIView(frame: Constant.defaultFrame)
84 | sut.isHidden = false
85 | sut.isUserInteractionEnabled = true
86 | sut.addSubview(target)
87 |
88 | // when
89 | let output = sut.passthrough(CGPoint(x: 10, y: 10), with: nil)
90 |
91 | // then
92 | XCTAssertNotNil(output)
93 | XCTAssertEqual(output, target)
94 | }
95 |
96 | func testPassThroughUserInteractionDisabledViewWithSubview() {
97 | // given
98 | let sut = UIView(frame: Constant.defaultFrame)
99 | let target = UIView(frame: Constant.defaultFrame)
100 | sut.isHidden = false
101 | sut.isUserInteractionEnabled = false
102 | sut.addSubview(target)
103 |
104 | // when
105 | let output = sut.passthrough(CGPoint(x: 10, y: 10), with: nil)
106 |
107 | // then
108 | XCTAssertNil(output)
109 | }
110 |
111 | func testPassThroughAlphaShownViewWithSubview() {
112 | // given
113 | let sut = UIView(frame: Constant.defaultFrame)
114 | let target = UIView(frame: Constant.defaultFrame)
115 | sut.isHidden = false
116 | sut.addSubview(target)
117 |
118 | do {
119 | // when
120 | sut.alpha = 0.1
121 | let output = sut.passthrough(CGPoint(x: 10, y: 10), with: nil)
122 |
123 | // then
124 | XCTAssertNotNil(output)
125 | XCTAssertEqual(output, target)
126 | }
127 |
128 | do {
129 | // when
130 | sut.alpha = 1.0
131 | let output = sut.passthrough(CGPoint(x: 10, y: 10), with: nil)
132 |
133 | // then
134 | XCTAssertNotNil(output)
135 | XCTAssertEqual(output, target)
136 | }
137 | }
138 |
139 | func testPassThroughAlphaHiddenViewWithSubview() {
140 | // given
141 | let sut = UIView(frame: Constant.defaultFrame)
142 | let target = UIView(frame: Constant.defaultFrame)
143 | sut.isHidden = false
144 | sut.addSubview(target)
145 |
146 | do {
147 | // when
148 | sut.alpha = 0.0
149 | let output = sut.passthrough(CGPoint(x: 10, y: 10), with: nil)
150 |
151 | // then
152 | XCTAssertNil(output)
153 | }
154 |
155 | do {
156 | // when
157 | sut.alpha = 0.01
158 | let output = sut.passthrough(CGPoint(x: 10, y: 10), with: nil)
159 |
160 | // then
161 | XCTAssertNil(output)
162 | }
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/Tests/HypeUITests/ViewArrayBuilderTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Hyperconnect Inc.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | import XCTest
18 |
19 | // MARK: - ViewArrayBuilderTests
20 |
21 | @testable import HypeUI
22 | final class ViewArrayBuilderTests: XCTestCase {
23 | func testViewArrayBuilder() {
24 | // given
25 | let label = UILabel()
26 | let button = UIButton()
27 | let image = UIImageView()
28 | let expect = [label, button, image]
29 |
30 | // when
31 | let output = ViewArrayBuilder.buildBlock(expect)
32 |
33 | // then
34 | XCTAssertEqual(output, expect)
35 | }
36 |
37 | func testViewArrayResultBuilder() {
38 | // given
39 | let label = UILabel()
40 | let button = UIButton()
41 | let image = UIImageView()
42 | let expect = [label, button, image]
43 |
44 | // when
45 | let output = ViewArrayBuilder.buildBlock(label, button, image)
46 |
47 | // then
48 | XCTAssertEqual(output, expect)
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Tests/HypeUITests/ViewBuildableTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Hyperconnect Inc.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | import XCTest
18 |
19 | // MARK: - ViewBuildableTests
20 |
21 | @testable import HypeUI
22 | final class ViewBuildableTests: XCLayoutTestCase {
23 |
24 | func testHStackWithViewBuildable() {
25 | struct ProfileView: ViewBuildable {
26 | @Behavior var country: String
27 | @Behavior var name: String
28 |
29 | func build() -> UIView {
30 | VStack {
31 | HStack(alignment: .center, spacing: 12) {
32 | Text("")
33 | .linked($country, keyPath: \.text)
34 | .font(UIFont.systemFont(ofSize: 20, weight: .regular))
35 | .accessibilityIdentifier("country")
36 | Text("")
37 | .linked($name, keyPath: \.text)
38 | .font(UIFont.systemFont(ofSize: 20, weight: .regular))
39 | .accessibilityIdentifier("name")
40 | }
41 | }
42 | }
43 | }
44 |
45 | // Given
46 |
47 | let sut = ProfileView(country: "🇪🇸", name: "Español")
48 |
49 | // When
50 |
51 | contentView.addSubviewWithFit(
52 | VStack {
53 | HStack {
54 | sut
55 | Spacer()
56 | }.build().build().build()
57 | }
58 | )
59 |
60 | // Then
61 |
62 | let countryText = contentView.findView(identifier: "country") as? Text
63 | let nameText = contentView.findView(identifier: "name") as? Text
64 | XCTAssertEqual(countryText?.text, "🇪🇸")
65 | XCTAssertEqual(nameText?.text, "Español")
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/Tests/HypeUITests/ViewTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Hyperconnect Inc.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | import XCTest
18 | import UIKit
19 |
20 | // MARK: - ViewTests
21 |
22 | @testable import HypeUI
23 | final class ViewTests: XCLayoutTestCase {
24 |
25 | func testPaddingWithUIEdgeInsets() {
26 | // given
27 | let sut = HStack(spacing: 5) {
28 | Image()
29 | .frame(width: 20, height: 20)
30 | Text()
31 | .frame(width: 100, height: 20)
32 | }.padding(UIEdgeInsets(top: 10, left: 20, bottom: 10, right: 20))
33 |
34 | contentView.addSubviewWithFit(
35 | VStack(alignment: .center) {
36 | sut
37 | Spacer()
38 | }
39 | )
40 | contentView.layoutIfNeeded()
41 |
42 | // then
43 | XCTAssertEqual(sut.frame.size, CGSize(width: 20 + 125 + 20, height: 10 + 20 + 10))
44 | XCTAssertEqual(sut.frame.midX, sut.frame.midX)
45 |
46 | // https://github.com/SnapKit/SnapKit/blob/develop/Tests/SnapKitTests/Tests.swift#L95
47 | XCTAssertNotNil(sut.constraints.first { $0.firstAttribute == .leading && $0.secondAttribute == .leading })
48 | XCTAssertNotNil(sut.constraints.first { $0.firstAttribute == .trailing && $0.secondAttribute == .trailing })
49 | XCTAssertNotNil(sut.constraints.first { $0.firstAttribute == .top && $0.secondAttribute == .top })
50 | XCTAssertNotNil(sut.constraints.first { $0.firstAttribute == .bottom && $0.secondAttribute == .bottom })
51 | }
52 |
53 | func testPaddingWithEdges() {
54 | // given
55 | let sut = HStack(spacing: 5) {
56 | Image()
57 | .frame(width: 20, height: 20)
58 | Text()
59 | .frame(width: 100, height: 20)
60 | }.padding(.all, 20)
61 |
62 | contentView.addSubviewWithFit(
63 | VStack(alignment: .center) {
64 | sut
65 | Spacer()
66 | }
67 | )
68 | contentView.layoutIfNeeded()
69 |
70 | // then
71 | XCTAssertEqual(sut.frame.size, CGSize(width: 20 + 125 + 20, height: 20 + 20 + 20))
72 | XCTAssertEqual(sut.frame.midX, sut.frame.midX)
73 |
74 | // https://github.com/SnapKit/SnapKit/blob/develop/Tests/SnapKitTests/Tests.swift#L95
75 | XCTAssertNotNil(sut.constraints.first { $0.firstAttribute == .leading && $0.secondAttribute == .leading })
76 | XCTAssertNotNil(sut.constraints.first { $0.firstAttribute == .trailing && $0.secondAttribute == .trailing })
77 | XCTAssertNotNil(sut.constraints.first { $0.firstAttribute == .top && $0.secondAttribute == .top })
78 | XCTAssertNotNil(sut.constraints.first { $0.firstAttribute == .bottom && $0.secondAttribute == .bottom })
79 | }
80 |
81 | func testFrameWidth() {
82 | // given
83 | var sut = Spacer()
84 |
85 | // when
86 | sut = sut.frame(width: 10)
87 |
88 | // then
89 | XCTAssertNotNil(sut.constraints.first { $0.firstAttribute == .width && $0.constant == 10 })
90 | XCTAssertNil(sut.constraints.first { $0.firstAttribute == .height })
91 | }
92 |
93 | func testFrameHeight() {
94 | // given
95 | var sut = Spacer()
96 |
97 | // when
98 | sut = sut.frame(height: 10)
99 |
100 | // then
101 | XCTAssertNotNil(sut.constraints.first { $0.firstAttribute == .height && $0.constant == 10 })
102 | XCTAssertNil(sut.constraints.first { $0.firstAttribute == .width })
103 | }
104 |
105 | func testStackDistributedToEqual() {
106 | // given
107 | let sut = HStack(alignment: .center) {
108 | Text("🌺 Hello")
109 | .font(UIFont.systemFont(ofSize: 20, weight: .bold))
110 | Text("🦄 HypeUI")
111 | .font(UIFont.systemFont(ofSize: 16, weight: .regular))
112 | ScrollView(.vertical) {
113 | Text("Scrolling...")
114 | .font(UIFont.systemFont(ofSize: 14, weight: .heavy))
115 | }
116 | }
117 |
118 | // when
119 | contentView.addSubviewWithFit(
120 | HStack(alignment: .center) {
121 | VStack(alignment: .center) {
122 | sut.distributed(.fillEqually)
123 | }
124 | }
125 | )
126 | sut.layoutIfNeeded()
127 |
128 | // then
129 | XCTAssertEqual(Set(sut.subviews.map { $0.bounds.width }).count, 1)
130 | XCTAssertEqual(Set(sut.subviews.map { $0.bounds.height }).count, 3)
131 | }
132 |
133 | func testOverlayAlignmentCenter() {
134 | // given
135 | let sut = Text()
136 | .frame(width: 100, height: 100)
137 |
138 | // when
139 | contentView.addSubviewWithFit(
140 | Text()
141 | .frame(width: 500, height: 500)
142 | .overlay(alignment: .center, view: sut)
143 | )
144 | contentView.layoutIfNeeded()
145 |
146 | // then
147 | XCTAssertEqual(contentView.center, contentView.convert(CGPoint(x: sut.bounds.midX, y: sut.bounds.midY), from: sut))
148 | }
149 |
150 | func testOverlayAlignmentTopLeading() {
151 | // given
152 | let sut = Text()
153 | .frame(width: 100, height: 100)
154 |
155 | // when
156 | contentView.addSubviewWithFit(
157 | Text()
158 | .frame(width: 500, height: 500)
159 | .overlay(alignment: .topLeading, view: sut)
160 | )
161 | contentView.layoutIfNeeded()
162 |
163 | // then
164 | XCTAssertEqual(CGRect(x: 0, y: 0, width: 100, height: 100), contentView.convert(sut.bounds, from: sut))
165 | }
166 |
167 | func testOverlayAlignmentBottomTrailing() {
168 | // given
169 | let sut = Text()
170 | .frame(width: 100, height: 100)
171 |
172 | // when
173 | contentView.addSubviewWithFit(
174 | Text()
175 | .frame(width: 500, height: 500)
176 | .overlay(alignment: .bottomTrailing, view: sut)
177 | )
178 | contentView.layoutIfNeeded()
179 |
180 | // then
181 | let output = contentView.convert(sut.bounds, from: sut)
182 | XCTAssertEqual(contentView.frame.maxX - 100, output.minX)
183 | XCTAssertEqual(contentView.frame.maxY - 100, output.minY)
184 | XCTAssertEqual(CGSize(width: 100, height: 100), output.size)
185 | }
186 |
187 | func testCenter() {
188 | // Given
189 | let sut = Text()
190 | .frame(width: 200, height: 200)
191 |
192 | // When
193 | contentView.addSubviewWithFit(
194 | ZStack {
195 | UIView()
196 | .frame(width: 400, height: 400)
197 | sut.center()
198 | }.center()
199 | )
200 | contentView.layoutIfNeeded()
201 |
202 | // Then
203 | XCTAssertEqual(sut.center, CGPoint(x: 200, y: 200))
204 | XCTAssertEqual(sut.bounds.size, CGSize(width: 200, height: 200))
205 | }
206 | }
207 |
--------------------------------------------------------------------------------
/Tests/HypeUITests/XCLayoutTestCase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Hyperconnect Inc.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | import XCTest
18 |
19 | // MARK: - XCLayoutTestCase
20 |
21 | @testable import HypeUI
22 | class XCLayoutTestCase: XCTestCase {
23 | var contentView: UIView!
24 |
25 | override func setUpWithError() throws {
26 | let viewController = UIViewController()
27 | let window = UIWindow()
28 | window.addSubview(viewController.view)
29 | window.makeKeyAndVisible()
30 | contentView = UIView()
31 | viewController.view.addSubviewWithFit(contentView)
32 | RunLoop.current.run(until: Date())
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Tests/HypeUITests/ZStackTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Hyperconnect Inc.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | import XCTest
18 |
19 | // MARK: - ZStackTests
20 |
21 | @testable import HypeUI
22 | final class ZStackTests: XCLayoutTestCase {
23 |
24 | func testZStack() {
25 | // given
26 | let image = Image()
27 | let text = Text("*****")
28 | let stack = VStack {
29 | Spacer()
30 | Spacer()
31 | Spacer()
32 | }
33 |
34 | // when
35 | let sut = ZStack {
36 | image
37 | text
38 | stack
39 | }
40 | contentView.addSubviewWithFit(sut)
41 | contentView.layoutIfNeeded()
42 |
43 | XCTAssertEqual(image.frame, text.frame)
44 | XCTAssertEqual(image.frame, stack.frame)
45 | }
46 |
47 | func testZStackWithVStack() {
48 | // given
49 | let spacer = Spacer()
50 |
51 | // when
52 | let sut = ZStack {
53 | VStack(spacing: 10) {
54 | Image()
55 | .frame(width: 20, height: 20)
56 | Text("*****")
57 | .frame(height: 20)
58 | spacer
59 | }
60 | }
61 | contentView.addSubviewWithFit(sut)
62 | contentView.layoutIfNeeded()
63 |
64 | // then
65 | XCTAssertEqual(spacer.frame.minY, 60) // 20 + 10 + 20 + 10
66 | XCTAssertEqual(spacer.frame.minX, 0) // Distribution.fill
67 | }
68 | }
69 |
--------------------------------------------------------------------------------