Skip to content
ย 
ย 

Latest commit

ย 

History

117 Commits

Folders and files

NameName
Last commit message
Last commit date
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ ํŒ€์› ์†Œ๊ฐœ

UY


๐Ÿ–ฅ ํ”„๋กœ์ ํŠธ ์†Œ๊ฐœ

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„ค๋ช…

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


โฑ๏ธ ๊ฐœ๋ฐœ ๊ธฐ๊ฐ„ ๋ฐ ์‚ฌ์šฉ ๊ธฐ์ˆ 

  • ๊ฐœ๋ฐœ ๊ธฐ๊ฐ„: 2022.07.25 ~ 2022.07.28 (4์ผ)
  • ์‚ฌ์šฉ ๊ธฐ์ˆ : UIKit, MVVM, URLSession, Coredata, NSCache, FileManager, UICollectionViewLayout


๐Ÿ–ผ ๋””์ž์ธ ํŒจํ„ด๊ณผ ์„ค๊ณ„

MVVM

แ„†แ…ฎแ„Œแ…ฆ drawio



โš ๏ธ ํŠธ๋Ÿฌ๋ธ” ์ŠˆํŒ…

  • Repository collectionView์—์„œ ์…€ ์‚ญ์ œ์‹œ ๋ฌธ์ œ

    • 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)
    }


  • CollectionView Cell์˜ ContextMenu์™€์˜ ์‹ธ์›€

cell์˜ ์‚ญ์ œ์‹œ contextMenu๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค. ์ด ๊ณผ์ •์—์„œ ๋‚ด๊ฐ€ ์ง€์ •ํ•ด์ค€ cornerRadius์™€ ์‹œ์Šคํ…œ์—์„œ ์ œ๊ณต๋˜๋Š” UITargetPreview์˜ cornerRadius๊ฐ€ ๋งž์ง€ ์•Š์•˜๋‹ค.
์•„๋ž˜์™€ ๊ฐ™์ด preview์˜ cornerRadius ํฐ ๋ถ€๋ถ„์ด cell์˜ ํฌ๊ธฐ๋ณด๋‹ค ์ปค์„œ ๋งค์šฐ ๊ฑฐ์Šฌ๋ฆฌ๋Š” ์†ํ†ฑ ๊ฑฐ์Šค๋Ÿฌ๋ฏธ ๊ฐ™์€ UI๊ฐ€ ์™„์„ฑ ๋˜์—ˆ๋‹ค.

แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2022-07-27 แ„‹แ…ฉแ„’แ…ฎ 5 18 22

๋ถ„๋ช… 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)
    }

image



  • Picture list์—์„œ์˜ ์‚ญ์ œ ๊ธฐ๋Šฅ

์ด๋ฏธ์ง€๋“ค์˜ ๋ฆฌ์ŠคํŠธ์—์„œ ๋ณ„ํ‘œ ๋ชจ์–‘์˜ ์ €์žฅ ๋ฒ„ํŠผ์„ ๋ˆŒ๋ €๋‹ค ๋‹ค์‹œ ํ•œ๋ฒˆ ๋ˆ„๋ฅด๋ฉด ์‚ญ์ œ ๋˜๋Š” ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•˜๋ ค ํ–ˆ๋‹ค.

๊ธฐ์กด Picture List์—์„  ์ €์žฅ ๊ธฐ๋Šฅ ์™ธ์— coredata๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ผ์ด ์—†์—ˆ๋‹ค. ๋‹น์—ฐํžˆ fetch๋ฅผ ํ•ด์•ผ ํ•  ์ผ์ด ์—†๊ธฐ์— ์ €์žฅ ๋ถ€๋ถ„์—์„œ๋งŒ ์ฝ”์–ด๋ฐ์ดํ„ฐ์— ์ ‘๊ทผํ•œ๋‹ค.

ํ•˜์ง€๋งŒ ์‚ญ์ œ๋ฅผ ํ•˜๊ธฐ ์œ„ํ•ด์„  coredata์˜ entity๊ฐ€ ํ•„์š”ํ•˜๋‹ค. Repository Scene์—์„  ๋ณด์—ฌ์ฃผ๋Š” ์ •๋ณด๊ฐ€ coredata์—์„œ fetchํ•œ ๋ฐ์ดํ„ฐ๊ธฐ ๋•Œ๋ฌธ์— ์‰ฝ๊ฒŒ ์ ‘๊ทผ ๊ฐ€๋Šฅํ–ˆ๋‹ค.

๋˜ํ•œ cell์˜ ์žฌ์‚ฌ์šฉ ๋ฌธ์ œ ๋•Œ๋ฌธ์— Picture List์—์„  CoreData์— ์ €์žฅ์—ฌ๋ถ€๊ฐ€ ์ค‘์š”ํ•˜์˜€๋‹ค.

  • ์ €์žฅํ•œ ์ฝ”์–ด๋ฐ์ดํ„ฐ๋ฅผ ๋”ฐ๋กœ ๊ฐ€์ง€๊ณ  ์žˆ๊ธฐ?

    ์ €์žฅ ์‹œ์ ์—์„œ ์ฝ”์–ด๋ฐ์ดํ„ฐ entity๋ฅผ ๋”ฐ๋กœ ๊ฐ€์ง€๊ณ  ์žˆ์œผ๋ฉด ์–ด๋–จ๊นŒ? ๊ทธ๋ ‡๋‹ค๋ฉด ์ €์žฅ์ด ๋  ๋•Œ ๋งˆ๋‹ค coredata fetch๋ฅผ ํ•˜์ง€ ์•Š์•„๋„ ๋˜์„œ ํšจ์œจ์ ์ผ ๊ฒƒ ๊ฐ™๋‹ค.

    โ‡’ Reposity Scene์—์„œ ์‚ญ์ œ๊ฐ€ ๋˜๋ฉด ๋ฐ์ดํ„ฐ์˜ ์—…๋ฐ์ดํŠธ๋ฅผ ์•Œ๊ธฐ ์œ„ํ•ด ์–ด์ฒ˜ํ”ผ fetch๋ฅผ ํ•ด์™€์•ผ ํ•œ๋‹ค. ๋”ฐ๋ผ์„œ ํ๊ธฐ.

  • ๊ฒฐ๊ตญ fetch all

    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]
    }


Invalid update ์—๋Ÿฌ

  • ์…€์„ ์ €์žฅํ•˜๊ณ  ์‚ญ์ œ ํ• ๋•Œ ๋งˆ๋‹ค. ์ฆ‰ 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()
        }


๐Ÿ“ฑ์‹คํ–‰ ํ™”๋ฉด

์•ฑ ์ง„์ž…๊ณผ ์‚ฌ์ง„ ๋ชฉ๋ก ์‚ฌ์ง„ ์ €์žฅ ๋ฆฌ์ŠคํŠธ์—์„œ์˜ ์‚ฌ์ง„ ์‚ญ์ œ ์ €์žฅ์†Œ์—์„œ์˜ ์‚ญ์ œ ํ™”๋ฉด์—์„œ ์‚ฌ๋ผ์ง„ ์‚ฌ์ง„ ์‚ญ์ œ
Simulator Screen Recording - iPhone 12 Pro - 2022-07-28 at 16 23 52 Simulator Screen Recording - iPhone 12 Pro - 2022-07-28 at 16 25 25 Simulator Screen Recording - iPhone 12 Pro - 2022-07-28 at 16 37 33 Simulator Screen Recording - iPhone 12 Pro - 2022-07-28 at 16 26 06 Simulator Screen Recording - iPhone 12 Pro - 2022-07-28 at 16 31 15

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages