Skip to content

Repository files navigation

iOS 16.0 Swift 5.10 Xcode 15.3

์ˆ˜๋‹ค์ผ๊ธฐ

myAppIcon

App Store

ํ”„๋กœ์ ํŠธ ์†Œ๊ฐœ

์ˆ˜๋‹ค์ผ๊ธฐ๋Š” ์Œ์„ฑ์œผ๋กœ ์ผ๊ธฐ๋ฅผ ์ž‘์„ฑํ•˜๊ณ  ๊ฐ์ •์„ ๋ถ„์„ํ•ด์ฃผ๋Š” ์Œ์„ฑ ์ผ๊ธฐ ์•ฑ์ž…๋‹ˆ๋‹ค. ์ž์‹ ์˜ ๋ชฉ์†Œ๋ฆฌ๋กœ ์ผ๊ธฐ๋ฅผ ๊ธฐ๋กํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ž๋™์œผ๋กœ ๊ฐ์ • ๋ถ„์„์„ ํ†ตํ•ด ๋‹ค์–‘ํ•œ ๊ฐ์ •์„ ์‹œ๊ฐ์ ์œผ๋กœ ํ‘œํ˜„ํ•ฉ๋‹ˆ๋‹ค.

  • ์ง„ํ–‰ ๊ธฐ๊ฐ„
    • ๊ธฐํš : 2024.09.11 ~ 2024.09.14
    • ๊ฐœ๋ฐœ : 2024.09.15 ~ 2024.10.01
    • ์ถœ์‹œ : 2024.10.02
  • ๊ธฐ์ˆ  ์Šคํƒ
    • ๊ฐœ๋ฐœ ํ™˜๊ฒฝ
      • iOS : Swift 5.10, Xcode 15.3
      • ์„œ๋ฒ„ : GPT API ์‚ฌ์šฉ
    • ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
      • iOS : SwiftUI, FSCalendar, Kingfisher, Realm, SnapKit, AVFoundation, URLSession
    • Deployment Target : iOS 16.0

์ฃผ์š” ํ‚ค์›Œ๋“œ

  • Realm: ๋กœ์ปฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋กœ ์ผ๊ธฐ์™€ ๊ฐ์ • ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๊ณ  ์กฐํšŒ.
  • SwiftUI: UI ๊ตฌ์„ฑ.
  • UICollectionView & DiffableDataSource: ๋‚ ์งœ๋ณ„ ์ผ๊ธฐ ๊ด€๋ฆฌ ๋ฐ ๋ฐ์ดํ„ฐ ๋™๊ธฐํ™” ์ตœ์ ํ™”.
  • CALayer: ๊ฐ์ • ํŠธ๋ž˜ํ‚น์—์„œ ์žŽ์‚ฌ๊ท€์˜ ์‹œ๊ฐ์  ํ‘œํ˜„์„ ์œ„ํ•ด ์ปค์Šคํ…€ ๋ทฐ ๋ฐ ๋ ˆ์ด์–ด๋ฅผ ์‚ฌ์šฉ.
  • MVVM ํŒจํ„ด: ๋ฐ์ดํ„ฐ์™€ ๋ทฐ๋ฅผ ํšจ์œจ์ ์œผ๋กœ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ViewModel๊ณผ Repository๋ฅผ ์‚ฌ์šฉ.
  • SpeechRecognizer: ์Œ์„ฑ ํ…์ŠคํŠธ ๋ณ€ํ™˜ ๊ธฐ๋Šฅ ๊ตฌํ˜„.
  • AVFoundation: ์Œ์„ฑ ๋…น์Œ ๊ธฐ๋Šฅ ๋ฐ ํŒŒ์ผ ์ €์žฅ ๊ด€๋ฆฌ.
  • FileManager: ๋…น์Œ ํŒŒ์ผ ๊ด€๋ฆฌ.
  • TextEditor: ์ผ๊ธฐ ํŽธ์ง‘ ๋ฐ ์‹ค์‹œ๊ฐ„ ํ…์ŠคํŠธ ํ‘œ์‹œ.
  • APIํ†ต์‹  (URLSession): ๊ฐ์ • ๋ถ„์„ API ์—ฐ๊ฒฐ.
  • NSRegularExpression: API ์‘๋‹ต ๋ฐ์ดํ„ฐ ํŒŒ์‹ฑ์— ์‚ฌ์šฉ.
  • Charts: SwiftUI ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ํ™œ์šฉํ•œ ๊ฐ์ • ๋ถ„์„ ๊ฒฐ๊ณผ ์‹œ๊ฐํ™”.

