인프런 커뮤니티 질문&답변

(알 수 없음)님의 프로필 이미지

작성한 질문수

간단하게 만들어 보는 iOS 설정앱

@ViewBuilder 사용 질문입니다

24.09.08 23:39 작성

·

23

·

수정됨

0

안녕하세요! 다름이 아니라 강의를 보면 @ViewBuilder 어노테이션을 사용하시는데 강사님이 쓰신 블로그인지는 모르겠지만

https://dev200ok.blogspot.com/2022/05/swiftui.html(블로그 주인 이름이 강사님과 동일)

위 링크에 따르면

  • SwiftUI에서 UI를 그리다 보면, 뷰를 따로 떼어내서 작업을 해야하는 경우들이 발생

  • @ViewBuilder 어노테이션을 붙여 뷰를 만들어 주는 메소드를 만들어 줄 수 있습니다. 뷰를 코드와 분리하기 때문에 코드의 가독성을 높여주며 다른 뷰를 독립적으로 보기 좋음

  • @ViewBuilder를 붙여서 만든다면 암시적으로 해당 뷰 안에서만들어 사용하겠다는 뜻

위와 같은 이유로 @ViewBuilder를 사용한다고 되어 있습니다 근데 제 코드를 보시면

import SwiftUI

struct ContentView: View {
    @State private var airplaneMode: Bool = false
    
    func Profile() -> some View{
        NavigationLink{
            Text("프로필 화면")
        } label: {
            HStack{
                Image(systemName: "person.fill")
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .frame(width: 40, height: 40)
                    .padding(.all, 10)
                    .background(.green)
                    .clipShape(Circle())
                VStack(alignment: .leading, spacing: 3){
                    Text("홍길동")
                        .font(.system(size: 24))
                        .fontWeight(.regular)
                    Text("Apple ID, iCloud, 미디어 및 구입")
                        .font(.system(size: 14))
                }
                .padding(.leading, 6)
            }
            .padding(.vertical, 10)
        }
    }
    
    func ImageDesign(sysName: String, imgColor: Color) -> some View{
        Image(systemName: sysName)
            .resizable()
            .aspectRatio(contentMode: .fit)
            .frame(width: 20, height: 20)
            .padding(.all, 4)
            .background(imgColor)
            .foregroundColor(.white)
            .cornerRadius(6)
    }
    
    func ListMenuLetter(sysName: String, NaviLinkText: String, imgColor: Color) -> some View{
        HStack{
            NavigationLink{
                switch NaviLinkText{
                case "WI-FI":
                    Text("WI-FI 화면")
                case "Bluetooth":
                    Text("Bluetooth 화면")
                default:
                    EmptyView() // 빈 뷰를 반환
                }
            } label: {
                switch NaviLinkText{
                case "WI-FI":
                    ImageDesign(sysName: sysName, imgColor: imgColor)
                    HStack{
                        Text("WI-FI")
                        Spacer()
                        Text("IPTIME")
                            .foregroundColor(.gray)
                    }
                case "Bluetooth":
                    ImageDesign(sysName: sysName, imgColor: imgColor)
                    HStack{
                        Text("Bluetooth")
                        Spacer()
                        Text("켬")
                            .foregroundColor(.gray)
                    }
                default:
                    EmptyView() // 빈 뷰를 반환
                }
            }
        }
    }
    
    func ListMenu(sysName: String, NaviLinkText: String, imgColor: Color) -> some View{
        HStack{
            ImageDesign(sysName: sysName, imgColor: imgColor)
            
            if NaviLinkText != "에어플레인 모드"{
                NavigationLink(NaviLinkText){ // 여기에 지금 label이 따로 지정되어 있지 않은데 이럴 경우 괄호 안에 있는 NaviLinkText가 label 역할을 함
                    switch NaviLinkText{
                    case "셀룰러":
                        Text("셀룰러 화면")
                    case "개인용 핫스팟":
                        Text("개인용 핫스팟 화면")
                    case "스크린 타임":
                        Text("스크린 타임 화면")
                    case "일반":
                        Text("일반 화면")
                    case "손쉬운 사용":
                        Text("손쉬운 사용 화면")
                    case "개인 정보 보호":
                        Text("개인 정보 보호 화면")
                    case "암호":
                        Text("암호 화면")
                    default:
                        EmptyView() // 빈 뷰를 반환
                    }
                }
            }
            else{
                Toggle("에어플레인 모드", isOn: $airplaneMode)
            }
        }
    }
    
