Mobile map (search and route)

View on GitHub
Sample viewer app

Display maps and use locators to enable search and routing offline using a mobile map package.

Image of mobile map search and route 1 Image of mobile map search and route 2

Use case

Mobile map packages make it easy to transmit and store the necessary components for an offline map experience including transportation networks (for routing/navigation), locators (address search, forward and reverse geocoding), and maps.

A field worker might download a mobile map package to support their operations while working offline.

How to use the sample

A list of maps from a mobile map package will be displayed. If the map contains transportation networks, the list item will have a navigation icon. Tap on a map in the list to open it. If a locator task is available, tap on the map to reverse geocode the location's address. If transportation networks are available, a route will be calculated between geocode locations.

How it works

  1. Create an AGSMobileMapPackage from its path.
  2. Get a list of maps inside the package using the maps property.
  3. If the package has a locator, access it using the locatorTask property.
  4. To see if a map contains transportation networks, check each map's transportationNetworks property.

Relevant API

  • AGSGeocodeResult
  • AGSMobileMapPackage
  • AGSReverseGeocodeParameters
  • AGSRoute
  • AGSRouteParameters
  • AGSRouteResult
  • AGSRouteTask
  • AGSTransportationNetworkDataset

Offline data

This sample uses the San Francisco mobile map package and the Yellowstone mobile map package. Both are downloaded from ArcGIS Online automatically.

Tags

disconnected, field mobility, geocode, network, network analysis, offline, routing, search, transportation

Sample Code

MapPackageCell.swiftMapPackagesListViewController.swiftMobileMapViewController.swift
                                                                                                           
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
//
// Copyright 2016 Esri.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import UIKit
import ArcGIS

protocol MapPackageCellDelegate: AnyObject {
    func mapPackageCell(_ mapPackageCell: MapPackageCell, didSelectMap map: AGSMap)
}

class MapPackageCell: UITableViewCell, UICollectionViewDataSource, UICollectionViewDelegate {
    @IBOutlet var titleLabel: UILabel!
    @IBOutlet var collectionView: UICollectionView!

    @IBOutlet var collectionViewHeightConstraint: NSLayoutConstraint!
    @IBOutlet var collectionViewTopConstraint: NSLayoutConstraint!

    weak var delegate: MapPackageCellDelegate?

    var isCollapsed = true {
        didSet {
            isCollapsed ? self.collapseCell() : self.expandCell()
        }
    }

    var mapPackage: AGSMobileMapPackage! {
        didSet {
            self.loadMapPackage()
        }
    }

    func loadMapPackage() {
        self.mapPackage.load { [weak self] (error: Error?) in
            if let error = error {
                print(error.localizedDescription)
            } else {
                // update title label
                self?.titleLabel.text = self?.mapPackage.item?.title

                self?.collectionView.reloadData()
            }
        }
    }

    func expandCell() {
        self.collectionViewTopConstraint.constant = 8
        self.collectionViewHeightConstraint.constant = 110
    }

    func collapseCell() {
        self.collectionViewTopConstraint.constant = 0
        self.collectionViewHeightConstraint.constant = 0
    }

    // MARK: - UICollectionViewDataSource

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return self.mapPackage?.maps.count ?? 0
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MobileMapCell", for: indexPath)

        // label
        let label = cell.viewWithTag(11) as! UILabel
        label.text = "Map \(indexPath.item + 1)"

        // border
        cell.layer.borderColor = UIColor.black.cgColor
        cell.layer.borderWidth = 1

        // search image view
        let searchImageView = cell.viewWithTag(12) as! UIImageView
        searchImageView.isHidden = (self.mapPackage.locatorTask == nil)

        let map = self.mapPackage.maps[indexPath.item]
        // route image view
        let routeImageView = cell.viewWithTag(13) as! UIImageView
        routeImageView.isHidden = map.transportationNetworks.isEmpty

        // thumbnail
        let imageView = cell.viewWithTag(14) as! UIImageView
        imageView.image = map.item?.thumbnail?.image
        imageView.layer.borderColor = UIColor.gray.cgColor
        imageView.layer.borderWidth = 1

        return cell
    }

    // MARK: - UICollectionViewDelegate

    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        self.delegate?.mapPackageCell(self, didSelectMap: self.mapPackage.maps[indexPath.item])
    }
}

Your browser is no longer supported. Please upgrade your browser for the best experience. See our browser deprecation post for more details.