| UY |
|---|
![]() |
- ์๋ฒ API๋ฅผ ์ด์ฉํ์ฌ ์ด๋ฏธ์ง๋ฅผ ๋ฐ์์, ๊ฐ๋ณ ์ธ๋ก ๊ธธ์ด์ ๋ ์ด์์์ผ๋ก ๋ํ๋ ๋๋ค.
- ์ํ๋ ์ฌ์ง์ ์ ํํด ์ ์ฅํ๋ ๊ธฐ๋ฅ์ ๊ฐ์ต๋๋ค.
- ์์ดํฐ, ์ธ๋ก ๋ชจ๋๋ง ์ง์ํ๋ ์ฑ์ ๋๋ค.
- ๊ฐ๋ฐ ๊ธฐ๊ฐ: 2022.07.25 ~ 2022.07.28 (4์ผ)
- ์ฌ์ฉ ๊ธฐ์ :
UIKit,MVVM,URLSession,Coredata,NSCache,FileManager,UICollectionViewLayout
-
-
collectionView.deleteItem(at:)์ ์คํ ํ๋ก์ฐ
- ์ฐ์ ์ ์ ์ญ์ ํ๋ค(์ค์ ๋ณด์ฌ์ง๋ ์ ์์ ์ญ์ ๋ฅผ ํ๋ค)
- ๊ทธํ ๋ค์ reload๋ฅผ ์คํํ์ฌ ๋ณํ๋ ๋ฐ์ดํฐ๋ฅผ ์ง์์ง ์๋ฆฌ์ ์ง์ด๋ฃ๋๋ค
- ์ด๋ ์ ์ ์ฌ์ด์ง์ ๋ณํ๊ฐ ์์ผ๋ฉด ๋ณํ ์์ผ์ ์ง์ด ๋ฃ๋๋ค.
-
๋ฌธ์ ์
- collectoinView๋ฅผ reload ํ ๋ viewModel์ count๋ฅผ ๊ฐ์ ธ์ ์ด๋ค.
- ์ญ์ ์ viewModel์ imageList๋ฅผ ๊ฐฑ์ ์์ผ์ฃผ์ง ์์๋ค. (CoreData์ FileManager๋ง ์ฒ๋ฆฌํด ์ฃผ์๋ค.)
- ๋๋ฌธ์ ๋ชจ๋ ์ ๋ณด๊ฐ ์ญ์ ๋์ด๋ imageList์ count๋ ์ญ์ ๋๊ธฐ ์ ๊ทธ๋๋ก์ด๊ธฐ ๋๋ฌธ์ ๋ฌธ์ ๊ฐ ๋ฐ์
-
// ImageRepository VC
// ๊ธฐ์กด ์ฝ๋
{
picturesCollectionView.deleteItems(at: [indexPath])
let item = viewModel.image(at: indexPath.row)
CoreDataManager.shared.delete(entity: item)
PicterestFileManager.shared.deletePicture(fileName: item.id!)
}
//----------- ๊ฐ์ ------------
{
picturesCollectionView.deleteItems(at: [indexPath])
viewModel.deleteImage(at: indexPath)
}
// ViewModel
func deleteImage(at indexPath: IndexPath) {
let item = imageList[indexPath.item]
CoreDataManager.shared.delete(entity: item)
PicterestFileManager.shared.deletePicture(fileName: item.id!)
imageList.remove(at: indexPath.row)
}cell์ ์ญ์ ์ contextMenu๋ฅผ ์ฌ์ฉํ๋ค. ์ด ๊ณผ์ ์์ ๋ด๊ฐ ์ง์ ํด์ค cornerRadius์ ์์คํ
์์ ์ ๊ณต๋๋ UITargetPreview์ cornerRadius๊ฐ ๋ง์ง ์์๋ค.
์๋์ ๊ฐ์ด preview์ cornerRadius ํฐ ๋ถ๋ถ์ด cell์ ํฌ๊ธฐ๋ณด๋ค ์ปค์ ๋งค์ฐ ๊ฑฐ์ฌ๋ฆฌ๋ ์ํฑ ๊ฑฐ์ค๋ฌ๋ฏธ ๊ฐ์ UI๊ฐ ์์ฑ ๋์๋ค.
๋ถ๋ช preView๋ฅผ ๊ฑด๋๋ ค์ค ์ ์์๊ฑฐ๋ผ ์๊ฐํ์ฌ
collectionView(_:previewForHighlightingContextMenuWithConfiguration:) ๋ฅผ ์ฌ์ฉํ์๋ค.
preview์ ์ ๊ทผํ์ฌ UIPreviewParameters์ backgroundColor์ clear๋ก ๋ฐ๊ฟ์ฃผ๊ฑฐ๋ visiblePath๋ฅผ ํตํด preview ์์ฒด๋ฅผ cell์ด ์๋ cell์ contentView๋ก ๊น์๋ฒ๋ ธ๋ค.
func collectionView(_ collectionView: UICollectionView, previewForHighlightingContextMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? {
guard let indexPath = configuration.identifier as? IndexPath,
let cell = collectionView.cellForItem(at: indexPath) else { return nil }
let parameters = UIPreviewParameters()
parameters.backgroundColor = .clear
parameters.visiblePath = UIBezierPath(roundedRect: cell.contentView.bounds, cornerRadius: cell.contentView.layer.cornerRadius)
return UITargetedPreview(view: cell, parameters: parameters)
}์ด๋ฏธ์ง๋ค์ ๋ฆฌ์คํธ์์ ๋ณํ ๋ชจ์์ ์ ์ฅ ๋ฒํผ์ ๋๋ ๋ค ๋ค์ ํ๋ฒ ๋๋ฅด๋ฉด ์ญ์ ๋๋ ๊ธฐ๋ฅ์ ์ถ๊ฐํ๋ ค ํ๋ค.
๊ธฐ์กด Picture List์์ ์ ์ฅ ๊ธฐ๋ฅ ์ธ์ coredata๋ฅผ ์ฌ์ฉํ๋ ์ผ์ด ์์๋ค. ๋น์ฐํ fetch๋ฅผ ํด์ผ ํ ์ผ์ด ์๊ธฐ์ ์ ์ฅ ๋ถ๋ถ์์๋ง ์ฝ์ด๋ฐ์ดํฐ์ ์ ๊ทผํ๋ค.
ํ์ง๋ง ์ญ์ ๋ฅผ ํ๊ธฐ ์ํด์ coredata์ entity๊ฐ ํ์ํ๋ค. Repository Scene์์ ๋ณด์ฌ์ฃผ๋ ์ ๋ณด๊ฐ coredata์์ fetchํ ๋ฐ์ดํฐ๊ธฐ ๋๋ฌธ์ ์ฝ๊ฒ ์ ๊ทผ ๊ฐ๋ฅํ๋ค.
๋ํ cell์ ์ฌ์ฌ์ฉ ๋ฌธ์ ๋๋ฌธ์ Picture List์์ CoreData์ ์ ์ฅ์ฌ๋ถ๊ฐ ์ค์ํ์๋ค.
-
์ ์ฅ ์์ ์์ ์ฝ์ด๋ฐ์ดํฐ entity๋ฅผ ๋ฐ๋ก ๊ฐ์ง๊ณ ์์ผ๋ฉด ์ด๋จ๊น? ๊ทธ๋ ๋ค๋ฉด ์ ์ฅ์ด ๋ ๋ ๋ง๋ค coredata fetch๋ฅผ ํ์ง ์์๋ ๋์ ํจ์จ์ ์ผ ๊ฒ ๊ฐ๋ค.
โ Reposity Scene์์ ์ญ์ ๊ฐ ๋๋ฉด ๋ฐ์ดํฐ์ ์ ๋ฐ์ดํธ๋ฅผ ์๊ธฐ ์ํด ์ด์ฒํผ fetch๋ฅผ ํด์์ผ ํ๋ค. ๋ฐ๋ผ์ ํ๊ธฐ.
-
fetch all์ ์๊ฐ ๋ณต์ก๋๋ O(n)์ด๋ค. ๋ฐฐ์ด์ ์ ๋ถ ๋ฐ์์จ๋ค.
๋ฌธ์ ๋ ๋ฐฐ์ด์ ํ์ํ๊ฑฐ๋ ์ญ์ ํ ๋์ด๋ค.
์ด๋ฅผ ์ํด ํด์ฌ๋ฅผ ์ฌ์ฉํ์ฌ ์ ์ฅ๋ ๋ฐ์ดํฐ๋ฅผ ์ญ์ ํ๊ฑฐ๋ ์ฐพ๋๋ก ํด๋ณด์.
ํด์ฌ์ ํ์์ ์ต์ ์ ๊ฒฝ์ฐ O(n)์ด๊ธฐ ๋๋ฌธ์ ์ค๋ ๊ฑธ๋ ค๋ดค์ O(2n)์ด๋ค.
-
ListVM๊ณผ RepoVM์ด ์๋ค.
ListVC์์ cell์ด reuse ๋ ๋, ๋ฒํผ์ select ์ฌ๋ถ๋ฅผ false๋ก ๋ฐ๊ฟ์ค๋ค.
์ฆ, ๋ชจ๋ธ์ cell์ ์ ์ฅ ์ฌ๋ถ๋ฅผ ์์์ผ ํ๋ค๋ ๊ฒ์ด๋ค.
๋ฐ๋ผ์ ์ด๋ฏธ์ง๊ฐ ์ ์ฅ๋๊ณ ์ญ์ ๋๋ ๋ชจ๋ ๊ณผ์ ์์ ๋ชจ๋ธ์ด ์ ๋ฐ์ดํธ ๋์ผ ํ๋ค.
VM์ ์ ์ฅ๊ณผ ์ญ์ ๋ฅผ ๋ด๋นํ๊ณ ๊ฐ์ง๊ณ ์๋ ๋ชจ๋ธ์ ์ ๋ฐ์ดํธ ์์ผ์ค๋ค.
์ฌ๊ธฐ๊น์ง ๋ฌธ์ ๊ฐ ์๋ค.
RepoVM์์ ์ญ์ ๊ฐ ๋์์ ๋, CoreData๋ ๊ฐ์ง๊ณ ์๋ entity๋ฅผ ์ญ์ ํ๋ค.
ListVC์์ viewWillAppear๋ฅผ ํตํด list๋ฅผ ๋ค์ ํ๋ฒ ์ ๋ฐ์ดํธ ์์ผ์ค๋ค.
-
๊ธฐ์กด ImageData์ CoreData์ ์ ์ฅ ์ฌ๋ถ๋ฅผ ์์ ์๋ ๋ชจ๋ธ์ ๋ง๋ค์๋ค.
struct SavableImageData { var imageData: ImageData var isSaved: Bool = false init(imageData: ImageData, isSaved: Bool = false) { self.imageData = imageData self.isSaved = isSaved } }
์ด ๋ฐ์ดํฐ ๋ชจ๋ธ์ ListVM์ ํตํด ListVC์ ๋ฟ๋ ค์ง๊ณ cell์ data๋ฅผ fetchํ๊ธฐ ์ ์ coreData์ ํด๋น id๊ฐ ์กด์ฌํ๋์ง ์กฐํํ์ฌ ์ ์ฅ ์ฌ๋ถ๋ฅผ ํ๋จํ๋ค.
// PictureListVM func image(at index: Int) -> SavableImageData { if CoreDataManager.shared.searchPicture(id: imageList[index].imageData.id) == nil { imageList[index].isSaved = false return imageList[index] } return imageList[index] }
- ์ ์ ์ ์ฅํ๊ณ ์ญ์ ํ ๋ ๋ง๋ค. ์ฆ collectionView๋ฅผ reload ํ ๋๋ง๋ค ์ด์ ๊ฐ์ ์๋ฌ๊ฐ ๋ฐ์ ํ๋ค.
[UICollectionView] Performing reloadData as a fallback
Invalid update: invalid number of items in section 0.
The number of items contained in an existing section after the update (8)
must be equal to the number of items contained in that section before the update (5) (...)
- ์ ๋ฐ์ดํธ ์ดํ cell์ ๊ฐ์์ ์ด์ cell์ ๊ฐ์๊ฐ ๋ค๋ฅด๋ค๊ณ ํ๋ค.
- ๋น์ฐํ๊ฑฐ ์๋๊ฐ?
viewModel.imageListUpdate = { [weak self] in
DispatchQueue.main.async {
self?.picterestCollectionView.reloadData()
}
}๋ฌธ์ ๋ ๋ฐ๋ก ์ main ์ฐ๋ ๋์๋ค.
๋๋ฌด๋ ๋น์ฐํ๊ฒ ๋ทฐ์ ์ ๋ฐ์ดํธ๋ main ์ฐ๋ ๋์์ ์งํ ํด์ผ ํ๋๊ฒ์ด์๋ค.
์๋ํ๋ฉด ์ฐ๋ฆฌ๊ฐ ๋ณดํต ImageList๋ฅผ ์ ๋ฐ์ดํธ ํ๋ ์์ ์ ๋คํธ์ํฌ ํต์ ์ด ๋๋๋ ์์ ์ด๋ค.
๊ทธ ์ฐ๋ ๋ ์์์ ํด๋น ๋ชจ๋ธ์ ์ ๋ฐ์ดํธ ํด์ฃผ๊ธฐ ๋๋ฌธ์ UI ์ ๋ฐ์ดํธ๋ฅผ ์ํด์ main ์ฐ๋ ๋์์ ์์ ์ ํ๋ค.
ํ์ง๋ง coredata์ fileManager๋ฅผ ํตํด ๋ฐ์ดํฐ๋ฅผ ์ง์์ฃผ๋ ํ์์ collectoinView์ ๋ฟ๋ ค์ค ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ง๊ณ ์๋ ImageList๋ฅผ ์ง์์ฃผ๋ ์์ ์ ์ด๋ฏธ ๋ฉ์ธ ์ฐ๋ ๋์์ ๋์๊ฐ๊ณ ์๋ค.
// ViewModel์ ์ญ์
func deleteImage(at indexPath: IndexPath) {
let item = imageList[indexPath.item]
CoreDataManager.shared.delete(entity: item)
PicterestFileManager.shared.deletePicture(fileName: item.id!)
imageList.remove(at: indexPath.row)
self.imageListUpdateAfterDelete()
}
// View์์์ reloading
viewModel.imageListUpdateAfterDelete = { [weak self] in
self?.picturesCollectionView.reloadData()
}| ์ฑ ์ง์ ๊ณผ ์ฌ์ง ๋ชฉ๋ก | ์ฌ์ง ์ ์ฅ | ๋ฆฌ์คํธ์์์ ์ฌ์ง ์ญ์ | ์ ์ฅ์์์์ ์ญ์ | ํ๋ฉด์์ ์ฌ๋ผ์ง ์ฌ์ง ์ญ์ |
|---|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
![]() |








