스트리밍은 대부분의 브라우저와
Developer 앱에서 사용할 수 있습니다.
-
공간 SharePlay 경험 빌드하기
GroupActivities 프레임워크를 사용해 visionOS에 고유한 공유 및 협업 경험을 빌드하는 방법을 알아보세요. SharePlay가 visionOS에서 사람들이 같은 공간에 있는 것처럼 느끼는 경험을 어떻게 창출하는지 배우고, 몰입형 앱이 참가자 간 공유 컨텍스트를 어떻게 존중할 수 있는지 탐색해 봅니다.
리소스
관련 비디오
WWDC23
WWDC21
WWDC19
-
다운로드
♪ 감미로운 인스트루멘탈 힙합 ♪ ♪ 안녕하세요, 윌럼입니다 Group Activities를 담당하는 엔지니어죠 오늘은 미아와 함께 훌륭한 공간 SharePlay 앱을 빌드하는 방법을 이야기하겠습니다 애초에 SharePlay는 다른 사람들과 모여서 거리와 무관하게 같이 활동하기 위한 기능인데요 이 플랫폼에서는 FaceTime 중에 그 자리에 있다는 느낌을 키울 수 있습니다 이 플랫폼에서 FaceTime을 하면 모든 Spatial Persona가 사용자의 방에서 물리적인 공간을 차지하고 단일한 윈도우에 한정되지 않습니다 덕분에 다른 사람들과 함께 있다는 느낌이 강해지죠 저희는 여기에 공유 콘텍스트를 도입했습니다 공유 콘텍스트에서는 모든 사용자가 같은 상대 위치에서 다른 모두를 보게 됩니다 각기 다른 두 시점으로 이를 나타내 보죠 왼쪽은 코너의 시점이고 오른쪽은 미아의 시점입니다 코너가 미아의 Persona를 가리키면 미아는 코너의 Persona가 자신을 가리키는 걸 봅니다 우리는 이를 공유 경험으로 확장합니다 SharePlay도 그 일환이죠 여기서는 시스템이 모두에게 동일한 상대 위치로 윈도우를 배치합니다 이때 템플릿이 쓰입니다 템플릿에 따라 참가자와 공유 앱이 각자에 대해 어떻게 배치될지가 결정되죠 이렇게 해서 공간 일관성이 생깁니다 앱은 변위를 직접 처리하지 않고 시스템에 맡길 수 있습니다 앱에서 고려할 한 가지는 시각적 일관성입니다 시각적 일관성이란 모든 사용자가 앱 내에서 동일한 콘텐츠를 본다는 의미입니다 화이트보드 앞에 다른 사람들과 함께 있다고 상상해 보세요 모두 똑같은 화이트보드 뷰를 보고 한 사람이 뭔가를 가리키면 다른 사람들은 지시된 대상을 볼 수 있습니다 저희는 공간 경험에서도 이를 모방하고자 합니다 SharePlay를 활용해서요 앞서 보여 드린 사례로 돌아가 봅시다 이 앱에는 시각적 일관성이 없습니다 코너가 앱의 네모를 가리키면 미아는 코너의 Persona가 원을 가리키는 걸 보게 되죠 이러면 함께 있다는 느낌이 깨지고 맙니다 그러나 앱에 시각적 일관성이 있다면 미아는 코너가 네모를 가리키는 걸 분명히 보게 됩니다 앱이라면 의무적으로 콘텐츠와 그 배치를 모든 참가자에게 동기화해야 합니다 그래야 모두가 똑같은 앱을 보고 있다는 느낌이 나죠 실제로 함께 있는 것처럼요 '공간 SharePlay 경험 디자인하기' 세션에서 이 개념을 한층 자세히 다루니 더 알고 싶으시다면 이 세션을 강력히 추천합니다 이 플랫폼의 FaceTime에 도입된 새로운 개념을 몇 가지 살펴봤으니 윈도우형 앱에 SharePlay를 적용할 때 고려해야 할 사항을 살펴보고 기존 iOS SharePlay 앱이 어떤 단계를 추가로 거쳐야 공간 환경 내 사용 경험의 질이 높아질지 알아봅시다 이후에 미아가 보여드릴 신규 API로는 놀라운 몰입형 SharePlay 경험을 만들 수 있습니다 윈도우형 앱에서 SharePlay를 지원하고자 신규 system coordinator를 그룹 세션에 추가했는데요 system coordinator는 두 가지 일을 합니다 활성화된 SharePlay 세션의 시스템 상태를 받고 SharePlay 중에 추가 구성을 넣을 수 있게 하죠 제가 이야기할 두 가지는 윈도우형 앱과 몰입형 앱 모두와 관련이 있습니다 isSpatial 플래그와 템플릿 preference인데요 참가자가 공간 컴퓨팅 환경에 있는지를 알아야 필요한 시각적 일관성의 정도를 판단할 수 있는데요 예를 보여 드리죠 이건 코너의 1인칭 시점입니다 빨간색으로 보이는 미아와 통화하면서 SharePlay로 공유한 문서를 보고 있습니다 이 시나리오에서 미아의 시점은 오른쪽과 같습니다 코너가 파란색으로 보이죠 코너가 스크롤하면 스크롤 위치가 동기화되어야 합니다 그래야 코너의 스크롤이 미아에게도 보여서 시각적 일관성이 유지되거든요 SharePlay 중에 Freeform 문서를 움직일 때 실제로 이렇게 합니다 공간 컴퓨팅 환경이 아니라면 다른 사람이 문서와 상호 작용한다는 콘텍스트가 없겠죠 코너가 스크롤해도 동기화되지 않아서 미아는 문서에서 같은 위치를 계속 볼 겁니다 로컬 참가자가 공간 컴퓨팅 환경에 있는지를 observe 하고 참가자 간 동기화 대상을 조정하려면 그룹 세션의 system coordinator를 먼저 준비해야 합니다 로컬 참가자가 공간 컴퓨팅 환경에 있는지를 알려면 로컬 참가자 상태의 isSpatial이 true인지 확인하세요 상태를 observe할 때는 localParticipantState 프로퍼티를 사용하면 됩니다 비동기 시퀀스를 반환하는데 이는 로컬 참가자 상태의 업데이트를 거쳐 반복되죠 system coordinator로는 앱에서 템플릿 preference를 넣을 수도 있습니다 템플릿은 시스템에 의한 모든 참가자 배치를 앱에 따라 상대적으로 결정해서 공간 일관성을 확보하는 데 쓰입니다 우리는 세 가지 템플릿을 지원합니다 Side-by-Side 템플릿에서는 모든 참가자가 원호로 배치되어 공유된 앱을 바라봅니다 일반적인 윈도우 씬을 사용하는 앱이라면 이게 기본 템플릿으로 적용될 겁니다 Conversational 템플릿에서는 모든 참가자가 반원으로 배치되고 앱은 반원 앞에 배치됩니다 앱 콘텐츠가 중심이 아닌 경험이라면 이 템플릿이 좋습니다 SharePlay로 음악을 재생할 때처럼요 마지막은 Surround 템플릿입니다 참가자가 원을 그리며 배치되고 앱은 원의 중심에 놓입니다 이 템플릿을 쓰려면 앱이 볼류메트릭 씬을 사용해야만 합니다 참가자와 앱 사이의 거리는 앱의 크기에 달렸습니다 앱이 크면 사람들에게서 멀리 배치되고 앱이 작다면 더 가까이 배치되죠 SharePlay 앱은 system coordinator로 템플릿 preference를 제공합니다 FaceTime은 그룹 세션 활성화 중에 이 템플릿을 적용하려 할 겁니다 SharePlay 앱에서는 세 preference를 쓸 수 있는데요 .none이면 시스템 기본 동작을 따라서 세로 앱에서는 Side-by-Side가 되고 볼류메트릭 앱에서는 Surround가 됩니다 이게 기본 preference로 사용되죠 .sideBySide를 쓰면 Side-by-Side 템플릿을 적용하려 합니다 앱의 종류와 무관하게요 마지막으로 .conversational이면 해당 템플릿을 적용하려 합니다 템플릿 preference를 설정하려면 그룹 세션에 참여하기 전에 먼저 system coordinator의 구성에 이걸 넣으면 됩니다 템플릿을 사용하는 건 앱과 참가자를 경험을 즐길 최적의 위치에 배치하기 위해서입니다 그런데 앱에 윈도우 씬이 여러 개거나 현재 포그라운드 상태인 게 하나 이상이면 어떨까요? 씬이 세 개인 앱을 예로 들어 보죠 왼쪽에 보이는 건 브라우즈 뷰입니다 가운데 작은 씬으로는 콘텐츠 탐색을 간단히 수행할 수 있고 마지막 씬에 나오는 건 한 콘텐츠에 대한 상세 정보입니다 모든 씬이 동시에 열릴 수 있어서 사용자는 콘텐츠를 쉽게 탐색하며 내용을 읽을 수 있습니다 SharePlay 중이라면 상세 씬을 공유하고 싶겠죠 하지만 모든 씬이 열려 있으면 엉뚱한 씬이 템플릿에 사용될 수도 있는데요 저희는 그 해결책으로 scene association을 추가했습니다 이러면 시스템은 앱에서 SharePlay 활동을 호스팅하는 씬을 알 수 있습니다 알아야 하는 이유는 두 가지입니다 첫째는 윈도우 위의 Share 메뉴가 씬을 공유받은 사람에게 나타나야 하기 때문이죠 여러 윈도우가 열려 있을 때 유용한 표시로 혹시 모를 민망한 상황을 방지할 수 있습니다 자기도 모르는 사이에 실수로 공유 윈도우와 상호 작용 할 수도 있으니까요 더 중요한 이유는 템플릿에 사용될 윈도우 씬이 여기에 따라 결정된다는 점인데요 사용자의 앱이 단일 씬 앱이면 걱정할 필요가 없습니다 가능한 씬은 하나뿐이므로 그 씬이 자동으로 그룹 세션에 연동됩니다 그러나 다중 씬 앱에서 SharePlay를 사용할 때는 고려할 사항이 있습니다 기능을 적용하지 않으면 열린 씬이 임의로 선택되는데 그 씬이 적절하지 않을 수도 있으니까요 scene association을 도입하는 방법을 알아볼 텐데 SharePlay 활동을 시작한 사람은 어떻게 되는지부터 살펴봅시다 그룹 활동이 활성화되면 우리는 각 씬을 살피며 활동을 처리할 수 있는 씬을 확인합니다 이를 위해 그룹 활동의 활동 식별자에 대한 씬 활성화 조건을 평가합니다 씬 활성화 조건에서는 두 가지를 체크해야 합니다 can과 prefers죠 can은 씬이 처리할 수 있다는 의미고 prefers는 더 적합한 씬으로 식별자를 보내 줍니다 우선 왼쪽 씬부터 확인해 봅시다 활동 식별자 처리에 대해 can인지 prefers인지를요 can이지만 prefers는 아니군요 중간 씬을 확인하니 can도 아니네요 마지막 씬을 보면 can과 prefers 모두 해당합니다 마지막 씬은 can인 동시에 prefers이기도 하니 이 씬을 그룹 세션에 연동하겠습니다 식별자를 처리할 수 있는 씬이 없다면 새로운 윈도우 씬을 시작할 겁니다 사용자의 SharePlay 경험에 별도의 씬이 필요할 때 좋은 방법입니다 처리에 can인 여러 씬 중 prefers인 것은 없다면 그 씬 중 하나가 임의로 선택될 겁니다 그룹 활동을 활성화하지 않고 수신하는 참가자라면 이미 실행 중인 앱에서 같은 로직이 실행됩니다 앱이 실행되고 있지 않다면 시작되고 첫 번째 씬이 연동될 겁니다 SwiftUI 앱 라이프 사이클을 사용하는 앱에서 씬 활성화 조건을 구체화하려면 handlesExternalEvents를 쓰면 됩니다 그룹 활동의 활동 식별자를 preferring이나 allowing 문자열 집합에 넣으세요 UIKit 앱 라이프 사이클을 사용하는 앱이라면 술부를 지정해 UI scene의 활성화 조건을 설정하면 됩니다 씬 활성화 조건을 더 알아보려면 이 2019년 세션을 확인하세요 예시를 하나 더 생각해 봅시다 이번에는 문서 기반 앱인데요 각 씬이 다른 문서를 보여 주고 있습니다 이제 SharePlay로 첫 번째 문서에서 협업해 볼 텐데요 그룹 활동 식별자를 다시 활용해서 각 씬의 활성화 조건을 평가하겠습니다 안타깝게도 씬 자체에 내재적인 차이는 없어서 모든 씬이 매칭되어 잘못된 씬이 연동됩니다 이 경우에는 씬 매칭에 사용된 식별자를 바꿔야 합니다 이를 위해 추가한 게 scene association behavior죠 앱은 이걸로 적절한 씬을 찾는 데 쓰일 식별자를 얻을 수 있습니다 예시로 돌아가면 모든 참가자가 동의하는 각 문서의 고유한 식별자를 쓸 수 있는데요 이 식별자를 활용해 적절한 씬을 찾을 수 있습니다 이 상태로 씬을 평가하면 씬 활성화 조건을 간단히 설정할 수 있죠 첫 번째 씬만 매칭되고 최종적으로는 적절한 씬이 연동됩니다 사용될 식별자를 지정하려면 그룹 활동 메타데이터에서 scene association behavior를 설정하면 됩니다 SharePlay 세션에 참여하는 모든 참가자에게 동일한 식별자가 사용된다는 걸 유념해 주세요 모두가 동의할 식별자를 사용하는 게 좋습니다 scene association behavior는 세 유형을 지원하는데요 Default는 처음으로 보게 되는 동작입니다 씬 연동에 사용된 식별자가 그룹 활동 식별자가 되는 경우죠 이름에서 알 수 있듯 이는 기본 동작으로 하나를 명시적으로 지정하지 않으면 나옵니다 Content 동작에서는 사용자 지정 식별자를 지정할 수 있죠 이를 사용하기 좋은 앱은 씬마다 콘텐츠가 다르게 나타나고 그룹 활동이 그 콘텐츠에 묶여 있는 앱입니다 마지막은 None 동작입니다 None 동작은 씬 연동을 비활성화하죠 연동되는 씬이 없으므로 앱의 어떤 씬에서도 공유 배너를 볼 수 없습니다 공간 일관성이 깨졌다는 의미이기도 하죠 이는 특정 경우에만 사용해야 합니다 씬이 추가로 필요하지 않은 몰입형 앱에서나 씬에 나타나는 콘텐츠가 SharePlay 세션 참가자마다 다른 경우에서요 그룹 세션에 연동된 씬이 무엇인지 알아야 할 때도 있는데요 씬 연동에 복수의 씬이 선택될 수 있을 때 이런 상황이 생기죠 이 정보를 얻고자 새로운 프로퍼티를 그룹 세션에 표시했습니다 sceneSessionIdentifier죠 이 프로퍼티에는 그룹 세션과 연동된 씬 세션의 식별자가 들어 있습니다 훌륭한 SharePlay 경험 빌드에는 씬 연동이 결정적이니 꼭 적용해 보세요 다중 씬을 지원하는 앱이라면요 끝으로 Share 메뉴의 SharePlay를 이야기해 봅시다 FaceTime 중에는 모든 윈도우 위에 공유 배너가 있습니다 방금 보았듯 SharePlay 중에 공유되는 씬이 나타나는데요 이건 SharePlay 중이 아니라도 유용합니다 씬이 공유되지 않을 때면 해당 씬을 공유할 다양한 방법을 보여 주죠 윈도우를 그냥 공유하는 것도 하나의 방법입니다 다른 사람들은 모두 상호 작용 없는 비디오 피드를 윈도우에서 보게 됩니다 이는 모든 앱에서 기본값으로 작동하지만 SharePlay는 여기서 더 나아갔습니다 윈도우의 현재 콘텐츠와 관련된 그룹 활동을 표시함으로써 해당 메뉴에 띄워 줍니다 이렇게 하면 SharePlay 활동의 발견성이 향상되는 동시에 여러분의 앱 UI에 SharePlay 전용 버튼을 넣을 필요도 없어집니다 앱에서 그룹 활동을 표시하는 방법은 AirDrop으로 SharePlay를 시작하는 방법과 같습니다 iOS 17에서 SharePlay 앱을 열면 AirDrop으로 SharePlay를 시작할 수 있죠 그룹 활동을 가져오려면 나타나는 씬의 UI 리스폰더 체인을 시스템에서 검토하고 리스폰더 중 하나의 활동 아이템 구성에 지정된 그룹 활동을 찾아야 합니다 이 방법으로 SharePlay가 가능한 콘텐츠를 보여 주는 뷰 컨트롤러의 활동 아이템 구성에서 그룹 활동을 설정할 수 있으며 이는 자동으로 선택됩니다 활동 아이템 구성을 설정하려면 우선 활성화할 수 있는 activity를 만들어 주세요 다음으로는 item provider를 만들고 거기에 group activity를 등록합니다 UIActivityItemsConfiguration을 item provider와 함께 초기화합니다 마지막으로 확인할 것은 적절한 메타데이터가 구성에 표시되는지입니다 Share 메뉴에는 그 데이터가 보이게 되니까요 확인하려면 UIActivityItemsConfiguration에서 metadataProvider를 사용하고 LPLinkMetadata 객체를 주면 됩니다 LinkPresentationMetadata 키로요 title과 image provider는 Share 메뉴에 사용됩니다 UIActivityItemsConfigurationReading과 일치하면 각자의 클래스를 사용해도 모두 작동할 겁니다 윈도우형 SharePlay 경험을 이 플랫폼에 빌드할 때 고려할 사항을 이야기했는데요 미아, 몰입형 앱에서 공유 경험을 빌드하는 방법을 소개해 주시겠어요? 물론이죠, 윌럼 이제 몰입형 앱에서 SharePlay가 어떻게 작동하는지 알아봅시다 이 플랫폼에서는 몰입형 경험을 쉽게 만들 수 있죠 Immersive Space를 쓰면 됩니다 Immersive Space는 특수한 씬으로 다양한 몰입 방식을 사용해서 한계를 넘어서는 앱 콘텐츠를 만들 수 있습니다 Immersive Space가 앱에서 열리면 다른 앱은 시스템에 의해 모두 백그라운드로 가고 사용자는 이 앱의 무한한 세계에 온전히 집중할 수 있습니다 Immersive Space와 몰입 방식에 대해서는 아래 세션에서 더 알아볼 수 있습니다 Immersive Space는 FaceTime 중 언제든 시작할 수 있습니다 개별 몰입형 공간에 있을 때는 사용자만의 세계에 있고 다른 사람들과의 공유 콘텍스트는 끊어집니다 그래서 다른 사람들은 시스템에 의해 숨겨지고 여러분은 연락처 사진으로만 보입니다 다른 사람들과 같이 있지 않다는 표시죠 음성으로는 여전히 소통할 수 있습니다 그래도 FaceTime에서 혼자 있기는 싫죠 마법 같은 순간을 몰입형 세계에서 공유해 봅시다 저도 몰입형 공간에 대해 친구들과 공유하고 싶은 멋진 아이디어가 많은데요 이런 그룹 활동도 생각해 볼 수 있죠 우주에서 노닐면서 아름다운 지구를 함께 탐험하는 겁니다 이걸 해내려면 우리가 공유하는 앱에는 그룹 몰입형 공간이 설정되어야 합니다 모두와 공유할 수 있게요 system coordinator의 구성을 보면 supportsGroupImmersiveSpace 플래그가 있습니다 이 플래그를 활성화하면 앱은 시스템에 이 앱의 Immersive Space가 공유되어야 하며 몰입형 그룹 활동을 지원하겠다고 전달합니다 이 플래그가 활성화된 그룹 세션에 참여하면 사용자의 몰입형 공간은 그룹 몰입형 공간이 됩니다 시스템은 공간 원점을 템플릿으로 정의된 공유 위치로 이동해서 공간 일관성을 위한 공유 좌표계를 확립합니다 그룹 몰입형 공간에 있는 사람들은 서로를 Persona로 보게 되죠 이제 그룹 몰입형 공간이 있고 공유 좌표계와 그 안의 사람들도 있으니 내부의 같은 위치에 객체를 배치해서 공간 일관성을 확보할 수 있습니다 글로브 탐색 활동 예시에서는 원점 상단 중앙에 글로브를 배치하고 원점에 대해 동일한 오프셋을 모두에게 적용할 수 있습니다 그러면 모두 글로브를 같은 지점에서 보게 되죠 다음으로 시각적 일관성을 유지하는 방법을 알아봅시다 가령 글로브를 회전하려면 그 방향이 모두에게 동기화되어 있는지 확인해야 합니다 상태 동기화를 위해 GroupSession과 GroupSessionMessenger를 어떻게 사용해야 할지 궁금하시면 아래의 SharePlay 세션을 확인해 주세요 UI 요소를 추가할 수도 있습니다 그룹 몰입형 공간에 있는 각 사람에게 맞춰서요 예를 들어 모든 참가자에게 이런 개별 제어 메뉴를 줄 수 있죠 각자의 앞에 있고 이걸로 공유 글로브를 쉽게 조작할 수 있습니다 제어를 적절히 배치하려면 로컬 사용자가 그룹 몰입형 공간 내 어디에 있는지를 알아야 합니다 GeometryReader3D에서 systemExperienceDisplacement 메서드를 사용하면 되는데요 이 메서드는 시스템이 공간 원점을 로컬 참가자와 어떻게 떨어뜨려 배치했는지를 알려 줍니다 Pose3D 구조체가 반환되는데 이동과 회전이 모두 존재합니다 공간 원점에 대한 로컬 사용자의 위치는 이 포즈를 반전시켜 구할 수 있습니다 그 위치로 제어 메뉴를 오프셋하고 회전시키면 로컬 사용자에게 맞춰 메뉴가 나타납니다 주목할 한 가지는 사용자가 움직여도 변위에는 업데이트가 없다는 겁니다 공간 변위는 최초 배치 시점에만 제공된다는 거죠 덕분에 사람에 맞게 콘텐츠를 배치하기가 쉽습니다 각자의 위치를 완전히 추적할 필요가 없죠 글로브 탐색 활동에는 Surround 템플릿이 좋습니다 앱에 그룹형 몰입 공간만 있고 가운데에 공유 콘텐츠가 있죠 그런데 사용자의 그룹 활동에 공유 윈도우와 그룹 몰입형 공간이 함께 있다면 어떨까요? 태양계를 공부하는 이런 그룹 활동을 빌드하려 한다고 해 봅시다 우선 행성에 대한 내용을 공유 윈도우에서 읽으며 시작하겠죠 이어서 버튼을 눌러 그룹 몰입형 공간에서 행성 모델을 자세히 살펴볼 수 있습니다 저는 사람들이 두 경우 모두 나란히 서 있으면 좋겠어요 이때 템플릿을 어떻게 쓰면 좋을지 알아봅시다 기본적으로 앱에 그룹 몰입형 공간만 있으면 Surround 템플릿이 적용되고 공간 원점은 중앙에 배치됩니다 볼류메트릭 씬이 앱에서 공유되면 동일한 레이아웃이 적용되죠 그런데 앱에서 세로 윈도우가 공유되면 Side-by-Side 템플릿이 기본으로 선택되고 공유 윈도우의 바로 아래에 공간 원점이 옵니다 하지만 템플릿 preference는 언제든 바꿀 수 있죠 사용자의 그룹 활동에 최적인 쪽으로요 .conversational 템플릿을 선택해도 되고 .sideBySide 템플릿을 선택해도 됩니다 사실 태양계 탐험 활동에는 이 템플릿이 좋겠네요 앞서 알아보았듯 템플릿 내의 거리는 공유 윈도우의 크기에 따라서 동적으로 조정됩니다 그룹 몰입형 공간만 있을 때 그 거리가 적절한지 확인할 방법이 궁금하실 텐데요 이 문제를 해결할 유용한 수정 방법으로 content extent가 있습니다 템플릿 preference 수정자로 몰입형 그룹 활동과 윈도우 기반 그룹 활동에서 모두 잘 작동하죠 앱은 이 값을 포인트로 설정할 수 있습니다 콘텐츠 중앙부터 제일 먼 가장자리까지의 거리로요 content extent가 설정되면 활성화된 템플릿이 앱에 따라 사람들을 배치할 때 이 값을 고려하게 됩니다 그럼 템플릿에 대해 알아본 내용을 적용해서 앞서 보여 드린 태양계 탐험 활동의 그룹 세션을 설정해 봅시다 supportsGroupImmersiveSpace를 먼저 활성화해야 합니다 사람들이 몰입형 공간에서 객체를 같이 볼 수 있게요 이어서 템플릿 preference를 .sideBySide로 설정하고 contentExtent를 위한 수정자를 추가하세요 그래야 사람들이 계속 나란히 서서 공유 콘텐츠와 적절한 거리를 유지할 수 있습니다 좋아요, 확인해 보죠 사람들과 함께하는 그룹 활동이 어떻게 보일까요? 제가 친구 코너와 같이 있는 모습입니다 공유 윈도우의 행성을 나란히 서서 살펴보고 있죠 이후 버튼을 누르면 저는 토성을 탐험하러 그룹 몰입형 공간으로 들어가게 됩니다 저는 모델의 아름다움에 감탄하는 동시에 코너가 같이 있지 않다는 걸 인식합니다 코너의 연락처 사진만 보일 뿐이죠 사실 코너는 뒤에 남겨진 채로 여전히 윈도우를 보고 있습니다 코너는 버튼을 직접 눌러야 그룹 몰입형 공간에 들어올 수 있죠 앞서 알아보았듯 그룹 몰입형 공간에 같이 있는 게 아니라면 공유 콘텍스트를 갖지 못합니다 따라서 시스템은 Spatial Persona를 가리고 연락처 사진만 보여 줍니다 서로의 Persona가 계속 보이면 코너가 혼란스러울 수도 있죠 자신에게 안 보이는 무언가에 제가 놀라고 있을 테니까요 우리의 그룹 몰입형 공간이 다른 몰입 방식을 보여도 같은 상황이 발생할 수 있습니다 예를 들어 저는 완전 몰입형 환경에서 주위를 둘러보고 있는데 코너는 passthrough에 남아 있는 거죠 하지만 코너가 같은 그룹 몰입형 공간으로 저를 자동으로 따라온다면 정말 좋을 겁니다 코너가 저와 같이 있지 않을 때도 제 위치를 알고 쉽게 합류할 수 있는 거죠 몰입형 그룹 활동에서 접촉 쪼개기를 최소화할 때 활용하기 좋은 도구가 있는데요 system coordinator의 groupImmersionStyle입니다 선택 몰입 방식의 비동기 시퀀스가 제공되어 다른 사람이 참여하는 그룹 몰입형 공간의 구체적인 몰입 방식을 앱에 전달합니다 그 사람이 그룹 몰입형 공간을 벗어나면 nil을 전달하고요 제가 그룹 몰입형 공간을 열면 코너의 앱이 그 몰입 방식을 받아서 방식이 매칭되는 그룹 몰입형 공간을 열게 됩니다 그러면 우리는 그룹 몰입형 공간에 같이 있으면서 공유 콘텍스트를 다시 갖게 되죠 마찬가지로 코너가 버튼을 눌러서 그룹 몰입형 공간을 떠나면 제 앱은 nil을 받아서 몰입형 공간을 종료하고요 그룹 몰입 방식을 사용하면 모두가 언제나 함께할 수 있습니다 급한 일이 생겨 몰입형 공간에서 일시적으로 빠져나와야 할 상황이 생길 수도 있는데요 그때는 Digital Crown을 한 번만 누르면 됩니다 그러면 시스템은 다른 사람들을 방해하지 않고 그룹 몰입 방식을 변경합니다 빠져나갈 때는 SharePlay 배너 알림이 보입니다 그룹 내 다른 사람들의 위치를 알려 주고 그룹 몰입형 공간으로 다시 돌아가 참여할 수 있는 버튼을 띄워 주죠 참여 버튼을 탭하면 그룹 몰입형 공간을 설정하기 위한 그룹 몰입 방식이 사용자의 앱으로 전송됩니다 이번 세션에서 많은 내용을 다뤘으니 요약해 봅시다 공유 콘텍스트를 알아봤고 system coordinator와 scene association을 사용해 공간 일관성과 시각적 일관성을 윈도우형과 몰입형 공유 앱에서 관리하는 법도 알아봤습니다 앱에서 사람들을 적절히 배치하기 위한 템플릿 preference도 소개했고요 마지막으로 Share 메뉴를 사용해 SharePlay를 시작하는 새 방법도 보여 드렸습니다 시청해 주셔서 감사합니다 여러분이 SharePlay로 빌드할 놀라운 경험들이 너무나 기대됩니다 ♪
-
-
4:08 - Observe the local participant state
for await session in ExploreActivity.sessions() { guard let systemCoordinator = await session.systemCoordinator else { continue } let isLocalParticipantSpatial = systemCoordinator.localParticipantState.isSpatial Task.detached { for await localParticipantState in systemCoordinator.localParticipantStates { if localParticipantState.isSpatial { // Start syncing scroll position } else { // Stop syncing scroll position } } } }
-
6:10 - Configure the spatial template preferences
for await session in ExploreActivity.sessions() { guard let systemCoordinator = await session.systemCoordinator else { continue } var configuration = SystemCoordinator.Configuration() configuration.spatialTemplatePreference = .sideBySide systemCoordinator.configuration = configuration session.join() }
-
9:10 - Configuring scene activation conditions
@main struct ExploreTogetherApp: App { var body: some Scene { WindowGroup { ContentView() .handlesExternalEvents( preferring: ["com.example.explore-together.activity"], allowing: ["com.example.explore-together.activity"] ) } } }
-
9:30 - Configuring scene activation conditions
class SceneDelegate: NSObject, UISceneDelegate { func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { // ... scene.activationConditions.canActivateForTargetContentIdentifierPredicate = NSPredicate(format: "self == %@", "com.example.explore-together.activity") scene.activationConditions.prefersToActivateForTargetContentIdentifierPredicate = NSPredicate(format: "self == %@", "com.example.explore-together.activity") } }
-
10:40 - Setting scene association behavior
struct ExploreActivity: GroupActivity { var metadata: GroupActivityMetadata { var metadata = GroupActivityMetadata() // ... metadata.sceneAssociationBehavior = .content("document-1") return metadata } }
-
13:44 - Starting SharePlay
// Create the activity let activity = ExploreActivity() // Register the activity on the item provider let itemProvider = NSItemProvider() itemProvider.registerGroupActivity(activity) // Create the activity items configuration let configuration = UIActivityItemsConfiguration(itemProviders: [itemProvider]) // Provide the metadata for the group activity configuration.metadataProvider = { key in guard key == .linkPresentationMetadata else { return nil } let metadata = LPLinkMetadata() metadata.title = "Explore Together" metadata.imageProvider = NSItemProvider(object: UIImage(named: "explore-activity")!) return metadata } self.activityItemsConfiguration = configuration
-
16:03 - Configure group ImmersiveSpace
for await session in ExploreActivity.sessions() { guard let systemCoordinator = await session.systemCoordinator else { continue } var configuration = SystemCoordinator.Configuration() configuration.supportsGroupImmersiveSpace = true systemCoordinator.configuration = configuration }
-
17:51 - System Experience Displacement
// Use immersiveSpaceDisplacement to offset contents in group immersive space var body: some Scene { ImmersiveSpace(id: "earth") { GeometryReader3D { proxy in let displacement = proxy.immersiveSpaceDisplacement(in: .global).inverse Control() .offset(displacement.position) .rotation3DEffect(displacement.rotation) } } }
-
20:46 - Spatial Template Preferences
// Configure the spatial template preferences with content extent for await session in ExploreSolarActivity.sessions() { guard let systemCoordinator = await session.systemCoordinator else { continue } var configuration = SystemCoordinator.Configuration() configuration.supportsGroupImmersiveSpace = true configuration.spatialTemplatePreference = .sideBySide.contentExtent(200) systemCoordinator.configuration = configuration }
-
22:32 - Receive group immersion style to configure group immersive space
// Receive group immersion style to configure group immersive space for await session in ExploreSolarActivity.sessions() { guard let systemCoordinator = await session.systemCoordinator else { continue } Task.detached { for await immersionStyle in systemCoordinator.groupImmersionStyle { if let immersionStyle { // Open an immersive space with the same immersion style } else { // Dismiss the immersive space } } } }
-
-
찾고 계신 콘텐츠가 있나요? 위에 주제를 입력하고 원하는 내용을 바로 검색해 보세요.
쿼리를 제출하는 중에 오류가 발생했습니다. 인터넷 연결을 확인하고 다시 시도해 주세요.