스위프트/UIKit

[UIKit] Collection View 검색 기능

강철곰탱이 2023. 11. 6. 03:51

Collection View에서 cell의 section을 검색하여 검색한 내용을 필터링해서 보여주는 기능을 만들어보자.

 


 

Search Collection View

 

 

 

 

이 프로젝트에서는 storyboard를 사용하지 않고 만들었다.

storyboard를 사용하지 않고 코드로 작성하는 방법은 아래의 링크에 정리해뒀다.

 

2023.11.05 - [스위프트] - [swift] storyboard없이 Navigation controller

 

또한  autolayout을 쉽게 구현하도록 하는 Snapkit 라이브러리를 사용하였다.

CocoaPod을 이용해 Snapkit 라이브러리를 설치해야 한다.

 

 

[swift] CocoaPod 설치와 사용법

iOS 및 macOS 애플리케이션에서 사용할 수 있는 라이브러리 및 코드 모듈을 공유하기 위한 패키지 관리 도구이다. CocoaPod 사용법에 대해 배워보자. CocoaPod CocoaPod이란? CocoaPod은 개발에서 사용하는

steelbeartaeng2.tistory.com

 

 

디렉터리 구조

 

 

 

  • CollectionViewMain: collectionView의 Delegate/DataSource, 검색 로직
  • CollectionViewHeader: section 헤더 설정
  • CollectionViewMainCell: collectionViewCell 설정
  • ImageData: 이미지 데이터 목록 

 

 

주요 구성 요소

 

 

  • collectionView: 이미지 데이터 표시
  • collectionViewCell: CollectionView에 보여질 customCell
  • collectionReusableView: 섹션 헤더 정의
  • searchInputText: 검색 바
  • seletedCellCountLabel: 선택된 이미지 항목의 수 
  • topBarView: 검색 바 및 선택된 항목 표시 label을 포함하는 상단 바 뷰
  • ImageData: image data 정의

 

 

핵심 기능

 

 

1️⃣ 검색 및 스크롤:

 

  • textfield에 입력된 텍스트를 기반으로 해당 섹션으로 스크롤

 

2️⃣ collectionView 데이터 정렬 및 헤더 표시:

 

  • 섹션 별 title과 cell 표시

 

3️⃣ cell 선택 및 업데이트:

 

  • 이미지 선택시 경계선 강조
  • 선택된 항목 수에 따라 label 업데이트와 textfield 위치 동적으로 조절

 

 


 

CollectionViewController

 

 

 

UICollectionViewDelegate

 

 

collectionView의 동작과 이벤트를 커스터마이징할 수 있다.

 

  • collectionView(_:didSelectItemAt:): 사용자가 셀을 탭했을 때 호출
  • collectionView(_:willDisplay:forItemAt:): 셀이 화면에 나타나기 전에 호출

 

UICollectionViewDataSource

 

 

collectionView에 표시될 데이터와 각 셀의 구성을 제공한다.

 

    • collectionView(_:numberOfItemsInSection:): 섹션의 아이템 개수 반환
    • collectionView(_:cellForItemAt:): 각 아이템에 대한 셀을 반환
    • collectionView(_:viewForSupplementaryElementOfKind: at:): 섹션의 보충요소 반환
    • numberOfSections(in:): 컬렉션 뷰에 표시할 섹션의 총 수를 반환

 

 

 

UICollectionViewDelegateFlowLayout

 

 

주로 아이템의 크기, 간격등의 레이아웃 설정을 다루는 데 사용

 

  • collectionView(_:layout:minimumLineSpacingForSectionAt:): 컬렉션 뷰의 섹션 간 수직으로 항목 간 최소 간격 설정
  • collectionView(_:layout:minimumInteritemSpacingForSectionAt:): 컬렉션 뷰의 섹션 내 수평으로 항목 간의 최소 간격 설정
  • collectionView(_:layout:sizeForItemAt:): 컬렉션 뷰의 항목 크기를 설정
  • collectionView(_:layout:referenceSizeForHeaderInSection:): 섹션 헤더의 크기 설정
  • collectionView(_:layout:insetForSectionAt:): 섹션 내부 여백을 설정

 


