Skip to content

Commit 417ea95

Browse files
authored
Merge pull request #43 from Esri/timeSlider
New Component: Time Slider
2 parents d6fa3c1 + 9ff4490 commit 417ea95

36 files changed

+3074
-3
lines changed

Documentation/README.md

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
### Table of Contents
22

3-
- [Scalebar](Scalebar)
4-
- [Legend View Controller](LegendViewController)
3+
* [Compass](Compass)
4+
* [Job Manager](JobManager)
5+
* [Legend View Controller](LegendViewController)
6+
* [Measure Toolbar](MeasureToolbar)
7+
* [Scalebar](Scalebar)
8+
* [TimeSlider](TimeSlider)
33.3 KB
Loading
47.1 KB
Loading
Loading
Loading
Loading
36.3 KB
Loading
Loading
Loading
Loading
33.3 KB
Loading
34.9 KB
Loading
66.3 KB
Loading

Documentation/TimeSlider/README.md

+126
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
#TimeSlider
2+
3+
The Time Slider provides controls that allow you to visualize temporal data for time enabled layers in a map or scene.
4+
5+
###Features of the Time Slider
6+
7+
![timeslider](Images/timeslider.png)
8+
9+
* Tap the **Play** button to play a time animation that steps through your data sequentially. Playback interval, direction and whether to repeat or reverse can be configured from the time slider properties. While playing, the **Pause** button displays in its place.
10+
* Tap the **Forward** button to step forward to the next time stamp.
11+
* Tap the **Back** button to step back to the previous time stamp.
12+
* Drag the **Lower Thumb** (Current Extent Start Time) or **Upper Thumb** (Current Extent End Time) to the right or left to step through your temporal data interactively.
13+
14+
#### Slider Thumbs
15+
16+
How the Time Slider's thumbs are presented will vary depending on whether the current time extent represents a time window or instant and whether the start or end time is pinned.
17+
18+
* **Time Window**: If the current time extent represents a non-instantaneous duration of time, and neither the start nor end time is pinned, then two thumbs will be shown on the slider, as shown below. Both thumbs can be manipulated by end users.
19+
20+
![time-window](Images/time-window.png)
21+
22+
* **Time Window - Start or End Time Pinned:** If the start or end time is pinned using the `isStartTimePinned` or `isEndTimePinned`, then the corresponding slider thumb is replaced with a bar to visually indicate that the start or end time cannot be manipulated. This allows user to cumulatively view all data from the pinned thumb until the specified position of other thumb.
23+
24+
![start-time-pinned](Images/start-time-pinned.png)
25+
26+
![end-time-pinned](Images/end-time-pinned.png)
27+
28+
* **Time Instant:** When the Time Slider's current time extent is specified as a time instant (i.e. `AGSTimeExtent.startTime` equals `AGSTimeExtent.endTime`), then one slider thumb is shown to represent the specified instant.
29+
30+
![time-instant](Images/time-instant.png)
31+
32+
#### Labels
33+
34+
The labels are shown on slider to help user understand the time filtering options slider offers.
35+
36+
* **Full Extent Labels:** Use `fullExtentLabelsVisible` to control the visibility of full extent labels.
37+
38+
* **Current Extent Labels:** Use `LabelMode.thumbs`to display the labels for the current extent start and end time.
39+
40+
![labelmode-thumbs](Images/labelmode-thumbs.png)
41+
42+
* **Time Step Interval Labels:** Use `LabelMode.ticks` to display the labels for the time step intervals instead of for the current time extent.
43+
44+
![labelmode-ticks](Images/labelmode-ticks.png)
45+
46+
#### Display Components
47+
48+
User can control whether to show `Slider` and/or `Playback Buttons` using `isSliderVisible` and `playbackButtonsVisible`.
49+
50+
![only-slider-visible](Images/only-slider-visible.png)
51+
52+
![only-playback-buttons-visible](Images/only-playback-buttons-visible.png)
53+
54+
### Usage
55+
56+
Initialize TimeSlider and add constraints to position it.
57+
58+
```swift
59+
// Configure time slider
60+
let timeSlider = TimeSlider()
61+
timeSlider.labelMode = .ticks
62+
timeSlider.addTarget(self, action: #selector(TimeSliderExample.timeSliderValueChanged(timeSlider:)), for: .valueChanged)
63+
view.addSubview(timeSlider)
64+
65+
// Add constraints to position the slider
66+
let margin: CGFloat = 10.0
67+
timeSlider.translatesAutoresizingMaskIntoConstraints = false
68+
timeSlider.bottomAnchor.constraint(equalTo: mapView.attributionTopAnchor, constant: -margin).isActive = true
69+
70+
if #available(iOS 11.0, *) {
71+
timeSlider.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: margin).isActive = true
72+
timeSlider.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -margin).isActive = true
73+
}
74+
else {
75+
timeSlider.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: margin).isActive = true
76+
timeSlider.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -margin).isActive = true
77+
}
78+
```
79+
80+
Use one of three initialize helper function to setup properties of the Time Slider.
81+
82+
```swift
83+
public func initializeTimeProperties(geoView: AGSGeoView, observeGeoView: Bool, completion: @escaping (Error?)->Void)
84+
public func initializeTimeProperties(timeAwareLayer: AGSTimeAware, completion: @escaping (Error?)->Void)
85+
public func initializeTimeSteps(timeStepCount: Int, fullExtent: AGSTimeExtent, completion: @escaping (Error?)->Void)
86+
```
87+
88+
To see it in action, try out the [Examples](../../Examples) and refer to [TimeSliderExample.swift](../../Examples/ArcGISToolkitExamples/TimeSliderExample.swift) in the project.
89+
90+
### Themes
91+
92+
| Theme | Example |
93+
|:-----------: |:------: |
94+
|`Black` |![Black](Images/black.png) |
95+
|`Blue` |![Blue](Images/blue.png) |
96+
|`Ocean Blue` |![Ocean Blue](Images/ocean-blue.png) |
97+
98+
### Customization
99+
100+
You can customize many visual elements of the TimeSlider such as -
101+
102+
* `currentExtentFillColor`
103+
* `currentExtentLabelColor`
104+
* `currentExtentLabelFont`
105+
* `currentExtentLabelDateStyle`
106+
* `fullExtentBorderColor`
107+
* `fullExtentBorderWidth`
108+
* `fullExtentFillColor`
109+
* `fullExtentLabelColor`
110+
* `fullExtentLabelFont`
111+
* `fullExtentLabelDateStyle`
112+
* `timeStepIntervalLabelDateStyle`
113+
* `timeStepIntervalLabelColor`
114+
* `timeStepIntervalLabelFont`
115+
* `timeStepIntervalTickColor`
116+
* `thumbFillColor`
117+
* `thumbBorderColor`
118+
* `thumbBorderWidth`
119+
* `thumbSize`
120+
* `thumbCornerRadius`
121+
* `playbackButtonsFillColor`
122+
* `layerExtentFillColor`
123+
* `trackHeight`
124+
* `theme`
125+
126+

