iOS/UIKIT

서치바? 검색창은 어떻게 사용하는가?

밤새는 탐험가89 2024. 7. 5. 12:14

 

구현 내용

  • Search 탭에 서치바 기능을 추가하고 검색 결과를 테이블 + 컬렉션뷰와 혼합하여 보여준다. 

 

구현 순서 

  • UISearchController 타입의 searchController 라는 변수를 선언한다.
  • searchController와 SearchResultsViewController 사이에는 searchController.searchResultsUpdater = self 가 있다. 
  • 해당 변수에 결과를 받아서 보여주는 SearchResultsViewController를 생성한다. 
  • SearchResultsViewController 내에는 결과 값을 어떤 식으로 보여줄 지 정한다.

구현 코드

  • TMDB 사이트 내 Search -> Multi 부분에 들어가서 API 리퀘스트 함수를 확인한다. 

https://developer.themoviedb.org/reference/search-multi

 

Search - Multi

Use multi search when you want to search for movies, TV shows and people in a single request.

developer.themoviedb.org

 

  • APICaller.swift 내에 아래 함수를 구현한다. 
  • keyWord 라는 파라미터를 통해 얻은 입력값을 쿼리에 넣어 확인한다. 
func getDiscoverMulti(with keyWord: String, completion: @escaping (Result<[Title], Error>) -> Void) {
        guard let keyWord = keyWord.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) else { return }
        let url = URL(string: "https://api.themoviedb.org/3/search/multi")!
        var components = URLComponents(url: url, resolvingAgainstBaseURL: true)!
        let queryItems: [URLQueryItem] = [
          URLQueryItem(name: "query", value: keyWord),
          URLQueryItem(name: "include_adult", value: "false"),
          URLQueryItem(name: "language", value: "en-US"),
          URLQueryItem(name: "page", value: "1"),
        ]
        components.queryItems = components.queryItems.map { $0 + queryItems } ?? queryItems

        var request = URLRequest(url: components.url!)
        request.httpMethod = "GET"
        request.timeoutInterval = 10
        request.allHTTPHeaderFields = [
          "accept": "application/json",
          "Authorization": "Bearer \(Constants.API_KEY)"
        ]
        
        let task = URLSession.shared.dataTask(with: request) { data, response, error in
            if let error = error {
                print("Error occurred: \(error.localizedDescription)")
                completion(.failure(APIError.failedToGetData))
                return
            }
            guard let data = data else {
                print("No data received")
                completion(.failure(APIError.failedToGetData))
                return
            }
            do {
                // Print JSON data to debug
//                if let json = try? JSONSerialization.jsonObject(with: data, options: []) {
//                    print("JSON Response: \(json)")
//                }
                let results = try JSONDecoder().decode(TrendingTitleResponse.self, from: data)
                completion(.success(results.results))
            } catch {
                print("JSON Decoding error: \(error.localizedDescription)")
                completion(.failure(APIError.failedToGetData))
            }
        }
        task.resume()
    }

 

 

  • SearchViewController.swift 내에 아래 코드를 구현한다.
import UIKit

class SearchViewController: UIViewController {
     
    // MARK: UI Components
    private let searchController: UISearchController = {
        let controller = UISearchController(searchResultsController: SearchResultsViewController())
        controller.searchBar.placeholder = "Search for a Movie or Tv or Person"
        controller.searchBar.searchBarStyle = .minimal
        return controller
    }()

    // MARK: Life Cycle
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .systemBackground
        title = "Searh"
        navigationController?.navigationBar.prefersLargeTitles = true
        navigationController?.navigationItem.largeTitleDisplayMode = .always
        
        navigationItem.searchController = searchController
        navigationController?.navigationBar.tintColor = .label
        
        searchControllerDelegate()
    }

    
    // MARK: Functions
    private func searchControllerDelegate() {
        searchController.searchResultsUpdater = self
    }
    
}

// MARK: Extensions
extension SearchViewController: UISearchResultsUpdating {
    func updateSearchResults(for searchController: UISearchController) {
        let searchBar = searchController.searchBar
        
        guard let keyWord = searchBar.text,
              !keyWord.trimmingCharacters(in: .whitespaces).isEmpty,
              keyWord.trimmingCharacters(in: .whitespaces).count >= 2,
              let resultsController = searchController.searchResultsController as? SearchResultsViewController else { return }
        
        APICaller.shared.getDiscoverMulti(with: keyWord) { result in
            DispatchQueue.main.async {
                switch result {
                case .success(let titles):
                    DispatchQueue.main.async {
                        resultsController.titles = titles
                        resultsController.searchResultsCollectionView.reloadData()
                    }
                case .failure(let error):
                    print(error.localizedDescription)
                }
            }
        }
    }
}

 

 

  • UISearchController의 searchResultsUpdater 속성에 현재 뷰 컨트롤러(self)를 할당한 것으로, 이를 통해 UISearchController는 검색 텍스트가 변경될 때마다 현재 뷰 컨트롤러의 updateSearchResults(for:) 메서드를 호출한다.
  • updateSearchResults(for:) 메서드에서는 검색어를 사용하여 데이터를 가져오고, 검색 결과를 업데이트한다.
searchController.searchResultsUpdater = self

 

 

 

  • SearchResultsViewController.swift 파일 내 코드 구현한다. 
import UIKit

class SearchResultsViewController: UIViewController {
    
    // MARK: Variables
    var titles: [Title] = []
    
    // MARK: UI Components
    let searchResultsCollectionView: UICollectionView = {
        let layout = UICollectionViewFlowLayout()
        let width = UIScreen.main.bounds.width / 3 - 2
        layout.itemSize = CGSize(width: width, height: 200)
        layout.minimumInteritemSpacing = 0
        layout.minimumLineSpacing = 2
        let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
        collectionView.register(TitleCollectionViewCell.self, forCellWithReuseIdentifier: TitleCollectionViewCell.identifier)
        return collectionView
    }()

    // MARK: Life Cycle
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .systemBackground
        view.addSubview(searchResultsCollectionView)
        searchResultsCollectionViewDelegate()
    }
    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        searchResultsCollectionView.frame = view.bounds
    }
    
    // MARK: Functions
    private func searchResultsCollectionViewDelegate() {
        searchResultsCollectionView.delegate = self
        searchResultsCollectionView.dataSource = self
    }
}

// MARK: Extensions
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
    }
}