구현

 

1️⃣ image 저장

 

assets에 다음과 같이 색상 사진을 저장해준다.

 

 

 

다음과 같이 ImageData를 만들어 assets에 저장된 색상 사진을 적용하여 배열을 만든다.

 

  • ImageData.swift
import UIKit

struct ImageData{
    let imageName: String
    let image: UIImage
    let sectionName: String
}

extension ImageData{
    static let list: [ImageData] = [
        ImageData(imageName: "blue", image: UIImage(named: "blue")!, sectionName: "Section1"),
        ImageData(imageName: "red", image: UIImage(named: "red")!, sectionName: "Section1"),
        ImageData(imageName: "purple", image: UIImage(named: "purple")!, sectionName: "Section1"),
        ImageData(imageName: "green", image: UIImage(named: "green")!, sectionName: "Section1"),
        ImageData(imageName: "pink", image: UIImage(named: "pink")!, sectionName: "Section2"),
        ImageData(imageName: "navy", image: UIImage(named: "navy")!, sectionName: "Section2"),
        ImageData(imageName: "yellow", image: UIImage(named: "yellow")!, sectionName: "Section3")
    ]
}

 

 

2️⃣ 구성요소 설정

 

🔎 데이터 구조 정의

 

  • sectionData: [String: [ImgaeData]] 이미지 데이터를 섹션별로 구분하여 저장
  • selectedItems: [IndexPath: Bool] 선택된 cell인지 판단

 

 

 

custom Cell

 

 

custom cell은 tableview에서 정리해놨으니 참고바란다.

 

 

[swift] TableView + Custom Cell

iOS 애플리케이션 개발에서 중요한 컴포넌트 중 하나가 UITableView이다. 테이블 형식의 데이터를 표시하는데 사용하며 데이터를 목록 또는 그리드 형태로 표시할 수 있으며 주로 목록 형식으로 사

steelbeartaeng2.tistory.com

 

 

import UIKit
import SnapKit

class CollectionViewMainCell: UICollectionViewCell {
    let cellImage: UIImageView = {
        let imageView = UIImageView()
        imageView.contentMode = .center
        return imageView
    }()

    func initCell() {
        addSubview(cellImage)
        
        cellImage.snp.makeConstraints { make in
            make.edges.equalToSuperview()
        }
    }
}

 

collectionView와 custom cell을 아래와 같이 연결해줘야 화면에 잘 나오게 된다.

 

 

 

 

SectionHeader 

 

 

섹션 헤더를 표현하기 위해 UICollectionReusableView를 상속받아 선언해 준다.

 

import UIKit
import SnapKit

class CollectionViewHeader: UICollectionReusableView {
    let titleLabel: UILabel = {
        let label = UILabel()
        label.font = UIFont.boldSystemFont(ofSize: 16)
        label.textColor = .black
        return label
    }()
    
    func initHeader() {
        addSubview(titleLabel)
        titleLabel.snp.makeConstraints { make in
            make.top.equalToSuperview().offset(50)
            make.centerX.equalToSuperview()
        }
    }
}

 

 

ViewController에서 CollectionView에 섹션 헤더를 등록해보자.

 

 

    • CollectionViewHeader.self : 등록할 View의 타입 지정
    • forSupplementaryViewOfKind: 등록할 View의 종류, 위에서는 섹션 헤더를 나타내기 위해UICollectionView.elementKindSectionHeader를 사용
    • withReuseIdentifier재사용을 위한 식별자 지정

 

func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
    if kind == UICollectionView.elementKindSectionHeader {
        let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "HeaderView", for: indexPath) as! CollectionViewHeader
        headerView.initHeader()

        let sectionName = Array(sectionData.keys)[indexPath.section]
        headerView.titleLabel.text = sectionName

        return headerView

    } else {
        return UICollectionReusableView()
    }
}

 

 

