Skip to content

wjdtjq6/Pictar

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

21 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

๐Ÿž๏ธ Pictar (ํ”ฝ์ฐจ)

Unsplash API ๊ธฐ๋ฐ˜์˜ ๊ณ ํ•ด์ƒ๋„ ์‚ฌ์ง„ ๊ฒ€์ƒ‰ & ์ปฌ๋ ‰์…˜ ์•ฑ


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

๊ฐœ๋ฐœ ๊ธฐ๊ฐ„: 2024.7.22 ~ 2024.7.28
๊ฐœ๋ฐœ ์ธ์›: 1์ธ (๊ธฐํš/๋””์ž์ธ/๊ฐœ๋ฐœ)


๐Ÿ›  ๊ธฐ์ˆ  ์Šคํƒ

iOS

  • Language: Swift 5.10
  • Framework: UIKit
  • Minimum Target: iOS 15.0

์•„ํ‚คํ…์ฒ˜ & ๋””์ž์ธ ํŒจํ„ด

  • Architecture: MVC
  • Design Pattern: Singleton

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค & ๋„คํŠธ์›Œํ‚น

  • Local Storage: Realm + FileManager
  • Network: Alamofire
  • Image Caching: Kingfisher

์™ธ๋ถ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

  • UI/Layout: SnapKit, Then
  • Utility: Toast

๐Ÿ“‹ ์ฃผ์š” ๊ธฐ๋Šฅ

์‹ค์‹œ๊ฐ„ ์ด๋ฏธ์ง€ ๊ฒ€์ƒ‰ ์‹œ์Šคํ…œ

  • ์ตœ์‹ ์ˆœ/๊ด€๋ จ์ˆœ ์ •๋ ฌ ๊ธฐ๋Šฅ
  • 12๊ฐ€์ง€ ์ปฌ๋Ÿฌ ํ•„ํ„ฐ๋ง (ํ‘๋ฐฑ/์ปฌ๋Ÿฌ)
  • Unsplash API ๊ธฐ๋ฐ˜ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ํ‘œ์‹œ

์˜คํ”„์…‹ ๊ธฐ๋ฐ˜ ํŽ˜์ด์ง€๋„ค์ด์…˜

  • ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ ๊ธฐ๋ฐ˜ ๋ฐ์ดํ„ฐ ์š”์ฒญ ๊ด€๋ฆฌ
  • ์Šคํฌ๋กค ๊ธฐ๋ฐ˜ ๋‹ค์Œ ํŽ˜์ด์ง€ ์ž๋™ ๋กœ๋”ฉ

์ข‹์•„์š” ๊ธฐ๋Šฅ๊ณผ ์ปฌ๋ ‰์…˜ ๊ด€๋ฆฌ

  • Realm๊ณผ FileManager ์—ฐ๋™ ์ €์žฅ ์‹œ์Šคํ…œ
  • ์ตœ์‹ ์ˆœ/๊ณผ๊ฑฐ์ˆœ ์ปฌ๋ ‰์…˜ ์ •๋ ฌ

MBTI ๊ธฐ๋ฐ˜ ํ”„๋กœํ•„ ์‹œ์Šคํ…œ

  • 16๊ฐ€์ง€ MBTI ์œ ํ˜• ์„ ํƒ ๊ธฐ๋Šฅ
  • ๋‹‰๋„ค์ž„ ์œ ํšจ์„ฑ ๊ฒ€์ฆ ์‹œ์Šคํ…œ

์‚ฌ์ง„ ์ƒ์„ธ ์ •๋ณด ์ œ๊ณต

  • ์กฐํšŒ์ˆ˜, ๋‹ค์šด๋กœ๋“œ ์ˆ˜ ํ‘œ์‹œ
  • ์—…๋กœ๋“œ ๋‚ ์งœ, ์ด๋ฏธ์ง€ ํฌ๊ธฐ ์ •๋ณด
  • ์ž‘๊ฐ€ ํ”„๋กœํ•„ ์ •๋ณด ์—ฐ๋™

