- ํ ํ๋ก์ ํธ(2์ธ)
- ํ๋ก์ ํธ ๊ธฐ๊ฐ: 2022.01.03 ~ 2022.01.28
- ํค์๋
- ํ๋ก์ ํธ ์๊ฐ
- ํ๋ก์ ํธ ์ฃผ์๊ธฐ๋ฅ
- ํ๋ก์ ํธ ๊ธฐ์ ์คํ
- ๊ณ ๋ฏผํ ๋ด์ฉ
- Trouble Shooting
- "URLSessionDataTask๋ฅผ ์ฑํํ ํ์ ์ init()์ deprecated ๊ฒฝ๊ณ ..?"
- "Segument Control์ ์ด์ฉํ์ฌ ํ๋ฉด์ ํ ์ ์คํฌ๋กค ์์น๊ฐ ์ ์์ ์ด์ง ์์ ๊ฒฝ์ฐ"
- "์คํฌ๋กค ํ๋ ํ์ฌ ์์น๊ฐ ๋น์ ์์ ์ผ๋ก ์นด์ดํธ๋๋ ํ์"
- "CollectionView๋ก Paging์ cell์ด ๋ฐ๋ฆฌ๋ ๋ฌธ์ "
- ์๋กญ๊ฒ ์๊ฒ๋ ๊ฒ
์์กด์ฑ ์ฃผ์ (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, Stub ๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด ํ์ฉํ์์ต๋๋ค.
- URLProtocol์ ์์๋ฐ์ ๊ฐ์ง Session์ ๋ง๋ค๊ณ ๋คํธ์ํฌ์ ๋ฌด๊ดํ ํ
์คํธ๋ฅผ ์งํํ์์ต๋๋ค.
- ์๋ฒ๋ก ๋ณด๋ผ ์์ฒญ(GET, POST, PATCH, DELETE)์ ์ฌ๋ฐ๋ฅด๊ฒ ๋ง๋๋์ง
- ์คํจํ๋ ๋คํธ์ํฌ, ์ฑ๊ณตํ๋ ๋คํธ์ํฌ์ ๋ฐ๋ผ ํธ๋ค๋ง์ ํ๊ณ ์๋์ง
- ์๋ฒ์ ์ํ ๋ชฉ๋ก์ ์์ฒญํ์ ๋, ์ด๋ฏธ์ง๋ URL ํํ๋ก ๋ฐ๊ฒ๋๋๋ฐ, ๋น๋๊ธฐ๋ก ์์
์ ์ฒ๋ฆฌํ๊ฒ ๋๋ฉด, ํ
์คํธ๊ฐ ๋จผ์ ๋จ๊ณ ๊ทธ ๋ค์์ ์ด๋ฏธ์ง๋ค์ด ์์ฐจ์ ์ผ๋ก ๋จ๋ ํ์์ด ์์์ต๋๋ค.
- ๋ฐ๋ผ์ ๋ชจ๋ ๋ฐ์ดํฐ๊ฐ ๋ค์ด๋ก๋๊ฐ ์๋ฃ๋ ์์ ์ ํ๋ฒ์ ๋ฐ์ดํฐ๋ฅผ ๋์์ฃผ๋ ๊ฒ์ด ์ข๋ ์ข๋ค๊ณ ํ๋จ๋์ด ๋๊ธฐ ํ์๋ฅผ ๋์ด ํ ๋ฐ์ดํฐ ๋ค์ด๋ก๋๋ฅผ ์๋ฃํ๋ฉด ํ๋ฒ์ ์ํ ๋ชฉ๋ก์ ๋์ฐ๋๋ก ๊ตฌ์ฑํ์์ต๋๋ค.
- ์ํ ๋ชฉ๋ก์ ๋ค์ํ์ด์ง๋ฅผ ๋์ด๊ฐ๋ ๋ฐฉ๋ฒ์
Pagination
์ผ๋ก ๊ตฌํํ์์ต๋๋ค.- ์คํฌ๋กค์ด ํ๋จ์ ๊ฐ๊น์์ง๋ฉด ๋ค์ํ์ด์ง๋ฅผ ๋ก๋ํ๋๋ก ๊ตฌ์ฑ
- ๊ธฐ์กด์๋ ์ํ ๋ฑ๋ก ์ ์
๋ ฅ๊ฐ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ
Done
๋ฒํผ์ ๋๋ฅผ ๋๋ง๋ค Alert์ ํตํด ํ์ธํ ์ ์์์ผ๋, ๋๋ฌด ๋ง์ Alert์ ๊ท์ฐฎ๊ณ ๋ฒ๊ฑฐ๋กญ๋ค๊ณ ํ๋จ๋์ด, ๋๋ฅด๊ธฐ ์ ์ ์ค์๊ฐ์ผ๋ก ํ์ธํ ์ ์๋๋ก ๊ธฐ๋ฅ ์ถ๊ฐํ์ฌ ์ฌ์ฉ์ฑ์ ํฅ์์์ผ์ฃผ์์ต๋๋ค. - ํค๋ณด๋๊ฐ ์ฝํ ์ธ ๋ฅผ ๊ฐ๋ฆฌ์ง ์๋๋ก ์ ์ฝ์กฐ๊ฑด์ ์กฐ์ ํ์ฌ ๊ตฌํํ์์ต๋๋ค.
- ์ด๋ฏธ์ง ์์ธ๋ณด๊ธฐ ๊ธฐ๋ฅ์ ์ถ๊ฐํ์ฌ ์ํ์ ์ข๋ ์์ธํ ๋ณผ ์ ์๋๋ก ๊ตฌํํ์์ต๋๋ค.
- ์ด๋ฏธ์ง ํฐ์น ์ ์ด๋ฏธ์ง ์์ธ๋ณด๊ธฐ ํ๋ฉด์ผ๋ก ์ด๋
- 2๋ฒ ํญ์ ๋น ๋ฅด๊ฒ ํ ๋ ํ๋/์ถ์ ๊ธฐ๋ฅ ์ ๊ณต
์ํฉ
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
์ด์
URLSessionDataTaskinit()
์ด 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์ isPagingEnabled์ true๋ก ์ฃผ๋ฉด ์๋ ์์์ ๊ฐ์ด ์คํฌ๋กค ํ์ ๋ cell์ด ์กฐ๊ธ์ฉ ๋ฐ๋ฆฌ๋ ํ์์ด ๋ํ๋ฌ๋ค. -
์ด์
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๋ ์ด๋ป๊ฒ ์คํฌ๋กค์ด ๋ฐ๋ผ์ค๊ฒ ๋ง๋ค๊น?
- ์ด ๋ถ๋ถ์ ํ
์คํธ๋ทฐ์ ์คํฌ๋กค ๊ธฐ๋ฅ์ false๋ก ์ ๊ฑฐํ๋ค.
TextView.isScrollEnabled = false
- ๊ทธ๋ฆฌ๊ณ ์คํ ๋ ์ด์์์ผ๋ก TextView์ ๋์ด๊ฐ ๋ ๊ณ ์ ๋์ด์๋ ๊ฒ์ด ์๋๋ผ ๋์ด๋ ์๋ ์๋๋ก
priority
๋ฅผ ์กฐ์ ํด์ฃผ๋ฉด ๋๋ค.
- ์ด ๋ถ๋ถ์ ํ
์คํธ๋ทฐ์ ์คํฌ๋กค ๊ธฐ๋ฅ์ false๋ก ์ ๊ฑฐํ๋ค.
- ์์ ๊ฐ์ด ์ธํ
ํ๋ค๋ฉด, ํ
์คํธ๋ทฐ๊ฐ ์์ Text๊ฐ ๊ธธ์ด์ง ์๋ก ๋์ด๊ฐ ๋์ด๋๊ณ , ๊ทธ์ ๋ฐ๋ผ ์คํฌ๋กค๋ ์๋์ผ๋ก ๋ด๋ ค์จ๋ค.
- ํด๊ฒฐํด๋ณด๋ฉด์ ๋ฉ๋ชจ์ฅ๋ ์ด๋ฐ ๋ฐฉ์์ธ๊ฑด๊ฐ? ํ๋ค...!
[ํ์ต ๊ธฐ๋ก ํ์ ]
- STEP 1 : ๋คํธ์ํน ํ์ ๊ตฌํ
- STEP 2 : ์ํ ๋ชฉ๋ก ํ๋ฉด ๊ตฌํ
- STEP Bouns : ๋ก์ปฌ ์บ์ ๊ตฌํ
- STEP 3 : ์ํ ๋ฑ๋ก/์์ ํ๋ฉด ๊ตฌํ
- STEP 4 : ์ํ ์์ธํ๋ฉด ๊ตฌํ
- ๋คํธ์ํฌ ํต์ ์ ๋ด๋นํ ํ์ ์ ์ค๊ณํฉ๋๋ค.
- Mock ๋ฐ์ดํฐ๋ฅผ ํ์ฉํ์ฌ ๋จ์ํ ์คํธ๋ฅผ ์ํํฉ๋๋ค.
- ํ ํ์ ์ด ํ๋์ ์ญํ ๋ง ํ ์ ์๋๋ก ์ค๊ณ์ ๋ง์ ๊ณ ๋ฏผ์ ํด๋ณด์๋ค.
์ค์ ๋คํธ์ํฌ์์ ๋ด๋ ค์ค๋ ๋ณ์๋ช
์ด ์ค๋ค์ดํฌ ์ผ์ด์ค๋ฅผ ์ฌ์ฉํ๋ ๋ณ์๋ 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"
}
- 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
-
- ํ๋์ฝ๋ฉ์ ๊ฐ์ ํ๊ธฐ ์ํด enum ํ์ ์ ๋ง๋ค์ด Address์ HTTPMethod์ ๊ฐ๋ค์ ๋ถ๋ฅํด์ฃผ์๋ค.
- Requestํ ๋, ๊ทธ๋ฆฌ๊ณ Responseํ๋ ํ์ ์ด ์ธ๋ถ์ ์ผ๋ก ๋ฌ๋ผ ProductModification, ProductRegistration ๋ฑ... ๊ฐ ํ์ ์ ๋ชจ๋ ๊ตฌํํ์๋ค.
- ์ํ ์ญ์ , ๋ฑ๋ก, ์กฐํ ๋ฑ ์ฌ๋ฌ๊ฐ์ง ์์ฒญ์ request ๋ฉ์๋ ํ๋๋ฅผ ์ค๋ฒ๋ก๋ฉ์ ํ์ฉํ์ฌ ์์ฑํ์๋ค.
- ํ ์คํธ ์์ฑ์ ์ํด ์์กด์ฑ ์ฃผ์ ์ ํ์ฉํ์ฌ Mock, Stub ๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด ํ์ฉํ์๋ค.
- URLProtocol์ ์์๋ฐ์ ํด๋์ค๋ฅผ ๋ง๋ค๊ณ ์ฌ์ ์๋ฅผ ํด์ฃผ์๋ค.
- ์ด ๋ฐฉ๋ฒ์ URLSession์ dataTask๋ฅผ ์ง์ Stub์ผ๋ก ๋ง๋๋ ๋ฐฉ๋ฒ๋ ์์์ง๋ง, URLSessionDataTask๋ฅผ ์ฑํํ ํ์ ์ init()์ ์ ์ํ๋ deprecated ๊ฒฝ๊ณ ๊ฐ ๋ ์ ์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ์ญ์ ํ URLProtocol์ ํ์ฉํ๋ ๋ฐฉ๋ฒ์ผ๋ก ๋ก์ง์ ๋ณ๊ฒฝํ์๋ค.
- ๋น๋๊ธฐ ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ ๋๊ธฐ ๋ฉ์๋๋ ๋น๋๊ธฐ ๋ฉ์๋ ํ ์คํธ๋ก ์งํํด์ผํ ๊น?
- URLProtocol๊ณผ URLSession์ ๊ด๊ณ๊ฐ ์ ํํ๊ฒ ์ดํด๋์ง ์๋๋ค...
- Health Checker์ ํ์์ฑ์ ๋ชจ๋ฅด๊ฒ ๋ค..
- ํ ์คํธ ์ Request์ ๋ฐ๋๋ ์ฒดํฌ๋ฅผ ํด์ผํ ๊น?
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
์ด์
URLSessionDataTaskinit()
์ด 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์ ๊ฐ์ด ๋ฃ๋ ๋ฐฉ๋ฒ
-
์ํฉ
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๋ฅผ ๋ฃ์ด์ค๋ค.
- HTTP ํต์ ๊ท๊ฒฉ์ ํ์ธํด์ JSONํ์ผ๊ณผ Imageํ์ผ์ ๋ฐ๋์ ์ถ๊ฐํ๊ฒ ์ฝ๋๋ฅผ ์ง์ผํ๋ค.
-
ํด๊ฒฐ
์์์ ์ ๋ฆฌํ ์์๋๋ก ๋ฐ๋๋ฅผ ์ถ๊ฐํ๋๋ก ์ฝ๋๋ฅผ ์์ฑํ์๋ค.
multipart/form-data
- API๋ฌธ์ ์ฝ๋ ๋ฐฉ๋ฒ
- ํ์ฑํ JSON ๋ฐ์ดํฐ์ ๋งคํํ ๋ชจ๋ธ ์ค๊ณ
CodingKeys
ย ํ๋กํ ์ฝ์ ํ์ฉ
- URL Session์ ํ์ฉํ ์๋ฒ์์ ํต์
URLRequest
๋ฅผ ์ค์ ํ๋ ๋ฐฉ๋ฒ- Testableํ ๋คํธ์ํฌ ์ฝ๋ ์์ฑํ๊ธฐ
- ๋คํธ์ํฌ ์ํฉ๊ณผ ๋ฌด๊ดํ ๋คํธ์ํน ๋ฐ์ดํฐ ํ์ ์ ๋จ์ ํ ์คํธ(Unit Test)
- ํ
์คํธ ์ฝ๋์ ์ค๋ณต๋๋ ๋ถ๋ถ์ ๊ฐ์
- ๋น ์ง ์ฃผ์ ๋ฐ ์ค๋ฐ๊ฟ์ ์์
- Image์ ํ๋กํผํฐ ๋ค์ด๋ฐ์ ๋ช ํํ๊ฒ ์์
- ํ๋์ฝ๋ฉ ๋์ด์๋ ๋ฌธ์์ด์ ๋ฐ๋ก enum ํ์ ์ผ๋ก ๋นผ์ฃผ์ด ๊ฐ์
- ์๋ฌ์ ๋ค์ด๋ฐ์ ๋ช ํํ๊ฒ ๊ฐ์
- Parser, Parsable์ ๋ค์ด๋ฐ์ JSON์ ๋ง๋ถํ ๋ช ํํ๊ฒ ๊ฐ์
- ์ ๊ทผ์ ์ด๊ฐ ๋ถ์ด์์ง ์์ ํ๋กํผํฐ์ ์ ๊ทผ์ ์ด๋ฅผ ์ถ๊ฐ
- Address์ ๋ค์ด๋ฐ์ ๋ช ํํ๊ฒ ๊ฐ์ (APIAddress)
์ํ๋ชฉ๋ก์ ๋ณผ ์ ์๋ ํ๋ฉด์ ๊ตฌํํฉ๋๋ค.
-
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๋ฅผ ํ์ฉํ์ฌ ์ทจ์ ๋ฒํผ์ ๊ตฌ์ฑ
- HIG๋ฅผ ์ฐธ๊ณ ํ์ฌ ์ํ๋ฑ๋ก ๋ฒํผ์ ๋๋ ์ ๋
- collectionview์ flowlayout์ ๋ณ๊ฒฝํ ๋
AutoLayout ์ถฉ๋ ๊ด๋ จ ๊ฒฝ๊ณ
๊ฐ ๋จ๋๋ฐ, ํด๊ฒฐ ๋ฐฉ๋ฒ์ ๋ชจ๋ฅด๊ฒ ๋ค. SegmentControl
์ ํ์ฉํ์ฌ List๋ Grid๋ฅผ ์ ํํ ๋ ์๊ธฐ๋ ์ฝ๊ฐ์ ๋๋ ์ด์ ์์ธ์ ๋ชจ๋ฅด๊ฒ ๋ค.
1. Segument Control์ ์ด์ฉํ์ฌ ํ๋ฉด์ ํ ์ ์คํฌ๋กค ์์น๊ฐ ์ ์์ ์ด์ง ์์ ๊ฒฝ์ฐ
-
์ํฉ
FlowLayout์ ํ์ฉํ์ฌ ํ๋ฉด์ ์ ํํ ๋, ์คํฌ๋กค์ด ์๋จ์ ์์นํ๋๊ฒ ์๋๋ผ ์ ๋ฉ๋๋ก์ธ ์์น์ ๊ฐ์๋ ํ์์ด ๋ฐ์ํ๋ค. -
์ด์
๋ ์ด์์์ด ์๋ก ๋ค๋ฅด๊ธฐ ๋๋ฌธ์ ์คํฌ๋กค์ ์ขํ๋ ๋ค๋ฅธ ๊ฒ์ผ๋ก ์ถ์ธก์ด ๋์๋ค. -
ํด๊ฒฐ
๋ฐ๋ผ์ ์ด ๋ถ๋ถ์ ํ๋ฉด์ ์ ํํ ๋ ์คํฌ๋กค์ ์์น๋ฅผ ์๋จ์ ์์นํ๊ฒ ์ค์ ํด์ฃผ๋ ํด๊ฒฐ๋์๋ค.extension UIScrollView { func scrollToTop() { let topOffset = CGPoint(x: 0, y: -contentInset.top) setContentOffset(topOffset, animated: false) } }
UICollectionView
UICollectionViewFlowLayout
Networking
์ ํตํ ๋ทฐ์ ๋ํ ๋น๋๊ธฐ ์ฒ๋ฆฌreloadData
Xib File
UISegmentedControl
UIActivityIndicatorView
- Asset์ ๋ฑ๋ก๋์ด์๋ ์ด๋ฏธ์ง ์ค์ ๊ฐ ์์
- ์ ๋ฐ์ ์ธ ๋ค์ด๋ฐ ์์
- ์ผํญ์ฐ์ฐ์๋ก ์กฐ๊ฑด๋ฌธ ๊ฐ์
- ๋น ์ ธ์๋ ์ ๊ทผ์ ์ด ์ถ๊ฐ
- ๋์ ์ผ๋ก ๋ ์ด์์์ ์ก์ ์ ์๋๋กย UICollectionViewDelegateFlowLayout์ ์ฑํ
- ๊ฐ๋ก๋ชจ๋, ์ธ๋ก๋ชจ๋์์๋ ๋ ์ด์์์ด ๋ญ๊ฐ์ง์ง ์๋๋ก ๊ฐ์
์๋ฒ์์ ๋ฐ์ ๋ฐ์ดํฐ๋ฅผ ๋ก์ปฌ์ ์บ์ํฉ๋๋ค.
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 } }
-
- ์คํฌ๋กค ์ yOffset์ ๋น์ ์์ ์ผ๋ก ์นด์ดํธ๊ฐ ๋๋ ๋ถ๋ถ์ด ๋ฌธ์ ์ฌ์ ์์ง ํ๋ฆฌ์ง ์์๋๋ฐ, ํด๋น ๋ถ๋ถ์ ๊ทธ๋ฅ ๋์ด๊ฐ๋ ๋๋๊ฑด์ง[?] ์ฝ๊ฐ์ ์ฐ์ฐํจ์ด ๋จ๋๋ค.
- ํญ์ ๋จ๋ ์๋ฌ๋ ์๋๊ณ , ๊ฐํ์ ์ผ๋ก ๋จ๋ ์๋ฌ๋ค. ์คํฌ๋กค์ ํ๋จ๊น์ง ํ์ ๋ ๋์ฝ๋ฉ์ ์คํจํ๋ ๊ฒฝ์ฐ๊ฐ ์์ต๋๋ค. ๋๋ฒ๊น ์ ํด๋ณด๋ ๊ฒฐ๊ณผ๋ ์๋์ ๊ฐ๋ค.
- ๋ทฐ์ปจํธ๋กค๋ฌ ์ชฝ์์ ๋คํธ์ํฌ ๋งค๋์ ์
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๋ฅผ ๋ํด์ค ์ ์๋๋ก ์์๋ฐฉํธ์ผ๋ก ์์ ํด์ฃผ์๋ค.
UIScrollViewDelegate
๋ฅผ ์ด์ฉํpagination
๊ตฌํCache
์ ๋ํ ๊ฐ๋ Caching
์ ๋ฒ์
- ์ฝ๋ฉ ์ปจ๋ฒค์ ์ ๋ง์ถ์ด ๋ฉ์๋์ ์ค๋ฐ๊ฟ์ ์์
- ์ฌ์ฉํ์ง ์๋ ํ์ ์ ์ญ์
- ๋ค์ด๋ฐ ๊ฐ์
- API ๋ฌธ์๋ฅผ ๋ค์ ๊ฒํ ํ์ฌ ํ์ ์ ๊ฐ์
- ์ค๋ณต๋๋ ์ฝ๋๋ฅผ ์ ๊ฑฐํ์ฌ ๋ฆฌํฉํ ๋ง
์ํ๋ฑ๋ก, ์ํ์์ ํ๋ฉด์ ๊ตฌํํฉ๋๋ค.
- CollectionView์ Cell๊ณผ Header๋ฅผ ํ์ฉํ์ฌ View๋ฅผ ๊ตฌ์ฑ
- ์ด๋ฏธ์ง๊ฐ 5์ฅ์ด ์ฑ์์ก์ ๋ ๊ฒฝ๊ณ ์ผ๋ฟ์ ๋์ฐ๋๋ก ๊ตฌํ
- ์ฌ์ฉ์ ์ ์ฅ์์ ๋ช๊ฐ์ ์ด๋ฏธ์ง๊ฐ ์ถ๊ฐ๋์๋์ง ์๊ฐ์ ์ผ๋ก ํ์ธํ ์ ์๋๋ก Label ์ถ๊ฐ ๊ตฌํ
- ์ด๋ฏธ์ง ํ์ผ ์ฉ๋์ด 300KB ์ด์์ผ ๊ฒฝ์ฐ
UIGraphicsImageRenderer
๋ฅผ ์ด์ฉํ์ฌ 20ํผ์ผํธ์ฉ resize
- ์๊ตฌ์ฌํญ๊ณผ API ๋ฌธ์๋ฅผ ์ฐธ๊ณ ํ์ฌ ์ฌ์ฉ์๊ฐ ์ ๋๋ก ์ ๋ ฅํ์ง ์์ ๋ถ๋ถ์ด ์๋ค๋ฉด ์ผ๋ฟ์ ํตํด ์ด๋ค ๋ถ๋ถ์ด ์๋ชป๋์๋์ง ์๋ ค์ค ์ ์๋๋ก ๊ตฌํ
- ํ๋ฉด ์ ํ ์ ์ํ ๋ฑ๋ก, ์ํ ์์ flag๋ฅผ ์ ๋ฌํ์ฌ ํด๋น flag์ ๋ํ ๋ถ๊ธฐ์ฒ๋ฆฌ๋ฅผ ํตํด์ ViewController๋ฅผ ์ฌํ์ฉ
- ์ ์ ์ปค์ ธ๊ฐ๋ ViewController๋ฅผ ๋ค์ด์ดํธ ์ํค๊ธฐ ์ํด DataSource๋ฅผ ๋ฐ๋ก ํ์ ์ผ๋ก ๋นผ๋์ด ViewController์ ์ฃผ์ ์ํค๋๋ก ๊ตฌํ
- ScrollView์ NotificationCenter ๋๊ฐ์ง๋ฅผ ํ์ฉ
- ScrollView์
contentInset
์keyboardFrame ์ฌ์ด์ฆ์ ๋์ด
๋งํผ ๋ฒํผ๋ฅผ ์ถ๊ฐํ์ฌ ์คํฌ๋กค๋ทฐ๋ฅผ ํ์ฅํ๋ฏ๋ก์จ ํค๋ณด๋ ๊ฐ๋ฆผํ์์ ํด๊ฒฐscrollIndicatorInsets
๋ ๊ฐ์ด ๋ณ๊ฒฝํด์ฃผ๋๋ก ํ์์.
- TextView์์ ์ค๋ฐ๊ฟ์ ํ๋ฉด์ ์ปค์๊ฐ ๋ด๋ ค๊ฐ ๋ ๋ฐ๋ผ๊ฐ ์ ์๋๋ก, ScrollEnabled ๊ธฐ๋ฅ์ false๋ก ๋นํ์ฑํ
- ์ดํ ์คํ ๋ ์ด์์์ผ๋ก TextView์ ๋์ด๊ฐ ๋ ๊ณ ์ ๋์ด์๋ ๊ฒ์ด ์๋๋ผ ๋์ด๋ ์๋ ์๋๋กย
priority
๋ฅผ ์กฐ์ .- ํ ์คํธ๋ทฐ๊ฐ ์์ Text๊ฐ ๊ธธ์ด์ง ์๋ก ๋์ด๊ฐ ๋์ด๋๊ณ , ๊ทธ์ ๋ฐ๋ผ ์คํฌ๋กค๋ ์๋์ผ๋ก ๋ด๋ ค์จ๋ค.
- ImageHeaderView์ RegisterViewController, ๊ฐ๊ฐ์ ViewController๋ค ์ฌ์ด์ ์ํต์ ์ํด์ notification์ ์ฌ์ฉ
- ์ํ ๋ฑ๋ก, ์ํ ์์ ์ ๊ฐ ViewController์ notification postํ์ฌ update ์ด๋ฒคํธ๋ฅผ ์ ๋ฌ
- ์ด๋ฒคํธ ์ ๋ฌ ์ API์
request
ํ์ฌ ๋ฐ์ดํฐ๋ฅผ ์ ๋ฐ์ดํธ - MainViewController์ CollectionView๋ฅผ ์๋๋ก ์ธ์ด๋ด๋ ธ์ ๋ ์ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์ค๋ ๊ธฐ๋ฅ ์ถ๊ฐ ๊ตฌํ
- ์ํ ๋ฑ๋ก์์๋ API ๋ช ์ธ๋ฅผ ๋ฐ๋ฅด๊ธฐ ์ํด ๋น๋ฐ๋ฒํธ๊ฐ ์ด๋ฏธ ์ธํ ๋ ์ํ๋ก ๋ฑ๋ก์ด ๋์ง๋ง, ์ํ ์์ ์์๋ ์์ ํ๊ธฐ ์ ๋น๋ฐ๋ฒํธ๋ฅผ ๋จผ์ ํ์ธํ๊ณ , ๋น๋ฐ๋ฒํธ๊ฐ ๋ง๋ค๋ฉด ์์ ํ ์ ์๋๋ก ๊ตฌํ
-
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๋ฅผ ์ด๋ป๊ฒ ๋ค์ด์ดํธ ์์ผ์ผํ๋์ง...
ํค๋ณด๋๊ฐ ์ฝํ ์ธ ๋ฅผ ๊ฐ๋ฆฌ์ง ์๋๋ก ํ๋ ๋ฐฉ๋ฒ
๋ฌธ์
NotificationCenter๋ฅผ ํ์ฉํ์ฌ ํค๋ณด๋ํ๋ ์์ ๊ณ์ฐํ๊ณ , ๊ทธ์ ๋ง๊ฒ ScrollView์ contentInset์ ์กฐ์ ํด์ฃผ์ด ์ฝํ ์ธ ๋ฅผ ๊ฐ๋ฆฌ์ง ์๋๋ก ํด๊ฒฐ์ ํด๋ณด์๋ค.์ด์
๊ตฌ๊ธ๋ง์ ํด๋ณธ ๊ฒฐ๊ณผ TextView์ ๊ฒฝ์ฐ ์คํฌ๋กค ๊ธฐ๋ฅ์ ์์ค ํ์ ๋์ด๋ฅผ ๊ณ ์ ์์ผ์ผ ์ด๋ฌํ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์๋ค๊ณ ๋์์์๋ค. ์ดํดํ ๋ฐ๋ก๋ TextView์ ๋์ด๊ฐ ๊ณ์ ๋์ด๋ ์ ์๊ณ , ์คํฌ๋กค์ด ๊ธฐ๋ฅ์ด ๊ฐ๋ฅํ ์ํ์์๋ ๋ฐ๊นฅ์ ๊น๋ ค์๋ ์คํฌ๋กค๋ทฐ ์ ์ฅ์์๋ ๋ทฐ์ ๋์ด๊ฐ ๋ณํ์ง ์์ผ๋ ์คํฌ๋กค์ ์ขํ๋ ๊ทธ๋๋ก์ธ ๊ฒ์ด๋ผ๊ณ ์ดํด๊ฐ ๋์๋ค.ํด๊ฒฐ
๋ฐ๋ผ์ UITextView์์คํฌ๋กค ๊ธฐ๋ฅ
์ ๋นํ์ฑํํ ํ, ๋์ด๊ฐ ๋ ๊ณ ์ ๋์ด์๋ ๊ฒ์ด ์๋๋ผ ๋ด๋ถ์ Text์ ๋ฐ๋ผ ๋์ด๋ ์๋ ์๋๋ก ์คํ ๋ ์ด์์์ฐ์ ์์
๋ฅผ ์กฐ์ ํด์ฃผ์๋ค.
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
๋ฉ์๋๋ฅผ ํธ์ถํ๋ ๋ก์ง์ผ๋ก ๋ณ๊ฒฝ๋๋ค.
- ์ฌ์ฉ์ ์นํ์ ์ธ
UI/UX
๊ตฌํ URLSession
์ ํ์ฉํ multipart-form ์์ฒญ ์ ์กUIImagePickerController
๋ฅผ ํ์ฉํ์ฌ ์ฌ์ฉ์ ์จ๋ฒ์ ์ ๊ทผํ๋ ๋ฐฉ๋ฒUIGraphicsImageRenderer
๋ฅผ ์ด๋ฏธ์ง๋ฅผ ๋๋๋ง ํ์ฌ ์์ถํ๋ ๋ฐฉ๋ฒUICollectionReusableView
๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒUIScrollView
๋ฅผ ํ์ฉํ์ฌ ํค๋ณด๋๊ฐ ์ปจํ ์ธ ๋ฅผ ๊ฐ๋ฆฌ๋ ๋ถ๋ถ์ ํด๊ฒฐํ๋ ๋ฐฉ๋ฒUITextField
๋ฅผ ๊ฐ์ง๊ณ ์๋UIAlertController
ํ์ฉUIRefreshControl
๋ฅผ ํ์ฉํ๋ ๋ฐฉ๋ฒ
- AlertConstant๋ฅผย
์ ๊ฑฐ
ํ๊ณ ยUIViewController+extension
ย ๋ถ๋ถ ์ ์ฒด์ ์ผ๋ก ๊ฐ์ - CollectionView์์ย
refreshing
ํ ๋ยindex out of range
ย ์๋ฌ๋๋ ๋ถ๋ถ ๋น๋๊ธฐ์ ์ผ๋ก ์ฒ๋ฆฌํ์ฌ ํด๊ฒฐ - ๋ค์ด๋ฐ ๋ถ๋ถ ์ ์ฒด์ ์ผ๋ก ๊ฐ์
- ์จ๋ฒ์ ์ ๊ทผํ ๋ ๋ณด์ฌ์ง๋ description์ ์์ ํ์ฌ ๊ฐ์
- HIG๋ฅผ ์ฐธ๊ณ ํ์ฌ alert, ActionSheet์ย
๋ฒํผ ์์๋ฅผ ๋ณ๊ฒฝ
ํ์ฌ ๊ฐ์ - DetailViewControllerย
๋ฉ์๋ ์์
๋ฅผ ๊ฐ์ - ์ด๋ฏธ์ง ์ญ์ ์ 'x'๋ฒํผ์ ํด๋ฆญํ์ ๋ ์ญ์ ๋๋๋ก ์์ ํ์ฌ ๊ฐ์
- ์
์ ์ ํํ์ ๋ ๋ณํ๊ฐ ๋ฐ์ํ๋๋กย
selectedBackgroundView
ย ์ค์
์ํ์ ์์ธ๋ด์ฉ์ ํ์ธํ ์ ์๋ ํ๋ฉด์ ๊ตฌํํฉ๋๋ค
- ์ฌ์ฉ์๊ฐ ๋ณผ ํ์๊ฐ ์๋ ์๋ฌ์ ๊ฒฝ์ฐ(๋คํธ์ํฌ ์๋ฌ) ์ผ๋ฟ์ ๋์ฐ๋ ๋์ print๋ฅผ ํธ์ถํ๋๋ก ํ์ฌ, ์๋ฌ์ฒ๋ฆฌ๋ฅผ ํด์ฃผ์๋ค.
- UIFont๊ฐ ์ ๊ณตํ๋ preferredFont๋ฅผ ์ฌ์ฉํ๋ฉด ๋ฐ๋ก ๊ตต๊ธฐ๋ฅผ ์ง์ ํ ์ ์๊ณ , ์ง์ ๋ font๋ง ์ฌ์ฉํด์ผํ๋ค. ๋ฐ๋๋ก systemFont๋ฅผ ์ฌ์ฉํ๋ค๋ฉด Dynamic Type์ด ๋์ํ์ง ์๋๋ค.
- extension์ ํตํด์ ๊ตต๊ธฐ๋ฅผ ์ง์ ํด๋ Dynamic Type์ ์ง์ํ๋ ๋ฉ์๋๋ฅผ ๊ตฌํํ์ฌ ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐ.
-
๋ค๋ฅธ ๋ง์ผ์ฑ์ ์ดํด๋ณด๋ฉด ์์ธ๋ณด๊ธฐ๋ก ๋์ด๊ฐ ๋ ๋ณด๊ณ ์๋ ์ด๋ฏธ์ง๋ฅผ ๊ทธ๋๋ก ๋๊ฒจ์ ์์ธ๋ณด๊ธฐ ํ๋ฉด์ผ๋ก ์ ํ๋๋ ๊ฒ์ ํ์ธํ ์ ์๋ค. ์ฌ์ฉ์ ์ ์ฅ์์๋ ๋ณด๊ณ ์๋ ์ด๋ฏธ์ง๋ฅผ ๊ทธ๋๋ก ์์ธ๋ณด๊ธฐ๋ก ๋ณด๋ ๊ฒ์ด ๋ฐ๋์งํ๋ค๊ณ ํ๋จ๋์ด ํด๋น ๊ธฐ๋ฅ์ ๊ตฌํํ์๋ค.
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 ) }
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.
- ์ด ๋ถ๋ถ์ ์ฐธ๊ณ ํ์ฌ, ์๊ตฌ์ฌํญ์ฒ๋ผ ์ญ์ ๋ฒํผ์ ์๋์ ์์นํด์๋๊ฒ ์๋๋ผ ์๋จ์ ์์นํ๋๋ก ๊ฐ์ ํ์๋ค.
- ์์ธํ์ด์งํ๋ฉด์์ ์ด๋ฏธ์ง๋ฅผ ํ์ํ ๋ CollectionView์ isPaging๊ณผ ๊ฐ๋ก ์คํฌ๋กค์ ํตํด์ paging์ ๊ตฌํํ์๋ค.
- pageControl์ ์ด์ฉํ์ฌ ํ์ฌ ํ์ด์ง์ ์์น๋ฅผ ๋ํ๋ด๊ณ PageControl์ dot์ ํญํ์ฌ๋ ํ์ด์ง๊ฐ ์ ํ๋๋ ๊ธฐ๋ฅ์ ๊ตฌํํ์๋ค.
DetailViewController
โImageDetailViewController
๋ก ์ ํํ ๋ viewDidLoad์์ collectionView.scrollToItem
์ ํธ์ถ ์ ์ ๋๋ก ์ ์ฉ๋์ง ์์์ DispatchQueue.main.asyncAfter
๋ฅผ ํ์ฉํ๋๋ฐ, ์ ์ ํ์ง ์ ๋ชจ๋ฅด๊ฒ ๋ค.- ๋คํธ์ํน ํ๋ ๋ถ๋ถ์ด ๋ง์น ์ฐ๋งฅ..์ฒ๋ผ ๊ณตํฌ์ ๋ค์ฌ์ฐ๊ธฐ๊ฐ ์๊ฒจ๋ฌ๋๋ฐ, ์ด๋ฌํ ๋ถ๋ถ์ ์ด๋ป๊ฒ ๋ ๊ฐ์ ํ ์ ์์์ง ๋์ด์ ๋ ์ค๋ฅด์ง๊ฐ ์๋๋ค.
- CollectionView์ FlowLayout์ ๋ฐ๋ก ์ฝ๋๋ก ๋์ ํด์ฃผ์ง ์์ผ๋ฉด, UICollectionViewDelegateFlowLayout๋ฅผ ์ฑํํ์ฌ ๋ ์ด์์์ ์ค์ ํด์ฃผ์ด๋ ๋ ์ด์์์ด ์ ์ฉ๋์ง ์๋๋ฐ, ์๊ทธ๋ฐ๊ฑธ๊น?
- ScrollView๋ฅผ ํ์ฉํด์ Zoom์ ๊ตฌํํ์์ง๋ง, ๋๋ฐ์ด์ค ์ ์ฒด ํฌ๊ธฐ์ ๋ง์ถฐ์ Scale์ ์ค์ ํ๋ ๋ฐฉ๋ฒ์ ๋ชจ๋ฅด๊ฒ ๋ค.
- ์ด๋ฏธ์ง๊ฐ ๋๋ฐ์ด์ค ํฌ๊ธฐ๋ณด๋ค ์ปค์ง์ง ์๋๋ก ํ๊ณ ์ถ๋ค...
-
๋ฌธ์
๋ฉ์ธํ์ด์ง์์ ์์ธํ์ด์ง๋ก ๋์ด๊ฐ๋ ์ด๋ฏธ์ง๊ฐ ๋ณด์ด์ง ์๋ ํ์์ด ๋ํ๋ฌ๋ค. -
์ด์
์ด๋ฏธ์ง๋ฅผ ๋คํธ์ํนํ๋ ์๋๋ณด๋ค ํ๋ฉด์ด ์ ํ๋๋ ์์ ์ด ๋นจ๋ผ์ ๋ํ๋๋ ๋ฌธ์ ์๋ค. -
ํด๊ฒฐ
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 ... } }
-
๋ฌธ์
CollectionView์ isPagingEnabled์ true๋ก ์ฃผ๋ฉด ์๋ ์์์ ๊ฐ์ด ์คํฌ๋กค ํ์ ๋ cell์ด ์กฐ๊ธ์ฉ ๋ฐ๋ฆฌ๋ ํ์์ด ๋ํ๋ฌ๋ค. -
์ด์
CollectionView์ ๊ฒฝ์ฐ minimumLineSpacing์ด ๊ธฐ๋ณธ์ ์ผ๋ก ๊ฐ(10.0)์ด ๋ค์ด๊ฐ์๋ค. ํด๋น ๊ฐ ๋๋ฌธ์ ์คํฌ๋กค ์ ๋ฐ๋ฆผํ์์ด ์์๋ ๊ฒ์ด์๋ค. -
ํด๊ฒฐ
minimumLineSpacing์ 0์ผ๋ก ์ค์ ํด์ฃผ๋ ์คํฌ๋กค ์ cell์ด ์กฐ๊ธ์ฉ ๋ฐ๋ฆฌ๋ ํ์์ด ํด๊ฒฐ๋์๋ค.
-
๋ฌธ์
UITableView๊ฐ์ ๊ฒฝ์ฐ์๋ ๊ธฐ๋ณธ์ ์ผ๋ก seleted ํ์ ๋, ํ์ ๋ฐฐ๊ฒฝ์ด ์ฌ๋ผ์ง์ง ์์์ delegate ๋ฉ์๋๋ฅผ ํ์ฉํ์ฌ deselect๋ฅผ ํด์ฃผ์ด์ผ ๋ฐฐ๊ฒฝ์์ด ๋ค์ ์๋๋๋ก ๋์์์๋ค. -
์ด์
ํ์ง๋ง UICollectionView ๊ฐ์ ๊ฒฝ์ฐ์๋ ์๋ฌด๊ฒ๋ ์ค์ ๋์ด์์ง ์๊ธฐ ๋๋ฌธ์ ์ด ๋ถ๋ถ์ ์ง์ ์ค์ ์ ํด์ฃผ์ด์ผ ํ๋ค. -
ํด๊ฒฐ
์ ์ ์ด๊ธฐํํ ๋ selectedBackgroundView๋ฅผ ์ง์ ํด์ฃผ๊ณ , backgroundColor๋ฅผ ์ ํ์ค๋ค.cell.selectedBackgroundView = UIView(frame: self.bounds) cell.selectedBackgroundView?.backgroundColor = .systemGray5
-
ํ์ง๋ง ์์ ๊ฐ์ด ๋ฐฐ๊ฒฝ์์ด ๋ฐ๋ ์ฑ๋ก ๋จ์์๋ค. ๋ฐ๋ผ์ Delegate ๋ฉ์๋ ์ค didSelectItemAt๋ฅผ ๊ตฌํํ์ฌ deselectItem๋ฅผ ํธ์ถํด์ ์ ์ ํ์ ํด์ ์์ผ์ฃผ์ด์ผ ํ๋ค.
// UICollectionViewDelegate... func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { collectionView.deselectItem(at: indexPath, animated: true) }
- ์ด๋ ๊ฒ ํ๋ฉด ์ ์์ ์ผ๋ก ์ ์ ์ ํํ์ ๋, ์ ํ๋์๋ค๋ ํจ๊ณผ๊ฐ ์ผ์ด๋๋ฉด์ ํ๋ฉด์ ํ์ด ๋๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
- UIPageControl์ ํ์ฉํ๋ ๋ฐฉ๋ฒ
- GestureRecognizer๋ฅผ ํตํด ํน์ ํฐ์น์ด๋ฒคํธ๋ฅผ ์ฒ๋ฆฌํ๋ ๋ฐฉ๋ฒ
- UIScrollView์ Zoom ๊ธฐ๋ฅ์ ํ์ฉํ๋ ๋ฐฉ๋ฒ
- UIFontMetrics๋ฅผ ํ์ฉํ์ฌ Custom Font์๋ Dynamic Type์ ์ ์ฉํด๋ณด๋ ๋ฐฉ๋ฒ
- UICollectionView๋ฅผ ํ์ฉํด์ Paging์ ๊ตฌํํ๋ ๋ฐฉ๋ฒ
- UIAlertController์ ํ
์คํธ ํ๋๋ฅผ ์ถ๊ฐํ์ฌ ํ์ฉํ๋ ๋ฐฉ๋ฒ
- Handler ํ์ฉ