반응형

SwiftUI에서 많이 하는 실수 - sheet / fullScreenCover 상태를 여러 곳에서 제어해 모달이 깜빡이거나 중복되는 실수

.sheet와 .fullScreenCover는 하나의 상태값(isPresented / item)을 기준으로 동작합니다.
이 값을 여러 View나 여러 로직에서 동시에 건드리면,
모달이 떴다가 사라지거나, 아예 안 뜨는 등 예측이 어려운 동작이 발생합니다.


1. 문제 원인

  • 같은 Bool을 Parent/Child 양쪽에서 수정
  • 하나의 View에 sheet가 여러 개 걸려 있고, 조건 분기가 복잡
  • onAppear/onDisappear/onChange/Task 등 여러 위치에서 isPresented를 건드림

2. 나타나는 증상

  • 버튼을 눌렀는데 모달이 잠깐 깜빡이고 사라짐
  • 특정 상황에서는 전혀 뜨지 않거나, 두 번 뜨려다 실패
  • print로 디버깅해보면 짧은 시간에 true/false가 여러 번 토글됨

3. 잘못된 코드 예시

struct ContentView: View {
    @State private var showSheet = false

    var body: some View {
        VStack {
            Button("Sheet 열기") {
                showSheet = true
            }
        }
        .sheet(isPresented: $showSheet) {
            // ❌ 자식 View가 같은 바인딩을 직접 제어
            ChildSheet(showSheet: $showSheet)
        }
    }
}

struct ChildSheet: View {
    @Binding var showSheet: Bool

    var body: some View {
        VStack {
            Text("Child Sheet")
            Button("닫기") {
                showSheet = false
            }
        }
        .onAppear {
            // ❌ 이 시점에 또 상태를 바꾸면 예측 어려움
            // showSheet = false
        }
    }
}

4. 올바른 코드 예시 (enum 기반 단일 진입점)

struct ContentView: View {
    enum ActiveSheet: Identifiable {
        case settings
        case help

        var id: Int {
            switch self {
            case .settings: return 0
            case .help:     return 1
            }
        }
    }

    @State private var activeSheet: ActiveSheet? = nil

    var body: some View {
        VStack {
            Button("설정 열기") {
                activeSheet = .settings
            }
            Button("도움말 열기") {
                activeSheet = .help
            }
        }
        .sheet(item: $activeSheet) { item in
            switch item {
            case .settings:
                SettingsView(onClose: { activeSheet = nil })
            case .help:
                HelpView(onClose: { activeSheet = nil })
            }
        }
    }
}

struct SettingsView: View {
    let onClose: () -> Void
    var body: some View {
        VStack {
            Text("설정")
            Button("닫기", action: onClose)
        }
    }
}
  • 모달을 띄우고 닫는 모든 로직이 ContentView 하나에서만 상태를 변경합니다.

5. 정리 및 팁

  • 모달의 “표시 여부”는 가능하면 한 곳(상위 레벨)에서만 관리합니다.
  • 자식 View에는 onClose: () -> Void 콜백만 내려보내고, 실제 플래그 변경은 부모에서만 수행하는 패턴이 가장 안전합니다.

 

반응형
Posted by 까칠코더
,