Skip to content

UngQ/NuriRock

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

35 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

๐Ÿ‡ฐ๐Ÿ‡ทย NuriROCK(ๆจ‚) - ํ•œ๊ตญ์„ ์ฆ๊ธฐ์ž!

  • ํ”„๋กœ์ ํŠธ ๊ธฐ๊ฐ„: 2024. 03. 08. ~ 2024. 03. 24.
  • App Store ์ถœ์‹œ์ž‘ (๋งํฌ)

nurirock

๐Ÿ—’๏ธย Introduction

  • ํ•œ๊ตญ์˜ ์ฃผ์š” ๋„์‹œ๋“ค์˜ ๊ด€๊ด‘์ง€, ๋ง›์ง‘ ๋“ฑ์„ ์‰ฝ๊ฒŒ ๊ฒ€์ƒ‰ํ•˜๊ณ  ๋ถ๋งˆํฌ์— ์ €์žฅํ•˜์—ฌ ์—ฌํ–‰์— ๋„์›€์„ ์ค„ ์ˆ˜ ์žˆ๋Š” ์–ดํ”Œ
  • Configuration: iOS 16.0+
  • ๋‹ค๊ตญ์–ด(ํ•œ๊ตญ์–ด, ์˜์–ด) ์ ์šฉ
  • ๋‹คํฌ๋ชจ๋“œ ์ง€์›

๐Ÿ—’๏ธย Features

  • ํ•œ๊ตญ ์ฃผ์š” ๋„์‹œ๋ณ„, ๊ด€๊ด‘ ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ
  • ์›ํ•˜๋Š” ๋‚ ์งœ์— ์ง„ํ–‰ํ•˜๋Š” ์ถ•์ œ, ๊ณต์—ฐ, ํ–‰์‚ฌ ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ
  • ํ‚ค์›Œ๋“œ๋กœ ๊ด€๊ด‘์ง€ ๊ฒ€์ƒ‰ ๋ฐ ๊ธฐ๋ก ์ €์žฅ ๊ธฐ๋Šฅ
  • ๊ด€๊ด‘์ง€ ๋ถ๋งˆํฌ ์ €์žฅ ๊ธฐ๋Šฅ
  • ๊ด€๊ด‘์ง€ ๋””ํ…Œ์ผ ์ •๋ณด ํ™•์ธ ๊ธฐ๋Šฅ
  • ์ž์‹ ์˜ ์œ„์น˜์™€ ์„ ํƒํ•œ ๊ด€๊ด‘์ง€์˜ ๊ฑฐ๋ฆฌ ํ™•์ธ ๊ธฐ๋Šฅ

๐Ÿ—’๏ธย Technology Stack

  • Framework

    • Code Base UIKit
  • Pattern

    • MVVM
      • UI์™€ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ๋ถ„๋ฆฌํ•˜์—ฌ ์œ ์ง€๋ณด์ˆ˜์™€ ํ…Œ์ŠคํŠธ๊ฐ€ ์šฉ์ดํ•˜๋„๋ก ํ•˜๊ธฐ ์œ„ํ•ด MVVM ํŒจํ„ด์„ ์‚ฌ์šฉ
  • Library

    • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค

      • Realm
        • ๋ฐ์ดํ„ฐ์™€ UI๊ฐ„์˜ ๋™๊ธฐํ™” ๋ฐ ์•ฑ์˜ ์‘๋‹ต ์†๋„๋ฅผ ํ–ฅ์ƒ์‹œํ‚ค๋ฅผ ์œ„ํ•ด ์‚ฌ์šฉ
    • ๋ฐฑ์—”๋“œ ์„œ๋น„์Šค

      • Firebase
        • ๋ฉ”์‹œ์ง• ๋ฐ ์˜ค๋ฅ˜ ๋ณด๊ณ ๋ฅผ ์ˆ˜์ง‘ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ
    • ๋„คํŠธ์›Œํฌ

      • Alamofire
        • ๋„คํŠธ์›Œํฌ ์š”์ฒญ์„ ๊ฐ„ํŽธํ•˜๊ณ  ํšจ์œจ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ
    • ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ

      • Kingfisher
        • ์ด๋ฏธ์ง€๋ฅผ ๋‹ค์šด๋กœ๋“œํ•˜๊ณ  ์บ์‹ฑํ•˜๋Š” ์ž‘์—…์„ ๊ฐ„ํŽธํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ
    • UI ๊ตฌ์„ฑ

      • Tabman
        • ์ง๊ด€์ ์ด๊ณ  ์‚ฌ์šฉํ•˜๊ธฐ ์‰ฌ์šด ํƒญ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ œ๊ณตํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ
      • Snapkit
        • ์˜คํ†  ๋ ˆ์ด์•„์›ƒ์„ ์ฝ”๋“œ๋กœ ์‰ฝ๊ฒŒ ์ž‘์„ฑํ•˜๊ณ  ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ
      • Toast
        • ์‚ฌ์šฉ์ž์—๊ฒŒ ๊ฐ„๋‹จํ•œ ํŒ์—… ๋ฉ”์‹œ์ง€๋ฅผ ํ‘œ์‹œํ•˜์—ฌ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ง๊ด€์ ์œผ๋กœ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ
      • SVProgressHUD
        • ๋” ์ง๊ด€์ ์œผ๋กœ ๋กœ๋”ฉ์— ๋Œ€ํ•œ HUD ํ‘œ์‹œ๋ฅผ ์œ„ํ•ด ์‚ฌ์šฉ
      • FSCalendar
        • Customize๊ฐ€ ์šฉ์ดํ•œ ์บ˜๋ฆฐ๋”๋ฅผ ์œ„ํ•ด ์‚ฌ์šฉ