๐Ÿ”ง ํŠธ๋Ÿฌ๋ธ” ์ŠˆํŒ…

1. ์˜คํ”„์…‹ ๊ธฐ๋ฐ˜ ํŽ˜์ด์ง€๋„ค์ด์…˜ ๋ฐ์ดํ„ฐ ๊ด€๋ฆฌ

๋ฌธ์ œ ์ƒํ™ฉ

  • ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ ๊ด€๋ฆฌ ๋ฐ ๋ฐ์ดํ„ฐ ์ค‘๋ณต ๋ฌธ์ œ
  • ์ •๋ ฌ ์˜ต์…˜ ๋ณ€๊ฒฝ ์‹œ ํŽ˜์ด์ง€ ์ดˆ๊ธฐํ™” ํ•„์š”
  • ์Šคํฌ๋กค ์‹œ ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ ์‹œ์  ์ œ์–ด ํ•„์š”

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

extension SearchViewController: UICollectionViewDataSourcePrefetching {
    func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) {
        for i in indexPaths {
            if i.row == searchList.results.count-1 && page < searchList.total_pages {
                page += 1
                toggleCall()
            }
        }
    }
}

func toggleCall() {
    if toggleButton.isSelected {
        callAPI(query: query, order_by: "latest")
    } else {
        callAPI(query: query, order_by: "relevant")
    }
}
  • prefetchItemsAt์„ ํ†ตํ•œ ๋‹ค์Œ ํŽ˜์ด์ง€ ๋กœ๋“œ ์‹œ์  ์ œ์–ด
  • page ๋ณ€์ˆ˜๋กœ ์ค‘๋ณต ํ˜ธ์ถœ ๋ฐฉ์ง€
  • ์ •๋ ฌ ๋ณ€๊ฒฝ ์‹œ page ์ดˆ๊ธฐํ™”๋กœ ๋ฐ์ดํ„ฐ ์ •ํ•ฉ์„ฑ ์œ ์ง€

2. ์ด๋ฏธ์ง€ ์ €์žฅ ๋ฐ ๊ด€๋ฆฌ

๋ฌธ์ œ ์ƒํ™ฉ

  • ๋Œ€์šฉ๋Ÿ‰ ์ด๋ฏธ์ง€ ํŒŒ์ผ์˜ ํšจ์œจ์  ๊ด€๋ฆฌ ํ•„์š”
  • Realm๊ณผ FileManager ๊ฐ„ ๋ฐ์ดํ„ฐ ๋™๊ธฐํ™” ์ด์Šˆ
  • ์ด๋ฏธ์ง€ ์‚ญ์ œ ์‹œ ์ €์žฅ์†Œ ์ •๋ฆฌ ๋ฌธ์ œ

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

func saveImageToDocument(image: UIImage, filename: String) {
    guard let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
    let fileURL = documentDirectory.appendingPathComponent("\(filename).jpg")
    guard let data = image.jpegData(compressionQuality: 0.5) else { return }
    do {
        try data.write(to: fileURL)
    } catch {
        print("file save error", error)
    }
}

func removeImageFromDocument(filename: String) {
    guard let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
    let fileURL = documentDirectory.appendingPathComponent("\(filename).jpg")
    if FileManager.default.fileExists(atPath: fileURL.path) {
        do {
            try FileManager.default.removeItem(at: fileURL)
        } catch {
            print("file remove error", error)
        }
    }
}
  • ์ด๋ฏธ์ง€ ์••์ถ•์„ ํ†ตํ•œ ์ €์žฅ์†Œ ๊ณต๊ฐ„ ์ตœ์ ํ™”
  • ํŒŒ์ผ ์กด์žฌ ์—ฌ๋ถ€ ํ™•์ธ์œผ๋กœ ์•ˆ์ •์„ฑ ํ™•๋ณด
  • ์—๋Ÿฌ ์ฒ˜๋ฆฌ๋ฅผ ํ†ตํ•œ ์˜ˆ์™ธ ์ƒํ™ฉ ๊ด€๋ฆฌ

