Computer >> คอมพิวเตอร์ >  >> สมาร์ทโฟน >> iPhone

วิธีใช้ UISearchController ในแอพ iOS

สวัสดีทุกๆคน! ในบทความนี้ เราจะมาเรียนรู้วิธีใช้ UISearchController ในแอป iOS

เราจะสร้างอะไร

เรากำลังจะสร้างแอปพลิเคชั่นค้นหาภาพยนตร์ซึ่งใช้ TMDB API เพื่อดึงข้อมูลภาพยนตร์และแสดงโดยใช้ UICollectionView ตามคำค้นหาของผู้ใช้

การตั้งค่าโครงการ

เปิด Xcode และสร้างโปรเจ็กต์ iOS App เปล่าใหม่ - ตรวจสอบให้แน่ใจว่าคุณได้เลือก UIKit ไม่ใช่ SwiftUI

ในแอพนี้ เราจะใช้รูปแบบ MVC เพื่อจัดระเบียบโครงการโดยสร้างกลุ่มและไฟล์ Swift ต่อไปนี้:

วิธีใช้ UISearchController ในแอพ iOS

ตอนนี้ปิดโครงการ Xcode ของคุณ เปิดเทอร์มินัลแล้วย้ายไปที่ไดเรกทอรีโครงการของคุณ ที่นี่เราต้องเพิ่ม SD WebImage Cocoa Pods เพื่อดาวน์โหลดและแคชภาพโปสเตอร์ภาพยนตร์แบบอะซิงโครนัส

พิมพ์คำสั่งต่อไปนี้ในเทอร์มินัล:

pod init

ตอนนี้ เมื่อคุณแสดงรายการเนื้อหาของไดเร็กทอรี คุณจะเห็นว่ามี Podfile ใหม่ เปิดไฟล์โดยใช้โปรแกรมแก้ไขข้อความ (ที่นี่ฉันใช้ Vim) แก้ไข Podfile ของคุณเพื่อให้ดูเหมือนกับภาพด้านล่าง บันทึกและปิด Podfile

วิธีใช้ UISearchController ในแอพ iOS

ตอนนี้เราได้ระบุ 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 ได้โดยเรียกใช้ในบุรุษไปรษณีย์

วิธีใช้ UISearchController ในแอพ iOS

วิธีสร้างแบบจำลองสำหรับการตอบกลับ 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

ยินดีด้วย! คุณได้เรียนรู้การใช้ UISearchController ในแอป iOS แล้ว