✅ 서치바 설정
- 서치바 설정으로 원하는 데이터 검색
- SearchController 설정
- 네비게이션 타이틀 설정
import UIKit
class SearchViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
title = "Search"
navigationController?.navigationBar.prefersLargeTitles = true
navigationController?.navigationItem.largeTitleDisplayMode = .always
}
}
- 검색 결과를 보여줄 수 있는 테이블 생성
- 이때 셀은 기존에 작성했던 TitleTableViewCell 로 등록
import UIKit
class SearchViewController: UIViewController {
private let discoverTable: UITableView = {
let table = UITableView()
table.register(TitleTableViewCell.self, forCellReuseIdentifier: TitleTableViewCell.identifier)
return table
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
title = "Search"
navigationController?.navigationBar.prefersLargeTitles = true
navigationController?.navigationItem.largeTitleDisplayMode = .always
view.addSubview(discoverTable)
discoverTable.delegate = self
discoverTable.dataSource = self
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
discoverTable.frame = view.bounds
}
}
extension SearchViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: TitleTableViewCell.identifier, for: indexPath) as? TitleTableViewCell else { return UITableViewCell()
}
return cell
}
}
- Search API를 받아 오기
- APICaller 파일 내에 Search 관련 함수 구현
https://developer.themoviedb.org/reference/search-movie
func getDiscoverMovies(completion: @escaping (Result<[Title], Error>) -> Void) {
let headers = [
"accept": "application/json",
"Authorization": "Bearer eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiI2NzgwMjVmOWI4NTM3Mjk5MjI3NDhkMTZmZWI0NDJmOSIsInN1YiI6IjY1ZTUyODRmMjBlNmE1MDE4NjUzYzIwYSIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.Hd-3l416D_iUzXWz37jvK9O4X0PvEBZW4nXdJ159srM"
]
let request = NSMutableURLRequest(url: NSURL(string: "https://api.themoviedb.org/3/discover/movie?include_adult=false&include_video=false&language=en-US&page=1&sort_by=popularity.desc")! as URL,
cachePolicy: .useProtocolCachePolicy,
timeoutInterval: 10.0)
request.httpMethod = "GET"
request.allHTTPHeaderFields = headers
let task = URLSession.shared.dataTask(with: request as URLRequest) { data, _, error in
guard let data = data, error == nil else {
return
}
do {
let results = try JSONDecoder().decode(TrendingTitleResponse.self, from: data)
completion(.success(results.results))
} catch {
completion(.failure(APIError.failedTogetData))
}
}
task.resume()
}
- SearchController 내에 위에 작성한 getDiscoverMovies() 메서드를 호출
import UIKit
class SearchViewController: UIViewController {
private var titles: [Title] = [Title]()
private let discoverTable: UITableView = {
let table = UITableView()
table.register(TitleTableViewCell.self, forCellReuseIdentifier: TitleTableViewCell.identifier)
return table
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
title = "Search"
navigationController?.navigationBar.prefersLargeTitles = true
navigationController?.navigationItem.largeTitleDisplayMode = .always
view.addSubview(discoverTable)
discoverTable.delegate = self
discoverTable.dataSource = self
fetchDiscoverMovies()
}
private func fetchDiscoverMovies() {
APICaller.shared.getDiscoverMovies { [weak self] result in
switch result {
case.success(let titles):
self?.titles = titles
DispatchQueue.main.async {
self?.discoverTable.reloadData()
}
case.failure(let error):
print(error.localizedDescription)
}
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
discoverTable.frame = view.bounds
}
}
extension SearchViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return titles.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: TitleTableViewCell.identifier, for: indexPath) as? TitleTableViewCell else { return UITableViewCell()
}
let title = titles[indexPath.row]
let model = TitleViewModel(titleName: (title.original_name ?? title.original_title) ?? "Unknown name", posterURL: title.poster_path ?? "")
cell.configure(with: model)
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 140
}
}
- SearchResultViewController 파일 생성
import UIKit
class SearchResultsViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemGreen
}
}
- SearchViewController 내에 서치 컨트롤러 삽입
private let searchController: UISearchController = {
let controller = UISearchController(searchResultsController: SearchResultsViewController())
controller.searchBar.placeholder = "Search for a Movie or Tv show"
controller.searchBar.searchBarStyle = .minimal
return controller
}()
- viewDidLoad() 메서드 내에 네비게이션 아이템으로 서치컨트롤러 설정
navigationItem.searchController = searchController
navigationController?.navigationBar.tintColor = .label
- 검색 결과를 보여주는 SearchResultsViewController 내에 collectionView 선언
import UIKit
class SearchResultsViewController: UIViewController {
private var titles: [Title] = [Title]()
private let searchResultsCollectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.itemSize = CGSize(width: UIScreen.main.bounds.width / 3 - 5, height: 200)
layout.minimumInteritemSpacing = 0
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.register(TitleCollectionViewCell.self, forCellWithReuseIdentifier: TitleCollectionViewCell.identifier)
return collectionView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
view.addSubview(searchResultsCollectionView)
searchResultsCollectionView.delegate = self
searchResultsCollectionView.dataSource = self
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
searchResultsCollectionView.frame = view.bounds
}
}
extension SearchResultsViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: TitleCollectionViewCell.identifier, for: indexPath) as? TitleCollectionViewCell else { return UICollectionViewCell()
}
cell.backgroundColor = .blue
return cell
}
}
- APICaller 파일 내에 search 메서드 설정
func search(with query: String, completion: @escaping (Result<[Title],Error>) -> Void) {
// query 설정
guard let query = query.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) else { return }
guard let url = URL(string: "\(Constants.baseURL)/3/search/multi?query=captain?api_key=\(Constants.API_KEY)&include_adult=false&language=en-US&page=1") else { return }
let task = URLSession.shared.dataTask(with: URLRequest(url: url)) { data, _, error in
guard let data = data, error == nil else { return }
do {
let results = try JSONDecoder().decode(TrendingTitleResponse.self, from: data)
completion(.success(results.results))
} catch {
completion(.failure(APIError.failedTogetData))
}
}
task.resume()
}
- searchViewController 클래스에서 searchController의 대리자 선언
searchController.searchResultsUpdater = self
extension SearchViewController: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
let searchBar = searchController.searchBar
guard let query = searchBar.text,
!query.trimmingCharacters(in: .whitespaces).isEmpty,
query.trimmingCharacters(in: .whitespaces).count >= 3,
let resultsController = searchController.searchResultsController as? SearchResultsViewController else { return }
APICaller.shared.search(with: query) { result in
DispatchQueue.main.async {
switch result {
case.success(let titles):
resultsController.titles = titles
resultsController.searchResultsCollectionView.reloadData()
case.failure(let error):
print(error.localizedDescription)
}
}
}
}
}
- 최종 SearchResultsViewController 파일
import UIKit
class SearchResultsViewController: UIViewController {
public var titles: [Title] = [Title]()
public let searchResultsCollectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.itemSize = CGSize(width: UIScreen.main.bounds.width / 3 - 5, height: 200)
layout.minimumInteritemSpacing = 0
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.register(TitleCollectionViewCell.self, forCellWithReuseIdentifier: TitleCollectionViewCell.identifier)
return collectionView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
view.addSubview(searchResultsCollectionView)
searchResultsCollectionView.delegate = self
searchResultsCollectionView.dataSource = self
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
searchResultsCollectionView.frame = view.bounds
}
}
extension SearchResultsViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return titles.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: TitleCollectionViewCell.identifier, for: indexPath) as? TitleCollectionViewCell else { return UICollectionViewCell()
}
let title = titles[indexPath.row]
cell.configure(with: title.poster_path ?? "")
return cell
}
}
- 최종 SearchViewController 파일
import UIKit
class SearchViewController: UIViewController {
private var titles: [Title] = [Title]()
private let discoverTable: UITableView = {
let table = UITableView()
table.register(TitleTableViewCell.self, forCellReuseIdentifier: TitleTableViewCell.identifier)
return table
}()
private let searchController: UISearchController = {
let controller = UISearchController(searchResultsController: SearchResultsViewController())
controller.searchBar.placeholder = "Search for a Movie or Tv show"
controller.searchBar.searchBarStyle = .minimal
return controller
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
title = "Search"
navigationController?.navigationBar.prefersLargeTitles = true
navigationController?.navigationItem.largeTitleDisplayMode = .always
view.addSubview(discoverTable)
discoverTable.delegate = self
discoverTable.dataSource = self
navigationItem.searchController = searchController
navigationController?.navigationBar.tintColor = .label
fetchDiscoverMovies()
searchController.searchResultsUpdater = self
}
private func fetchDiscoverMovies() {
APICaller.shared.getDiscoverMovies { [weak self] result in
switch result {
case.success(let titles):
self?.titles = titles
DispatchQueue.main.async {
self?.discoverTable.reloadData()
}
case.failure(let error):
print(error.localizedDescription)
}
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
discoverTable.frame = view.bounds
}
}
extension SearchViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return titles.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: TitleTableViewCell.identifier, for: indexPath) as? TitleTableViewCell else { return UITableViewCell()
}
let title = titles[indexPath.row]
let model = TitleViewModel(titleName: (title.original_name ?? title.original_title) ?? "Unknown name", posterURL: title.poster_path ?? "")
cell.configure(with: model)
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 140
}
}
extension SearchViewController: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
let searchBar = searchController.searchBar
guard let query = searchBar.text,
!query.trimmingCharacters(in: .whitespaces).isEmpty,
query.trimmingCharacters(in: .whitespaces).count >= 3,
let resultsController = searchController.searchResultsController as? SearchResultsViewController else { return }
APICaller.shared.search(with: query) { result in
DispatchQueue.main.async {
switch result {
case.success(let titles):
// resultsController 내의 titles 와 searchResultsCollectionView 경우에는 public으로 변경한다.
resultsController.titles = titles
resultsController.searchResultsCollectionView.reloadData()
case.failure(let error):
print(error.localizedDescription)
}
}
}
}
}
https://youtu.be/HnWWo0b4v9w?si=Wu_FK0TwCcxAYKX0
'Project > NETFLIX' 카테고리의 다른 글
넷플릭스 앱 클론 11편 (영화 상세페이지) (0) | 2024.05.16 |
---|---|
NETFLIX CLONE 10편 (유튜브 API 갖고오기) (0) | 2024.05.14 |
NETFLIX CLONE 8편 (업커밍 관련 뷰 생성) (0) | 2024.05.09 |
NETFLIX CLONE 7편 (오픈소스 SDWebImage, 컬렉션뷰셀 적용) (0) | 2024.05.08 |
NETFLIX CLONE 6편 (String 기능 확장, 섹션별 함수 생성) (0) | 2024.05.08 |