티스토리 뷰

반응형

[SwiftUI] re-rendering, re-draw 추측 에서 SwiftUI가 내부적으로 리렌더링을 어떻게 결정하는 지 추측해보았는데요 

리렌더링을 커스터마이징 (?) 할 수 있는 방법도 있습니다. 

 

# 1. SwiftUI 는 다시 안그리는데, 나는 다시 그리고 싶은 경우

뷰는 extract subview를 하면 별도의 identity를 부여받아, 부모 뷰와 같이 리렌더링이 안됩니다. 

부모의 body가 재호출될 때 extracted 한 서브뷰를 항상 같이 다시 그리고 싶으면 id를 활용합니다. 

 

 

[ 기본 동작 ] 

ContentView의 body가 호출될 때 NoMatterText는 다시 안그려짐

struct ContentView: View {
    @State private var isOn = true
    
    private var lightStack: some View {
        VStack(spacing: 10) {
            Text(isOn ? "ON" : "OFF")
                .background(.random)
            
            Button {
                isOn.toggle()
            } label: {
                Text("🔦")
            }.background(.random)
        }
    }
    
    var body: some View {
        lightStack
        
        Spacer()
            .frame(height: 50)
            .background(.random)

        NoMatterText()
    }
}

struct NoMatterText: View {

    var body: some View {
        Text("NO MATTER!")
            .background(.random)
    }
}

 

 

 

[ 커스마이징 후 동작 ]

ContentView의 body가 호출될 때 NoMatterText도 다시 그려짐 (identity를 내가 갱신하기 때문) 

struct ContentView: View {
    @State private var isOn = true
    
    private var lightStack: some View {
        VStack(spacing: 10) {
            Text(isOn ? "ON" : "OFF")
                .background(.random)
            
            Button {
                isOn.toggle()
            } label: {
                Text("🔦")
            }.background(.random)
        }
    }
    
    var body: some View {
        lightStack
        
        Spacer()
            .frame(height: 50)

        NoMatterText().id(UUID())
    }
}

 

 

 

 

 

# 2. SwiftUI 는 다시 그리는데, 나는 다시 안그리고 싶은 경우

State와 달리 ObservableObject는 구체적인 dependency에 따라 리렌더링을 하지 않습니다. 

관련 없는 서브뷰가 다시 그려지는 게 싫으면 Equatable 을 활용합니다. 

 

 

[ 기본 동작 ]

특정 dependency와 관련 없는 서브뷰들도 다 다시 그려짐

class SomeState: ObservableObject {
    @Published var isOn = true
    @Published var noMatterText = "NO MATTER!"
}

struct ContentView: View {
    @StateObject var state = SomeState()
    
    var body: some View {
        LightStack(isOn: $state.isOn)
        
        Spacer()
            .frame(height: 50)
        
        NoMatterText(text: $state.noMatterText)
    }
}

struct LightStack: View {
    @Binding var isOn: Bool
    
    var body: some View {
        VStack(spacing: 10) {
            Text(isOn ? "ON" : "OFF")
                .background(.random)
            
            Button {
                isOn.toggle()
            } label: {
                Text("🔦")
            }.background(.random)
        }
    }
}

struct NoMatterText: View {
    
    @Binding var text: String
    
    var body: some View {
        Text(text)
            .background(.random)
            .onTapGesture {
                text += "!"
            }
    }
}

 

 

 

[ 커스마이징 후 동작 ]

특정 dependency랑 관련 있는 서브뷰만 다시 그려짐 /  관련 없는 서브뷰는 안그려짐 (equality를 내가 정했기때문)

class SomeState: ObservableObject {
    @Published var isOn = true
    @Published var noMatterText = "NO MATTER!"
}

struct ContentView: View {
    @StateObject private var state = SomeState()
    
    var body: some View {
        LightStack(isOn: $state.isOn)
        
        Spacer()
            .frame(height: 50)

        NoMatterText(text: $state.noMatterText)
    }
}

private struct LightStack: View, Equatable {
    
    @Binding var isOn: Bool

    var body: some View {
        VStack(spacing: 10) {
            Text(isOn ? "ON" : "OFF")
                .background(.random)
            
            Button {
                isOn.toggle()
            } label: {
                Text("🔦")
            }.background(.random)
        }
    }
    
    static func == (lhs: LightStack, rhs: LightStack) -> Bool {
        lhs.isOn == rhs.isOn
    }
}

struct NoMatterText: View, Equatable {

    @Binding var text: String
    
    var body: some View {
        Text(text)
            .background(.random)
            .onTapGesture {
                text += "!"
            }
    }
    
    static func == (lhs: NoMatterText, rhs: NoMatterText) -> Bool {
        lhs.text == rhs.text
    }
}

 

 

참고로 SwiftUI에 equatable() 이란 modifier도 있는데, 

이것을 안쓰고 Equatable 만 채택해줘도 잘 동작하더라구요! 

 

 

반응형
댓글