Skip to content

Commit e640ed3

Browse files
Mark Pospeselmpospese
Mark Pospesel
authored andcommitted
[Issue-41] Update README
1 parent 8518f83 commit e640ed3

File tree

5 files changed

+245
-8
lines changed

5 files changed

+245
-8
lines changed
File renamed without changes.

README.md

+195-4
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,16 @@ _Core components for iOS to accelerate building user interfaces in code._
55
This lightweight framework primarily comprises:
66

77
* UIView extensions for declarative Auto Layout
8+
* Protocols to aid loading string, color, and image assets
89
* UIColor extensions for WCAG 2.0 contrast ratio calculations
910
* (iOS only) UIScrollView extensions to assist with keyboard avoidance
1011

1112
It also contains miscellaneous other Foundation and UIKit extensions.
1213

14+
Licensing
15+
----------
16+
Y—CoreUI is licensed under the [Apache 2.0 license](LICENSE).
17+
1318
Documentation
1419
----------
1520

@@ -56,7 +61,7 @@ container.constrain(.leadingAnchor, to: leadingAnchor)
5661
container.constrain(.trailingAnchor, to: trailingAnchor)
5762
container.constrain(.topAnchor, to: topAnchor)
5863
container.constrain(.bottomAnchor, to: bottomAnchor)
59-
```
64+
```
6065

6166
There are overrides to handle the common use case of placing one view below another or to the trailing side of another:
6267

