Skip to content

๐Ÿ“ก์„œ๋ฒ„์™€ ํ†ต์‹ ํ•˜๋Š” ๋‹น๊ทผ๋งˆ์ผ“๐Ÿฅ• ๊ฐ™์€ ์•ฑ

Notifications You must be signed in to change notification settings

jryoun1/OpenMarket

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

OpenMarketApp

์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์™€์„œ ์ƒํ’ˆ์„ ๋ณด์—ฌ์ฃผ๊ณ  ๋“ฑ๋ก ์ˆ˜์ • ๋ฐ ์‚ญ์ œ๋ฅผ ํ•  ์ˆ˜ ์žˆ๋Š” ๋‹น๊ทผ๋งˆ์ผ“๐Ÿฅ• ๊ฐ™์€ ์•ฑ ๊ตฌํ˜„ํ•˜๊ธฐ

Index


๊ธฐ๋Šฅ

์ƒํ’ˆ ๋ชฉ๋ก

์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ์ƒํ’ˆ ๋ชฉ๋ก์„ ๋ฐ›์•„์˜ค๋ฉฐ LIST, GRID ํ˜•ํƒœ๋ฅผ ์„ ํƒํ•˜์—ฌ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

๋˜ํ•œ ์Šคํฌ๋กค์„ ์•„๋ž˜๋กœ ๋‚ด๋ ค์„œ ์„œ๋ฒ„์— ๋“ฑ๋ก๋œ ์ƒํ’ˆ๋“ค์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.



์ƒํ’ˆ ์ƒ์„ธ ์ •๋ณด

LIST, GRID ์ƒํƒœ์—์„œ ์ƒํ’ˆ์„ ์„ ํƒํ•˜๋ฉด ์ƒํ’ˆ์— ๋Œ€ํ•œ ์ƒ์„ธ ์ •๋ณด๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. (์™ผ์ชฝ: LIST / ์˜ค๋ฅธ์ชฝ: GRID)


์ƒํ’ˆ ๋“ฑ๋ก

์‚ฌ์šฉ์ž๋กœ๋ถ€ํ„ฐ ์ž…๋ ฅ ์‚ฌํ•ญ๋“ค์„ ์ž…๋ ฅ ๋ฐ›๊ณ , ๋“ฑ๋ก ๋ฒ„ํŠผ์„ ๋ˆŒ๋ €์„ ๋•Œ ํ•„์ˆ˜ ์ž…๋ ฅ ์‚ฌํ•ญ๋“ค์ด ์ž…๋ ฅ๋˜์ง€ ์•Š์œผ๋ฉด ์š”๊ตฌํ•œ๋‹ค.

์ „๋ถ€ ์ž…๋ ฅ๋œ ๊ฒฝ์šฐ์—๋Š” ๋“ฑ๋ก ์™„๋ฃŒ ์ฐฝ์ด ๋œฌ๋‹ค.



์ƒํ’ˆ ์ˆ˜์ •

ํ•„์ˆ˜ ์ž…๋ ฅ ์‚ฌํ•ญ๋“ค์ด ์ „๋ถ€ ์ž…๋ ฅ๋˜๊ณ  ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ผ์น˜ํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š” ์ˆ˜์ • ์™„๋ฃŒ ์ฐฝ์ด ๋œฌ๋‹ค. (์™ผ์ชฝ)

๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ์—๋Š” ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ๋‹ฌ๋ผ์„œ ์ˆ˜์ •ํ•  ์ˆ˜ ์—†๋‹ค๋Š” ์ฐฝ์ด ๋œฌ๋‹ค. (์˜ค๋ฅธ์ชฝ)


์ƒํ’ˆ ์‚ญ์ œ

๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ผ์น˜ํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š” ์‚ญ์ œ ์™„๋ฃŒ ์ฐฝ์ด ๋œฌ๋‹ค. (์™ผ์ชฝ)

๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ์—๋Š” ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ๋‹ฌ๋ผ์„œ ์‚ญ์ œํ•  ์ˆ˜ ์—†๋‹ค๋Š” ์ฐฝ์ด ๋œฌ๋‹ค. (์˜ค๋ฅธ์ชฝ)


์ถ”๊ฐ€ ๊ธฐ๋Šฅ

OpenMarketApp ์‹œ์ž‘ํ•  ๋•Œ loading animation ํ™”๋ฉด ๊ตฌํ˜„

์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ์ด๋ฏธ์ง€๋ฅผ ๋ฐ›์•„์˜ฌ ๋•Œ, ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜จ๋‹ค๋Š” ๊ฒƒ์„ ๋”์šฑ ๋ฐ˜์‘์ ์œผ๋กœ ์•Œ๋ ค์ฃผ๊ธฐ ์œ„ํ•ด์„œ skeleton view ์ ์šฉ

์‚ฌ์šฉ์ž๊ฐ€ ์›ํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š” ๋ฐ์ดํ„ฐ๋ฅผ refresh ๊ฐ€๋Šฅ



์„ค๊ณ„ ๋ฐ ๊ตฌํ˜„

ViewController ๊ตฌ์„ฑ

ViewControllers

  • LaunchScreenViewController : LaunchScreen ์ดํ›„์— Loading Animation์„ ๋ณด์—ฌ์ฃผ๊ธฐ ์œ„ํ•œ ViewController
  • ItemListViewController : LIST, GRID ํ˜•ํƒœ๋กœ ๋ณผ ์ˆ˜ ์žˆ์œผ๋ฉฐ ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ์ƒํ’ˆ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์™€์„œ ๋ณด์—ฌ์ฃผ๋Š” ViewController
  • ItemUploadViewController : ์ƒํ’ˆ์„ ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ๋Š” ViewController
  • ItemDetailViewController : ItemListViewController์—์„œ ํŠน์ • ์ƒํ’ˆ์„ ์„ ํƒํ•˜๋ฉด ์ƒ์„ธ ์ •๋ณด๋ฅผ ๋ณด์—ฌ์ฃผ๋ฉฐ, ์˜ค๋ฅธ์ชฝ ์œ„์˜ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด ์ƒํ’ˆ์„ ์ˆ˜์ •ํ•˜๊ฑฐ๋‚˜ ์‚ญ์ œํ•  ์ˆ˜ ์žˆ๋Š” ViewController

MVVM DesignPattern ์ ์šฉ

ItemListViewController & ItemListViewModel - MVVM

ViewControllers

  • Item : ์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ฌ ์ˆ˜ ์žˆ๋Š” Decodable์„ ์ฑ„ํƒํ•œ Model
  • ItemListViewModel : ์‹ค์ œ๋กœ ์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์™€์„œ Observable property์ธ itemList์— ๊ฐ’์„ ์ถ”๊ฐ€ํ•ด์„œ ItemListViewController์˜ itemTableView, itemCollectionView์— ์‚ฌ์šฉ๋˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ViewModel
  • ItemListCellViewModel : ItemTableViewCell, ItemTableViewFooterView, ItemCollectionViewCell, ItemCollectionReusableFooterView์™€ ๊ฐ™์ด ์‹ค์ œ๋กœ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ณด์—ฌ์ง€๋Š” ๋ชจ์Šต์œผ๋กœ Model์ธ Item ํƒ€์ž…์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€๊ณตํ•˜๋Š” ViewModel
  • ItemListViewController : ItemTableView, ItemCollectionView๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๊ณ , ItemListViewModel ํƒ€์ž…์ธ viewModel property๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์œผ๋ฉฐ, viewModel์˜ itemList ์˜ ๊ฐ’์ด ๋ณ€ํ•  ๋•Œ ๋งˆ๋‹ค ํ™”๋ฉด๋„ ๊ทธ์— ๋งž๊ฒŒ ์—…๋ฐ์ดํŠธ๋ฅผ ํ•˜๊ฒŒ ๋œ๋‹ค.
  • Observable : ์ œ๋„ค๋ฆญ ํด๋ž˜์Šค์ด๋ฉฐ ๊ด€์ฐฐํ•˜๊ณ  ์‹ถ์€ ๋Œ€์ƒ์— ๋Œ€ํ•ด์„œ ViewModel์—์„œ Observable type์œผ๋กœ ์„ ์–ธํ•œ ๋’ค, ๊ฐ’์ด ๋ฐ”๋€Œ์—ˆ์„ ๋•Œ ์ฒ˜๋ฆฌ์— ๋Œ€ํ•ด์„œ View์—์„œ bind๋ฅผ ์ž‘์„ฑํ•ด์ฃผ๋ฉด ๋œ๋‹ค.
ItemUploadViewController & ItemUploadViewModel - MVVM


  • ItemToUpload : ์„œ๋ฒ„์— ๋ฐ์ดํ„ฐ๋ฅผ ์—…๋กœ๋“œ ํ•  ์ˆ˜ ์žˆ๋Š” Encodable์„ ์ฑ„ํƒํ•œ Model
  • ItemUploadViewModel : ์‚ฌ์šฉ์ž์˜ ์‚ฌ์ง„ ์„ ํƒ๊ณผ, ์ƒํ’ˆ์— ๋Œ€ํ•œ ๋‚ด์šฉ๋“ค์„ ์ œ๋Œ€๋กœ ์ž…๋ ฅํ–ˆ๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•œ Observable property๋“ค๊ณผ, ItemDetailViewController์—์„œ ์ˆ˜์ • ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜์—ฌ ์˜จ ๊ฒฝ์šฐ์— ๊ธฐ์กด์˜ ๊ฐ’๋“ค์„ ํ™”๋ฉด์— ํ‘œ์‹œํ•ด์ฃผ๊ธฐ ์œ„ํ•œ Observable property๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค. ๋˜ํ•œ ์‹ค์ œ๋กœ ์ƒํ’ˆ์„ HTTP Method์ธ POST, PATCH๋ฅผ ํ•˜๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค.
  • ItemUploadCollectionViewCell : ์‚ฌ์šฉ์ž๊ฐ€ ์‚ฌ์ง„์„ ์„ ํƒํ–ˆ์„ ๋•Œ, ์„ ํƒํ•œ ์‚ฌ์ง„๋“ค์„ ๋ณด์—ฌ์ฃผ๊ธฐ ์œ„ํ•ด์„œ CollectionView๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์ด๋•Œ Custom Cell๋กœ์„œ ์‚ฌ์šฉ๋œ๋‹ค.
  • ItemUploadCollectionReusableHeaderView : collectionView์˜ headerView๋กœ ์‚ฌ์ง„ ์„ ํƒ์„ ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฒ„ํŠผ์„ ๋„ฃ์–ด์ค€๋‹ค.
  • ItemUploadViewController : ImageCollectionView๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์œผ๋ฉฐ, ItemUploadViewModel ํƒ€์ž…์ธ viewModel property๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค. viewModel์˜ Observable property ๊ฐ’๋“ค์ด ๋ณ€ํ•  ๋•Œ ๋งˆ๋‹ค bind ๋ฉ”์„œ๋“œ์—์„œ ์ฒ˜๋ฆฌํ•œ ๋Œ€๋กœ ํ™”๋ฉด๋„ ์—…๋ฐ์ดํŠธ ๋œ๋‹ค. ItemListViewController ์—์„œ + ๋ฒ„ํŠผ์„ ํ†ตํ•ด์„œ ๋“ค์–ด์˜ค๋Š” ๊ฒฝ์šฐ์™€ ItemDetailViewController ์—์„œ ์ˆ˜์ • ๋ฒ„ํŠผ์„ ํ†ตํ•ด์„œ ๋“ค์–ด์˜ค๋Š” ๊ฒฝ์šฐ์— ๋„ค๋น„๊ฒŒ์ด์…˜ ๋ฐ”์™€ ๋ฒ„ํŠผ์˜ ํƒ€์ดํ‹€์ด ๋‹ค๋ฅด๊ณ , ์„œ๋ฒ„๋กœ์˜ ๋ฐ์ดํ„ฐ ์ „์†ก ๋ฐฉ๋ฒ•์ด POST, PATCH๋กœ ๋‹ค๋ฅด๋‹ค.
