본문 바로가기
SwiftUI

SwiftUI - Lazy Navigation

by 고고 2022. 3. 16.

안녕하세요. 고고입니다.

SwiftUI로 개발을 하다보면 List와 NavigationLink로 아래와 같은 화면을 만들 때가 많습니다.

List(1...100, id: \.self) { id in
    NavigationLink(destination: SecondView(id: id)) {
        Text("\(id)번째 항목")
    }
}

 

 

아래 코드는 FirstView에서 리스트의 항목을 하나 클릭하면 SecondView로 이동합니다.

struct FirstView: View {
    var body: some View {
        List(1...100, id: \.self) { id in
            NavigationLink(destination: SecondView(id: id)) {
                Text("\(id)번째 항목")
            }
        }.navigationTitle("FirstView")
    }
}

class SecondViewModel: ObservableObject {
    let id: Int
    
    init(id: Int) {
        self.id = id
        print("init - SecondViewModel \(id)")
    }
    
    deinit {
        print("deinit - SecondViewModel \(id)")
    }
}

struct SecondView: View {
    @ObservedObject var viewModel: SecondViewModel
    let id: Int
    
    init(id: Int) {
        self.id = id
        self.viewModel = SecondViewModel(id: id)
    }
    
    var body: some View {
        Text("Hello World!")
            .navigationTitle("\(id) SecondView")
    }
}

 

 

 

FirstView로 들어가면 현재 화면에 보이는 16개의 SecondView의 이니셜라이저가 호출되며 SecondViewModel도 함께 생성되게 됩니다.

 

 

 

만약 스크롤을 내리면 추가로 화면에 표시된 SecondView가 더 생성됩니다. 이렇게 생성된 SecondView들은 FirstView를 나가기 전까지 사라지지 않습니다.

 

 

 FirstView를 나가면 사라집니다.

 

 

 

이렇게 NavigationLink의 destination인 뷰의 생성자가 자꾸 호출되는 게 싫다면, LazyView로 감싸주면 됩니다.

import SwiftUI

struct LazyView<Content: View>: View {
    let build: () -> Content
    
    init(_ build: @autoclosure @escaping () -> Content) {
        self.build = build
    }
    
    init(@ViewBuilder _ build: @escaping () -> Content) {
        self.build = build
    }
    
    var body: Content {
        build() // << everything is created here
    }
}
struct FirstView: View {
    var body: some View {
        List(1...100, id: \.self) { id in
            NavigationLink(destination: LazyView(SecondView(id: id))) {
                Text("\(id)번째 항목")
            }
        }.navigationTitle("FirstView")
    }
}

class SecondViewModel: ObservableObject {
    let id: Int
    
    init(id: Int) {
        self.id = id
        print("init - SecondViewModel \(id)")
    }
    
    deinit {
        print("deinit - SecondViewModel \(id)")
    }
}

struct SecondView: View {
    @ObservedObject var viewModel: SecondViewModel
    let id: Int
    
    init(id: Int) {
        self.id = id
        self.viewModel = SecondViewModel(id: id)
    }
    
    var body: some View {
        Text("Hello World!")
            .navigationTitle("\(id) SecondView")
    }
}

 

 

이렇게 LazyView로 감싸주니 FirstView에서 destination인 SecondView의 생성자가 호출되지 않습니다.

NavigationLink(destination: LazyView { SecondView(id: id) })
NavigationLink(destination: LazyView(SecondView(id: id)))

 

 

LazyView로 감싸면 아래처럼 리스트의 항목을 클릭해서 SecondView로 이동했을 때에만 단 하나의 SecondView가 생성됩니다.

 

 

SecondView에서 뒤로가기를 선택하면 deinit됩니다.

 

 

List 또는 ForEach로 많은 NavigationLink를 만들고, destination인 뷰의 생성자를 많이 호출하는 게 부담된다면 LazyView를 사용하는 것을 추천드립니다.

 

 

참고 출처 : https://stackoverflow.com/questions/61742647/swift-navigationlink-calling-destinations-init-method-multiple-times/61743337#61743337

댓글