디자인 시스템으로 버튼, 텍스트 필드 등 여러 가지 컴포넌트를 만들었지만 우선 텍스트 필드로 예시를 들어보도록 하겠다.

해당 이미지는 'Gmaket Design system' 이미지다.(설명을 쉽게 하기 위해 사용하도록 하겠다.)
텍스트 필드를 디자인 시스템으로 만들기 위해서는 다음과 같은 규칙들이 필요할 것이다.
색상 - 포커스 on, error, 텍스트 색상, title 색상, sub title 색상...
폰트 - 플레이스홀더, 텍스트, 타이틀, 서브 타이틀, error message ...
추가로 텍스트 필드 위, 아래의 타이틀, 에러 메세지 여부 등..
과거 디자인 시스템을 만들고자 할 때 떠올렸던 방법은
1. switch 문으로 각각을 케이스로 만들어서 case를 호출하는 방법
2. 생성자에 필요한 정보를 입력해 컴포넌트를 만들어 내는 방법
1. switch 문 사용
enum TextFieldStyleType {
case normal
case underline
case rounded
}
...
var body: some View {
switch style {
case .normal:
TextField(placeholder, text: $text)
...
...
StyledTextField(placeholder: "이름 입력", text: $name, style: .normal, errorMessage: "에러 메세지", title: "타이틀", subTitle: "서브 타이틀")
이렇게 만들 수 있을 것이다. 이렇게 하면 case 별로 정의가 잘되고 쉽게 호출할 수 있긴 하지만 Case 별로 새로 컴포넌트를 다시 정의 해야 하기 때문에 '비슷한 컴포넌트에 변형인데 다시 만들기 불편하다'라고 생각이 들었다.
추가로 디자인은 케이스로 만들어서 호출할 수 있지만 결국 title, errorMessage, subTitle 데이터를 입력해줘야 하고 init() 생성자의 길이가 길어서 불편하다고 느껴졌다.
2. init 생성자에 추가와 초기값 활용 ✅
case 문을 써도 init 생성자가 길어진다면 case문을 사용하지 않고 init으로 디자인시스템을 활용하면 되겠다라는 생각이 처음에는 더 컸다.
// ✅ init에 기본값 지정
init(
title: String? = nil,
subtitle: String? = nil,
placeholder: String,
text: Binding<String>,
backgroundColor: Color = Color.gray.opacity(0.1),
borderColor: Color = .gray,
cornerRadius: CGFloat = 8,
padding: CGFloat = 12
) {
self.title = title
self.subtitle = subtitle
self.placeholder = placeholder
self._text = text
self.backgroundColor = backgroundColor
self.borderColor = borderColor
self.cornerRadius = cornerRadius
self.padding = padding
}
var body: some View {
VStack(alignment: .leading, spacing: 6) {
if let title {
Text(title)
.font(.headline)
}
TextField(placeholder, text: $text)
.padding(padding)
.background(backgroundColor)
.overlay(
RoundedRectangle(cornerRadius: cornerRadius)
.stroke(borderColor, lineWidth: 1)
)
.cornerRadius(cornerRadius)
if let subtitle {
Text(subtitle)
.font(.caption)
.foregroundColor(.gray)
}
}
}
이렇게 만들면 필요한 케이스, 컴포넌트가 필요할 때 Init 생성자에 값에 추가를 하면 된다. 추가로 필요없다면 init에 초기값이 정해져 있기 때문에 꼭 추가를 하지 않아도 컴포넌트가 완성될 수 있다는 장점이 있었다.
하지만 결국 필요한 케이스가 많아졌을 때 init 부분이 엄청 길어지는 단점이 있었다. 추가로 디자인을 조금 바꾸려고 해도 init에 추가하는 과정이 번거로움이 있었다.
public init(text: Binding<String>, placeholder: String, isDisable: Bool? = nil, isSecureField: Bool, keyboardType: UIKeyboardType? = nil, backgroundColor: UIColor? = UIColor(named: "ColorGray50"), font: UIFont = .systemFont(ofSize: 12), fontColor: UIColor? = UIColor.black, verticalPadding: CGFloat = 12, placeHolderColor: UIColor? = nil, cursorColor: UIColor? = nil, onFocusOut: @escaping () -> Void) {
self._text = text
self.placeholder = placeholder
self.isDisable = isDisable
self.isSecureField = isSecureField
self.keyboardType = keyboardType
self.backgroundColor = backgroundColor
self.font = font
self.fontColor = fontColor
self.verticalPadding = verticalPadding
self.placeHolderColor = placeHolderColor
self.cursorColor = cursorColor
self.onFocusOut = onFocusOut
}
이 부분을 어떻게 해결하면 좋을까 생각을 하다 결국 SwiftUI의 컴포넌트 구성 방식을 살펴봤다.


extension Text {} 안에 정의 되어 있고 return 이 다시 Text이다. 이걸 함수형 체이닝이라고 한다.
결국 SwiftUI의 UI 구성 방식은 필요한 컴포넌트는 선언하고 디자인을 수정하기 위해 다시 리턴을 하는 메커니즘을 갖는다.
이러한 메커니즘을 그대로 다시 사용하는 것이 SwiftUI로 UI 를 만드는 입장에서 익숙하고 편한 방식이 될 수 있겠다.라고 생각했다.
'개발경험' 카테고리의 다른 글
| 디자인 시스템 개발 이야기(3) - 함수형 체이닝을 통한 디자인 시스템 (0) | 2025.10.04 |
|---|---|
| single source 아키텍처로 가는 길(2) - Store 패턴 및 개발 시 고려사항 (0) | 2025.09.18 |
| 디자인 시스템 개발 이야기(1) - 아토믹 디자인 (0) | 2025.08.24 |
| [Swift] CMPedomter 러닝앱 케이던스 만들기 (3) | 2025.06.06 |
| [Swift Unit Test] "오운완" 효과적인 테스트를 위한 고민(코드 재사용) (0) | 2023.09.19 |