Skip to content

๐Ÿ›’ REST API์™€์˜ ์—ฐ๋™์„ ํ†ตํ•ด ๊ฐ„๋‹จํ•œ ๋งˆ์ผ“ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•ด๋ณผ ์ˆ˜ ์žˆ๋Š” ์•ฑ

Notifications You must be signed in to change notification settings

leeari95/ios-open-market

ย 
ย 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

๐Ÿ›’ ์˜คํ”ˆ๋งˆ์ผ“ ํ”„๋กœ์ ํŠธ

  • ํŒ€ ํ”„๋กœ์ ํŠธ(2์ธ)
  • ํ”„๋กœ์ ํŠธ ๊ธฐ๊ฐ„: 2022.01.03 ~ 2022.01.28

๋ชฉ์ฐจ

ํ‚ค์›Œ๋“œ

  • ์˜์กด์„ฑ ์ฃผ์ž…(DI) URLSession URLProtocol URLRequest
  • API HTTP TCP/IP MIME-Type multipart/form-data
  • application/json Result Codable CodingKey Async Test

  • UICollectionView UICollectionViewFlowLayout
  • Supplyment UICollectionReusableView performBatchUpdates
  • reloadData Xib File UISegmentedControl

  • NSCache UIActivityIndicatorView

  • UIRefreshControl UIGraphicsImageRenderer UIImagePicker
  • UIScrollViewDelegate UITextField UIAlertController

  • UIPageControl GestureRecognizer UIFontMetrics
  • UIScrollView zoomScale Dynamic Type UICollectionView Paging
  • UIAlertController UITextFlied

โญ๏ธ ํ”„๋กœ์ ํŠธ ์†Œ๊ฐœ

REST API์™€์˜ ์—ฐ๋™์„ ํ†ตํ•ด ๊ฐ„๋‹จํ•œ ๋งˆ์ผ“๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•ด๋ณผ ์ˆ˜ ์žˆ๋Š” ์•ฑ์ด์—์š”. ๐Ÿ“ฑ

๋งˆ์ผ“์— ํŒ๋งคํ•  ์ƒํ’ˆ์„ ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ์–ด์š” ! ๐Ÿ›’

์ˆ˜์ •, ์‚ญ์ œ ๊ธฐ๋Šฅ์„ ํ†ตํ•ด ๋“ฑ๋กํ•œ ์ƒํ’ˆ์„ ์ˆ˜์ •ํ•ด๋ณผ ์ˆ˜๋„ ์žˆ์–ด์š”. ๐Ÿ› 

๋“ฑ๋ก๋˜์–ด์žˆ๋Š” ์ƒํ’ˆ๋“ค์„ ์‚ฌ์ง„์„ ํ†ตํ•ด ์ž์„ธํžˆ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ”


โœจ ํ”„๋กœ์ ํŠธ ์ฃผ์š”๊ธฐ๋Šฅ

๐Ÿ” ์•ฑ์„ ์ตœ์ดˆ์— ์‹คํ–‰ํ–ˆ์„ ๋•Œ ๋ฐ์ดํ„ฐ๊ฐ€ ๋กœ๋“œ๋  ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐ ํšจ๊ณผ๋ฅผ ํ‘œ์‹œํ•ด์š”.

๐Ÿ‘†๐Ÿป ์Šคํฌ๋กค์„ ์ผ์ • ๊ธธ์ด ์ด์ƒ ๋‚ด๋ฆฌ๊ฒŒ ๋˜๋ฉด ๋„คํŠธ์›Œํ‚น์„ ํ†ตํ•ด ๋‹ค์Œ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ถˆ๋Ÿฌ์˜ต๋‹ˆ๋‹ค.

โ™ป๏ธ ์ตœ์ƒ๋‹จ์—์„œ ํ™”๋ฉด์„ ์•„๋ž˜๋กœ ์žก์•„๋‹น๊ธฐ๋ฉด ์ƒํ’ˆ ๋ชฉ๋ก์„ ๊ฐฑ์‹ ํ•  ์ˆ˜ ์žˆ์–ด์š” !

๐Ÿ‘€ ์ƒ๋‹จ ๋ฒ„ํŠผ์„ ํ†ตํ•ด ๋ณด๊ธฐ๋ชจ๋“œ๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์–ด์š”.

โœจ ๊ธฐ๊ธฐ ๋ฐฉํ–ฅ์— ๋”ฐ๋ผ ๊ฐ ํ–‰๋‹น ๋ณด์—ฌ์ฃผ๋Š” ์ƒํ’ˆ์˜ ๊ฐœ์ˆ˜๊ฐ€ ๋‹ฌ๋ผ์ง‘๋‹ˆ๋‹ค.

โž• ์šฐ์ธก + ๋ฒ„ํŠผ์„ ํ†ตํ•ด ์ƒํ’ˆ์„ ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ์–ด์š”.

๐Ÿ“ธ ์‚ฌ์ง„์ฒฉ์—์„œ ๋“ฑ๋กํ•  ์‚ฌ์ง„์„ ์„ ํƒํ•  ์ˆ˜ ์žˆ๊ณ , ๊ธฐ์กด์— ๋“ฑ๋กํ•œ ์‚ฌ์ง„์„ ์Šคํฌ๋กค๋ง์„ ํ†ตํ•ด ํ™•์ธํ•  ์ˆ˜ ์žˆ์–ด์š”.

๐Ÿ—‘ ๋“ฑ๋กํ•œ ์ด๋ฏธ์ง€์˜ ์šฐ์ธก ์ƒ๋‹จ์— ์œ„์น˜ํ•œ x๋ฒ„ํŠผ์„ ํ†ตํ•ด ์ด๋ฏธ์ง€๋ฅผ ์‚ญ์ œํ•  ์ˆ˜ ์žˆ์–ด์š”.

๐Ÿ‘๐Ÿป ํ‚ค๋ณด๋“œ ํ”„๋ ˆ์ž„์„ ๊ณ ๋ คํ•˜์—ฌ ์ž…๋ ฅํ•  TextView๊ฐ€ ํ‚ค๋ณด๋“œ์— ๊ฐ€๋ฆฌ์ง€ ์•Š๊ฒŒ ์ œ์•ฝ์„ ์กฐ์ •ํ–ˆ์–ด์š”.

โš ๏ธ ์‹ค์‹œ๊ฐ„์œผ๋กœ ์ž…๋ ฅ๊ฐ’์˜ ์œ ํšจ์„ฑ์„ ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ์–ด์š”!

โœ”๏ธ ์ƒํ’ˆ์„ ๋“ฑ๋กํ•˜๊ณ ๋‚˜๋ฉด ์ž๋™์œผ๋กœ ์ด์ „ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•˜๋ฉฐ, ๋„คํŠธ์›Œํ‚น์„ ํ†ตํ•ด ์ƒˆ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐฑ์‹ ํ•ด์š”.

๐Ÿ›  ์ƒํ’ˆ ์ƒ์„ธ ์กฐํšŒ ํ™”๋ฉด ์šฐ์ธก ์ƒ๋‹จ์˜ ๋”๋ณด๊ธฐ ๋ฒ„ํŠผ์„ ํ†ตํ•ด ์ƒํ’ˆ์„ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ์–ด์š”.

๐ŸŒŸ ํŽ˜์ด์ง•์„ ํ†ตํ•ด ์—ฌ๋Ÿฌ ์ด๋ฏธ์ง€๋ฅผ ์กฐํšŒํ•  ์ˆ˜ ์žˆ์–ด์š”.

๐Ÿ” ์ด๋ฏธ์ง€๋ฅผ ํด๋ฆญํ•˜๊ณ  2๋ฒˆ ํƒญ์„ ํ•˜๋ฉด ์ด๋ฏธ์ง€๊ฐ€ 2๋ฐฐ ํฌ๊ธฐ๋กœ ํ™•๋Œ€/์ถ•์†Œ๊ฐ€ ๋˜์š” !

๐Ÿ—‘ ์ƒํ’ˆ ์‚ญ์ œ ํ›„์—๋Š” ์ƒ์„ธํ™”๋ฉด์„ ๋ฒ—์–ด๋‚˜๋ฉฐ ์ƒํ’ˆ ๋ชฉ๋ก์„ ๊ฐฑ์‹ ํ•ด์š”.


๐Ÿ’ช๐Ÿป ํ”„๋กœ์ ํŠธ ๊ธฐ์ˆ  ์Šคํƒ

UI Network Decoding / Encoding Caching Test
UIKit URLSession
Data
Codable
JSONEncoder / JSONDecoder
Data(multipart/form-data)
NSCache XCTest

๐Ÿค” ๊ณ ๋ฏผํ•œ ๋‚ด์šฉ

๋‹จ์ผ ์ฑ…์ž„ ์›์น™ ์ค€์ˆ˜

  • ๊ฐ€๋…์„ฑ ํ–ฅ์ƒ ๋ฐ ๊ตฌ์กฐ ํŒŒ์•…์ด ์ˆ˜์›”ํ•˜๋„๋ก ๊ฐ ์—ญํ• ๋“ค์„ ์ตœ๋Œ€ํ•œ ์ž˜๊ฒŒ ์ชผ๊ฐœ์„œ ์„ค๊ณ„๋ฅผ ์ง„ํ–‰ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

์˜์กด์„ฑ ์ฃผ์ž… ๋ฐ Mockํƒ€์ž… ์„ค๊ณ„๋กœ ์œ ๋‹› ํ…Œ์ŠคํŠธ ์ง„ํ–‰

  • ํ…Œ์ŠคํŠธ ์ž‘์„ฑ์„ ์œ„ํ•ด ์˜์กด์„ฑ ์ฃผ์ž…์„ ํ™œ์šฉํ•˜์—ฌ Mock, Stub ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด ํ™œ์šฉํ•˜์˜€์Šต๋‹ˆ๋‹ค.
  • URLProtocol์„ ์ƒ์†๋ฐ›์€ ๊ฐ€์งœ Session์„ ๋งŒ๋“ค๊ณ  ๋„คํŠธ์›Œํฌ์™€ ๋ฌด๊ด€ํ•œ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ•˜์˜€์Šต๋‹ˆ๋‹ค.
    • ์„œ๋ฒ„๋กœ ๋ณด๋‚ผ ์š”์ฒญ(GET, POST, PATCH, DELETE)์„ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋งŒ๋“œ๋Š”์ง€
    • ์‹คํŒจํ•˜๋Š” ๋„คํŠธ์›Œํฌ, ์„ฑ๊ณตํ•˜๋Š” ๋„คํŠธ์›Œํฌ์— ๋”ฐ๋ผ ํ•ธ๋“ค๋ง์„ ํ•˜๊ณ  ์žˆ๋Š”์ง€

์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ๊ณ ๋ คํ•œ UI ์„ค๊ณ„

  • ์„œ๋ฒ„์— ์ƒํ’ˆ ๋ชฉ๋ก์„ ์š”์ฒญํ–ˆ์„ ๋•Œ, ์ด๋ฏธ์ง€๋Š” URL ํ˜•ํƒœ๋กœ ๋ฐ›๊ฒŒ๋˜๋Š”๋ฐ, ๋น„๋™๊ธฐ๋กœ ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•˜๊ฒŒ ๋˜๋ฉด, ํ…์ŠคํŠธ๊ฐ€ ๋จผ์ € ๋œจ๊ณ  ๊ทธ ๋‹ค์Œ์— ์ด๋ฏธ์ง€๋“ค์ด ์ˆœ์ฐจ์ ์œผ๋กœ ๋œจ๋Š” ํ˜„์ƒ์ด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.
    • ๋”ฐ๋ผ์„œ ๋ชจ๋“  ๋ฐ์ดํ„ฐ๊ฐ€ ๋‹ค์šด๋กœ๋“œ๊ฐ€ ์™„๋ฃŒ๋œ ์‹œ์ ์— ํ•œ๋ฒˆ์— ๋ฐ์ดํ„ฐ๋ฅผ ๋„์›Œ์ฃผ๋Š” ๊ฒƒ์ด ์ข€๋” ์ข‹๋‹ค๊ณ  ํŒ๋‹จ๋˜์–ด ๋Œ€๊ธฐ ํ‘œ์‹œ๋ฅผ ๋„์šด ํ›„ ๋ฐ์ดํ„ฐ ๋‹ค์šด๋กœ๋“œ๋ฅผ ์™„๋ฃŒํ•˜๋ฉด ํ•œ๋ฒˆ์— ์ƒํ’ˆ ๋ชฉ๋ก์„ ๋„์šฐ๋„๋ก ๊ตฌ์„ฑํ•˜์˜€์Šต๋‹ˆ๋‹ค.
  • ์ƒํ’ˆ ๋ชฉ๋ก์˜ ๋‹ค์ŒํŽ˜์ด์ง€๋ฅผ ๋„˜์–ด๊ฐ€๋Š” ๋ฐฉ๋ฒ•์„ Pagination์œผ๋กœ ๊ตฌํ˜„ํ•˜์˜€์Šต๋‹ˆ๋‹ค.
    • ์Šคํฌ๋กค์ด ํ•˜๋‹จ์— ๊ฐ€๊นŒ์›Œ์ง€๋ฉด ๋‹ค์ŒํŽ˜์ด์ง€๋ฅผ ๋กœ๋“œํ•˜๋„๋ก ๊ตฌ์„ฑ
  • ๊ธฐ์กด์—๋Š” ์ƒํ’ˆ ๋“ฑ๋ก ์‹œ ์ž…๋ ฅ๊ฐ’ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ Done ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅผ ๋•Œ๋งˆ๋‹ค Alert์„ ํ†ตํ•ด ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ์œผ๋‚˜, ๋„ˆ๋ฌด ๋งŽ์€ Alert์€ ๊ท€์ฐฎ๊ณ  ๋ฒˆ๊ฑฐ๋กญ๋‹ค๊ณ  ํŒ๋‹จ๋˜์–ด, ๋ˆ„๋ฅด๊ธฐ ์ „์— ์‹ค์‹œ๊ฐ„์œผ๋กœ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ธฐ๋Šฅ ์ถ”๊ฐ€ํ•˜์—ฌ ์‚ฌ์šฉ์„ฑ์„ ํ–ฅ์ƒ์‹œ์ผœ์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.
  • ํ‚ค๋ณด๋“œ๊ฐ€ ์ฝ˜ํ…์ธ ๋ฅผ ๊ฐ€๋ฆฌ์ง€ ์•Š๋„๋ก ์ œ์•ฝ์กฐ๊ฑด์„ ์กฐ์ •ํ•˜์—ฌ ๊ตฌํ˜„ํ•˜์˜€์Šต๋‹ˆ๋‹ค.
  • ์ด๋ฏธ์ง€ ์ƒ์„ธ๋ณด๊ธฐ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•˜์—ฌ ์ƒํ’ˆ์„ ์ข€๋” ์ž์„ธํžˆ ๋ณผ ์ˆ˜ ์žˆ๋„๋ก ๊ตฌํ˜„ํ•˜์˜€์Šต๋‹ˆ๋‹ค.
    • ์ด๋ฏธ์ง€ ํ„ฐ์น˜ ์‹œ ์ด๋ฏธ์ง€ ์ƒ์„ธ๋ณด๊ธฐ ํ™”๋ฉด์œผ๋กœ ์ด๋™
    • 2๋ฒˆ ํƒญ์„ ๋น ๋ฅด๊ฒŒ ํ•  ๋•Œ ํ™•๋Œ€/์ถ•์†Œ ๊ธฐ๋Šฅ ์ œ๊ณต

๐Ÿ›  Trouble Shooting

"URLSessionDataTask๋ฅผ ์ฑ„ํƒํ•œ ํƒ€์ž…์— init()์— deprecated ๊ฒฝ๊ณ ..?"

  • ์ƒํ™ฉ URLSessionDataTask์„ ๋Œ€์ฒดํ•  ๊ฐ์ฒด๋กœย StubURLSessionDataTaskย ๋ฅผ ๊ตฌํ˜„ํ•˜๋‹ค๊ฐ€ ๊ฒฝ๊ณ ๋ฅผ ๋งˆ์ฃผํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค.
class StubURLSessionDataTask: URLSessionDataTask {
    var dummyData: DummyData?

    // init ๋ถ€๋ถ„์—์„œ ์—๋Ÿฌ๊ฐ€ ๋‚ฌ๋‹ค.
    init(dummy: DummyData?, completionHandler: DataTaskCompletionHandler?) {
        self.dummyData = dummy
        self.dummyData?.completionHandler = completionHandler
    }

    override func resume() {
        dummyData?.completion()
    }
}

'init()' was deprecated in iOS 13.0: Please use -[NSURLSession dataTaskWithRequest:] or other NSURLSession methods to create instances

  • ์ด์œ  URLSessionDataTask init()์ด IOS13 ์ดํ›„์— deprecatede๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ํ•ด๋‹น ๊ฒฝ๊ณ ๋ฅผ ์—†์• ๊ณ  ์‹ถ์–ด์„œ ๊ตฌ๊ธ€๋ง์„ ํ•˜๋‹ค๊ฐ€ URLProtocol์„ ๋ฐœ๊ฒฌํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค.
  • ํ•ด๊ฒฐ URLProtocol์„ ์ƒ์†๋ฐ›์€ MockURLProtocol์„ ๋งŒ๋“ค์–ด์„œ URLSession configuration์„ ๊ตฌ์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ณ  ๊ธฐ์กด์— ๋งŒ๋“ค์—ˆ๋˜ StubURLSessionDataTask, DummyData, MockSession ํƒ€์ž…์€ ๋”์ด์ƒ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ฒŒ๋˜์–ด ๋ชจ๋‘ ์‚ญ์ œํ•ด์ฃผ์—ˆ๋‹ค.
    • URLProtocol์ด๋ž€?
      • URL ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ์„ ๋‹ค๋ฃจ๋Š” ์ถ”์ƒํด๋ž˜์Šค
    • URLProtocol์€ URLProtocolClient ํ”„๋กœํ† ์ฝœ์„ ํ†ตํ•ด ๋„คํŠธ์›Œํฌ ์ง„ํ–‰ ์ƒํ™ฉ์„ ์ „๋‹ฌํ•œ๋‹ค.
    • ํ…Œ์ŠคํŠธ ๋ฒˆ๋“ค์—์„œ MockURLProtocol ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค๊ณ  ๋ฉ”์†Œ๋“œ๋ฅผ ์žฌ์ •์˜ ํ•ด์ค€๋‹ค.
    • ๋กœ๋“œ๋ฅผ ํ•  ๋•Œ ์„ค์ •ํ•œ ํ›„ ์ „๋‹ฌํ•  Data, Error, Response๋ฅผ ๋”•์…”๋„ˆ๋ฆฌ๋กœ ์„ค์ •ํ•ด์ค€๋‹ค.
      • ์ด ๊ฐ’์€ URLProtocol์— ์—ฐ๊ฒฐํ•˜์—ฌ ์„ค์ •๊ฐ’์„ ์„ธํŒ…ํ•ด์ฃผ๊ธฐ ์œ„ํ•œ ๊ฐ’์ด ๋œ๋‹ค.
    • Unit Test๋ฅผ ์œ„ํ•ด ์ƒ์†๋ฐ›์•„์„œ ์˜ค๋ฒ„๋ผ์ด๋“œ ํ•จ์œผ๋กœ์จ ์ปค์Šคํ…€ ํ•˜์—ฌ Mock ๊ฐ์ฒด๋ฅผ ์ƒˆ๋กญ๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.
      • ๊ธฐ์กด์ฒ˜๋Ÿผ ์™ธ๋ถ€ ๋„คํŠธ์›Œํฌ์— ์š”์ฒญ์„ ์ง์ ‘ ๋ณด๋‚ด๋Š” ๋™์ž‘์ด ์•„๋‹ˆ๋ผ, ์š”์ฒญ์„ ๊ฐ€๋กœ์ฑ„์„œ ์›ํ•˜๋Š” ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•˜๊ฒŒ ๋” ์ปค์Šคํ…€ ํ•˜๋Š” ์ž‘์—…์ด๋‹ค.
      • ์ฆ‰ ์›๋ž˜ ๊ฐ™์ด ์›น ์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ๊ณผ์ •์ด ์•„๋‹ˆ๊ณ , ๋‚ด๊ฐ€ ์„ค์ •ํ•œ ๊ฐ’(data, response)์„ ๊ทธ๋Œ€๋กœ ๋ฐ˜ํ™˜ํ•˜๊ฒŒ ๋งŒ๋“ค์–ด ์ฃผ๋Š” ๊ณผ์ •์ธ ๊ฒƒ์ด๋‹ค.

"Segument Control์„ ์ด์šฉํ•˜์—ฌ ํ™”๋ฉด์ „ํ™˜ ์‹œ ์Šคํฌ๋กค ์œ„์น˜๊ฐ€ ์ •์ƒ์ ์ด์ง€ ์•Š์€ ๊ฒฝ์šฐ"

  • ์ƒํ™ฉ FlowLayout์„ ํ™œ์šฉํ•˜์—ฌ ํ™”๋ฉด์„ ์ „ํ™˜ํ•  ๋•Œ, ์Šคํฌ๋กค์ด ์ƒ๋‹จ์— ์œ„์น˜ํ•˜๋Š”๊ฒŒ ์•„๋‹ˆ๋ผ ์ œ๋ฉ‹๋Œ€๋กœ์ธ ์œ„์น˜์— ๊ฐ€์žˆ๋Š” ํ˜„์ƒ์ด ๋ฐœ์ƒํ–ˆ๋‹ค.

  • ์ด์œ  ๋ ˆ์ด์•„์›ƒ์ด ์„œ๋กœ ๋‹ค๋ฅด๊ธฐ ๋•Œ๋ฌธ์— ์Šคํฌ๋กค์˜ ์ขŒํ‘œ๋„ ๋‹ค๋ฅธ ๊ฒƒ์œผ๋กœ ์ถ”์ธก์ด ๋˜์—ˆ๋‹ค.

  • ํ•ด๊ฒฐ ๋”ฐ๋ผ์„œ ์ด ๋ถ€๋ถ„์„ ํ™”๋ฉด์„ ์ „ํ™˜ํ•  ๋•Œ ์Šคํฌ๋กค์˜ ์œ„์น˜๋ฅผ ์ƒ๋‹จ์— ์œ„์น˜ํ•˜๊ฒŒ ์„ค์ •ํ•ด์ฃผ๋‹ˆ ํ•ด๊ฒฐ๋˜์—ˆ๋‹ค.

    extension UIScrollView {
        func scrollToTop() {
            let topOffset = CGPoint(x: 0, y: -contentInset.top)
            setContentOffset(topOffset, animated: false)
        }
    }

"์Šคํฌ๋กค ํ•˜๋Š” ํ˜„์žฌ ์œ„์น˜๊ฐ€ ๋น„์ •์ƒ์ ์œผ๋กœ ์นด์šดํŠธ๋˜๋Š” ํ˜„์ƒ"

์Šคํฌ๋กค์ด ํ•˜๋‹จ์— ๊ฐ€๊นŒ์›Œ์ง€๋ฉด ๋‹ค์ŒํŽ˜์ด์ง€๋ฅผ ๋กœ๋“œํ•˜๋„๋ก ๋กœ์ง์„ ์งฐ์œผ๋‚˜, ์Šคํฌ๋กคํ•˜๋Š” ํ˜„์žฌ ์œ„์น˜๊ฐ€ ๋น„์ •์ƒ์ ์œผ๋กœ ์นด์šดํŠธ๋˜์–ด ๋””์ฝ”๋”ฉ์ด ์‹คํŒจํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์ƒ๊ฒผ๋‹ค. ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด LLDB๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๋””๋ฒ„๊น…์„ ์ง„ํ–‰ํ•˜์˜€๋‹ค.

  • ํ•ญ์ƒ ๋œจ๋Š” ์—๋Ÿฌ๋Š” ์•„๋‹ˆ๊ณ , ๊ฐ„ํ—์ ์œผ๋กœ ๋œจ๋Š” ์—๋Ÿฌ๋‹ค. ์Šคํฌ๋กค์„ ํ•˜๋‹จ๊นŒ์ง€ ํ–ˆ์„ ๋•Œ ๋””์ฝ”๋”ฉ์— ์‹คํŒจํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์—ˆ๋‹ค. ๋””๋ฒ„๊น…์„ ํ•ด๋ณด๋‹ˆ ๊ฒฐ๊ณผ๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.
  • ๋ทฐ์ปจํŠธ๋กค๋Ÿฌ ์ชฝ์—์„œ ๋„คํŠธ์›Œํฌ ๋งค๋‹ˆ์ €์˜ fetch ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ชฝ์—์„œ ์—๋Ÿฌ๊ฐ€ ๋‚˜๋Š” ๊ฒƒ ๊ฐ™์€๋ฐ, ๋„คํŠธ์›Œํฌ์—์„œ๋Š” success๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ธด ํ–ˆ์œผ๋‚˜ ์กฐํšŒ๋ฅผ ํ•ด๋ณด๋ฉด ๋ฐ์ดํ„ฐ๊ฐ€ ๋น„์–ด์žˆ๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.
  • Response๋ฅผ ํ™•์ธํ•ด๋ณด๋ฉด 204์ฝ”๋“œ๋กœ ์‘๋‹ตํ•˜๊ณ  ์žˆ๋‹ค.
  • ์Šคํฌ๋กค ํ•˜๋Š” ๋ถ€๋ถ„์— ์ค‘๋‹จ์ ์„ ์ฐ๊ณ  ํ™•์ธํ•ด๋ณด๋‹ˆ currentPage๊ฐ€ 104๊ฐ€ ๋˜์–ด์žˆ๋Š” ๊ฒƒ๋„ ํ™•์ธ๋˜์—ˆ๋‹ค. ์ € ์กฐ๊ฑด๋ฌธ์ด๋ผ๋ฉด 104๊ฐ€ ๋  ์ˆ˜๊ฐ€ ์—†๋Š”๋ฐ.. ์–ด๋Š์ˆœ๊ฐ„ ์Šคํฌ๋กค์„ ๊ณ„์‚ฐํ•˜๋Š” ๋ถ€๋ถ„(yOffset)์—์„œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•ด์„œ ๋น„์ •์ƒ์ ์œผ๋กœ currentPage๊ฐ€ ์˜ฌ๋ผ๊ฐ€๋Š” ๊ฒƒ ๊ฐ™๋‹ค.
if heightRemainBottomHeight < frameHeight ,
   let page = page, page.hasNext, page.pageNumber == currentPage {
    currentPage += 1
    self.requestProducts()
}
  • ๋”ฐ๋ผ์„œ ์œ„์™€ ๊ฐ™์ด ์กฐ๊ฑด๋ฌธ์„ ํ•˜๋‚˜ ๋” ์ถ”๊ฐ€ํ•ด์„œ ์•ˆ์ „ํ•˜๊ฒŒ currentPage๋ฅผ ๋”ํ•ด์ค„ ์ˆ˜ ์žˆ๋„๋ก ์ˆ˜์ •ํ•ด์ฃผ์—ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋‹ˆ ๋”์ด์ƒ ํ•ด๋‹น ๋ฒ„๊ทธ๋Š” ๋ฐœ์ƒํ•˜์ง€ ์•Š์•˜๋‹ค.

"CollectionView๋กœ Paging์‹œ cell์ด ๋ฐ€๋ฆฌ๋Š” ๋ฌธ์ œ"

  • ๋ฌธ์ œ CollectionView์˜ isPagingEnabled์„ true๋กœ ์ฃผ๋ฉด ์•„๋ž˜ ์˜ˆ์‹œ์™€ ๊ฐ™์ด ์Šคํฌ๋กค ํ–ˆ์„ ๋•Œ cell์ด ์กฐ๊ธˆ์”ฉ ๋ฐ€๋ฆฌ๋Š” ํ˜„์ƒ์ด ๋‚˜ํƒ€๋‚ฌ๋‹ค.

    Untitled

  • ์ด์œ  CollectionView์˜ ๊ฒฝ์šฐ minimumLineSpacing์ด ๊ธฐ๋ณธ์ ์œผ๋กœ ๊ฐ’(10.0)์ด ๋“ค์–ด๊ฐ€์žˆ๋‹ค. ํ•ด๋‹น ๊ฐ’ ๋•Œ๋ฌธ์— ์Šคํฌ๋กค ์‹œ ๋ฐ€๋ฆผํ˜„์ƒ์ด ์žˆ์—ˆ๋˜ ๊ฒƒ์ด์˜€๋‹ค.

  • ํ•ด๊ฒฐ minimumLineSpacing์„ 0์œผ๋กœ ์„ค์ •ํ•ด์ฃผ๋‹ˆ ์Šคํฌ๋กค ์‹œ cell์ด ์กฐ๊ธˆ์”ฉ ๋ฐ€๋ฆฌ๋˜ ํ˜„์ƒ์ด ํ•ด๊ฒฐ๋˜์—ˆ๋‹ค.


๐Ÿ”ฅ ์ƒˆ๋กญ๊ฒŒ ์•Œ๊ฒŒ๋œ ๊ฒƒ

