개발을 시작하는 이야기

[SwiftUI] Lecture 2: Learning more about SwiftUI 본문

개발 이야기/Swift

[SwiftUI] Lecture 2: Learning more about SwiftUI

Teiresias 2022. 4. 10. 18:45

강의 보기 : YouTube :: Stanford

struct ContentView: View {
    var body: some View {
        HStack {
        	ZStack {
                RoundedRectangle(cornerRadius: 20.0)
                    .stroke(lineWidth: 3)
                Text("Hello, CS193p!")
            }
            ZStack {
                RoundedRectangle(cornerRadius: 20.0)
                    .stroke(lineWidth: 3)
                Text("Hello, CS193p!")
            }
            ZStack {
                RoundedRectangle(cornerRadius: 20.0)
                    .stroke(lineWidth: 3)
                Text("Hello, CS193p!")
            }
            ZStack {
                RoundedRectangle(cornerRadius: 20.0)
                    .stroke(lineWidth: 3)
                Text("Hello, CS193p!")
            }
        }
        .padding(.horizontal)
        .foregroundColor(.red)
    }
}

HStackHorizontal Stack으로 수평으로 객체를 묶어주는 역할을 한다. 그럼 수직으로 묶는 건 VStack이 되겠다.

위 코드에서 ZStack이 반복되는 것을 볼 수 있다. 우리의 코드는 반복되는 것을 참을 수 없기 때문에 ZStack을 모듈화 하여 코드를 간결하게 유지하도록 한다.

struct ContentView: View {
    var body: some View {
        HStack {
        	CardView()
        	CardView()
        	CardView()
        	CardView()
        }
        .padding(.horizontal)
        .foregroundColor(.red)
    }
}

struct CardView: View {
    var body: some View {
        ZStack {
            let shape = RoundedRectangle(cornerRadius: 20.0)
            	.stroke(lineWidth: 3)
            Text("Hello, CS193p!")
        }
    }
}

이제는 CardView에서 클릭이벤트를 만들어준다.

struct CardView: View {
    var content: String
    @State private var isFaceUp: Bool = true
    
    var body: some View {
        ZStack {
            let shape = RoundedRectangle(cornerRadius: 20.0)
            if isFaceUp {
                shape.fill().foregroundColor(.white)
                shape.strokeBorder(lineWidth: 3)
                Text(content).font(.largeTitle)
            } else {
                shape.fill()
            }
        }
        .onTapGesture {
            isFaceUp = !isFaceUp
        }
    }
}

Text가 들어갈 곳에는 content 변수를 사용해서 처리를 해주고,

카드의 앞면, 뒷면은 Bool Type을 사용하여 상태를 확인해주는데 여기서 @State 라는것이 등장한다.

@State - 상태프로퍼티

 상태 프로퍼티는 뷰의 상태에 대한 가장 기본적인 형태이다. 상태 프로퍼티의 값이 변경되면 SwiftUI는 뷰를 다시 재 랜더링 하여 보여준다. 그렇다면 왜 상태 프로퍼티를 사용하는 것일까.

어느 두 개의 뷰가 있고 서로 바인딩되어 있다고 해보자. 어느 하나의 뷰에서 특정 프로퍼티의 값이 바뀔 때마다 다른 한 뷰도 실시간으로 업데이트되어야 할 때가 있는데 이때 사용하는 것이다.

 상태 프로퍼티의 선언은 @State 프로퍼티 래퍼를 사용하며, 상태 프로퍼티의 값이 바뀔 때마다 바인딩된 모든 뷰들 역시 재 랜더링 된다. 한편, 상태 값은 해당 뷰에 대한 것이기 때문에 private 프로퍼티로 선언되어야 한다.

 

struct ContentView: View {
    var emojis = ["💡", "📋", "🖥", "😺", "🗺", "😱", "🙈", "🤔", "📪", "👨‍🏫", "📱", "🎉", "📄", "💁", "📞", "👨‍💻", "⚒", "🙋", "🤵‍♂️", "😀", "😃", "😄", "😁"]
    @State private var emojiCount = 20
    
    var body: some View {
        VStack {
            ScrollView {
                LazyVGrid(columns: [GridItem(.adaptive(minimum: 65))]) {
                    ForEach(emojis[0..<emojiCount], id: \.self, content: { emoji in
                        CardView(content: emoji).aspectRatio(2/3, contentMode: .fit)
                    })
                }
            }
            Spacer()
            HStack {
                remove
                Spacer()
                add
            }
            .font(.largeTitle)
            .padding(.horizontal)
        }
        .padding(.horizontal)
        .foregroundColor(/*@START_MENU_TOKEN@*/.red/*@END_MENU_TOKEN@*/)
    }
    
    var remove: some View {
        Button {
            if emojiCount > 1 {
                emojiCount -= 1
            }
        } label: {
            Image(systemName: "minus.circle")
        }
    }
    var add: some View {
        Button {
            if emojiCount < emojis.count {
                emojiCount += 1
            }
        } label: {
            Image(systemName: "plus.circle")
        }
    }
}

ForEach 구문은 이모지를 각각의 뷰에 순서대로 넣어주게 된다. Swift의 forEach() 방식과 비슷하지만 눈에 띄는 차이점은  id: \. self라고 할 수 있다. 배열의 각 요소를 고유하게 식별할 수 있도록 해주는 요소이다. Array 내부 요소가 가지고 있는 값 자체를 id 값으로 설정을 해준다. 위 경우에는 💡는 id 값으로 💡를,  📋는 id 값으로 📋를 갖는다고 볼 수 있다. 그래서 만일 Array에서 값이 중복이 된다면 id값이 동일하게 설정되어 같은 값으로 인식될 수 있으니 주의해야 한다.

 

LazyVGrid를 살펴보자

Lazy LazyVStack , LazyHStack , LazyVGrid , LazyHGrid와 같이 Lazy가 붙은 뷰 빌더들을 볼 수 있는데, Swift에서 Lazy와 함께 선언된 프로퍼티는 처음 호출될 때 값을 계산하게 된다. 그래서 필요하지 않은 시간 동안은 값을 생성하지 않기 때문에 데이터를 효율적으로 사용할 수 있다.

V는 Vertical 

Grid는 List와 함께 비교하여 많이 설명되는데, List는 Vertical Direction으로, TableView와 흡사하다. Grid는 Horizontal Direction으로, CollectionView와 흡사하다. Instagram / Pinterest / Netflix의 레이아웃을 떠올리면 된다.

그럼 LazyVGrid는 호출되기 전엔 값을 생성하지 않는 세로 형태의 아이템 레이아웃이라고 할 수 있다.

 

Spacer()는 객체와 객체 사이의 공간을 만들어주는 역할을 하는데 객체의 크기를 해치지 않는 선에서 최대의 공간을 갖게 된다.


struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
            .preferredColorScheme(.light)
.previewInterfaceOrientation(.portrait)
        ContentView()
            .preferredColorScheme(.dark)
    }
}

Priview의 ContentView를 두 개를 만들어 light모드와 Dark 모드를 모두 볼 수 있게 해 준다

 

 

참조 : LazyVGrid, ForEach, Spacer

'개발 이야기 > Swift' 카테고리의 다른 글

[SwiftUI] Lecture 4: Memorize Game Logic  (0) 2022.04.14
[SwiftUI] Lecture 3: MVVM and the Swift type system  (0) 2022.04.12
[SwiftUI] Lecture 1: Getting started with SwiftUI  (0) 2022.04.08
UIKit  (0) 2022.04.05
Foundation  (0) 2022.04.04