@@ -110,7 +115,193 @@ addSubview(container)
110115
container.constrainEdgesToMargins()
111116
```
112117

113-
### 2. UIColor extensions for WCAG 2.0 contrast ratio calculations
118+
#### Constrain size
119+
120+
There are three convenience methods for constraining the size of a view.
121+
122+
```swift
123+
// constrain a button's size to 44 x 44 (3 different ways)
124+
let button = UIButton()
125+
addSubview(button)
126+
127+
button.constrainSize(CGSize(width: 44, height: 44))
128+
button.constrainSize(width: 44, height: 44)
129+
button.constrainSize(44)
130+
```
131+
132+
#### Constrain center
133+
134+
There is an Auto Layout convenience method for centering views.
135+
136+
```swift
137+
// center a container view to its superview
138+
let container = UIView()
139+
addSubview(container)
140+
141+
container.constrainCenter()
142+
143+
// center a button horizontally
144+
let button = UIButton()
145+
addSubview(button)
146+
147+
button.constrainCenter(.x)
148+
149+
// align a button and a label vertically by their centers
150+
let button = UIButton()
151+
let label = UILabel()
152+
addSubview(button)
153+
addSubview(label)
154+
155+
button.constrainCenter(.y, to: label)
156+
```
157+
158+
#### Constrain aspect ratio
159+
160+
There is an Auto Layout convenience method for constraining aspect ratio:
161+
162+
```swift
163+
// constrain to a 16:9 aspect ratio
164+
mediaPlayer.constrainAspectRatio(16.0 / 9)
165+
166+
// constrain to a 1:1 aspect ratio
167+
profileImage.constrainAspectRatio(1)
168+
```
169+
170+
### 2. Protocols to aid loading string, color, and image assets
171+
172+
We have extensions that accelerate loading strings, colors, and images (and make it easy to unit test them).
173+
174+
#### `Localizable`
175+
176+
Easily load localized string resources from any string-based `Enum`. All you need to do is declare conformance to `Localizable` and you gain access to a `localized: String` property.
177+
178+
```swift
179+
// Conform your Enum to Localizable
180+
enum SettingConstants: String, Localizable, CaseIterable {
181+
case title = "Settings_Title"
182+
case color = "Settings_Color"
183+
}
184+
185+
// Then access the localized string
186+
label.text = SettingsConstants.title.localized
187+
```
188+
189+
Unit testing is then easy:
190+
191+
```swift
192+
func test_Setting_Constants_loadsLocalizedString() {
193+
SettingConstants.allCases.forEach {
194+
// Given a localized string constant
195+
let string = $0.localized
196+
// it should not be empty
197+
XCTAssertFalse(string.isEmpty)
198+
// and it should not equal its key
199+
XCTAssertNotEqual($0.rawValue, string)
200+
}
201+
}
202+
```
203+
204+
The protocol also allows you to specify the bundle containing the localized strings and the optional table name.
205+
206+
#### `Colorable`
207+
208+
Easily load color assets from any string-based `Enum`. All you need to do is declare conformance to `Colorable` and you gain access to a `color: Color` property. You can even define a `fallbackColor` instead of `nil` or `.clear` so that UI elements won’t be invisible in the event of a failure (but they’re bright pink by default to catch your eye).
209+
210+
```swift
211+
// Conform your Enum to Colorable
212+
enum PrimaryColors: String, CaseIterable, Colorable {
213+
case primary50
214+
case primary100
215+
}
216+
217+
// Then access the color
218+
label.textColor = PrimaryColors.primary50.color
219+
```
220+
221+
Unit testing is easy:
222+
223+
```swift
224+
func test_PrimaryColors_loadsColor() {
225+
PrimaryColors.allCases.forEach {
226+
XCTAssertNotNil($0.loadColor())
227+
}
228+
}
229+
```
230+
231+
The protocol also allows you to specify the bundle containing the color assets, the optional namespace, and the fallback color.
232+
233+
#### `ImageAsset`
234+
235+
Easily load image assets from any string-based `Enum`. All you need to do is declare conformance to `ImageAsset` and you gain access to an `image: UIImage` property. You can even define a `fallbackImage` instead of `nil` so that UI elements won’t be invisible in the event of a failure (but it’s a bright pink square by default to catch your eye).
236+
237+
```swift
238+
// Conform your Enum to ImageAsset
239+
enum Flags: String, ImageAsset {
240+
case unitedStates = "flag_us"
241+
case india = "flag_in"
242+
}
243+
244+
let flag: Flags = .india
245+
// Then access the image
246+
let image: UIImage = flag.image
247+
```
248+
249+
If you add `CaseIterable` to your enum, then it becomes super simple to write unit tests to make sure they’re working properly (and you can add, update, modify the enum cases without needing to update your unit test).
250+
251+
```swift
252+
enum Icons: String, CaseIterable, ImageAsset {
253+
case value1
254+
case value2
255+
...
256+
case valueLast
257+
}
258+
259+
func test_iconsEnum_loadsImage() {
260+
Icons.allCases.forEach {
261+
XCTAssertNotNil($0.loadImage())
262+
}
263+
}
264+
```
265+
266+
The protocol also allows you to specify the bundle containing the image assets, the optional namespace, and the fallback image.
267+
268+
#### `SystemImage`
269+
270+
Easily load system images (SF Symbols) from any string-based `Enum`. All you need to do is declare conformance to `SystemImage` and you gain access to an `image: UIImage` property. Like `ImageAsset` above, you can define a `fallbackImage`.
271+
272+
Why bother doing this when it just wraps `UIImage(systemName:)`? Because
273+
1. `UIImage(systemName:)` returns `UIImage?` while `SystemImage.image` returns `UIImage`.
274+
2. Organizing your system images into enums encourages better architecture (and helps avoid stringly-typed errors).
275+
3. Easier to unit test.
276+
277+
```swift
278+
// Conform your Enum to SystemImage
279+
enum Checkbox: String, SystemImage {
280+
case checked = "checkmark.square"
281+
case unchecked = "square"
282+
}
283+
284+
// Then access the image
285+
button.setImage(Checkbox.unchecked.image, for: .normal)
286+
button.setImage(Checkbox.checked.image, for: .selected)
287+
```
288+
289+
If you add `CaseIterable` to your enum, then it becomes super simple to write unit tests to make sure they’re working properly (and you can add, update, modify the enum cases without needing to update your unit test).
290+
291+
```swift
292+
enum Checkbox: String, CaseIterable, SystemImage {
293+
case checked = "checkmark.square"
294+
case unchecked = "square"
295+
}
296+
297+
func test_checkboxEnum_loadsImage() {
298+
Checkbox.allCases.forEach {
299+
XCTAssertNotNil($0.loadImage())
300+
}
301+
}
302+
```
303+
304+
### 3. UIColor extensions for WCAG 2.0 contrast ratio calculations
114305

115306
Y—CoreUI contains a number of extensions to make working with colors easier. The most useful of them may be WCAG 2.0 contrast calculations. Given any two colors (representing foreground and background colors), you can calculate the contrast ration between them and evaluate whether that passes particular WCAG 2.0 standards (AA or AAA). You can even write unit tests to quickly check all color pairs in your app across all color modes. That could look like this:
116307

@@ -185,7 +376,7 @@ final class ColorsTests: XCTestCase {
185376
}
186377
```
187378

188-
### 3. UIScrollView extensions to assist with keyboard avoidance
379+
### 4. UIScrollView extensions to assist with keyboard avoidance
189380

190381
#### FormViewController
191382

