Разработка
Добавляем Image Playground в приложение
Интеграция Apple Image Playground с SwiftUI или UIKit открывает новые возможности для создания динамических визуальных эффектов, интерактивного контента и продвинутых инструментов дизайна.
Помимо приложения для пользователей, разработчикам Image Playground предлагает фреймворк, позволяющий интегрировать генерацию изображений в приложения, встраивая компоненты, вызывающие и представляющие интерфейс Image Playground, доступный как в UIKit, так и в SwiftUI.
Чтобы углубиться в тему, мы реализуем приложение, генерирующее обложки для историй.
1. Начало работы
Подключаем фичу в SwiftUI
Чтобы воспользоваться преимуществами Image Playground, независимо от того, разрабатываете ли вы в UIKit или SwiftUI, импортируйте фреймворк Image Playground.
import ImagePlayground
Во-вторых, обязательно установите минимальное развертывание на 18.1 для iOS и 15.1 для macOS, чтобы избежать ошибок Xcode и иметь возможность создавать приложение только для поддерживаемых операционных систем.
Как вариант, установите атрибуты, чтобы убедиться в наличии Image Playgrounds в операционной системе перед его использованием.
Обработайте в точке входа в приложение ОС устройства:
import SwiftUI @main struct StoryCoverGenerator: App { var body: some Scene { WindowGroup { // 1. The available version if #available(iOS 18.1, macOS 15.1, *) { ContentView() // 2. Fallback on earlier versions } else { Text("Operating System not supported") } } } }
- Убедитесь, что версия ОС не ниже iOS 18.1 или macOS 15.1.
- Обеспечьте резервное представление для устройств с меньшими версиями.
Сделайте то же самое для ContentView
, но теперь в отношении доступности функции Image Playground.
import SwiftUI import ImagePlayground // 1. Define the OS availability @available(iOS 18.1, macOS 15.1, *) struct ContentView: View { // 2. Check the availability of the feature @Environment(\.supportsImagePlayground) private var supportsImagePlayground var body: some View { if supportsImagePlayground { // Shows the UI to use the feature } else { Text("This device does not support Image Generation features.") } } }
- Добавьте атрибут
@available
, чтобы указать компилятору доступность этой структуры в ОС. - Проверьте специальную переменную окружения для обработки отображения фичи в зависимости от типа устройства, чтобы избежать включения компонентом функции на устройствах с подходящей ОС, но не поддерживающих Apple Intelligence.
Подключаем фичу в UIKit
Давайте рассмотрим, как использовать преимущества атрибутов для обработки версии ОС и доступности функций при реализации в UIKit.
Во-первых, создайте UIViewController
для обработки представления, когда функция недоступна.
import UIKit class UnavailableViewController: UIViewController { // Create the UI as you see fit }
Во-вторых, создайте UIViewController
, отвечающий за генерацию обложки истории, и установите атрибуты доступности ОС.
import UIKit import ImagePlayground // The os availability attributes @available(iOS 18.1,macOS 15.1, *) // The view controller responsible for the Story Cover Generation class StoryCoverGeneratorViewController: UIViewController { ... }
Теперь мы готовы обработать сцену, чтобы представить ее в соответствии с доступностью функции.
import UIKit import ImagePlayground class SceneDelegate: UIResponder, UIWindowSceneDelegate { func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let _ = (scene as? UIWindowScene) else { return } // 1. The view controller let vc: UIViewController // 2. Check the os version and the feature availability if #available(iOS 18.1, macOS 15.1, *), ImagePlaygroundViewController.isAvailable { // a. ViewController for the Story Cover Generator vc = StoryCoverGeneratorViewController() } else { // b. ViewController if unavailable vc = UnavailableViewController() } let navigationController = UINavigationController(rootViewController: vc) self.window?.rootViewController = navigationController self.window?.makeKeyAndVisible() } }
- Объявите переменную, в которой будет храниться контроллер представления.
- Используйте атрибут для проверки версии ОС и доступности функции с помощью
ImagePlaygroundViewController.isAvailable
. - Инициализируйте все в зависимости от того, доступна функция или нет:
- присвойте переменной экземпляр
StoryCoverGeneratorViewController
, если Image Playground доступен; - присвойте экземпляр
UnavailableViewController
, если она недоступна.
- присвойте переменной экземпляр
2. Интеграция Image Playground
Интеграция в приложение SwiftUI
Метод imagePlaygroundSheet(isPresented:concept:sourceImageURL:onCompletion:onCancellation:)
позволяет представить экран, на котором пользователь создает изображения из указанных входных данных.
Она принимает несколько параметров:
isPresented
— ожидается значение Bool, которое включает отображение представления листа, что приводит к открытию интерфейса Image Playground;concept
— строка, описывающая содержимое, в соответствии с которым будет создаваться изображение;sourceImage
ожидает URL-адрес файла в зависимости от того, должен ли промпт содержать начальную визуальную подсказку или нет. После того как эта функция открыта, ее можно переопределить непосредственно из интерфейса Image Playground. Метод позволяет сделать то же самое, используя изображение вместо URL.onCompletion
, замыкание без возвращаемого значения, которое получает сгенерированное изображение с данными параметрами:url
, URL, указывающий на временный файл изображенияonCancellation
, замыкание для обработки отмены процесса создания изображения, когда пользователь выходит из интерфейса создания без выбора изображения. После выполнения этого замыкания система автоматически закрывает экран.
import SwiftUI import ImagePlayground @available(iOS 18.1,macOS 15.1, *) struct ContentView: View { // Enabling the sheet @State var isPresented: Bool = false var body: some View { VStack { // Application UI } // ImagePlayground Sheet Presenter .imagePlaygroundSheet( isPresented: $isPresented, concept: "a red cat with blue gloves" ) { url in // Use the URL } } }
Другой метод, позволяющий описать изображение с помощью большего количества концепций, — это imagePlaygroundSheet(isPresented:concepts:sourceImageURL:onCompletion:onCancellation:)
, который требует объект [ImagePlaygroundConcept]
, коллекцию текстовых элементов, описывающих содержимое, по которым надо сгенерировать изображение.
.imagePlaygroundSheet( isPresented: $isPresented, // Image playground concepts concepts: [ ImagePlaygroundConcept.text("red cat with blue gloves"), ImagePlaygroundConcept.text("big park with a pink river") ]) { url in // Use the URL }
Метод text(_:)
создает концепты изображения из коротких строк, описывающих содержание изображения, которое пользователь хочет сгенерировать.
Имейте в виду, что:
- Метод лучше работает с отдельными словами или короткими предложениями;
- Если строка слишком длинная, она автоматически разбивает ее на более короткие концепции, выбирая только самые важные.
Когда речь идет о более длинных строках, следует использовать метод extracted(from:title:)
, который способен выбрать несколько концептов.
// ImagePlayground Sheet Presenter .imagePlaygroundSheet( isPresented: $isPresented, // Image playground concepts concepts: [ ImagePlaygroundConcept.extracted( from: "A red cat, named Rusty, strutted through the park, with his blue gloves catching everyone’s attention. He waved at a squirrel juggling acorns and joined a duck painting by the pond. Spotting a kite tangled in a tree, Rusty climbed up, saved the kite, and became the park’s unexpected hero.", title: "Rusty, the blue gloves cat") ]) { url in // Use the URL }
Этот метод возвращает объект ImagePlaygroundConcept
и принимает 2 параметра:
- Текст, длинный объект
String
, из которого выбираются концепты для игровой площадки — если его длина не является минимально необходимой для модели, строка может быть использована как есть; - Опциональный заголовок, короткая строка, представляющая текст в сжатом виде, что помогает модели извлекать из него основные понятия.
Теперь давайте объединим все в нашем представлении, чтобы сгенерировать возможные обложки на основе истории, предложенной пользователем.
import SwiftUI import ImagePlayground @available(iOS 18.1, macOS 15.1, *) struct ContentView: View { // Enabling the sheet @State var isPresented: Bool = false var storyPlaceholder: String = "Write your story here..." // URL of image created by AI @State var imageURL: URL? // Story prompt @State var story: String = "" // Determine the availability of the feature. @Environment(\.supportsImagePlayground) private var supportsImagePlayground var body: some View { if supportsImagePlayground { ScrollView { VStack(alignment: .center) { ZStack { // Image loaded from Image Playground generation if let url = imageURL { AsyncImage(url: url) { image in image.resizable() .scaledToFill() .frame(width: 200, height: 300) .clipped() .cornerRadius(8) } placeholder: { ProgressView() } } else { ZStack(alignment: .center){ RoundedRectangle(cornerRadius: 8) .fill(Color.red) .frame(width: 200, height: 300) } if imageURL == nil { Text(""" Story Cover Generator """) .font(.title) .bold() } } } .shadow(color: .black.opacity(0.3), radius: 8, x: 4, y: 4) .padding() VStack(alignment: .leading) { Text("Story:") .font(.title3) .bold() .padding(.bottom, 5) Text("Describe your story in no more than 250 words") .font(.subheadline) .bold() .foregroundStyle(.secondary) .padding(.bottom) TextField(storyPlaceholder, text: $story, axis: .vertical) .lineLimit(15) .padding(5) .background(Color.clear) .overlay( RoundedRectangle(cornerRadius: 12) .stroke(Color.red.opacity(0.5), lineWidth: 1) ) } .padding() } .padding() Button { isPresented = self.checkStoryLength(story: story) } label: { Text("Generate") .foregroundStyle(.red) } // ImagePlayground Sheet Presenter .imagePlaygroundSheet( isPresented: $isPresented, concepts: [ImagePlaygroundConcept.extracted(from: story, title: nil)]) { url in imageURL = url } .padding() } } else { Text("This device does not support Image Generation feature.") .foregroundStyle(.red) } } // Check the lenght of Story private func checkStoryLength(story: String) -> Bool { if story.components(separatedBy: " ").count >= 250 { return false } return true } }
В этом приложении пользователи могут ввести в текстовое поле рассказ длиной до 250 слов и сгенерировать изображение, соответствующее этому рассказу.
Когда пользователь нажимает кнопку Generate, приложение проверяет длину рассказа, а затем отображает интерфейс Image Playground. Условная проверка среды гарантирует, что функция доступна на поддерживаемых устройствах, а в противном случае выводит сообщение об ошибке.
Интеграция в приложение UIKit
Чтобы представить интерфейс Image Playground в UIKit, используйте ImagePlaygroundViewController
. Расширьте класс StoryCoverGeneratorViewController
новым методом, отвечающим за представление листа Image Playground.
extension StoryCoverGeneratorViewController { @IBAction private func openImagePlayground(with story: String) { // 1. Initialize the playground let playground = ImagePlaygroundViewController() // 2. Delegation playground.delegate = self // 2. Set extracted concepts from the story in the playground playground.concepts = [.extracted(from: story, title: nil)] // 3. Present the ImagePlaygroundViewController present(playground, animated: true, completion: nil) } }
Метод openImagePlayground(with:)
, получающий сюжет в виде значения String, работает следующим образом:
- Инициализирует
ImagePlaygroundViewController
; - Устанавливает свойство делегата для перехвата событий жизненного цикла;
- Извлекает концепции из параметра с помощью метода
extracted(from:title:)
и сохраняет их в свойствеconceptsproperty
контроллераImagePlaygroundViewController
; - Показывает
ImagePlaygroundViewController
.
Расширьте класс StoryCoverGeneratorViewController
, чтобы он соответствовал протоколу ImagePlaygroundViewController.Delegate
.
// Conforming to ImagePlaygroundViewController.Delegate extension StoryCoverGeneratorViewController: ImagePlaygroundViewController.Delegate { // 1. The delegate stub returning the generated image to the delegate func imagePlaygroundViewController(_ imagePlaygroundViewController: ImagePlaygroundViewController, didCreateImageAt imageURL: URL) { // 2. Set the image if let image = UIImage(contentsOfFile: imageURL.path) { imageView.image = image } else { print("Error loading image from URL: \(imageURL)") } // 3. Dismiss the sheet dismiss(animated: true, completion: nil) } }
- Реализуйте метод
imagePlaygroundViewController(_:didCreateImageAt:)
, возвращающий делегату URL сгенерированного изображения; - Установите изображение, которое будет отображаться в пользовательском интерфейсе;
- Выйти из экрана Image Playground.
Интеграция в наше приложение Story Cover Generator будет выглядеть следующим образом:
import UIKit import ImagePlayground class StoryCoverGeneratorViewController: UIViewController { // Image Generated View that will display the generated image private lazy var imageView: UIImageView = { ... }() // Cover Label private let coverLabel: UILabel = { ... }() // Headline private let storyLabel: UILabel = { ... }() // Subheadline private let instructionLabel: UILabel = { ... }() // TextField private let storyTextField: UITextField = { ... }() // Generate Button private let generateButton: UIButton = { ... }() // Lifecycle override func viewDidLoad() { super.viewDidLoad() setupUI() } // Setting up the UI: add the subviews and the layout constraints private func setupUI() { ... } // 1. Method triggered on the button tap @objc private func generateButtonTapped() { // 2. Check the length of story input if let storyText = storyTextField.text, storyText.components(separatedBy: " ").count <= 250 { // 3. Present the Image Playground interface openImagePlayground(with: storyText) } else { // 4. Present Error Alert let alert = UIAlertController(title: "Invalid Input", message: "Please limit your story to 250 words.", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) present(alert, animated: true, completion: nil) } } } extension StoryCoverGeneratorViewController: ImagePlaygroundViewController.Delegate { // Initialize and present the Image Playground interface. @IBAction private func openImagePlayground(with story: String) { // Initialize the playground, get set up to be notified of lifecycle events. let playground = ImagePlaygroundViewController() playground.delegate = self // Set extracted concepts from the story in the playground playground.concepts = [.extracted(from: story, title: nil)] present(playground, animated: true, completion: nil) } // The delegate stub returning the generated image to the delegate func imagePlaygroundViewController(_ imagePlaygroundViewController: ImagePlaygroundViewController, didCreateImageAt imageURL: URL) { // Add the image to the image view if let image = UIImage(contentsOfFile: imageURL.path) { imageView.image = image } else { print("Error loading image from URL: \(imageURL)") } dismiss(animated: true, completion: nil) } }
- Создайте метод, который будет срабатывать при нажатии на кнопку «Сгенерировать».
- Проверьте длину вводимого сюжета.
- Представьте интерфейс Image Playground с помощью метода
openImagePlayground(with story:)
. - Выдавайте предупреждение об ошибке, если история длиннее, чем ожидалось.
import UIKit import ImagePlayground class StoryCoverGeneratorViewController: UIViewController { // Properties of the view controller // Generate Button private let generateButton: UIButton = { // Code that creates the button // 1. Add the action to the target button.addTarget(self, action: #selector(generateButtonTapped), for: .touchUpInside) return button }() // Methods of the view controller }
Добавьте этот метод в качестве действия к цели кнопки Generate.
Заключение
Интеграция Apple Image Playground с SwiftUI или UIKit открывает новые возможности для создания динамических визуальных эффектов, интерактивного контента и продвинутых инструментов дизайна. Разработчики могут легко создавать визуально насыщенный опыт, сохраняя при этом бесшовную интеграцию с разными фреймворками в своих приложениях.
На данный момент возможности ограничены, так как Apple будет расширять возможности на базе Apple Intelligence в течение года. Многообещающим является тот факт, что даже если эти возможности ограничены на данный момент, в дальнейшем они будут только улучшаться.
Если в ваших приложениях есть сценарии использования, в которых могут быть использованы возможности генерации изображений, имеет смысл начать готовиться к будущему, в котором качество результатов, создаваемых этими инструментами, будет гораздо выше.