๐Ÿ“ฑ ์ฃผ์š” ํ™”๋ฉด

๐Ÿ—“๏ธ ๋ฉ”์ธ ํ™”๋ฉด, ๐ŸŒณ ๊ฐ์ • ํŠธ๋ž˜ํ‚น, ๐ŸŒ™ ๋‹คํฌ ๋ชจ๋“œ


๐Ÿ“š ๋‚ด ์ผ๊ธฐ ๋ชจ์Œ์ง‘ ๋ฐ ๐Ÿ” ์ €์žฅ๋œ ์ผ๊ธฐ ์ƒ์„ธ ๋ณด๊ธฐ


๐Ÿ“ ์ผ๊ธฐ ์ž‘์„ฑ (์ œ๋ชฉ ์ž…๋ ฅ) ๋ฐ ๐ŸŽค ์ผ๊ธฐ ์ž‘์„ฑ (์Œ์„ฑ ๋…น์Œ)


๐Ÿ“Š ๋ถ„์„ ๊ฒฐ๊ณผ

1๏ธโƒฃ STEP1. ํ…๋ฐ”ํ™”๋ฉด (๋ฉ”์ธ, ๊ฐ์ •ํŠธ๋ž˜ํ‚น, ๋‚ด ์ผ๊ธฐ ๋ชจ์Œ์ง‘)

