์๋ค์ผ๊ธฐ๋ ์์ฑ์ผ๋ก ์ผ๊ธฐ๋ฅผ ์์ฑํ๊ณ ๊ฐ์ ์ ๋ถ์ํด์ฃผ๋ ์์ฑ ์ผ๊ธฐ ์ฑ์ ๋๋ค. ์์ ์ ๋ชฉ์๋ฆฌ๋ก ์ผ๊ธฐ๋ฅผ ๊ธฐ๋กํ ์ ์์ผ๋ฉฐ, ์๋์ผ๋ก ๊ฐ์ ๋ถ์์ ํตํด ๋ค์ํ ๊ฐ์ ์ ์๊ฐ์ ์ผ๋ก ํํํฉ๋๋ค.
- ์งํ ๊ธฐ๊ฐ
- ๊ธฐํ : 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 ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํ์ฉํ ๊ฐ์ ๋ถ์ ๊ฒฐ๊ณผ ์๊ฐํ.
๐๏ธ ๋ฉ์ธ ํ๋ฉด, ๐ณ ๊ฐ์ ํธ๋ํน, ๐ ๋คํฌ ๋ชจ๋
๐ ๋ด ์ผ๊ธฐ ๋ชจ์์ง ๋ฐ ๐ ์ ์ฅ๋ ์ผ๊ธฐ ์์ธ ๋ณด๊ธฐ
๐ ์ผ๊ธฐ ์์ฑ (์ ๋ชฉ ์ ๋ ฅ) ๋ฐ ๐ค ์ผ๊ธฐ ์์ฑ (์์ฑ ๋ น์)
๐ ๋ถ์ ๊ฒฐ๊ณผ
- ๋ฉ์ธํ๋ฉด
- ์ ํํ ๋ ์ง์ ์์ฑ๋ ์ผ๊ธฐ๋ฅผ ์กฐํํ๊ฑฐ๋ ์ ์ผ๊ธฐ๋ฅผ ์์ฑํ ์ ์๋ ๊ธฐ๋ฅ์ ์ ๊ณต.
- MonthCalendarViewControllerWrapper๋ UIKit์ UICollectionView์ DiffableDataSource๋ฅผ ํ์ฉํด ๋ ์ง๋ณ ์ผ๊ธฐ๋ฅผ ๊ด๋ฆฌํฉ๋๋ค, ๋ฐ์ดํฐ ์ ๋ฐ์ดํธ ์ ๋ฐ์ํ๋ ๋ณต์กํ ๋๊ธฐํ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด DiffableDataSource๋ฅผ ๋์ ํ์ฌ, ๋ฐ์ดํฐ์ ์ผ๊ด์ฑ์ ์ ์งํ๊ณ UI ์ ๋ฐ์ดํธ์ ์ฑ๋ฅ์ ์ต์ ํ.
- ๋ ์ง๋ณ๋ก ์ผ๊ธฐ ๋ฐ์ดํฐ๋ฅผ ์บ์ฑํ์ฌ, ๋ฐ๋ณต์ ์ธ ๋ฐ์ดํฐ ์์ฒญ ์์ด ๋น ๋ฅด๊ฒ ํ๋ฉด์ ๋ ๋๋ง. ์บ์ฑ๋ ๋ฐ์ดํฐ๋ ๋ ์ง๊ฐ ์ ํ๋ ๋๋ง๋ค ์ ์ฌ๋๋ฉฐ, Realm์์ ์กฐํ๋ ๋ฐ์ดํฐ๋ ๋น๋๊ธฐ์ ์ผ๋ก ์ ๋ฐ์ดํธ๋ฉ๋๋ค.
- ๊ฐ์ ํธ๋ํน
- ์ฌ์ฉ์ ๊ฐ์ ์ ์๊ฐ์ ์ผ๋ก ํธ๋ํนํ๋ ๊ธฐ๋ฅ. ๊ฐ์ ๋ถ์ ๊ฒฐ๊ณผ๋ ๋๋ฌด์ ์์ฌ๊ท ์์์ผ๋ก ํํ๋๋ฉฐ, LeafShape๋ SwiftUI์ Shape ํ๋กํ ์ฝ์ ์ค์ํ์ฌ ๋ฒกํฐ ๊ทธ๋ํฝ์ผ๋ก ๊ฐ์ ์ํ๋ฅผ ์๊ฐํํฉ๋๋ค.
- ์์ฌ๊ท์ ์์์ AI ๊ฐ์ ๋ถ์ API๋ฅผ ํตํด ๋ฐํ๋ ๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๋์ ์ผ๋ก ๋ณ๊ฒฝ๋๋ฉฐ, ๊ฐ ์์ฌ๊ท๋ ๊ฐ์ ์ ๋ฐ๋ผ ์์น๊ฐ ๋ฌ๋ผ์ง๋๋ค. ์ด๋, CALayer๋ฅผ ์ฌ์ฉํด ๋ ์ด์ด๋ณ๋ก ์ฑ๋ฅ ์ต์ ํ๋ฅผ ๊ตฌํํ์ต๋๋ค.
- ๋ด ์ผ๊ธฐ ๋ชจ์์ง:
- ์ฌ์ฉ์๊ฐ ์์ฑํ ์ผ๊ธฐ ๋ฐ์ดํฐ๋ฅผ Realm์์ ๋ถ๋ฌ์ ๋ ์ง๋ณ๋ก ์ ๋ ฌํ์ฌ ํ์ํฉ๋๋ค.
- DiaryCardView๋ LazyVStack์ ์ฌ์ฉํด ์ฑ๋ฅ์ ์ต์ ํํ๋ฉฐ, ๋๋์ ์ผ๊ธฐ ๋ฐ์ดํฐ๋ฅผ ์คํฌ๋กคํ ๋ ๋ถ๋๋ฝ๊ฒ ๋ ๋๋ง๋ฉ๋๋ค.
- FetchRequest๋ ๋น๋๊ธฐ์ ์ผ๋ก ์ด๋ฃจ์ด์ง๋ฉฐ, ๋ฐ์ดํฐ ๋ก๋ฉ ์ค์๋ ์ฌ์ฉ์ ๊ฒฝํ์ ์ ์งํ๊ธฐ ์ํด, Combine์ ํ์ฉํด ๋ฐ์ดํฐ ํ๋ฆ์ ์ ์ดํ์ต๋๋ค.
- ์์ฌ๊ท์ ๋ชจ์๊ณผ ์๊ฐ์ ํํ์ ์ต์ ํ
- ๊ฐ์ ํธ๋ํน ํ๋ฉด์์ ๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ์ด๋ป๊ฒ ํจ๊ณผ์ ์ผ๋ก ์๊ฐํํ ์ง ๊ณ ๋ฏผํ์ต๋๋ค. ์ฒ์์๋ ์ฌ์ง์ ์ฌ์ฉํ ์ง ๊ณ ๋ คํ์ง๋ง, SwiftUI์ Shape ํ๋กํ ์ฝ์ ํ์ฉํด ์ปค์คํ LeafShape๋ฅผ ๊ตฌํํ ์ ์๋ค๋ ๊ฒ์ ์๊ฒ ๋์์ต๋๋ค. ์ด๋ฅผ ํตํด Bezier ๊ณก์ ์ ์ฌ์ฉํ์ฌ ๋ถ๋๋ฌ์ด ๊ณก์ ์ ์์ฌ๊ท ๋ชจ์์ ๊ทธ๋ ธ์ผ๋ฉฐ, ๊ฐ์ ์ ๊ฐ๋์ ๋ฐ๋ผ ์์ฌ๊ท์ ํฌ๊ธฐ์ ๋ชจ์์ด ๋ค๋ฅด๊ฒ ๋ณํ๋๋ก ์ค์ ํ์ต๋๋ค.
- ํญ๋ฐ ์ปค์คํฐ๋ง์ด์ง๊ณผ ๋์ ๋์ด ์กฐ์
- ๋ค์ํ ํ๋ฉด ํฌ๊ธฐ์ ๊ธฐ๊ธฐ์ ๋์ํ๊ธฐ ์ํด ํญ๋ฐ์ ๋์ด๋ฅผ ๋์ ์ผ๋ก ์กฐ์ ํ๋ ๋ถ๋ถ์ ์ค์ ์ ๋์์ต๋๋ค.
- iPhone 15 Pro์ iPhone SE์ ๊ฐ์ ๋ค์ํ ํ๋ฉด ํฌ๊ธฐ์์ ์ผ๊ด๋ UI ๊ฒฝํ์ ์ ๊ณตํ๊ธฐ ์ํด, GeometryReader๋ฅผ ์ฌ์ฉํด ํญ๋ฐ์ ๋ ์ด์์์ ๋๋ฐ์ด์ค์ ํ๋ฉด ํฌ๊ธฐ์ ๋ง๊ฒ ์๋ ์กฐ์ ํ๋๋ก ๊ตฌํํ์ต๋๋ค. ์ด๋ก ์ธํด ํญ ์ ํ ์ ํ๋ฉด์ด ๋ถ๋๋ฝ๊ฒ ์ ํ๋๋ฉด์๋ ๋ค์ํ ๊ธฐ๊ธฐ์์ ์ผ๊ด๋ ์ฌ์ฉ์ ๊ฒฝํ์ ์ ๊ณตํ ์ ์์์ต๋๋ค.
- ์ผ๊ธฐ ์ญ์ ์ ์ฑ์ด ์ถฉ๋ํ๋ ๋ฌธ์
- ์ผ๊ธฐ๋ฅผ ์ญ์ ํ View๊ฐ ์ ๋๋ก ๊ฐฑ์ ๋์ง ์๊ฑฐ๋ ํฌ๋์ํ๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ต๋๋ค. ์ญ์ ๋ ์ผ๊ธฐ์ ์ฐ๊ฒฐ๋ ๋ฐ์ดํฐ๋ฅผ ์ฒ๋ฆฌํ๋ ๊ณผ์ ์์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๊ฒ ๊ฐ์๊ณ ์ฑ์ด ๊ฐ์ ์ข ๋ฃ ๋์์ต๋๋ค.
- ํด๊ฒฐ๊ณผ์
- ์ผ๊ธฐ ์ญ์ ํ ํ์๋ 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() }
- ๊ฒฐ๊ณผ
- ์ญ์ ํ์๋ ์์ ํ๊ฒ View๊ฐ ๊ฐฑ์ ๋์์ต๋๋ค.
- ์ผ๊ธฐ ์์ฑ ๋ฐ ๋
น์๊ธฐ๋ฅ
- ๋ ์ง๋ฅผ ์ ํํ, STT(์์ฑํ ์คํธ ๋ณํ)๊ธฐ๋ฅ์ ์ฌ์ฉํด ์์ฑ์ผ๋ก ์ผ๊ธฐ๋ฅผ ์์ฑํ ์ ์์ต๋๋ค. ๋ น์ ๊ธฐ๋ฅ์ AVFoundation ํ๋ ์์ํฌ๋ฅผ ํ์ฉํ์ฌ ํตํฉ๋์์ผ๋ฉฐ, ์ฌ์ฉ์๊ฐ ์ง์ ์์ฑ์ ๋ น์ํ ํ ์ด๋ฅผ ํ์ผ ์์คํ ์ ์ ์ฅํฉ๋๋ค. ๋ น์ ํ์ผ์ ์ฑ ๋ด๋ถ์ FileManager ๊ฒฝ๋ก์ ์ ์ฅ๋๋ฉฐ, ์ ์ฅ๋ ํ์ผ์ ๋์ค์ ์ฌ์ ๊ฐ๋ฅํ๊ฑฐ๋ ์์ฑ ๋ฐ์ดํฐ๋ก ํ์ฉ๋ฉ๋๋ค.
- STT: SpeechRecognizer๋ฅผ ํตํด ๋ น์๋ ์์ฑ์ ์ค์๊ฐ์ผ๋ก ํ ์คํธ๋ก ๋ณํํด ์ค๋๋ค. ์ต๋ 500์๊น์ง ์ ์ฅ๋๋๋ก ํ์๊ณ , TextEditor๋ฅผ ํตํด ํธ์ง๋ ์ผ๊ธฐ์ ๋ด์ฉ์ ํ๋ฉด์ ์ค์๊ฐ์ผ๋ก ํ์๋ฉ๋๋ค. ์์ฑ ์ธ์๊ณผ ํ ์คํธ ๋ณํ์ AVAudioEngine์ ์ฌ์ฉํด ์ค์๊ฐ์ผ๋ก ์ฒ๋ฆฌ๋ฉ๋๋ค.
- STT๊ธฐ๋ฅ์ ์ ํ์ฑ๊ณผ ์์ง ์ต์ ํ
- ์ฒ์์๋ STT(์์ฑ ํ ์คํธ ๋ณํ) ๊ธฐ๋ฅ์ ์ ํ๋์ ์์ง์ ์ด๋ป๊ฒ ์ต์ ํํ ์ง ์ ๋ชฐ๋์ต๋๋ค. ์์ฑ ์ธ์์์ ๋ฐ์ํ๋ ์ค๋ฅ์ ์ ํ๋ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด SpeechRecognizer์ ์ธ์ด ์ค์ ์ ์ฌ์ฉ์์ ๋๋ฐ์ด์ค์ ๋ง์ถฐ ๋์ ์ผ๋ก ๋ณ๊ฒฝํ๊ณ , ์์ฑ ์ธ์ ๊ฒฐ๊ณผ๊ฐ ์ค์๊ฐ์ผ๋ก ๋ฐ์๋๋๋ก ๊ฐ์ ํ์ต๋๋ค.
- ๋ํ, ๋๋ถ๋ถ์ ์ค๋์ค ์ํํธ์จ์ด๋ 32kHz, 24kHz, 16kHz ๋๋ 12kHz๋ฅผ ์ฌ์ฉํ๋ค๋ ๊ฒ์ ์ฐพ์์ต๋๋ค. ๊ทธ ์ด์์ CD ์ค๋์ค์ ํ์ค์ด๋ผ๊ณ ํ์ฌ ํ๋ฒ ์์ฑ ํ์ผ์ ํฌ๊ธฐ์ ์์ง ์ฌ์ด์ ๊ท ํ์ ๋ง์ถ๊ธฐ ์ํด ๋ค์ํ ์ํ ๋ ์ดํธ๋ก ํ ์คํธ๋ฅผ ์งํํ์ต๋๋ค. 8kHz ๋ถํฐ ์คํํ ๊ฒฐ๊ณผ 12kHz์๋ถํฐ ์ถฉ๋ถํ ์ฌ์ฉ์๋ค์ด ์์๋ฃ๊ธฐ ์ฌ์ด ์์ง์ ์ ๊ณตํ์ต๋๋ค.(์ฉ๋ ํฌ๊ธฐ๋ ๊ณ ๋ ค)
- ๋
น์ ์๊ฐ ์ด๊ณผ์ STT๊ธฐ๋ฅ ์ค๋จ ์๋๋ ํ์
- ์์ฑ ๋ น์ ๊ธฐ๋ฅ์ด ์ต๋ 3๋ถ์ผ๋ก ์ ํ๋์ด์์ง๋ง, 3๋ถ์ด ์ง๋๋ STT๋ ๊ณ์ ์คํ๋๊ณ ์์์ต๋๋ค. ์ฆ ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์์ฑ์ธ์์ ์งํ๋๊ณ ์์์ต๋๋ค.
- ํด๊ฒฐ๊ณผ์
- ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ์ํด ๋จผ์ 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๋ถ์ด ์ง๋ ์ ์์ฑ ๋ น์ ๋ฐ STT ๊ธฐ๋ฅ์ด ๋์์ ์ข ๋ฃ๋์์ต๋๋ค.
- ์ฌ์ฉ์๊ฐ ์์ฑ์ผ๋ก ์ผ๊ธฐ๋ฅผ ์์ฑํ๊ณ ์ ์ฅํ ๋, ํด๋น ์ผ๊ธฐ์ ํ ์คํธ ๋ฐ์ดํฐ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๊ฐ์ ๋ถ์ API๋ฅผ ํธ์ถํฉ๋๋ค. ๊ฐ์ ๋ถ์ API๋ ํ ์คํธ์์ ๊ธ์ ๊ณผ ๋ถ์ ์ ๋น์จ์ ์ฐ์ถํ ํ, ์ด๋ฅผ ํตํด ๊ธฐ์จ, ์ฌํ, ๋ถ๋ ธ, ๊ณตํฌ 4๊ฐ์ ์ฃผ์ ๊ฐ์ ์ค ํ๋๋ก ๋์ถํ๋ ๋ฐฉ์์ผ๋ก ์๋ํฉ๋๋ค.
- ๊ฐ์ ๋ฐ์ดํฐ ์๊ฐํ
- ๊ฐ์ ๋ถ์ ๊ฒฐ๊ณผ๋ฅผ ์ด๋ป๊ฒ ํํํ ์ง ๊ณ ๋ฏผ์ด ๋ง์์ต๋๋ค. ์ฒ์์๋ ๋จ์ํ ํ ์คํธ๋ก ํ์ํ ๊น ์๊ฐํ์ผ๋ SwiftUI์ Charts๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํ์ฉํด๋ณด๋ฉด ์ข์๊ฑฐ ๊ฐ์ ์ด ๋ฐฉ์์ ์ฑํํ์ต๋๋ค. ์ด๋ฅผ ํตํด ์ฌ์ฉ์๋ ๊ฐ์ ๋ถ์ ๊ฒฐ๊ณผ๋ฅผ ์๊ฐ์ ์ผ๋ก ํ๋์ ํ์ธ์ด ๊ฐ๋ฅํด ์ฝ๊ฒ ๋ ์ดํดํ ์ ์๋ ์์น๋ผ๊ณ ์๊ฐํ์ต๋๋ค.
- APIํธ์ถ ๊ฒฐ๊ณผ ํ์ฑ๋ฌธ์
- ์ฒ์์๋ ๊ฐ์ ๋ถ์์ ์ํด API๋ฅผ ํธ์ถํ์ ๋, ์๋ต์ด ๋ด๊ฐ ์ํ๋ ๊ตฌ์กฐํ๋ ๋ฐ์ดํฐ๊ฐ ์๋๋ผ, ๋จ์ํ "์ด ๊ธ์ ๊ธ์ ์ด๋ฉฐ ~~%, ๋ถ์ ์ด๋ฉฐ ~~%"์ ๊ฐ์ ์์ ํ ๋ฌธ์ฅ์ผ๋ก ์์ต๋๋ค. ๊ธ์ ๊ณผ ๋ถ์ ์ ๋น์จ์ ์ซ์๋ก, ๊ฐ์ ์ํ๋ ๋ฐ๋ก ์ถ์ถํ๊ณ ์ถ์๋๋ฐ ์ด๋ป๊ฒ ์ฒ๋ฆฌ๋ฅผ ํด์ผํ ์ง ๋ฌธ์ ๊ฐ ์๊ฒผ๋ค.
- ํด๊ฒฐ๊ณผ์
- 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 "๋ถ์ ์คํจ" }
- ๊ฒฐ๊ณผ
- ์ด๋ ๊ฒ ์ ๊ท ํํ์์ ์ฌ์ฉํ์ฌ ์๋ต์ ํ์ฑํ๋๋ ๋ด๊ฐ์ํ๋ ๊ธ์ /๋ถ์ /๊ฐ์ ๋น์จ ๋ฐ ์ํ๋ฅผ ์ ํํ๊ฒ ์ถ์ถ ํ ์ ์์์ต๋๋ค. ์ด๋ฅผ ์ด์ฉํด์ ๋ฐ๋ก UI์ ๋ฐ์ํ ์ ์์์ต๋๋ค.






