Как сделать элементы декора VisionOS видимыми при взгляде на них
Узнайте, как сделать элементы декора VisionOS видимыми при взгляде на них с помощью эффектов наведения и системного определения взгляда вместо данных отслеживания глаз. Полное руководство с практическими примерами кода и стратегиями реализации.
Как сделать так, чтобы орнамент VisionOS оставался видимым, пока пользователь на него смотрит?
У меня есть орнамент в visionOS, который автоматически скрывается. Есть ли способ определить, смотрит ли пользователь на него, чтобы оставить его видимым? В идеале он также должен появляться, когда пользователь смотрит на его местоположение — аналогично тому, как представление вкладок автоматически расширяется и остается расширенным, пока на него смотрят.
Я пробовал использовать @FocusState, но это не сработало. Поскольку Apple не предоставляет данные отслеживания взгляда, какие альтернативные методы существуют для достижения такого поведения?
struct TestModifier: ViewModifier {
@State var showOrnament = true
func body(content: Content) -> some View {
content
.ornament(
visibility: showOrnament ? .visible : .hidden,
attachmentAnchor: .scene(.bottom)
) {
Text("Ornament Content")
}
.task {
try await Task.sleep(for: .seconds(2))
showOrnament = false
}
}
}
Орнаменты visionOS не предоставляют прямой доступ к данным отслеживания взгляда по соображениям конфиденциальности, но вы можете добиться поведения автоматического расширения с помощью эффектов наведения и системного обнаружения взгляда. Платформа автоматически применяет эффекты наведения, когда пользователи смотрят на интерактивные элементы, что вы можете использовать для поддержания видимости орнаментов во время взаимодействия взглядом.
Содержание
- Поведение орнаментов visionOS
- Эффекты наведения для обнаружения взгляда
- Пользовательские эффекты наведения в visionOS 2
- Практические примеры реализации
- Соображения конфиденциальности и ограничения
- Альтернативные подходы
Поведение орнаментов visionOS
Орнаменты visionOS разработаны как ненавязчивые компоненты пользовательского интерфейса, которые предоставляют контекстные элементы управления и информацию, не заслоняя основной контент. По умолчанию орнаменты автоматически скрываются для уменьшения визуального шума, когда они неактивно требуются. Это поведение контролируется параметром visibility в модификаторе .ornament(), который может быть установлен в .visible, .hidden или автоматические состояния.
Ключевое замечание из Документации для разработчиков Apple заключается в том, что орнаменты должны “предоставлять элементы управления и информацию, связанные с окном, не загромождая и не заслоняя содержимое окна”. Эта философия дизайна означает, что система автоматически управляет видимостью на основе шаблонов взаимодействия пользователя.
Эффекты наведения для обнаружения взгляда
Хотя прямые данные отслеживания взгляда недоступны разработчикам, visionOS предоставляет эффекты наведения, которые автоматически активируются, когда пользователи смотрят на интерактивные элементы. Согласно документации Unity PolySpatial, “компонент VisionOSHoverEffect предоставляет подсказку платформе применить системный эффект наведения, когда пользователь наводит курсор на этот объект. Обычно это используется для визуальной подсказки пользователю, что объект интерактивен. Этот эффект активируется взглядом или касанием руки.”
Вы можете использовать это системное обнаружение взгляда, сделав содержимое орнамента интерактивным:
struct GazeResponsiveOrnament: ViewModifier {
@State private var isHovered = false
func body(content: Content) -> some View {
content
.ornament(
visibility: isHovered ? .visible : .automatic,
attachmentAnchor: .scene(.bottom)
) {
// Делаем содержимое орнамента интерактивным для активации наведения
Button(action: {}) {
Text("Содержимое орнамента")
.padding()
.background(Color.blue.opacity(0.3))
.cornerRadius(8)
}
.hoverEffect(.lift) // Система отреагирует на взгляд
.onHover { hovering in
isHovered = hovering
}
}
}
}
Пользовательские эффекты наведения в visionOS 2
В visionOS 2 был представлен мощный API пользовательских эффектов наведения, который позволяет разработчикам создавать сложные эффекты наведения, реагирующие на взгляд пользователя. Как упоминалось в сессии WWDC, “пользовательские эффекты наведения могут применяться к представлениям SwiftUI в любом месте вашего приложения, включая орнаменты и вложения Reality View”.
Ключевое преимущество заключается в том, что эти пользовательские эффекты могут быть “предварительно вычислены при запуске представления, предотвращая отслеживание взгляда для защиты конфиденциальности пользователя”, при этом все еще обеспечивая необходимую визуальную обратную связь:
struct CustomHoverOrnament: ViewModifier {
@State private var isHovered = false
func body(content: Content) -> some View {
content
.ornament(
visibility: isHovered ? .visible : .automatic,
attachmentAnchor: .scene(.bottom)
) {
VStack {
Text("Интерактивные элементы управления")
.padding()
Button("Действие") { }
.buttonStyle(.borderedProminent)
}
.frame(width: 200)
.background(.regularMaterial, in: RoundedRectangle(cornerRadius: 12))
.customHoverEffect(
.continuous, // Плавная анимация
isHovered: $isHovered,
effect: { isHovering in
// Пользовательский эффект на основе состояния наведения
scaleEffect(isHovering ? 1.05 : 1.0)
.shadow(color: .blue.opacity(isHovering ? 0.3 : 0), radius: isHovering ? 12 : 4)
}
)
}
}
}
Практические примеры реализации
Вот полная реализация, объединяющая обнаружение наведения с автоматическим управлением видимостью:
struct SmartOrnamentModifier: ViewModifier {
@State private var isHovered = false
@State private var isVisible = false
@State private var hideTimer: Timer?
func body(content: Content) -> some View {
content
.ornament(
visibility: isVisible ? .visible : .hidden,
attachmentAnchor: .scene(.bottom)
) {
InteractiveOrnamentContent(
isHovered: $isHovered,
onInteractionStart: { startTimer() },
onInteractionEnd: { stopTimer() }
)
}
.onChange(of: isHovered) { newValue in
if newValue {
showOrnament()
}
}
}
private func showOrnament() {
isVisible = true
stopTimer()
}
private func startTimer() {
hideTimer = Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) { _ in
if !isHovered {
hideOrnament()
}
}
}
private func stopTimer() {
hideTimer?.invalidate()
hideTimer = nil
}
private func hideOrnament() {
isVisible = false
}
}
struct InteractiveOrnamentContent: View {
@Binding var isHovered: Bool
let onInteractionStart: () -> Void
let onInteractionEnd: () -> Void
var body: some View {
VStack(spacing: 12) {
Text("Умные элементы управления")
.font(.headline)
HStack(spacing: 16) {
Button("Опция 1") { }
.buttonStyle(.bordered)
Button("Опция 2") { }
.buttonStyle(.bordered)
}
}
.padding(16)
.background(.regularMaterial, in: RoundedRectangle(cornerRadius: 12))
.shadow(color: .blue.opacity(0.2), radius: 8)
.onHover { hovering in
if hovering && !isHovered {
isHovered = true
onInteractionStart()
} else if !hovering && isHovered {
isHovered = false
onInteractionEnd()
}
}
.customHoverEffect(
.interactive,
isHovered: $isHovered,
effect: { isHovering in
scaleEffect(isHovering ? 1.05 : 1.0)
.animation(.spring(response: 0.3, dampingFraction: 0.7), value: isHovering)
}
)
}
}
// Использование:
struct ContentView: View {
var body: some View {
Text("Основное содержимое")
.modifier(SmartOrnamentModifier())
}
}
Соображения конфиденциальности и ограничения
Важно понимать ограничения, связанные с обнаружением взгляда в visionOS. Как отмечено в Форумах для разработчиков Apple, “Информация о положении глаз/взгляда недоступна для приложений visionOS”. Это ограничение существует для защиты конфиденциальности пользователя.
Системные эффекты наведения работают путем обнаружения, когда взгляд пользователя задерживается на интерактивном элементе, но эта обработка происходит на системном уровне без предоставления необработанных данных взгляда приложениям. Этот подход позволяет обеспечить интуитивное взаимодействие при сохранении конфиденциальности.
Альтернативные подходы
Если эффекты наведения не соответствуют вашим потребностям, рассмотрите эти альтернативные подходы:
1. Видимость на основе жестов
struct GestureResponsiveOrnament: ViewModifier {
@State private var isVisible = false
@State private var tapCount = 0
func body(content: Content) -> some View {
content
.ornament(
visibility: isVisible ? .visible : .hidden,
attachmentAnchor: .scene(.bottom)
) {
VStack {
Text("Нажмите для взаимодействия")
.onTapGesture {
tapCount += 1
isVisible = true
// Автоматическое скрытие после задержки
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
if tapCount == 1 { tapCount = 0 }
isVisible = false
}
}
if tapCount > 1 {
Button("Оставаться видимым") { tapCount = 2 }
}
}
.padding()
}
}
}
2. Контекстно-зависимая видимость
struct ContextAwareOrnament: ViewModifier {
@State private var isVisible = false
@State private var currentActivity: ActivityType = .idle
enum ActivityType {
case idle, selecting, editing, viewing
}
func body(content: Content) -> some View {
content
.onReceive(NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)) { _ in
updateVisibility(for: .viewing)
}
.ornament(
visibility: isVisible ? .visible : .hidden,
attachmentAnchor: .scene(.bottom)
) {
ContextAwareContent(currentActivity: $currentActivity)
}
}
private func updateVisibility(for activity: ActivityType) {
currentActivity = activity
isVisible = shouldShow(for: activity)
}
private func shouldShow(for activity: ActivityType) -> Bool {
switch activity {
case .selecting, .editing: return true
case .viewing: return true // Показывать, когда пользователь активно просматривает
case .idle: return false
}
}
}
3. Интеграция с голосовыми командами
struct VoiceControlledOrnament: ViewModifier {
@State private var isVisible = false
@FocusState private var isListening: Bool
func body(content: Content) -> some View {
content
.ornament(
visibility: isVisible ? .visible : .hidden,
attachmentAnchor: .scene(.bottom)
) {
VStack {
Image(systemName: "mic.fill")
.foregroundColor(isListening ? .red : .blue)
.onTapGesture {
toggleListening()
}
if isListening {
Text("Слушаю...")
.font(.caption)
}
}
.padding()
}
.onAppear {
// Можно интегрировать с системой голосовых команд
setupVoiceCommands()
}
}
private func toggleListening() {
isListening.toggle()
isVisible = isListening
}
private func setupVoiceCommands() {
// Реализация интегрировалась бы с системными голосовыми командами
// для показа/скрытия орнамента на основе голосового ввода
}
}
Источники
- Орнаменты | Документация для разработчиков Apple
- Отслеживание взгляда в VisionOS | Форумы для разработчиков Apple
- Эффекты наведения | PolySpatial visionOS | 2.0.4
- Создание пользовательских эффектов наведения в visionOS - WWDC24 - Apple Developer
- Советы по SwiftUI и эксклюзивные материалы об иммерсивном видео | Medium
- Создание орнаментов в visionOS | Create with Swift
- Функции доступности visionOS | AppleMagazine
Заключение
Чтобы поддерживать видимость орнаментов visionOS, пока пользователи на них смотрят, следует использовать встроенные в систему эффекты наведения, а не пытаться получить прямой доступ к данным отслеживания взгляда. Ключевые выводы:
- Используйте
@FocusStateи эффекты наведения для обнаружения взаимодействия пользователя взглядом с содержимым орнамента - Реализуйте API
CustomHoverEffectв visionOS 2 для создания сложной визуальной обратной связи - Объединяйте обнаружение наведения с автоматическим скрытием на основе таймера для оптимального пользовательского опыта
- Помните, что прямые данные отслеживания взгляда недоступны из-за ограничений конфиденциальности
- Рассмотрите альтернативные подходы, такие как голосовые команды или контекстно-зависимая видимость, если эффекты наведения не соответствуют вашим потребностям
Наиболее эффективное решение объединяет onHover обнаружение с модификатором .customHoverEffect для создания орнаментов, реагирующих на взгляд пользователя, при сохранении системных мер защиты конфиденциальности.