How to Efficiently Apply Design Requirements to App Development
How DelightRoom applies design to app development
Have you ever found it annoying to apply fonts, sizes, and colors to text while developing an app? Or have you ever made a typo while entering values and had to sit through a painfully long build process all over again?
Applying design requirements is an inevitable part of app development. Since the tasks are repetitive, they can get tedious, and typing out all those values often leads to typos.
In the beginning, there was annoyance
Let's say we're implementing the screen below in the app. This is actually one of the screens included in the Today's Panel of the Alarmy iOS app.
Let's think about how to display the text "Drink a glass of water" in the app. If we break it down into steps, it looks like this:
1. What What are we going to show?
The text "Drink a glass of water"
2. How How are we going to show it?
The font and size to apply to the text
The color to apply to the text
3.Where Where are we going to show it?
The position to display the text with the applied font and color
If we write each step in code, it looks like this:
//What
let titleText: String = "물 한잔 마시기"
//How
let attributes: [NSAttributedString.Key: Any]
= [.font: UIFont.systemFont(ofSize: 20, weight: .heavy),
.foregroundColor: UIColor(red: 31 / 255, green: 37 / 255, blue: 46 / 255, alpha: 1.0)]
let attributedText = NSMutableAttributedString(string: titleText, attributes: attributes)
//Where
let label = UILabel(frame: CGRect(x: 10, y: 10, width: 300, height: 20)
label.attributedText = attributedTextLet's look at the "How" part again. The font, size, and color values were entered manually based on the numerical values from the design requirements. What if we had to apply this process to every single piece of text? Doing it one by one invites typos. It's not easy to modify, making maintenance a pain. But above all, it's just annoying. In this post, we'll cover how the Alarmy iOS team efficiently applies and implements these requirements.
We have the ADS (Alarmy Design System)
Alarmy has a design system. Fonts, sizes, and colors are all defined within it. Thanks to this system, our design outputs maintain consistency.
I thought it would be much more efficient if we could utilize this system during development, too. It prevents the invisible risk of human error—like entering the wrong font, size, or color value. The code becomes cleaner, and modifications become easier, boosting engineer productivity. Plus, it would make communicating with designers much smoother. Since we'd be talking through predefined requirements, discussions would be easier and revisions much more manageable.
Annoyance sparks efficiency
Let's find out how fonts, sizes, and colors are defined in the design system and take a look at how we implement them in code.
Step 1. Typography
Typography corresponds to the fonts and sizes in the design system.
Typography has unique names like Hero, Hero2, Title1. And the appropriate font and size are assigned to each. If we express this in code, it looks like this:
public enum Typography {
case hero1
case hero2
case title1
case title2
case title3
//...
}
extension Typography {
var font: UIFont {
let result: UIFont
switch self {
case .hero1: result = UIFont.systemFont(ofSize: 110, weight: .heavy)
case .hero2: result = UIFont.systemFont(ofSize: 48, weight: .heavy)
case .title1: result = UIFont.systemFont(ofSize: 32, weight: .heavy)
case .title2: result = UIFont.systemFont(ofSize: 26, weight: .heavy)
case .title3: result = UIFont.systemFont(ofSize: 20, weight: .heavy)
//...
return result
}
}Using this, we can improve the "How" part of the previous code like so:
//How
let attributes: [NSAttributedString.Key: Any]
= [.font: Typography.title3,
.foregroundColor: UIColor(red: 31 / 255, green: 37 / 255, blue: 46 / 255, alpha: 1.0)]
let attributedText = NSMutableAttributedString(string: titleText, attributes: attributes)The UIFont.systemFont(ofSize: 20, weight: .heavy) code has been simplified to .font: Typography.title3.
Step 2. Color
Next, let's look at the Color section. Colors are defined as follows:
Similarly, Color also has unique names like background, appbar. Specific color values are assigned to them as well. If we express this in code, it looks like this:
extension UIColor {
public static var background: UIColor {
return UIColor(hexString: "#F5F9FF")
}
public static var appbar: UIColor {
return UIColor(hexString: "#E8F1FF")
}
public static var scrim: UIColor {
return UIColor(hexString: "#30435F").withAlphaComponent(0.8)
}
public static var onSurfaceDisabled: UIColor {
return UIColor(hexString: "#BDCFED")
}
public static var surface: UIColor {
return UIColor(hexString: "#FFFFFF")
}
public static var onSurfaceHighEmphasis: UIColor {
return UIColor(hexString: "#1F252E")
}
//...
}Now let's modify the code in the "How" section again.
//How
let attributes: [NSAttributedString.Key: Any]
= [.font: Typography.title3,
.foregroundColor: UIColor.onSurfaceHighEmphasis]
let attributedText = NSMutableAttributedString(string: titleText, attributes: attributes)The .foregroundColor: UIColor(red: 31 / 255, green: 37 / 255, blue: 46 / 255, alpha: 1.0 ) part has been simplified to .foregroundColor: UIColor.onSurfaceHighEmphasis.
Step 3. extension
We were finally able to prevent human errors by using predefined values. However, the code still wasn't intuitive. It was also a bit inconvenient to use.
To improve this, we added the following extension. (It's totally fine if you don't technically understand the code below. 🙂)
extension String {
private func attributedString(type: Typography) -> NSMutableAttributedString {
let attributes: [NSAttributedString.Key: Any] = [.font: type.font, .foregroundColor: UIColor.onSurfaceHighEmphasis]
return NSMutableAttributedString(string: self, attributes: attributes)
}
public var title3: NSMutableAttributedString {
return attributedString(type: .title3)
}
//...
}
extension NSMutableAttributedString {
public func addColor(_ uiColor: UIColor) -> NSMutableAttributedString {
let result = self
let range = NSRange(location: 0, length: self.length)
if attribute(.foregroundColor, at: 0, effectiveRange: nil) != nil {
removeAttribute(.foregroundColor, range: range)
}
result.addAttribute(.foregroundColor, value: uiColor, range: range)
return result
}
//...
}If we apply the improvement above, the "How" section can be expressed in just one line of code.
//How
let attributedText = titleText.title3.addColor(.onSurfaceHighEmphasis)Let's read the code. It flows like this: "Apply the title3 font to titleText (Drink a glass of water) and add the onSurfaceHighEmphasis color." This makes it much more readable, and the expression itself is cleaner.
Step 4. one more thing,,
Most of the codes above are implemented using extension. And while I shortened them for convenience here, Typography and Color actually exceed 100 lines of code each, making them quite bulky. I didn't want to add even a single second to our already lengthy build times.
The codes we wrote have no dependencies on the app and are highly reusable. So, I figured it made sense to create a Swift Package. That's how the ALDesign module, which implements our design system, was born.
Engineers don't need to know the implementation details above; they just need to write the code like this:
import ALDesign
//How
let attributedText = titleText.title3.addColor(.onSurfaceHighEmphasis)Conclusion
After applying the design system, we were able to transform our collaboration and communication to be more comfortable and efficient. Revision requests became clearer, and thanks to that, our long build times were significantly reduced. To put it qualitatively, it looks like this. Before, when exchanging development-related feedback with a designer, our communication went something like this:
디자이너: 여기 문구가 실제 앱으로 보니 좀 작은 느낌인데요. 조금 키워서 볼 수 있을까요?
개발자: 얼마나요?
디자이너: 음..한 2 정도?
개발자: 네, 잠시만요~
(5분의 빌드 타임 후)
개발자: 이렇게 어떠세요?
디자이너: 죄송한데 한 2정도 더 크게 볼 수 있을까요?
개발자: 넵~
(또다시 5분의 빌드 타임 후)
개발자: 좀 더 키워왔어요~
디자이너: 네, 이렇게 가요!After applying the design system, communication became much more concise and efficient.
디자이너: 여기 문구가 실제 앱으로 보니 좀 작은 느낌인데요. 조금 키워서 볼 수 있을까요? 적용된 타이포그라피가 title3인데 title2로요!
개발자: 네, 잠시만요~
(5분의 빌드 타임 후)
디자이너: 네, 이렇게 가요!Development work also gained the following benefits, improving engineering productivity.
We can clean up the code consistently by improving the parts where identical implementations are reused.
Human error decreases since there is no need to manually enter font, size, and color values.
Revision work is straightforward.
Up to this point, we've looked at how to efficiently apply design requirements to app development. For me, it was a great experience to realize the efficiency that DelightRoom strives for, and technically, to get more comfortable working with Swift Packages. Thanks for reading to the end!
Smis, Swift man in Seoul
I'd like to wrap up this post by thanking all the Delighters who offered encouragement and support throughout the process of applying the design system and taking it all the way to modularization.
If you want an environment where you can grow as an iOS engineer! Check out the Alarmy team
References