스트리밍은 대부분의 브라우저와
Developer 앱에서 사용할 수 있습니다.
-
UI 애니메이션 및 전환 효과 향상하기
탐색 및 표시에 확대/축소 전환을 적용하여 앱에서 연속성을 향상시키는 방법과 SwiftUI 애니메이션으로 UIKit 뷰의 애니메이션을 구현하여 자연스럽게 이어지는 애니메이션을 손쉽게 빌드하는 방법을 알아봅니다.
챕터
- 0:00 - Introduction
- 1:34 - New transitions!
- 2:07 - Zoom transitions in SwiftUI
- 3:02 - Zoom transitions in UIKit
- 4:15 - UIKit view controller life cycle and callbacks
- 7:02 - Additional tips for UIKit
- 8:10 - SwiftUI animation
- 9:46 - Animating representables
- 11:20 - Gesture-driven animations
리소스
관련 비디오
WWDC23
-
다운로드
‘UI 애니메이션 및 전환 효과 향상하기‘에서는 SwiftUI, UIKit 및 AppKit 애니메이션이 친구처럼 친숙하게 느껴지실 것입니다 그리고 그 우정을 기념하기 위해 우정 팔찌를 만들었습니다 그 우정의 힘으로 오늘 여러분과 공유할 몇 가지의 멋진 새 기능과 API가 있습니다 탭한 셀이 화면을 가득 채울 정도로 확대되는 새 확대/축소 기능을 앱에 추가하거나 UIKit 또는 AppKit 뷰에서도 SwiftUI 애니메이션으로 유동적인 상호 작용을 빌드하는 방법을 배우고 싶다면 이 영상이 도움이 될 것입니다 이 비디오의 모든 애니메이션은 절반 속도로 느려져 있습니다 이 API를 위해 우정 팔찌 디자인 기획 앱을 만들었습니다 API의 각 첫 글자로 제가 좋아하는 API를 인코딩합니다 이 의미는 바로 ‘SwiftUI.Animation.spring.repeatForever()‘입니다 이제 이 우정이 강력한 이유를 자세히 알아보겠습니다 먼저 탐색 및 프레젠테이션을 위한 몇 가지 새로운 상위 수준 전환에 대해 설명하겠습니다 또 이 새 전환을 지원하는 데 도움이 되는 SwiftUI 애니메이션과 UIKit 및 AppKit 간의 새로운 통합에 대해 살펴보겠습니다 그 다음 혼합 계층 구조에서 표현이 가능한 유형을 통해 SwiftUI 애니메이션을 연결하는 방법을 알아봅니다 마지막으로, 연속 제스처가 포함될 때 UIView 및 NSView를 애니메이션으로 처리하는 것이 얼마나 강력한지 설명합니다 새로운 전환 효과를 확인해 보세요 iOS 18에는 새 확대/축소 전환 효과가 있습니다 이 새 전환 효과를 사용하면 탭하는 셀이 수신 뷰로 바뀝니다 단순한 시각적 모습이 아니라 지속적으로 상호 작용하므로 처음부터 또는 전환 중에 탭하고 드래그해서 이동할 수 있게 됩니다 큰 셀에서 전환하는 앱의 부분에서 확대/축소 전환을 사용하면 전환하는 동안 화면에 동일한 UI 요소를 유지하여 앱의 연속성을 높일 수 있습니다 코드에 적용해 보겠습니다 여기에 팔찌의 미리 보기를 표시하고 탭하면 전체 팔찌 편집기로 푸시하는 NavigationLink에 대한 기존 코드입니다 이 코드를 사용하면 기본 슬라이드 전환이 발생하여 들어오는 뷰가 뒤쪽 가장자리에서 옵니다 이는 확대/축소 전환 효과를 사용하기에 완벽한 상황입니다
확대/축소 전환을 설정하려면 두 가지 작업을 해야 합니다 첫 번째, 원한다는 표현을 합니다 즉, 표시된 뷰에 navigationTransitionStyle 편집자를 추가하고 확대/축소 전환을 지정합니다 두 번째로, 이 편집자를 소스 뷰에 연결하여 시스템이 확대/축소할 뷰를 알 수 있게 합니다 두 위치 모두에서 동일한 식별자와 네임스페이스를 지정하여 SwiftUI가 미리 보기 표시 뷰와 함께하는지 알 수 있게 합니다 이제 확대/축소합니다
UIKit으로 확대/축소 전환 적용 시에도 비슷합니다 여기에 팔찌 미리 보기를 탭하면 해당 팔찌에 대한 편집기를 생성하고 현재 탐색 컨트롤러에 푸시하는 코드가 있습니다
확대/축소를 적용하려면 먼저 푸시된 뷰 컨트롤러에서 확대/축소할 것을 지정합니다 그리고 확대/축소 전환 소스로 사용할 뷰를 제공합니다 잘 작동하죠
확대/축소 전환 효과에 전달된 클로저는 확대 시 실행되고 축소 시 다시 실행되며 안정적 식별자를 캡처해야 합니다 이 경우 뷰를 직접 캡처하는 대신 뷰를 가져오는 데 사용할 수 있는 팔찌 모델 대상체를 사용합니다 이는 컬렉션 뷰와 같이 소스 뷰가 재사용될 수 있는 경우에 중요합니다
이제 편집기 종료 없이 팔찌 간에 스와이프할 수 있으므로 편집기의 팔찌가 변경될 수도 있습니다 이 문제를 처리하고 올바른 팔찌 미리 보기로 축소하려면 클로저에 전달된 컨텍스트를 사용하여 편집기에서 현재 팔찌를 검색합니다
그리고 참고로 이 동일한 API는 SwiftUI와 UIKit 모두에서 fullScreenCover 및 시트 프리젠테이션 API와 작동합니다 이제 UIKit 앱에 대해 잠시 더 자세히 살펴보고 이러한 새 유연한 전환 효과가 뷰 컨트롤러 수명 주기 appearance 콜백과 어떻게 작동하는지 살펴보겠습니다 SwiftUI와 관련하여 UIKit에서 콜백 수행 작업에 대해 조금 알아보겠습니다
이 예제를 통틀어 푸시되는 팔찌 편집기 뷰 컨트롤러 상태를 고려합니다 그리고 먼저 시스템이 어떻게 작동하는지 보여 주는 모든 사례를 살펴보겠습니다
빨간 점은 편집기의 현재 상태를 나타내며 콜백에 의해 이동하면서 메서드가 호출됩니다 보통 사용자 상호작용 없이 푸시가 완료되면 편집기는 Disappeared 상태에서 시작한 다음
전환 중에 Appearing을 거쳐 viewWillAppear, isAppearing didAppear를 호출하고 전환이 완료되면 Appeared 상태로 끝납니다 마찬가지로, 팝업을 실행하여 완료되면 편집기는 전환 중에 Disappearing 상태를 거쳐 이동한 다음 다시 Disappeared 상태로 끝납니다 뒤로 버튼을 탭하거나 대화식 스와이프로 팝업이 시작된 것도 마찬가지입니다
되감기해서 취소된 팝업이 작동하는지 검토합니다 조금만 드래그하고 있으면 팝업 전환이 시작될 때 편집기가 Disappearing 상태로 이동합니다 그런 다음 팝업이 취소되도록 손가락을 떼면 애니메이션이 완료될 때까지 실행되지만 마지막에는 뷰 컨트롤러가 곧바로 Appearing 상태로 이동한 다음 실행 루프에서 한 번 회전해 Appeared 상태로 이동합니다 기존 전환 시나리오의 콜백 타이밍을 다뤄 보았습니다
이제 이러한 새로운 전환 효과의 유동성을 테스트할 때 상황을 보여 드리겠습니다 편집기가 Disappeared 상태에 있는 처음으로 돌아가 보겠습니다 푸시를 시작했고 이제 편집기는 Appearing 상태입니다 이제 푸시 도중에 뒤로 버튼을 탭하거나 뒤로 스와이프하여 팝업을 시작하면 어떻게 되는지 보겠습니다 이 경우 푸시가 취소되지 않습니다 대신 푸시가 즉시 완료되며 편집기가 곧바로 Appeared 상태로 전환되고 실행 루프의 동일한 방향 전환 때 팝업 전환이 시작되고 Disappearing 상태로 이동합니다 그리고 여기부터 완료되거나 취소될 수 있는 일반적인 팝업 전환입니다
푸시 취소는 팝업 취소 방법과 다르며 이는 의도적인 결과입니다 개념적으로 시스템은 중단 푸시를 취소하지 않습니다 그 대신에 푸시는 항상 팝업으로 변환됩니다 푸시된 뷰 컨트롤러의 관점에서 볼 때 항상 Appeared 상태에 도달하는데 시스템이 항상 그랬던 것처럼 전체 콜백 주기로 실행된다는 걸 의미합니다 휴! appearance 콜백이 친구라는 걸 상기시켜 줄 또 다른 우정 팔찌가 필요한 것 같네요
이 새로운 기능은 기존 코드와 최대한 호환됩니다 푸시 및 팝업 전환이 언제든 시작되는 이 새로운 세계에서 코드가 완벽하게 작동하도록 UIKit 앱의 추가 팁이 있습니다 언제든지 새로운 전환 효과를 시작할 준비를 해 보세요 전환 중에 있는 것과 전환 중이 아닌 것을 다르게 처리하려고 하지 마세요 전환이 진행되면 탭 처리기는 푸시를 호출하지 못합니다 전환 실행 여부에 관계없이 푸시를 호출하면 됩니다
임시 전환 상태를 최소한으로 유지하세요
상태가 적을수록 다른 코드가 전환 상태에 종속될 가능성이 줄어듭니다 정리할 일이 하나 줄어듭니다
하지만 전환 중에 상태를 추적해야 하는 경우 viewDidAppear 또는 viewDidDisappear로 재설정합니다 이렇게 하면 전환이 끝날 때 호출되도록 보장됩니다 탐색 컨트롤러 delegate 메서드 will didShowViewController를 사용하는 경우에도 마찬가지입니다 마지막으로, SwiftUI를 앱에 통합합니다 명령적이기보다는 기능적이기 때문에 SwiftUI는 지속적 변화가 있는 세계를 처리할 때 적합합니다 새 전환에 유용한 건 SwiftUI 애니메이션과 UIKit, AppKit 뷰에 애니메이션을 적용하기 위한 새 하위 수준 API입니다 이로써 맞춤형 UI를 빌드하는 방법을 확인해 보겠습니다 여기 팔찌 편집기에서 하단 상자에 있는 구슬을 탭하여 팔찌 끝에 추가할 수 있습니다
이 UI가 UIKit 또는 AppKit으로 구현된 경우 기존 애니메이션 API를 사용하여 호출 매개변수에서 스프링을 설명하고 클로저에서 뷰 속성을 업데이트합니다 SwiftUI의 경우 유사한 구문이 있는데 SwiftUI 애니메이션 유형으로 애니메이션을 설명하고 클로저에서 상태를 업데이트합니다 둘 모두의 장점을 누릴 수 있다면 정말 멋지지 않을까요? 이제 iOS 18에서 가능합니다 SwiftUI 애니메이션으로 UIKit AppKit 뷰에 적용할 수 있습니다 SwiftUI CustomAnimations 포함 전체 SwiftUI 애니메이션 유형을 사용하여 UIKit 뷰에 애니메이션을 적용할 수 있습니다
코드가 CALayer와 작동하는 경우 이 새 API를 사용할 때 고려할 몇몇 의미가 있습니다 기존 UIKit API는 CAAnimation을 생성한 다음 뷰 레이어에 추가합니다 하지만 SwiftUI 애니메이션은 CAAnimation을 생성하지 않으며 뷰의 레이어 프레젠테이션 값에 직접 애니메이션을 적용합니다 이러한 프레젠테이션 값은 여전히 프레젠테이션 레이어에 반영됩니다
UIKit, AppKit 뷰에 애니메이션 적용 방법을 알아봤으니 UIViewRepresentable 같은 표현 가능 유형 컨텍스트의 애니메이션 적용 방법을 살펴봅니다 BeadBox라는 구슬 상자에 대한 기존 UIView가 있고 이 표현 가능한 래퍼를 사용하여 SwiftUI 앱에 삽입합니다 이 ‘isOpen‘ 바인딩으로 열고 닫는 뚜껑이 있습니다 지금 상자를 여닫을 때 뚜껑이 나타났다가 사라지지만 애니메이션을 적용하고 싶습니다
BeadBoxWrapper가 UIKit으로 구현 여부에 관계없이 수행하는 자연스러운 방법은 바인딩에 애니메이션 편집자를 추가하는 것입니다 BeadBoxWrapper가 SwiftUI로 구현된다면 작동하게 됩니다! 하지만 BeadBoxWrapper는 뚜껑 아래 UIKit으로 구현되므로 애니메이션을 직접 연결해야 합니다
여기서 컨텍스트에서 새로운 ‘animate‘ 메서드를 사용했는데 이를 통해 이 업데이트와 관련된 트랜잭션의 애니메이션을 ‘updateUIView‘ 메서드에서 변경한 UIView에 적용할 수 있습니다 트랜잭션에 존재하는 모든 SwiftUI 애니메이션을 가져오고 UIView.animate 메서드에 연결하여 뚜껑을 위아래로 움직입니다
잘 작동하죠
현재 트랜잭션에 애니메이션이 적용되지 않은 경우 애니메이션 및 완성 효과는 즉시 인라인으로 호출되므로 이 코드는 업데이트에 애니메이션 적용 여부에 관계없이 작동합니다 그리고 특히 SwiftUI View, UIView의 단일 애니메이션이 동기화로 실행됩니다
이제 개별 동작에 대한 응답으로 애니메이션을 실행하는 방법을 다루었으므로 동일한 API가 연속 제스처에 대한 응답으로 실행될 때 더욱 강력해질 수 있는 방법을 알아봅니다 구슬 상자로 돌아가서 팬 제스처를 사용하여 상자 밖으로 구슬을 드래그하고 팔찌 끝쪽으로 날아가게 하고 싶습니다
이제 팬 제스처 응답으로 상자 밖 구슬 드래그 작업을 처리하는 일부 UIKit 코드입니다 팬 제스처가 변경되면 구슬의 중심은 제스처의 변환에 따라 업데이트됩니다 그리고 제스처가 종료되면 최종 위치로 구슬이 이동합니다 여기에 애니메이션을 적용하려면 구슬의 현재 속도를 기반으로 스프링의 초기 속도를 계산하고 구슬이 현재 위치에서 최종 위치까지 이동할 거리로 나누어 단위 속도로 변환해야 합니다 하지만 그렇게 할 필요가 없다면 더 쉽지 않을까요? 예 SwiftUI 애니메이션은 동일한 효과를 가진 이 동등한 SwiftUI 코드에서 볼 수 있듯이 제스처 중 서로 병합하여 제스처가 끝날 때 속도를 보존하는 기능을 이미 갖추고 있습니다 제스처가 끝날 때 초기 속도를 계산할 필요가 없습니다
UIView에 애니메이션 적용 시 동일한 기법을 적용할 수 있고 동일 SwiftUI 애니메이션이 새 UIView 애니메이션 메서드에 전달 가능합니다
화면에서 드래그하면 손가락 움직임 시 제스처가 계속 변경 이벤트를 실행합니다 각 이벤트는 새 ‘.interactiveSpring‘ 애니메이션을 생성하며 각 새 애니메이션은 마지막의 대상을 변경합니다 그 다음 제스처가 끝나면 최종 비대화식 스프링 애니메이션이 생성됩니다 이 스프링 효과는 InteractiveSprings 속도로써 지속적인 속도로 애니메이션을 전달합니다 지속적인 속도가 마음에 무척 드시나요? 이렇게 최고로요 애니메이션과 전환 효과는 여기까지, 이제 여러분 차례입니다 확대/축소할 큰 셀 위치에 확대/축소 전환으로 앱 전체 시각적 연속성을 높일 수 있습니다 확대/축소 전환 효과는 지속적으로 대화식이므로 코드가 언제든 전환을 처리하도록 준비되어 있는지 확인하세요 SwiftUI.Animation으로 UIKit AppKit 뷰에 애니메이션을 적용하세요 특히 지속적 속도 유지가 중요한 UI에서는 더 그렇습니다 코드를 훨씬 단순화하고 멋진 애니메이션을 만드세요 SwiftUI 애니메이션의 전체 모음에 대해 자세히 알아보려면 Kyle의 ‘SwiftUI 애니메이션 살펴보기‘를 보고 스프링에 대해 자세히 알아보려면 Jacob의 ‘스프링 애니메이션 만들기‘를 확인하세요 그리고 모든 지인에게 알려 주세요 이 지식을 세련된 팔찌에 인코딩하고 공유하는 것도 좋고요
-
-
2:10 - Zoom transition in SwiftUI
NavigationLink { BraceletEditor(bracelet) .navigationTransitionStyle( .zoom( sourceID: bracelet.id, in: braceletList ) ) } label: { BraceletPreview(bracelet) } .matchedTransitionSource( id: bracelet.id, in: braceletList )
-
3:02 - Zoom transition in UIKit
func showEditor(for bracelet: Bracelet) { let braceletEditor = BraceletEditor(bracelet) braceletEditor.preferredTransition = .zoom { context in let editor = context.zoomedViewController as! BraceletEditor return cell(for: editor.bracelet) } navigationController?.pushViewController(braceletEditor, animated: true) }
-
8:39 - Animate UIView with SwiftUI animation
UIView.animate(.spring(duration: 0.5)) { bead.center = endOfBracelet }
-
9:56 - Animating representables
struct BeadBoxWrapper: UIViewRepresentable { @Binding var isOpen: Bool func updateUIView(_ box: BeadBox, context: Context) { context.animate { box.lid.center.y = isOpen ? -100 : 100 } } } struct BraceletEditor: View { @State private var isBeadBoxOpen = false var body: some View { BeadBoxWrapper($isBeadBoxOpen.animated()) .onTapGesture { isBeadBoxOpen.toggle() } } }
-
11:39 - Gesture-driven animations
switch gesture.state { case .changed: UIView.animate(.interactiveSpring) { bead.center = gesture.translation } case .ended: UIView.animate(.spring) { bead.center = endOfBracelet } }
-
-
찾고 계신 콘텐츠가 있나요? 위에 주제를 입력하고 원하는 내용을 바로 검색해 보세요.
쿼리를 제출하는 중에 오류가 발생했습니다. 인터넷 연결을 확인하고 다시 시도해 주세요.