Как реализовать автозаполнение для адреса с помощью Apple Map Kit

Я хочу автоматически заполнить адрес для пользователя так же, как в Google Api по этой ссылке:

https://developers.google.com/maps/documentation/javascript/places-autocomplete?hl=en

Как я могу реализовать ту же функциональность, используя комплект Apple Map?

Я попытался использовать Geo Coder, я написал это, например:

@IBAction func SubmitGeoCode(sender: AnyObject) {

    let address = "1 Mart"
    let coder = CLGeocoder()

    coder.geocodeAddressString(address) { (placemarks, error) -> Void in

        for placemark in placemarks! {

            let lines = placemark.addressDictionary?["FormattedAddressLines"] as? [String]

            for addressline in lines! {
                print(addressline)
            }
        }
    }
}

Однако результаты очень разочаровывают.

Какие-либо API-интерфейсы Apple, доступные для реализации такой функциональности, или я должен пойти в Google API?

Спасибо

6 ответов

Решение

Обновление - я создал простой пример проекта с использованием Swift 3, так как оригинальный ответ был написан на Swift 2.

В iOS 9.3 новый класс называется MKLocalSearchCompleter был представлен, это позволяет создать решение автозаполнения, вы просто передаете queryFragment, как показано ниже:

var searchCompleter = MKLocalSearchCompleter()
searchCompleter.delegate = self
var searchResults = [MKLocalSearchCompletion]()

searchCompleter.queryFragment = searchField.text!

Затем обработайте результаты запроса, используя MKLocalSearchCompleterDelegate:

extension SearchViewController: MKLocalSearchCompleterDelegate {

    func completerDidUpdateResults(completer: MKLocalSearchCompleter) {
        searchResults = completer.results
        searchResultsTableView.reloadData()
    } 

    func completer(completer: MKLocalSearchCompleter, didFailWithError error: NSError) {
        // handle error
    }
}

И отобразить адрес результатов в соответствующем формате:

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let searchResult = searchResults[indexPath.row]
    let cell = UITableViewCell(style: .Subtitle, reuseIdentifier: nil)
    cell.textLabel?.text = searchResult.title
    cell.detailTextLabel?.text = searchResult.subtitle
    return cell
}

Затем вы можете использовать MKLocalCompletion объект для создания экземпляра MKLocalSearchRequest таким образом получая доступ к MKPlacemark и все другие полезные данные:

let searchRequest = MKLocalSearchRequest(completion: completion!)
let search = MKLocalSearch(request: searchRequest)
search.startWithCompletionHandler { (response, error) in
    let coordinate = response?.mapItems[0].placemark.coordinate
}

Swift 5 + Combine + (опционально) решение SwiftUI

Кажется, есть ряд комментариев по поводу других решений, которым нужна версия, совместимая с более поздними версиями Swift. Кроме того, похоже, что (как и я) людям также понадобится решение SwiftUI.

Это основано на предыдущих предложениях, но использует Combine для отслеживания ввода, устранения ошибок и последующего предоставления результатов через издателя.

В MapSearch ObservableObject легко используется в SwiftUI (пример предоставлен), но также может использоваться в ситуациях, отличных от SwiftUI.

MapSearch ObservableObject

      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
        
        $searchTerm
            .debounce(for: .seconds(0.5), scheduler: RunLoop.main)
            .removeDuplicates()
            .flatMap({ (currentSearchTerm) in
                self.searchTermToResults(searchTerm: currentSearchTerm)
            })
            .sink(receiveCompletion: { (completion) in
                //handle error
            }, receiveValue: { (results) in
                self.locationResults = results
            })
            .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) {
        currentPromise?(.failure(error))
    }
}

Интерфейс SwiftUI, включая отображаемые местоположения

      
struct ContentView: View {
    @StateObject private var mapSearch = MapSearch()
    
    var body: some View {
        NavigationView {
            Form {
                Section {
                    TextField("Address", text: $mapSearch.searchTerm)
                }
                Section {
                    ForEach(mapSearch.locationResults, id: \.self) { location in
                        NavigationLink(destination: Detail(locationResult: location)) {
                            VStack(alignment: .leading) {
                                Text(location.title)
                                Text(location.subtitle)
                                    .font(.system(.caption))
                            }
                        }
                    }
                }
            }.navigationTitle(Text("Address search"))
        }
    }
}

class DetailViewModel : ObservableObject {
    @Published var isLoading = true
    @Published private var coordinate : CLLocationCoordinate2D?
    @Published var region: MKCoordinateRegion = MKCoordinateRegion()
    
    var coordinateForMap : CLLocationCoordinate2D {
        coordinate ?? CLLocationCoordinate2D()
    }
    
    func reconcileLocation(location: MKLocalSearchCompletion) {
        let searchRequest = MKLocalSearch.Request(completion: location)
        let search = MKLocalSearch(request: searchRequest)
        search.start { (response, error) in
            if error == nil, let coordinate = response?.mapItems.first?.placemark.coordinate {
                self.coordinate = coordinate
                self.region = MKCoordinateRegion(center: coordinate, span: MKCoordinateSpan(latitudeDelta: 0.03, longitudeDelta: 0.03))
                self.isLoading = false
            }
        }
    }
    
