๐Ÿ–ผ๏ธ Image Playground

Apple Intelligence ๊ธฐ๋ฐ˜ AI ์ด๋ฏธ์ง€ ์ƒ์„ฑ ํ”„๋ ˆ์ž„์›Œํฌ

iOS 18+Apple Intelligence

โœจ Image Playground๋ž€?

Image Playground๋Š” iOS 18์—์„œ ๋„์ž…๋œ Apple Intelligence ๊ธฐ๋ฐ˜ ์ด๋ฏธ์ง€ ์ƒ์„ฑ ํ”„๋ ˆ์ž„์›Œํฌ์ž…๋‹ˆ๋‹ค. ํ…์ŠคํŠธ ํ”„๋กฌํ”„ํŠธ, ๊ฐœ๋…, ์Šคํƒ€์ผ์„ ์กฐํ•ฉํ•˜์—ฌ ์˜จ๋””๋ฐ”์ด์Šค AI๋กœ ๋…์ฐฝ์ ์ธ ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ๋ฉ”์‹œ์ง€ ์•ฑ, ๋…ธํŠธ ์•ฑ, ์†Œ์…œ ์•ฑ ๋“ฑ์—์„œ ์‚ฌ์šฉ์ž๊ฐ€ ์‰ฝ๊ฒŒ ์ด๋ฏธ์ง€๋ฅผ ๋งŒ๋“ค๊ณ  ๊ณต์œ ํ•  ์ˆ˜ ์žˆ๋„๋ก ์‹œ์Šคํ…œ UI์™€ API๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ํ”„๋ผ์ด๋ฒ„์‹œ๋ฅผ ๋ณดํ˜ธํ•˜๋ฉฐ ๋””๋ฐ”์ด์Šค์—์„œ ์ง์ ‘ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค.

๐Ÿ’ก ํ•ต์‹ฌ ๊ธฐ๋Šฅ: AI ์ด๋ฏธ์ง€ ์ƒ์„ฑ ยท ํ…์ŠคํŠธ ํ”„๋กฌํ”„ํŠธ ยท ์Šคํƒ€์ผ ์„ ํƒ (Animation/Illustration/Sketch) ยท ์˜จ๋””๋ฐ”์ด์Šค ์ฒ˜๋ฆฌ ยท ์‹œ์Šคํ…œ UI ํ†ตํ•ฉ ยท ๋น ๋ฅธ ์ƒ์„ฑ

๐ŸŽจ 1. ๊ธฐ๋ณธ ์ด๋ฏธ์ง€ ์ƒ์„ฑ

ImagePlayground ์‹œ์Šคํ…œ UI๋ฅผ ํ†ตํ•ด ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

ImagePlaygroundView.swift โ€” ๊ธฐ๋ณธ ์ƒ์„ฑ
import SwiftUI

// NOTE: Image Playground๋Š” iOS 18์˜ ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ์œผ๋กœ,
// ์•„์ง ๊ณต๊ฐœ API๊ฐ€ ํ™•์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.
// ์•„๋ž˜๋Š” ์˜ˆ์ƒ๋˜๋Š” API ํŒจํ„ด์ž…๋‹ˆ๋‹ค.

struct ImagePlaygroundView: View {
    @State private var generatedImage: Image?
    @State private var showImageGenerator = false
    @State private var prompt = ""