ItemDetailViewController & ItemDetailViewModel - MVVM


  • Item : ์ƒ์„ธ ์ •๋ณด๋ฅผ ๋ณด์—ฌ์ฃผ๊ธฐ ์œ„ํ•ด์„œ Decodable์„ ์ฑ„ํƒํ•œ Model
  • ItemToUpload : ์ˆ˜์ •์„ ์„ ํƒํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š” ์‚ฌ์šฉํ•˜๋Š” Encodable์„ ์ฑ„ํƒํ•œ Model
  • ItemToDeletion : ์‚ญ์ œ๋ฅผ ์„ ํƒํ•˜๋Š” ๊ฒฝ์šฐ ์‚ฌ์šฉํ•˜๋Š” Encodable์„ ์ฑ„ํƒํ•œ Model
  • ItemDetailViewModel : ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ์ƒํ’ˆ์˜ ์ •๋ณด๋ฅผ ๋ฐ›์•„์„œ ํ™”๋ฉด์— ๋ณด์—ฌ์ฃผ๊ธฐ ์œ„ํ•œ Observable property๋“ค์„ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค. ๋˜ํ•œ ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ค๊ณ , ์‚ญ์ œ ์š”์ฒญ์„ ๋ณด๋‚ด๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค.
  • ItemDetailCollectionViewCell : ์ƒํ’ˆ ์ด๋ฏธ์ง€๋“ค์ด ์—ฌ๋Ÿฌ ๊ฐœ์ธ ๊ฒฝ์šฐ์—๋Š” collectionView์™€ page control์„ ์‚ฌ์šฉํ•ด์„œ ์ขŒ/์šฐ๋กœ ์Šคํฌ๋กคํ•˜์—ฌ ์ด๋ฏธ์ง€๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ItemDetailViewController : ImageCollectionView๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์œผ๋ฉฐ, ItemDetailViewModel ํƒ€์ž…์ธ viewModel property๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค. viewModel์˜ Observable property๋“ค์˜ ๊ฐ’์— ๋ณ€ํ™”๊ฐ€ ์žˆ์„ ๋•Œ, bind ๋ฉ”์„œ๋“œ์— ์˜ํ•ด์„œ ์ฒ˜๋ฆฌํ•œ๋Œ€๋กœ ํ™”๋ฉด์ด ์—…๋ฐ์ดํŠธ ๋œ๋‹ค.

View๊ฐ„์˜ Data ์ „์†ก - Delegate ์‚ฌ์šฉ

ViewControllers

ViewController์™€ ViewController, View ๊ฐ„์˜ ๋ฐ์ดํ„ฐ๋ฅผ backward๋กœ ๋ฐ›๊ธฐ ์œ„ํ•ด์„œ delegate protocol์„ ๊ตฌํ˜„ํ•˜์—ฌ ์‚ฌ์šฉํ•˜์˜€๋‹ค.


์—ญํ•  ๋ถ„๋ฐฐ

view ๊ด€๋ จ

class ์—ญํ• 
LaunchScreenViewController LaunchScreen ์ดํ›„์— Loading animation์„ ๋ณด์—ฌ์ฃผ๊ณ , ItemListViewController ๋กœ ํ™”๋ฉด ์ „ํ™˜
ItemListViewController - LIST, GRID ํ˜•ํƒœ๋กœ ์ƒํ’ˆ๋“ค์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค
- ๋‚ด๋น„๊ฒŒ์ด์…˜ ์˜ค๋ฅธ์ชฝ + ๋ฒ„ํŠผ์„ ๋ˆŒ๋Ÿฌ์„œ ์ƒํ’ˆ์„ ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ๋‹ค
- ์ƒํ’ˆ์„ ๋ˆŒ๋Ÿฌ์„œ ์ƒํ’ˆ์˜ ์ƒ์„ธ ์ •๋ณด๋ฅผ ๋ณผ ์ˆ˜ ์žˆ๋‹ค
ItemUploadViewController - ItemListViewController ์—์„œ + ๋ฒ„ํŠผ์„ ๋ˆŒ๋Ÿฌ์„œ ์ƒํ’ˆ ๋“ฑ๋ก ํ™”๋ฉด์œผ๋กœ ์˜ฌ ์ˆ˜ ์žˆ๋‹ค
- ItemDetailViewController ์—์„œ ์ˆ˜์ • ๋ฒ„ํŠผ์„ ๋ˆŒ๋Ÿฌ์„œ ์ƒํ’ˆ ๋“ฑ๋ก ํ™”๋ฉด์œผ๋กœ ์˜ฌ ์ˆ˜ ์žˆ๋‹ค
- + ๋ฒ„ํŠผ์œผ๋กœ ๋“ค์–ด์˜ค๋ฉด ๋ชจ๋“  ์ •๋ณด ์ƒˆ๋กญ๊ฒŒ ์ž…๋ ฅ, ์ˆ˜์ • ๋ฒ„ํŠผ์œผ๋กœ ๋“ค์–ด์˜ค๋ฉด ๊ธฐ์กด์˜ ์ •๋ณด๊ฐ€ ์ž…๋ ฅ๋˜์–ด์ ธ ์žˆ๋‹ค
- ์ƒํ’ˆ์˜ ์ •๋ณด๋ฅผ ์ž…๋ ฅํ•˜๊ณ  ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ๋‹ค
ItemDetailViewController - ItemListViewController ์—์„œ ์ƒํ’ˆ์„ ๋ˆŒ๋Ÿฌ์„œ ์ƒ์„ธ ์ •๋ณด ํ™”๋ฉด์œผ๋กœ ์˜ฌ ์ˆ˜ ์žˆ๋‹ค
- ๋“ฑ๋ก๋œ ์‚ฌ์ง„์ด ์—ฌ๋Ÿฌ ์žฅ์ด๋ฉด ์ขŒ/์šฐ๋กœ ์Šคํฌ๋กคํ•ด์„œ ๋ณผ ์ˆ˜ ์žˆ๋‹ค
- ๋‚ด๋น„๊ฒŒ์ด์…˜ ์˜ค๋ฅธ์ชฝ ๋ฒ„ํŠผ์„ ๋ˆŒ๋Ÿฌ์„œ ์ƒํ’ˆ์„ ์ˆ˜์ •ํ•˜๊ฑฐ๋‚˜ ์‚ญ์ œํ•  ์ˆ˜ ์žˆ๋‹ค
- ์ˆ˜์ •์„ ๋ˆ„๋ฅด๋ฉด ItemUploadViewController ๋กœ ์ด๋™ํ•œ๋‹ค
- ์‚ญ์ œ๋ฅผ ๋ˆ„๋ฅด๋ฉด ์‚ฌ์šฉ์ž๋กœ๋ถ€ํ„ฐ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅ๋ฐ›๊ณ  ์ผ์น˜ํ•˜๋ฉด ์ƒํ’ˆ์„ ์‚ญ์ œํ•œ๋‹ค
ItemListViewModel - ์„œ๋ฒ„์—์„œ page์— ๋งž๋Š” ItemList๋ฅผ fetchํ•˜๋Š” ๋ฉ”์„œ๋“œ๊ฐ€ ์žˆ๊ณ , ๊ฒฐ๊ณผ๋ฅผ Observable property์— Item ํ˜•ํƒœ๋กœ ์ €์žฅํ•œ๋‹ค
- paging๊ณผ ๊ด€๋ จ๋œ property๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค
ItemListCellViewModel - ๋„คํŠธ์›Œํ‚น์„ ํ†ตํ•ด ๋ฐ›์•„์˜จ Item ํƒ€์ž…์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์‹ค์ œ ํ™”๋ฉด์— ๋ณด์—ฌ์ง€๋Š” ๊ฐ’์œผ๋กœ ๊ฐ€๊ณตํ•œ๋‹ค
ItemUploadViewModel - ItemListViewController ๋กœ๋ถ€ํ„ฐ ์˜ค๋Š” ๊ฒฝ์šฐ์—๋Š” ๋น„์–ด์žˆ๋Š” ํ™”๋ฉด์„ ์ œ๊ณตํ•œ๋‹ค
- ItemListViewController ๋กœ๋ถ€ํ„ฐ ์˜ค๋Š” ๊ฒฝ์šฐ์—๋Š” ์ƒํ’ˆ์„ HTTP Method ์ค‘์—์„œ POST๋กœ ๋ณด๋‚ธ๋‹ค
- ItemDetailViewController ๋กœ๋ถ€ํ„ฐ ์˜ค๋Š” ๊ฒฝ์šฐ์—๋Š” ๊ธฐ์กด์˜ ์ƒํ’ˆ์˜ ์ •๋ณด๋ฅผ ํ™”๋ฉด์— ์ œ๊ณตํ•œ๋‹ค
- ItemDetailViewController ๋กœ๋ถ€ํ„ฐ ์˜ค๋Š” ๊ฒฝ์šฐ์—๋Š” ๋ณ€๊ฒฝ๋œ ๋ถ€๋ถ„๋งŒ HTTP Method ์ค‘์—์„œ PATCH๋กœ ๋ณด๋‚ธ๋‹ค
- ๋‘ ๊ฒฝ์šฐ ๋ชจ๋‘ ์‚ฌ์šฉ์ž์˜ ์ž…๋ ฅ์— ๋Œ€ํ•œ ์กฐ๊ฑด์„ ๊ฒ€์‚ฌํ•˜๊ณ  ๋งŒ์กฑํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ error message์™€ ํ•จ๊ป˜ ๋นจ๊ฐ„์ƒ‰์œผ๋กœ ํ‘œ์‹œํ•ด์ค€๋‹ค
ItemDetailViewModel - ItemListViewController ๋กœ๋ถ€ํ„ฐ ์‚ฌ์šฉ์ž๊ฐ€ ์„ ํƒํ•˜๋Š” ๊ฒฝ์šฐ ํ•ด๋‹น Item ์ •๋ณด๋ฅผ ๋„คํŠธ์›Œํ‚น์„ ํ†ตํ•ด ๋ฐ›์•„์™€์„œ ์ œ๊ณตํ•œ๋‹ค
- ์ˆ˜์ • ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋Š” ๊ฒฝ์šฐ ItemToUpload ํƒ€์ž…์œผ๋กœ Item ์ •๋ณด๋ฅผ ๋ณ€ํ™˜ํ•ด์ฃผ๋Š” ํ•จ์ˆ˜๋ฅผ ๊ฐ€์ง„๋‹ค
- ์‚ญ์ œ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋Š” ๊ฒฝ์šฐ ItemToDeletion ํƒ€์ž…์„ ์ƒ์„ฑํ•˜์—ฌ DELETE๋ฅผ ์š”์ฒญํ•œ๋‹ค.
Observable - ViewModel์˜ ๋ฐ์ดํ„ฐ๋“ค ์ค‘์—์„œ ๊ด€์ฐฐ์ด ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ ํƒ€์ž…์— observer ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•œ ์ œ๋„ค๋ฆญ ํƒ€์ž… ํด๋ž˜์Šค
- ViewModel์—์„œ Observable ํ”„๋กœํผํ‹ฐ๋“ค์€ ViewController์—์„œ bind ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด์„œ ๋ณ€ํ™”๊ฐ€ ์žˆ์„ ๋•Œ ์ฒ˜๋ฆฌ๋ฅผ ๊ตฌํ˜„ํ•ด์ค€๋‹ค

Network ๊ด€๋ จ

class/struct/enum ์—ญํ• 
APIRequest - makeRequest(), parseResponse() ํ•จ์ˆ˜๋ฅผ ๊ฐ€์ง€๋Š” protocol
APIReqeustLoader - APIRequest protocol ์ฑ„ํƒ
- URLSession์„ ์ƒ์„ฑ์ž๋ฅผ ํ†ตํ•ด์„œ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค
- loadAPIRequest() ๋ผ๋Š” ๋„คํŠธ์›Œํ‚น์„ ํ•˜๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ๊ฐ€์ง„๋‹ค
HTTPMethod - HTTP Method๋ฅผ ๊ฐ€์ง€๋Š” enum
OpenMarketAPI - OpenMarketAPI baseURL ๊ฐ€์ง€๋Š” enum
GetItemListAPIRequest - APIReqeust protocol ์ฑ„ํƒํ•˜๋ฉฐ ItemList์— ๋Œ€ํ•ด์„œ GET ์š”์ฒญ ์‹œ ์‚ฌ์šฉ
GetItemAPIRequest - APIReqeust protocol ์ฑ„ํƒํ•˜๋ฉฐ Item์— ๋Œ€ํ•ด์„œ GET ์š”์ฒญ ์‹œ ์‚ฌ์šฉ
GetImageAPIRequest - APIReqeust protocol ์ฑ„ํƒํ•˜๋ฉฐ Image๋ฐ์ดํ„ฐ์— ๋Œ€ํ•ด์„œ GET ์š”์ฒญ ์‹œ ์‚ฌ์šฉ
PostItemAPIRequest - APIReqeust protocol ์ฑ„ํƒํ•˜๋ฉฐ POST ์š”์ฒญ ์‹œ ์‚ฌ์šฉ
PatchItemAPIRequest - APIReqeust protocol ์ฑ„ํƒํ•˜๋ฉฐ PATCH ์š”์ฒญ ์‹œ ์‚ฌ์šฉ
DeleteItemAPIRequest - APIReqeust protocol ์ฑ„ํƒํ•˜๋ฉฐ DELETE ์š”์ฒญ ์‹œ ์‚ฌ์šฉ

