View in English

  • 메뉴 열기 메뉴 닫기
  • Apple Developer
검색
검색 닫기
  • Apple Developer
  • 뉴스
  • 둘러보기
  • 디자인
  • 개발
  • 배포
  • 지원
  • 계정
페이지에서만 검색

빠른 링크

5 빠른 링크

비디오

메뉴 열기 메뉴 닫기
  • 컬렉션
  • 주제
  • 전체 비디오
  • 소개

더 많은 비디오

  • 소개
  • 요약
  • 자막 전문
  • 코드
  • 함께하면 더욱 탁월한 SwiftUI 및 RealityKit

    visionOS 26에서 SwiftUI와 RealityKit을 원활하게 결합하는 방법을 알아보세요. 애니메이션 및 ConfigurationCatalog 지원 등 Model3D의 개선 사항을 살펴보고 RealityView로 원활하게 전환하는 방법을 보여드립니다. SwiftUI 애니메이션을 활용하여 RealityKit 구성 요소의 변화를 유도하고, 상호작용 조작을 구현하며, 새로운 SwiftUI 구성 요소를 이용하여 보다 풍부한 상호작용을 수행하고 SwiftUI 코드에서 RealityKit의 변경 사항을 확인하는 방법을 알아볼 수 있습니다. 또한 프레임워크간 좌표 변환을 위한 통합 좌표 변환을 사용하는 방법도 확인하세요.

    챕터

    • 0:00 - 서론
    • 1:24 - Model3D 개선 사항
    • 6:13 - RealityView 변환
    • 11:52 - 개체 조작
    • 15:35 - SwiftUI 구성 요소
    • 19:08 - 정보 흐름
    • 24:56 - 통합 좌표 변환
    • 27:01 - 애니메이션
    • 29:41 - 다음 단계

    리소스

    • Canyon Crosser: Building a volumetric hike-planning app
    • Rendering hover effects in Metal immersive apps
      • HD 비디오
      • SD 비디오

    관련 비디오

    WWDC24

    • Reality Composer Pro에서 대화식 3D 콘텐츠 만들기

    WWDC23

    • SwiftUI의 Observation 알아보기

    WWDC21

    • Dive into RealityKit 2

    WWDC20

    • Data Essentials in SwiftUI
  • 비디오 검색…

    안녕하세요, 저는 RealityKit 엔지니어 Amanda입니다 저는 Maks입니다 SwiftUI 엔지니어고요 오늘은 SwiftUI와 RealityKit을 함께 더 효과적으로 사용할 수 있는 멋진 개선 사항을 소개하죠 이 귀여운 장면을 보세요 하늘에 떠 있는 매력적인 SwiftUI 로봇과 지상에 있는 RealityKit 로봇이 서로 만나고 싶어 하네요 둘이 가까워지면 불꽃이 튀는데 진정한 상호작용을 위해 충분히 가까워지려면 어떻게 할까요? Maks와 제가 일반 UI 세상과 대화형 3D 콘텐츠 세상을 결합하는 방법을 알려 드리겠습니다 먼저 Model3D의 몇 가지 향상 사항을 살펴보겠습니다 그런 다음 Model3D 사용에서 RealityView 사용으로 전환하는 방법과 한쪽을 선택하는 방법을 알아봅니다 새로운 Object Manipulation API를 볼까요

    RealityKit은 새 Component 유형으로 더 많은 SwiftUI 측면을 통합하죠 이제 정보가 SwiftUI와 RealityKit 간에 양쪽으로 흐를 수 있습니다

    그 어느 때보다 쉽게 좌표 공간을 전환할 수 있습니다 SwiftUI 애니메이션으로 RealityKit 컴포넌트 변경을 촉진합니다 정리해 볼까요

    Model3D를 사용해 코드 한 줄로 앱에서 3D 모델을 표시합니다 visionOS 26에서 두 가지 향상으로 더 많은 Model3D 작업이 가능하죠 애니메이션 재생 ConfigurationCatalog에서 로드 등입니다 Model3D는 SwiftUI 보기라 SwiftUI 레이아웃 시스템에 참여합니다 이 레이아웃 시스템을 사용해 로봇 이름이 표시된 작은 표지판을 만들겠습니다

    이제 표지판에 로봇 이름이 Sparky라고 적혀 있습니다

    Sparky는 춤도 잘 춘답니다 아티스트는 이 애니메이션과 로봇 모델 애셋을 번들화했습니다 visionOS 26의 새로운 기능은 Model3DAsset 유형입니다 Model3DAsset을 사용해 Model3D를 구성하여 3D 콘텐츠에서 애니메이션을 로드 및 제어합니다 모델은 애셋에서 애니메이션을 로드하며 재생할 것을 사용자가 선택할 수 있습니다 “모델”은 다소 무거운 용어인데 UI 프레임과 3D 게임 프레임워크를 수렴하는 이 세션에서 특히 그렇습니다 UI 프레임워크에서 “모델”은 앱에서 사용하는 정보를 나타내는 데이터 구조를 말합니다 모델은 데이터와 비즈니스 논리를 담고 있어 보기가 이 정보를 표시할 수 있습니다 RealityKit과 같은 3D 프레임워크에서 모델은 장면에 배치할 수 있는 3D 객체를 말하죠 ModelComponent를 통해 접근하는데 이는 모양을 정의하는 메시 리소스와 외형을 결정하는 머티리얼로 구성됩니다 때로 이런 상황이 발생합니다 두 세상이 충돌하며 용어가 섞이고 때로는 겹치는 겁니다 Sparky와 애니메이션으로 돌아가죠

    선택기, 재생 버튼, 시간 이동 막대 위에 Model3D를 배치합니다 RobotView에서 애니메이션화된 로봇 자체를 표시하고 그 아래에 재생할 애니메이션을 선택할 선택기와 애니메이션 재생 컨트롤을 배치합니다 먼저 장면 이름으로 Model3DAsset을 초기화해 번들에서 로드합니다 그런 다음 애셋이 준비되면 Model3D 이니셜라이저에 전달하고 그 아래에 VStack에서 이 모델 애셋에서 사용 가능한 애니메이션이 나열된 맞춤형 선택기를 표시합니다 목록에서 항목을 선택하면 애셋의 `selectedAnimation`을 선택기가 새 값으로 설정합니다 그런 다음 Model3DAsset은 AnimationPlaybackController를 생성해 선택된 애니메이션의 재생을 제어합니다 애셋은 `animationPlaybackController`를 공급합니다 이 객체를 사용해 애니메이션을 일시 정지, 재개, 찾습니다

    animationController를 RobotAnimationControls 보기에 전달합니다 이 부분은 곧 살펴보겠습니다 visionOS 26에서 기존 RealityKit 클래스 `AnimationPlaybackController`는 이제 Observable입니다 SwiftUI 보기에서 `time` 속성을 관찰해 애니메이션의 진행을 표시합니다

    `controller`라는 @Bindable 속성이 있는데 `AnimationPlaybackController`를 보기의 데이터 모델로 사용하고 있다는 의미입니다 컨트롤러의 isPlaying 값이 변경되면 SwiftUI는 RobotAnimationControls 보기를 다시 평가합니다 애니메이션에 현재 시간을 표시하는 슬라이더가 있으며 이는 애니메이션의 총 시간을 기준으로 합니다 이 슬라이더를 드래그하면 애니메이션 전체를 이동할 수 있죠 Sparky가 축하 애니메이션을 보여 주네요 슬라이더를 사용해 빨리 감고 되감을 수 있습니다 춤춰 Sparky, 생일이잖아 춤이 끝나고 Sparky가 멋지게 차려 입고 싶다네요 온실로 가서 다른 로봇을 만나기로 했다고요 RealityKit의 ConfigurationCatalog 유형 향상 덕분에 도와줄 수 있죠 이 유형은 다른 메시 지오메트리 컴포넌트 값, 머티리얼 속성 등 엔티티의 대체 표현을 저장합니다 visionOS 26에서는 ConfigurationCatalog로 Model3D를 초기화하고 다양한 표현 간에 전환할 수 있습니다

    Sparky가 다양한 모습을 하도록 아티스트가 다양한 본체 유형으로 Reality File을 번들화했습니다 앱의 메인 번들에서 이 파일을 ConfigurationCatalog로 로드하죠 그런 다음 이 구성으로 Model3D 버튼을 만듭니다 이 팝오버에 구성 옵션이 표시됩니다 팝오버에서 선택하면 Sparky의 모습이 바뀝니다 춤 동작? 확인 완료했고요 겉모습? 확인 완료했고요 Sparky가 RealityKit 온실에서 새 친구를 만날 준비가 되었습니다 불꽃이 튈 예정이랍니다 불꽃이 튀도록 입자 이미터를 사용하겠습니다 하지만 Model3D 유형으로는 런타임에서 불가능한 일입니다 입자 이미터는 RealityKit 엔티티에 추가하는 컴포넌트입니다 잠시 후 살펴보기로 하고요 중요한 점은 Model3D가 컴포넌트 추가를 지원하지 않는다는 거죠 입자 이미터를 추가하기 위해 RealityView로 전환합니다 레이아웃을 바꾸지 않고 Model3D를 RealityView로 매끄럽게 대체하는 방법을 알려 드리겠습니다 먼저 보기를 Model3D에서 RealityView로 전환합니다 앱 번들에서 RealityView의 `make` 클로저 안에 식물학자 모델을 로드해 엔티티를 생성합니다 이 엔티티를 RealityView의 콘텐츠에 추가하면 화면에 Sparky가 나타납니다

    그런데 이름 표지판이 옆으로 너무 멀리 밀려났군요 Model3D를 사용할 때는 이러지 않았는데요 그 원인은 RealityView가 기본적으로 SwiftUI 레이아웃 시스템이 제공하는 사용 가능한 공간을 모두 차지하기 때문입니다 반면 Model3D는 기반 모델 파일의 고유 크기에 맞게 크기를 조정합니다 이 문제는 해결 가능합니다 `.fixedSize`가 포함된 새 `.realityViewLayoutBehavior` 수정자를 적용하면 RealityView가 모델의 초기 경계에 딱 맞게 래핑합니다 훨씬 낫네요 RealityView는 콘텐츠에서 엔티티의 시각적 경계를 사용해 크기를 파악합니다 이 크기 정보는 `make` 클로저가 실행된 직후 한 번만 평가됩니다 `realityViewLayoutBehavior`의 또 다른 옵션은 .flexible과 .centered입니다 세 RealityView 모두에서 Sparky 모델의 하단이 장면의 원점에 앉아 있으며 이 원점을 장치로 표시했습니다 축과 원점을 나타낸 여러 색깔의 작은 십자가죠 왼쪽은 `.flexible` 옵션을 사용해 RealityView가 수정자가 적용되지 않은 것처럼 작동합니다 원점은 보기의 중앙에 유지됩니다 `.centered` 옵션에서는 RealityView의 원점이 이동해 콘텐츠가 보기의 중앙에 배치됩니다 `.fixedSize`에서는 RealityView가 콘텐츠의 경계에 딱 맞게 래핑하며 RealityView가 Model3D처럼 동작합니다

    어느 것이든 RealityViewContent와 관련해 엔티티의 위치나 크기를 조정하지 않습니다 RealityView 자체 원점의 위치만 조정할 뿐입니다 RealityView에서 Sparky의 크기를 정리했습니다 Sparky 애니메이션을 다시 보죠 엔티티에 대해 Model3D의 새로운 애니메이션 API에서 RealityKit 애니메이션 API으로 바로 이동합니다 RealityKit의 다양한 애니메이션 작업 방법에 대한 자세한 내용은 “Reality Composer Pro에서 대화식 3D 콘텐츠 만들기”를 확인하세요 Model3D에서 RealityView로 전환해 이제 ParticleEmitterComponent를 Sparky에게 제공할 수 있습니다 두 로봇이 가까워질 때 불꽃이 튀어야 하니까요 입자 이미터로 작은 입자 수백 개의 애니메이션이 동시에 재생되는 효과를 낼 수 있습니다 불꽃놀이, 비, 반짝임처럼요 RealityKit에는 이를 위한 프리셋 값이 있으며 해당 프리셋을 조정해 원하는 효과를 낼 수 있습니다 Reality Composer Pro를 사용해 디자인하고 코드로 구성 가능하죠 ParticleEmitter는 컴포넌트로 엔티티에 추가합니다 컴포넌트는 RealityKit의 핵심이며 RealityKit은 “엔티티 컴포넌트 시스템” 패러다임에 기반합니다 장면의 각 객체는 엔티티이며 여기에 컴포넌트를 추가해 특성과 동작을 알려 줍니다 컴포넌트는 엔티티에 대한 데이터를 담고 있는 유형입니다 시스템은 특정 컴포넌트가 있는 엔티티를 처리해 데이터와 관련된 논리를 수행하죠 입자 애니메이션화 물리 처리와 렌더링 등을 위한 내장 시스템이 있습니다 RealityKit에서 자체 맞춤형 시스템을 작성해 게임 또는 앱에 맞춤형 논리를 활용할 수 있습니다 RealityKit의 엔티티 컴포넌트 시스템에 대한 자세한 내용은 RealityKit 자세히 알아보기 2 세션을 시청하세요 Sparky의 머리 양쪽에 입자 이미터를 추가하겠습니다 먼저 불꽃 효과의 컨테이너 역할을 할 보이지 않는 엔티티 두 개를 만듭니다 불꽃 이미터가 오른쪽을 가리키도록 디자인했고요 Sparky의 오른쪽에 있는 보이지 않는 엔티티에 직접 추가합니다

    반대편에서는 엔티티를 y축 기준으로 180도 회전시켜 왼쪽을 향하도록 합니다

    RealityView에서 모두 적용하면 Sparky의 애니메이션이 완성됩니다 이름 표지판이 올바른 위치에 있고 불꽃이 튀죠

    RealityKit은 이처럼 세밀한 창작물에 제격입니다 게임이나 플레이 지향 경험을 만드는 경우 또는 3D 콘텐츠의 동작을 세밀하게 제어해야 하는 경우 RealityView를 선택하세요 반면 Model3D로는 개별 독립 3D 애셋 자체를 표시할 수 있습니다 SwiftUI의 이미지 보기가 3D 애셋용이 된 것과 같습니다

    새 애니메이션 및 구성 카탈로그로 더 많은 Model3D 작업이 가능하죠 디자인이 발전해 엔티티, 컴포넌트 시스템에 직접 접근해야 한다면 realityViewLayoutBehavior로 Model3D에서 RealityView로 매끄럽게 전환할 수 있습니다 다음으로 visionOS 26의 새로운 Object Manipulation API를 설명하겠습니다 앱에서 가상 객체를 집을 수 있죠 객체 조작은 SwiftUI, RealityKit 모두에서 가능합니다 객체 조작을 통해 한 손으로 객체를 이동하고 한 손 또는 두 손으로 회전하고 두 손으로 핀치하고 드래그해 크기를 조절할 수 있습니다 한 손에서 다른 손으로 객체를 전달할 수도 있습니다

    활성화 방법은 두 가지입니다 객체가 RealityKit 엔티티인지 SwiftUI 보기인지에 따라 다른데요 SwiftUI에서는 새로운 `manipulable` 수정자를 추가하죠 크기 조절은 비활성화하되 한 손으로 로봇을 이동하고 회전하는 기능은 유지하기 위해 지원되는 연산을 지정합니다

    로봇이 매우 무거워 보이게 하고자 관성을 높게 지정하고요

    해당 수정자는 Sparky가 Model3D 보기에 표시될 때 작동합니다 전체 Model3D 또는 여기에 연결된 모든 보기에 적용됩니다 Sparky가 RealityView에 있을 때 전체 RealityView가 아니라 로봇 엔티티 자체에만 조작을 활성화하고 싶습니다 visionOS 26에는 엔티티에 설정해 객체 조작을 활성화할 수 있는 새로운 유형 ManipulationComponent가 있습니다 정적 함수 `configureEntity`는 엔티티에 ManipulationComponent를 추가하죠 또한 CollisionComponent를 추가해 이 엔티티를 탭한 시점을 상호작용 시스템에 알립니다 이 엔티티가 제스처에 반응함을 시스템에 알리는 InputTargetComponent를 추가하고 또 사용자가 보거나 마우스로 가리킬 때 시각적 효과를 적용하는 HoverEffectComponent를 추가합니다 장면 엔티티 조작 활성화에는 이 줄만 이용하면 됩니다 경험을 추가로 맞춤화하고자 할 때 전달 가능한 여러 매개변수도 있죠 보라색 스포트라이트 효과를 지정 중인데요 직접 터치와 간접 시선 및 핀치 등 모든 유형의 입력을 허용합니다 로봇의 외부 크기를 정의하는 충돌 모양을 제공하고요 사용자가 앱의 객체와 상호작용할 때 반응하기 위해 객체 조작 시스템은 상호작용이 시작되고 끝날 때 등 주요 순간에 이벤트를 발생시키며 엔티티를 이동, 회전 크기 조정할 때, 놓을 때와 한 손에서 다른 손으로 건넬 때 업데이트됩니다 이러한 이벤트를 구독하여 상태를 업데이트합니다 기본적으로 상호작용이 시작되거나 건네기가 발생하거나 객체를 놓으면 표준 사운드가 재생됩니다 맞춤형 사운드를 적용하기 위해 먼저 audioConfiguration을 `none`으로 설정합니다 표준 사운드가 비활성화되죠 그런 다음 ManipulationEvent DidHandOff를 구독합니다 이는 사용자가 한 손에서 다른 손으로 로봇을 넘길 때 전달됩니다 이 클로저에서 자체 오디오 리소스를 재생합니다 자, Maks Sparky의 흥미로운 여정이었습니다 Model3D에서 애니메이션화하고 RealityView로 옮겨 오고 불꽃으로 개성을 더하고, 사용자가 만지고 상호작용할 수 있게 했죠 RealityKit 온실까지 먼 길을 걸어 왔습니다 맞습니다 하지만 Sparky가 온실에 있는 로봇과 제대로 소통하려면 가상 공간의 객체에 새로운 기능이 필요합니다 제스처에 반응하고 자신에 대한 정보를 제시하고 동작을 트리거해야 합니다 SwiftUI에 잘 맞는 방식으로요

    RealityKit 온실을 향한 Sparky의 여정에서 중요한 것은 연결을 구축하는 것입니다 심층적인 연결에는 풍부한 상호작용이 필요합니다 이를 실현하고자 디자인된 것이 새로운 SwiftUI RealityKit 컴포넌트입니다 visionOS 26의 이 새로운 컴포넌트는 강력하고 익숙한 SwiftUI 기능을 RealityKit 엔티티로 직접 가져옵니다 RealityKit은 주요 컴포넌트 세 가지를 도입합니다 ViewAttachmentComponent를 사용하면 SwiftUI 보기를 엔티티에 직접 추가할 수 있습니다 GestureComponent는 엔티티가 터치 및 제스처에 반응하도록 하죠 PresentationComponent는 RealityKit 장면에서 팝오버 등 SwiftUI 보기를 표시합니다

    visionOS 1에서는 RealityView 이니셜라이저의 일부로 첨부 파일을 미리 선언 가능했죠 첨부 파일 보기 빌더를 평가한 후 시스템은 결과를 엔티티로 사용해 update 클로저를 호출했습니다 이러한 엔티티를 장면에 추가하고 3D 공간에 배치할 수 있었습니다 visionOS 26에서는 간단해졌습니다 이제 앱 어디서나 RealityKit 컴포넌트를 사용해 첨부 파일을 만들 수 있습니다 SwiftUI 보기를 제공해 ViewAttachmentComponent를 만듭니다 그런 다음 엔티티의 컴포넌트 컬렉션에 추가합니다

    이렇게 NameSign을 SwiftUI에서 RealityKit으로 이동했습니다 이제 제스처를 살펴볼까요 이미 `targetedToEntity` 제스처 수정자를 사용해 RealityView에 제스처를 첨부할 수 있는데요 GestureComponent는 visionOS 26의 새로운 기능입니다 ViewAttachmentComponent처럼 엔티티에 GestureComponent를 직접 추가해 일반 SwiftUI 제스처를 전달합니다 제스처 값은 기본적으로 엔티티의 좌표 공간에 보고됩니다 아주 편리하죠 GestureComponent와 탭 제스처를 사용해 이름 표지판을 켜고 끕니다

    직접 보세요 이 로봇의 이름은 Bolts라네요

    전문가 팁을 드리자면 제스처의 대상인 모든 엔티티에서 InputTargetComponent CollisionComponent도 추가하세요 이 조언은 GestureComponent와 대상 제스처 API에도 적용됩니다

    GestureComponent 및 ViewAttachmentComponent로 Bolts의 이름 표지판을 만들고요 Bolts는 특별 손님 Sparky를 맞을 준비를 하고 있습니다 Bolts는 최대한 멋진 모습으로 온실 만남에 임하고 싶습니다 겉모습을 바꿔야겠네요 Bolts의 이름 표지판을 모습 선택용 UI로 대체하겠습니다 정말 중대한 결정이죠

    강조를 위해 팝오버에서 이 UI를 보여 드리겠습니다 RealityKit에서 직접 PresentationComponent를 사용하는 겁니다

    먼저 `ViewAttachmentComponent`를 `PresentationComponent`로 바꿉니다 컴포넌트는 팝오버 표시 시점을 제어하고 사용자가 팝오버를 닫는 시점을 알리는 데 부울 바인딩을 사용합니다 `configuration` 매개변수는 표시될 표시 유형입니다 `popover`를 지정하고요 팝오버 안에는 Bolts를 꾸밀 구성 카탈로그 옵션이 있는 보기를 표시합니다 이제 Sparky의 방문에 대비해 Bolts의 색상을 고를 수 있겠네요

    Maks, Bolts가 쿨톤인 것 같으세요? 아니면 웜톤일까요?

    그거 패션 농담이죠

    Bolts의 단장이 끝났습니다 하지만 할 일을 먼저 해야 합니다 Bolts는 온실 물 주기 담당입니다 게임의 헤즈업 디스플레이처럼 Bolts의 온실 내 위치를 추적하는 미니 맵을 만들어 보겠습니다 이를 위해 로봇의 Transform 컴포넌트를 관찰해야 합니다 이제 visionOS 26에서 엔티티를 관찰할 수 있습니다 엔티티는 속성이 변경되면 다른 코드에 알릴 수 있습니다 알림을 받으려면 엔티티의 “observable” 속성을 읽으면 되죠

    “observable” 속성에서 엔티티의 위치, 크기, 회전과 하위 항목 컬렉션, 그리고 자체 맞춤형 컴포넌트를 포함한 컴포넌트의 변경 사항을 확인할 수 있습니다 `withObservationTracking` 블록을 사용해 이들 속성을 직접 관찰하죠 또는 SwiftUI의 내장 관찰 추적 기능을 활용합니다 SwiftUI로 미니맵을 구현할 겁니다 Observation에 대해 “SwiftUI의 Observation 알아보기”를 시청하세요 이 보기에서 미니맵에 엔티티의 위치를 표시합니다 엔티티의 이 관찰 가능한 값에 접근합니다 이 값에 따라 보기가 달라짐을 SwiftUI에 알려 주죠

    Bolts가 온실을 돌아다니며 식물에 물을 주면 위치가 바뀝니다 그럴 때마다 SwiftUI는 보기의 본문을 다시 호출해 미니맵에서 대응 기호를 이동합니다 SwiftUI의 데이터 흐름에 대한 자세한 설명은 “SwiftUI 데이터 기초” 세션을 확인하세요 로봇 친구들이 곧 만나겠네요 그게 목표죠 “모델”과 “모델”의 차이점을 설명하신 내용이 좋았어요 때로 데이터 모델에서 3D 객체 모델로 또는 그 반대로 데이터를 전달해야 하는데요 visionOS 26에서 관찰 가능한 엔티티로 새롭게 가능합니다 RealityView의 `update` 클로저로 SwiftUI에서 RealityKit으로 정보 전달이 원래 가능했습니다 이제 엔티티의 `observable` 속성으로 반대로 전송 가능합니다 RealityKit 엔티티는 모델 객체처럼 작동해 SwiftUI 보기 업데이트를 유발할 수 있습니다 정보가 양방향으로 흐를 수 있죠 SwiftUI에서 RealityKit으로 RealityKit에서 SwiftUI로요 하지만... 무한 루프가 될 가능성이 있지 않나요? 맞습니다 SwiftUI와 RealityKit 간에 무한 루프 생성을 방지하는 방법을 살펴보겠습니다 보기 본문 내에 있는 관찰 가능한 속성을 읽을 때 종속성이 생깁니다 보기가 그 속성에 종속되는 거죠 속성의 값이 변하면 SwiftUI는 보기를 업데이트하고 본문을 다시 실행합니다 RealityView에는 특별 동작이 있죠 update 클로저를 포함하는 보기 본문의 확장 프로그램으로 생각해 보세요

    SwiftUI는 해당 보기의 상태가 변경될 때마다 클로저를 호출하죠 클로저에서 명시적으로 관찰하는 상태가 변경될 때 외에도요 여기 RealityView의 update 클로저에서 위치를 변경합니다 이렇게 하면 위치 값에 쓰게 되고 SwiftUI는 보기를 업데이트하고 본문을 다시 실행합니다 무한 루프가 되죠

    무한 루프 생성을 방지하려면 update 클로저에서 관찰된 상태를 수정하지 마세요

    관찰하지 않는 엔티티는 자유롭게 수정해도 됩니다 이때는 무한 루프가 생기지 않는데 엔티티가 변경되어도 SwiftUI의 보기 본문 다시 평가가 트리거되지 않기 때문입니다 관찰된 속성을 수정해야 하는 경우 해당 속성의 기존 값을 확인하여 같은 값을 다시 쓰지 않게 하세요 그러면 사이클이 끊어져 무한 루프가 방지됩니다 RealityView의 make 클로저는 특별합니다 make 클로저의 관찰 가능한 속성에 접근해도 종속성이 생기지 않거든요 포함하는 보기의 관찰 범위에 포함되지 않습니다 또한 `make` 클로저는 변경 사항이 발생해도 다시 실행되지 않습니다 포함하는 보기가 먼저 나타났을 때만 실행됩니다 또한 자체 맞춤형 시스템에서 관찰된 엔티티의 속성을 업데이트할 수 있습니다 시스템의 update 함수는 SwiftUI 보기 본문 평가의 범위 내에 있지 않기 때문에 이 지점에서 관찰된 엔티티의 값을 변경하는 것이 좋습니다

    제스처의 클로저 또한 SwiftUI 보기 본문 평가의 범위 내에 있지 않습니다 사용자 입력에 대한 반응으로 호출되죠 여기서도 관찰한 엔티티의 값을 수정할 수 있습니다 요약하자면 관찰된 엔티티를 수정해도 되는 곳과 안 되는 곳이 있습니다

    앱에 무한 루프가 발생했다면 해결하는 방법을 알려 드립니다 대규모 보기를 소규모 개별 독립 보기로 분할해 보기별로 각자 필요한 상태만 보유하도록 합니다 이렇게 하면 관련 없는 엔티티가 변경되었을 때 소규모 보기가 다시 평가되지 않습니다 성능에도 유리하고요 Maks, 더 이상 `update` 클로저를 사용하지 않아도 된다는 것을 알아차리셨을 수 있는데요 이제 엔티티가 보기의 상태가 될 수 있기 때문에 평소 상태를 수정하던 곳에서 엔티티를 수정하고 update 클로저를 생략할 수 있죠 그렇습니다 무한 루프를 피하는 것은 반복해서 체득해야 할 부분 같은데요 하지만 update 클로저를 사용하지 않으면 그럴 가능성 자체가 낮겠죠 이제 Bolts와 Sparky를 만나게 해 볼까요 Bolts가 일을 마쳤으니 Sparky와 데이트할 시간입니다 Sparky를 데려와 두 로봇이 서로에게 가까워지면 둘 사이의 거리 감소를 나타낸 함수로 불꽃이 튀게 하고 싶습니다 여기에 새로운 Unified Coordinate Conversion API를 사용하겠습니다 지금 Sparky는 Model3D SwiftUI 보기에 있고 Bolts는 RealityKit 온실의 엔티티입니다 두 로봇 사이의 절대 거리를 알아야 합니다 둘이 다른 좌표 공간에 있다 해도요 이를 위해 Spatial 프레임워크는 이제 추상적 좌표 공간을 나타내는 `CoordinateSpace3D` 프로토콜을 정의합니다 서로 다른 프레임워크라도 CoordinateSpace3D를 준수하는 두 유형 간에 값을 쉽게 변환할 수 있습니다 RealityKit의 `Entity` 및 `Scene` 유형은 CoordinateSpace3D를 준수합니다 SwiftUI 쪽의 GeometryProxy3D에는 좌표 공간을 알려 주는 새로운 .coordinateSpace3D() 함수가 있죠 또한 여러 Gesture 유형이 주어진 CoordinateSpace3D에 상대적인 값을 제공할 수 있습니다 CoordinateSpace3D 프로토콜이 작동하면 먼저 Sparky의 좌표 공간의 값을 RealityKit과 SwiftUI에서 공유하는 좌표 공간으로 변환합니다 그 후 공유 공간에서 Bolts의 좌표 공간으로 변환하고 동시에 지점-미터 변환, 축 방향과 같은 낮은 수준 세부 사항을 고려합니다 Sparky의 Model3D 보기에서는 보기 지오메트리가 변경될 때마다 시스템이 `onGeometryChange3D` 함수를 호출합니다 GeometryProxy3D를 전달하는데 이를 사용해 좌표 공간을 얻습니다 그런 다음 보기의 위치를 엔티티 공간의 지점으로 변환할 수 있죠 이로써 두 로봇이 서로 얼마나 떨어져 있는지 알 수 있습니다 Amanda가 Bolts와 Sparky를 가까이 두자 불꽃이 늘어납니다 떨어뜨려 놓으면 불꽃이 약해지고요

    이제 로봇들에게 함께 움직이는 법과 동작을 맞추는 법을 가르치죠 RealityKit 컴포넌트에는 SwiftUI 기반 애니메이션을 사용합니다 SwiftUI에는 이미 보기 속성의 변경 사항을 묵시적으로 애니메이션화하는 유용한 애니메이션 API가 있습니다 여기서는 Sparky가 있는 Model3D 보기를 애니메이션화하죠 토글하면 왼쪽으로 가고 다시 토글하면 원래 위치로 돌아옵니다 `isOffset` 바인딩에 애니메이션을 추가합니다 탄력이 더 큰 애니메이션을 원한다고 명시하고요 visionOS 26에서는 이제 SwiftUI 애니메이션을 사용해 RealityKit 컴포넌트 변경 사항의 묵시적 애니메이션화가 가능합니다 RealityKit 애니메이션 블록 안의 엔티티에 지원되는 컴포넌트만 설정하면 프레임워크가 나머지를 처리합니다 애니메이션과 상태 변경을 연결하는 방법은 두 가지입니다 RealityView 내에서 `content.animate()`를 사용해 animate 블록 내의 컴포넌트에 새로운 값을 설정할 수 있습니다 RealityKit은 `update` 클로저를 트리거한 SwiftUI 트랜잭션과 연결된 애니메이션을 사용합니다 여기서는 탄력이 더 큰 애니메이션이죠

    다른 방법은 SwiftUI 애니메이션을 전달하는 새로운 Entity.animate() 함수와 컴포넌트에 새로운 값을 설정하는 클로저를 호출하는 것입니다 여기서는 isOffset 속성이 변경될 때마다 엔티티의 위치를 사용해 Sparky를 왼쪽 또는 오른쪽으로 보냅니다 animate 블록 내에 위치를 설정하면 Transform 컴포넌트의 묵시적 애니메이션이 시작되어 엔티티가 새로운 위치로 매끄럽게 이동합니다 묵시적 애니메이션의 강력함은 Amanda가 소개한 Object Manipulation API과 결합했을 때 빛납니다 SwiftUI 애니메이션으로 Bolts의 맞춤형 놓기 동작을 구현 가능하죠 먼저 객체 조작의 기본 놓기 동작을 .stay로 설정해 비활성화합니다 그런 다음 조작 상호작용의 WillRelease 이벤트를 구독합니다 객체를 놓으려고 할 때 Sparky의 transform을 identity로 설정해 Sparky를 반동식으로 돌려보냅니다 그러면 엔티티의 크기 조정 변환, 회전이 재설정됩니다 animate 블록 내에서 Sparky의 transform을 수정하는 것이므로 Sparky가 반동식으로 기본 위치로 돌아갑니다 Sparky가 원래 위치로 돌아오는 애니메이션이 더 재미있어졌죠 이러한 모든 내장 RealityKit 컴포넌트는 Transform, Audio 컴포넌트, 색상 속성이 있는 Model 및 Light 컴포넌트 등 묵시적 애니메이션을 지원합니다 Sparky와 Bolts의 긴 여정이었네요 SwiftUI와 RealityKit이 함께 멋지게 작동하니 멋지군요 이 연결을 통해 진정 뛰어난 공간 앱을 개발할 수 있게 되었습니다 가상과 현실을 실제로 이어 주죠 SwiftUI 컴포넌트를 RealityKit 장면에 원활하게 통합하고 엔티티가 동적으로 SwiftUI 상태 변경을 유발할 때의 가능성을 상상해 보세요 Sparky와 Bolts처럼 완전히 새로운 방식으로 SwiftUI와 RealityKit을 연결해 보세요 미래의 빌드에 함께해 주세요

    • 1:42 - Sparky in Model3D

      struct ContentView: View {
        var body: some View {
          Model3D(named: "sparky")
        }
      }
    • 1:52 - Sparky in Model3D with a name sign

      struct ContentView: View {
        var body: some View {
          HStack {
            NameSign()
            Model3D(named: "sparky")
          }
        }
      }
    • 3:18 - Display a model asset in a Model3D and present playback controls​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​

      struct RobotView: View {
        @State private var asset: Model3DAsset?
        var body: some View {
          if asset == nil {
            ProgressView().task { asset = try? await Model3DAsset(named: "sparky") }
          }
        }
      }
    • 3:34 - Display a model asset in a Model3D and present playback controls​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​

      struct RobotView: View {
        @State private var asset: Model3DAsset?
        var body: some View {
          if asset == nil {
            ProgressView().task { asset = try? await Model3DAsset(named: "sparky") }
          } else if let asset {
            VStack {
              Model3D(asset: asset)
              AnimationPicker(asset: asset)
            }
          }
        }
      }
    • 4:03 - Display a model asset in a Model3D and present playback controls​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​

      struct RobotView: View {
        @State private var asset: Model3DAsset?
        var body: some View {
          if asset == nil {
            ProgressView().task { asset = try? await Model3DAsset(named: "sparky") }
          } else if let asset {
            VStack {
              Model3D(asset: asset)
              AnimationPicker(asset: asset)
              if let animationController = asset.animationPlaybackController {
                RobotAnimationControls(playbackController: animationController)
              }
            }
          }
        }
      }
    • 4:32 - Pause, resume, stop, and change the move the play head in the animation​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​

      struct RobotAnimationControls: View {
        @Bindable var controller: AnimationPlaybackController
      
        var body: some View {
          HStack {
            Button(controller.isPlaying ? "Pause" : "Play") {
              if controller.isPlaying { controller.pause() }
              else { controller.resume() }
            }
      
            Slider(
              value: $controller.time,
              in: 0...controller.duration
            ).id(controller)
          }
        }
      }
    • 5:41 - Load a Model3D using a ConfigurationCatalog​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​

      struct ConfigCatalogExample: View {
        @State private var configCatalog: Entity.ConfigurationCatalog?
        @State private var configurations = [String: String]()
        @State private var showConfig = false
        var body: some View {
          if let configCatalog {
            Model3D(from: configCatalog, configurations: configurations)
              .popover(isPresented: $showConfig, arrowEdge: .leading) {
                ConfigPicker(
                  name: "outfits",
                  configCatalog: configCatalog,
                  chosenConfig: $configurations["outfits"])
              }
          } else {
            ProgressView()
              .task {
                await loadConfigurationCatalog()
              }
          }
        }
      }
    • 6:51 - Switching from Model3D to RealityView

      struct RobotView: View {
        let url: URL = Bundle.main.url(forResource: "sparky", withExtension: "reality")!
      
        var body: some View {
          HStack {
            NameSign()
            RealityView { content in
              if let sparky = try? await Entity(contentsOf: url) {
                content.add(sparky)
              }
            }
          }
        }
      }
    • 7:25 - Switching from Model3D to RealityView with layout behavior

      struct RobotView: View {
        let url: URL = Bundle.main.url(forResource: "sparky", withExtension: "reality")!
      
        var body: some View {
          HStack {
            NameSign()
            RealityView { content in
              if let sparky = try? await Entity(contentsOf: url) {
                content.add(sparky)
              }
            }
            .realityViewLayoutBehavior(.fixedSize)
          }
        }
      }
    • 8:48 - Switching from Model3D to RealityView with layout behavior and RealityKit animation

      struct RobotView: View {
        let url: URL = Bundle.main.url(forResource: "sparky", withExtension: "reality")!
      
        var body: some View {
          HStack {
            NameSign()
            RealityView { content in
              if let sparky = try? await Entity(contentsOf: url) {
                content.add(sparky)
                sparky.playAnimation(getAnimation())
              }
            }
            .realityViewLayoutBehavior(.fixedSize)
          }
        }
      }
    • 10:34 - Add 2 particle emitters; one to each side of the robot's head

      func setupSparks(robotHead: Entity) {
        let leftSparks = Entity()
        let rightSparks = Entity()
      
        robotHead.addChild(leftSparks)
        robotHead.addChild(rightSparks)
      
        rightSparks.components.set(sparksComponent())
        leftSparks.components.set(sparksComponent())
      
        leftSparks.transform.rotation = simd_quatf(Rotation3D(
          angle: .degrees(180),
          axis: .y))
      
        leftSparks.transform.translation = leftEarOffset()
        rightSparks.transform.translation = rightEarOffset()
      }
      
      // Create and configure the ParticleEmitterComponent
      func sparksComponent() -> ParticleEmitterComponent { ... }
    • 12:30 - Apply the manipulable view modifier

      struct RobotView: View {
        let url: URL
        var body: some View {
          HStack {
            NameSign()
            Model3D(url: url)
              .manipulable()
          }
        }
      }
    • 12:33 - Allow translate, 1- and 2-handed rotation, but not scaling

      struct RobotView: View {
        let url: URL
        var body: some View {
          HStack {
            NameSign()
            Model3D(url: url)
              .manipulable(
                operations: [.translation,
                             .primaryRotation,
                             .secondaryRotation]
             )
          }
        }
      }
    • 12:41 - The model feels heavy with high inertia

      struct RobotView: View {
        let url: URL
        var body: some View {
          HStack {
            NameSign()
            Model3D(url: url)
              .manipulable(inertia: .high)
          }
        }
      }
    • 13:18 - Add a ManipulationComponent to an entity​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​

      RealityView { content in
        let sparky = await loadSparky()
        content.add(sparky)
        ManipulationComponent.configureEntity(sparky)
      }
    • 13:52 - Add a ManipulationComponent to an entity​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​ with configuration

      RealityView { content in
        let sparky = await loadSparky()
        content.add(sparky)
        ManipulationComponent.configureEntity(
          sparky,
          hoverEffect: .spotlight(.init(color: .purple)),
          allowedInputTypes: .all,
          collisionShapes: myCollisionShapes()
        )
      }
    • 14:08 - Manipulation interaction events

      public enum ManipulationEvents {
      
        /// When an interaction is about to begin on a ManipulationComponent's entity
        public struct WillBegin: Event { }
        
        /// When an entity's transform was updated during a ManipulationComponent
        public struct DidUpdateTransform: Event { }
      
        /// When an entity was released
        public struct WillRelease: Event { }
      
        /// When the object has reached its destination and will no longer be updated
        public struct WillEnd: Event { }
      
        /// When the object is directly handed off from one hand to another
        public struct DidHandOff: Event { }
      }
    • 14:32 - Replace the standard sounds with custom ones

      RealityView { content in
        let sparky = await loadSparky()
        content.add(sparky)
      
        var manipulation = ManipulationComponent()
        manipulation.audioConfiguration = .none
        sparky.components.set(manipulation)
      
        didHandOff = content.subscribe(to: ManipulationEvents.DidHandOff.self) { event in
          sparky.playAudio(handoffSound)
        }
      }
    • 16:19 - Builder based attachments

      struct RealityViewAttachments: View {
        var body: some View {
          RealityView { content, attachments in
            let bolts = await loadAndSetupBolts()
            if let nameSign = attachments.entity(
              for: "name-sign"
            ) {
              content.add(nameSign)
              place(nameSign, above: bolts)
            }
            content.add(bolts)
          } attachments: {
            Attachment(id: "name-sign") {
              NameSign("Bolts")
            }
          }
          .realityViewLayoutBehavior(.centered)
        }
      }
    • 16:37 - Attachments created with ViewAttachmentComponent

      struct AttachmentComponentAttachments: View {
        var body: some View {
          RealityView { content in
            let bolts = await loadAndSetupBolts()
            let attachment = ViewAttachmentComponent(
                rootView: NameSign("Bolts"))
            let nameSign = Entity(components: attachment)
            place(nameSign, above: bolts)
            content.add(bolts)
            content.add(nameSign)
          }
          .realityViewLayoutBehavior(.centered)
        }
      }
    • 17:04 - Targeted to entity gesture API

      struct AttachmentComponentAttachments: View {
        @State private var bolts = Entity()
        @State private var nameSign = Entity()
      
        var body: some View {
          RealityView { ... }
          .realityViewLayoutBehavior(.centered)
          .gesture(
            TapGesture()
              .targetedToEntity(bolts)
              .onEnded { value in
                nameSign.isEnabled.toggle()
              }
          )
        }
      }
    • 17:10 - Gestures with GestureComponent

      struct AttachmentComponentAttachments: View {
        var body: some View {
          RealityView { content in
            let bolts = await loadAndSetupBolts()
            let attachment = ViewAttachmentComponent(
                rootView: NameSign("Bolts"))
            let nameSign = Entity(components: attachment)
            place(nameSign, above: bolts)
            bolts.components.set(GestureComponent(
              TapGesture().onEnded {
                nameSign.isEnabled.toggle()
              }
            ))
            content.add(bolts)
            content.add(nameSign)
          }
          .realityViewLayoutBehavior(.centered)
        }
      }
    • 0:00 - 서론
    • 기존 UI와 대화식 3D 콘텐츠를 원활하게 통합하는 RealityKit SwiftUI 개선 사항에 대해 알아보세요. 주요 업데이트로는 Model3D 및 RealityView의 개선, Object Manipulation API의 도입, 새 Component 유형, SwiftUI 및 RealityKit 간 양방향 데이터 흐름, 간편해진 좌표 공간 전환, RealityKit 구성 요소를 위한 SwiftUI 애니메이션이 있습니다.

    • 1:24 - Model3D 개선 사항
    • visionOS 26에서 Model3D를 사용하면 애니메이션을 포함한 3D 모델을 표시하고, ‘ConfigurationCatalog’로부터 로드할 수 있습니다. 이제 몇 줄의 코드만으로 대화식 3D 경험을 만들 수 있습니다. 새로운 ‘Model3DAsset’ 유형을 사용하여 애니메이션을 로드 및 제어하고, ‘ConfigurationCatalog’를 활용하여 복장이나 신체 유형과 같은 엔티티의 다양한 표현 간에 전환할 수 있습니다. SwiftUI 뷰와 선택기로 애니메이션을 적용하고, 가상 온실에서 다른 로봇을 만날 수 있도록 다양한 의상으로 꾸미며 제어할 수 있는 로봇 Sparky의 예시로 이러한 기능을 시연합니다.

    • 6:13 - RealityView 변환
    • 런타임 시 3D 모델에 입자 에미터를 추가하려면 ‘Model3D’ 대신 RealityKit의 ‘RealityView’를 사용해야 합니다. Model3D에서는 구성 요소의 추가를 지원하지 않기 때문입니다. RealityView는 모델 엔티티와 함께 로드되지만 기본적으로 사용 가능한 모든 공간을 차지하므로 레이아웃 문제가 발생합니다. 이 문제를 해결하기 위해 ‘realityViewLayoutBehavior’ 한정자를 ‘fixedSize’와 함께 적용하세요. 이렇게 하면 RealityView가 모델의 초기 경계에 딱 맞게 래핑합니다. ‘realityViewLayoutBehavior’에는 ‘flexible’, ‘centered’, ‘fixedSize’ 등 세 가지 다른 옵션이 있습니다. 각 옵션은 엔티티 재배치 없이 RealityView의 원래 지점에 영향을 미칩니다. Reality Composer Pro를 사용하여 코드에서 구성 가능한 입자 에미터를 설계하세요. 입자 에미터는 엔티티에 구성 요소로 추가됩니다. RealityKit은 ‘엔티티 컴포넌트 시스템’ 패러다임에 기반합니다. 이 예시에서는 불꽃 효과를 만들기 위해 입자 에미터를 추가하며, 보이지 않는 엔티티를 불꽃을 위한 컨테이너로 활용합니다. RealityView는 3D 콘텐츠의 세부적인 생성 및 세밀한 동작 제어에 탁월하며, Model3D는 단일 개별 3D 애셋을 표시할 때 적합합니다. 필요에 따라 두 기능 간에 원활하게 전환할 수 있습니다.

    • 11:52 - 개체 조작
    • visionOS 26의 새로운 Object Manipulation API를 사용하면 SwiftUI와 RealityKit를 사용하는 앱에서 사용자가 가상 객체와 상호작용할 수 있습니다. 사용자는 손으로 객체를 움직이고, 회전하고, 크기를 조정할 수 있으며, 심지어 양손 간에 객체를 전달할 수 있습니다. SwiftUI 뷰의 경우, ‘manipulable’ 한정자를 적용하여 지원되는 연산과 객체 관성을 맞춤화할 수 있습니다. RealityKit에서 정적 함수 ‘configureEntity’를 통해 엔티티에 ‘ManipulationComponent’를 추가하면 CollisionComponent, InputTargetComponent, HoverEffectComponent도 추가됩니다. 이 함수에는 동작 맞춤화를 위한 여러 매개변수가 있습니다. 상호작용 중 발생하는 ‘ManipulationEvents’(시작, 중단, 업데이트, 놓기, 전달 등)에 구독하여 앱 상태를 업데이트하고 경험을 맞춤화하세요. 기본 소리를 맞춤형 오디오 리소스로 대체할 수도 있습니다.

    • 15:35 - SwiftUI 구성 요소
    • 새로운 SwiftUI RealityKit 구성 요소는 RealityKit 장면 내에서 사용자 상호작용과 연결을 향상시킵니다. 이러한 구성 요소를 사용하면 SwiftUI 뷰를 3D 환경에 원활하게 통합할 수 있습니다. 주요 기능으로는 SwiftUI 뷰를 엔티티에 직접 추가할 수 있도록 하는 ‘ViewAttachmentComponent’, 엔티티가 터치와 제스처에 반응하도록 하는 ‘GestureComponent’, 장면 내에서 팝오버와 같은 SwiftUI 뷰를 표시할 수 있도록 하는 ‘PresentationComponent’가 있습니다. ‘PresentationComponent’의 configuration 매개변수는 표시될 표시 유형입니다. 이러한 개선 사항은 개발 절차를 간소화하고 개발자가 더욱 역동적이고 매력적인 경험을 만들 수 있도록 합니다. 예시에서는 탭 제스처로 켜고 끌 수 있는 이름 표지판을 로봇 Bolts에 추가합니다. 사용자는 몰입적인 RealityKit 환경에서 Bolts의 의상을 팝오버 메뉴를 통해 고를 수 있습니다.

    • 19:08 - 정보 흐름
    • visionOS 26에서는 엔티티를 관찰할 수 있으므로, 속성이 변경되면 로봇 Bolts와 같은 엔티티가 다른 코드에 이를 알릴 수 있습니다. 이 기능은 특히 Bolts가 온실에서 식물에 물을 줄 때의 움직임을 추적하는 데 유용합니다. 이 예시에서는 SwiftUI를 사용하여 Bolts의 위치를 실시간으로 표시하는 미니맵을 구현합니다. SwiftUI는 Bolts의 observable.position 속성에 접근하여 Bolts가 이동할 때마다 미니맵을 자동으로 업데이트합니다. 이러한 방식으로 observable 엔티티를 통해 구현되는 SwiftUI와 RealityKit 간의 양방향 데이터 흐름은 매우 중요한 새 도구입니다. 새로운 observable 기능을 신중하게 관리하지 않으면 무한 루프가 발생할 가능성이 있습니다. 이러한 루프를 피하려면 관찰된 상태를 수정하는 위치를 주의하세요. ‘RealityView’의 ‘update’ 클로저 내에서 관찰된 상태를 수정하지 마세요. 재귀형 업데이트 주기가 발생할 수 있습니다. 대신 SwiftUI의 보기 본문 평가 범위를 벗어나는 맞춤형 시스템, 제스처 클로저, ‘make’ 클로저와 같은 구체적인 위치에서 수정하세요. 큰 뷰를 작은 독립적인 뷰로 나누고, 상태가 어디에서 수정되는지에 주의하면 SwiftUI와 RealityKit 간 데이터 흐름이 원활하고 효율적으로 진행되도록 하고, 무한 루프를 방지하고, 애플리케이션의 전반적인 성능을 개선할 수 있습니다.

    • 24:56 - 통합 좌표 변환
    • 새로운 Unified Coordinate Conversion API는 RealityKit와 SwiftUI를 연결해 줍니다. ‘CoordinateSpace3D’ 프로토콜을 구현하면 두 프레임워크 간에 값을 원활하게 변환할 수 있습니다. 이를 통해 RealityKit의 엔티티인 Bolts와 SwiftUI의 Model3D인 Sparky 사이의 절대 거리를 계산하고, 두 개체가 가까워질 때 근접성에 따라 불꽃을 동적으로 생성할 수 있습니다.

    • 27:01 - 애니메이션
    • 이제 visionOS 26에서 SwiftUI의 애니메이션 API를 활용하여 RealityKit 구성 요소의 변경 사항을 묵시적 방식으로 애니메이션화할 수 있습니다. 애니메이션 블록 내에서 지원되는 구성 요소를 설정하기만 하면 엔티티의 부드럽고 탄력 있는 움직임이 가능해집니다. 이를 달성하는 방법은 두 가지가 있습니다. RealityView 내에서 ‘content.animate()’를 사용하거나 ‘Entity.animate()’를 직접 호출하면 됩니다. 이 통합을 통해 엔티티를 조작할 때 맞춤형 놓기 동작을 구현하고, 더 매력적이고 재미있는 3D 객체와의 상호작용을 만들 수 있습니다. Transform, Audio, Model, Light를 포함한 다양한 RealityKit 구성 요소는 이러한 묵시적 애니메이션을 지원합니다.

    • 29:41 - 다음 단계
    • SwiftUI와 RealityKit 간의 새 연결을 활용하여 SwiftUI 구성 요소를 RealityKit 장면에 통합하고 혁신적인 공간 앱을 개발하세요. 가상과 현실 간에 이어지는 역동적인 상호작용을 만들고 완전히 새로운 앱을 개발해 보세요.