    var body: some View {
        NavigationView {
            List{
                Section{
                    Profile()
                }
                
                Section{
                    ListMenu(sysName: "airplane", NaviLinkText: "에어플레인 모드", imgColor: .orange)
                    ListMenuLetter(sysName: "wifi", NaviLinkText: "WI-FI", imgColor: .blue)
                    ListMenuLetter(sysName: "b.circle", NaviLinkText: "Bluetooth", imgColor: .blue)
                    ListMenu(sysName: "antenna.radiowaves.left.and.right", NaviLinkText: "셀룰러", imgColor: .green)
                    ListMenu(sysName: "personalhotspot", NaviLinkText: "개인용 핫스팟", imgColor: .green)
                }
                
                Section{
                    ListMenu(sysName: "hourglass", NaviLinkText: "스크린 타임", imgColor: .blue)
                }
                
                Section{
                    ListMenu(sysName: "gear", NaviLinkText: "일반", imgColor: .gray)
                    ListMenu(sysName: "person.crop.circle", NaviLinkText: "손쉬운 사용", imgColor: .blue)
                    ListMenu(sysName: "hand.raised.fill", NaviLinkText: "개인 정보 보호", imgColor: .blue)
                }
                
                Section{
                    ListMenu(sysName: "key.fill", NaviLinkText: "암호", imgColor: .gray)
                }
            }
            .navigationTitle(Text("설정")) // 타이틀 위치 코드가 맨 위가 아닌데 정상 동작하는 이유는 무엇인가
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Profile 함수나 ImageDesign 등 뷰를 함수로 따로 빼서 독립적인 코드를 구성했는데 @ViewBuilder를 사용하지 않고도 아무런 문제가 없이 정상적으로 동작합니다. 사용하지 않아도 문제가 없는데 강의에서 @ViewBulider를 사용하시는 이유가 궁금합니다 답변 주시면 감사하겠습니다!

답변 1

0

인프런 AI 인턴님의 프로필 이미지

2024. 09. 09. 23:41

안녕하세요, 인프런 AI 인턴입니다.

@ViewBuilder 어노테이션은 SwiftUI에서 복잡한 뷰 구조를 더 간결하고 선언적으로 표현하기 위해 사용됩니다. 함수나 클로저에서 여러 뷰를 조건부로 표시하거나, 다양한 뷰를 조합하여 새로운 뷰 컴포넌트를 만들 때 유용하게 사용할 수 있습니다.

질문하신 코드에서는 @ViewBuilder를 사용하지 않고도 정상 동작하는 이유는, 개별 함수들이 이미 단일 뷰를 반환하고 있기 때문에, 복수의 뷰를 조건부로 반환하거나 복잡한 뷰 로직을 다루지 않기 때문입니다.

하지만, 만약 한 함수 내에서 여러 조건에 따라 다른 뷰를 반환해야 하거나, 뷰의 구성이 복잡해지는 경우에는 @ViewBuilder를 사용함으로써 더 나은 유연성과 코드의 가독성을 얻을 수 있습니다. 예를 들어, 조건에 따라 다른 뷰를 보여주거나, 여러 뷰를 순차적으로 배치해야 하는 상황에서 @ViewBuilder를 사용하면 코드를 훨씬 간결하고 명시적으로 작성할 수 있습니다.

요약하자면, 현재 코드에서는 @ViewBuilder를 필요로 하지 않을 수 있지만, 더 복잡한 뷰 구성이나 조건부 뷰 렌더링을 다루게 될 때 @ViewBuilder의 이점을 활용할 수 있습니다. 강의에서 @ViewBuilder를 사용하는 이유도 바로 이러한 유연성과 가독성을 높이기 위함일 것입니다.

(알 수 없음)님의 프로필 이미지

2024. 09. 10. 02:12

혹시 그럼 @ViewBuilder를 사용할만한 상황이 뭐가 있는지 코드로 알려주실 수 있을까요?