BB Quotes (2)
실제 값을 가져오는 함수 만들기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class ViewModel {
// 생략
func getData(for show: String) async {
status = .fetching
do {
quote = try await fetcher.fetchQuote(from: show)
character = try await fetcher.fetchCharacter(quote.character)
character.death = try await fetcher.fetchDeath(for: character.name)
status = .success
} catch {
status = .failed(error: error)
}
}
}
간단하다.
이전에 만들었던 코드를 기반으로 그냥 만들어 주기만 하면 된다.
View에 적용하기
이제 fetch 준비는 모두 끝났으니 contentview에 적용을 하여 데이터를 화면에 띄워보자.
이것도 크게 뭐 언급할것은 없어 보이긴 한다.
QuoteView를 만들어 주고 여기에 적용을 할 것이다.
이렇게 디자인을 할 예정이다.
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
struct QuoteView: View {
let vm = ViewModel()
let show: String
var body: some View {
GeometryReader { geo in
ZStack {
Image(show.lowercased().replacingOccurrences(of: " ", with: ""))
.resizable()
.frame(width: geo.size.width * 2.7, height: geo.size.height * 1.2)
VStack {
Text("\"\(vm.quote.quote)\"")
.multilineTextAlignment(.center)
.foregroundStyle(.white)
.padding()
.background(.black.opacity(0.5))
.clipShape(.rect(cornerRadius: 25))
.padding(.horizontal)
ZStack(alignment: .bottom) {
AsyncImage(url: vm.character.images[0]) { image in
image
.resizable()
.scaledToFill()
} placeholder: {
ProgressView()
}
.frame(width: geo.size.width / 1.1, height: geo.size.height / 1.8)
Text(vm.quote.character)
.foregroundStyle(.white)
.padding(10)
.frame(maxWidth: .infinity)
.background(.ultraThinMaterial)
}
.frame(width: geo.size.width / 1.1, height: geo.size.height / 1.8)
.clipShape(.rect(cornerRadius: 50))
Button("Get Random Quote") {
}
.font(.title)
.foregroundStyle(.white)
.padding()
.background(.green)
}
.frame(width: geo.size.width)
}
.frame(width: geo.size.width, height: geo.size.height)
}
.ignoresSafeArea()
}
}
뭐 딱히 코드에 대해 언급할건 없어보인다.
Asyncimage가 간만에 나왔는데 이것도 이전에 언급한적이 있으니 패스
현재까지 작성된 코드를 기반으로 만들어진 UI
Custom Color 만들기
버튼쪽 초록생이 쨍해서 Custom Color를 만들어본다.
Asset에서 +를 눌러 Colorset을 추가해준다.
그리고 appearances에서 우리는 굳이 light, dark를 나누지 않으므로 none으로 해준다.
panel의 스포이드를 이용해서
이렇게 원하는 사진을 그대로 가져올 수 있다.
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
32
33
34
35
36
37
38
39
40
struct QuoteView: View {
let vm = ViewModel()
let show: String
var body: some View {
GeometryReader { geo in
ZStack {
// 생략
VStack {
Spacer()
// 생략
ZStack(alignment: .bottom) {
// 생략
}
.frame(width: geo.size.width / 1.1, height: geo.size.height / 1.8)
.clipShape(.rect(cornerRadius: 50))
Spacer()
Button {
} label: {
Text("Get Random Quote")
.font(.title)
.foregroundStyle(.white)
.padding()
.background(.breakingBadGreen)
.clipShape(.rect(cornerRadius: 7))
.shadow(color: .breakingBadYellow, radius: 2)
}
Spacer(minLength: 95)
}
.frame(width: geo.size.width, height: geo.size.height)
}
.frame(width: geo.size.width, height: geo.size.height)
}
.ignoresSafeArea()
}
}
여기서 보면 Z,VStack에 모두 똑같은 frame을 적용했다 그이유를 보자.
- 둘 다 frame 설정을 하지 않았을 경우 (spacer는 현재 주석으로 disable 해둔 상태)
- ZStack에만 한 경우 (spacer는 현재 주석으로 disable 해둔 상태)
- 둘 다 frame 설정을 한 경우
🧱 GeometryReader + ZStack + VStack의 frame 처리 요약
1️⃣ ZStack에 .frame(width: geo.size.width, height: geo.size.height)
를 준 이유
GeometryReader
를 쓰면 기준 좌표가 좌측 상단으로 이동하여 화면 구성 중심이 어긋남- 이를 보정하기 위해
ZStack
에 frame을 지정하여 전체 뷰 중앙을 기준으로 다시 잡아줌
2️⃣ ZStack 내부 Image
가 .frame(width: geo.size.width * 2.7, height: geo.size.height * 1.2)
로 크게 지정된 이유
- 배경 이미지를 화면보다 크게 확대하여 약간의 스크롤 여유 공간과 시각적 효과를 주기 위함
- 문제는 이렇게 확대된 이미지가 부모 뷰(ZStack)의 frame을 덮어씌움
- → SwiftUI는 자식 뷰가 부모보다 크면, 부모의 실제 렌더링 영역도 그 크기를 따라감
3️⃣ VStack에 .frame(width: geo.size.width, height: geo.size.height)
를 따로 준 이유
- 이미지로 인해 ZStack의 크기가 커졌고,
VStack
은 자식 뷰 크기를 따라 자동 확장 - → 버튼이나 텍스트 등이 화면 밖으로 밀려남
- 그래서 VStack에도 고정 frame을 줘서 콘텐츠 영역을 화면 크기로 제한
✅ 최종 정리
뷰 | 역할과 이유 |
---|---|
ZStack.frame(...) | GeometryReader 기준을 중앙으로 고정하기 위해 |
Image.frame(... *2.7, *1.2) | 시각적으로 넓은 배경 제공 (하지만 뷰 크기 확장됨) |
VStack.frame(...) | 이미지에 의해 늘어난 크기를 무시하고, VStack의 콘텐츠를 화면 안에 고정하기 위해 필요 |
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.