[뭅챠! 개발일지] DAY 6 : 추천 콘텐츠, 비슷한 콘텐츠 기능

Deah (김준희)·2024년 6월 24일
1
post-thumbnail

안녕하세요. Deah 입니다.

뭅챠 개발일지는 오랜만이죠? 🥺
뭅챠에 새로운 기능을 넣어보려고 합니다. 바로 추천 콘테츠와 비슷한 콘텐츠 기능이에요!
이번 기능 구현은 한 화면에서 여러 개의 API 통신 진행을 학습하는 목적을 가지고 있습니다.

지난 2주간 타 프로젝트를 진행하느라 뭅챠 개발이 멈춰있었는데
오랜만에 뭅챠 코드를 보려고 하니 설레기도 하고 두렵기도 하네요 허허허.

호옥시나 지난 2주간의 저의 행보(?)가 궁금하신 분들은
미닝아웃 (Meaning-Out) 프로젝트도 한 번 훑어봐주세요!


그럼 오늘의 개발을 시작하도록 하겠습니다. 🚀

작업일시: 2024-06-24 (Mon)

기능 보여줄 위치 정하기

박스 오피스인기급상승

홈 탭

  • 메인 (현재 빈 화면 상태)
  • 검색 (BarButton)

박스오피스 탭

  • 현재 박스오피스 (원하는 일자 검색 가능)

인기급상승 탭

  • 인기급상승 콘텐츠 리스트
  • 인기급상승 콘텐츠 상세 (미완성 상태)

현재 뭅챠 프로젝트의 화면 구성입니다.

추천 콘텐츠와 비슷한 콘텐츠를 언제 어떻게 노출해야할까 고민하다가 제가 평소 OTT 플랫폼을 어떻게 사용 중인지를 생각해보았습니다. 주로 홈 화면에서 저에게 추천해주는 콘텐츠가 궁금하면 클릭해보거나, 보통은 제가 원하는 콘텐츠를 검색해보고 하단에 나와있는 비슷한 콘텐츠 영역에서 추가로 더 검색을 해보는 편이기 때문에 저도 이 플로우를 이용해야겠다고 생각했습니다.


넷플릭스 (비슷한 콘텐츠)티빙 (추천 콘텐츠)

현재 뭅챠는 홈 화면이 비어있는 상태이기 때문에(...) 홈에서 바로 추천 콘텐츠/비슷한 콘텐츠를 보여주는 것보다는 사용자가 어떤 콘텐츠를 검색했을 때, 검색 결과에 이어서 노출해주는 것이 더 맞겠다고 판단되었어요.

그래서 [홈 탭 - 검색] 에서 한 단계 더 들어가 검색 결과를 클릭했을 때 해당 콘텐츠와 비슷한 콘텐츠, 추천 콘텐츠를 노출해주는 방향으로 개발해보려고 합니다.


화면 만들기

한 화면에 추천 콘텐츠/비슷한 콘텐츠를 모두 노출해줄 예정이기 때문에
앞으로 '추천 콘텐츠' 화면으로 통일하겠습니다 :)

추천 콘텐츠 화면 기본 설정

  • RecommendViewController.swift
class RecommendViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        configureHierarchy()
    }
    
    private func configureHierarchy() {
        view.backgroundColor = .systemBackground
    }
    
}
  • SearchViewController.swift
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    let recommendVC = RecommendViewController()
    navigationController?.pushViewController(recommendVC, animated: true)
}

먼저 RecommendViewController 파일을 생성해주고 검색 결과 아이템(CollectionView Cell)을 클릭했을 때 추천 콘텐츠 화면으로 넘어갈 수 있도록 didSelectItemAt에서 화면을 연결해주었습니다.

그리고 이제 추천 콘텐츠 화면의 BarButton 색상을 바꿔주겠습니다.

navigationController?.navigationBar.tintColor = Constants.Color.Primary.pink

그럼 요로코롬 변한답니다!

이전까지는 BarButton을 매번 바꿔서 사용하느라 커스텀 함수를 통해서 UIBarButtonItem.tintColor를 통해 색상을 설정했었는데, 뷰 컨트롤러 내에서 기존 BarButton의 색을 바꾸는 건 처음이라 삽질을 좀 했네요.

// 삽질 내용 ^_ㅠ

navigationItem.leftBarButtonItem?.tintColor = Constants.Color.Primary.pink

