반응형

SwiftUI Study – ViewModifier를 제대로 설계해 코드 중복을 줄이고 재사용성을 극대화하는 방법

 

1. 왜 중요한가 (문제 배경)

SwiftUI에서 가장 강력한 기능 중 하나가 ViewModifier이다.

하지만 많은 개발자들이 이를 단순 스타일 래핑 정도로만 사용한다.

그 결과 다음 문제가 자주 발생한다.

  • 프로젝트 곳곳에 동일한 padding·cornerRadius·shadow 패턴이 중복
  • 다크모드·접근성·환경 변화 대응이 어려움
  • UI 스타일이 뷰마다 미묘하게 달라 일관성 붕괴
  • 수정 시 수십 개의 뷰를 직접 업데이트해야 하는 상황 발생
  • Modifier 내부에서 잘못 설계해 의도한 대로 작동하지 않음

문제의 핵심은 다음이다.

“ViewModifier는 단순 꾸미기 용도가 아니라,

UI 정책과 컴포넌트 규칙을 재사용 가능한 단일 소스로 관리하는 도구이다.”

올바르게 설계하면 UIKit의 디자인 시스템처럼 강력하게 사용할 수 있다.

 

2. 잘못된 패턴 예시

❌ 예시 1: 반복되는 UI 스타일을 그대로 뷰마다 붙여넣기

Text("확인")
    .padding()
    .background(Color.blue)
    .foregroundColor(.white)
    .cornerRadius(8)

문제점

- 다른 화면에서도 동일한 코드 반복

- 스타일 변경이 생기면 모든 파일 수정

- 디자인 정책 반영이 어려움

❌ 예시 2: Modifier 안에서 상태를 변경하는 실수

struct WrongModifier: ViewModifier {
    @State private var isOn = false   // ❌ Modifier가 상태를 가지면 재사용 불가

    func body(content: Content) -> some View {
        content
            .onTapGesture {
                isOn.toggle()  // ❌ Modifier는 상태 제어 역할이 아님
            }
    }
}

문제점

- Modifier는 순수해야 하는데 상태를 가지면 예측 불가

- 여러 뷰에서 동일 Modifier 사용 시 상태 충돌

- SwiftUI 구조에 반함

❌ 예시 3: Modifier가 너무 많은 역할을 담당

struct ComplexModifier: ViewModifier {
    let isEnabled: Bool
    let user: User

    func body(content: Content) -> some View {
        content
            .padding()
            .background(isEnabled ? Color.green : Color.red)
            .onAppear {
                if !user.isActive { /* 네트워크 호출 */ }   // ❌ ViewModifier 책임 아님
            }
    }
}

문제점

- Modifier가 UI 스타일이 아닌 비즈니스 로직까지 포함

- 테스트 불가능

- 재사용성 저하

 

3. 올바른 패턴 예시

✅ 예시 1: 핵심 UI 디자인 정책을 Modifier로 통합

struct PrimaryButtonModifier: ViewModifier {
    func body(content: Content) -> some View {
        content
            .font(.headline)
            .padding(.vertical, 12)
            .padding(.horizontal, 20)
            .background(Color.blue)
            .foregroundColor(.white)
            .cornerRadius(10)
    }
}

사용:

Text("확인").modifier(PrimaryButtonModifier())

장점

- 버튼 스타일 변경 시 Modifier만 바꾸면 UI 전체 반영

- 디자인 시스템 구축 가능

✅ 예시 2: 환경 변화 대응이 쉬운 Modifier

struct CardModifier: ViewModifier {
    @Environment(\.colorScheme) var cs

    func body(content: Content) -> some View {
        content
            .padding()
            .background(cs == .dark ? Color.black : Color.white)
            .cornerRadius(12)
            .shadow(radius: cs == .dark ? 0 : 4)
    }
}

장점

- 다크모드 정책 자동 반영

- 전체 앱 스타일이 중앙화됨

✅ 예시 3: Modifier를 확장 형태로 제공해 사용성 향상

extension View {
    func primaryButtonStyle() -> some View {
        self.modifier(PrimaryButtonModifier())
    }
}

사용:

Text("확인").primaryButtonStyle()

장점

- 훨씬 직관적인 API

- UIKit의 appearance처럼 정책 적용 가능

✅ 예시 4: 값 주입형 Modifier (인자 기반으로 UI 정책 조절)

struct BadgeModifier: ViewModifier {
    let count: Int

    func body(content: Content) -> some View {
        content
            .overlay(
                Text("\(count)")
                    .font(.caption)
                    .padding(6)
                    .background(Color.red)
                    .foregroundColor(.white)
                    .clipShape(Circle())
                    .offset(x: 12, y: -12),
                alignment: .topTrailing
            )
    }
}

장점

- 유연한 UI 컴포넌트 확장

- ViewModifier의 강력한 표현력 활용 가능

 

4. 실전 적용 팁

✔ 팁 1 – Modifier는 상태 없이 “순수한 스타일 계층”으로 유지

비즈니스 로직, 상태 변화 금지.

✔ 팁 2 – 앱의 UI 디자인 정책은 Modifier로 통합 관리

padding, cornerRadius, shadow 등 반복 패턴은 모두 Modifier화.

✔ 팁 3 – Extension을 제공해 사용성을 높일 것

.primaryButtonStyle() 처럼 읽기 좋은 API 제공.

✔ 팁 4 – 환경(Environment) 값 활용해 다크모드·Dynamic Type 대응

모든 스타일 정책이 Modifier 안에서 통일되므로 유지보수가 쉬워짐.

✔ 팁 5 – 복잡한 Modifier는 컴포넌트(커스텀 View)로 승격

Modifier는 스타일 중심, 로직이 필요하면 Custom View로 분리.

 

5. 정리

  • ViewModifier는 단순한 스타일 래핑이 아니라 UI 디자인 정책을 중앙화하는 강력한 수단이다.
  • Modifier는 순수해야 하며, 상태·비즈니스 로직을 포함하지 않아야 한다.
  • Extension을 활용해 재사용성과 가독성을 동시에 높일 수 있다.
  • 앱 전체의 시각적 일관성과 유지보수성을 높이는 핵심 도구로 활용해야 한다.
반응형
Posted by 까칠코더
,