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 효과를 동시에 적용하니까 안되었다!!😫😫😫
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 로 되어있다!!!
clipsToBounds 는
masksToBound 는
참고로 CALayer란
CALayer : 그림자, 그라데이션, 이미지, 텍스트 등 다양한 그래픽 콘텐츠를 표시하는 layer이다! (개발자소돌이님이 정말 잘 정리해주셨다!!)
즉, 두 메서드 모두 같은 속성을 건드리고 있는 것이다! 그래서 이에 대한 해결방안은? 이미지뷰 뒤에 같은 크기의 UIView를 두어 그 뷰에 그림자를 적용하는 것!(튜터님이 알려주셨다)
⛔️ 문제 : 이미지뷰에 cornerRadius와 shadow를 둘다 적용하려니 clipsToBound 속성이 충돌한다!!
👩🏫 해결방법 : 뷰를 분리해서 하나의 뷰에는 cornerRadius를 다른 하나에 shadow를 적용해주자!
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)
}
}
}
}
사진을 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의 현재 내용을 이미지로 만든다고 생각하면 된다.

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