코드네임 :

🍏 on시리즈 모디피어 본문

👥Club/🍀UMC🍀

🍏 on시리즈 모디피어

비엔 Vien 2025. 4. 1. 23:18

2주차인데 왜 3주차에 하시는 걸까용~~?ㅎㅎ

ㄴ ㅈㅅ


뷰의 생명 주기 및 이벤트 감지와 관련된 수정자(modifier)

- 사용자 인터렉션이나 데이터 변경을 효과적으로 처리하는 데 중요한 역할


onAppear

: 뷰가 화면에 나타날 때 실행되는 메서드

뷰의 초기 데이터를 로드하거나 특정 작업을 시작할 때 사용

 

< 기본 사용법 >

struct ContentView: View {
    var body: some View {
        Text("Hello, SwiftUI!")
            .onAppear {
                print("뷰가 나타났습니다!")
            }
    }
}

 

< 네트워크 호출 시 사용법 >

struct ContentView: View {
    @State private var data: String = "로딩 중..."
    
    var body: some View {
        Text(data)
            .onAppear {
                fetchData()
            }
    }
    
    func fetchData() {
        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
            data = "데이터 로드 완료"
        }
    }
}

 

List나 NavigationView 내에서 onAppear를 사용힐 경우, 해당 뷰가 다시 로드될 때마다 실행될 수 있으므로 불필요한 API 호출을 방지하기 위해 상태 관리가 필요

 

onAppear의 아주 아주 치명적인 단점

  1. 비동기 코드(async/await)을 다루기 불편
  2. 작업 취소가 불가능한 점(이 부분이 제일 중요합니다) -> 메모리에 미치는 영향이 있기때문
    1. onAppear 내부에서 실행된 작업은 뷰가 사라져도 취소되지 않습니다.
    2. 불필요한 네트워크 요청이나 비동기 작업이 계속 실행될 수 있습니다.

>> 메모리에 미치는 영향이 뭐냐??

  • 불필요한 네트워크 요청 지속 문제
    • 사용자가 뷰를 빠르게 이동할 경우, 여러 개의 네트워크 요청이 동시에 실행
    • 응답을 받을 필요가 없는 요청이 계속 남아있으면 CPU 사용량 증가
  • 메모리 누수
    • onAppear에서 실행된 비동기 작업이 뷰의 @State 변수를 캡처하면, 해당 뷰가 사라져도 메모리에 남아 있을 가능성
    • 네트워크 요청이 완료될 때까지 뷰가 해제되지 않으면 메모리 누수가 발생
  • 백그라운드 불필요한 작업 실행
    • 뷰가 화면에서 사라졌음에도 불구하고, 실행중인 Task가 백그라운드에서 계속 실행
    • 즉, 뷰가 존재하지 않아도 작업이 끝날 때까지 CPU 지속적으로 사용

⬇️ 해결 위해

 

task 수정자를 사용

: 뷰가 나타나기 전에 수행할 비동기 작업을 추가합니다.

- 비동기 작업을 뷰의 생명주기와 연동하여 자동으로 취소할 수 있습니다. 즉, 뷰가 사라질 때 작업이 자동 취소됩니다.

 

<기본사용법>

struct ContentView: View {
    var body: some View {
        Text("Hello, SwiftUI!")
            .task {
                print("뷰가 나타났습니다!")
            }
    }
}

 

<네트워크 호출 시 사용법>

struct ContentView: View {
    @State private var data: String = "로딩 중..."
    
    var body: some View {
        Text(data)
            .task {
                fetchData()
            }
    }
    
    func fetchData() {
        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
            data = "데이터 로드 완료"
        }
    }
}

 

onDisappear

: 뷰가 화면에서 사라질 때 실행되는 메서드

네트워크 요청을 취소할 때 리소스를 해제하는 용도로 사용됩니다.

 

<기본사용법>

struct ContentView: View {
    var body: some View {
        Text("Hello, SwiftUI!")
            .onDisappear {
                print("뷰가 사라졌습니다!")
            }
    }
}


<네트워크 취소 시 사용법>

struct ContentView: View {
    @State private var task: Task<(), Never>? = nil

    var body: some View {
        Text("데이터 로딩 중...")
            .onAppear {
                task = Task {
                    await networkData()
                }
            }
            .onDisappear {
                task?.cancel()
            }
    }

    func networkData() async {
        do {
            try await Task.sleep(nanoseconds: 5_000_000_000)
            print("데이터 로드 완료")
        } catch {
            print("작업이 취소되었습니다.")
        }
    }
}

 

<리소스 정리 (데이터 저장, 로그아웃 등)>

- 사용자가 앱을 종료하거나 다른 화면으로 이동할 때 리소스를 정리할 수 있습니다

struct LogoutView: View {
    var body: some View {
        Text("로그아웃 화면")
            .onDisappear {
                saveUserData()
            }
    }

    func saveUserData() {
        print("사용자 데이터 저장 완료")
    }
}

 

 

onDisappear 주의점

  • onDisappear는 단순히 코드가 실행되는 트리거 역할만 합니다.
  • 실행 중인 Task는 자동으로 취소되지 않으며, 직접 취소해야 합니다.

⬇️

