여러분, 안녕하세요! 이 기사에서는 iOS 앱에서 UISearchController를 사용하는 방법을 배울 것입니다.
무엇을 만들까요?
TMDB API를 사용하여 영화 정보를 가져오고 사용자의 검색어를 기반으로 UICollectionView를 사용하여 표시하는 영화 검색 애플리케이션을 구축할 것입니다.
프로젝트 설정
Xcode를 열고 비어 있는 새 iOS 앱 프로젝트를 만듭니다. SwiftUI가 아닌 UIKit을 선택해야 합니다.
이 앱에서는 MVC 패턴을 사용하므로 다음 그룹과 Swift 파일을 만들어 프로젝트를 구성합니다.
이제 Xcode 프로젝트를 닫습니다. 터미널을 열고 프로젝트 디렉토리로 이동하십시오. 여기에 영화 포스터 이미지를 비동기적으로 다운로드하고 캐시하기 위해 SD WebImage Cocoa Pods를 추가해야 합니다.
터미널에 다음 명령을 입력하십시오.
pod init
이제 디렉토리의 내용을 나열하면 새 Podfile이 있는 것을 볼 수 있습니다. 텍스트 편집기를 사용하여 파일을 엽니다(여기서는 Vim을 사용했습니다). 아래 이미지와 유사하게 보이도록 Podfile을 편집합니다. Podfile을 저장하고 닫습니다.
이제 SD WebImage를 지정했으므로 아래 명령을 실행하여 종속성을 설치할 수 있습니다.
pod install
보시다시피 iOS 프로젝트에 SD WebImage 포드를 성공적으로 추가했습니다. 이제 아래 명령을 실행하여 Xcode에서 프로젝트를 엽니다.
open PROJECT_NAME.xcworkspace
Xcode를 연 후 Command+B를 눌러 프로젝트를 빌드해야 합니다.
UIKit 및 프로그래밍 방식 UI를 사용하여 사용자 인터페이스를 디자인하는 방법
우리 앱은 검색 표시줄을 유지하기 위한 3개의 UIElement 탐색 표시줄, 실제 검색을 위한 UISearchBarController, 검색 결과를 표시하기 위한 UICollectionView가 필요합니다.
Scenedelegate.swift 파일을 열고 세션 메소드에 연결할 다음 코드를 그 안에 추가하십시오:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let scene = (scene as? UIWindowScene) else { return }
window = UIWindow(windowScene: scene) window?.rootViewController=UINavigationController(rootViewController:HomeVC())
window?.makeKeyAndVisible()
}
프로그래밍 방식의 UI를 사용하고 있기 때문에 먼저 루트 뷰 컨트롤러, 즉 사용자가 앱을 시작할 때 표시되는 첫 번째 화면을 언급해야 합니다.
여기 이 앱에서는 하나의 View Controller만 사용하므로 UINavigationController 안에 래핑합니다. 이것은 UISearchController를 배치할 수 있는 탐색 모음을 제공합니다.
HomeVC.swift 파일을 열고 다음 속성을 추가하십시오:
private var SearchBar: UISearchController = {
let sb = UISearchController()
sb.searchBar.placeholder = "Enter the movie name"
sb.searchBar.searchBarStyle = .minimal
return sb
}()
private var MovieCollectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
layout.itemSize = CGSize(width: UIScreen.main.bounds.width/3 - 10, height: 200)
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.register(MovieCell.self, forCellWithReuseIdentifier: MovieCell.ID)
return cv
}()
먼저 UISearchController를 만들고 자리 표시자 텍스트 및 스타일과 같은 속성을 구성합니다.
그런 다음 UICollectionView를 만들고 컬렉션 보기에서 사용해야 하는 레이아웃 유형을 지정합니다. 이 경우 UICollectionViewFlowLayout 및 스크롤 방향, 항목 크기 및 나중에 프로젝트에서 만들 사용자 지정 CollectionView 셀 클래스 지정과 같은 기타 속성입니다.
HomeVC 클래스 내에서 새 함수를 만들고 다음 코드를 추가하여 UICollectionView에 대해 프로그래밍 방식으로 자동 레이아웃 제약을 구성합니다.
//MARK: - HELPERS
func configureUI(){
MovieCollectionView.translatesAutoresizingMaskIntoConstraints = false
MovieCollectionView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
MovieCollectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
MovieCollectionView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
MovieCollectionView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
}
먼저 자동 크기 조정 마스크를 제약 조건으로 변환할 필요가 없다고 말합니다. 그런 다음 보기 컨트롤러의 네 면 모두에 컬렉션 보기를 고정합니다.
viewDidLoad()
내부 메소드에 다음 코드 라인을 추가하십시오:
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "Movie Search"
view.backgroundColor = .systemBackground
SearchBar.searchResultsUpdater = self
navigationItem.searchController = SearchBar
view.addSubview(MovieCollectionView)
MovieCollectionView.delegate = self
MovieCollectionView.dataSource = self
configureUI()
}
여기에서 먼저 ViewController의 제목을 지정하고 그 뒤에 systemBackground 색상인 배경색을 지정합니다. 장치가 조명 모드에 있으면 흰색 배경이 표시됩니다. 어두운 모드인 경우 어두운 배경을 표시합니다.
그런 다음 현재 보기 컨트롤러를 검색 결과 업데이터로 설정하고 탐색 모음에 SearchController를 추가하고 ViewController에 UICollectionView를 추가하고 대리자와 데이터 소스를 설정합니다. 마지막으로 자동 레이아웃을 사용하여 UICollectionView를 고정합니다.
HomeVC용 확장 프로그램을 만들고 UISearchResultsUpdating 프로토콜과 해당 스텁 메서드 updateSearchResults를 구현합니다.
extension HomeVC: UISearchResultsUpdating{
func updateSearchResults(for searchController: UISearchController) {
guard let query = searchController.searchBar.text else{return}
}
}
}
updateSearchResults()
메소드는 검색창에 입력된 텍스트가 변경되거나 사용자가 키보드의 검색 버튼을 탭할 때마다 호출됩니다.
다음으로 사용자 지정 UICollectionView 셀을 만들어야 합니다. MovieCell.swift 파일 안에 다음 코드를 추가하세요:
import Foundation
import UIKit
import SDWebImage
class MovieCell: UICollectionViewCell{
static let ID = "MovieCell"
private var MoviePosterImageView: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFit
// imageView.image = UIImage(systemName: "house")
return imageView
}()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(MoviePosterImageView)
configureUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension MovieCell{
func configureUI(){
MoviePosterImageView.translatesAutoresizingMaskIntoConstraints = false
MoviePosterImageView.topAnchor.constraint(equalTo: topAnchor).isActive = true
MoviePosterImageView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
MoviePosterImageView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
MoviePosterImageView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
}
func updateCell(posterURL: String?){
if let posterURL = posterURL {
guard let CompleteURL = URL(string: "https://image.tmdb.org/t/p/w500/\(posterURL)") else {return}
self.MoviePosterImageView.sd_setImage(with: CompleteURL)
}
}
}
여기에서 UICollectionView 클래스를 하위 분류하고 init()
를 구현하여 사용자 지정 컬렉션 보기 셀을 만듭니다. 기능.
영화 포스터 이미지를 표시하고 자동 레이아웃 제약 조건을 설정하기 위해 UIImageView를 만듭니다. 그런 다음 Movie 포스터 URL 문자열을 매개변수로 사용하고 UI 스레드/메인 스레드에 영향을 주지 않고 비동기적으로 다운로드하는 사용자 정의 함수를 만듭니다. 앞서 추가한 SD WebImage CocoaPod를 사용하여 이 작업을 수행합니다.
API 설정 방법
계속 진행하기 전에 계정을 만들어 TMDB API용 API 키를 가져와야 합니다(무료). API 키와 영화 이름을 매개변수로 사용하는 API의 영화 검색 끝점을 사용할 것입니다.
https://api.themoviedb.org/3/search/movie?api_key=API_KEY_HERE&query=batman
Postman에서 실행하여 API 응답을 검사할 수 있습니다.
API 응답을 위한 모델을 만드는 방법
이제 API에서 JSON 응답을 받습니다. Codable 프로토콜을 구현하는 모델 구조체를 생성하여 수행할 수 있는 Swift로 디코딩해야 합니다.
JSON을 Swift 웹 사이트에 사용하여 JSON 응답에 대한 모델 구조를 쉽게 생성할 수 있습니다. API 응답에 대한 모델 코드는 다음과 같습니다. Model.swift 파일에 복사하여 붙여넣으면 됩니다.
import Foundation
struct TrendingTitleResponse: Codable {
let results: [Title]
}
struct Title: Codable {
let id: Int
let media_type: String?
let original_name: String?
let original_title: String?
let poster_path: String?
let overview: String?
let vote_count: Int
let release_date: String?
let vote_average: Double
}
struct YoutubeSearchResponse: Codable {
let items: [VideoElement]
}
struct VideoElement: Codable {
let id: IdVideoElement
}
struct IdVideoElement: Codable {
let kind: String
let videoId: String
}
Swift를 사용하여 HTTP 요청을 수행하는 방법
이제 API의 JSON 응답을 반환하는 HTTP GET 요청을 수행하기 위해 Swift 코드를 작성해야 합니다.
Swift는 AFNetworking, AlamoFire 등과 같은 타사 라이브러리 없이도 네트워킹 코드를 더 쉽게 작성할 수 있도록 하는 URLSession 클래스를 제공합니다.
APIService.swift를 열고 다음 코드를 추가하세요:
import Foundation
class APIService{
static var shared = APIService()
let session = URLSession(configuration: .default)
func getMovies(for Query: String,completion:@escaping([Title]?,Error?)->Void){
guard let FormatedQuery = Query.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) else{return}
guard let SEARCH_URL = URL(string: "https://api.themoviedb.org/3/search/movie?api_key=API_KEY_HERE&query=\(FormatedQuery)") else {print("INVALID")
return}
let task = session.dataTask(with: SEARCH_URL) { data, response, error in
if let error = error {
print(error.localizedDescription)
completion(nil,error)
}
if let data = data {
do{
let decodedData = try JSONDecoder().decode(TrendingTitleResponse.self, from: data)
// print(decodedData)
completion(decodedData.results,nil)
}
catch{
print(error)
}
}
}
task.resume()
}
}
여기에서 싱글톤 패턴을 사용하여 API Service라는 클래스를 만들었으므로 이 클래스에 대한 인스턴스가 클래스의 정적 멤버로 필요합니다. 그런 다음 사용자 정의 메서드 getMovies()가 뒤따르는 기본 구성으로 네트워킹 작업을 위한 세션을 만들었습니다.
그런 다음 네트워킹 작업을 만들었습니다. 이 경우 dataTask()
을 사용하여 수행할 수 있는 HTTP GET 요청을 수행해야 합니다. URLSession 클래스의 메소드 URL을 매개변수로 사용하고 API에서 반환된 데이터, 오류가 발생한 경우 오류 데이터, 상태 코드 및 해당 메시지와 같은 HTTP 응답 정보가 포함된 응답을 포함하는 완료 처리기를 제공합니다.
오류가 있으면 오류 데이터와 함께 이 함수에서 빠져 나옵니다. 그렇지 않은 경우 Swift 모델을 기반으로 JSON 데이터를 디코딩하고 디코딩된 데이터로 이 함수를 빠져나옵니다.
UICollectionView에 검색 결과를 표시하는 방법
HomeVC.swift에서 Title 객체의 배열인 private 속성을 만듭니다. 여기에는 API에서 반환한 각 영화의 정보가 포함됩니다.
private var Movies = [Title]()
HomeVC.swift에서 HomeVC 클래스에 대한 확장을 만들고 UICollectionViewDelegate 및 UICollectionViewDatasource 프로토콜을 구현합니다. 그런 다음 numberOfItemsInSection(API에서 반환한 영화 수와 동일) 및 cellForItemAt(다운로드 및 포스터 이미지 설정과 같은 API 응답으로 셀을 실제로 채움)를 구현합니다.
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return Movies.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MovieCell.ID, for: indexPath) as? MovieCell{
// cell.backgroundColor = .systemBackground
cell.updateCell(posterURL: Movies[indexPath.row].poster_path)
return cell
}
return UICollectionViewCell()
}
마지막으로 updateSearchResults()
내부에서 수행하는 실제 API 호출을 수행해야 합니다. 이전에 구현한 대리자 메서드입니다. 해당 메소드 안에 다음 코드를 추가하십시오.
func updateSearchResults(for searchController: UISearchController) {
guard let query = searchController.searchBar.text else{return}
APIService.shared.getMovies(for:query.trimmingCharacters(in: .whitespaces)) { titles, error in
if let titles = titles {
self.Movies = titles
DispatchQueue.main.async {
self.MovieCollectionView.reloadData()
}
}
}
}
여기에서 사용자가 검색창에 입력하거나 검색 버튼을 누를 때마다 (검색창에 입력된 이름을 기반으로) 영화를 가져오기 위해 HTTP GET 요청을 합니다. 그런 다음 영화 포스터로 컬렉션 뷰 셀을 업데이트하는 CollectionView를 다시 로드합니다.
기본적으로 iOS는 백그라운드 스레드에서 HTTP 요청을 자동으로 만들기 때문에 기본 스레드/UI 스레드에서 이 작업을 수행해야 합니다. 즉, UI 요소를 업데이트하려면 UI/메인 스레드를 사용해야 합니다.
이제 시뮬레이터에서 앱을 실행하여 결과를 확인하세요.
축하합니다! iOS 앱에서 UISearchController를 사용하는 방법을 배웠습니다.