๐Ÿ” Visual Intelligence

์นด๋ฉ”๋ผ ๋ฒ„ํŠผ์œผ๋กœ ์„ธ์ƒ์„ ์ดํ•ดํ•˜๋Š” AI ์‹œ๊ฐ ๋ถ„์„

iOS 18+๐Ÿ†• 2024

โœจ Visual Intelligence๋ž€?

Visual Intelligence๋Š” iOS 18์—์„œ ๋„์ž…๋œ ํ˜์‹ ์ ์ธ ์‹œ๊ฐ AI ํ”„๋ ˆ์ž„์›Œํฌ์ž…๋‹ˆ๋‹ค. iPhone์˜ Camera Control ๋ฒ„ํŠผ์„ ๊ธธ๊ฒŒ ๋ˆŒ๋Ÿฌ ์‹ค์‹œ๊ฐ„์œผ๋กœ ์ฃผ๋ณ€ ํ™˜๊ฒฝ์„ ๋ถ„์„ํ•˜๊ณ , ๊ฐ์ฒด ์ธ์‹, ํ…์ŠคํŠธ ์ถ”์ถœ, ์žฅ์†Œ ์ •๋ณด, ์ œํ’ˆ ๊ฒ€์ƒ‰ ๋“ฑ ๋‹ค์–‘ํ•œ ์‹œ๊ฐ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. Apple Intelligence์™€ ํ†ตํ•ฉ๋˜์–ด ์˜จ๋””๋ฐ”์ด์Šค์—์„œ ๋น ๋ฅด๊ณ  ์ •ํ™•ํ•˜๊ฒŒ ์ฒ˜๋ฆฌ๋˜๋ฉฐ, ์‚ฌ์šฉ์ž ํ”„๋ผ์ด๋ฒ„์‹œ๋ฅผ ์™„๋ฒฝํ•˜๊ฒŒ ๋ณดํ˜ธํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ’ก ํ•ต์‹ฌ ๊ธฐ๋Šฅ: ์‹ค์‹œ๊ฐ„ ๊ฐ์ฒด ์ธ์‹ ยท ํ…์ŠคํŠธ ์ถ”์ถœ(OCR) ยท ์žฅ์†Œ/๋žœ๋“œ๋งˆํฌ ์ธ์‹ ยท ์ œํ’ˆ ๊ฒ€์ƒ‰ ยท QR/๋ฐ”์ฝ”๋“œ ์Šค์บ” ยท ๋ฒˆ์—ญ ยท ์˜จ๋””๋ฐ”์ด์Šค ์ฒ˜๋ฆฌ ยท Camera Control ํ†ตํ•ฉ

๐Ÿ“ธ 1. ๊ธฐ๋ณธ ์„ค์ •

Visual Intelligence ์„œ๋น„์Šค๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๊ณ  ๊ถŒํ•œ์„ ์š”์ฒญํ•ฉ๋‹ˆ๋‹ค.

VisualIntelligenceManager.swift โ€” ๊ธฐ๋ณธ ์„ค์ •
import SwiftUI
import AVFoundation

// Visual Intelligence ์„œ๋น„์Šค ๊ด€๋ฆฌ์ž
@Observable
class VisualIntelligenceManager {
    var isAuthorized = false
    var isAnalyzing = false
    var currentResult: AnalysisResult?

    // ์นด๋ฉ”๋ผ ๊ถŒํ•œ ์š”์ฒญ
    func requestAuthorization() async -> Bool {
        let status = await AVCaptureDevice.requestAccess(for: .video)
        await MainActor.run {
            isAuthorized = status
        }
        return status
    }

    // Visual Intelligence ์ง€์› ํ™•์ธ
    func isSupported() -> Bool {
        // iOS 18+ ๋ฐ Camera Control ์ง€์› ๊ธฐ๊ธฐ
        if #available(iOS 18.0, *) {
            return true
        }
        return false
    }
}

// ๋ถ„์„ ๊ฒฐ๊ณผ ํƒ€์ž…
struct AnalysisResult: Identifiable {
    let id = UUID()
    let type: AnalysisType
    let data: Any
    let timestamp = Date()
}

enum AnalysisType {
    case objectDetection
    case textExtraction
    case placeRecognition
    case productSearch
    case barcodeScanning
}

๐Ÿ‘๏ธ 2. ์‹ค์‹œ๊ฐ„ ๊ฐ์ฒด ์ธ์‹

