Hi,
I am trying to use mapkit auto completion in my SwiftUI project and whenever I run the view, I get a bunch of errors such as:
2022-02-12 19:45:59.095462-0800 Pickt[64199:2417526] [SearchAttribution] No matching attribution source found for com.timeout
2022-02-12 19:45:59.095595-0800 Pickt[64199:2417526] [SearchAttribution] No matching attribution source found for com.theculturetrip
2022-02-12 19:45:59.095822-0800 Pickt[64199:2417526] [SearchAttribution] No matching attribution source found for com.redtri
2022-02-12 19:45:59.095909-0800 Pickt[64199:2417526] [SearchAttribution] No matching attribution source found for com.fotospot
2022-02-12 19:45:59.096549-0800 Pickt[64199:2417526] [SearchAttribution] Error loading attribution info for identifier com.timeout from geod: Error Domain=GEOErrorDomain Code=-8 "No matching attribution source found for com.timeout" UserInfo={NSDebugDescription=No matching attribution source found for com.timeout}
2022-02-12 19:45:59.096679-0800 Pickt[64199:2417526] [SearchAttribution] Error loading attribution info for identifier com.theculturetrip from geod: Error Domain=GEOErrorDomain Code=-8 "No matching attribution source found for com.theculturetrip" UserInfo={NSDebugDescription=No matching attribution source found for com.theculturetrip}
2022-02-12 19:45:59.096761-0800 Pickt[64199:2417526] [SearchAttribution] Error loading attribution info for identifier com.redtri from geod: Error Domain=GEOErrorDomain Code=-8 "No matching attribution source found for com.redtri" UserInfo={NSDebugDescription=No matching attribution source found for com.redtri}
2022-02-12 19:45:59.096837-0800 Pickt[64199:2417526] [SearchAttribution] Error loading attribution info for identifier com.fotospot from geod: Error Domain=GEOErrorDomain Code=-8 "No matching attribution source found for com.fotospot" UserInfo={NSDebugDescription=No matching attribution source found for com.fotospot}
and then the auto complete breaks until I dismiss and reload the view. Here is my code:
MapAutoComplete.swift
import Foundation
import SwiftUI
import Combine
import MapKit
class MapSearch : NSObject, ObservableObject {
@Published var locationResults : [MKLocalSearchCompletion] = []
@Published var searchTerm = ""
private var cancellables : Set<AnyCancellable> = []
private var searchCompleter = MKLocalSearchCompleter()
private var currentPromise : ((Result<[MKLocalSearchCompletion], Error>) -> Void)?
override init() {
super.init()
searchCompleter.delegate = self
searchCompleter.resultTypes = MKLocalSearchCompleter.ResultType([.address])
$searchTerm
.debounce(for: .seconds(0.2), scheduler: RunLoop.main)
.removeDuplicates()
.flatMap({ (currentSearchTerm) in
self.searchTermToResults(searchTerm: currentSearchTerm)
})
.sink(receiveCompletion: { (completion) in
//handle error
}, receiveValue: { (results) in
self.locationResults = results.filter { $0.subtitle.contains("United States") } // This parses the subtitle to show only results that have United States as the country. You could change this text to be Germany or Brazil and only show results from those countries.
})
// .store(in: &cancellables)
}
func searchTermToResults(searchTerm: String) -> Future<[MKLocalSearchCompletion], Error> {
Future { promise in
self.searchCompleter.queryFragment = searchTerm
self.currentPromise = promise
}
}
}
extension MapSearch : MKLocalSearchCompleterDelegate {
func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
currentPromise?(.success(completer.results))
}
func completer(_ completer: MKLocalSearchCompleter, didFailWithError error: Error) {
//could deal with the error here, but beware that it will finish the Combine publisher stream
//currentPromise?(.failure(error))
}
}
struct ReversedGeoLocation {
let streetNumber: String // eg. 1
let streetName: String // eg. Infinite Loop
let city: String // eg. Cupertino
let state: String // eg. CA
let zipCode: String // eg. 95014
let country: String // eg. United States
let isoCountryCode: String // eg. US
var formattedAddress: String {
return """
\(streetNumber) \(streetName),
\(city), \(state) \(zipCode)
\(country)
"""
}
// Handle optionals as needed
init(with placemark: CLPlacemark) {
self.streetName = placemark.thoroughfare ?? ""
self.streetNumber = placemark.subThoroughfare ?? ""
self.city = placemark.locality ?? ""
self.state = placemark.administrativeArea ?? ""
self.zipCode = placemark.postalCode ?? ""
self.country = placemark.country ?? ""
self.isoCountryCode = placemark.isoCountryCode ?? ""
}
}
struct SearchBar: UIViewRepresentable {
@Binding var text: String
class Coordinator: NSObject, UISearchBarDelegate {
@Binding var text: String
init(text: Binding<String>) {
_text = text
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
text = searchText
}
}
func makeCoordinator() -> SearchBar.Coordinator {
return Coordinator(text: $text)
}
func makeUIView(context: UIViewRepresentableContext<SearchBar>) -> UISearchBar {
let searchBar = UISearchBar(frame: .zero)
searchBar.delegate = context.coordinator
searchBar.searchBarStyle = .minimal
searchBar.becomeFirstResponder()
return searchBar
}
func updateUIView(_ uiView: UISearchBar, context: UIViewRepresentableContext<SearchBar>) {
uiView.text = text
}
}
class LocationSearchService: NSObject, ObservableObject, MKLocalSearchCompleterDelegate {
@Published var searchQuery = ""
var completer: MKLocalSearchCompleter
@Published var completions: [MKLocalSearchCompletion] = []
var cancellable: AnyCancellable?
override init() {
completer = MKLocalSearchCompleter()
super.init()
cancellable = $searchQuery.assign(to: \.queryFragment, on: self.completer)
completer.delegate = self
}
func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
self.completions = completer.results
}
}
extension MKLocalSearchCompletion: Identifiable {}
struct searchView: View {
@ObservedObject var locationSearchService: LocationSearchService
@Environment(\.presentationMode) var presentationMode
static var address = ""
var body: some View {
VStack {
SearchBar(text: $locationSearchService.searchQuery)
List(locationSearchService.completions) { completion in
VStack(alignment: .leading) {
Text(completion.title)
Text(completion.subtitle)
.font(.subheadline)
.foregroundColor(.gray)
}
.onTapGesture {
searchView.address = completion.subtitle
presentationMode.wrappedValue.dismiss()
}
}
}
}
}
I am in desperate need of help, thx in advance!
@Mcrich23
I am assuming that all the code you have posted is not in one file? If that is the case can you edit the post and put breaks in to indicate what belongs where just so that it is easier to decipher.
Add the 3 backticks ``` where you need to break up each code block and provide a file name that you used. For example:
FileName1
Code here
Filename2
Code here
etc
etc
They are all in one file, do you think I should split it?
Yes you should so that the compiler can deal with it a bit easier, but for the time being I’ll see if I can figure out what’s going on.
I Split up the 1 file into 3, and updated my post.
1 Like
Seems to be less code that was the case previously. Are you sure that all the necessary code is there to be able to carry out some testing?
1 Like
Yes, all the code there is just the auto complete and it’s ui, non of the extra ui fluff. Thank you so much!
OK so what does all that code do?
What is the context in which it is used?
If you have this project on GitHub, that may make it easier to get something running to see what’s going on.
Here is a video showing what happens:
https://drive.google.com/file/d/1C418UmusQCfSY7_VRv74ygR5Pf_uJ1hi/view?usp=sharing
However, I get the errors and then the auto complete stops working.
Are you referring to Auto Complete as in the MapAutoComplete stops working or are you referring to Xcode Auto Complete?
The auto complete in the MapAutoComplete stops working.
Have you set breakpoints to examine what is happening in your code as compared to what you expected would happen?
I have not, but will try right now.
Realistically it is impossible for me to work out what is wrong without the rest of the code base so that I can observe what is happening.
Where would you suggest putting breakpoints?
Here is my other related code:
MapView.swift
struct MapView: View {
static var shared = MapView()
@State var pinLatitude: Double = 47.6062
@State var pinLongitude: Double = -122.3321
@State var loadLatitude: Double = 47.6062
@State var loadLongitude: Double = -122.3321
@State var zoom: Double = 0.2
@State var searchLocation = ""
@State var noMapLoc = false
@State var currentLoc = false
@Environment(\.presentationMode) var presentationMode
@State private var locManager = CLLocationManager() {
didSet {
if locManager.authorizationStatus == .authorizedWhenInUse || locManager.authorizationStatus == .authorizedAlways {
searchLocation = "Current Location"
}else {
allowLocation = true
}
}
}
@State var reloadMap = false {
didSet {
DispatchQueue.main.asyncAfter(deadline: .now() + .microseconds(1)) {
reloadMap = false
}
}
}
@State var shouldOpenPlacePicker = false
@State var allowLocation = false
var body: some View {
GeometryReader { geo in
VStack {
Text("Drop a pin to select your location")
.font(.headline)
.padding()
HStack {
Button("Cancel") {
noMapLoc = true
presentationMode.wrappedValue.dismiss()
}
.foregroundColor(.red)
.padding(.trailing)
Button("Done") {
print("pinCoordinate = lat: \(pinLatitude), long: \(pinLongitude)")
noMapLoc = false
presentationMode.wrappedValue.dismiss()
}
.padding(.leading)
}
if !reloadMap {
RawMapView(lat: loadLatitude, long: loadLongitude, zoom: zoom)
.overlay(alignment: .topTrailing, {
HStack {
Button(action: {shouldOpenPlacePicker = true}) {
Image(systemName: "magnifyingglass")
.foregroundColor(.primary)
}
if Utilities.currentLocationEnabled {
Button(action: {
locManager.requestWhenInUseAuthorization()
print("locManager authStatus = \(locManager.authorizationStatus)")
if locManager.authorizationStatus == .denied {
currentLoc = false
allowLocation = true
}else {
let coordinates = locManager.location?.coordinate
if Utilities.appType == .Debug {
if coordinates?.latitude == nil {
loadLatitude = 47.6062
}else {
loadLatitude = coordinates!.latitude
}
if coordinates?.longitude == nil {
loadLongitude = -122.3321
}else {
loadLongitude = coordinates!.longitude
}
}else {
loadLatitude = coordinates!.latitude
loadLongitude = coordinates!.longitude
}
zoom = 0.05
reloadMap = true
}
// if locManager.authorizationStatus == .authorizedWhenInUse || locManager.authorizationStatus == .authorizedAlways {
// }else {
// allowLocation = true
// }
}, label: {
// if (locManager.authorizationStatus == .authorizedWhenInUse || locManager.authorizationStatus == .authorizedAlways) {
// if loadLatitude == 47.6062 && loadLongitude == -122.3321 {
// Image(systemName: "location.fill")
// }else if loadLatitude == locManager.location?.coordinate.latitude && loadLongitude == locManager.location?.coordinate.longitude {
// Image(systemName: "location.fill")
// }else {
// Image(systemName: "location")
// }
// }else {
Image(systemName: "location")
// }
})
}
// Text("Enter Location")
}
.sheet(isPresented: $shouldOpenPlacePicker, onDismiss: {
// MapView.pinLatitude =
let address = searchView.address
let geoCoder = CLGeocoder()
geoCoder.geocodeAddressString(address) { (placemarks, error) in
guard
let placemarks = placemarks,
let location = placemarks.first?.location
else {
// handle no location found
return
}
loadLatitude = location.coordinate.latitude
loadLongitude = location.coordinate.longitude
zoom = 0.1
reloadMap = true
// Use your location
}
}, content: {searchView(locationSearchService: LocationSearchService())})
.descreteOpaqueBackground(color: .white, diameter: 2)
.padding()
})
}else {
// Loading(selectedType: .semiCircleSpin, duration: 0.0)
}
}
.ignoresSafeArea()
.alert(isPresented: $allowLocation, content: {
Alert(title: Text("Uh Oh"), message: Text("We're not able to find your location, please open settings to enable it."), primaryButton: .destructive(Text("Cancel"), action: {
allowLocation = false
}), secondaryButton: .default(Text("Open Settings"), action: {
allowLocation = false
if let appSettings = URL(string: UIApplication.openSettingsURLString) {
UIApplication.shared.open(appSettings)
}
}))
})
}
}
}
struct MapView_Previews: PreviewProvider {
static var previews: some View {
MapView()
}
}
RawMapView.swift
struct RawMapView: UIViewRepresentable {
var lat: Double
var long: Double
var zoom: Double
func updateUIView(_ mapView: MKMapView, context: Context) {
let span = MKCoordinateSpan(latitudeDelta: zoom, longitudeDelta: zoom)
var chicagoCoordinate = CLLocationCoordinate2D()
chicagoCoordinate.latitude = lat
chicagoCoordinate.longitude = long
let region = MKCoordinateRegion(center: chicagoCoordinate, span: span)
mapView.setRegion(region, animated: true)
}
func makeUIView(context: Context) -> MKMapView {
let myMap = MKMapView(frame: .zero)
let longPress = UILongPressGestureRecognizer(target: context.coordinator, action: #selector(RawMapViewCoordinator.addAnnotation(gesture:)))
longPress.minimumPressDuration = 0.5
myMap.addSwipeGestureRecognizer(for: [.down, .left, .right, .up], target: myMap, action: #selector(RawMapViewCoordinator.updateCoordinates(gesture:)))
myMap.addGestureRecognizer(longPress)
myMap.delegate = context.coordinator
return myMap
}
func makeCoordinator() -> RawMapViewCoordinator {
return RawMapViewCoordinator(self)
}
class RawMapViewCoordinator: NSObject, MKMapViewDelegate {
var entireMapViewController: RawMapView
init(_ control: RawMapView) {
self.entireMapViewController = control
}
@objc func updateCoordinates(gesture: UIGestureRecognizer) {
print("get map coordinates")
if let mapView = gesture.view as? MKMapView {
print("Map Coordinates = \(mapView.centerCoordinate.latitude), \(mapView.centerCoordinate.longitude)")
MapView.shared.loadLatitude = mapView.centerCoordinate.latitude
MapView.shared.loadLongitude = mapView.centerCoordinate.longitude
}
}
@objc func addAnnotation(gesture: UIGestureRecognizer) {
if gesture.state == .began {
if let mapView = gesture.view as? MKMapView {
for point in mapView.annotations {
mapView.removeAnnotation(point)
}
let point = gesture.location(in: mapView)
let coordinate = mapView.convert(point, toCoordinateFrom: mapView)
let annotation = MKPointAnnotation()
annotation.coordinate = coordinate
mapView.addAnnotation(annotation)
print("pinCoordinate = lat: \(coordinate.longitude), long: \(coordinate.longitude)")
MapView.shared.pinLatitude = coordinate.latitude
MapView.shared.pinLongitude = coordinate.longitude
}
}
}
}
}
@Mcrich23
Morris,
I am still getting errors. Can you provide the Utilities.swift
file and the file containing the definition of .descreteOpaqueBackground
OR paste in the definition and I’ll set up something so that .descreteOpaqueBackground works.
@Mcrich23
Morris,
Unfortunately this just gets more complex with more files and definitions missing including the model for Restaurants and Media, RemoteConfig…
Do you have this project on GitHub? Might be a whole lot easier. Either that or compress the entire project and post to Dropbox and then create a share link and post in a reply OR send me a private message with the DropBox share link.
If you use Google Drive then make sure that the share link does not require some kind of authentication on my part as I do not provide email addresses.
Cheers
Absolutely. However, my GitHub is private because I don’t want people copying my code when I eventually launch my app. Can I please have your GitHub username to add you as a collaborator?