
-
근거리에 있는 사람들과 visionOS 경험 공유하기
같은 방에서 Vision Pro를 사용하는 사람들을 위해 공유 경험을 만드는 방법을 알아보세요. 앱에 Shareplay를 통합하고 앱에서 ARKit을 활용하며, 근거리에 있는 사람과 FaceTime을 이용하는 사람들에게 업데이트된 창 공유 플로를 소개하고, 원활한 협력을 위해 새롭게 고안된 API를 살펴보는 방법을 보여드립니다. 눈에 띄고, 쉽게 인식 가능하며, 같은 공간에 있는 사람들이 참여할 수 있도록 협업 기능을 만드는 모범 사례를 확인하세요.
챕터
- 0:00 - 서론
- 0:56 - 근거리 공유에 대해 알아보기
- 4:21 - 근거리 활동 빌드하기
- 5:35 - 공유 메뉴를 통한 공유 기능 지원하기
- 9:15 - 근거리 사용자를 위해 경험 개선하기
- 10:37 - 사용자에 맞춰 콘텐츠 배치하기
- 13:20 - 공유된 미디어 재생 동기화하기
- 15:38 - 멀티 윈도우 지원하기
- 16:50 - 고정된 콘텐츠 공유하기
리소스
- AVPlaybackCoordinator
- Building a guessing game for visionOS
- Configure your visionOS app for sharing with people nearby
- groupActivityAssociation(_:)
- init(originFromAnchorTransform:sharedWithNearbyParticipants:)
- worldAnchorSharingAvailability
관련 비디오
WWDC25
WWDC24
WWDC23
WWDC21
-
비디오 검색…
visionOS FaceTime 팀 엔지니어 Connor입니다 여러분께 더 강력해진 공유, 협업 기능을 소개해 드리게 되어서 기쁩니다 visionOS 26에서는 같은 공간에 있는 주변 사람들과 앱 및 경험을 공유할 수 있습니다 Vision Pro 앱에서 다른 사람들과 함께 보고, 듣고 협업할 수 있죠 이 영상에서는 앱을 설계하고 구축해서 주변 사람들과 공유하는 법에 대해 배웁니다 먼저 이 기능을 간단히 소개하겠습니다 그리고 SharePlay로 주변 사람들과 대화형 경험을 구축하는 방법을 보여드리겠습니다 마지막으로 ARKit과 공유 월드 앵커로 물리적 공간에 공유 콘텐츠를 고정하는 법을 보여드리겠습니다 visionOS 26에서 주변 사람들과 공유하는 작동 원리부터 소개하죠 이 기능은 앱을 지금보다 훨씬 더 직관적이고 쉽게 검색할 수 있도록 합니다 모든 윈도우 막대 오른쪽에 있는 버튼을 탭하면 공유 메뉴가 열립니다 목록에서 주변 사람을 선택해서 쉽게 공유할 수 있습니다 누군가가 공유를 시작하면 공간에 있는 모든 사람이 공유자가 배치한 위치에서 윈도우를 볼 수 있죠 윈도우 막대는 녹색으로 바뀌어 공유 중임이 표시됩니다 공유 컨텍스트 경험은 그 공간에 있는 모든 사람에게 마법처럼 느껴집니다 동일한 위치에 윈도우가 나타나기 때문에 그 공간에 진짜로 앱이 존재하는 것처럼 말이죠 이야기를 나누고 가리키고 상호작용할 수 있습니다
이 시스템으로 모든 사람이 같은 위치, 같은 크기로 앱을 볼 수 있습니다
표시된 윈도우는 누구나 움직일 수 있고 다른 사람에게도 반영되어 자연스럽게 정렬됩니다
함께 앱 크기를 조정하거나 주변에 맞춰 정렬할 수도 있습니다 Digital Crown을 누르면 중심이 재정렬되고 앱은 모두에게 보기 좋은 위치로 다시 이동합니다 누군가가 무언가를 가리키거나 창 위로 손을 움직이면 그 사람이 잘 보이도록 하기 위해 콘텐츠가 사라집니다
같은 공간에 있는 사람에게만 공유되는 것이 아닙니다 근거리 공유의 가장 흥미로운 점은 FaceTime과 통합이 된다는 점이죠 주변 사람들과 앱을 공유할 때 원격으로 다른 사람을 FaceTime에 초대해서 어울리고 협업할 수 있습니다 상대는 공간 페르소나로 visionOS에 참여합니다 모두가 같은 공간에 있는 것처럼 느낄 수 있는 완전히 새로운 수준의 존재감이죠
공간 페르소나로 참여할 때 시스템은 최적의 위치를 찾아 주변 사람들과 함께 배치합니다
시스템이 공간 페르소나를 어디에 배치할지는 공유 중인 윈도우 유형에 따라 다릅니다 볼류메트릭 윈도우라면 페르소나는 볼륨 주변의 빈 공간을 채우게 됩니다 결과적으로 주변 및 원격 참가자 모두에게 이상적인 레이아웃이죠 iOS나 macOS처럼 FaceTime을 지원하는 다른 플랫폼에서도 참가할 수 있습니다
참가자의 영상은 공유 윈도우 옆에 표시됩니다 앱과 상호작용하면서 다른 사람들과 대화할 수 있죠 윈도우를 공유하면 해당 앱이 없어도 보기 전용 경험이 시작됩니다 별도의 앱 설치 없이 사용할 수 있습니다 하지만 같은 공간에서 다른 사람들과 함께 나누는 대화형 경험에는 SharePlay가 필요합니다 SharePlay는 Apple 플랫폼 전반 에서 FaceTime 협업의 핵심 요소죠 SharePlay는 실시간 상호작용을 지원하기 때문에 함께 영화를 보고 음악을 듣고 게임을 하고 협업하는 것 외에도 많은 일을 함께할 수 있습니다 visionOS의 기존 SharePlay 앱은 아무런 변경 없이 주변 사람과 공유할 수 있습니다 SharePlay에 새롭게 추가된 기능으로는 특히 같은 공간의 사람들을 위해 앱을 설계하고 강화할 수 있습니다
SharePlay가 처음이라면 “Group Activity로 맞춤형 경험 구축하기”와 “공간 SharePlay 경험 빌드하기” 영상을 먼저 추천합니다 저는 그 영상에서 소개한 개념을 확장해 설명하겠습니다 이번에 다루는 주제는 훌륭한 근거리 공유 경험을 만드는 데 도움이 될 것입니다 먼저 공유 메뉴에 SharePlay 활동을 띄워서 사람들이 쉽게 공유를 시작할 수 있게 합니다 주변에 있는 참가자를 확인하고 같은 공간에 있는 사람들의 경험을 향상시키는 방법과 주변 참가자의 포즈에 맞춰 콘텐츠를 배치하는 법을 다룹니다 AVPlayer를 사용한 공유 미디어 재생 예제로 영상과 오디오를 같은 공간에서 동기화하는 방법도 다룹니다 마지막으로 앱에 창이 여러 개 있을 때 SharePlay와 연동할 창을 선택하는 방법을 알아봅니다 먼저 공유 메뉴에서 SharePlay 활동이 사용 가능하도록 설정합니다 새 공유 메뉴가 visionOS 26에 도입되면서 앱을 공유하는 것이 훨씬 더 쉬워졌죠 이 기능을 적극 활용하려면 SwiftUI 또는 UIKit API로 GroupActivity를 공유 메뉴에 노출시켜야 합니다 사람들이 공유 버튼을 누르면 GroupActivity가 활성화되고 SharePlay가 시작되죠 볼류메트릭 윈도우의 경우 활동이 노출된 앱에서만 공유 메뉴를 사용할 수 있습니다 SharePlay로 보드게임 앱을 만들어서 친구 집에서 함께 즐긴다고 가정해 봅시다
먼저 보드게임 앱에 BoardGameActivity라고 하는 단순한 그룹활동을 만듭니다 GroupActivities는 SharePlay 프레임워크이자 공유 경험의 첫 번째 단계입니다 게임의 주요 장면은 보드게임 뷰를 보여주는 볼류메트릭 Window Group으로 설정했습니다 SwiftUI 앱이기 때문에 뷰 계층 구조에 ShareLink를 추가해야 BoardGameActivity가 공유 메뉴에 노출됩니다 시작하려는 BoardGameActivity을 패스하고 앱 UI에 영향이 없도록 hidden 속성으로 설정합니다 누군가가 공유 메뉴에서 노출된 활동을 공유하면 자동으로 활성화되어 GroupSession을 생성합니다 GroupActivity에서 수동으로 activate() 메서드를 호출하는 것과 같은 방식이죠
BoardGameActivity에 세션을 보고 자동 생성된 GroupSession을 가져올 수 있습니다 이제 SharePlay를 시작해 보죠 생성된 세션을 구성하고 참여하겠습니다
이제 다른 사람들이 공유 메뉴에서 직접 제 앱을 공유할 수 있습니다 지금까지 창 기반이나 볼류메트릭 앱에 대해 다뤘지만 공유 경험이 몰입형 공간을 사용하면 몇 가지를 추가 고려해야 공유 메뉴가 항상 접근 가능합니다 윈도우 기반 경험을 공간의 마루에 놓여있는 실제 보드게임 테이블 크기로 바꾸고 테마 3D 오브젝트로 주변을 꾸며서 더 현실감 있는 경험을 만들고자 합니다 그러려면 ImmersiveSpace에 앱을 넣어야겠죠 그런데 문제가 발생했습니다 몰입형 공간에 윈도우 막대가 없는데 어떻게 앱을 공유하나요? 앱 내에 자체 버튼을 만들면 윈도우나 볼륨 없이도 사람들이 공유를 시작할 수 있습니다 누군가가 그 버튼을 누르면 BoardGameActivity에서 activate 메서드를 호출합니다 visionOS 26의 새로운 기능으로 활동이 activate되면 FaceTime 외부에서도 자동으로 공유 버튼이 나타납니다 창과 몰입형 경험 모두에서 이 기능을 사용할 수 있습니다 이제 주변 사람을 선택하거나 FaceTime을 시작해서 앱 내에서 직접 공유를 시작할 수 있습니다
더 좋은 방법은 비 몰입형 모드를 앱 내에 계속 제공하는 겁니다
비 몰입형 윈도우의 공유 메뉴로 공유를 시작하고 모두가 참여한 후 몰입형 공간으로 전환할 수 있습니다
아까와 마찬가지로 SharePlay를 시작하려면 GroupSession에 참여해야 합니다 또, 시스템 코디네이터에서 supportsGroupImmersiveSpace 속성을 true로 설정해야 몰입형 공간이 같은 방의 모두와 공유됩니다 앱이 공유되었다면 주변 사람들의 경험을 향상시키거나 맞춤화하기 위한 최적의 방법이 무엇인지 알아볼 때 입니다 새 SharePlay API는 주변 참가자를 확인할 수 있습니다 근거리 공유 중 FaceTime을 사용할 때 특히 유용하죠 보드게임 앱에서 같은 공간에 있는 사람들이 FaceTime으로 참여한 사람들과 대결하도록 해 보죠 물리적으로 함께 있는 참가자를 확인하고 주변 사람들을 팀으로 제안할 수 있어야겠죠
그러려면 주변 참가자와 원격 참가자를 구분할 수 있어야 합니다
앞에서 가입했던 GroupSession으로 시작합니다 세션의 참가자 정보는 GroupSession의 활성화된 참가자로 확인합니다 참가자 상태에서 isNearbyWithLocalParticipant 속성을 확인합니다 제 주변에 있는 사람은 true 값으로 나타납니다 제 주변에 있는 사람만 확인하면 되니 로컬 참가자는 제외합니다
이제 주변에 있는 참가자들과 한 팀이 되어 원격 참가자들과 대결할 수 있게 됩니다 같은 공간에 있는 사람들의 경험을 개선하려면 앱과 비교해 상대적인 참가자의 위치를 고려하는 것도 중요합니다 사람들 옆이나 앞에 콘텐츠를 배치할 수 있으니까요 새 SharePlay API는 공유 시작 시 사람들의 정확한 위치를 알려 주죠 예를 들어 보겠습니다 지금까지는 주변 사람들이 앱 앞에 있다고 가정했지만 실제로는 그 공간의 어디에나 있을 수 있죠 앱은 공유를 시작할 때 제자리에 고정되어 있고 사람들 사이에서 자동으로 위치가 이동되지는 않습니다 특히 몰입형 공간 앱에서는 콘텐츠를 원래 몰입형 공간이 아닌 사람들 주변에 배치하고 싶을 수 있습니다 예시로 든 몰입형 보드게임 앱에서 주변 사람 각자에게 점수판을 보여주려고 합니다
그러려면 먼저 주변 참가자가 ImmersiveSpace 장면에 상대적으로 어디에 있는지를 알아야 합니다 ParticipantState의 Pose 속성으로 알 수 있죠
이전처럼 참여 중인 Group Session을 사용합니다 localParticipant에 대한 정보를 얻으려면 세션의 시스템 코디네이터로 액세스해서 localParticipantState의 비동기 시퀀스를 확인합니다 localParticipantState가 있으면 그 상태의 새로운 pose 속성을 읽을 수 있습니다 ImmersiveSpace 장면에 대한 로컬 참가자의 위치를 확인합니다
pose 속성은 실시간으로 참가자 위치를 추적하지 않고 공유가 시작되거나 Digital Crown을 가진 사람이 재중심화하는 등 중요 이벤트가 있으면 업데이트됩니다 로컬 참가자 pose로 점수판을 놓을 위치를 계산해서 참가자 옆에 점수판을 띄울 수 있습니다
참가자의 실제 pose에 더해, 앱을 기준으로 고정된 물리적 위치인 참가자의 Seat Pose도 알아볼 수 있습니다 참가자의 고정 위치와 공간 템플릿으로 자리 배치를 맞춤화하는 방법은 “SharePlay에서 공간영상을 페르소나 템플릿 맞춤화하기”를 참고하세요 이 영상의 정보를 활용하면 앱에서 읽어들인 고정 위치로 고급 기능을 구현하고 사람들을 실제 자리로 안내할 수도 있습니다 사람들이 콘텐츠를 가장 잘 즐길 수 있는 위치로 말이죠 사람들의 실제 위치에 콘텐츠를 배치하고 싶을 때는 고정 위치에서 참가자 위치로 전환해 보세요
영상이나 오디오를 앱에서 공유하는 경우 visionOS 26에서 업그레이드된 AVPlayer로 같은 공간에 있는 사람들 간에 재생을 조율할 수 있습니다 공유 미디어를 같은 공간에 가져오면 독특한 문제 특히 사람들이 물리적으로 가까워 발생하는 문제가 생길 수 있습니다 주변 사람에게 미디어를 공유했는데 다른 참가자 기기에서 나오는 소리가 들릴 수 있죠 소리가 조금만 어긋나도 모든 주변 참가자에게 큰 방해가 됩니다 그래서 모든 사람이 같은 콘텐츠를 동시에 보고 듣는 것이 중요합니다 visionOS 26의 새 기능인 AVPlayer 와 AVPlaybackCoordinator는 같은 공간의 사람들에게 정확한 오디오/비디오 동기화를 제공하죠
이 기능을 보여드리기 전에 언제든지 공유 영상에서 게임 방법을 볼 수 있도록 보드 게임 앱에 설명 영상을 추가하고자 합니다 모든 사람은 정확히 동시에 영상을 보고 들어야 합니다 영상이 끝나면 방금 배운 내용을 바탕으로 게임을 다시 시작할 수 있습니다
앱에서 AVPlaybackCoordinator를 만들고 GroupSession을 설정하죠 이렇게 하면 같은 공간에 있는 사람들 간에 영상 재생이 동기화됩니다 즉 앱에서 에코나 딜레이 없이 모든 사람이 정확히 같은 시점에 설명 영상을 보고 들을 수 있습니다
SharePlay용 AVPlaybackCoordinator 설정은 “Group Activities로 미디어 경험 조율하기” 영상을 참고하세요 visionOS용 미디어 앱 개발이 처음이라면 “훌륭한 공간 재생 경험 만들기” 영상도 추천드립니다 이제 앱의 오디오와 영상 재생이 동기화되었습니다 하지만 설명 영상 윈도우을 추가했더니 새로운 문제가 생겼습니다 보드게임과 설명 영상 WindowGroup 2개가 동시에 열려 있게 된 거죠 1개의 윈도우를 공유 대상으로 지정해서 문제를 해결해 보겠습니다 visionOS 26의 새로운 SharePlay API에 있는 뷰 수정자로 어떤 시점에 어떤 WindowGroup을 SharePlay와 공유할지 선택하죠 멀티 윈도우를 지원하는 앱에 혁신적인 제어 권한을 제공합니다
이 보드게임 앱에서는 이전의 WindowGroup이 있습니다 설명 영상을 추가하기 위해 영상 뷰를 호스팅할 두 번째 WindowGroup을 생성합니다 새 groupActivityAssociation 뷰 수정자를 해당 WindowGroup에 추가합니다
primary를 .groupActivityAssociation에 전달해, 이 비디오가 열려 있는 동안에는 공유 비디오로 만들겠다고 시스템에 알리는 거죠
이제 모두가 준비되었을 때 완벽화게 동기화된 공유 영상을 본 후 다시 게임으로 돌아올 수 있습니다
이 기능을 활용해 여러분이 만들게 될 멋진 사회적 경험이 기대됩니다 지금이야 말로 SharePlay를 도입하기 가장 좋은 때입니다 그러나 GroupActivity가 새로운 주변 공유 API를 제공하는 유일한 프레임워크는 아닙니다 visionOS 26에서는 ARKit의 공유 월드 앵커로 동일한 물리적 위치에 콘텐츠를 고정할 수도 있습니다 먼저 몰입형 공간과 일반적인 월드 앵커의 주요 개념에 대해 간단히 짚고 넘어가겠습니다 앱이 ImmersiveSpace를 사용하는 경우 공간 어디에나 자유롭게 콘텐츠를 배치할 수 있습니다 사용자 주변에 무언가를 표시하기에는 유용하지만 ImmersiveSpace가 동일한 물리적 위치에 유지된다는 보장은 없죠 누군가가 Digital Crown을 잡고 중심을 재설정하면 몰입형 공간의 전체 위치가 조정됩니다 다양한 사례에서 활용할 수 있지만 콘텐츠를 물리 공간에 고정할 수는 없습니다 이때 ARKit의 World Anchor가 사용됩니다 월드 앵커는 물리 공간 내 특정한 고정 위치에 콘텐츠를 배치합니다 앵커로 고정한 콘텐츠는 시간이 지나거나 재중심화를 하는 경우에도 처음 위치에 고정되어 있습니다
예를 들어 현실 공간에 가상의 가구를 배치해서 가구 배치 계획을 세우거나 새로운 디자인을 실험할 때 도움이 되겠죠 이런 앱에서는 시간이 지나거나 사용자가 이동하더라도 가구가 동일한 물리적 위치에 계속 머물러 있어야 합니다 그러려면 몰입형 공간의 원점을 기준으로 특정한 위치에 콘텐츠를 배치하고 월드 앵커로 그 자리에 고정해야 합니다 먼저 ARKitSession과 WorldTrackingProvider를 설정하죠 WorldTrackingProvider를 세션에 실행합니다 가구 앱에서 새 앵커를 추가하고 싶으면 ImmersiveSpace에서 가구를 위한 특정 transform에 WorldAnchor를 생성한 다음 WorldTrackingProvider에 추가합니다 WorldTrackingProvide의 anchorUpdates 시퀀스의 업데이트를 기다립니다 새 앵커가 추가되면 시퀀스가 호출됩니다 이제 해당 앵커 아래에 가구를 배치하면 됩니다
같은 물리적 공간에서 사람들이 앱을 공유할 수 있게 되면서 월드 앵커의 중요성이 더 높아졌습니다 주변에 누군가와 공유할 때 공간 자체가 공유 컨텍스트의 일부가 되죠 월드 앵커로 공간을 공유 컨텍스트로 활용할 수 있습니다 특정 물리 위치에 공유 콘텐츠를 배치하려면 새로운 API를 사용하여 앵커를 주변 참가자와 공유된 상태로 표시해야 합니다 SharePlay 세션 중에만 이 API는 월드 앵커의 생성을 허용하고 주변에 있는 참가자에게만 자동으로 공유됩니다 SharePlay가 활성화되어 있을 때만 공유 앵커를 생성할 수 있고 일반 앵커와는 달리 공유가 끝나면 사라집니다
앞선 가구 배치 앱에서 이 기능을 사용해 보겠습니다 누군가가 방에 가구를 추가하면 주변 참가자에게도 해당 가구를 표시하려고 합니다 마치 방을 함께 설계하는 것처럼 느껴지겠죠 공유 월드 앵커를 만들기 전에 이것을 사용할 수 있는지 먼저 확인해야 합니다 WorldTrackingProvider에서 WorldAnchorSharingAvailability 를 확인하죠. available이면 앱이 주변 사람들과 공유되고 있고, 공유 월드 앵커를 만들 수 있다는 뜻입니다
공유 월드 앵커를 만드는 방법은 일반 월드 앵커를 만드는 방법과 유사합니다 가구를 추가하는 사람은 월드 앵커를 생성할 때 haredWithNearbyParticipants 값을 true로 설정합니다 모든 주변 참가자의 WorldTrackingProviders에 anchorUpdates 시퀀스에서 새 공유 앵커를 받고 모든 사람의 기기에 동일한 식별자가 존재합니다 이 식별자를 읽고 동기화하면 각 기기에서 앵커에 연결할 콘텐츠를 식별할 수 있습니다
SharePlay 외부의 자체 네트워킹 레이어를 사용하는 비즈니스 앱에서도 공유 월드 앵커를 활용할 수 있습니다 주변 사람들과 공유할 수 있는 공간 기반 비즈니스 앱 개발은 “공간 비즈니스 앱의 개선 사항 살펴보기” 영상을 참고해 보세요
주변 사람들과의 공유로 같은 공간에서 함께 콘텐츠를 즐길 수 있습니다 공유 월드 앵커는 함께 있는 공간을 온전히 활용하는 경험을 사람들에게 제공합니다
이 영상이 끝나면 여러분의 앱에서 SharePlay를 이용해 대화형 공유 경험을 어떻게 지원할지 고민해 보세요 어떻게 하면 사람들이 앱의 콘텐츠에 동참할 수 있을까요? SharePlay를 도입했다면 새 공유 메뉴를 활용해 누구나 시스템 UI에서 앱을 공유할 수 있게 해 보세요 시스템 UI에서 공유 경험을 조명하는 가장 좋은 방법입니다 어떻게 하면 주변 참가자와 원격 참가자 모두를 위한 경험을 설계할 수 있을지도 생각해 보세요 공유 기능은 주변 참가자와 원격 참가자 모두를 지원합니다 이제 여러분의 앱으로 같은 공간 안에 있는 사람들을 전 세계에 있는 사람들과 연결할 수 있습니다 모든 가능성을 열어두세요 그리고 물리 공간을 활용하는 경험을 만들어 보세요 더욱 생생하고 상호작용적인 가상 콘텐츠를 World Anchor로 실제 공간에서 구현해 보세요 이 모든 것을 결합하면 가능성은 무한해집니다 협업을 위해 물리적 벽에 화이트보드를 띄우거나 거실에서 몰입형 게임을 즐기거나 방을 친구들과 영화를 즐기는 극장으로 바꿀 수도 있습니다 근거리 경험 공유라는 새롭고 강력한 도구로 주변 사람들과 연결되고 협업할 수 있습니다 사람들과 함께 있는 동안 더욱 함께 있음을 실감하는 경험을 만들어 주시기를 바랍니다
-
-
6:21 - Expose an activity with GroupActivities and SwiftUI
// Expose an activity with GroupActivities and SwiftUI import SwiftUI import GroupActivities struct BoardGameActivity: GroupActivity, Transferable { var metadata: GroupActivityMetadata = { var metadata = GroupActivityMetadata() metadata.title = "Play Together" return metadata }() } struct BoardGameApp: App { var body: some Scene { WindowGroup { BoardGameView() ShareLink(item: BoardGameActivity(), preview: SharePreview("Play Together")) .hidden() } .windowStyle(.volumetric) } } struct BoardGameView: View { var body: some View { // Board game content } }
-
7:14 - Join a GroupSession with GroupActivities
// Join a GroupSession with GroupActivities func observeSessions() async { // Sessions are created automatically when the activity is activated for await session in BoardGameActivity.sessions() { // Additional configuration and setup // Join SharePlay session.join() } }
-
8:57 - Join and configure a GroupSession with GroupActivities
// Join a GroupSession with GroupActivities func observeSessions() async { // Sessions are created automatically when the activity is activated for await session in BoardGameActivity.sessions() { // Additional configuration and setup guard let systemCoordinator = await session.systemCoordinator else { continue } systemCoordinator.configuration.supportsGroupImmersiveSpace = true // Join SharePlay session.join() } }
-
9:59 - Check for nearby participants with GroupActivities
// Check for nearby participants with GroupActivities func observeParticipants(session: GroupSession<BoardGameActivity>) async { for await activeParticipants in session.$activeParticipants.values { let nearbyParticipants = activeParticipants.filter { $0.isNearbyWithLocalParticipant && $0 != session.localParticipant } } }
-
11:42 - Observe local participant pose with GroupActivities
// Observe local participant pose with GroupActivities func observeLocalParticipantState(session: GroupSession<BoardGameActivity>) async { guard let systemCoordinator = await session.systemCoordinator else { return } for await localParticipantState in systemCoordinator.localParticipantStates { let localParticipantPose = localParticipantState.pose // Place presented content relative to the local participant pose } }
-
15:54 - Associate a specific window with GroupActivities and SwiftUI
// Associate a specific window with GroupActivities and SwiftUI import SwiftUI import GroupActivities struct BoardGameApp: App { var body: some Scene { WindowGroup { BoardGameView() ShareLink(item: BoardGameActivity(), preview: SharePreview("Play Together")) .hidden() } .windowStyle(.volumetric) WindowGroup(id: "InstructionalVideo") { InstructionalVideoView() .groupActivityAssociation(.primary("InstructionalVideo")) } } } struct BoardGameView: View { var body: some View { // Board game content } } struct InstructionalVideoView: View { var body: some View { // Video content } }
-
18:27 - Create a world anchor with ARKit
// Create a world anchor with ARKit import ARKit class AnchorController { func setUp(session: ARKitSession, provider: WorldTrackingProvider) async throws { try await session.run([provider]) } func createAnchor(at transform: simd_float4x4, provider: WorldTrackingProvider) async throws { let anchor = WorldAnchor(originFromAnchorTransform: transform) try await provider.addAnchor(anchor) } func observeWorldTracking(provider: WorldTrackingProvider) async { for await update in provider.anchorUpdates { switch update.event { case .added, .updated, .removed: // Add, update, or remove furniture break } } } }
-
20:02 - Observe sharing availability with ARKit
// Observe sharing availability with ARKit func observeSharingAvailability(provider: WorldTrackingProvider) async { for await sharingAvailability in provider.worldAnchorSharingAvailability { if sharingAvailability == .available { // Store availability to check when creating a new shared world anchor } } }
-
20:24 - Create a shared world anchor with ARKit
// Create a shared world anchor with ARKit import ARKit class SharedAnchorController { func setUp(session: ARKitSession, provider: WorldTrackingProvider) async throws { try await session.run([provider]) } func createAnchor(at transform: simd_float4x4, provider: WorldTrackingProvider) async throws { let anchor = WorldAnchor(originFromAnchorTransform: transform, sharedWithNearbyParticipants: true) try await provider.addAnchor(anchor) } func observeWorldTracking(provider: WorldTrackingProvider) async { for await update in provider.anchorUpdates { switch update.event { case .added, .updated, .removed: // Add, update, or remove furniture. Updates with shared anchors from others! let anchorIdentifier = update.anchor.id } } } }
-
-
- 0:00 - 서론
visionOS 26에서 FaceTime 사용자는 앱과 경험을 Apple Vision Pro를 착용한 주변 사용자와 공유할 수 있습니다. 즉, SharePlay 및 ARKit를 사용하면 같은 물리적 공간에서 함께 콘텐츠를 보고, 듣고, 콘텐츠와 상호작용할 수 있습니다.
- 0:56 - 근거리 공유에 대해 알아보기
visionOS 26의 새로운 공유 기능을 사용하면 윈도우 막대 오른쪽에 있는 버튼을 탭하여 주변 사용자와 앱을 쉽게 공유할 수 있습니다. 공유 윈도우는 방에 있는 모든 사용자를 대상으로 같은 물리적 위치에 나타나며, 그 공간에 앱이 실제로 존재하는 것처럼 사용자는 앱에 관해 이야기를 나누고, 앱을 가리키고, 앱과 상호작용할 수 있습니다. 시스템은 윈도우가 모든 사용자에게 일관된 모습과 크기로 표시되도록 합니다. 누구나 윈도우를 옮기거나 크기를 조정할 수 있으며, 이러한 변경 사항은 모든 사람에게 반영됩니다. 원격 사용자는 visionOS에서 FaceTime을 통해 공유 세션에 참여할 수 있으며, 공유 공간 내에서 공간 페르소나로 나타나게 됩니다. iOS 또는 macOS를 통해 참여하는 경우 공유 윈도우 옆에 비디오 피드로 나타나 위치와 관계없이 원활한 협업이 가능합니다.
- 4:21 - 근거리 활동 빌드하기
visionOS의 모든 기존 SharePlay 앱은 변경 없이도 주변 사용자와 공유될 수 있습니다. 하지만 같은 공간에 있는 사용자를 위해 SharePlay 경험을 개선할 때 사용할 수 있는 새로운 기능이 있습니다. 새 기능에는 새로운 공유 메뉴에서 SharePlay 활동이 검색 가능하도록 하고, 주변에 있는 참가자를 감지하고, pose에 따라 콘텐츠를 배치하고, 미디어 재생을 동기화하는 기능이 포함됩니다.
- 5:35 - 공유 메뉴를 통한 공유 기능 지원하기
visionOS 26에서 앱에 대해 SharePlay를 활성화하려면 SwiftUI나 UIKit API로 GroupActivity를 노출해야 합니다. 이 기능을 활성화하면 사용자가 새로운 공유 메뉴에서 바로 SharePlay를 시작할 수 있습니다. 볼류메트릭 윈도우의 경우, 활동을 사용하려면 먼저 공유 메뉴에서 노출되어야 합니다. 기존 공유 메뉴에 접근할 수 없는 몰입형 공간의 경우 앱 내에서 공유 메뉴를 활성화하는 버튼을 제공할 수 있습니다. 또한 비 몰입형 모드를 제공하여 사용자가 공유 메뉴에서 공유한 다음, 모든 사용자가 세션에 참여한 후 몰입형 모드로 전환하도록 할 수 있습니다.
- 9:15 - 근거리 사용자를 위해 경험 개선하기
SharePlay API를 사용하여 GroupSession에서 주변 참가자를 식별하세요. 활성화된 참여자를 관찰하고 ‘isNearbyWithLocalParticipant’ 속성을 확인하면 로컬 사용자와 원격 사용자를 구별할 수 있습니다. 이 접근 방식을 사용하면 옆에서 같이 게임을 플레이하는 주변 사용자와 FaceTime를 통해 참여하는 원격 참가자를 각각 자동으로 팀으로 묶는 것과 같은 기능을 사용할 수 있습니다.
- 10:37 - 사용자에 맞춰 콘텐츠 배치하기
‘ParticipantState’의 새로운 ‘pose’ 속성은 앱 세션 내 참가자의 공간적 위치를 제공하며, 이를 통해 각 참가자 옆에 콘텐츠를 동적으로 배치할 수 있습니다. 이는 몰입형 경험을 개선하고, 사용자를 최적의 좌석 위치로 안내하는 것과 같은 고급 기능을 활용할 수 있게 합니다.
- 13:20 - 공유된 미디어 재생 동기화하기
visionOS 26에서 AVPlayer는 같은 물리적 공간에 있는 사용자의 오디오 및 비디오 재생 동기화를 개선하여 오디오 지연 및 에코로 인한 문제를 해결합니다. 이 새로운 기능은 모든 사용자가 콘텐츠를 동시에 보고 듣는 원활한 공유 미디어 경험을 제공합니다. ‘AVPlaybackCoordinator’를 활용하면 정확한 동기화를 구현할 수 있습니다.
- 15:38 - 멀티 윈도우 지원하기
visionOS 26의 새로운 SharePlay API를 사용하면 ‘groupActivityAssociation’ 뷰 한정자를 통해 앱 내의 특정 ‘WindowGroup’을 SharePlay와 연결할 수 있습니다. 이 뷰 한정자는 공유 콘텐츠 간 원활한 전환을 가능하게 하여 멀티 윈도우 앱 내 경험을 향상시킵니다.
- 16:50 - 고정된 콘텐츠 공유하기
ARKit은 visionOS 26에서 공유 월드 앵커를 도입하여 앱이 여러 세션과 사용자 간에 지속적으로 유지되는 고정된 물리적 위치에 가상 콘텐츠를 배치할 수 있도록 합니다. 이러한 지속성은 가상 객체가 실제 세계에 고정되어 있어야 하는 협업 앱에서 특히 유용합니다. 새로운 ‘sharedWithNearbyParticipants’ 매개변수를 사용하여 SharePlay 세션 중 월드 앵커를 공유된 상태로 표시하면 근처 참가자가 동일한 가상 콘텐츠를 보고 이와 상호작용할 수 있습니다. ‘worldAnchorSharingAvailability’ API를 사용하여 공유가 가능한지 확인하세요. 이 기능은 SharePlay 외부에서도 사용할 수 있으므로 비즈니스 앱이 맞춤형 네트워킹 레이어에서 공유 앵커를 활용할 수 있습니다. 공유 월드 앵커는 가상 화이트보드, 몰입형 게임, 친구들과 함께 하는 영화 감상 등 협업 경험에 대한 새로운 가능성을 열어주며, 가상 콘텐츠가 물리적 세계에서 더 현실감 있게 제시되도록 하고, 가상 콘텐츠와의 상호작용을 강화합니다.