Skip to content

Commit 9566539

Browse files
committed
🎨 Phase 3-5: ARKit, Bluetooth, Foundation Models 튜토리얼 추가
Phase 3: ARKit (4챕터) - ARView 설정, 평면 감지, 3D 모델 배치, 제스처 Phase 4: Core Bluetooth (4챕터) - Central Manager, 스캔, 연결, 읽기/쓰기 Phase 5: Foundation Models (4챕터) 🆕 iOS 26 - 소개, 프롬프트, 스트리밍, Tool Calling
1 parent 8603fb4 commit 9566539

39 files changed

Lines changed: 615 additions & 0 deletions
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<!DOCTYPE html>
2+
<html lang="ko"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
3+
<title>🥽 AR 가구 배치 앱 — HIG Lab</title>
4+
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;700;900&family=JetBrains+Mono&display=swap" rel="stylesheet">
5+
<style>
6+
:root{--bg:#fafafa;--text:#1d1d1f;--accent:#0071e3;--code-bg:#1e1e2e;--border:#d2d2d7}
7+
*{margin:0;padding:0;box-sizing:border-box}body{font-family:'Noto Sans KR',sans-serif;background:var(--bg);color:var(--text);line-height:1.8}
8+
article{max-width:800px;margin:0 auto;padding:48px 24px}
9+
h1{font-size:36px;font-weight:900;margin-bottom:16px}h2{font-size:24px;font-weight:800;margin:40px 0 16px}p{margin-bottom:16px}
10+
.code-block{background:var(--code-bg);border-radius:12px;margin:20px 0;overflow:hidden}
11+
pre.code-body{margin:0;padding:16px;font-family:'JetBrains Mono',monospace;font-size:13px;color:#cdd6f4;white-space:pre;overflow-x:auto}
12+
.kw{color:#cba6f7}.type{color:#89b4fa}.func{color:#89dceb}.str{color:#a6e3a1}.comment{color:#6c7086}
13+
</style></head>
14+
<body>
15+
<article>
16+
<h1>🥽 AR 가구 배치 앱 만들기</h1>
17+
<p>ARKit과 RealityKit으로 가구를 미리 배치해보는 AR 앱을 만듭니다.</p>
18+
19+
<h2>📱 ARView 설정</h2>
20+
<div class="code-block"><pre class="code-body"><span class="kw">let</span> arView = <span class="type">ARView</span>(frame: .zero)
21+
<span class="kw">let</span> config = <span class="type">ARWorldTrackingConfiguration</span>()
22+
config.planeDetection = [.horizontal, .vertical]
23+
arView.session.run(config)</pre></div>
24+
25+
<h2>🪑 3D 모델 배치</h2>
26+
<div class="code-block"><pre class="code-body"><span class="kw">let</span> entity = <span class="kw">try await</span> <span class="type">ModelEntity</span>.load(named: <span class="str">"chair.usdz"</span>)
27+
<span class="kw">let</span> anchor = <span class="type">AnchorEntity</span>(world: position)
28+
anchor.addChild(entity)
29+
arView.scene.addAnchor(anchor)</pre></div>
30+
</article>
31+
</body></html>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<!DOCTYPE html>
2+
<html lang="ko"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
3+
<title>📶 BLE 기기 스캐너 — HIG Lab</title>
4+
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;700;900&family=JetBrains+Mono&display=swap" rel="stylesheet">
5+
<style>
6+
:root{--bg:#fafafa;--text:#1d1d1f;--code-bg:#1e1e2e}*{margin:0;padding:0;box-sizing:border-box}
7+
body{font-family:'Noto Sans KR',sans-serif;background:var(--bg);color:var(--text);line-height:1.8}
8+
article{max-width:800px;margin:0 auto;padding:48px 24px}
9+
h1{font-size:36px;font-weight:900;margin-bottom:16px}h2{font-size:24px;font-weight:800;margin:40px 0 16px}p{margin-bottom:16px}
10+
.code-block{background:var(--code-bg);border-radius:12px;margin:20px 0}
11+
pre.code-body{margin:0;padding:16px;font-family:'JetBrains Mono',monospace;font-size:13px;color:#cdd6f4;white-space:pre;overflow-x:auto}
12+
.kw{color:#cba6f7}.type{color:#89b4fa}.func{color:#89dceb}
13+
</style></head>
14+
<body>
15+
<article>
16+
<h1>📶 BLE 기기 스캐너 만들기</h1>
17+
<p>Core Bluetooth로 주변 BLE 기기를 스캔하고 연결합니다.</p>
18+
19+
<h2>🔌 Central Manager 설정</h2>
20+
<div class="code-block"><pre class="code-body"><span class="kw">let</span> centralManager = <span class="type">CBCentralManager</span>(delegate: <span class="kw">self</span>, queue: <span class="kw">nil</span>)
21+
22+
<span class="kw">func</span> <span class="func">centralManagerDidUpdateState</span>(_ central: <span class="type">CBCentralManager</span>) {
23+
isBluetoothOn = (central.state == .poweredOn)
24+
}</pre></div>
25+
26+
<h2>📡 기기 스캔</h2>
27+
<div class="code-block"><pre class="code-body">centralManager.<span class="func">scanForPeripherals</span>(withServices: <span class="kw">nil</span>, options: <span class="kw">nil</span>)</pre></div>
28+
</article>
29+
</body></html>
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<!DOCTYPE html>
2+
<html lang="ko"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
3+
<title>🤖 온디바이스 AI 챗봇 — HIG Lab</title>
4+
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;700;900&family=JetBrains+Mono&display=swap" rel="stylesheet">
5+
<style>
6+
:root{--bg:#fafafa;--text:#1d1d1f;--code-bg:#1e1e2e;--purple:#af52de}*{margin:0;padding:0;box-sizing:border-box}
7+
body{font-family:'Noto Sans KR',sans-serif;background:var(--bg);color:var(--text);line-height:1.8}
8+
article{max-width:800px;margin:0 auto;padding:48px 24px}
9+
h1{font-size:36px;font-weight:900;margin-bottom:16px}h2{font-size:24px;font-weight:800;margin:40px 0 16px}p{margin-bottom:16px}
10+
.badge{display:inline-block;background:var(--purple);color:#fff;padding:4px 12px;border-radius:20px;font-size:12px;font-weight:700;margin-bottom:16px}
11+
.code-block{background:var(--code-bg);border-radius:12px;margin:20px 0}
12+
pre.code-body{margin:0;padding:16px;font-family:'JetBrains Mono',monospace;font-size:13px;color:#cdd6f4;white-space:pre;overflow-x:auto}
13+
.kw{color:#cba6f7}.type{color:#89b4fa}.func{color:#89dceb}.str{color:#a6e3a1}.comment{color:#6c7086}
14+
</style></head>
15+
<body>
16+
<article>
17+
<span class="badge">🆕 iOS 26</span>
18+
<h1>🤖 온디바이스 AI 챗봇 만들기</h1>
19+
<p>Foundation Models를 활용해 프라이버시를 보호하는 AI 앱을 만듭니다. 모든 처리가 기기 내에서 이루어집니다.</p>
20+
21+
<h2>✨ Foundation Models 특징</h2>
22+
<p>• 프라이버시 보호 (데이터가 기기를 떠나지 않음)<br>
23+
• 오프라인 지원 (네트워크 불필요)<br>
24+
• Swift 네이티브 async/await API</p>
25+
26+
<h2>💬 기본 사용법</h2>
27+
<div class="code-block"><pre class="code-body"><span class="kw">import</span> <span class="type">FoundationModels</span>
28+
29+
<span class="kw">let</span> model = <span class="type">LanguageModel</span>.default
30+
<span class="kw">let</span> result = <span class="kw">try await</span> model.<span class="func">generate</span>(prompt: <span class="str">"안녕하세요"</span>)
31+
print(result.text)</pre></div>
32+
33+
<h2>🔄 스트리밍 응답</h2>
34+
<div class="code-block"><pre class="code-body"><span class="kw">for try await</span> token <span class="kw">in</span> model.<span class="func">streamGenerate</span>(prompt: prompt) {
35+
response += token.text <span class="comment">// 실시간으로 텍스트 추가</span>
36+
}</pre></div>
37+
38+
<h2>🔧 Tool Calling</h2>
39+
<div class="code-block"><pre class="code-body">@<span class="type">Tool</span>
40+
<span class="kw">struct</span> <span class="type">WeatherTool</span> {
41+
<span class="kw">static let</span> description = <span class="str">"현재 날씨를 조회합니다"</span>
42+
43+
@<span class="type">Parameter</span>(description: <span class="str">"도시 이름"</span>)
44+
<span class="kw">var</span> city: <span class="type">String</span>
45+
46+
<span class="kw">func</span> <span class="func">execute</span>() <span class="kw">async</span> -> <span class="type">String</span> {
47+
<span class="kw">return</span> <span class="str">"\(city) 날씨: 맑음, 23°C"</span>
48+
}
49+
}</pre></div>
50+
</article>
51+
</body></html>

tutorials/arkit/Package.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// swift-tools-version: 5.9
2+
import PackageDescription
3+
let package = Package(
4+
name: "HIGARKit",
5+
platforms: [.iOS(.v17), .macOS(.v14)],
6+
products: [.library(name: "HIGARKit", targets: ["HIGARKit"])],
7+
dependencies: [.package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0")],
8+
targets: [.target(name: "HIGARKit")]
9+
)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# ``HIGARKit``
2+
ARKit과 RealityKit을 활용한 증강현실 앱을 만듭니다.
3+
## Topics
4+
### Tutorials
5+
- <doc:tutorials/Table-of-Contents>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import SwiftUI
2+
import RealityKit
3+
4+
struct ARContainerView: UIViewRepresentable {
5+
func makeUIView(context: Context) -> ARView {
6+
let arView = ARView(frame: .zero)
7+
8+
// AR 세션 설정
9+
let config = ARWorldTrackingConfiguration()
10+
config.planeDetection = [.horizontal, .vertical]
11+
arView.session.run(config)
12+
13+
return arView
14+
}
15+
16+
func updateUIView(_ uiView: ARView, context: Context) {}
17+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import ARKit
2+
import RealityKit
3+
4+
class PlaneDetectionDelegate: NSObject, ARSessionDelegate {
5+
func session(_ session: ARSession, didAdd anchors: [ARAnchor]) {
6+
for anchor in anchors {
7+
guard let planeAnchor = anchor as? ARPlaneAnchor else { continue }
8+
9+
// 평면 유형 확인
10+
switch planeAnchor.classification {
11+
case .floor:
12+
print("바닥 감지")
13+
case .wall:
14+
print("벽 감지")
15+
case .table:
16+
print("테이블 감지")
17+
default:
18+
break
19+
}
20+
}
21+
}
22+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import RealityKit
2+
3+
func loadAndPlaceModel(in arView: ARView, at position: SIMD3<Float>) async {
4+
do {
5+
// USDZ 모델 로딩
6+
let entity = try await ModelEntity.load(named: "chair.usdz")
7+
8+
// 앵커 생성
9+
let anchor = AnchorEntity(world: position)
10+
anchor.addChild(entity)
11+
12+
// 씬에 추가
13+
arView.scene.addAnchor(anchor)
14+
15+
// 그림자 활성화
16+
entity.generateCollisionShapes(recursive: true)
17+
arView.installGestures(.all, for: entity)
18+
} catch {
19+
print("모델 로딩 실패: \(error)")
20+
}
21+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import RealityKit
2+
import UIKit
3+
4+
extension ARView {
5+
func setupGestures() {
6+
// 탭 제스처 - 모델 선택/배치
7+
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap))
8+
addGestureRecognizer(tap)
9+
}
10+
11+
@objc func handleTap(_ recognizer: UITapGestureRecognizer) {
12+
let location = recognizer.location(in: self)
13+
14+
// 레이캐스트로 평면 찾기
15+
guard let result = raycast(from: location, allowing: .existingPlaneGeometry, alignment: .horizontal).first else { return }
16+
17+
// 해당 위치에 모델 배치
18+
Task {
19+
await loadAndPlaceModel(in: self, at: result.worldTransform.position)
20+
}
21+
}
22+
}
23+
24+
extension simd_float4x4 {
25+
var position: SIMD3<Float> {
26+
SIMD3(columns.3.x, columns.3.y, columns.3.z)
27+
}
28+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
@Tutorial(time: 15) {
2+
@Intro(title: "ARKit 기초") {
3+
ARKit과 RealityKit의 기본 개념을 학습합니다.
4+
}
5+
@Section(title: "ARView 설정") {
6+
@ContentAndMedia {
7+
RealityKit의 ARView로 AR 경험을 시작합니다.
8+
}
9+
@Steps {
10+
@Step {
11+
ARView를 SwiftUI에서 사용합니다.
12+
@Code(name: "ARContainerView.swift", file: "01-arview.swift")
13+
}
14+
}
15+
}
16+
}

0 commit comments

Comments
 (0)