Offline edit and sync

View on GitHubSample viewer app

Synchronize offline edits with a feature service using a popup.

Map with features Select feature layers Edit features

Use case

A survey worker who works in an area without an internet connection could take a geodatabase of survey features offline at their office, make edits and add new features to the offline geodatabase in the field, and sync the updates with the online feature service after returning to the office.

How to use the sample

Tap the "Generate Geodatabase" button and then pan and zoom to position the red rectangle around the area to be taken offline. Tap "Done" and choose the feature layers to take the area and features offline. To edit features, tap to select a feature, and use the popup to edit the attributes. To sync the edits with the feature service, tap the "Sync" button. Tap "Switch to service" to reload the service area.

How it works

  1. Create an AGSGeodatabaseSyncTask from a URL to a feature service.
  2. Generate the geodatabase sync task with default parameters using AGSGeodatabaseSyncTask.defaultGenerateGeodatabaseParameters(withExtent:completion:).
  3. Create an AGSGenerateGeodatabaseJob object with AGSGeodatabaseSyncTask.generateJob(with:downloadFileURL:), passing in the parameters and a path to where the geodatabase should be downloaded locally.
  4. Start the job and get a geodatabase as a result.
  5. Set the sync direction to .bidirectional.
  6. To enable editing, load the geodatabase and get its feature tables. Create feature layers from the feature tables and add them to the map's operational layers collection.
  7. Create an AGSSyncGeodatabaseJob object with AGSGeodatabaseSyncTask.syncJob(with:geodatabase:), passing in the parameters and geodatabase as arguments.
  8. Start the sync job to synchronize the edits.
  9. To switch to service mode, unregister the geodatabase using AGSGeodatabaseSyncTask.unregisterGeodatabase(_:completion:).

Relevant API

  • AGSFeatureLayer
  • AGSFeatureTable
  • AGSGenerateGeodatabaseJob
  • AGSGenerateGeodatabaseParameters
  • AGSGeodatabaseSyncTask
  • AGSSyncGeodatabaseJob
  • AGSSyncGeodatabaseParameters
  • AGSSyncLayerOption

Offline data

This sample uses a San Francisco offline basemap tile package.

About the data

The basemap uses an offline tile package of San Francisco. The online feature service has features with wildfire information.

Tags

feature service, geodatabase, offline, synchronize

Sample Code

FeatureLayersViewController.swiftFeatureLayersViewController.swiftOfflineEditingViewController.swift
Use dark colors for code blocksCopy
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
//
// 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

class FeatureLayersViewController: UITableViewController {
    @IBOutlet weak var doneButton: UIBarButtonItem!

    /// The layer infos to display in the table view.
    var featureLayerInfos = [AGSIDInfo]() {
        didSet {
            guard isViewLoaded else { return }
            tableView.reloadData()
        }
    }
    /// The layer infos selected in the table view.
    var selectedLayerInfos: [AGSIDInfo] {
        if let indexPaths = tableView.indexPathsForSelectedRows {
            return indexPaths.map { featureLayerInfos[$0.row] }
        } else {
            return []
        }
    }

    var onCompletion: (([Int]) -> Void)?

    private func updateDoneButtonEnabledState() {
        doneButton?.isEnabled = !selectedLayerInfos.isEmpty
    }

    // MARK: - UITableViewDataSource

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return featureLayerInfos.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "FeatureLayerCell", for: indexPath)

        let layerInfo = featureLayerInfos[indexPath.row]
        cell.textLabel?.text = layerInfo.name
        if let indexPaths = tableView.indexPathsForSelectedRows, indexPaths.contains(indexPath) {
            cell.accessoryType = .checkmark
        } else {
            cell.accessoryType = .none
        }

        return cell
    }

    // MARK: - UITableViewDelegate

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.cellForRow(at: indexPath)?.accessoryType = .checkmark
        updateDoneButtonEnabledState()
    }

    override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
        tableView.cellForRow(at: indexPath)?.accessoryType = .none
        updateDoneButtonEnabledState()
    }

    // MARK: - Actions

    @IBAction func cancelAction(_ sender: UIBarButtonItem) {
        dismiss(animated: true)
    }

    @IBAction func doneAction(_ sender: UIBarButtonItem) {
        // Get selected layer ids.
        let selectedLayerIds = selectedLayerInfos.map { $0.id }

        // Run the completion handler.
        onCompletion?(selectedLayerIds)

        dismiss(animated: true)
    }
}

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