궁금한 내용을 검색해보세요!
이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.
서근 개발노트
티스토리에 팔로잉
PROJECT/Simple

SwiftUI Project10 : 영화 캐릭터 정보 앱 #1

서근
QUOTE THE DAY

“ 위대한 과학은 거인의 어깨 위에 거인이 올라선 경우처럼 대단하게 비유된다. 하지만, 소프트웨어 산업은 난쟁이 발가락 위에 난쟁이가 서있는 것처럼 매우 쉽게 이야기 된다. ”

- Alan Cooper (앨런 쿠퍼)
Written by SeogunSEOGUN

 

 

Project10의 첫 번째 포스팅입니다.

영화 캐릭터 정보 앱

이번 프로젝트에서는 지브리 스튜디오의 캐릭터의 정보를 앱에 나타내면서 SwiftUI 기능 사용해보겠습니다.

뷰 구성하기

우선 아래 이미지 파일을 다운받아서 Assets 폴더에 업로드해주겠습니다.

캐릭터.zip
1.84MB
SwiftUI Project10 : 영화 캐릭터 정보 앱  #1

그리고 위 이미지 같은 뷰를 작성해보겠습니다. 이 뷰는 HStack 이 최상위에서 뷰를 감싸고 있고 VStack 이 자식 뷰로 캐릭터 정보를 감싼 형태입니다.

swift
UNFOLDED
// 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도 이런 방식으로 가져올 수 있습니다.

SwiftUI Project10 : 영화 캐릭터 정보 앱  #1

 

padding을 사용하는 법은 다양하게 있지만 위에서는 적용하고 싶은 위치를 배열로 지정해 여백을 주고 있습니다.

화면을 보면 테두리도 없고 어딘가 부족해 보입니다. 여기서 backgroundcornerRadius, shadow를 넣어보도록 하겠습니다. 넣기 전에 한 가지 알아야 할 것이 있는데 바로 Color.primary입니다. Color.primary는 다크 모드와 라이트 모드일 때 자동적으로 색을 테마에 맞게 바꿔주는 효과를 가지고 있습니다.

swift
UNFOLDED
// 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)

SwiftUI Project10 : 영화 캐릭터 정보 앱  #1

뷰 추출하기

이제 밑바탕이 될 뷰를 완성되었습니다. 복잡하지 않은 뷰 인데도 뭔가 너무 깁니다.

또 동일한 뷰를 여러 번 반복해서 작성하려면 그 길이가 더 길어지겠죠? 부분 수정을 할 때도 힘들 테고요😭

 

이럴 땐 각 항목을 별도의 프로퍼티나 뷰로 추출해서 뷰의 핵심인 main body에서는 간결하게 유지하는 것이 가장 좋습니다.

 

위에 뷰를 보기 쉽게 나눠보도록 하겠습니다.

뷰 나누기

1. 최상위 뷰 인 HStack커맨드 + 클릭해서 VStack 감싸줍니다.

SwiftUI Project10 : 영화 캐릭터 정보 앱  #1

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

SwiftUI Project10 : 영화 캐릭터 정보 앱  #1
Subview로 추출된 화면

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

swift
UNFOLDED
// Home
VStack {
ProductRow()
ProductRow()
ProductRow()
}

SwiftUI Project10 : 영화 캐릭터 정보 앱  #1

그럼 다음 그림과 같은 결과를 볼 수 있게 됩니다. ProductRow만 불러오면 쉽게 재사용을 할 수 있다는 말이죠. 앞으로 이 기능을 적극적으로 사용해보도록 하겠습니다.

ProductRow 파일 생성

이제 ProductRow라는 새로운 파일로 위 코드를 옮겨서 저장하도록 하겠습니다. [SwiftUI View] 템플릿을 선택해서 ProductRow.swift라고 이름을 정해주고 Home 그룹 안으로 넣어줍니다. 

 

여기까지 했으면 Home에는 ProductRow()세 번 반복되는 코드만 있으면 됩니다.

swift
UNFOLDED
// Home View
struct Home: View {
var body: some View {
VStack {
ProductRow()
ProductRow()
ProductRow()
}
}
}

프로퍼티로 추출

ProductRow의 뷰도 세밀하게 나누어 보도록 하겠습니다. 이번에는 [Extract SubView]로 추출 하지 않습니다.

1. 캐릭터 이미지 

캐릭터 이미지를 [productImage] 프로퍼티로 추출합니다. 그리고 body에서는 productImage를 호출해줍니다.

swift
UNFOLDED
//ProductRow
var productImage: some View {
Image("Sosuke")
.resizable()
.scaledToFill()
.frame(width: 150)
.clipped()
}

2. 캐릭터 정보

이번에는 VStack을 [ProductDescription] 프로퍼티라고 이름짓고 코드를 전부 옮겨줍니다.

swift
UNFOLDED
// 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)

잊지 말고 bodyProductDescription을 추가해줘야 합니다.

 

현재까지의 body 화면입니다.

swift
UNFOLDED
// ProductRow
var body: some View {
HStack {
productImage
productDescription
}
...
}

3. 캐릭터 정보 세분화

ProductDescription을 한번 더 구분하여 나누어 주겠습니다. 해당 프로퍼티에서 HStack을 떼어 내서 footerView라고 이름 지어줍니다.

swift
UNFOLDED
// 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)
}
}
swift
UNFOLDED
// ProductDescription
var productDescription: some View {
VStack(alignment: .leading){
Text("소스케")
...
footerView
}
.padding([.leading, .bottom], 10)
.padding([.trailing, .top])
}

4. 현재까지의 형태

여기까지 했다면 코드 형태는 다음과 같습니다.

swift
UNFOLDED
// 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를 제외한 나머지 프로퍼티들을 옮겨주겠습니다.

swift
UNFOLDED
// ProductRow
private extension ProductRow {
var productImage: some View {...}
var productDescription: some View {...}
var footerView: some View {...}
}

6. 최종 형태

swift
UNFOLDED
// 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를 구현하는 작업만 담당하고, 그 외에는 확장 영역으로 명확하게 구분해 줌으로써 앞으로 코드를 관리하게 수월하게 하도록 하기 위함입니다. 기능의 차이는 전혀 없습니다.

전체코드

swift
FOLDED

 

읽어주서서 감사합니다🤟

 

 

 

 


잘못된 내용이 있으면 언제든 피드백 부탁드립니다.


서근


위처럼 이미지 와 함께 댓글을 작성할 수 있습니다.