3. ์ข‹์•„์š” ๊ธฐ๋Šฅ ๊ตฌํ˜„

๋ฌธ์ œ ์ƒํ™ฉ

  • ์ข‹์•„์š” ์ƒํƒœ ๋ณ€๊ฒฝ ์‹œ UI ์—…๋ฐ์ดํŠธ ์ง€์—ฐ
  • ์ด๋ฏธ์ง€ ์ €์žฅ๊ณผ Realm ๋ฐ์ดํ„ฐ ๋™๊ธฐํ™” ํ•„์š”
  • ๋‹ค์ค‘ ํ™”๋ฉด์—์„œ์˜ ์ƒํƒœ ๊ด€๋ฆฌ ์ด์Šˆ

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

@objc func likeButtonPressed(sender: UIButton) {
    let index = searchList.results[sender.tag]
    let imageID = index.id

    if let existingLike = realmList.first(where: { $0.id == imageID }) {
        try! realm.write {
            existingLike.isLike.toggle()
            if !existingLike.isLike {
                removeImageFromDocument(filename: imageID)
                removeImageFromDocument(filename: imageID + "_user")
            }
            sender.isSelected = existingLike.isLike
        }
        self.view.makeToast("์ข‹์•„์š” ๋ชฉ๋ก์—์„œ ์ œ๊ฑฐ๋์–ด์š”!")
    } else {
        // ์ƒˆ๋กœ์šด ์ข‹์•„์š” ํ•ญ๋ชฉ ์ถ”๊ฐ€
        let newLikeData = LikeList(
            id: imageID,
            date: Date(),
            // ...์ดˆ๊ธฐํ™”
        )
        try! realm.write {
            realm.add(newLikeData)
        }
        self.view.makeToast("์ข‹์•„์š” ๋ชฉ๋ก์— ์ถ”๊ฐ€๋์–ด์š”!")
    }
}
  • Realm ํŠธ๋žœ์žญ์…˜ ๋‚ด์—์„œ ์ƒํƒœ ๋ณ€๊ฒฝ ์ฒ˜๋ฆฌ
  • FileManager ์—ฐ๋™์œผ๋กœ ์ด๋ฏธ์ง€ ์ž๋™ ๊ด€๋ฆฌ
  • Toast ๋ฉ”์‹œ์ง€๋กœ ์ฆ‰๊ฐ์ ์ธ ์‚ฌ์šฉ์ž ํ”ผ๋“œ๋ฐฑ ์ œ๊ณต

๐Ÿ“ ํšŒ๊ณ 

Keep (์œ ์ง€ํ•  ์ )

  • FileManager์™€ Realm์„ ์—ฐ๋™ํ•œ ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ์ €์žฅ์†Œ ์„ค๊ณ„
  • UICollectionViewDataSourcePrefetching์„ ํ™œ์šฉํ•œ ํšจ์œจ์ ์ธ ํŽ˜์ด์ง€๋„ค์ด์…˜

Problem (๊ฐœ์„ ํ•  ์ )

  • ๋‹ค์ค‘ ํ•„ํ„ฐ ์ ์šฉ ์‹œ ๊ฒ€์ƒ‰ ์„ฑ๋Šฅ ์ตœ์ ํ™”
  • ์ข‹์•„์š” ์ƒํƒœ ๋ณ€๊ฒฝ ์‹œ ๋™์‹œ์„ฑ ์ฒ˜๋ฆฌ ๊ฐœ์„ 

Try (์‹œ๋„ํ•  ์ )

  • ์ด๋ฏธ์ง€ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์บ์‹ฑ ์ „๋žต ์žฌ์„ค๊ณ„
  • ์ปฌ๋Ÿฌ ๊ธฐ๋ฐ˜ ์ด๋ฏธ์ง€ ๋ถ„๋ฅ˜ ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๊ฐœ์„ 
  • FileManager ์ €์žฅ์†Œ ๊ด€๋ฆฌ ์ž๋™ํ™”

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages