สวัสดีทุกๆคน! ในบทความนี้ เราจะมาเรียนรู้วิธีใช้ UISearchController ในแอป iOS
เราจะสร้างอะไร
เรากำลังจะสร้างแอปพลิเคชั่นค้นหาภาพยนตร์ซึ่งใช้ TMDB API เพื่อดึงข้อมูลภาพยนตร์และแสดงโดยใช้ UICollectionView ตามคำค้นหาของผู้ใช้
การตั้งค่าโครงการ
เปิด Xcode และสร้างโปรเจ็กต์ iOS App เปล่าใหม่ - ตรวจสอบให้แน่ใจว่าคุณได้เลือก UIKit ไม่ใช่ SwiftUI
ในแอพนี้ เราจะใช้รูปแบบ MVC เพื่อจัดระเบียบโครงการโดยสร้างกลุ่มและไฟล์ Swift ต่อไปนี้:
ตอนนี้ปิดโครงการ Xcode ของคุณ เปิดเทอร์มินัลแล้วย้ายไปที่ไดเรกทอรีโครงการของคุณ ที่นี่เราต้องเพิ่ม SD WebImage Cocoa Pods เพื่อดาวน์โหลดและแคชภาพโปสเตอร์ภาพยนตร์แบบอะซิงโครนัส
พิมพ์คำสั่งต่อไปนี้ในเทอร์มินัล:
pod init
ตอนนี้ เมื่อคุณแสดงรายการเนื้อหาของไดเร็กทอรี คุณจะเห็นว่ามี Podfile ใหม่ เปิดไฟล์โดยใช้โปรแกรมแก้ไขข้อความ (ที่นี่ฉันใช้ Vim) แก้ไข Podfile ของคุณเพื่อให้ดูเหมือนกับภาพด้านล่าง บันทึกและปิด Podfile
ตอนนี้เราได้ระบุ SD WebImage แล้ว เราสามารถติดตั้งการพึ่งพาได้โดยใช้คำสั่งด้านล่าง:
pod install
อย่างที่คุณเห็น เราได้เพิ่มพ็อด SD WebImage สำเร็จในโครงการ iOS ของเราแล้ว เรียกใช้คำสั่งด้านล่างเพื่อเปิดโครงการของเราใน Xcode
open PROJECT_NAME.xcworkspace
หลังจากเปิด Xcode แล้ว อย่าลืมสร้างโปรเจ็กต์โดยกด Command+B
วิธีออกแบบอินเทอร์เฟซผู้ใช้โดยใช้ UIKit และ UI แบบเป็นโปรแกรม
แอปของเราต้องการแถบการนำทาง UIElements สามแถบเพื่อเก็บแถบค้นหา 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 แบบเป็นโปรแกรม อันดับแรกเราต้องพูดถึงตัวควบคุม Root View ของเรา นั่นคือหน้าจอแรกที่จะแสดงเมื่อผู้ใช้เปิดแอป
ในแอพนี้เราใช้ 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
}
ก่อนอื่น เราบอกว่าเราไม่จำเป็นต้องแปลงมาสก์การปรับขนาดอัตโนมัติเป็นข้อจำกัด จากนั้นเราตรึงมุมมองคอลเล็กชันไว้ที่ทั้งสี่ด้านของ View Controller
ภายใน 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 ของเราตามด้วยสีพื้นหลังซึ่งเป็นสีพื้นหลังของระบบ หากอุปกรณ์อยู่ในโหมดแสง จะแสดงพื้นหลังสีขาว หากอยู่ในโหมดมืด แสดงว่าพื้นหลังมืด
จากนั้นเราตั้งค่าปัจจุบันให้ตัวควบคุมการดูเป็นตัวอัปเดตผลการค้นหา จากนั้นเพิ่ม SearchController ของเราไปที่แถบนำทาง เพิ่ม UICollectionView ไปที่ ViewController และตัวแทนการตั้งค่าและแหล่งข้อมูล สุดท้ายเราตรึง UICollectionView โดยใช้เค้าโครงอัตโนมัติ
สร้างส่วนขยายสำหรับ HomeVC และใช้โปรโตคอล UISearchResultsUpdating และวิธีการอัปเดตSearchResults ของต้นขั้ว
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 เพื่อแสดงภาพโปสเตอร์ภาพยนตร์และตั้งค่าข้อจำกัดเลย์เอาต์อัตโนมัติสำหรับภาพนั้น จากนั้น เราสร้างฟังก์ชันที่ผู้ใช้กำหนดซึ่งนำพารามิเตอร์สตริง URL โปสเตอร์ภาพยนตร์ และดาวน์โหลดแบบอะซิงโครนัสโดยไม่ส่งผลต่อเธรด UI/เธรดหลัก ทำได้โดยใช้ SD WebImage CocoaPod ที่เราเพิ่มไว้ก่อนหน้านี้
วิธีตั้งค่า API ของเรา
ก่อนดำเนินการต่อ คุณจะต้องรับคีย์ API สำหรับ TMDB API โดยสร้างบัญชี (ฟรี) เราจะใช้จุดสิ้นสุดการค้นหาภาพยนตร์ของ API ซึ่งใช้คีย์ API และชื่อภาพยนตร์เป็นพารามิเตอร์
https://api.themoviedb.org/3/search/movie?api_key=API_KEY_HERE&query=batman
คุณสามารถตรวจสอบการตอบสนองของ API ได้โดยเรียกใช้ในบุรุษไปรษณีย์
วิธีสร้างแบบจำลองสำหรับการตอบกลับ API
ตอนนี้เราได้รับการตอบสนอง JSON จาก API เราจำเป็นต้องถอดรหัสพวกมันเป็น Swift ซึ่งเราสามารถทำได้โดยการสร้าง model struct ที่ใช้โปรโตคอล Codable
เราสามารถสร้าง model struct สำหรับการตอบสนอง JSON ของเราได้อย่างง่ายดายโดยใช้ JSON to Swift เว็บไซต์ นี่คือรหัสรุ่นสำหรับการตอบกลับ 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
}
วิธีดำเนินการคำขอ HTTP โดยใช้ Swift
ตอนนี้ เราต้องเขียนโค้ด Swift เพื่อทำการร้องขอ HTTP GET ซึ่งส่งคืนการตอบสนอง JSON ของ API
Swift มีคลาส URLSession ซึ่งช่วยให้เขียนโค้ดเครือข่ายได้ง่ายขึ้นโดยไม่ต้องใช้ไลบรารีของบุคคลที่สาม เช่น AFNetworking, AlamoFire เป็นต้น
เปิด 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()
จากนั้นเราก็สร้างงานเครือข่ายของเรา ในกรณีนี้ เราจำเป็นต้องดำเนินการคำขอ HTTP GET ซึ่งสามารถทำได้โดยใช้ dataTask()
เมธอดของคลาส URLSession ใช้ URL เป็นพารามิเตอร์และให้ตัวจัดการความสมบูรณ์ซึ่งมีข้อมูลที่ส่งคืนจาก API ข้อมูลข้อผิดพลาดหากมีข้อผิดพลาดเกิดขึ้น และการตอบสนองที่มีข้อมูลการตอบกลับ HTTP เช่น รหัสสถานะและข้อความที่เกี่ยวข้อง
หากมีข้อผิดพลาด เราจะออกจากฟังก์ชันนี้ด้วยข้อมูลข้อผิดพลาด หากไม่เป็นเช่นนั้น เราจะถอดรหัสข้อมูล JSON ของเราตาม Swift Model และหลีกหนีจากฟังก์ชันนี้ด้วยข้อมูลที่ถอดรหัส
วิธีแสดงผลการค้นหาบน UICollectionView
ใน HomeVC.swift ให้สร้างคุณสมบัติส่วนตัวซึ่งเป็นอาร์เรย์ของวัตถุชื่อ สิ่งเหล่านี้จะเก็บข้อมูลของภาพยนตร์แต่ละเรื่องส่งคืนโดย API
private var Movies = [Title]()
ใน HomeVC.swift ให้สร้างส่วนขยายสำหรับคลาส HomeVC และใช้โปรโตคอล UIColletionViewDelegate และ 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()
}
สุดท้ายเราต้องทำการเรียก API จริงซึ่งเราทำใน updateSearchResults()
วิธีการมอบหมายที่เราได้ดำเนินการก่อนหน้านี้ ภายในวิธีการนั้นให้เพิ่มรหัสต่อไปนี้:
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 ซึ่งอัปเดตเซลล์มุมมองคอลเลกชันด้วยโปสเตอร์ภาพยนตร์
โปรดทราบว่าเราจำเป็นต้องดำเนินการนี้ในเธรดหลัก/เธรด UI เนื่องจากโดยค่าเริ่มต้น iOS จะสร้างคำขอ HTTP ในเธรดพื้นหลังโดยอัตโนมัติ ซึ่งหมายความว่าเราจำเป็นต้องใช้ UI/Main Thread เพื่ออัปเดตองค์ประกอบ UI ของเรา
เรียกใช้แอปของคุณในโปรแกรมจำลองเพื่อดูผลลัพธ์:
ยินดีด้วย! คุณได้เรียนรู้การใช้ UISearchController ในแอป iOS แล้ว