안녕하세요. Deah 입니다.
지난 개발에 이어서 인기급상승 탭을 계속 디벨롭 해보겠습니다.
인기급상승 탭에서 콘텐츠 셀을 눌렀을 때 나타날 상세 화면을 구현해볼 예정이에요.
시작해보겠습니다.🤓
✨ 작업일시: 2024-06-11 (Tue)
인기급상승 탭에서 각 콘텐츠 셀을 누르면 해당하는 콘텐츠의 상세 정보를 띄워주는 화면으로 연결됩니다.
그래서 해당 화면의 레이아웃을 잡아주어야 해요.
(색상이 색동저고리 같네요....)
상단에 대표 이미지를 보여줄 imageView를 얹고, 그 위에 콘텐츠 제목과 포스터를 노출할 자리를 잡았습니다. 그리고 아래에는 tableView를 만들어 2개의 섹션으로 나누어서 0번째 섹션에는 콘텐츠의 줄거리를 AutomaticDimension으로, 1번째 섹션에는 출연하는 배우나 스탭들의 정보를 노출하기 위해 서로 다른 2개의 Cell을 만들어주었습니다.
검색 기능을 만들기 전 지금까지 작업된 화면 구성은 이렇습니다.
- 홈 탭
- 회원가입 (홈에서 오른쪽 상단 barButton 클릭)- 박스오피스 탭
- 인기급상승 탭
- 콘텐츠 상세 정보 (인기급상승 탭에서 해당 Cell 클릭)
하지만 검색 기능을 만드려고 보니, 하단 탭에는 보기 좋은 정도인(?) 3개의 화면이 모두 자리잡고 있었고, 검색 탭을 추가로 만들자니 탭의 간격이 좁아지는게 보기 좋지 않다고 느꼈습니다.
화면 구성에 대한 모든 부분을 기획하고 진행하는 것이 아니라 학습한 내용을 적용하면서 디벨롭 해가는 프로젝트이다보니 화면을 추가할 때마다 어디서 어떻게 보여줘야 할지에 대한 고민을 하게되는 거 같아요.
그래서 당장 자주 확인하지 않는 회원가입 씬을 일단 임시로 제거하고, 인기급상승 탭에서도 당장 필요하지 않지만 화면상 임시로 만들어주었던 메뉴와 검색 barButton을 삭제하고 홈 화면에서 검색 기능을 넣는 방향으로 진행하기로 결정했습니다.
func setBarButtons() {
// 익스텐션으로 만들어놓은 함수 활용
addImgBarBtn(title: nil, image: SystemImage.search!, target: self, action: #selector(searchBtnClicked), type: .right, color: Color.Primary.pink)
}
@objc func searchBtnClicked() {
navigationController?.pushViewController(SearchViewController(), animated: true)
}
기존에 있던 회원가입 BarButtonItem을 없애고 SearchViewController로 넘어갈 수 있도록 검색 버튼을 넣어주었습니다.
처음 검색 기능을 만들기 시작할 때, 검색 버튼을 눌렀을 때 SearchViewController로 이동하고 SearchViewController에서는 NavigationBar에 뒤로가기 버튼과 SearchBar가 함께 있는 구성을 그리고 싶었습니다.
func setBarButtons() {
addImgBarBtn(title: nil, image: SystemImage.back!, target: self, action: #selector(backBarBtnClicked), type: .left, color: Color.Primary.pink)
let searchBar = UISearchBar(frame: CGRect(x: 0, y: 0, width: 300, height: 0))
searchBar.placeholder = "영화, 드라마, 시리즈를 검색해 보세요!"
self.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: searchBar)
}
@objc func backBarBtnClicked() {
navigationController?.popViewController(animated: true)
}
그래서 위와 같은 코드로 searchBar를 만들고 rightBarButtonItem에 넣어주는 방법으로 시도했는데 뒤로가기 버튼과 SearchBar의 간격이 너무 넓었습니다.
SearchBar의 너비값을 수정하자니 고정값으로 넣어버리면 다양한 기종의 대응이 어려울 거 같았고, 모든 기종의 디바이스 너비를 계산해서 SearchBar 너비를 설정해줘야 할까 생각이 들어 시도했는데
func setBarButtons() {
addImgBarBtn(title: nil, image: SystemImage.back!, target: self, action: #selector(backBarBtnClicked), type: .left, color: Color.Primary.pink)
// 디바이스 너비 계산 - 모든 기종 대응
var screenWidth = UIScreen.main.bounds.size.width
let searchBar = UISearchBar(frame: CGRect(x: 0, y: 0, width: screenWidth - 40, height: 0))
searchBar.placeholder = "영화, 드라마, 시리즈를 검색해 보세요!"
self.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: searchBar)
}
@objc func backBarBtnClicked() {
navigationController?.popViewController(animated: true)
}
이 상태에서 시뮬레이터로 iPhone 15, iPhone 15 Pro Max 두 기종을 확인해봤더니 결과는.... 실패!
func configureLayout() {
searchBar.snp.makeConstraints { make in
make.top.equalTo(view.safeAreaLayoutGuide)
make.horizontalEdges.equalTo(view.safeAreaLayoutGuide).inset(8)
make.height.equalTo(44)
}
searchCollectionView.snp.makeConstraints { make in
make.top.equalTo(searchBar.snp.bottom).offset(16)
make.horizontalEdges.bottom.equalTo(view.safeAreaLayoutGuide)
}
}
func configureUI() {
searchBar.searchBarStyle = .minimal
searchBar.placeholder = "영화, 드라마, 시리즈를 검색해 보세요!"
searchCollectionView.backgroundColor = .lightGray
}
NavigationBar에 넣지 않고 레이아웃 잡을 때 그 위치로 조정해보기도 했는데 결과적으로 위치 선정은 가능하지만 SearchBar 클릭이 안 되어 이리저리 시도해보다가 일단 후퇴합니다...
가운데 타이틀을 넣어주고 SearchBar를 아래로 빼니 화면상으로 이상해보이지는 않아서 일단 이렇게 진행하고 추후에 다시 도전해봐야겠습니다.
SearchBar 아래로는 CollectionView와 CollectionView Cell을 만들어서 결과값(콘텐츠 이미지)가 들어갈만한 사이즈로 조절해주었습니다.
(CollectionView는 콘텐츠마다 사이즈를 조절할 수도 있지만 미디어 콘텐츠 검색에서는 굳이 해당 기능이 필요없을 거 같아서 고정된 크기 포스터를 노출하는 방향으로 진행)
검색 기능에서는 TMDb Search API를 사용하는데 API가 영화/드라마/사람 등 세부적으로 나뉘어져 있어서 사용자가 검색할 때에도 어떤 검색을 사용할지를 정하는 카테고리 분리가 필요하다고 생각했습니다.
let categoryControl = UISegmentedControl(items: ["영화", "TV 시리즈", "영화인"])
그래서 SearchViewController 상단에 Segmented Control을 추가하고 viewDidLoad 시점에서 기본값을 0번째 인덱스인 영화로 설정해주었습니다. 그리고 추후 사용자가 선택한 값으로 API를 나눠서 호출 할 수 있도록 연결해볼 예정입니다.
func callSearchRequest(query: String) {
let URL = "\(API.URL.KMDB.Search.tv)\(query)"
let headers: HTTPHeaders = [
"Authorization": API.KEY.kmdb,
"accept": "application/json"
]
AF.request(URL,
headers: headers)
.responseDecodable(of: TVSearch.self) { res in
switch res.result {
case .success(let value):
print("검색 성공")
if self.page == 1 {
self.searchList.results = value.results
} else {
self.searchList.results.append(contentsOf: value.results)
}
self.searchCollectionView.reloadData()
if self.page == 1 {
self.searchCollectionView.scrollsToTop = true
}
case .failure(let error):
print("검색 실패")
print(error)
}
}
}
// TableView DatasourceFetching
extension SearchViewController: UICollectionViewDataSourcePrefetching {
func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) {
print("Prefetch", indexPaths)
for indexPath in indexPaths {
if searchList.results.count - 2 == indexPath.item {
page += 1
callSearchRequest(query: searchBar.text!)
}
}
}
}
// CollectionView
extension SearchViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return searchList.results.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: SearchCollectionViewCell.id, for: indexPath) as! SearchCollectionViewCell
let idx = indexPath.item
cell.configureCellHierarchy()
cell.configureCellLayout()
cell.configureCellUI()
cell.configureCellData(data: searchList.results[idx])
return cell
}
}
대략적인 구조는 이렇고, 페이지가 1일 때는 새로운 검색어를 입력했다는 의미로 검색 결과 배열을 새롭게 할당해주고, 페이지가 1이 아닐 때에는 계속 스크롤하면서 같은 검색어 내에서 추가 데이터를 append 해주어 데이터가 쌓이도록 했습니다.
완성된 화면은 이렇게!
(아직 Segmented Control은 기능이랑 이어놓지 않아서 추후 연결할 계획입니다.)
결과가 여러 개일 때 | 결과가 하나일 때 |
---|---|
![]() | ![]() |
검색해보니 예전부터 생기던 고질적인 오류(?) 같아서 해결하지 못하고 놔두고 있습니다. 거슬리긴 하지만 앱이 터지는 오류는 아니기 때문에 일단 패스! 추후 어떤 오류인지 딥하게 검색해봐야겠네요 👀