🌐 KO

πŸ–ΌοΈ Image Playground

⭐ Difficulty: ⭐⭐ ⏱️ Est. Time: 1h πŸ“‚ Graphics & Media

Apple Intelligence-based AI image generation framework

iOS 18+Apple Intelligence

✨ Image Playground?

Image Playground is an Apple Intelligence-based image generation framework introduced in iOS 18. It creates unique images on-device by combining text prompts, concepts, and styles. It provides system UI and APIs so users can easily create and share images in Messages, Notes, social apps and more. All processing happens on-device to protect privacy.

πŸ’‘ Key Features: AI Image Generation Β· Text Prompts Β· Style Selection (Animation/Illustration/Sketch) Β· On-Device Processing Β· System UI Integration Β· Fast Generation

🎨 1. κΈ°λ³Έ 이미지 생성

Generate images through the ImagePlayground system UI.

ImagePlaygroundView.swift β€” Basic Generation
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. μŠ€νƒ€μΌ 선택

Generate images in various styles including Animation, Illustration, and 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 β€” Messages Integration
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. 컨셉 기반 생성

Combine predefined concepts (themes, places, objects) to generate images.

ConceptGenerator.swift β€” Concept Composition
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. κ³ κΈ‰ μ„€μ •

Configure advanced options like image size, quality, and transformations.

AdvancedSettings.swift β€” Advanced Settings
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 Guidelines

🎯 Practical Usage

πŸ“š Learn More

⚑️ Performance Tips: Image Playground leverages the Neural Engine for fast image generation, though complex prompts may take longer. Show progress to the user during generation, and generate only while the app is in the foreground (background generation isn't supported). Generated images are stored on device with iCloud sync support.
πŸ“ μ°Έκ³ : Image Playground is an Apple Intelligence feature of iOS 18 β€” APIs may change at official release. The above code is based on expected patterns; refer to Apple's official documentation for actual implementation.

πŸ“Ž Apple Official Resources

πŸ“˜ Documentation 🎬 WWDC Sessions