1414
1515import ArcGIS
1616import SwiftUI
17+ import UniformTypeIdentifiers
1718
1819extension CreateMobileGeodatabaseView {
19- /// The view model for the sample.
20+ // MARK: Model
21+
22+ /// The model used to store the geo model and other expensive objects used in the view.
2023 @MainActor
2124 class Model : ObservableObject {
22- // MARK: Properties
23-
24- /// A map with a topographic basemap centered on Harpers Ferry, West Virginia, USA.
25+ /// A map with a topographic basemap centered on Harpers Ferry, WV, USA.
2526 let map : Map = {
2627 let map = Map ( basemapStyle: . arcGISTopographic)
2728 map. initialViewpoint = Viewpoint ( latitude: 39.3238 , longitude: - 77.7332 , scale: 1e4 )
2829 return map
2930 } ( )
3031
31- /// A URL to a temporary geodatabase.
32- private let geodatabaseURL : URL
33-
34- /// A URL to a temporary directory to store the geodatabase.
35- private let directoryURL : URL
36-
37- /// The mobile geodatabase.
38- private var geodatabase : Geodatabase ?
32+ /// A geodatabase file that can be exported with the system's file exporter.
33+ let geodatabaseFile = GeodatabaseFile ( )
3934
4035 /// The feature table in the geodatabase.
4136 private var featureTable : GeodatabaseFeatureTable ?
@@ -57,125 +52,57 @@ extension CreateMobileGeodatabaseView {
5752 FieldDescription ( name: " collection_timestamp " , fieldType: . date)
5853 ] )
5954
60- // Set unnecessary properties to false.
61- description. hasAttachments = false
62- description. hasM = false
63- description. hasZ = false
64-
6555 return description
6656 } ( )
6757
6858 /// The list of features in the feature table.
69- @Published private( set) var features : [ FeatureItem ] = [ ]
59+ @Published private( set) var features = [ FeatureItem ] ( )
7060
71- /// The error shown in the error alert.
72- @Published var error : Error ?
73-
74- init ( ) {
75- // Create the temporary directory using file manager.
76- directoryURL = FileManager
77- . default
78- . temporaryDirectory
79- . appendingPathComponent ( ProcessInfo ( ) . globallyUniqueString)
80- try ? FileManager . default. createDirectory ( at: directoryURL, withIntermediateDirectories: false )
61+ /// Creates a new feature table from a geodatabase.
62+ func createFeatureTable( ) async throws {
63+ // Create a new geodatabase.
64+ try await geodatabaseFile. createGeodatabase ( )
8165
82- // Create the geodatabase path with the directory URL.
83- geodatabaseURL = directoryURL
84- . appendingPathComponent ( " LocationHistory " , isDirectory: false )
85- . appendingPathExtension ( " geodatabase " )
86- }
87-
88- deinit {
89- try ? FileManager . default. removeItem ( at: directoryURL)
90- }
91-
92- // MARK: Methods
93-
94- /// Creates a mobile geodatabase with a feature table.
95- func createGeodatabase( ) async {
96- do {
97- // Remove the geodatabase file if it already exists.
98- if FileManager . default. fileExists ( atPath: geodatabaseURL. path) {
99- try FileManager . default. removeItem ( at: geodatabaseURL)
100- }
101-
102- // Create an empty mobile geodatabase at the given URL.
103- geodatabase = try await Geodatabase . createEmpty ( fileURL: geodatabaseURL)
104-
105- // Create a new feature table in the geodatabase using a table description.
106- if let table = try await geodatabase? . makeTable ( description: tableDescription) {
107- // Load the feature table.
108- try await table. load ( )
109- featureTable = table
110-
111- // Create a feature layer using the table and add it to the map.
112- let featureLayer = FeatureLayer ( featureTable: table)
113- map. addOperationalLayer ( featureLayer)
114- }
115- } catch {
116- self . error = error
117- }
66+ // Create a feature table in the geodatabase using a table description.
67+ guard let table = try await geodatabaseFile. geodatabase? . makeTable (
68+ description: tableDescription
69+ ) else { return }
70+ featureTable = table
71+
72+ // Create a feature layer using the table and add it to the map.
73+ let featureLayer = FeatureLayer ( featureTable: table)
74+ map. addOperationalLayer ( featureLayer)
11875 }
11976
12077 /// Adds a feature to the feature table at a given map point.
12178 /// - Parameter mapPoint: The map point used to make the feature.
122- func addFeature( at mapPoint: Point ) async {
79+ func addFeature( at mapPoint: Point ) async throws {
12380 guard let featureTable else { return }
124- do {
125- // Create an attribute with the current date.
126- let attributes = [ " collection_timestamp " : Date ( ) ]
127-
128- // Create a feature with the attributes and point.
129- let feature = featureTable. makeFeature ( attributes: attributes, geometry: mapPoint)
130-
131- // Add the feature to the feature table.
132- try await featureTable. add ( feature)
133-
134- // Add the feature to the list of features.
135- if let oid = feature. attributes [ " oid " ] as? Int64 ,
136- let timeStamp = feature. attributes [ " collection_timestamp " ] as? Date {
137- features. append ( FeatureItem ( oid: oid, timestamp: timeStamp) )
138- }
139- } catch {
140- self . error = error
141- }
142- }
143-
144- /// Presents the sheet containing options to share the geodatabase.
145- func presentShareSheet( ) {
146- // Create the activity view controller with the geodatabase URL.
147- let activityViewController = UIActivityViewController (
148- activityItems: [ geodatabaseURL] ,
149- applicationActivities: nil
150- )
15181
152- // Present the activity view controller.
153- let windowScene = UIApplication . shared. connectedScenes. first as? UIWindowScene
154- windowScene? . keyWindow? . rootViewController? . present ( activityViewController, animated: true )
82+ // Create an attribute with the current date.
83+ let attributes = [ " collection_timestamp " : Date ( ) ]
15584
156- // Reset the geodatabase once it has been shared.
157- activityViewController. completionWithItemsHandler = { [ weak self] _, completed, _, error in
158- if completed {
159- Task { [ weak self] in
160- await self ? . resetGeodatabase ( )
161- }
162- } else if let error {
163- self ? . error = error
164- }
85+ // Create a feature with the attributes and point.
86+ let feature = featureTable. makeFeature ( attributes: attributes, geometry: mapPoint)
87+
88+ // Add the feature to the feature table.
89+ try await featureTable. add ( feature)
90+
91+ // Add the feature to the list of features.
92+ if let oid = feature. attributes [ " oid " ] as? Int64 ,
93+ let timeStamp = feature. attributes [ " collection_timestamp " ] as? Date {
94+ features. append ( FeatureItem ( oid: oid, timestamp: timeStamp) )
16595 }
16696 }
16797
168- /// Removes all the existing features and creates a new geodatabase .
169- private func resetGeodatabase ( ) async {
170- // Close the geodatabase to cease all adjustments .
171- geodatabase ? . close ( )
98+ /// Removes all the existing features from the map .
99+ func resetFeatures ( ) throws {
100+ // Delete the geodatabase.
101+ try geodatabaseFile . deleteGeodatabase ( )
172102
173103 // Remove the current features and layers.
174104 features. removeAll ( )
175105 map. removeAllOperationalLayers ( )
176-
177- // Create a new mobile geodatabase.
178- await createGeodatabase ( )
179106 }
180107 }
181108
@@ -186,4 +113,77 @@ extension CreateMobileGeodatabaseView {
186113 /// The collection timestamp of the the feature.
187114 let timestamp : Date
188115 }
116+
117+ // MARK: GeodatabaseFile
118+
119+ /// A geodatabase file that can be used with the native file exporter.
120+ final class GeodatabaseFile {
121+ /// The mobile geodatabase used to create the geodatabase file.
122+ private( set) var geodatabase : Geodatabase ?
123+
124+ /// A URL to the temporary geodatabase file.
125+ private let geodatabaseURL : URL
126+
127+ /// A URL to the temporary directory containing the geodatabase file.
128+ private let directoryURL : URL
129+
130+ init ( ) {
131+ // Create the temporary directory using file manager.
132+ directoryURL = FileManager . default. temporaryDirectory. appendingPathComponent (
133+ ProcessInfo ( ) . globallyUniqueString
134+ )
135+ try ? FileManager . default. createDirectory (
136+ at: directoryURL,
137+ withIntermediateDirectories: false
138+ )
139+
140+ // Create the geodatabase path with the directory URL.
141+ geodatabaseURL = directoryURL
142+ . appendingPathComponent ( " LocationHistory " , isDirectory: false )
143+ . appendingPathExtension ( " geodatabase " )
144+ }
145+
146+ deinit {
147+ try ? FileManager . default. removeItem ( at: directoryURL)
148+ }
149+
150+ /// Creates an empty mobile geodatabase file.
151+ func createGeodatabase( ) async throws {
152+ // Create an empty mobile geodatabase at the given URL.
153+ geodatabase = try await Geodatabase . createEmpty ( fileURL: geodatabaseURL)
154+ }
155+
156+ /// Deletes the geodatabase file.
157+ func deleteGeodatabase( ) throws {
158+ // Close the geodatabase to cease all adjustments.
159+ geodatabase? . close ( )
160+
161+ // Remove the geodatabase file if it exists.
162+ if FileManager . default. fileExists ( atPath: geodatabaseURL. path) {
163+ try FileManager . default. removeItem ( at: geodatabaseURL)
164+ }
165+ }
166+ }
167+ }
168+
169+ extension CreateMobileGeodatabaseView . GeodatabaseFile : FileDocument {
170+ /// The file and data types that the document reads from.
171+ static var readableContentTypes = [ UTType . geodatabase]
172+
173+ /// Creates a document and initializes it with the contents of a file.
174+ convenience init ( configuration: ReadConfiguration ) throws {
175+ fatalError ( " Loading geodatabase files is not supported by this sample. " )
176+ }
177+
178+ /// Serializes a document snapshot to a file wrapper.
179+ func fileWrapper( configuration: WriteConfiguration ) throws -> FileWrapper {
180+ return try FileWrapper ( url: geodatabaseURL)
181+ }
182+ }
183+
184+ extension UTType {
185+ /// A type that represents a geodatabase file.
186+ static var geodatabase : Self {
187+ UTType ( filenameExtension: " geodatabase " ) !
188+ }
189189}
0 commit comments