일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 스타트업주니어로살아남기
- SWIFT
- 독서후기
- contentInset
- 스위프트
- 부트캠프
- roundingMode
- xcode
- Mock
- Failed to register bundle identifier
- mvvm
- 독후감
- Info.plist
- View Life Cycle
- IOS
- delegation
- NumberFormatter
- Modality
- @available
- 아이폰
- Codegen
- 책후기
- Navigation
- Structures and Classes
- viewcontroller
- human interface guidelines
- SWIFTUI
- 야곰아카데미
- NotificationCenter
- UIResponder
- Today
- Total
호댕의 iOS 개발
[SwiftUI] 2. SwiftUI에 대해 더 알아보기 (Stanford CS193P) - LazyVGrid, CustomView, ScrollView 등등 본문
[SwiftUI] 2. SwiftUI에 대해 더 알아보기 (Stanford CS193P) - LazyVGrid, CustomView, ScrollView 등등
호르댕댕댕 2023. 1. 17. 00:02
저번에 들은 강의에 이어 다음 강의를 듣고 내용을 정리해보고자 한다.
저번에는 단순히 카드 뷰를 옆으로 나열하는 뷰를 그렸다면 이번에는 좀 더 복잡한 뷰를 그렸다.
1. 특정 조건의 Preview 추가하기
일단 처음에는 가볍게 저번에 거의 사용하지 않는다고 했던 PreviewProvider를 만져보자.
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.preferredColorScheme(.dark) // View Modifier
ContentView()
.preferredColorScheme(.light) // View Modifier
}
}
이렇게 하면 ContentView에 대한 프리뷰를 2개를 띄울 수 있고 각각 다크모드, 라이트 모드로 띄워줄 수 있다.
빠르게 조건에 맞는 프리뷰를 확인할 수 있는 것이다.
그런데 UIKit을 공부할 때도 처음에는 가시적으로 보이는 스토리보드를 사용했지만 가면 갈수록 코드로만 UI를 짜는 것이 익숙해졌기 때문에 프리뷰도 점점 사용을 안할 것 같긴 하다.
(물론 스토리보드와 프리뷰는 아예 사용 목적이 다르긴 하지만 말이다)
2. 커스텀 뷰 만들기
UIKit에서도 UIView를 상속받아서 커스텀 뷰를 따로 만들 수 있는 것처럼 SwiftUI에서도 내부에 들어갈 뷰를 따로 빼서 선언할 수 있다.
하긴 하나의 객체에 모든 뷰를 몰빵해놓으면 너무 방대해져서 가독성도 떨어지고 하니 당연히 분리가 가능할 것 같았다.
지금은 간단한 뷰이긴 하지만 단일책임의 원칙도 있고 하니...
만드는 방법은 정말 간단하다. 가장 상단의 뷰가 View 프로토콜을 채택하고 body 프로퍼티를 구현해줬던 것과 동일하게 만들어주면 된다.
이 카드 하나하나를 커스텀 뷰로 빼놓는다고 생각해보자.
struct CardView: View {
let image: String
@State private var isFaceUp: Bool = false
var body: some View {
ZStack(alignment: .center) {
let roundedRectangle = RoundedRectangle(cornerRadius: 25)
if isFaceUp {
roundedRectangle
.fill()
.foregroundColor(.white)
roundedRectangle
.strokeBorder(lineWidth: 3)
Text("\(image)")
.font(.largeTitle)
.foregroundColor(.brown)
} else {
roundedRectangle
.fill()
}
}
.onTapGesture {
isFaceUp.toggle()
}
}
}
그리고 ZStack 같은 View Builder 내부에선 함수처럼 지역 변수를 선언해서 사용할 수도 있다.
위 예시에선 반복적으로 사용하고 있는 RoundedRectangle(cornerRadius: 25)를 따로 변수로 선언해서 사용해줬다.
그리고 UIKit을 쓰던 나에게 또 생소한 것이 등장한다.
이전 블로그 포스팅에서 간단하게 정리하긴 했지만... 안 쓰다보니 또 잊어버린 Property Wrapper이다.
UIKit에선 @objc 이런 것들은 종종 사용했지만 @State는 한 번도 사용한 적이 없었다.
<@State 공식 문서>
SwiftUI에 존재하는 프로퍼티 래퍼로 이렇게 선언한 프로퍼티에 의존하고 있는 뷰는 값이 변경됨에 따라 뷰를 다시 그리게 된다.
State 인스턴스의 경우 값 자체는 아니며 단순히 값을 읽고 쓰는 하나의 수단이 된다.
다만 State로 선언된 값은 자식 뷰에선 따로 수정이 불가능하며 만약 수정을 위해선 @Binding을 쓰라고 안내하고 있다.
위 예제에선 CardView라는 곳에서 직접 값을 수정하고 있기 때문에 @State를 문제없이 사용 가능했다.
3. 조금 더 다양한 뷰 그려보기
a) ForEach 사용하기
ForEach 이거 배열 전체를 쭉 도는 반복문 아니야라고 생각할 수 있다.
결론은 맞다.
[배열 elemnt].forEach { } 이런 식으로 사용했던 문법과는 조금 달랐다.
개념 상으론 반복문을 쭉 돌긴 하지만 SwiftUI에선 이를 통해 뷰까지 그려주는 구조체가 존재했다.
ForEach(emojis[0..<emojiCount], id: \.self) { emoji in
CardView(image: emoji)
.aspectRatio(2/3, contentMode: .fit)
}
이렇게 하면 지정된 emojis 만큼 CardView를 그리게 된다.
이때 id를 따로 지정해줘야 한다.
반복문에 들어갈 Data가 Identifiable이 채택되어 있어야 하는 것이다.
여기서 emojis는 단순히 String 배열이기 때문에 따로 Identifiable이 채택되어 있지 않고 따라서 id를 따로 써주지 않는다면
이렇게 컴파일 에러가 발생할 것이다.
왜 Identifiable을 채택하도록 했을까 고민을 했는데 내가 생각한 근거는 아래와 같다.
데이터로 들어가는 값이 동일할 수 있는데 값 자체는 동일하더라도 서로 다른 뷰를 그려야 하기 때문이다. 배열로 데이터를 넣었기 때문에 중복값도 존재할 수 있는데 이들을 구분할 수 있는 값 자체가 필요하기 때문에 id를 요구하게 된다.
그래서 이때는 ForEach를 생성할 때 id를 넣어주면 된다.
여기서 또 나에게 익숙치 않은 문법이 등장한다.
\.self가 바로 그것이다.
처음엔 좀 헷갈렸는데 지금 emojis 배열에 각각 접근하면서 String 데이터에 접근하고 있는데 그 데이터 자체(self)를 id로 넣겠다 이말이다!!
그래서 만약 배열에 중복된 값이 있다면 동일한 id를 갖는 뷰를 그리게 된다.
이때 아예 동일한 뷰로 인식해서 하나의 View를 탭하더라도 동일한 View도 탭이 된 것처럼 동작하게 된다.
b) LazyVGrid
다른 뷰들은 이름이 거의 유사해서 대충 이게 이거구나 보고 짐작할 수 있었는데 LazyVGrid는 조금 생소했다.
여기선 columns에 GridItem 배열을 넣어서 몇 개의 item으로 Grid가 구성되어 있는지 정해줄 수 있다.
여기서 넣은 배열의 count가 UIKit의 numberOfRow 역할인 것이다.
GridItem을 생성할 때 넣는 값에 따라 각 셀의 특성이 바뀐다.
- GridItem(.adaptive(minimum:)) : 최소 너비를 지정한 값으로 가지도록 하며, 가로 모드로 전환이 되어 전체 View의 너비도 늘어난다면 GridItem으로 넣은 개수만큼 생기는 것이 아니라 최소 너비를 준수하면서 최대한 채우는 식으로 구현이 된다.
- GridItem(.fix()) : 넣은 값으로 고정해서 Cell을 그리게 된다.
- GridItem(.flexible()) : 다른 셀들의 크기에 맞춰 유동적으로 Cell이 그려진다.
참 Apple은 네이밍을 기깔나게 잘하는 것 같긴 하다.
이름만 봐도 어떤 기능인지 알기 쉬운 것 같다. (물론 모든 게 그렇진 않지만)
'Software Engineering > iOS' 카테고리의 다른 글
[iOS] 사용자에게 어떻게 리뷰를 받을 수 있을까? (+ SKStoreReviewController) (3) | 2023.04.20 |
---|---|
[Tuist] Tuist를 통해 파일 생성하기 (0) | 2023.01.17 |
[SwiftUI] 1. SwiftUI 시작하기 (Stanford CS193P) - some View (0) | 2023.01.15 |
[iOS] 제너럴하게 사용할 수 있는 Custom PickerView를 구현해보자 (2) | 2023.01.01 |
[배포/인증서] 인증서를 갱신해보자 (Distribution / APNs) (0) | 2022.12.12 |