Skip to content

Commit 6dd916d

Browse files
committed
feat: add 10 roadmap integration projects
Complete sample projects demonstrating framework combinations: 1. ๐Ÿ›’ EcommerceApp - SwiftUI + SwiftData + StoreKit 2 + PassKit + CloudKit 2. ๐Ÿšด FitTracker - SwiftUI + HealthKit + MapKit + CoreLocation + ActivityKit 3. ๐Ÿค– AIAssistant - SwiftUI + Foundation Models + App Intents + Vision 4. ๐Ÿ“ธ PhotoLab - SwiftUI + PhotosUI + Core Image + PencilKit + Vision 5. ๐ŸŽฎ SpaceBlast - SwiftUI + SpriteKit + Core Haptics 6. ๐Ÿ’ฌ PeerChat - SwiftUI + MultipeerConnectivity + UserNotifications 7. ๐Ÿ  SmartHome - SwiftUI + Core Bluetooth + Core NFC 8. ๐Ÿ” SecureNotes - SwiftUI + SwiftData + CryptoKit + LocalAuthentication 9. ๐Ÿ—บ๏ธ TravelGuide - SwiftUI + MapKit + WeatherKit + EventKit 10. ๐Ÿ“„ DocReader - SwiftUI + PDFKit + Vision + VisionKit Each project includes: - Complete SwiftUI app structure - Framework integration examples - README with learning points
1 parent 71eb9cd commit 6dd916d

56 files changed

