본문 바로가기
IT/swift

100 days of SwiftUI - Day23

by 가능성1g 2024. 11. 19.
반응형

이번 클래스는 왜 View 가 클래스가 아닌 struct 인지 와 기본 템플릿의 body 는 왜 some View 를 리턴하는가에 대한 이야기 였다. 

결론만 요약해서 말하면,

클래스 인 경우 쓰지 않는 속성과 메소드로 간단한 장면을 그릴때에도 무거워지기 때문에 가볍고 빠르게 만들기 위해 View 를 쓰고 있고

some View 는 역시 어떤걸 리턴할 지 모르지만

모두 대응하는 값으로 설정하기 위한것이다! 로 볼수 있겠다. (View 는 protocol 이다 )

추가적으로, modifier 의 순서는 중요하다! modifier 는 구조체의 끝에 .로 선언 및 추가 수정을 가하는것 인데,

그냥 UI 컴포넌트를 빌더 패턴 체인 호출(java에서처럼) 같이 쓴다고 보면 될듯 하다. 

어쨌든 순서의 중요성은 아래의 예를 확인하면 된다.

//
//  ContentView.swift
//  ViewsAndModifiers
//
//  Created by HanTJ on 11/18/24.
//

import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Button("Hello, world!") {
                print(type(of: self.body))
            }
            .background(.red)
            .frame(width: 200, height: 200)
            
            Button("Hello, world!") {
                print(type(of: self.body))
            }
            .frame(width: 200, height: 200)
            .background(.red)
        }
        .padding()
    }
}

#Preview {
    ContentView()
}

순차적이니 당연한거 같지만, 알아둬야 할 정보이다. 

 

조건에 따른 View 형태 바꾸기 예제다(Conditional View). 토클을 이용해서, 속성 값을 지정하게 했다. 삼항연산자도 사용했다. 

//
//  ContentView.swift
//  ViewsAndModifiers
//
//  Created by HanTJ on 11/18/24.
//

import SwiftUI

struct ContentView: View {
    @State private var useRedText = false
    
    var body: some View {
        Button("Hello World") {
            // flip the Boolean between true and false
            useRedText.toggle()
        }
        .foregroundStyle(useRedText ? .red : .blue)

    }
}

#Preview {
    ContentView()
}

 

여러개의 View 를 묶어서 취급하고 싶을때는 계속 써온 VStack, HStack, ZStack 을 쓰거나 

정렬속성은 없는 Group을 쓰거나, @ViewBuilder를 쓸 수 있다.

var spells: some View {
    VStack {
        Text("Lumos")
        Text("Obliviate")
    }
}

var spells: some View {
    Group {
        Text("Lumos")
        Text("Obliviate")
    }
}

@ViewBuilder var spells: some View {
    Text("Lumos")
    Text("Obliviate")
}

 

modifier를 동일한것을 계속적으로 쓴다면 View를 별도로 구성해서 쓸수 있다.

//
//  ContentView.swift
//  ViewsAndModifiers
//
//  Created by HanTJ on 11/18/24.
//

import SwiftUI

struct ContentView: View {
    @State private var useRedText = false
    
    var body: some View {
        VStack(spacing: 10) {
            Text("First")
                .font(.largeTitle)
                .padding()
                .foregroundStyle(.white)
                .background(.blue)
                .clipShape(.capsule)

            Text("Second")
                .font(.largeTitle)
                .padding()
                .foregroundStyle(.white)
                .background(.blue)
                .clipShape(.capsule)
        }
    }
}

#Preview {
    ContentView()
}

 

바꾸기!!

별도의 CapsuleText를 만들고 이를 호출하는걸로 바꿨다. 깔끔!

//
//  ContentView.swift
//  ViewsAndModifiers
//
//  Created by HanTJ on 11/18/24.
//

import SwiftUI

struct ContentView: View {
    @State private var useRedText = false
    
    var body: some View {
        VStack(spacing: 10) {
            CapsuleText(text: "First")
            CapsuleText(text: "Second")
        }
    }
}