Error ๊ด€๋ จ

enum/protocol ์—ญํ• 
OpenMarketError OpenMarketApp ๋‚ด๋ถ€์—์„œ ๋ฐœ์ƒํ•˜๋Š” Error๋“ค์„ ์ •์˜ํ•œ enum
AlertString Alert๋ฅผ ๋ณด์—ฌ์ค„ ๋•Œ ์‚ฌ์šฉ๋˜๋Š” String์„ ์ •์˜ํ•œ enum
AlertShowable Alert๋ฅผ ๋ณด์—ฌ์ฃผ์–ด์•ผํ•˜๋Š” ViewController๊ฐ€ ์ฑ„ํƒํ•˜๋Š” protocol

Utilities

class/struct/enum ์—ญํ• 
CustomNumberFormatter ViewModel์—์„œ View์— ๋ณด์—ฌ์งˆ ๋•Œ ์ˆ˜๋Ÿ‰, ๊ธˆ์•ก ๋“ฑ์— ๋Œ€ํ•ด์„œ ์ž๋ฆฌ์ˆ˜์— ๋”ฐ๋ฅธ ์ฝค๋งˆ(,)๋ฅผ ๋„ฃ๊ธฐ ์œ„ํ•œ struct
OpenMarketViewString OpenMarket ์•ฑ ๋‚ด๋ถ€์˜ View๋“ค์—์„œ ๋ณด์—ฌ์ง€๋Š” String๋“ค์„ ์ •์˜ํ•œ enum
(ItemListViewString, ItemUploadViewString, ItemDetailViewString)
ImageCacheManager ์ด๋ฏธ์ง€ ์บ์‹ฑ์„ ์œ„ํ•ด์„œ ์‚ฌ์šฉํ•˜๋Š” singleton class
ISO4217_CurrencyCode ISO4217; ํ†ตํ™”์˜ ์ด๋ฆ„์„ ์ •์˜ํ•˜๊ธฐ ์œ„ํ•œ ํ†ตํ™”์˜ ์ข…๋ฅ˜๋ฅผ ๊ฐ€์ง€๋Š” enum

SkeletionView

SkeletonView third-party ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์‚ฌ์šฉ

SkeletonView ์‚ฌ์šฉ ์ด์œ  ๋ฐ ๋ฐฉ๋ฒ•

Skeleton View : Loading ๋˜๋Š” ๋™์•ˆ์˜ ์‹ค์ œ๋กœ ๋ณด์—ฌ์งˆ View์™€ ๋น„์Šทํ•œ ํ˜•ํƒœ์˜ View๋ฅผ ์˜๋ฏธ

  • ์‚ฌ์šฉ ์ด์œ  : ์‚ฌ์šฉ์ž๊ฐ€ ๋ฐ์ดํ„ฐ๊ฐ€ ๋กœ๋”ฉ๋˜๋Š” ๋™์•ˆ์— ๋นˆ ํŽ˜์ด์ง€๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ๊ฒƒ๋ณด๋‹ค skeletion view๋ฅผ ํ†ตํ•ด ๋กœ๋”ฉ๋˜๊ณ  ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ๋ณด์—ฌ์ฃผ๋ฉด App์ด ๋” ๋ฐ˜์‘์ ์ด๊ณ  ๋น ๋ฅด๋‹ค๊ณ  ๋Š๋ผ๊ธฐ ๋•Œ๋ฌธ์— ์‚ฌ์šฉํ•œ๋‹ค. ๋˜ํ•œ ๊ธฐ์กด์˜ loading spinner๋ณด๋‹ค๋Š” ์ปจํ…์ธ ์˜ ๋Œ€๋žต์ ์ธ ํ˜•ํƒœ๋ฅผ ๋ณด์—ฌ์ฃผ๊ธฐ ๋•Œ๋ฌธ์— ์‚ฌ์šฉ์ž๋กœ ํ•˜์—ฌ๊ธˆ App์ด ์ง„ํ–‰๋˜๊ณ  ์žˆ๋‹ค๊ณ  ๋Š๋ผ๊ฒŒ ํ•œ๋‹ค.
  • ์‚ฌ์šฉ ๋ฐฉ๋ฒ• : SkeletionView ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋ฅผ ์‚ฌ์šฉ
    • UITableView ์— ์ ์šฉํ•˜๋ฏ€๋กœ, ๊ธฐ์กด์˜ UITableViewDatasource ๋Œ€์‹ ์— SkeletonTableViewDataSource ๋ฅผ ์ฑ„ํƒํ•˜์—ฌ ๊ตฌํ˜„
    • Storyboard ์™€ ์ฝ”๋“œ๋กœ ๊ตฌํ˜„๋œ View๋“ค ์ค‘์—์„œ SkeletionView๋ฅผ ์ ์šฉํ•  View์— isSkeletonable ์†์„ฑ์„ true๋กœ ์„ค์ •
    • ๋ณด์—ฌ์งˆ ์‹œ์ ๊ณผ ์‚ฌ๋ผ์งˆ ์‹œ์ ์—์„œ SkeletonView๋ฅผ ๋ณด์—ฌ์ฃผ๊ณ  ์ˆจ๊ธฐ๋Š” ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ
  • ๊ณ ๋ คํ•  ๋งŒํ•œ ์‚ฌํ•ญ
    • SkeletionView ์‚ฌ์šฉํ•  ํ™”๋ฉด : OpenMarketApp์—์„œ๋Š” ๋งจ ์ฒ˜์Œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์™€์„œ ๋ณด์—ฌ์ฃผ๋Š” ํ™”๋ฉด์—์„œ์˜ ๋กœ๋”ฉ์ด ์žˆ์„ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ํ•ด๋‹น ํ™”๋ฉด์—๋งŒ SkeletionView๋ฅผ ์ ์šฉ
    • SkeletionView๊ฐ€ ๋งค๋ฒˆ ๋ณด์—ฌ์ ธ์•ผํ•˜๋Š”๊ฐ€์— ๋Œ€ํ•œ ๊ณ ๋ฏผ : ๋งจ ์ฒ˜์Œ ์ดํ›„์— ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค์‹œ ๋ฐ›์•„์˜ค๊ฑฐ๋‚˜ ํ•  ๋•Œ๋Š” SkeletionView๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ๊ทธ ์ด์œ ๋Š” ๋งจ ์ฒ˜์Œ์— ์•ฑ์— ๋“ค์–ด์™”์„ ๋•Œ์—๋Š” ์–ด๋– ํ•œ ์ปจํ…์ธ ๊ฐ€ ๋‚˜์˜ฌ์ง€ ๋ชจ๋ฅด๊ธฐ ๋•Œ๋ฌธ์— ์ปจํ…์ธ ์˜ ํ˜•ํƒœ๋ฅผ ์˜ˆ์ƒํ•  ์ˆ˜ ์žˆ๋Š” SkeletionView๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์‚ฌ์šฉ์„ฑ์„ ๋†’์—ฌ์ค„ ์ˆ˜ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์•ฑ์„ ์‚ฌ์šฉํ•˜๋Š” ๋„์ค‘์— ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค์‹œ ๋‹ค์šด ๋ฐ›๋Š” ๊ฒฝ์šฐ์—๋Š” ์ปจํ…์ธ ๊ฐ€ ์–ด๋– ํ•œ ํ˜•ํƒœ์ธ์ง€ ์ด๋ฏธ ์•Œ๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๊ตณ์ด SkeletionView๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ธฐ์กด์˜ ๋ฐ์ดํ„ฐ๋“ค์„ ๊ฐ€๋ฆฌ์ง€ ์•Š๊ณ , indicator๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋กœ๋”ฉ์ค‘์ž„์„ ํ‘œ์‹œํ•˜๋„๋ก ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

Pagination(=Paging)

ScrollViewDidScroll() ๋ฉ”์„œ๋“œ ์‚ฌ์šฉ

Pagination ์‚ฌ์šฉ ์ด์œ  ๋ฐ ๋ฐฉ๋ฒ•
  • ์‚ฌ์šฉ ์ด์œ  : tableview๋‚˜ collectionview๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ, ์„œ๋ฒ„์—์„œ ๋งŽ์€ ๋ฆฌ์ŠคํŠธ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ฌ ๋•Œ ํ•œ ๋ฒˆ์— ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ค๋ฉด ๊ธฐ๊ธฐ์™€ ์„œ๋ฒ„์—๊ฒŒ ๋ถ€๋‹ด์ด ๋˜๋ฏ€๋กœ ์ผ์ •๋Ÿ‰์˜ ๋ฆฌ์ŠคํŠธ ๋ฐ์ดํ„ฐ๋งŒ ๋ณด๋‚ด์ฃผ๊ณ  ์Šคํฌ๋กค์„ ๋‚ด๋ ธ์„ ๋•Œ ์ถ”๊ฐ€๋กœ ๋ฆฌ์ŠคํŠธ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋‚ด์ฃผ๊ธฐ ์œ„ํ•ด์„œ ์‚ฌ์šฉ

  • ๊ตฌํ˜„ ๋ฐฉ๋ฒ• : ScrollYOffset ์„ ํ™œ์šฉํ•˜์—ฌ ๊ตฌํ˜„

    • 1๏ธโƒฃ scrollView์˜ contentOffset.y ๊ตฌํ•˜๊ธฐ
      contentOffset.y = scroll์ด ๋˜๊ธฐ ์ „ scrollview์˜ scrollview.frame.size.height๋ฅผ ๊ธฐ์ค€์œผ๋กœ ํ™”๋ฉด์—์„œ์˜ scrollview์˜ y ์ขŒํ‘œ

    • 2๏ธโƒฃ scrollView์˜ contentSize.height ๊ตฌํ•˜๊ธฐ
      contentSize.height = scrollView ๋‚ด content์˜ ํฌ๊ธฐ (ํ˜„์žฌ tableview์— ๋“ค์–ด์žˆ๋Š” cell๋“ค์˜ ๊ฐœ์ˆ˜์— ๋”ฐ๋ฅธ ๋†’์ด)

    • 3๏ธโƒฃ scrollView์˜ frame.height ๊ตฌํ•˜๊ธฐ
      frame.height = ํ™”๋ฉด์˜ ์„ธ๋กœ ๋†’์ด

    • 1๏ธโƒฃ > 2๏ธโƒฃ - 3๏ธโƒฃ ์กฐ๊ฑด์„ ๋งŒ์กฑํ•˜๋Š” ๊ฒฝ์šฐ์— paging ์ˆ˜ํ–‰


์ด๋ฏธ์ง€ ์—ฌ๋Ÿฌ ์žฅ ์„ ํƒํ•˜๊ธฐ

BSImagePicker third-party ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์‚ฌ์šฉ

BSImagePicker ์‚ฌ์šฉ ์ด์œ  ๋ฐ ๋ฐฉ๋ฒ•

BSImagePicker : iOS์—์„œ ์—ฌ๋Ÿฌ ์žฅ์˜ ์‚ฌ์ง„์„ ์„ ํƒํ•˜๊ธฐ ์œ„ํ•ด์„œ ์‚ฌ์šฉํ•˜๋Š” ์„œ๋“œํŒŒํ‹ฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

  • ์‚ฌ์šฉ ์ด์œ  : Swift์—์„œ๋Š” UIImagePickerController ๋ฅผ ์ œ๊ณตํ•˜์—ฌ ์ด๋ฏธ์ง€๋ฅผ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ UIImagePickerController ์—์„œ ์—ฌ๋Ÿฌ ์žฅ์„ ์„ ํƒํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ์ด๋ฏธ์ง€๋ฅผ ํ•œ ์žฅ์”ฉ ์—ฌ๋Ÿฌ ๋ฒˆ ์„ ํƒํ•ด์•ผํ–ˆ๋‹ค. ์ด๋Š” ์—ฌ๋Ÿฌ ์žฅ์˜ ์‚ฌ์ง„์„ ์„ ํƒํ•  ๋•Œ ๋งค๋ฒˆ UIImagePickerController ๋ฅผ ๋„์šฐ๋Š” ๊ฒƒ์€ ๋น„ํšจ์œจ์ ์ด๋ผ๊ณ  ์ƒ๊ฐํ–ˆ๋‹ค. ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ iOS14 ์ด์ƒ์—์„œ๋ถ€ํ„ฐ๋Š” ์ ์šฉ๊ฐ€๋Šฅํ•œ PHPickerViewController ๊ฐ€ ์žˆ์ง€๋งŒ iOS 14 ์ด์ƒ์—์„œ๋งŒ ์‚ฌ์šฉ๊ฐ€๋Šฅํ•ด์„œ ์™ธ๋ถ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ๋กœ ๊ฒฐ์ •ํ–ˆ๋‹ค.
  • ๊ธฐ๋Šฅ : ์—ฌ๋Ÿฌ ์žฅ ์„ ํƒ / ์ „์ฒดํ™”๋ฉด preview / ์•จ๋ฒ” ์ „ํ™˜ / images, Live Photo, video ์„ ํƒ ๊ฐ€๋Šฅ
  • ์‚ฌ์šฉ ๋ฐฉ๋ฒ•
    • Cocoapods์— BSImagePicker ์ถ”๊ฐ€
    • Info.plist ์—์„œ ์‚ฌ์ง„ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์‚ฌ์šฉ ๊ถŒํ•œ ์š”์ฒญ ์ž‘์„ฑ
    • BSImagePicker ๋ฅผ import ํ•˜๊ณ , ImagePickerController ์ƒ์„ฑ
    • presentImagePicker() ๋ฉ”์„œ๋“œ์—์„œ finished ๋ถ€๋ถ„ ๊ตฌํ˜„
      ์ด๋•Œ ์ด๋ฏธ์ง€๋Š” PHAsset ํƒ€์ž…์ด๋ฏ€๋กœ, ์ด๋ฅผ UIImage ํƒ€์ž…์œผ๋กœ ๋ณ€ํ™˜ํ•˜๊ธฐ ์œ„ํ•ด์„œ PHImageManager ์‚ฌ์šฉ

ํ†ตํ™” ์ข…๋ฅ˜ ์„ ํƒํ•˜๊ธฐ

PickerView๋ฅผ ์‚ฌ์šฉ

PickerView ์‚ฌ์šฉ ์ด์œ  ๋ฐ ๋ฐฉ๋ฒ•

PickerView : ์‚ฌ์šฉ์ž๊ฐ€ ํ•˜๋‚˜ ์ด์ƒ์˜ ์„ ํƒ์ง€ ๊ฐ€์šด๋ฐ ํ•˜๋‚˜๋ฅผ ์„ ํƒํ•ด์•ผํ•˜๋Š” ๊ฒฝ์šฐ ์‚ฌ์šฉํ•˜๋Š” view

  • ์‚ฌ์šฉ ์ด์œ  : OpenMarketApp ๋‚ด๋ถ€์—์„œ ์‚ฌ์šฉ์ž๊ฐ€ ์ƒํ’ˆ์„ ๋“ฑ๋กํ•˜๊ฑฐ๋‚˜, ์ˆ˜์ •ํ•˜๋Š” ํ™”๋ฉด์—์„œ ํ†ตํ™”๋ฅผ ์„ ํƒํ•  ๋•Œ pickerview๋ฅผ ์‚ฌ์šฉํ•˜์˜€๋‹ค. ์ด์œ ๋Š” ํ†ตํ™”์˜ ๊ฒฝ์šฐ์—๋Š” ์„œ๋ฒ„์—์„œ ISO4217๋ฅผ ๋”ฐ๋ฅด๊ธฐ ๋•Œ๋ฌธ์— ์ •ํ•ด์ง„ ํ†ตํ™” ๋ฌธ์ž์—ด์„ ์„œ๋ฒ„๋กœ ์ „์†กํ•ด์•ผํ•œ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์‚ฌ์šฉ์ž๊ฐ€ ISO4217์— ํ•ด๋‹นํ•˜๋Š” ํ†ตํ™”๋ช…์„ ๋ชจ๋ฅผ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์‹ค์ œ๋กœ ์‚ฌ์šฉํ•˜๋Š” ํ†ตํ™”์˜ ์ด๋ฆ„์„ ์˜์–ด๋กœ ํ‘œ๊ธฐํ•˜๊ณ , ํ•ด๋‹น ํ†ตํ™”๋ฅผ ์„ ํƒํ–ˆ์„ ๋•Œ ISO4217์— ๋Œ€์‘ํ•˜๋Š” ํ†ตํ™”๋ช…์ด ๋ฐ˜ํ™˜๋˜๋„๋ก ๊ตฌํ˜„ํ•˜์˜€๋‹ค.
  • ์‚ฌ์šฉ ๋ฐฉ๋ฒ•
    • ISO4217์„ ๋”ฐ๋ฅด๋Š” ํ†ตํ™”์˜ ์ข…๋ฅ˜๋ฅผ enum์œผ๋กœ ์ƒ์„ฑ
    • UIPickerView ๋ฅผ UITextfield ์˜ inputView๋กœ ์ •์˜
    • pickerView์˜ delegate, dataSource๋ฅผ ์„ค์ •ํ•˜๊ณ  ํ•„์š”ํ•œ ๋ฉ”์„œ๋“œ๋ฅผ ๊ตฌํ˜„

Page Control ์‚ฌ์šฉํ•ด์„œ ์ด๋ฏธ์ง€ ๋„˜๊ธฐ๊ธฐ

CollectionView & PageControl ์‚ฌ์šฉํ•ด์„œ ๊ตฌํ˜„

PageControl ์‚ฌ์šฉํ•œ ๋ถ€๋ถ„ ๋ฐ ๋ฐฉ๋ฒ•

PageControl : ํŽ˜์ด์ง•์„ ํ•˜๋Š” ํ™”๋ฉด์ด ์žˆ์„ ๋•Œ, ํŽ˜์ด์ง€๋ฅผ ํ‘œ์‹œํ•˜๋Š” ์ˆ˜ํ‰์œผ๋กœ ์ด๋ค„์ง„ ์ ๋“ค์„ ํ‘œ์‹œํ•˜๋Š” ์ปจํŠธ๋กค

  • ์‚ฌ์šฉ ๋ถ€๋ถ„ : ์ƒํ’ˆ ์ƒ์„ธ ์ •๋ณด ํ™”๋ฉด์—์„œ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์ด๋ฏธ์ง€๊ฐ€ ์žˆ์„ ๋•Œ ์ด๋ฅผ ์ขŒ/์šฐ๋กœ ์Šคํฌ๋กค ํ•  ๋•Œ ํŽ˜์ด์ง€๋ฅผ ํ‘œ์‹œํ•˜๊ธฐ ์œ„ํ•ด์„œ ์‚ฌ์šฉ
  • ์‚ฌ์šฉ ๋ฐฉ๋ฒ•
    • pageControl์„ View์— ์ƒ์„ฑ
    • collectionView์—์„œ ๊ฐ€๋กœ๋กœ ์Šคํฌ๋กค์„ ํ•˜๋Š”๋ฐ ์ด๋•Œ pageControl์„ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ showHorizontalScrollIndicator ์„ false ์„ค์ •
    • collectionView์˜ isPagingEnable ์„ true๋กœ ์„ค์ •
    • scrollViewDidScroll ํ•จ์ˆ˜์—์„œ pageControl์˜ currentPage ๋ฅผ ์„ค์ •ํ•˜๋Š” ๋กœ์ง ์ž‘์„ฑ

Trouble shooting

1๏ธโƒฃ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅํ•˜๋„๋ก ๋ชจ๋ธ ๋ฐ ๋„คํŠธ์›Œํ‚น ๋‹ด๋‹น ํƒ€์ž…์˜ ๊ตฌํ˜„

2๏ธโƒฃ ์‹œ์ž‘ ํ™”๋ฉด์—์„œ ์• ๋‹ˆ๋ฉ”์ด์…˜ ๋„ฃ๊ธฐ

3๏ธโƒฃ ItemListViewController์—์„œ tableview์™€ collectionview์—์„œ ์Šคํฌ๋กค์˜ ๋ฒ„๋ฒ…์ž„๊ณผ ์ด๋ฏธ์ง€๊ฐ€ ์ œ๋Œ€๋กœ ๋“ค์–ด๊ฐ€์ง€ ์•Š๋Š” ๋ฌธ์ œ

4๏ธโƒฃ ์ด๋ฏธ์ง€ ํ•œ ๋ฒˆ์— ์—ฌ๋Ÿฌ ๊ฐœ ์„ ํƒํ•˜๋Š” ๋ฐฉ๋ฒ•

5๏ธโƒฃ ItemListViewController์—์„œ์˜ ์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์™€์„œ reloadํ•˜๋Š” ๊ฒฝ์šฐ


๋‹จ์œ„ ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅํ•˜๋„๋ก ๋ชจ๋ธ ๋ฐ ๋„คํŠธ์›Œํ‚น ๋‹ด๋‹น ํƒ€์ž…์˜ ๊ตฌํ˜„

  • ๋ฌธ์ œ ์ƒํ™ฉ

    • ๋„คํŠธ์›Œํฌ ์—†์ด๋„ ํ…Œ์ŠคํŠธ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋„๋ก ๋„คํŠธ์›Œํ‚น ํƒ€์ž…์„ ๊ตฌํ˜„ํ•˜๊ณ  ์‹ถ์—ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ง€๋‚œ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ๋„คํŠธ์›Œํ‚น ํƒ€์ž…์„ Singleton์„ ์‚ฌ์šฉํ•ด์„œ ๋‚ด๋ถ€์— ๋ชจ๋“  fetch, patch, post, delete์™€ ๊ฐ™์€ ๋ฉ”์„œ๋“œ๋ฅผ ๊ฐ€์ง€๋„๋ก ๊ตฌํ˜„ํ–ˆ์—ˆ๋Š”๋ฐ, singleton์„ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋ฉด ์•ฑ ๋‚ด๋ถ€์—์„œ ๊ณ„์†ํ•ด์„œ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์ฐจ์ง€ํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ด๋Ÿฌํ•œ ๋ฐฉ๋ฒ•๋ณด๋‹ค๋Š” ๋„คํŠธ์›Œํ‚น์ด ํ•„์š”ํ•  ๋•Œ๋งˆ๋‹ค ์ƒ์„ฑํ•˜์—ฌ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์„ ๊ณ ๋ฏผํ•ด๋ณด๊ฒŒ ๋˜์—ˆ๋‹ค. ๋˜ํ•œ API๊ฐ€ ์ถ”๊ฐ€๋˜์—ˆ์„ ๋•Œ SOLID์˜ OCP์— ๋”ฐ๋ผ์„œ ๊ธฐ์กด์˜ ํŒŒ์ผ์ด๋‚˜, ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•˜์ง€ ์•Š๊ณ  ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ตฌํ˜„ํ•˜๋ ค๊ณ  ๋…ธ๋ ฅํ•ด๋ณด์•˜๋‹ค.
  • ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

    • ๋จผ์ € ๋„คํŠธ์›Œํฌ๊ฐ€ ์—†์ด๋„ ํ…Œ์ŠคํŠธ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋„๋กํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” MockURLProtocol ์„ ์‚ฌ์šฉํ•ด์„œ ์˜์กด์„ฑ์„ ์ฃผ์ž…์‹œ์ผœ์„œ ํ…Œ์ŠคํŠธ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋„๋ก ๊ตฌํ˜„ํ•˜์˜€๋‹ค. ๊ทธ๋ฆฌ๊ณ  OCP๋ฅผ ์ง€ํ‚ค๊ธฐ ์œ„ํ•ด์„œ ๊ฐ๊ฐ์˜ APIRequest๋ฅผ ๊ฐ๊ฐ์˜ ํŒŒ์ผ๋กœ ๋งŒ๋“ค๊ณ , APIRequest protocol์„ ์ƒ์„ฑํ•˜๊ณ  APIRequstLoader ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑํ•˜์—ฌ API์— ๋”ฐ๋ผ์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ตฌํ˜„ํ•˜์˜€๋‹ค.

      protocol APIRequest {
          associatedtype RequestDataType
          associatedtype ResponseDataType
          
          func makeRequest(from data: RequestDataType) throws -> URLRequest
          func parseResponse(data: Data) throws -> ResponseDataType
      }
      
      final class APIRequestLoader<T: APIRequest> {
          let apiRequest: T
          let urlSession: URLSession
      		
        	// ์˜์กด์„ฑ์„ ์ฃผ์ž…ํ•ด์ฃผ๋Š” ๋ถ€๋ถ„์ด๊ธฐ๋„ ํ•˜๋ฉฐ, URLSession์˜ ๊ธฐ๋ณธ ๊ฐ’์€ .shared๋กœ ๋Œ€์ž…ํ•˜์—ฌ ์ฃผ์ž…ํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ์—๋Š” 
        	// ๊ธฐ๋ณธ์ ์œผ๋กœ URLSession.shared๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก ํ•œ๋‹ค. 
          init(apiReqeust: T, urlSession: URLSession = .shared) {
              self.apiRequest = apiReqeust
              self.urlSession = urlSession
          }
          
          func loadAPIReqeust(requestData: T.RequestDataType,
                              completion: @escaping (T.ResponseDataType?, OpenMarketError?) -> Void) {
             // networking
          }
      }
      
      // ๊ฐ๊ฐ์˜ APIRequest ์ค‘์— ํ•˜๋‚˜์ธ GetItemListAPIRequest
      struct GetItemListAPIRequest: APIRequest {
          func makeRequest(from page: Int) throws -> URLRequest {
              guard var components = URLComponents(string: OpenMarketAPI.baseURL) else {
                  throw OpenMarketError.failToMakeURL
              }
              components.path += "items/\(page)"
              return URLRequest(url: components.url!)
          }
          
          func parseResponse(data: Data) throws -> ItemList {
              return try JSONDecoder().decode(ItemList.self, from: data)
          }
      }

์‹œ์ž‘ ํ™”๋ฉด์—์„œ ์• ๋‹ˆ๋ฉ”์ด์…˜ ๋„ฃ๊ธฐ

  • ๋ฌธ์ œ ์ƒํ™ฉ
    • ์‹ค์ œ ๋‹น๊ทผ ๋งˆ์ผ“ ์•ฑ์„ ๋ณด๋ฉด ์•ฑ์ด ์‹œ์ž‘๋  ๋•Œ indicator๊ฐ€ ๋Œ์•„๊ฐ€๊ณ  ์žˆ๋‹ค. ๋”ฐ๋ผ์„œ LauchScreen์—์„œ loading indicator๋ฅผ startํ•˜๋ ค๊ณ  ํ–ˆ์œผ๋‚˜ ์‹คํ–‰๋˜์ง€ ์•Š์•˜๋‹ค.
  • ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•
    • ์œ„์˜ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ ์›์ธ์€ LauchScreen์—์„œ๋Š” Custom Class ๋ฐ Attributes ๋ฐฐ์น˜๊ฐ€ ๋ถˆ๊ฐ€ํ•˜๋‹ค. ์ฆ‰, LauchScreen์€ staticํ•œ ์ƒํƒœ์ด๊ธฐ ๋•Œ๋ฌธ์— ๋‹น๊ทผ ๋งˆ์ผ“ ์•ฑ์ฒ˜๋Ÿผ animation์ด ๋™์ž‘ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” gif ํ˜•ํƒœ์˜ image๋ฅผ ๋„ฃ์–ด์„œ LaunchScreen์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‚˜, Inital ViewController๋ฅผ Launch Screen ์ดํ›„์— ๋„์šฐ๋ฉด์„œ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์ถ”๊ฐ€ํ•˜๊ณ  ์ดํ›„์— ๋‹ค๋ฅธ ViewController๋กœ ๋„˜์–ด๊ฐ€๋Š” ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๋ฒˆ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” LaunchScreenViewController ๋ฅผ initial ViewController๋กœ ์‚ฌ์šฉํ•˜์—ฌ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์ถ”๊ฐ€ํ•˜๊ณ  ItemListViewController ๋กœ ๋„˜์–ด๊ฐ€๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ ๊ตฌํ˜„ํ•˜์˜€๋‹ค.

ItemListViewController์—์„œ tableview์™€ collectionview์—์„œ ์Šคํฌ๋กค์˜ ๋ฒ„๋ฒ…์ž„๊ณผ ์ด๋ฏธ์ง€๊ฐ€ ์ œ๋Œ€๋กœ ๋“ค์–ด๊ฐ€์ง€ ์•Š๋Š” ๋ฌธ์ œ

  • ๋ฌธ์ œ ์ƒํ™ฉ
    • ItemListViewController ์—์„œ๋Š” ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ์ƒํ’ˆ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์™€์„œ tableview์™€ collectionview์— ๋ณด์—ฌ์ฃผ๊ฒŒ ๋œ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ด๋•Œ paging์„ ์—ฌ๋Ÿฌ ๋ฒˆ ํ•˜๊ณ  ์Šคํฌ๋กค์„ ์œ„ ์•„๋ž˜๋กœ ๋น ๋ฅด๊ฒŒ ์›€์ง์ด๊ฑฐ๋‚˜ ํ•  ๋•Œ ์Šคํฌ๋กค์ด ๋ฒ„๋ฒ…์ด๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. ๋˜ํ•œ ์ด๋ฏธ์ง€๊ฐ€ ์‹ค์ œ ์ƒํ’ˆ์˜ ์ด๋ฏธ์ง€์™€๋Š” ๋‹ค๋ฅธ ์ด๋ฏธ์ง€๊ฐ€ ๋“ค์–ด๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ๋„ ๋ฐœ์ƒํ•œ๋‹ค.
  • ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•
    • ์ด๋Ÿฌํ•œ ๋ฌธ์ œ์˜ ์›์ธ์€ ์ƒํ’ˆ์˜ ์ด๋ฏธ์ง€๋ฅผ ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ๋ฐ›์•„์˜ค๋Š” ๊ณผ์ •์— ์‚ฌ์šฉ์ž๊ฐ€ ์Šคํฌ๋กค์„ ์›€์ง์ด๊ฒŒ ๋˜๋ฉด, ๋‹ค๋ฅธ cell์— ๋ฐ›์•„์˜จ ์ด๋ฏธ์ง€๊ฐ€ ๋“ค์–ด๊ฐ€๋Š” ๊ฒฝ์šฐ๊ฐ€ ์ƒ๊ธฐ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ๋˜ํ•œ ์Šคํฌ๋กค์ด ๋ฒ„๋ฒ…๊ฑฐ๋ฆฌ๋Š” ๋ฌธ์ œ ์—ญ์‹œ๋„ ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์™€์„œ imageview์— ๋„ฃ๋Š” ๋น„๋™๊ธฐ ๊ณผ์ •์—์„œ ์Šคํฌ๋กค์„ ๋‚ด๋ฆฌ๋Š” ๊ฒƒ ์†๋„๋ณด๋‹ค ๋งŽ์€ ์‹œ๊ฐ„์ด ์†Œ์š”๋˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.
    • ๋”ฐ๋ผ์„œ ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ๋ฐฉ๋ฒ•์—๋Š” cellForItemAt() , cellForRowAt() ๋ฉ”์„œ๋“œ์—์„œ image๋ฅผ ๋„ฃ์œผ๋ ค๊ณ  ํ•˜๋Š” cell์˜ index์™€ ํ˜„์žฌ dequeueํ•œ reusableCell์˜ index๋ฅผ ๋น„๊ตํ•ด์„œ ๊ฐ™์€ ๊ฒฝ์šฐ์—๋งŒ ๋„ฃ์–ด์ฃผ๋„๋ก ํ•˜๋ฉด ๋œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋˜ ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์€ ์ด๋ฏธ์ง€๋ฅผ ์บ์‹ฑํ•˜์—ฌ ํ•œ ๋ฒˆ ๋‹ค์šด ๋ฐ›์€ ์ด๋ฏธ์ง€์˜ ๊ฒฝ์šฐ์—๋Š” ๋ฐ”๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜์—ฌ์„œ ์Šคํฌ๋กค์ด ๋ฒ„๋ฒ…๊ฑฐ๋ฆฌ๊ฑฐ๋‚˜, ์ด๋ฏธ์ง€๊ฐ€ ์ž˜๋ชป๋“ค์–ด๊ฐ€๋Š” ๊ฒฝ์šฐ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค.

์ด๋ฏธ์ง€ ํ•œ ๋ฒˆ์— ์—ฌ๋Ÿฌ ๊ฐœ ์„ ํƒํ•˜๋Š” ๋ฐฉ๋ฒ•

  • ๋ฌธ์ œ ์ƒํ™ฉ

    • ItemUploadViewController ์—์„œ๋Š” ์‚ฌ์šฉ์ž๋กœ๋ถ€ํ„ฐ ์ด๋ฏธ์ง€๋ฅผ ์ตœ์†Œ 1๊ฐœ๋ถ€ํ„ฐ ์ตœ๋Œ€ 5๊ฐœ๊นŒ์ง€ ๋ฐ›์„ ์ˆ˜ ์žˆ์–ด์•ผํ•œ๋‹ค. ๊ทธ๋ ‡๊ธฐ ์œ„ํ•ด์„œ๋Š” ์‚ฌ์šฉ์ž๋Š” ์‚ฌ์ง„์ฒฉ์œผ๋กœ๋ถ€ํ„ฐ ์ด๋ฏธ์ง€๋ฅผ ์—ฌ๋Ÿฌ ๊ฐœ ์„ ํƒํ•  ์ˆ˜ ์žˆ์–ด์•ผํ•œ๋‹ค. ๋ฌผ๋ก  UIImagePickerController ์™€ delegate ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์ด๋ฏธ์ง€๋ฅผ ํ•œ ๊ฐœ์”ฉ ์—ฌ๋Ÿฌ๋ฒˆ ์„ ํƒํ•˜์—ฌ ๊ณ ๋ฅผ ์ˆ˜ ์žˆ๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ์ตœ์†Œ 1๊ฐœ ์ตœ๋Œ€ 5๊ฐœ์˜ ์‚ฌ์ง„์„ ์˜ฌ๋ฆด ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ImagePickerController๋ฅผ ์ ๊ฒŒ๋Š” 1๋ฒˆ๋ถ€ํ„ฐ, ์ตœ๋Œ€ 5๋ฒˆ ํ˜น์€ ์‚ฌ์ง„์ด ๋ง˜์— ๋“ค์ง€ ์•Š์•„์„œ ์‚ญ์ œํ–ˆ๋‹ค๊ฐ€ ๋‹ค๋ฅธ ์‚ฌ์ง„์„ ๊ณ ๋ฅด๊ฒŒ ๋œ๋‹ค๋ฉด ๊ณ„์†ํ•ด์„œ ImagePickerController๋ฅผ ๋„์›Œ์•ผํ•œ๋‹ค. ๋”ฐ๋ผ์„œ ํ•œ ๋ฒˆ์— ์—ฌ๋Ÿฌ์žฅ์„ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์„ ๊ณ ๋ฏผํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค.
  • ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

    • ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ๋ฐฉ๋ฒ•์—๋Š” iOS 14์ด์ƒ์—์„œ๋Š” ์ƒˆ๋กœ ์†Œ๊ฐœ๋œ PHPickerViewController ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์—ฌ๋Ÿฌ ์žฅ์˜ ์‚ฌ์ง„์„ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ iOS 14์ด์ƒ์˜ ์ ์œ ์œจ์€ 2021๋…„ 6์›” ๊ธฐ์ค€์œผ๋กœ iPhone์€ 85%, iPad๋Š” 79%์ด๋ฏ€๋กœ iOS 14 ์ด์ „์˜ ๋ฒ„์ „์—์„œ๋„ ์•ฑ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์„ ์ฑ„ํƒํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์ด๋ฏธ์ง€๋ฅผ ์—ฌ๋Ÿฌ ๊ฐœ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋Š” ์„œ๋“œํŒŒํ‹ฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ฑ„ํƒํ•˜์—ฌ ์‚ฌ์šฉํ•˜์˜€๋‹ค. BSImagePicker, OpalImagePicker, RMImagePicker ๋“ฑ ์—ฌ๋Ÿฌ ์„œ๋“œ ํŒŒํ‹ฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์žˆ์—ˆ์ง€๋งŒ ๊ทธ ์ค‘์—์„œ Apple์˜ ๊ธฐ๋ณธ ์‚ฌ์ง„์ฒฉ๊ณผ ๋น„์Šทํ•œ UI๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์–ด์„œ ์‚ฌ์šฉ์ž๋“ค์ด ์ด์งˆ๊ฐ์„ ๋Š๋ผ์ง€ ์•Š๊ณ  ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” BSImagePicker๋ฅผ ์ฑ„ํƒํ•˜์˜€๋‹ค. ๋”ฐ๋ผ์„œ BSImagePicker๋ฅผ pod์— ์ถ”๊ฐ€ํ•˜๊ณ  ์‚ฌ์šฉํ•ด์ฃผ์—ˆ๋‹ค.

    • ์ด๋•Œ ImagePickerController()๋ฅผ ๋„์šฐ๊ธฐ ์œ„ํ•ด์„œ ํ˜„์žฌ ๋ณด์ด๋Š” ํ™”๋ฉด์—์„œ์˜ ์ตœ์ƒ์œ„ ViewController๋ฅผ ์•Œ์•„์•ผ ํ•  ํ•„์š”๊ฐ€ ์žˆ์—ˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด์„œ UIWindow๋ฅผ extensionํ•˜์—ฌ currentViewController๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ๊ตฌํ˜„ํ•˜์˜€๋‹ค.

      extension UIWindow {
        	// iOS 13 ์ด์ „์—์„œ๋Š” keyWindow๊ฐ€ ์žˆ์ง€๋งŒ, 13 ์ดํ›„์˜ ๋ฒ„์ „์€ scene์˜ ๊ฐœ๋…์ด ์ƒ๊ธฐ๋ฉด์„œ 
        	// ์—ฌ๋Ÿฌ ๊ฐœ์˜ scene ์ค‘์—์„œ keyWindow๋ฅผ ์ฐพ๊ธฐ ์œ„ํ•ด์„œ ์•„๋ž˜์™€ ๊ฐ™์ด filter๋ฅผ ํ•ด์ฃผ๋Š” ์ž‘์—… ํ•„์š”
          static var key: UIWindow? {
              if #available(iOS 13, *) {
                  return UIApplication.shared.windows.filter{ $0.isKeyWindow }.first
              }
              else {
                  return UIApplication.shared.keyWindow
              }
          }
          
          public var currentViewController: UIViewController? {
              return self.getCurrentViewController(from: self.rootViewController)
          }
          
          public func getCurrentViewController(from viewController: UIViewController?) -> UIViewController? {
              if let navigationController = viewController as? UINavigationController {
                  return self.getCurrentViewController(from: navigationController.visibleViewController)
              }
              else if let tabBarController = viewController as? UITabBarController {
                  return self.getCurrentViewController(from: tabBarController.selectedViewController)
              }
              else {
                  guard let currentViewController = viewController?.presentedViewController else {
                      return viewController
                  }
                  return self.getCurrentViewController(from: currentViewController)
              }
          }
      }

ItemListViewController์—์„œ์˜ ์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์™€์„œ reloadํ•˜๋Š” ๊ฒฝ์šฐ

  • ๋ฌธ์ œ ์ƒํ™ฉ

    • ItemUploadViewController์—์„œ ์ƒํ’ˆ ๋“ฑ๋ก, ์ƒํ’ˆ ์ˆ˜์ •์ด ์ด๋ค„์ง€๊ฑฐ๋‚˜, ItemDetailViewController ์—์„œ ์ƒํ’ˆ ์‚ญ์ œ๊ฐ€ ์ผ์–ด๋‚˜๋Š” ๊ฒฝ์šฐ์—๋Š” ์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฏ€๋กœ ์ƒˆ๋กญ๊ฒŒ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์™€์„œ ํ™”๋ฉด์— ๋ณด์—ฌ์ฃผ์–ด์•ผํ•œ๋‹ค. ๋˜ํ•œ ์‚ฌ์šฉ์ž๊ฐ€ ๊ฐ€์žฅ ์ตœ๊ทผ์˜ ์ƒํ’ˆ ๋ฐ์ดํ„ฐ์˜ ๋ณ€๊ฒฝ์„ ์•Œ๊ธฐ ์œ„ํ•ด์„œ refresh๋ฅผ ํ•˜๋ ค๊ณ  ํ•  ๋•Œ์—๋„ ๋ฐ์ดํ„ฐ๋ฅผ ์ƒˆ๋กญ๊ฒŒ ๋ฐ›์•„์™€์„œ ํ™”๋ฉด์— reloadํ•ด์ฃผ์–ด์•ผํ•œ๋‹ค.

    • ๋”ฐ๋ผ์„œ viewWillAppear() ๋ฉ”์„œ๋“œ์—์„œ ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ค๊ณ  ํ™”๋ฉด์„ reloadํ•˜๋Š” ๋กœ์ง์„ ๋„ฃ์—ˆ๋Š”๋ฐ + ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๊ณ  ๋“ฑ๋ก์€ ํ•˜์ง€ ์•Š๊ณ  ๋’ค๋กœ ๊ฐ€๋Š” ๊ฒฝ์šฐ๋‚˜, ๋น„๋ฐ€๋ฒˆํ˜ธ์˜ ์˜ค๋ฅ˜๋กœ ์ธํ•ด์„œ ์ˆ˜์ •์ด๋‚˜ ์‚ญ์ œ๊ฐ€ ๋˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ์—๋„ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ค๊ณ  reloadํ•˜๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค. ๋˜ํ•œ ์•„๋ž˜์™€ ๊ฐ™์ด ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ–ˆ๋”๋‹ˆ, 1ํŽ˜์ด์ง€๋ถ€ํ„ฐ reload๋ฅผ ํ•˜์ง€๋งŒ ํ™”๋ฉด์— ๋ณด์ด๋Š” page๋Š” 1ํŽ˜์ด์ง€๋ถ€ํ„ฐ ๋ณด์—ฌ์ง€์ง€ ์•Š๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค.

      override func viewWillAppear(_ animated: Bool) {
          super.viewWillAppear(animated)
          viewModel.currentPage = 1
          viewModel.fetchData(page: viewModel.currentPage)
      }
  • ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

    • ์‹ค์ œ๋กœ ์ƒํ’ˆ์ด ์„œ๋ฒ„์— ๋“ฑ๋ก, ์ˆ˜์ •, ์‚ญ์ œ๊ฐ€ ๋˜๋Š” ๊ฒฝ์šฐ์—๋งŒ ItemListViewController ์—์„œ ์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ค๊ณ  ํ™”๋ฉด์„ reloadํ•˜๋„๋ก Notification์„ isItemChange ๋ณ€์ˆ˜์˜ ๊ฐ’์˜ ๋ฐ”๊ฟ”์ฃผ๋„๋ก ์ˆ˜์ •ํ•˜์˜€๋‹ค. ๊ทธ๋ฆฌ๊ณ  1ํŽ˜์ด์ง€๋ถ€ํ„ฐ reload๋ฅผ ํ•ด์ฃผ๊ธฐ ์œ„ํ•ด์„œ dataSource์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฐ์—ด์˜ ๋ชจ๋“  ๊ฐ’์„ ์ง€์›Œ์ฃผ๋Š” ๋ฐฉ์‹์œผ๋กœ ์ˆ˜์ •ํ•˜์˜€๋‹ค.

      override func viewWillAppear(_ animated: Bool) {
          if viewModel.isItemChanged {
              viewModel.currentPage = 1
              viewModel.itemList.value?.removeAll()
              viewModel.fetchData(page: viewModel.currentPage)
              viewModel.isItemChanged = false
          }
      }
      
      // ์‹ค์ œ๋กœ ๋“ฑ๋ก, ์ˆ˜์ •, ์‚ญ์ œ๊ฐ€ ์ด๋ค„์ง€๋Š” ๊ฒฝ์šฐ์—๋Š” Notification์„ ํ†ตํ•ด์„œ viewModel.isItemChange ๊ฐ’ ๋ณ€๊ฒฝ 
      @objc private func didReceiveItemDataChanged(_ notification: Notification) {
          viewModel.isItemChanged = true
      }

๊ด€๋ จ ํ•™์Šต ๋‚ด์šฉ

MVVM Design Pattern

  • ์ ์šฉ ์ด์œ  : ๊ธฐ์กด์˜ ๋‚ ์”จ ์•ฑ ํ”„๋กœ์ ํŠธ ์—์„œ๋Š” MVC ๋””์ž์ธ ํŒจํ„ด์„ ์‚ฌ์šฉํ–ˆ์—ˆ๋‹ค. ์ด๋•Œ ๋‹ค์–‘ํ•œ ๋กœ์ง๋“ค์„ Controller์—์„œ ์ฒ˜๋ฆฌํ•˜๋‹ค๋ณด๋‹ˆ Controller๊ฐ€ massiveํ•˜๊ฒŒ ๋˜์—ˆ๊ณ , ์ด๋ฅผ ํ•ด๊ฒฐํ•  ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณด๋‹ค๊ฐ€ MVVM ๋””์ž์ธ ํŒจํ„ด์— ๋Œ€ํ•ด์„œ ๊ณต๋ถ€ํ•˜๊ฒŒ ๋˜์—ˆ๊ณ  ์ด๋ฒˆ OpenMarketApp ํ”„๋กœ์ ํŠธ์— ์ ์šฉํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค.
  • ์ ์šฉ ๊ฒฐ๊ณผ : MVVM ๋””์ž์ธ ํŒจํ„ด์€ MVC ๋””์ž์ธ ํŒจํ„ด๊ณผ๋Š” ๋‹ค๋ฅด๊ฒŒ View์— ์—…๋ฐ์ดํŠธ ํ•  ๋ฐ์ดํ„ฐ๋ฅผ ViewModel์„ ํ†ตํ•ด์„œ ์ฒ˜๋ฆฌํ•จ์œผ๋กœ์จ ๊ธฐ์กด์— Controller๊ฐ€ ์ฒ˜๋ฆฌ๋Š” ํ•˜๋Š” ๊ฒƒ๋“ค์„ ๋ฐฉ์ง€ํ•ด Controller๊ฐ€ ์ปค์ง€๋Š” ๊ฒƒ์„ ๋ง‰์„ ์ˆ˜ ์žˆ์—ˆ๊ณ , ๋˜ํ•œ view์™€ model์‚ฌ์ด์˜ ๋…๋ฆฝ์„ฑ์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์—ˆ๋‹ค.

์—ญํ• 

  • M(Model)
    • ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ๋ฅผ ์ •์˜
    • ViewModel์ด Model์„ ์†Œ์œ ํ•˜๊ณ  ๊ฐ€๊ณตํ•˜์—ฌ View์— ๊ฐฑ์‹ 
  • V(View)
    • UIView, UIViewController๊ฐ€ MVVM์˜ View์— ์†ํ•จ
    • ๋ง ๊ทธ๋Œ€๋กœ ๋ณด์—ฌ์ฃผ๋Š” ์ž‘์—…๊ณผ ์œ ์ €์˜ ์ธํ„ฐ๋ž™์…˜์„ ๋ฐ›๋Š” ์—ญํ• 
    • ์œ ์ €์˜ ์ธํ„ฐ๋ž™์…˜์„ ViewModel์—๊ฒŒ ๋ช…๋ นํ•˜๊ณ , ViewModel์ด ์—…๋ฐ์ดํŠธ ์š”์ฒญํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด์—ฌ์ค€๋‹ค
  • VM(ViewModel)
    • View์— ์‹ค์ œ๋กœ ๋ณด์—ฌ์งˆ ๋ฐ์ดํ„ฐ๋กœ Model์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€๊ณตํ•˜๋Š” ์—ญํ• 
    • View๊ฐ€ ์œ ์ € ์ธํ„ฐ๋ ‰์…˜์„ ๋ณด๋‚ด์ฃผ๋ฉด ์ด์— ์•Œ๋งž๋Š” ์ž‘์—…์„ ์ฒ˜ํ•˜๊ณ  View๋ฅผ ๋ณ€๊ฒฝ

๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ

  • View๊ฐ€ ๋ณ€ํ•˜๊ฒŒ ๋  ๋•Œ, Model๊ณผ UI์š”์†Œ(View) ๊ฐ„์˜ ์‹ฑํฌ๋ฅผ ๋งž์ถฐ์ฃผ๋Š” ๊ฒƒ
  • View์™€ ๋กœ์ง์ด ๋ถ„๋ฆฌ๋˜์–ด ์žˆ์–ด๋„ ํ•œ ์ชฝ์ด ๋ฐ”๋€Œ๋ฉด, ๋‹ค๋ฅธ ์ชฝ๋„ ์—…๋ฐ์ดํŠธ๊ฐ€ ์ด๋ฃจ์–ด์ ธ ๋ฐ์ดํ„ฐ์˜ ์ผ๊ด€์„ฑ์„ ์œ ์ง€ํ•˜๋„๋ก ํ•ด์ฃผ๋Š” ๊ฒƒ์ด๋‹ค.
  • View๊ฐ€ ์ž์‹ ์ด ๋ณ€ํ™”ํ•˜๊ธฐ ์œ„ํ•ด์„œ ๊ฐ์ง€ํ•ด์•ผ ํ•  ํ•„์š”๊ฐ€ ์žˆ๋Š” ViewModel์˜ ์š”์†Œ๋ฅผ ๊ฐ์ง€ ๋Œ€์ƒ์œผ๋กœ ์„ค์ •ํ•˜๊ณ , ์š”์†Œ์— ๋ณ€ํ™”๊ฐ€ ์ƒ๊ธฐ๋ฉด ์Šค์Šค๋กœ ๋ณ€ํ™”ํ•จ
  • ๋ฐฉ๋ฒ• : KVO / Delegation / Property Observer / Combine

์žฅ์ 

  • MVC ํŒจํ„ด์˜ View์™€ Model ์‚ฌ์ด์˜ ๋…๋ฆฝ์„ฑ์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋„๋กํ•จ
  • View์— ๊ด€ํ•œ ๋กœ์ง๊ณผ ๋น„์ง€๋‹ˆ์Šค ๋กœ์ง์„ ์ฒ ์ €ํžˆ ๊ตฌ๋ถ„ํ•˜์—ฌ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅ

๋‹จ์ 

  • ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ์ด ํ•„์ˆ˜์ ์œผ๋กœ ์š”๊ตฌ๋จ
  • ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ์„ ์œ„ํ•ด์„œ Boilerplate code๋ฅผ ์ž‘์„ฑํ•ด์•ผํ•˜๋Š”๋ฐ, View๊ฐ€ ๊ฐ„๋‹จํ•œ ๋กœ์ง์ด๋ผ๋ฉด ๋ฐฐ๋ณด๋‹ค ๋ฐฐ๊ผฝ์ด ๋” ํฐ ๊ฒฝ์šฐ๊ฐ€ ๋ฐœ์ƒํ•˜๊ฒŒ ๋จ

๐Ÿ‘‰๐Ÿป MVVM Design Pattern์— ๋Œ€ํ•œ ํ•™์Šต ๋ธ”๋กœ๊ทธ๋กœ ๊ฐ€๊ธฐ


Launch Screen

App์ด ์‹œ์ž‘๋  ๋•Œ ๋‚˜ํƒ€๋‚ฌ๋‹ค๊ฐ€ ์ฒซ ๋ฒˆ์งธ ํ™”๋ฉด์œผ๋กœ ๋น ๋ฅด๊ฒŒ ์ „ํ™˜๋˜๋ฉฐ, App์ด ๋ฐ˜์‘์ ์ด๊ณ  ๋น ๋ฅด๋‹ค๋Š” ์ธ์ƒ์„ ์ฃผ๊ธฐ ์œ„ํ•ด์„œ ์‚ฌ์šฉ๋œ๋‹ค.

์‹œ๊ฐ์ ์ธ ํšจ๊ณผ๋ฅผ ์ฃผ๊ธฐ ์œ„ํ•ด์„œ ์‚ฌ์šฉ๋˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ๋ฐ˜์‘์ ์ด๊ณ  ๋น ๋ฅด๋‹ค๋Š” ์ธ์‹์„ ์ฃผ๊ธฐ ์œ„ํ•ด์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

  • ์‹คํ–‰ํ•˜๋ฉด 2์ดˆ ์ •๋„ Launch Screen์ด ๋‚˜ํƒ€๋‚˜๋Š”๋ฐ, OS์—์„œ ํ•„์š”ํ•œ ์ •๋ณด๋“ค์„ Disk์—์„œ ๋ฉ”๋ชจ๋ฆฌ๋กœ ์˜ฌ๋ฆฌ๋Š” ๊ณผ์ •์—์„œ ์ง€์—ฐ๋  ๋•Œ ๋ณด์—ฌ์ฃผ๋Š” ๊ฒƒ์ด๋‹ค
  • LauchScreen ViewController์—๋Š” Custom Class ๋ฐ Attributes ๋ฐฐ์น˜ ๋ถˆ๊ฐ€
    • Static ํ•œ ์ƒํƒœ์ด๊ธฐ ๋•Œ๋ฌธ์— ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค.

๋”ฐ๋ผ์„œ ์‹œ์ž‘ ํ™”๋ฉด์—์„œ indicator๊ฐ€ ๋Œ์•„๊ฐ€๋Š” Launch Screen์„ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด์„œ๋Š” Inital ViewController๋ฅผ Launch Screen ์ดํ›„์— ๋„์šฐ๋ฉด์„œ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์ถ”๊ฐ€ํ•˜๊ณ  ์ดํ›„์— ๋‹ค๋ฅธ ViewController๋กœ ๋„˜์–ด๊ฐ€๋„๋ก ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ฑ„ํƒํ•œ๋‹ค.


HTTP

์ธํ„ฐ๋„ท ์ƒ์—์„œ ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„๊ฐ€ ์ž์›์„ ์ฃผ๊ณ  ๋ฐ›์„ ๋•Œ ์“ฐ๋Š” ํ†ต์‹  ๊ทœ์•ฝ

ํด๋ผ์ด์–ธํŠธ์—์„œ ์„œ๋ฒ„๋กœ ํŒŒ์ผ์„ ์—…๋กœ๋“œ ํ•˜๋Š” ๊ณผ์ •

  • ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์›น๋ธŒ๋ผ์šฐ์ €๋ผ๋ฉด ํผ์„ ํ†ตํ•ด์„œ ํŒŒ์ผ์„ ๋“ฑ๋กํ•ด์„œ ์ „์†กํ•˜๊ฒŒ ๋˜๊ณ , ์›น ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ๋ณด๋‚ด๋Š” HTTP ๋ฉ”์‹œ์ง€๋Š” Content-Type ์†์„ฑ์ด multipart/form-data๋กœ ์ง€์ •๋œ๋‹ค. ์„œ๋ฒ„๋Š” ๋ฉ€ํ‹ฐํŒŒํŠธ ๋ฉ”์‹œ์ง€์— ๋Œ€ํ•ด์„œ ๊ฐ ํŒŒ๋“œ๋ณ„๋กœ ๋ถ„๋ฆฌํ•˜์—ฌ ๊ฐœ๋ณ„ ํŒŒ์ผ์˜ ์ •๋ณด๋ฅผ ์–ป๊ฒŒ ๋œ๋‹ค.
  • ์ด๋ฏธ์ง€ ํŒŒ์ผ๋„ ๋ฌธ์ž๋กœ ์ด๋ค„์ ธ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฏธ์ง€ ํŒŒ์ผ์„ ์ŠคํŽ™์— ๋งž๊ฒŒ ๋ฌธ์ž๋กœ ์ƒ์„ฑํ•˜์—ฌ HTTP request body์— ๋‹ด์•„์„œ ์„œ๋ฒ„๋กœ ์ „์†กํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

HTTP(request, response) ๋Š” ์œ„์˜ ๊ทธ๋ฆผ๊ณผ ๊ฐ™์ด 4๊ฐœ์˜ ํŒŒํŠธ๋กœ ๋‚˜๋ˆŒ ์ˆ˜ ์žˆ์œผ๋ฉฐ Message Body์— ๋“ค์–ด๊ฐ€๋Š” ํƒ€์ž…์„ HTTP Header์˜ Content-Type ํ•„๋“œ์— ๋ช…์‹œํ•ด ์ค„ ์ˆ˜ ์žˆ๋‹ค. ํ•ด๋‹น ํ•„๋“œ์— ๋“ค์–ด๊ฐˆ ์ˆ˜ ์žˆ๋Š” ํƒ€์ž… ์ค‘ ํ•˜๋‚˜๊ฐ€ multipart์ด๋‹ค.

Form์ด๋ž€

์ž…๋ ฅ ์–‘์‹ ์ „์ฒด๋ฅผ ๊ฐ์‹ธ๋Š” ํƒœ๊ทธ๋ฅผ ์˜๋ฏธํ•œ๋‹ค.

  • name : form์˜ ์ด๋ฆ„์œผ๋กœ ์„œ๋ฒ„๋กœ ๋ณด๋‚ด์งˆ ๋•Œ ์ด๋ฆ„์˜ ๊ฐ’์œผ๋กœ ๋ฐ์ดํ„ฐ ์ „์†ก
  • action : form์ด ์ „์†ก๋˜๋Š” ์„œ๋ฒ„ url ๋˜๋Š” html ๋งํฌ
  • method : ์ „์†ก ๋ฐฉ๋ฒ• / GET: Default / POST: ๋ฐ์ดํ„ฐ๋ฅผ url์— ๊ณต๊ฐœํ•˜์ง€ ์•Š๊ณ  ์ˆจ๊ฒจ์„œ ์ „์†ก
  • autocomplete : .on ์œผ๋กœ ์„ค์ •ํ•˜๋ฉด form ์ „์ฒด์— ์ž๋™ ์™„์„ฑ ํ—ˆ์šฉ
  • enctype : ํผ ๋ฐ์ดํ„ฐ๊ฐ€ ์„œ๋ฒ„๋กœ ์ œ์ถœ๋  ๋•Œ ํ•ด๋‹น ๋ฐ์ดํ„ฐ๊ฐ€ ์ธ์ฝ”๋”ฉ ๋˜๋Š” ๋ฐฉ๋ฒ•
    • application/x-www-form-urlencoded : default๋กœ ๊ฐ’์œผ๋กœ ๋ชจ๋“  ๋ฌธ์ž๋“ค์„ ์„œ๋ฒ„๋กœ ๋ณด๋‚ด๊ธฐ ์ „์— ์ธ์ฝ”๋”ฉ๋จ์„ ๋ช…์‹œ
    • text/plain : ๊ณต๋ฐฑ๋ฌธ์ž๋Š” "+" ๊ธฐํ˜ธ๋กœ ๋ณ€ํ™˜ํ•˜์ง€๋งŒ ๋‚˜๋จธ์ง€ ๋ฌธ์ž๋Š” ๋ชจ๋‘ ์ธ์ฝ”๋”ฉ๋˜์ง€ ์•Š์Œ์„ ๋ช…์‹œ
    • multipart/form-data : ๋ชจ๋“  ๋ฌธ์ž๋ฅผ ์ธ์ฝ”๋”ฉํ•˜์ง€ ์•Š์Œ์„ ๋ช…์‹œ / ์ฃผ๋กœ ํŒŒ์ผ์ด๋‚˜ ์ด๋ฏธ์ง€๋ฅผ ์„œ๋ฒ„๋กœ ์ „์†กํ•  ๋•Œ ์‚ฌ์šฉ

Multipart , Multipart/form-data

HTTP Header์— Message Body์— ๋“ค์–ด๊ฐˆ ๋ฐ์ดํ„ฐ ํƒ€์ž…์„ ์ •์˜ํ•˜๋Š” Content-type์˜ ํ•„๋“œ ์ค‘์—์„œ MIME(Multipurpose Internet Mail Extensions) ํƒ€์ž… ์ค‘์˜ ํ•˜๋‚˜์ด๋‹ค.

ํŒŒ์ผ์„ ์—…๋กœ๋“œ ํ•  ๋•Œ ์‚ฌ์ง„ ์„ค๋ช…๊ณผ ์‚ฌ์ง„์„ ์œ„ํ•œ input 2๊ฐœ ์žˆ๋‹ค๊ณ  ํ•  ๋•Œ, ์‚ฌ์ง„ ์„ค๋ช… input ์˜ content-type์€ application/x-www-form-urlencoded ์ด ๋  ๊ฒƒ์ด๊ณ , ์‚ฌ์ง„ input ์˜ content-type์€ image/jpeg ๊ฐ€ ๋  ๊ฒƒ์ด๋‹ค. ์ด๋•Œ HTTP Request Body์˜ content-type์œผ๋กœ๋Š” ํ•˜๋‚˜์˜ ํƒ€์ž…์ด ๋“ค์–ด๊ฐ€์•ผํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ด๋Ÿฌํ•œ ๊ฒฝ์šฐ, ์ฆ‰ ํ•œ ๊ฐœ์˜ body์— 2์ข…๋ฅ˜ ์ด์ƒ์˜ ๋ฐ์ดํ„ฐ๋‚˜ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ๋ฉ”์‹œ์ง€๋ฅผ ํ•˜๋‚˜์˜ ๋ฉ”์„ธ์ง€๋กœ ๊ตฌ๋ถ„ํ•ด์„œ ๋งŒ๋“ค์–ด์ฃผ๋Š” ๊ฒƒ์ด ๋ฐ”๋กœ multipart ํƒ€์ž…์ด๋‹ค.

  • Multipart ํƒ€์ž…์„ ํ†ตํ•ด MIME์€ ํŠธ๋ฆฌ ๊ตฌ์กฐ์˜ ๋ฉ”์„ธ์ง€ ํ˜•์‹์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ๋‹ค
  • Multipart ๋ฉ”์‹œ์ง€๋Š” "Content-type:" ํ—ค๋”์— boundary ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ํฌํ•จ
  • boundary๋Š” ๋ฉ”์‹œ์ง€ ํŒŒํŠธ๋ฅผ ๊ตฌ๋ถ„ํ•˜๋Š” ์—ญํ• ์„ ํ•˜๋ฉฐ, ๋ฉ”์‹œ์ง€์˜ ์‹œ์ž‘๊ณผ ๋ ๋ถ€๋ถ„๋„ ๋‚˜ํƒ€๋ƒ„
  • ์ฒซ ๋ฒˆ์งธ boundary ์ „์— ๋‚˜์˜ค๋Š” ๋‚ด์šฉ์€ MIME์„ ์ง€์›ํ•˜์ง€ ์•Š๋Š” ํด๋ผ์ด์–ธํŠธ๋ฅผ ์œ„ํ•ด ์ œ๊ณต
  • boundary๋ฅผ ์„ ํƒํ•˜๋Š” ๊ฒƒ์€ ํด๋ผ์ด์–ธํŠธ์˜ ๋ชซ (์ฃผ๋กœ ๋ฌด์ž‘์œ„ ๋ฌธ์ž(UUID)๋ฅผ ์„ ํƒํ•ด์„œ ๋ฉ”์‹œ์ง€ ๋ณธ๋ฌธ๊ณผ์˜ ์ถฉ๋Œ์„ ํ”ผํ•จ)

HTTP ํ†ต์‹  ๊ทœ์•ฝ

  • Content-Type : multipart/form-data๋กœ ์ง€์ •๋˜์–ด์•ผํ•จ
  • ์ „์†ก๋˜๋Š” ํŒŒ์ผ ๋ฐ์ดํ„ฐ์˜ ๊ตฌ๋ถ„์ž๋กœ boundary์— ์ง€์ •๋˜์–ด ์žˆ๋Š” ๋ฌธ์ž์—ด์„ ์ด์šฉ
  • boundary์˜ ๋ฌธ์ž์—ด ์ค‘ ๋งˆ์ง€๋ง‰ ------WebKitFormBoundary(UUID)-- ๋Š” ๋งˆ์ง€๋ง‰์— -- ๊ฐ€ ์ถ”๊ฐ€๋กœ ๋ถ™๋Š”๋ฐ, ์ด๋Š” body์˜ ๋์„ ์•Œ๋ฆฌ๋Š” ์˜๋ฏธ

โš ๏ธ header ์™€ header ๋ฅผ ๊ตฌ๋ถ„ํ•˜๋Š” ๊ฒƒ์€ ๊ฐœํ–‰ ๋ฌธ์ž์ด๊ณ , header ์™€ body ๋ฅผ ๊ตฌ๋ถ„ํ•˜๋Š” ๊ฒƒ์€ ๊ฐœํ–‰ ๋ฌธ์ž 2๊ฐœ, body ์— ํฌํ•จ๋˜์–ด ์žˆ๋Š” filedata ๋ฅผ ๊ตฌ๋ถ„ํ•˜๋Š” ๊ฒƒ์€ boundary์ด๋‹ค.

๐Ÿ‘‰๐Ÿป HTTP multipart/form-data์— ๋Œ€ํ•œ ํ•™์Šต ๋ธ”๋กœ๊ทธ๋กœ ๊ฐ€๊ธฐ


URLSession Unit Test

๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์—ฌ๋Ÿฌ ํ™˜๊ฒฝ์—์„œ๋ถ€ํ„ฐ ๋…๋ฆฝ์ ์œผ๋กœ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ•  ์ˆ˜ ์žˆ์–ด์•ผํ•œ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ํ…Œ์ŠคํŠธ์—์„œ ์‹ค์ œ๋กœ ๋„คํŠธ์›Œํฌ ํ˜ธ์ถœ์ด ์ด๋ค„์ง„๋‹ค๋ฉด, ํ˜ธ์ถœ์— ๋”ฐ๋ฅธ ๊ฒฐ๊ณผ ๊ฐ’์„ ์˜ˆ์ƒํ•ด์•ผํ•œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ด๋•Œ ๋„คํŠธ์›Œํฌ ํ˜ธ์ถœ์— ๋”ฐ๋ผ ๊ฒฐ๊ณผ ๊ฐ’์€ ๋„คํŠธ์›Œํฌ์˜ ์ƒํ™ฉ ํ˜น์€ ์„œ๋ฒ„์— ์ €์žฅ๋˜์–ด ์žˆ๋Š” ๋ฐ์ดํ„ฐ์— ๋”ฐ๋ผ ๋งค๋ฒˆ ๋‹ฌ๋ผ์งˆ ์ˆ˜ ์žˆ๋‹ค.

์ฆ‰, ํ•ด๋‹น ๋„คํŠธ์›Œํ‚น ๋ชจ๋“ˆ๋งŒ ๊ฐ€์ง€๊ณ  ํ…Œ์ŠคํŠธ๋ฅผ ํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹Œ, **๋„คํŠธ์›Œํฌ ์ƒํ™ฉ์— โ€œ์˜์กด์ โ€**์ด๊ฒŒ ๋˜๊ณ , ์ด๋Š” ํ•ญ์ƒ ๊ฐ™์€ ๊ฒฐ๊ณผ ๊ฐ’์„ ๋ณด์žฅํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ์œ ๋‹›ํ…Œ์ŠคํŠธ ์ž‘์„ฑ ์‹œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๊ฒŒ ๋œ๋‹ค. ๋”ฐ๋ผ์„œ ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด์„œ URLProtocol ์„ ์ƒ์†๋ฐ›์€ MockURLProtocol ์„ ์‚ฌ์šฉํ•˜์—ฌ ๋„คํŠธ์›Œํฌ ์ƒํ™ฉ์— ์˜์กด์ ์ด์ง€ ์•Š๊ณ  input, output์„ ์ฃผ์ž…ํ•˜์—ฌ ๋ชจ๋“ˆ์ด ์ •์ƒ์ ์œผ๋กœ ๋™์ž‘ํ•˜๋Š”์ง€ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๋‹ค.

๐Ÿ‘‰๐Ÿป URLSession Unit Test์— ๋Œ€ํ•œ ํ•™์Šต ๋ธ”๋กœ๊ทธ๋กœ ๊ฐ€๊ธฐ


ํ˜„์žฌ ๋ทฐ ์ปจํŠธ๋กค๋Ÿฌ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐฉ๋ฒ•

extension UIWindow {
    public var currentViewController: UIViewController? {
        return self.getCurrentViewController(from: self.rootViewController)
    }
    
    public func getCurrentViewController(from viewController: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
        if let navigationController = viewController as? UINavigationController {
            return self.getCurrentViewController(from: navigationController.visibleViewController)
        }
        else if let tabBarController = viewController as? UITabBarController {
            return self.getCurrentViewController(from: tabBarController.selectedViewController)
        }
        else {
            guard let currentViewController = viewController?.presentedViewController else {
                return viewController
            }
            return self.getCurrentViewController(from: currentViewController)
        }
    }
}

์œ„์˜ extension์—์„œ์˜ ๊ตฌํ˜„์„ ํ™œ์šฉํ•˜๋ฉด ๊ธฐ๊ธฐ์—์„œ ๋ณด์ด๋Š” ํ˜„์žฌ viewController๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ด๋•Œ keyWindow ๊ฐ€ iOS 13์ด์ƒ๋ถ€ํ„ฐ multiple scenes์„ ์ง€์›ํ•˜๋Š” app์—์„œ๋Š” ์—ฌ๋Ÿฌ ๊ฐœ์˜ scene์ด ์žˆ์„ ์ˆ˜ ์žˆ๊ณ , ์ด๋•Œ ์—ฐ๊ฒฐ๋œ ๋ชจ๋“  scene๋“ค์˜ key window๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ธฐ ๋•Œ๋ฌธ์— deprecated๋˜์—ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์ด๋ฅผ ๋ฒ„์ „ ํ˜น์€ ๊ธฐ๊ธฐ์— ๋”ฐ๋ผ ์ฒ˜๋ฆฌํ•ด์ฃผ์–ด์•ผํ•œ๋‹ค.

extension UIWindow {
    static var key: UIWindow? {
        if #available(iOS 13, *) {
            return UIApplication.shared.windows.filter{ $0.isKeyWindow }.first
        }
        else {
            return UIApplication.shared.keyWindow
        }
    }
}

๋”ฐ๋ผ์„œ iOS 13 ์ด์ƒ ๊ทธ๋ฆฌ๊ณ  ์ด์ „ ๋ฒ„์ „์— ๋Œ€ํ•ด์„œ key๋ผ๋Š” ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ตฌํ˜„ํ•ด์ฃผ์—ˆ๋‹ค.


UINavigationController์—์„œ ์›ํ•˜๋Š” ViewController๋กœ ์ด๋™

UINavigationController ์˜ navigation stack์—์„œ ์›ํ•˜๋Š” ViewController๋กœ ๊ฐ€๊ธฐ ์œ„ํ•ด์„œ๋Š” popToViewController ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. ์ด๋•Œ ํ•ด๋‹น ViewController๊ฐ€ navigation stack์— ์žˆ๋Š”์ง€ ๊ณ ์ฐจํ•จ์ˆ˜ filter๋ฅผ ์‚ฌ์šฉํ•ด์„œ ํ™•์ธํ•˜๊ณ  ์žˆ๋Š” ๊ฒฝ์šฐ popToViewController ๋ฉ”์„œ๋“œ ํ˜ธ์ถœํ•œ๋‹ค.

guard let itemListViewController = viewController.navigationController?.viewControllers.filter({$0.isKind(of: ItemListViewController.self)}).first else {
    return
}
viewController.navigationController?.popToViewController(itemListViewController, animated: true)

About

๐Ÿ“ก์„œ๋ฒ„์™€ ํ†ต์‹ ํ•˜๋Š” ๋‹น๊ทผ๋งˆ์ผ“๐Ÿฅ• ๊ฐ™์€ ์•ฑ

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages