Skip to content

Commit 04080d7

Browse files
authored
Merge pull request #438 from Esri/chris-webb/New-EditFeatureAttachments
[New] Edit feature attachments
2 parents 146ddb6 + 883bcab commit 04080d7

File tree

6 files changed

+437
-0
lines changed

6 files changed

+437
-0
lines changed

Samples.xcodeproj/project.pbxproj

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,14 +187,18 @@
187187
9529D1942C01676200B5C1A3 /* SelectFeaturesInSceneLayerView.swift in Copy Source Code Files */ = {isa = PBXBuildFile; fileRef = 954AEDED2C01332600265114 /* SelectFeaturesInSceneLayerView.swift */; };
188188
9537AFB42C2208B5000923C5 /* AddENCExchangeSetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9537AFB32C2208B5000923C5 /* AddENCExchangeSetView.swift */; };
189189
9537AFD72C220EF0000923C5 /* ExchangeSetwithoutUpdates in Resources */ = {isa = PBXBuildFile; fileRef = 9537AFD62C220EF0000923C5 /* ExchangeSetwithoutUpdates */; settings = {ASSET_TAGS = (AddEncExchangeSet, ); }; };
190+
9547085C2C3C719800CA8579 /* EditFeatureAttachmentsView.Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9547085B2C3C719800CA8579 /* EditFeatureAttachmentsView.Model.swift */; };
190191
954708642C3C798C00CA8579 /* AddENCExchangeSetView.swift in Copy Source Code Files */ = {isa = PBXBuildFile; fileRef = 9537AFB32C2208B5000923C5 /* AddENCExchangeSetView.swift */; };
191192
954AEDEE2C01332600265114 /* SelectFeaturesInSceneLayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 954AEDED2C01332600265114 /* SelectFeaturesInSceneLayerView.swift */; };
192193
955271612C0E6749009B1ED4 /* AddRasterFromServiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 955271602C0E6749009B1ED4 /* AddRasterFromServiceView.swift */; };
193194
955AFAC42C10FD6F009C8FE5 /* ApplyMosaicRuleToRastersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 955AFAC32C10FD6F009C8FE5 /* ApplyMosaicRuleToRastersView.swift */; };
194195
955AFAC62C110B8A009C8FE5 /* ApplyMosaicRuleToRastersView.swift in Copy Source Code Files */ = {isa = PBXBuildFile; fileRef = 955AFAC32C10FD6F009C8FE5 /* ApplyMosaicRuleToRastersView.swift */; };
196+
9579FCEA2C3360BB00FC8A1D /* EditFeatureAttachmentsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9579FCE92C3360BB00FC8A1D /* EditFeatureAttachmentsView.swift */; };
197+
9579FCEC2C33616B00FC8A1D /* EditFeatureAttachmentsView.swift in Copy Source Code Files */ = {isa = PBXBuildFile; fileRef = 9579FCE92C3360BB00FC8A1D /* EditFeatureAttachmentsView.swift */; };
195198
95A3773C2C0F93770044D1CC /* AddRasterFromServiceView.swift in Copy Source Code Files */ = {isa = PBXBuildFile; fileRef = 955271602C0E6749009B1ED4 /* AddRasterFromServiceView.swift */; };
196199
95A572192C0FDCC9006E8B48 /* ShowScaleBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95A572182C0FDCC9006E8B48 /* ShowScaleBarView.swift */; };
197200
95A5721B2C0FDD34006E8B48 /* ShowScaleBarView.swift in Copy Source Code Files */ = {isa = PBXBuildFile; fileRef = 95A572182C0FDCC9006E8B48 /* ShowScaleBarView.swift */; };
201+
95ADF34F2C3CBAE800566FF6 /* EditFeatureAttachmentsView.Model.swift in Copy Source Code Files */ = {isa = PBXBuildFile; fileRef = 9547085B2C3C719800CA8579 /* EditFeatureAttachmentsView.Model.swift */; };
198202
95D2EE0F2C334D1600683D53 /* ShowServiceAreaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95D2EE0E2C334D1600683D53 /* ShowServiceAreaView.swift */; };
199203
95DEB9B62C127A92009BEC35 /* ShowViewshedFromPointOnMapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95DEB9B52C127A92009BEC35 /* ShowViewshedFromPointOnMapView.swift */; };
200204
95DEB9B82C127B5E009BEC35 /* ShowViewshedFromPointOnMapView.swift in Copy Source Code Files */ = {isa = PBXBuildFile; fileRef = 95DEB9B52C127A92009BEC35 /* ShowViewshedFromPointOnMapView.swift */; };
@@ -518,6 +522,8 @@
518522
dstPath = "";
519523
dstSubfolderSpec = 7;
520524
files = (
525+
95ADF34F2C3CBAE800566FF6 /* EditFeatureAttachmentsView.Model.swift in Copy Source Code Files */,
526+
9579FCEC2C33616B00FC8A1D /* EditFeatureAttachmentsView.swift in Copy Source Code Files */,
521527
954708642C3C798C00CA8579 /* AddENCExchangeSetView.swift in Copy Source Code Files */,
522528
95E980742C26189E00CB8912 /* BrowseOGCAPIFeatureServiceView.swift in Copy Source Code Files */,
523529
955AFAC62C110B8A009C8FE5 /* ApplyMosaicRuleToRastersView.swift in Copy Source Code Files */,
@@ -824,9 +830,11 @@
824830
950D5B0C2C2CC35D00DF2E4E /* hydrography */ = {isa = PBXFileReference; lastKnownFileType = folder; path = hydrography; sourceTree = "<group>"; };
825831
9537AFB32C2208B5000923C5 /* AddENCExchangeSetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddENCExchangeSetView.swift; sourceTree = "<group>"; };
826832
9537AFD62C220EF0000923C5 /* ExchangeSetwithoutUpdates */ = {isa = PBXFileReference; lastKnownFileType = folder; path = ExchangeSetwithoutUpdates; sourceTree = "<group>"; };
833+
9547085B2C3C719800CA8579 /* EditFeatureAttachmentsView.Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditFeatureAttachmentsView.Model.swift; sourceTree = "<group>"; };
827834
954AEDED2C01332600265114 /* SelectFeaturesInSceneLayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectFeaturesInSceneLayerView.swift; sourceTree = "<group>"; };
828835
955271602C0E6749009B1ED4 /* AddRasterFromServiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddRasterFromServiceView.swift; sourceTree = "<group>"; };
829836
955AFAC32C10FD6F009C8FE5 /* ApplyMosaicRuleToRastersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplyMosaicRuleToRastersView.swift; sourceTree = "<group>"; };
837+
9579FCE92C3360BB00FC8A1D /* EditFeatureAttachmentsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditFeatureAttachmentsView.swift; sourceTree = "<group>"; };
830838
95A572182C0FDCC9006E8B48 /* ShowScaleBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShowScaleBarView.swift; sourceTree = "<group>"; };
831839
95D2EE0E2C334D1600683D53 /* ShowServiceAreaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShowServiceAreaView.swift; sourceTree = "<group>"; };
832840
95DEB9B52C127A92009BEC35 /* ShowViewshedFromPointOnMapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShowViewshedFromPointOnMapView.swift; sourceTree = "<group>"; };
@@ -1162,6 +1170,7 @@
11621170
E070A0A1286F3B3400F2B606 /* Download preplanned map area */,
11631171
E004A6EE284E4B7A002A1FE6 /* Download vector tiles to local cache */,
11641172
D733CA182BED980D00FBDE4C /* Edit and sync features with feature service */,
1173+
9579FCEB2C3360CA00FC8A1D /* Edit feature attachments */,
11651174
D762AF5E2BF6A7B900ECE3C7 /* Edit features with feature-linked annotation */,
11661175
D764B7DE2BE2F89D002E2F92 /* Edit geodatabase with transactions */,
11671176
D73E619D2BDB21F400457932 /* Edit with branch versioning */,
@@ -1755,6 +1764,15 @@
17551764
path = "Apply mosaic rule to rasters";
17561765
sourceTree = "<group>";
17571766
};
1767+
9579FCEB2C3360CA00FC8A1D /* Edit feature attachments */ = {
1768+
isa = PBXGroup;
1769+
children = (
1770+
9579FCE92C3360BB00FC8A1D /* EditFeatureAttachmentsView.swift */,
1771+
9547085B2C3C719800CA8579 /* EditFeatureAttachmentsView.Model.swift */,
1772+
);
1773+
path = "Edit feature attachments";
1774+
sourceTree = "<group>";
1775+
};
17581776
95A5721A2C0FDCCE006E8B48 /* Show scale bar */ = {
17591777
isa = PBXGroup;
17601778
children = (
@@ -3107,6 +3125,7 @@
31073125
D73E619E2BDB21F400457932 /* EditWithBranchVersioningView.swift in Sources */,
31083126
D7705D642AFC570700CC0335 /* FindClosestFacilityFromPointView.swift in Sources */,
31093127
E088E1572862579D00413100 /* SetSurfacePlacementModeView.swift in Sources */,
3128+
9579FCEA2C3360BB00FC8A1D /* EditFeatureAttachmentsView.swift in Sources */,
31103129
D762AF5F2BF6A7B900ECE3C7 /* EditFeaturesWithFeatureLinkedAnnotationView.swift in Sources */,
31113130
1CAF831F2A20305F000E1E60 /* ShowUtilityAssociationsView.swift in Sources */,
31123131
00E7C15C2BBE1BF000B85D69 /* SnapGeometryEditsView.swift in Sources */,
@@ -3180,6 +3199,7 @@
31803199
D7635FFB2B9277DC0044AB97 /* ConfigureClustersView.Model.swift in Sources */,
31813200
D7EAF35A2A1C023800D822C4 /* SetMinAndMaxScaleView.swift in Sources */,
31823201
1C19B4F52A578E46001D2506 /* CreateLoadReportView.Model.swift in Sources */,
3202+
9547085C2C3C719800CA8579 /* EditFeatureAttachmentsView.Model.swift in Sources */,
31833203
0042E24528E4F82C001F33D6 /* ShowViewshedFromPointInSceneView.ViewshedSettingsView.swift in Sources */,
31843204
D7DDF8532AF47C6C004352D9 /* FindRouteAroundBarriersView.swift in Sources */,
31853205
1C9B74D929DB54560038B06F /* ChangeCameraControllerView.swift in Sources */,
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// Copyright 2024 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+
//
7+
// https://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 ArcGIS
16+
import SwiftUI
17+
18+
extension EditFeatureAttachmentsView {
19+
@MainActor
20+
class Model: ObservableObject {
21+
/// A map with ArcGIS Oceans basemap style.
22+
let map = Map(basemapStyle: .arcGISOceans)
23+
24+
/// The currently selected map feature.
25+
var selectedFeature: ArcGISFeature?
26+
27+
/// The placement of the callout on the map.
28+
@Published var calloutPlacement: CalloutPlacement?
29+
30+
/// Holds the attachments of the currently selected feature.
31+
@Published private(set) var attachments: [Attachment] = []
32+
33+
/// The text shown on the callout.
34+
@Published private(set) var calloutText = ""
35+
36+
/// The detail text shown on the callout.
37+
@Published private(set) var calloutDetailText = ""
38+
39+
/// The feature layer populated with data by the feature table using the remote service url.
40+
let featureLayer = FeatureLayer(featureTable: ServiceFeatureTable(url: .featureServiceURL))
41+
42+
init() {
43+
map.addOperationalLayer(featureLayer)
44+
}
45+
46+
/// Selects the tapped feature.
47+
/// - Parameter feature: The selected feature.
48+
func selectFeature(_ feature: ArcGISFeature) async throws {
49+
selectedFeature = feature
50+
featureLayer.selectFeature(feature)
51+
try await fetchAttachmentsAndUpdate(feature)
52+
}
53+
54+
/// Updates the callout text for the selected feature.
55+
private func updateCalloutDetails(for feature: ArcGISFeature) {
56+
let title = feature.attributes["typdamage"] as? String
57+
calloutText = title ?? "Callout"
58+
calloutDetailText = "Number of attachments: \(attachments.count)"
59+
}
60+
61+
/// Adds a new attachment with the given parameters and syncs this change with the server.
62+
/// - Parameters:
63+
/// - name: The attachment name.
64+
/// - type: The attachments data type.
65+
/// - dataElement: The attachment data.
66+
func addAttachment(named name: String, type: String, dataElement: Data) async throws {
67+
if let selectedFeature,
68+
let table = selectedFeature.table as? ServiceFeatureTable,
69+
table.hasAttachments {
70+
try await selectedFeature.addAttachment(
71+
named: "Attachment.png",
72+
contentType: "png",
73+
data: dataElement
74+
)
75+
_ = try await table.applyEdits()
76+
try await fetchAttachmentsAndUpdate(selectedFeature)
77+
}
78+
}
79+
80+
/// Deletes the specified attachment and syncs the changes with the server.
81+
/// - Parameter attachment: The attachment to be deleted.
82+
func deleteAttachment(_ attachment: Attachment) async throws {
83+
if let selectedFeature,
84+
let table = selectedFeature.table as? ServiceFeatureTable,
85+
table.hasAttachments {
86+
try await selectedFeature.deleteAttachment(attachment)
87+
_ = try await table.applyEdits()
88+
try await fetchAttachmentsAndUpdate(selectedFeature)
89+
}
90+
}
91+
92+
/// Fetches attachments for the selected feature from the server.
93+
private func refreshAttachments(for feature: ArcGISFeature) async throws {
94+
if let table = feature.table as? ServiceFeatureTable,
95+
table.hasAttachments {
96+
attachments = try await feature.attachments
97+
}
98+
}
99+
100+
/// Fetches attachments from server and updates the selected feature's callout with the details.
101+
private func fetchAttachmentsAndUpdate(_ feature: ArcGISFeature) async throws {
102+
try await refreshAttachments(for: feature)
103+
updateCalloutDetails(for: feature)
104+
}
105+
}
106+
}
107+
108+
private extension URL {
109+
static var featureServiceURL: URL {
110+
URL(string: "https://sampleserver6.arcgisonline.com/arcgis/rest/services/DamageAssessment/FeatureServer/0")!
111+
}
112+
}

0 commit comments

Comments
 (0)