    var body: some View {
        VStack(spacing: 20) {
            // ์ƒ์„ฑ๋œ ์ด๋ฏธ์ง€ ํ‘œ์‹œ
            if let generatedImage {
                generatedImage
                    .resizable()
                    .scaledToFit()
                    .frame(maxHeight: 300)
                    .cornerRadius(12)
                    .shadow(radius: 5)
            } else {
                RoundedRectangle(cornerRadius: 12)
                    .fill(Color.gray.opacity(0.2))
                    .frame(height: 300)
                    .overlay {
                        VStack {
                            Image(systemName: "photo.on.rectangle.angled")
                                .font(.system(size: 50))
                            Text("์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•ด๋ณด์„ธ์š”")
                                .font(.headline)
                        }
                        .foregroundStyle(.secondary)
                    }
            }

            // ํ”„๋กฌํ”„ํŠธ ์ž…๋ ฅ
            TextField("์›ํ•˜๋Š” ์ด๋ฏธ์ง€ ์„ค๋ช…", text: $prompt)
                .textFieldStyle(.roundedBorder)
                .padding(.horizontal)

            // ์ƒ์„ฑ ๋ฒ„ํŠผ
            Button {
                showImageGenerator = true
            } label: {
                Label("์ด๋ฏธ์ง€ ์ƒ์„ฑ", systemImage: "wand.and.stars")
                    .frame(maxWidth: .infinity)
                    .padding()
                    .background(Color.blue)
                    .foregroundStyle(.white)
                    .cornerRadius(12)
            }
            .padding(.horizontal)
            .disabled(prompt.isEmpty)
        }
        .padding()
        // Image Playground ์‹œํŠธ ํ‘œ์‹œ
        // .imagePlayground(isPresented: $showImageGenerator, prompt: prompt) { result in
        //     if case .success(let image) = result {
        //         generatedImage = Image(uiImage: image)
        //     }
        // }
    }
}

// ์˜ˆ์ƒ๋˜๋Š” ์ด๋ฏธ์ง€ ์ƒ์„ฑ ๊ฒฐ๊ณผ ํƒ€์ž…
enum ImageGenerationResult {
    case success(UIImage)
    case failure(Error)
    case cancelled
}

๐ŸŽญ 2. ์Šคํƒ€์ผ ์„ ํƒ

Animation, Illustration, Sketch ๋“ฑ ๋‹ค์–‘ํ•œ ์Šคํƒ€์ผ๋กœ ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

StyleSelector.swift โ€” ์Šคํƒ€์ผ ์„ ํƒ
import SwiftUI

// Image Playground ์Šคํƒ€์ผ ์˜ต์…˜
enum ImagePlaygroundStyle: String, CaseIterable, Identifiable {
    case animation = "์• ๋‹ˆ๋ฉ”์ด์…˜"
    case illustration = "์ผ๋Ÿฌ์ŠคํŠธ"
    case sketch = "์Šค์ผ€์น˜"

    var id: String { rawValue }

    var icon: String {
        switch self {
        case .animation: return "film"
        case .illustration: return "paintbrush"
        case .sketch: return "pencil.tip"
        }
    }

    var description: String {
        switch self {
        case .animation:
            return "3D ์บ๋ฆญํ„ฐ์™€ ์ƒ๋™๊ฐ ์žˆ๋Š” ์• ๋‹ˆ๋ฉ”์ด์…˜ ์Šคํƒ€์ผ"
        case .illustration:
            return "์ƒ์„ธํ•˜๊ณ  ๋‹ค์ฑ„๋กœ์šด ์ผ๋Ÿฌ์ŠคํŠธ๋ ˆ์ด์…˜"
        case .sketch:
            return "๋‹จ์ˆœํ•˜๊ณ  ์˜ˆ์ˆ ์ ์ธ ์Šค์ผ€์น˜"
        }
    }
}

struct StyleSelectorView: View {
    @State private var selectedStyle: ImagePlaygroundStyle = .animation
    @State private var prompt = ""
    @State private var generatedImage: Image?
    @State private var isGenerating = false

