###### tags: `README`
# 📚 Digital Library
## 프로젝트 소개
환경부_디지털도서관 소장자료 목록을 받아와 GridView 로 표현한 앱 입니다.
## 📑 목차
- [🔑 핵심기술](#🔑-핵심기술)
- [📱 실행화면](#📱-실행화면)
- [🔭 프로젝트 구조](#🔭-프로젝트-구조)
- [⚙️ 적용한 기술](#⚙️-적용한-기술)
## 🔑 핵심기술
- **`UI 구현`**
- SwiftUI
- NavigationLink
- **`비동기처리`**
- async-await
- DispatchQueue
- **`라이브러리`**
- LWBNetwork
<br>
## 📱 실행화면
| 홈화면|상세화면|
|:---:|:---:|
|<img src="https://hackmd.io/_uploads/H1g1pIPVn.png" width="200">|<img src="https://hackmd.io/_uploads/Hyb628DV3.png" width="200">|
<br>
## 🔭 프로젝트 구조
- 간단한 프로젝트기에 아키텍쳐를 적용하지 않고, Model 과 View 로만 프로젝트를 구성했습니다.
## ⚙️ 적용한 기술
### ✅ LWBNetwork
- 직접 만든 네트워킹 라이브러리 입니다. 자주 사용하던 네트워킹 코드를 추상화 하여 라이브러리화 했습니다.
- `async-await` 으로 네트워크 비동기 처리가 되어있어, 이 라이브러리를 사용하는 프로젝트에서도 `async-await` 으로 비동기 처리를 해주어야 합니다.
### ✅ 당겨서 다음 페이지 불러오기
- 데이터 20개 씩 페이징을 구현하는 목적이 있었습니다.
- 스크롤이 스크롤뷰 최하단에 닿은 상태에서, 사용자가 더 아래를 보기 위해 당겼을 때를 인식하기 위해 `PreferenceKey` 를 이용하여 ScrollOffset 상태를 구독했습니다.
- 지정한 offSet 값(100) 을 넘게 당기면 다음 데이터를 불러오도록 설정 했습니다.
```swift
.onPreferenceChange(ScrollOffsetKey.self) { value in
let atBottomValue = DeviceSize.height - CGFloat(160)
let scrollOffset = atBottomValue - value
if scrollOffset > 100 {
guard shouldFetchMoreBooks == false else { return }
shouldFetchMoreBooks = true
}
}
```

### ✅ ToastMessage
- 마지막 페이지에 도달했을 때, 알림을 주기 위해 `ToastMessage`를 구현했습니다.

### ✅ Sticky Header
- 사용자에게 익숙한 UI를 제공하기 위해 상세보기에서 `Sticky Header`를 구현했습니다.

### ✅ LightMode / DarkMode
- 라이트모드와 다크모드 두 모드에서 색감차이를 보이지 않도록 `Semantic Color`를 이용했습니다.
| 홈화면|상세화면|
|:---:|:---:|
|<img src="https://hackmd.io/_uploads/B1f5WPDVn.png" width="200">|<img src="https://hackmd.io/_uploads/ryb8-Pv4n.png" width="200">|
<br>
## ⚠️ Trouble Shooting
### 🛠 이미지 크기의 다양성
- 책 표지 이미지가 없는 것에 대해서는 `PlaceholderImage` 를 만들어 적용 해주었습니다.
- 책 표지 이미지가 있어도 크기가 다양해서 width 값만 통일 시켜주고 최대 높이값에 맞춰 셀 높이를 형성했습니다.
### 🛠 async메서드 사용시 data-race 문제 발생
- 사용자가 스크롤을 지정값(100) 이상 당겼을 때, 다음 데이터를 불러오도록 설정했는데, 100을 넘은 순간이 여러번 발행 되어 수많은 요청이 동시에 발생되어 다음페이지 데이터가 아닌 같은 페이지 데이터를 중복해서 불러오는 현상이 있었습니다.
- 뮤텍스락의 역할을 해줄 `Bool` 타입 변수를 이용해서 한번의 당김에 한번의 요청만 일어나게 만들고, `asyncAfter` 메서드를 사용해서 `Bool` 타입 변수는 1초뒤에 `toggle()` 이 되도록 만들어 중복요청을 막아주었습니다.
## 🔗 References
- [Apple Docs - SwiftUI](https://developer.apple.com/documentation/swiftui/#essentials)
- [환경부_디지털도서관 소장목록 API](https://www.data.go.kr/data/3041077/openapi.do)