프로젝트 맥락
본 글은 SwiftUI로 Apple Developer Academy_Challenge 2에서 개발 중인 ‘Re:ToU(오늘의 너)’ 앱을 기반으로 작성되었습니다.
‘오늘의 너’는 사용자가 하루 동안의 감정을 기록하고, 회고하는 기능을 중심으로 구성되어 있습니다.
특히, 감정 선택 → 회고 작성 → 저장까지 자연스럽게 이어지는 사용자 흐름을 위해 ViewModel을 통한 상태 관리가 필수적이었습니다.
기능 설명
SwiftUI로 앱을 개발할 때, 화면(UI)과 데이터(State)를 함께 관리하면 코드가 금방 복잡해진다.
특히 화면 전환, 사용자 입력 처리, 여러 화면 간 데이터 공유가 필요한 상황에서는 상태 관리를 따로 분리하는 것이 중요하다.
이를 위해 SwiftUI에서는 ViewModel을 활용해 상태를 관리하고, View는 데이터 표현에만 집중하는 구조를 지향한다.
본 프로젝트에서는 감정 선택, 작성 완료 버튼 활성화, 회고 작성 등의 상태를 ViewModel로 분리하여 관리했다.
시도 동기
초기 개발 단계에서는 화면마다 @State를 선언해 필요한 데이터를 직접 관리했다.
@State private var selectedEmotion: String = ""
@State private var reflectionText: String = ""
이 방법은 구현이 간단하다는 장점이 있었지만, 화면 전환이나 상태 공유가 필요한 상황에서 문제가 발생했다.
특히 작성 화면과 리스트 화면 간 데이터를 넘길 때, 값이 초기화되거나 의도치 않은 동작이 빈번하게 나타났다.
이러한 문제를 해결하고, 더 유지보수하기 좋은 구조를 만들기 위해 ViewModel을 도입했다.
문제 발견
ViewModel을 사용하면서 가장 먼저 직면한 문제는 생명주기 관리였다.
ViewModel을 잘못 선언하면 화면이 새로 그려질 때마다 ViewModel이 재생성되어 상태가 초기화되는 현상이 발생했다.
흔히 발생하는 에러 메시지
- “Publishing changes from within view updates is not allowed”원인: SwiftUI가 View를 그리고 있는 중에는 상태 변경을 허용하지 않기 때문이다.
- 해결: 상태 변경은 반드시 .onAppear나 비동기 처리 이후에 하도록 수정해야 한다.
- 이 에러는 ViewModel 내부에서 뷰 업데이트 타이밍에 상태를 변경할 때 발생한다.
- “Multiple instances of StateObject are being created” 경고해결: ViewModel이 필요한 곳에서 @StateObject는 한 번만 선언하고, 이후에는 @ObservedObject나 @EnvironmentObject로 연결해야 한다.
- 이 경고는 @StateObject를 잘못 사용해서 View가 새로 생성될 때마다 ViewModel 인스턴스가 중복 생성될 때 발생한다.
문제 해결
SwiftUI에서는 ViewModel을 어떻게 선언하느냐에 따라 상태 유지가 결정된다.
주요 패턴은 다음과 같다.
@StateObject
— View에서 직접 소유하는 경우
@StateObject private var viewModel = ReflectionViewModel()
- View가 생성될 때 딱 한 번 ViewModel을 생성하고 유지한다.
@ObservedObject
— 외부에서 주입받는 경우
@ObservedObject var viewModel: ReflectionViewModel
- 이미 생성된 ViewModel을 외부에서 주입받아 관찰한다.
@EnvironmentObject
— 여러 View에서 공유할 경우
@EnvironmentObject var userSettings: UserSettings
- 앱 전체 또는 여러 화면이 하나의 ViewModel을 공유할 때 사용한다.
예시 코드: ViewModel 공유 구조
// 상위 View
struct MainView: View {
@StateObject private var viewModel = ReflectionViewModel()
var body: some View {
NavigationView {
ReflectionListView()
.environmentObject(viewModel)
}
}
}
// 하위 View
struct ReflectionListView: View {
@EnvironmentObject var viewModel: ReflectionViewModel
var body: some View {
List(viewModel.reflections) { reflection in
Text(reflection.text)
}
}
}
비슷한 접근 방법 비교
방법설명장점단점
View 내부 @State 관리 | View에서 직접 상태를 선언하고 관리 | 빠르고 간단 | 화면이 많아지면 코드 복잡도 급증 |
ViewModel 분리 (@StateObject, @ObservedObject) | 상태를 ViewModel로 따로 관리 | 구조적이고 유지보수 용이 | 초기에 설계 시간이 필요 |
전역 State 관리 (예: Redux 스타일) | 앱 전체를 하나의 상태로 통합 관리 | 일관성과 테스트 용이 | 소규모 앱에는 오버 엔지니어링 가능성 |
느낀 점
ViewModel을 분리하고 상태 관리를 체계화하니
화면 전환이나 데이터 흐름을 훨씬 명확하게 이해할 수 있었다.
특히 SwiftUI는 “상태 변화에 따라 화면이 반응한다”는 철학이 핵심이기 때문에,
초기에 ViewModel과 State를 올바르게 설정해두는 것이 앱 품질에 직결된다는 점을 다시 한 번 체감했다.
이후 기능 확장이나 리팩토링을 할 때도 구조적인 이점을 크게 얻을 수 있었다.
SwiftUI를 통한 개인 프로젝트이며 Swift를 처음 사용해보기 때문에 이번 프로젝트를 통해 배운 내용을 정리할 예정입니다.
부족한 내용이 많을 수 있는데 그러한 부분은 댓글을 통해 알려주시면 제가 성장하는데 있어서 많은 도움이 될 듯 합니다. 감사합니다 :)
'iOS & SwiftUI' 카테고리의 다른 글
SwiftUI에서 작성/수정 화면을 분리하고 전환 흐름 구성하기 (2) | 2025.05.06 |
---|---|
SwiftUI에서 감정 이모지 선택 UI 구현하기: HStack 기반 구조 (2) | 2025.05.05 |
SwiftUI에서 입력 완료 여부에 따라 버튼 활성화 상태 제어하기 (1) | 2025.05.03 |
SwiftUI에서 NavigationLink를 안전하게 사용하는 방법: 상태 기반 전환 설계하기 (0) | 2025.05.01 |
SwiftUI 앱에서 UserDefaults를 이용한 로컬 데이터 저장 방식 설계하기 (2) | 2025.04.29 |