This is the first article in a series going into detail about the Open Source Work for iOS that I have been leading at YML.
One of the first Swift package we created and released was Y—CoreUI. As its name suggests, it is one of our core libraries for doing UI work on iOS. It consists primarily of:
1. UIView
extensions for declarative Auto Layout
2. UIColor
extensions for WCAG 2.0 contrast ratio calculations
3. Protocols to aid loading string, color, and image assets
4. Model objects to declare drop shadows and animations
UIView extensions for declarative Auto Layout
I’m a big proponent of building UI’s entirely in code when working in UIKit. NSLayoutConstraint
is an old friend, but cumbersome to use. These layout extensions just create NSLayoutContraint
‘s but in a more declarative and less verbose manner and with smart defaults for most parameters. Want to constrain a subview to all 4 sides of its superview?
subview.constrainEdges()
Want to pin a subview to the center of its superview?
subview.constrainCenter()
It’s that simple. Need to do something more complex like centering a view horizontally but ensuring that it doesn’t overflow its superview? That’s also simple.
subview.constrainCenter(.x)
subview.constrainEdges(.horizontal, relatedBy: .greaterThanOrEqual)
(That last line is clever enough to invert the relationship and created the .trailingAnchor
constraint with the .lessThanOrEqual
relation.)
UIColor extensions for WCAG 2.0 contrast ratio calculations
I think this is my favorite part of this library. WCAG 2.0 specifies contrast requirements for foreground and background colors that appear together to meet AA or AAA levels of compliance (I like this handy checker). This ratio works by converting the color into LCh color space (great article on LCh) and comparing the Luminance values. There are various design tools to check these contrast ratios, but what if we could do it programmatically as part of unit tests and across both light and dark modes and regular and increased contrast modes?
XCTAssert(
foregroundColor.isSufficientContrast(
to: backgroundColor,
context: .normalText,
level: .AA
)
)
As a bonus we expanded the calculations to work when the foreground color is not opaque (this is the case for Apple’s system colors such as UIColor.secondaryLabel
). If you need the exact contrast ration value, there’s a method for that as well.
let ratio: CGFloat = foregroundColor.contrastRatio(to: backgroundColor)
Protocols to aid loading string, color, and image assets
These are a series of protocols that make it simple and testable to load localized strings, colors, images, and even SF Symbols using enums. Of course it’s trivial to load a color from an asset catalog using UIColor(named: "someName")
, but from my viewpoint this has some drawbacks:
1. not easily unit testable
2. fails silently (returns nil
)
Y—CoreUI provides a Colorable
protocol that any string-based enum automatically conforms to and adds the following methods:
var color: UIColor { get }
func loadColor() -> UIColor?
loadColor()
is failable and ideal for unit testing, while color
is guaranteed to return a color (by default UIColor.systemPink
as a fallback color), but will also log a warning to the console if it falls back. Usage and unit testing is simple:
enum WarningColors: String, Colorable, CaseIterable {
case warning50
case warning100
}
func test_warningColors_deliversColors {
WarningColors.forEach {
XCTAssertNotNil($0.loadColor())
}
}
The Localizable
, ImageAsset
, and SystemImage
protocols do the same thing for localized string resources, image assets, and system images (SF Symbols), respectively.
Model objects to declare drop shadows and animations
A large part of our jobs as iOS developers is rendering user interfaces from designs, but this can some times be difficult because the way designs are described in design files can differ from how they are implemented on iOS. For example, Figma and Sketch describe drop shadows as they are defined for the web using the parameters: horizontal offset, vertical offset, blur radius, and spread. On iOS though we have shadowRadius
, which when you look in Stack Overflow contains incorrect answers as to how to convert blur radius to shadowRadius
. In Y—CoreUI we defined an Elevation
object that describes a drop shadow in web/design terminology and knows how to properly apply them to views. This includes correctly calculating spread for rects and round rects. The Elevation
object was designed to be automatically generated from Figma tokens using transformation tools such as Style Dictionary.
Another useful model object is Animation
which lets us build components with customizable animations without necessarily knowing exactly which type of animation will be chosen. We leverage this for the animations in our snackbar/toast library, Y—Snackbar.
/// Animation curve
public enum Curve: Equatable {
/// Regular animation curve
case regular(options: UIView.AnimationOptions)
/// Spring animation
case spring(damping: CGFloat, velocity: CGFloat, options: UIView.AnimationOptions = [])
}
That wraps up a cursory overview of YML’s core iOS UI library. In my next article I will describe its companion library for rendering design system typography, Y—Matter Type.