LocationSearch.swift 2.69 KB
Newer Older
1
2
3
4
//
//  LocationFinder.swift
//  MapKit Testing Search Functionality
//
5
6
//  Created by Brian Porumb on 25.11.21.
//
7
8
9
10
11
12
13

import Foundation
import Combine
import MapKit

class LocationService: NSObject, ObservableObject {
    
14
    // Differnt search states
15
16
17
18
19
20
21
22
    enum LocationStatus: Equatable {
        case idle
        case noResults
        case isSearching
        case error(String)
        case result
    }
    
Brian Porumb's avatar
Brian Porumb committed
23
24
25
    // queryFragment used in view gets updated every time the user types something
    // default status is idle
    // searchResults contains all the results from the queries
26
27
28
29
30
31
32
    @Published var queryFragment: String = ""
    @Published private(set) var status: LocationStatus = .idle
    @Published private(set) var searchResults: [MKLocalSearchCompletion] = []
    
    private var queryCancellable: AnyCancellable?
    private let searchCompleter: MKLocalSearchCompleter!
    
Brian Porumb's avatar
Brian Porumb committed
33
    // initiate the search completer, set the delegate on self
34
35
36
37
38
    init(searchCompleter: MKLocalSearchCompleter = MKLocalSearchCompleter()) {
        self.searchCompleter = searchCompleter
        super.init()
        self.searchCompleter.delegate = self
        
Brian Porumb's avatar
Brian Porumb committed
39
40
41
        // receive a stream from the queryFragment in the view
        // debounce (wait) for 250miliseconds before pushing the event further
        // sink returns the updated string after waiting the specified amount of time
42
43
44
45
        queryCancellable = $queryFragment
            .receive(on: DispatchQueue.main)
            .debounce(for: .milliseconds(250), scheduler: RunLoop.main, options: nil)
            .sink(receiveValue: { fragment in
Brian Porumb's avatar
Brian Porumb committed
46
47
                
                // if fragment isn't empty then set the queryFrament to the current updated string
48
49
50
51
52
53
54
55
56
57
58
                self.status = .isSearching
                if !fragment.isEmpty {
                    self.searchCompleter.queryFragment = fragment
                } else {
                    self.status = .idle
                    self.searchResults = []
                }
        })
    }
}

Brian Porumb's avatar
Brian Porumb committed
59
// every time the queryFragment gets updated these functions get called
60
61
extension LocationService: MKLocalSearchCompleterDelegate {
    func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
Brian Porumb's avatar
Brian Porumb committed
62
63
64
        // query the current input, filter out the results that have subtitles
        // results with no subtitles are usually countries and cities
        // remove filter if you want to get points of interest as well
65
66
67
68
69
70
71
72
        self.searchResults = completer.results.filter({ $0.subtitle == "" })
        self.status = completer.results.isEmpty ? .noResults : .result
    }
    
    func completer(_ completer: MKLocalSearchCompleter, didFailWithError error: Error) {
        self.status = .error(error.localizedDescription)
    }
}