iOS 응용 (SwiftUI)

[iOS - SwiftUI] 뷰 주입받는 방법 (Generics, AnyView)

jake-kim 2025. 3. 12. 01:13

뷰 주입받는 방법 3가지

  특성 단점  
1. generics 최적화 유지, 타입 안전 뷰 타입이 고정됨 ⭐ 추천
2. AnyView 동적 뷰 변경 가능 성능 저하 가능성 ⚠️ 가능하면 피할 것

1. generics 방법

  • View를 준수하는 타입으로 한정하여, 제네릭을 선언하는 방식
    • 최적화가 유지되는 장점
// 주입받은 뷰를 표시하는 컨테이너
struct ContainerView<Content: View>: View {
    let content: Content

    var body: some View {
        VStack {
            Text("현재 뷰")
                .font(.headline)
            content
        }
        .padding()
        .background(Color.gray.opacity(0.2))
        .cornerRadius(10)
    }
}

// 외부에서 동적으로 뷰를 주입
struct ContentView: View {
    @State private var isTextView = true

    var body: some View {
        VStack {
            if isTextView {
                ContainerView(content: Text("이것은 텍스트 뷰"))
            } else {
                ContainerView(content: Button("버튼입니다") {})
            }

            Button("뷰 변경") {
                isTextView.toggle()
            }
            .padding()
        }
    }
}
  • 단점은 뷰 타입이 고정
    • currentView에서 ContainerView에서 제네릭 타입이 Text로 지정되었지만 밑에서 Button을 넣어서 컴파일 오류가 발생
struct ContentView: View {
    @State private var isTextView = true
    @State private var currentView = ContainerView(content: Text("초기 뷰"))

    var body: some View {
        VStack {
            currentView
            Button("뷰 변경") {
                if isTextView {
                    currentView = ContainerView(content: Button("버튼입니다") {})
                    // ❌ 컴파일 오류: Text와 Button은 서로 다른 타입
                } else {
                    currentView = ContainerView(content: Text("다시 텍스트 뷰"))
                }
                isTextView.toggle()
            }
        }
    }
}
  • 주의)
    • if-else는 서로 다른 뷰 트리이므로 아래처럼 사용하면 컴파일 에러 발생 x
struct ContentView: View {
    @State private var isTextView = true

    var body: some View {
        VStack {
            if isTextView {
                ContainerView(content: Text("이것은 텍스트 뷰"))
            } else {
                ContainerView(content: Button("버튼입니다") {})
            }

            Button("뷰 변경") {
                isTextView.toggle()
            }
            .padding()
        }
    }
}

cf) @ViewBuilder와 같이 사용하는패턴

  • 아래 setView2처럼 파라미터에 @ViewBuilder를 선언하고 제네릭을 반환하는데 이때 @ViewBuilder는 이 함수를 사용하는 쪽에서 선언적으로 사용할 수 있는 기능
struct MyView<Content: View>: View {
    let content: Content?
    
    var body: some View {
        content
    }
    
    init(content: (() -> Content)? = nil) {
        self.content = content?()
    }
    
    func getView1(_ view: () -> Content) -> some View {
        VStack {
            Text("")
            view()
        }
    }
    
    func getView2(@ViewBuilder _ view: () -> Content) -> some View {
        VStack {
            Text("")
            view()
        }
    }
}

// 사용하는 쪽

struct ContentView: View {
    var body: some View {
        MyView().getView1 {
            Text("1")
        }
        
        MyView().getView2 {
            Text("1")
            Text("2")
        }
    }
}

 

2. AnyView 방법

  • AnyView는 뷰 타입들을 지워버려서 사용할때 편리하게 할 수 있는 효과가 있으나, 단점이 여러가지가 존재
    • SwiftUI 내부적으로 뷰 트리 구조 파악이 어려워서 재랜더링되는 효과
    • 타입 정보를 잃어버려서 성능 저하의 위험이 존재
struct ContentView: View {
    @State private var currentView: AnyView = AnyView(Text("초기 텍스트 뷰"))

    var body: some View {
        VStack {
            currentView

            Button("뷰 변경") {
                if currentView is AnyView, currentView.body is Text {
                    currentView = AnyView(Button("버튼 뷰") {})
                } else {
                    currentView = AnyView(Text("다시 텍스트 뷰"))
                }
            }
            .padding()
        }
    }
}

결론

  • 뷰를 주입받을 땐 성능을 생각하여 AnyView 사용을 지양하고 generics를 사용할 것