1-1 ์ฃผ์š”๊ธฐ๋Šฅ

  • ๋ฉ”์ธํ™”๋ฉด
    • ์„ ํƒํ•œ ๋‚ ์งœ์— ์ž‘์„ฑ๋œ ์ผ๊ธฐ๋ฅผ ์กฐํšŒํ•˜๊ฑฐ๋‚˜ ์ƒˆ ์ผ๊ธฐ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณต.
    • MonthCalendarViewControllerWrapper๋Š” UIKit์˜ UICollectionView์™€ DiffableDataSource๋ฅผ ํ™œ์šฉํ•ด ๋‚ ์งœ๋ณ„ ์ผ๊ธฐ๋ฅผ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค, ๋ฐ์ดํ„ฐ ์—…๋ฐ์ดํŠธ ์‹œ ๋ฐœ์ƒํ•˜๋Š” ๋ณต์žกํ•œ ๋™๊ธฐํ™”๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด DiffableDataSource๋ฅผ ๋„์ž…ํ•˜์—ฌ, ๋ฐ์ดํ„ฐ์˜ ์ผ๊ด€์„ฑ์„ ์œ ์ง€ํ•˜๊ณ  UI ์—…๋ฐ์ดํŠธ์˜ ์„ฑ๋Šฅ์„ ์ตœ์ ํ™”.
    • ๋‚ ์งœ๋ณ„๋กœ ์ผ๊ธฐ ๋ฐ์ดํ„ฐ๋ฅผ ์บ์‹ฑํ•˜์—ฌ, ๋ฐ˜๋ณต์ ์ธ ๋ฐ์ดํ„ฐ ์š”์ฒญ ์—†์ด ๋น ๋ฅด๊ฒŒ ํ™”๋ฉด์„ ๋ Œ๋”๋ง. ์บ์‹ฑ๋œ ๋ฐ์ดํ„ฐ๋Š” ๋‚ ์งœ๊ฐ€ ์„ ํƒ๋  ๋•Œ๋งˆ๋‹ค ์ ์žฌ๋˜๋ฉฐ, Realm์—์„œ ์กฐํšŒ๋œ ๋ฐ์ดํ„ฐ๋Š” ๋น„๋™๊ธฐ์ ์œผ๋กœ ์—…๋ฐ์ดํŠธ๋ฉ๋‹ˆ๋‹ค.
  • ๊ฐ์ • ํŠธ๋ž˜ํ‚น
    • ์‚ฌ์šฉ์ž ๊ฐ์ •์„ ์‹œ๊ฐ์ ์œผ๋กœ ํŠธ๋ž˜ํ‚นํ•˜๋Š” ๊ธฐ๋Šฅ. ๊ฐ์ • ๋ถ„์„ ๊ฒฐ๊ณผ๋Š” ๋‚˜๋ฌด์˜ ์žŽ์‚ฌ๊ท€ ์ƒ‰์ƒ์œผ๋กœ ํ‘œํ˜„๋˜๋ฉฐ, LeafShape๋Š” SwiftUI์˜ Shape ํ”„๋กœํ† ์ฝœ์„ ์ค€์ˆ˜ํ•˜์—ฌ ๋ฒกํ„ฐ ๊ทธ๋ž˜ํ”ฝ์œผ๋กœ ๊ฐ์ • ์ƒํƒœ๋ฅผ ์‹œ๊ฐํ™”ํ•ฉ๋‹ˆ๋‹ค.
    • ์žŽ์‚ฌ๊ท€์˜ ์ƒ‰์ƒ์€ AI ๊ฐ์ • ๋ถ„์„ API๋ฅผ ํ†ตํ•ด ๋ฐ˜ํ™˜๋œ ๊ฐ์ • ๋ฐ์ดํ„ฐ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋™์ ์œผ๋กœ ๋ณ€๊ฒฝ๋˜๋ฉฐ, ๊ฐ ์žŽ์‚ฌ๊ท€๋Š” ๊ฐ์ •์— ๋”ฐ๋ผ ์œ„์น˜๊ฐ€ ๋‹ฌ๋ผ์ง‘๋‹ˆ๋‹ค. ์ด๋•Œ, CALayer๋ฅผ ์‚ฌ์šฉํ•ด ๋ ˆ์ด์–ด๋ณ„๋กœ ์„ฑ๋Šฅ ์ตœ์ ํ™”๋ฅผ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค.
  • ๋‚ด ์ผ๊ธฐ ๋ชจ์Œ์ง‘:
    • ์‚ฌ์šฉ์ž๊ฐ€ ์ž‘์„ฑํ•œ ์ผ๊ธฐ ๋ฐ์ดํ„ฐ๋ฅผ Realm์—์„œ ๋ถˆ๋Ÿฌ์™€ ๋‚ ์งœ๋ณ„๋กœ ์ •๋ ฌํ•˜์—ฌ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.
    • DiaryCardView๋Š” LazyVStack์„ ์‚ฌ์šฉํ•ด ์„ฑ๋Šฅ์„ ์ตœ์ ํ™”ํ•˜๋ฉฐ, ๋Œ€๋Ÿ‰์˜ ์ผ๊ธฐ ๋ฐ์ดํ„ฐ๋ฅผ ์Šคํฌ๋กคํ•  ๋•Œ ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ๋ Œ๋”๋ง๋ฉ๋‹ˆ๋‹ค.
    • FetchRequest๋Š” ๋น„๋™๊ธฐ์ ์œผ๋กœ ์ด๋ฃจ์–ด์ง€๋ฉฐ, ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ ์ค‘์—๋„ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•ด, Combine์„ ํ™œ์šฉํ•ด ๋ฐ์ดํ„ฐ ํ๋ฆ„์„ ์ œ์–ดํ–ˆ์Šต๋‹ˆ๋‹ค.

1-2 ๊ณ ๋ฏผํ•œ์ 

  1. ์žŽ์‚ฌ๊ท€์˜ ๋ชจ์–‘๊ณผ ์‹œ๊ฐ์  ํ‘œํ˜„์˜ ์ตœ์ ํ™”
    • ๊ฐ์ • ํŠธ๋ž˜ํ‚น ํ™”๋ฉด์—์„œ ๊ฐ์ • ๋ฐ์ดํ„ฐ๋ฅผ ์–ด๋–ป๊ฒŒ ํšจ๊ณผ์ ์œผ๋กœ ์‹œ๊ฐํ™”ํ• ์ง€ ๊ณ ๋ฏผํ–ˆ์Šต๋‹ˆ๋‹ค. ์ฒ˜์Œ์—๋Š” ์‚ฌ์ง„์„ ์‚ฌ์šฉํ• ์ง€ ๊ณ ๋ คํ–ˆ์ง€๋งŒ, SwiftUI์˜ Shape ํ”„๋กœํ† ์ฝœ์„ ํ™œ์šฉํ•ด ์ปค์Šคํ…€ LeafShape๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด Bezier ๊ณก์„ ์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ถ€๋“œ๋Ÿฌ์šด ๊ณก์„ ์˜ ์žŽ์‚ฌ๊ท€ ๋ชจ์–‘์„ ๊ทธ๋ ธ์œผ๋ฉฐ, ๊ฐ์ •์˜ ๊ฐ•๋„์— ๋”ฐ๋ผ ์žŽ์‚ฌ๊ท€์˜ ํฌ๊ธฐ์™€ ๋ชจ์–‘์ด ๋‹ค๋ฅด๊ฒŒ ๋ณ€ํ•˜๋„๋ก ์„ค์ •ํ–ˆ์Šต๋‹ˆ๋‹ค.
  2. ํƒญ๋ฐ” ์ปค์Šคํ„ฐ๋งˆ์ด์ง•๊ณผ ๋™์  ๋†’์ด ์กฐ์ •
    • ๋‹ค์–‘ํ•œ ํ™”๋ฉด ํฌ๊ธฐ์™€ ๊ธฐ๊ธฐ์— ๋Œ€์‘ํ•˜๊ธฐ ์œ„ํ•ด ํƒญ๋ฐ”์˜ ๋†’์ด๋ฅผ ๋™์ ์œผ๋กœ ์กฐ์ •ํ•˜๋Š” ๋ถ€๋ถ„์— ์ค‘์ ์„ ๋‘์—ˆ์Šต๋‹ˆ๋‹ค.
    • iPhone 15 Pro์™€ iPhone SE์™€ ๊ฐ™์€ ๋‹ค์–‘ํ•œ ํ™”๋ฉด ํฌ๊ธฐ์—์„œ ์ผ๊ด€๋œ UI ๊ฒฝํ—˜์„ ์ œ๊ณตํ•˜๊ธฐ ์œ„ํ•ด, GeometryReader๋ฅผ ์‚ฌ์šฉํ•ด ํƒญ๋ฐ”์˜ ๋ ˆ์ด์•„์›ƒ์„ ๋””๋ฐ”์ด์Šค์˜ ํ™”๋ฉด ํฌ๊ธฐ์— ๋งž๊ฒŒ ์ž๋™ ์กฐ์ •ํ•˜๋„๋ก ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด๋กœ ์ธํ•ด ํƒญ ์ „ํ™˜ ์‹œ ํ™”๋ฉด์ด ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ์ „ํ™˜๋˜๋ฉด์„œ๋„ ๋‹ค์–‘ํ•œ ๊ธฐ๊ธฐ์—์„œ ์ผ๊ด€๋œ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

1-3 ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ…

  1. ์ผ๊ธฐ ์‚ญ์ œ์‹œ ์•ฑ์ด ์ถฉ๋Œํ•˜๋Š” ๋ฌธ์ œ
  • ์ผ๊ธฐ๋ฅผ ์‚ญ์ œ ํ›„ View๊ฐ€ ์ œ๋Œ€๋กœ ๊ฐฑ์‹ ๋˜์ง€ ์•Š๊ฑฐ๋‚˜ ํฌ๋ž˜์‹œํ•˜๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. ์‚ญ์ œ๋œ ์ผ๊ธฐ์— ์—ฐ๊ฒฐ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ณผ์ •์—์„œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๊ฒƒ ๊ฐ™์•˜๊ณ  ์•ฑ์ด ๊ฐ•์ œ ์ข…๋ฃŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
  1. ํ•ด๊ฒฐ๊ณผ์ •
  • ์ผ๊ธฐ ์‚ญ์ œํ•œ ํ›„์—๋„ View์—์„œ ์—ฌ์ „ํžˆ ์‚ญ์ œ๋œ DiaryEntry๊ฐ์ฒด์— ์ ‘๊ทผํ•˜๋ ค๊ณ  ์‹œ๋„ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ฐœ์ƒํ•œ ๊ฒƒ์ด์—ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ ๊ฐ์ฒด๊ฐ€ ๋” ์ด์ƒ ์กด์žฌํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ์•ฑ์ด ์ถฉ๋Œํ•˜๊ฒŒ ๋œ๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด deleteEntry() ๋ฉ”์„œ๋“œ์—์„œ entry = DiaryEntry()๋กœ ์ดˆ๊ธฐํ™”ํ•˜์—ฌ ์‚ญ์ œ๋œ ์ผ๊ธฐ์— ๋Œ€ํ•œ ์ฐธ์กฐ๋ฅผ ์ฆ‰์‹œ ์ œ๊ฑฐํ–ˆ์Šต๋‹ˆ๋‹ค.
      func deleteEntry() {
          if let audioFileName = entry.audioFilePath {
              let fullPath = getFullAudioPath(for: audioFileName)
              let fileManager = FileManager.default
              if fileManager.fileExists(atPath: fullPath) {
                  do {
                      try fileManager.removeItem(atPath: fullPath)
                      print("๋…น์Œ ํŒŒ์ผ ์‚ญ์ œ ์™„๋ฃŒ")
                  } catch {
                      print("๋…น์Œ ํŒŒ์ผ ์‚ญ์ œ ์‹คํŒจ: \(error)")
                  }
              }
          }
          repository.deleteEntryAndFolderIfNeeded(entry)
          // ์‚ญ์ œ ํ›„ ๋นˆ DiaryEntry๋กœ ๋ฐ”๋กœ ์ดˆ๊ธฐํ™” ํ–ˆ์Šต๋‹ˆ๋‹ค.
          entry = DiaryEntry()
          firstNaviLinkActive = false
          dismiss()
      }
  1. ๊ฒฐ๊ณผ
    • ์‚ญ์ œ ํ›„์—๋„ ์•ˆ์ „ํ•˜๊ฒŒ View๊ฐ€ ๊ฐฑ์‹ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