    var body: some View {
        ScrollView {
            VStack(spacing: 24) {
                // ์Šคํƒ€์ผ ์„ ํƒ
                VStack(alignment: .leading, spacing: 12) {
                    Text("์Šคํƒ€์ผ ์„ ํƒ")
                        .font(.headline)

                    HStack(spacing: 12) {
                        ForEach(ImagePlaygroundStyle.allCases) { style in
                            Button {
                                selectedStyle = style
                            } label: {
                                VStack(spacing: 8) {
                                    Image(systemName: style.icon)
                                        .font(.title2)
                                    Text(style.rawValue)
                                        .font(.caption)
                                }
                                .frame(maxWidth: .infinity)
                                .padding()
                                .background(
                                    selectedStyle == style ?
                                        Color.blue : Color.gray.opacity(0.2)
                                )
                                .foregroundStyle(
                                    selectedStyle == style ? .white : .primary
                                )
                                .cornerRadius(12)
                            }
                        }
                    }

                    Text(selectedStyle.description)
                        .font(.caption)
                        .foregroundStyle(.secondary)
                }
                .padding(.horizontal)

                // ํ”„๋กฌํ”„ํŠธ ์ž…๋ ฅ
                VStack(alignment: .leading, spacing: 8) {
                    Text("์ด๋ฏธ์ง€ ์„ค๋ช…")
                        .font(.headline)

                    TextField("์˜ˆ: ์šฐ์ฃผ๋ฅผ ์—ฌํ–‰ํ•˜๋Š” ๊ณ ์–‘์ด", text: $prompt, axis: .vertical)
                        .textFieldStyle(.roundedBorder)
                        .lineLimit(3...5)
                }
                .padding(.horizontal)

                // ์ƒ์„ฑ ๋ฒ„ํŠผ
                Button {
                    generateImage()
                } label: {
                    HStack {
                        if isGenerating {
                            ProgressView()
                                .tint(.white)
                            Text("์ƒ์„ฑ ์ค‘...")
                        } else {
                            Image(systemName: "wand.and.stars")
                            Text("์ด๋ฏธ์ง€ ์ƒ์„ฑ")
                        }
                    }
                    .frame(maxWidth: .infinity)
                    .padding()
                    .background(Color.blue)
                    .foregroundStyle(.white)
                    .cornerRadius(12)
                }
                .padding(.horizontal)
                .disabled(prompt.isEmpty || isGenerating)

                // ์ƒ์„ฑ๋œ ์ด๋ฏธ์ง€
                if let generatedImage {
                    VStack(alignment: .leading, spacing: 8) {
                        Text("์ƒ์„ฑ๋œ ์ด๋ฏธ์ง€")
                            .font(.headline)

                        generatedImage
                            .resizable()
                            .scaledToFit()
                            .cornerRadius(12)
                            .shadow(radius: 5)
                    }
                    .padding(.horizontal)
                }
            }
            .padding(.vertical)
        }
    }

    func generateImage() {
        isGenerating = true

        // ์‹ค์ œ ๊ตฌํ˜„์—์„œ๋Š” Image Playground API ํ˜ธ์ถœ
        Task {
            try? await Task.sleep(for: .seconds(2))
            isGenerating = false
            // generatedImage = ...
        }
    }
}

๐Ÿ’ฌ 3. ๋ฉ”์‹œ์ง€ ์•ฑ ํ†ตํ•ฉ

๋ฉ”์‹œ์ง€ ์•ฑ์—์„œ ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ๊ณต์œ ํ•ฉ๋‹ˆ๋‹ค.

MessageExtension.swift โ€” ๋ฉ”์‹œ์ง€ ํ†ตํ•ฉ
import SwiftUI
import Messages

// ๋ฉ”์‹œ์ง€ ํ™•์žฅ์—์„œ Image Playground ์‚ฌ์šฉ
struct MessageImageGeneratorView: View {
    @State private var prompt = ""
    @State private var generatedImage: UIImage?
    @State private var showGenerator = false

    var onImageGenerated: (UIImage) -> Void

    var body: some View {
        VStack {
            TextField("์ด๋ฏธ์ง€ ์„ค๋ช…", text: $prompt)
                .textFieldStyle(.roundedBorder)
                .padding()

            Button("์ƒ์„ฑํ•˜๊ธฐ") {
                showGenerator = true
            }
            .buttonStyle(.borderedProminent)
            .disabled(prompt.isEmpty)

            if let generatedImage {
                Image(uiImage: generatedImage)
                    .resizable()
                    .scaledToFit()
                    .frame(maxHeight: 200)
                    .cornerRadius(12)

                Button("๋ฉ”์‹œ์ง€๋กœ ์ „์†ก") {
                    onImageGenerated(generatedImage)
                }
                .buttonStyle(.borderedProminent)
            }
        }
    }
}

// ๋ฉ”์‹œ์ง€ ์Šคํ‹ฐ์ปค๋กœ ๋ณ€ํ™˜
extension UIImage {
    func toMessageSticker() -> MSSticker? {
        // ์ž„์‹œ ํŒŒ์ผ๋กœ ์ €์žฅ
        let tempURL = FileManager.default.temporaryDirectory
            .appendingPathComponent(UUID().uuidString)
            .appendingPathExtension("png")

        guard let data = self.pngData() else { return nil }

        do {
            try data.write(to: tempURL)
            return try MSSticker(contentsOfFileURL: tempURL, localizedDescription: "Generated")
        } catch {
            print("โŒ ์Šคํ‹ฐ์ปค ์ƒ์„ฑ ์‹คํŒจ: \(error)")
            return nil
        }
    }
}

๐ŸŽ 4. ์ปจ์…‰ ๊ธฐ๋ฐ˜ ์ƒ์„ฑ

์‚ฌ์ „ ์ •์˜๋œ ์ปจ์…‰(ํ…Œ๋งˆ, ์žฅ์†Œ, ์‚ฌ๋ฌผ)์„ ์กฐํ•ฉํ•˜์—ฌ ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

ConceptGenerator.swift โ€” ์ปจ์…‰ ์กฐํ•ฉ
import SwiftUI

// ์ด๋ฏธ์ง€ ์ƒ์„ฑ ์ปจ์…‰
enum ImageConcept: String, CaseIterable {
    // ์บ๋ฆญํ„ฐ
    case cat = "๊ณ ์–‘์ด"
    case dog = "๊ฐ•์•„์ง€"
    case astronaut = "์šฐ์ฃผ์ธ"
    case robot = "๋กœ๋ด‡"

    // ์žฅ์†Œ
    case space = "์šฐ์ฃผ"
    case beach = "ํ•ด๋ณ€"
    case mountain = "์‚ฐ"
    case city = "๋„์‹œ"

    // ํ™œ๋™
    case flying = "๋‚ ์•„๊ฐ€๋Š”"
    case dancing = "์ถค์ถ”๋Š”"
    case reading = "์ฑ… ์ฝ๋Š”"
    case playing = "๋†€๊ณ  ์žˆ๋Š”"
}

struct ConceptBasedGeneratorView: View {
    @State private var selectedCharacter: ImageConcept?
    @State private var selectedPlace: ImageConcept?
    @State private var selectedActivity: ImageConcept?
    @State private var generatedImage: Image?

    let characters: [ImageConcept] = [.cat, .dog, .astronaut, .robot]
    let places: [ImageConcept] = [.space, .beach, .mountain, .city]
    let activities: [ImageConcept] = [.flying, .dancing, .reading, .playing]

    var combinedPrompt: String {
        var parts: [String] = []
        if let activity = selectedActivity {
            parts.append(activity.rawValue)
        }
        if let character = selectedCharacter {
            parts.append(character.rawValue)
        }
        if let place = selectedPlace {
            parts.append("in \(place.rawValue)")
        }
        return parts.joined(separator: " ")
    }

    var body: some View {
        ScrollView {
            VStack(spacing: 24) {
                // ์บ๋ฆญํ„ฐ ์„ ํƒ
                conceptSection(
                    title: "์บ๋ฆญํ„ฐ",
                    concepts: characters,
                    selected: $selectedCharacter
                )

                // ํ™œ๋™ ์„ ํƒ
                conceptSection(
                    title: "ํ™œ๋™",
                    concepts: activities,
                    selected: $selectedActivity
                )

                // ์žฅ์†Œ ์„ ํƒ
                conceptSection(
                    title: "์žฅ์†Œ",
                    concepts: places,
                    selected: $selectedPlace
                )

                // ์กฐํ•ฉ๋œ ํ”„๋กฌํ”„ํŠธ
                VStack(alignment: .leading, spacing: 8) {
                    Text("์ƒ์„ฑ๋  ์ด๋ฏธ์ง€")
                        .font(.headline)

                    Text(combinedPrompt.isEmpty ? "์œ„์—์„œ ์„ ํƒํ•ด์ฃผ์„ธ์š”" : combinedPrompt)
                        .padding()
                        .frame(maxWidth: .infinity, alignment: .leading)
                        .background(Color.gray.opacity(0.1))
                        .cornerRadius(8)
                }
                .padding(.horizontal)

                // ์ƒ์„ฑ ๋ฒ„ํŠผ
                Button {
                    generateImageWithConcepts()
                } label: {
                    Label("์ด๋ฏธ์ง€ ์ƒ์„ฑ", systemImage: "wand.and.stars")
                        .frame(maxWidth: .infinity)
                        .padding()
                        .background(Color.blue)
                        .foregroundStyle(.white)
                        .cornerRadius(12)
                }
                .padding(.horizontal)
                .disabled(combinedPrompt.isEmpty)

                // ๊ฒฐ๊ณผ
                if let generatedImage {
                    generatedImage
                        .resizable()
                        .scaledToFit()
                        .cornerRadius(12)
                        .padding(.horizontal)
                }
            }
            .padding(.vertical)
        }
    }

