Edit features using feature forms

View on GitHub

Display and edit feature attributes using feature forms.

Screenshot of Edit features using feature forms sample

Use case

Feature forms help enhance the accuracy, efficiency, and user experience of attribute editing in your application. Forms can be authored as part of the web map using Field Maps Designer or using Map Viewer. This allows a simplified user experience to edit feature attribute data on the web map.

How to use the sample

Tap a feature on the map to open a sheet displaying the feature form. Select form elements in the list and perform edits to update the field values. Tap the submit icon to commit the changes on the web map.

How it works

  1. Create a Map using a Portal URL and item ID and pass it to a MapView.
  2. When the map is tapped, perform an identify operation to check if the tapped location is an ArcGISFeature.
  3. Create a FeatureForm object using the identified ArcGISFeature.
    • Note: If the feature's FeatureLayer, ArcGISFeatureTable, or the SubtypeSublayer has an authored FeatureFormDefinition, then this definition will be used to create the FeatureForm. If such a definition is not found, a default definition is generated.
  4. Use the FeatureForm toolkit component to display the feature form configuration by providing the created featureForm object.
  5. Optionally, you can add the validationErrors(_:) modifier to the FeatureForm toolkit component to determine the visibility of validation errors.
  6. Once edits are added to the form fields, check if the validation errors list is empty using featureForm.validationErrors to verify that there are no errors.
  7. To commit edits on the service geodatabase:
    1. Call featureForm.finishEditing() to save edits to the database.
    2. Retrieve the backing service feature table's geodatabase using serviceFeatureTable.serviceGeodatabase.
    3. Verify the service geodatabase can commit changes back to the service using serviceGeodatabase.serviceInfo.canUseServiceGeodatabaseApplyEdits.
    4. If apply edits are allowed, call serviceGeodatabase.applyEdits() to apply local edits to the online service.
    5. If edits are not allowed on the ServiceGeodatabase, then apply edits to the ServiceFeatureTable using ServiceFeatureTable.applyEdits().

Relevant API

  • ArcGISFeature
  • FeatureForm
  • FeatureLayer
  • FieldFormElement
  • GroupFormElement
  • ServiceFeatureTable
  • ServiceGeodatabase

About the data

This sample uses a feature forms enabled Feature Form Places web map, which contains fictional places in San Diego of various hotels, restaurants, and shopping centers, with relevant reviews and ratings.

Additional information

Follow the tutorial to create your own form using the Map Viewer. This sample uses the Feature Form Toolkit component. For information about setting up the toolkit, as well as code for the underlying component, visit ArcGIS Maps SDK for Swift Toolkit.

Tags

edits, feature, feature forms, form, toolkit

Sample Code

EditFeaturesUsingFeatureFormsView.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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
// Copyright 2024 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
//
//   https://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 ArcGIS
import ArcGISToolkit
import SwiftUI

struct EditFeaturesUsingFeatureFormsView: View {
    /// The map that contains the features for editing.
    @State private var map = Map(
        item: PortalItem(
            portal: .arcGISOnline(connection: .anonymous),
            id: .featureFormPlacesWebMap
        )
    )

    /// The feature form for the selected feature.
    @State private var featureForm: FeatureForm?

    /// The point on the screen where the user tapped.
    @State private var tapPoint: CGPoint?

    /// A Boolean value indicating whether the feature form panel is presented.
    @State private var isShowingFeatureForm = false

    /// A Boolean value indicating whether the discard edits alert is presented.
    @State private var isShowingDiscardEditsAlert = false

    /// A Boolean value indicating whether the feature form has any validation errors.
    @State private var hasValidationErrors = false

    /// A Boolean value indicating whether the feature form edits are being applied.
    @State private var isApplyingEdits = false

    /// The error shown in the error alert.
    @State private var error: Error?

    /// The text explaining the next step in the sample's workflow.
    private var instructionText: String {
        if !isShowingFeatureForm {
            "Tap on a feature to edit."
        } else if hasValidationErrors {
            "Fix the errors to apply the edits."
        } else if isApplyingEdits {
            "Applying edits..."
        } else {
            "Use the form to edit the feature's attributes."
        }
    }