self.navigationItem.backBarButtonItem?.tintColor = Constants.Color.Primary.pink

navigationController?.navigationBar.scrollEdgeAppearance?.titleTextAttributes = [.foregroundColor: Constants.Color.Primary.pink]

상단 타이틀 넣기

제일 먼저 상단에 검색한 콘텐츠명을 넣어줄 레이블을 만들어볼게요!
기존의 탭 화면들(홈, 박스오피스, 인기급상승)과 동일감을 주기 위해 비슷하게 디자인했습니다.

let searchTitleLabel = UILabel()    // (검색어)
let searchSubLabel = UILabel()      // 을(를) 검색했어요!

컬렉션 뷰와 셀 만들기

추천 콘텐츠를 띄워줄 화면을 간단히 스케치해보았어요.

넷플릭스와 티빙처럼 화면 상단에는 기존의 검색한 콘텐츠의 내용을 보여주고 싶었는데, 일단 오늘은 추천 콘텐츠로 두 개의 API 통신을 한 화면에서 하는 학습이 우선이기에 컬렉션 뷰를 통해서 콘텐츠들을 먼저 넣어두고 나중에 시간을 내서 상단에 추가적으로 기존 검색 아이템 정보를 넣어보겠습니다.

CollectionView와 포스터가 들어갈 CollectionView Cell을 만들고, CollectionView에 프로토콜과 셀을 연결해주고 확인을 해보겠습니다.

lazy var recommendCollectionView = UICollectionView(
    frame: .zero,
    collectionViewLayout: recommendCollectionViewLayout()
)
	// 생략
    
    return layout
}
recommendCollectionView.delegate = self
recommendCollectionView.dataSource = self     
recommendCollectionView.register(RecommendCollectionViewCell.self, forCellWithReuseIdentifier: RecommendCollectionViewCell.id)
extension RecommendViewController: UICollectionViewDelegate, UICollectionViewDataSource {
    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return 2
    }
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {

        if section == 0 {
            return 1
        } else {
            return 20
        }
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: RecommendCollectionViewCell.id, for: indexPath)
        
        return cell
    }
}

🤯 ??????????????? 머선 일이고... 제가 원한 화면 디자인은 아닙니다...
컬렉션 뷰를 만들고 컬렉션 뷰 내에서 섹션을 나누어 해결하면 될 거라고 생각했는데 아니었나봐요.

그래서 검색을 하다가 컬렉션 뷰 프로토콜에 여러 개의 컬렉션 뷰를 연결할 수 있다는 걸 알았습니다.
(테이블 뷰도 마찬가지!)

참고자료
iOS ) 하나의 델리게이트(Delegate)에 여러개의 요소 연결?

레퍼런스를 토대로 비슷한 콘텐츠를 보여줄 similarCollectionView와 추천 콘텐츠를 보여줄 recommendCollectionView를 분리하여 만들어주고, 각각의 컬렉션 뷰에 delegate, dataSource 프로토콜을 연결, register을 통해 셀도 연결해주었습니다.

// 비슷한 콘텐츠 컬렉션 뷰
similarCollectionView.delegate = self
similarCollectionView.dataSource = self
similarCollectionView.register(RecommendCollectionViewCell.self, forCellWithReuseIdentifier: RecommendCollectionViewCell.id)
        
// 추천 콘텐츠 컬렉션 뷰
recommendCollectionView.delegate = self
recommendCollectionView.dataSource = self
recommendCollectionView.register(RecommendCollectionViewCell.self, forCellWithReuseIdentifier: RecommendCollectionViewCell.id)

(대략 이런 식으로 두 컬렉션 모두 계층 구조, 레이아웃, UI 설정을 마쳐줌!)

대략 비슷해졌습니다. 이제 각각의 컬렉션 뷰마다 타이틀을 넣어봐야 할텐데요!

RecommendViewController에서 UILabel을 통해 서브 타이틀 레이블을 만들어서 다시 레이아웃을 잡아주고, 폰트 사이즈를 설정해주었습니다.


API 연결하기

기존 네트워크 통신을 진행하는 로직은 모두 해당 뷰 컨트롤러 안에서 작성되어 있었습니다.
이 네트워크 로직부터 분리하고 시작하려고 해요.

  • /Managers/NetworkManager.swift
class NetworkManager {
    
    static let shared = NetworkManager()
    
    private init() {}
    