struct CapsuleText: View {
    var text: String

    var body: some View {
        Text(text)
            .font(.largeTitle)
            .padding()
            .foregroundStyle(.white)
            .background(.blue)
            .clipShape(.capsule)
    }
}

#Preview {
    ContentView()
}

 

 

ViewModifier를 이용하면, View에 Modifier 를 붙일때 쓰는걸 추가로 만들 수 있다.

//
//  ContentView.swift
//  ViewsAndModifiers
//
//  Created by HanTJ on 11/18/24.
//

import SwiftUI

struct ContentView: View {
    @State private var useRedText = false
    
    var body: some View {
        Text("Hello World")
            .modifier(Title()) //modifier로 스타일을 추가!
    }
}

struct Title: ViewModifier {  //ViewModifier protocol 을 이용
    func body(content: Content) -> some View {
        content
            .font(.largeTitle)
            .foregroundStyle(.white)
            .padding()
            .background(.blue)
            .clipShape(.rect(cornerRadius: 10))
    }
}

#Preview {
    ContentView()
}

 

extension을 이용하면, 기존 struct에 바로 붙이기가 가능!

더욱 깔끔해 졌는데 더 머리가 아파짐 ㅡㅜ. 그리고 이건 여러명이 같이 쓸때는 위험쓰한 상황 가능

//
//  ContentView.swift
//  ViewsAndModifiers
//
//  Created by HanTJ on 11/18/24.
//

import SwiftUI

struct ContentView: View {
    @State private var useRedText = false
    
    var body: some View {
        Text("Hello World")
            .titleStyle()
    }
}

extension View {
    func titleStyle() -> some View {
        modifier(Title())
    }
}
struct Title: ViewModifier {  //ViewModifier protocol 을 이용
    func body(content: Content) -> some View {
        content
            .font(.largeTitle)
            .foregroundStyle(.white)
            .padding()
            .background(.blue)
            .clipShape(.rect(cornerRadius: 10))
    }
}

#Preview {
    ContentView()
}

 

추가예제로 어디든 붙는 워터마크!

//
//  ContentView.swift
//  ViewsAndModifiers
//
//  Created by HanTJ on 11/18/24.
//

import SwiftUI

struct ContentView: View {
    @State private var useRedText = false
    
    var body: some View {
        Color.blue
            .frame(width: 300, height: 200)
            .watermarked(with: "Hacking with Swift")
    }
}

struct Watermark: ViewModifier {
    var text: String

    func body(content: Content) -> some View {
        ZStack(alignment: .bottomTrailing) {
            content
            Text(text)
                .font(.caption)
                .foregroundStyle(.white)
                .padding(5)
                .background(.black)
        }
    }
}

extension View {
    func watermarked(with text: String) -> some View {
        modifier(Watermark(text: text))
    }
}

#Preview {
    ContentView()
}

 

그리드를 표기하는 컨테이너도 다음과 같이 구현이 가능하다!

지금까지 배운것들을 응용하여, 뷰를 담는 컨테이너 형태의 그리드이다! 

//
//  ContentView.swift
//  ViewsAndModifiers
//
//  Created by HanTJ on 11/18/24.
//

import SwiftUI

struct ContentView: View {
    @State private var useRedText = false
    
    var body: some View {
        GridStack(rows: 4, columns: 4) { row, col in
            Image(systemName: "\(row * 4 + col).circle")
            Text("R\(row) C\(col)")
        }}
}

struct GridStack<Content: View>: View {
    let rows: Int
    let columns: Int
    @ViewBuilder let content: (Int, Int) -> Content //content안에 여러개의 View를 선언할수 있게 된다!

    var body: some View {
        VStack {
            ForEach(0..<rows, id: \.self) { row in
                HStack {
                    ForEach(0..<columns, id: \.self) { column in
                        content(row, column)
                    }
                }
            }
        }
    }
}

#Preview {
    ContentView()
}

믓지다!

반응형