[iOS] 영화예매앱 만들기[2] - 콜렉션 뷰와 페이지네이션

Kiwi·2024년 5월 1일

iOS

목록 보기
15/15
post-thumbnail

🌳 BodyView - 콜렉션 뷰

bodyView는 resultView에 collectionView를 쌓아서 완성했다!

    let resultView: UIView = UIView()
    
    private let collectionViewFlowLayout: UICollectionViewFlowLayout = {
        let layout = UICollectionViewFlowLayout()
        layout.scrollDirection = .vertical
        return layout
    }()
    
    lazy var cardContent: UICollectionView = {
        let collectionView = UICollectionView(frame: .zero, collectionViewLayout: collectionViewFlowLayout)
        collectionView.translatesAutoresizingMaskIntoConstraints = false
        collectionView.delegate = self
        collectionView.dataSource = self
        collectionView.register(SearchViewCell.self, forCellWithReuseIdentifier: "SearchViewCell")
        collectionView.backgroundColor = .clear
        return collectionView
    }()

UICollectionFlowLayout은 UICollectionView의 레이아웃을 관리하는 클래스로, 셀의 배치, 셀의 크기 및 간격 조정, 헤더 및 푸터 지정, 스크롤 방향 조절, 부분적인 레이아웃 업데이트를 지원한다.



💢 셀의 이미지뷰에 cornerRadius랑 shadow 동시에 적용하기

셀의 이미지뷰와 cornerRadius랑 shadow 효과를 동시에 적용하니까 안되었다!!😫😫😫

    lazy var movieImage: UIImageView = {
        let imageView = UIImageView()
        imageView.contentMode = .scaleAspectFill
        imageView.layer.cornerRadius = 10
        imageView.clipsToBounds = true
        imageView.layer.shadowColor = UIColor.black.cgColor
        imageView.layer.shadowOpacity = 0.6
        imageView.layer.shadowOffset = CGSize(width: 0, height: 0)
        imageView.layer.shadowRadius = 5
        imageView.layer.masksToBounds = false
        imageView.layer.shouldRasterize = false
        return imageView
    }()

위의 코드를 적용해서 실행하면 요렇게 나온다. cornerRadius가 적용 안된걸 확인할 수 있다.그리고 imageView의 사방에 그림자를 적용했는데 아래에만 적용된걸 볼 수 있다.

hierachy구조를 보면

옆에 인스펙터 창을 보면 clipsToBounds를 분명 true로 설정했는데 off 로 되어있다!!!

👩‍🏫 clipsToBound && masksToBound (feat.CALayer)

clipsToBounds 는

  • UIView의 프로퍼티
  • 디폴트값은 false
  • true : superView 이외 영역의 Sub View는 Draw 하지 않는다
  • false : superView 이외 영역의 Sub View도 Draw 한다

masksToBound 는

  • CALayer의 프로퍼티
  • 디폴트값은 false
  • true : superLayer 이외 영역의 subLayer는 Draw 하지 않는다
  • false : superLayer 이외 영역의 subLayer도 Draw 한다

참고로 CALayer란
CALayer : 그림자, 그라데이션, 이미지, 텍스트 등 다양한 그래픽 콘텐츠를 표시하는 layer이다! (개발자소돌이님이 정말 잘 정리해주셨다!!)

즉, 두 메서드 모두 같은 속성을 건드리고 있는 것이다! 그래서 이에 대한 해결방안은? 이미지뷰 뒤에 같은 크기의 UIView를 두어 그 뷰에 그림자를 적용하는 것!(튜터님이 알려주셨다)

⛔️ 문제 : 이미지뷰에 cornerRadius와 shadow를 둘다 적용하려니 clipsToBound 속성이 충돌한다!!
👩‍🏫 해결방법 : 뷰를 분리해서 하나의 뷰에는 cornerRadius를 다른 하나에 shadow를 적용해주자!

참고한 블로그
참고한 블로그



💢 셀의 특정 indexPath만 reloadData 하기

api에서 page단위로 영화 정보를 받아오기 때문에 나는 페이지를 내리다가 마지막 셀을 willDisplay할때 loadSearchMore()를 실행해서 데이터를 받아와서 기존 데이터인 movieResult배열에 추가해주는 방식으로 코드를 작성했다.

    func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
        if indexPath.row == movieResult.count - 1{
            if searchWord == "" {
                loadTrendingMore()
            } else {
                loadSearchMore()
            }
        }
    }

여기서 loadTrendingMore()랑 loadSearchMore()은 불러오는 api 차이이고 구조는 같다. loadSearchMore()에서 특정 indexPath만 로드하기 위해 self.cardContent.reloadData() 대신 self.cardContent.reconfigureItems(at: newIndexPaths)를 사용했다.

    func loadSearchMore() {
        if page <= searchResult.totalPages! {
            page += 1
            networkManager.fetchSearchResult(page: page, searchKeyword: searchWord) {  [weak self] result in
                guard let self = self else { return }
                switch result {
                case .success(let response):
                    let startIndex = self.movieResult.count - 1 // 새로운 아이템의 시작 인덱스
                    let endIndex =  self.movieResult.count + response.results.count // 새로운 아이템의 끝 인덱스
                    self.movieResult += response.results
                    var newIndexPaths: [IndexPath] = []
                    for index in startIndex..<endIndex {
                        let indexPath = IndexPath(row: index, section: 0)
                        newIndexPaths.append(indexPath)
                    }
                    DispatchQueue.main.async {
                        //self.cardContent.reloadData()
                        self.cardContent.reconfigureItems(at: newIndexPaths)
                    }
                case .failure(let error):
                    self.printError(error)
                }
            }
        }
    }



🌳 BodyView - UIView as Image

사진을 api에서 로드해오는 과정에서 이미지가 로딩중일때 로딩이미지를, 이미지가 없을때는 로고 이미지를 표시하기로 했다. 아래와 같이 로딩으로 등록된 에셋은 로딩 아이콘이다.

로고 이미지는 로코 아이콘이 아니라 흰바탕에 로고가 있는 로고 이미지이다.

로딩과 같은 경우 그대로 이미지뷰에 넣어주면 이상한 모양이 나온다!

로딩될때 살짝 확인할 수 있는데 안예쁘다!! 그래서 생각한 방법은 일단 로고를 정중앙에 배치한 뷰를 이미지화 해서 로딩이미지로 사용하는 것이다! 그래서 열심히 인터넷 검색을 통해 다음과 같은 extension을 찾았다.

extension UIView {
  func asImage() -> UIImage {
        let renderer = UIGraphicsImageRenderer(bounds: bounds)
        return renderer.image { rendererContext in
            layer.render(in: rendererContext.cgContext)
        }
    }
}

이 asImage 메서드를 보자. UIGraphicsImageRenderer는 이미지를 렌더링 하기 위한 클래스로 UIView의 크기에 해당하는 렌더러를 생성한다. 그리고 리턴값에 있는 클로저는 UIView의 현재 내용을 이미지로 만든다고 생각하면 된다.

로딩 이미지를 표시하는 방법은 텍스트의 플레이스 홀더를 지정하듯이 네트워크 매니저를 통해 이미지를 받기전에 미리 로딩이미지를 넣어주는 방식으로 진행하였다.

profile
🐣 iOS Developer

0개의 댓글