Project10의 첫 번째 포스팅입니다.
영화 캐릭터 정보 앱
이번 프로젝트에서는 지브리 스튜디오의 캐릭터의 정보를 앱에 나타내면서 SwiftUI
기능 사용해보겠습니다.
뷰 구성하기
우선 아래 이미지 파일을 다운받아서 Assets
폴더에 업로드해주겠습니다.

그리고 위 이미지 같은 뷰를 작성해보겠습니다. 이 뷰는 HStack
이 최상위에서 뷰를 감싸고 있고 VStack
이 자식 뷰로 캐릭터 정보를 감싼 형태입니다.
// Home HStack { Image("Sosuke") .resizable() .scaledToFill() .frame(width: 150) .clipped() VStack(alignment: .leading){ HStack { Text("소스케") .font(.headline) .fontWeight(.medium) Spacer() Text("벼랑위의 포뇨") .font(.footnote).foregroundColor(.gray) } .padding(.bottom, 10) Text("5살 남자 아이. 본작품의 주인공. 벼랑 위에 있는 2~3층 정도 되는 주택에서 엄마와 함께 살고 있다.") .font(.footnote) .foregroundColor(.secondary) .lineLimit(3) Spacer() // 1 HStack(spacing: 0) { Text("₩").font(.footnote) + Text("214000").font(.headline) Spacer() Image(systemName: "heart") .imageScale(.large) .foregroundColor(.lightRed) .frame(width: 32, height: 32) Image(systemName: "cart") .foregroundColor(.lightRed) .frame(width: 32, height: 32) } } // 2 .padding([.leading, .bottom], 10) .padding([.trailing, .top]) } .frmae(height: 150)
위 코드에서 1번을 보게 되면 Text(.init())
으로 symbol 이미지를 가져왔습니다. 물론 Image(systemName: "")
으로 가져 올 수도 있지만 Text
도 이런 방식으로 가져올 수 있습니다.

padding
을 사용하는 법은 다양하게 있지만 위에서는 적용하고 싶은 위치를 배열로 지정해 여백을 주고 있습니다.
화면을 보면 테두리도 없고 어딘가 부족해 보입니다. 여기서 background
와 cornerRadius
, shadow
를 넣어보도록 하겠습니다. 넣기 전에 한 가지 알아야 할 것이 있는데 바로 Color.primary
입니다. Color.primary
는 다크 모드와 라이트 모드일 때 자동적으로 색을 테마에 맞게 바꿔주는 효과를 가지고 있습니다.
// Home HStack { ... } .frame(height: 150) .background(Color.primary.colorInvert()) //다크모드 라이트모드 .cornerRadius(10) .shadow(color: Color.primary.opacity(0.3), radius: 1, x: 2, y: 2) .padding(.vertical, 10)

뷰 추출하기
이제 밑바탕이 될 뷰를 완성되었습니다. 복잡하지 않은 뷰 인데도 뭔가 너무 깁니다.
또 동일한 뷰를 여러 번 반복해서 작성하려면 그 길이가 더 길어지겠죠? 부분 수정을 할 때도 힘들 테고요😭
이럴 땐 각 항목을 별도의 프로퍼티나 뷰로 추출해서 뷰의 핵심인 main body
에서는 간결하게 유지하는 것이 가장 좋습니다.
위에 뷰를 보기 쉽게 나눠보도록 하겠습니다.
뷰 나누기
1. 최상위 뷰 인 HStack
에 커맨드 + 클릭해서 VStack
감싸줍니다.

2. HStack
에 다시 한번 커멘드 + 클릭해서 [Extract SubView
]를 선택합니다.

3. ExtractedView()
의 이름을 ProductRow()
라고 재지정해주고, Home
의 body
프로퍼티에 ProductRow
를 3번 반복되도록 작성하겠습니다.
// Home VStack { ProductRow() ProductRow() ProductRow() }

