๋™์ผํ•œ UI ๊ตฌ์„ฑ์„ ๊ฐ€์ง€์ง€๋งŒ ๊ฐ€๋กœ๋ชจ๋“œ์—์„œ ์„ธ๋กœ๋ชจ๋“œ๋กœ ๋ณ€๊ฒฝ๋ ๋•Œ ํ™”๋ฉด์˜ ๋„“์ด๊ฐ€ ๋‹ฌ๋ผ์ง€๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ํŠน์ • UI์—์„œ๋Š” ํ™”๋ฉด์ด ๊นจ์งˆ ์ˆ˜ ์žˆ์–ด์„œ ์ด์— ๋Œ€์‘ํ•ด์•ผ ํ•ด์š”. ViewBuilder๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์—ฌ๋Ÿฌ ํ™”๋ฉด์—์„œ ๋Œ€์‘ ๊ฐ€๋Šฅํ•œ View๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!

ํ˜น์‹œ ViewBuilder๊ฐ€ ๋ฌด์—‡์ธ์ง€ ์ž˜ ๋ชจ๋ฅด๊ฒ ๋‹ค๋ฉด ์•„๋ž˜ ํฌ์ŠคํŒ…์„ ์ฐธ๊ณ ํ•˜์„ธ์š”!

 

 

[SwiftUI] ViewBuilder ์•Œ์•„๋ณด๊ธฐ

Definition @resultBuilder struct ViewBuilder ํด๋กœ์ €์—์„œ ๋ทฐ๋ฅผ ๊ตฌ์„ฑํ•˜๋Š” ์‚ฌ์šฉ์ž ์ง€์ • ํŒŒ๋ผ๋ฏธํ„ฐ ์†์„ฑ func contextMenu( @ViewBuilder menuItems: () -> MenuItems ) -> some View ํด๋กœ์ € ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ํ†ตํ•ด child view๋ฅผ ์ƒ์„ฑํ•˜๊ณ ์ž

framios.tistory.com

 

ํ˜น์‹œ ์นด์นด์˜คํ†ก ์ž ๊ธˆํ™”๋ฉด์„ ๋ณด์‹ ์  ์žˆ์œผ์‹ ๊ฐ€์š”? 

์ž ๊ธˆ ํ™”๋ฉด์—์„œ ์„ธ๋กœ ๋ชจ๋“œ๋กœ ๋ณ€๊ฒฝํ•˜๋ฉด ์ด์— ๋Œ€์‘ํ•ด์„œ UI๊ฐ€ ์žฌ๋ฐฐ์น˜ ๋˜์š”. ๊ทธ๋Ÿฌ๋‚˜ SwiftUI๋กœ ๋‹จ์ˆœํžˆ View๋ฅผ ๊ทธ๋ฆฌ๊ฒŒ ๋˜๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด ๋ฉ๋‹ˆ๋‹ค

 

์„ธ๋กœ ๋ชจ๋“œ์—์„œ๋Š” ์ž˜ ๋‚˜์˜ค๋˜ ๋ทฐ๊ฐ€ ๊ฐ€๋กœ ๋ชจ๋“œ์—์„œ๋Š” ์œ„ ์•„๋ž˜ ๊ณต๊ฐ„์ด ๋ถ€์กฑํ•ด์„œ ์งค๋ฆฌ๊ฒŒ ๋˜๊ณ , ๋„“์ด๊ฐ€ ์ปค์ ธ์„œ UI๊ฐ€ ์˜†์œผ๋กœ ๋Š˜์–ด๋‚˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. 

์ด๋ฅผ ๋Œ€์‘ํ•˜๊ธฐ ์œ„ํ•ด GeometryReader์™€ @ViewBuilder๋ฅผ ์‚ฌ์šฉํ•ด ์ค„ ๊ฒŒ์š”!

 

์™„์„ฑ๋œ ์ฝ”๋“œ๋Š” ์•„๋ž˜ ๋ ˆํฌ์ง€ํ† ๋ฆฌ์—์„œ ๋‹ค์šด๋กœ๋“œ ๋ฐ›์œผ์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

 

GitHub - youabledev/landscape_password_swiftui: ๋น„๋ฐ€๋ฒˆํ˜ธ ์ž…๋ ฅํ™”๋ฉด ๊ฐ€๋กœ๋ชจ๋“œ ์„ธ๋กœ๋ชจ๋“œ ์ง€์›ํ•˜๊ธฐ

