Skip to Content
ExamplesChange the timeline range

MapsGL iOS - Change the timeline range

Move the animation window between history and forecast at runtime.

Change the timeline range

The full file is ChangeTimelineRangeExample.swift in the demo app .

ChangeTimelineRangeExample.swift
// // ChangeTimelineRangeExample.swift // Demo // import Combine import CoreLocation import MapsGLMaps import SwiftUI /// Moving the timeline's start and end independently, while it runs. /// /// `timeline.startDate` and `timeline.endDate` bound the window every layer animates /// over. Assigning either one re-scales the playhead and makes each layer re-resolve /// the intervals it needs, so widening from a day of history to a week is one /// assignment rather than a teardown. struct ChangeTimelineRangeExample: View { /// Offsets from now, in hours — the same set the JavaScript example offers. private enum Offset: Int, CaseIterable, Identifiable { case now = 0 case oneDay = 24 case threeDays = 72 case sevenDays = 168 var id: Int { rawValue } func label(isStart: Bool) -> String { switch self { case .now: "Now" case .oneDay: isStart ? "-1 day" : "+1 day" case .threeDays: isStart ? "-3 days" : "+3 days" case .sevenDays: isStart ? "-7 days" : "+7 days" } } } @State private var startOffset: Offset = .now @State private var endOffset: Offset = .oneDay @State private var startDate = Date() @State private var endDate = Date() @State private var isActive = false @State private var isPaused = false var body: some View { ExampleMapView(center: CLLocationCoordinate2D(latitude: 47, longitude: 20), zoom: 4) { mapController, subscriptions in do { let beforeId = DemoMapProvider.weatherLayerInsertBeforeId( for: mapController, layerCode: .windParticles ) try mapController.addWeatherLayer(for: .windParticles, beforeId: beforeId) } catch { print("Failed to add the wind particles layer: \(error)") } applyRange(to: mapController) } overlay: { mapController in ExampleControlPanel { ExampleControlGroup(title: "Range") { HStack(alignment: .top, spacing: 24) { offsetPicker("Start", selection: $startOffset, isStart: true, resolved: startDate, mapController: mapController) offsetPicker("End", selection: $endOffset, isStart: false, resolved: endDate, mapController: mapController) } } ExampleControlGroup { playback(for: mapController) } } } } @ViewBuilder private func offsetPicker( _ title: String, selection: Binding<Offset>, isStart: Bool, resolved: Date, mapController: DemoMapController? ) -> some View { VStack(alignment: .leading, spacing: 2) { Picker(title, selection: selection) { ForEach(Offset.allCases) { offset in Text(offset.label(isStart: isStart)).tag(offset) } } .pickerStyle(.menu) .onChange(of: selection.wrappedValue) { _ in guard let mapController else { return } applyRange(to: mapController) } Text(Self.formatter.string(from: resolved)) .font(.caption2) .foregroundStyle(.secondary) } } @ViewBuilder private func playback(for mapController: DemoMapController?) -> some View { HStack(spacing: 16) { Button(isActive ? "Stop" : "Play") { guard let timeline = mapController?.timeline else { return } if timeline.isActive { timeline.stop() } else { timeline.play() } syncPlaybackState(from: timeline) } Button(isPaused ? "Resume" : "Pause") { guard let timeline = mapController?.timeline else { return } if timeline.isPaused { timeline.resume() } else { timeline.pause() } syncPlaybackState(from: timeline) } .disabled(!isActive) } .exampleControlSizing() } /// Start runs back from now, end runs forward. private func applyRange(to mapController: DemoMapController) { let now = Date() startDate = Self.startOfHour(containing: now.addingTimeInterval(-Double(startOffset.rawValue) * 3600)) endDate = now.addingTimeInterval(Double(endOffset.rawValue) * 3600) mapController.timeline.startDate = startDate mapController.timeline.endDate = endDate } /// The top of the hour `date` falls in. /// /// Weather data is published on the hour, and a layer draws the frame at or before /// the playhead. A range beginning at an arbitrary moment therefore starts *before* /// its own first published interval — "Now" at 15:30 asks for a window whose first /// frame is 16:00 — and the map stays blank until playback reaches it. Snapping the /// start back to the hour puts a frame under the playhead from the first draw. private static func startOfHour(containing date: Date) -> Date { Calendar.current.dateInterval(of: .hour, for: date)?.start ?? date } private func syncPlaybackState(from timeline: Timeline) { isActive = timeline.isActive isPaused = timeline.isPaused } private static let formatter: DateFormatter = { let formatter = DateFormatter() formatter.dateFormat = "EEE h:mm a" return formatter }() } #Preview { ChangeTimelineRangeExample() }
© 2026 Xweather (opens in a new tab)Terms of Service (opens in a new tab)Privacy Policy (opens in a new tab)