본문으로 건너뛰기

SwiftUI 뷰를 재사용 가능하게 만드는 3가지 전략

3 Key Strategies to Make SwiftUI Views More Reusable

matteomanferdini.com

핵심 요약

  1. 거대한 뷰의 일부를 독립된 View 구조체로 추출하면 부모 뷰는 화면 구성을 설명하고, 하위 뷰는 구체적인 UI 표현을 캡슐화할 수 있습니다.
  2. 여러 뷰에 공통 스타일이나 동작을 적용할 때는 ViewModifier로 수정자 묶음을 재사용하면 일관성을 유지하고 본문을 간결하게 만들 수 있습니다.
  3. 제네릭과 @ViewBuilder를 사용하면 내부 콘텐츠 종류를 미리 정하지 않는 컨테이너를 만들어 텍스트, 이미지, 복합 뷰에 같은 레이아웃을 적용할 수 있습니다.

도입

SwiftUI에서는 화면을 빠르게 구성할 수 있지만, 초기에는 편리했던 단일 content view가 커지면 레이아웃과 UI 로직이 한곳에 얽힙니다. 중첩된 스택과 복사된 코드가 늘면 읽기와 디버깅이 어려워지고, 한 곳의 수정이 다른 복사본에 반영되지 않습니다. 글은 이런 단일 대형 뷰를 재사용 가능한 구성 요소로 나누는 세 가지 방법을 제시합니다.

상세 요약

단일 대형 뷰를 나누는 이유

SwiftUI의 선언형 구문은 복잡한 화면도 빠르게 구성하게 하지만, 화면 전체를 하나의 content view에 계속 추가하면 본문이 커지고 UI 로직과 레이아웃이 얽힙니다. 코드가 커질수록 중첩된 스택과 복사된 구성 요소가 의도를 가립니다. 색상이나 UI 버그를 수정할 때 여러 복사본을 직접 찾아야 하고, 큰 파일은 이해와 유지보수를 어렵게 만듭니다. 이는 관심사 분리와 DRY 원칙을 해칩니다.

가장 단순한 출발점은 뷰 추출입니다.

  • 목록 행, 카드, 사용자 지정 버튼처럼 독립적인 UI 요소를 찾습니다.
  • View 프로토콜을 따르는 새 구조체를 만들고, 렌더링에 필요한 데이터만 단순한 프로퍼티로 전달합니다.
  • 부모 뷰에서 새 뷰를 호출해 기존 코드 조각을 대체합니다.

Xcode의 Refactor 메뉴에는 선택한 코드를 파일로 추출하는 Extract Selection to File 옵션도 있습니다. 추출 후 부모 뷰는 화면에 무엇이 놓이는지 표현하고, 하위 뷰는 그 요소가 어떻게 보이는지를 담당합니다. 독립된 뷰는 별도로 미리 보고 다룰 수 있습니다.

computed property는 재사용 가능한 뷰를 대체하지 않는다

큰 body를 computed property로 나누면 본문은 짧아 보이지만, UI 로직은 부모 뷰의 범위 안에 남습니다. 부모가 전체 레이아웃뿐 아니라 세부 구성 요소까지 책임지고, 프로퍼티가 부모 상태에 의존할 수 있어 결합도도 유지됩니다. 다른 화면에서 해당 프로퍼티를 그대로 공유할 수 없으므로 재사용하려면 코드를 복사해야 합니다.

원문은 computed property에 독립된 identity가 없으므로 SwiftUI의 diffing과 렌더링 최적화가 전용 구조체만큼 효과적이지 않을 수 있다고 설명합니다. 해당 UI 조각만을 위한 SwiftUI preview를 사용할 수 없다는 점도 한계입니다. 다만 computed property 자체가 잘못된 것은 아니며, 재사용해야 할 UI를 그 안에 가두는 것이 문제입니다.

스타일과 동작은 ViewModifier로 묶기

같은 폰트, 색상, 그림자 같은 스타일이나 공통 동작을 여러 종류의 뷰에 적용하려면 ViewModifier가 적합합니다. ViewModifier 프로토콜을 따르는 구조체에 표준 modifier들을 묶어 두면, Text, Image, Button 등 서로 다른 뷰에 같은 규칙을 적용할 수 있습니다. View 확장과 함께 사용하면 호출부에서 점 표기법으로 적용할 수 있습니다.

modifier 구조체는 단순한 View 확장 메서드보다 더 많은 요소를 캡슐화할 수 있습니다. @State, @GestureState, @Animation 같은 프로퍼티 래퍼를 보유할 수 있고, @Environment로 환경 값에도 접근할 수 있습니다. 또한 SwiftUI 뷰 계층에서 별도 노드로 취급되며, 다른 프로토콜을 채택하거나 제네릭 매개변수로 전달할 수 있습니다. 화면의 콘텐츠와 스타일링 책임을 나누고 공통 스타일의 중복을 줄이는 방식입니다.

다양한 콘텐츠를 담는 제네릭 컨테이너

일반적인 뷰의 매개변수만으로 표현하기 어려운 컨테이너에는 제네릭과 @ViewBuilder를 사용할 수 있습니다. 컨테이너는 내부에 표시할 뷰의 정체를 알지 못한 채 외부 레이아웃을 제공하고, 호출부에서 텍스트, 이미지 또는 복잡한 상호작용 뷰를 전달합니다.

예를 들어 텍스트만 표시하는 TextCard를 이미지에도 쓰려면 기존 코드와 대부분 같은 새 뷰가 필요할 수 있습니다. 제네릭 컨테이너로 바꾸면 레이아웃은 유지하면서 내부 콘텐츠만 교체할 수 있습니다. 컨테이너의 소스 코드를 수정하지 않고 뷰 계층을 구성할 수 있으며, 비슷한 타입의 정의를 반복하지 않아도 됩니다.

세 전략은 재사용 대상에 따라 구분됩니다. UI 조각은 별도 View 구조체로 추출하고, 반복되는 스타일과 동작은 ViewModifier로 캡슐화하며, 여러 종류의 콘텐츠를 담는 공통 틀은 제네릭과 @ViewBuilder로 구성합니다. 원문은 이를 모듈화의 출발점으로 제시하고, 더 나아가 SwiftUI에서 MVVM 같은 설계 패턴을 적용해 루트 뷰와 콘텐츠 계층을 분리하는 방법도 언급합니다.

결론

기본적인 분리는 독립된 View 구조체를 추출하는 데서 시작합니다. 여러 뷰에 같은 스타일이나 동작을 적용할 때는 ViewModifier를 사용하고, 컨테이너의 레이아웃을 유지하면서 내용만 바꾸려면 제네릭과 @ViewBuilder를 적용합니다. 각 방법은 재사용 대상이 UI 구성 요소인지, 스타일인지, 다양한 내부 콘텐츠를 담는 틀인지에 따라 선택하며, computed property만으로 코드를 나누는 방식은 독립성과 공유 가능성을 제공하지 않습니다.