>>위에서 학습한 .task를 사용하면 뷰가 사라질 때 자동으로 취소됩니다.

>>즉, onDisappear에서 수동으로 취소할 필요가 없습니다.

struct ContentView: View {
    var body: some View {
        Text("데이터 로딩 중...")
            .task {
                await fetchData()
            }
    }

    func fetchData() async {
        do {
            try await Task.sleep(nanoseconds: 5_000_000_000) // 5초 대기
            print("데이터 로드 완료")
        } catch {
            print("작업이 취소되었습니다.")  // 뷰가 사라지면 자동 취소됨
        }
    }
}

 

 


OnChange

: 제스처의 값이 변경될 때 수행할 동작을 추가

-  특정 값이 변경될 때 실행되는 메서드

-  @State, @Binding, @ObservedObject, @EnvironmentObject 등의 값이 변경될 때 이를 감지하여 실행할 수 있습니다. 

- 값이 변경될 때만 동작하며, 초기에 실행되지 않습니다.

struct ContentView: View {
    @State private var text = ""

    var body: some View {
        TextField("입력하세요", text: $text)
            .padding()
            .onChange(of: text) { oldValue, newValue in
                print("텍스트 변경됨: \(newValue)")
            }
    }
}

 

 

<onChange를 이용한 API 호출>

struct SearchView: View {
    @State private var query = ""
    @State private var results: [String] = []

    var body: some View {
        VStack {
            TextField("검색어 입력", text: $query)
                .onChange(of: query) { oldQuery, newQuery in
                    fetchResults(for: newQuery)
                }

            List(results, id: \.self) { result in
                Text(result)
            }
        }
    }

    func fetchResults(for query: String) {
        guard !query.isEmpty else { return }
        print("'\(query)'에 대한 검색 실행")
    }
}

onReceive

: 퍼블리셔가 내보낸 데이터를 감지할 때 수행할 작업을 추가

 

(SwiftUI에서 onReceive는 Combine 프레임워크의 Publisher를 감지하여 특정 작업을 실행할 때 사용됩니다. Combine 프레임워크에 대한 간단한 학습은 9주차에서 비동기 관련 처리할 때 배우게 될 겁니다. 그러니 여기서의 설명은 생략하도록 하겠습니다.)

 


onSubmit

: 사용자가 보기에 값을 제출할 때 수행할 작업을 추가

- onSubmit은 사용자가 입력을 완료하고 Enter(리턴)키를 눌렀을 때 실행되는 메서드

- SwiftUI의 TextField 또는 SecureField에서 입력 완료 이벤트를 감지하여 실행할 수 있습니다.

 

<기본 사용법>

struct ContentView: View {
    @State private var text = ""

    var body: some View {
        TextField("입력하세요", text: $text)
            .textFieldStyle(.roundedBorder)
            .onSubmit {
                print("사용자가 입력 완료: \(text)")
            }
    }
}

 

<입력 완료 후 키보드 닫기>

struct ContentView: View {
    @State private var text = ""

    var body: some View {
        TextField("이름을 입력하세요", text: $text)
            .textFieldStyle(.roundedBorder)
            .onSubmit {
                hideKeyboard()
            }
    }

    func hideKeyboard() {
        UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
    }
}

 

 

<검색 기능 구현>

struct SearchView: View {
    @State private var query = ""

    var body: some View {
        TextField("검색어 입력", text: $query)
            .textFieldStyle(.roundedBorder)
            .onSubmit {
                search()
            }
    }

    func search() {
        guard !query.isEmpty else { return }
        print("검색 실행: \(query)")
    }
}

 

 

<로그인 입력 완료 후 검증> - 오 이걸로 과제 수정하면 되겟긔

struct LoginView: View {
    @State private var username = ""
    @State private var password = ""

    var body: some View {
        VStack {
            TextField("아이디", text: $username)
                .textFieldStyle(.roundedBorder)
            
            SecureField("비밀번호", text: $password)
                .textFieldStyle(.roundedBorder)
                .onSubmit {
                    login()
                }
            
            Button("로그인", action: login)
        }
        .padding()
    }

    func login() {
        guard !username.isEmpty, !password.isEmpty else {
            print("아이디와 비밀번호를 입력하세요.")
            return
        }
        print("로그인 시도: \(username), \(password)")
    }
}

onTabGesture

: 탭 제스처를 인식할 때 수행할 동작을 추가

- onTapGesture는 SwiftUI에서 사용자가 화면을 탭했을 때 특정 동작을 수행하도록 하는 제스처 모디파이어

 

<기본 사용법>  - 음! 이거 지구본 모양 눌렀을 때 안녕하세요! 뜸

 

<onTabGesture 탭 횟수 설정>

- 기본적으로 onTapGesture는 한 번 탭하면 동작하지만, count 매개변수를 사용하면 특정 횟수만큼 탭해야 동작하도록 설정할 수 있음

(위랑 거의 똑같은 코드라 실행 화면 안넣음)

import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundStyle(.tint)
                .onTapGesture(count: 2) {
                    print("안녕하세요!!")
                }
            Text("Hello, world!")
        }
        .padding()
    }
}

#Preview {
    ContentView()
}