2๏ธโƒฃ STEP2. ์ผ๊ธฐ ์ž‘์„ฑ ํ™”๋ฉด ๋ฐ ๋…น์Œ ๊ธฐ๋Šฅ(STT)

2-1 ์ฃผ์š”๊ธฐ๋Šฅ

  • ์ผ๊ธฐ ์ž‘์„ฑ ๋ฐ ๋…น์Œ๊ธฐ๋Šฅ
    • ๋‚ ์งœ๋ฅผ ์„ ํƒํ›„, STT(์Œ์„ฑํ…์ŠคํŠธ ๋ณ€ํ™˜)๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•ด ์Œ์„ฑ์œผ๋กœ ์ผ๊ธฐ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋…น์Œ ๊ธฐ๋Šฅ์€ AVFoundation ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ํ™œ์šฉํ•˜์—ฌ ํ†ตํ•ฉ๋˜์—ˆ์œผ๋ฉฐ, ์‚ฌ์šฉ์ž๊ฐ€ ์ง์ ‘ ์Œ์„ฑ์„ ๋…น์Œํ•œ ํ›„ ์ด๋ฅผ ํŒŒ์ผ ์‹œ์Šคํ…œ์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. ๋…น์Œ ํŒŒ์ผ์€ ์•ฑ ๋‚ด๋ถ€์˜ FileManager ๊ฒฝ๋กœ์— ์ €์žฅ๋˜๋ฉฐ, ์ €์žฅ๋œ ํŒŒ์ผ์€ ๋‚˜์ค‘์— ์žฌ์ƒ ๊ฐ€๋Šฅํ•˜๊ฑฐ๋‚˜ ์Œ์„ฑ ๋ฐ์ดํ„ฐ๋กœ ํ™œ์šฉ๋ฉ๋‹ˆ๋‹ค.
    • STT: SpeechRecognizer๋ฅผ ํ†ตํ•ด ๋…น์Œ๋œ ์Œ์„ฑ์„ ์‹ค์‹œ๊ฐ„์œผ๋กœ ํ…์ŠคํŠธ๋กœ ๋ณ€ํ™˜ํ•ด ์ค๋‹ˆ๋‹ค. ์ตœ๋Œ€ 500์ž๊นŒ์ง€ ์ €์žฅ๋˜๋„๋ก ํ•˜์˜€๊ณ , TextEditor๋ฅผ ํ†ตํ•ด ํŽธ์ง‘๋œ ์ผ๊ธฐ์˜ ๋‚ด์šฉ์„ ํ™”๋ฉด์— ์‹ค์‹œ๊ฐ„์œผ๋กœ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. ์Œ์„ฑ ์ธ์‹๊ณผ ํ…์ŠคํŠธ ๋ณ€ํ™˜์€ AVAudioEngine์„ ์‚ฌ์šฉํ•ด ์‹ค์‹œ๊ฐ„์œผ๋กœ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค.