์นด๋ฉ”๋ผ๋ฅผ ํ†ตํ•ด ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ฌผ์ฒด๋ฅผ ๊ฐ์ง€ํ•˜๊ณ  ๋ถ„๋ฅ˜ํ•ฉ๋‹ˆ๋‹ค.

ObjectDetectionView.swift โ€” ๊ฐ์ฒด ์ธ์‹
import SwiftUI
import Vision

struct ObjectDetectionView: View {
    @State private var manager = VisualIntelligenceManager()
    @State private var detectedObjects: [DetectedObject] = []
    @State private var showCamera = false

    var body: some View {
        VStack(spacing: 20) {
            // ์นด๋ฉ”๋ผ ํ”„๋ฆฌ๋ทฐ
            ZStack {
                Rectangle()
                    .fill(Color.black)
                    .aspectRatio(3/4, contentMode: .fit)
                    .cornerRadius(12)

                if manager.isAnalyzing {
                    ProgressView("๋ถ„์„ ์ค‘...")
                        .tint(.white)
                }

                // ๊ฐ์ง€๋œ ๊ฐ์ฒด ์˜ค๋ฒ„๋ ˆ์ด
                ForEach(detectedObjects) { obj in
                    Rectangle()
                        .stroke(Color.green, lineWidth: 2)
                        .frame(width: obj.bounds.width, height: obj.bounds.height)
                        .position(x: obj.bounds.midX, y: obj.bounds.midY)
                        .overlay(alignment: .topLeading) {
                            Text(obj.label)
                                .font(.caption)
                                .padding(4)
                                .background(Color.green)
                                .foregroundStyle(.white)
                                .cornerRadius(4)
                                .offset(x: obj.bounds.minX, y: obj.bounds.minY)
                        }
                }
            }

            // ๊ฐ์ง€๋œ ๊ฐ์ฒด ๋ฆฌ์ŠคํŠธ
            VStack(alignment: .leading, spacing: 12) {
                Text("๊ฐ์ง€๋œ ๊ฐ์ฒด")
                    .font(.headline)

                if detectedObjects.isEmpty {
                    Text("์นด๋ฉ”๋ผ ๋ฒ„ํŠผ์„ ๊ธธ๊ฒŒ ๋ˆŒ๋Ÿฌ ์Šค์บ”์„ ์‹œ์ž‘ํ•˜์„ธ์š”")
                        .font(.subheadline)
                        .foregroundStyle(.secondary)
                } else {
                    ForEach(detectedObjects) { obj in
                        HStack {
                            Image(systemName: obj.icon)
                                .foregroundStyle(.blue)
                            VStack(alignment: .leading) {
                                Text(obj.label)
                                    .font(.subheadline)
                                Text("\(Int(obj.confidence * 100))% ์‹ ๋ขฐ๋„")
                                    .font(.caption)
                                    .foregroundStyle(.secondary)
                            }
                            Spacer()
                            Button("๊ฒ€์ƒ‰") {
                                searchObject(obj)
                            }
                            .buttonStyle(.bordered)
                        }
                        .padding()
                        .background(Color.gray.opacity(0.1))
                        .cornerRadius(8)
                    }
                }
            }
            .padding()
        }
        .task {
            await manager.requestAuthorization()
        }
    }

    func searchObject(_ object: DetectedObject) {
        print("๐Ÿ” ๊ฒ€์ƒ‰: \(object.label)")
    }
}

// ๊ฐ์ง€๋œ ๊ฐ์ฒด ๋ชจ๋ธ
struct DetectedObject: Identifiable {
    let id = UUID()
    let label: String
    let confidence: Double
    let bounds: CGRect

    var icon: String {
        // ๊ฐ์ฒด ํƒ€์ž…์— ๋”ฐ๋ผ ์•„์ด์ฝ˜ ๋ฐ˜ํ™˜
        switch label.lowercased() {
        case let l where l.contains("person"): return "person.fill"
        case let l where l.contains("car"): return "car.fill"
        case let l where l.contains("dog"): return "pawprint.fill"
        case let l where l.contains("cat"): return "cat.fill"
        default: return "cube.fill"
        }
    }
}

๐Ÿ“ 3. ํ…์ŠคํŠธ ์ถ”์ถœ (OCR)

์ด๋ฏธ์ง€ ์† ํ…์ŠคํŠธ๋ฅผ ์ธ์‹ํ•˜๊ณ  ์ถ”์ถœํ•˜์—ฌ ๋ณต์‚ฌ, ๋ฒˆ์—ญ, ๊ฒ€์ƒ‰์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

TextExtractionView.swift โ€” ํ…์ŠคํŠธ ์ถ”์ถœ
import SwiftUI
import Vision

@Observable
class TextExtractionManager {
    var extractedText: [RecognizedText] = []
    var isProcessing = false

    // ํ…์ŠคํŠธ ์ถ”์ถœ (Live Text)
    func extractText(from image: UIImage) async {
        await MainActor.run { isProcessing = true }

        guard let cgImage = image.cgImage else { return }

        let request = VNRecognizeTextRequest { request, error in
            guard error == nil,
                  let observations = request.results as? [VNRecognizedTextObservation]
            else { return }

            let recognizedTexts = observations.compactMap { observation in
                guard let topCandidate = observation.topCandidates(1).first else {
                    return nil
                }

                return RecognizedText(
                    text: topCandidate.string,
                    confidence: topCandidate.confidence,
                    bounds: observation.boundingBox
                )
            }

            Task { @MainActor in
                self.extractedText = recognizedTexts
                self.isProcessing = false
            }
        }

        // ์ •ํ™•๋„ ์„ค์ •
        request.recognitionLevel = .accurate
        request.usesLanguageCorrection = true

        let handler = VNImageRequestHandler(cgImage: cgImage, options: [:])
        try? handler.perform([request])
    }
}

struct RecognizedText: Identifiable {
    let id = UUID()
    let text: String
    let confidence: Float
    let bounds: CGRect
}

struct TextExtractionView: View {
    @State private var manager = TextExtractionManager()
    @State private var selectedImage: UIImage?
    @State private var showImagePicker = false

    var body: some View {
        ScrollView {
            VStack(spacing: 20) {
                // ์ด๋ฏธ์ง€ ์„ ํƒ
                if let selectedImage {
                    Image(uiImage: selectedImage)
                        .resizable()
                        .scaledToFit()
                        .cornerRadius(12)
                } else {
                    Button {
                        showImagePicker = true
                    } label: {
                        VStack {
                            Image(systemName: "photo.badge.plus")
                                .font(.system(size: 50))
                            Text("์ด๋ฏธ์ง€ ์„ ํƒ")
                        }
                        .frame(height: 200)
                        .frame(maxWidth: .infinity)
                        .background(Color.gray.opacity(0.1))
                        .cornerRadius(12)
                    }
                }

                // ์ถ”์ถœ๋œ ํ…์ŠคํŠธ
                if manager.isProcessing {
                    ProgressView("ํ…์ŠคํŠธ ์ถ”์ถœ ์ค‘...")
                        .padding()
                } else if !manager.extractedText.isEmpty {
                    VStack(alignment: .leading, spacing: 12) {
                        Text("์ถ”์ถœ๋œ ํ…์ŠคํŠธ")
                            .font(.headline)

                        ForEach(manager.extractedText) { item in
                            HStack {
                                Text(item.text)
                                    .textSelection(.enabled)

                                Spacer()

                                Menu {
                                    Button {
                                        copyText(item.text)
                                    } label: {
                                        Label("๋ณต์‚ฌ", systemImage: "doc.on.doc")
                                    }

                                    Button {
                                        translateText(item.text)
                                    } label: {
                                        Label("๋ฒˆ์—ญ", systemImage: "translate")
                                    }

                                    Button {
                                        searchText(item.text)
                                    } label: {
                                        Label("๊ฒ€์ƒ‰", systemImage: "magnifyingglass")
                                    }
                                } label: {
                                    Image(systemName: "ellipsis.circle")
                                }
                            }
                            .padding()
                            .background(Color.gray.opacity(0.1))
                            .cornerRadius(8)
                        }
                    }
                }
            }
            .padding()
        }
        .onChange(of: selectedImage) { _, newImage in
            if let newImage {
                Task {
                    await manager.extractText(from: newImage)
                }
            }
        }
    }

    func copyText(_ text: String) {
        UIPasteboard.general.string = text
    }

    func translateText(_ text: String) {
        print("๐ŸŒ ๋ฒˆ์—ญ: \(text)")
    }

    func searchText(_ text: String) {
        print("๐Ÿ” ๊ฒ€์ƒ‰: \(text)")
    }
}

๐Ÿ“ 4. ์žฅ์†Œ ์ธ์‹

๋žœ๋“œ๋งˆํฌ, ๊ฑด๋ฌผ, ์žฅ์†Œ๋ฅผ ์ธ์‹ํ•˜๊ณ  ๊ด€๋ จ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

PlaceRecognitionView.swift โ€” ์žฅ์†Œ ์ธ์‹
import SwiftUI
import CoreLocation
import MapKit

struct RecognizedPlace: Identifiable {
    let id = UUID()
    let name: String
    let category: String
    let address: String
    let coordinate: CLLocationCoordinate2D
    let rating: Double?
    let photoURL: URL?
}

@Observable
class PlaceRecognitionManager {
    var recognizedPlace: RecognizedPlace?
    var isRecognizing = false

    // ์žฅ์†Œ ์ธ์‹
    func recognizePlace(from image: UIImage, location: CLLocation?) async {
        isRecognizing = true

        // ์‹ค์ œ๋กœ๋Š” Vision + Maps API ์‚ฌ์šฉ
        try? await Task.sleep(for: .seconds(1))

        // ์˜ˆ์‹œ ๋ฐ์ดํ„ฐ
        recognizedPlace = RecognizedPlace(
            name: "์• ํ”Œ ํŒŒํฌ",
            category: "๋žœ๋“œ๋งˆํฌ",
            address: "One Apple Park Way, Cupertino, CA",
            coordinate: CLLocationCoordinate2D(latitude: 37.3349, longitude: -122.0090),
            rating: 4.8,
            photoURL: nil
        )

        isRecognizing = false
    }
}

struct PlaceRecognitionView: View {
    @State private var manager = PlaceRecognitionManager()
    @State private var cameraPosition: MapCameraPosition = .automatic

    var body: some View {
        VStack(spacing: 20) {
            // ์นด๋ฉ”๋ผ ๋ทฐ (์‹ค์ œ๋กœ๋Š” AVCaptureSession)
            Rectangle()
                .fill(Color.black)
                .aspectRatio(3/4, contentMode: .fit)
                .cornerRadius(12)
                .overlay {
                    if manager.isRecognizing {
                        ProgressView("์žฅ์†Œ ์ธ์‹ ์ค‘...")
                            .tint(.white)
                    }
                }

            // ์ธ์‹๋œ ์žฅ์†Œ ์ •๋ณด
            if let place = manager.recognizedPlace {
                VStack(alignment: .leading, spacing: 16) {
                    // ์žฅ์†Œ ์ •๋ณด
                    VStack(alignment: .leading, spacing: 8) {
                        HStack {
                            Text(place.name)
                                .font(.title2)
                                .fontWeight(.bold)

                            Spacer()

                            if let rating = place.rating {
                                HStack(spacing: 4) {
                                    Image(systemName: "star.fill")
                                        .foregroundStyle(.yellow)
                                    Text("\(rating, specifier: "%.1f")")
                                        .fontWeight(.semibold)
                                }
                            }
                        }

                        Text(place.category)
                            .font(.subheadline)
                            .foregroundStyle(.secondary)

                        Label(place.address, systemImage: "mappin.circle")
                            .font(.footnote)
                    }
                    .padding()
                    .background(Color.gray.opacity(0.1))
                    .cornerRadius(12)

                    // ์ง€๋„
                    Map(position: $cameraPosition) {
                        Marker(place.name, coordinate: place.coordinate)
                    }
                    .frame(height: 200)
                    .cornerRadius(12)

                    // ์•ก์…˜ ๋ฒ„ํŠผ
                    HStack(spacing: 12) {
                        Button {
                            openInMaps(place)
                        } label: {
                            Label("์ง€๋„์—์„œ ๋ณด๊ธฐ", systemImage: "map")
                                .frame(maxWidth: .infinity)
                        }
                        .buttonStyle(.borderedProminent)

                        Button {
                            sharePlace(place)
                        } label: {
                            Label("๊ณต์œ ", systemImage: "square.and.arrow.up")
                                .frame(maxWidth: .infinity)
                        }
                        .buttonStyle(.bordered)
                    }
                }
                .padding(.horizontal)
            }

            Spacer()
        }
        .padding(.vertical)
    }

    func openInMaps(_ place: RecognizedPlace) {
        let mapItem = MKMapItem(
            placemark: MKPlacemark(coordinate: place.coordinate)
        )
        mapItem.name = place.name
        mapItem.openInMaps()
    }

    func sharePlace(_ place: RecognizedPlace) {
        print("๐Ÿ“ค \(place.name) ๊ณต์œ ")
    }
}