    func conceptSection(
        title: String,
        concepts: [ImageConcept],
        selected: Binding<ImageConcept?>
    ) -> some View {
        VStack(alignment: .leading, spacing: 12) {
            Text(title)
                .font(.headline)

            ScrollView(.horizontal, showsIndicators: false) {
                HStack(spacing: 12) {
                    ForEach(concepts, id: \.self) { concept in
                        Button {
                            if selected.wrappedValue == concept {
                                selected.wrappedValue = nil
                            } else {
                                selected.wrappedValue = concept
                            }
                        } label: {
                            Text(concept.rawValue)
                                .padding(.horizontal, 16)
                                .padding(.vertical, 8)
                                .background(
                                    selected.wrappedValue == concept ?
                                        Color.blue : Color.gray.opacity(0.2)
                                )
                                .foregroundStyle(
                                    selected.wrappedValue == concept ? .white : .primary
                                )
                                .cornerRadius(20)
                        }
                    }
                }
                .padding(.horizontal)
            }
        }
    }

    func generateImageWithConcepts() {
        // Image Playground API๋กœ ์ด๋ฏธ์ง€ ์ƒ์„ฑ
        print("๐ŸŽจ ํ”„๋กฌํ”„ํŠธ: \(combinedPrompt)")
    }
}

โš™๏ธ 5. ๊ณ ๊ธ‰ ์„ค์ •

์ด๋ฏธ์ง€ ํฌ๊ธฐ, ํ’ˆ์งˆ, ๋ณ€ํ˜• ๋“ฑ ๊ณ ๊ธ‰ ์˜ต์…˜์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.

AdvancedSettings.swift โ€” ๊ณ ๊ธ‰ ์„ค์ •
import SwiftUI

// ์ด๋ฏธ์ง€ ์ƒ์„ฑ ์„ค์ •
struct ImageGenerationSettings {
    var style: ImagePlaygroundStyle = .animation
    var aspectRatio: AspectRatio = .square
    var numberOfVariations: Int = 1
    var seed: Int? = nil // ์ผ๊ด€๋œ ๊ฒฐ๊ณผ๋ฅผ ์œ„ํ•œ ์‹œ๋“œ

    enum AspectRatio: String, CaseIterable {
        case square = "1:1"
        case portrait = "3:4"
        case landscape = "4:3"
    }
}

struct AdvancedGeneratorView: View {
    @State private var settings = ImageGenerationSettings()
    @State private var prompt = ""
    @State private var variations: [Image] = []