3️⃣ 이미지 섹션 별로 구분

 

 

 

organizeDataIntoSections() 는 ImageData.list에서 가져온 데이터를 섹션 별로 구분하기 위한 메서드이다.

저장된 sectionName에 따라 sectionData에 key, value 쌍으로 저장된다.

 

 private func organizeDataIntoSections() {
    for imageData in ImageData.list {
        if sectionData[imageData.sectionName] == nil {
            sectionData[imageData.sectionName] = []
        }
        sectionData[imageData.sectionName]?.append(imageData)
    }
}

 

 

4️⃣ cell 선택

 

cell 선택 action

 

 

  • delegate로 선택된 cell인지 아닌지 판단을 하고 selectedItems에 true/false 값을 저장한다.
  • collectionView.reloadItems(at: [indexPath])로 collectionView를 reload하여 true/false에 따라 디자인을 설정한다.

 

extension CollectionViewMain: UICollectionViewDelegate {
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        if let cell = collectionView.cellForItem(at: indexPath) {
            if selectedItems[indexPath] == true {
                // Deselect
                selectedItems[indexPath] = false
            } else {
                // Select
                selectedItems[indexPath] = true
            }
            collectionView.reloadItems(at: [indexPath])
            
            updateSelectedItemCount()
        }
    }
}

 

 

  • collectionView가 reload될 때마다 seletedItems가 true/false 값에 따라 디자인을 다르게 적용해준다.

 

 func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewMainCell", for: indexPath) as! CollectionViewMainCell
    cell.initCell()

    let sectionName = Array(sectionData.keys)[indexPath.section]
    let imageData = sectionData[sectionName]?[indexPath.row]
    cell.cellImage.image = imageData?.image

    if let isSelected = selectedItems[indexPath], isSelected {
        cell.layer.borderColor = UIColor.black.cgColor
        cell.layer.borderWidth = 4
    } else {
        cell.layer.borderColor = UIColor.clear.cgColor
        cell.layer.borderWidth = 0
    }

    return cell
}

 

 

 

선택된 항목 수에 따라 업데이트

 

 

 

selectedItems의 true/false 값으로 선택된 cell의 수를 확인한다.

선택된 cell의 개수에 따라 seletecItemCount의 text가 바뀐다. 

 

func updateSelectedItemCount() {
    let count = selectedItems.values.filter { $0 == true }.count

    if count > 0{
        seletecItemCount.text = "\(count) photo selected"

        searchInputText.snp.remakeConstraints { make in
           make.top.equalToSuperview().offset(60)
           make.height.equalTo(40)
           make.width.equalTo(150)
           make.leading.equalTo(view.snp.leading).offset(50)
        }
    }else{
        seletecItemCount.text = ""

        searchInputText.snp.remakeConstraints { make in
            make.top.equalToSuperview().offset(60)
            make.height.equalTo(40)
            make.width.equalTo(150)
            make.centerX.equalToSuperview()
        }
    }

    UIView.animate(withDuration: 0.3) {
        self.view.layoutIfNeeded()//즉시 업데이트
    }
}

 

 

5️⃣ 검색 기능 구현

 

 

사용자가 textField에 입력한 텍스트와 sectionData의 key값과 일치한다면 해당 섹션으로 스크롤 되도록 한다.

 

extension CollectionViewMain: UITextFieldDelegate {
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        if let searchText = textField.text, !searchText.isEmpty {
            for (section, _ ) in sectionData {
                if section.lowercased() == searchText.lowercased() {//대소문자 구분없이
                    if let sectionIndex = Array(sectionData.keys).firstIndex(of: section) {
                        let indexPath = IndexPath(item: 0, section: sectionIndex)
                        
                        collectionView.scrollToItem(at: indexPath, at: .top, animated: true)
                    }
                }
            }
        }
        
        textField.resignFirstResponder()//키보드 내리기
        return true
    }
}