    /// The feature layer on the map.
    private var featureLayer: FeatureLayer {
        map.operationalLayers.first as! FeatureLayer
    }

    var body: some View {
        MapViewReader { mapViewProxy in
            MapView(map: map)
                .onSingleTapGesture { screenPoint, _ in
                    if isShowingFeatureForm {
                        isShowingDiscardEditsAlert = true
                    } else {
                        tapPoint = screenPoint
                    }
                }
                .task(id: tapPoint) {
                    // Identifies the feature at the tapped point and creates a feature form from it.
                    guard let tapPoint else { return }

                    do {
                        let identifyLayerResult = try await mapViewProxy.identify(
                            on: featureLayer,
                            screenPoint: tapPoint,
                            tolerance: 10
                        )
                        if let feature = identifyLayerResult.geoElements.first as? ArcGISFeature {
                            featureLayer.selectFeature(feature)
                            featureForm = FeatureForm(feature: feature)
                            isShowingFeatureForm = true
                        }
                    } catch {
                        self.error = error
                    }
                }
                .overlay(alignment: .top) {
                    Text(instructionText)
                        .multilineTextAlignment(.center)
                        .frame(maxWidth: .infinity, alignment: .center)
                        .padding(8)
                        .background(.regularMaterial, ignoresSafeAreaEdges: .horizontal)
                }
                .floatingPanel(isPresented: $isShowingFeatureForm) { [featureForm] in
                    if let featureForm {
                        VStack {
                            featureFormToolbar

                            // Displays the feature form using the toolkit component.
                            FeatureFormView(featureForm: featureForm)
                                .padding(.horizontal)
                                .task {
                                    for await validationErrors in featureForm.$validationErrors {
                                        hasValidationErrors = !validationErrors.isEmpty
                                    }
                                }
                        }
                    }
                }
                .alert("Discard Edits", isPresented: $isShowingDiscardEditsAlert) {
                    Button("Cancel", role: .cancel) {}
                    Button("Discard", role: .destructive) {
                        featureForm?.discardEdits()
                        endEditing()
                    }
                } message: {
                    Text("Any changes made within the form will be lost.")
                }
                .errorAlert(presentingError: $error)
        }
    }

    /// The toolbar for the feature form panel.
    private var featureFormToolbar: some View {
        HStack {
            Button("Discard Edits", systemImage: "xmark.circle", role: .destructive) {
                isShowingDiscardEditsAlert = true
            }

            Spacer()

            Text("Edit Feature")

            Spacer()

            Button("Done", systemImage: "checkmark") {
                isApplyingEdits = true
            }
            .disabled(hasValidationErrors)
            .task(id: isApplyingEdits) {
                guard isApplyingEdits else { return }
                defer { isApplyingEdits = false }

                do {
                    try await applyEdits()
                    endEditing()
                } catch {
                    self.error = error
                }
            }
        }
        .fontWeight(.medium)
        .labelStyle(.iconOnly)
        .padding()
    }

    /// Closes the feature form panel and resets the feature selection.
    private func endEditing() {
        isShowingFeatureForm = false
        featureLayer.clearSelection()
    }

    /// Applies the edits made in the feature form to the feature service.
    private func applyEdits() async throws {
        guard let featureForm else { return }

        // Saves the feature form edits to the database.
        try await featureForm.finishEditing()

        guard let serviceFeatureTable = featureForm.feature.table as? ServiceFeatureTable else {
            return
        }

        // Applies the edits to the feature service using either the database or feature table.
        if let serviceGeodatabase = serviceFeatureTable.serviceGeodatabase,
           let serviceInfo = serviceGeodatabase.serviceInfo,
           serviceInfo.canUseServiceGeodatabaseApplyEdits {
            _ = try await serviceGeodatabase.applyEdits()
        } else {
            _ = try await serviceFeatureTable.applyEdits()
        }
    }
}

private extension PortalItem.ID {
    /// The ID to the "Feature Form Places" web map portal item on ArcGIS Online.
    static var featureFormPlacesWebMap: Self { .init("516e4d6aeb4c495c87c41e11274c767f")! }
}

#Preview {
    EditFeaturesUsingFeatureFormsView()
}

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