Разбираемся, как использовать Volume для создания иммерсивного 3D-опыта в visionOS.

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

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

Volume в visionOS
Volume в visionOS

Добавление Volume в проект

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

Окно и объем, с которыми мы будем работать
Окно и объем, с которыми мы будем работать

Наше путешествие начинается с создания нового проекта в visionOS. Убедитесь, что вы настроили все необходимые конфигурации и присвоили проекту соответствующее имя.

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

Xcode – Настройка Immersive Space Renderer
Xcode – Настройка Immersive Space Renderer

Мы будем использовать файл 3D-модели .usdz — единый, самодостаточный формат файла, который содержит всю необходимую информацию для отображения 3D-объекта или сцены без каких-либо внешних зависимостей.

GlassCube

GlassCube.usdz5 KB

Перенести файл в 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.

Xcode – Превью Model3D-куба
Xcode – Превью Model3D-куба

Прежде чем двигаться дальше, давайте добавим в нашу общую 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». Записаться можно по ссылке.

Комментарии (0)