Lines changed: 6495 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import SwiftUI
2+
3+
@main
4+
struct AIAssistantApp: App {
5+
var body: some Scene {
6+
WindowGroup {
7+
ContentView()
8+
}
9+
}
10+
}
11+
12+
struct ContentView: View {
13+
@State private var selectedTab = 0
14+
15+
var body: some View {
16+
TabView(selection: $selectedTab) {
17+
ChatView()
18+
.tabItem {
19+
Label("์ฑ„ํŒ…", systemImage: "bubble.left.and.bubble.right")
20+
}
21+
.tag(0)
22+
23+
ImageAnalysisView()
24+
.tabItem {
25+
Label("์ด๋ฏธ์ง€ ๋ถ„์„", systemImage: "photo.on.rectangle.angled")
26+
}
27+
.tag(1)
28+
29+
SettingsView()
30+
.tabItem {
31+
Label("์„ค์ •", systemImage: "gear")
32+
}
33+
.tag(2)
34+
}
35+
}
36+
}
37+
38+
#Preview {
39+
ContentView()
40+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import AppIntents
2+
import FoundationModels
3+
4+
// MARK: - Ask AI Intent
5+
struct AskAIIntent: AppIntent {
6+
static var title: LocalizedStringResource = "AI์—๊ฒŒ ์งˆ๋ฌธํ•˜๊ธฐ"
7+
static var description = IntentDescription("AI ์–ด์‹œ์Šคํ„ดํŠธ์—๊ฒŒ ์งˆ๋ฌธํ•ฉ๋‹ˆ๋‹ค.")
8+
9+
@Parameter(title: "์งˆ๋ฌธ", requestValueDialog: "๋ฌด์—‡์„ ๋ฌผ์–ด๋ณผ๊นŒ์š”?")
10+
var question: String
11+
12+
static var parameterSummary: some ParameterSummary {
13+
Summary("AI์—๊ฒŒ \(\.$question) ๋ฌผ์–ด๋ณด๊ธฐ")
14+
}
15+
16+
func perform() async throws -> some IntentResult & ReturnsValue<String> & ProvidesDialog {
17+
// Foundation Models ์‚ฌ์šฉ
18+
let session = LanguageModelSession()
19+
20+
do {
21+
let response = try await session.respond(to: question)
22+
return .result(
23+
value: response.content,
24+
dialog: IntentDialog(stringLiteral: response.content)
25+
)
26+
} catch {
27+
return .result(
28+
value: "์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค, ๋‹ต๋ณ€์„ ์ƒ์„ฑํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.",
29+
dialog: "AI ์‘๋‹ต ์ƒ์„ฑ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค."
30+
)
31+
}
32+
}
33+
34+
// Siri ์ œ์•ˆ
35+
static var openAppWhenRun: Bool = false
36+
}
37+
38+
// MARK: - App Shortcuts Provider
39+
struct AIShortcutsProvider: AppShortcutsProvider {
40+
static var appShortcuts: [AppShortcut] {
41+
AppShortcut(
42+
intent: AskAIIntent(),
43+
phrases: [
44+
"AIํ•œํ…Œ ๋ฌผ์–ด๋ด \(.applicationName)",
45+
"\(.applicationName)์—์„œ \(\.$question) ๋ฌผ์–ด๋ด",
46+
"AI ์–ด์‹œ์Šคํ„ดํŠธํ•œํ…Œ ์งˆ๋ฌธํ•ด์ค˜ \(.applicationName)"
47+
],
48+
shortTitle: "AI์—๊ฒŒ ์งˆ๋ฌธ",
49+
systemImageName: "sparkles"
50+
)
51+
}
52+
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import Foundation
2+
import FoundationModels
3+
4+
@Observable
5+
final class AIManager {
6+
private var session: LanguageModelSession?
7+
8+
private(set) var isGenerating = false
9+
private(set) var currentResponse = ""
10+
private(set) var isAvailable = false
11+
12+
init() {
13+
Task {
14+
await checkAvailability()
15+
}
16+
}
17+
18+
// MARK: - Availability Check
19+
@MainActor
20+
func checkAvailability() async {
21+
let status = SystemLanguageModel.default.availability
22+
23+
switch status {
24+
case .available:
25+
isAvailable = true
26+
case .unavailable:
27+
isAvailable = false
28+
@unknown default:
29+
isAvailable = false
30+
}
31+
}
32+
33+
// MARK: - Create Session
34+
@MainActor
35+
func createSession(systemPrompt: String = "๋‹น์‹ ์€ ์นœ์ ˆํ•˜๊ณ  ๋„์›€์ด ๋˜๋Š” AI ์–ด์‹œ์Šคํ„ดํŠธ์ž…๋‹ˆ๋‹ค. ํ•œ๊ตญ์–ด๋กœ ๋‹ต๋ณ€ํ•ด์ฃผ์„ธ์š”.") {
36+
let instructions = SystemLanguageModel.Instructions(systemPrompt)
37+
session = LanguageModelSession(instructions: instructions)
38+
}
39+
40+
// MARK: - Send Message (Streaming)
41+
@MainActor
42+
func sendMessage(_ text: String) async throws -> String {
43+
guard isAvailable else {
44+
throw AIError.notAvailable
45+
}
46+
47+
if session == nil {
48+
createSession()
49+
}
50+
51+
guard let session else {
52+
throw AIError.sessionNotCreated
53+
}
54+
55+
isGenerating = true
56+
currentResponse = ""
57+
58+
defer { isGenerating = false }
59+
60+
// ์ŠคํŠธ๋ฆฌ๋ฐ ์‘๋‹ต
61+
let stream = session.streamResponse(to: text)
62+
63+
for try await partial in stream {
64+
currentResponse = partial.content
65+
}
66+
67+
return currentResponse
68+
}
69+
70+
// MARK: - Send Message (Non-streaming)
71+
@MainActor
72+
func sendMessageSync(_ text: String) async throws -> String {
73+
guard isAvailable else {
74+
throw AIError.notAvailable
75+
}
76+
77+
if session == nil {
78+
createSession()
79+
}
80+
81+
guard let session else {
82+
throw AIError.sessionNotCreated
83+
}
84+
85+
isGenerating = true
86+
defer { isGenerating = false }
87+
88+
let response = try await session.respond(to: text)
89+
return response.content
90+
}
91+
92+
// MARK: - Reset Session
93+
func resetSession() {
94+
session = nil
95+
}
96+
97+
enum AIError: LocalizedError {
98+
case notAvailable
99+
case sessionNotCreated
100+
101+
var errorDescription: String? {
102+
switch self {
103+
case .notAvailable:
104+
return "AI ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. iOS 26 ์ด์ƒ ๋ฐ Apple Silicon์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค."
105+
case .sessionNotCreated:
106+
return "AI ์„ธ์…˜์„ ์ƒ์„ฑํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."
107+
}
108+
}
109+
}
110+
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import Foundation
2+
import Vision
3+
import UIKit
4+
5+
@Observable
6+
final class VisionManager {
7+
private(set) var recognizedText = ""
8+
private(set) var detectedObjects: [DetectedObject] = []
9+
private(set) var isProcessing = false
10+
11+
struct DetectedObject: Identifiable {
12+
let id = UUID()
13+
let label: String
14+
let confidence: Float
15+
let boundingBox: CGRect
16+
}
17+
18+
// MARK: - Text Recognition (OCR)
19+
@MainActor
20+
func recognizeText(from image: UIImage) async throws -> String {
21+
guard let cgImage = image.cgImage else {
22+
throw VisionError.invalidImage
23+
}
24+
25+
isProcessing = true
26+
defer { isProcessing = false }
27+
28+
return try await withCheckedThrowingContinuation { continuation in
29+
let request = VNRecognizeTextRequest { request, error in
30+
if let error {
31+
continuation.resume(throwing: error)
32+
return
33+
}
34+
35+
let observations = request.results as? [VNRecognizedTextObservation] ?? []
36+
let text = observations.compactMap { $0.topCandidates(1).first?.string }.joined(separator: "\n")
37+
38+
continuation.resume(returning: text)
39+
}
40+
41+
// ํ•œ๊ตญ์–ด + ์˜์–ด ์ธ์‹
42+
request.recognitionLanguages = ["ko-KR", "en-US"]
43+
request.recognitionLevel = .accurate
44+
45+
let handler = VNImageRequestHandler(cgImage: cgImage)
46+
47+
do {
48+
try handler.perform([request])
49+
} catch {
50+
continuation.resume(throwing: error)
51+
}
52+
}
53+
}
54+
55+
// MARK: - Image Classification
56+
@MainActor
57+
func classifyImage(_ image: UIImage) async throws -> [DetectedObject] {
58+
guard let cgImage = image.cgImage else {
59+
throw VisionError.invalidImage
60+
}
61+
62+
isProcessing = true
63+
defer { isProcessing = false }
64+
65+
return try await withCheckedThrowingContinuation { continuation in
66+
let request = VNClassifyImageRequest { request, error in
67+
if let error {
68+
continuation.resume(throwing: error)
69+
return
70+
}
71+
72+
let observations = request.results as? [VNClassificationObservation] ?? []
73+
let objects = observations.prefix(5).map { observation in
74+
DetectedObject(
75+
label: observation.identifier,
76+
confidence: observation.confidence,
77+
boundingBox: .zero
78+
)
79+
}
80+
81+
continuation.resume(returning: objects)
82+
}
83+
84+
let handler = VNImageRequestHandler(cgImage: cgImage)
85+
86+
do {
87+
try handler.perform([request])
88+
} catch {
89+
continuation.resume(throwing: error)
90+
}
91+
}
92+
}
93+
94+
// MARK: - Face Detection
95+
@MainActor
96+
func detectFaces(in image: UIImage) async throws -> Int {
97+
guard let cgImage = image.cgImage else {
98+
throw VisionError.invalidImage
99+
}
100+
101+
isProcessing = true
102+
defer { isProcessing = false }
103+
104+
return try await withCheckedThrowingContinuation { continuation in
105+
let request = VNDetectFaceRectanglesRequest { request, error in
106+
if let error {
107+
continuation.resume(throwing: error)
108+
return
109+
}
110+
111+
let faceCount = request.results?.count ?? 0
112+
continuation.resume(returning: faceCount)
113+
}
114+
115+
let handler = VNImageRequestHandler(cgImage: cgImage)
116+
117+
do {
118+
try handler.perform([request])
119+
} catch {
120+
continuation.resume(throwing: error)
121+
}
122+
}
123+
}
124+
125+
enum VisionError: LocalizedError {
126+
case invalidImage
127+
128+
var errorDescription: String? {
129+
switch self {
130+
case .invalidImage:
131+
return "์ด๋ฏธ์ง€๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."
132+
}
133+
}
134+
}
135+
}

0 commit comments

Comments
ย (0)