개인 프로젝트 중 color를 namespace로 지정해 주려다가 좀 더 효율적이게 사용해 줄 수는 없을까 생각하다가 SwiftGen라는 도구를 찾았는데 보다 선언적인 프로그래밍을 할 수 있을 것 같아서 사용해 주게 되었다.
보통은 UIColor의 extension에 편의 생성자 만들어서 hex나 rgb를 컬러 활용할 수 있도록 해주고 열거형에 타입 프로퍼티(static let)를 사용하여 namespace로 지정해주곤한다.
아래처럼 말이다.
extension UIColor {
convenience init(hexCode: String, alpha: CGFloat = 1.0) {
var hexFormatted: String = hexCode.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).uppercased()
if hexFormatted.hasPrefix("#") {
hexFormatted = String(hexFormatted.dropFirst())
}
assert(hexFormatted.count == 6, "Invalid hex code used.")
var rgbValue: UInt64 = 0
Scanner(string: hexFormatted).scanHexInt64(&rgbValue)
self.init(red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0,
green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0,
blue: CGFloat(rgbValue & 0x0000FF) / 255.0,
alpha: alpha)
}
}
//namespace
enum Color {
static let mainText = UIColor(hexCode: "8FCB9B")
}
namespace에 이렇게 지정해서 컬러를 사용할 때에는 Color.mainText 이런 식으로 접근해서 사용할 수 있게 만들어놓곤 하는 것이다.
하지만 나는 다크모드까지 함께 지원해 주는 asset catalog(에셋을 관리해 주기 위한 폴더)로 색을 지정해주고 싶은데 컬러를 asset으로 추가할 경우 사용할 때 저장한 컬러이름을 string으로 정확하게 입력해주어야 한다.
이렇게...
label.textColor = UIColor(named : "gray2")
이렇게 string으로 입력해줘야 하는 경우에 우리가 모든 상황에서 완벽하게 오타 없이 써줄 수 있을까? 난 자신 없다..ㅎㅎ
그리고 오타로 작성을 해도 컴파일 타임에 제대로 컬러 이름을 넣어주었는지 여부를 알아챌 수 없다는 문제점이 있다. 이러한 오타의 위험을 줄여주고 선언적인 프로그래밍을 할 수 있도록 도와주는 도구가 있는데 그게 바로 swifgen인 것이다.
SwiftGen이란?
관련문서에서는 SwiftGen을 이렇게 소개한다.
The Swift code generator for your assets, storyboards, Localizable.strings, … — Get rid of all String-based APIs!
(assets, storyboards, Localizable.strings 등을 위한 Swift 코드 생성기 — 모든 문자열 기반 API를 제거하세요!)
풀어서 설명하자면,
- SwiftGen은 프로젝트에 있는 리소스를 Swift 코드로 자동 생성해서 타입 안정성을 부여하는 도구라고 볼 수 있다.
- 일반적으로 리소스 ( Asset파일이나, 폰트 등 ) 들을 불러와서 코드로 작성할 때, 이름을 정확하게 입력해야 문제없이 불러올 수 있는데, 간혹 실수로 오타를 내거나 하면 불러오질 못한다. 곧바로 오타라는 것을 캐치하면 좋겠지만 때로는 다른 문제라고 생각하여 시간을 허비하는 경우도 발생한다.
- 그러므로, SwiftGen은 이러한 오타의 위험을 줄여줌으로써 좀 더 개발에 집중할 수 있도록 도와준다.
label.textColor = UIColor(named : "mainYellow")
button.setImage("imageName", for : .normal)
이런 식으로 코드를 작성해 주었을 때의 문제점은 이제 충분히 알았으니 SwiftGen을 어떻게 사용해서 선언적인 프로그래밍을 해줄 수 있을지 알아보자!
설치 및 활용 과정
1) 설치
설치할 수 있는 여러가지 방법이 있는데 https://github.com/SwiftGen/SwiftGen 참고하기!!
나는 이 중 homebrew로 설치하는 방법을 택했다
--> homebrew로 설치
// 터미널에
$ brew update
$ brew install swiftgen
2) configuration file 생성
swiftgen 설치했으면 이제 특정 리소스들을 코드로 생성하기 위한 swiftgen,yml 파일이 필요하다.
- 이는 터미널로도 해당 프로젝트 최상단에서 swiftgen config init 이라고 입력하여 파일을 생성할 수 있고,
- 또는 Build Phase - Run Script에서 다음과 같이 작성하여 빌드하면 파일이 생성되는 걸 확인할 수 있다.
$ cd {Xcode 프로젝트 위치}
$ swiftgen config init
yml 파일 생성 성공 시 이런 메시지가 뜬다.
yml 파일을 만드는 데 성공하면 주석으로 이루어진 swiftgen.yml 파일이 열린다.
3) swiftgen.yml 파일 작성
이렇게 열린 yml 파일에서 코드로 생성하고자 하는 리소스들을 인풋 경로와, 아웃풋 경로를 적절히 작성하면 된다.
나는 에셋 파일과 폰트에 대한 swift 코드를 생성하고 싶기 때문에 xcassets와 fonts라는 swiftgen의 파서(parser)들을 기준으로 적어주었다.
xcassets:
inputs:
- Colors.xcassets
- Assets.xcassets
outputs:
- templateName: swift5
output: Assets+Generated.swift
params:
enumName: Assets
fonts:
inputs:
- Font/Pretendard
- Font/Suit
outputs:
templateName: swift5
output: Fonts+Generated.swift
4) 실제 코드 파일 생성
- yml 파일을 작성해주고 나서 swiftgen config run 명령어를 사용하여 우리가 설정한 경로에 실제 코드가 생성되도록 해주어야한다.
$ swiftgen config run
실제 swift코드가 생성되는 파일을 생성하는 것에 성공했을 때는 이렇게 메시지가 뜬다.
근데 파일 계층으로 봤을 때는 생기는데 xcode 프로그램 내부 파일 구조에서는 안 생기기 때문에
중요한 것은 우리가 직접 프로젝트에 파일을 옮겨와 주어야 한다!!
- 이렇게 내가 output에 지정한 이름대로 생성된 파일( Assets+Generated.swift, Fonts+Generated.swift 등)을 열어보면 이런식으로 열거형이 만들어져 있다.
internal enum Assets {
internal enum Assets {
internal static let accentColor = ColorAsset(name: "AccentColor")
internal static let filledEgg = ImageAsset(name: "filledEgg")
}
internal enum Colors {
internal static let gray1 = ColorAsset(name: "Gray1")
internal static let black = ColorAsset(name: "black")
internal static let eggWhite = ColorAsset(name: "eggWhite")
internal static let gray2 = ColorAsset(name: "gray2")
internal static let gray3 = ColorAsset(name: "gray3")
internal static let gray4 = ColorAsset(name: "gray4")
internal static let mainYellow = ColorAsset(name: "mainYellow")
internal static let white = ColorAsset(name: "white")
}
}
5) run script 작성
- 근데 만약 컬러를 더 추가하고 싶다고 가정하자.
- xcassets 파일에만 컬러 추가한다고 생성해 준 Assets+Generated.swift코드까지 바뀔 수 없다는 것을 우리는 안다.
purple이라는 컬러를 추가했을 때 이렇게 xcassets 파일에만 추가해 주고 프로젝트 빌드한다고 Assets+Generated.swift 파일의 Colors 열거형이 수정되진 않기 때문에 run script를 작성해주면 편하다!!
Targets → Build Phases -> 새로운 run script 추가 → run script 작성해 주고 빌드해주면 기존 파일 업데이트되어 있을 것이다!!
즉, 새로운 색깔을 asset파일에 추가해 주는 상황을 예로 들면, 터미널에 다시 swiftgen config run 명령어로 파일을 생성하고... 파일을 프로젝트에 옮겨오고... 하기에 불편하니까 빌드 한 번 할 때 자동으로 새로운 컬러를 다시 코드화하도록 run script를 작성해 주면 편리한 것이다.
실제 사용
- 이렇게 리소스가 코드화된 swift파일을 을 프로젝트로 가져왔으면
- 이걸 활용해서 String으로 적어야 하는 것을 보수할 수 있다.
- 오타가 있을 때 컴파일 타임에 오류를 알 수 없기 때문에 위험할 수 있는 것을swifegen을통해 코드화함으로써 해소할 수 있는 것!
//swiftgen 사용하지 않았을 때
label.textColor = UIColor(named : "gray2")
imageView.image = UIImage(named: "filledEgg")
label.font = UIFont(name: "Pretendard-ExtraBold", size: 20)
//swiftgen 으로 컬러 사용
label.textColor = Assets.Colors.gray2.color
//swiftgen 으로 이미지 사용
imageView.image = Assets.Assets.filledEgg.image
//swiftgen 으로 폰트 사용
label.font = FontFamily.Pretendard.extraBold.font(size: 20)