[SwiftUI] Working with UI Controls

원문: 튜토리얼 페이지

Working with UI Controls

끝날 때까지 끝난 것이 아니다, 이번에는 프로필 화면을 만들어본다.

완성했을 때의 화면은 위와 같다.

Display a User Profile

우선 edit 모드가 없는 User Profile을 만든다.

  1. Profile.swift 생성 사용자의 Profile에 표기할 정보를 담을 Profile.swift 를 생성한다.

이름과 기타 정보를 담고 있는 Profile 구조체를 만들어주었다.

  1. ProfileHost, ProfileSummary SwiftUI 파일 생성

image

Profile 구조체에 있는 status들을 표기할 수 있도록 ProfileSummary 뷰로 따로 화면을 만들어 관리한다. VStack 내부에 Profile의 값들을 추가해주었다.

그 후 ProfileHost의 body에서 ProfileSummary 화면을 불러오면

var body: some View {
        VStack(alignment: .leading, spacing: 20) {
            ProfileSummary(profile: draftProfile)
        }
        .padding()
}

요렇게 ProfileHost의 preview에서도 profile이 잘 나온다.

다음에 나올 단계들에서 다른 정보들이 더 추가되어야 하므로 VStack에 ProfileSummary를 넣어주었다.

  1. ProfileSummary에 Badge 추가

image

Badge와 이름을 함께 관리하는 HikeBadge를 생성한다. 이 때 배지의 그리기 로직은 렌더링하는 프레임의 크기에 따라 결과를 생성하기 때문에 원하는 모양을 유지하려면 300 x 300 포인트의 프레임으로 렌더링한다. 최종 그래픽에 대해 원하는 크기를 가져오려면 렌더링된 결과의 크기를 조정하고 비교적 작은 프레임에 배치한다.

배지 뷰를 추가했으면 이제 ProfileSummary로 넘어가 배지를 추가해보자. 최종 이미지로 봤을 때 배지가 여러개 들어갈 수도 있으므로 Scroll 뷰 내부에 HStack으로 배지를 구성하면 될 것 같다.

                Divider()
                
                Text("Completed Badges")
                    .font(.headline)
                
                ScrollView(.horizontal) {
                    HStack {
                        HikeBadge(name: "First Hike")
                        HikeBadge(name: "Earth Day")
                            .hueRotation(Angle(degrees: 90))
                        HikeBadge(name: "Tenth Hike")
                            .grayscale(0.5)
                            .hueRotation(Angle(degrees: 45))
                    }
                    .padding(.bottom)
                }

ProfileSummary의 VStack 내부에 위와 같이 코드를 추가하면

따란~

이제 전에 만들어둔 그래프도 가져와서 넣어보자. 모델 데이터가 필요하기 때문에 ProfileSummary에 @EnvironmentObject var modelData: ModelData를 선언해주고 Badge 아래에 HikeView를 추가한다.

  1. CategoryHome에 프로필 진입점 추가 Profile로 진입할 버튼을 CategoryHome에 만들어준다.
.toolbar {
                Button {
                    showingProfile.toggle()
                } label: {
                    Label("User Profile", systemImage: "person.crop.circle")
                }
            }
            .sheet(isPresented: $showingProfile) {
                ProfileHost()
                    .environmentObject(modelData)
            }

툴바와 sheet를 사용한다.

Add an Edit Mode

프로필을 수정할 수 있도록 한다. 수정 모드로 진입할 때는 수정 가능한 항목만 노출되고 나머지는 보이지 않도록 처리한다.

  1. ProfileHost의 preview에 .environmentObject(Modeldata()) 추가
    • ProfileHost에서는 사용하지 않지만 ProfileHost에서 호출하는 ProfileSummary에서 사용하므로 필요하다. (사용하지 않으면 미리보기 불가능)
  2. ProfileHost에 @Environment(.editMode) var editMode 추가
    • Editmode를 판별하는 데 사용할 프로퍼티. @Environment로 선언해 값에 접근한다.
  3. ProfileHost에 Edit 버튼 추가

image

  • Edit 버튼을 우측에 붙이기 위해 Spacer()를 사용했다.
  • EditButtun()으로 버튼 자체를 지원한다니..
  1. ModelData에 Profile 값 추가
    • profile view를 dismiss 한 이후에도 지속적으로 값을 가지고 있을 수 있도록 ModelData에 Profile 값을 추가한다.
    • 값을 수정하게 되므로 @Published를 붙인다.
  2. ProfileHost에서 ProfileSummary를 호출할 때 modelData의 profile을 주입하도록 수정

ProfileSummary(profile: modelData.profile) 로 호출한다.

  1. editMode에 따라 다른 뷰를 보여주도록 if 구문 추가
if editMode?.wrappedValue == .inactive {
   ProfileSummary(profile: modelData.profile)
} else {
   Text("Profile Editor")
}
  • editMode의 값에 따라 inactive면 summary를 아니면 editor 화면을 보여주도록 한다.
    • 에디터는 다음 섹션에서 구현!

Define the Profile Editor

  1. ProfileEditor SwiftUI 파일 생성

image

  • ProfileEditor를 생성하고 ProfileHost에서 Text로 적어둔 부분에서 ProfileEditor를 보여주도록 수정한다.
    • 생성시 profile에는 미리 선언해두었던 draftProfile을 주입한다.
  1. ProfileEditor에서 수정이 가능한 항목을 추가

Delay Edit Propagation

편집 모드에서 정보를 수정한 것이 Done 버튼을 누를때까지 ModelData에 반영되지 않게 한다. 그러기 위해서는 draft copy를 편집 모드에서 사용하고 사용자가 done을 눌러 편집을 완료했을 때 수정된 draft copy가 실제 데이터에 반영되도록 해야한다.

  1. ProfileHost에 cancel 버튼 추가
    • cancel 버튼을 눌렀을 때는 수정하던 데이터가 반영되지 않고 done일 때만 반영할 수 있도록 우선 editMode가 active일 때만 cancel 버튼 노출한다.
  2. ProfileEditor의 .onAppear과 .onDissapear의 상태를 설정
    • onAppear인 경우에는 modelData의 profile로 draftProfile을 업데이트
    • onDissapear인 경우에는 modelData에 draftProfile 내용을 업데이트

결과화면

마무리

storyboard에서 할 때는 viewDidLoad 같은 메서드가 있어서 뷰의 라이프 사이클에 맞춰 뭔가를 해줄 수 있었는데 swiftUI에는 .onAppear처럼 사용한다는 걸 알게 됐다.

이제 검색앱을 간단하게 만들어보러 가야하는데 하면서 swiftUI 개념을 조금 더 공부할 필요가 크게 느껴진다… @Environment로 값을 관리하는 것도 어색하네..ㅠ

Leave a comment