Find the location for an address, or places of interest near a location or within a specific area.
Use case
A user can input a raw address into the app's search bar and zoom to the address location. When getting directions or looking for nearby places, users may only know what the place has ("food"), the type of place ("gym"), or the generic place name ("Starbucks"), rather than the specific address. You can get suggestions and locations for these places of interest (POIs) using a natural language query. Additionally, you can filter the results to a specific area.
How to use the sample
Choose an address from the suggestions or submit your own address to show its location on the map in a callout. Tap on a result pin to display its address. If you pan away from the result area, a "Repeat Search Here" button will appear. Tap it to query again for the currently viewed area on the map.
How it works
- Create a
LocatorSearchSource
using the toolkit. - Create a
SearchView
using the toolkit with the locator search source. - Perform a search. Identify a result pin graphic in the graphics overlay and show a callout with its address.
Relevant API
- ArcGISToolkit.LocatorSearchSource
- ArcGISToolkit.SearchView
- GeocodeParameters
- LocatorTask
- SearchResult
- SearchSuggestion
- SuggestParameters
Additional information
This sample uses the World Geocoding Service. For more information, see Geocoding service from Esri Developer website.
Tags
address, businesses, geocode, locations, locator, places of interest, POI, point of interest, search, suggestions, toolkit
Sample Code
// Copyright 2022 Esri
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import ArcGIS
import ArcGISToolkit
import SwiftUI
struct SearchWithGeocodeView: View {
/// The viewpoint used by the search view to pan/zoom the map to the extent
/// of the search results.
@State private var viewpoint: Viewpoint? = Viewpoint(
center: Point(
x: -93.258133,
y: 44.986656,
spatialReference: .wgs84
),
scale: 1e6
)
/// Denotes whether the map view is navigating. Used for the repeat search
/// behavior.
@State private var isGeoViewNavigating = false
/// The current map view extent. Used to allow repeat searches after
/// panning/zooming the map.
@State private var geoViewExtent: Envelope?
/// The center for the search.
@State private var queryCenter: Point?
/// The screen point to perform an identify operation.
@State private var identifyScreenPoint: CGPoint?
/// The tap location to perform an identify operation.
@State private var identifyTapLocation: Point?
/// The placement for a graphic callout.
@State private var calloutPlacement: CalloutPlacement?
/// Provides search behavior customization.
@ObservedObject private var locatorDataSource = LocatorSearchSource(
name: "My Locator",
maximumResults: 10,
maximumSuggestions: 5
)
/// The view model for the sample.
@StateObject private var model = Model()
var body: some View {
MapViewReader { proxy in
MapView(
map: model.map,
viewpoint: viewpoint,
graphicsOverlays: [model.searchResultsOverlay]
)
.onSingleTapGesture { screenPoint, tapLocation in
identifyScreenPoint = screenPoint
identifyTapLocation = tapLocation
}
.onNavigatingChanged { isGeoViewNavigating = $0 }
.onViewpointChanged(kind: .centerAndScale) {
queryCenter = $0.targetGeometry.extent.center
}
.onVisibleAreaChanged { newVisibleArea in
// For "Repeat Search Here" behavior, use `geoViewExtent` and
// `isGeoViewNavigating` modifiers on the search view.
geoViewExtent = newVisibleArea.extent
}
.callout(placement: $calloutPlacement.animation()) { placement in
// Show the address of user tapped location graphic.
// To get the fully geocoded address, use "Place_addr".
Text(placement.geoElement?.attributes["Match_addr"] as? String ?? "Unknown Address")
.padding()
}
.task(id: identifyScreenPoint) {
guard let screenPoint = identifyScreenPoint,
// Identifies when user taps a graphic.
let identifyResult = try? await proxy.identify(
on: model.searchResultsOverlay,
screenPoint: screenPoint,
tolerance: 10
)
else {
return
}
// Creates a callout placement at the user tapped location.
calloutPlacement = identifyResult.graphics.first.flatMap { graphic in
CalloutPlacement.geoElement(graphic, tapLocation: identifyTapLocation)
}
identifyScreenPoint = nil
identifyTapLocation = nil
}
.overlay {
SearchView(
sources: [locatorDataSource],
viewpoint: $viewpoint
)
.resultsOverlay(model.searchResultsOverlay)
.queryCenter($queryCenter)
.geoViewExtent($geoViewExtent)
.isGeoViewNavigating($isGeoViewNavigating)
.onQueryChanged { query in
if query.isEmpty {
// Hides the callout when query is cleared.
calloutPlacement = nil
}
}
.padding()
}
}
}
}
private extension SearchWithGeocodeView {
/// The model used to store the geo model and other expensive objects
/// used in this view.
class Model: ObservableObject {
/// A map with imagery basemap.
let map = Map(basemapStyle: .arcGISImagery)
/// The graphics overlay used by the search toolkit component to display
/// search results on the map.
let searchResultsOverlay = GraphicsOverlay()
}
}
#Preview {
SearchWithGeocodeView()
}