2-2 ๊ณ ๋ฏผํ•œ์ 

  • STT๊ธฐ๋Šฅ์˜ ์ •ํ™•์„ฑ๊ณผ ์Œ์งˆ ์ตœ์ ํ™”
    • ์ฒ˜์Œ์—๋Š” STT(์Œ์„ฑ ํ…์ŠคํŠธ ๋ณ€ํ™˜) ๊ธฐ๋Šฅ์˜ ์ •ํ™•๋„์™€ ์Œ์งˆ์„ ์–ด๋–ป๊ฒŒ ์ตœ์ ํ™”ํ• ์ง€ ์ž˜ ๋ชฐ๋ž์Šต๋‹ˆ๋‹ค. ์Œ์„ฑ ์ธ์‹์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์˜ค๋ฅ˜์™€ ์ •ํ™•๋„ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด SpeechRecognizer์˜ ์–ธ์–ด ์„ค์ •์„ ์‚ฌ์šฉ์ž์˜ ๋””๋ฐ”์ด์Šค์— ๋งž์ถฐ ๋™์ ์œผ๋กœ ๋ณ€๊ฒฝํ–ˆ๊ณ , ์Œ์„ฑ ์ธ์‹ ๊ฒฐ๊ณผ๊ฐ€ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ฐ˜์˜๋˜๋„๋ก ๊ฐœ์„ ํ–ˆ์Šต๋‹ˆ๋‹ค.
    • ๋˜ํ•œ, ๋Œ€๋ถ€๋ถ„์˜ ์˜ค๋””์˜ค ์†Œํ”„ํŠธ์›จ์–ด๋Š” 32kHz, 24kHz, 16kHz ๋˜๋Š” 12kHz๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์ฐพ์•˜์Šต๋‹ˆ๋‹ค. ๊ทธ ์ด์ƒ์€ CD ์˜ค๋””์˜ค์˜ ํ‘œ์ค€์ด๋ผ๊ณ  ํ•˜์—ฌ ํ•œ๋ฒˆ ์Œ์„ฑ ํŒŒ์ผ์˜ ํฌ๊ธฐ์™€ ์Œ์งˆ ์‚ฌ์ด์˜ ๊ท ํ˜•์„ ๋งž์ถ”๊ธฐ ์œ„ํ•ด ๋‹ค์–‘ํ•œ ์ƒ˜ํ”Œ ๋ ˆ์ดํŠธ๋กœ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ–ˆ์Šต๋‹ˆ๋‹ค. 8kHz ๋ถ€ํ„ฐ ์‹คํ—˜ํ•œ ๊ฒฐ๊ณผ 12kHz์—๋ถ€ํ„ฐ ์ถฉ๋ถ„ํžˆ ์‚ฌ์šฉ์ž๋“ค์ด ์•Œ์•„๋“ฃ๊ธฐ ์‰ฌ์šด ์Œ์งˆ์„ ์ œ๊ณตํ–ˆ์Šต๋‹ˆ๋‹ค.(์šฉ๋Ÿ‰ ํฌ๊ธฐ๋„ ๊ณ ๋ ค)

2-3 ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ…

  1. ๋…น์Œ ์‹œ๊ฐ„ ์ดˆ๊ณผ์‹œ STT๊ธฐ๋Šฅ ์ค‘๋‹จ ์•ˆ๋˜๋Š” ํ˜„์ƒ
    • ์Œ์„ฑ ๋…น์Œ ๊ธฐ๋Šฅ์ด ์ตœ๋Œ€ 3๋ถ„์œผ๋กœ ์ œํ•œ๋˜์–ด์žˆ์ง€๋งŒ, 3๋ถ„์ด ์ง€๋‚˜๋„ STT๋Š” ๊ณ„์† ์‹คํ–‰๋˜๊ณ  ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ์ฆ‰ ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์Œ์„ฑ์ธ์‹์€ ์ง„ํ–‰๋˜๊ณ  ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.
  2. ํ•ด๊ฒฐ๊ณผ์ •
    • ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ์œ„ํ•ด ๋จผ์ € STT์™€ ๋…น์Œ์ด ์–ด๋–ป๊ฒŒ ๋™๊ธฐํ™” ๋˜๋Š”์ง€ ํ™•์ธํ–ˆ์Šต๋‹ˆ๋‹ค. ์ฒ˜์Œ์—๋Š” stopTranscribing() ๋ฉ”์„œ๋“œ์—์„œ audioEngine๊ณผ recognitionTask๋ฅผ ์ค‘๋‹จํ•˜๊ธฐ ์œ„ํ•ด audioEngine.stop(), recognitionRequest?.endAudio(), recognitionTask?.cancel()์„ ํ˜ธ์ถœํ–ˆ์ง€๋งŒ, ์ด ๋ฐฉ๋ฒ•๋งŒ์œผ๋กœ๋Š” STT๊ฐ€ ์™„์ „ํžˆ ์ข…๋ฃŒ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. STT์˜ ์ผ๋ถ€ ๋ฆฌ์†Œ์Šค๊ฐ€ ์—ฌ์ „ํžˆ ํ• ๋‹น๋œ ์ƒํƒœ๋กœ ๋‚จ์•„ ์žˆ์–ด, STT ๊ธฐ๋Šฅ์ด ๊ณ„์† ๋™์ž‘ํ•˜๊ณ  ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด recognitionRequest์™€ recognitionTask๋ฅผ nil๋กœ ์„ค์ •ํ•˜๊ณ  ๋‚˜์„œ์•ผ STT ๊ธฐ๋Šฅ์ด ์™„์ „ํžˆ ์ค‘๋‹จ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
     func stopTranscribing() {
         if audioEngine.isRunning {
             audioEngine.stop()
             recognitionRequest?.endAudio()
             recognitionTask?.cancel()
         }
     
         accumulatedTranscript = transcript
     
         recognitionRequest = nil
         recognitionTask = nil
     }
     
  3. ๊ฒฐ๊ณผ
    • 3๋ถ„์ด ์ง€๋‚ ์‹œ ์Œ์„ฑ ๋…น์Œ ๋ฐ STT ๊ธฐ๋Šฅ์ด ๋™์‹œ์— ์ข…๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

