포스트

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를 만들어 주고 여기에 적용을 할 것이다.

Image

이렇게 디자인을 할 예정이다.

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가 간만에 나왔는데 이것도 이전에 언급한적이 있으니 패스

Image

현재까지 작성된 코드를 기반으로 만들어진 UI

Custom Color 만들기

버튼쪽 초록생이 쨍해서 Custom Color를 만들어본다.

Image

Asset에서 +를 눌러 Colorset을 추가해준다.

Image 그리고 appearances에서 우리는 굳이 light, dark를 나누지 않으므로 none으로 해준다.

panel의 스포이드를 이용해서

Image

이렇게 원하는 사진을 그대로 가져올 수 있다.

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을 적용했다 그이유를 보자.

  1. 둘 다 frame 설정을 하지 않았을 경우 (spacer는 현재 주석으로 disable 해둔 상태) Image
  2. ZStack에만 한 경우 (spacer는 현재 주석으로 disable 해둔 상태) Image
  3. 둘 다 frame 설정을 한 경우 Image

🧱 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 라이센스를 따릅니다.