Developer Footer

  • 비디오
  • WWDC25
  • 함께하면 더욱 탁월한 SwiftUI 및 RealityKit
  • 메뉴 열기 메뉴 닫기
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    메뉴 열기 메뉴 닫기
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • SF Symbols
    메뉴 열기 메뉴 닫기
    • 손쉬운 사용
    • 액세서리
    • 앱 확장 프로그램
    • App Store
    • 오디오 및 비디오(영문)
    • 증강 현실
    • 디자인
    • 배포
    • 교육
    • 서체(영문)
    • 게임
    • 건강 및 피트니스
    • 앱 내 구입
    • 현지화
    • 지도 및 위치
    • 머신 러닝 및 AI
    • 오픈 소스(영문)
    • 보안
    • Safari 및 웹(영문)
    메뉴 열기 메뉴 닫기
    • 문서(영문)
    • 튜토리얼
    • 다운로드(영문)
    • 포럼(영문)
    • 비디오
    메뉴 열기 메뉴 닫기
    • 지원 문서
    • 문의하기
    • 버그 보고
    • 시스템 상태(영문)
    메뉴 열기 메뉴 닫기
    • Apple Developer
    • App Store Connect
    • 인증서, 식별자 및 프로파일(영문)
    • 피드백 지원
    메뉴 열기 메뉴 닫기
    • Apple Developer Program
    • Apple Developer Enterprise Program
    • App Store Small Business Program
    • MFi Program(영문)
    • News Partner Program(영문)
    • Video Partner Program(영문)
    • Security Bounty Program(영문)
    • Security Research Device Program(영문)
    메뉴 열기 메뉴 닫기
    • Apple과의 만남
    • Apple Developer Center
    • App Store 어워드(영문)
    • Apple 디자인 어워드
    • Apple Developer Academy(영문)
    • WWDC
    Apple Developer 앱 받기
    Copyright © 2025 Apple Inc. 모든 권리 보유.
    약관 개인정보 처리방침 계약 및 지침