코드네임 :

🍎 2주차 실습 본문

👥Club/🍀UMC🍀

🍎 2주차 실습

비엔 Vien 2025. 3. 25. 13:31

않이 너무 어려워요,,,,,,,,,,,,,,,,,,,ㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜ

 

 

import Foundation
import SwiftUI


@Observable
class MovieViewModel {
    
    var currentIndex: Int = 0
    
    let movieModel:[MovieModel] = [
        .init(movieImage: .init(.mickey), movieName: "미키", movieLike: 972, movieReserCount: 30.8),
        .init(movieImage: .init(.toyStory), movieName: "토이스토리", movieLike: 999, movieReserCount: 99.8),
        .init(movieImage: .init(.brutalis), movieName: "브루탈리스트", movieLike: 302, movieReserCount: 24.8),
        .init(movieImage: .init(.snowWhite), movieName: "백설공주", movieLike: 302, movieReserCount: 3.8),
        .init(movieImage: .init(.whiplash), movieName: "위플래시", movieLike: 604, movieReserCount: 62.2),
        .init(movieImage: .init(.conclave), movieName: "콘클라베", movieLike: 392, movieReserCount: 43.9),
        .init(movieImage: .init(.theFall), movieName: "더폴", movieLike: 30, movieReserCount: 2.1)
        ]
    
    
    // 이전 영화로 돌아가기, 단 첫번째 영화일 경우 마지막 영화로 전환
    public func previousMovie() {
        currentIndex = (currentIndex - 1 + movieModel.count) % movieModel.count
    }
    
    // 오른쪽버튼을 눌렀을때 다음 영화로 이동한느 함수
    public func nextMovie() {
        currentIndex = (currentIndex + 1) % movieModel.count
    }
}

 

import Foundation
import SwiftUI

struct MovieModel{
    let movieImage: Image
    let movieName: String /* 영화 이름 */
    let movieLike: Int /* 영화 좋아요 */
    let movieReserCount: Double /* 영화 예매율 */
}

 

 

import SwiftUI

struct MovieCard: View {
    
    let movieInfo: MovieModel //외부에서 MovieModel을 주입받아서 이 View에서 사용하도록 함
                                //init(movieInfo: MovieModel)는 사실 Swift가 자동 생성해주는데, 명시적으로 써서 가독성을 높인 것 같아.
    
    init(movieInfo: MovieModel) {
        self.movieInfo = movieInfo
    }
    
    var body: some View {
        VStack(spacing: 5) { //세로 방향으로 정렬하면서 자식 뷰들 간의 간격을 5포인트로 설정
            movieInfo.movieImage //영화 포스터 이미지를 표시.
            
            Text(movieInfo.movieName)
                .font(.system(size: 20, weight: .bold))
                .foregroundStyle(Color.black)
            
            HStack {
                movieLike
                
                Spacer()//Spacer()를 넣어서 왼쪽에는 좋아요, 오른쪽 끝에는 예매율 텍스트가 가도록 정렬
                
                Text("예매율 \(String(format: "%.1f", movieInfo.movieReserCount))%")
                    .font(.system(size: 9, weight: .regular))
                    .foregroundStyle(Color.black)
            }
        }
        
        /* 상위 뷰의 프레임을 꼭 넣어주세요! 피그마에 보시면 fixed로 고정되어 있는게 보이실겁니다.*/
        /* HStack 내부의 Spacer()로 부모 뷰의 사이즈에 영향을 받게됩니다.*/
        .frame(width: 120, height: 216)
    }
    
    /// 하단 영화 좋아요
    private var movieLike: some View { //좋아요 정보(하트 아이콘 + 숫자) 부분을 별도로 컴포넌트로 뽑은 것!
        HStack(spacing: 6) { //하단에 좋아요 정보와 예매율을 좌우로 배치
            Image(systemName: "heart.fill")
                .foregroundStyle(Color.red)
                .frame(width: 15, height: 14) //하트 아이콘은 빨간색, 크기 고정
            
            Text("\(movieInfo.movieLike)")
                .font(.system(size: 9, weight: .regular))
                .foregroundStyle(Color.black)
        }
    }
}

#Preview {
    MovieCard(movieInfo:  .init(movieImage: .init(.mickey), movieName: "미키", movieLike: 972, movieReserCount: 30.8))
} //프리뷰에서 실제로 “미키” 데이터를 넣어서 카드 UI가 잘 나오는지 확인할 수 있도록 예제 데이터를 미리 넣어둔 거야!

 

 

import SwiftUI
import Observation

struct MovieView: View {
    
