티스토리 뷰

 

TabView로 뷰를 구현하는데 탭뷰에 연결된 뷰를 클릭하면 모든 탭의 뷰들이 초기화되는 걸 확인할 수 있었다.

그래서 이걸 해결하고자 TabView의 렌더링 되는 과정과 LazyView를 사용하여 해결한 방법을 알아보자.

 

1️⃣ TabView 렌더링

 

 

 

TabView | Apple Developer Documentation

A view that switches between multiple child views using interactive user interface elements.

developer.apple.com

 

TabView는 기본적으로 모든 탭에 연결된 뷰를 미리 로드하고 초기화한다고 한다.

즉, 하나의 탭을 선택하더라도 실제로는 모든 탭의 뷰가 동시에 초기화된다는 것!!!!

 

그래서 하나의 탭 선택한 것 뿐인데 다른 뷰들이 자꾸 초기화되던 문제가 있었구나,,,ㅎㅎㅎㅎ

 

그럼 이렇게 모든 탭의 뷰를 한번에 초기화하고,

다른 탭으로 이동할 때마다 다른 뷰들이 초기화되는게 왜 문제가 될까??

 

뷰의 수가 적거나, 또는 탭에 연결된 뷰가 복잡하지 않은 경우에는 괜찮지만 다음과 같은 경우에는 문제가 될 수 있다.

 

  • 무거운 데이터를 가지고 있는 뷰: 대량의 데이터를 처리하는 뷰는 초기화하는 데 시간이 걸림
  • 실시간 데이터나 api 요청이 많은 뷰: 각 탭이 불필요하게 많은 네트워크 요청을 함

 

이처럼 사용하지 않는 뷰인데 이미 메모리에 로드된 상태이기 때문에 성능이 저하될 수 있다.

 

 

2️⃣ TabView 렌더링 예제

 

 

세개의 뷰를 가진 탭뷰를 만들어보자.

struct ContentView: View {
    @State private var selectedTab = 0
    
    var body: some View {
        TabView(selection: $selectedTab) {
            FirstView()
                .tabItem {
                    Image(systemName: "1.square.fill")
                    Text("First")
                }
                .tag(0)

            SecondView()
                .tabItem {
                    Image(systemName: "2.square.fill")
                    Text("Second")
                }
                .tag(1)

            ThirdView()
                .tabItem {
                    Image(systemName: "3.square.fill")
                    Text("Third")
                }
                .tag(2)
        }
    }
}

 

각 뷰들은 다음과 같이 구현했다.

struct FirstView: View {
    init() {
        print("FirstView initialized")
    }
    
    var body: some View {
        print("FirstView body executed")
        return Text("This is the First View")
            .font(.largeTitle)
    }
}

struct SecondView: View {
    init() {
        print("SecondView initialized")
    }
    
    var body: some View {
        print("SecondView body executed")
        return Text("This is the Second View")
            .font(.largeTitle)
    }
}

struct ThirdView: View {
    init() {
        print("ThirdView initialized")
    }
    
    var body: some View {
        print("ThirdView body executed")
        return Text("This is the Third View")
            .font(.largeTitle)
    }
}

 

이제 코드를 실행해보면 탭뷰를 로드하면 바로 보여지는 뷰는 FirstView이고, 다른 탭으로 이동한 적이 없는데 SecondView와 ThirdView가 초기화되는 걸 확인할 수 있다.

 

 

SecondView로 이동하면 어떻게 될까??

 

그럼 더이상 모든 뷰들이 초기화되지 않고 처음에 모든 뷰가 한번에 초기화된 그 상태로 유지되면서 SecondView의 body만 실행한다.

TabView에서 탭을 이동하면 그 뷰의 body가 실행되기 때문이다.

 

 

 

어라?? 근데 탭을 이동했는데 SecondView가 아닌 다른 뷰들이 초기화되지 않는거지,,

원래 다른 탭 이동하면 다 초기화되는거 아닌가??

 

TabView는 기본적으로 뷰를 캐시해서 탭 전환시 다시 초기화하지 않는다고 한다.

그래서 TabView를 처음 로드할 때 모든 뷰들을 초기화하고 탭을 이동하면 더이상 초기화하지 않는 것이다.

 

만약 id 속성으로 TabView에 특정 키를 제공하면, 각 탭이 전환될 때마다 뷰를 새로 생성할 수 있게 된다.

즉, id를 변경할 때마다 SwiftUI는 뷰가 전혀 다른 것으로 인식해서 해당 뷰를 새로 초기화한다. 

 

struct ContentView: View {
    @State private var selectedTab = 0
    
    var body: some View {
        TabView(selection: $selectedTab) {
            FirstView()
                .id(selectedTab)
                .tabItem {
                    Image(systemName: "1.square.fill")
                    Text("First")
                }
                .tag(0)

            SecondView()
                .id(selectedTab)
                .tabItem {
                    Image(systemName: "2.square.fill")
                    Text("Second")
                }
                .tag(1)

            ThirdView()
                .id(selectedTab)
                .tabItem {
                    Image(systemName: "3.square.fill")
                    Text("Third")
                }
                .tag(2)
        }
    }
}

 

빨간줄 아랫 부분부터 SecondView로 이동한 로직인데 확인해보자.

 

SecondView로 이동하고 body가 실행된 후, id 때문에 SecondView를 다른 뷰로 인식해서 모든 뷰를 다시 초기화하는 로직을 확인할 수 있다. 