๐Ÿ›๏ธ 5. ์ œํ’ˆ ๊ฒ€์ƒ‰

์ƒํ’ˆ์„ ์ธ์‹ํ•˜๊ณ  ์˜จ๋ผ์ธ์—์„œ ์œ ์‚ฌํ•œ ์ œํ’ˆ์„ ์ฐพ์Šต๋‹ˆ๋‹ค.

ProductSearchView.swift โ€” ์ œํ’ˆ ๊ฒ€์ƒ‰
import SwiftUI

struct Product: Identifiable {
    let id = UUID()
    let name: String
    let price: Decimal
    let brand: String
    let imageURL: URL?
    let productURL: URL
    let similarity: Double // ์œ ์‚ฌ๋„ 0-1
}

@Observable
class ProductSearchManager {
    var searchResults: [Product] = []
    var isSearching = false

    // ์‹œ๊ฐ์  ์œ ์‚ฌ ์ œํ’ˆ ๊ฒ€์ƒ‰
    func searchSimilarProducts(from image: UIImage) async {
        isSearching = true

        // ์‹ค์ œ๋กœ๋Š” Vision + ์‡ผํ•‘ API ์—ฐ๋™
        try? await Task.sleep(for: .seconds(1.5))

        // ์˜ˆ์‹œ ๊ฒฐ๊ณผ
        searchResults = [
            Product(
                name: "๋‚˜์ดํ‚ค ์—์–ด ๋งฅ์Šค",
                price: 189000,
                brand: "Nike",
                imageURL: nil,
                productURL: URL(string: "https://nike.com")!,
                similarity: 0.95
            ),
            Product(
                name: "์•„๋””๋‹ค์Šค ์šธํŠธ๋ผ๋ถ€์ŠคํŠธ",
                price: 219000,
                brand: "Adidas",
                imageURL: nil,
                productURL: URL(string: "https://adidas.com")!,
                similarity: 0.87
            )
        ]

        isSearching = false
    }
}

struct ProductSearchView: View {
    @State private var manager = ProductSearchManager()
    @State private var selectedImage: UIImage?

    var body: some View {
        ScrollView {
            VStack(spacing: 20) {
                // ์Šค์บ”ํ•œ ์ œํ’ˆ ์ด๋ฏธ์ง€
                if let selectedImage {
                    Image(uiImage: selectedImage)
                        .resizable()
                        .scaledToFit()
                        .frame(height: 200)
                        .cornerRadius(12)
                }

                // ๊ฒ€์ƒ‰ ์ค‘
                if manager.isSearching {
                    VStack(spacing: 12) {
                        ProgressView()
                        Text("์œ ์‚ฌ ์ œํ’ˆ ๊ฒ€์ƒ‰ ์ค‘...")
                            .font(.subheadline)
                            .foregroundStyle(.secondary)
                    }
                    .padding()
                }

                // ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ
                if !manager.searchResults.isEmpty {
                    VStack(alignment: .leading, spacing: 16) {
                        Text("์œ ์‚ฌ ์ œํ’ˆ \(manager.searchResults.count)๊ฐœ")
                            .font(.headline)

                        ForEach(manager.searchResults) { product in
                            ProductRow(product: product)
                        }
                    }
                }
            }
            .padding()
        }
    }
}

struct ProductRow: View {
    let product: Product

    var body: some View {
        HStack(spacing: 12) {
            // ์ œํ’ˆ ์ด๋ฏธ์ง€
            RoundedRectangle(cornerRadius: 8)
                .fill(Color.gray.opacity(0.2))
                .frame(width: 80, height: 80)
                .overlay {
                    Image(systemName: "shoeprint.fill")
                        .font(.title)
                        .foregroundStyle(.secondary)
                }

            // ์ œํ’ˆ ์ •๋ณด
            VStack(alignment: .leading, spacing: 4) {
                Text(product.brand)
                    .font(.caption)
                    .foregroundStyle(.secondary)

                Text(product.name)
                    .font(.subheadline)
                    .fontWeight(.medium)

                Text("\(product.price as NSDecimalNumber)์›")
                    .font(.subheadline)
                    .fontWeight(.bold)

                // ์œ ์‚ฌ๋„
                HStack(spacing: 4) {
                    Image(systemName: "checkmark.circle.fill")
                        .font(.caption)
                        .foregroundStyle(.green)
                    Text("\(Int(product.similarity * 100))% ์ผ์น˜")
                        .font(.caption)
                        .foregroundStyle(.secondary)
                }
            }

            Spacer()

            // ๋ฐ”๋กœ๊ฐ€๊ธฐ
            Button {
                UIApplication.shared.open(product.productURL)
            } label: {
                Image(systemName: "arrow.right.circle.fill")
                    .font(.title2)
                    .foregroundStyle(.blue)
            }
        }
        .padding()
        .background(Color.gray.opacity(0.05))
        .cornerRadius(12)
    }
}

