
-
SwiftUI 공간 레이아웃 만나보기
SwiftUI를 사용하여 공간 경험을 빌드하는 새로운 도구를 알아보세요. visionOS에서 3D SwiftUI 뷰의 기본 사항을 학습하고, 깊이 정렬로 기존 레이아웃을 사용자 정의하며, 수정자를 사용하여 공간에서 뷰를 회전 및 배치하세요. 공간 컨테이너를 사용하여 동일한 3D 공간에서 뷰를 정렬하여 몰입력이 뛰어나고 매력적인 앱을 생성하는 방법을 알아보세요.
챕터
리소스
- Canyon Crosser: Building a volumetric hike-planning app
- Human Interface Guidelines: Designing for visionOS
관련 비디오
WWDC25
WWDC22
WWDC19
-
비디오 검색…
안녕하세요, “SwiftUI 공간 레이아웃 만나 보기” 세션입니다 저는 SwiftUI 팀의 엔지니어 Trevor입니다 이 세션에서는 SwiftUI로 멋진 공간 경험을 빌드하는 기술을 살펴봅니다
저는 SwiftUI의 새 공간 레이아웃 기능을 활용해 좋아하는 앱인 BOT-anist를 확장했습니다
다양한 기본 요소, 색상, 소재로 재미있는 로봇을 맞춤화할 수 있는 앱이죠 새로 만든 봇으로 나만의 가상 정원을 가꿀 수 있습니다 작은 로봇 만들기가 참 재미있는데 최근에는 제 창작물을 카탈로그화하는 새로운 보기를 작업 중입니다
이제 로봇 맞춤화 외에도 봇을 저장하고 많은 봇을 수집할 수 있습니다
로봇을 탐색할 수 있는 새로운 3D 장면을 보여 드리겠습니다 모든 경험은 SwiftUI로 만든 것입니다
이전에 visionOS에서 3D 경험을 빌드하신 적이 있다면 사용하셨을 수 있는 RealityKit은
3D 앱을 빌드하기 좋은 프레임워크입니다 특히 물리 시뮬레이션처럼 동작이 복잡한 앱에 좋습니다
백그라운드가 SwiftUI라면 이미 아는 선언적 구문으로 빌드하고 싶을 수 있습니다 앱의 모든 곳에 RealityKit 기능이 필요하지 않을 수도 있습니다 이제 visionOS 26에서 SwiftUI의 기존 2D 레이아웃 도구와 아이디어로 3D 앱을 빌드할 수 있습니다
SwiftUI 레이아웃에는 애니메이션, 크기 조정과 상태 관리 지원이 내장되어 있습니다 즉 캐러셀에서 봇을 제거하면 SwiftUI가 나머지 로봇의 위치와 크기를 애니메이션화해 공간 변화를 수용할 수 있습니다
볼륨을 조정하면 캐러셀과 각 로봇의 크기가 자동 조정됩니다 이 자동 로봇 배열을 빌드하는 데 사용한 새 도구를 살펴보겠습니다
그 전에, SwiftUI 레이아웃 시스템의 3D 확장 프로그램은 기존 2D 레이아웃 개념에 기초하죠
SwiftUI 레이아웃 작업이 처음이라면 이 콘텐츠에 앞서 “SwiftUI로 맞춤형 보기 빌드하기”와 “SwiftUI로 맞춤형 레이아웃 작성하기”를 확인하세요 여기서는 visionOS의 3D SwiftUI 보기에 대한 기본 사항과 깊이 정렬로 기존 레이아웃을 맞춤화하는 방법을 다룹니다 레이아웃 시스템에서 보기를 회전하는 새 수정자 rotation3DLayout, 마지막으로 SpatialContainer와 spatialOverlay가 3D 공간에서 보기를 정렬하는 방법입니다
보기와 레이아웃 시스템을 이야기해 보겠습니다
앱의 각 보기에 대해 SwiftUI는 너비, 높이, X 및 Y 위치를 계산합니다
크기 조정이 불가능한 이미지 등 일부 보기는 애셋의 크기에 맞는 고정된 프레임이 있습니다
이 Color와 같은 일부 보기는 유연한 프레임이 있으며 상위 항목이 제공한 모든 공간을 차지합니다
레이아웃은 하위 항목을 최종 프레임으로 작성합니다 이 노란색 VStack 프레임은 사용 가능한 공간과 포함된 하위 항목에 따라 결정됩니다 여기서 높이는 안에 있는 두 이미지 보기의 합입니다 visionOS의 동작 방식은 같지만 보기가 visionOS에서 3D입니다 레이아웃 시스템 동작은 동일한데 2차원이 아니라 3차원에 적용된 것입니다
각 보기의 의미로 너비와 높이 외에 SwiftUI는 깊이 및 Z 위치를 계산합니다 저는 흔히 테두리 수정자로 iOS에서 2D 프레임을 시각화하죠
여기서 자체 debugBorder3D 수정자를 만들어 visionOS에서 3D 프레임을 시각화했습니다 마지막에 이 수정자를 빌드한 방법을 보여 드리겠습니다 오늘 배울 API를 사용했습니다
debugBorder3D는 Model3D가 이미지와 비슷하게 동작함을 보여 주지만 2차원이 아니라 3차원에서는 고정된 너비, 높이, 깊이가 있습니다 모든 보기는 3D지만 일부 보기는 깊이가 없습니다
Image, Color, Text 등 평면 경험 빌드에 사용하는 보기 중 대다수는 깊이를 차지하지 않아 iOS에서와 똑같이 동작합니다
일부 보기는 깊이가 유연한데 Color가 기본 제안되는 사용 가능한 너비와 높이를 차지하는 방식과 동일합니다 visionOS에서 RealityView 같은 특정 보기는 기본 제안된 사용 가능한 모든 깊이를 차지합니다
GeometryReader3D도 크기 조정 동작이 유연하며
크기 조정 가능 수정자가 적용된 Model3D도 마찬가지입니다 이 윈도우의 너비에 맞게 로봇을 엿가락처럼 쭉 늘렸죠 그런데 이 종횡비에서는 얼굴이 좀 길군요 원래 비율로 돌리되 사용 가능한 공간에 맞게 크기를 늘리고 싶습니다
resizable() 외에 새 scaledToFit3D 수정자를 사용하면 로봇이 모델의 종횡비를 유지하면서 사용 가능한 너비, 높이, 깊이에 맞게 커지거나 작아집니다
그런데 이 깊이는 어디서 나왔을까요? 너비, 높이와 마찬가지로 윈도우 콘텐츠는 루트 깊이 제안을 받습니다 크기를 조정할 수 있는 너비, 높이와 달리 이 깊이 제안은 윈도우에 고정되어 있습니다 이 깊이를 벗어나면 시스템에 의해 콘텐츠가 잘릴 수 있습니다
마찬가지로 볼륨은 콘텐츠에 너비, 높이, 깊이를 제안하지만 볼륨에서는 깊이도 조정할 수 있죠 볼륨 또는 윈도우 사용에 대한 자세한 내용은 휴먼 인터페이스 가이드라인의 “visionOS를 위한 디자인”을 확인하세요
일부 보기는 포함된 보기의 깊이 제안을 변경할 수 있습니다 VStack이 하위 보기의 높이를 작성할 때처럼 ZStack은 깊이를 작성합니다 이 ZStack의 깊이는 앞뒤로 배치된 두 로봇을 포함하는 데 필요한 깊이입니다
VStack은 사용 가능한 공간, 하위 항목 수, 하위 항목 유형 등 각종 요소를 기준으로 하위 보기에 다양한 높이를 제안할 수 있습니다 비슷하게 ZStack은 같은 요소를 기준으로 하위 항목에 다양한 깊이를 제안할 수 있습니다 여기서 RealityView는 ZStack에서 로봇을 앞으로 밀어 장면의 사용 가능한 모든 깊이를 채웁니다
기존 레이아웃 유형 및 스택은 실제로 visionOS에서 3D이며 깊이에 대해 합리적인 기본 동작을 적용합니다 이 예에서 HStack은 상위 항목의 깊이 제안을 달성하고 내부의 두 모델이 공간에 딱 맞도록 자체 깊이를 설정합니다
HStack은 또한 기본적으로 두 로봇의 후면을 일렬로 맞추죠
이 개념을 깊이 정렬이라고 합니다 깊이 정렬은 기존 SwiftUI 레이아웃 유형을 맞춤화해 3D 보기 및 깊이를 효과적으로 수용할 수 있는 새로운 도구입니다 수직 또는 수평 정렬을 활용한 적이 있으시다면 익숙할 것입니다 새 볼류메트릭 윈도우를 빌드해 좋아하는 로봇과 각각의 이름 및 설명을 표시하고 싶습니다 먼저 로봇 Model3D의 코드를 업데이트해 재사용하기 좋도록 만들겠습니다
크기에 맞게 조정된 Model3D부터 시작합니다
새 Model3DAsset 유형을 사용하도록 리팩터링합니다 그러면 로봇의 모델을 미리 로드할 수 있습니다 새 ResizableRobotView로 전체를 둘러싸는데, 이 보기는 앱 전체에서 사용할 수 있습니다 일단 debugBorder3D도 제거합니다
이제 ResizableRobotView가 포함된 VStack으로 RobotProfile과 봇의 세부 정보가 적힌 RobotNameCard를 만듭니다
그런데 문제가 있습니다
카드가 VStack 뒤에 있어 내용을 읽기 어렵습니다 그리고 로봇 모델 뒤쪽에 자리해 동떨어져 보입니다
HStack에서 구성을 통해 콘텐츠를 중앙, 상단, 하단 가장자리에 정렬하듯이 visionOS에서 깊이에 따른 보기 정렬 방식을 구성해야 할 수 있습니다
기본적으로 스택 및 레이아웃 유형은 후면 깊이 정렬을 사용하죠 이제 visionOS 26에서 레이아웃 유형에 관계없이 DepthAlignments를 맞춤화할 수 있습니다
VStackLayout을 사용하도록 RobotProfile을 업데이트하겠습니다
그러면 depthAlignment 수정자를 적용할 수 있습니다 .front 정렬을 요청하고요
center 또는 back 가이드를 사용할 수도 있습니다
하지만 로봇 이름 카드가 읽기 쉬우려면 front가 알맞겠죠
이렇게 Zapper Ironheart와 몰랐던 사실을 정리한 지식이 보기 좋게 표시되었습니다
표준 전면, 후면 또는 중앙 깊이 정렬은 해당 세 가지 표준 구성을 원할 때 적합합니다 하지만 이보다 더 복잡한 동작이 필요하다면 어떨까요?
좋아하는 세 로봇을 표시하는 볼륨을 만들고 있는데요 HStack에 로봇 프로필 보기 3개가 있습니다 저는 Greg-gear Mendel을 제일 좋아하므로 이 보기에서 다른 둘보다 두드러져 보였으면 합니다
사실 일종의 깊이 연단을 구상하는 중이었는데요 좋아하는 로봇일수록 저와 거리가 가까운 형태입니다 Robot 1, 2, 3 순으로 가까운 거죠
위에서 보았을 때 이렇게 배치되었으면 합니다 첫 번째 로봇의 후면이 두 번째 로봇의 중앙 그리고 세 번째 로봇의 전면과 깊이가 정렬되어야 합니다 이를 위해 맞춤형 깊이 정렬이 필요합니다
먼저 DepthAlignmentID 프로토콜을 준수하는 새 구조체를 정의합니다
하나의 요구 사항을 구현하는데 바로 이 정렬의 기본값입니다
DepthPodiumAlignment의 기본값으로 전면 정렬 가이드를 사용합니다
그런 다음 이 새 DepthAlignmentID 유형을 사용하는 정적 상수를 깊이 정렬에 정의합니다
이제 각 로봇이 포함된 HStack에서 이 depthPodium 정렬 가이드를 깊이 정렬로 사용할 수 있습니다
이렇게 하면 방금 이 가이드에 지정한 기본값에 따라 모든 로봇의 정면이 정렬됩니다
이제 후행 로봇의 depthPodium 정렬 가이드를 맞춤화하여 이 로봇의 깊이 중심을 이 가이드에 맞춰 정렬합니다
중앙 로봇을 수정하여 후면을 depthPodium 가이드에 맞춥니다
선행 로봇은 계속해서 전면 가이드를 이 정렬의 기본값으로 사용합니다
시뮬레이터에서는 이렇습니다
봇들의 깊이가 엇갈려 있어 누가 봐도 Greg-gear Mendel이 제가 가장 좋아하는 로봇입니다
깊이 정렬은 기존 레이아웃에서 깊이 위치를 조정하고 싶을 때 유용합니다 하지만 좀 더 깊이 중심적인 것을 빌드하려면 어떻게 해야 할까요? 회전 레이아웃은 고급 3D 사용 사례에 유용한 도구입니다 기존의 rotation3DEffect 수정자에 익숙하실 수 있습니다 축을 중심으로 보기를 회전시키는 시각적 효과를 보기에 적용하죠
이 수정자는 기본 회전에 적합합니다
하지만 HStack에 모델을 설명 카드와 함께 배치하고 Z축을 따라 로켓을 90도 회전시키면 카드에 묻혀 볼륨이 부족해집니다
회전 효과 전후에 디버깅 와이어프레임을 적용하면 상황을 이해하기가 좀 더 쉽습니다 빨간색 실선 와이어프레임은 효과에 의해 회전되며 파란색 점선 와이어프레임은 레이아웃 시스템에서 이해하는 로켓 지오메트리의 위치입니다 HStack의 크기 조정 및 콘텐츠 배치는 이 파란색 프레임에 따르죠 두 선은 일치하지 않는데 시각적 효과는 레이아웃에 영향을 미치지 않기 때문입니다 즉 rotation3DEffect를 사용할 때 HStack은 로켓의 회전된 지오메트리에 대해 모릅니다
scaleEffect, 오프셋 등 모든 시각 효과에
해당되는 내용입니다
모든 경우에서 레이아웃 시스템은 수정자 때문에 보기의 크기나 배치를 조정하지 않습니다 하나의 보기를 애니메이션화하되 주변 프레임에 영향을 주지 않고자 할 때 유용합니다
하지만 영향이 생긴다면요? 회전된 로켓을 어떻게 고칠까요?
좋은 소식입니다 visionOS 26에 새 rotation3DLayout 수정자가 도입됩니다 레이아웃 시스템에서 회전된 보기의 프레임을 수정해 주죠 로켓 모델에 적용하면 HStack이 크기와 배치를 조정해 로켓과 세부 정보가 적힌 카드를 위한 공간을 충분히 확보합니다
rotation3DLayout을 통해 모든 각도와 축에서 회전시킬 수 있습니다 즉 로켓을 45도 회전시킬 수 있습니다 우주로 발사되는 것 같은 느낌이 나네요
rotation3DLayout 수정자 전후에 디버깅 와이어프레임을 적용하죠 로켓의 회전된 프레임이 빨간색으로 표시됩니다 파란색 와이어프레임은 레이아웃 시스템에서 수정된 보기의 프레임을 나타냅니다 파란색 바운딩 박스가 상위 항목 측에 맞춰 정렬되었고 빨간색 회전된 프레임에 딱 맞습니다
이제 rotation3DLayout을 사용해 비디오 초반에 보여 드린 로봇 캐러셀을 빌드하는 방법을 살펴보겠습니다
먼저 “SwiftUI로 맞춤형 레이아웃 작성하기”의 RadialLayout을 가져옵니다
이 맞춤형 레이아웃 유형은 원 안에 보기를 배치하며 둘레는 사용 가능한 너비, 높이에 따라 정의됩니다
MyRadialLayout은 원래 iOS에서 2D 보기를 배치할 용도로 작성되었지만
visionOS에서도 작동합니다
2D 반려동물 이미지 대신 3D 로봇 모델을 배치할 때도
ForEach를 사용해 이 맞춤형 레이아웃에 각 로봇의 크기 조정 가능 Model3D를 배치할 수 있습니다
지금도 괜찮아 보이지만 아직 세로 경험입니다 볼륨에 맞게 로봇을 수평으로 배치하고 싶습니다
방사형 레이아웃에 rotation3DLayout을 적용해 보기를 X축의 90도 방향으로 회전시킵니다 앞서 캐러셀의 높이였던 값이 이제 레이아웃 시스템에서 회전된 보기의 깊이를 정의합니다 이제 캐러셀이 올바른 방향으로 배치되었지만 로봇이 누워 있죠 잘 때가 아닌데요
X축의 -90도 방향으로 회전시키는 rotation3DEffect를 사용해 ForEach의 각 로봇을 반대 방향으로 회전시켜 세울 수 있죠 잠자던 드로이드들이 깨어났네요 마지막으로 하나만 고치겠습니다 캐러셀이 볼륨의 높이 내부에 정렬되어 있는데요 캐러셀이 볼륨의 기반 플레이트에 딱 붙었으면 합니다
캐러셀 전체에 debugBorder3D가 적용되어 있으면 알아차리기 더 쉽습니다
2D 레이아웃 때와 같은 전략을 사용할 수 있습니다 VStack에서 캐러셀을 아래로 밀고 위에 스페이서를 놓고 싶습니다 로봇들이 볼륨 하단에 있으니 멋져 보이네요 3D 레이아웃 작업 시 유용한 두 가지 도구를 더 살펴보겠습니다 SpatialContainer와 spatialOverlay입니다 로봇 캐러셀에 기능을 하나 더 추가하고 싶습니다 로봇을 탭하면 로봇이 선택되고 컨트롤 메뉴가 나오며 모델 하단에 링이 표시되어 선택 사실을 나타내야 합니다 이 링도 Model3D로 표현됩니다 링은 로봇과 같은 3D 공간을 채워야 합니다 링이 어떤 축에 쌓이지 않았으면 합니다 모델을 같은 3D 공간에 배치할 수 있는 새 도구가 필요합니다
새 SpatialContainer API를 사용하면 일련의 마트료시카처럼 같은 3D 공간에 여러 보기를 배치할 수 있습니다
모든 보기에 3차원 정렬을 적용할 수 있습니다 여기서는 모든 하위 항목을 bottomFront 정렬 가이드에 따라 정렬합니다
여기서는 topTrailingBack 가이드에 따릅니다
spatialOverlay는 비슷한 도구인데 같은 3D 공간의 단일 보기를 다른 보기로 중첩할 수 있습니다
SpatialContainer와 유사하게 3D 정렬을 지원합니다
일렬로 세울 보기는 로봇과 선택 링 두 개뿐입니다 중요한 것은 로봇의 지오메트리입니다 로봇 크기에 맞게 링 크기를 조정하면 좋겠습니다 spatialOverlay를 사용해 선택한 로봇 시각적 효과를 구현해 보죠
로봇 모델에 spatialOverlay 수정자를 추가합니다 선택된 것으로 표시되면 크기 조정 가능 링 보기를 콘텐츠로 배치합니다 하단 정렬을 사용해 링의 하단을 로봇의 하단과 맞춥니다
로봇 캐러셀이 멋져 보이네요 기존의 모든 작성 가능 SwiftUI API로 더 쉽게 개선할 수 있습니다
배운 내용을 요약하는 차원에서 debugBorder3D 수정자를 구현해 볼까요
앞서 보여 드린 수정자가 Model3D에 적용된 모습입니다
View에서 debugBorder3D 메서드를 확장 프로그램으로 정의합니다 수정된 콘텐츠에 spatialOverlay를 적용하고 같은 3D 공간에서 테두리를 이 테두리가 적용된 보기로 렌더링합니다
2D 테두리, 스페이서, 다른 2D 테두리가 포함된 ZStack을 내부에 배치합니다
다음으로 ZStack 전체에 rotation3DLayout을 적용해 보기의 선행 및 후행 면에 테두리를 배치합니다
마지막으로 이 내부 ZStack을 후면 및 전면의 2D 테두리와 함께 다른 ZStack 내부에 배치합니다 모든 가장자리에 테두리가 생겼죠
새 3D API로 기존 2D SwiftUI 수정자를 작성해 새로운 것을 만들 수 있어 정말 유용합니다
대다수의 레이아웃 도구와 수정자에 3D 아날로그 기능이 있는데 2D 컨텍스트에서 이미 익숙하실 수도 있습니다 비슷한 다른 API는 문서를 확인해 보세요
SwiftUI는 3D 앱을 빌드하기 좋은 도구지만 여러 사용 사례에서 여전히 RealityKit을 활용하며 흔히 둘을 같은 앱에서 함께 사용하기도 합니다
이제 SwiftUI 콘텐츠가 3D이므로 SwiftUI가 RealityKit 코드와 상호작용해야 할 수 있습니다 제 친구 Maks와 Amanda가 두 프레임워크로 BOTanist에 놀라운 추가 기능을 빌드했습니다 자세한 내용은 “함께하면 더욱 탁월한 SwiftUI 및 RealityKit”을 확인하세요 3D에서 여러분의 앱이 어떤 모습일지 기대됩니다
-
-
3:02 - Robot Image Frame
// Some views have fixed frames Image("RobotHead") .border(.red)
-
3:05 - Color Frame
// Some views have flexible frames Color.blue .border(.red)
-
3:15 - Layout Composed Frame
// Layouts compose the frames of their children VStack { Image("RobotHead") .border(.red) Image("RobotHead") .border(.red) } .border(.yellow)
-
4:00 - Model3D Frame
// Some views have fixed depth Model3D(named: "Robot") .debugBorder3D(.red)
-
4:25 - Zero Depth Views
// Many views have 0 depth HStack { Image("RobotHead") .debugBorder3D(.red) Text("Hello! I'm a piece of text. I have 0 depth.") .debugBorder3D(.red) Color.blue .debugBorder3D(.red) .frame(width: 200, height: 200) }
-
4:41 - RealityView Depth
// RealityView takes up all available space including depth RealityView { content in // Setup RealityView content } .debugBorder3D(.red)
-
4:56 - GeometryReader3D Depth
// GeometryReader3D uses all available depth GeometryReader3D { proxy in // GeometryReader3D content } .debugBorder3D(.red)
-
5:01 - Model3D scaledToFit3D
// Scaling a Model3D to fit available space Model3D(url: robotURL) {aresolved in resolved.resizable() }aplaceholder: { ProgressView() } .scaledToFit3D() .debugBorder3D(.red)
-
6:15 - ZStack depth
// ZStack composes subview depths ZStack { Model3D(named: "LargeRobot") .debugBorder3D(.red) Model3D(named: "BabyBot") .debugBorder3D(.red) } .debugBorder3D(.yellow)
-
6:33 - ZStack with RealityView
// ZStack composes subview depths ZStack { RealityView { ... } .debugBorder3D(.red) Model3D(named: "BabyBot") .debugBorder3D(.red) } .debugBorder3D(.yellow)
-
6:57 - Layouts are 3D
// HStack also composes subview depths HStack { Model3D(named: "LargeRobot") .debugBorder3D(.red) Model3D(named: "BabyBot") .debugBorder3D(.red) } .debugBorder3D(.yellow)
-
7:50 - ResizableRobotView
struct ResizableRobotView: View { let asset: Model3DAsset var body: some View { Model3D(asset: asset) { resolved in resolved .resizable() } .scaledToFit3D() } }
-
8:11 - Robot Profile 1
//`Layout` types back align views by default struct RobotProfile: View { let robot: Robot var body: some View { VStack { ResizableRobotView(asset: robot.model3DAsset) RobotNameCard(robot: robot) } .frame(width: 300) } }
-
8:38 - Customizing Vertical Alignment
// Customizing vertical alignment HStack(alignment: .bottom) { Image("RobotHead") .border(.red) Color.blue .frame(width: 100, height: 100) .border(.red) } .border(.yellow)
-
8:52 - Customizing Depth Alignment
// Customizing depth alignments struct RobotProfile: View { let robot: Robot var body: some View { VStackLayout().depthAlignment(.front) { ResizableRobotView(asset: robot.model3DAsset) RobotNameCard(robot: robot) } .frame(width: 300) } }
-
9:45 - Robot Favorite Row
struct FavoriteRobotsRow: View { let robots: [Robot] var body: some View { HStack { RobotProfile(robot: robots[2]) RobotProfile(robot: robots[0]) RobotProfile(robot: robots[1]) } } }
-
10:27 - Custom Depth Alignment ID
// Defining a custom depth alignment guide struct DepthPodiumAlignment: DepthAlignmentID { static func defaultValue(in context: ViewDimensions3D) -> CGFloat { context[.front] } } extension DepthAlignment { static let depthPodium = DepthAlignment(DepthPodiumAlignment.self) }
-
10:51 - Customizing Depth Alignment Guides
// Views can customize their alignment guides struct FavoritesRow: View { let robots: [Robot] var body: some View { HStackLayout().depthAlignment(.depthPodium) { RobotProfile(robot: robots[2]) RobotProfile(robot: robots[0]) .alignmentGuide(.depthPodium) { $0[DepthAlignment.back] } RobotProfile(robot: robots[1]) .alignmentGuide(.depthPodium) { $0[DepthAlignment.center] } } } }
-
12:00 - Rotation3DEffect
// Rotate views using visual effects Model3D(named: "ToyRocket") .rotation3DEffect(.degrees(45), axis: .z)
-
12:10 - Rotation3DLayout
// Rotate using any axis or angle HStackLayout().depthAlignment(.front) { RocketDetailsCard() Model3D(named: "ToyRocket") .rotation3DLayout(.degrees(isRotated ? 45 : 0), axis: .z) }
-
14:42 - Pet Radial Layout
// Custom radial Layout struct PetRadialLayout: View { let pets: [Pet] var body: some View { MyRadialLayout { ForEach(pets) { pet in PetImage(pet: pet) } } } }
-
14:56 - Rotated Robot Carousel
struct RobotCarousel: View { let robots: [Robot] var body: some View { VStack { Spacer() MyRadialLayout { ForEach(robots) { robot in ResizableRobotView(asset: robot.model3DAsset) .rotation3DLayout(.degrees(-90), axis: .x) } } .rotation3DLayout(.degrees(90), axis: .x) } }
-
17:00 - Spatial Container
// Aligning views in 3D space SpatialContainer(alignment: .topTrailingBack) { LargeBox() MediumBox() SmallBox() }
-
17:35 - Spatial Overlay
// Aligning overlayed content LargeBox() .spatialOverlay(alignment: .bottomLeadingFront) { SmallBox() }
-
17:47 - Selection Ring Spatial Overlay
struct RobotCarouselItem: View { let robot: Robot let isSelected: Bool var body: some View { ResizableRobotView(asset: robot.model3DAsset) .spatialOverlay(alignment; .bottom) { if isSelected { ResizableSelectionRingModel() } } }
-
18:32 - DebugBorder3D
extension View { func debugBorder3D(_ color: Color) -> some View { spatialOverlay { ZStack { Color.clear.border(color, width: 4) ZStack { Color.clear.border(color, width: 4) Spacer() Color.clear.border(color, width: 4) } .rotation3DLayout(.degrees(90), axis: .y) Color.clear.border(color, width: 4) } } }
-
-
- 0:00 - 서론
SwiftUI의 새로운 3D 레이아웃 기능을 visionOS 26에서 사용하면 SwiftUI의 선언적 구문을 사용하여 3D 앱을 빌드할 수 있습니다. 이러한 기능은 기존 2D 레이아웃 개념을 기반으로 하고 애니메이션, 크기 조정, 상태 관리에 대한 기본 지원을 제공합니다. BOT-anist 예제 앱은 사람들이 가상 정원에서 로봇을 사용자 지정하고 카탈로그화하는 방법을 보여 줍니다. SwiftUI 레이아웃을 처음 사용하는 경우, 이 콘텐츠를 더 자세히 살펴보기 전에 ‘SwiftUI로 사용자 지정 레이아웃 만들기’와 ‘SwiftUI로 맞춤형 레이아웃 작성하기’를 확인하세요.
- 2:47 - 3D 뷰
SwiftUI에서 레이아웃 시스템은 앱의 각 뷰에 대한 너비, 높이, X, Y 위치를 계산합니다. 일부 뷰에는 고정된 프레임이 있는 반면, 다른 뷰에는 부모 뷰가 제공하는 사용 가능한 공간을 채우는 유연한 프레임이 있습니다. visionOS에서는 이 개념이 3차원으로 확장됩니다. 이제 각 뷰에는 너비와 높이 외에도 깊이와 Z 위치가 있습니다. 레이아웃 시스템은 2D와 유사하게 동작하지만 3D 공간에 적용됩니다. 뷰는 고정, 유연 또는 0 깊이를 가질 수 있습니다. ‘resizable’ 수정자가 있는 GeometryReader3D 및 Model3D는 사용 가능한 모든 깊이를 차지할 수 있습니다. 새로운 ‘scaledToFit3D’ 수정자는 크기를 조절하는 동안 종횡비를 유지하는 데 도움이 됩니다. 윈도우는 고정된 루트 깊이를 제안하는 반면, 볼륨은 크기를 조절할 수 있는 깊이를 제안합니다. 이 깊이를 벗어난 내용은 시스템에 클립될 수 있습니다. ZStack은 VStack이 높이를 구성하는 것처럼 깊이를 구성하고 HStack 및 VStack과 같은 기존 레이아웃 유형은 자동으로 visionOS의 3D가 되어 요소를 뒷면을 따라 정렬 등 깊이에 대한 합리적인 기본 동작을 적용합니다.
- 7:18 - 깊이 정렬
깊이 정렬은 visionOS 26의 새로운 기능으로, 3D 공간에서 위치 지정 뷰를 사용자 지정하는 데 사용할 수 있고 2D에서 수직 및 수평 정렬이 작동하는 방식과 유사합니다. 이러한 접근 방식은 특히 볼류메트릭 창을 만들거나 3D 모델을 표시할 때 유용합니다. 이 예제에서는 깊이 정렬을 사용하여 로봇 프로필 뷰의 가독성을 개선하는 방법을 보여 줍니다. 로봇 이름 카드에 ‘.front’ 깊이 정렬 수정자를 적용하면 앞쪽으로 이동하여 보기가 더 쉬워집니다. 더 복잡한 시나리오에서는 사용자 정의 깊이 정렬을 만들 수 있습니다. 이 예제에서는 사용자 정의 ‘DepthPodiumAlignment’를 정의하여 세 개의 로봇 프로필 보기를 심도 있게 배열하고 선호하는 로봇을 뷰어에 가장 가까이 배치하여 눈에 띄는 느낌을 만드는 방법을 보여 줍니다.
- 11:41 - 회전 레이아웃
visionOS 26에서는 기존 ‘rotation3DEffect’ 수정자의 한계를 해결하기 위해 새로운 ‘rotation3DLayout’ 수정자가 도입되었습니다. ‘rotation3DEffect’ 수정자는 레이아웃 시스템에 영향을 주지 않고 시각적 회전만 적용하기 때문에 HStack과 같은 컨테이너 내에서 뷰를 회전할 때 문제가 발생합니다. ‘rotation3DEffect’는 다른 프레임에 영향을 주지 않고 뷰에 애니메이션을 적용하려는 경우에도 유용합니다. ‘rotation3DLayout’ 수정자는 레이아웃 시스템 내에서 회전된 뷰의 프레임을 수정하여 적절한 크기 및 배치 조정을 지원하여 더 복잡한 3D 레이아웃이 가능합니다. 예를 들어, 사용자 정의 RadialLayout을 X축을 따라 90도 회전하여 수평 방향의 로봇 회전목마를 만들 수 있습니다. 회전목마에 있는 각 로봇은 반대 방향으로 회전하여 똑바로 서게 됩니다. 스페이서가 있는 VStack을 사용하는 등의 추가 조정을 적용하면 캐러셀을 볼륨 바닥과 일치시켜 세련되고 시각적으로 매력적인 3D 사용자 인터페이스를 만들 수 있습니다.
- 16:28 - 공간 컨테이너
‘SpatialContainer’ 및 ‘SpatialOverlay’는 3D 레이아웃을 위한 SwiftUI의 새로운 도구입니다. SpatialContainer는 정렬 옵션을 사용하여 3D 공간에 여러 개의 뷰를 중첩할 수 있게 해 주는 반면, SpatialOverlay는 하나의 뷰를 다른 뷰 위에 중첩시킵니다. 이 예제에서는 SpatialOverlay를 사용하여 회전형 로봇에 대한 선택 링을 만들고 로봇의 아래쪽에 맞춥니다. ‘debugBorder3D’ 수정자 예제는 ‘SpatialOverlay’, ZStacks, ‘rotation3DLayout’을 사용하여 디버깅 목적으로 모든 Model3D에 3D 테두리를 추가하는 뷰의 확장 프로그램으로 설명됩니다.
- 19:22 - 다음 단계
SwiftUI는 이제 익숙한 2D 수정자와 새로운 3D API를 사용하여 3D 앱 개발을 활성화합니다. SwiftUI 및 RealityKit를 결합하면 더욱 향상된 기능을 사용할 수 있습니다. ‘함께하면 더욱 탁월한 SwiftUI 및 RealityKit’에서 이러한 통합의 예를 찾아보세요.