--- id: ios-charts-health title: iOS Charts / HealthKit / Spatial category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [ios, charts, healthkit, spatial, vibe-coding] tech_stack: { language: "Swift", applicable_to: ["iOS"] } applied_in: [] aliases: [Swift Charts, HealthKit, ScreenCaptureKit, spatial audio, AVFoundation, health data] --- # iOS Charts / HealthKit / Spatial > iOS 16+ Swift Charts (built-in), HealthKit (sensor data), Spatial Audio (immersive), ScreenCaptureKit (screen recording). ## πŸ“– 핡심 κ°œλ… - Swift Charts: declarative chart. - HealthKit: μ‚¬μš©μž health data (consent). - Spatial Audio: 3D positional sound. - ScreenCaptureKit: macOS / iOS 17+ screen capture. ## πŸ’» μ½”λ“œ νŒ¨ν„΄ ### Swift Charts ```swift import Charts struct SalesData: Identifiable { let id = UUID() let date: Date let amount: Double } struct SalesChart: View { let data: [SalesData] var body: some View { Chart(data) { item in LineMark( x: .value("Date", item.date), y: .value("Amount", item.amount) ) .foregroundStyle(.blue) AreaMark( x: .value("Date", item.date), y: .value("Amount", item.amount) ) .foregroundStyle(.blue.opacity(0.2)) } .frame(height: 300) } } ``` ### Bar / Pie / Scatter ```swift Chart(data) { BarMark(x: .value("Cat", $0.category), y: .value("Sales", $0.sales)) } Chart(data) { SectorMark(angle: .value("Sales", $0.sales)) } Chart(data) { PointMark(x: .value("X", $0.x), y: .value("Y", $0.y)) } ``` ### Mixed ```swift Chart { ForEach(data) { d in BarMark(x: .value("Day", d.day), y: .value("Sales", d.sales)) LineMark(x: .value("Day", d.day), y: .value("Trend", d.trend)) .foregroundStyle(.red) } } ``` ### Interaction (iOS 17+) ```swift @State var selectedDate: Date? Chart(data) { d in LineMark(x: .value("Date", d.date), y: .value("Sales", d.sales)) } .chartXSelection(value: $selectedDate) .overlay { if let date = selectedDate, let item = data.first(where: { $0.date == date }) { Tooltip(item: item) } } ``` ### Customization ```swift Chart(data) { LineMark(...) .interpolationMethod(.catmullRom) .lineStyle(StrokeStyle(lineWidth: 2, dash: [5, 5])) } .chartXAxis { AxisMarks(values: .stride(by: .day)) { value in AxisValueLabel(format: .dateTime.month(.abbreviated)) AxisGridLine() } } .chartYAxis { AxisMarks(position: .leading) } .chartLegend(position: .top, alignment: .leading) ``` ### HealthKit setup ```swift import HealthKit class HealthManager: ObservableObject { let store = HKHealthStore() func request() async throws { let read: Set = [ HKQuantityType(.stepCount), HKQuantityType(.heartRate), HKQuantityType(.activeEnergyBurned), ] try await store.requestAuthorization(toShare: [], read: read) } func steps(for date: Date) async throws -> Double { let type = HKQuantityType(.stepCount) let predicate = HKQuery.predicateForSamples(withStart: date.startOfDay, end: date.endOfDay) return try await withCheckedThrowingContinuation { cont in let query = HKStatisticsQuery(quantityType: type, quantitySamplePredicate: predicate, options: .cumulativeSum) { _, result, error in if let error { cont.resume(throwing: error); return } let steps = result?.sumQuantity()?.doubleValue(for: .count()) ?? 0 cont.resume(returning: steps) } self.store.execute(query) } } } ``` ```xml NSHealthShareUsageDescription We use your step count to show daily activity ``` ### Live workout (HealthKit) ```swift let session = try HKWorkoutSession( healthStore: store, configuration: HKWorkoutConfiguration().apply { $0.activityType = .running $0.locationType = .outdoor } ) let builder = session.associatedWorkoutBuilder() session.startActivity(with: Date()) // Live data session.delegate = self // β†’ didChangeTo, didCollectDataOf // End session.end() let workout = try await builder.finishWorkout() ``` ### Watch + iPhone sync ```swift // Workout κ°€ watch μ—μ„œ μ‹œμž‘, iPhone μ—μ„œ view. // HKWorkoutSession κ°€ μžλ™ sync. ``` ### Spatial Audio (AVFoundation) ```swift import AVFoundation let engine = AVAudioEngine() let player = AVAudioPlayerNode() let env = AVAudioEnvironmentNode() env.position = AVAudio3DPoint(x: 0, y: 0, z: 0) // listener engine.attach(player) engine.attach(env) engine.connect(player, to: env, format: nil) engine.connect(env, to: engine.outputNode, format: env.outputFormat(forBus: 0)) // Source position player.position = AVAudio3DPoint(x: 5, y: 0, z: -10) // 5m right, 10m forward let file = try AVAudioFile(forReading: url) player.scheduleFile(file, at: nil) try engine.start() player.play() ``` ### Music Spatial Audio ```swift let asset = AVAsset(url: musicURL) // Check spatial let mix = AVAudioMix() let params = AVMutableAudioMixInputParameters() params.audioTimePitchAlgorithm = .spectral // Apple Music API κ°€ spatial track ν‘œμ‹œ. ``` ### ScreenCaptureKit (iOS 17+ / macOS) ```swift import ScreenCaptureKit func startScreenRecording() async throws { let content = try await SCShareableContent.current guard let display = content.displays.first else { return } let filter = SCContentFilter(display: display, excludingApplications: [], exceptingWindows: []) let config = SCStreamConfiguration() config.width = display.width * 2 config.height = display.height * 2 config.minimumFrameInterval = CMTime(value: 1, timescale: 60) let stream = SCStream(filter: filter, configuration: config, delegate: self) try stream.addStreamOutput(self, type: .screen, sampleHandlerQueue: .main) try await stream.startCapture() } // Output extension ViewController: SCStreamOutput { func stream(_ stream: SCStream, didOutputSampleBuffer sampleBuffer: CMSampleBuffer, of type: SCStreamOutputType) { // Frame buffer β€” file λ˜λŠ” stream } } ``` β†’ macOS κ°€ κ°€μž₯ 일반. iOS 17+ 도 지원. ### App Intents (Charts + HealthKit) ```swift struct DailyStepsIntent: AppIntent { static var title: LocalizedStringResource = "View Daily Steps" func perform() async throws -> some IntentResult & ProvidesDialog { let steps = try await HealthManager.shared.steps(for: Date()) return .result(dialog: "You walked \(steps) steps today.") } } ``` β†’ Siri / Shortcut κ°€ chart data 호좜. ### Widget + Chart ```swift struct StepsWidget: Widget { var body: some WidgetConfiguration { StaticConfiguration(kind: "steps", provider: StepsProvider()) { entry in VStack { Text("Today") Chart(entry.data) { BarMark(x: .value("Hour", $0.hour), y: .value("Steps", $0.steps)) } } } } } ``` ### Live Activity + Chart ```swift struct WorkoutLiveActivity: Widget { var body: some WidgetConfiguration { ActivityConfiguration(for: WorkoutAttributes.self) { context in HStack { Text("\(context.state.heartRate) bpm") Chart(context.state.recentBpm) { LineMark(x: .value("Time", $0.time), y: .value("BPM", $0.bpm)) } .frame(height: 40) } } } } ``` ### Vision OS Charts ```swift // SwiftUI Charts μžλ™ ν˜Έν™˜ visionOS. // 3D depth μΆ”κ°€ κ°€λŠ₯ (.padding3D, depth modifier). ``` ### Health Connect (Android 비ꡐ) ``` Android: - Health Connect (Google) β€” μ‚¬μš©μž health data - Background read / write - λΉ„μŠ· idea, λ‹€λ₯Έ API iOS HealthKit + Android Health Connect = cross-platform health app. ``` ### CoreMotion (sensor) ```swift import CoreMotion let manager = CMMotionManager() manager.deviceMotionUpdateInterval = 1.0 / 60 // 60 Hz manager.startDeviceMotionUpdates(to: .main) { motion, error in guard let m = motion else { return } let heading = m.attitude.yaw let pitch = m.attitude.pitch let roll = m.attitude.roll } ``` β†’ AR / motion-aware app. ### Privacy / consent (HealthKit) ``` 1. Info.plist usage description. 2. Request μ‹œ μ‚¬μš©μž dialog. 3. κ±°λΆ€ κ°€λŠ₯ β€” graceful handle. 4. Background read = μΆ”κ°€ κΆŒν•œ. 5. Apple Watch κ°€ 별도. ``` ### Data export ```swift let predicate = HKQuery.predicateForSamples(...) let query = HKSampleQuery(sampleType: type, predicate: predicate, limit: 0, sortDescriptors: nil) { _, samples, _ in // CSV / JSON export } ``` β†’ User-controlled data export. ### Anchor query (incremental) ```swift let query = HKAnchoredObjectQuery(type: type, predicate: nil, anchor: lastAnchor, limit: HKObjectQueryNoLimit) { _, samples, _, anchor, _ in // μƒˆ sample 만 self.lastAnchor = anchor } query.updateHandler = { _, samples, _, anchor, _ in // Real-time updates } store.execute(query) ``` β†’ Live updates. ### Apple Music API ```swift import MusicKit let request = MusicCatalogSearchRequest(term: query, types: [Song.self]) let response = try await request.response() for song in response.songs { print(song.title, song.artistName) } ``` β†’ Music app 톡합. ### Live Activities + Sensor ```swift // λ§€ 5초 μ—…λ°μ΄νŠΈ let timer = Timer.scheduledTimer(withTimeInterval: 5, repeats: true) { _ in Task { let heartRate = try await HealthManager.shared.currentHeartRate() let state = WorkoutAttributes.ContentState(heartRate: heartRate) await activity.update(.init(state: state, staleDate: nil)) } } ``` ## πŸ€” μ˜μ‚¬κ²°μ • κΈ°μ€€ | μž‘μ—… | μΆ”μ²œ | |---|---| | Chart | Swift Charts | | Health data | HealthKit | | Workout | HKWorkoutSession | | Screen record | ScreenCaptureKit | | Spatial audio | AVAudioEngine env | | Sensor | CoreMotion | | Music | MusicKit | ## ❌ μ•ˆν‹°νŒ¨ν„΄ - **HealthKit 직접 raw save**: validate + transform. - **Background fetch 빈번**: battery. throttle. - **Privacy description λΉˆμ•½**: deny 자주. - **Chart μ‹€μ‹œκ°„ 큰 dataset**: throttle / aggregate. - **Spatial 단일 source 만**: 의미 X. ν™˜κ²½ + 닀쀑 source. - **App store review μ‹œ fake data**: reject. ## πŸ€– LLM ν™œμš© 힌트 - Swift Charts = built-in. Type-safe. - HealthKit + watch + widget 톡합. - ScreenCaptureKit = modern (iOS 17+). - Spatial audio = environment + position. ## πŸ”— κ΄€λ ¨ λ¬Έμ„œ - [[iOS_App_Intents_Shortcuts]] - [[iOS_watchOS_Patterns]] - [[iOS_Live_Activities]]