๐ŸŽฏ 6. Camera Control ํ†ตํ•ฉ

iPhone 16์˜ Camera Control ๋ฒ„ํŠผ๊ณผ ํ†ตํ•ฉํ•˜์—ฌ ๋น ๋ฅธ ์‹คํ–‰์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

CameraControlIntegration.swift โ€” Camera Control
import SwiftUI

// Camera Control ์ œ์Šค์ฒ˜ ํ•ธ๋“ค๋ง
@Observable
class CameraControlManager {
    var isPressed = false
    var pressStartTime: Date?

    // ๊ธธ๊ฒŒ ๋ˆ„๋ฅด๊ธฐ ๊ฐ์ง€
    func handleLongPress() {
        isPressed = true
        pressStartTime = Date()

        // Visual Intelligence ํ™œ์„ฑํ™”
        activateVisualIntelligence()
    }

    // ๋ฒ„ํŠผ ๋†“๊ธฐ
    func handleRelease() {
        guard let startTime = pressStartTime else { return }

        let duration = Date().timeIntervalSince(startTime)

        if duration > 0.5 {
            // ๊ธด ๋ˆ„๋ฅด๊ธฐ: ํ™”๋ฉด ์บก์ฒ˜ ๋ฐ ๋ถ„์„
            captureAndAnalyze()
        }

        isPressed = false
        pressStartTime = nil
    }

    func activateVisualIntelligence() {
        print("๐Ÿ“ธ Visual Intelligence ํ™œ์„ฑํ™”")
    }

    func captureAndAnalyze() {
        print("๐Ÿ” ํ™”๋ฉด ์บก์ฒ˜ ๋ฐ ๋ถ„์„ ์‹œ์ž‘")
    }
}

struct CameraControlDemoView: View {
    @State private var manager = CameraControlManager()

    var body: some View {
        VStack {
            Text("Camera Control ๋ฒ„ํŠผ์„ ๊ธธ๊ฒŒ ๋ˆ„๋ฅด์„ธ์š”")
                .font(.headline)
                .padding()

            // ์‹œ๋ฎฌ๋ ˆ์ด์…˜์šฉ ๋ฒ„ํŠผ
            Button {
                manager.handleLongPress()

                Task {
                    try? await Task.sleep(for: .seconds(1))
                    manager.handleRelease()
                }
            } label: {
                Circle()
                    .fill(manager.isPressed ? Color.blue : Color.gray)
                    .frame(width: 80, height: 80)
                    .overlay {
                        Image(systemName: "camera.fill")
                            .font(.title)
                            .foregroundStyle(.white)
                    }
            }
        }
    }
}

๐Ÿ’ก HIG ๊ฐ€์ด๋“œ๋ผ์ธ

๐ŸŽฏ ์‹ค์ „ ํ™œ์šฉ

๐Ÿ“š ๋” ์•Œ์•„๋ณด๊ธฐ

โšก๏ธ ์„ฑ๋Šฅ ํŒ: Visual Intelligence๋Š” Neural Engine์„ ํ™œ์šฉํ•˜์—ฌ ์‹ค์‹œ๊ฐ„ ๋ถ„์„์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์นด๋ฉ”๋ผ ํ”„๋ฆฌ๋ทฐ๋Š” 30fps๋กœ ์œ ์ง€ํ•˜๊ณ , ๋ถ„์„์€ ์‚ฌ์šฉ์ž๊ฐ€ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅผ ๋•Œ๋งŒ ์ˆ˜ํ–‰ํ•˜์—ฌ ๋ฐฐํ„ฐ๋ฆฌ๋ฅผ ์ ˆ์•ฝํ•˜์„ธ์š”. ๋„คํŠธ์›Œํฌ ๊ฒ€์ƒ‰์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ ์˜จ๋””๋ฐ”์ด์Šค ๋ถ„์„ ํ›„ ์„ ํƒ์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.