Examples/ArcGISToolkitExamples.xcodeproj/project.pbxproj

+4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10+
2140781E209B629000FBFDCC /* TimeSliderExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2140781D209B629000FBFDCC /* TimeSliderExample.swift */; };
1011
883904441DF6022A001F3188 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 883904421DF6022A001F3188 /* Main.storyboard */; };
1112
883904461DF6022A001F3188 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 883904451DF6022A001F3188 /* Assets.xcassets */; };
1213
883904491DF6022A001F3188 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 883904471DF6022A001F3188 /* LaunchScreen.storyboard */; };
@@ -64,6 +65,7 @@
6465
/* End PBXCopyFilesBuildPhase section */
6566

6667
/* Begin PBXFileReference section */
68+
2140781D209B629000FBFDCC /* TimeSliderExample.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimeSliderExample.swift; sourceTree = "<group>"; };
6769
8839043B1DF6022A001F3188 /* ArcGISToolkitExamples.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ArcGISToolkitExamples.app; sourceTree = BUILT_PRODUCTS_DIR; };
6870
883904431DF6022A001F3188 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
6971
883904451DF6022A001F3188 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
@@ -143,6 +145,7 @@
143145
88B689C11E96EDF400B67FAB /* MeasureExample.swift */,
144146
88B689C41E96EDF400B67FAB /* ScalebarExample.swift */,
145147
88DBC2A01FE83D6000255921 /* JobManagerExample.swift */,
148+
2140781D209B629000FBFDCC /* TimeSliderExample.swift */,
146149
);
147150
name = Examples;
148151
sourceTree = "<group>";
@@ -263,6 +266,7 @@
263266
buildActionMask = 2147483647;
264267
files = (
265268
88B689C81E96EDF400B67FAB /* AppDelegate.swift in Sources */,
269+
2140781E209B629000FBFDCC /* TimeSliderExample.swift in Sources */,
266270
88B689CB1E96EDF400B67FAB /* MeasureExample.swift in Sources */,
267271
88DBC2A11FE83D6000255921 /* JobManagerExample.swift in Sources */,
268272
88B689D11E96EDF400B67FAB /* VCListViewController.swift in Sources */,

Examples/ArcGISToolkitExamples/ExamplesViewController.swift

+2-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ class ExamplesViewController: VCListViewController {
2626
("Measure", MeasureExample.self, nil),
2727
("Scalebar", ScalebarExample.self, nil),
2828
("Legend", LegendExample.self, nil),
29-
("Job Manager", JobManagerExample.self, nil)
29+
("Job Manager", JobManagerExample.self, nil),
30+
("Time Slider", TimeSliderExample.self, nil)
3031
]
3132

3233
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
//
2+
// Copyright 2018 Esri.
3+
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import UIKit
16+
import ArcGISToolkit
17+
import ArcGIS
18+
19+
class TimeSliderExample: MapViewController {
20+
21+
private var map = AGSMap(basemap: AGSBasemap.topographic())
22+
private var timeSlider = TimeSlider()
23+
24+
override func viewDidLoad() {
25+
super.viewDidLoad()
26+
27+
// Set a map with ESRI topographic basemap to mapView
28+
mapView.map = map
29+
30+
// Configure time slider
31+
timeSlider.isHidden = true
32+
timeSlider.labelMode = .ticks
33+
timeSlider.addTarget(self, action: #selector(TimeSliderExample.timeSliderValueChanged(timeSlider:)), for: .valueChanged)
34+
view.addSubview(timeSlider)
35+
36+
//
37+
// Add constraints to position the slider
38+
let margin: CGFloat = 10.0
39+
timeSlider.translatesAutoresizingMaskIntoConstraints = false
40+
timeSlider.bottomAnchor.constraint(equalTo: mapView.attributionTopAnchor, constant: -margin).isActive = true
41+
42+
if #available(iOS 11.0, *) {
43+
timeSlider.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: margin).isActive = true
44+
timeSlider.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -margin).isActive = true
45+
}
46+
else {
47+
timeSlider.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: margin).isActive = true
48+
timeSlider.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -margin).isActive = true
49+
}
50+
51+
// Add layer
52+
let mapImageLayer = AGSArcGISMapImageLayer(url: URL(string: "https://sampleserver6.arcgisonline.com/arcgis/rest/services/911CallsHotspot/MapServer")!)
53+
mapView.map?.operationalLayers.add(mapImageLayer)
54+
mapImageLayer.load(completion: { [weak self] (error) in
55+
//
56+
// Make sure self is around
57+
guard let strongSelf = self else {
58+
return
59+
}
60+
61+
// If layer fails to load then
62+
// return with an error.
63+
guard error == nil else {
64+
strongSelf.showError(error!)
65+
return
66+
}
67+
68+
// Zoom to full extent of layer
69+
if let fullExtent = mapImageLayer.fullExtent {
70+
strongSelf.mapView.setViewpoint(AGSViewpoint(targetExtent: fullExtent), completion: nil)
71+
}
72+
73+
strongSelf.timeSlider.initializeTimeProperties(geoView: strongSelf.mapView, observeGeoView: true, completion: { [weak self] (error) in
74+
//
75+
// Make sure self is around
76+
guard let strongSelf = self else {
77+
return
78+
}
79+
80+
// If time slider fails to init then
81+
// return with an error.
82+
guard error == nil else {
83+
strongSelf.showError(error!)
84+
return
85+
}
86+
87+
// Show the time slider
88+
strongSelf.timeSlider.isHidden = false
89+
})
90+
})
91+
}
92+
93+
@objc func timeSliderValueChanged(timeSlider: TimeSlider) {
94+
if mapView.timeExtent != timeSlider.currentExtent {
95+
mapView.timeExtent = timeSlider.currentExtent
96+
}
97+
}
98+
99+
//MARK: - Show Error
100+
101+
private func showError(_ error: Error) {
102+
let alertController = UIAlertController(title: "Error", message: error.localizedDescription, preferredStyle: .alert)
103+
alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
104+
present(alertController, animated: true, completion: nil)
105+
}
106+
}
107+
108+

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Toolkit components that will simplify your iOS app development with ArcGIS Runti
99
* [Legend View Controller](Documentation/LegendViewController)
1010
* [Measure Toolbar](Documentation/MeasureToolbar)
1111
* [Scalebar](Documentation/Scalebar)
12+
* [TimeSlider](Documentation/TimeSlider)
1213

