如何使用Apple Map Kit实现地址的自动完成

时间:2015-10-28 00:09:13

标签: ios swift geocoding

我想自动填写用户的地址,与google api在此链接中提供的地址相同:

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

如何使用Apple map kit实现相同的功能?

我曾尝试使用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)
            }
        }
    }
}

然而,结果非常令人失望。

任何可用于实现此类功能的Apple API,还是应该使用google api?

谢谢

5 个答案:

答案 0 :(得分:65)

更新 - 我使用Swift 3创建了一个简单的示例项目here,原始答案是用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
}

答案 1 :(得分:2)

我的回答完全基于@George McDonnell。我希望对那些在实施最后一个方面遇到麻烦的人有所帮助。

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)
    }
}

答案 2 :(得分:2)

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) {
        //could deal with the error here, but beware that it will finish the Combine publisher stream
        //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))
    }
}

答案 3 :(得分:1)

  

此问题的样本项目可以从HERE 下载

在此示例项目中,此问题是通过MKLocalSearchRequest和MapKit实现的。

它正在显示自动填充地点,就像谷歌地方API一样,并且可以将注释点放在Apple的地图上(不在谷歌地图上,我希望这只是你在寻找。)

但是,它无法显示您从Google Places API获得的准确结果。问题在于地理编码数据库显然并不完整,而且Apple并不是领导这一领域的公司 - 谷歌就是。

附加示例应用的一些屏幕截图,以便您了解它是否对您的要求有用。

Apple's autocomplete view.

Plotting the annotation point on Apple's map.

  

希望这就是你要找的东西!

答案 4 :(得分:0)

您绝对应该使用Google API。我使用Apple的地理编码API已经挣扎了很长时间,我可以向你保证它们的质量非常有限。

如果您想看到如此低质量的最明显示例,请查看我的this问题。

使用Google的API,您可以获得更准确的结果。此外,他们还拥有自动完成API。您可以找到有关他们的更多信息here

我已经尝试过它们,并设法让它们很好地工作。