"ํ‚ค๋ณด๋“œ๋ฅผ ํ™”๋ฉด์—์„œ ์—†์• ๋Š” ์—ฌ๋Ÿฌ๊ฐ€์ง€ ๋ฐฉ๋ฒ•"

  • ์‚ฝ์งˆ์„ ์ข€ ํ•ด๋ณธ ๊ฒฐ๊ณผ ์—ฌ~๋Ÿฌ ๋ฐฉ๋ฒ•์ด ์žˆ์—ˆ๋‹ค.
    • Recognizer๋ฅผ ๋“ฑ๋กํ•ด์„œ ํ„ฐ์น˜์‹œ ํ‚ค๋ณด๋“œ๋ฅผ ์‚ฌ๋ผ์ง€๋„๋ก ํ•˜๊ธฐ.
    // Recognizer๋ฅผ ํ™œ์šฉํ•ด์„œ ๋ทฐ์ปจ์„ ํ„ฐ์น˜ํ•˜๋ฉด ํ‚ค๋ณด๋“œ ์‚ฌ๋ผ์ง€๋Š” ์ฝ”๋“œ
    func hideKeyboard() {
        let tap = UITapGestureRecognizer(target: self, action: #selector(UIViewController.dismissKeyboard))
        view.addGestureRecognizer(tap)
    }
    
    @objc func dismissKeyboard() {
        view.endEditing(true)
    }
    • touchesBegan ๋ฉ”์†Œ๋“œ๋ฅผ ํ™œ์šฉํ•˜์—ฌ View๋ฅผ ํ„ฐ์น˜์‹œ ํ‚ค๋ณด๋“œ๋ฅผ ์‚ฌ๋ผ์ง€๊ฒŒ ํ•˜๊ธฐ.
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesBegan(touches, with: event)
        self.endEditing(true)
    }    
    • ScrollView์— keyboardDismissMode๋ฅผ drag๋กœ ์„ค์ •ํ•ด์ฃผ๊ธฐ
      • scrollView.keyboardDismissMode = .onDrag
  • ์ด๋ฒˆ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” touchesBegan๊ณผ ScrollView์— keyboardDismissMode๋ฅผ ํ™œ์šฉํ•˜์—ฌ ํ‚ค๋ณด๋“œ๋ฅผ ๋‚ด๋ฆด ์ˆ˜ ์žˆ๋„๋ก ํ•ด์ฃผ์—ˆ๋‹ค.
  • addGestureRecognizer๋Š” ์‹คํ—˜ํ•ด๋ณด์•˜๋Š”๋ฐ, ์ด๊ฑธ ๋“ฑ๋กํ–ˆ์„ ๋•Œ ๊ฐ™์€ ๋ทฐ์ปจ์— ์žˆ๋Š” collectionView์˜ delegate ๋ฉ”์†Œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋˜์ง€ ์•Š๋Š” ํ˜„์ƒ์„ ๋ฐœ๊ฒฌํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค.

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

extension UIViewController {
    func hideKeyboard() {
        let tap = UITapGestureRecognizer(target: self, action: #selector(UIViewController.dismissKeyboard))
        view.addGestureRecognizer(tap)
        tap.cancelsTouchesInView = false // ์ด๊ฑฐ ์ถ”๊ฐ€ํ•˜๋‹ˆ๊นŒ ํ•ด๊ฒฐ๋Œ...
    }

    @objc private func dismissKeyboard() {
        view.endEditing(true)
    }
}

[cancelsTouchesInView]

  • Bool ํƒ€์ž…์˜ ํ”„๋กœํผํ‹ฐ๋กœ default ๊ฐ’์€ true์ด๋‹ค.
  • Gesture Recognizer๊ฐ€ ์ œ์Šค์ฒ˜๋ฅผ ์ธ์‹ํ•˜๋ฉด ๋‚˜๋จธ์ง€ ํ„ฐ์น˜์ •๋ณด๋“ค์„ ๋ทฐ๋กœ ์ „๋‹ฌํ•˜์ง€ ์•Š๊ณ  ์ด์ „์— ์ „๋‹ฌ๋œ ํ„ฐ์น˜๋“ค์€ ์ทจ์†Œ๋œ๋‹ค.
  • ํ•˜์ง€๋งŒ false๋กœ ํ• ๋‹นํ•œ๋‹ค๋ฉด ์ œ์Šค์ฒ˜๋ฅผ ์ธ์‹ํ•œ ํ›„์—๋„ ํ„ฐ์น˜ ์ •๋ณด๋ฅผ ๋ทฐ์— ์ „๋‹ฌํ•˜๊ฒŒ ๋œ๋‹ค.

๋ฌธ์ œ๊ฐ€ ํ•ด๊ฒฐ๋œ ์ด์œ  ๊ธฐ์กด์—๋Š” cancelsTouchesInView๊ฐ’์ด true์—ฌ์„œ ๋‚˜๋จธ์ง€ ํ„ฐ์น˜์ •๋ณด๋“ค์„ ๋ทฐ๋กœ ์ „๋‹ฌํ•˜์ง€ ์•Š๊ณ  ์ทจ์†Œ๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์— TableView์˜ Select๊ฐ€ ๋จน์ง€ ์•Š์•˜๋˜ ๊ฒƒ์ด์˜€๋‹ค. false๋กœ ํ• ๋‹นํ•ด์คŒ์œผ๋กœ์จ ์ œ์Šค์ฒ˜๋ฅผ ์ธ์‹ํ•œ ํ›„์—๋„ Gesture Recognizer์˜ ํŒจํ„ด๊ณผ๋Š” ๋ฌด๊ด€ํ•˜๊ฒŒ ํ„ฐ์น˜ ์ •๋ณด๋ฅผ ๋ทฐ์— ์ „๋‹ฌํ•˜๊ฒŒ ๋˜์–ด ์ด ๋ฌธ์ œ๊ฐ€ ํ•ด๊ฒฐ๋˜์—ˆ๋˜ ๊ฒƒ์ด๋‹ค.

"์ด๋ฏธ์ง€๋ฅผ ๋ฆฌ์‚ฌ์ด์ฆˆ ํ•˜๋Š” ๋ฐฉ๋ฒ•"

  • ํฌ๊ธฐ๋ฅผ ์ค„์ด๊ณ , ํ™”์งˆ์„ ๋‚ฎ์ถฐ์„œ ์••์ถ•์„ ํ•œ๋‹ค.
  • UIImage๋ฅผ extensionํ•˜์—ฌ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ–ˆ๋‹ค.
  • ์ตœ๋Œ€ ํŒŒ์ผ ํฌ๊ธฐ๋ฅผ ์ง€์ •ํ•˜๊ณ  ๊ทธ ์ดํ•˜๊ฐ€ ๋  ๋•Œ๊นŒ์ง€ ๋ฐ˜๋ณตํ•˜๋ฉฐ ์ด๋ฏธ์ง€์˜ ํฌ๊ธฐ๋ฅผ ์ค„์ธ๋‹ค.
extension UIImage {
    func resized(percentage: CGFloat) -> UIImage { // ์‚ฌ์ด์ฆˆ๋ฅผ ํผ์„ผํŠธ๋งŒํผ ๋ฆฌ์‚ฌ์ด์ฆˆํ•˜๋Š” ๋ฉ”์†Œ๋“œ
        let size = CGSize(width: size.width * percentage, height: size.height * percentage)
        return UIGraphicsImageRenderer(size: size, format: imageRendererFormat).image { _ in
            draw(in: CGRect(origin: .zero, size: size))
        }
    }

    func compress() -> UIImage { // ํ˜ธ์ถœ์‹œ Data์˜ count๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์šฉ๋Ÿ‰์— ๋งž๊ฒŒ ํ™”์งˆ์„ ๋‚ฎ์ถ˜๋‹ค.
        var compressImage = self
        var quality: CGFloat = 1 // ํ™”์งˆ
        let maxDataSize = 307200 // ์ตœ๋Œ€ํฌ๊ธฐ
        guard var compressedImageData = compressImage.jpegData(compressionQuality: 1) else {
            return UIImage()
        }
        while compressedImageData.count > maxDataSize {
            quality *= 0.8 // 20ํผ์„ผํŠธ ์”ฉ ๊ฐ์†Œ
            compressImage = compressImage.resized(percentage: quality)
            compressedImageData = compressImage.jpegData(compressionQuality: quality) ?? Data()
        }
        return compressImage
    }
}

"ํ‚ค๋ณด๋“œ๊ฐ€ ์ฝ˜ํ…์ธ ๋ฅผ ๊ฐ€๋ฆฌ์ง€ ์•Š๋„๋ก ํ•˜๋Š” ๋ฐฉ๋ฒ•"

  • 1 ScrollView๋ฅผ ํ™œ์šฉํ•˜๊ธฐ
    • ํ‚ค๋ณด๋“œ์˜ ์œ„์น˜๋งŒํผ ์Šคํฌ๋กค์˜ ์œ„์น˜๋ฅผ ๋ฐ”๊ฟ”์ฃผ๋Š” ๋ฐฉ์‹
  • 2 NotificationCenter ํ™œ์šฉ
    • ํ‚ค๋ณด๋“œ๊ฐ€ ๋‚˜ํƒ€๋‚˜๊ณ  ์‚ฌ๋ผ์งˆ ๋•Œ๋งˆ๋‹ค Notification์˜ ์•Œ๋ฆผ์„ ๋ฐ›๊ธฐ
    • iOS 9 ์ด์ƒ ๋ฒ„์ „์„ ํƒ€๊ฒŸ์œผ๋กœ ์•ฑ์„ ๋งŒ๋“ ๋‹ค๋ฉด ์ž๋™์œผ๋กœ removeObserver๋ฅผ ํ•ด์ค€๋‹ค. ๋”ฐ๋ผ์„œ ์‹ ๊ฒฝ์“ฐ์ง€ ์•Š์•„๋„ ๋œ๋‹ค!
private func setUpNotification() {
    NotificationCenter.default.addObserver(
        self,
        selector: #selector(keyboardWillShow(_:)),
        name: UIResponder.keyboardWillShowNotification, // show
        object: nil
    )
    NotificationCenter.default.addObserver(
        self,
        selector: #selector(keyboardWillHide(_:)),
        name: UIResponder.keyboardWillHideNotification, // hide
        object: nil
    )
}
  • ์•Œ๋ฆผ์„ ๋ฐ›์•˜์„ ๋•Œ ํ˜ธ์ถœํ•  ๋ฉ”์†Œ๋“œ ๊ตฌํ˜„ํ•˜๊ธฐ
@objc private func keyboardWillShow(_ notification: Notification) {
    guard let userInfo = notification.userInfo as NSDictionary?,
          var keyboardFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue,
          let scrollView = self.superview?.superview as? UIScrollView,
          let view = self.superview?.superview?.superview else {
              return
          }
    var contentInset = scrollView.contentInset
    contentInset.bottom = keyboardFrame.size.height
    scrollView.contentInset = contentInset
    scrollView.scrollIndicatorInsets = scrollView.contentInset
}

@objc private func keyboardWillHide(_ notification: NSNotification) {
    guard let scrollView = self.superview?.superview as? UIScrollView else {
        return
    }
    scrollView.contentInset = UIEdgeInsets.zero
    scrollView.scrollIndicatorInsets = scrollView.contentInset
}
  • Noti๋กœ ๋ฐ›์€ keyboardFrame์„ ๋ฐ”์ธ๋”ฉ ํ•œ๋‹ค.
  • ์ฝ˜ํ…์ธ ์˜ ์ƒํ•˜์ขŒ์šฐ๋กœ ์•ˆ์ชฝ ์—ฌ๋ฐฑ์„ ์ฃผ๋Š” contentInset์˜ bottom์„ keyboardFrame.size์˜ ๋†’์ด๋กœ ๋Œ€์ž…ํ•ด์ค€๋‹ค.
    • ScrollView์˜ ์„œ๋ธŒ๋ทฐ ํฌ๊ธฐ๋ฅผ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š๊ณ  ์Šคํฌ๋กค ๋ทฐ ํฌ๊ธฐ๋ฅผ ํ™•์žฅํ•œ๋‹ค๋Š” ์˜๋ฏธ
    • ํ‚ค๋ณด๋“œ๋กœ ์ธํ•ด ๊ฐ€๋ ค์ง€๋Š” ๋ถ€๋ถ„์„ ์Šคํฌ๋กค๋ทฐ ์•„๋ž˜์ชฝ์œผ๋กœ keyboardFrame ์‚ฌ์ด์ฆˆ์˜ ๋†’์ด๋งŒํผ ๋ฒ„ํผ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ์Šคํฌ๋กค๋ทฐ๋ฅผ ํ™•์žฅํ•จ์œผ๋กœ์จ ํ‚ค๋ณด๋“œ ๊ฐ€๋ฆผํ˜„์ƒ์„ ํ•ด๊ฒฐํ•˜๋Š” ๊ฒƒ์ด๋‹ค.
  • contentInset์„ ๋ณ€๊ฒฝํ•˜๋ฉด scrollIndicatorInsets์—๋„ ์˜ํ–ฅ์„ ๋ฏธ์นœ๋‹ค.
    • ์Šคํฌ๋กค ์‹œ ๋ณด์ด๋Š” ์Šคํฌ๋กค ํ‘œ์‹œ๋ฅผ ๋งํ•œ๋‹ค.
  • scrollIndicatorInsets๊ฐ€ contentInset์œผ๋กœ ์ถ”๊ฐ€ํ•œ ๋ฒ„ํผ๊ณต๊ฐ„์—๋„ ํ‘œ์‹œ๊ฐ€ ๋œ๋‹ค.
    • ์ด๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” scrollIndicatorInsets ์†์„ฑ๋„ ๊ฐ™์ด ๋ณ€๊ฒฝํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค.
  • ์ด๋ ‡๊ฒŒ ์Šคํฌ๋กค๋ทฐ๋ฅผ ํ™œ์šฉํ•˜๋ฉด ํ‚ค๋ณด๋“œ๊ฐ€ ํ™”๋ฉด๊ฐ€๋ฆฌ๋Š” ํ˜„์ƒ์„ ์†์‰ฝ๊ฒŒ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค.

๊ทธ๋Ÿผ.. UITextView๋Š”...์–ด๋–ป๊ฒŒ?

  • ํ…์ŠคํŠธ ํ•„๋“œ๋Š” ์œ„ ๋ฐฉ๋ฒ•์œผ๋กœ ํ•ด๊ฒฐ์ด ๋˜์—ˆ์ง€๋งŒ UITextView๋Š” ์–ด๋–ป๊ฒŒ ์Šคํฌ๋กค์ด ๋”ฐ๋ผ์˜ค๊ฒŒ ๋งŒ๋“ค๊นŒ?
    • ์ด ๋ถ€๋ถ„์€ ํ…์ŠคํŠธ๋ทฐ์˜ ์Šคํฌ๋กค ๊ธฐ๋Šฅ์„ false๋กœ ์ œ๊ฑฐํ•œ๋‹ค.
      • TextView.isScrollEnabled = false
    • ๊ทธ๋ฆฌ๊ณ  ์˜คํ† ๋ ˆ์ด์•„์›ƒ์œผ๋กœ TextView์˜ ๋†’์ด๊ฐ€ ๋Š˜ ๊ณ ์ •๋˜์–ด์žˆ๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ๋Š˜์–ด๋‚  ์ˆ˜๋„ ์žˆ๋„๋ก priority๋ฅผ ์กฐ์ ˆํ•ด์ฃผ๋ฉด ๋œ๋‹ค.
  • ์œ„์™€ ๊ฐ™์ด ์„ธํŒ…ํ•œ๋‹ค๋ฉด, ํ…์ŠคํŠธ๋ทฐ๊ฐ€ ์•ˆ์— Text๊ฐ€ ๊ธธ์–ด์งˆ ์ˆ˜๋ก ๋†’์ด๊ฐ€ ๋Š˜์–ด๋‚˜๊ณ , ๊ทธ์— ๋”ฐ๋ผ ์Šคํฌ๋กค๋„ ์ž๋™์œผ๋กœ ๋‚ด๋ ค์˜จ๋‹ค.
    • ํ•ด๊ฒฐํ•ด๋ณด๋ฉด์„œ ๋ฉ”๋ชจ์žฅ๋„ ์ด๋Ÿฐ ๋ฐฉ์‹์ธ๊ฑด๊ฐ€? ํ–ˆ๋‹ค...!

top

[ํ•™์Šต ๊ธฐ๋ก ํ”์ ]

๋ชฉ์ฐจ

STEP 1 : ๋„คํŠธ์›Œํ‚น ํƒ€์ž… ๊ตฌํ˜„

  • ๋„คํŠธ์›Œํฌ ํ†ต์‹ ์„ ๋‹ด๋‹นํ•œ ํƒ€์ž…์„ ์„ค๊ณ„ํ•ฉ๋‹ˆ๋‹ค.
  • Mock ๋ฐ์ดํ„ฐ๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๋‹จ์œ„ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

1-1 ๊ณ ๋ฏผํ–ˆ๋˜ ๊ฒƒ

1. ๋‹จ์ผ ์ฑ…์ž„ ์›์น™(Single responsibility principle)

  • ํ•œ ํƒ€์ž…์ด ํ•˜๋‚˜์˜ ์—ญํ• ๋งŒ ํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ค๊ณ„์— ๋งŽ์€ ๊ณ ๋ฏผ์„ ํ•ด๋ณด์•˜๋‹ค.

2. CodingKeys ํ™œ์šฉ

์‹ค์ œ ๋„คํŠธ์›Œํฌ์—์„œ ๋‚ด๋ ค์˜ค๋Š” ๋ณ€์ˆ˜๋ช…์ด ์Šค๋„ค์ดํฌ ์ผ€์ด์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ณ€์ˆ˜๋Š” Codingkey๋ฅผ ์ด์šฉํ•˜์—ฌ parsingํ•˜๋Š” key๋ฅผ ๋ฐ”๊ฟ”์ฃผ์—ˆ์œผ๋ฉฐ ์Šค๋„ค์ดํฌ์ผ€์ด์Šค๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”, ์ฆ‰ ํƒ€์ž…์˜ ๋ณ€์ˆ˜๋ช…๊ณผ ์ผ์น˜ํ•˜๋ฉด rawValue๋ฅผ ๋ช…์‹œํ•  ํ•„์š”๊ฐ€ ์—†์–ด ๊ฐ€๋…์„ฑ์„ ์œ„ํ•ด ํ•œ ์ค„๋กœ case๋ฅผ ํ•ฉ์ณ์ฃผ์—ˆ๋‹ค.

enum CodingKeys: String, CodingKey {
   case id, stock, name, thumbnail, currency, price, images, vendors
   case vendorID = "vendor_id"
   case bargainPrice = "bargain_price"
   case discountedPrice = "discounted_price"
   case createdAt = "created_at"
   case issuedAt = "issued_at"
}

3. NetworkManager์™€ Network

  • Networkํ•˜๋Š” ๊ณผ์ •์—์„œ ์—ญํ• ๋งˆ๋‹ค ๊ฐ์ฒด๋ฅผ ๊ตฌ๋ถ„ํ•˜์—ฌ ๊ตฌํ˜„ํ•˜์˜€๋‹ค.
    • Network : dataTask()๋ฅผ ํ†ตํ•ด SessionDataTask๋ฅผ ์„œ๋ฒ„๋กœ ์ „์†กํ•ด ์ง์ ‘ ๋„คํŠธ์›Œํ‚นํ•˜๋Š” ๊ฐ์ฒด

      func execute(request: URLRequest, completion: @escaping (Result<Data?, Error>) -> Void) {
              session.dataTask(with: request) { data, response, error in
              ...
    • NetworkManager : Network์˜ excute๋ฅผ ํ†ตํ•ด data๋ฅผ ๋ฐ›์•„ decodingํ•˜๋Š” fetch()๋ฅผ ๊ฐ€์ง„ ๊ฐ์ฒด

      func fetch<T: Decodable>(request: URLRequest,
                                  decodingType: T.Type,
                                  completion: @escaping (Result<T, Error>) -> Void) {
              
           network.execute(request: request) { result in

4. Name Space

  • ํ•˜๋“œ์ฝ”๋”ฉ์„ ๊ฐœ์„ ํ•˜๊ธฐ ์œ„ํ•ด enum ํƒ€์ž…์„ ๋งŒ๋“ค์–ด Address์™€ HTTPMethod์˜ ๊ฐ’๋“ค์„ ๋ถ„๋ฅ˜ํ•ด์ฃผ์—ˆ๋‹ค.

5. Request, Response

  • Requestํ•  ๋•Œ, ๊ทธ๋ฆฌ๊ณ  Responseํ•˜๋Š” ํƒ€์ž…์ด ์„ธ๋ถ€์ ์œผ๋กœ ๋‹ฌ๋ผ ProductModification, ProductRegistration ๋“ฑ... ๊ฐ ํƒ€์ž…์„ ๋ชจ๋‘ ๊ตฌํ˜„ํ•˜์˜€๋‹ค.

6. Overloading function

  • ์ƒํ’ˆ ์‚ญ์ œ, ๋“ฑ๋ก, ์กฐํšŒ ๋“ฑ ์—ฌ๋Ÿฌ๊ฐ€์ง€ ์š”์ฒญ์„ request ๋ฉ”์†Œ๋“œ ํ•˜๋‚˜๋ฅผ ์˜ค๋ฒ„๋กœ๋”ฉ์„ ํ™œ์šฉํ•˜์—ฌ ์ž‘์„ฑํ•˜์˜€๋‹ค.

7. Test Doubles

  • ํ…Œ์ŠคํŠธ ์ž‘์„ฑ์„ ์œ„ํ•ด ์˜์กด์„ฑ ์ฃผ์ž…์„ ํ™œ์šฉํ•˜์—ฌ Mock, Stub ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด ํ™œ์šฉํ•˜์˜€๋‹ค.
  • URLProtocol์„ ์ƒ์†๋ฐ›์€ ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค๊ณ  ์žฌ์ •์˜๋ฅผ ํ•ด์ฃผ์—ˆ๋‹ค.
    • ์ด ๋ฐฉ๋ฒ•์€ URLSession์˜ dataTask๋ฅผ ์ง์ ‘ Stub์œผ๋กœ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•๋„ ์žˆ์—ˆ์ง€๋งŒ, URLSessionDataTask๋ฅผ ์ฑ„ํƒํ•œ ํƒ€์ž…์— init()์„ ์ •์˜ํ•˜๋‹ˆ deprecated ๊ฒฝ๊ณ ๊ฐ€ ๋– ์„œ ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ์‚ญ์ œ ํ›„ URLProtocol์„ ํ™œ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ ๋กœ์ง์„ ๋ณ€๊ฒฝํ•˜์˜€๋‹ค.

1-2 ์˜๋ฌธ์ 

  • ๋น„๋™๊ธฐ ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋™๊ธฐ ๋ฉ”์„œ๋“œ๋Š” ๋น„๋™๊ธฐ ๋ฉ”์„œ๋“œ ํ…Œ์ŠคํŠธ๋กœ ์ง„ํ–‰ํ•ด์•ผํ• ๊นŒ?
  • URLProtocol๊ณผ URLSession์˜ ๊ด€๊ณ„๊ฐ€ ์ •ํ™•ํ•˜๊ฒŒ ์ดํ•ด๋˜์ง€ ์•Š๋Š”๋‹ค...
  • Health Checker์˜ ํ•„์š”์„ฑ์„ ๋ชจ๋ฅด๊ฒ ๋‹ค..
  • ํ…Œ์ŠคํŠธ ์‹œ Request์˜ ๋ฐ”๋””๋„ ์ฒดํฌ๋ฅผ ํ•ด์•ผํ• ๊นŒ?

1-3 Trouble Shooting

1. URLSessionDataTask๋ฅผ ์ฑ„ํƒํ•œ ํƒ€์ž…์— init()์— deprecated ๊ฒฝ๊ณ ..?

1. URLSessionDataTask๋ฅผ ์ฑ„ํƒํ•œ ํƒ€์ž…์— init()์— deprecated ๊ฒฝ๊ณ ..?

  • ์ƒํ™ฉ URLSessionDataTask์„ ๋Œ€์ฒดํ•  ๊ฐ์ฒด๋กœย StubURLSessionDataTaskย ๋ฅผ ๊ตฌํ˜„ํ•˜๋‹ค๊ฐ€ ๊ฒฝ๊ณ ๋ฅผ ๋งˆ์ฃผํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค.
class StubURLSessionDataTask: URLSessionDataTask {
    var dummyData: DummyData?

    // init ๋ถ€๋ถ„์—์„œ ์—๋Ÿฌ๊ฐ€ ๋‚ฌ๋‹ค.
    init(dummy: DummyData?, completionHandler: DataTaskCompletionHandler?) {
        self.dummyData = dummy
        self.dummyData?.completionHandler = completionHandler
    }

    override func resume() {
        dummyData?.completion()
    }
}

'init()' was deprecated in iOS 13.0: Please use -[NSURLSession dataTaskWithRequest:] or other NSURLSession methods to create instances

  • ์ด์œ  URLSessionDataTask init()์ด IOS13 ์ดํ›„์— deprecatede๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ํ•ด๋‹น ๊ฒฝ๊ณ ๋ฅผ ์—†์• ๊ณ  ์‹ถ์–ด์„œ ๊ตฌ๊ธ€๋ง์„ ํ•˜๋‹ค๊ฐ€ URLProtocol์„ ๋ฐœ๊ฒฌํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค.
  • ํ•ด๊ฒฐ URLProtocol์„ ์ƒ์†๋ฐ›์€ MockURLProtocol์„ ๋งŒ๋“ค์–ด์„œ URLSession configuration์„ ๊ตฌ์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ณ  ๊ธฐ์กด์— ๋งŒ๋“ค์—ˆ๋˜ StubURLSessionDataTask, DummyData, MockSession ํƒ€์ž…์€ ๋”์ด์ƒ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ฒŒ๋˜์–ด ๋ชจ๋‘ ์‚ญ์ œํ•ด์ฃผ์—ˆ๋‹ค.
    • URLProtocol์ด๋ž€?
      • URL ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ์„ ๋‹ค๋ฃจ๋Š” ์ถ”์ƒํด๋ž˜์Šค
    • URLProtocol์€ URLProtocolClient ํ”„๋กœํ† ์ฝœ์„ ํ†ตํ•ด ๋„คํŠธ์›Œํฌ ์ง„ํ–‰ ์ƒํ™ฉ์„ ์ „๋‹ฌํ•œ๋‹ค.
    • ํ…Œ์ŠคํŠธ ๋ฒˆ๋“ค์—์„œ MockURLProtocol ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค๊ณ  ๋ฉ”์†Œ๋“œ๋ฅผ ์žฌ์ •์˜ ํ•ด์ค€๋‹ค.
    • ๋กœ๋“œ๋ฅผ ํ•  ๋•Œ ์„ค์ •ํ•œ ํ›„ ์ „๋‹ฌํ•  Data, Error, Response๋ฅผ ๋”•์…”๋„ˆ๋ฆฌ๋กœ ์„ค์ •ํ•ด์ค€๋‹ค.
      • ์ด ๊ฐ’์€ URLProtocol์— ์—ฐ๊ฒฐํ•˜์—ฌ ์„ค์ •๊ฐ’์„ ์„ธํŒ…ํ•ด์ฃผ๊ธฐ ์œ„ํ•œ ๊ฐ’์ด ๋œ๋‹ค.
    • Unit Test๋ฅผ ์œ„ํ•ด ์ƒ์†๋ฐ›์•„์„œ ์˜ค๋ฒ„๋ผ์ด๋“œ ํ•จ์œผ๋กœ์จ ์ปค์Šคํ…€ ํ•˜์—ฌ Mock ๊ฐ์ฒด๋ฅผ ์ƒˆ๋กญ๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.
      • ๊ธฐ์กด์ฒ˜๋Ÿผ ์™ธ๋ถ€ ๋„คํŠธ์›Œํฌ์— ์š”์ฒญ์„ ์ง์ ‘ ๋ณด๋‚ด๋Š” ๋™์ž‘์ด ์•„๋‹ˆ๋ผ, ์š”์ฒญ์„ ๊ฐ€๋กœ์ฑ„์„œ ์›ํ•˜๋Š” ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•˜๊ฒŒ ๋” ์ปค์Šคํ…€ ํ•˜๋Š” ์ž‘์—…์ด๋‹ค.
      • ์ฆ‰ ์›๋ž˜ ๊ฐ™์ด ์›น ์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ๊ณผ์ •์ด ์•„๋‹ˆ๊ณ , ๋‚ด๊ฐ€ ์„ค์ •ํ•œ ๊ฐ’(data, response)์„ ๊ทธ๋Œ€๋กœ ๋ฐ˜ํ™˜ํ•˜๊ฒŒ ๋งŒ๋“ค์–ด ์ฃผ๋Š” ๊ณผ์ •์ธ ๊ฒƒ์ด๋‹ค.
2. multipart form-data ์•ˆ์— ์ด๋ฏธ์ง€์™€ JSON์„ ๊ฐ™์ด ๋„ฃ๋Š” ๋ฐฉ๋ฒ•

2. multipart form-data ์•ˆ์— ์ด๋ฏธ์ง€์™€ JSON์„ ๊ฐ™์ด ๋„ฃ๋Š” ๋ฐฉ๋ฒ•

  • ์ƒํ™ฉ JSON์€ ์ธ์ฝ”๋”ฉํ•ด์„œ ๋ฐ”๋””์— ์ถ”๊ฐ€ํ•ด์ฃผ๋ฉด ๋˜์ง€๋งŒ, multipart form-data์˜ ๊ฒฝ์šฐ ์–‘์‹์ด ๋‹ฌ๋ž๋‹ค.

  • ์ด์œ  ์•„๋ž˜ ์–‘์‹์— ๋งž์ถฐ์„œ JSON๊ณผ ์ด๋ฏธ์ง€ํŒŒ์ผ์„ ๋ณ€ํ™˜ํ•ด์„œ ๋ฐ”๋””์— ๋„ฃ์–ด์ฃผ๊ธฐ ์œ„ํ•ด์„œ multipart form-data์œผ๋กœ body์— ํŒŒ์ผ์„ ์‹ค์–ด๋ณด๋Š” ์ž‘์—…์„ ์ฐพ์•„๋ณด์•˜๋‹ค.

    POST /test.html HTTP/1.1 // \r\n
    Host: example.org // \r\n
    Content-Type: multipart/form-data;boundary="boundary" // \r\n
     // \r\n
    --boundary // \r\n
    Content-Disposition: form-data; name="field1" // \r\n
    // \r\n
    value1 // \r\n
    --boundary // \r\n
    Content-Disposition: form-data; name="field2"; filename="example.txt" // \r\n
     // \r\n
    value2 // \r\n
    --boundary-- // \r\n
    • HTTP ํ†ต์‹  ๊ทœ๊ฒฉ์„ ํ™•์ธํ•ด์„œ JSONํŒŒ์ผ๊ณผ ImageํŒŒ์ผ์„ ๋ฐ”๋””์— ์ถ”๊ฐ€ํ•˜๊ฒŒ ์ฝ”๋“œ๋ฅผ ์งœ์•ผํ–ˆ๋‹ค.
      • Content-Type์ด multipart form-data๋กœ ์ง€์ •๋˜์–ด ์žˆ์–ด์•ผํ•œ๋‹ค.
      • ์ „์†ก๋˜๋Š” ํŒŒ์ผ ๋ฐ์ดํ„ฐ์˜ ๊ตฌ๋ถ„์ž๋กœ boundary์— ์ง€์ •๋˜์–ด์žˆ๋Š” ๋ฌธ์ž์—ด์„ ์ด์šฉํ•œ๋‹ค.
      • ๋งˆ์ง€๋ง‰์—๋Š” boundary ์–‘์˜†์— -- ๋ฅผ ๋ถ™์—ฌ์„œ ๋ฐ”๋””์˜ ๋์„ ์•Œ๋ฆฐ๋‹ค.
      • header์™€ header๋ฅผ ๊ตฌ๋ถ„ํ•˜๊ธฐ ์œ„ํ•ด ๊ฐœํ–‰๋ฌธ์ž๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค. \r\n
      • header์™€ body๋ฅผ ๊ตฌ๋ถ„ํ•˜๊ธฐ ์œ„ํ•ด ๊ฐœํ–‰๋ฌธ์ž 2๊ฐœ๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค. \r\n\r\n
      • body์— ํฌํ•จ๋˜์–ด์žˆ๋Š” file data๋ฅผ ๊ตฌ๋ถ„ํ•˜๊ธฐ ์œ„ํ•ด boundary๋ฅผ ๋„ฃ์–ด์ค€๋‹ค.
  • ํ•ด๊ฒฐ ์œ„์—์„œ ์ •๋ฆฌํ•œ ์–‘์‹๋Œ€๋กœ ๋ฐ”๋””๋ฅผ ์ถ”๊ฐ€ํ•˜๋„๋ก ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์˜€๋‹ค.

1-4 ๋ฐฐ์šด ๊ฐœ๋…

  • multipart/form-data
  • API๋ฌธ์„œ ์ฝ๋Š” ๋ฐฉ๋ฒ•
  • ํŒŒ์‹ฑํ•œ JSON ๋ฐ์ดํ„ฐ์™€ ๋งคํ•‘ํ•  ๋ชจ๋ธ ์„ค๊ณ„
    • CodingKeysย ํ”„๋กœํ† ์ฝœ์˜ ํ™œ์šฉ
  • URL Session์„ ํ™œ์šฉํ•œ ์„œ๋ฒ„์™€์˜ ํ†ต์‹ 
    • URLRequest๋ฅผ ์„ค์ •ํ•˜๋Š” ๋ฐฉ๋ฒ•
    • Testableํ•œ ๋„คํŠธ์›Œํฌ ์ฝ”๋“œ ์ž‘์„ฑํ•˜๊ธฐ
      • ๋„คํŠธ์›Œํฌ ์ƒํ™ฉ๊ณผ ๋ฌด๊ด€ํ•œ ๋„คํŠธ์›Œํ‚น ๋ฐ์ดํ„ฐ ํƒ€์ž…์˜ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ(Unit Test)

1-5 PR ํ›„ ๊ฐœ์„ ์‚ฌํ•ญ

  • ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์— ์ค‘๋ณต๋˜๋Š” ๋ถ€๋ถ„์„ ๊ฐœ์„ 
    • ๋น ์ง„ ์ฃผ์„ ๋ฐ ์ค„๋ฐ”๊ฟˆ์„ ์ˆ˜์ •
  • Image์˜ ํ”„๋กœํผํ‹ฐ ๋„ค์ด๋ฐ์„ ๋ช…ํ™•ํ•˜๊ฒŒ ์ˆ˜์ •
  • ํ•˜๋“œ์ฝ”๋”ฉ ๋˜์–ด์žˆ๋Š” ๋ฌธ์ž์—ด์„ ๋”ฐ๋กœ enum ํƒ€์ž…์œผ๋กœ ๋นผ์ฃผ์–ด ๊ฐœ์„ 
  • ์—๋Ÿฌ์˜ ๋„ค์ด๋ฐ์„ ๋ช…ํ™•ํ•˜๊ฒŒ ๊ฐœ์„ 
  • Parser, Parsable์˜ ๋„ค์ด๋ฐ์„ JSON์„ ๋ง๋ถ™ํ˜€ ๋ช…ํ™•ํ•˜๊ฒŒ ๊ฐœ์„ 
  • ์ ‘๊ทผ์ œ์–ด๊ฐ€ ๋ถ™์–ด์žˆ์ง€ ์•Š์€ ํ”„๋กœํผํ‹ฐ์— ์ ‘๊ทผ์ œ์–ด๋ฅผ ์ถ”๊ฐ€
  • Address์˜ ๋„ค์ด๋ฐ์„ ๋ช…ํ™•ํ•˜๊ฒŒ ๊ฐœ์„  (APIAddress)

top

STEP 2 : ์ƒํ’ˆ ๋ชฉ๋ก ํ™”๋ฉด ๊ตฌํ˜„

์ƒํ’ˆ๋ชฉ๋ก์„ ๋ณผ ์ˆ˜ ์žˆ๋Š” ํ™”๋ฉด์„ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.

2-1 ๊ณ ๋ฏผํ–ˆ๋˜ ๊ฒƒ

  • CollectionView ํ•˜๋‚˜๋กœ Cell ๋‘๊ฐœ๋ฅผ ํ™œ์šฉํ•˜์—ฌ ํ™”๋ฉด์„ ์ „ํ™˜ํ•˜๊ธฐ

    • Custom Cell์„ ๊ตฌํ˜„ํ•˜๊ณ , ๋‘๊ฐœ์˜ ๋ ˆ์ด์•„์›ƒ์„ ๋งŒ๋“ค์–ด ์…€๋งŒ ๋ฐ”๊ฟ”์ฃผ๋Š” ๋ฐฉ์‹์œผ๋กœ ๋ชฉ๋กํ™”๋ฉด ๊ตฌ์„ฑ
    • FlowLayout์„ ํ™œ์šฉํ•˜์—ฌ Cell์˜ ๋ ˆ์ด์•„์›ƒ์„ ๊ตฌ์„ฑ
    • ์„œ๋ฒ„์—์„œ ์ƒํ’ˆ ๋ชฉ๋ก์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ๋ถ€๋ถ„๊ณผ ๋ทฐ๋ฅผ ๊ทธ๋ฆฌ๋Š” ๋ถ€๋ถ„ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ ๊ตฌํ˜„
  • CollectionView cell ๊ฐ๊ฐ xib๋กœ ๊ตฌํ˜„

    • CollectionView์˜ GridCell, ListCell์„ ๊ฐ๊ฐ xibํŒŒ์ผ์„ ์ƒ์„ฑํ•˜์—ฌ storyboard๋กœ ๊ตฌํ˜„ํ•˜์˜€๊ณ  ๋‘๊ฐœ์˜ xib์— ๋Œ€ํ•œ ์ฝ”๋“œ๋Š” ProductCell ํ•˜๋‚˜์˜ cell๋กœ ๊ตฌ์„ฑ
  • Network๋ฅผ ํ†ตํ•ด data๋ฅผ ๊ฐ€์ ธ์™€ CollectionView๋ฅผ ๊ตฌ์„ฑ

    • API์˜ Data๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•ด productList Searchํ•˜๋Š” request ์ƒ์„ฑํ•˜์—ฌ networkManager์˜ fetch()๋กœ network๋ฅผ ์ง„ํ–‰ํ•˜์˜€๊ณ  ๊ฐ€์ ธ์˜จ data๋กœ collectionViewloadํ•˜์˜€๋‹ค.

      let request = networkManager.requestListSearch(page: 1, itemsPerPage: 10) else {
      ...        
      networkManager.fetch(request: request, decodingType: Products.self) { result in
          switch result {
              case .success(let products):
                 self.productList = products.pages
                 self.collectionViewLoad()
              ...
  • CollectionView๋ฅผ ์žฌ๊ตฌ์„ฑํ•˜๋Š” ๊ฒฝ์šฐ reloadData() ์‚ฌ์šฉ

    • SegmentControl์„ ์ด์šฉํ•ด flowlayout์„ ๋ณ€๊ฒฝํ•˜๋Š” ๊ฒฝ์šฐ CollectionView๋ฅผ ์žฌ๊ตฌ์„ฑํ•˜๊ธฐ ์œ„ํ•ด reloadData๋ฅผ ์‚ฌ์šฉํ•˜์˜€๋‹ค.

      // list -> gird, grid -> list๋กœ ๋ณ€๊ฒฝ
      @IBAction private func switchSegmentedControl(_ sender: UISegmentedControl) {
              switch sender.selectedSegmentIndex {
              case 0:
                  currentCellIdentifier = ProductCell.listIdentifier
                  collectionView.setUpListFlowLayout()
                  collectionView.scrollToTop()
                  collectionView.reloadData()
      
  • alert์„ ์ด์šฉํ•œ Error Handling

    • OpenMarket app์—์„œ ๋ฐœ์ƒํ•œ error๋Š” alert ์ฐฝ์„ ๋„์›Œ error๋ฅผ ๋‚˜ํƒ€๋‚ด์—ˆ๋‹ค.
    • localizedError ํ”„๋กœํ† ์ฝœ์˜ errorDescription์„ ์ด์šฉํ•˜์—ฌ description์„ ์ •์˜ํ•˜์˜€๊ณ  error.localizedDescription์œผ๋กœ error Message๋ฅผ ์ถœ๋ ฅํ•˜๋„๋ก ์—๋Ÿฌ์ฒ˜๋ฆฌ.
  • ์ƒํ’ˆ๋“ฑ๋ก ๋ฒ„ํŠผ Segue

    • HIG๋ฅผ ์ฐธ๊ณ ํ•˜์—ฌ ์ƒํ’ˆ๋“ฑ๋ก ๋ฒ„ํŠผ์„ ๋ˆŒ๋ €์„ ๋•Œ Navigation ํ˜•ํƒœ๊ฐ€ ์•„๋‹ˆ๋ผ Modal๋กœ ๋„์šฐ๋„๋ก ๊ตฌ์„ฑ
    • Navigation Bar๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์ทจ์†Œ ๋ฒ„ํŠผ์„ ๊ตฌ์„ฑ

2-2 ์˜๋ฌธ์ 

  • collectionview์˜ flowlayout์„ ๋ณ€๊ฒฝํ•  ๋•Œ AutoLayout ์ถฉ๋Œ ๊ด€๋ จ ๊ฒฝ๊ณ ๊ฐ€ ๋œจ๋Š”๋ฐ, ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์„ ๋ชจ๋ฅด๊ฒ ๋‹ค.
  • SegmentControl์„ ํ™œ์šฉํ•˜์—ฌ List๋‚˜ Grid๋ฅผ ์ „ํ™˜ํ•  ๋•Œ ์ƒ๊ธฐ๋Š” ์•ฝ๊ฐ„์˜ ๋”œ๋ ˆ์ด์˜ ์›์ธ์„ ๋ชจ๋ฅด๊ฒ ๋‹ค.

2-3 Trouble Shooting

1. Segument Control์„ ์ด์šฉํ•˜์—ฌ ํ™”๋ฉด์ „ํ™˜ ์‹œ ์Šคํฌ๋กค ์œ„์น˜๊ฐ€ ์ •์ƒ์ ์ด์ง€ ์•Š์€ ๊ฒฝ์šฐ

https://i.imgur.com/DRtK0Xs.gif

  • ์ƒํ™ฉ FlowLayout์„ ํ™œ์šฉํ•˜์—ฌ ํ™”๋ฉด์„ ์ „ํ™˜ํ•  ๋•Œ, ์Šคํฌ๋กค์ด ์ƒ๋‹จ์— ์œ„์น˜ํ•˜๋Š”๊ฒŒ ์•„๋‹ˆ๋ผ ์ œ๋ฉ‹๋Œ€๋กœ์ธ ์œ„์น˜์— ๊ฐ€์žˆ๋Š” ํ˜„์ƒ์ด ๋ฐœ์ƒํ–ˆ๋‹ค.

  • ์ด์œ  ๋ ˆ์ด์•„์›ƒ์ด ์„œ๋กœ ๋‹ค๋ฅด๊ธฐ ๋•Œ๋ฌธ์— ์Šคํฌ๋กค์˜ ์ขŒํ‘œ๋„ ๋‹ค๋ฅธ ๊ฒƒ์œผ๋กœ ์ถ”์ธก์ด ๋˜์—ˆ๋‹ค.

  • ํ•ด๊ฒฐ ๋”ฐ๋ผ์„œ ์ด ๋ถ€๋ถ„์„ ํ™”๋ฉด์„ ์ „ํ™˜ํ•  ๋•Œ ์Šคํฌ๋กค์˜ ์œ„์น˜๋ฅผ ์ƒ๋‹จ์— ์œ„์น˜ํ•˜๊ฒŒ ์„ค์ •ํ•ด์ฃผ๋‹ˆ ํ•ด๊ฒฐ๋˜์—ˆ๋‹ค.

    extension UIScrollView {
        func scrollToTop() {
            let topOffset = CGPoint(x: 0, y: -contentInset.top)
            setContentOffset(topOffset, animated: false)
        }
    }

2-4 ๋ฐฐ์šด ๊ฐœ๋…

  • UICollectionView UICollectionViewFlowLayout
  • Networking์„ ํ†ตํ•œ ๋ทฐ์— ๋Œ€ํ•œ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ
  • reloadData
  • Xib File
  • UISegmentedControl
  • UIActivityIndicatorView

2-5 PR ํ›„ ๊ฐœ์„ ์‚ฌํ•ญ

  • Asset์— ๋“ฑ๋ก๋˜์–ด์žˆ๋Š” ์ด๋ฏธ์ง€ ์„ค์ •๊ฐ’ ์ˆ˜์ •
  • ์ „๋ฐ˜์ ์ธ ๋„ค์ด๋ฐ ์ˆ˜์ •
  • ์‚ผํ•ญ์—ฐ์‚ฐ์ž๋กœ ์กฐ๊ฑด๋ฌธ ๊ฐœ์„ 
  • ๋น ์ ธ์žˆ๋Š” ์ ‘๊ทผ์ œ์–ด ์ถ”๊ฐ€
  • ๋™์ ์œผ๋กœ ๋ ˆ์ด์•„์›ƒ์„ ์žก์„ ์ˆ˜ ์žˆ๋„๋กย UICollectionViewDelegateFlowLayout์„ ์ฑ„ํƒ
    • ๊ฐ€๋กœ๋ชจ๋“œ, ์„ธ๋กœ๋ชจ๋“œ์—์„œ๋„ ๋ ˆ์ด์•„์›ƒ์ด ๋ญ‰๊ฐœ์ง€์ง€ ์•Š๋„๋ก ๊ฐœ์„ 

top

STEP Bonus : ๋กœ์ปฌ ์บ์‹œ ๊ตฌํ˜„

์„œ๋ฒ„์—์„œ ๋ฐ›์€ ๋ฐ์ดํ„ฐ๋ฅผ ๋กœ์ปฌ์— ์บ์‹œํ•ฉ๋‹ˆ๋‹ค.

3-1 ๊ณ ๋ฏผํ–ˆ๋˜ ๊ฒƒ

  • Pagination
    • ์Šคํฌ๋กค์ด ํ•˜๋‹จ์— ๊ฐ€๊นŒ์›Œ์ง€๋ฉด ๋‹ค์Œ ํŽ˜์ด์ง€๋ฅผ ๋กœ๋“œํ•˜๋„๋ก ๊ตฌ์„ฑ
    • scrollViewDidScroll()๋ฅผ ์ด์šฉํ•˜์—ฌ ๊ตฌํ˜„
  • Cache
    • ์•ฑ์ด ์‹คํ–‰ํ•˜๋Š” ๋™์•ˆ ์บ์‹œ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์„ ์ˆ˜ ์žˆ๋„๋ก ์‹ฑ๊ธ€ํ†ค ํŒจํ„ด์œผ๋กœ ImageManager ํƒ€์ž…์„ ์ƒ์„ฑ

    • Cell์—์„œ URL๋กœ ์ด๋ฏธ์ง€๋ฅผ ๊ฐ€์ ธ์˜ค๋˜ ๋ถ€๋ถ„์„ ์บ์‹ฑ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๋„๋ก ๊ตฌ์„ฑ

      if let cachedImage = ImageManager.shared.loadCachedData(for: url) {
          productImageView.image = cachedImage
      } else {
          ImageManager.shared.downloadImage(with: url) { image in
          ImageManager.shared.setCacheData(of: image, for: url)
          self.productImageView.image = image
          }
      }

3-2 ์˜๋ฌธ์ 

  • ์Šคํฌ๋กค ์‹œ yOffset์„ ๋น„์ •์ƒ์ ์œผ๋กœ ์นด์šดํŠธ๊ฐ€ ๋˜๋Š” ๋ถ€๋ถ„์ด ๋ฌธ์ œ์—ฌ์„œ ์•„์ง ํ’€๋ฆฌ์ง€ ์•Š์•˜๋Š”๋ฐ, ํ•ด๋‹น ๋ถ€๋ถ„์„ ๊ทธ๋ƒฅ ๋„˜์–ด๊ฐ€๋„ ๋˜๋Š”๊ฑด์ง€[?] ์•ฝ๊ฐ„์˜ ์ฐ์ฐํ•จ์ด ๋‚จ๋Š”๋‹ค.

3-3 Trouble Shooting

1. ์Šคํฌ๋กค ํ•˜๋Š” ํ˜„์žฌ ์œ„์น˜๊ฐ€ ๋น„์ •์ƒ์ ์œผ๋กœ ์นด์šดํŠธ๋˜๋Š” ํ˜„์ƒ

  • ํ•ญ์ƒ ๋œจ๋Š” ์—๋Ÿฌ๋Š” ์•„๋‹ˆ๊ณ , ๊ฐ„ํ—์ ์œผ๋กœ ๋œจ๋Š” ์—๋Ÿฌ๋‹ค. ์Šคํฌ๋กค์„ ํ•˜๋‹จ๊นŒ์ง€ ํ–ˆ์„ ๋•Œ ๋””์ฝ”๋”ฉ์— ์‹คํŒจํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๋””๋ฒ„๊น…์„ ํ•ด๋ณด๋‹ˆ ๊ฒฐ๊ณผ๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.
  • ๋ทฐ์ปจํŠธ๋กค๋Ÿฌ ์ชฝ์—์„œ ๋„คํŠธ์›Œํฌ ๋งค๋‹ˆ์ €์˜ fetch ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ชฝ์—์„œ ์—๋Ÿฌ๊ฐ€ ๋‚˜๋Š” ๊ฒƒ ๊ฐ™์€๋ฐ, ๋„คํŠธ์›Œํฌ์—์„œ๋Š” success๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ธด ํ–ˆ์œผ๋‚˜ ์กฐํšŒ๋ฅผ ํ•ด๋ณด๋ฉด ๋ฐ์ดํ„ฐ๊ฐ€ ๋น„์–ด์žˆ๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.
  • Response๋ฅผ ํ™•์ธํ•ด๋ณด๋ฉด 204์ฝ”๋“œ๋กœ ์‘๋‹ตํ•˜๊ณ  ์žˆ๋‹ค.
  • ์Šคํฌ๋กค ํ•˜๋Š” ๋ถ€๋ถ„์— ์ค‘๋‹จ์ ์„ ์ฐ๊ณ  ํ™•์ธํ•ด๋ณด๋‹ˆ currentPage๊ฐ€ 104๊ฐ€ ๋˜์–ด์žˆ๋Š” ๊ฒƒ๋„ ํ™•์ธ๋˜์—ˆ๋‹ค. ์ € ์กฐ๊ฑด๋ฌธ์ด๋ผ๋ฉด 104๊ฐ€ ๋  ์ˆ˜๊ฐ€ ์—†๋Š”๋ฐ.. ์–ด๋Š์ˆœ๊ฐ„ ์Šคํฌ๋กค์„ ๊ณ„์‚ฐํ•˜๋Š” ๋ถ€๋ถ„(yOffset)์—์„œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•ด์„œ ๋น„์ •์ƒ์ ์œผ๋กœ currentPage๊ฐ€ ์˜ฌ๋ผ๊ฐ€๋Š” ๊ฒƒ ๊ฐ™๋‹ค.
if heightRemainBottomHeight < frameHeight ,
   let page = page, page.hasNext, page.pageNumber == currentPage {
    currentPage += 1
    self.requestProducts()
}
  • ๋”ฐ๋ผ์„œ ์œ„์™€ ๊ฐ™์ด ์กฐ๊ฑด๋ฌธ์„ ํ•˜๋‚˜ ๋” ์ถ”๊ฐ€ํ•ด์„œ ์•ˆ์ „ํ•˜๊ฒŒ currentPage๋ฅผ ๋”ํ•ด์ค„ ์ˆ˜ ์žˆ๋„๋ก ์ž„์‹œ๋ฐฉํŽธ์œผ๋กœ ์ˆ˜์ •ํ•ด์ฃผ์—ˆ๋‹ค.

3-4 ๋ฐฐ์šด ๊ฐœ๋…

  • UIScrollViewDelegate๋ฅผ ์ด์šฉํ•œ pagination ๊ตฌํ˜„
  • Cache์˜ ๋Œ€ํ•œ ๊ฐœ๋…
    • Caching์˜ ๋ฒ”์œ„

3-5 PR ํ›„ ๊ฐœ์„ ์‚ฌํ•ญ

  • ์ฝ”๋”ฉ ์ปจ๋ฒค์…˜์— ๋งž์ถ”์–ด ๋ฉ”์†Œ๋“œ์˜ ์ค„๋ฐ”๊ฟˆ์„ ์ˆ˜์ •
  • ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ํƒ€์ž…์„ ์‚ญ์ œ
  • ๋„ค์ด๋ฐ ๊ฐœ์„ 
  • API ๋ฌธ์„œ๋ฅผ ๋‹ค์‹œ ๊ฒ€ํ† ํ•˜์—ฌ ํƒ€์ž…์„ ๊ฐœ์„ 
  • ์ค‘๋ณต๋˜๋Š” ์ฝ”๋“œ๋ฅผ ์ œ๊ฑฐํ•˜์—ฌ ๋ฆฌํŒฉํ† ๋ง

top

STEP 3 : ์ƒํ’ˆ ๋“ฑ๋ก/์ˆ˜์ • ํ™”๋ฉด ๊ตฌํ˜„

์ƒํ’ˆ๋“ฑ๋ก, ์ƒํ’ˆ์ˆ˜์ • ํ™”๋ฉด์„ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.

4-1 ๊ณ ๋ฏผํ–ˆ๋˜ ๊ฒƒ

1. ์ƒํ’ˆ๋“ฑ๋ก ํ™”๋ฉด ๊ตฌํ˜„์‹œ ์ด๋ฏธ์ง€๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๊ธฐ๋Šฅ

  • CollectionView์˜ Cell๊ณผ Header๋ฅผ ํ™œ์šฉํ•˜์—ฌ View๋ฅผ ๊ตฌ์„ฑ
  • ์ด๋ฏธ์ง€๊ฐ€ 5์žฅ์ด ์ฑ„์›Œ์กŒ์„ ๋•Œ ๊ฒฝ๊ณ ์–ผ๋Ÿฟ์„ ๋„์šฐ๋„๋ก ๊ตฌํ˜„
  • ์‚ฌ์šฉ์ž ์ž…์žฅ์—์„œ ๋ช‡๊ฐœ์˜ ์ด๋ฏธ์ง€๊ฐ€ ์ถ”๊ฐ€๋˜์—ˆ๋Š”์ง€ ์‹œ๊ฐ์ ์œผ๋กœ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋„๋ก Label ์ถ”๊ฐ€ ๊ตฌํ˜„
  • ์ด๋ฏธ์ง€ ํŒŒ์ผ ์šฉ๋Ÿ‰์ด 300KB ์ด์ƒ์ผ ๊ฒฝ์šฐ UIGraphicsImageRenderer๋ฅผ ์ด์šฉํ•˜์—ฌ 20ํผ์„ผํŠธ์”ฉ resize

2. ์ž…๋ ฅ ๊ฒ€์ฆ

  • ์š”๊ตฌ์‚ฌํ•ญ๊ณผ API ๋ฌธ์„œ๋ฅผ ์ฐธ๊ณ ํ•˜์—ฌ ์‚ฌ์šฉ์ž๊ฐ€ ์ œ๋Œ€๋กœ ์ž…๋ ฅํ•˜์ง€ ์•Š์€ ๋ถ€๋ถ„์ด ์žˆ๋‹ค๋ฉด ์–ผ๋Ÿฟ์„ ํ†ตํ•ด ์–ด๋–ค ๋ถ€๋ถ„์ด ์ž˜๋ชป๋˜์—ˆ๋Š”์ง€ ์•Œ๋ ค์ค„ ์ˆ˜ ์žˆ๋„๋ก ๊ตฌํ˜„

3. RegisterViewController์„ ์žฌํ™œ์šฉ

  • ํ™”๋ฉด ์ „ํ™˜ ์‹œ ์ƒํ’ˆ ๋“ฑ๋ก, ์ƒํ’ˆ ์ˆ˜์ • flag๋ฅผ ์ „๋‹ฌํ•˜์—ฌ ํ•ด๋‹น flag์— ๋Œ€ํ•œ ๋ถ„๊ธฐ์ฒ˜๋ฆฌ๋ฅผ ํ†ตํ•ด์„œ ViewController๋ฅผ ์žฌํ™œ์šฉ

4. DataSource๋ฅผ ViewController์™€ ๋ถ„๋ฆฌ

  • ์ ์  ์ปค์ ธ๊ฐ€๋Š” ViewController๋ฅผ ๋‹ค์ด์–ดํŠธ ์‹œํ‚ค๊ธฐ ์œ„ํ•ด DataSource๋ฅผ ๋”ฐ๋กœ ํƒ€์ž…์œผ๋กœ ๋นผ๋‘์–ด ViewController์— ์ฃผ์ž…์‹œํ‚ค๋„๋ก ๊ตฌํ˜„

5. ํ‚ค๋ณด๋“œ๊ฐ€ ์ฝ˜ํ…์ธ ๋ฅผ ๊ฐ€๋ฆฌ๋Š” ํ˜„์ƒ ํ•ด๊ฒฐ

  • ScrollView์™€ NotificationCenter ๋‘๊ฐ€์ง€๋ฅผ ํ™œ์šฉ
  • ScrollView์˜ contentInset์„ keyboardFrame ์‚ฌ์ด์ฆˆ์˜ ๋†’์ด๋งŒํผ ๋ฒ„ํผ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ์Šคํฌ๋กค๋ทฐ๋ฅผ ํ™•์žฅํ•˜๋ฏ€๋กœ์จ ํ‚ค๋ณด๋“œ ๊ฐ€๋ฆผํ˜„์ƒ์„ ํ•ด๊ฒฐ
    • scrollIndicatorInsets๋„ ๊ฐ™์ด ๋ณ€๊ฒฝํ•ด์ฃผ๋„๋ก ํ•˜์˜€์Œ.
  • TextView์—์„œ ์ค„๋ฐ”๊ฟˆ์„ ํ•˜๋ฉด์„œ ์ปค์„œ๊ฐ€ ๋‚ด๋ ค๊ฐˆ ๋•Œ ๋”ฐ๋ผ๊ฐˆ ์ˆ˜ ์žˆ๋„๋ก, ScrollEnabled ๊ธฐ๋Šฅ์„ false๋กœ ๋น„ํ™œ์„ฑํ™”
  • ์ดํ›„ ์˜คํ† ๋ ˆ์ด์•„์›ƒ์œผ๋กœ TextView์˜ ๋†’์ด๊ฐ€ ๋Š˜ ๊ณ ์ •๋˜์–ด์žˆ๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ๋Š˜์–ด๋‚  ์ˆ˜๋„ ์žˆ๋„๋กย priority๋ฅผ ์กฐ์ ˆ.
    • ํ…์ŠคํŠธ๋ทฐ๊ฐ€ ์•ˆ์— Text๊ฐ€ ๊ธธ์–ด์งˆ ์ˆ˜๋ก ๋†’์ด๊ฐ€ ๋Š˜์–ด๋‚˜๊ณ , ๊ทธ์— ๋”ฐ๋ผ ์Šคํฌ๋กค๋„ ์ž๋™์œผ๋กœ ๋‚ด๋ ค์˜จ๋‹ค.

6. View์™€ Controller๊ฐ„์˜ ์†Œํ†ต

  • ImageHeaderView์™€ RegisterViewController, ๊ฐ๊ฐ์˜ ViewController๋“ค ์‚ฌ์ด์— ์†Œํ†ต์„ ์œ„ํ•ด์„œ notification์„ ์‚ฌ์šฉ

7. ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณ€ํ™”ํ•  ๋•Œ Updateํ•˜๋Š” ๊ธฐ๋Šฅ

  • ์ƒํ’ˆ ๋“ฑ๋ก, ์ƒํ’ˆ ์ˆ˜์ • ์‹œ ๊ฐ ViewController์— notification postํ•˜์—ฌ update ์ด๋ฒคํŠธ๋ฅผ ์ „๋‹ฌ
  • ์ด๋ฒคํŠธ ์ „๋‹ฌ ์‹œ API์— requestํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ์—…๋ฐ์ดํŠธ
  • MainViewController์˜ CollectionView๋ฅผ ์•„๋ž˜๋กœ ์“ธ์–ด๋‚ด๋ ธ์„ ๋•Œ ์ƒˆ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ค๋Š” ๊ธฐ๋Šฅ ์ถ”๊ฐ€ ๊ตฌํ˜„

8. ์ƒํ’ˆ์„ ์ˆ˜์ •ํ•  ๋•Œ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ํ™•์ธํ•˜๋Š” ์ž‘์—…

  • ์ƒํ’ˆ ๋“ฑ๋ก์‹œ์—๋Š” API ๋ช…์„ธ๋ฅผ ๋”ฐ๋ฅด๊ธฐ ์œ„ํ•ด ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ด๋ฏธ ์„ธํŒ…๋œ ์ƒํƒœ๋กœ ๋“ฑ๋ก์ด ๋˜์ง€๋งŒ, ์ƒํ’ˆ ์ˆ˜์ •์‹œ์—๋Š” ์ˆ˜์ •ํ•˜๊ธฐ ์ „ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋จผ์ € ํ™•์ธํ•˜๊ณ , ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ๋งž๋‹ค๋ฉด ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ตฌํ˜„

4-2 ์˜๋ฌธ์ 

  • Delegate vs Notification

    view์™€ Controller ์‚ฌ์ด์— ์†Œํ†ตํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ delegate pattern์„ ์‚ฌ์šฉํ•˜์˜€๋Š”๋ฐ DataSource ํŒŒ์ผ์„ ๋ถ„๋ฆฌํ•˜๋ฉด์„œ DataSource์—์„œ delegate ๊ฐ์ฒด๋ฅผ ์ด์šฉํ•˜๋Š” ๊ฒƒ์— ํ•œ๊ณ„๋ฅผ ๋Š๊ปด Notificationcenter๋ฅผ ์‚ฌ์šฉ

  • ํ‚ค๋ณด๋“œ๋ฅผ ๋‚ด๋ฆฌ๋Š” ๋ฐฉ๋ฒ• ์ค‘ Recognizer๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•˜๋ฉด CollectionView์˜ Delegate ๋ฉ”์†Œ๋“œ ํ˜ธ์ถœ์ด ๋จนํ†ต์ด ๋˜๋Š”๋ฐ ์–ด๋–ค ๋ฌธ์ œ์ธ์ง€ ๋ชจ๋ฅด๊ฒ ๋‹ค.

    // Recognizer๋ฅผ ํ™œ์šฉํ•ด์„œ ๋ทฐ์ปจ์„ ํ„ฐ์น˜ํ•˜๋ฉด ํ‚ค๋ณด๋“œ ์‚ฌ๋ผ์ง€๋Š” ๋ฉ”์†Œ๋“œ
    func hideKeyboard() {
        let tap = UITapGestureRecognizer(target: self, action: #selector(UIViewController.dismissKeyboard))
        view.addGestureRecognizer(tap)
    }
    
    @objc func dismissKeyboard() {
        view.endEditing(true)
    }
  • cell์„ dequeueReusable ํ•ด์ฃผ๋Š” ๊ตฌ๊ฐ„์—์„œ productList๋ฅผ ํ†ตํ•ด ์…€์„ ๊ตฌ์„ฑํ•˜๊ณ  ์žˆ๋Š”๋ฐ, MainViewController์„ ์—…๋ฐ์ดํŠธ ํ•  ๋•Œ ๊ฐ„ํ—์ ์œผ๋กœ out of range ์—๋Ÿฌ๊ฐ€ ๋‚˜๋Š”๋ฐ, ์ •ํ™•ํ•œ ์—๋Ÿฌ ์›์ธ์„ ์ฐพ์ง€ ๋ชปํ–ˆ๋‹ค.

  • ์ ์  ๋น„๋Œ€ํ•ด์ง€๋Š” ViewController๋ฅผ ์–ด๋–ป๊ฒŒ ๋‹ค์ด์–ดํŠธ ์‹œ์ผœ์•ผํ•˜๋Š”์ง€...

4-3 Trouble Shooting

ํ‚ค๋ณด๋“œ๊ฐ€ ์ฝ˜ํ…์ธ ๋ฅผ ๊ฐ€๋ฆฌ์ง€ ์•Š๋„๋ก ํ•˜๋Š” ๋ฐฉ๋ฒ•

1. ํ‚ค๋ณด๋“œ๊ฐ€ ์ฝ˜ํ…์ธ ๋ฅผ ๊ฐ€๋ฆฌ์ง€ ์•Š๋„๋ก ํ•˜๋Š” ๋ฐฉ๋ฒ•

  • ๋ฌธ์ œ NotificationCenter๋ฅผ ํ™œ์šฉํ•˜์—ฌ ํ‚ค๋ณด๋“œํ”„๋ ˆ์ž„์„ ๊ณ„์‚ฐํ•˜๊ณ , ๊ทธ์— ๋งž๊ฒŒ ScrollView์˜ contentInset์„ ์กฐ์ •ํ•ด์ฃผ์–ด ์ฝ˜ํ…์ธ ๋ฅผ ๊ฐ€๋ฆฌ์ง€ ์•Š๋„๋ก ํ•ด๊ฒฐ์„ ํ•ด๋ณด์•˜๋‹ค.
    • ๊ทธ๋Ÿฌ๋‚˜ UITextField ๊ฒฝ์šฐ์—๋Š” ์ œ๋Œ€๋กœ ๋™์ž‘ํ•˜์˜€์ง€๋งŒ UITextView์˜ ๊ฒฝ์šฐ์—๋Š” ์ œ๋Œ€๋กœ ๋™์ž‘ํ•˜์ง€ ์•Š์•˜๋‹ค.
    • https://i.imgur.com/4LOEfyc.gif
  • ์ด์œ  ๊ตฌ๊ธ€๋ง์„ ํ•ด๋ณธ ๊ฒฐ๊ณผ TextView์˜ ๊ฒฝ์šฐ ์Šคํฌ๋กค ๊ธฐ๋Šฅ์„ ์—†์•ค ํ›„์— ๋†’์ด๋ฅผ ๊ณ ์ •์‹œ์ผœ์•ผ ์ด๋Ÿฌํ•œ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ  ๋‚˜์™€์žˆ์—ˆ๋‹ค. ์ดํ•ดํ•œ ๋ฐ”๋กœ๋Š” TextView์˜ ๋†’์ด๊ฐ€ ๊ณ„์† ๋Š˜์–ด๋‚  ์ˆ˜ ์žˆ๊ณ , ์Šคํฌ๋กค์ด ๊ธฐ๋Šฅ์ด ๊ฐ€๋Šฅํ•œ ์ƒํƒœ์—์„œ๋Š” ๋ฐ”๊นฅ์— ๊น”๋ ค์žˆ๋Š” ์Šคํฌ๋กค๋ทฐ ์ž…์žฅ์—์„œ๋Š” ๋ทฐ์˜ ๋†’์ด๊ฐ€ ๋ณ€ํ•˜์ง€ ์•Š์œผ๋‹ˆ ์Šคํฌ๋กค์˜ ์ขŒํ‘œ๋„ ๊ทธ๋Œ€๋กœ์ธ ๊ฒƒ์ด๋ผ๊ณ  ์ดํ•ด๊ฐ€ ๋˜์—ˆ๋‹ค.
  • ํ•ด๊ฒฐ ๋”ฐ๋ผ์„œ UITextView์˜ ์Šคํฌ๋กค ๊ธฐ๋Šฅ์„ ๋น„ํ™œ์„ฑํ™”ํ•œ ํ›„, ๋†’์ด๊ฐ€ ๋Š˜ ๊ณ ์ •๋˜์–ด์žˆ๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ๋‚ด๋ถ€์˜ Text์— ๋”ฐ๋ผ ๋Š˜์–ด๋‚  ์ˆ˜๋„ ์žˆ๋„๋ก ์˜คํ† ๋ ˆ์ด์•„์›ƒ ์šฐ์„ ์ˆœ์œ„๋ฅผ ์กฐ์ •ํ•ด์ฃผ์—ˆ๋‹ค.
CollectionView๋ฅผ Refreshing ํ•  ๋•Œ ๋ฐœ์ƒ๋˜๋Š” index out of range

2. CollectionView๋ฅผ Refreshing ํ•  ๋•Œ ๋ฐœ์ƒ๋˜๋Š” index out of range

  • ๋ฌธ์ œ cell์„ dequeueReusable ํ•ด์ฃผ๋Š” ๊ตฌ๊ฐ„์—์„œ productList๋ฅผ ํ†ตํ•ด ์…€์„ ๊ตฌ์„ฑํ•˜๊ณ  ์žˆ๋Š”๋ฐ, MainViewController์—์„œ UIRefreshControl๋ฅผ ํ†ตํ•ด ์—…๋ฐ์ดํŠธ ํ•  ๋•Œ ๊ฐ„ํ—์ ์œผ๋กœย out of rangeย ์—๋Ÿฌ๊ฐ€ ๋‚˜๋Š”๋ฐ ๋””๋ฒ„๊น…์„ ํ•ด๋ณด์•˜๋‹ค.
  • ์ด์œ  dequeueReusable ํ•ด์ฃผ๋Š” ๊ตฌ๊ฐ„๊ณผ updataMainView() ๋ฉ”์†Œ๋“œ ๋‚ด๋ถ€๋ฅผ print๋ฅผ ํ•ด๋ณด๋ฉด์„œ ๋””๋ฒ„๊น…์„ ํ•ด๋ณธ ๊ฒฐ๊ณผ, CollectionView์˜ cellForItemAt ๋ฉ”์†Œ๋“œ๊ฐ€ ๋‹จ์ˆœ ๋“œ๋ž˜๊ทธ๋ฅผ ํ•  ๋•Œ๋„ ํ˜ธ์ถœํ•˜๊ณ  ์žˆ์—ˆ๋‹ค.
  • ๋”ฐ๋ผ์„œ ์œ„์—์„œ ์•„๋ž˜๋กœ ์“ธ์–ด๋‚ด๋ฆฌ๋Š” ๊ณผ์ •์—์„œ cellForItemAt์˜ ํ˜ธ์ถœ๊ณผ updataMainView ํ˜ธ์ถœ ์‹œ์ ์ด ๊ฐ„ํ—์ ์œผ๋กœ ๋’ค๋ฐ”๋€Œ๋Š” ์‹œ์ ์ด ์ƒ๊ฒจ์„œ ์ด๋Ÿฌํ•œ ์—๋Ÿฌ๊ฐ€ ๋‚ฌ๋˜ ๊ฒƒ์ด๋ผ๊ณ  ์ดํ•ดํ–ˆ๋‹ค.
  • ํ•ด๊ฒฐ ๋™๊ธฐ์ ์œผ๋กœ ๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœ์‹œํ‚ค๋„๋ก ํ•ด์•ผํ•˜๋‚˜ ํ–ˆ์—ˆ์ง€๋งŒ, ์ด ๋ถ€๋ถ„์€ ๋น„๋™๊ธฐ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜์—ฌ ํ•ด๊ฒฐํ•˜์˜€๋‹ค.
    • updataMainView๊ฐ€ ํ˜ธ์ถœ๋  ๋•Œ DispatchQueue.global()๋กœ ์ž‘์—…์„ ๋ณด๋‚ด๋„๋ก ํ•˜์˜€๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋ฉ”์ธ์“ฐ๋ ˆ๋“œ๊ฐ€ ์•„๋‹Œ ๋‹ค๋ฅธ ์“ฐ๋ ˆ๋“œ์—์„œ updataMainView์˜ ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•˜๊ณ , ๊ทธ ํ›„์— CollectionView์˜ cellForItemAt ๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๋กœ์ง์œผ๋กœ ๋ณ€๊ฒฝ๋œ๋‹ค.

4-4 ๋ฐฐ์šด ๊ฐœ๋…

  • ์‚ฌ์šฉ์ž ์นœํ™”์ ์ธ UI/UX ๊ตฌํ˜„
  • URLSession์„ ํ™œ์šฉํ•œ multipart-form ์š”์ฒญ ์ „์†ก
  • UIImagePickerController๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์‚ฌ์šฉ์ž ์•จ๋ฒ”์— ์ ‘๊ทผํ•˜๋Š” ๋ฐฉ๋ฒ•
  • UIGraphicsImageRenderer ๋ฅผ ์ด๋ฏธ์ง€๋ฅผ ๋žœ๋”๋ง ํ•˜์—ฌ ์••์ถ•ํ•˜๋Š” ๋ฐฉ๋ฒ•
  • UICollectionReusableView๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•
  • UIScrollView๋ฅผ ํ™œ์šฉํ•˜์—ฌ ํ‚ค๋ณด๋“œ๊ฐ€ ์ปจํ…์ธ ๋ฅผ ๊ฐ€๋ฆฌ๋Š” ๋ถ€๋ถ„์„ ํ•ด๊ฒฐํ•˜๋Š” ๋ฐฉ๋ฒ•
  • UITextField๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” UIAlertController ํ™œ์šฉ
  • UIRefreshControl๋ฅผ ํ™œ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•

4-5 PR ํ›„ ๊ฐœ์„ ์‚ฌํ•ญ

  • AlertConstant๋ฅผย ์ œ๊ฑฐํ•˜๊ณ ย UIViewController+extensionย ๋ถ€๋ถ„ ์ „์ฒด์ ์œผ๋กœ ๊ฐœ์„ 
  • CollectionView์—์„œย refreshingํ•  ๋•Œย index out of rangeย ์—๋Ÿฌ๋‚˜๋Š” ๋ถ€๋ถ„ ๋น„๋™๊ธฐ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜์—ฌ ํ•ด๊ฒฐ
  • ๋„ค์ด๋ฐ ๋ถ€๋ถ„ ์ „์ฒด์ ์œผ๋กœ ๊ฐœ์„ 
  • ์•จ๋ฒ”์— ์ ‘๊ทผํ•  ๋•Œ ๋ณด์—ฌ์ง€๋Š” description์„ ์ˆ˜์ •ํ•˜์—ฌ ๊ฐœ์„ 
  • HIG๋ฅผ ์ฐธ๊ณ ํ•˜์—ฌ alert, ActionSheet์˜ย ๋ฒ„ํŠผ ์ˆœ์„œ๋ฅผ ๋ณ€๊ฒฝํ•˜์—ฌ ๊ฐœ์„ 
  • DetailViewControllerย ๋ฉ”์†Œ๋“œ ์ˆœ์„œ๋ฅผ ๊ฐœ์„ 
  • ์ด๋ฏธ์ง€ ์‚ญ์ œ์‹œ 'x'๋ฒ„ํŠผ์„ ํด๋ฆญํ–ˆ์„ ๋•Œ ์‚ญ์ œ๋˜๋„๋ก ์ˆ˜์ •ํ•˜์—ฌ ๊ฐœ์„ 
  • ์…€์„ ์„ ํƒํ–ˆ์„ ๋•Œ ๋ณ€ํ™”๊ฐ€ ๋ฐœ์ƒํ•˜๋„๋กย selectedBackgroundViewย ์„ค์ •

top

STEP 4 : ์ƒํ’ˆ ์ƒ์„ธํ™”๋ฉด ๊ตฌํ˜„

์ƒํ’ˆ์˜ ์ƒ์„ธ๋‚ด์šฉ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋Š” ํ™”๋ฉด์„ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค

5-1 ๊ณ ๋ฏผํ–ˆ๋˜ ๊ฒƒ

1. ์—๋Ÿฌ์ฒ˜๋ฆฌ

  • ์‚ฌ์šฉ์ž๊ฐ€ ๋ณผ ํ•„์š”๊ฐ€ ์—†๋Š” ์—๋Ÿฌ์˜ ๊ฒฝ์šฐ(๋„คํŠธ์›Œํฌ ์—๋Ÿฌ) ์–ผ๋Ÿฟ์„ ๋„์šฐ๋Š” ๋Œ€์‹  print๋ฅผ ํ˜ธ์ถœํ•˜๋„๋ก ํ•˜์—ฌ, ์—๋Ÿฌ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์ฃผ์—ˆ๋‹ค.

2. Custom Font์—๋„ Dynamic Type์„ ์ ์šฉ

  • UIFont๊ฐ€ ์ œ๊ณตํ•˜๋Š” preferredFont๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋”ฐ๋กœ ๊ตต๊ธฐ๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์—†๊ณ , ์ง€์ •๋œ font๋งŒ ์‚ฌ์šฉํ•ด์•ผํ•œ๋‹ค. ๋ฐ˜๋Œ€๋กœ systemFont๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด Dynamic Type์ด ๋™์ž‘ํ•˜์ง€ ์•Š๋Š”๋‹ค.
    • extension์„ ํ†ตํ•ด์„œ ๊ตต๊ธฐ๋ฅผ ์ง€์ •ํ•ด๋„ Dynamic Type์„ ์ง€์›ํ•˜๋Š” ๋ฉ”์†Œ๋“œ๋ฅผ ๊ตฌํ˜„ํ•˜์—ฌ ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐ.

3. ์ด๋ฏธ์ง€ ์ƒ์„ธ๋ณด๊ธฐ ์‹œ ๋ณด๊ณ ์žˆ๋˜ ์ด๋ฏธ์ง€๊ฐ€ ๊ทธ๋Œ€๋กœ ๋„˜์–ด๊ฐ€๋„๋ก ๊ตฌํ˜„

  • ๋‹ค๋ฅธ ๋งˆ์ผ“์•ฑ์„ ์‚ดํŽด๋ณด๋ฉด ์ƒ์„ธ๋ณด๊ธฐ๋กœ ๋„˜์–ด๊ฐˆ ๋•Œ ๋ณด๊ณ ์žˆ๋˜ ์ด๋ฏธ์ง€๋ฅผ ๊ทธ๋Œ€๋กœ ๋„˜๊ฒจ์„œ ์ƒ์„ธ๋ณด๊ธฐ ํ™”๋ฉด์œผ๋กœ ์ „ํ™˜๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. ์‚ฌ์šฉ์ž ์ž…์žฅ์—์„œ๋„ ๋ณด๊ณ ์žˆ๋˜ ์ด๋ฏธ์ง€๋ฅผ ๊ทธ๋Œ€๋กœ ์ƒ์„ธ๋ณด๊ธฐ๋กœ ๋ณด๋Š” ๊ฒƒ์ด ๋ฐ”๋žŒ์งํ•˜๋‹ค๊ณ  ํŒ๋‹จ๋˜์–ด ํ•ด๋‹น ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜์˜€๋‹ค.

    DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
        let indexPath = IndexPath(item: self.currentPage, section: 0)
        self.collectionView.scrollToItem(
            at: indexPath,
            at: [.centeredHorizontally, .centeredVertically],
            animated: false
         )
    }

4. HIG๋ฅผ ์ฐธ๊ณ ํ•˜์—ฌ Action Sheet๋ฅผ ์„ค๊ณ„

Make destructive choices prominent.ย Use red for buttons that perform destructive or dangerous actions, and display these buttons at the top of an action sheet.

  • ์ด ๋ถ€๋ถ„์„ ์ฐธ๊ณ ํ•˜์—ฌ, ์š”๊ตฌ์‚ฌํ•ญ์ฒ˜๋Ÿผ ์‚ญ์ œ ๋ฒ„ํŠผ์„ ์•„๋ž˜์— ์œ„์น˜ํ•ด์žˆ๋Š”๊ฒŒ ์•„๋‹ˆ๋ผ ์ƒ๋‹จ์— ์œ„์น˜ํ•˜๋„๋ก ๊ฐœ์„ ํ•˜์˜€๋‹ค.

5. CollectionView, PageControl์„ ์ด์šฉํ•œ ์ด๋ฏธ์ง€ Paging

  • ์ƒ์„ธํŽ˜์ด์ง€ํ™”๋ฉด์—์„œ ์ด๋ฏธ์ง€๋ฅผ ํ‘œ์‹œํ•  ๋•Œ CollectionView์˜ isPaging๊ณผ ๊ฐ€๋กœ ์Šคํฌ๋กค์„ ํ†ตํ•ด์„œ paging์„ ๊ตฌํ˜„ํ•˜์˜€๋‹ค.
  • pageControl์„ ์ด์šฉํ•˜์—ฌ ํ˜„์žฌ ํŽ˜์ด์ง€์˜ ์œ„์น˜๋ฅผ ๋‚˜ํƒ€๋‚ด๊ณ  PageControl์˜ dot์„ ํƒญํ•˜์—ฌ๋„ ํŽ˜์ด์ง€๊ฐ€ ์ „ํ™˜๋˜๋Š” ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜์˜€๋‹ค.

5-2 ์˜๋ฌธ์ 

  • DetailViewController โ†’ ImageDetailViewController ๋กœ ์ „ํ™˜ํ•  ๋•Œ viewDidLoad์—์„œ collectionView.scrollToItem์„ ํ˜ธ์ถœ ์‹œ ์ œ๋Œ€๋กœ ์ ์šฉ๋˜์ง€ ์•Š์•„์„œ DispatchQueue.main.asyncAfter๋ฅผ ํ™œ์šฉํ–ˆ๋Š”๋ฐ, ์ ์ ˆํ•œ์ง€ ์ž˜ ๋ชจ๋ฅด๊ฒ ๋‹ค.
  • ๋„คํŠธ์›Œํ‚น ํ•˜๋Š” ๋ถ€๋ถ„์ด ๋งˆ์น˜ ์‚ฐ๋งฅ..์ฒ˜๋Ÿผ ๊ณตํฌ์˜ ๋“ค์—ฌ์“ฐ๊ธฐ๊ฐ€ ์ƒ๊ฒจ๋‚ฌ๋Š”๋ฐ, ์ด๋Ÿฌํ•œ ๋ถ€๋ถ„์„ ์–ด๋–ป๊ฒŒ ๋” ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ์„์ง€ ๋”์ด์ƒ ๋– ์˜ค๋ฅด์ง€๊ฐ€ ์•Š๋Š”๋‹ค.
  • CollectionView์— FlowLayout์„ ๋”ฐ๋กœ ์ฝ”๋“œ๋กœ ๋Œ€์ž…ํ•ด์ฃผ์ง€ ์•Š์œผ๋ฉด, UICollectionViewDelegateFlowLayout๋ฅผ ์ฑ„ํƒํ•˜์—ฌ ๋ ˆ์ด์•„์›ƒ์„ ์„ค์ •ํ•ด์ฃผ์–ด๋„ ๋ ˆ์ด์•„์›ƒ์ด ์ ์šฉ๋˜์ง€ ์•Š๋Š”๋ฐ, ์™œ๊ทธ๋Ÿฐ๊ฑธ๊นŒ?
  • ScrollView๋ฅผ ํ™œ์šฉํ•ด์„œ Zoom์„ ๊ตฌํ˜„ํ•˜์˜€์ง€๋งŒ, ๋””๋ฐ”์ด์Šค ์ „์ฒด ํฌ๊ธฐ์— ๋งž์ถฐ์„œ Scale์„ ์„ค์ •ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ชจ๋ฅด๊ฒ ๋‹ค.
    • ์ด๋ฏธ์ง€๊ฐ€ ๋””๋ฐ”์ด์Šค ํฌ๊ธฐ๋ณด๋‹ค ์ปค์ง€์ง€ ์•Š๋„๋ก ํ•˜๊ณ ์‹ถ๋‹ค...

5-3 Trouble Shooting

1. ์ƒ์„ธํŽ˜์ด์ง€๋กœ ๋„˜์–ด๊ฐˆ๋•Œ ์ด๋ฏธ์ง€๊ฐ€ ๋‚˜ํƒ€๋‚˜์ง€ ์•Š๋Š” ๋ฌธ์ œ

  • ๋ฌธ์ œ ๋ฉ”์ธํŽ˜์ด์ง€์—์„œ ์ƒ์„ธํŽ˜์ด์ง€๋กœ ๋„˜์–ด๊ฐˆ๋•Œ ์ด๋ฏธ์ง€๊ฐ€ ๋ณด์ด์ง€ ์•Š๋Š” ํ˜„์ƒ์ด ๋‚˜ํƒ€๋‚ฌ๋‹ค.

  • ์ด์œ  ์ด๋ฏธ์ง€๋ฅผ ๋„คํŠธ์›Œํ‚นํ•˜๋Š” ์†๋„๋ณด๋‹ค ํ™”๋ฉด์ด ์ „ํ™˜๋˜๋Š” ์‹œ์ ์ด ๋นจ๋ผ์„œ ๋‚˜ํƒ€๋‚˜๋Š” ๋ฌธ์ œ์˜€๋‹ค.

  • ํ•ด๊ฒฐ DispatchGroup์„ ์ด์šฉํ•˜์—ฌ ๋„คํŠธ์›Œํ‚นํ•˜๋Š” ์ฝ”๋“œ ๋‚ด completion ํƒˆ์ถœ ํด๋กœ์ €๊ฐ€ ๋ชจ๋‘ ๋๋‚œ ์‹œ์ ์— View๋ฅผ ๋„์›Œ์ฃผ๋„๋ก ํ•˜์˜€๋‹ค.

    dispatchGroup.enter()
    ImageManager.shared.downloadImage(with: newImage.url) { image in
        ImageManager.shared.setCacheData(of: image, for: newImage.url)
        self.images.append(image)
        dispatchGroup.leave()
    }
    
    dispatchGroup.notify(queue: .main) {
        DispatchQueue.main.async {
            // view 
            ...
        }
    }

2. CollectionView๋กœ Paging์‹œ cell์ด ๋ฐ€๋ฆฌ๋Š” ๋ฌธ์ œ

  • ๋ฌธ์ œ CollectionView์˜ isPagingEnabled์„ true๋กœ ์ฃผ๋ฉด ์•„๋ž˜ ์˜ˆ์‹œ์™€ ๊ฐ™์ด ์Šคํฌ๋กค ํ–ˆ์„ ๋•Œ cell์ด ์กฐ๊ธˆ์”ฉ ๋ฐ€๋ฆฌ๋Š” ํ˜„์ƒ์ด ๋‚˜ํƒ€๋‚ฌ๋‹ค.

    Untitled

  • ์ด์œ  CollectionView์˜ ๊ฒฝ์šฐ minimumLineSpacing์ด ๊ธฐ๋ณธ์ ์œผ๋กœ ๊ฐ’(10.0)์ด ๋“ค์–ด๊ฐ€์žˆ๋‹ค. ํ•ด๋‹น ๊ฐ’ ๋•Œ๋ฌธ์— ์Šคํฌ๋กค ์‹œ ๋ฐ€๋ฆผํ˜„์ƒ์ด ์žˆ์—ˆ๋˜ ๊ฒƒ์ด์˜€๋‹ค.

  • ํ•ด๊ฒฐ minimumLineSpacing์„ 0์œผ๋กœ ์„ค์ •ํ•ด์ฃผ๋‹ˆ ์Šคํฌ๋กค ์‹œ cell์ด ์กฐ๊ธˆ์”ฉ ๋ฐ€๋ฆฌ๋˜ ํ˜„์ƒ์ด ํ•ด๊ฒฐ๋˜์—ˆ๋‹ค.

    Untitled

3. Cell์ด ์„ ํƒ๋˜์—ˆ๋‹ค๋Š” ํ‘œ์‹œ๊ฐ€ ์•ˆ๋‚˜๋Š” ๋ฌธ์ œ

  • ๋ฌธ์ œ UITableView๊ฐ™์€ ๊ฒฝ์šฐ์—๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ seleted ํ–ˆ์„ ๋•Œ, ํšŒ์ƒ‰ ๋ฐฐ๊ฒฝ์ด ์‚ฌ๋ผ์ง€์ง€ ์•Š์•„์„œ delegate ๋ฉ”์†Œ๋“œ๋ฅผ ํ™œ์šฉํ•˜์—ฌ deselect๋ฅผ ํ•ด์ฃผ์–ด์•ผ ๋ฐฐ๊ฒฝ์ƒ‰์ด ๋‹ค์‹œ ์›๋ž˜๋Œ€๋กœ ๋Œ์•„์™”์—ˆ๋‹ค.

  • ์ด์œ  ํ•˜์ง€๋งŒ UICollectionView ๊ฐ™์€ ๊ฒฝ์šฐ์—๋Š” ์•„๋ฌด๊ฒƒ๋„ ์„ค์ •๋˜์–ด์žˆ์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ์ด ๋ถ€๋ถ„์„ ์ง์ ‘ ์„ค์ •์„ ํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค.

  • ํ•ด๊ฒฐ ์…€์„ ์ดˆ๊ธฐํ™”ํ•  ๋•Œ selectedBackgroundView๋ฅผ ์ง€์ •ํ•ด์ฃผ๊ณ , backgroundColor๋ฅผ ์ž…ํ˜€์ค€๋‹ค.

    cell.selectedBackgroundView = UIView(frame: self.bounds)
    cell.selectedBackgroundView?.backgroundColor = .systemGray5

    https://camo.githubusercontent.com/025483b76ced080acf668db52a4b733a66eafafdf1faacd13e4989ccdfe7c207/68747470733a2f2f692e696d6775722e636f6d2f6a67736c7062672e676966

  • ํ•˜์ง€๋งŒ ์œ„์™€ ๊ฐ™์ด ๋ฐฐ๊ฒฝ์ƒ‰์ด ๋ฐ”๋€ ์ฑ„๋กœ ๋‚จ์•„์žˆ๋‹ค. ๋”ฐ๋ผ์„œ Delegate ๋ฉ”์†Œ๋“œ ์ค‘ didSelectItemAt๋ฅผ ๊ตฌํ˜„ํ•˜์—ฌ deselectItem๋ฅผ ํ˜ธ์ถœํ•ด์„œ ์…€ ์„ ํƒ์„ ํ•ด์ œ์‹œ์ผœ์ฃผ์–ด์•ผ ํ•œ๋‹ค.

    // UICollectionViewDelegate...
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        collectionView.deselectItem(at: indexPath, animated: true)
    }

    https://camo.githubusercontent.com/6f1c32140bce0c850ef827cbfc8a2659e9f36f24764b853cedf99fc84d08d67c/68747470733a2f2f692e696d6775722e636f6d2f333169746748432e676966

    • ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์ •์ƒ์ ์œผ๋กœ ์…€์„ ์„ ํƒํ–ˆ์„ ๋•Œ, ์„ ํƒ๋˜์—ˆ๋‹ค๋Š” ํšจ๊ณผ๊ฐ€ ์ผ์–ด๋‚˜๋ฉด์„œ ํ™”๋ฉด์ „ํ™˜์ด ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

5-4 ๋ฐฐ์šด ๊ฐœ๋…

  • UIPageControl์„ ํ™œ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•
  • GestureRecognizer๋ฅผ ํ†ตํ•ด ํŠน์ • ํ„ฐ์น˜์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•
  • UIScrollView์˜ Zoom ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•
  • UIFontMetrics๋ฅผ ํ™œ์šฉํ•˜์—ฌ Custom Font์—๋„ Dynamic Type์„ ์ ์šฉํ•ด๋ณด๋Š” ๋ฐฉ๋ฒ•
  • UICollectionView๋ฅผ ํ™œ์šฉํ•ด์„œ Paging์„ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐฉ๋ฒ•
  • UIAlertController์˜ ํ…์ŠคํŠธ ํ•„๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ํ™œ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•
    • Handler ํ™œ์šฉ

top

About

๐Ÿ›’ REST API์™€์˜ ์—ฐ๋™์„ ํ†ตํ•ด ๊ฐ„๋‹จํ•œ ๋งˆ์ผ“ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•ด๋ณผ ์ˆ˜ ์žˆ๋Š” ์•ฑ

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Swift 100.0%