[ProJect 일지] 영화 예매 앱 만들기 (2)

2024.04.22 - [◽️ Programming/T I L] - [ProJect 일지] 영화 예매 앱 만들기 (1)

ScrollView..

오늘 이 스크롤 뷰 때문에 새벽까지 진짜 오기로 달렸다.. 결국엔 적용시키는데 성공해서 너무 기쁘다.. 이제야 어느정도 스크롤 뷰가 어떻게 적용되는지 확실하게 알게 된 것 같아 늦었지만 뿌듯한 하루인 것 같다. 그래도 하루에 하나는 알고 가는 느낌이라..

 

오늘 참 손이 많이 갔던 스크롤뷰 먼저 적용하는 과정을 보면

먼저 스크롤 뷰를 넣어준 후 그 안에 요소들을 관리하기 수월하도록 UIView를 넣어준다.

그 후 이 뷰를 스크롤 뷰의 Content Layout Guide 에 사진과 같이 맞춰준 후

 

Frame Layout에 가로 길이가 동일하도록 수정한다. 이때 수치가 임의적으로 들어가있을 수 있으므로 0으로 맞춰주는 것이 필수!

그 이후 넣어준 UIView의 높이를 원하는 만큼 잡으면 그 지점의 마지막까지 스크롤이 가능해진다..

스크롤 뷰가 그냥 간단하게 다른 컴포넌트들과 마찬가지로 쉽게 넣어서 사용한다고 생각했던게 오산이었고 이 기회에 스크롤 뷰 사용법을 아주 확실하게 알 수 있었다.

 

 

새로운 API 데이터 호출

이제 스크롤 뷰가 됐으니 밑에 새로운 영화 목록들을 가져올 수 있도록 설정해 두자.

이전에 가져왔던 네트워킹 코드를 그대로 활용해 새로운 API 데이터를 가져오기 수월했다.

func fetchTopRatedMovies(completion: @escaping (Swift.Result<data, error="">) -> Void) {
    guard let movieURL2 = URL(string: "<https://api.themoviedb.org/3/movie/top_rated>") else {
        let error = NSError(domain: "InvalidURL", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid movie URL"])
        completion(.failure(error))
        return
    }
    
    var request = URLRequest(url: movieURL2)
    request.httpMethod = "GET"
    
    
    request.addValue("application/json", forHTTPHeaderField: "Accept")
    request.addValue("Bearer \\(apiKey)", forHTTPHeaderField: "Authorization")
    
    
    var components = URLComponents(url: movieURL2, resolvingAgainstBaseURL: true)!
    components.queryItems = [
        URLQueryItem(name: "language", value: "ko-KR"),
        URLQueryItem(name: "page", value: "1")
    ]
    guard let url = components.url else {
        let error = NSError(domain: "InvalidURL", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid movie URL"])
        completion(.failure(error))
        return
    }
    request.url = url
    
    let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
        if let error = error {
            completion(.failure(error))
            return
        }
        
        guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else {
            let error = NSError(domain: "HTTPError", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid HTTP response"])
            completion(.failure(error))
            return
        }
        
        guard let data = data else {
            let error = NSError(domain: "InvalidData", code: -1, userInfo: [NSLocalizedDescriptionKey: "No data received"])
            completion(.failure(error))
            return
        }
        
        completion(.success(data))
    }
    task.resume()
}
</data,>

이와 같이 NetworkingManager에 새로운 데이터를 받을 수 있도록 연결하고

func fetchSecondaryData() {
    netWorkingManager.fetchTopRatedMovies { [weak self] result in
        switch result {
        case .success(let data):
            do {
                let decoder = JSONDecoder()
                self?.secondaryMovieData = try decoder.decode(Welcome.self, from: data)
                DispatchQueue.main.async {
                    self?.subCollectionView.reloadData()
                }
            } catch {
                print("\\(error)")
            }
        case .failure(let error):
            print("\\(error)")
        }
    }
}

새로운 데이터 전달 메소드를 만들어 설정하면 동일하게 설정이 가능하다.

나는 이번에 컬렉션 뷰를 두개를 활용했기 때문에 새로운 컬렉션 뷰 셀을 만들어 연결해준 후 기존에 사용했던 collectionview datasource와 delegate를 그대로 사용할 수 있도록 if 문을 사용해 넣어주었다.

extension MainViewController: UICollectionViewDelegate, UICollectionViewDataSource {
    // 첫번째 컬렉션 뷰 셀 갯수와 두번째 컬렉션 뷰 셀 갯수 설정
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        if collectionView == mainCollectionView {
            return movieData?.results.count ?? 0
        } else if collectionView == subCollectionView {
            return secondaryMovieData?.results.count ?? 0
        }
        return 0
    }

이렇게 셀의 갯수를 각각 다른 컬렉션 뷰에 적용 될 수 있도록 넣어준 뒤

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        if collectionView == mainCollectionView {
            guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath) as? MainCollectionViewCell else {
                fatalError("Failed to dequeue MainCollectionViewCell")
            }
            
            if let movieData = movieData {
                let movie = movieData.results[indexPath.item]
                cell.collectionMainLabel.text = movie.title
                
                let voteCount = movie.voteCount * 1000
                let formatter = NumberFormatter()
                formatter.numberStyle = .decimal
                formatter.maximumFractionDigits = 0
                if let formattedVoteCount = formatter.string(from: NSNumber(value: voteCount)) {
                    cell.collectionSubLabel.text = "\\(formattedVoteCount)명"
                }
            }
            
            cell.collectionButton.layer.borderWidth = 1
            cell.collectionButton.layer.borderColor = UIColor.systemIndigo.cgColor
            cell.collectionButton.layer.cornerRadius = 15
            
            
            if let movie = movieData?.results[indexPath.item] {
                fetchImage(for: movie.posterPath) { image in
                    DispatchQueue.main.async {
                        cell.collectionMainImage.image = image
                    }
                }
            }
            
            return cell
        } 

첫번째 셀에 원래대로 데이터를 넣어주고

else if collectionView == subCollectionView {
            
            guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "UpRateViewCell", for: indexPath) as? UpRateCollectionViewCell else {
                fatalError("Failed to dequeue SubCollectionViewCell")
            }
            
            if let movieData = secondaryMovieData {
                let movie = movieData.results[indexPath.item]
                cell.upRateCollectionMainLabel.text = movie.title
                
                let voteCount = movie.voteCount * 1000
                let formatter = NumberFormatter()
                formatter.numberStyle = .decimal
                formatter.maximumFractionDigits = 0
                if let formattedVoteCount = formatter.string(from: NSNumber(value: voteCount)) {
                    cell.upRateCollectionSubLabel.text = "\\(formattedVoteCount)명"
                }
            }
            
            cell.upRateButton.layer.borderWidth = 1
            cell.upRateButton.layer.borderColor = UIColor.systemIndigo.cgColor
            cell.upRateButton.layer.cornerRadius = 15
            
            
            if let movie = secondaryMovieData?.results[indexPath.item] {
                fetchImage(for: movie.posterPath) { image in
                    DispatchQueue.main.async {
                        cell.upRateCollectionImage.image = image
                    }
                }
            }
            
            return cell
        }
        return UICollectionViewCell()
    }
    

}

두번째 셀에도 첫번째 셀에 넣었던 데이터와 유사하게 넣어주니 각각 컬렉션 뷰에 해당하는 데이터가 딱 들어갔다.

 

이렇게 두번째 컬렉션 뷰 까지 넣기 성공!

 

관객수 설정하기

그리고 이제 데이터에는 천명 단위로 나와있는 관객 수를 직관적으로 볼 수 있도록 데이터 포맷을 넣어주어 변환하였다.

let voteCount = movie.voteCount * 1000
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.maximumFractionDigits = 0
if let formattedVoteCount = formatter.string(from: NSNumber(value: voteCount)) {
    cell.upRateCollectionSubLabel.text = "\\(formattedVoteCount)명"
}

이렇게 넣어주니 사진과 같이 제대로 잘 출력되는 것을 볼 수 있었다.

블로그에 작성한거 외에 이미지 추출에 성공한거랑 여러가지 등등 디테일하게 한건 있지만 새벽시간까지 스크롤뷰에 시달리느라 얼른 정리하고 자야겠다..

 

오늘은 여기까지.. 이제 메인의 디테일적인 부분을 좀 더 살려 완성도를 높히고 마무리한 뒤 얼른 로그인 구현해야지