    @AppStorage("movieName") private var movieName: String = "" //@AppStorage는 UserDefaults에 값을 자동으로 저장 및 불러오는 속성 래퍼
    //    movieName에 값을 넣으면 자동으로 디바이스에 저장되고, 앱 재실행 시에도 그대로 유지
     
    private var viewModel: MovieViewModel = .init()
    //MovieViewModel 인스턴스를 하나 만들어서 영화 리스트와 현재 인덱스를 관리
    
    
    
    var body: some View {
        VStack(spacing: 56) { //전체를 세로 스택으로 쌓고, 각 컴포넌트 간의 간격을 56pt로 넓게 줬어
            MovieCard(movieInfo: viewModel.movieModel[viewModel.currentIndex]) //viewModel.movieModel 배열에서 **현재 선택된 인덱스(currentIndex)**의 영화 데이터를 넣어주는 거야!
            //    viewModel.currentIndex :  현재 보고 있는 영화가 몇 번째인지 나타내는 인덱스 값 (0, 1, 2, …)
            //viewModel.movieModel[viewModel.currentIndex] : 배열에서 현재 인덱스에 해당하는 MovieModel 하나를 꺼내오는 것!
            
            leftRightChange
            
            settingMovie
            
            printAppStorageValue
        }
        .padding()
    }
    
    /// 왼쪽 오른쪽 change 버튼
    private var leftRightChange: some View {
        HStack {
            Group {
                makeChevron(name: "chevron.left", action: viewModel.previousMovie)
                
                Spacer()
                
                Text("영화 바꾸기")
                    .font(.system(size: 20, weight: .regular))
                
                Spacer()
                
                makeChevron(name: "chevron.right", action: viewModel.nextMovie)
            }
            .foregroundStyle(Color.black)
        }
        .frame(width: 256)
        .padding(.vertical, 17) /*세로로 패딩 17만큼 */
        .padding(.horizontal, 22) /* 가로로 패딩 22만큼씩 */
    }
    
    /// 화살표 재사용하기 위한 하위 뷰
    /// - Parameters:
    ///   - name: 이미지 이름 설정
    ///   - action: 버튼이 가지는 액션 기능 넣기, @escpaing은 추후 문법을 통해 배우게 될 겁니다!
    /// - Returns: some View 타입 반환
    private func makeChevron(name: String, action: @escaping () -> Void) -> some View { //이름만 넣으면 왼쪽/오른쪽 화살표 버튼을 리턴하는 커스텀 함
        Button(action: {
            action()
        }, label: {
            Image(systemName: name)
                .resizable()
                .frame(width: 17.47, height: 29.73)
        })
    }
    
    /// 대표 영화 설정
    private var settingMovie: some View {
        Button(action: {
            /* 현재 인덱스틔 영화 이름 AppStorage에 저장 */
            self.movieName = viewModel.movieModel[viewModel.currentIndex].movieName // 현재 viewModel에서 선택된 영화의 movieName을 가져와서 @AppStorage에 저장된 movieName 변수에 대입
            //viewModel.movieModel : 영화 목록 배열 (MovieModel 배열)
            //viewModel.movieModel[viewModel.currentIndex] : 현재 인덱스에 해당하는 영화 정보
            //viewModel.movieModel[viewModel.currentIndex].movieName : 그 영화의 이름 (예: “토이스토리”)
            //self.movieName :  @AppStorage("movieName")에 연결된 변수로, 사용자의 디바이스에 저장되는 값
            //근데 왜 굳이 self를 붙이는 걸까? =>self.를 붙이면 “나는 이 클래스 안에 있는 movieName 속성을 수정하고 있어!” 라고 명확히 드러내주는 거지. 특히 @AppStorage처럼 속성 래퍼가 걸려 있는 변수는 self.를 붙여서 프로퍼티임을 확실히 보여주면 가독성이 좋음.
        }, label: {
            Text("대표 영화로 설정")
                .font(.system(size: 20, weight: .regular))
                .foregroundStyle(Color.black)
                .padding(.top, 21)
                .padding(.bottom, 20)
                .padding(.leading, 53)
                .padding(.trailing, 52)
                .overlay(content: {
                    RoundedRectangle(cornerRadius: 20)
                        .fill(Color.clear)
                        .stroke(Color.black, style: .init(lineWidth: 1))
                })
        })
    }
    
    /// 하단 AppStorage에 저장된 영화 확인 텍스트
    private var printAppStorageValue: some View {
        VStack(spacing: 17) {
            Text("@AppStorage에 저장된 영화")
                .font(.system(size: 30, weight: .regular))
                .foregroundStyle(Color.black)
            
            Text("현재 저장된 영화 : \(movieName)")
                .font(.system(size: 20, weight: .regular))
                .foregroundStyle(Color.red)
        }
    }
}

#Preview {
    MovieView()
}