본문 바로가기

UIKIT

카테고리별 검색 결과를 5개 보여주는 방법

구현 목적 

  • 서치바에 검색어를 입력합니다. 
  • 검색어를 기반으로 외부 API에 데이터를 요청합니다. 
  • 요청해서 받아온 데이터를 contentTypeId 별로 구분해서 최소 5개 씩 테이블 형식으로 보여줍니다. 
  • 이때, 외부에서 받아온 데이터를 1페이지 당 10개의 데이터를 가져옵니다.
  • contentpyeId 별로 보여주는 최소 데이터 개수 5개를 채우지 못하면, 그 다음 페이지로 넘어갑니다. 
  • 이를 반복해서 최소 데이터 개수를 맞춥니다. 

 

 

구현 코드 

1. searchForKeyword(with:page:completion:)

이 함수는 네트워크 요청을 보내어 특정 페이지의 검색 결과를 가져오고, 데이터를 contentTypeId별로 그룹화한 뒤 completion 클로저를 호출하여 **총 결과 수(totalCount)**를 전달합니다.

  • 네트워크 요청: NetworkManager.shared.searchKeywordList를 사용해 주어진 page와 keyword로 데이터를 요청합니다.
  • 결과 처리:
    • categorizeByContentTypeId(searchList)를 호출하여 searchList의 데이터를 contentTypeId별로 분류하여 누적합니다.
    • completion(totalCount)을 호출하여 총 결과 수(totalCount)를 상위 호출자에게 전달합니다.
  • UI 업데이트: DispatchQueue.main.async를 통해 UI를 갱신하고, 결과가 없으면 테이블 뷰를 숨깁니다.
func searchForKeyword(with keyword: String, page: Int, completion: @escaping (Int) -> Void) {
        NetworkManager.shared.searchKeywordList(pageNo: String(page) ,keyword: keyword) { [weak self] results in
            switch results {
            case .success(let items):
                let searchList = items.response.body.items.item
                let totalCount = items.response.body.totalCount
                self?.categorizeByContentTypeId(searchList)
                completion(totalCount)
                
                DispatchQueue.main.async {
                    // 검색 결과가 있으면 테이블 뷰를 표시하고 없으면 숨깁니다.
                    self?.spotResultsTableView.isHidden = searchList.isEmpty
                    self?.spotResultsTableView.reloadData()
                    
                }
                
            case .failure(let error):
                print(error.localizedDescription)
            }
        }
    }

 

 

2. fetchForKeyword(with:)

이 함수는 초기 keyword 검색을 실행하며, 각 contentTypeId별로 최소 5개 이상의 데이터를 확보할 때까지 데이터를 요청합니다.

  • 전체 페이지 계산:
    • totalCount와 itemsPerPage를 사용하여 전체 페이지 수(totalPages)를 계산합니다.
  • 각 contentTypeId별 최소 5개 확보:
    • ensureMinimumItems 함수를 호출하여, 각 contentTypeId가 최소 5개 이상의 데이터를 확보하도록 추가 페이지를 요청합니다.
  • UI 업데이트: 데이터가 비어 있으면 테이블 뷰를 숨기고, 아니라면 reloadData()로 데이터를 다시 로드합니다.
func fetchForKeyword(with keyword: String) {
        searchForKeyword(with: keyword, page: 1) { [weak self] totalCount in
            guard let self = self else { return }
            
            // 전체 페이지 수 계산
            let itemsPersPage = 10
            let totalPages = (totalCount / itemsPersPage) + (totalCount % itemsPersPage > 0 ? 1 : 0)
            
            // 각 contentTypId별 최소 5개 확보
            for contentTypeId in self.spotResultsByContentTypeId.keys {
                self.ensureMinimumItems(for: contentTypeId, keyword: keyword, currentPage: 1, totalPages: totalPages)
            }
            
            DispatchQueue.main.async {
                self.spotResultsTableView.isHidden = self.spotResultsByContentTypeId.isEmpty
                self.spotResultsTableView.reloadData()
            }
        }
    }

 

 

3. categorizeByContentTypeId(_:)

이 함수는 가져온 데이터(items)를 contentTypeId별로 그룹화하여 spotResultsByContentTypeId 딕셔너리에 누적하는 역할을 합니다.

  • 데이터 누적:
    • spotResultsByContentTypeId[contentTypeId, default: []].append(item)을 통해, 각 contentTypeId에 해당하는 배열이 없다면 빈 배열을 초기화한 후 item을 추가합니다.
    • 이 방법으로 removeAll() 없이 데이터를 누적하여, 이후 요청한 페이지의 데이터도 spotResultsByContentTypeId에 추가됩니다.
    func categorizeByContentTypeId(_ items: [AttractionItem]) {
        //spotResultsByContentTypeId.removeAll()
        for item in items {
            let contenttypeid = item.contenttypeid
            // default: [] 구문을 사용하면, contentTypeId 키가 spotResults 딕셔너리에 없을 경우 빈 배열 []이 자동으로 초기화
            //
            spotResultsByContentTypeId[contenttypeid, default: []].append(item)
        }
    }

 

 

4. ensureMinimumItems(for:keyword:currentPage:totalPages:)

이 함수는 각 contentTypeId가 최소 5개 이상의 데이터를 확보할 수 있도록 반복적으로 추가 페이지를 요청합니다.

  • 데이터 수 확인:
    • spotResultsByContentTypeId[contentTypeId]?.count를 통해 현재 확보된 데이터 수(itemCount)를 확인합니다.
  • 추가 페이지 요청 조건:
    • itemCount가 5개 미만이고 currentPage가 totalPages보다 작을 때만 추가 페이지를 요청합니다.
    • searchForKeyword를 호출하여 nextPage의 데이터를 가져오고, 이를 categorizeByContentTypeId로 추가한 뒤 ensureMinimumItems를 재귀 호출하여 조건을 충족할 때까지 반복합니다.
    func ensureMinimumItems(for contentTypeId: String, keyword: String, currentPage: Int, totalPages: Int) {
        
        // 현재 확보된 데이터 수 확인
        let itemCount = spotResultsByContentTypeId[contentTypeId]?.count ?? 0
        
        
        // 최소 5개 확보되지 않았고, 다음 페이지가 없는 경우 추가 설정
        if itemCount < 5, currentPage < totalPages {
            let nextPage = currentPage + 1
            searchForKeyword(with: keyword, page: nextPage) { [weak self] _ in
                self?.ensureMinimumItems(for: contentTypeId, keyword: keyword, currentPage: nextPage, totalPages: totalPages)
            }
        }
    }

 

 

전체 동작 흐름 요약

  1. fetchForKeyword 함수가 searchForKeyword로 첫 번째 페이지의 데이터를 요청합니다.
  2. searchForKeyword가 데이터를 가져와 categorizeByContentTypeId로 누적하고, completion을 통해 총 결과 수를 fetchForKeyword로 전달합니다.
  3. fetchForKeyword는 totalCount를 기반으로 전체 페이지 수를 계산한 후, ensureMinimumItems를 통해 contentTypeId별로 최소 5개 이상의 데이터가 확보될 때까지 추가 요청을 보냅니다.
  4. 모든 요청이 완료되면 spotResultsTableView.reloadData()로 데이터를 갱신합니다.