SwiftUI에서 불필요한 렌더링 제거하기(3) - 다른 View의 @State 값 변화로 인해 View가 초기화되는 현상

0

SwiftUI 렌더링 개선

목록 보기
3/8

서론

https://velog.io/@sustainable-git/SwiftUI에서-불필요한-렌더링-제거하기
https://velog.io/@sustainable-git/SwiftUI에서-불필요한-렌더링-제거하기2

앞선 포스팅에서 아래와 같은 것들을 확인하였습니다.

  1. 렌더링이 다시 일어남을 확인하는 방법
  2. 모듈화를 통해 렌더링이 다시 일어나지 않도록 하는 방법
  3. @State 값 변화에 렌더링이 다시 일어난다는 점을 활용하여 computed property를 @State 처럼 활용하는 방법
  4. ViewModel의 @Published 값의 변화가 이를 참조하는 모든 View를 업데이트한다는 점
  5. @EnvironmentObject를 활용해 자식 View를 렌더링하지 않고 손자 View를 업데이트 하는 방법

오늘은 실제로 일어날만한(저에게 실제로 일어났던) 일을 소개해드리겠습니다.


서로 다른 Picker의 값이 서로를 초기화시키는 이슈

시, 분, 초를 나타내야 하는 Picker가 있고, 각 Picker는 독립적이게 움직여야 합니다.
하지만 영상을 자세히 보면 분이 움직이고 있는 와중 초가 변경되고 있습니다.
14분이던 분 Picker를 가속해서 돌리는 와중 초를 변경하면 분 Picker가 초기화되어 다시 14분으로 돌아갑니다.
해당 코드는 시, 분 초 중 하나라도 값이 정해지면 body 전체를 다시 그리면서 정해지지 않은(움직이는 Picker) 값은 초기화하여 그리게 됩니다.

import SwiftUI

struct ContentView: View {
    @State private var hours: Int = 0
    @State private var minutes: Int = 0
    @State private var seconds: Int = 0
    
    var body: some View {
        HStack {
            Picker("시", selection: $hours) {
                ForEach(0...5, id: \.self) { hour in
                    Text("\(hour)시")
                }
            }
            Picker("분", selection: $minutes) {
                ForEach(0...60, id: \.self) { minute in
                    Text("\(minute)분")
                }
            }
            Picker("초", selection: $seconds) {
                ForEach(0...60, id: \.self) { second in
                    Text("\(second)초")
                }
            }
        }
        .pickerStyle(.wheel)
    }
}

서로 다른 Picker가 서로의 랜더링에 영향을 주지 않으려면

앞선 포스팅들을 살펴보면 이 이슈의 원인을 쉽게 이해할 수 있습니다.
하나라도 @State 값이 정해지면 body를 다시 그려야 합니다.
모듈화가 되어 있다면 해당 @State에 영향을 받는 모듈만 업데이트 되지만, 그렇지 않다면 전부 다시 그립니다.
때문에 각 Picker를 모듈화 하면 이를 쉽게 해결할 수 있을 것입니다.

import SwiftUI

struct ContentView: View {
    @State private var hours: Int = 0
    @State private var minutes: Int = 0
    @State private var seconds: Int = 0
    
    var body: some View {
        HStack {
            MyPickerView(unit: "시", range: 0...5, bindedTime: $hours)
            MyPickerView(unit: "분", range: 0...60, bindedTime: $minutes)
            MyPickerView(unit: "초", range: 0...60, bindedTime: $seconds)
        }
        .pickerStyle(.wheel)
    }
}

fileprivate struct MyPickerView: View {
    let unit: String
    let range: ClosedRange<Int>
    @Binding var bindedTime: Int
    
    
    var body: some View {
        Picker(unit, selection: $bindedTime) {
            ForEach(range, id: \.self) { time in
                Text("\(time)\(unit)")
            }
        }
    }
}


결론

위 현상은 SwiftUI를 조금만 쓰다 보면 여러분도 겪게될 일입니다.
하지만, 이를 모르고 지나친다면 영원히 발견하지 못할 수도 있습니다.
특히 개발 편의를 위해 하나의 ViewModel을 여러 View에서 참조하도록 개발하는 경우가 많은데, 이 때 View가 다시 그려지는 것에 많은 주의가 필요합니다.

profile
https://github.com/sustainable-git

0개의 댓글

관련 채용 정보