스트리밍은 대부분의 브라우저와
Developer 앱에서 사용할 수 있습니다.
-
SwiftUI에서 고급 애니메이션 사용하기
SwiftUI의 최신 업데이트로 애니메이션의 질을 한 단계 높여 보세요. 애니메이션을 제작하면서 여러 단계를 구축하고, 키 프레임을 사용해 조정된 멀티트랙 애니메이션 효과를 추가하고, 고유한 방식으로 API를 결합해 앱에 생명을 더하는 방법을 알아보세요.
챕터
- 0:00 - Introduction
- 2:23 - Animation phases
- 8:12 - Keyframes
- 15:07 - Tips and tricks
리소스
관련 비디오
WWDC23
-
다운로드
♪ ♪
안녕하세요 이번 시간에는 SwiftUI에서 고급 애니메이션을 사용하는 법을 소개합니다 저는 SwiftUI 팀 소속 팀입니다 SwiftUI의 강력한 애니메이션 도구는 여러분의 앱을 돋보이게 합니다 중간에 멈출 수 있는 물리 기반 동작 애니메이션으로 프레임워크 전반에 자연스럽게 통합되어 있죠 오늘은 여러분의 앱에서 애니메이션의 기능을 한 단계 높여 줄 새로운 도구를 소개하겠습니다 시작하기 전에 여러분이 이미 알고 계신 SwiftUI의 애니메이션 도구를 살펴보겠습니다 좋아하는 반려동물에 투표하는 앱을 다른 세션에서 보셨을 거예요 데모를 단순화하기 위해 다른 옵션을 전부 삭제했어요 어차피 고양이가 1위니까요 앱에 애니메이션을 추가하려면 'withAnimation'을 사용하거나 'animation' 수정자를 추가하면 동작이 바로 시작됩니다 응용 프로그램의 상태가 변경되면 SwiftUI는 기존 상태에 보간된 애니메이션에 새로운 상태를 적용합니다 하지만 애니메이션이든 인생이든 가장 보람찬 경험은 어디에서 오고 어디로 가는지에 너무 집착하지 않을 때 나옵니다 때로는 정해진 길에서 벗어나 여정 자체에 집중해야 특별한 결과를 얻을 수 있어요 한 상태에서 다른 상태로 변하는 것만 애니메이션은 아니죠 오늘은 더 복잡한 다단계 애니메이션을 만드는 새롭고 강력한 도구를 소개해 드리겠습니다 두 상태 사이의 애니메이팅만이 아니라 일련의 여러 단계를 정의할 수 있는 애니메이션이죠 특히 다음 두 가지 경우에 아주 효과적입니다 뷰가 보일 때 루프가 계속되는 반복 애니메이션과 이벤트 발생 시 펄스 동작을 하는 이벤트 중심 애니메이션이죠 오늘은 이런 애니메이션을 더 쉽게 만들게 하는 새로운 API군을 소개해 드리겠습니다 먼저 애니메이션 페이즈부터 소개하겠습니다 애니메이션을 구성하는 사전 계획 된 상태를 이용해 SwiftUI가 자동으로 진행하게 하는 기능이죠 키 프레임으로 애니메이션을 발전시키는 법도 보여드리죠 마지막으로는 이 API를 최대한 활용할 수 있는 몇 가지 고급 기술을 소개하겠습니다 준비가 끝났습니다 그럼 시작해 볼게요 Swift를 쓰지 않을 때 저는 산악 달리기를 즐깁니다 산악 코스는 굉장히 길어요 울트라마라톤 완주에는 하루 또는 며칠이 걸리기도 하죠 저는 경기 일정을 정리하는 앱을 만들었습니다 달리기에 필요한 중요 사항을 기억하는 데도 도움이 되죠 산에서 달릴 때는 영양이 굉장히 중요합니다 피로가 쌓이는 경기 후반에는 음식 챙겨 먹는 걸 잊기 쉽죠 그래서 앱에 음식 섭취 시간을 알려주는 기능을 추가했습니다 보시다시피 화면 하단에 뜨는 미리 알림이 식사 시간이 지났음을 알려주고 있죠 하지만 문제가 있어요 경기 후반에는 너무나도 지쳐 있기 때문에 이렇게 은은한 알림은 놓치기가 쉽습니다 실수로 식사를 거르는 일은 피하고 싶으니 미리 알림에 동작을 추가해 눈에 잘 띄게 해 볼게요 이 한 가지 뷰에 집중하겠습니다 눈에 더 잘 띄도록 애니메이션 하이라이트 효과를 줄게요 '.phaseAnimator' 수정자를 적용해 이 뷰를 애니메이팅합니다 페이즈 애니메이터 수정자는 다단계 애니메이션에서 각 단계를 정의하는 일련의 상태를 제공합니다 SwiftUI가 각 상태 사이를 자동으로 애니메이팅하죠 이때는 하이라이트가 있고 없는 두 상태 사이만 애니메이팅하면 되기 때문에 불리언값을 사용할 수 있죠 이번에는 현재의 phase에 맞춰 뷰를 바꿔 주는 수정자 몇 가지를 적용해 보겠습니다 투명도 수정자부터 시작할게요 하이라이트된 뷰는 100% 불투명해지고 그렇지 않을 때는 50% 투명해지게 하겠습니다 곧바로 뷰에서 애니메이팅이 시작됩니다 SwiftUI가 여러분 대신 무엇을 해주고 있는지 알아보죠 이 뷰에서 페이즈 애니메이터 수정자에는 두 가지 phase가 제공됩니다 false와 true죠 뷰가 처음 나타날 때는 첫 번째 phase가 활성화되어 뷰는 50% 투명해집니다 SwiftUI는 뷰가 완전히 불투명한 다음 phase로 즉시 애니메이션 전환을 시작합니다 이 애니메이션이 종료되면 SwiftUI는 다시 진행을 하죠 phase가 두 개뿐이니 루프가 처음부터 반복됩니다 그래서 애니메이션은 두 상태 사이를 순환하죠 물론 두 개 이상의 phase를 포함하게 할 수도 있습니다 뷰 수정자는 몇 개든 더 추가할 수 있어요 잠시 후에 보여드리죠 지금 뷰의 애니메이션 효과는 굉장히 은은합니다 투명도를 바꾸는 대신 전경 스타일을 바꿔 보죠 하이라이트될 때는 빨간색을 아닐 때는 기본 전경 스타일로 돌아가게 하겠습니다 이렇게 하면 훨씬 잘 보이죠 그런데 애니메이션이 좀 거칠군요 SwiftUI는 기본적으로 스프링 애니메이션을 사용합니다 상태가 급격히 바뀔 때는 스프링도 좋지만 여기에는 더 부드럽고 일관적인 애니메이션이 어울려요 'animation' 클로저를 추가해 애니메이션을 바꿔 보겠습니다 각 phase에 다른 애니메이션을 사용할 때를 대비해 애니메이팅 대상인 phase를 입력합니다 하지만 이럴 때 저는 easeIn, easeOut 애니메이션을 지속 시간을 수정해서 사용하곤 합니다 애니메이션의 상호 작용 상태 변화를 1초씩 지속되게 만드는 경우는 드뭅니다 사람들이 애니메이션이 끝나기를 기다리게 되면 곤란하니까요 하지만 여기서 목표는 은은한 효과를 내는 것이니 조금 느리게 움직여도 괜찮습니다 식사를 걸렀을 때의 저처럼요 경기 중 영양 공급 문제를 해결했으니 또 다른 애니메이션 페이즈 사용법을 알아보죠 이벤트에 의해 작동하는 애니메이션입니다 저는 꽤 오래전부터 이 앱을 개발했어요 이제 제 친구들의 경기를 볼 수 있는 기능도 추가했죠 이 이모티콘은 다른 사람들이 남긴 반응입니다 달리기를 하다 보면 이런 생각이 들곤 하죠 '나는 이걸 왜 할까?' '왜 이렇게 먼 거리를 달리겠다고 했지?' 이럴 때 이 앱이 할 수 있는 건 누군가 '좋아요'를 표시할 때 반응을 추가해 외부의 인정에 대한 열망을 충족시켜 드리는 거죠 다른 사람이 반응을 추가하면 재생되는 애니메이션을 만들게요 먼저 애니메이션의 phase부터 정의하겠습니다 두 상태 사이만 오갔던 아까의 예시와는 달리 이번에는 더 복잡한 애니메이션을 만들 거예요 애니메이션의 단계 목록을 정의할 때는 열거형이 좋습니다 세 가지 케이스를 추가할게요 초기 외형 케이스 뷰를 올리는 케이스 스케일을 키우는 케이스입니다 뷰 본문을 단순화하기 위해 여러 가지 효과를 정의하는 연산 프로퍼티를 이 열거형에 추가하겠습니다 애니메이션 재생 시 뷰가 점프하게 하고 싶으니 연산 수직 오프셋 프로퍼티를 추가하겠습니다 각 케이스가 우측 오프셋으로 돌아가게 열거형을 수정합니다 여기에 연산 프로퍼티 두 가지도 추가해서 뷰의 스케일과 전경 스타일을 정의했습니다 구현까지는 보여드리지 않겠지만 여기서는 switch문도 사용해요 수직 오프셋 프로퍼티와 같습니다 다시 뷰로 돌아가서 애니메이션을 추가해 보죠 phaseAnimator 수정자를 추가했지만 이번에는 '트리거'값을 부여합니다 페이즈 애니메이터 수정자에 트리거값을 부여하면 수정자는 우리가 지정한 변경값을 관찰하게 되죠 그리고 변화가 일어났을 때 우리가 지정한 phase로 애니메이팅을 시작합니다 우리가 phase 유형에서 정의한 연산 프로퍼티를 사용하면 뷰에 수정자를 적용할 수 있죠 이 애니메이션은 제대로 작동하고 있습니다만 뭔가 어색합니다 좀 느려 보이죠 각각의 전환 애니메이션을 사용자화해서 원하는 효과를 얻어 볼게요 스프링 애니메이션도 몇 종류 있습니다 이제 훨씬 나아 보여요 이 애니메이션을 더 발전시키면 어떨까요? 80km나 160km 코스를 완주한 사람이 있으면 그동한 고생한 보람을 느낄 수 있게 애니메이션으로 축하의 메시지를 전하고 싶습니다 더 강력한 제어가 필요할 때는 키 프레임 도구를 사용하면 됩니다 이번에는 타이밍과 움직임을 완전히 제어할 수 있는 복잡하고 조직화된 애니메이션을 키 프레임으로 정의하는 법을 알아보죠 먼저 지금까지 사용했던 phase와 키 프레임의 다른 점을 말씀드리겠습니다 phase는 뷰에 한 번에 하나씩 제공되는 개별 상태를 정의하죠 SwiftUI는 그 상태들 사이를 애니메이팅할 때 여러분이 이미 알고 계시는 같은 애니메이션 유형을 씁니다 개별 상태로 모델링되는 애니메이션에는 이게 잘 맞아요 상태 전환이 일어날 때 모든 프로퍼티는 동시에 애니메이팅됩니다 애니메이션이 끝나면 SwiftUI는 다음 상태를 애니메이팅하죠 이것은 애니메이션의 모든 phase에서 계속됩니다 하지만 각 프로퍼티를 독립적으로 애니메이팅하고 싶다면요? 그때 키 프레임을 쓰는 겁니다 키 프레임은 애니메이션 안에서 특정 시간의 값을 정의합니다 이 뷰에 회전 효과를 넣어서 예시 애니메이션을 보여드리죠 이 점들이 키 프레임을 나타내요 애니메이션 중 각 지점에서 사용할 각도입니다 애니메이션이 재생될 때 SwiftUI는 키 프레임 사이의 값을 보간합니다 우리는 그걸 이용해 뷰에 수정자를 적용할 수 있죠 키 프레임은 고유 타이밍으로 개별 트랙을 정의해 여러 가지 효과를 동시에 애니메이팅할 수 있게 합니다 키 프레임을 사용하면 SwiftUI의 수정자를 전부 불러올 수 있으니 정말 강력한 도구죠 이 예시에서는 키 프레임을 여러 트랙에 적용했습니다 수직 스트레치와 스케일 이동까지요 다시 뷰로 돌아가서 코드로는 어떻게 보이는지 보죠 만들고 싶은 애니메이션을 어느 정도 정했으니 먼저 이 애니메이션을 이끌 프로퍼티를 정의하겠습니다 그러려면 구조체를 생성해야 해요 독립적으로 애니메이팅될 다양한 프로퍼티를 전부 포함하죠 'Animatable' 프로토콜에 적합한 값이라면 키 프레임은 전부 애니메이팅할 수 있습니다 몇몇 프로퍼티가 사용하는 'Double'은 Animatable에 적합합니다 개별 상태를 모델링하는 phase와는 달리 키 프레임은 여러분이 특정하는 유형의 보간된 값을 생성합니다 애니메이션이 진행되는 동안 SwiftUI는 모든 프레임의 유형값을 제공해 뷰를 업데이트할 수 있게 하죠 이번에는 keyframeAnimator 수정자를 추가합니다 이 수정자는 아까 사용한 페이즈 애니메이터와 비슷하지만 키 프레임을 받아들이죠 초깃값으로 사용하기 위한 구조체의 인스턴스를 제공하면 우리가 정의한 키 프레임이 이 값에 애니메이션을 적용합니다 이어서 구조체의 개별 프로퍼티를 위한 뷰에 수정자를 적용하죠 마지막으로 키 프레임을 정의해 보겠습니다 키 프레임은 복잡한 애니메이션도 쉽게 만들 수 있게 합니다 각 프로퍼티마다 다른 키 프레임을 쓰니까요 이건 키 프레임이 여러 트랙으로 구성되기 때문에 가능합니다 각 트랙은 애니메이팅하는 유형의 개별 프로퍼티를 제어합니다 프로퍼티는 트랙 생성 때 제공된 키 패스로 지정되죠 여기서는 스케일 프로퍼티에 키 프레임을 추가합니다 먼저 선형 키 프레임을 추가해 초기 스케일값을 반복하고 0.36초간 지속할 거예요 0.36초로 정한 이유가 궁금하시겠죠 애니메이션의 느낌을 바꿔 가며 다양한 값을 시도해 보고 정했습니다 이건 키 프레임의 중요한 특징 중 하나죠 앱과 잘 맞는 애니메이션을 찾으려면 실험이 필요합니다 Xcode 프리뷰로 애니메이션을 섬세하게 다듬어 보세요 이어서 'SpringKeyframe'을 추가합니다 스프링 기능으로 값을 타깃 쪽으로 당기는 기능이죠 지속 시간도 설정했습니다 스프링 키 프레임에 지속 시간을 설정하면 스프링 기능은 그 시간만큼만 값을 애니메이팅합니다 그 후에는 다음 키 프레임에 보간이 시작되죠 마지막으로 스케일을 애니메이팅해서 1.0으로 돌리는 스프링 키 프레임을 하나 더 추가할게요 여러 종류의 키 프레임이 각 값의 보간을 제어합니다 자, 지금까지 LinearKeyframe과 SpringKeyframe을 보셨습니다 사실 키 프레임의 종류는 네 가지인데요 어떤 차이가 있는지 설명해 드릴게요 LinearKeyframe은 이전 키 프레임의 벡터 공간을 선형으로 보간합니다 SpringKeyframe은 이름 그대로 스프링 기능을 사용해 이전 키 프레임의 타깃값을 보간하죠 CubicKeyframe은 키 프레임 사이 보간에 베지어 곡선을 사용합니다 큐빅 키 프레임 여러 개를 조합해 시퀀스를 만들면 캣멀롬 스플라인과 일치하는 곡선이 나옵니다 마지막으로 MoveKeyframe은 보간 없이 해당 값으로 이동하죠 키 프레임은 사용자화를 지원해서 자유로운 제어가 가능합니다 애니메이션 안에서 키 프레임을 여러 가지 섞어 쓸 수도 있죠 SwiftUI는 키 프레임 사이의 속도를 유지하여 애니메이션이 연속 재생 될 수 있게 하죠 다시 뷰로 돌아와서 다음 트랙을 추가할 차례입니다 여기서는 선형과 스프링 키 프레임으로 수직 이동을 애니메이팅했어요 뷰는 점프하기 직전에 살짝 내려와서 준비를 하죠 스프링 키 프레임으로 모델링한 겁니다 위로 올라가기 전에 아래로 살짝 내려오게 했어요 지금도 좋지만 애니메이팅할 프로퍼티가 두 개 더 있습니다 수직 스트레치와 회전입니다 수직 스트레치부터 시작하죠 큐빅 키 프레임을 쓸 겁니다 알맞은 동작을 만들려면 시행착오가 필요하지만 키 프레임을 이용하면 애니메이션 모델링을 얼마든지 실험해 볼 수 있습니다 찌그러지고 펴지는 동작 덕분에 애니메이션이 더 활기차 보이죠 마지막으로 회전 동작도 추가합니다 자연스러워 보이네요 아까 보셨던 곡선 기억하시죠? 이건 저희가 방금 제작한 애니메이션의 시각화입니다 다른 SwiftUI 수정자를 적용할 트랙을 추가할 수도 있어요 저도 여러 가지 조합을 시도해 보았습니다 이번에는 키 프레임의 모델을 살펴보죠 키 프레임은 사전에 정의된 애니메이션입니다 유동적, 상호 작용형 UI가 필요한 상황에서 일반 SwiftUI 애니메이션을 대체할 수 없다는 뜻이죠 키 프레임은 재생 가능한 비디오 클립에 가깝습니다 얼마든지 제어할 수 있지만 제약도 있습니다 애니메이션의 진행을 사용자가 정확히 지정하기 때문에 키 프레임 애니메이션은 스프링처럼 자연스러운 리타기팅이 불가능하기 때문에 애니메이션 도중 키 프레임을 바꾸는 건 좋지 않습니다 키 프레임은 우리가 정의하는 유형의 값을 애니메이팅하죠 뷰에 수정자를 적용할 때 그걸 사용하면 되고요 단일 키 프레임 트랙에는 단일 수정자를 쓸 수도 있고 수정자 여러 개의 조합을 써도 됩니다 여러분 마음이에요 애니메이션은 우리가 정의하는 값에 따라 생성되기 때문에 프레임마다 업데이트가 일어납니다 그러니 뷰에 키 프레임 애니메이션을 적용할 때는 비싼 작업은 하지 않는 것이 좋습니다 마지막으로 키 프레임을 더 활용하는 법을 보여드리죠 제 앱에는 각 코스를 보여주는 경기 지도가 포함됩니다 저는 자동으로 줌인해서 코스를 따라가는 애니메이션을 추가하고 싶어요 MapKit는 키 프레임을 사용해 카메라를 움직이게 합니다 저는 'Map' 뷰를 이용해 코스를 보여주고 있죠 제 뷰에는 이미 경로가 있어요 각 경기 구간의 좌표가 전부 들어 있는 모델이죠 여정을 만들기 위해 상태 프로퍼티와 변경 버튼을 추가할게요 마지막으로 추가할 새 수정자는 'mapCameraKeyframeAnimator'죠 여기에 트리거값을 부과하고 키 프레임을 추가합니다 앞서 보여드린 예시에서 하트 아이콘을 쓴 것처럼요 트리거값이 바뀔 때마다 지도는 키 프레임으로 애니메이팅을 하죠 키 프레임의 마지막 값이 애니메이션 끝에 쓰이는 카메라값을 결정하게 됩니다 마지막으로 버튼을 누르면 여정이 시작됩니다 애니메이팅 중에 사용자가 제스처를 사용하면 애니메이션은 사라지고 사용자는 카메라를 제어할 수 있게 되죠 중앙 좌표와 방향, 거리를 독립적으로 애니메이팅한 덕에 코스 전체를 부드러운 애니메이션으로 볼 수 있고 줌아웃으로 조감도도 볼 수 있습니다 끝으로 키 프레임에 수동으로 값을 부여해 원하는 효과를 얻는 방법을 보여드리겠습니다 keyframeAnimator 수정자는 아까 보셨죠 'KeyframeTimeline' 유형을 수정자 밖에서 사용하면 키 프레임과 트랙 집합을 캡처할 수 있습니다 애니메이션을 정의하는 키 프레임 트랙과 초깃값으로 이 유형을 초기화하면 돼요 뷰 수정자와 똑같습니다 KeyframeTimeline이 제공하는 API는 지속 시간을 결정하는데 이는 가장 긴 트랙의 지속 시간과 같습니다 애니메이션의 범위 안에서는 어느 시간의 값도 계산 가능하죠 그래서 Swift 차트로 키 프레임을 쉽게 시각화할 수 있습니다 아까 보여드린 곡선 시각화도 그 방법으로 만들었어요 키 프레임으로 정의된 곡선을 마음대로 사용하거나 키 프레임과 다른 API를 결합할 수도 있다는 뜻입니다 지오메트리 프록시와 결합해 스크롤 위치를 사용하면 키 프레임 기반 효과를 취소할 수도 있고 'TimelineView'로 시간 기반 업데이트도 할 수 있죠 언제 사용해야 할지 잘 모르겠다고 해도 괜찮습니다 고급 도구이니까요 많은 개발자가 뷰 수정자를 계속 쓰고 싶어 할 거예요 그래도 이 빌딩 블록을 여러분의 앱에서 얼마나 창의적으로 사용하실지 기대됩니다 이야기를 마칠 시간이군요 저희의 새로운 API군을 즐겁게 쓰셨으면 합니다 연속 애니메이션에서는 꼭 phase를 사용하세요 여러분이 알고 계신 애니메이션 유형을 사용하니 빠르게 작업할 수 있을 겁니다 키 프레임은 완전한 제어가 필요한 더 복잡한 애니메이션에 쓰시고요 마지막으로 즐겁게 탐구하세요 애니메이션의 세계는 정말 신나요 이 새로운 도구들이 여러분과 여러분의 앱을 새로운 곳으로 이끌길 바랍니다 감사합니다 ♪ ♪
-
-
0:42 - Scale Animation
struct Avatar: View { var petImage: Image @State private var selected: Bool = false var body: some View { petImage .scaleEffect(selected ? 1.5 : 1.0) .onTapGesture { withAnimation { selected.toggle() } } } }
-
3:13 - Boolean Phases
OverdueReminderView() .phaseAnimator([false, true]) { content, value in content .foregroundStyle(value ? .red : .primary) } animation: { _ in .easeInOut(duration: 1.0) }
-
6:20 - Custom Phases
ReactionView() .phaseAnimator( Phase.allCases, trigger: reactionCount ) { content, phase in content .scaleEffect(phase.scale) .offset(y: phase.verticalOffset) } animation: { phase in switch phase { case .initial: .smooth case .move: .easeInOut(duration: 0.3) case .scale: .spring( duration: 0.3, bounce: 0.7) } } enum Phase: CaseIterable { case initial case move case scale var verticalOffset: Double { switch self { case .initial: 0 case .move, .scale: -64 } } var scale: Double { switch self { case .initial: 1.0 case .move: 1.1 case .scale: 1.8 } } }
-
9:48 - Keyframes
ReactionView() .keyframeAnimator(initialValue: AnimationValues()) { content, value in content .foregroundStyle(.red) .rotationEffect(value.angle) .scaleEffect(value.scale) .scaleEffect(y: value.verticalStretch) .offset(y: value.verticalTranslation) } keyframes: { _ in KeyframeTrack(\.angle) { CubicKeyframe(.zero, duration: 0.58) CubicKeyframe(.degrees(16), duration: 0.125) CubicKeyframe(.degrees(-16), duration: 0.125) CubicKeyframe(.degrees(16), duration: 0.125) CubicKeyframe(.zero, duration: 0.125) } KeyframeTrack(\.verticalStretch) { CubicKeyframe(1.0, duration: 0.1) CubicKeyframe(0.6, duration: 0.15) CubicKeyframe(1.5, duration: 0.1) CubicKeyframe(1.05, duration: 0.15) CubicKeyframe(1.0, duration: 0.88) CubicKeyframe(0.8, duration: 0.1) CubicKeyframe(1.04, duration: 0.4) CubicKeyframe(1.0, duration: 0.22) } KeyframeTrack(\.scale) { LinearKeyframe(1.0, duration: 0.36) SpringKeyframe(1.5, duration: 0.8, spring: .bouncy) SpringKeyframe(1.0, spring: .bouncy) } KeyframeTrack(\.verticalTranslation) { LinearKeyframe(0.0, duration: 0.1) SpringKeyframe(20.0, duration: 0.15, spring: .bouncy) SpringKeyframe(-60.0, duration: 1.0, spring: .bouncy) SpringKeyframe(0.0, spring: .bouncy) } } struct AnimationValues { var scale = 1.0 var verticalStretch = 1.0 var verticalTranslation = 0.0 var angle = Angle.zero }
-
15:22 - Map Keyframes
struct RaceMap: View { let route: Route @State private var trigger = false var body: some View { Map(initialPosition: .rect(route.rect)) { MapPolyline(coordinates: route.coordinates) .stroke(.orange, lineWidth: 4.0) Marker("Start", coordinate: route.start) .tint(.green) Marker("End", coordinate: route.end) .tint(.red) } .toolbar { Button("Tour") { trigger.toggle() } } .mapCameraKeyframeAnimation(trigger: playTrigger) { initialCamera in KeyframeTrack(\MapCamera.centerCoordinate) { let points = route.points for point in points { CubicKeyframe(point.coordinate, duration: 16.0 / Double(points.count)) } CubicKeyframe(initialCamera.centerCoordinate, duration: 4.0) } KeyframeTrack(\.heading) { CubicKeyframe(heading(from: route.start.coordinate, to: route.end.coordinate), duration: 6.0) CubicKeyframe(heading(from: route.end.coordinate, to: route.end.coordinate), duration: 8.0) CubicKeyframe(initialCamera.heading, duration: 6.0) } KeyframeTrack(\.distance) { CubicKeyframe(24000, duration: 4) CubicKeyframe(18000, duration: 12) CubicKeyframe(initialCamera.distance, duration: 4) } } } }
-
16:26 - KeyframeTimeline
// Keyframes let myKeyframes = KeyframeTimeline(initialValue: CGPoint.zero) { KeyframeTrack(\.x) {...} KeyframeTrack(\.y) {...} } // Duration in seconds let duration: TimeInterval = myKeyframes.duration // Value for time let value = myKeyframes.value(time: 1.2)
-
-
찾고 계신 콘텐츠가 있나요? 위에 주제를 입력하고 원하는 내용을 바로 검색해 보세요.
쿼리를 제출하는 중에 오류가 발생했습니다. 인터넷 연결을 확인하고 다시 시도해 주세요.