Skip to content

Commit 62fea7a

Browse files
authored
Merge pull request #369 from Esri/v.next
[Release] 200.4.0
2 parents 05b3a37 + 8597ea3 commit 62fea7a

File tree

235 files changed

+8218
-877
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

235 files changed

+8218
-877
lines changed

README.md

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,25 @@
11
# ArcGIS Maps SDK for Swift Samples
22

3-
This repository contains Swift sample code demonstrating the capabilities of [ArcGIS Maps SDK for Swift](https://developers.arcgis.com/swift/) and how to use them in your own app. The project can be opened in Xcode and run on a simulator or a device.
3+
This repository contains Swift sample code demonstrating the capabilities of the [ArcGIS Maps SDK for Swift](https://developers.arcgis.com/swift/) and how to use those capabilities in your own app. The project can be opened in Xcode and run on a simulator or a device.
4+
5+
## Features
6+
7+
* Maps - Open, create, interact with and save maps
8+
* Scenes - Visualize 3D environments and symbols
9+
* Layers - Display vector and raster data in maps and scenes
10+
* Augmented Reality - View data overlaid on the real world through your device's camera
11+
* Visualization - Show graphics, popups, callouts, sketches, and style maps with symbols and renderers
12+
* Edit and Manage Data - Add, delete, and edit features and attachments, and taking data offline
13+
* Search and Query - Find addresses, places, and points of interest
14+
* Routing and Logistics - Calculate routes between locations and around barriers
15+
* Analysis - Perform spatial analysis via geoprocessing tasks and services
16+
* Cloud and Portal - Search for web maps and securely connect to your portal
17+
* Utility Networks - Work with utility networks, performing traces and exploring network elements
418

519
## Requirements
620

7-
* [ArcGIS Maps SDK for Swift](https://developers.arcgis.com/swift/) 200.3 (or newer)
8-
* [ArcGIS Maps SDK for Swift Toolkit](https://github.com/Esri/arcgis-maps-sdk-swift-toolkit) 200.3 (or newer)
21+
* [ArcGIS Maps SDK for Swift](https://developers.arcgis.com/swift/) 200.4 (or newer)
22+
* [ArcGIS Maps SDK for Swift Toolkit](https://github.com/Esri/arcgis-maps-sdk-swift-toolkit) 200.4 (or newer)
923
* Xcode 15.0 (or newer)
1024

1125
The *ArcGIS Maps SDK for Swift Samples app* has a *Target SDK* version of *15.0*, meaning that it can run on devices with *iOS 15.0* or newer.
@@ -21,6 +35,9 @@ The *ArcGIS Maps SDK for Swift Samples app* has a *Target SDK* version of *15.0*
2135
2236
## Configuring API Keys
2337

38+
> [!IMPORTANT]
39+
> Acquire the keys from your [dashboard](https://developers.arcgis.com/dashboard). Visit the developer's website to learn more about [API keys](https://developers.arcgis.com/documentation/mapping-apis-and-services/security/api-keys/).
40+
2441
To run this app and access specific, ready-to-use services such as basemap layer, follow the steps to add an API key to a secrets file stored in the project file's directory, `$(SRCROOT)/.secrets`.
2542

2643
1. Create a hidden secrets file in the project file's directory.
@@ -29,7 +46,7 @@ To run this app and access specific, ready-to-use services such as basemap layer
2946
touch .secrets
3047
```
3148

32-
2. Add your **API Key** to the secrets file aforementioned. Adding an API key allows you to access a set of ready-to-use services, including basemaps. Acquire the keys from your [dashboard](https://developers.arcgis.com/dashboard). Visit the developer's website to learn more about [API keys](https://developers.arcgis.com/documentation/mapping-apis-and-services/security/api-keys/).
49+
2. Add your **API Key** to the aforementioned secrets file. Adding an API key allows you to access a set of ready-to-use services, including basemaps.
3350

3451
```sh
3552
echo ARCGIS_API_KEY_IOS=your-api-key >> .secrets
@@ -54,7 +71,7 @@ Find a bug or want to request a new feature? Please let us know by [creating an
5471

5572
## Licensing
5673

57-
Copyright 2022 - 2023 Esri
74+
Copyright 2022 - 2024 Esri
5875

5976
Licensed under the Apache License, Version 2.0 (the "License");
6077
you may not use this file except in compliance with the License.

Samples.xcodeproj/project.pbxproj

Lines changed: 387 additions & 49 deletions
Large diffs are not rendered by default.

Samples.xcodeproj/xcshareddata/xcschemes/Samples.xcscheme

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<Scheme
3-
LastUpgradeVersion = "1500"
3+
LastUpgradeVersion = "1530"
44
version = "1.3">
55
<BuildAction
66
parallelizeBuildables = "YES"

Scripts/CI/common.py

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
# A set of words that get omitted during letter-case checks.
2424
# This set will be updated when a special word appears in a new sample.
2525
exception_proper_nouns = {
26+
'Arcade',
2627
'ArcGIS Online',
2728
'ArcGIS Pro',
2829
'GeoPackage',
@@ -32,21 +33,19 @@
3233
'Web Mercator'
3334
}
3435

35-
# A set of category folder names for legacy support.
36+
# A set of category folder names.
3637
categories = {
37-
'Maps',
38-
'Layers',
39-
'Features',
40-
'Display information',
41-
'Search',
42-
'Edit data',
43-
'Geometry',
44-
'Route and directions',
4538
'Analysis',
46-
'Cloud and portal',
39+
'Augmented Reality',
40+
'Cloud and Portal',
41+
'Edit and Manage Data',
42+
'Layers',
43+
'Maps',
4744
'Scenes',
48-
'Utility network',
49-
'Augmented reality'
45+
'Routing and Logistics',
46+
'Search and Query',
47+
'Utility Networks',
48+
'Visualization'
5049
}
5150
# endregion
5251

@@ -378,6 +377,19 @@ def flush_to_json_string(self) -> str:
378377

379378
return json.dumps(data, indent=4, sort_keys=True)
380379

380+
def check_category(self) -> None:
381+
"""
382+
Check if
383+
1. metadata contains a category.
384+
2. category is valid.
385+
386+
:return: None. Throws if exception occurs.
387+
"""
388+
if not self.category:
389+
raise Exception(f'Error category - Missing category.')
390+
elif self.category not in categories:
391+
raise Exception(f'Error category - Invalid category - "{self.category}".')
392+
381393

382394
class Readme:
383395
essential_headers = {

Scripts/CI/metadata_checker.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,12 @@ def run_check(path: str) -> None:
7878
diff = '\n'.join(unified_diff(expected, actual))
7979
raise Exception(f'Error inconsistent metadata - {path} - {diff}')
8080

81+
# 4. Check category.
82+
try:
83+
checker.check_category()
84+
except Exception as err:
85+
raise Exception(f'{checker.folder_path} - {err}')
86+
8187

8288
def all_samples(path: str):
8389
"""

Scripts/DowloadPortalItemData.swift

Lines changed: 78 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
// A mapping of item IDs to filenames is maintained in the download directory.
2020
// This mapping efficiently checks whether an item has already been downloaded.
2121
// If an item already exists, it will skip that item.
22+
// To delete and re-downloaded an item, remove its entry from the plist.
2223

2324
import Foundation
2425

@@ -31,7 +32,7 @@ struct SampleDependency: Decodable {
3132
}
3233

3334
/// A Portal Item and its data URL.
34-
struct PortalItem {
35+
struct PortalItem: Hashable {
3536
static let arcGISOnlinePortalURL = URL(string: "https://www.arcgis.com")!
3637

3738
/// The identifier of the item.
@@ -125,61 +126,47 @@ func uncompressArchive(at sourceURL: URL, to destinationURL: URL) throws {
125126
process.waitUntilExit()
126127
}
127128

128-
/// Downloads file from portal and write the file(s) to appropriate path(s).
129+
/// Downloads a file from a URL to a given location.
129130
/// - Parameters:
130131
/// - sourceURL: The portal URL to the resource.
131-
/// - downloadDirectory: The directory that stores downloaded data.
132-
/// - completion: A closure to handle the results.
133-
func downloadFile(at sourceURL: URL, to downloadDirectory: URL, completion: @escaping (Result<URL, Error>) -> Void) {
134-
let downloadTaskCompleted = { (temporaryURL: URL?, response: URLResponse?, error: Error?) in
135-
if let temporaryURL = temporaryURL,
136-
let response = response,
137-
let suggestedFilename = response.suggestedFilename {
138-
do {
139-
let downloadName: String
140-
let isArchive = (suggestedFilename as NSString).pathExtension == "zip"
141-
// If the downloaded file is an archive and contains
142-
// - 1 file, use the name of that file.
143-
// - multiple files, use the suggested filename (*.zip).
144-
// If it is not an archive, use the server suggested filename.
145-
if isArchive {
146-
let count = try count(ofFilesInArchiveAt: temporaryURL)
147-
if count == 1 {
148-
downloadName = try name(ofFileInArchiveAt: temporaryURL)
149-
} else {
150-
downloadName = suggestedFilename
151-
}
152-
} else {
153-
downloadName = suggestedFilename
154-
}
155-
156-
let downloadURL = downloadDirectory.appendingPathComponent(downloadName, isDirectory: false)
157-
158-
if FileManager.default.fileExists(atPath: downloadURL.path) {
159-
try FileManager.default.removeItem(at: downloadURL)
160-
}
161-
162-
if isArchive {
163-
let extractURL = downloadURL.pathExtension == "zip"
164-
// Uncompresses to directory named after archive.
165-
? downloadURL.deletingPathExtension()
166-
// Uncompresses to appropriate subdirectory.
167-
: downloadURL.deletingLastPathComponent()
168-
try uncompressArchive(at: temporaryURL, to: extractURL)
169-
} else {
170-
try FileManager.default.moveItem(at: temporaryURL, to: downloadURL)
171-
}
172-
173-
completion(.success(downloadURL))
174-
} catch {
175-
completion(.failure(error))
176-
}
177-
} else if let error = error {
178-
completion(.failure(error))
132+
/// - downloadDirectory: The directory to store the downloaded data in.
133+
/// - Throws: Exceptions when downloading, naming, uncompressing, and moving the file.
134+
/// - Returns: The name of the downloaded file.
135+
func downloadFile(from sourceURL: URL, to downloadDirectory: URL) async throws -> String? {
136+
let (temporaryURL, response) = try await URLSession.shared.download(from: sourceURL)
137+
138+
guard let suggestedFilename = response.suggestedFilename else { return nil }
139+
let isArchive = NSString(string: suggestedFilename).pathExtension == "zip"
140+
141+
let downloadName: String = try {
142+
// If the downloaded file is an archive and contains
143+
// - 1 file, use the name of that file.
144+
// - multiple files, use the server suggested filename (*.zip).
145+
// If it is not an archive, use the server suggested filename.
146+
if isArchive,
147+
try count(ofFilesInArchiveAt: temporaryURL) == 1 {
148+
return try name(ofFileInArchiveAt: temporaryURL)
149+
} else {
150+
return suggestedFilename
179151
}
152+
}()
153+
let downloadURL = downloadDirectory.appendingPathComponent(downloadName, isDirectory: false)
154+
155+
try? FileManager.default.removeItem(at: downloadURL)
156+
157+
if isArchive {
158+
let extractURL = downloadURL.pathExtension == "zip"
159+
// Uncompresses to directory named after archive.
160+
? downloadURL.deletingPathExtension()
161+
// Uncompresses to appropriate subdirectory.
162+
: downloadURL.deletingLastPathComponent()
163+
164+
try uncompressArchive(at: temporaryURL, to: extractURL)
165+
} else {
166+
try FileManager.default.moveItem(at: temporaryURL, to: downloadURL)
180167
}
181-
let downloadTask = URLSession.shared.downloadTask(with: sourceURL, completionHandler: downloadTaskCompleted)
182-
downloadTask.resume()
168+
169+
return downloadName
183170
}
184171

185172
// MARK: Script Entry
@@ -207,7 +194,7 @@ if !FileManager.default.fileExists(atPath: downloadDirectoryURL.path) {
207194
}
208195

209196
/// Portal Items created from iterating through all metadata's "offline\_data".
210-
let portalItems: [PortalItem] = {
197+
let portalItems: Set<PortalItem> = {
211198
do {
212199
// Finds all subdirectories under the root Samples directory.
213200
let sampleSubDirectories = try FileManager.default
@@ -218,7 +205,7 @@ let portalItems: [PortalItem] = {
218205
// Omit the decoding errors from samples that don't have dependencies.
219206
let sampleDependencies = sampleJSONs
220207
.compactMap { try? parseJSON(at: $0) }
221-
return sampleDependencies.flatMap(\.offlineData)
208+
return Set(sampleDependencies.lazy.flatMap(\.offlineData))
222209
} catch {
223210
print("error: Error decoding Samples dependencies: \(error.localizedDescription)")
224211
exit(1)
@@ -241,39 +228,54 @@ let previousDownloadedItems: DownloadedItems = {
241228
}()
242229
var downloadedItems = previousDownloadedItems
243230

244-
// Asynchronously downloads portal items.
245-
let dispatchGroup = DispatchGroup()
246-
247-
portalItems.forEach { portalItem in
248-
let destinationURL = downloadDirectoryURL.appendingPathComponent(portalItem.identifier, isDirectory: true)
249-
// Checks if a directory exists or not, to see if an item is already downloaded.
250-
if FileManager.default.fileExists(atPath: destinationURL.path) {
251-
print("info: Item \(portalItem.identifier) has already been downloaded.")
252-
} else {
231+
await withTaskGroup(of: Void.self) { group in
232+
for portalItem in portalItems {
233+
let destinationURL = downloadDirectoryURL.appendingPathComponent(
234+
portalItem.identifier,
235+
isDirectory: true
236+
)
237+
238+
// Checks to see if an item needs downloading.
239+
guard downloadedItems[portalItem.identifier] == nil ||
240+
!FileManager.default.fileExists(atPath: destinationURL.path) else {
241+
print("note: Item already downloaded: \(portalItem.identifier)")
242+
continue
243+
}
244+
245+
// Deletes the directory when the item is not in the plist.
246+
try? FileManager.default.removeItem(at: destinationURL)
247+
253248
do {
254-
// Creates an enclosing directory with portal item ID as its name.
255-
try FileManager.default.createDirectory(at: destinationURL, withIntermediateDirectories: false)
249+
// Creates an enclosing directory with the portal item ID as its name.
250+
try FileManager.default.createDirectory(
251+
at: destinationURL,
252+
withIntermediateDirectories: false
253+
)
256254
} catch {
257-
print("error: Error creating download directory: \(error.localizedDescription).")
255+
print("error: Error creating download directory: \(error.localizedDescription)")
258256
exit(1)
259257
}
260-
print("info: Downloading item \(portalItem.identifier)")
261-
fflush(stdout)
262-
dispatchGroup.enter()
263-
downloadFile(at: portalItem.dataURL, to: destinationURL) { result in
264-
switch result {
265-
case .success(let url):
266-
downloadedItems[portalItem.identifier] = url.lastPathComponent
267-
dispatchGroup.leave()
268-
case .failure(let error):
258+
259+
group.addTask {
260+
do {
261+
guard let downloadName = try await downloadFile(
262+
from: portalItem.dataURL,
263+
to: destinationURL
264+
) else { return }
265+
print("note: Downloaded item: \(portalItem.identifier)")
266+
fflush(stdout)
267+
268+
_ = await MainActor.run {
269+
downloadedItems.updateValue(downloadName, forKey: portalItem.identifier)
270+
}
271+
} catch {
269272
print("error: Error downloading item \(portalItem.identifier): \(error.localizedDescription)")
270273
URLSession.shared.invalidateAndCancel()
271274
exit(1)
272275
}
273276
}
274277
}
275278
}
276-
dispatchGroup.wait()
277279

278280
// Updates the downloaded items property list record if needed.
279281
if downloadedItems != previousDownloadedItems {

Scripts/GenerateSampleViewSourceCode.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,11 @@ private let arrayRepresentation = """
143143
[
144144
\(entries)
145145
]
146+
#if targetEnvironment(macCatalyst) || targetEnvironment(simulator)
147+
// Exclude AR samples from Mac Catalyst and Simulator targets
148+
// as they don't have camera and sensors available.
149+
.filter { $0.category != "Augmented Reality" }
150+
#endif
146151
"""
147152

148153
do {
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"images" : [
3+
{
4+
"idiom" : "universal",
5+
"scale" : "1x"
6+
},
7+
{
8+
"filename" : "[email protected]",
9+
"idiom" : "universal",
10+
"scale" : "2x"
11+
},
12+
{
13+
"filename" : "[email protected]",
14+
"idiom" : "universal",
15+
"scale" : "3x"
16+
}
17+
],
18+
"info" : {
19+
"author" : "xcode",
20+
"version" : 1
21+
}
22+
}
129 KB
Loading
248 KB
Loading

0 commit comments

Comments
 (0)