KoreaMango 나무

[iOS App Dev Tutorials] SwiftUI - Persistence and Concurrency (5) 본문

iOS/iOS App Dev Tutorials

[iOS App Dev Tutorials] SwiftUI - Persistence and Concurrency (5)

KoreaMango 2022. 5. 18. 21:29
 

Apple Developer Documentation

 

developer.apple.com

5. Handling Errors

앱을 개발하는 동안 구문 또는 의미론적 오류가 발생하여 수정되었을 수 있다. 예를 들어 네트워크 연결이 끊어져서 파일에서 데이터를 읽는 것을 실패할 수 있다. 이런 문제가 발생했을 때 안내를 제공해서 오류를 해결하는 방법에 대해 알아보자!

Section 1. Add an Error Wrapper Structure

항상 작업을 완료하거나 출력을 생성하는 기능이 있을 수 있다. 파일에 읽기 권한이 없거나 지정한 경로에 없을 수도 있다. 아니면 인코딩 형식이 올바르지 않을 수도 있다.

작업이 실패했을 때 무엇이 오류를 야기했는지 사용자에게 이해시켜주는 것이 도움이 된다.

import Foundation

struct ErrorWrapper: Identifiable {
    let id: UUID
		let error: Error
    let guidance: String

		init(id: UUID = UUID(), error: Error, guidance: String) {
        self.id = id
        self.error = error
        self.guidance = guidance
    }
}

Model 그룹에 ErrorWrapper 를 생성하고 고유한 id 값을 제공하기 위해 Identifiable을 추가한다.

오류 프로토콜을 사용해서 오류 처리 속성을 명시적으로 할당할 수 있다. 그리고 init 을 한다.

Section 2. Create an Error View

사용자에게 에러를 보여주고 설명해주는 view를 생성해보자.

struct ErrorView_Previews: PreviewProvider {
    enum SampleError: Error {
        case errorRequired
    }
    
		static var wrapper: ErrorWrapper {
        ErrorWrapper(error: SampleError.errorRequired,
                     guidance: "You can safely ignore this error.")
    }

    static var previews: some View {
        ErrorView(errorWrapper: wrapper)
    }
}

가끔 SwiftUI previews를 효과적으로 사용하기 위해서 리얼한 샘플 데이터가 필요할 수 있다. 그래서 이렇게 preview에 sample 에러를 넣어준다.

우선 샘플 에러를 보내기 위한 enum을 만들고, errorRequired property를 사용해 에러를 초기화하는 정적 프로퍼티를 만든다. 그 다음 값을 preview에 전달한다.

struct ErrorView: View {
    let errorWrapper: ErrorWrapper
    
    var body: some View {
        VStack {
            Text("An error has occurred!")
                .font(.title)
                .padding(.bottom)
            Text(errorWrapper.error.localizedDescription)
                .font(.headline)
            Text(errorWrapper.guidance)
                .font(.caption)
                .padding(.top)
            Spacer()
        }
        .padding()
        .background(.ultraThinMaterial)
        .cornerRadius(16)
    }
}

error는 localizedDescription을 제공한다.

Section 3. Report Errors

모달을 통해서 앱 화면의 흐름을 방해하는 것은 좋지 않지만 에러는 예외이다. 데이터 바인딩을 사용해서 오류 보기의 모달 표시를 트리거한다.

struct ErrorView: View {
    let errorWrapper: ErrorWrapper
    @Environment(\\.dismiss) private var dismiss
    
    var body: some View {
        NavigationView {
           ...
        }
				...
				.navigationBarTitleDisplayMode(.inline)
        .toolbar {
            ToolbarItem(placement: .navigationBarTrailing) {
                Button("Dismiss") {
                    dismiss()
                }
            }
        }
    }
}

@Enviroment 프로퍼티를 사용함으로써 view의 environment stores를 읽을 수 있다. view의 presentation 모드나 scene phase,visibility, color scheme 같은..

이 케이스에서는 view의 dismiss structure을 허용하고 view를 dismiss하기 위한 함수같은 것을 부른다.

ToolbarItem의 Button안에 dismiss를 추가한다. dismiss 는 구조체이다. 만약 구조체가 callAsFunction()을 포함한다면 그것을 함수처럼 부를 수 있다.

struct ScrumdingerApp: App {
    @StateObject private var store = ScrumStore()
    @State private var errorWrapper: ErrorWrapper?
    
    var body: some Scene {
        WindowGroup {
            NavigationView {
                ScrumsView(scrums: $store.scrums) {
                    Task {
                        do {
                            try await ScrumStore.save(scrums: store.scrums)
                        } catch {
                            errorWrapper = ErrorWrapper(error: error, guidance: "Try again later.")
                        }
                    }
                }
            }
            .task {
                do {
                    store.scrums = try await ScrumStore.load()
                } catch {
	                   errorWrapper = ErrorWrapper(error: error, guidance: "Scrumdinger will load sample data and continue.")
                }
            }
						.sheet(item: $errorWrapper, onDismiss: {
							store.scrums = DailyScrum.sampleData
            }) { wrapper in
							ErrorView(errorWrapper: wrapper)
            }
        }
    }
}

메인 App에 errorWrapper 라는 상태 변수를 옵셔널로 선언한다. 옵셔널로 선언했기 때문에 기본 값은 nil이고 이 상태 변수에 값이 할당되면 SwiftUI는 View를 업데이트한다.

이후 errorWrapper를 바인딩하는 sheet 를 추가한다. 모달 시트는 모달이 닫혔을 때 실행하게 하는 클로저를 제공한다. 그리고 모달 시트에 표시할 view를 제공하기 위한 클로저도 제공한다.

에러가 발생했을 때에 errorView에 wrapper를 제공하고 임시로 sampleData를 저장소의 스크럼에 넣는다.

Section 4. Simulate Data Corruption

이 섹션은 Terminal을 통해서 앱의 샌드박스에 들어간 다음 데이터를 조작한다.

그렇게 했을 때 앱에 발생하는 errorView를 확인해본다. 그리고 에러창을 dismiss 했을 때 샘플 데이터가 다시 채워져서 정상적으로 돌아온 것을 확인할 수 있다. 자세한 것은 직접 찾아가서 알아보도록 !