    var body: some View {
        Form {
            Section("ํ”„๋กฌํ”„ํŠธ") {
                TextField("์ด๋ฏธ์ง€ ์„ค๋ช…", text: $prompt, axis: .vertical)
                    .lineLimit(3...5)
            }

            Section("์Šคํƒ€์ผ") {
                Picker("์Šคํƒ€์ผ", selection: $settings.style) {
                    ForEach(ImagePlaygroundStyle.allCases) { style in
                        Text(style.rawValue).tag(style)
                    }
                }
            }

            Section("๋น„์œจ") {
                Picker("์ข…ํšก๋น„", selection: $settings.aspectRatio) {
                    ForEach(ImageGenerationSettings.AspectRatio.allCases, id: \.self) { ratio in
                        Text(ratio.rawValue).tag(ratio)
                    }
                }
                .pickerStyle(.segmented)
            }

            Section("๋ณ€ํ˜•") {
                Stepper("๋ณ€ํ˜• ๊ฐœ์ˆ˜: \(settings.numberOfVariations)", value: $settings.numberOfVariations, in: 1...4)
            }

            Section {
                Button("์ƒ์„ฑํ•˜๊ธฐ") {
                    generateVariations()
                }
                .frame(maxWidth: .infinity)
                .disabled(prompt.isEmpty)
            }

            if !variations.isEmpty {
                Section("๊ฒฐ๊ณผ") {
                    ForEach(variations.indices, id: \.self) { index in
                        VStack {
                            variations[index]
                                .resizable()
                                .scaledToFit()
                                .cornerRadius(12)

                            HStack {
                                Button("์ €์žฅ") {
                                    saveImage(at: index)
                                }
                                .buttonStyle(.bordered)

                                Button("๊ณต์œ ") {
                                    shareImage(at: index)
                                }
                                .buttonStyle(.bordered)
                            }
                        }
                    }
                }
            }
        }
        .navigationTitle("๊ณ ๊ธ‰ ์„ค์ •")
    }

    func generateVariations() {
        // Image Playground API๋กœ ์—ฌ๋Ÿฌ ๋ณ€ํ˜• ์ƒ์„ฑ
        print("๐ŸŽจ \(settings.numberOfVariations)๊ฐœ ๋ณ€ํ˜• ์ƒ์„ฑ")
    }

    func saveImage(at index: Int) {
        // ํฌํ†  ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ์ €์žฅ
        print("๐Ÿ’พ ์ด๋ฏธ์ง€ \(index) ์ €์žฅ")
    }

    func shareImage(at index: Int) {
        // ๊ณต์œ  ์‹œํŠธ ํ‘œ์‹œ
        print("๐Ÿ“ค ์ด๋ฏธ์ง€ \(index) ๊ณต์œ ")
    }
}

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

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

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

โšก๏ธ ์„ฑ๋Šฅ ํŒ: Image Playground๋Š” Neural Engine์„ ํ™œ์šฉํ•˜์—ฌ ๋น ๋ฅด๊ฒŒ ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•˜์ง€๋งŒ, ๋ณต์žกํ•œ ํ”„๋กฌํ”„ํŠธ๋Š” ๋” ์˜ค๋ž˜ ๊ฑธ๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ƒ์„ฑ ์ค‘ ์‚ฌ์šฉ์ž์—๊ฒŒ ์ง„ํ–‰ ์ƒํƒœ๋ฅผ ํ‘œ์‹œํ•˜๊ณ , ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ƒ์„ฑ์€ ์ง€์›๋˜์ง€ ์•Š์œผ๋ฏ€๋กœ ์•ฑ์ด ํฌ๊ทธ๋ผ์šด๋“œ์— ์žˆ์„ ๋•Œ๋งŒ ์ƒ์„ฑํ•˜์„ธ์š”. ์ƒ์„ฑ๋œ ์ด๋ฏธ์ง€๋Š” ๋””๋ฐ”์ด์Šค์— ์ €์žฅ๋˜๋ฉฐ iCloud ๋™๊ธฐํ™”๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.
๐Ÿ“ ์ฐธ๊ณ : Image Playground๋Š” iOS 18์˜ Apple Intelligence ๊ธฐ๋Šฅ์œผ๋กœ, ์ •์‹ ์ถœ์‹œ ์‹œ API๊ฐ€ ๋ณ€๊ฒฝ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์œ„ ์ฝ”๋“œ๋Š” ์˜ˆ์ƒ๋˜๋Š” ํŒจํ„ด์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์ž‘์„ฑ๋˜์—ˆ์œผ๋ฉฐ, ์‹ค์ œ ๊ตฌํ˜„์€ Apple์˜ ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”.