본문 바로가기

Swift(IOS)

[내용정리] Swift - 코드로(Programmatically) 검색기능 구현

 

 

구현하게될 화면

구현할 SearchView는 한글자 한글자 입력시마다 

해당 키워드와 같은 키워드인 문자들을 호출한다. 

 

현재 스터디로 UI개발을 코드베이스로 하고있어

스토리보드는 클래스와의 연결용으로 사용중이다.

 

1. Main 스토리보드에 VC를 하나 만들어준다.

  • 이후 구현하게될 클래스명을 SearchViewController로 칭하게될 예정이여서 클래스명을 동일하게 설정해둔다.

Main.storyboard

 

 

2. 만들어 놓은 SeachViewController를 Navigation Controller에 Embed 해준다

우리는 navigationItem에 searchBar를 넣어줄거기 때문에 Embed를 미리 해주어야 한다. 

Navigation Controller 를 Embe

 

 

3. Model을 따로 구조체로 만들어준다

  • 모델은 데이터이므로 배열에 검색시에 나올데이터에 정의를 해두었다.( 추후 API통신으로 서버에 있는 데이터를 불러올 예정이다.)
  • filterValue같은경우는 우리가 한글자 입력할때마다 해당 빈배열에 한글자씩 들어오게하려고 정의를 해두었다.
  • Codable로 선언해둔 이유는 나중에 API를 통해 받을데이터 타입이 Json이기 때문에 선언해주었다.
    Codable에 대해서 따로 확인이 필요하면 정리한 블로그가 존재한다. https://yoonds-develop.tistory.com/13
struct DestiData: Codable {
    static let shared = DestiData(place: "")
    let place: String
    var filterValue: Array<String> = []
    let placeData: Array<String> = ["강남", "강기도", "강원도", "서울시", "서강시"]

    init(place: String) {
        self.place = place
    }
}

 

 

 

4. Cocoa Touch Class를 통하여 UIViewController를 서브로 두는 SeachViewController를 생성해주자

 

 

5. 해당 클래스 내부에 searchBar변수를 생성해준다.

    • .obscuresBackgroundDuringPresentation는 검색바 선택시에 해당외에 나머지는 어둡게할건지? false를 주게되면 밝게 가저갑니다.
    • .searchController = searchController  이부분이 핵심입니다. 우측의 생성해준 객체
      navigationItem의 searchController로 넣어줌으로써 생성된 객체 SearchViewController의 서치바로 등록이 된것입니다.
    • .prefersLargeTitles는 title을 큰title로 등록할건지에 대한 속성입니다.
    • .hidesSearchBarWhenScrolling는 스크롤시에 searchBar부분을 유지할것인지, 숨길것인지에 대한 속성입니다.
lazy var userSearchBar: UISearchController = {
        let searchController = UISearchController()
        searchController.searchBar.placeholder = "목적지를 입력해주세요"
        searchController.obscuresBackgroundDuringPresentation = false
        self.navigationItem.searchController = searchController
        self.navigationItem.title = "목적지 검색"
        self.navigationController?.navigationBar.prefersLargeTitles = true
        self.navigationItem.hidesSearchBarWhenScrolling = false
       
        return searchController
    }()

 

 

6.  데이터가 노출될 테이블이 필요하니 테이블을 생성해준다.

  • register를 등록하여 이테이블에 대한 이름을 지정해준다 "dataCell" 
 lazy var serachDataTable: UITableView = {
        let tableView = UITableView()
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "dataCell")
        
        return tableView
    }()

 

 

 

7. 모델의 데이터 미리보여주기

  • 위에서 만들어둔 userSearchBar객체가 active(타이핑 등)중이면서 Text가 비어있지 않으면 true 그렇지 않다면 false를 반환한다.
var filterCheck: Bool {
        let searchController = self.navigationItem.searchController
        let isActive = searchController?.isActive ?? false
        let isEmpty = searchController?.searchBar.text?.isEmpty ?? false
        
        return isActive && isEmpty
    }
  •  

 

8. 테이블에 대한 레이아웃잡기

  • 스냅킷을 활용하여 Layout을 잡고있어서 스냅킷 import가 필요하다
    "스냅킷 관련하여 다룰 블로그가 등록될 예정"
  • .multipliedBy(1.0).offset(0) 이 부분은 1.0배수, 0이기때문에 생략하여도 상관없지만
    레이아웃 잡을대 항상 등록했던 옵션이여서 습관적으로 만들어 두었다.
func setLayout() {
        placeDataTable.snp.makeConstraints {
            $0.top.equalToSuperview().multipliedBy(1.0).offset(0)
            $0.leading.equalToSuperview().multipliedBy(1.0).offset(0)
            $0.bottom.equalToSuperview().multipliedBy(1.0).offset(0)
            $0.right.equalToSuperview().multipliedBy(1.0).offset(0)
        }
        
    }

 

9. 생성해둔 클래스에서 Delegate사용을 위해 프로토콜 채택을 진행한다.

  • UISearchResultsUpdating, UITableViewDataSource
class SearchViewController: UIViewController, UISearchResultsUpdating, UITableViewDataSource

 

 