๋น„๋ฐ€๋ฒˆํ˜ธ ์ž…๋ ฅํ™”๋ฉด ๊ฐ€๋กœ๋ชจ๋“œ ์„ธ๋กœ๋ชจ๋“œ ์ง€์›ํ•˜๊ธฐ. Contribute to youabledev/landscape_password_swiftui development by creating an account on GitHub.

github.com

 

๊ธฐ๋ณธ View ๊ทธ๋ฆฌ๊ธฐ

ํฌ๊ฒŒ ๋‘ ๋ถ€๋ถ„์œผ๋กœ ์ชผ๊ฐœ์–ด View๋ฅผ ๊ทธ๋ ค ์ค๋‹ˆ๋‹ค. ์•”ํ˜ธ ์ž…๋ ฅ๊ณผ ์„ค๋ช… ๊ธ€, ๋ฒ„ํŠผ์„ ๋ˆŒ๋ €์„ ๋•Œ ์ฑ„์›Œ์ง€๋Š” ์›์€ ์„ธ๋กœ ๋ชจ๋“œ์—์„œ ์™ผ์ชฝ์— ์œ„์น˜ํ•˜๊ฒŒ ๋˜๊ณ  ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•˜๋Š” ํ‚คํŒจ๋“œ๋Š” ์˜ค๋ฅธ์ชฝ์œผ๋กœ ์œ„์น˜ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ํ‚คํŒจ๋“œ๋ฅผ ํ•˜๋‚˜์˜ View๋กœ ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋‹ค๋ฅธ View๋กœ ์ชผ๊ฐœ์–ด ์ฝ”๋”ฉํ•ด ์ค„๊ฒŒ์š”

struct InfoView: View {
    var body: some View {
        VStack(spacing: 0) {
            Text("์•”ํ˜ธ ์ž…๋ ฅ")
                .font(.title)
                .fontWeight(.bold)
                .padding(.bottom, 10)
            
            Text("์นด์นด์˜คํ†ก ์•”ํ˜ธ๋ฅผ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”.")
                .foregroundStyle(.gray)
                .font(.system(size: 14))
                .padding(.bottom, 18)
            
            HStack(spacing: 20) {
                ForEach(0..<4) { index in
                    Image(systemName: "circle")
                }
            }
        }
    }
}

๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์€ ์ œ์™ธํ•˜๊ณ  UI์— ๊ด€๋ จ๋œ ์ฝ”๋“œ๋งŒ ๊ฐ„๋‹จํžˆ ์ž‘์„ฑํ–ˆ์–ด์š”. ์ด์ œ ํ‚คํŒจ๋“œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ• ๊ฒŒ์š”

struct KeypadView: View {
    let numbers = [
        ["1", "2", "3"],
        ["4", "5", "6"],
        ["7", "8", "9"],
        [" ", "0", " "]
    ]
    
    var body: some View {
        VStack(spacing: 0) {
            ForEach(0..<numbers.count, id: \.self) { rowIndex in
                HStack(spacing: 0) {
                    ForEach(0..<numbers[rowIndex].count, id: \.self) { columnIndex in
                        Button {
                            print(numbers[rowIndex][columnIndex])
                        } label: {
                            if rowIndex == numbers.count - 1 && columnIndex == numbers[rowIndex].count - 1 {
                                Image(systemName: "eraser")
                                    .keypadButtonStyle()
                                    
                            } else {
                                Text("\(numbers[rowIndex][columnIndex])")
                                    .keypadButtonStyle()
                            }
                        } //: Button
                    }
                }
            } //: ForEach
        } //: VStack
    }
}
struct KeypadButtonStyle: ViewModifier {
    func body(content: Content) -> some View {
        content
            .font(.title)
            .foregroundStyle(.black)
            .padding(.vertical, 20)
            .frame(maxWidth: .infinity)
    }
}

extension View {
    func keypadButtonStyle() -> some View {
        self.modifier(KeypadButtonStyle())
    }
}

์ค‘๋ณต๋˜๋Š” modifier๋Š” custom ViewModifier๋ฅผ ๋งŒ๋“ค์–ด ์ฃผ์—ˆ์–ด์š”. 

 

@ViewBuilder์™€ GeometryReader ์‚ฌ์šฉํ•˜๊ธฐ