๐Ÿ’ฌ Description

1. Optimistic UI ์ ์šฉ

Simulator Screen Recording - iPhone 15 Pro - 2024-03-30 at 11 30 43 !Simulator Screen Recording - iPhone 15 Pro - 2024-03-31 at 17 20 56
์ ์šฉ ์ „ ์ ์šฉ ํ›„
  • ์‚ฌ์šฉ์ž ์ž…์žฅ์„ ๊ณ ๋ คํ•˜์—ฌ ๋ถ๋งˆํฌ์‹œ Incidator๋ฅผ ๊ตฌํ˜„ํ•˜์˜€์ง€๋งŒ, ์˜คํžˆ๋ ค ๋„ˆ๋ฌด ์žฆ์€ Indicator ๋‚จ์šฉ์œผ๋กœ ์ธํ•ด ์‚ฌ์šฉ์ž์—๊ฒŒ ์ŠคํŠธ๋ ˆ์Šค๋ฅผ ์ค„ ๊ฒƒ

  • ๊ฒŒ๋‹ค๊ฐ€ ์ด๋ฒˆ ํ”„๋กœ์ ํŠธ์— ์‚ฌ์šฉํ•œ ํ•œ๊ตญ๊ด€๊ด‘๊ณต์‚ฌ API ๋Š” ๊ฐ€๋” ์„œ๋ฒ„ํ†ต์‹ ์ด ์ž˜ ์•ˆ๋ ๋•Œ๊ฐ€ ์žˆ์–ด์„œ ์žฆ์€ Indicator ๋Š” ์‚ฌ์šฉ์ž ์ž…์žฅ์—์„œ ๋” ๋‹ต๋‹ตํ•จ

  • ๊ด€๋ จ ๊ฐœ์ธ ํฌ์ŠคํŒ…

    ์ฃผ์š”์ฝ”๋“œ
    @objc private func bookmarkButtonClickedInBottomCV(\_ sender: UIButton) {
    
      guard let data = viewModel.outputFestivalData.value?.response.body.items?.item?[sender.tag] else {
          return }
      // ๋ถ๋งˆํฌ ์ƒํƒœ ํ™•์ธ
      let isBookmarked = viewModel.repository.isBookmarked(contentId: data.contentid)
    
      // Optimistic UI ์—…๋ฐ์ดํŠธ
      sender.setImage(UIImage(systemName: isBookmarked ? "bookmark" : "bookmark.fill"), for: .normal)
    
      // ๋ถ๋งˆํฌ ์ƒํƒœ์— ๋”ฐ๋ผ ๋ถ๋งˆํฌ ์ถ”๊ฐ€ ๋˜๋Š” ์‚ญ์ œ
      if isBookmarked {
          viewModel.repository.deleteBookmark(data: data)
          // ์‚ญ์ œ ํ›„ UI ์—…๋ฐ์ดํŠธ ํ•„์š” ์—†์Œ
      } else {
          viewModel.repository.addBookmark(id: data.contentid) { success in
              DispatchQueue.main.async {
                  if !success {
                      // ์š”์ฒญ ์‹คํŒจ ์‹œ UI ๋˜๋Œ๋ฆฌ๊ธฐ
                      sender.setImage(UIImage(systemName: "bookmark"), for: .normal)
                      // ์‹คํŒจ ํ”ผ๋“œ๋ฐฑ ์ œ๊ณต
                  }
              }
          }
      }

2. accessibilityIdentifier์„ ํ™œ์šฉํ•œ DiffableDatasource + Realm ๊ตฌํ˜„

  • ๋ถ๋งˆํฌ ์‚ญ์ œ์‹œ Button.tag๋“ค์€ ๊ธฐ์กด tag ๊ฐ’๋“ค์„ ๊ทธ๋Œ€๋กœ ๊ฐ€์ง€๊ณ  ์žˆ์–ด์„œ Item๋“ค์˜ row ๊ฐ’๊ณผ tag ๊ฐ’์˜ ๋ถˆ์ผ์น˜ ๋ฐœ์ƒ

  • indexPath ๊ธฐ๋ฐ˜์ด ์•„๋‹Œ, identifier์„ ์ด์šฉํ•˜์—ฌ ์…€ ์‚ญ์ œ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๋‹ˆ, tag ๋ถˆ์ผ์น˜, ์• ๋‹ˆ๋ฉ”์ด์…˜ ํšจ๊ณผ ๋“ฑ ๋ชจ๋“  ๋ฌธ์ œ์ ์ด ํ•ด๊ฒฐ ๋˜์—ˆ์œผ๋ฉฐ DiffableDatasource์— ๋” ํšจ์œจ์ 

    ์ฃผ์š”์ฝ”๋“œ
      //๊ธฐ์กด ์ฝ”๋“œ
      private func configureDataSource() {
          let cellRegistration = UICollectionView.CellRegistration<ResultCollectionViewCell, Bookmark> { (cell, indexPath, identifier) in
              cell.updateUIInBookmarkVC(identifier)
    
              cell.bookmarkButton.tag = indexPath.item
              cell.bookmarkButton.addTarget(self, action: #selector(self.bookmarkButtonClicked), for: .touchUpInside)
              cell.bookmarkButton.setImage(UIImage(systemName: "bookmark.fill"), for: .normal)
    
              ...
      }
    
      @objc private func bookmarkButtonClicked(_ sender: UIButton) {
          viewModel.repository.deleteBookmarkInBookmarkView(data: Array(viewModel.outputBookmarks.value ?? [])[sender.tag])
    
      }
    
      private func updateSnapshot() {
          var snapshot = NSDiffableDataSourceSnapshot<Section, Bookmark>()
    
          snapshot.appendSections([.main])
    
          let bookmarks = viewModel.outputBookmarks.value ?? []
    
          snapshot.appendItems(bookmarks, toSection: .main)
    
          dataSource.apply(snapshot, animatingDifferences: true) //reloadData
          self.updateMapView(with: bookmarks)
    
          DispatchQueue.main.asyncAfter(deadline: .now() + 0.3){
              self.bookmarkCollectionView.reloadData()
          }
      }
    
      //๋ณ€๊ฒฝ ์ฝ”๋“œ
      cell.bookmarkButton.accessibilityIdentifier = identifier.contentid

3. ํ•œ๊ตญ๊ด€๊ด‘๊ณต์‚ฌ API ์„œ๋ฒ„ ์ƒํƒœ ๋ถˆ์•ˆ์ •์œผ๋กœ ์ธํ•œ ๋„คํŠธ์›Œํฌ ํ†ต์‹  ์‹คํŒจ์‹œ, ์žฌํ˜ธ์ถœ ๊ตฌํ˜„

  • ์„œ๋ฒ„๊ฐ€ ๋ถˆ์•ˆ์ •ํ•˜์—ฌ ์‹œ๊ฐ„๋Œ€์— ๋”ฐ๋ผ, ๊ฐ™์€ Parameter๋กœ ํ˜ธ์ถœ์„ ํ•˜์—ฌ๋„ ๋„คํŠธ์›Œํฌ ํ†ต์‹ ์ด ์‹คํŒจํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žฆ์Œ (ํ‰๊ท  ์ƒˆ๋ฒฝ์—๋Š” 3๋ฒˆ ํ˜ธ์ถœ์‹œ 1๋ฒˆ ์‹คํŒจ, ์˜คํ›„์—๋Š” 100๋ฒˆ ํ˜ธ์ถœ์‹œ 1๋ฒˆ ์‹คํŒจ)

  • Errorํ™•์ธ ๊ฒฐ๊ณผ, Routing Error ๋กœ ํ™•์ธ๋˜์—ˆ์œผ๋ฉฐ ์ œ๊ฐ€ ์•„๋‹Œ Server์—์„œ ํ•ด๊ฒฐํ•ด์ค˜์•ผ ํ•˜๋Š” ๋ฌธ์ œ (ํ•œ๊ตญ๊ด€๊ด‘๊ณต์‚ฌ API๋ฅผ ์ œ๊ณตํ•ด์ฃผ๋Š” ๊ณต๊ณต๋ฐ์ดํ„ฐํฌํ„ธ์— ๋ฌธ์˜ํ•ด๋ณด์•˜์ง€๋งŒ, ์ด ๋ฌธ์ œ๋ฅผ ์ธ์ง€ํ•˜๊ณ  ์žˆ์œผ๋‚˜ ๋‹น์žฅ์€ ํ•ด๊ฒฐํ•˜์ง€ ๋ชป ํ•œ๋‹ค๋Š” ๋‹ต๋ณ€)

  • ํ˜ธ์ถœ์‹œ, ์žฌ์‹œ๋„ ํšŸ์ˆ˜๋ฅผ ์„ค์ • (retryCount)

  • ์‹คํŒจํ•  ๊ฒฝ์šฐ, 3์ดˆ์˜ ๊ฐ„๊ฒฉ์„ ๋‘๊ณ  retryCount๋ฅผ 1์”ฉ ์ฐจ๊ฐํ•˜๋ฉฐ ์žฌํ˜ธ์ถœํ•˜๊ฒŒ ๊ตฌํ˜„

  • retryCount๋ฅผ ๋ชจ๋‘ ์ฐจ๊ฐํ•˜๊ณ ๋„ ์‹คํŒจํ•  ๊ฒฝ์šฐ๋Š” ์„œ๋ฒ„๊ฐ€ ์ž‘๋™์ด ์•ˆ๋˜๋Š” ์‹œ์ ์œผ๋กœ ๊ฐ„์ฃผํ•˜์—ฌ, completionHandler๋ฅผ ํ™œ์šฉํ•˜์—ฌ View์—์„œ ๋‚˜์ค‘์— ์žฌ์‹œ๋„ ํ•˜๋ผ๋Š” Alert์„ ๋„์šฐ๋„๋ก ๊ตฌํ˜„

    ์ฃผ์š”์ฝ”๋“œ
    	func request<T: Decodable>(type: T.Type, api: API, retryCount: Int = 2, completionHandler: @escaping (T?, AFError?) -> Void) {
    
      	session.request(api.endPoint,
      				 method: api.method,
      				 parameters: api.parameter,
      				 encoding: api.encoding).responseDecodable(of: T.self) { response in
      		switch response.result {
      		case .success(let success):
      			print("๋„คํŠธ์›Œํฌ ํ†ต์‹  ์„ฑ๊ณต!")
      			completionHandler(success, nil)
      		case .failure(let failure):
      			print("์—๋Ÿฌ")
      			if retryCount > 0 {
    
      				DispatchQueue.main.asyncAfter(deadline: .now()) {
      					self.request(type: type, api: api, retryCount: retryCount - 1, completionHandler: completionHandler)
      					print(retryCount)
      				}
      			} else {
      				print("์—ฌ๊ธฐ๋กœ์˜ค๋‚˜")
      				completionHandler(nil, failure)
      			}
    
      		}
      	}
      }

๐ŸŽฎย ์ฃผ์š”๊ธฐ๋Šฅ UI

nurirock-launchscreen nurirock-citychange nurirock-contentchange nurirock-event
animate ํ™œ์šฉํ•œ ๋Ÿฐ์น˜์Šคํฌ๋ฆฐ ๋„์‹œ๋ณ„ ๊ฒ€์ƒ‰ ์ปจํ…์ธ ๋ณ„ ๊ฒ€์ƒ‰ ๋‚ ์งœ๋ณ„ ํ–‰์‚ฌ ๊ฒ€์ƒ‰
nurirock-keyword nurirock-bookmark nurirock-detail
ํ‚ค์›Œ๋“œ ๊ฒ€์ƒ‰ ๋ถ๋งˆํฌ ์ €์žฅ ์„ธ๋ถ€์‚ฌํ•ญ ์กฐํšŒ

๐Ÿ“ธ ํ”„๋กœ์ ํŠธ ๋ฐœํ‘œ ๊ธฐ๋…์‚ฌ์ง„

20240402_123148_1

About

๐Ÿ‡ฐ๐Ÿ‡ท NuriROCK(ๆจ‚) - ํ•œ๊ตญ์„ ์ฆ๊ธฐ์ž!

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages