# Jumak
### [๐ฑ ์ฑ ์ค์นํ๋ฌ ๊ฐ๊ธฐ](https://apps.apple.com/kr/app/%EC%A3%BC%EB%A7%89/id6470310590)
> ๋ด ์ฃผ๋ณ์ ๋ง๊ฑธ๋ฆฌ ์ฃผ๋ง ์ฐพ๊ธฐ ๐ถ, Jumak
>
>
> v1.0.0 **๊ฐ๋ฐ๊ธฐ๊ฐ: 2023.09.26 ~ 2023.10.23**
>
> **์ง์์ ์ธ ์
๋ฐ์ดํธ**: 2023.10.23 ~ (์งํ์ค)
# **โจย ํ๋ก์ ํธ ์ฃผ์ ํ๋ฉด**
![แแ
ฎแแ
กแจ แ
แ
ฆแแ
ฉ แแ
ตแแ
ตแแ
ต 002](https://github.com/kimkyuchul/Jumak/assets/25146374/17b3c9a2-82e3-4eb6-96c3-e44ba259a846)
### ์ฃผ์ ๊ธฐ๋ฅ
- ์ฌ์ฉ์ ์์น ๊ธฐ๋ฐ ๋ง๊ฑธ๋ฆฌ, ํ์ , ๋ณด์ ์ฃผ๋ง ์ฐพ๊ธฐ ๊ธฐ๋ฅ ์ ๊ณต
- ์ฃผ๋ง ์ฆ๊ฒจ์ฐพ๊ธฐ, ํ์ ๋ฑ๋ก ๋ฐ ํด๋น ์ฃผ๋ง์์์ ์ํผ์๋ ์์ฑ, ์กฐํ
- ํ์ , ์ฆ๊ฒจ์ฐพ๊ธฐ, ์ํผ์๋ ๋ฑ๋ก์ ํตํ ์ฃผ๋ง ๋ฆฌ์คํธ ์กฐํ ๋ฐ ๋ค์ํ ํํฐ๋ง
# **โ๏ธย ๊ฐ๋ฐํ๊ฒฝ ๋ฐ ๊ธฐ์ ์คํ**
- Minimum Deployments: iOS 15.5
- Dependence Manager : **SPM & CocoaPod(NaverMap)**
- Swift Version: 5.8.1
- `UIKit` `MVVM` `RxSwift` `RxCocoa`
- `Codebase UI` `SnapKit`
- `DiffableDataSource` `CompositionalLayout` `PHPickerViewController` `RxDataSources` `RxGesture` `RxKeyboard`
- `CoreLocation` `NaverMap`
- `Alamofire` `RxReachability`
- `RealmSwift`
- `Firebase Crashlytics` `Firebase Push Notifications`
# **๐ฅ ๊ธฐ์ ์ ๋์ **
### Clean Architecture
**Why**
- ์ฝ 4์ฃผ๋ผ๋ ๊ธฐ๊ฐ ์์ ์ฑ์คํ ์ด ์ถ์๋ผ๋ ๋ชฉํ๋ฅผ ์ก์์ต๋๋ค. ๋์์ธ, ๊ธฐํ ๋ฑ์ด ๊ฐ๋ฐ ์ค์๋ ์์ ๋์ด, ์๋น์ค์ ๋ฐฉํฅ๊ณผ ์คํ, UI ๋ฑ์ด ๋ณ๊ฒฝ๋ ์ ์๋ค๊ณ ์๊ฐํ์ต๋๋ค. ํต์ฌ ๋น์ฆ๋์ค ๋ก์ง๊ณผ ๋ณ๊ฒฝ์ด ์์ฃผ ๋ฐ์ํ๋ ์ธ๋ถ์ ๋ ์ด์ด๋ฅผ ๋ช
ํํ๊ฒ ๋ถ๋ฆฌํ์ฌ ๊ฒฐํฉ๋๋ฅผ ๋ฎ์ถ ์ ์๋ ๊ตฌ์กฐ ์ค๊ณ๋ฅผ ๊ณ ๋ฏผํ๊ณ , Clean-Architecture์ ์ฑํํ๊ฒ ๋์์ต๋๋ค.
**Result**
- ViewModel์ ๋น์ฆ๋์ค ๋ก์ง๋ค์ UseCase๋ก, ๋คํธ์ํฌ๋ ์ธ๋ถ ํ๋ ์์ํฌ์ ๋ํ ์์ฒญ์ Repository๋ก ๋ถ๋ฆฌํด ๊ฐ ๋ ์ด์ด์ ์ญํ ์ ๋ถ๋ช
ํ๊ฒ ๋๋์ด, ์ฝ๋์ ๊ฒฐํฉ๋๋ฅผ ๋ฎ์ถ๊ณ , ์์กด์ฑ์ด Domain Layer๋ฅผ ํฅํ๋๋ก ๊ตฌํํ ์ ์์์ต๋๋ค.
- MVVM ๊ตฌ์กฐ์์ ViewModel์ด ๋ชจ๋ ๋น์ฆ๋์ค ๋ก์ง์ ์ฒ๋ฆฌํ๋ ๊ฒ์ ํผํ ์ ์์์ต๋๋ค.
- ๊ฐ๊ฐ์ ๋ ์ด์ด๋ฅผ ์ญํ ์ ๋ฐ๋ผ ๋ถ๋ฆฌํ์ฌ ๋ฐฉ๋ํ ์์ ์ฝ๋๋ฅผ ์ฝ๊ฒ ํ์
ํ ์ ์์์ต๋๋ค.
---
### MVVM + Input Output Patten
**Why**
- ๋ทฐ๊ฐ ํ๋ฉด์ ๊ทธ๋ฆฌ๋ ์ญํ ๋ง ๋ด๋นํ๊ณ , ๋น์ฆ๋์ค ๋ก์ง์ ๋ํ ๋ถ๋ฆฌ๋ฅผ ์ํด MVVM ํจํด์ ๋์
ํ์ต๋๋ค.
- iOS MVVM์ ํ์ค์ด ์๊ณ ๊ตฌํํ๋ ์ฌ๋๋ง๋ค ํจํด์ด ๋ค๋ฅผ ์ ์์ต๋๋ค. MVVM ํจํด์ ์ ํํํ๊ณ , ๋ฐ์ดํฐ ํ๋ฆ์ ๋จ๋ฐฉํฅ์ผ๋ก ๊ด๋ฆฌํ๊ธฐ ์ํด Input/Output ํจํด์ ํ์ฉํ์ต๋๋ค.
**Result**
- ํ๋ฉด์์ ์ผ์ด๋๋ ๋ชจ๋ ์ด๋ฒคํธ๋ฅผ Input์ผ๋ก ์ ์ํ์ฌ ๋น์ฆ๋์ค ๋ก์ง์ ์์ฒญํ๊ณ , ๊ฒฐ๊ณผ๋ก ๊ฐฑ์ ๋๋ ๊ฐ๋ค์ Output์ ๋ฐ์ธ๋ฉํด ๋ทฐ ์ปจํธ๋กค๋ฌ๋ Ouput์ ๋ณด๊ณ ํ๋ฉด์ ๊ทธ๋ฆฌ๋๋ก ๊ตฌํํ ์ ์์์ต๋๋ค.
- Input/Output ํจํด์ ํ์ฉํด ์ผ๊ด์ฑ ์๋ ๊ตฌ์กฐ์ ๋ทฐ๋ชจ๋ธ ์ฝ๋๋ฅผ ๋ง๋ค ์ ์์ด, ๊ฐ๋
์ฑ์ ๋์ผ ์ ์์์ต๋๋ค.
---
### RxSwift
**Why**
- ๊ฐ ๊ฐ์ฒด์์ ์ฐ์๋ escaping closure์ผ๋ก ์ธํ ์ฐ์๋ ์ฝ๋ฐฑ์ ๋ฐ์ดํฐ ํ๋ฆ์ ํผํ๊ณ ์ถ์์ต๋๋ค.
- Notification Center, GCD๋ฑ ๋ณตํฉ์ ์ด๊ณ ๋ค์ํ ๋น๋๊ธฐ API๋ฅผ ํ์ฉํ๊ธฐ ๋ณด๋จ, ์ผ๊ด๋ ๋น๋๊ธฐ ํ๋ ์์ํฌ ํ์ฉํ๊ณ ์ถ์์ต๋๋ค.
**Result**
- escaping closure๊ฐ ์๋ RxSwift์ Operator๋ฅผ ํ์ฉํ์ฌ ์ฝ๋ ์์ด ๊ฐ์ํ๊ณ , ์ดํดํ๊ธฐ ์ฌ์์ก์ต๋๋ค. ์ฝ๋์ ๋ฐฉ๋ํด์ง๊ณผ ์ค์๋ฅผ ๋ฐฉ์งํ ์ ์์์ต๋๋ค.
- ์ค๋ก์ง RxSwift๋ง ํ์ฉํด ํ๋์ ๋น๋๊ธฐ ์ฝ๋๋ก ๊ฐ๋ฐํ ์ ์์๊ณ , ๊ธฐ์กด์ ๋ณตํฉ์ ์ธ ๋น๋๊ธฐ์ฝ๋์ ๊ฐ๋
์ฑ์ ์ฌ๋ฆฌ๊ณ ์ ์ง๋ณด์๋ฅผ ์ฝ๊ฒ ๋ง๋ค ์ ์์์ต๋๋ค.
- RxTraits๋ฅผ ํ์ฉํด Thread ๊ด๋ฆฌ๋ฅผ ์ฝ๊ณ ๊ฐํธํ๊ฒ ํ ์ ์์์ต๋๋ค.
# ๐ซก TroubleShooting
### 1. **๊ฒ์ํ ์์น๊ฐ GeocodeLocation์ ํ ์ ์๋ ์ง์ญ์ผ ๊ฒฝ์ฐ ๋ฐํ์ ์ค๋ฅ ์ด์**
CLGeocoder์ ํ์ฉํ Address String Observable์ ๋ฐํํ๋ ๋ฉ์๋๋ฅผ ๊ตฌํํ๋ค. ์ ์ ์๋ ์์น์์ ์ฃผ๋ง ์ฌ ๊ฒ์ ์ `if let error = error` ๋ก ๋น ์ง๋ ๊ฑธ ํ์ธํ ์ ์์๋ค.
CLGeocoder ๊ณต์ ๋ฌธ์๋ฅผ ์ฐพ์๋ณธ ๊ฒฐ๊ณผ ํน์ ์์น์ ์ ๋ณด๋ฅผ ์ฌ์ฉํ ์ ์๋ ๊ฒฝ์ฐ ์๋ฌ๋ฅผ ์ค๋ค๋ ๊ฒ์ ํ์ธํ๋ค.
```swift
func reverseGeocodeLocation(location: CLLocation) -> Observable {
let geocoder = CLGeocoder()
return Observable.create { emitter in
geocoder.reverseGeocodeLocation(location) { placemarks, error in
if let error = error {
emitter.onError(error)
return
}
guard let placemark = placemarks?.first else {
emitter.onError(error.unsafelyUnwrapped)
return
}
let formattedAddress = self.getAddressString(from: placemark)
emitter.onNext(formattedAddress)
emitter.onCompleted()
}
return Disposables.create()
}
}
```
ViewModel์์ ์์น ์ฌ๊ฒ์ ๋ฒํผ ์ ํ ์ flatMap์ ํตํด ์์ reverseGeocodeLocation์ Output์ currentUserAddresst์ ๋ฐ์ธ๋ฉ ํ๊ณ ์์๋ค. ํด๋น ๊ตฌ๋ฌธ์์ ์๋ฌ ์ฒ๋ฆฌ๊ฐ ํ์ํ๋ค.
1๋ฒ์ฒ๋ผ ์ฒ๋ฆฌํ ๊ฒฝ์ฐ subscribe์ error๋ก ๋จ์ด์ง ์ดํ ์คํธ๋ฆผ์ด ๋๊ฒจ์ ์์น ์ฌ๊ฒ์ ๋ฒํผ ์ด๋ฒคํธ๊ฐ ๋ฐฉ์ถ๋์ง ์๋๋ค.
2๋ฒ์ฒ๋ผ flatMap์ catchAndReturn์ ํตํด Error Default๊ฐ์ ๋ณด๋ด๊ณ ์คํธ๋ฆผ์ด ๋๊ธฐ์ง ์๋๋ก ์ฒ๋ฆฌํ๋ค.
```swift
/// 1๋ฒ - ์คํธ๋ฆผ ๋๊น
input.didSelectRefreshButton
.withUnretained(self)
.flatMapLatest { owner, location in
let reverseGeocodeObservable = owner.locationUseCase.reverseGeocodeLocation(location: location.convertToCLLocation)
return reverseGeocodeObservable
}
.subscribe(onNext: { locationAddress in
output.currentUserAddress.accept(locationAddress)
}) { error in
output.currentUserAddress.accept("์ ์ ์๋ ์ง์ญ")
}
.disposed(by: disposeBag)
/// 2๋ฒ - ์คํธ๋ฆผ์ด ์ ์ง
input.didSelectRefreshButton
.withUnretained(self)
.flatMapLatest { owner, location in
let reverseGeocodeObservable = owner.locationUseCase.reverseGeocodeLocation(location: location.convertToCLLocation)
.catchAndReturn("์ ์ ์๋ ์ง์ญ์
๋๋ค.")
return reverseGeocodeObservable
}
.bind(to: output.currentUserAddress)
.disposed(by: disposeBag)
```
---
### 2. **CollectionView ํ์ด์ง ์ Cell์ Index์ Map Annotation์ด ๋์ผํ Index๋ก ์ ํ & ๋งต ์ค์ฌ ์ขํ ์ด๋ ๋ก์ง ๊ตฌํ**
![Simulator Screen Recording - iPhone 14 Pro - 2023-11-02 at 19 06 37](https://github.com/kimkyuchul/Jumak/assets/25146374/66f249df-fed7-4b22-898b-c85f4a55b43b)
์์ ์์๊ณผ ๊ฐ์ด ํ๋จ ์ฃผ๋ง ์ ๋ณด CollectionView๋ฅผ ํ์ด์ง ์ Annotation๊ณผ ๋งต ์ค์ฌ ์ขํ๊ฐ ์ด๋๋์ด์ผ ํ๋ค.
Compositional Layout์ visibleitemsinvalidationhandler์ ํ์ฉํ๋ค.
visibleItems.last?.indexPath.row(ํ์ด์ง ๋ ๋ ๋ง๋ค ํ์ฌ ์น์
ํ๋ฉด์ ํ์๋ ์์ดํ
์ indexPath)์ Subject์ ๋ด์์ viewModel์ input์ผ๋ก ํ์ฉํ๋ค.
๋๋ถ์ด transform ์์ฑ์ ์ฌ์ฉํ์ฌ ํ์ด์ง ์ ์
์ด ์ปค์ง๊ณ ์ค์ด๋๋ Carousel view ํจ๊ณผ๋ฅผ ์ ์ฉํ๋ค.
```swift
section.visibleItemsInvalidationHandler = { [weak self] (visibleItems, offset, env) in
visibleItems.forEach { item in
let intersectedRect = item.frame.intersection(CGRect(x: offset.x, y: offset.y, width: env.container.contentSize.width, height: item.frame.height))
let percentVisible = intersectedRect.width / item.frame.width
if percentVisible >= 1.0 {
if let currentIndex = visibleItems.last?.indexPath.row {
self?.visibleItemsRelay.accept(currentIndex)
}
}
let scale = 0.5 + (0.5 * percentVisible)
item.transform = CGAffineTransform(scaleX: 0.98, y: scale)
}
}
```
viewModel์์ ํ์ด์ง ์ visibleItems Index ๊ฐ์ ํ ๋๋ก Annotation๊ณผ ๋งต ์ค์ฌ ์ขํ๊ฐ ์ด๋๋๋๋ก ๊ตฌํํ๋ค.
[[iOS] Compositional Layout์ visibleitemsinvalidationhandler ํ์ฉ](https://medium.com/@kyuchul2/ios-compositional-layout%EC%9D%98-visibleitemsinvalidationhandler-%ED%99%9C%EC%9A%A9-190cde90c933)
```swift
// didScrollStoreCollectionView == visibleItemsRelay
input.didScrollStoreCollectionView
.distinctUntilChanged()
.withUnretained(self)
.bind(onNext: { owner, visibleIndex in
guard let index = visibleIndex else { return }
let store = owner.storeList[index]
output.setCameraPosition.accept((store.y, store.x))
output.selectedMarkerIndex.accept(index)
})
.disposed(by: disposeBag)
```
---
### 3. **Map์ Annotation ์ ํ ์ CollectionView ์คํฌ๋กค ์ด๋ฒคํธ ๋๋ฌธ์ ์ฌ๋ฌ Annotation์ ์ ํํ๊ณ ์ค๋ ์ด์**
![ezgif com-resize (2)](https://github.com/kimkyuchul/Jumak/assets/25146374/e52745c3-651d-497d-8cdf-ebba918b5fad)
2๋ฒ ์ด์์์ CollectionView ํ์ด์ง ์ visibleItems Index๋ฅผ ๋ฐฉ์ถํ์ฌ Cell์ ์ ํ๋ Index์ Annotation์ ๋์ผํ๊ฒ ์ ํ๋๊ฒ ํ๊ณ , ํด๋น Index๋ก ๋งต ์ค์ฌ ์ขํ๋ฅผ ์ด๋์ํค๊ฒ ๊ตฌํํ๋ค.
๋ฐ๋๋ก Map์ Annotation ์ ํ ์ ํด๋น Annotation ์ธ๋ฑ์ค๋ก CollectionView๊ฐ ์คํฌ๋กค๋์ด์ผ ํ๋๋ฐ `selectItem` ์ ๋๋ฉ์ด์
๋๋ฌธ์ ๋งต ์ค์ฌ ์ขํ๊ฐ ์ฌ๋ฌ ๋ง์ปค๋ฅผ ๋ค๋ ธ๋ค ์ค๋ ์ด์๋ฅผ ๋ฐ๊ฒฌํ๋ค.
```swift
output.selectedMarkerIndex
.distinctUntilChanged()
.withLatestFrom(output.storeList) { index, storeList in
return (index, storeList)
}
.withUnretained(self)
.bind(onNext: { owner, data in
let (selectedIndex, storeList) = data
owner.setUpMarker(selectedIndex: selectedIndex, storeList: storeList)
owner.locationView.storeCollectionView.selectItem(at: IndexPath(row: selectedIndex ?? 0, section: 0), animated: true, scrollPosition: .centeredHorizontally)
})
.disposed(by: disposeBag)
```
์คํฌ๋กค(ํ์ด์ง) ์ด๋ฒคํธ input ์ต์ ๋ฒ๋ธ์ `debounce` ๋ฅผ ๊ฑธ์๋ค. `debounce`๋ ์ผ์ ์๊ฐ๋์ ์๋ก์ด ์ด๋ฒคํธ๊ฐ ์์ ๋์๋ง ์ด๋ฒคํธ๋ฅผ ์ ๋ฌํ๋ฉฐ, ์ค๊ฐ์ ๋ค์ด์ค๋ ์ด๋ฒคํธ๋ค์ ๋ฌด์ํ๋ค.
์ด๋ฅผ ํ์ฉํ์ฌ `selectItem`์ ์คํฌ๋กค ์ ๋๋ฉ์ด์
๋ ๋ค์ด์ค๋ visibleItems Index๋ฅผ ๋ฌด์ํ๊ณ , ์คํฌ๋กค ์ ๋๋ฉ์ด์
์ด ๋๋๊ณ ๋ง์ง๋ง์ ๋ค์ด์จ visibleItems Index ๊ฐ๋ง ๋ฐ์์ ์ ํํ Annotation์ ๋งต ์ค์ฌ ์ขํ๋ก ์ด๋์ํฌ ์ ์์๋ค.
```swift
locationView.visibleItemsRelay.asObservable().debounce(.milliseconds(300), scheduler: MainScheduler.asyncInstance)
```
---
### 4. **RxReachability ๋คํธ์ํฌ ์ํ ๊ฐ์ง**
NaverMap์ ๊ฒฝ์ฐ Map์ด init๋๋ ์์ ์ ๋คํธ์ํฌ ์ฐ๊ฒฐ ์คํจ ์ ๋ฌดํ naver map error code -1020 ์๋ฌ๋ฅผ ๋ฐฉ์ถ -> ์ฆ, Map์ด ํฌํจ๋ ๋ทฐ๊ฐ ๊ทธ๋ ค์ง๊ธฐ ์ ์ ๋คํธ์ํฌ ์ํ ๊ฐ์ง๊ฐ ํ์ํ๋ค.
๋๋ถ์ด NaverMap์ ์ฌ์ฉํ๋ ์์นด, ์๊ธฐ์ ๋ฑ์ ๊ฒฝ์ฐ MapView๊ฐ ๊ทธ๋ ค์ง๊ธฐ ์ ์ ๋คํธ์ํฌ ์ฒดํฌ๋ฅผ ์งํํ๋๊ฒ์ฒ๋ผ ๋ณด์๋ค. (B์ Map์ด ํฌํจ๋์ด ์๋ค๊ณ ์น๋ฉด, A์์ ๋คํธ์ํฌ๋ฅผ ๊ฐ์งํด์ ๋คํธ์ํฌ ๋ฏธ์ฐ๊ฒฐ ์ B๋ก ์ง์
ํ๋ ๋ทฐ๋ฅผ ๋ง์๋ฒ๋ฆผ)
BaseViewController์์ Reachability์ ํ์ฉํด viewWillAppear ์ startNotifier() viewWillDisappear ์ stopNotifier() ๋๋๋ก ๊ตฌํํ๊ณ reachability?.rx.isDisconnected ์ rx.makeErrorAlert๋ฅผ ๋ฐฉ์ถํ๋๋ก ํ๋ค.
```swift
import Reachability
import RxReachability
class BaseViewController: UIViewController, BaseViewControllerProtocol, BaseBindableProtocol {
var disposeBag: DisposeBag = .init()
var reachability: Reachability?
override func viewDidLoad() {
super.viewDidLoad()
do {
reachability = try Reachability()
} catch {
print("Reachability ์๋ฌ: \(error)")
}
bindReachability())
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
try? reachability?.startNotifier()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
reachability?.stopNotifier()
}
func bindReachability() {
reachability?.rx.isDisconnected
.withUnretained(self)
.flatMap { owner, _ in
return owner.rx.makeErrorAlert(
title: "๋คํธ์ํฌ ์ฐ๊ฒฐ ์ค๋ฅ",
message: "๋คํธ์ํฌ ์ฐ๊ฒฐ์ด ๋ถ์์ ํฉ๋๋ค.",
cancelButtonTitle: "ํ์ธ"
)
}
.subscribe()
.disposed(by: disposeBag)
}
}
```
์ฑ์ ์ฒซ๋ฒ์งธ๋ก ๋ณด์ฌ์ง๋ ๋ทฐ์์ NaverMap์ ์ฌ์ฉํ๊ณ ์์๊ธฐ ๋๋ฌธ์, Splash์์ ๋คํธ์ํฌ๋ฅผ ๊ฐ์งํ์ฌ `reachability?.rx.isConnected` ์์๋ง Main์ผ๋ก ์ด๋๋๋๋ก ํ๋ค.
Main์์๋ `reachability?.rx.isReachable` ๋ก ๋คํธ์ํฌ ๋ฏธ์ฐ๊ฒฐ ๋ทฐ๋ฅผ ํธ๋ค๋งํ๋`rx.handleNetworkErrorViewVisibility`๋ฅผ ๋ฐ์ธ๋ฉํด์ ๋คํธ์ํฌ ๋ฏธ์ฐ๊ฒฐ ์ Detail๋ก ์ด๋๋์ง ๋ชปํ๋๋ก ๊ตฌํํ๋ค.
```swift
// Splash์์ ๋คํธ์ํฌ๋ฅผ ๊ฐ์งํ์ฌ, ๋คํธ์ํฌ ๋ฏธ์ฐ๊ฒฐ ์ Main์ผ๋ก ์ด๋๋์ง ๋ชปํ๋๋ก ๊ตฌํ (Main์ Map์ด ์กด์ฌํ๊ธฐ ๋๋ฌธ)
final class SplashViewController: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
reachability?.rx.isConnected
.withUnretained(self)
.subscribe(onNext: { owner, _ in
RootHandler.shard.update(.main)
})
.disposed(by: disposeBag)
}
// Main์์ ๋คํธ์ํฌ๋ฅผ ๊ฐ์งํ์ฌ, ๋คํธ์ํฌ ๋ฏธ์ฐ๊ฒฐ ์ Detail๋ก ์ด๋๋์ง ๋ชปํ๋๋ก ๊ตฌํ (Detail์ Map์ด ์กด์ฌํ๊ธฐ ๋๋ฌธ)
final class LocationViewController: BaseViewController {
override func bindReachability() {
super.bindReachability()
let isReachable = reachability?.rx.isReachable
.distinctUntilChanged()
.share()
isReachable?
.bind(to: locationView.rx.handleNetworkErrorViewVisibility)
.disposed(by: disposeBag)
isReachable?
.withUnretained(self)
.bind(onNext:{ owner, isReachable in
if !isReachable {
owner.clearMarker()
}
})
.disposed(by: disposeBag)
}
```
![ezgif com-resize (1)](https://github.com/kimkyuchul/Jumak/assets/25146374/37bf445e-8679-4f71-a51e-4ea8ad068cb2)
![ezgif com-resize](https://github.com/kimkyuchul/Jumak/assets/25146374/a21dae17-b79f-4a7d-b7b3-c5659a8649ec)
---
์ฃผ๋ง ์๋น์ค์ ์ฃผ์ฐจ๋ณ ๊ฐ๋ฐ ์ผ์ง๋ฅผ ๋ณด๊ณ ์ถ์ผ์๋ค๋ฉด!ย [๐ถ ์ฃผ๋ง ํ๋ก์ ํธ Iteration](https://www.notion.so/bee21dc07a0a46aea22f20a6a15c3615?pvs=21)
# **๐ซย ์คํ ํ๋ฉด**
๐ถ ๋ด ์์น ๊ทผ์ฒ ํน์ ๋ด๊ฐ ๊ฒ์ํ๊ณ ์ถ์ ์์น์์ ๋ง๊ฑธ๋ฆฌ ์ฃผ๋ง์ ์ฐพ์ ์ ์์ด์.
![Simulator Screen Recording - iPhone 14 Pro - 2023-11-02 at 19 05 37](https://github.com/kimkyuchul/Jumak/assets/25146374/1e0d07bf-c1fb-4c56-9dc8-c0a3b8ec666f)
![Simulator Screen Recording - iPhone 14 Pro - 2023-11-02 at 19 07 03](https://github.com/kimkyuchul/Jumak/assets/25146374/28b681be-a941-413a-8b87-04415fb43a1b)
๐ถ ๋ง๊ฑธ๋ฆฌ์ง๋์์ ์ฐพ์ ์ฃผ๋ง์ ์ ํํด ์์ธ ์ ๋ณด๋ฅผ ์ป์ผ์ธ์. ์ฃผ๋ง๊น์ง์ ๊ธธ์ฐพ๊ธฐ ๊ธฐ๋ฅ๊ณผ ์ฆ๊ฒจ์ฐพ๊ธฐ ๋ฐ ํ์ ๋ฑ๋ก์ด ๊ฐ๋ฅํฉ๋๋ค.
![Simulator Screen Recording - iPhone 14 Pro - 2023-11-02 at 19 35 00](https://github.com/kimkyuchul/Jumak/assets/25146374/a77c3536-884f-46df-8aab-1137acf0315a)
๐ถ ํด๋น ์ฃผ๋ง์์ ์์๋ ์ํผ์๋๋ฅผ ๋ฑ๋กํ์ธ์. ์ํผ์๋ ์กฐํ์ ์ญ์ ๋ ๊ฐ๋ฅํฉ๋๋ค.
![Simulator Screen Recording - iPhone 14 Pro - 2023-11-02 at 19 43 26](https://github.com/kimkyuchul/Jumak/assets/25146374/a50761bc-c5b4-4440-88ba-a8bed6455884)
![Simulator Screen Recording - iPhone 14 Pro - 2023-11-02 at 19 39 30](https://github.com/kimkyuchul/Jumak/assets/25146374/7a02d966-b4f6-4fac-bd73-f646606546db)
๐ถ ํ์ , ์ฆ๊ฒจ์ฐพ๊ธฐ, ์ํผ์๋ ๋ฑ๋ก์ ํตํด ๋๋ง์ ์ฃผ๋ง ๋ฆฌ์คํธ๋ฅผ ๋ง๋ค์ด๋ณด์ธ์. ๋ค์ํ ํํฐ ๊ธฐ๋ฅ์ผ๋ก ์ต์ ํ๋ ์ฃผ๋ง์ ์ ์ ํ ์ ์์ต๋๋ค.
![Simulator Screen Recording - iPhone 14 Pro - 2023-11-02 at 19 53 56](https://github.com/kimkyuchul/Jumak/assets/25146374/daaae176-dbe2-4b2a-9119-9e394cad87f1)
![Simulator Screen Recording - iPhone 14 Pro - 2023-11-02 at 19 46 58](https://github.com/kimkyuchul/Jumak/assets/25146374/0abca9be-ba36-4f74-827d-1c5d32b76ae1)