    func clear() {
        isLoading = true
    }
}

struct Detail : View {
    var locationResult : MKLocalSearchCompletion
    @StateObject private var viewModel = DetailViewModel()
    
    struct Marker: Identifiable {
        let id = UUID()
        var location: MapMarker
    }
    
    var body: some View {
        Group {
            if viewModel.isLoading {
                Text("Loading...")
            } else {
                Map(coordinateRegion: $viewModel.region,
                    annotationItems: [Marker(location: MapMarker(coordinate: viewModel.coordinateForMap))]) { (marker) in
                    marker.location
                }
            }
        }.onAppear {
            viewModel.reconcileLocation(location: locationResult)
        }.onDisappear {
            viewModel.clear()
        }
        .navigationTitle(Text(locationResult.title))
    }
}

Мой ответ полностью основан на @George McDonnell's. Надеюсь, это поможет парням, у которых проблемы с внедрением последнего.

import UIKit
import MapKit

class ViewController: UIViewController {

    @IBOutlet weak var searchBar: UISearchBar!
    @IBOutlet weak var tableVIew: UITableView!

    //create a completer
    lazy var searchCompleter: MKLocalSearchCompleter = {
        let sC = MKLocalSearchCompleter()
        sC.delegate = self
        return sC
    }()

    var searchSource: [String]?
}

extension ViewController: UISearchBarDelegate {
    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
        //change searchCompleter depends on searchBar's text
        if !searchText.isEmpty {
            searchCompleter.queryFragment = searchText
        }
    }
}

extension ViewController: UITableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return searchSource?.count ?? 0
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        //I've created SearchCell beforehand; it might be your cell type
        let cell = self.tableVIew.dequeueReusableCell(withIdentifier: "SearchCell", for: indexPath) as! SearchCell

        cell.label.text = self.searchSource?[indexPath.row]
//            + " " + searchResult.subtitle

        return cell
    }
}

extension ViewController: MKLocalSearchCompleterDelegate {
    func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
        //get result, transform it to our needs and fill our dataSource
        self.searchSource = completer.results.map { $0.title }
        DispatchQueue.main.async {
            self.tableVIew.reloadData()
        }
    }

    func completer(_ completer: MKLocalSearchCompleter, didFailWithError error: Error) {
        //handle the error
        print(error.localizedDescription)
    }
}

Вам определенно следует обратиться к API Google. Я долго боролся с API-интерфейсами геокодирования Apple, и я могу заверить вас, что они имеют очень ограниченное качество.

Если вы хотите увидеть самый яркий пример такого низкого качества, проверьте мой вопрос.

С помощью API Google вы можете достичь гораздо более точных результатов. Кроме того, они имеют API автозаполнения. Вы можете узнать больше о них здесь.

Я попробовал их и сумел заставить их работать довольно хорошо.

Простое решение — SwiftUI

Как : searchText связан с Textfield, когда Textfield изменяется, searchText запрашивается (сравнивается) с глобальными адресами.

Завершение запроса вызывает команду completerDidUpdateResults, которая обновляет список SearchThis.swift этими результатами (адресами).

SearchThis.swift (SwiftUI)

      import SwiftUI
import Foundation

struct SearchThis : View {
    @StateObject var searchModel = SearchModel()
    
    var body: some View {
        VStack {
            TextField("Type Here", text: $searchModel.searchText)
                .onChange(of: searchModel.searchText) { newValue in
                    searchModel.completer.queryFragment = searchModel.searchText
                }
            List(searchModel.locationResult, id: \.self) { results in
                Button(results.title) {print("hi")}
            }
        }
    }
}

struct SearchThis_Previews: PreviewProvider {
    static var previews: some View {
        SearchThis()
    }
}

SearchModel.swift (класс)

      import MapKit

class SearchModel: NSObject, ObservableObject, MKLocalSearchCompleterDelegate {
    @Published var searchText = ""
    @Published var locationResult: [MKLocalSearchCompletion] = []
    
    
    let completer = MKLocalSearchCompleter()
    
    override init() {
        super.init()
        completer.delegate = self
    }

    func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
        locationResult = completer.results
    }

    func completer(_ completer: MKLocalSearchCompleter, didFailWithError error: Error) {
        print(error.localizedDescription)
    }
}

ОБРАЗЕЦ ПРОЕКТА ДЛЯ ЭТОГО ВОПРОСА МОЖНО СКАЧАТЬ С ЗДЕСЬ

В этом примере проекта эта проблема достигается с помощью MKLocalSearchRequest и MapKit.

Он отображает места автозаполнения точно так же, как и API мест Google, и может разместить точку аннотации на карте Apple (не на карте Google, я надеюсь, что это только вы ищете).

Однако он не дает такого точного результата, как вы можете получить из Google Places API. Поскольку проблема заключается в том, что база геокодирования явно не полна, и Apple не является компанией, которая возглавляет эту область, - Google.

Прикрепите несколько скриншотов примера приложения, чтобы вы могли увидеть, полезно ли это для ваших требований или нет.

Автозаполнение зрения Apple

Построение точки аннотации на карте Apple

Надеюсь, это то, что вы ищете!

Другие вопросы по тегам