Разбираемся, как использовать Volume для создания иммерсивного 3D-опыта в visionOS.
Контент в visionOS-приложениях может быть представлен в виде окон (Windows) или объемных фигур (Volumes), и каждый из этих способов имеет свои отличительные особенности. Как правило, в окнах отображается 2D- или 3D-контент, как, например, медиагалерея в Photos, а объемы идеально подходят для представления иммерсивных 3D-объектов и контента.
Volume (или “объем”) представляет 3D-контент, не занимающий все доступное пространство, который пользователи могут наблюдать в любом удобном для них ракурсе. Объемы служат контейнерами для иммерсивного опыта, позволяя исследовать реалистичные представления 3D-объектов.

Добавление Volume в проект
В этой статье я хочу продемонстрировать вам пару шагов, которые вам необходимо будет совершить в рамках разработки приложения для visionOS с использованием Volume. Мы создадим простое visionOS-приложение, состоящее из PrimaryWindowView, которое по нажатию кнопки открывает SecondaryVolumeView.

Наше путешествие начинается с создания нового проекта в visionOS. Убедитесь, что вы настроили все необходимые конфигурации и присвоили проекту соответствующее имя.
При настройке Immersive Space Renderer (рендерер иммерсивного пространства) выберите опцию None. Хотя Apple и предлагает шаблоны для всех возможных иммерсивных пространств, выбор опции None сейчас позволит нам разобраться в основных концепциях с нуля.

Мы будем использовать файл 3D-модели .usdz — единый, самодостаточный формат файла, который содержит всю необходимую информацию для отображения 3D-объекта или сцены без каких-либо внешних зависимостей.
Перенести файл в Xcode проще простого — нужно перетащить его в основную папку проекта. Не забудьте убедиться, что при появлении окна импорта в Xcode выбрана опция "copy item if needed" (копировать элемент при необходимости).
Для начала создадим новый файл SwiftUI под названием CubeView:
import SwiftUI import RealityKit struct CubeView: View { @State private var angle: Angle = .degrees(0) var body: some View { VStack(spacing: 18.0) { Model3D(named: "GlassCube") { model in switch model { case .empty: ProgressView() case .success(let resolvedModel3D): resolvedModel3D .scaleEffect(0.4) .rotation3DEffect(angle, axis: .x) .rotation3DEffect(angle, axis: .y) .animation(.linear(duration: 18).repeatForever(), value: angle) .onAppear { angle = .degrees(359) } case .failure(let error): Text(error.localizedDescription) @unknown default: EmptyView() } } } } } #Preview { CubeView() }
CubeView использует SwiftUI и RealityKit для создания представления, отображающего вращающийся 3D-куб. Он использует представление Model3D для загрузки 3D-модели с именем GlassCube. Этот куб будет непрерывно вращаться вокруг своих осей x и y.
Изначально превью куба будет отображаться в окне. В дальнейшем мы изменим код так, чтобы куб отображался в Volume.

Прежде чем двигаться дальше, давайте добавим в нашу общую ViewModel переменную secondaryVolumeIsShowing. Эта переменная будет выступать в качестве нового контроллера, определяющего, будет ли отображаться наш вторичный объем (secondaryVolume).
import Foundation @Observable class ViewModel { var secondaryVolumeIsShowing: Bool = false }
Сначала создадим SecondaryVolumeView. Это представление будет служить в качестве вторичного объема, обособленного от первичного окна, предоставляя дополнительное пространство для отображения контента или взаимодействия.
import SwiftUI struct SecondaryVolumeView: View { @Environment(ViewModel.self) private var model var body: some View { ZStack(alignment: .bottom) { CubeView() Text("This is a volume") .padding() .glassBackgroundEffect(in: .capsule) } .onDisappear { model.secondaryVolumeIsShowing.toggle() } } } #Preview { SecondaryVolumeView() .environment(ViewModel()) }
После создания SecondaryVolumeView, мы переключим наше внимание на создание PrimaryWindowView. Это основной интерфейс, который служит главным центром взаимодействия. Нам понадобится простая кнопка, способная открывать/закрывать SecondaryWindowView, который мы только что создали.
import SwiftUI struct PrimaryWindowView: View { @Environment(ViewModel.self) var model @Environment(\.openWindow) private var openWindow @Environment(\.dismissWindow) private var dismissWindow var body: some View { @Bindable var model = model VStack(spacing: 18.0) { Image(systemName: "1.circle.fill") .resizable() .aspectRatio(contentMode: .fit) .frame(width: 100,height: 100) .fontWeight(.light) .padding() Text("this is the primary window") .font(.title) .fontWeight(.light) Toggle("Open the secondary volume", isOn: $model.secondaryVolumeIsShowing) .toggleStyle(.button) .onChange(of: model.secondaryVolumeIsShowing) { _, isShowing in if isShowing { openWindow(id: "secondaryVolume") } else { dismissWindow(id: "secondaryVolume") } } } } } #Preview(windowStyle: .automatic) { PrimaryWindowView() .environment(ViewModel()) }
И последний шаг — обновление структуры, соответствующей протоколу App, файла, автоматически генерируемого Xcode и обеспечивающего точку входа в ваше приложение.
import SwiftUI @main struct ImplementingWindowsAndVolumesApp: App { @State private var model = ViewModel() var body: some Scene { WindowGroup { PrimaryWindowView() .environment(model) } WindowGroup(id: "secondaryVolume") { SecondaryVolumeView() .environment(model) } .windowStyle(.volumetric) .defaultSize(width: 0.1, height: 0.1, depth: 0.1, in: .meters) } }
Мы немного изменим стиль этого окна с помощью .windowStyle(.volumetric). Эта строчка изменит не только внешний вид, но и поведение окна. Вместо того чтобы быть плоским, как большинство обычных окон, это окно будет иметь некоторую глубину и объем, что придает ему 3D-подобный вид.
С помощью .defaultSize(width: 0.1, height: 0.1, depth: 0.1, in: .meters) мы устанавливаем размер этого окна по умолчанию. Мы определяем начальный размер окна в 3D-пространстве.

Вот таким нехитрым образом мы создали полностью готовую структуру для открытия 3D-объектов в Volume в нашем приложении. Следуя этой схеме, вы можете реализовать в своем приложении столько Volume-объемов, сколько вам нужно.
Соображения по поводу объемов
Объемы и окна имеют некоторые общие характеристики, но обладают отличительными чертами, которые и определяют их роль в пространственных интерфейсах.
Объемы содержат 3D-объект с углом обзора 360°.
Объемы не отображают рамки вокруг 3D-объектов.
Объемы предоставляют те же элементы управления для изменения положения или закрытия, что и окна.
В общем пространстве (Shared Space) как объем, так и окно имеют начальное положение, определяемое системой.
Объем также может иметь стеклянный фон.
Окна достаточно универсальны для отображения 3D-элементов, но для демонстрации трехмерного контента предпочтение обычно отдается объемам.
В заключение приглашаем всех желающих на открытую онлайн-встречу «Будущее мобильной разработки на iOS», которая пройдет 20 мая в рамках курса «iOS Developer. Professional». Записаться можно по ссылке.