아,,, id 때문에 탭을 선택하면 계속 상관없는 뷰도 같이 초기화되고 있었구나~~ 

 

 

 

그럼 탭뷰를 로드했을 때 모든 뷰가 초기화되는 것이 아니라 선택된 탭만 초기화하고 싶다면 어떻게 해야될까?

 

바로 LazyView를 사용하면 된다!!

 

 

3️⃣ LazyView 

 

 

LazyView는 SwiftUI에서 제공하는 뷰는 아니지만 TabView, NavigationView, 또는 List에서 지연 로딩을 구현하기 위해 직접 만들어 사용하는 커스텀 뷰이다. 목적은 뷰의 초기화를 지연시켜서 메모리 사용량을 줄이고, 성능을 최적화하는 것이다.

 

TabView는 모든 탭 뷰를 초기화하는게 문제였는데 이걸 LazyView를 사용해서 해결할 수 있다.

뷰의 초기화를 지연해서 선택된 탭의 뷰만 초기화하도록 하는 것이다!!

 

LazyView 코드를 확인해보자.

 

struct LazyView<Content: View>: View {
    let build: () -> Content
    
    init(_ build: @escaping () -> Content) {
        self.build = build
    }
    
    var body: Content {
        build()
    }
}

 

  • init(_ build: @escaping () -> Content): 생성 시 뷰를 생성하는 클로저를 받아 build 프로퍼티에 저장한다.
  • var body: Content { build() }: body 프로퍼티가 호출될 때 실제 뷰를 생성하는 build 클로저를 실행한다.

 

즉, Content는 View 프로토콜을 준수하는 타입이고 LazyView로 A 뷰를 감싸면, A 뷰는 build 클로저에 저장된다.
이후 LazyView의 body 프로퍼티가 실행될 때, 즉 탭을 A 뷰로 이동하는 경우에 build 클로저가 실행되어 A 뷰가 초기화된다.

 

 

4️⃣ LazyView 적용

 

 

코드에서 SecondView만 LazyView로 감싸보자.

 

struct ContentView: View {
    @State private var selectedTab = 0
    
    var body: some View {
        TabView(selection: $selectedTab) {
            FirstView()
                .tabItem {
                    Image(systemName: "1.square.fill")
                    Text("First")
                }
                .tag(0)

            LazyView{SecondView()}
                .tabItem {
                    Image(systemName: "2.square.fill")
                    Text("Second")
                }
                .tag(1)

            ThirdView()
                .tabItem {
                    Image(systemName: "3.square.fill")
                    Text("Third")
                }
                .tag(2)
        }
    }
}

 

실행해보면 LazyView를 적용한 SecondView를 제외한 모든 뷰가 초기화되고 있는걸 확인할 수 있다.

즉, LazyView가 SecondView의 초기화를 지연하고 있다는 의미다.

 

 

SecondView로 탭을 이동해보자.

이제 SecondView가 초기화 되면서 body도 같이 실행되는 걸 확인할 수 있다!!

 

 

id 속성을 적용한 예제에서도 SecondView에 LazyView를 적용하여 결과를 확인해보자.

 

 

실행해보면 SecondView를 제외한 FirstView와 ThirdView는 초기화되고 있다.

SecondView로 이동해보자.

 

 

SecondView로 이동하니까 초기화가 되는 걸 확인할 수 있다.

id 속성때문에 탭 이동할때마다 모든 뷰가 초기화되는 경우에도 LazyView를 적용하여 무분별한 초기화를 줄이고, 해당 뷰를 보여줄때만 초기화하도록 제한할 수 있다.

 

이렇게 함으로써, TabView의 모든 탭의 뷰가 로드되는 걸 방지하고 필요한 뷰만 로드하게 만들 수 있다.

 


 

 

LazyView를 통해 TabView의 뷰 초기화를 지연시켜 메모리를 효율적으로 관리할 수 있다는 점을 배울 수 있었다. 이전에는 TabView의 렌더링 방식을 제대로 이해하지 못한 채로 사용했는데, 이제는 TabView가 모든 자식 뷰를 한 번에 초기화하는 방식이라는 걸 확실히 알 수 있다!!

그리고 LazyView를 사용해서 초기화 지연을 할 수 있다는 걸 알았고, 앞으로도 유용하게 사용할 것 같다~~

 


🧐 더이상 TabView에서 LazyView의 기능을 쓸 수 없다...?

 

내가 확인한 2025.04.05를 기준으로 TabView에서 LazyView를 사용하여 초기화를 지연하는 방법은 사용할 수 없는 것 같다.

 

기존에는 SwiftUI TabView가 모든 탭의 뷰를 미리 초기화하는 구조였지만, LazyView를 활용하면 특정 탭이 선택될 때에만 해당 뷰를 초기화하도록 우회할 수 있었다. 하지만 최근 버전(iOS 17 이후)에서는 Apple이 내부 최적화를 강화하면서, LazyView를 감싸더라도 모든 탭의 뷰가 TabView 빌드 시점에 초기화되는 현상을 확인했다.

 

아쉽지만 LazyView를 사용해도 TabView가 빌드될때 모든 탭의 뷰들이 초기화되는 걸 확인했다...

결국 원하는 탭을 선택했을 때 해당 뷰를 초기화하는 방법을 사용하고 싶다면, 더 이상 기존 방식으로는 대응이 어렵고, 뷰 로딩 시점을 직접 제어할 수 있는 커스텀 TabView 구현을 해야할 것 같다.

 

 

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
글 보관함