    let headers: HTTPHeaders = [
        "Authorization": API.KEY.kmdb,
        "accept": "application/json"
    ]
    
    
    // 비슷한 콘텐츠 - TV
    func getSimilarTVContents(id: Int,
    						completionHandler: @escaping (TVSimilar) -> Void
    ) {
    let URL = "\(API.URL.KMDB.Similar.movie)\(id)/similar?language=ko"
     
    AF.request(URL, headers: headers)
        .responseDecodable(of: MovieSimilar.self) { res in
            switch res.result {
            case .success(let value):
                completionHandler(value)
            case .failure(let error):
                print("비슷한 콘텐츠 에러", error)
            }
        }
    }
}

네트워크 로직만을 담당할 네트워크 매니저 파일을 생성해주고 초기화 구문에 private 키워드를 통해서 인스턴스를 생성하지 않도록 만들어주었습니다.

  • RecommendViewController.swift
NetworkManager.shared.getSimilarTVContents(id: itemId) { data in
    if data.results.count == 0 {
        self.showAlert("검색 결과가 없어요!", message: "다른 작품을 검색해 보세요.")
        self.navigationController?.popViewController(animated: true)
        return
    } else {
        self.similarList = data.results
        self.similarCollectionView.reloadData()
    }
}
  • cellForItemAt
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: RecommendCollectionViewCell.id, for: indexPath) as! RecommendCollectionViewCell
        
    if collectionView == similarCollectionView {
        let item = similarList[indexPath.item]
        cell.configureCellData(data: item)
        return cell
    } else {
        .
        .
        .
    }
}

추천 콘텐츠 화면에서는 탈출 클로져를 통해 API 응답값을 받아서 similarList에 할당해주고
similarList를 가지고 RecommendCollectionViewCell에 데이터를 넣어주었습니다.


그럼 이렇게 비슷한 콘텐츠들이 보여집니다!
같은 방법으로 추천 콘텐츠까지 진행하면 끝😎


ETC.

이슈와 브랜치 관리 시작

뭅챠 프로젝트에서도 드디어 이슈와 브랜치 관리를 시작했습니다!

iOS 개발 초반에는 사실 이슈, 브랜치 관리까지 챙길 여유가 없었는데요... 미닝아웃 프로젝트를 진행하면서 단순 코드 작성 외적으로 프로젝트 전반에 걸쳐 브랜치 관리, 문서 관리를 같이 진행해야겠다고 생각했어요.

나중에 해야지는 절대 이뤄지지 않는다고 생각하는 편이기 때문에 저는 최대한 동시다발적으로 함께 챙기는 것을 선호하는 편입니다. (Concurrently 하네요ㅎ) 귀차니즘이 많은 INTP은 미래의 저를 못 믿거든요 🤷‍♀️ (라고 하면서 미루는 건 계속 잘 하는 중)

아무튼,
미닝아웃에서는 일단 브랜치 관리를 다시 시작하는 것에 의의를 두었기 때문에 브랜치명 같은 경우 GitHub에서 이슈와 연결하여 만들어주는 대로 하이픈(-)을 통해 브랜치를 만들었었습니다. 아래처럼요!

e.g) 이슈번호-prefix-진행내용

하지만 이렇게 브랜치명을 만들 경우, 폴더링이 되지 않는다는 애로사항이 발생합니다.

물론 GitHub 레포지토리 상에서는 폴더로 구분되지 않는 것처럼 보이지만 IDE나 GUI 툴에서 확인해보면 제가 만든 feat/1-similar-recommendation-view 브랜치가 /feat 폴더에 들어가있는 걸 볼 수 있어요.

프로젝트 규모가 커질수록 해당 브랜치가 어떤 작업을 수행했는지 명확하게 하는 것이 중요하다고 생각하기 때문에 작은 규모의 1인 개발부터 먼저 이런 습관화를 들이고싶어 뭅챠에서는 브랜치명에 폴더링까지 신경써보기로 했습니다.


또러블 슈팅 🧨

비슷한 콘텐츠 연결을 하고나니 갑자기... 영화 검색이 안 됩니다.....
디코딩 문제 같은데, 영화 검색 관련한 데이터 구조체는 건드린 부분이 없는 거 같아서
대체 어디에서 문제가 생긴 건지 도통 모르겠네요. 🥲

다음 포스팅에는 이 부분을 해결해보겠습니다....
그럼 안녕!!!!!!!!!!!!!

profile
기록 중독 개발자의 기록하는 습관

0개의 댓글