[내일배움캠프 13~19주차](leafLog) 5. API 호출로 필터링 검색 구현

yeseul jang·2026년 4월 10일

내일배움캠프

목록 보기
31/32

📌 API 호출로 필터링 검색 구현

이번에 실내정원용 식물 검색 기능을 구현하면서 농사로 Open API를 이용해 식물 목록 조회, 상세 조회, 필터 목록 조회, 그리고 필터 조건 기반 검색까지 구현이 목표였다.
그리고 농사로 API 가이드는 그렇게 친절하지 않았다...🫠🫠

🔎 API 구조인지 파악하기

응답은 JSON이 아니라 XML로 받아서 디코딩해야 한다.
이미 디코딩은 구현은 끝내놓아서 일단 어떤 목록이 있는지 확인 해보았다.

lightList: 광도요구 목록
grwhstleList: 생육형태 목록
lefcolrList: 잎색 목록
lefmrkList: 잎무늬 목록
flclrList: 꽃색 목록
fmldecolrList: 열매색 목록
ignSeasonList: 꽃피는 계절 목록
winterLwetList: 겨울 최저온도 목록
waterCycleList: 물주기 목록
gardenList: 실내정원용 식물 목록
gardenDtl: 실내정원용 식물 상세

이거만 가져오면 되나? 했는데 이건 그냥 필터 옵션 목록을 가져오는 API였다. 옵션을 서버에서 받아와서 버튼에서 선택지를 보여주기 위해서 필요한 항목들이었다. 여기서 부터 정신이 혼미해졌다.
저기서 가져온 옵션들에서 검색어, 검색 타입, 필터 코드들을 파라미터로 넘기면 조건에 맞는 식물 목록을 반환한다. 실제 필터링 검색은 gardenList을 다시 호출해야 완료 됐다.

🔎 흐름 보기

📘 앱 시작 또는 필터 화면 진입 시

1. 각 필터 종류별 옵션 목록 API를 호출
lightList, waterCycleList 등등 호출
2. 응답으로 받은 옵션들의 code, name 데이터를 저장
광도요구: 054001 / 낮은 광도, 054002 / 중간 광도, 054003 / 높은 광도
3. 사용자는 이 목록 중 하나를 선택
화면에는 code는 보이지 않게 하고 name으로 보여짐

📘 사용자가 검색할 때

1. 원래는 검색어와 검색 기준(식물명,학명,영명) 필수적으로 선택해야 검색이 이루어지지만 식물명으로만 검색 가능하도록 식물명 검색 강제함!

검색어: 몬스테라
검색 기준: 식물명
광도요구: 중간 광도
물주기: 주 1회

2. 선택된 필터 상태를 저장

query = "몬스테라"
searchType = sCntntsSj
light = 054002
waterCycle = 053001

3. 필터 상태 기반으로 gardenList API를 호출

{baseURL}/gardenList
?apiKey=인증키
&sType=sCntntsSj
&sText=몬스테라
&lightChkVal=054002
&waterCycleSel=053001

4. 응답으로 받은 식물 목록을 화면에 보여줌

cntntsNo: 12974
cntntsSj: 몬스테라 델리시오사
...(생략)

📘 사용자가 상세를 눌렀을 때

1. 목록에서 받은 cntntsNo를 사용해서 gardenDtl API를 호출

{baseURL}/gardenDtl?apiKey=인증키&cntntsNo=12974

2. 상세 데이터를 화면에 보여준다!!

  • 🤪🤪 길기도하다...
  • 심지어 searchType(검색 타입)의 경우 필수였는데 API 가이드 문서에는 어떤 걸 넣어야 하는지 나와있지도 않았다. 그래서 예시 사이트 코드를 받아서 VSCode로 열어서 열심히 어떻게 구현했는지 살펴봤다... 오타도 많았다.. 그치만 필터링 검색이 어떻게 이루어지는지에 이해하는데 많은 도움이 되었다.

🔎 NetworkManager 역할 분리

📘 fetchPlantList 함수

func fetchPlantList(
    keyword: String,
    searchType: PlantSearchType = .plantName,
    filterState: PlantFilterState = .init(),
    pageNo: Int = 1,
    numOfRows: Int = 10
) async throws -> [PlantSummary]

검색어, 검색 타입, 필터 상태, 페이지 정보를 받아서 gardenList를 호출

📘 fetchPlantDetail 함수

func fetchPlantDetail(contentNumber: String) async throws -> PlantDetail

목록에서 받은 cntntsNo를 이용해 상세 조회

📘 fetchFilterOptions 함수

func fetchFilterOptions(kind: PlantFilterKind) async throws -> [PlantFilterOption]

필터 종류 하나에 대한 옵션 목록을 요청

  • kind == .light라면 lightList를 호출(enum으로 나누어둠)
enum PlantFilterKind: CaseIterable, Hashable {
    case searchType
    case light
    case growthStyle
    case leafColor
    case leafPattern
    case flowerColor
    case fruitColor
    case bloomingSeason
    case winterMinTemperature
    case waterCycle
}

📘 fetchAllFilterOptions 함수

func fetchAllFilterOptions() async throws -> [PlantFilterKind: [PlantFilterOption]]

필터 종류가 많기때문에 하나씩 순차 호출하면 느리기 때문에 병렬로 호출했다.(나중에 이거 트러블 슈팅 써야함)

📌 필터 옵션 목록을 API 호출로 구현 한 이유?

사용자가 검색하기 전에 필터를 선택하려면 먼저 선택 가능한 옵션들이 필요함
예를 들어 광도요구는 API 가이드 문서를 보면 다음 코드가 있다

055001: 낮은 광도
055002: 중간 광도
055003: 높은 광도

사실 이걸 앱에서 하드코딩하고 싶었다. 하지만 꾹 참고 서버에서 받아오는 구조로 맞췄다.
(바뀔 일은 없어보이지만 혹시라도)서버가 바뀌면 바로 오류가 날 가능성이 있고 어차피 서버상에서 코드와 표시명을 함께 보내주기 때문에 그냥 타입을 만들어서 받았다

struct PlantFilterOption: Decodable, Equatable {
    let code: String
    let name: String
}

📌 HTTP 성공 == API 성공??

HTTP 성공과 API 성공이 달랐다.
그래서 HTTP 레벨 성공만 보고 화면에 데이터를 그리려고 했지만 아무것도 뜨지 않는 문제가 발생했다. 오류는 안뜨는데 결과값도 안떠서 아직 빌드가 안된건가 하고 맥북만 원망했다.
농사로 API는 HTTP 200이어도 API 내부적으로는 실패일 수 있었다 그래서 응답의 header.resultCode를 추가로 확인해야 했다.

00: 정상 처리
11: 인증키 문제
12: 인증키 일시 중지
13: 잘못된 서비스/오퍼레이션
91: 시스템 오류
private func validate(header: PlantResponseHeader) throws {
    guard header.resultCode == "00" else {
        throw NetworkError.apiError(code: header.resultCode, message: header.resultMsg)
    }
}

API 가이드 문서에 결과코드를 참고해서 이걸 기준으로 판단하는 메서드를 만들었다. 그리고 fetch해오는 함수 안에서 검사를 해주었다.

+) 추후 추가 예정

  • 페이지네이션을 고려
  • 병렬 호출로 초기 로딩 개선
  • 필터 상태(PlantFilterState)로 관리
  • 검색 필터 상태를 API 파라미터로 안전하게 변환하는 구조
profile
iOS 개발

0개의 댓글