├── .gitignore
├── .swiftlint.yml
├── .swiftpm
└── xcode
│ └── xcshareddata
│ └── xcschemes
│ └── ArcGISToolkit.xcscheme
├── Documentation
├── AR
│ └── README.md
├── Bookmarks
│ └── README.md
├── Compass
│ └── README.md
├── FloorFilter
│ └── README.md
├── JobManager
│ └── README.md
├── LegendViewController
│ └── README.md
├── MeasureToolbar
│ └── README.md
├── PopupController
│ └── README.md
├── README.md
├── Scalebar
│ ├── Images
│ │ ├── alternating-bar.png
│ │ ├── bar.png
│ │ ├── dual-unit-line.png
│ │ ├── graduated-line.png
│ │ └── line.png
│ └── README.md
├── TemplatePicker
│ └── README.md
└── TimeSlider
│ ├── Images
│ ├── black.png
│ ├── blue.png
│ ├── end-time-pinned.png
│ ├── labelmode-thumbs.png
│ ├── labelmode-ticks.png
│ ├── ocean-blue.png
│ ├── only-playback-buttons-visible.png
│ ├── only-slider-visible.png
│ ├── start-time-pinned.png
│ ├── time-instant.png
│ ├── time-window.png
│ └── timeslider.png
│ └── README.md
├── Examples
├── .swiftlint.yml
├── ArcGISToolkitExamples.xcodeproj
│ ├── project.pbxproj
│ └── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── ArcGISToolkitExamples
│ ├── ARExample.swift
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ │ ├── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ └── Contents.json
│ ├── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ ├── BookmarksExample.swift
│ ├── CompassExample.swift
│ ├── ExamplesViewController.swift
│ ├── FloorFilterExample.swift
│ ├── Info.plist
│ ├── JobManagerExample.swift
│ ├── LegendExample.swift
│ ├── MeasureExample.swift
│ ├── Misc
│ │ ├── ARStatusViewController.storyboard
│ │ ├── ARStatusViewController.swift
│ │ ├── CalibrationView.swift
│ │ ├── Plane.swift
│ │ └── UserDirectionsView.swift
│ ├── PopupExample.swift
│ ├── ScalebarExample.swift
│ ├── TemplatePickerExample.swift
│ ├── TimeSliderExample.swift
│ └── VCListViewController.swift
├── README.md
└── exportOptions.plist
├── LICENSE
├── Package.swift
├── README.md
├── Sources
└── ArcGISToolkit
│ ├── AR
│ └── ArcGISARView.swift
│ ├── BookmarksTableViewController.swift
│ ├── BookmarksViewController.swift
│ ├── CancelGroup.swift
│ ├── Coalescer.swift
│ ├── Compass.swift
│ ├── Extensions.swift
│ ├── FloorFilter
│ ├── FloorFilterViewController.swift
│ ├── FloorFilterViewModel.swift
│ ├── SiteFacilityPromptViewController.swift
│ └── SiteFacilityTableViewCell.swift
│ ├── JobManager.swift
│ ├── LegendViewController.swift
│ ├── MapViewController.swift
│ ├── MeasureToolbar.swift
│ ├── PopupController.swift
│ ├── Resources
│ ├── Assets.xcassets
│ │ ├── Back.imageset
│ │ │ ├── Backward132.png
│ │ │ ├── Backward44.png
│ │ │ ├── Backward88.png
│ │ │ └── Contents.json
│ │ ├── CompassIcon.imageset
│ │ │ ├── Contents.json
│ │ │ ├── iOS8_NorthCompass108.png
│ │ │ ├── iOS8_NorthCompass36_D.png
│ │ │ └── iOS8_NorthCompass72.png
│ │ ├── Contents.json
│ │ ├── Dot.imageset
│ │ │ ├── Contents.json
│ │ │ └── fiber_manual_record_black_18dp.svg
│ │ ├── Forward.imageset
│ │ │ ├── Contents.json
│ │ │ ├── Forward132.png
│ │ │ ├── Forward44.png
│ │ │ └── Forward88.png
│ │ ├── MeasureArea.imageset
│ │ │ ├── Contents.json
│ │ │ ├── iOS8_Toolbar_MeasureArea22.png
│ │ │ ├── iOS8_Toolbar_MeasureArea44.png
│ │ │ └── iOS8_Toolbar_MeasureArea66.png
│ │ ├── MeasureFeature.imageset
│ │ │ ├── Contents.json
│ │ │ ├── MeasureFeature22.png
│ │ │ ├── MeasureFeature44.png
│ │ │ └── MeasureFeature66.png
│ │ ├── MeasureLength.imageset
│ │ │ ├── Contents.json
│ │ │ ├── iOS8_Toolbar_MeasureLength22.png
│ │ │ ├── iOS8_Toolbar_MeasureLength44.png
│ │ │ └── iOS8_Toolbar_MeasureLength66.png
│ │ ├── Pause.imageset
│ │ │ ├── Contents.json
│ │ │ ├── Pause132.png
│ │ │ ├── Pause44.png
│ │ │ └── Pause88.png
│ │ ├── Play.imageset
│ │ │ ├── Contents.json
│ │ │ ├── Play132.png
│ │ │ ├── Play44.png
│ │ │ └── Play88.png
│ │ ├── Redo.imageset
│ │ │ ├── Contents.json
│ │ │ ├── iOS8_TabBar_Redo22s.png
│ │ │ ├── iOS8_TabBar_Redo44s.png
│ │ │ └── iOS8_TabBar_Redo66s.png
│ │ ├── Site.imageset
│ │ │ ├── Contents.json
│ │ │ ├── business_black_18dp (1).svg
│ │ │ ├── business_black_24dp (1)-1.svg
│ │ │ └── business_black_24dp (1).svg
│ │ └── Undo.imageset
│ │ │ ├── Contents.json
│ │ │ ├── iOS8_TabBar_Undo22s.png
│ │ │ ├── iOS8_TabBar_Undo44s.png
│ │ │ └── iOS8_TabBar_Undo66s.png
│ ├── FloorFilter.storyboard
│ ├── FloorFilterSiteFacilityCell.xib
│ ├── Legend.storyboard
│ └── PrivacyInfo.xcprivacy
│ ├── Scalebar.swift
│ ├── TableViewController.swift
│ ├── TemplatePickerViewController.swift
│ ├── TimeSlider.swift
│ └── UnitsViewController.swift
└── Tests
└── ArcGISToolkitTests
├── ArcGISToolkitTests.swift
├── BookmarksViewControllerTests.swift
└── FloorFilterViewModelTests.swift
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Filesystem
6 | .DS_Store
7 |
8 | ## Build generated
9 | build/
10 | DerivedData/
11 |
12 | ## Various settings
13 | *.pbxuser
14 | !default.pbxuser
15 | *.mode1v3
16 | !default.mode1v3
17 | *.mode2v3
18 | !default.mode2v3
19 | *.perspectivev3
20 | !default.perspectivev3
21 | xcuserdata/
22 |
23 | ## Other
24 | *.moved-aside
25 | *.xcuserstate
26 |
27 | ## Obj-C/Swift specific
28 | *.hmap
29 | *.ipa
30 | *.dSYM.zip
31 | *.dSYM
32 |
33 | ## Playgrounds
34 | timeline.xctimeline
35 | playground.xcworkspace
36 |
37 | # Swift Package Manager
38 | #
39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
40 | # Packages/
41 | .build/
42 | Package.resolved
43 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
44 |
45 | # CocoaPods
46 | #
47 | # We recommend against adding the Pods directory to your .gitignore. However
48 | # you should judge for yourself, the pros and cons are mentioned at:
49 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
50 | #
51 | # Pods/
52 |
53 | # Carthage
54 | #
55 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
56 | # Carthage/Checkouts
57 |
58 | Carthage/Build
59 |
60 | # fastlane
61 | #
62 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
63 | # screenshots whenever they are needed.
64 | # For more information about the recommended setup visit:
65 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md
66 |
67 | fastlane/report.xml
68 | fastlane/Preview.html
69 | fastlane/screenshots
70 | fastlane/test_output
71 |
--------------------------------------------------------------------------------
/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | opt_in_rules:
2 | - anyobject_protocol
3 | - array_init
4 | - attributes
5 | - block_based_kvo
6 | - closure_end_indentation
7 | - closure_spacing
8 | - collection_alignment
9 | - contains_over_filter_count
10 | - contains_over_filter_is_empty
11 | - contains_over_first_not_nil
12 | - convenience_type
13 | - discouraged_direct_init
14 | - discouraged_optional_boolean
15 | - empty_collection_literal
16 | - empty_count
17 | - empty_string
18 | - empty_xctest_method
19 | - explicit_init
20 | - extension_access_modifier
21 | - fatal_error_message
22 | - first_where
23 | - function_default_parameter_at_end
24 | - identical_operands
25 | - joined_default_parameter
26 | - legacy_random
27 | - let_var_whitespace
28 | - literal_expression_end_indentation
29 | - lower_acl_than_parent
30 | - modifier_order
31 | - multiline_arguments
32 | - multiline_function_chains
33 | - multiline_parameters
34 | - operator_usage_whitespace
35 | - operator_whitespace
36 | - overridden_super_call
37 | - override_in_extension
38 | - prohibited_super_call
39 | - redundant_nil_coalescing
40 | - redundant_type_annotation
41 | - sorted_first_last
42 | - static_operator
43 | - toggle_bool
44 | - trailing_closure
45 | - untyped_error_in_catch
46 | - vertical_parameter_alignment_on_call
47 | - vertical_whitespace_closing_braces
48 | - vertical_whitespace_opening_braces
49 | - xctfail_message
50 | - yoda_condition
51 | disabled_rules:
52 | - file_length
53 | - for_where
54 | - force_cast
55 | - function_body_length
56 | - function_parameter_count
57 | - identifier_name
58 | - large_tuple
59 | - line_length
60 | - nesting
61 | - todo
62 | - type_body_length
63 |
64 | trailing_whitespace:
65 | ignores_empty_lines: true
66 |
67 | excluded:
68 | - Pods
69 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/xcshareddata/xcschemes/ArcGISToolkit.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
38 |
39 |
44 |
45 |
47 |
53 |
54 |
55 |
56 |
57 |
67 |
68 |
74 |
75 |
81 |
82 |
83 |
84 |
86 |
87 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/Documentation/AR/README.md:
--------------------------------------------------------------------------------
1 | # Augmented reality (AR)
2 |
3 | [](https://developers.arcgis.com/ios/scenes-3d/display-scenes-in-augmented-reality/) [](https://developers.arcgis.com/ios/swift/sample-code/collect-data-in-ar/) [](https://developers.arcgis.com/ios/swift/sample-code/display-scenes-in-tabletop-ar/) [](https://developers.arcgis.com/ios/swift/sample-code/explore-scenes-in-flyover-ar/)
4 |
5 | Augmented reality experiences are designed to "augment" the physical world with virtual content that respects real world scale, position, and orientation of a device. In the case of Runtime, a SceneView displays 3D geographic data as virtual content on top of a camera feed which represents the real, physical world.
6 |
7 | The Augmented Reality (AR) toolkit component allows quick and easy integration of AR into your application for a wide variety of scenarios. The toolkit recognizes the following common patterns for AR:
8 | * **Flyover**: Flyover AR allows you to explore a scene using your device as a window into the virtual world. A typical flyover AR scenario will start with the scene’s virtual camera positioned over an area of interest. You can walk around and reorient the device to focus on specific content in the scene.
9 | * **Tabletop**: Scene content is anchored to a physical surface, as if it were a 3D-printed model.
10 | * **World-scale**: Scene content is rendered exactly where it would be in the physical world. A camera feed is shown and GIS content is rendered on top of that feed. This is used in scenarios ranging from viewing hidden infrastructure to displaying waypoints for navigation.
11 |
12 | The AR toolkit component is comprised of one class: `ArcGISARView`. This is a subclass of `UIView` that contains the functionality needed to display an AR experience in your application. It uses `ARKit`, Apple's augmented reality framework to display the live camera feed and handle real world tracking and synchronization with the Runtime SDK's `AGSSceneView`. The `ArcGISARView` is responsible for starting and managing an `ARKit` session. It uses a user-provided `AGSLocationDataSource` for getting an initial GPS location and when continuous GPS tracking is required.
13 |
14 | ### Features of the AR component
15 |
16 | - Allows display of the live camera feed
17 | - Manages `ARKit` `ARSession` lifecycle
18 | - Tracks user location and device orientation through a combination of `ARKit` and the device GPS
19 | - Provides access to an `AGSSceneView` to display your GIS 3D data over the live camera feed
20 | - `ARScreenToLocation` method to convert a screen point to a real-world coordinate
21 | - Easy access to all `ARKit` and `AGSLocationDataSource` delegate methods
22 |
23 | ### Usage
24 |
25 | ```swift
26 | let arView = ArcGISARView(renderVideoFeed: true)
27 | view.addSubview(arView)
28 | arView.translatesAutoresizingMaskIntoConstraints = false
29 | NSLayoutConstraint.activate([
30 | arView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
31 | arView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
32 | arView.topAnchor.constraint(equalTo: view.topAnchor),
33 | arView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
34 | ])
35 |
36 |
37 | // Create a simple scene.
38 | arView.sceneView.scene = AGSScene(basemapStyle: .arcGISImagery)
39 |
40 | // Set a AGSCLLocationDataSource, used to get our initial real-world location.
41 | arView.locationDataSource = AGSCLLocationDataSource()
42 |
43 | // Start tracking our location and device orientation
44 | arView.startTracking(.initial) { (error) in
45 | print("Start tracking error: \(String(describing: error))")
46 | }
47 |
48 | ```
49 |
50 | You must also add the following entries to your application's `Info.plist` file. These are required to allow access to the camera (for the live video feed) and to allow access to location services (when using the `AGSCLLocationDataSource`):
51 |
52 | * Privacy – Camera Usage Description ([NSCameraUsageDescription](https://developer.apple.com/documentation/bundleresources/information_property_list/nscamerausagedescription))
53 | * Privacy – Location When In Use Usage Description ([NSLocationWhenInUseUsageDescription](https://developer.apple.com/documentation/bundleresources/information_property_list/nslocationwheninuseusagedescription))
54 |
55 | To see it in action, try out the [Examples](../../Examples) and refer to [ARExample.swift](../../Examples/ArcGISToolkitExamples/ARExample.swift) in the project.
56 |
--------------------------------------------------------------------------------
/Documentation/Bookmarks/README.md:
--------------------------------------------------------------------------------
1 | # Bookmarks
2 |
3 | The Bookmarks component will display a list of bookmarks in a table view and allows the user to select a bookmark and perform some action.
4 |
5 | ## Bookmarks Behavior
6 |
7 | The `BookmarksTableViewController` can be created using either an `AGSGeoView` or an array of `AGSBookmark`s.
8 |
9 | Clients must set the `delegate` property and implement the `bookmarksViewController:didSelect` delegate method in order to be notified when a bookmark is selected.
10 |
11 | The `BookmarksTableViewController` observes changes to the `map` or `scene` property on the `AGSGeoView` and also the map or scene's `bookmarks` property and will update the list of bookmarks accordingly.
12 |
13 | ## Usage
14 |
15 | ```swift
16 | bookmarksVC = BookmarksViewController(geoView: mapView)
17 | bookmarksVC?.delegate = self
18 |
19 | ...
20 |
21 | func bookmarksViewController(_ controller: BookmarksViewController, didSelect bookmark: AGSBookmark) {
22 | if let viewpoint = bookmark.viewpoint {
23 | mapView.setViewpoint(viewpoint, duration: 2.0)
24 | dismiss(animated: true)
25 | }
26 | }
27 | ```
28 |
29 | To see it in action, try out the [Examples](../../Examples) and refer to [BookmarksExample.swift](../../Examples/ArcGISToolkitExamples/BookmarksExample.swift) in the project.
30 |
--------------------------------------------------------------------------------
/Documentation/Compass/README.md:
--------------------------------------------------------------------------------
1 | # Compass
2 |
3 | The Compass (alias North arrow) shows where north is in the MapView. The Compass supports reset rotation.
4 |
5 | The ArcGIS Runtime SDK currently supports rotating the map with 2-finger gesture on MapView and SceneView interactively by default and while the map will snap to north when rotating using gestures, the compass provides an easier way. The Compass Toolkit component will appear when the map is rotated and, when tapped, re-orientates the map back to north and hides the compass icon (note that the MapView auto-snaps back to north when it's within a threshold of north, and in that case the compass also auto hides).
6 |
7 | ### Compass Behavior
8 |
9 | Whenever the map is not orientated North (non-zero bearing) the compass appears. When reset to north, it disappears. A property allows you to disable the auto-hide feature so that it always shows.
10 |
11 | When the compass is tapped, the map orients back to north (zero bearing), the default orientation and the compass fades away, or after a short duration disappears.
12 |
13 | ### Usage
14 |
15 | ```swift
16 | let compass = Compass(mapView: mapView)
17 | self.view.addSubview(compass)
18 | ```
19 |
20 | To see it in action, try out the [Examples](../../Examples) and refer to [CompassExample.swift](../../Examples/ArcGISToolkitExamples/CompassExample.swift) in the project.
21 |
--------------------------------------------------------------------------------
/Documentation/FloorFilter/README.md:
--------------------------------------------------------------------------------
1 | # FloorFilter
2 |
3 | The `FloorFilter` component simplifies visualization of GIS data for a specific floor of a building in your application. It allows you to filter down the floor plan data displayed in your geo view to a site, a building in the site, or a floor in the building.
4 |
5 | The ArcGIS Runtime SDK currently supports filtering a 2D floor aware map based on the sites, buildings, or levels in the map.
6 |
7 |
8 |
9 | ### Behavior
10 |
11 | When the Site button is tapped, a prompt opens so the user can select a site and then a facility. After selecting a site and facility, a list of levels is displayed either above or below the site button.
12 |
13 | ### Usage
14 |
15 | ```swift
16 |
17 | addChild(floorFilterViewController)
18 | view.addSubview(floorFilterViewController.view)
19 | floorFilterViewController.didMove(toParent: self)
20 |
21 | ```
22 |
23 | To see it in action, try out the [Examples](../../Examples) and refer to [FloorFilterExample.swift](../../Examples/ArcGISToolkitExamples/FloorFilterExample.swift) in the project.
24 |
--------------------------------------------------------------------------------
/Documentation/JobManager/README.md:
--------------------------------------------------------------------------------
1 | # Job Manager
2 |
3 | Jobs by definition are long running operations and especially when using mobile devices, applications are backgrounded, terminated and re-launched based on rules specific to each platform. Our jobs supports pause and resume workflows but we don't have an easy way to implement persistence to the application lifecycles without quite a bit custom code.
4 |
5 | The Job Manager is a toolkit component that you can just plug into the application and then give it tasks you want to persist when the application is backgrounded/terminated and an easy way to rehydrate when the application is re-launched.
6 |
7 | ### Usage
8 |
9 | ```swift
10 | // create and load a task
11 | let task = AGSGeodatabaseSyncTask(url: URL)
12 | task.load { error in
13 |
14 | // make sure we are still around...
15 | guard let strongSelf = self else {
16 | return
17 | }
18 |
19 | // make sure task does not get released
20 | guard let strongTask = task else {
21 | return
22 | }
23 |
24 | // set up params
25 | let params = AGSGenerateGeodatabaseParameters()
26 | //
27 |
28 | // generate job
29 | let job = strongTask.generateJob(with: params, downloadFileURL: downloadURL)
30 |
31 | // register the job with our JobManager shared instance
32 | JobManager.shared.register(job: job)
33 |
34 | // start the job
35 | job.start(statusHandler: strongSelf.jobStatusHandler, completion: strongSelf.jobCompletionHandler)
36 |
37 | }
38 | ```
39 |
40 | To see it in action, try out the [Examples](../../Examples) and refer to [JobManagerExample.swift](../../Examples/ArcGISToolkitExamples/JobManagerExample.swift) in the project.
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/Documentation/LegendViewController/README.md:
--------------------------------------------------------------------------------
1 | # Legend View Controller
2 |
3 | A Legend View Controller displays a legend for a set of layers in a Map or Scene contained in a MapView or SceneView. A legend conveys the meaning of the symbols used to represent features in the layer. For each layer, the legend contains a patch displaying the symbology used along with some explanatory text. The Legend View Controller is dynamic and only contains information about visible layers. As layers go in and out of scale range, or are turned on/off, the legend updates to include only those layers that are visible to the user in the MapView or SceneView.
4 |
5 | ### Usage
6 |
7 | ```swift
8 | let legendVC = LegendViewController.makeLegendViewController(geoView: mapView)
9 | self.present(legendVC, animated:true, completion:nil)
10 | ```
11 |
12 | To see it in action, try out the [Examples](../../Examples) and refer to [LegendExample.swift](../../Examples/ArcGISToolkitExamples/LegendExample.swift) in the project.
13 |
--------------------------------------------------------------------------------
/Documentation/MeasureToolbar/README.md:
--------------------------------------------------------------------------------
1 | # MeasureToolbar
2 |
3 | The MeasureToolbar provides an all-in-one toolbar that you can add to your application to give your user full measure capabilities. The user can sketch on the associated map view and get immediate feedback with geodetic measurements for length or area.
4 |
5 | ### Usage
6 |
7 | ```swift
8 | let measureToolbar = MeasureToolbar(mapView: mapView)
9 | view.addSubview(measureToolbar)
10 | ```
11 |
12 | To see it in action, try out the [Examples](../../Examples) and refer to [MeasureExample.swift](../../Examples/ArcGISToolkitExamples/MeasureExample.swift) in the project.
13 |
--------------------------------------------------------------------------------
/Documentation/PopupController/README.md:
--------------------------------------------------------------------------------
1 | # PopupController
2 |
3 | The PopupController makes it easy to wire up an `AGSPopupsViewController` for a complete feature editing and collecting experience. To use it, you instantiate the `PopupController` and hold on to it. You must pass it a reference to your `UIViewController` and to your `AGSGeoView`.
4 |
5 | ### Usage
6 |
7 | ```swift
8 | var popupController: PopupController?
9 |
10 | override func viewDidLoad() {
11 | super.viewDidLoad()
12 |
13 | // Create a map
14 | let portal = AGSPortal.arcGISOnline(withLoginRequired: false)
15 | let portalItem = AGSPortalItem(portal: portal, itemID: "<>")!
16 | map = AGSMap(item: portalItem)
17 |
18 | // set the map on the mapview
19 | mapView.map = map
20 |
21 | // instantiate the popup controller
22 | popupController = PopupController(geoViewController: self, geoView: mapView)
23 | }
24 | ```
25 |
26 | Note: Make sure to add `Privacy - Photo Library Usage Description`, `Privacy - Microphone Usage Description`, and `Privacy - Camera Usage Description` to your project's `Info.plist` to correctly add attachments.
27 |
28 | To see it in action, try out the [Examples](../../Examples) and refer to [PopupExample.swift](../../Examples/ArcGISToolkitExamples/PopupExample.swift) in the project.
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/Documentation/README.md:
--------------------------------------------------------------------------------
1 | ### Table of Contents
2 |
3 | * [Augmented reality (AR)](AR) - Integrates the scene view with ARKit to enable augmented reality (AR).
4 | * [Bookmarks](Bookmarks) - Shows bookmarks, from a map, scene, or a list.
5 | * [Compass](Compass) - Shows a compass direction when the map is rotated. Auto-hides when the map points north up.
6 | * [FloorFilter](FloorFilter) - Allows to filter floor plan data in a geo view by a site, a building in the site, or a floor in the building.
7 | * [JobManager](JobManager) - Suspends and resumes ArcGIS Runtime tasks when the app is background, terminated, and relaunched.
8 | * [LegendViewController](LegendViewController) - Displays a legend for all the layers in a map or scene contained in an `AGSGeoView`.
9 | * [MeasureToolbar](MeasureToolbar) - Allows measurement of distances and areas on the map view.
10 | * [PopupController](PopupController) - Display details and media, edit attributes, geometry and related records, and manage the attachments of features and graphics (popups are defined in the popup property of features and graphics).
11 | * [Scalebar](Scalebar) - Displays current scale reference.
12 | * [TemplatePickerViewController](TemplatePicker) - Allows a user to choose a template from a list of `AGSFeatureTemplate` when creating new features.
13 | * [TimeSlider](TimeSlider) - Allows interactively defining a temporal range (i.e. time extent) and animating time moving forward or backward. Can be used to manipulate the time extent in a MapView or SceneView.
14 |
--------------------------------------------------------------------------------
/Documentation/Scalebar/Images/alternating-bar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Esri/arcgis-runtime-toolkit-ios/3919a18208679aa47deec2f2e9218836c4bb9933/Documentation/Scalebar/Images/alternating-bar.png
--------------------------------------------------------------------------------
/Documentation/Scalebar/Images/bar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Esri/arcgis-runtime-toolkit-ios/3919a18208679aa47deec2f2e9218836c4bb9933/Documentation/Scalebar/Images/bar.png
--------------------------------------------------------------------------------
/Documentation/Scalebar/Images/dual-unit-line.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Esri/arcgis-runtime-toolkit-ios/3919a18208679aa47deec2f2e9218836c4bb9933/Documentation/Scalebar/Images/dual-unit-line.png
--------------------------------------------------------------------------------
/Documentation/Scalebar/Images/graduated-line.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Esri/arcgis-runtime-toolkit-ios/3919a18208679aa47deec2f2e9218836c4bb9933/Documentation/Scalebar/Images/graduated-line.png
--------------------------------------------------------------------------------
/Documentation/Scalebar/Images/line.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Esri/arcgis-runtime-toolkit-ios/3919a18208679aa47deec2f2e9218836c4bb9933/Documentation/Scalebar/Images/line.png
--------------------------------------------------------------------------------
/Documentation/Scalebar/README.md:
--------------------------------------------------------------------------------
1 | # Scalebar
2 |
3 | A scalebar displays the representation of an accurate linear measurement on the map. It provides a visual indication through which users can determine the size of features or the distance between features on a map. A scale bar is a line or bar divided into parts. It is labeled with its ground length, usually in multiples of map units, such as tens of kilometers or hundreds of miles.
4 |
5 | The scalebar uses geodetic calculations to provide accurate measurements for maps of any spatial reference. The measurement is accurate for the center of the map extent being displayed. This means at smaller scales (zoomed way out) you might find it somewhat inaccurate at the extremes of the visible extent. As the map is panned and zoomed, the scalebar automatically grows and shrinks and updates its measurement based on the new map extent.
6 |
7 | ### Usage
8 |
9 | ```swift
10 | let scalebar = Scalebar(mapView: mapView)
11 | scalebar.style = .alternatingBar
12 | scalebar.units = .metric
13 | scalebar.alignment = .left
14 | view.addSubview(scalebar)
15 | ```
16 |
17 | To see it in action, try out the [Examples](../../Examples) and refer to [ScalebarExample.swift](../../Examples/ArcGISToolkitExamples/ScalebarExample.swift) in the project.
18 |
19 |
20 | ### Styles
21 |
22 | | Style | Example |
23 | |------------- |-------- |
24 | |`line` | |
25 | |`graduated line` | |
26 | |`bar` | |
27 | |`alternatingBar` | |
28 | |`dualUnitLine` | |
29 |
30 |
31 |
32 | ### Units
33 |
34 | Two options are available - `metric` and `imperial`. Defaults to the option most appropriate for the device locale. `metric` displays distances in meters and kilometers depending on the map scale, and `imperial` displays distances in feet and miles.
35 |
36 | ### Alignment
37 |
38 | As you pan & zoom the map, the scalebar automatically grows and shrinks to update its measurements for the new map extent. The `alignment` property indicates how the scalebar has been placed on the UI and which section of the scalebar should remain fixed. For example, if you place it in the lower-left corner of the UI, set the alignment to `left` which means that it will grow/shrink on the right side.
39 |
40 | ### Customization
41 |
42 | You can customize many visual elements of the scalebar such as -
43 |
44 | * `fillColor`
45 | * `alternateFillColor`
46 | * `lineColor`
47 | * `shadowColor`
48 | * `textColor`
49 | * `textShadowColor`
50 | * `font`
51 |
52 |
--------------------------------------------------------------------------------
/Documentation/TemplatePicker/README.md:
--------------------------------------------------------------------------------
1 | # TemplatePickerViewController
2 |
3 | The `TemplatePickerViewController` is a `UIViewController` subclass that allows the user to choose from a list of `AGSFeatureTemplates`. This is a common use case for creating a new feature.
4 |
5 | ### Usage
6 |
7 | ```swift
8 |
9 | // Instantiate the TemplatePickerViewController
10 | let templatePicker = TemplatePickerViewController(map: map)
11 |
12 | // Assign the delegate
13 | templatePicker.delegate = self
14 |
15 | // Present the template picker
16 | navigationController?.pushViewController(templatePicker, animated: true)
17 | ```
18 |
19 | To see it in action, try out the [Examples](../../Examples) and refer to [TemplatePickerExample.swift](../../Examples/ArcGISToolkitExamples/TemplatePickerExample.swift) in the project.
20 |
--------------------------------------------------------------------------------
/Documentation/TimeSlider/Images/black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Esri/arcgis-runtime-toolkit-ios/3919a18208679aa47deec2f2e9218836c4bb9933/Documentation/TimeSlider/Images/black.png
--------------------------------------------------------------------------------
/Documentation/TimeSlider/Images/blue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Esri/arcgis-runtime-toolkit-ios/3919a18208679aa47deec2f2e9218836c4bb9933/Documentation/TimeSlider/Images/blue.png
--------------------------------------------------------------------------------
/Documentation/TimeSlider/Images/end-time-pinned.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Esri/arcgis-runtime-toolkit-ios/3919a18208679aa47deec2f2e9218836c4bb9933/Documentation/TimeSlider/Images/end-time-pinned.png
--------------------------------------------------------------------------------
/Documentation/TimeSlider/Images/labelmode-thumbs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Esri/arcgis-runtime-toolkit-ios/3919a18208679aa47deec2f2e9218836c4bb9933/Documentation/TimeSlider/Images/labelmode-thumbs.png
--------------------------------------------------------------------------------
/Documentation/TimeSlider/Images/labelmode-ticks.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Esri/arcgis-runtime-toolkit-ios/3919a18208679aa47deec2f2e9218836c4bb9933/Documentation/TimeSlider/Images/labelmode-ticks.png
--------------------------------------------------------------------------------
/Documentation/TimeSlider/Images/ocean-blue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Esri/arcgis-runtime-toolkit-ios/3919a18208679aa47deec2f2e9218836c4bb9933/Documentation/TimeSlider/Images/ocean-blue.png
--------------------------------------------------------------------------------
/Documentation/TimeSlider/Images/only-playback-buttons-visible.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Esri/arcgis-runtime-toolkit-ios/3919a18208679aa47deec2f2e9218836c4bb9933/Documentation/TimeSlider/Images/only-playback-buttons-visible.png
--------------------------------------------------------------------------------
/Documentation/TimeSlider/Images/only-slider-visible.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Esri/arcgis-runtime-toolkit-ios/3919a18208679aa47deec2f2e9218836c4bb9933/Documentation/TimeSlider/Images/only-slider-visible.png
--------------------------------------------------------------------------------
/Documentation/TimeSlider/Images/start-time-pinned.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Esri/arcgis-runtime-toolkit-ios/3919a18208679aa47deec2f2e9218836c4bb9933/Documentation/TimeSlider/Images/start-time-pinned.png
--------------------------------------------------------------------------------
/Documentation/TimeSlider/Images/time-instant.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Esri/arcgis-runtime-toolkit-ios/3919a18208679aa47deec2f2e9218836c4bb9933/Documentation/TimeSlider/Images/time-instant.png
--------------------------------------------------------------------------------
/Documentation/TimeSlider/Images/time-window.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Esri/arcgis-runtime-toolkit-ios/3919a18208679aa47deec2f2e9218836c4bb9933/Documentation/TimeSlider/Images/time-window.png
--------------------------------------------------------------------------------
/Documentation/TimeSlider/Images/timeslider.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Esri/arcgis-runtime-toolkit-ios/3919a18208679aa47deec2f2e9218836c4bb9933/Documentation/TimeSlider/Images/timeslider.png
--------------------------------------------------------------------------------
/Documentation/TimeSlider/README.md:
--------------------------------------------------------------------------------
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 | 
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 | 
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 | 
25 |
26 | 
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 | 
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 | 
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 | 
45 |
46 | #### Display Components
47 |
48 | User can control whether to show `Slider` and/or `Playback Buttons` using `isSliderVisible` and `playbackButtonsVisible`.
49 |
50 | 
51 |
52 | 
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 | timeSlider.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: margin).isActive = true
71 | timeSlider.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -margin).isActive = true
72 | ```
73 |
74 | Use one of three initialize helper function to setup properties of the Time Slider.
75 |
76 | ```swift
77 | public func initializeTimeProperties(geoView: AGSGeoView, observeGeoView: Bool, completion: @escaping (Error?)->Void)
78 | public func initializeTimeProperties(timeAwareLayer: AGSTimeAware, completion: @escaping (Error?)->Void)
79 | public func initializeTimeSteps(timeStepCount: Int, fullExtent: AGSTimeExtent, completion: @escaping (Error?)->Void)
80 | ```
81 |
82 | To see it in action, try out the [Examples](../../Examples) and refer to [TimeSliderExample.swift](../../Examples/ArcGISToolkitExamples/TimeSliderExample.swift) in the project.
83 |
84 | ### Themes
85 |
86 | | Theme | Example |
87 | |:-----------: |:------: |
88 | |`Black` | |
89 | |`Blue` | |
90 | |`Ocean Blue` | |
91 |
92 | ### Customization
93 |
94 | You can customize many visual elements of the TimeSlider such as -
95 |
96 | * `currentExtentFillColor`
97 | * `currentExtentLabelColor`
98 | * `currentExtentLabelFont`
99 | * `currentExtentLabelDateStyle`
100 | * `fullExtentBorderColor`
101 | * `fullExtentBorderWidth`
102 | * `fullExtentFillColor`
103 | * `fullExtentLabelColor`
104 | * `fullExtentLabelFont`
105 | * `fullExtentLabelDateStyle`
106 | * `timeStepIntervalLabelDateStyle`
107 | * `timeStepIntervalLabelColor`
108 | * `timeStepIntervalLabelFont`
109 | * `timeStepIntervalTickColor`
110 | * `thumbFillColor`
111 | * `thumbBorderColor`
112 | * `thumbBorderWidth`
113 | * `thumbSize`
114 | * `thumbCornerRadius`
115 | * `playbackButtonsFillColor`
116 | * `layerExtentFillColor`
117 | * `trackHeight`
118 | * `theme`
119 |
--------------------------------------------------------------------------------
/Examples/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | ../.swiftlint.yml
--------------------------------------------------------------------------------
/Examples/ArcGISToolkitExamples.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Examples/ArcGISToolkitExamples.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Examples/ArcGISToolkitExamples/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Esri.
2 |
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | // http://www.apache.org/licenses/LICENSE-2.0
7 |
8 | // Unless required by applicable law or agreed to in writing, software
9 | // distributed under the License is distributed on an "AS IS" BASIS,
10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | // See the License for the specific language governing permissions and
12 | // limitations under the License.
13 |
14 | import UIKit
15 | import ArcGIS
16 | import ArcGISToolkit
17 |
18 | @UIApplicationMain
19 | class AppDelegate: UIResponder, UIApplicationDelegate {
20 | var window: UIWindow?
21 |
22 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
23 | // Override point for customization after application launch.
24 |
25 | #warning("Require user to sign in with an ArcGIS identity or set your developer API key")
26 | /*
27 | Use of Esri location services, including basemaps and geocoding, requires either an ArcGIS identity or an API Key. For more information see https://links.esri.com/arcgis-runtime-security-auth.
28 | 1) ArcGIS identity: An ArcGIS named user account that is a member of an organization in ArcGIS Online or ArcGIS Enterprise.
29 | 2) API key: A permanent key that gives your application access to Esri location services. Create a new API key or access existing API keys from your ArcGIS for Developers dashboard (https://links.esri.com/arcgis-api-keys).
30 | Production deployment of applications built with ArcGIS Runtime requires you to license ArcGIS Runtime functionality. For more information see https://links.esri.com/arcgis-runtime-license-and-deploy.
31 | */
32 | // Uncomment the following line to access Esri location services using an API key.
33 | // AGSArcGISRuntimeEnvironment.apiKey = "<#API Key#>"
34 | return true
35 | }
36 |
37 | func applicationWillResignActive(_ application: UIApplication) {
38 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
39 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
40 | }
41 |
42 | func applicationDidEnterBackground(_ application: UIApplication) {
43 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
44 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
45 | }
46 |
47 | func applicationWillEnterForeground(_ application: UIApplication) {
48 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
49 | }
50 |
51 | func applicationDidBecomeActive(_ application: UIApplication) {
52 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
53 | }
54 |
55 | func applicationWillTerminate(_ application: UIApplication) {
56 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
57 | }
58 |
59 | /// We need to forward these calls to ArcGIS so that we can be a good citizen and allow ArcGIS to call the completionHandler
60 | /// once the associated download session is finished handling events.
61 | func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
62 | AGSApplicationDelegate.shared().application(
63 | application,
64 | handleEventsForBackgroundURLSession: identifier,
65 | completionHandler: completionHandler
66 | )
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/Examples/ArcGISToolkitExamples/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "20x20",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "20x20",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "29x29",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "29x29",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "40x40",
66 | "scale" : "1x"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "size" : "40x40",
71 | "scale" : "2x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "76x76",
76 | "scale" : "1x"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "size" : "76x76",
81 | "scale" : "2x"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "size" : "83.5x83.5",
86 | "scale" : "2x"
87 | }
88 | ],
89 | "info" : {
90 | "version" : 1,
91 | "author" : "xcode"
92 | }
93 | }
--------------------------------------------------------------------------------
/Examples/ArcGISToolkitExamples/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Examples/ArcGISToolkitExamples/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/Examples/ArcGISToolkitExamples/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/Examples/ArcGISToolkitExamples/BookmarksExample.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2019 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 BookmarksExample: MapViewController, BookmarksViewControllerDelegate {
20 | var bookmarksVC: BookmarksViewController?
21 | var bookmarksButton = UIBarButtonItem()
22 |
23 | override func viewDidLoad() {
24 | super.viewDidLoad()
25 |
26 | // Add Bookmark button that will display the BookmarksTableViewController.
27 | bookmarksButton = UIBarButtonItem(barButtonSystemItem: .bookmarks, target: self, action: #selector(showBookmarks))
28 | navigationItem.rightBarButtonItem = bookmarksButton
29 |
30 | // Create the map from a portal item and assign to the mapView.
31 | let portal = AGSPortal.arcGISOnline(withLoginRequired: false)
32 | let portalItem = AGSPortalItem(portal: portal, itemID: "16f1b8ba37b44dc3884afc8d5f454dd2")
33 | mapView.map = AGSMap(item: portalItem)
34 |
35 | // Create the BookmarksTableViewController.
36 | bookmarksVC = BookmarksViewController(geoView: mapView)
37 |
38 | // Add a cancel button.
39 | let cancelButton = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancel))
40 | bookmarksVC?.navigationItem.rightBarButtonItem = cancelButton
41 | bookmarksVC?.delegate = self
42 | }
43 |
44 | @objc
45 | func showBookmarks() {
46 | if let bookmarksVC = bookmarksVC {
47 | // Display the bookmarksVC as a popover controller.
48 | bookmarksVC.modalPresentationStyle = .popover
49 | if let popoverPresentationController = bookmarksVC.popoverPresentationController {
50 | popoverPresentationController.delegate = self
51 | popoverPresentationController.barButtonItem = bookmarksButton
52 | }
53 | present(bookmarksVC, animated: true)
54 | }
55 | }
56 |
57 | @objc
58 | func cancel() {
59 | dismiss(animated: true)
60 | }
61 |
62 | func bookmarksViewController(_ controller: BookmarksViewController, didSelect bookmark: AGSBookmark) {
63 | if let viewpoint = bookmark.viewpoint {
64 | mapView.setViewpoint(viewpoint, duration: 2.0)
65 | dismiss(animated: true)
66 | }
67 | }
68 | }
69 |
70 | extension BookmarksExample: UIPopoverPresentationControllerDelegate {
71 | func presentationController(_ controller: UIPresentationController, viewControllerForAdaptivePresentationStyle style: UIModalPresentationStyle) -> UIViewController? {
72 | return UINavigationController(rootViewController: controller.presentedViewController)
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/Examples/ArcGISToolkitExamples/CompassExample.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Esri.
2 |
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | // http://www.apache.org/licenses/LICENSE-2.0
7 |
8 | // Unless required by applicable law or agreed to in writing, software
9 | // distributed under the License is distributed on an "AS IS" BASIS,
10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | // See the License for the specific language governing permissions and
12 | // limitations under the License.
13 |
14 | import UIKit
15 | import ArcGISToolkit
16 | import ArcGIS
17 |
18 | class CompassExample: MapViewController {
19 | var map: AGSMap?
20 |
21 | override func viewDidLoad() {
22 | super.viewDidLoad()
23 |
24 | // Initialize an AGSMap with the Modern Antique basemap and center it on South America.
25 | map = AGSMap(basemapStyle: .arcGISModernAntique)
26 | map?.initialViewpoint = AGSViewpoint(latitude: -25, longitude: -56, scale: 6e7)
27 | mapView.map = map
28 |
29 | // Create the compass and add it to our view.
30 | let compass = Compass(mapView: mapView)
31 | compass.translatesAutoresizingMaskIntoConstraints = false
32 | self.view.addSubview(compass)
33 |
34 | // Get the superview's layout.
35 | let margins = view.layoutMarginsGuide
36 |
37 | // Position the compass in the top right corner using auto layout constraints.
38 | // The Compass handles width/height constraints, so we don't need to do that here.
39 | compass.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 12.0).isActive = true
40 | compass.trailingAnchor.constraint(equalTo: margins.trailingAnchor).isActive = true
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Examples/ArcGISToolkitExamples/ExamplesViewController.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Esri.
2 |
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | // http://www.apache.org/licenses/LICENSE-2.0
7 |
8 | // Unless required by applicable law or agreed to in writing, software
9 | // distributed under the License is distributed on an "AS IS" BASIS,
10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | // See the License for the specific language governing permissions and
12 | // limitations under the License.
13 |
14 | import UIKit
15 | import ArcGISToolkit
16 |
17 | class ExamplesViewController: VCListViewController {
18 | override func viewDidLoad() {
19 | super.viewDidLoad()
20 |
21 | self.title = "Toolkit Samples"
22 |
23 | self.viewControllerInfos = [
24 | ("Compass", CompassExample.self, nil),
25 | ("Measure", MeasureExample.self, nil),
26 | ("Scalebar", ScalebarExample.self, nil),
27 | ("Legend", LegendExample.self, nil),
28 | ("Job Manager", JobManagerExample.self, nil),
29 | ("Time Slider", TimeSliderExample.self, nil),
30 | ("Popup Controller", PopupExample.self, nil),
31 | ("Template Picker", TemplatePickerExample.self, nil),
32 | ("AR", ARExample.self, nil),
33 | ("Bookmarks", BookmarksExample.self, nil),
34 | ("Floor Filter", FloorFilterExample.self, nil)
35 | ]
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Examples/ArcGISToolkitExamples/FloorFilterExample.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 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 Foundation
16 | import UIKit
17 | import ArcGIS
18 | import ArcGISToolkit
19 |
20 | class FloorFilterExample: MapViewController {
21 | var floorFilterVC: FloorFilterViewController?
22 |
23 | override func viewDidLoad() {
24 | super.viewDidLoad()
25 |
26 | // Create the map from a portal item and assign to the mapView.
27 | let portal = AGSPortal(url: URL(string: "https://indoors.maps.arcgis.com/")!, loginRequired: false)
28 | let portalItem = AGSPortalItem(portal: portal, itemID: "f133a698536f44c8884ad81f80b6cfc7")
29 | let map = AGSMap(item: portalItem)
30 | mapView.map = map
31 |
32 | // The expansion direction of the floor filter determines which way it will open.
33 | // If the floor filter is placed on the top of the screen, it is recommended to use the style 'down'.
34 | // If floor filter placed on the bottom of the screen, then use 'up'.
35 | let floorFilterVC = FloorFilterViewController.makeFloorFilterView(
36 | geoView: mapView,
37 | expansionDirection: .up
38 | )
39 | self.floorFilterVC = floorFilterVC
40 | floorFilterVC.onSelectedLevelChangedListener = {
41 | print("Level was changed.")
42 | }
43 |
44 | // Add floor filter to the current view.
45 | floorFilterVC.view.translatesAutoresizingMaskIntoConstraints = false
46 | addChild(floorFilterVC)
47 | view.addSubview(floorFilterVC.view)
48 | floorFilterVC.didMove(toParent: self)
49 |
50 | let leadingConstraint = CGFloat(40)
51 | floorFilterVC.view.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: leadingConstraint).isActive = true
52 |
53 | // This places the floor filter at the bottom of the view,
54 | // anchored to the top of the map view's attribution bar.
55 | floorFilterVC.view.bottomAnchor.constraint(equalTo: mapView.attributionTopAnchor, constant: -leadingConstraint).isActive = true
56 |
57 | // This places the floor filter at the top of the view,
58 | // just below the top anchor of the safe area layout guide.
59 | // Set the constraint to true and set the bottom constraint to false
60 | floorFilterVC.view.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: leadingConstraint).isActive = false
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/Examples/ArcGISToolkitExamples/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | $(MARKETING_VERSION)
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | NSAppTransportSecurity
24 |
25 | NSAllowsArbitraryLoads
26 |
27 |
28 | NSCameraUsageDescription
29 | For collecting attachments in popups
30 | NSLocationWhenInUseUsageDescription
31 | For showing the current location in a map
32 | NSMicrophoneUsageDescription
33 | For collecting attachments in popups
34 | NSPhotoLibraryUsageDescription
35 | For collecting attachments in popups
36 | UIBackgroundModes
37 |
38 | UILaunchStoryboardName
39 | LaunchScreen
40 | UIMainStoryboardFile
41 | Main
42 | UIRequiredDeviceCapabilities
43 |
44 | armv7
45 |
46 | UISupportedInterfaceOrientations
47 |
48 | UIInterfaceOrientationPortrait
49 | UIInterfaceOrientationLandscapeLeft
50 | UIInterfaceOrientationLandscapeRight
51 |
52 | UISupportedInterfaceOrientations~ipad
53 |
54 | UIInterfaceOrientationPortrait
55 | UIInterfaceOrientationPortraitUpsideDown
56 | UIInterfaceOrientationLandscapeLeft
57 | UIInterfaceOrientationLandscapeRight
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/Examples/ArcGISToolkitExamples/LegendExample.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Esri.
2 |
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | // http://www.apache.org/licenses/LICENSE-2.0
7 |
8 | // Unless required by applicable law or agreed to in writing, software
9 | // distributed under the License is distributed on an "AS IS" BASIS,
10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | // See the License for the specific language governing permissions and
12 | // limitations under the License.
13 |
14 | import UIKit
15 | import ArcGISToolkit
16 | import ArcGIS
17 |
18 | class LegendExample: MapViewController {
19 | let portal = AGSPortal.arcGISOnline(withLoginRequired: false)
20 | var portalItem: AGSPortalItem?
21 | var map: AGSMap?
22 | var legendVC: LegendViewController?
23 |
24 | override func viewDidLoad() {
25 | super.viewDidLoad()
26 |
27 | // create and set map on mapView
28 | portalItem = AGSPortalItem(portal: portal, itemID: "1966ef409a344d089b001df85332608f")
29 | map = AGSMap(item: portalItem!)
30 | mapView.map = map
31 |
32 | legendVC = LegendViewController.makeLegendViewController(geoView: mapView)
33 |
34 | // add button that will show the SwitchBasemapViewController
35 | let bbi = UIBarButtonItem(title: "Legend", style: .plain, target: self, action: #selector(showLegendAction))
36 | navigationItem.rightBarButtonItem = bbi
37 | }
38 |
39 | @objc
40 | func showLegendAction() {
41 | if let legendVC = legendVC {
42 | navigationController?.pushViewController(legendVC, animated: true)
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Examples/ArcGISToolkitExamples/MeasureExample.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Esri.
2 |
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | // http://www.apache.org/licenses/LICENSE-2.0
7 |
8 | // Unless required by applicable law or agreed to in writing, software
9 | // distributed under the License is distributed on an "AS IS" BASIS,
10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | // See the License for the specific language governing permissions and
12 | // limitations under the License.
13 |
14 | import UIKit
15 | import ArcGISToolkit
16 | import ArcGIS
17 |
18 | class MeasureExample: MapViewController {
19 | var measureToolbar: MeasureToolbar!
20 |
21 | override func viewDidLoad() {
22 | super.viewDidLoad()
23 |
24 | // set a map on our mapView
25 | let portalItem = AGSPortalItem(portal: AGSPortal.arcGISOnline(withLoginRequired: false), itemID: "32eb38c48f91421a96e64fe1af492030")
26 | let map = AGSMap(item: portalItem)
27 | mapView.map = map
28 |
29 | // create a MeasureToolbar and add it to the view controller
30 | measureToolbar = MeasureToolbar(mapView: mapView)
31 | measureToolbar.translatesAutoresizingMaskIntoConstraints = false
32 | view.addSubview(measureToolbar)
33 | measureToolbar.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
34 | measureToolbar.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
35 | measureToolbar.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
36 | measureToolbar.heightAnchor.constraint(equalToConstant: 44).isActive = true
37 | }
38 |
39 | override func viewDidLayoutSubviews() {
40 | super.viewDidLayoutSubviews()
41 |
42 | // update content inset for mapview
43 | mapView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: measureToolbar.frame.height, right: 0)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Examples/ArcGISToolkitExamples/Misc/ARStatusViewController.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Esri.
2 |
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | // http://www.apache.org/licenses/LICENSE-2.0
7 |
8 | // Unless required by applicable law or agreed to in writing, software
9 | // distributed under the License is distributed on an "AS IS" BASIS,
10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | // See the License for the specific language governing permissions and
12 | // limitations under the License.
13 |
14 | import UIKit
15 | import ARKit
16 | import ArcGIS
17 |
18 | extension ARCamera.TrackingState {
19 | var description: String {
20 | switch self {
21 | case .normal:
22 | return "Normal"
23 | case .notAvailable:
24 | return "Tracking unavailable"
25 | case .limited(.excessiveMotion):
26 | return "Limited - Excessive Motion"
27 | case .limited(.insufficientFeatures):
28 | return "Limited - Insufficient Features"
29 | case .limited(.initializing):
30 | return "Limited - Initializing"
31 | default:
32 | return ""
33 | }
34 | }
35 | }
36 |
37 | extension AGSLocationDataSourceStatus {
38 | var description: String {
39 | switch self {
40 | case .stopped:
41 | return "Stopped"
42 | case .starting:
43 | return "Starting"
44 | case .started:
45 | return "Started"
46 | case .failedToStart:
47 | return "Failed to start"
48 | case .stopping:
49 | return "Stopping"
50 | @unknown default:
51 | fatalError("Unknown AGSLocationDataSourceStatus")
52 | }
53 | }
54 | }
55 |
56 | /// A view controller for display AR-related status information.
57 | class ARStatusViewController: UITableViewController {
58 | @IBOutlet var trackingStateLabel: UILabel!
59 | @IBOutlet var frameRateLabel: UILabel!
60 | @IBOutlet var errorDescriptionLabel: UILabel!
61 | @IBOutlet var sceneLabel: UILabel!
62 | @IBOutlet var translationFactorLabel: UILabel!
63 | @IBOutlet var horizontalAccuracyLabel: UILabel!
64 | @IBOutlet var verticalAccuracyLabel: UILabel!
65 | @IBOutlet var locationDataSourceStatusLabel: UILabel!
66 |
67 | /// The `ARKit` camera tracking state.
68 | var trackingState: ARCamera.TrackingState = .notAvailable {
69 | didSet {
70 | DispatchQueue.main.async { [weak self] in
71 | guard let self = self else { return }
72 | self.trackingStateLabel?.text = self.trackingState.description
73 | }
74 | }
75 | }
76 |
77 | /// The calculated frame rate of the `SceneView` and `ARKit` display.
78 | var frameRate: Int = 0 {
79 | didSet {
80 | DispatchQueue.main.async { [weak self] in
81 | guard let self = self else { return }
82 | self.frameRateLabel?.text = "\(self.frameRate) fps"
83 | }
84 | }
85 | }
86 |
87 | /// The current error message.
88 | var errorMessage: String? {
89 | didSet {
90 | DispatchQueue.main.async { [weak self] in
91 | guard let self = self else { return }
92 | self.errorDescriptionLabel?.text = self.errorMessage
93 | }
94 | }
95 | }
96 |
97 | /// The label for the currently selected scene.
98 | var currentScene: String = "None" {
99 | didSet {
100 | DispatchQueue.main.async { [weak self] in
101 | guard let self = self else { return }
102 | self.sceneLabel?.text = self.currentScene
103 | }
104 | }
105 | }
106 |
107 | /// The translation factor applied to the current scene.
108 | var translationFactor: Double = 1.0 {
109 | didSet {
110 | DispatchQueue.main.async { [weak self] in
111 | guard let self = self else { return }
112 | self.translationFactorLabel?.text = String(format: "%.1f", self.translationFactor)
113 | }
114 | }
115 | }
116 |
117 | /// The horizontal accuracy of the last location.
118 | var horizontalAccuracyMeasurement = Measurement(value: 1, unit: UnitLength.meters) {
119 | didSet {
120 | DispatchQueue.main.async { [weak self] in
121 | guard let self = self else { return }
122 | self.horizontalAccuracyLabel?.text = self.measurementFormatter.string(from: self.horizontalAccuracyMeasurement)
123 | }
124 | }
125 | }
126 |
127 | /// The vertical accuracy of the last location.
128 | var verticalAccuracyMeasurement = Measurement(value: 1, unit: UnitLength.meters) {
129 | didSet {
130 | DispatchQueue.main.async { [weak self] in
131 | guard let self = self else { return }
132 | self.verticalAccuracyLabel?.text = self.measurementFormatter.string(from: self.verticalAccuracyMeasurement)
133 | }
134 | }
135 | }
136 |
137 | /// The status of the location data source.
138 | var locationDataSourceStatus: AGSLocationDataSourceStatus = .stopped {
139 | didSet {
140 | DispatchQueue.main.async { [weak self] in
141 | guard let self = self else { return }
142 | self.locationDataSourceStatusLabel?.text = self.locationDataSourceStatus.description
143 | }
144 | }
145 | }
146 |
147 | private let measurementFormatter: MeasurementFormatter = {
148 | let formatter = MeasurementFormatter()
149 | formatter.unitOptions = [.naturalScale, .providedUnit]
150 | return formatter
151 | }()
152 |
153 | override func viewDidLoad() {
154 | super.viewDidLoad()
155 |
156 | // Add a blur effect behind the table view.
157 | tableView.backgroundView = UIVisualEffectView(effect: UIBlurEffect(style: .regular))
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/Examples/ArcGISToolkitExamples/Misc/CalibrationView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2019 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 ArcGIS
17 | import ArcGISToolkit
18 |
19 | /// A view displaying controls for adjusting a scene view's location, heading, and elevation. Used to calibrate an AR session.
20 | class CalibrationView: UIView {
21 | /// Denotes whether to show the elevation control and label; defaults to `true`.
22 | var elevationControlVisibility = true {
23 | didSet {
24 | elevationSlider.isHidden = !elevationControlVisibility
25 | elevationLabel.isHidden = !elevationControlVisibility
26 | }
27 | }
28 |
29 | /// The `ArcGISARView` containing the origin camera we will be updating.
30 | private var arcgisARView: ArcGISARView!
31 |
32 | /// The label displaying calibration directions.
33 | private let calibrationDirectionsLabel: UILabel = {
34 | let label = UILabel(frame: .zero)
35 | label.textAlignment = .center
36 | label.font = UIFont.systemFont(ofSize: 24.0)
37 | label.textColor = .darkText
38 | label.numberOfLines = 0
39 | label.text = "Calibrating..."
40 | return label
41 | }()
42 |
43 | /// The UISlider used to adjust elevation.
44 | private let elevationSlider: UISlider = {
45 | let slider = UISlider(frame: .zero)
46 | slider.minimumValue = -50.0
47 | slider.maximumValue = 50.0
48 | return slider
49 | }()
50 |
51 | /// The UISlider used to adjust heading.
52 | private let headingSlider: UISlider = {
53 | let slider = UISlider(frame: .zero)
54 | slider.minimumValue = -10.0
55 | slider.maximumValue = 10.0
56 | return slider
57 | }()
58 |
59 | /// The elevation label..
60 | private let elevationLabel = UILabel(frame: .zero)
61 |
62 | /// Initialized a new calibration view with the `ArcGISARView`.
63 | ///
64 | /// - Parameters:
65 | /// - arcgisARView: The `ArcGISARView` containing the originCamera we're updating.
66 | init(_ arcgisARView: ArcGISARView) {
67 | self.arcgisARView = arcgisARView
68 |
69 | super.init(frame: .zero)
70 |
71 | // Create visual effects view to show the label on a blurred background.
72 | let labelView = UIVisualEffectView(effect: UIBlurEffect(style: .light))
73 | labelView.layer.cornerRadius = 8.0
74 | labelView.layer.masksToBounds = true
75 |
76 | // Add the label to our label view and set up constraints.
77 | labelView.contentView.addSubview(calibrationDirectionsLabel)
78 | calibrationDirectionsLabel.translatesAutoresizingMaskIntoConstraints = false
79 | NSLayoutConstraint.activate([
80 | calibrationDirectionsLabel.leadingAnchor.constraint(equalTo: labelView.leadingAnchor, constant: 8),
81 | calibrationDirectionsLabel.trailingAnchor.constraint(equalTo: labelView.trailingAnchor, constant: -8),
82 | calibrationDirectionsLabel.topAnchor.constraint(equalTo: labelView.topAnchor, constant: 8),
83 | calibrationDirectionsLabel.bottomAnchor.constraint(equalTo: labelView.bottomAnchor, constant: -8)
84 | ])
85 |
86 | // Add the label view to our view and set up constraints.
87 | addSubview(labelView)
88 | labelView.translatesAutoresizingMaskIntoConstraints = false
89 | NSLayoutConstraint.activate([
90 | labelView.centerXAnchor.constraint(equalTo: centerXAnchor),
91 | labelView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: 8.0)
92 | ])
93 |
94 | // Add the heading label and slider.
95 | let headingLabel = UILabel(frame: .zero)
96 | headingLabel.text = "Heading"
97 | headingLabel.textColor = .yellow
98 | addSubview(headingLabel)
99 | headingLabel.translatesAutoresizingMaskIntoConstraints = false
100 | NSLayoutConstraint.activate([
101 | headingLabel.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor, constant: 16),
102 | headingLabel.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, constant: -16)
103 | ])
104 |
105 | addSubview(headingSlider)
106 | headingSlider.translatesAutoresizingMaskIntoConstraints = false
107 | NSLayoutConstraint.activate([
108 | headingSlider.leadingAnchor.constraint(equalTo: headingLabel.trailingAnchor, constant: 16),
109 | headingSlider.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor, constant: -16),
110 | headingSlider.centerYAnchor.constraint(equalTo: headingLabel.centerYAnchor)
111 | ])
112 |
113 | // Add the elevation label and slider.
114 | elevationLabel.text = "Elevation"
115 | elevationLabel.textColor = .yellow
116 | addSubview(elevationLabel)
117 | elevationLabel.translatesAutoresizingMaskIntoConstraints = false
118 | NSLayoutConstraint.activate([
119 | elevationLabel.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor, constant: 16),
120 | elevationLabel.bottomAnchor.constraint(equalTo: headingLabel.topAnchor, constant: -24)
121 | ])
122 |
123 | addSubview(elevationSlider)
124 | elevationSlider.translatesAutoresizingMaskIntoConstraints = false
125 | NSLayoutConstraint.activate([
126 | elevationSlider.leadingAnchor.constraint(equalTo: elevationLabel.trailingAnchor, constant: 16),
127 | elevationSlider.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor, constant: -16),
128 | elevationSlider.centerYAnchor.constraint(equalTo: elevationLabel.centerYAnchor)
129 | ])
130 |
131 | // Setup actions for the two sliders. The sliders operate as "joysticks", where moving the slider thumb will start a timer
132 | // which rotates or elevates the current camera when the timer fires. The elevation and heading delta
133 | // values increase the further you move away from center. Moving and holding the thumb a little bit from center
134 | // will rotate/elevate just a little bit, but get progressively more the further from center the thumb is moved.
135 | headingSlider.addTarget(self, action: #selector(headingChanged(_:)), for: .valueChanged)
136 | headingSlider.addTarget(self, action: #selector(touchUpHeading(_:)), for: [.touchUpInside, .touchUpOutside])
137 |
138 | elevationSlider.addTarget(self, action: #selector(elevationChanged(_:)), for: .valueChanged)
139 | elevationSlider.addTarget(self, action: #selector(touchUpElevation(_:)), for: [.touchUpInside, .touchUpOutside])
140 |
141 | elevationSlider.isHidden = !elevationControlVisibility
142 | elevationLabel.isHidden = !elevationControlVisibility
143 | }
144 |
145 | required init?(coder aDecoder: NSCoder) {
146 | fatalError("init(coder:) has not been implemented")
147 | }
148 |
149 | override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
150 | // If the user tapped in the view (and not in the sliders), do not handle the event.
151 | // This allows the view below the calibration view to handle touch events. In this case,
152 | // that view is the SceneView.
153 | let hitView = super.hitTest(point, with: event)
154 | if hitView == self {
155 | return nil
156 | } else {
157 | return hitView
158 | }
159 | }
160 |
161 | // The timers for the "joystick" behavior.
162 | private var elevationTimer: Timer?
163 | private var headingTimer: Timer?
164 |
165 | /// Handle an elevation slider value-changed event.
166 | ///
167 | /// - Parameter sender: The slider tapped on.
168 | @objc
169 | func elevationChanged(_ sender: UISlider) {
170 | if elevationTimer == nil {
171 | // Create a timer which elevates the camera when fired.
172 | elevationTimer = Timer(timeInterval: 0.25, repeats: true) { [weak self] (_) in
173 | let delta = self?.joystickElevation() ?? 0.0
174 | // print("elevate delta = \(delta)")
175 | self?.elevate(delta)
176 | }
177 |
178 | // Add the timer to the main run loop.
179 | guard let timer = elevationTimer else { return }
180 | RunLoop.main.add(timer, forMode: .default)
181 | }
182 | }
183 |
184 | /// Handle an heading slider value-changed event.
185 | ///
186 | /// - Parameter sender: The slider tapped on.
187 | @objc
188 | func headingChanged(_ sender: UISlider) {
189 | if headingTimer == nil {
190 | // Create a timer which rotates the camera when fired.
191 | headingTimer = Timer(timeInterval: 0.1, repeats: true) { [weak self] (_) in
192 | let delta = self?.joystickHeading() ?? 0.0
193 | // print("rotate delta = \(delta)")
194 | self?.rotate(delta)
195 | }
196 |
197 | // Add the timer to the main run loop.
198 | guard let timer = headingTimer else { return }
199 | RunLoop.main.add(timer, forMode: .default)
200 | }
201 | }
202 |
203 | /// Handle an elevation slider touchUp event. This will stop the timer.
204 | ///
205 | /// - Parameter sender: The slider tapped on.
206 | @objc
207 | func touchUpElevation(_ sender: UISlider) {
208 | elevationTimer?.invalidate()
209 | elevationTimer = nil
210 | sender.value = 0.0
211 | }
212 |
213 | /// Handle a heading slider touchUp event. This will stop the timer.
214 | ///
215 | /// - Parameter sender: The slider tapped on.
216 | @objc
217 | func touchUpHeading(_ sender: UISlider) {
218 | headingTimer?.invalidate()
219 | headingTimer = nil
220 | sender.value = 0.0
221 | }
222 |
223 | /// Rotates the camera by `deltaHeading`.
224 | ///
225 | /// - Parameter deltaHeading: The amount to rotate the camera.
226 | private func rotate(_ deltaHeading: Double) {
227 | let camera = arcgisARView.originCamera
228 | let newHeading = camera.heading + deltaHeading
229 | arcgisARView.originCamera = camera.rotate(toHeading: newHeading, pitch: camera.pitch, roll: camera.roll)
230 | }
231 |
232 | /// Change the cameras altitude by `deltaAltitude`.
233 | ///
234 | /// - Parameter deltaAltitude: The amount to elevate the camera.
235 | private func elevate(_ deltaAltitude: Double) {
236 | arcgisARView.originCamera = arcgisARView.originCamera.elevate(withDeltaAltitude: deltaAltitude)
237 | }
238 |
239 | /// Calculates the elevation delta amount based on the elevation slider value.
240 | ///
241 | /// - Returns: The elevation delta.
242 | private func joystickElevation() -> Double {
243 | let deltaElevation = Double(elevationSlider.value)
244 | return pow(deltaElevation, 2) / 50.0 * (deltaElevation < 0 ? -1.0 : 1.0)
245 | }
246 |
247 | /// Calculates the heading delta amount based on the heading slider value.
248 | ///
249 | /// - Returns: The heading delta.
250 | private func joystickHeading() -> Double {
251 | let deltaHeading = Double(headingSlider.value)
252 | return pow(deltaHeading, 2) / 25.0 * (deltaHeading < 0 ? -1.0 : 1.0)
253 | }
254 | }
255 |
--------------------------------------------------------------------------------
/Examples/ArcGISToolkitExamples/Misc/Plane.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2019 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 ARKit
16 |
17 | /// Helper class to visualize a plane found by ARKit
18 | class Plane: SCNNode {
19 | let node: SCNNode
20 |
21 | init(anchor: ARPlaneAnchor, in sceneView: ARSCNView) {
22 | // Create a node to visualize the plane's bounding rectangle.
23 | let extent = SCNPlane(width: CGFloat(anchor.extent.x), height: CGFloat(anchor.extent.z))
24 | node = SCNNode(geometry: extent)
25 | node.simdPosition = anchor.center
26 |
27 | // `SCNPlane` is vertically oriented in its local coordinate space, so
28 | // rotate it to match the orientation of `ARPlaneAnchor`.
29 | node.eulerAngles.x = -.pi / 2
30 |
31 | super.init()
32 |
33 | node.opacity = 0.6
34 | guard let material = node.geometry?.firstMaterial
35 | else { fatalError("SCNPlane always has one material") }
36 |
37 | material.diffuse.contents = UIColor.white
38 |
39 | // Add the plane node as child node so they appear in the scene.
40 | addChildNode(node)
41 | }
42 |
43 | required init?(coder aDecoder: NSCoder) {
44 | fatalError("init(coder:) has not been implemented")
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Examples/ArcGISToolkitExamples/Misc/UserDirectionsView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2019 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 |
17 | /// A custom view for displaying directions to the user.
18 | class UserDirectionsView: UIVisualEffectView {
19 | private let userDirectionsLabel: UILabel = {
20 | let label = UILabel(frame: .zero)
21 | label.textAlignment = .center
22 | label.font = UIFont.systemFont(ofSize: 24.0)
23 | label.textColor = .darkText
24 | label.numberOfLines = 0
25 | label.text = "Initializing ARKit..."
26 | return label
27 | }()
28 |
29 | override init(effect: UIVisualEffect?) {
30 | super.init(effect: effect)
31 |
32 | // Set a corner radius.
33 | layer.cornerRadius = 8.0
34 | layer.masksToBounds = true
35 |
36 | contentView.addSubview(userDirectionsLabel)
37 | userDirectionsLabel.translatesAutoresizingMaskIntoConstraints = false
38 | NSLayoutConstraint.activate([
39 | userDirectionsLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8),
40 | userDirectionsLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -8),
41 | userDirectionsLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8),
42 | userDirectionsLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8)
43 | ])
44 | }
45 |
46 | required init?(coder aDecoder: NSCoder) {
47 | fatalError("init(coder:) has not been implemented")
48 | }
49 |
50 | /// Updates the displayed user directions string. If `message` is nil or empty, this will hide the view. If `message` is not empty, it will display the view.
51 | ///
52 | /// - Parameter message: the new string to display.
53 | func updateUserDirections(_ message: String?) {
54 | UIView.animate(withDuration: 0.25) { [weak self] in
55 | self?.alpha = (message?.isEmpty ?? true) ? 0.0 : 1.0
56 | self?.userDirectionsLabel.text = message
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/Examples/ArcGISToolkitExamples/PopupExample.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2016 Esri.
2 |
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | // http://www.apache.org/licenses/LICENSE-2.0
7 |
8 | // Unless required by applicable law or agreed to in writing, software
9 | // distributed under the License is distributed on an "AS IS" BASIS,
10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | // See the License for the specific language governing permissions and
12 | // limitations under the License.
13 |
14 | import UIKit
15 | import ArcGISToolkit
16 | import ArcGIS
17 |
18 | class PopupExample: MapViewController {
19 | var map: AGSMap?
20 |
21 | var popupController: PopupController?
22 |
23 | override func viewDidLoad() {
24 | super.viewDidLoad()
25 |
26 | // Create a map
27 | map = AGSMap(basemapStyle: .arcGISTopographic)
28 | let featureTable = AGSServiceFeatureTable(url: URL(string: "https://sampleserver6.arcgisonline.com/arcgis/rest/services/DamageAssessment/FeatureServer/0")!)
29 | let featureLayer = AGSFeatureLayer(featureTable: featureTable)
30 | map?.operationalLayers.add(featureLayer)
31 |
32 | // Here we give the feature layer a default popup definition.
33 | // We have to load it first to create a default popup definition.
34 | // If you create the map from a portal item, you can define the popup definition
35 | // in the webmap and avoid this step.
36 | featureLayer.load { _ in
37 | featureLayer.popupDefinition = AGSPopupDefinition(popupSource: featureLayer)
38 | }
39 |
40 | // Another way to create the map is with a portal item:
41 | // let portal = AGSPortal.arcGISOnline(withLoginRequired: false)
42 | // let portalItem = AGSPortalItem(portal: portal, itemID: "<>")!
43 | // map = AGSMap(item: portalItem)
44 |
45 | // set the map on the mapview
46 | mapView.map = map
47 |
48 | // Log if there is any error loading the map
49 | map?.load { error in
50 | if let error = error {
51 | print("error loading map: \(error)")
52 | }
53 | }
54 |
55 | // instantiate the popup controller
56 | popupController = PopupController(geoViewController: self, geoView: mapView)
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Examples/ArcGISToolkitExamples/ScalebarExample.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Esri.
2 |
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | // http://www.apache.org/licenses/LICENSE-2.0
7 |
8 | // Unless required by applicable law or agreed to in writing, software
9 | // distributed under the License is distributed on an "AS IS" BASIS,
10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | // See the License for the specific language governing permissions and
12 | // limitations under the License.
13 |
14 | import UIKit
15 | import ArcGISToolkit
16 | import ArcGIS
17 |
18 | class ScalebarExample: MapViewController, AGSGeoViewTouchDelegate {
19 | var map: AGSMap?
20 | var scalebar: Scalebar?
21 |
22 | override func viewDidLoad() {
23 | super.viewDidLoad()
24 |
25 | map = AGSMap(basemapStyle: .arcGISTopographic)
26 | mapView.map = map
27 |
28 | let width = CGFloat(175)
29 | let xMargin = CGFloat(10)
30 | let yMargin = CGFloat(10)
31 |
32 | // lower left scalebar
33 | let sb = Scalebar(mapView: mapView)
34 | sb.units = .metric
35 | sb.alignment = .left
36 | sb.style = .alternatingBar
37 | view.addSubview(sb)
38 |
39 | // add constraints so it's anchored to lower left corner
40 | sb.translatesAutoresizingMaskIntoConstraints = false
41 | sb.widthAnchor.constraint(equalToConstant: width).isActive = true
42 | sb.bottomAnchor.constraint(equalTo: mapView.attributionTopAnchor, constant: -yMargin).isActive = true
43 | sb.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: xMargin).isActive = true
44 | scalebar = sb
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Examples/ArcGISToolkitExamples/TemplatePickerExample.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Esri.
2 |
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | // http://www.apache.org/licenses/LICENSE-2.0
7 |
8 | // Unless required by applicable law or agreed to in writing, software
9 | // distributed under the License is distributed on an "AS IS" BASIS,
10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | // See the License for the specific language governing permissions and
12 | // limitations under the License.
13 |
14 | import UIKit
15 | import ArcGISToolkit
16 | import ArcGIS
17 |
18 | class TemplatePickerExample: MapViewController {
19 | var map: AGSMap?
20 |
21 | override func viewDidLoad() {
22 | super.viewDidLoad()
23 |
24 | // Create a map
25 |
26 | map = AGSMap(basemapStyle: .arcGISTopographic)
27 | let featureTable = AGSServiceFeatureTable(url: URL(string: "https://sampleserver6.arcgisonline.com/arcgis/rest/services/DamageAssessment/FeatureServer/0")!)
28 | let featureLayer = AGSFeatureLayer(featureTable: featureTable)
29 | map?.operationalLayers.add(featureLayer)
30 |
31 | // Here we give the feature layer a default popup definition.
32 | // We have to load it first to create a default popup definition.
33 | // If you create the map from a portal item, you can define the popup definition
34 | // in the webmap and avoid this step.
35 | featureLayer.load { _ in
36 | featureLayer.popupDefinition = AGSPopupDefinition(popupSource: featureLayer)
37 | }
38 |
39 | // set the map on the mapview
40 | mapView.map = map
41 |
42 | // Log if there is any error loading the map
43 | map?.load { error in
44 | if let error = error {
45 | print("error loading map: \(error)")
46 | }
47 | }
48 |
49 | // add bar button item for showing templates
50 | let bbi = UIBarButtonItem(title: "Templates", style: .plain, target: self, action: #selector(showTemplates))
51 | navigationItem.rightBarButtonItem = bbi
52 | }
53 |
54 | @objc
55 | private func showTemplates() {
56 | guard let map = map else { return }
57 |
58 | // Instantiate the TemplatePickerViewController
59 | let templatePicker = TemplatePickerViewController(map: map)
60 |
61 | // Assign the delegate
62 | templatePicker.delegate = self
63 |
64 | // Present the template picker
65 | self.navigationController?.pushViewController(templatePicker, animated: true)
66 | }
67 | }
68 |
69 | extension TemplatePickerExample: TemplatePickerViewControllerDelegate {
70 | public func templatePickerViewControllerDidCancel(_ templatePickerViewController: TemplatePickerViewController) {
71 | // This is where you handle the user canceling the template picker
72 |
73 | // dismiss the template picker
74 | navigationController?.popToViewController(self, animated: true)
75 |
76 | let alert = UIAlertController(title: "TemplatePickerExample", message: "User cancelled", preferredStyle: .alert)
77 | let action = UIAlertAction(title: "OK", style: .default, handler: nil)
78 | alert.addAction(action)
79 | alert.preferredAction = action
80 | present(alert, animated: true)
81 | }
82 |
83 | public func templatePickerViewController(_ templatePickerViewController: TemplatePickerViewController, didSelect featureTemplateInfo: FeatureTemplateInfo) {
84 | // This is where you handle the user making a selection with the template picker
85 |
86 | // dismiss the template picker
87 | navigationController?.popToViewController(self, animated: true)
88 |
89 | let alert = UIAlertController(title: "TemplatePickerExample", message: "User selected \(featureTemplateInfo.featureTemplate.name)", preferredStyle: .alert)
90 | let action = UIAlertAction(title: "OK", style: .default, handler: nil)
91 | alert.addAction(action)
92 | alert.preferredAction = action
93 | present(alert, animated: true)
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/Examples/ArcGISToolkitExamples/TimeSliderExample.swift:
--------------------------------------------------------------------------------
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 | private var map = AGSMap(basemapStyle: .arcGISTopographic)
21 | private var timeSlider = TimeSlider()
22 |
23 | override func viewDidLoad() {
24 | super.viewDidLoad()
25 |
26 | // Set a map with ESRI topographic basemap to mapView
27 | mapView.map = map
28 |
29 | // Configure time slider
30 | timeSlider.isHidden = true
31 | timeSlider.labelMode = .ticks
32 | timeSlider.addTarget(self, action: #selector(TimeSliderExample.timeSliderValueChanged(timeSlider:)), for: .valueChanged)
33 | view.addSubview(timeSlider)
34 |
35 | //
36 | // Add constraints to position the slider
37 | let margin: CGFloat = 10.0
38 | timeSlider.translatesAutoresizingMaskIntoConstraints = false
39 | timeSlider.bottomAnchor.constraint(equalTo: mapView.attributionTopAnchor, constant: -margin).isActive = true
40 | timeSlider.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: margin).isActive = true
41 | timeSlider.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -margin).isActive = true
42 |
43 | // Add layer
44 | let mapImageLayer = AGSArcGISMapImageLayer(url: URL(string: "https://sampleserver6.arcgisonline.com/arcgis/rest/services/911CallsHotspot/MapServer")!)
45 | mapView.map?.operationalLayers.add(mapImageLayer)
46 | mapImageLayer.load { [weak self] (error) in
47 | // Make sure self is around
48 | guard let self = self else {
49 | return
50 | }
51 |
52 | // If layer fails to load then
53 | // return with an error.
54 | guard error == nil else {
55 | self.showError(error!)
56 | return
57 | }
58 |
59 | // Zoom to full extent of layer
60 | if let fullExtent = mapImageLayer.fullExtent {
61 | self.mapView.setViewpoint(AGSViewpoint(targetExtent: fullExtent), completion: nil)
62 | }
63 |
64 | self.timeSlider.initializeTimeProperties(geoView: self.mapView, observeGeoView: true) { [weak self] (error) in
65 | // Make sure self is around
66 | guard let self = self else {
67 | return
68 | }
69 |
70 | // If time slider fails to init then
71 | // return with an error.
72 | guard error == nil else {
73 | self.showError(error!)
74 | return
75 | }
76 |
77 | // Show the time slider
78 | self.timeSlider.isHidden = false
79 | }
80 | }
81 | }
82 |
83 | @objc
84 | func timeSliderValueChanged(timeSlider: TimeSlider) {
85 | if mapView.timeExtent != timeSlider.currentExtent {
86 | mapView.timeExtent = timeSlider.currentExtent
87 | }
88 | }
89 |
90 | // MARK: - Show Error
91 |
92 | private func showError(_ error: Error) {
93 | let alertController = UIAlertController(title: "Error", message: error.localizedDescription, preferredStyle: .alert)
94 | alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
95 | present(alertController, animated: true)
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/Examples/ArcGISToolkitExamples/VCListViewController.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Esri.
2 |
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | // http://www.apache.org/licenses/LICENSE-2.0
7 |
8 | // Unless required by applicable law or agreed to in writing, software
9 | // distributed under the License is distributed on an "AS IS" BASIS,
10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | // See the License for the specific language governing permissions and
12 | // limitations under the License.
13 |
14 | import UIKit
15 | import ArcGISToolkit
16 |
17 | open class VCListViewController: UITableViewController {
18 | public var storyboardName: String?
19 |
20 | public var viewControllerInfos: [(vcName: String, viewControllerType: UIViewController.Type, nibName: String?)] = [] {
21 | didSet {
22 | self.tableView.reloadData()
23 | }
24 | }
25 |
26 | override open func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
27 | return viewControllerInfos.count
28 | }
29 |
30 | override open func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
31 | let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
32 | cell.textLabel?.text = viewControllerInfos[indexPath.row].vcName
33 | return cell
34 | }
35 |
36 | override open func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
37 | let t = viewControllerInfos[indexPath.row].viewControllerType
38 | let nibName = viewControllerInfos[indexPath.row].nibName
39 | var vcOpt: UIViewController?
40 |
41 | // first check storyboard
42 | if let storyboardName = self.storyboardName {
43 | let sb = UIStoryboard(name: storyboardName, bundle: nil)
44 | if let nibName = nibName {
45 | // this is how you can check to see if that identifier is in the nib, based on http://stackoverflow.com/a/34650505/1687195
46 | if let dictionary = sb.value(forKey: "identifierToNibNameMap") as? NSDictionary {
47 | if dictionary.value(forKey: nibName) != nil {
48 | vcOpt = sb.instantiateViewController(withIdentifier: nibName)
49 | }
50 | }
51 | }
52 | }
53 |
54 | if vcOpt == nil {
55 | vcOpt = t.init(nibName: nibName, bundle: nil)
56 | }
57 |
58 | if let vc = vcOpt {
59 | navigationController?.pushViewController(vc, animated: true)
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/Examples/README.md:
--------------------------------------------------------------------------------
1 | This project contains examples of how to use the toolkit components.
2 |
3 | ## Instructions
4 |
5 |
6 | 1. Clone or download this repo
7 | 2. Open the Examples project in Xcode by double clicking on the `ArcGISToolkitExamples.xcodeproj` file
8 | 3. Build and run the project
9 |
--------------------------------------------------------------------------------
/Examples/exportOptions.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.3
2 | /*
3 | COPYRIGHT 1995-2021 ESRI
4 |
5 | All rights reserved under the copyright laws of the United States
6 | and applicable international laws, treaties, and conventions.
7 |
8 | This material is licensed for use under the Esri Master License
9 | Agreement (MLA), and is bound by the terms of that agreement.
10 | You may redistribute and use this code without modification,
11 | provided you adhere to the terms of the MLA and include this
12 | copyright notice.
13 |
14 | See use restrictions at http://www.esri.com/legal/pdfs/mla_e204_e300/english
15 |
16 | For additional information, contact:
17 | Environmental Systems Research Institute, Inc.
18 | Attn: Contracts and Legal Services Department
19 | 380 New York Street
20 | Redlands, California, USA 92373
21 |
22 | email: contracts@esri.com
23 | */
24 |
25 | import PackageDescription
26 |
27 | // This is setup by default to use a hosted version of the ArcGIS Runtime for iOS.
28 | // To use a local installation of ArcGIS Runtime for iOS, uncomment the lines below declaring
29 | // the `localArcGISPackage` constant and use that as the package dependency instead of the
30 | // hosted version.
31 | // import class Foundation.ProcessInfo
32 | // let localArcGISPackage = ProcessInfo.processInfo.environment["HOME"]! + "/Library/SDKs/ArcGIS"
33 |
34 | let package = Package(
35 | name: "arcgis-runtime-toolkit-ios",
36 | platforms: [
37 | .iOS(.v14)
38 | ],
39 | products: [
40 | .library(
41 | name: "ArcGISToolkit",
42 | targets: ["ArcGISToolkit"]
43 | )
44 | ],
45 | dependencies: [
46 | .package(
47 | name: "arcgis-runtime-ios",
48 | // path: localArcGISPackage
49 | url: "https://github.com/Esri/arcgis-runtime-ios", .upToNextMinor(from: "100.15.0")
50 | )
51 | ],
52 | targets: [
53 | .target(
54 | name: "ArcGISToolkit",
55 | dependencies: [.product(name: "ArcGIS", package: "arcgis-runtime-ios")],
56 | resources: [
57 | .process("Resources/PrivacyInfo.xcprivacy")
58 | ]
59 | ),
60 | .testTarget(
61 | name: "ArcGISToolkitTests",
62 | dependencies: ["ArcGISToolkit"]
63 | )
64 | ]
65 | )
66 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ArcGIS Runtime Toolkit for iOS
2 |
3 | [](Documentation)
4 | [](https://cocoapods.org/)
5 | [](https://github.com/apple/swift-package-manager/)
6 |
7 | The ArcGIS Runtime SDK for iOS Toolkit contains components that will simplify your iOS app development. Check out the
8 | [Examples](/Examples) project to see these components in action or read through the [Documentation](/Documentation) to
9 | learn more about them.
10 |
11 |
12 | >Try [ArcGIS Maps SDK for Swift version 200](https://github.com/Esri/arcgis-maps-sdk-swift-toolkit/)
13 | >
14 | >ArcGIS Runtime SDK for iOS version 100.15 is a long-term support release focused exclusively on bug fixes and minor updates.
15 | >
16 | >ArcGIS Maps SDK for Swift version 200 builds on the proven architecture of 100.15, and provides a new API designed exclusively for developing iOS apps using Swift - Apple's modern, powerful, and intuitive programming language.
17 |
18 | To use Toolkit in your project:
19 |
20 | * **[Install with Swift Package Manager](#swift-package-manager)** - Add
21 | `https://github.com/Esri/arcgis-runtime-toolkit-ios` as the package repository URL.
22 | * **[Install with CocoaPods](#cocoapods)** - Add `pod 'ArcGIS-Runtime-Toolkit-iOS'` to your podfile
23 | * **[Build manually](#manual)** - Build and include manually if you'd like to customize or extend toolkit
24 |
25 | ## Toolkit Components
26 |
27 | * **[Augmented reality (AR)](Documentation/AR)** - Integrates the scene view with ARKit to enable augmented reality
28 | (AR).
29 | * **[Bookmarks](Documentation/Bookmarks)** - Shows bookmarks, from a map, scene, or a list.
30 | * **[Compass](Documentation/Compass)** - Shows a compass direction when the map is rotated. Auto-hides when the map
31 | points north up.
32 | * **[FloorFilter](Documentation/FloorFilter)** - Allows to filter floor plan data in a geo view by a site, a building in the site, or a floor in the building.
33 | * **[JobManager](Documentation/JobManager)** - Suspends and resumes ArcGIS Runtime tasks when the app is background,
34 | terminated, and relaunched.
35 | * **[LegendViewController](Documentation/LegendViewController)** - Displays a legend for all the layers in a map or
36 | scene contained in an `AGSGeoView`.
37 | * **[MeasureToolbar](Documentation/MeasureToolbar)** - Allows measurement of distances and areas on the map view.
38 | * **[PopupController](Documentation/PopupController)** - Display details and media, edit attributes, geometry and
39 | related records, and manage the attachments of features and graphics (popups are defined in the popup property of
40 | features and graphics).
41 | * **[Scalebar](Documentation/Scalebar)** - Displays current scale reference.
42 | * **[TemplatePickerViewController](Documentation/TemplatePicker)** - Allows a user to choose a template from a list of
43 | `AGSFeatureTemplate` when creating new features.
44 | * **[TimeSlider](Documentation/TimeSlider)** - Allows interactively defining a temporal range (i.e. time extent) and
45 | animating time moving forward or backward. Can be used to manipulate the time extent in a MapView or SceneView.
46 |
47 | ## Requirements
48 |
49 | * [ArcGIS Runtime SDK for iOS](https://developers.arcgis.com/ios/) 100.15.0 (or higher)
50 | * Xcode 13.0 (or higher)
51 |
52 | The *ArcGIS Runtime Toolkit for iOS* has a *Target SDK* version of *14.0*, meaning that it can run on devices with *iOS
53 | 14.0* or newer.
54 |
55 | ## Instructions
56 |
57 | ### Swift Package Manager
58 |
59 | 1. Open your project in Xcode
60 | 2. Go to *File* > *Swift Packages* > *Add Package Dependency* option
61 | 3. Enter `https://github.com/Esri/arcgis-runtime-toolkit-ios` as the package repository URL
62 | 4. Choose version 100.15.0 or a later version. Click Next.
63 |
64 | Note: The Toolkit Swift Package adds the ArcGIS SDK Swift Package as a dependency so no need to add both separately. If
65 | you already have the ArcGIS SDK Swift Package delete that and just add the Toolkit Swift Package.
66 |
67 | New to Swift Package Manager? Visit [swift.org/package-manager/](https://swift.org/package-manager/).
68 |
69 | ### Cocoapods
70 |
71 | 1. Add `pod 'ArcGIS-Runtime-Toolkit-iOS'` to your podfile
72 | 2. Run `pod install`. This will download the toolkit and the ArcGIS Runtime SDK for iOS which the toolkit depends upon
73 | and then configure your project to reference them both
74 | 3. Add `import ArcGISToolkit` in your source code and start using the toolkit components
75 |
76 | New to cocoapods? Visit [cocoapods.org](https://cocoapods.org/)
77 |
78 | ### Manual
79 |
80 | 1. Clone or download this repo
81 | 2. Drag and Drop the `arcgis-runtime-toolkit-ios` folder into your project through the Xcode Project Navigator pane
82 | 3. Add the *ArcGISToolkit* library in your app, by adding it to the Frameworks, Libraries, and Embedded Content section
83 | of the General pane for your app target. The *ArcGISToolkit* library contains the *ArcGIS Runtime SDK for iOS*
84 | library, so you don't need to add that separately.
85 | 4. Add `import ArcGIS` and `import ArcGISToolkit` in your source code and start using the toolkit components
86 |
87 | Note: The manual installation method also allows you to use a local installation ArcGIS Runtime SDK for iOS by making
88 | minor edits to the [swift package](Package.swift).
89 |
90 | ## Configure API Key
91 |
92 | Some of the toolkit components and examples utilize a set of ready-to-use ArcGIS Platform services, including basemaps, and therefore require an API Key to be set in `AppDelegate.swift`. Please see the [setup guide](https://developers.arcgis.com/ios/get-started/#3-access-services-and-content-with-an-api-key) for more information.
93 |
94 | ## SwiftLint
95 |
96 | Both the Toolkit and Examples app support SwiftLint. You can install SwiftLint from
97 | [here](https://github.com/realm/SwiftLint). It is not necessary to have it installed in order to build, but you will
98 | get a warning without it. The specific rules the linter uses can be found in the `swiftlint.yml` files in the `Toolkit`
99 | and `Examples` directories.
100 |
101 | ## Additional Resources
102 |
103 | * [Developers guide documentation](https://developers.arcgis.com/ios)
104 | * [Runtime API Reference](https://developers.arcgis.com/ios/api-reference)
105 | * [Samples](https://github.com/Esri/arcgis-runtime-samples-ios)
106 | * Got a question? Ask the community on our
107 | [forum](http://geonet.esri.com/community/developers/native-app-developers/arcgis-runtime-sdk-for-ios)
108 |
109 | ## Issues
110 |
111 | Find a bug or want to request a new feature? Please let us know by [submitting an
112 | issue](https://github.com/Esri/arcgis-runtime-toolkit-ios/issues/new).
113 |
114 | ## Contributing
115 |
116 | Esri welcomes contributions from anyone and everyone. Please see our [guidelines for
117 | contributing](https://github.com/esri/contributing).
118 |
119 | ## Licensing
120 |
121 | Copyright 2017 - 2022 Esri
122 |
123 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
124 | License. You may obtain a copy of the License at
125 |
126 |
127 |
128 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an
129 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
130 | language governing permissions and limitations under the License.
131 |
132 | A copy of the license is available in the repository's [LICENSE]( /LICENSE) file.
133 |
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/BookmarksTableViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2019 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 ArcGIS
17 |
18 | class BookmarksTableViewController: UITableViewController {
19 | var bookmarks = [AGSBookmark]() {
20 | didSet {
21 | if let indexPath = tableView.indexPathForSelectedRow {
22 | tableView.deselectRow(at: indexPath, animated: false)
23 | }
24 | tableView.reloadData()
25 | }
26 | }
27 |
28 | weak var delegate: BookmarksViewControllerDelegate?
29 |
30 | private var cellReuseIdentifier = "cell"
31 |
32 | // Private property to store selection action for table cell.
33 | private var selectAction: ((AGSBookmark) -> Void)?
34 |
35 | // Executed for tableview row selection.
36 | func setSelectAction(_ action : @escaping ((AGSBookmark) -> Void)) {
37 | self.selectAction = action
38 | }
39 |
40 | override func viewDidLoad() {
41 | super.viewDidLoad()
42 | tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellReuseIdentifier)
43 | }
44 |
45 | // MARK: - Table view data source
46 |
47 | override func numberOfSections(in tableView: UITableView) -> Int {
48 | return 1
49 | }
50 |
51 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
52 | return bookmarks.count
53 | }
54 |
55 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
56 | let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier, for: indexPath)
57 | cell.backgroundColor = .clear
58 | cell.textLabel?.text = bookmarks[indexPath.row].name
59 | return cell
60 | }
61 |
62 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
63 | selectAction?(bookmarks[indexPath.row])
64 | tableView.deselectRow(at: indexPath, animated: true)
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/BookmarksViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2019 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 ArcGIS
17 |
18 | /// The protocol you implement to respond to user bookmark selections.
19 | /// - Since: 100.7.0
20 | public protocol BookmarksViewControllerDelegate: AnyObject {
21 | /// Tells the delegate that the user has selected a bookmark.
22 | ///
23 | /// - Parameters:
24 | /// - controller: The view controller calling the delegate method.
25 | /// - bookmark: The new bookmark selected.
26 | /// - Since: 100.7.0
27 | func bookmarksViewController(_ controller: BookmarksViewController, didSelect bookmark: AGSBookmark)
28 | }
29 |
30 | /// The `BookmarksViewController` will display a list of bookmarks in a table view and allows the user to select a bookmark and perform some action.
31 | /// It can be created using either an `AGSGeoView` or an array of `AGSBookmark`s.
32 | /// - Since: 100.7.0
33 | public class BookmarksViewController: UIViewController {
34 | /// The array of `AGSBookmark`s to display.
35 | /// - Since: 100.7.0
36 | public var bookmarks = [AGSBookmark]() {
37 | didSet {
38 | tableViewController.bookmarks = bookmarks
39 | }
40 | }
41 |
42 | /// The `AGSGeoView` containing either an `AGSMap` or `AGSScene` with the `AGSBookmark`s to display.
43 | /// - Since: 100.7.0
44 | public var geoView: AGSGeoView? {
45 | didSet {
46 | geoViewDidChange(oldValue)
47 | }
48 | }
49 |
50 | /// The delegate of the bookmarks view controller. Clients must set the `delegate` property and implement the `bookmarksViewController:didSelect` delegate method in order to be notified when a bookmark is selected.
51 | /// - Since: 100.7.0
52 | public weak var delegate: BookmarksViewControllerDelegate?
53 |
54 | /// The observations used to observe changes to the bookmarks and map/scene.
55 | private var bookmarksObservation: NSKeyValueObservation?
56 | private var mapOrSceneObservation: NSKeyValueObservation?
57 |
58 | /// The view controller which handles the actual bookmark display and user interaction.
59 | private lazy var tableViewController = BookmarksTableViewController()
60 |
61 | /// Returns a BookmarksViewController which will display the `AGSBookmark`s in the `bookmarks` array.
62 | /// - Parameter bookmarks: A sequence of `AGSBookmark`s to display.
63 | /// - Since: 100.7.0
64 | public init(bookmarks: S) where S.Element == AGSBookmark {
65 | super.init(nibName: nil, bundle: nil)
66 | self.bookmarks.append(contentsOf: bookmarks)
67 | tableViewController.bookmarks = self.bookmarks
68 | sharedInit()
69 | }
70 |
71 | /// Returns a BookmarksViewController which will display the array of `AGSBookmark` found in the `AGSGeoView`s `AGSMap` or `AGSScene`.
72 | /// - Parameter geoView: The `AGSGeoView` containing the `AGSBookmark` array in either the map or scene.
73 | /// - Since: 100.7.0
74 | public init(geoView: AGSGeoView) {
75 | super.init(nibName: nil, bundle: nil)
76 | self.geoView = geoView
77 | geoViewDidChange(nil)
78 | sharedInit()
79 | }
80 |
81 | @available(*, unavailable)
82 | required init?(coder: NSCoder) {
83 | fatalError("init(coder:) has not been implemented")
84 | }
85 |
86 | private func sharedInit() {
87 | title = "Bookmarks"
88 | }
89 |
90 | override public func viewDidLoad() {
91 | super.viewDidLoad()
92 |
93 | // Setup our internal BookmarksTableViewController and add it as a child.
94 | addChild(tableViewController)
95 | view.addSubview(tableViewController.view)
96 | tableViewController.view.translatesAutoresizingMaskIntoConstraints = false
97 | NSLayoutConstraint.activate([
98 | tableViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
99 | tableViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
100 | tableViewController.view.topAnchor.constraint(equalTo: view.topAnchor),
101 | tableViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor)
102 | ])
103 | tableViewController.didMove(toParent: self)
104 |
105 | // Set the closure to be executed when the user selects a bookmark.
106 | tableViewController.setSelectAction { [weak self] (bookmark: AGSBookmark) in
107 | guard let self = self, let delegate = self.delegate else { return }
108 | delegate.bookmarksViewController(self, didSelect: bookmark)
109 | }
110 | }
111 |
112 | private func geoViewDidChange(_ previousGeoView: AGSGeoView?) {
113 | if let mapView = geoView as? AGSMapView {
114 | mapView.map?.load { [weak self] (error) in
115 | guard let self = self, let mapView = self.geoView as? AGSMapView else { return }
116 | if let error = error {
117 | print("Error loading map: \(error)")
118 | } else {
119 | self.bookmarks = mapView.map?.bookmarks as? [AGSBookmark] ?? []
120 | }
121 | }
122 |
123 | // Add an observer to handle changes to the map.bookmarks array.
124 | addBookmarksObserver(map: mapView.map)
125 |
126 | // Add an observer to handle changes to the mapView.map.
127 | mapOrSceneObservation = mapView.observe(\.map) { [weak self] (_, _) in
128 | self?.bookmarks = mapView.map?.bookmarks as? [AGSBookmark] ?? []
129 |
130 | // When the map changes, we again need to add an observer to handle changes to the map.bookmarks array.
131 | self?.addBookmarksObserver(map: mapView.map)
132 | }
133 | } else if let sceneView = geoView as? AGSSceneView {
134 | sceneView.scene?.load { [weak self] (error) in
135 | guard let self = self, let sceneView = self.geoView as? AGSSceneView else { return }
136 | if let error = error {
137 | print("Error loading map: \(error)")
138 | } else {
139 | self.bookmarks = sceneView.scene?.bookmarks as? [AGSBookmark] ?? []
140 | }
141 | }
142 |
143 | // Add an observer to handle changes to the scene.bookmarks array.
144 | addBookmarksObserver(scene: sceneView.scene)
145 |
146 | // Add an observer to handle changes to the sceneView.scene.
147 | mapOrSceneObservation = sceneView.observe(\.scene) { [weak self] (_, _) in
148 | self?.bookmarks = sceneView.scene?.bookmarks as? [AGSBookmark] ?? []
149 |
150 | // When the scene changes, we again need to add an observer to handle changes to the scene.bookmarks array.
151 | self?.addBookmarksObserver(scene: sceneView.scene)
152 | }
153 | }
154 | }
155 |
156 | private func addBookmarksObserver(map: AGSMap?) {
157 | bookmarksObservation = map?.observe(\.bookmarks) { [weak self] (map, _) in
158 | DispatchQueue.main.async {
159 | self?.bookmarks = map.bookmarks as? [AGSBookmark] ?? []
160 | }
161 | }
162 | }
163 |
164 | private func addBookmarksObserver(scene: AGSScene?) {
165 | bookmarksObservation = scene?.observe(\.bookmarks) { [weak self] (scene, _) in
166 | DispatchQueue.main.async {
167 | self?.bookmarks = scene.bookmarks as? [AGSBookmark] ?? []
168 | }
169 | }
170 | }
171 | }
172 |
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/CancelGroup.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Esri.
2 |
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | // http://www.apache.org/licenses/LICENSE-2.0
7 |
8 | // Unless required by applicable law or agreed to in writing, software
9 | // distributed under the License is distributed on an "AS IS" BASIS,
10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | // See the License for the specific language governing permissions and
12 | // limitations under the License.
13 |
14 | import ArcGIS
15 | import Foundation
16 |
17 | /**
18 | Wraps multiple AGSCancelables into a single cancelable object.
19 | */
20 | @objc
21 | public class CancelGroup: NSObject, AGSCancelable {
22 | /// Cancels all the AGSCancelables in the group.
23 | public func cancel() {
24 | children.forEach { $0.cancel() }
25 | _canceled = true
26 | }
27 |
28 | private var _canceled: Bool = false
29 |
30 | /// Whether or not the group is canceled.
31 | public func isCanceled() -> Bool {
32 | return _canceled
33 | }
34 |
35 | /// The children associated with this group.
36 | public var children: [AGSCancelable] = [AGSCancelable]()
37 | }
38 |
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Coalescer.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Esri.
2 |
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | // http://www.apache.org/licenses/LICENSE-2.0
7 |
8 | // Unless required by applicable law or agreed to in writing, software
9 | // distributed under the License is distributed on an "AS IS" BASIS,
10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | // See the License for the specific language governing permissions and
12 | // limitations under the License.
13 |
14 | import Foundation
15 |
16 | internal class Coalescer {
17 | // Class to coalesce actions into intervals.
18 | // This is helpful for the Scalebar because we get updates to the visibleArea up to 60hz and we
19 | // don't need to redraw the Scalebar that often
20 |
21 | var dispatchQueue: DispatchQueue
22 | var interval: DispatchTimeInterval
23 | var action: (() -> Void)
24 |
25 | init (dispatchQueue: DispatchQueue, interval: DispatchTimeInterval, action: @escaping (() -> Void)) {
26 | self.dispatchQueue = dispatchQueue
27 | self.interval = interval
28 | self.action = action
29 | }
30 |
31 | private var count = 0
32 |
33 | func ping() {
34 | // synchronize to a serial queue, in this case main thread
35 | if !Thread.isMainThread {
36 | DispatchQueue.main.async { self.ping() }
37 | return
38 | }
39 |
40 | // increment the count
41 | count += 1
42 |
43 | // the first time the count is incremented, it dispatches the action
44 | if count == 1 {
45 | dispatchQueue.asyncAfter(deadline: DispatchTime.now() + interval) {
46 | // call the action
47 | self.action()
48 |
49 | // reset the count
50 | self.resetCount()
51 | }
52 | }
53 | }
54 |
55 | private func resetCount() {
56 | // synchronize to a serial queue, in this case main thread
57 | if !Thread.isMainThread {
58 | DispatchQueue.main.async { self.count = 0 }
59 | } else {
60 | self.count = 0
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Compass.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Esri.
2 |
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | // http://www.apache.org/licenses/LICENSE-2.0
7 |
8 | // Unless required by applicable law or agreed to in writing, software
9 | // distributed under the License is distributed on an "AS IS" BASIS,
10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | // See the License for the specific language governing permissions and
12 | // limitations under the License.
13 |
14 | import UIKit
15 | import ArcGIS
16 |
17 | public class Compass: UIImageView {
18 | public var heading: Double = 0.0 { // Rotation - bound to MapView.MapRotation
19 | didSet {
20 | mapView.setViewpointRotation(heading, completion: nil)
21 | }
22 | }
23 | public var autoHide: Bool = true { // Auto hides when north is up
24 | didSet {
25 | animateCompass()
26 | }
27 | }
28 | public var width: CGFloat = 30.0 {
29 | didSet {
30 | widthConstraint?.isActive = false
31 | widthConstraint = widthAnchor.constraint(equalToConstant: width)
32 | widthConstraint?.isActive = true
33 | }
34 | }
35 | public var height: CGFloat = 30 {
36 | didSet {
37 | heightConstraint?.isActive = false
38 | heightConstraint = heightAnchor.constraint(equalToConstant: height)
39 | heightConstraint?.isActive = true
40 | }
41 | }
42 |
43 | private var mapView: AGSMapView
44 |
45 | // the width and height constraints
46 | private var widthConstraint: NSLayoutConstraint?
47 | private var heightConstraint: NSLayoutConstraint?
48 |
49 | private var rotationObservation: NSKeyValueObservation?
50 |
51 | public init(mapView: AGSMapView) {
52 | self.mapView = mapView
53 |
54 | super.init(frame: .zero)
55 |
56 | // Set our image to the CompassIcon in the Assets
57 | image = UIImage(named: "CompassIcon", in: .module, compatibleWith: nil)
58 |
59 | // add gesture recognizer to know when arrow is tapped
60 | let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(compassTapped))
61 | self.addGestureRecognizer(tapGestureRecognizer)
62 | self.isUserInteractionEnabled = true
63 |
64 | // animate the compass visibility, if necessary
65 | animateCompass()
66 |
67 | // Add Compass as an observer of the mapView's rotation.
68 | rotationObservation = mapView.observe(\.rotation, options: .new) {[weak self] (_, change) in
69 | guard let rotation = change.newValue else {
70 | return
71 | }
72 |
73 | // make sure that UI changes are made on the main thread
74 | DispatchQueue.main.async {
75 | guard let self = self else {
76 | return
77 | }
78 |
79 | let mapRotation = self.degreesToRadians(degrees: (360 - rotation))
80 | // Rotate north arrow to match the map view rotation.
81 | self.transform = CGAffineTransform(rotationAngle: mapRotation)
82 | // Animate the compass visibility (if necessary)
83 | self.animateCompass()
84 | }
85 | }
86 | }
87 |
88 | public required init?(coder aDecoder: NSCoder) {
89 | fatalError("init(coder:) has not been implemented")
90 | }
91 |
92 | @objc
93 | func compassTapped() {
94 | mapView.setViewpointRotation(0, completion: nil)
95 | }
96 |
97 | func animateCompass() {
98 | let alpha: CGFloat = (mapView.rotation == 0.0) && autoHide ? 0.0 : 1.0
99 | if alpha != self.alpha {
100 | UIView.animate(withDuration: 0.25) {
101 | self.alpha = alpha
102 | }
103 | }
104 | }
105 |
106 | func degreesToRadians(degrees: Double) -> CGFloat {
107 | return CGFloat(degrees * Double.pi / 180)
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Extensions.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Esri.
2 |
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | // http://www.apache.org/licenses/LICENSE-2.0
7 |
8 | // Unless required by applicable law or agreed to in writing, software
9 | // distributed under the License is distributed on an "AS IS" BASIS,
10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | // See the License for the specific language governing permissions and
12 | // limitations under the License.
13 |
14 | import Foundation
15 | import UIKit
16 |
17 | #if !SWIFT_PACKAGE
18 | // This is a workaround for cocoapods compatibility.
19 | extension Bundle {
20 | static var module: Bundle { Bundle(identifier: "org.cocoapods.ArcGISToolkit")! }
21 | }
22 | #endif
23 |
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/FloorFilter/FloorFilterViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 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 Foundation
16 | import UIKit
17 | import ArcGIS
18 |
19 | /// View Model class that contains the Data Model of the Floor Filter
20 | /// Also contains the business logic to filter and change the map extent based on selected site/level/facility
21 | final class FloorFilterViewModel {
22 | /// The MapView, Map and Floor Manager are set in the FloorFilterViewController when the map is loaded
23 | var mapView: AGSMapView?
24 |
25 | var floorManager: AGSFloorManager? {
26 | return mapView?.map?.floorManager
27 | }
28 |
29 | var sites: [AGSFloorSite] {
30 | return floorManager?.sites ?? []
31 | }
32 |
33 | /// Facilities in the selected site
34 | /// If no site is selected then the list is empty
35 | /// If the sites data does not exist in the map, then use all the facilities in the map
36 | var facilities: [AGSFloorFacility] {
37 | guard let floorManager = floorManager else { return [] }
38 | return sites.isEmpty ? floorManager.facilities : floorManager.facilities.filter { $0.site == selectedSite }
39 | }
40 |
41 | /// Levels that are visible in the expanded Floor Filter levels table view
42 | /// Sort the levels by verticalOrder in a descending order
43 | var visibleLevelsInExpandedList: [AGSFloorLevel] {
44 | guard let floorManager = floorManager else { return [] }
45 | return facilities.isEmpty ? floorManager.levels : floorManager.levels.filter {
46 | $0.facility == selectedFacility
47 | }.sorted {
48 | $0.verticalOrder > $1.verticalOrder
49 | }
50 | }
51 |
52 | /// All the levels in the map
53 | /// make this property public so it can be accessible to test
54 | var allLevels: [AGSFloorLevel] {
55 | return floorManager?.levels ?? []
56 | }
57 |
58 | /// The site, facility, and level that are selected by the user
59 | var selectedSite: AGSFloorSite?
60 | var selectedFacility: AGSFloorFacility?
61 | var selectedLevel: AGSFloorLevel?
62 |
63 | /// Gets the default level for a facility
64 | /// Uses level with vertical order 0 otherwise gets the lowest level
65 | func defaultLevel(for facility: AGSFloorFacility?) -> AGSFloorLevel? {
66 | return allLevels.first(where: { level in
67 | level.facility == facility && level.verticalOrder == .zero
68 | }) ?? lowestLevel()
69 | }
70 |
71 | /// Returns the AGSFloorLevel with the lowest verticalOrder.
72 | private func lowestLevel() -> AGSFloorLevel? {
73 | let sortedLevels = allLevels.sorted {
74 | $0.verticalOrder < $1.verticalOrder
75 | }
76 | return sortedLevels.first {
77 | $0.verticalOrder != .min && $0.verticalOrder != .max
78 | }
79 | }
80 |
81 | /// Sets the visibility of all the levels on the map based on the vertical order of the current selected level
82 | func filterMapToSelectedLevel() {
83 | guard let selectedLevel = selectedLevel else { return }
84 | allLevels.forEach {
85 | $0.isVisible = $0.verticalOrder == selectedLevel.verticalOrder
86 | }
87 | }
88 |
89 | /// Zooms to the facility if there is a selected facility, otherwise zooms to the site.
90 | func zoomToSelection() {
91 | if let selectedFacility = selectedFacility {
92 | zoom(to: selectedFacility)
93 | } else if let selectedSite = selectedSite {
94 | zoom(to: selectedSite)
95 | }
96 | }
97 |
98 | private func zoom(to floorSite: AGSFloorSite) {
99 | zoomToExtent(mapView: mapView, envelope: floorSite.geometry?.extent)
100 | }
101 |
102 | private func zoom(to floorFacility: AGSFloorFacility) {
103 | zoomToExtent(mapView: mapView, envelope: floorFacility.geometry?.extent)
104 | }
105 |
106 | private func zoomToExtent(mapView: AGSMapView?, envelope: AGSEnvelope?) {
107 | guard let mapView = mapView,
108 | let envelope = envelope
109 | else { return }
110 |
111 | let padding = 1.5
112 | let envelopeWithBuffer = AGSEnvelope(
113 | center: envelope.center,
114 | width: envelope.width * padding,
115 | height: envelope.height * padding
116 | )
117 |
118 | if !envelopeWithBuffer.isEmpty {
119 | let viewPoint = AGSViewpoint(targetExtent: envelopeWithBuffer)
120 | mapView.setViewpoint(viewPoint, duration: 0.5, completion: nil)
121 | }
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/FloorFilter/SiteFacilityPromptViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 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 ArcGIS
17 | import Foundation
18 |
19 | /// ViewController for the site and facility prompt.
20 | final class SiteFacilityPromptViewController: UIViewController {
21 | weak var delegate: FloorFilterViewControllerDelegate?
22 | var viewModel = FloorFilterViewModel()
23 |
24 | /// UI Elements and constraints
25 | @IBOutlet var siteFacilitySearchBar: UISearchBar!
26 | @IBOutlet var backBtn: UIButton!
27 | @IBOutlet var closeBtn: UIButton!
28 | @IBOutlet var promptTitle: UILabel!
29 | @IBOutlet var promptSubtitle: UILabel!
30 | @IBOutlet var promptTitleSubtitleStackView: UIStackView!
31 | @IBOutlet var siteFacilityTableView: UITableView!
32 | @IBOutlet var designableViewHeight: NSLayoutConstraint!
33 |
34 | /// Show the facilities list directly if the map has no sites configured or if there is a previously selected facility.
35 | private var isShowingFacilities = false
36 | private var isSearchActive = false
37 |
38 | /// Filtered facilities and sites list based on search query.
39 | private var filteredSearchFacilities: [AGSFloorFacility] = []
40 | private var filteredSearchSites: [AGSFloorSite] = []
41 |
42 | private func filteredFacilities() -> [AGSFloorFacility] {
43 | if (isSearchActive) {
44 | return filteredSearchFacilities
45 | } else {
46 | return viewModel.facilities
47 | }
48 | }
49 |
50 | private func filteredSites() -> [AGSFloorSite] {
51 | if (isSearchActive) {
52 | return filteredSearchSites
53 | } else {
54 | return viewModel.sites
55 | }
56 | }
57 |
58 | override func viewWillAppear(_ animated: Bool) {
59 | super.viewWillAppear(animated)
60 | isShowingFacilities = (viewModel.sites.isEmpty || viewModel.selectedFacility != nil)
61 | initializeSiteFacilityTableView()
62 | }
63 |
64 | override func viewDidLoad() {
65 | super.viewDidLoad()
66 | initializeSiteFacilityTableView()
67 | initializeSiteFacilitySearchBar()
68 | initializeButtonsClickListeners()
69 |
70 | updatePromptTitle()
71 | }
72 |
73 | private func initializeButtonsClickListeners() {
74 | closeBtn.addTarget(self, action: #selector(closeSiteFacilityPrompt), for: .touchUpInside)
75 | closeBtn.isUserInteractionEnabled = true
76 | backBtn.addTarget(self, action: #selector(backButtonTapped), for: .touchUpInside)
77 | backBtn.isUserInteractionEnabled = true
78 | }
79 |
80 | @objc func closeSiteFacilityPrompt() {
81 | dismiss(animated: true)
82 | }
83 |
84 | @objc func backButtonTapped() {
85 | dismissSearchBar()
86 | isShowingFacilities = false
87 | siteFacilityTableView?.reloadData()
88 | }
89 |
90 | private func updatePromptTitle() {
91 | promptTitle.font = UIFont.boldSystemFont(ofSize: 17)
92 | promptSubtitle.isHidden = true
93 | promptSubtitle.text = ""
94 | if (isShowingFacilities) {
95 | // Add the subtitle when showing facilities
96 | promptSubtitle.isHidden = false
97 | promptSubtitle.text = "Select a Facility"
98 | promptTitle.text = viewModel.selectedSite?.name ?? ""
99 | backBtn.isHidden = false
100 | } else {
101 | promptTitle?.text = viewModel.selectedSite?.name ?? "Select a Site"
102 | backBtn?.isHidden = true
103 | }
104 | }
105 | }
106 |
107 | /// Extension for Search Bar functions.
108 | extension SiteFacilityPromptViewController: UISearchBarDelegate {
109 | private func initializeSiteFacilitySearchBar() {
110 | siteFacilitySearchBar.delegate = self
111 | resetSearchFilteredResults()
112 | siteFacilitySearchBar.tintColor = .customBlue
113 | }
114 |
115 | func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
116 | isSearchActive = true
117 | siteFacilitySearchBar.showsCancelButton = true
118 | }
119 |
120 | func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
121 | isSearchActive = false
122 | siteFacilitySearchBar.resignFirstResponder()
123 | siteFacilitySearchBar.endEditing(true)
124 | }
125 |
126 | func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
127 | isSearchActive = false
128 | siteFacilitySearchBar.text = ""
129 | siteFacilitySearchBar.resignFirstResponder()
130 | siteFacilitySearchBar.endEditing(true)
131 | }
132 |
133 | func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
134 | isSearchActive = false
135 | siteFacilitySearchBar.resignFirstResponder()
136 | siteFacilitySearchBar.endEditing(true)
137 | }
138 |
139 | private func resetSearchFilteredResults() {
140 | filteredSearchSites = viewModel.sites
141 | filteredSearchFacilities = viewModel.facilities
142 | }
143 |
144 | private func dismissSearchBar() {
145 | isSearchActive = false
146 | siteFacilitySearchBar.text = ""
147 | siteFacilitySearchBar.resignFirstResponder()
148 | siteFacilitySearchBar.endEditing(true)
149 | siteFacilitySearchBar.showsCancelButton = false
150 | }
151 | }
152 |
153 | /// Extension for the Sites and Facilities Table View.
154 | extension SiteFacilityPromptViewController: UITableViewDataSource, UITableViewDelegate {
155 | private func initializeSiteFacilityTableView() {
156 | siteFacilityTableView.delegate = self
157 | siteFacilityTableView.dataSource = self
158 | siteFacilityTableView.separatorStyle = .none
159 | siteFacilityTableView.register(UINib(nibName: "FloorFilterSiteFacilityCell", bundle: .module), forCellReuseIdentifier: "FloorFilterSiteFacilityCell")
160 | siteFacilityTableView.estimatedRowHeight = 45.0
161 | siteFacilityTableView.rowHeight = UITableView.automaticDimension
162 | }
163 |
164 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
165 | updatePromptTitle()
166 | return isShowingFacilities ? filteredFacilities().count : filteredSites().count
167 | }
168 |
169 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
170 | let cell = siteFacilityTableView.dequeueReusableCell(withIdentifier: "FloorFilterSiteFacilityCell", for: indexPath) as! SiteFacilityTableViewCell
171 | let sites = filteredSites()
172 | let facilities = filteredFacilities()
173 |
174 | // If there are no sites in the map, then directly show the facilities list.
175 | if (sites.isEmpty) {
176 | isShowingFacilities = true
177 | }
178 |
179 | cell.siteFacilityDotImg.isHidden = true
180 | cell.siteFacilityNameLabel?.font = UIFont(name: "Avenir", size: 16)
181 |
182 | if (isShowingFacilities) {
183 | cell.siteFacilityNameLabel.text = facilities[indexPath.row].name
184 | cell.accessoryType = .none
185 |
186 | // Highlight any previously selected Facility.
187 | if (cell.siteFacilityNameLabel.text == viewModel.selectedFacility?.name) {
188 | cell.siteFacilityDotImg.isHidden = false
189 | cell.siteFacilityNameLabel?.font = UIFont.boldSystemFont(ofSize: 16.0)
190 | }
191 | } else {
192 | cell.siteFacilityNameLabel?.text = sites[indexPath.row].name
193 | cell.accessoryType = .disclosureIndicator
194 |
195 | // If the user clicks on Back Button, then highlight any previously selected Site.
196 | if (cell.siteFacilityNameLabel.text == viewModel.selectedSite?.name) {
197 | cell.siteFacilityDotImg.isHidden = false
198 | cell.siteFacilityNameLabel?.font = UIFont.boldSystemFont(ofSize: 16.0)
199 | }
200 | }
201 |
202 | return cell
203 | }
204 |
205 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
206 | let cell = siteFacilityTableView.cellForRow(at: indexPath) as! SiteFacilityTableViewCell
207 | let sites = filteredSites()
208 | let facilities = filteredFacilities()
209 |
210 | cell.siteFacilityDotImg.isHidden = true
211 | cell.siteFacilityNameLabel?.font = UIFont.boldSystemFont(ofSize: 16.0)
212 |
213 | if (isShowingFacilities) {
214 | viewModel.selectedFacility = facilities[indexPath.row]
215 |
216 | // When a facility is selected, reset the previously selected level.
217 | viewModel.selectedLevel = nil
218 |
219 | // Close the prompt and zoom to the selected facility.
220 | closeSiteFacilityPrompt()
221 | viewModel.zoomToSelection()
222 | delegate?.siteFacilityIsUpdated(viewModel: viewModel)
223 |
224 | // Reset the search bar.
225 | resetSearchFilteredResults()
226 | dismissSearchBar()
227 | } else {
228 | viewModel.selectedSite = sites[indexPath.row]
229 | viewModel.selectedFacility = nil
230 | viewModel.selectedLevel = nil
231 |
232 | dismissSearchBar()
233 |
234 | // Zoom to the map to the selected site in case the user closes the prompt without selecting a facility.
235 | viewModel.zoomToSelection()
236 | delegate?.siteFacilityIsUpdated(viewModel: viewModel)
237 |
238 | // Reload the list to show the list of facilities for the selected site.
239 | isShowingFacilities = true
240 | updatePromptTitle()
241 | siteFacilityTableView.reloadData()
242 | }
243 | }
244 |
245 | /// Filter the sites or facilities data based on the search query.
246 | func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
247 | if (isShowingFacilities) {
248 | // If the search query is empty then set FilteredSearchFacilities to all the facilities in the data.
249 | filteredSearchFacilities = searchText.isEmpty ? viewModel.facilities : viewModel.facilities.filter {
250 | (facility: AGSFloorFacility) -> Bool in
251 | return facility.name.range(of: searchText, options: .caseInsensitive, range: nil, locale: nil) != nil
252 | }
253 | } else {
254 | // If the search query is empty then set FilteredSearchSites to all the sites in the data.
255 | filteredSearchSites = searchText.isEmpty ? viewModel.sites : viewModel.sites.filter {
256 | (site: AGSFloorSite) -> Bool in
257 | return site.name.range(of: searchText, options: .caseInsensitive, range: nil, locale: nil) != nil
258 | }
259 | }
260 | siteFacilityTableView.reloadData()
261 | }
262 | }
263 |
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/FloorFilter/SiteFacilityTableViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 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 ArcGIS
17 | import Foundation
18 |
19 | /// Custom cell for site/facility.
20 | class SiteFacilityTableViewCell: UITableViewCell {
21 | @IBOutlet var siteFacilityNameLabel: UILabel!
22 | @IBOutlet var siteFacilityDotImg: UIImageView!
23 | }
24 |
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/JobManager.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Esri.
2 |
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | // http://www.apache.org/licenses/LICENSE-2.0
7 |
8 | // Unless required by applicable law or agreed to in writing, software
9 | // distributed under the License is distributed on an "AS IS" BASIS,
10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | // See the License for the specific language governing permissions and
12 | // limitations under the License.
13 |
14 | import ArcGIS
15 | import Foundation
16 |
17 | internal typealias JSONDictionary = [String: Any]
18 | public typealias JobStatusHandler = (AGSJobStatus) -> Void
19 | public typealias JobCompletionHandler = (Any?, Error?) -> Void
20 |
21 | //
22 | // MARK: JobManager
23 |
24 | private let _jobManagerSharedInstance = JobManager(jobManagerID: "shared")
25 |
26 | /**
27 | The JobManager is a class that will manage serializing kicked off Jobs to the NSUserDefaults when the app is backgrounded.
28 | Then when the JobManager is re-created on launch of an app, it will deserialize the Jobs and provide them for you via it's
29 | Job's property.
30 |
31 | The JobManager works with any AGSJob subclass. Such as AGSSyncGeodatabaseJob, AGSGenerateGeodatabaseJob, AGSExportTileCacheJob, AGSEstimateTileCacheSizeJob, AGSGenerateOfflineMapJob, AGSOfflineMapSyncJob, etc.
32 |
33 | Use the shared instance of the JobManager, or create your own with a unique ID. When you kick off a Job, register it with the JobManager.
34 |
35 | For supporting background fetch you can forward the call from your AppDelegate's
36 | `func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping`
37 | function, to the same function in this class.
38 |
39 | method.
40 | */
41 | public class JobManager: NSObject {
42 | /// Default shared instance of the JobManager.
43 | public class var shared: JobManager {
44 | return _jobManagerSharedInstance
45 | }
46 |
47 | /// The JobManager ID, provided during initialization.
48 | public let jobManagerID: String
49 |
50 | /// Flag to signify that we shouldn't write to User Defaults.
51 | ///
52 | /// Used internally when reading stored `AGSJob`s from the User Defaults during init().
53 | private var suppressSaveToUserDefaults = false
54 |
55 | private var kvoContext = 0
56 |
57 | /// A dictionary of Unique IDs and `AGSJob`s that the `JobManager` is managing.
58 | public private(set) var keyedJobs = [String: AGSJob]() {
59 | willSet {
60 | // Need `self` because of a Swift bug.
61 | self.keyedJobs.values.forEach { unObserveJobStatus(job: $0) }
62 | }
63 | didSet {
64 | keyedJobs.values.forEach { observeJobStatus(job: $0) }
65 |
66 | // If there was a change, then re-store the serialized AGSJobs in UserDefaults
67 | if keyedJobs != oldValue {
68 | saveJobsToUserDefaults()
69 | }
70 | }
71 | }
72 |
73 | /// A convenience accessor to the `AGSJob`s that the `JobManager` is managing.
74 | public var jobs: [AGSJob] {
75 | return Array(keyedJobs.values)
76 | }
77 |
78 | private var jobsDefaultsKey: String {
79 | return "com.esri.arcgis.runtime.toolkit.jobManager.\(jobManagerID).jobs"
80 | }
81 |
82 | private var jobStatusObservations = [String: NSKeyValueObservation]()
83 |
84 | /// Create a JobManager with an ID.
85 | ///
86 | /// - Parameter jobManagerID: An arbitrary identifier for this JobManager.
87 | public required init(jobManagerID: String) {
88 | self.jobManagerID = jobManagerID
89 | super.init()
90 | loadJobsFromUserDefaults()
91 | }
92 |
93 | deinit {
94 | keyedJobs.values.forEach { unObserveJobStatus(job: $0) }
95 | }
96 |
97 | private func toJSON() -> JSONDictionary {
98 | return keyedJobs.compactMapValues { try? $0.toJSON() }
99 | }
100 |
101 | // Observing job status code
102 | private func observeJobStatus(job: AGSJob) {
103 | let observer = job.observe(\.status, options: [.new]) { [weak self] (_, _) in
104 | self?.saveJobsToUserDefaults()
105 | }
106 | jobStatusObservations[job.serverJobID] = observer
107 | }
108 |
109 | private func unObserveJobStatus(job: AGSJob) {
110 | if let observer = jobStatusObservations[job.serverJobID] {
111 | observer.invalidate()
112 | jobStatusObservations.removeValue(forKey: job.serverJobID)
113 | }
114 | }
115 |
116 | /// Register an `AGSJob` with the `JobManager`.
117 | ///
118 | /// - Parameter job: The AGSJob to register.
119 | /// - Returns: A unique ID for the AGSJob's registration which can be used to unregister the job.
120 | @discardableResult
121 | public func register(job: AGSJob) -> String {
122 | let jobUniqueID = NSUUID().uuidString
123 | keyedJobs[jobUniqueID] = job
124 | return jobUniqueID
125 | }
126 |
127 | /// Unregister an `AGSJob` from the `JobManager`.
128 | ///
129 | /// - Parameter job: The job to unregister.
130 | /// - Returns: `true` if the job was found, `false` otherwise.
131 | @discardableResult
132 | public func unregister(job: AGSJob) -> Bool {
133 | if let jobUniqueID = keyedJobs.first(where: { $0.value === job })?.key {
134 | keyedJobs[jobUniqueID] = nil
135 | return true
136 | }
137 | return false
138 | }
139 |
140 | /// Unregister an `AGSJob` from the `JobManager`.
141 | ///
142 | /// - Parameter jobUniqueID: The job's unique ID, returned from calling `register()`.
143 | /// - Returns: `true` if the Job was found, `false` otherwise.
144 | @discardableResult
145 | public func unregister(jobUniqueID: String) -> Bool {
146 | let removed = keyedJobs.removeValue(forKey: jobUniqueID) != nil
147 | return removed
148 | }
149 |
150 | /// Clears the finished `AGSJob`s from the `JobManager`.
151 | public func clearFinishedJobs() {
152 | keyedJobs = keyedJobs.filter {
153 | let status = $0.value.status
154 | return !(status == .failed || status == .succeeded)
155 | }
156 | }
157 |
158 | /// Checks the status for all `AGSJob`s calling a completion block when completed.
159 | ///
160 | /// - Parameter completion: A completion block that is called when the status of all `AGSJob`s has been checked. Passed `true` if all statuses were retrieves successfully, or `false` otherwise.
161 | /// - Returns: An `AGSCancelable` group that can be used to cancel the status checks.
162 | @discardableResult
163 | public func checkStatusForAllJobs(completion: @escaping (Bool) -> Void) -> AGSCancelable {
164 | let cancelGroup = CancelGroup()
165 |
166 | let group = DispatchGroup()
167 |
168 | var completedWithoutErrors = true
169 |
170 | keyedJobs.forEach {
171 | group.enter()
172 | let cancellable = $0.value.checkStatus { error in
173 | if error != nil {
174 | completedWithoutErrors = false
175 | }
176 | group.leave()
177 | }
178 | cancelGroup.children.append(cancellable)
179 | }
180 |
181 | group.notify(queue: .main) {
182 | completion(completedWithoutErrors)
183 | }
184 |
185 | return cancelGroup
186 | }
187 |
188 | /// A helper function to call from a UIApplication's delegate when using iOS's Background Fetch capabilities.
189 | ///
190 | /// Checks the status for all `AGSJob`s and calls the completion handler when done.
191 | ///
192 | /// This method can be called from:
193 | /// `func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void))`
194 | ///
195 | /// See [Apple's documentation](https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623125-application)
196 | /// for more details.
197 | ///
198 | /// - Parameters:
199 | /// - application: See [Apple's documentation](https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623125-application)
200 | /// - completionHandler: See [Apple's documentation](https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623125-application)
201 | @available(iOS, deprecated: 13.0, message: "Please use 'UIApplication.shared.beginBackgroundTask(expirationHandler:)' when kicking off your job instead")
202 | public func application(application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
203 | if keyedJobs.isEmpty {
204 | return completionHandler(.noData)
205 | } else {
206 | checkStatusForAllJobs { completedWithoutErrors in
207 | if completedWithoutErrors {
208 | completionHandler(.newData)
209 | } else {
210 | completionHandler(.failed)
211 | }
212 | }
213 | }
214 | }
215 |
216 | /// Resume all paused and not-started `AGSJob`s.
217 | ///
218 | /// An `AGSJob`'s status is `.paused` when it is created from JSON. So any `AGSJob`s that have been reloaded from User Defaults will be in the `.paused` state.
219 | ///
220 | /// See the [Tasks and Jobs](https://developers.arcgis.com/ios/programming-patterns/tasks-and-jobs/#pause-resume-or-cancel-a-job)
221 | /// guide topic for more details.
222 | ///
223 | /// - Parameters:
224 | /// - statusHandler: A callback block that is called by each active `AGSJob` when the `AGSJob`'s status changes or its messages array is updated.
225 | /// - completion: A callback block that is called by each `AGSJob` when it has completed.
226 | public func resumeAllPausedJobs(statusHandler: @escaping JobStatusHandler, completion: @escaping JobCompletionHandler) {
227 | keyedJobs.lazy.filter { $0.value.status == .paused || $0.value.status == .notStarted }.forEach {
228 | $0.value.start(statusHandler: statusHandler, completion: completion)
229 | }
230 | }
231 |
232 | /// Pauses any currently running job.
233 | public func pauseAllJobs() {
234 | keyedJobs.values.forEach {
235 | guard $0.status == .started else { return }
236 | $0.progress.pause()
237 | }
238 | }
239 |
240 | /// Saves all managed `AGSJob`s to User Defaults.
241 | ///
242 | /// This happens automatically when the `AGSJob`s are registered/unregistered.
243 | /// It also happens when an `AGSJob`'s status changes.
244 | private func saveJobsToUserDefaults() {
245 | guard !suppressSaveToUserDefaults else { return }
246 |
247 | UserDefaults.standard.set(self.toJSON(), forKey: jobsDefaultsKey)
248 | }
249 |
250 | /// Load any `AGSJob`s that have been saved to User Defaults.
251 | ///
252 | /// This happens when the `JobManager` is initialized. All `AGSJob`s will be in the `.paused` state when first restored from JSON.
253 | ///
254 | /// See the [Tasks and Jobs](https://developers.arcgis.com/ios/programming-patterns/tasks-and-jobs/#pause-resume-or-cancel-a-job)
255 | /// guide topic for more details.
256 | private func loadJobsFromUserDefaults() {
257 | if let storedJobsJSON = UserDefaults.standard.dictionary(forKey: jobsDefaultsKey) {
258 | suppressSaveToUserDefaults = true
259 | keyedJobs = storedJobsJSON.compactMapValues { $0 is JSONDictionary ? (try? AGSJob.fromJSON($0)) as? AGSJob : nil }
260 | suppressSaveToUserDefaults = false
261 | }
262 | }
263 | }
264 |
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/MapViewController.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Esri.
2 |
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | // http://www.apache.org/licenses/LICENSE-2.0
7 |
8 | // Unless required by applicable law or agreed to in writing, software
9 | // distributed under the License is distributed on an "AS IS" BASIS,
10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | // See the License for the specific language governing permissions and
12 | // limitations under the License.
13 |
14 | import UIKit
15 | import ArcGIS
16 |
17 | open class MapViewController: UIViewController {
18 | public let mapView = AGSMapView(frame: CGRect.zero)
19 |
20 | override open func viewDidLoad() {
21 | super.viewDidLoad()
22 |
23 | mapView.frame = view.bounds
24 | mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
25 | view.addSubview(mapView)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Resources/Assets.xcassets/Back.imageset/Backward132.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Esri/arcgis-runtime-toolkit-ios/3919a18208679aa47deec2f2e9218836c4bb9933/Sources/ArcGISToolkit/Resources/Assets.xcassets/Back.imageset/Backward132.png
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Resources/Assets.xcassets/Back.imageset/Backward44.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Esri/arcgis-runtime-toolkit-ios/3919a18208679aa47deec2f2e9218836c4bb9933/Sources/ArcGISToolkit/Resources/Assets.xcassets/Back.imageset/Backward44.png
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Resources/Assets.xcassets/Back.imageset/Backward88.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Esri/arcgis-runtime-toolkit-ios/3919a18208679aa47deec2f2e9218836c4bb9933/Sources/ArcGISToolkit/Resources/Assets.xcassets/Back.imageset/Backward88.png
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Resources/Assets.xcassets/Back.imageset/Contents.json:
--------------------------------------------------------------------------------
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 | }
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Resources/Assets.xcassets/CompassIcon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "iOS8_NorthCompass36_D.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "iOS8_NorthCompass72.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "iOS8_NorthCompass108.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Resources/Assets.xcassets/CompassIcon.imageset/iOS8_NorthCompass108.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Esri/arcgis-runtime-toolkit-ios/3919a18208679aa47deec2f2e9218836c4bb9933/Sources/ArcGISToolkit/Resources/Assets.xcassets/CompassIcon.imageset/iOS8_NorthCompass108.png
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Resources/Assets.xcassets/CompassIcon.imageset/iOS8_NorthCompass36_D.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Esri/arcgis-runtime-toolkit-ios/3919a18208679aa47deec2f2e9218836c4bb9933/Sources/ArcGISToolkit/Resources/Assets.xcassets/CompassIcon.imageset/iOS8_NorthCompass36_D.png
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Resources/Assets.xcassets/CompassIcon.imageset/iOS8_NorthCompass72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Esri/arcgis-runtime-toolkit-ios/3919a18208679aa47deec2f2e9218836c4bb9933/Sources/ArcGISToolkit/Resources/Assets.xcassets/CompassIcon.imageset/iOS8_NorthCompass72.png
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Resources/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Resources/Assets.xcassets/Dot.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "compression-type" : "automatic",
5 | "filename" : "fiber_manual_record_black_18dp.svg",
6 | "idiom" : "universal",
7 | "scale" : "1x"
8 | },
9 | {
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "author" : "xcode",
20 | "version" : 1
21 | },
22 | "properties" : {
23 | "preserves-vector-representation" : true
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Resources/Assets.xcassets/Dot.imageset/fiber_manual_record_black_18dp.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Resources/Assets.xcassets/Forward.imageset/Contents.json:
--------------------------------------------------------------------------------
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 | }
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Resources/Assets.xcassets/Forward.imageset/Forward132.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Esri/arcgis-runtime-toolkit-ios/3919a18208679aa47deec2f2e9218836c4bb9933/Sources/ArcGISToolkit/Resources/Assets.xcassets/Forward.imageset/Forward132.png
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Resources/Assets.xcassets/Forward.imageset/Forward44.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Esri/arcgis-runtime-toolkit-ios/3919a18208679aa47deec2f2e9218836c4bb9933/Sources/ArcGISToolkit/Resources/Assets.xcassets/Forward.imageset/Forward44.png
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Resources/Assets.xcassets/Forward.imageset/Forward88.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Esri/arcgis-runtime-toolkit-ios/3919a18208679aa47deec2f2e9218836c4bb9933/Sources/ArcGISToolkit/Resources/Assets.xcassets/Forward.imageset/Forward88.png
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Resources/Assets.xcassets/MeasureArea.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "iOS8_Toolbar_MeasureArea22.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "iOS8_Toolbar_MeasureArea44.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "iOS8_Toolbar_MeasureArea66.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Resources/Assets.xcassets/MeasureArea.imageset/iOS8_Toolbar_MeasureArea22.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Esri/arcgis-runtime-toolkit-ios/3919a18208679aa47deec2f2e9218836c4bb9933/Sources/ArcGISToolkit/Resources/Assets.xcassets/MeasureArea.imageset/iOS8_Toolbar_MeasureArea22.png
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Resources/Assets.xcassets/MeasureArea.imageset/iOS8_Toolbar_MeasureArea44.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Esri/arcgis-runtime-toolkit-ios/3919a18208679aa47deec2f2e9218836c4bb9933/Sources/ArcGISToolkit/Resources/Assets.xcassets/MeasureArea.imageset/iOS8_Toolbar_MeasureArea44.png
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Resources/Assets.xcassets/MeasureArea.imageset/iOS8_Toolbar_MeasureArea66.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Esri/arcgis-runtime-toolkit-ios/3919a18208679aa47deec2f2e9218836c4bb9933/Sources/ArcGISToolkit/Resources/Assets.xcassets/MeasureArea.imageset/iOS8_Toolbar_MeasureArea66.png
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Resources/Assets.xcassets/MeasureFeature.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "MeasureFeature22.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "MeasureFeature44.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "MeasureFeature66.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Resources/Assets.xcassets/MeasureFeature.imageset/MeasureFeature22.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Esri/arcgis-runtime-toolkit-ios/3919a18208679aa47deec2f2e9218836c4bb9933/Sources/ArcGISToolkit/Resources/Assets.xcassets/MeasureFeature.imageset/MeasureFeature22.png
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Resources/Assets.xcassets/MeasureFeature.imageset/MeasureFeature44.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Esri/arcgis-runtime-toolkit-ios/3919a18208679aa47deec2f2e9218836c4bb9933/Sources/ArcGISToolkit/Resources/Assets.xcassets/MeasureFeature.imageset/MeasureFeature44.png
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Resources/Assets.xcassets/MeasureFeature.imageset/MeasureFeature66.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Esri/arcgis-runtime-toolkit-ios/3919a18208679aa47deec2f2e9218836c4bb9933/Sources/ArcGISToolkit/Resources/Assets.xcassets/MeasureFeature.imageset/MeasureFeature66.png
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Resources/Assets.xcassets/MeasureLength.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "iOS8_Toolbar_MeasureLength22.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "iOS8_Toolbar_MeasureLength44.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "iOS8_Toolbar_MeasureLength66.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Resources/Assets.xcassets/MeasureLength.imageset/iOS8_Toolbar_MeasureLength22.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Esri/arcgis-runtime-toolkit-ios/3919a18208679aa47deec2f2e9218836c4bb9933/Sources/ArcGISToolkit/Resources/Assets.xcassets/MeasureLength.imageset/iOS8_Toolbar_MeasureLength22.png
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Resources/Assets.xcassets/MeasureLength.imageset/iOS8_Toolbar_MeasureLength44.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Esri/arcgis-runtime-toolkit-ios/3919a18208679aa47deec2f2e9218836c4bb9933/Sources/ArcGISToolkit/Resources/Assets.xcassets/MeasureLength.imageset/iOS8_Toolbar_MeasureLength44.png
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Resources/Assets.xcassets/MeasureLength.imageset/iOS8_Toolbar_MeasureLength66.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Esri/arcgis-runtime-toolkit-ios/3919a18208679aa47deec2f2e9218836c4bb9933/Sources/ArcGISToolkit/Resources/Assets.xcassets/MeasureLength.imageset/iOS8_Toolbar_MeasureLength66.png
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Resources/Assets.xcassets/Pause.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "Pause44.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "Pause88.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "Pause132.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | },
23 | "properties" : {
24 | "template-rendering-intent" : "template"
25 | }
26 | }
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Resources/Assets.xcassets/Pause.imageset/Pause132.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Esri/arcgis-runtime-toolkit-ios/3919a18208679aa47deec2f2e9218836c4bb9933/Sources/ArcGISToolkit/Resources/Assets.xcassets/Pause.imageset/Pause132.png
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Resources/Assets.xcassets/Pause.imageset/Pause44.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Esri/arcgis-runtime-toolkit-ios/3919a18208679aa47deec2f2e9218836c4bb9933/Sources/ArcGISToolkit/Resources/Assets.xcassets/Pause.imageset/Pause44.png
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Resources/Assets.xcassets/Pause.imageset/Pause88.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Esri/arcgis-runtime-toolkit-ios/3919a18208679aa47deec2f2e9218836c4bb9933/Sources/ArcGISToolkit/Resources/Assets.xcassets/Pause.imageset/Pause88.png
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Resources/Assets.xcassets/Play.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "Play44.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "Play88.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "Play132.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | },
23 | "properties" : {
24 | "template-rendering-intent" : "template"
25 | }
26 | }
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Resources/Assets.xcassets/Play.imageset/Play132.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Esri/arcgis-runtime-toolkit-ios/3919a18208679aa47deec2f2e9218836c4bb9933/Sources/ArcGISToolkit/Resources/Assets.xcassets/Play.imageset/Play132.png
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Resources/Assets.xcassets/Play.imageset/Play44.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Esri/arcgis-runtime-toolkit-ios/3919a18208679aa47deec2f2e9218836c4bb9933/Sources/ArcGISToolkit/Resources/Assets.xcassets/Play.imageset/Play44.png
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Resources/Assets.xcassets/Play.imageset/Play88.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Esri/arcgis-runtime-toolkit-ios/3919a18208679aa47deec2f2e9218836c4bb9933/Sources/ArcGISToolkit/Resources/Assets.xcassets/Play.imageset/Play88.png
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Resources/Assets.xcassets/Redo.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "iOS8_TabBar_Redo22s.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "iOS8_TabBar_Redo44s.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "iOS8_TabBar_Redo66s.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Resources/Assets.xcassets/Redo.imageset/iOS8_TabBar_Redo22s.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Esri/arcgis-runtime-toolkit-ios/3919a18208679aa47deec2f2e9218836c4bb9933/Sources/ArcGISToolkit/Resources/Assets.xcassets/Redo.imageset/iOS8_TabBar_Redo22s.png
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Resources/Assets.xcassets/Redo.imageset/iOS8_TabBar_Redo44s.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Esri/arcgis-runtime-toolkit-ios/3919a18208679aa47deec2f2e9218836c4bb9933/Sources/ArcGISToolkit/Resources/Assets.xcassets/Redo.imageset/iOS8_TabBar_Redo44s.png
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Resources/Assets.xcassets/Redo.imageset/iOS8_TabBar_Redo66s.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Esri/arcgis-runtime-toolkit-ios/3919a18208679aa47deec2f2e9218836c4bb9933/Sources/ArcGISToolkit/Resources/Assets.xcassets/Redo.imageset/iOS8_TabBar_Redo66s.png
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Resources/Assets.xcassets/Site.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "business_black_18dp (1).svg",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "business_black_24dp (1).svg",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "business_black_24dp (1)-1.svg",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | },
23 | "properties" : {
24 | "template-rendering-intent" : "template"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Resources/Assets.xcassets/Site.imageset/business_black_18dp (1).svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Resources/Assets.xcassets/Site.imageset/business_black_24dp (1)-1.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Resources/Assets.xcassets/Site.imageset/business_black_24dp (1).svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Resources/Assets.xcassets/Undo.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "iOS8_TabBar_Undo22s.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "iOS8_TabBar_Undo44s.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "iOS8_TabBar_Undo66s.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Resources/Assets.xcassets/Undo.imageset/iOS8_TabBar_Undo22s.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Esri/arcgis-runtime-toolkit-ios/3919a18208679aa47deec2f2e9218836c4bb9933/Sources/ArcGISToolkit/Resources/Assets.xcassets/Undo.imageset/iOS8_TabBar_Undo22s.png
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Resources/Assets.xcassets/Undo.imageset/iOS8_TabBar_Undo44s.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Esri/arcgis-runtime-toolkit-ios/3919a18208679aa47deec2f2e9218836c4bb9933/Sources/ArcGISToolkit/Resources/Assets.xcassets/Undo.imageset/iOS8_TabBar_Undo44s.png
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Resources/Assets.xcassets/Undo.imageset/iOS8_TabBar_Undo66s.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Esri/arcgis-runtime-toolkit-ios/3919a18208679aa47deec2f2e9218836c4bb9933/Sources/ArcGISToolkit/Resources/Assets.xcassets/Undo.imageset/iOS8_TabBar_Undo66s.png
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Resources/FloorFilterSiteFacilityCell.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/Resources/PrivacyInfo.xcprivacy:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NSPrivacyAccessedAPITypes
6 |
7 |
8 | NSPrivacyAccessedAPIType
9 | NSPrivacyAccessedAPICategoryUserDefaults
10 | NSPrivacyAccessedAPITypeReasons
11 |
12 | CA92.1
13 |
14 |
15 |
16 | NSPrivacyTrackingDomains
17 |
18 | NSPrivacyTracking
19 |
20 | NSPrivacyCollectedDataTypes
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/TableViewController.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Esri.
2 |
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | // http://www.apache.org/licenses/LICENSE-2.0
7 |
8 | // Unless required by applicable law or agreed to in writing, software
9 | // distributed under the License is distributed on an "AS IS" BASIS,
10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | // See the License for the specific language governing permissions and
12 | // limitations under the License.
13 |
14 | import UIKit
15 |
16 | open class TableViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
17 | public var cellReuseIdentifier = "cell"
18 | public var tableView = UITableView(frame: .zero)
19 |
20 | override open func viewDidLoad() {
21 | super.viewDidLoad()
22 |
23 | tableView.translatesAutoresizingMaskIntoConstraints = false
24 | view.addSubview(tableView)
25 |
26 | NSLayoutConstraint.activate([
27 | tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
28 | tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
29 | tableView.topAnchor.constraint(equalTo: view.topAnchor),
30 | tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
31 | ])
32 |
33 | tableView.delegate = self
34 | tableView.dataSource = self
35 | tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellReuseIdentifier)
36 | }
37 |
38 | open func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
39 | return 0
40 | }
41 |
42 | open func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
43 | return UITableViewCell()
44 | }
45 |
46 | public func goBack(_ completion: (() -> Void)? ) {
47 | if let nc = navigationController {
48 | nc.popViewController(animated: true)
49 | completion?()
50 | } else {
51 | self.dismiss(animated: true) {
52 | completion?()
53 | }
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/Sources/ArcGISToolkit/UnitsViewController.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Esri.
2 |
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | // http://www.apache.org/licenses/LICENSE-2.0
7 |
8 | // Unless required by applicable law or agreed to in writing, software
9 | // distributed under the License is distributed on an "AS IS" BASIS,
10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | // See the License for the specific language governing permissions and
12 | // limitations under the License.
13 |
14 | import UIKit
15 | import class ArcGIS.AGSUnit
16 |
17 | /// The protocol you implement to respond as the user interacts with the units
18 | /// view controller.
19 | public protocol UnitsViewControllerDelegate: AnyObject {
20 | /// Tells the delegate that the user has cancelled selecting a unit.
21 | ///
22 | /// - Parameter unitsViewController: The current units view controller.
23 | func unitsViewControllerDidCancel(_ unitsViewController: UnitsViewController)
24 | /// Tells the delegate that the user has selected a unit.
25 | ///
26 | /// - Parameters:
27 | /// - unitsViewController: The current units view controller.
28 | func unitsViewControllerDidSelectUnit(_ unitsViewController: UnitsViewController)
29 | }
30 |
31 | /// A view controller for selecting a unit from a list of units.
32 | public class UnitsViewController: TableViewController {
33 | /// The delegate of the units view controller.
34 | public weak var delegate: UnitsViewControllerDelegate?
35 |
36 | /// The units presented to the user.
37 | public var units = [AGSUnit]() {
38 | didSet {
39 | guard isViewLoaded else { return }
40 | tableView.reloadData()
41 | }
42 | }
43 |
44 | /// The currently selected unit.
45 | public var selectedUnit: AGSUnit? {
46 | didSet {
47 | guard selectedUnit != oldValue else { return }
48 | selectedUnitDidChange(oldValue)
49 | }
50 | }
51 |
52 | /// The units that match the search predicate or `nil` if the search field
53 | /// is empty.
54 | private var filteredUnits: [AGSUnit]? {
55 | didSet {
56 | guard filteredUnits != oldValue else { return }
57 | tableView.reloadData()
58 | }
59 | }
60 |
61 | /// Called in response to the Cancel button being tapped.
62 | @objc
63 | private func cancel() {
64 | // If the search controller is still active, the delegate will not be
65 | // able to dismiss this if they showed this modally.
66 | // (or wrapped it in a navigation controller and showed that modally)
67 | // Only do this if not being presented from a nav controller
68 | // as in that case, it causes problems when the delegate that pushed this VC
69 | // tries to pop it off the stack.
70 | if presentingViewController != nil {
71 | navigationItem.searchController?.isActive = false
72 | }
73 | delegate?.unitsViewControllerDidCancel(self)
74 | }
75 |
76 | /// Called in response to `selectedUnit` changing.
77 | ///
78 | /// - Parameter previousSelectedUnit: The previous value of `selectedUnit`.
79 | private func selectedUnitDidChange(_ previousSelectedUnit: AGSUnit?) {
80 | guard isViewLoaded else { return }
81 | var indexPaths = [IndexPath]()
82 | if let unit = previousSelectedUnit, let indexPath = indexPath(for: unit) {
83 | indexPaths.append(indexPath)
84 | }
85 | if let unit = selectedUnit, let indexPath = indexPath(for: unit) {
86 | indexPaths.append(indexPath)
87 | }
88 | guard !indexPaths.isEmpty else { return }
89 | tableView.reloadRows(at: indexPaths, with: .automatic)
90 | }
91 |
92 | override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
93 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
94 | sharedInitialization()
95 | }
96 |
97 | public required init?(coder aDecoder: NSCoder) {
98 | super.init(coder: aDecoder)
99 | sharedInitialization()
100 | }
101 |
102 | private func sharedInitialization() {
103 | title = "Units"
104 | navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(UnitsViewController.cancel))
105 | definesPresentationContext = true
106 | navigationItem.searchController = makeSearchController()
107 | }
108 |
109 | /// Creates a search controller for searching the list of units.
110 | ///
111 | /// - Returns: A configured search controller.
112 | private func makeSearchController() -> UISearchController {
113 | let searchController = UISearchController(searchResultsController: nil)
114 | searchController.obscuresBackgroundDuringPresentation = false
115 | searchController.hidesNavigationBarDuringPresentation = false
116 | searchController.searchResultsUpdater = self
117 |
118 | let searchBar = searchController.searchBar
119 | searchBar.spellCheckingType = .no
120 | searchBar.autocapitalizationType = .none
121 | searchBar.autocorrectionType = .no
122 |
123 | return searchController
124 | }
125 |
126 | override public func viewDidLoad() {
127 | super.viewDidLoad()
128 | }
129 |
130 | // MARK: TableView delegate/datasource methods
131 |
132 | public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
133 | // when the user taps on a unit
134 | //
135 | tableView.deselectRow(at: indexPath, animated: true)
136 | let unit = unitForCell(at: indexPath)
137 | guard unit != selectedUnit else { return }
138 | selectedUnit = unit
139 | // If the search controller is still active, the delegate will not be
140 | // able to dismiss this if they showed this modally.
141 | // (or wrapped it in a navigation controller and showed that modally)
142 | // Only do this if not being presented from a nav controller
143 | // as in that case, it causes problems when the delegate that pushed this VC
144 | // tries to pop it off the stack.
145 | if presentingViewController != nil {
146 | navigationItem.searchController?.isActive = false
147 | }
148 | delegate?.unitsViewControllerDidSelectUnit(self)
149 | }
150 |
151 | override public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
152 | return filteredUnits?.count ?? units.count
153 | }
154 |
155 | override public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
156 | let unit = unitForCell(at: indexPath)
157 | let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier, for: indexPath)
158 | cell.textLabel?.text = unit.pluralDisplayName
159 | cell.accessoryType = unit == selectedUnit ? .checkmark : .none
160 | return cell
161 | }
162 |
163 | // MARK: IndexPath -> Info
164 |
165 | /// Returns the index path of the cell corresponding to the given unit.
166 | ///
167 | /// - Parameter unit: A unit.
168 | /// - Returns: An index path or `nil` if there is no cell corresponding to
169 | /// the given unit.
170 | private func indexPath(for unit: AGSUnit) -> IndexPath? {
171 | if let filteredUnits = filteredUnits {
172 | if let row = filteredUnits.firstIndex(of: unit) {
173 | return IndexPath(row: row, section: 0)
174 | } else {
175 | return nil
176 | }
177 | } else if let row = units.firstIndex(of: unit) {
178 | return IndexPath(row: row, section: 0)
179 | } else {
180 | return nil
181 | }
182 | }
183 |
184 | /// The unit for the cell at the given index path.
185 | ///
186 | /// - Parameter indexPath: An index path.
187 | /// - Returns: The unit corresponding to the cell at the given index path.
188 | private func unitForCell(at indexPath: IndexPath) -> AGSUnit {
189 | return filteredUnits?[indexPath.row] ?? units[indexPath.row]
190 | }
191 | }
192 |
193 | extension UnitsViewController: UISearchResultsUpdating {
194 | public func updateSearchResults(for searchController: UISearchController) {
195 | if let text = searchController.searchBar.text?.trimmingCharacters(in: .whitespaces),
196 | !text.isEmpty {
197 | filteredUnits = units.filter {
198 | $0.pluralDisplayName.range(of: text, options: .caseInsensitive) != nil
199 | }
200 | } else {
201 | filteredUnits = nil
202 | }
203 | }
204 | }
205 |
--------------------------------------------------------------------------------
/Tests/ArcGISToolkitTests/ArcGISToolkitTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2019 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 XCTest
16 | import ArcGISToolkit
17 | import ArcGIS
18 |
19 | class ArcGISToolkitTests: XCTestCase {
20 | func testComponentCreation() {
21 | // Compass
22 | let compass = Compass(mapView: AGSMapView(frame: .zero))
23 | XCTAssertNotNil(compass)
24 |
25 | // LegendViewController
26 | var legendVC = LegendViewController.makeLegendViewController(geoView: AGSMapView(frame: .zero))
27 | XCTAssertNotNil(legendVC)
28 |
29 | legendVC = LegendViewController.makeLegendViewController()
30 | XCTAssertNotNil(legendVC)
31 |
32 | // MeasureToolbar
33 | let measureToolbar = MeasureToolbar(mapView: AGSMapView(frame: .zero))
34 | XCTAssertNotNil(measureToolbar)
35 |
36 | // Scalebar
37 | let scaleBar = Scalebar(mapView: AGSMapView(frame: .zero))
38 | XCTAssertNotNil(scaleBar)
39 |
40 | // UnitsViewController
41 | let unitsVC = UnitsViewController()
42 | XCTAssertNotNil(unitsVC)
43 |
44 | // Job Manager
45 | let jobManager = JobManager.shared
46 | XCTAssertNotNil(jobManager)
47 |
48 | // PopupController
49 | let vc = UIViewController()
50 | let popupController = PopupController(geoViewController: vc, geoView: AGSMapView(frame: .zero))
51 | XCTAssertNotNil(popupController)
52 |
53 | // TimeSlider
54 | let timeSlider = TimeSlider()
55 | XCTAssertNotNil(timeSlider)
56 |
57 | // BookarksViewController
58 | var bookmarksVC = BookmarksViewController(geoView: AGSMapView(frame: .zero))
59 | XCTAssertNotNil(bookmarksVC)
60 |
61 | let bookmark = AGSBookmark(name: "Barcelona", viewpoint: AGSViewpoint(latitude: 41.385063, longitude: 2.173404, scale: 6e5))
62 | bookmarksVC = BookmarksViewController(bookmarks: [bookmark])
63 | XCTAssertNotNil(bookmarksVC)
64 |
65 | // FloorFilterView
66 | let floorFilterVC = FloorFilterViewController.makeFloorFilterView(geoView: AGSMapView(frame: .zero))
67 | XCTAssertNotNil(floorFilterVC)
68 | }
69 | }
70 |
71 | /// Helper function to load an `AGSLoadable` object, waiting until it's loaded to return.
72 | /// - Parameter object: The loadable object.
73 | public func XCTLoad(_ object: AGSLoadable, file: StaticString = #file, line: UInt = #line) {
74 | // Wait for the object to load.
75 | let loadExp = XCTestExpectation(description: "expectation for `object.load`")
76 | object.load { (error) in
77 | XCTAssertNil(error, file: file, line: line)
78 | loadExp.fulfill()
79 | }
80 | let waitResult = XCTWaiter().wait(for: [loadExp], timeout: 5.0)
81 | XCTAssertEqual(waitResult, .completed, file: file, line: line)
82 | }
83 |
--------------------------------------------------------------------------------
/Tests/ArcGISToolkitTests/BookmarksViewControllerTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2019 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 XCTest
16 | import ArcGISToolkit
17 | import ArcGIS
18 |
19 | class BookmarksViewControllerTests: XCTestCase {
20 | override func setUp() async throws {
21 | AGSArcGISRuntimeEnvironment.apiKey = "<#API Key#>"
22 | }
23 |
24 | /// Tests the creation of the `BookmarksViewController` using a list of `AGSBookmark`.
25 | func testBookmarksList() {
26 | let bookmarks = generateBookmarks()
27 | let bookmarksVC = BookmarksViewController(bookmarks: bookmarks)
28 | XCTAssertEqual(bookmarks.count, bookmarksVC.bookmarks.count)
29 | }
30 |
31 | /// Tests the creation of the `BookmarksViewController` using an `AGSMapView` by verifying the `bookmarks` property returns the correct number of bookmarks.
32 | func testBookmarksMapView() {
33 | let mapView = AGSMapView()
34 | let map = AGSMap(basemapStyle: .arcGISStreets)
35 | mapView.map = map
36 |
37 | let bookmarks = generateBookmarks()
38 | map.bookmarks.addObjects(from: bookmarks)
39 |
40 | // Wait for the map to load. This allows the observers to be set up.
41 | XCTLoad(map)
42 |
43 | // Create the BookmarksViewController.
44 | let bookmarksVC = BookmarksViewController(geoView: mapView)
45 | XCTAssertEqual(map.bookmarks.count, bookmarksVC.bookmarks.count)
46 | }
47 |
48 | /// Tests the ability to detect changes to the map and bookmarks array in order to update the list of bookmarks.
49 | func testChangeMapAndBookmarks() {
50 | let mapView = AGSMapView()
51 | let map = AGSMap(basemapStyle: .arcGISStreets)
52 | mapView.map = map
53 |
54 | let bookmarks = generateBookmarks()
55 | map.bookmarks.addObjects(from: bookmarks)
56 |
57 | // Wait for the map to load. This allows the observers to be set up.
58 | XCTLoad(map)
59 |
60 | // Create the BookmarksViewController.
61 | let bookmarksVC = BookmarksViewController(geoView: mapView)
62 |
63 | // Change the map.
64 | let newMap = AGSMap(basemapStyle: .arcGISImagery)
65 | newMap.bookmarks.add(AGSBookmark(name: "Mysterious Desert Pattern", viewpoint: AGSViewpoint(latitude: 27.3805833, longitude: 33.6321389, scale: 6e3)))
66 | mapView.map = newMap
67 |
68 | // Wait for the map to load. This allows the observers to be set up.
69 | XCTLoad(newMap)
70 |
71 | // Check if bookmarks property is updated.
72 | XCTAssertEqual(newMap.bookmarks.count, bookmarksVC.bookmarks.count)
73 |
74 | // Add bookmark to list.
75 | mapView.map?.bookmarks.add(AGSBookmark(name: "Strange Symbol", viewpoint: AGSViewpoint(latitude: 37.401573, longitude: -116.867808, scale: 6e3)))
76 |
77 | // Wait for a bit so the bookmark-changed notification can be propagated.
78 | let exp = XCTestExpectation(description: "generic wait...")
79 | XCTWaiter().wait(for: [exp], timeout: 2.0)
80 |
81 | // Check if bookmarks property is updated.
82 | XCTAssertEqual(newMap.bookmarks.count, bookmarksVC.bookmarks.count)
83 | }
84 |
85 | /// Tests the creation of the `BookmarksViewController` using an `AGSSceneView` by verifying the `bookmarks` property returns the correct number of bookmarks
86 | func testBookmarksSceneView() {
87 | let sceneView = AGSSceneView()
88 | let scene = AGSScene(basemapStyle: .arcGISStreets)
89 | sceneView.scene = scene
90 |
91 | let bookmarks = generateBookmarks()
92 | scene.bookmarks.addObjects(from: bookmarks)
93 |
94 | // Wait for the scene to load. This allows the observers to be set up.
95 | XCTLoad(scene)
96 |
97 | // Create the BookmarksViewController.
98 | let bookmarksVC = BookmarksViewController(geoView: sceneView)
99 | XCTAssertEqual(scene.bookmarks.count, bookmarksVC.bookmarks.count)
100 | }
101 |
102 | /// Tests the ability to detect changes to the scene and bookmarks array in order to update the list of bookmarks.
103 | func testChangeSceneAndBookmarks() {
104 | let sceneView = AGSSceneView()
105 | let scene = AGSScene(basemapStyle: .arcGISStreets)
106 | sceneView.scene = scene
107 |
108 | let bookmarks = generateBookmarks()
109 | scene.bookmarks.addObjects(from: bookmarks)
110 |
111 | // Wait for the scene to load. This allows the observers to be set up.
112 | XCTLoad(scene)
113 |
114 | // Create the BookmarksViewController.
115 | let bookmarksVC = BookmarksViewController(geoView: sceneView)
116 |
117 | // Change the scene.
118 | let newScene = AGSScene(basemapStyle: .arcGISImagery)
119 | newScene.bookmarks.add(AGSBookmark(name: "Mysterious Desert Pattern", viewpoint: AGSViewpoint(latitude: 27.3805833, longitude: 33.6321389, scale: 6e3)))
120 | sceneView.scene = newScene
121 |
122 | // Wait for the scene to load. This allows the observers to be set up.
123 | XCTLoad(newScene)
124 |
125 | // Check if bookmarks property is updated.
126 | XCTAssertEqual(newScene.bookmarks.count, bookmarksVC.bookmarks.count)
127 |
128 | // Add bookmark to list.
129 | sceneView.scene?.bookmarks.add(AGSBookmark(name: "Strange Symbol", viewpoint: AGSViewpoint(latitude: 37.401573, longitude: -116.867808, scale: 6e3)))
130 |
131 | // Wait for a bit so the bookmark-changed notification can be propagated.
132 | let exp = XCTestExpectation(description: "generic wait...")
133 | XCTWaiter().wait(for: [exp], timeout: 2.0)
134 |
135 | // Check if bookmarks property is updated.
136 | XCTAssertEqual(newScene.bookmarks.count, bookmarksVC.bookmarks.count)
137 | }
138 |
139 | /// Tests changing the mapView and ensuring the `bookmarks` property is updated.
140 | func testChangeMapView() {
141 | let mapView = AGSMapView()
142 | let map = AGSMap(basemapStyle: .arcGISStreets)
143 | mapView.map = map
144 |
145 | let bookmarks = generateBookmarks()
146 | map.bookmarks.addObjects(from: bookmarks)
147 |
148 | // Wait for the map to load. This allows the observers to be set up.
149 | XCTLoad(map)
150 |
151 | // Create the BookmarksViewController.
152 | let bookmarksVC = BookmarksViewController(geoView: mapView)
153 |
154 | // Verify the bookmarks are set, so we know when we change them.
155 | XCTAssertEqual(map.bookmarks.count, bookmarksVC.bookmarks.count)
156 |
157 | // Change the mapView.
158 | let newMapView = AGSMapView()
159 | let newMap = AGSMap(basemapStyle: .arcGISImagery)
160 | newMapView.map = newMap
161 |
162 | // Create a new array of `AGSBookmark` and add to map.
163 | var newBookmarks = [AGSBookmark(name: "Mysterious Desert Pattern", viewpoint: AGSViewpoint(latitude: 27.3805833, longitude: 33.6321389, scale: 6e3))]
164 | newBookmarks.append(contentsOf: generateBookmarks())
165 | newMap.bookmarks.addObjects(from: newBookmarks)
166 |
167 | // Set the new map view on the bookmarks view controller.
168 | bookmarksVC.geoView = newMapView
169 |
170 | // Wait for the map to load. This allows the observers to be set up.
171 | XCTLoad(newMap)
172 |
173 | // Check if bookmarks property is updated.
174 | XCTAssertEqual(newMap.bookmarks.count, bookmarksVC.bookmarks.count)
175 | }
176 |
177 | /// Tests changing the sceneView and ensuring the `bookmarks` property is updated.
178 | func testChangeSceneView() {
179 | let sceneView = AGSSceneView()
180 | let scene = AGSScene(basemapStyle: .arcGISStreets)
181 | sceneView.scene = scene
182 |
183 | let bookmarks = generateBookmarks()
184 | scene.bookmarks.addObjects(from: bookmarks)
185 |
186 | // Wait for the scene to load. This allows the observers to be set up.
187 | XCTLoad(scene)
188 |
189 | // Create the BookmarksViewController.
190 | let bookmarksVC = BookmarksViewController(geoView: sceneView)
191 |
192 | // Verify the bookmarks are set, so we know when we change them.
193 | XCTAssertEqual(scene.bookmarks.count, bookmarksVC.bookmarks.count)
194 |
195 | // Change the sceneView.
196 | let newSceneView = AGSSceneView()
197 | let newScene = AGSScene(basemapStyle: .arcGISImagery)
198 | newSceneView.scene = newScene
199 |
200 | // Create a new array of `AGSBookmark` and add to map.
201 | var newBookmarks = [AGSBookmark(name: "Mysterious Desert Pattern", viewpoint: AGSViewpoint(latitude: 27.3805833, longitude: 33.6321389, scale: 6e3))]
202 | newBookmarks.append(contentsOf: generateBookmarks())
203 | newScene.bookmarks.addObjects(from: newBookmarks)
204 |
205 | // Set the new scene view on the bookmarks view controller.
206 | bookmarksVC.geoView = newSceneView
207 |
208 | // Wait for the scene to load. This allows the observers to be set up.
209 | XCTLoad(newScene)
210 |
211 | // Check if bookmarks property is updated.
212 | XCTAssertEqual(newScene.bookmarks.count, bookmarksVC.bookmarks.count)
213 | }
214 |
215 | /// Generates a list of predefined bookmarks for testing.
216 | func generateBookmarks() -> [AGSBookmark] {
217 | return [AGSBookmark(name: "Barcelona", viewpoint: AGSViewpoint(latitude: 41.385063, longitude: 2.173404, scale: 6e5)),
218 | AGSBookmark(name: "Portland", viewpoint: AGSViewpoint(latitude: 44.977753, longitude: -93.265015, scale: 6e5)),
219 | AGSBookmark(name: "Minneapolis", viewpoint: AGSViewpoint(latitude: 44.977753, longitude: -93.265015, scale: 6e5)),
220 | AGSBookmark(name: "Edinburgh", viewpoint: AGSViewpoint(latitude: 55.953251, longitude: -3.188267, scale: 6e5))]
221 | }
222 | }
223 |
--------------------------------------------------------------------------------
/Tests/ArcGISToolkitTests/FloorFilterViewModelTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 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 XCTest
16 | @testable import ArcGISToolkit
17 | import ArcGIS
18 |
19 | final class FloorFilterViewModelTests: XCTestCase {
20 | func testSitesData() throws {
21 | let portal = AGSPortal(url: URL(string: "https://indoors.maps.arcgis.com/")!, loginRequired: false)
22 | let portalItem = AGSPortalItem(portal: portal, itemID: "f133a698536f44c8884ad81f80b6cfc7")
23 | let map = AGSMap(item: portalItem)
24 | let mapView = AGSMapView()
25 | mapView.map = map
26 |
27 | XCTLoad(map)
28 | let floorManager = try XCTUnwrap(map.floorManager)
29 | XCTLoad(floorManager)
30 | let viewModel = FloorFilterViewModel()
31 | viewModel.mapView = mapView
32 | XCTAssertEqual(viewModel.sites.count, 1)
33 | }
34 |
35 | func testFacilitiesData() throws {
36 | let portal = AGSPortal(url: URL(string: "https://indoors.maps.arcgis.com/")!, loginRequired: false)
37 | let portalItem = AGSPortalItem(portal: portal, itemID: "f133a698536f44c8884ad81f80b6cfc7")
38 | let map = AGSMap(item: portalItem)
39 | let mapView = AGSMapView()
40 | mapView.map = map
41 |
42 | XCTLoad(map)
43 | let floorManager = try XCTUnwrap(map.floorManager)
44 | XCTLoad(floorManager)
45 | let viewModel = FloorFilterViewModel()
46 | viewModel.mapView = mapView
47 | viewModel.selectedSite = viewModel.sites.first
48 | XCTAssertEqual(viewModel.facilities.count, 1)
49 | }
50 |
51 | func testLevelsData() throws {
52 | let portal = AGSPortal(url: URL(string: "https://indoors.maps.arcgis.com/")!, loginRequired: false)
53 | let portalItem = AGSPortalItem(portal: portal, itemID: "f133a698536f44c8884ad81f80b6cfc7")
54 | let map = AGSMap(item: portalItem)
55 | let mapView = AGSMapView()
56 | mapView.map = map
57 |
58 | XCTLoad(map)
59 | let floorManager = try XCTUnwrap(map.floorManager)
60 | XCTLoad(floorManager)
61 | let viewModel = FloorFilterViewModel()
62 | viewModel.mapView = mapView
63 | XCTAssertEqual(viewModel.allLevels.count, 3)
64 | }
65 | }
66 |
--------------------------------------------------------------------------------