@@ -281,7 +472,7 @@ Prior to submitting a pull request you should:
281472

282473
When submitting a pull request:
283474

284-
* Use the [provided pull request template](PULL_REQUEST_TEMPLATE.md) and populate the Introduction, Purpose, and Scope fields at a minimum.
475+
* Use the [provided pull request template](.github/pull_request_template.md) and populate the Introduction, Purpose, and Scope fields at a minimum.
285476
* If you're submitting before and after screenshots, movies, or GIF's, enter them in a two-column table so that they can be viewed side-by-side.
286477

287478
When merging a pull request:

Sources/YCoreUI/Extensions/UIKit/UIView+constrainAspectRatio.swift

+10
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,16 @@
99
import UIKit
1010

1111
extension UIView {
12+
/* Use-case examples
13+
14+
// constrain to a 16:9 aspect ratio
15+
mediaPlayer.constrainAspectRatio(16.0 / 9)
16+
17+
// constrain to a 1:1 aspect ratio
18+
profileImage.constrainAspectRatio(1)
19+
20+
*/
21+
1222
/// Constrain the aspect ratio for the receiving view.
1323
/// - Parameters:
1424
/// - ratio: aspect ratio

Sources/YCoreUI/Extensions/UIKit/UIView+constrainCenter.swift

+24
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,30 @@
99
import UIKit
1010

1111
extension UIView {
12+
/* Use-case examples
13+
14+
// center a container view to its superview
15+
let container = UIView()
16+
addSubview(container)
17+
18+
container.constrainCenter()
19+
20+
// center a button horizontally
21+
let button = UIButton()
22+
addSubview(button)
23+
24+
button.constrainCenter(.x)
25+
26+
// align a button and a label vertically by their centers
27+
let button = UIButton()
28+
let label = UILabel()
29+
addSubview(button)
30+
addSubview(label)
31+
32+
button.constrainCenter(.y, to: label)
33+
34+
*/
35+
1236
/// Center alignment options
1337
public struct Center: OptionSet {
1438
/// corresponding raw value

Sources/YCoreUI/Extensions/UIKit/UIView+constrainSize.swift

+16-4
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,25 @@
99
import UIKit
1010

1111
extension UIView {
12+
/* Use-case examples
13+
14+
// constrain a button's size to 44 x 44 (3 different ways)
15+
let button = UIButton()
16+
addSubview(button)
17+
18+
button.constrainSize(CGSize(width: 44, height: 44))
19+
button.constrainSize(width: 44, height: 44)
20+
button.constrainSize(44)
21+
22+
*/
23+
1224
/// Constrain the receiving view with given size
1325
/// - Parameters:
1426
/// - size: size of view to constrain to
1527
/// - relation: relation to evaluate (towards size) (default `.equal`)
1628
/// - priority: constraint priority (default `.required`)
1729
/// - isActive: whether to activate the constraint or not (default `true`)
18-
/// - Returns: The created layout constraint
30+
/// - Returns: dictionary of constraints created, keyed by `.width, .height`
1931
@discardableResult public func constrainSize(
2032
_ size: CGSize,
2133
relatedBy relation: NSLayoutConstraint.Relation = .equal,
@@ -48,7 +60,7 @@ extension UIView {
4860
/// - relation: relation to evaluate (towards width and height) (default `.equal`)
4961
/// - priority: constraint priority (default `.required`)
5062
/// - isActive: whether to activate the constraint or not (default `true`)
51-
/// - Returns: The created layout constraint
63+
/// - Returns: dictionary of constraints created, keyed by `.width, .height`
5264
@discardableResult public func constrainSize(
5365
width: CGFloat,
5466
height: CGFloat,
@@ -59,13 +71,13 @@ extension UIView {
5971
constrainSize(CGSize(width: width, height: height))
6072
}
6173

62-
/// Constrain the receiving view with given dimension
74+
/// Constrain the receiving view with given dimension applied to both width and height.
6375
/// - Parameters:
6476
/// - dimension: dimension of view to constrain to
6577
/// - relation: relation to evaluate (towards dimension) (default `.equal`)
6678
/// - priority: constraint priority (default `.required`)
6779
/// - isActive: whether to activate the constraint or not (default `true`)
68-
/// - Returns: The created layout constraint
80+
/// - Returns: dictionary of constraints created, keyed by `.width, .height`
6981
@discardableResult public func constrainSize(
7082
_ dimension: CGFloat,
7183
relatedBy relation: NSLayoutConstraint.Relation = .equal,

0 commit comments

Comments
 (0)