Mastering MapKit in SwiftUI & iOS 17 — Part 14
MapKit provides some subtle animations out of the box, but if we need a bit more control over the animation, we can leverage the power of mapCameraKeyframeAnimator
. It utilizes the provided keyframes to animate the camera of a Map view when the given trigger value changes. Upon the trigger value alteration, the map invokes the keyframes
closure to generate the keyframes responsible for animating the camera. The animation will persist for the duration of the specified keyframes. If the user performs a gesture while the animation is ongoing, it will be promptly terminated, enabling user interaction to assume control of the camera.
Let’s build an experience where we will fly from Alcatraz to GoldenGate bridge in SF Top Attractions app.
This is what we will be building
Start with a State property to control the animation.
import SwiftUI
import MapKit
struct MapCameraAnimator: View {
@State var animate: Bool = false
We will fetch and store coordinates for both locations in local variables.
import SwiftUI
import MapKit
struct MapCameraAnimator: View {
@State var animate: Bool = false
let alcatraz = SFPlace.topAttractions[0].coordinates
let goldenGate = SFPlace.topAttractions[1].coordinates
Next, create another State property to set map camera position on the top of Alcatraz.
import SwiftUI
import MapKit
struct MapCameraAnimator: View {
@State var animate: Bool = false
let alcatraz = SFPlace.topAttractions[0].coordinates
let goldenGate = SFPlace.topAttractions[1].coordinates
@State private var mapCameraPosition = MapCameraPosition.camera(MapCamera(
centerCoordinate: SFPlace.topAttractions[0].coordinates, distance: 5000))
Let’s use the map view to render map on the screen
import SwiftUI
import MapKit
struct MapCameraAnimator: View {
@State var animate: Bool = false
let alcatraz = SFPlace.topAttractions[0].coordinates
let goldenGate = SFPlace.topAttractions[1].coordinates
@State private var mapCameraPosition = MapCameraPosition.camera(MapCamera(
centerCoordinate: SFPlace.topAttractions[0].coordinates, distance: 5000))
var body: some View {
VStack {
Map(position: $mapCameraPosition)
.mapStyle(.standard(elevation: .realistic))
We will use a button control to trigger the animation so let’s add that inside a safeAreaInset
import SwiftUI
import MapKit
struct MapCameraAnimator: View {
@State var animate: Bool = false
let alcatraz = SFPlace.topAttractions[0].coordinates
let goldenGate = SFPlace.topAttractions[1].coordinates
@State private var mapCameraPosition = MapCameraPosition.camera(MapCamera(
centerCoordinate: SFPlace.topAttractions[0].coordinates, distance: 5000))
var body: some View {
VStack {
Map(position: $mapCameraPosition)
.mapStyle(.standard(elevation: .realistic))
.safeAreaInset(edge: .bottom) {
Button("Animate") {
animate.toggle()
}
}
Now we are ready to add keyframe animation to the map, we will use mapCameraKeyframeAnimator modifier passing the animate State property as the trigger value.
import SwiftUI
import MapKit
struct MapCameraAnimator: View {
@State var animate: Bool = false
let alcatraz = SFPlace.topAttractions[0].coordinates
let goldenGate = SFPlace.topAttractions[1].coordinates
@State private var mapCameraPosition = MapCameraPosition.camera(MapCamera(
centerCoordinate: SFPlace.topAttractions[0].coordinates, distance: 5000))
var body: some View {
VStack {
Map(position: $mapCameraPosition)
.mapStyle(.standard(elevation: .realistic))
.safeAreaInset(edge: .bottom) {
Button("Animate") {
animate.toggle()
}
}
.mapCameraKeyframeAnimator(trigger: animate) { _ in
We will use KeyframeTrack which defines a sequence of keyframes animating a single property of a root type. For our use case, we will animate pitch, heading and centerCoordinate values.
import SwiftUI
import MapKit
struct MapCameraAnimator: View {
@State var animate: Bool = false
let alcatraz = SFPlace.topAttractions[0].coordinates
let goldenGate = SFPlace.topAttractions[1].coordinates
@State private var mapCameraPosition = MapCameraPosition.camera(MapCamera(
centerCoordinate: SFPlace.topAttractions[0].coordinates, distance: 5000))
var body: some View {
VStack {
Map(position: $mapCameraPosition)
.mapStyle(.standard(elevation: .realistic))
.safeAreaInset(edge: .bottom) {
Button("Animate") {
animate.toggle()
}
}
.mapCameraKeyframeAnimator(trigger: animate) { _ in
KeyframeTrack(\.pitch) {
LinearKeyframe(0, duration: 1.0)
LinearKeyframe(75, duration: 1.0)
}
KeyframeTrack(\.heading) {
LinearKeyframe(0, duration: 1.0)
LinearKeyframe(-70, duration: 1.0)
}
KeyframeTrack(\.centerCoordinate) {
LinearKeyframe(alcatraz, duration: 1.0)
LinearKeyframe(alcatraz, duration: 1.0)
LinearKeyframe(goldenGate, duration: 10)
}
}
}
}
}
Time to build and run
With that we have reached the end of this article. Thank you once again for reading. If you liked this, don’t forget to 👏 and follow 😍. Also visit us at https://www.devtechie.com