1314
## Requirements
1415
* [ArcGIS Runtime SDK for iOS](https://developers.arcgis.com/en/ios/) 100.2.1 (or higher)

Toolkit/ArcGISToolkit.xcodeproj/project.pbxproj

+4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10+
2140781C209B628200FBFDCC /* TimeSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2140781B209B628200FBFDCC /* TimeSlider.swift */; };
1011
881233751DF601A700B2EA8E /* ArcGISToolkit.h in Headers */ = {isa = PBXBuildFile; fileRef = 881233731DF601A700B2EA8E /* ArcGISToolkit.h */; settings = {ATTRIBUTES = (Public, ); }; };
1112
881233841DF601E900B2EA8E /* ArcGIS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 881233831DF601E900B2EA8E /* ArcGIS.framework */; };
1213
88B689FD1E96EFD700B67FAB /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88B689F01E96EFD700B67FAB /* Extensions.swift */; };
@@ -25,6 +26,7 @@
2526
/* End PBXBuildFile section */
2627

2728
/* Begin PBXFileReference section */
29+
2140781B209B628200FBFDCC /* TimeSlider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimeSlider.swift; sourceTree = "<group>"; };
2830
881233701DF601A700B2EA8E /* ArcGISToolkit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ArcGISToolkit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
2931
881233731DF601A700B2EA8E /* ArcGISToolkit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ArcGISToolkit.h; sourceTree = "<group>"; };
3032
881233741DF601A700B2EA8E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@@ -115,6 +117,7 @@
115117
88B689F71E96EFD700B67FAB /* Scalebar.swift */,
116118
88B689FB1E96EFD700B67FAB /* UnitsViewController.swift */,
117119
88DBC29E1FE83D4400255921 /* JobManager.swift */,
120+
2140781B209B628200FBFDCC /* TimeSlider.swift */,
118121
);
119122
name = Components;
120123
sourceTree = "<group>";
@@ -206,6 +209,7 @@
206209
88B68A081E96EFD700B67FAB /* UnitsViewController.swift in Sources */,
207210
E48405731E9BE7B700927208 /* LegendViewController.swift in Sources */,
208211
88B68A011E96EFD700B67FAB /* MeasureToolbar.swift in Sources */,
212+
2140781C209B628200FBFDCC /* TimeSlider.swift in Sources */,
209213
88DBC2A31FE83DB800255921 /* CancelGroup.swift in Sources */,
210214
88B68A071E96EFD700B67FAB /* TableViewController.swift in Sources */,
211215
88B689FD1E96EFD700B67FAB /* Extensions.swift in Sources */,
Loading
Loading
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"images" : [
3+
{
4+
"idiom" : "universal",
5+
"filename" : "Backward44.png",
6+
"scale" : "1x"
7+
},
8+
{
9+
"idiom" : "universal",
10+
"filename" : "Backward88.png",
11+
"scale" : "2x"
12+
},
13+
{
14+
"idiom" : "universal",
15+
"filename" : "Backward132.png",
16+
"scale" : "3x"
17+
}
18+
],
19+
"info" : {
20+
"version" : 1,
21+
"author" : "xcode"
22+
},
23+
"properties" : {
24+
"template-rendering-intent" : "template"
25+
}
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"images" : [
3+
{
4+
"idiom" : "universal",
5+
"filename" : "Forward44.png",
6+
"scale" : "1x"
7+
},
8+
{
9+
"idiom" : "universal",
10+
"filename" : "Forward88.png",
11+
"scale" : "2x"
12+
},
13+
{
14+
"idiom" : "universal",
15+
"filename" : "Forward132.png",
16+
"scale" : "3x"
17+
}
18+
],
19+
"info" : {
20+
"version" : 1,
21+
"author" : "xcode"
22+
},
23+
"properties" : {
24+
"template-rendering-intent" : "template"
25+
}
26+
}
Loading
Loading
Loading

0 commit comments

Comments
 (0)