Learn how to search for places of interest, such as hotels, cafes, and gas stations using the geocoding service.
Geocoding is the process of transforming an address or place name to a location on the earth's surface. A geocoding service allows you to quickly find places that meet specific criteria.
In this tutorial, you use a picklist in the user interface to select a category of places, for example, coffee shops or gas stations. You locate all the places that match this category by accessing a geocoding service. The places are displayed on the map so that you can click on them to get further information.
ArcGIS REST APIThis tutorial uses the geocoding service. For more information, see Geocoding service.
PrerequisitesBefore starting this tutorial:
You need an ArcGIS Location Platform or ArcGIS Online account.
Your system meets the system requirements.
To access the secure ArcGIS location services used in this tutorial, you must implement API key authentication or user authentication using an ArcGIS Location Platform or an ArcGIS Online account.
You can implement API key authentication or user authentication in this tutorial. Compare the differences below:
API key authentication
Learn more in API key authentication.
User authentication
Learn more in User authentication.
Security and authentication guideTo learn more about the different types of authentication, visit Types of authentication.
Create a new API key access token with privileges to access the secure resources used in this tutorial.
Complete the Create an API key tutorial and create an API key with the following privilege(s):
Copy and paste the API key access token into a safe location. It will be used in a later step.
Create new OAuth credentials to access the secure resources used in this tutorial.
Complete the Create OAuth credentials for user authentication tutorial to obtain a Client ID and Redirect URL.
A Client ID
uniquely identifies your app on the authenticating server. If the server cannot find an app with the provided Client ID, it will not proceed with authentication.
The Redirect URL
(also referred to as a callback url) is used to identify a response from the authenticating server when the system returns control back to your app after an OAuth login. Since it does not necessarily represent a valid endpoint that a user could navigate to, the redirect URL can use a custom scheme, such as my-app://auth
. It is important to make sure the redirect URL used in your app's code matches a redirect URL configured on the authenticating server.
Copy and paste the Client ID and Redirect URL into a safe location. They will be used in a later step.
All users that access this application need account privileges to access the ArcGIS Basemap Styles service and the ArcGIS Geocoding service.
Develop or DownloadYou have two options for completing this tutorial:
Option 1: Develop the codeTo start the tutorial, complete the Display a map tutorial. This creates a map to display the Santa Monica Mountains in California using the topographic basemap from the ArcGIS Basemap Styles service.
Continue with the following instructions search for places of interest, such as hotels, cafes, and gas stations using the ArcGIS Geocoding service. First, you need to set the develop credentials in your app so that you can access the ArcGIS Geocoding service.
Set developer credentialsIf you implemented API key authentication in the Display a map tutorial, the API key access token will only have the Basemaps privilege. The Find places tutorial requires the Geocoding privilege to find places using the LocatorTask
. To create an API Key access token that has the Basemaps and Geocoding privileges, see the Set up authentication step and then follow the instructions below.
Pass your API Key access token to the ArcGISEnvironment
.
In the Project Navigator, click MainApp.swift.
Set the ArcGISEnvironment.apiKey
property with your API key access token.
MainApp.swift
Use dark colors for code blocks
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
ArcGISEnvironment.apiKey = APIKey("<#YOUR-ACCESS-TOKEN#>")
Best Practice: The access token is stored directly in the code as a convenience for this tutorial. Do not store credentials directly in source code in a production environment.
Use the Authenticator
toolkit component to manage your OAuth credentials and pass it to the ArcGISEnvironment
.
In the Project Navigator, click MainApp.swift.
Set your PortalURL
, clientID
and redirectURL
values.
MainApp.swift
Use dark colors for code blocks
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
authenticator = Authenticator(oAuthUserConfigurations: [
OAuthUserConfiguration(
portalURL: URL(string: "<#YOUR-PORTAL-URL#>")!,
clientID: ""<#YOUR-CLIENT-ID#>"",
redirectURL: URL(string: "<#YOUR-REDIRECT-URL#>")!
)
])
ArcGISEnvironment.authenticationManager.handleChallenges(using: authenticator)
Best Practice: The OAuth credentials are stored directly in the code as a convenience for this tutorial. Do not store credentials directly in source code in a production environment.
Update the mapIn Xcode, in the Project Navigator, click ContentView.swift.
Create a private extension of ContentView
and make a private class named Model
of type ObservableObject
. Add a @StateObject
variable of the Model
to the ContentView
. See the programming patterns page for more information on how to manage states.
ContentView.swift
Use dark colors for code blocks
18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 19 20 21 22 22 22 22 22 22 22 22 22 22 22 23 24 25 26 27 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 29 30 31 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 33 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 35 36 37
Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line.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
struct ContentView: View {
@StateObject private var model = Model()
@State private var map = {
let map = Map(basemapStyle: .arcGISTopographic)
map.initialViewpoint = Viewpoint(latitude: 34.02700, longitude: -118.80500, scale: 72_000)
return map
}()
}
private extension ContentView {
private class Model: ObservableObject {
}
}
Create a GraphicsOverlay
named graphicsOverlay
in the Model
class. A graphics overlay is a container for graphics.
A graphics overlay is a container for graphics. It is used with a map view to display graphics on a map. You can add more than one graphics overlay to a map view. Graphics overlays are displayed on top of all the other layers.
ContentView.swift
Use dark colors for code blocks
134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 135 136 137 138 138 138 138 138 138 138 138 138 138 138 138 138 138 138 138 138 138 138 138 138 138 138 138 138 138 138 138 138 138 138 138 138 138 138 138 139 139 139
Add line.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
private class Model: ObservableObject {
let graphicsOverlay = GraphicsOverlay()
}
Add the graphics overlay to the map view, wrap the map view inside a MapViewReader
, and expose the MapViewProxy
class in its closure. MapViewProxy
provides operations that can be performed on the map view, such as 'identify'. For more information see Perform GeoView operations.
ContentView.swift
Use dark colors for code blocks
38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 39 40 41 42 43 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 43 42 41 40 39 38 37 35 33 31 29 27 25 23 21 19 18 17 16 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 16 17 18 18 18 18 18 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 -1 -2 -3 -4 -5 -6 -6 -6 -7 -8 -9 -10 -11 -12 -12 -12 -12 -12 -12 -12 -12 -12 -12 -12 -12 -12 -12 -12 -12 -12 -12 -12 -12 -12 -12 -12 -12 -12 -12 -12 -12 -12 -12 -12 -12 -12 -12 -12
Add line. Add line. Add line. Add line. Add line.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
var body: some View {
MapViewReader { mapViewProxy in
MapView(map: map, graphicsOverlays: [model.graphicsOverlay])
}
}
A locator task is used to search for places using a geocoding service. Results from this search contain the place location and additional information (attributes). Create the locator task along with any variables and methods needed to perform the search and display the results.
In the Model, create a LocatorTask
property named locator
based on the Geocoding service.
A locator task is used to convert an address to a point (geocode) or vice-versa (reverse geocode). An address includes any type of information that distinguishes a place. A locator involves finding matching locations for a given address. Reverse-geocoding is the opposite and finds the closest address for a given point.
ContentView.swift
Use dark colors for code blocks
108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 109 110 110 110 110 110 110 110 110 110 110 110 110 110 110 110 110 110 110 110 110 110 110 110 110 110 111 112 113 114 115 116 117 118 118 118 118 118 118 118 118 118 118 118 118 118 118 118 118 118 118 118 118 118 118 118 118 118 118 118 118 118 118 118 118 119 120 121
Add line. Add line. Add line.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
private extension ContentView {
private class Model: ObservableObject {
let graphicsOverlay = GraphicsOverlay()
let locator = LocatorTask(
url: URL(string: "https://geocode-api.arcgis.com/arcgis/rest/services/World/GeocodeServer")!
)
}
}
To support the geocode operation, create an enum
named Category
in the ContentView
extension. Provide a String
named "label" and a UIColor
named "color". Each category is searched using its label
and is distinguished on the map using its associated color
.
This tutorial uses category filtering to provide accurate search results based on pre-determined place categories. Feel free to modify this list to your specific requirements.
ContentView.swift
Use dark colors for code blocks
108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 108 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 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 143 144 145
Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line.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
private extension ContentView {
enum Category: CaseIterable, Equatable {
case coffeeShop, gasStation, food, hotel, parksOutdoors
var label: String {
switch self {
case .coffeeShop: return "Coffee shop"
case .gasStation: return "Gas station"
case .food: return "Food"
case .hotel: return "Hotel"
case .parksOutdoors: return "Parks and Outdoors"
}
}
var color: UIColor {
switch self {
case .coffeeShop: return .brown
case .gasStation: return .orange
case .food: return .purple
case .hotel: return .blue
case .parksOutdoors: return .green
}
}
}
private class Model: ObservableObject {
let graphicsOverlay = GraphicsOverlay()
let locator = LocatorTask(
url: URL(string: "https://geocode-api.arcgis.com/arcgis/rest/services/World/GeocodeServer")!
)
}
}
In the ContentView
struct, create a private variable named geoViewExtent
of type Envelope
with the @State
property wrapper. This will be used to define the search location.
ContentView.swift
Use dark colors for code blocks
18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 19 20 21 22 23 24 24 24 24 24 24 24 24 24 25 26 27 28 29 30 31 32 33 34 35 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 35 34 33 32 31 30 29 27 25 23 21 19 17 15 13 11 10 9 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 9 10 11 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 11 10 9 8 7 6 5 4 3 2 1 0 -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -19 -19 -19
Add line.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
struct ContentView: View {
@StateObject private var model = Model()
@State private var geoViewExtent: Envelope?
@State private var map = {
let map = Map(basemapStyle: .arcGISTopographic)
map.initialViewpoint = Viewpoint(latitude: 34.02700, longitude: -118.80500, scale: 72_000)
return map
}()
var body: some View {
MapViewReader { mapViewProxy in
MapView(map: map, graphicsOverlays: [model.graphicsOverlay])
}
}
}
In the body
, add the onVisibleAreaChanged(perform:)
method to the map view. Set the geoViewExtent
variable to the new visible area's extent.
ContentView.swift
Use dark colors for code blocks
38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 39 40 41 42 43 44 44 44 44 44 44 44 44 45 46 47 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 49 50 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51
Add line. Add line. Add line.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
var body: some View {
MapViewReader { mapViewProxy in
MapView(map: map, graphicsOverlays: [model.graphicsOverlay])
.onVisibleAreaChanged { newVisibleArea in
geoViewExtent = newVisibleArea.extent
}
}
}
In the Model
, create a private, asynchronous method called findPlaces(forCategory:searchPoint:)
to perform the geocode search operation. The method takes a parameter of type Category
that you created in the previous step to indicate which category of places to search for and a Point
that acts as the preferred search location.
ContentView.swift
Use dark colors for code blocks
134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 134 135 136 137 138 139 140 141 142 143 144 144 144 144 144 144 144 144 144 143 142 141 140 139 138 137 136 135 134 133 132 131 130 129 129 129 129 129 130 131 132 132 132
Add line. Add line. Add line.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
private class Model: ObservableObject {
let graphicsOverlay = GraphicsOverlay()
let locator = LocatorTask(
url: URL(string: "https://geocode-api.arcgis.com/arcgis/rest/services/World/GeocodeServer")!
)
func findPlaces(forCategory category: Category, searchPoint: Point? = nil) async {
}
}
Clear the previous results by removing all graphics from the graphics overlay. Create and configure new GeocodeParameters
. Populate them with the searchPoint
parameter as the search location and add result attribute names.
ContentView.swift
Use dark colors for code blocks
142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 143 144 145 146 147 148 149 149 149 149 148 147 146 145 144 143 142 141 140 139 138 137 136 135 134 134 134 134 134 135 135 135 135 135
Add line. Add line. Add line. Add line.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
func findPlaces(forCategory category: Category, searchPoint: Point? = nil) async {
graphicsOverlay.removeAllGraphics()
let geocodeParameters = GeocodeParameters()
geocodeParameters.preferredSearchLocation = searchPoint
geocodeParameters.addResultAttributeNames(["Place_addr", "PlaceName"])
}
Perform the search query using geocode(forSearchText:using:)
. Pass in the category's label
and the geocode parameters.
ContentView.swift
Use dark colors for code blocks
142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 143 144 145 146 147 148 149 150 151 152 152 152 152 152 152 152 152 152 152 152 152 152 152 152 152 153 154 155 156 157 157 157 157 157
Add line. Add line. Add line. Add line. Add line. Add line.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
func findPlaces(forCategory category: Category, searchPoint: Point? = nil) async {
graphicsOverlay.removeAllGraphics()
let geocodeParameters = GeocodeParameters()
geocodeParameters.preferredSearchLocation = searchPoint
geocodeParameters.addResultAttributeNames(["Place_addr", "PlaceName"])
do {
let geocodeResults = try await locator.geocode(forSearchText: category.label, using: geocodeParameters)
} catch {
print(error)
}
}
Create graphics for each of the results and add them to the graphics overlay.
ContentView.swift
Use dark colors for code blocks
142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 142 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 172 172 172 172
Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line.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
func findPlaces(forCategory category: Category, searchPoint: Point? = nil) async {
graphicsOverlay.removeAllGraphics()
let geocodeParameters = GeocodeParameters()
geocodeParameters.preferredSearchLocation = searchPoint
geocodeParameters.addResultAttributeNames(["Place_addr", "PlaceName"])
do {
let geocodeResults = try await locator.geocode(forSearchText: category.label, using: geocodeParameters)
if !geocodeResults.isEmpty {
let placeSymbol = SimpleMarkerSymbol(
style: .circle,
color: category.color,
size: 10
)
placeSymbol.outline = SimpleLineSymbol(
style: .solid,
color: .white,
width: 2
)
let graphics = geocodeResults.map { Graphic(geometry: $0.displayLocation, attributes: $0.attributes, symbol: placeSymbol) }
graphicsOverlay.addGraphics(graphics)
}
} catch {
print(error)
}
}
You will add a Picker to the user interface to show categories of places to find, for example, coffee shops or gas stations. Each category will be displayed with a different color on the map.
In the ContentView
struct, add a variable of type Category
with the @State
property wrapper and give it a default value of coffeeShop
. This will indicate the currently selected category.
ContentView.swift
Use dark colors for code blocks
18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 19 20 21 22 23 24 25 26 26 26 26 26 26 26 27 28 29 30 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31
Add line.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
struct ContentView: View {
@StateObject private var model = Model()
@State private var geoViewExtent: Envelope?
@State private var selectedCategory: Category = .coffeeShop
@State private var map = {
let map = Map(basemapStyle: .arcGISTopographic)
map.initialViewpoint = Viewpoint(latitude: 34.02700, longitude: -118.80500, scale: 72_000)
return map
}()
In the ContentView
body
, add a toolbar
view modifier to the map view that places a Toolbar
at the bottom of the view where the Picker
will be contained.
ContentView.swift
Use dark colors for code blocks
38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 39 40 41 42 43 44 44 44 44 44 44 44 44 45 46 47 48 48 48 48 48 48 48 48 48 47 46 45 44 43 42 41 39 37 35 33 31 29 27 25 23 22 21 20 19 19 19 20 21 22 22 22 22 22 22 22 22 22 22 22 22 23 24 25 26 27 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28
Add line. Add line. Add line. Add line. Add line.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
var body: some View {
MapViewReader { mapViewProxy in
MapView(map: map, graphicsOverlays: [model.graphicsOverlay])
.onVisibleAreaChanged { newVisibleArea in
geoViewExtent = newVisibleArea.extent
}
.toolbar {
ToolbarItemGroup(placement: .bottomBar) {
}
}
}
}
Add a Picker
to the toolbar and label it "Choose a category". Set the selection to $selectedCategory
. This will iterate through .allCases
of Category
to populate the Picker with all the category labels. Add the .labelsHidden
modifier.
ContentView.swift
Use dark colors for code blocks
85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 84 83 82 81 80 79 78 77 76 75 74 73 72 71 70 69 68 67 66 64 62 60 58 56 54 52 49 46 43 40 37 34 31 28 25 23 21 19 17 16 15 16 17 18 19 20 21 22 23 24 25 25 25 25 25 26 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27
Add line. Add line. Add line. Add line. Add line. Add line.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
.toolbar {
ToolbarItemGroup(placement: .bottomBar) {
Picker("Choose a category", selection: $selectedCategory) {
ForEach(Category.allCases, id: \.self) { category in
Text(category.label)
}
}
.labelsHidden()
}
}
Lastly, add a .task
modifier to the Picker
that calls the model's findPlaces(forCategory:searchPoint:)
function. Pass in selectedCategory
and the geoViewExtent?.center
. This will initiate a geocode search when a category is selected.
ContentView.swift
Use dark colors for code blocks
85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 84 83 82 81 80 79 78 77 76 75 74 73 72 71 70 69 68 67 66 64 62 60 58 56 54 52 49 46 43 40 37 34 31 28 25 23 21 19 17 16 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31
Add line. Add line. Add line.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
.toolbar {
ToolbarItemGroup(placement: .bottomBar) {
Picker("Choose a category", selection: $selectedCategory) {
ForEach(Category.allCases, id: \.self) { category in
Text(category.label)
}
}
.labelsHidden()
.task(id: selectedCategory) {
await model.findPlaces(forCategory: selectedCategory, searchPoint: geoViewExtent?.center)
}
}
}
An identify operation can be used to get information about a geoelement (such as a graphic) at a location where the user has tapped on the map. A callout can be used to display this information.
In the ContentView
struct, add objects to track the map and screen locations. Create Point
and CGPoint
variables with the @State
property wrappers. Name them mapLocation
and tapLocation
, respectively.
ContentView.swift
Use dark colors for code blocks
18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 19 20 21 22 23 24 25 26 27 28 29 29 29 29 30 31 32 33 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34
Add line. Add line.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
struct ContentView: View {
@StateObject private var model = Model()
@State private var geoViewExtent: Envelope?
@State private var selectedCategory: Category = .coffeeShop
@State private var tapLocation: CGPoint?
@State private var mapLocation: Point?
@State private var map = {
let map = Map(basemapStyle: .arcGISTopographic)
map.initialViewpoint = Viewpoint(latitude: 34.02700, longitude: -118.80500, scale: 72_000)
return map
}()
Add objects to support the callout. Create CalloutPlacement
and String
variables with the @State
property wrapper. Name them calloutPlacement
and calloutText
respectively.
ContentView.swift
Use dark colors for code blocks
18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37
Add line. Add line.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
struct ContentView: View {
@StateObject private var model = Model()
@State private var geoViewExtent: Envelope?
@State private var selectedCategory: Category = .coffeeShop
@State private var tapLocation: CGPoint?
@State private var mapLocation: Point?
@State private var calloutPlacement: CalloutPlacement?
@State private var calloutText: String?
@State private var map = {
let map = Map(basemapStyle: .arcGISTopographic)
map.initialViewpoint = Viewpoint(latitude: 34.02700, longitude: -118.80500, scale: 72_000)
return map
}()
In the body
, add a .callout
modifier to the map view. Pass in $calloutPlacement
as the placement parameter. In the closure, create a Text
object using the calloutText
and provide a default String
in case it is nil.
ContentView.swift
Use dark colors for code blocks
38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75
Add line. Add line. Add line. Add line. Add line. Add line.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
var body: some View {
MapViewReader { mapViewProxy in
MapView(map: map, graphicsOverlays: [model.graphicsOverlay])
.callout(placement: $calloutPlacement.animation(.default.speed(2))) { _ in
Text(calloutText ?? "No address found.")
.font(.callout)
.padding(8)
.frame(maxWidth: 350)
}
.onVisibleAreaChanged { newVisibleArea in
geoViewExtent = newVisibleArea.extent
}
.toolbar {
ToolbarItemGroup(placement: .bottomBar) {
Picker("Choose a category", selection: $selectedCategory) {
ForEach(Category.allCases, id: \.self) { category in
Text(category.label)
}
}
.labelsHidden()
.task(id: selectedCategory) {
await model.findPlaces(forCategory: selectedCategory, searchPoint: geoViewExtent?.center)
}
}
}
}
}
Add the onSingleTapGesture(perform:)
method to the map view and set mapLocation
and tapLocation
.
ContentView.swift
Use dark colors for code blocks
38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80
Add line. Add line. Add line. Add line.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
var body: some View {
MapViewReader { mapViewProxy in
MapView(map: map, graphicsOverlays: [model.graphicsOverlay])
.callout(placement: $calloutPlacement.animation(.default.speed(2))) { _ in
Text(calloutText ?? "No address found.")
.font(.callout)
.padding(8)
.frame(maxWidth: 350)
}
.onVisibleAreaChanged { newVisibleArea in
geoViewExtent = newVisibleArea.extent
}
.onSingleTapGesture { screenPoint, mapPoint in
tapLocation = screenPoint
mapLocation = mapPoint
}
.toolbar {
ToolbarItemGroup(placement: .bottomBar) {
Picker("Choose a category", selection: $selectedCategory) {
ForEach(Category.allCases, id: \.self) { category in
Text(category.label)
}
}
.labelsHidden()
.task(id: selectedCategory) {
await model.findPlaces(forCategory: selectedCategory, searchPoint: geoViewExtent?.center)
}
}
}
}
}
Add a .task
modifier to the map view, passing in tapLocation
as the idefntifier. Ensure that the location objects are not nil.
ContentView.swift
Use dark colors for code blocks
38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 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 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85 85
Add line. Add line. Add line. Add line.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
var body: some View {
MapViewReader { mapViewProxy in
MapView(map: map, graphicsOverlays: [model.graphicsOverlay])
.callout(placement: $calloutPlacement.animation(.default.speed(2))) { _ in
Text(calloutText ?? "No address found.")
.font(.callout)
.padding(8)
.frame(maxWidth: 350)
}
.onVisibleAreaChanged { newVisibleArea in
geoViewExtent = newVisibleArea.extent
}
.onSingleTapGesture { screenPoint, mapPoint in
tapLocation = screenPoint
mapLocation = mapPoint
}
.task(id: tapLocation) {
guard let tapLocation, let mapLocation else { return }
}
.toolbar {
ToolbarItemGroup(placement: .bottomBar) {
Picker("Choose a category", selection: $selectedCategory) {
ForEach(Category.allCases, id: \.self) { category in
Text(category.label)
}
}
.labelsHidden()
.task(id: selectedCategory) {
await model.findPlaces(forCategory: selectedCategory, searchPoint: geoViewExtent?.center)
}
}
}
}
}
Perform identify(on:screenPoint:tolerance:returnPopupsOnly:maximumResults:)
on the map view proxy to identify the graphics at the tapLocation
.
ContentView.swift
Use dark colors for code blocks
60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 61 62 63 64 65 66 67 68 69 70 70 70 70 70 70 70 70 70 70 71 72 73 74 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75
Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line.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
.task(id: tapLocation) {
guard let tapLocation, let mapLocation else { return }
do {
let identifyResult = try await mapViewProxy.identify(
on: model.graphicsOverlay,
screenPoint: tapLocation,
tolerance: 12
)
} catch {
print(error)
}
}
Lastly, assign the calloutText
and calloutPlacement
variables with with attributes from the first graphic of the identify results. This change in state will trigger the callout to be displayed.
ContentView.swift
Use dark colors for code blocks
60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 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 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84 84
Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line.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
.task(id: tapLocation) {
guard let tapLocation, let mapLocation else { return }
do {
let identifyResult = try await mapViewProxy.identify(
on: model.graphicsOverlay,
screenPoint: tapLocation,
tolerance: 12
)
if let graphic = identifyResult.graphics.first {
let placeName = graphic.attributes["PlaceName"] as? String ?? "Unknown"
let placeAddress = graphic.attributes["Place_addr"] as? String ?? "no address provided"
calloutText = "\(placeName)\n\(placeAddress)"
calloutPlacement = .location(mapLocation)
} else {
calloutPlacement = nil
}
} catch {
print(error)
}
}
Press Command + R to run the app.
If you are using the Xcode simulator your system must meet these minimum requirements: macOS 14 (Sonoma), Xcode 16, iOS 18. If you are using a physical device, then refer to the system requirements.
When the app opens, use the picker to search different categories of places in the Malibu area near Los Angeles, California. You can tap one of the places and see its name and address.
Alternatively, you can download the tutorial solution, as follows.
Option 2: Download the solutionClick the Download solution
link under Solution and unzip the file to a location on your machine.
Open the .xcodeproj
file in Xcode.
Since the downloaded solution does not contain authentication credentials, you must add the developer credentials that you created in the Set up authentication section.
Set developer credentials in the solutionTo allow your app users to access ArcGIS location services, use the developer credentials that you created in the Set up authentication step to authenticate requests for resources.
Pass your API Key access token to the ArcGISEnvironment
.
In the Project Navigator, click MainApp.swift.
Set the AuthenticationMode
to .apiKey
.
MainApp.swift
Use dark colors for code blocks
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
// Change the `AuthenticationMode` to `.apiKey` if your application uses API key authentication.
private var authenticationMode: AuthenticationMode { .apiKey }
Set the apiKey
property with your API key access token.
MainApp.swift
Expand
Use dark colors for code blocks1
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
// Please enter an API key access token if your application uses API key authentication.
private let apiKey = APIKey("<#YOUR-ACCESS-TOKEN#>")
Best Practice: The access token is stored directly in the code as a convenience for this tutorial. Do not store credentials directly in source code in a production environment.
Use the Authenticator
toolkit component to manage your OAuth credentials and pass it to the ArcGISEnvironment
.
In the Project Navigator, click MainApp.swift.
Set the AuthenticationMode
to .user
.
MainApp.swift
Use dark colors for code blocks
1
2
3
// Change the `AuthenticationMode` to `.user` if your application uses OAuth credentials.
private var authenticationMode: AuthenticationMode { .user }
Set your portalURL
, clientID
and redirectURL
values.
MainApp.swift
Expand
Use dark colors for code blocks1
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
// Setup an `Authenticator` with OAuth configuration if your application uses OAuth credentials.
@ObservedObject var authenticator = Authenticator(
oAuthUserConfigurations: [
OAuthUserConfiguration(
// Please enter OAuth credentials for user authentication.
portalURL: URL(string: "<#YOUR-PORTAL-URL#>")!,
clientID: "<#YOUR-CLIENT-ID#>",
redirectURL: URL(string: "<#YOUR-REDIRECT-URL#>")!
)
]
)
Best Practice: The OAuth credentials are stored directly in the code as a convenience for this tutorial. Do not store credentials directly in source code in a production environment.
Run the solutionPress Command + R to run the app.
If you are using the Xcode simulator your system must meet these minimum requirements: macOS 14 (Sonoma), Xcode 16, iOS 18. If you are using a physical device, then refer to the system requirements.
When the app opens, use the picker to search different categories of places in the Malibu area near Los Angeles, California. You can tap one of the places and see its name and address.
What's next?Learn how to use additional API features, ArcGIS location services, and ArcGIS tools in these tutorials:
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4