그럼 다음 그림과 같은 결과를 볼 수 있게 됩니다. ProductRow
만 불러오면 쉽게 재사용을 할 수 있다는 말이죠. 앞으로 이 기능을 적극적으로 사용해보도록 하겠습니다.
ProductRow 파일 생성
이제 ProductRow
라는 새로운 파일로 위 코드를 옮겨서 저장하도록 하겠습니다. [SwiftUI View
] 템플릿을 선택해서 ProductRow.swift
라고 이름을 정해주고 Home
그룹 안으로 넣어줍니다.
여기까지 했으면 Home
에는 ProductRow()
가 세 번 반복되는 코드만 있으면 됩니다.
// Home View struct Home: View { var body: some View { VStack { ProductRow() ProductRow() ProductRow() } } }
프로퍼티로 추출
ProductRow
의 뷰도 세밀하게 나누어 보도록 하겠습니다. 이번에는 [Extract SubView
]로 추출 하지 않습니다.
1. 캐릭터 이미지
캐릭터 이미지를 [productImage
] 프로퍼티로 추출합니다. 그리고 body
에서는 productImage
를 호출해줍니다.
//ProductRow var productImage: some View { Image("Sosuke") .resizable() .scaledToFill() .frame(width: 150) .clipped() }
2. 캐릭터 정보
이번에는 VStack
을 [ProductDescription
] 프로퍼티라고 이름짓고 코드를 전부 옮겨줍니다.
// Home HStack { Image("Sosuke") .resizable() .scaledToFill() .frame(width: 150) .clipped() VStack(alignment: .leading){ HStack { Text("소스케") .font(.headline) .fontWeight(.medium) Spacer() Text("벼랑위의 포뇨") .font(.footnote).foregroundColor(.gray) } .padding(.bottom, 10) Text("5살 남자 아이. 본작품의 주인공. 벼랑 위에 있는 2~3층 정도 되는 주택에서 엄마와 함께 살고 있다.") .font(.footnote) .foregroundColor(.secondary) .lineLimit(3) Spacer() // 1 HStack(spacing: 0) { Text("₩").font(.footnote) + Text("214000").font(.headline) Spacer() Image(systemName: "heart") .imageScale(.large) .foregroundColor(.lightRed) .frame(width: 32, height: 32) Image(systemName: "cart") .foregroundColor(.lightRed) .frame(width: 32, height: 32) } } // 2 .padding([.leading, .bottom], 10) .padding([.trailing, .top]) } .frmae(height: 150)
잊지 말고 body
에 ProductDescription
을 추가해줘야 합니다.
현재까지의 body
화면입니다.
// ProductRow var body: some View { HStack { productImage productDescription } ... }
3. 캐릭터 정보 세분화
ProductDescription
을 한번 더 구분하여 나누어 주겠습니다. 해당 프로퍼티에서 HStack
을 떼어 내서 footerView
라고 이름 지어줍니다.
// ProductRow var footerView: some View { HStack(spacing: 0) { Text("₩").font(.footnote) + Text("214000").font(.headline) Spacer() Image(systemName: "heart") .imageScale(.large) .foregroundColor(.lightRed) .frame(width: 32, height: 32) Image(systemName: "cart") .foregroundColor(.lightRed) .frame(width: 32, height: 32) } }
// ProductDescription var productDescription: some View { VStack(alignment: .leading){ Text("소스케") ... footerView } .padding([.leading, .bottom], 10) .padding([.trailing, .top]) }
4. 현재까지의 형태
여기까지 했다면 코드 형태는 다음과 같습니다.
// ProductRow struct ProductRow: View { var body: some View { HStack { productImage productDescription } .frame(height: 150) .background(Color.primary.colorInvert()) //다크모드 라이트모드 .cornerRadius(10) .shadow(color: Color.primary.opacity(0.3), radius: 1, x: 2, y: 2) .padding(.vertical, 10) } var productImage: some View {...} var productDescription: some View {...} var footerView: some View {...} }
결과는 동일하지만 이렇게 뷰를 나누게 되면 body
뷰에서는 전체 레이아웃이 어떤 형태를 가지고 있는지 한눈에 쉽게 알 수 있고, 개별 뷰에 대해서 상세하고 살펴볼 필요가 있을 때는 집중에서 볼 수 있습니다.
5. Extension
한 단계 나아가서 ProductRow
에 대한 익스텐션을 만들어 확장시켜주고 body
를 제외한 나머지 프로퍼티들을 옮겨주겠습니다.
// ProductRow private extension ProductRow { var productImage: some View {...} var productDescription: some View {...} var footerView: some View {...} }
6. 최종 형태
// ProductRow struct ProductRow: View { var body: some View {...} } private extension ProductRow { var productImage: some View {...} var productDescription: some View {...} var footerView: some View {...} } //프리뷰 struct ProductRow_preview: PreviewProvider { static var previews: some View { ProductRow() } }
이렇게 나누는 이유는 기본 타입에서는 프로퍼티를 정의하고 뷰 프로토콜의 핵심인 body
를 구현하는 작업만 담당하고, 그 외에는 확장 영역으로 명확하게 구분해 줌으로써 앞으로 코드를 관리하게 수월하게 하도록 하기 위함입니다. 기능의 차이는 전혀 없습니다.
전체코드
읽어주서서 감사합니다🤟
'PROJECT > Simple' 카테고리의 다른 글
SwiftUI Project10 : 영화 캐릭터 정보 앱 #3 - JSON (0) | 2021.04.19 |
---|---|
SwiftUI Project10 : 영화 캐릭터 정보 앱 #2 - 모델생성 (0) | 2021.04.17 |
SwiftUI Project9 : CustomTabView (geometryReader) (0) | 2021.03.26 |
SwiftUI Project8 : WebView and Image (0) | 2021.03.21 |
SwiftUI Project7 : Stack / ScrollView / Link (0) | 2021.03.11 |