10. 채택을 진행하게되면 필수 메소드를 구현하라고 한다. 

  • 작성자인 내가 만들고 싶어서 만든 함수명이 아닌 프로토콜에서 이미 정의된 함수명이다.
    해당 updateSearchResults함수는 서치바에서 한글자 칠때마다 실행되는 함수이다.
  • print로 보다시피 한글자 입력시마다 함수가 실행되어 글자가 찍히게 된다
  • .reloadData 유저가 글자를 입력할때마다 테이블을 불러와 해당값에 대한 내용을 보여준다.
 func updateSearchResults(for placeSearch: UISearchController) {
        guard let userText = placeSearch.searchBar.text
        else {
            return
        }
        print("사용자 입력 : ", userText )
        self.data.filterValue = self.data.placeData.filter{ 
        	$0.localizedCaseInsensitiveContains(userText)}
        self.placeDataTable.reloadData()
    }

 

print로 찍어놓은 부분이 한글자 입력할때마다 실행

 

 

11. table의 numberOfRowsInSection으로 몇개의 cell을 생성할건지 정해준다.

  • 3항연산을 활용하여 filterCheck시에 true(내가 지금 타이핑 중)이면 filterValue수 만큼
  • false(건들지않은 상태)이면 전체 데이터를 보여줘야하니 placeData 수만큼 cell을 생성해준다.
 func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {  
        
        return self.filterCheck ? self.data.filterValue.count : self.data.placeData.count
 }

 

 

12. table의 cellForRowAt으로 어떤 데이터를 활용하여 테이블을 구현할건지 정의 한다. 

  • 미리 객체로 만들어둔 placeDataTable을 활용할것이다.
  • 어떤 테이블을 사용할거야? 미리 "dataCell"로 정의했던 table을 지정해준다.
  • 이후 false(건들지않은 상태)일때 Model안에 있는 배열 데이터를 cell의 textLabel에 넣어 찍어준다.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "dataCell")!
        
        if self.filterCheck == false {
            cell.textLabel?.text = self.data.placeData[indexPath.row]
        } else {
            cell.textLabel?.text = self.data.filterValue[indexPath.row]
        }
        
        return cell
    }

 


 

전체코드

import UIKit
import SnapKit

class SearchViewController: UIViewController, UISearchResultsUpdating, UITableViewDataSource {
    
    lazy var userSearchBar: UISearchController = {
        let searchController = UISearchController()
        searchController.searchBar.placeholder = "목적지를 입력해주세요"
        // 검색이후 나머지뷰 어둡게?
        searchController.obscuresBackgroundDuringPresentation = false
        
        // searchController를 navi의 searchController에 넣어줘야함
        self.navigationItem.searchController = searchController
        self.navigationItem.title = "목적지 검색"
        self.navigationController?.navigationBar.prefersLargeTitles = true
        self.navigationItem.hidesSearchBarWhenScrolling = false
       
        return searchController
    }()
    
    lazy var placeDataTable: UITableView = {
        let tableView = UITableView()
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "dataCell")
        
        return tableView
    }()
    
    var data: DestiData = DestiData(place: "")
    
    var filterCheck: Bool {
        let searchController = self.navigationItem.searchController
        let isActive = searchController?.isActive ?? false
        let isEmpty = searchController?.searchBar.text?.isEmpty ?? false
        
        return isActive && isEmpty
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setUpView()
        setDelegate()
        setLayout()
    }
     
    func setUpView() {
        view.addSubview(placeDataTable)
    }
    
    func setDelegate() {
        placeDataTable.dataSource = self
        userSearchBar.searchResultsUpdater = self
    }
    
    func setLayout() {
        placeDataTable.snp.makeConstraints {
            $0.top.equalToSuperview().multipliedBy(1.0).offset(0)
            $0.leading.equalToSuperview().multipliedBy(1.0).offset(0)
            $0.bottom.equalToSuperview().multipliedBy(1.0).offset(0)
            $0.right.equalToSuperview().multipliedBy(1.0).offset(0)
        }
        
    }
    
    // delegate 관련 메소드
    // 서치바에서 검색시마다 해당 메소드 실행
    func updateSearchResults(for placeSearch: UISearchController) {
        guard let userText = placeSearch.searchBar.text
        else {
            return
        }
        print("사용자 입력 : ", userText )
        self.data.filterValue = self.data.placeData.filter{ $0.localizedCaseInsensitiveContains(userText)}
        self.placeDataTable.reloadData()
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        
        //3항연산으로 filterValue에 값이 있으면 반환, 없으면 items를 반환
        return self.filterCheck ? self.data.filterValue.count : self.data.placeData.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "dataCell")!
        
        if self.filterCheck == false {
            cell.textLabel?.text = self.data.placeData[indexPath.row]
        } else {
            cell.textLabel?.text = self.data.filterValue[indexPath.row]
        }
        
        return cell
    }
}

 

 

개인학습 목적으로 이해, 공부하면 정리한 내용이라 미숙한점이 있을수도 있다.

태클, 문의사항 언제든 환영