@ViewBuilder๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํด๋กœ์ €๋กœ ๋‹จ์ผ ๋ทฐ๋“ค์„ ๋ฐ›์„ ์ˆ˜ ์žˆ๊ณ  ์ด ๋‹จ์ผ ๋ทฐ๋ฅผ ์กฐํ•ฉํ•ด์„œ ๋ณตํ•ฉ ๋ทฐ๋ฅผ ๋งŒ๋“ค๊ฒŒ ๋˜์š”. VStack๊ณผ ScrollView์™€ ๊ฐ™์€ ์ปจํ…Œ์ด๋„ˆ ์—ญํ™œ์„ ํ•˜๋Š” Custom View๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์–ด์š”. 

struct LandscapeManageView<Content: View>: View {
    
    let content: Content
    
    init(@ViewBuilder content: () -> Content) {
        self.content = content()
    }
    
    var body: some View {
        GeometryReader { geometry in
            if geometry.size.height > geometry.size.width {
                VStack {
                    content
                } //: VStack
            } else {
                HStack(alignment: .center) {
                    content
                } //: HStack
                .frame(maxHeight: .infinity)
            }
        } //: GeometryReader
    }
}

์ฒ˜์Œ์—๋Š” @Environment(.\horizontalSizeClass)๋ฅผ ์ด์šฉํ•˜๋ฉด ๋˜๋Š” ์ค„ ์•Œ์•˜๋Š”๋ฐ iPad ๋Œ€์‘์„ ์œ„ํ•ด ์ค€๋น„๋œ ๊ฐ’์ด๋”๋ผ๊ตฌ์š”! GeometryReader๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๊ฐ„๋‹จํ•˜๊ฒŒ ๋†’์ด๊ฐ€ ๋„“์ด๋ณด๋‹ค ๋” ํฐ ๊ฒฝ์šฐ ์„ธ๋กœ๋ชจ๋“œ๋กœ, ์•„๋‹ˆ๋ผ๋ฉด ๊ฐ€๋กœ ๋ชจ๋“œ๋กœ ์ง€์ •ํ•ด ์ฃผ๋ฉด ๋ฉ๋‹ˆ๋‹ค. ํด๋กœ์ €๋กœ ์ „๋‹ฌ๋ฐ›์€ View๋Š” content๋ผ๋Š” ๊ฐ’์œผ๋กœ ๋ฐ›์•„์„œ body์— ํฌํ•จ ์‹œ์ผœ์ค˜์š”.

HStack ์˜ ๊ฒฝ์šฐ ์ปจํ…์ธ ์˜ ํฌ๊ธฐ์— ๋งž๊ฒŒ ๊ฐ์‹ธ์ง€๊ธฐ ๋•Œ๋ฌธ์— maxHeight๋ฅผ infinity๋กœ ์ค€ ๋’ค alignment๋ฅผ center๋กœ ์ง€์ •ํ•ด ์ฃผ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

 

์‹ค์ œ ์‚ฌ์šฉํ•˜๊ธฐ

struct ContentView: View {
    
    var body: some View {
        LandscapeManageView {
            Spacer()
            InfoView()
            Spacer()
            KeypadView()
        }
    }
}

๊ฐ€๋กœ๋ชจ๋“œ์™€ ์„ธ๋กœ๋ชจ๋“œ๋ฅผ ์ง€์›ํ•˜๊ณ  ์‹ถ์€ ํ™”๋ฉด์—์„œ ์ž‘์„ฑํ•œ LandscapeManageView๋ฅผ ์ ์šฉํ•ด ์ฃผ๋ฉด ๋ฉ๋‹ˆ๋‹ค. ๊ฐ๊ฐ์˜ ํ™”๋ฉด์—์„œ GeometryReader๋ฅผ ์‚ฌ์šฉํ•ด ํŒ๋ณ„ํ•˜๋Š” ์กฐ๊ฑด์‹๊ณผ ๊ฐ ์กฐ๊ฑด์— ํ•ด๋‹นํ•˜๋Š” ๋ฆฌํ„ด์„ ์ž‘์„ฑํ•  ํ•„์š”๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์— ์œ ์—ฐํ•˜๊ณ  ์žฌ์‚ฌ์šฉ์„ฑ์ด ๋†’๊ฒŒ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด์„œ ๋Œ€์‘ํ•  ์ˆ˜ ์žˆ์–ด์š”

 

์™„์„ฑ ํ™”๋ฉด

์ฐธ๊ณ  ์‚ฌ์ดํŠธ

์นด์นด์˜คํ†ก ์ž ๊ธˆํ™”๋ฉด

ViewBuilder์— ๋Œ€ํ•œ ์„ค๋ช… https://www.avanderlee.com/swiftui/viewbuilder/