Разбираемся, как использовать 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». Записаться можно по ссылке.