3๏ธโƒฃ STEP3. ๋ถ„์„ ํ™”๋ฉด (API ์—ฐ๊ฒฐ)

3-1 ์ฃผ์š”๊ธฐ๋Šฅ

  • ์‚ฌ์šฉ์ž๊ฐ€ ์Œ์„ฑ์œผ๋กœ ์ผ๊ธฐ๋ฅผ ์ž‘์„ฑํ•˜๊ณ  ์ €์žฅํ•  ๋•Œ, ํ•ด๋‹น ์ผ๊ธฐ์˜ ํ…์ŠคํŠธ ๋ฐ์ดํ„ฐ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ฐ์ • ๋ถ„์„ API๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. ๊ฐ์ • ๋ถ„์„ API๋Š” ํ…์ŠคํŠธ์—์„œ ๊ธ์ •๊ณผ ๋ถ€์ •์˜ ๋น„์œจ์„ ์‚ฐ์ถœํ•œ ํ›„, ์ด๋ฅผ ํ†ตํ•ด ๊ธฐ์จ, ์Šฌํ””, ๋ถ„๋…ธ, ๊ณตํฌ 4๊ฐœ์˜ ์ฃผ์š” ๊ฐ์ • ์ค‘ ํ•˜๋‚˜๋กœ ๋„์ถœํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.

3-2 ๊ณ ๋ฏผํ•œ์ 

  • ๊ฐ์ • ๋ฐ์ดํ„ฐ ์‹œ๊ฐํ™”
    • ๊ฐ์ • ๋ถ„์„ ๊ฒฐ๊ณผ๋ฅผ ์–ด๋–ป๊ฒŒ ํ‘œํ˜„ํ• ์ง€ ๊ณ ๋ฏผ์ด ๋งŽ์•˜์Šต๋‹ˆ๋‹ค. ์ฒ˜์Œ์—๋Š” ๋‹จ์ˆœํžˆ ํ…์ŠคํŠธ๋กœ ํ‘œ์‹œํ• ๊นŒ ์ƒ๊ฐํ–ˆ์œผ๋‚˜ SwiftUI์˜ Charts๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ํ™œ์šฉํ•ด๋ณด๋ฉด ์ข‹์„๊ฑฐ ๊ฐ™์•„ ์ด ๋ฐฉ์‹์„ ์ฑ„ํƒํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉ์ž๋Š” ๊ฐ์ • ๋ถ„์„ ๊ฒฐ๊ณผ๋ฅผ ์‹œ๊ฐ์ ์œผ๋กœ ํ•œ๋ˆˆ์— ํ™•์ธ์ด ๊ฐ€๋Šฅํ•ด ์‰ฝ๊ฒŒ ๋” ์ดํ•ดํ•  ์ˆ˜ ์žˆ๋Š” ์ˆ˜์น˜๋ผ๊ณ  ์ƒ๊ฐํ–ˆ์Šต๋‹ˆ๋‹ค.

3-3 ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ…

  1. APIํ˜ธ์ถœ ๊ฒฐ๊ณผ ํŒŒ์‹ฑ๋ฌธ์ œ
    • ์ฒ˜์Œ์—๋Š” ๊ฐ์ • ๋ถ„์„์„ ์œ„ํ•ด API๋ฅผ ํ˜ธ์ถœํ–ˆ์„ ๋•Œ, ์‘๋‹ต์ด ๋‚ด๊ฐ€ ์›ํ•˜๋Š” ๊ตฌ์กฐํ™”๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ์•„๋‹ˆ๋ผ, ๋‹จ์ˆœํžˆ "์ด ๊ธ€์€ ๊ธ์ •์ด๋ฉฐ ~~%, ๋ถ€์ •์ด๋ฉฐ ~~%"์™€ ๊ฐ™์€ ์„œ์ˆ ํ˜• ๋ฌธ์žฅ์œผ๋กœ ์™”์Šต๋‹ˆ๋‹ค. ๊ธ์ •๊ณผ ๋ถ€์ •์˜ ๋น„์œจ์„ ์ˆซ์ž๋กœ, ๊ฐ์ •์ƒํƒœ๋„ ๋”ฐ๋กœ ์ถ”์ถœํ•˜๊ณ  ์‹ถ์—ˆ๋Š”๋ฐ ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์•ผํ• ์ง€ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ฒผ๋‹ค.
  2. ํ•ด๊ฒฐ๊ณผ์ •
    • API ์‘๋‹ต์—์„œ ์›ํ•˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”์ถœํ•˜๊ธฐ ์œ„ํ•ด NSRegularExpression(์ •๊ทœ ํ‘œํ˜„์‹)์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฌธ์ž์—ด์„ ํŒŒ์‹ฑํ•˜๋Š” ๋ฐฉ์‹์„ ๋„์ž…ํ–ˆ์Šต๋‹ˆ๋‹ค.
     private func parseSentimentResult(_ sentimentText: String) -> [SentimentData] {
         let positiveRegex = try? NSRegularExpression(pattern: "๊ธ์ •:?\\s*(\\d+)%")
         let negativeRegex = try? NSRegularExpression(pattern: "๋ถ€์ •:?\\s*(\\d+)%")
     
         var positivePercentage: Int = 0
         var negativePercentage: Int = 0
     
         if let match = positiveRegex?.firstMatch(in: sentimentText, range: NSRange(sentimentText.startIndex..., in: sentimentText)) {
             if let range = Range(match.range(at: 1), in: sentimentText) {
                 positivePercentage = Int(sentimentText[range]) ?? 0
             }
         }
     
         if let match = negativeRegex?.firstMatch(in: sentimentText, range: NSRange(sentimentText.startIndex..., in: sentimentText)) {
             if let range = Range(match.range(at: 1), in: sentimentText) {
                 negativePercentage = Int(sentimentText[range]) ?? 0
             }
         }
     
         return [
             SentimentData(emotion: "๊ธ์ •", percentage: Double(positivePercentage)),
             SentimentData(emotion: "๋ถ€์ •", percentage: Double(negativePercentage))
         ]
         }
     
         private func parseEmotion(_ sentimentText: String) -> String {
         let emotionRegex = try? NSRegularExpression(pattern: "(๊ธฐ์จ|์Šฌํ””|๊ณตํฌ|๋ถ„๋…ธ)")
     
         if let match = emotionRegex?.firstMatch(in: sentimentText, range: NSRange(sentimentText.startIndex..., in: sentimentText)) {
             if let range = Range(match.range(at: 1), in: sentimentText) {
                 return String(sentimentText[range])
             }
         }
     
         return "๋ถ„์„ ์‹คํŒจ"
     }
  3. ๊ฒฐ๊ณผ
    • ์ด๋ ‡๊ฒŒ ์ •๊ทœ ํ‘œํ˜„์‹์„ ์‚ฌ์šฉํ•˜์—ฌ ์‘๋‹ต์„ ํŒŒ์‹ฑํ–ˆ๋”๋‹ˆ ๋‚ด๊ฐ€์›ํ–ˆ๋˜ ๊ธ์ •/๋ถ€์ •/๊ฐ์ • ๋น„์œจ ๋ฐ ์ƒํƒœ๋ฅผ ์ •ํ™•ํ•˜๊ฒŒ ์ถ”์ถœ ํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ์ด์šฉํ•ด์„œ ๋ฐ”๋กœ UI์— ๋ฐ˜์˜ํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages