├── Images
├── MapKit.png
├── MapKit_MapItems.png
└── MapKit_Completions.png
├── Example
├── Podfile
├── Sources
│ ├── Assets.xcassets
│ │ ├── Contents.json
│ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ ├── ViewController.swift
│ ├── AppDelegate.swift
│ ├── Base.lproj
│ │ ├── Main.storyboard
│ │ └── LaunchScreen.storyboard
│ └── Info.plist
├── Podfile.lock
├── Assets.xcassets
│ └── LaunchImage.launchimage
│ │ └── Contents.json
└── Example.xcodeproj
│ └── project.pbxproj
├── Sources
├── Resources
│ └── Material.xcassets
│ │ ├── Contents.json
│ │ ├── round_close_black_24pt.imageset
│ │ ├── round_close_black_24pt_1x.png
│ │ ├── round_close_black_24pt_2x.png
│ │ ├── round_close_black_24pt_3x.png
│ │ └── Contents.json
│ │ └── round_place_black_36pt.imageset
│ │ ├── round_place_black_36pt_1x.png
│ │ ├── round_place_black_36pt_2x.png
│ │ ├── round_place_black_36pt_3x.png
│ │ └── Contents.json
├── Extensions
│ ├── CLLocationCoordinate2D.swift
│ ├── CLPlacemark.swift
│ └── MKMapView.swift
├── Objects
│ ├── MapTabsView.swift
│ ├── MapItemTableViewCell.swift
│ ├── BottomSheetView.swift
│ ├── SearchCompletionTableViewCell.swift
│ ├── SearchCompletionTableViewCell.xib
│ └── MapItemTableViewCell.xib
├── Info.plist
├── MapKitSearchViewController.xib
└── MapKitSearchViewController.swift
├── Package.swift
├── .travis.yml
├── MapKitSearchView.podspec
├── LICENSE
├── .gitignore
├── MapKitSearchView.xcodeproj
├── xcshareddata
│ └── xcschemes
│ │ └── MapKitSearchView.xcscheme
└── project.pbxproj
└── README.md
/Images/MapKit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/philip-bui/mapkit-search-view/HEAD/Images/MapKit.png
--------------------------------------------------------------------------------
/Images/MapKit_MapItems.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/philip-bui/mapkit-search-view/HEAD/Images/MapKit_MapItems.png
--------------------------------------------------------------------------------
/Example/Podfile:
--------------------------------------------------------------------------------
1 | target 'Example' do
2 | use_frameworks!
3 |
4 | pod 'MapKitSearchView', :path => '../'
5 | end
6 |
--------------------------------------------------------------------------------
/Images/MapKit_Completions.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/philip-bui/mapkit-search-view/HEAD/Images/MapKit_Completions.png
--------------------------------------------------------------------------------
/Example/Sources/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Sources/Resources/Material.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Sources/Resources/Material.xcassets/round_close_black_24pt.imageset/round_close_black_24pt_1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/philip-bui/mapkit-search-view/HEAD/Sources/Resources/Material.xcassets/round_close_black_24pt.imageset/round_close_black_24pt_1x.png
--------------------------------------------------------------------------------
/Sources/Resources/Material.xcassets/round_close_black_24pt.imageset/round_close_black_24pt_2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/philip-bui/mapkit-search-view/HEAD/Sources/Resources/Material.xcassets/round_close_black_24pt.imageset/round_close_black_24pt_2x.png
--------------------------------------------------------------------------------
/Sources/Resources/Material.xcassets/round_close_black_24pt.imageset/round_close_black_24pt_3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/philip-bui/mapkit-search-view/HEAD/Sources/Resources/Material.xcassets/round_close_black_24pt.imageset/round_close_black_24pt_3x.png
--------------------------------------------------------------------------------
/Sources/Resources/Material.xcassets/round_place_black_36pt.imageset/round_place_black_36pt_1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/philip-bui/mapkit-search-view/HEAD/Sources/Resources/Material.xcassets/round_place_black_36pt.imageset/round_place_black_36pt_1x.png
--------------------------------------------------------------------------------
/Sources/Resources/Material.xcassets/round_place_black_36pt.imageset/round_place_black_36pt_2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/philip-bui/mapkit-search-view/HEAD/Sources/Resources/Material.xcassets/round_place_black_36pt.imageset/round_place_black_36pt_2x.png
--------------------------------------------------------------------------------
/Sources/Resources/Material.xcassets/round_place_black_36pt.imageset/round_place_black_36pt_3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/philip-bui/mapkit-search-view/HEAD/Sources/Resources/Material.xcassets/round_place_black_36pt.imageset/round_place_black_36pt_3x.png
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:4.0
2 | import PackageDescription
3 |
4 | let package = Package(
5 | name: "MapKitSearchView",
6 | products: [
7 | .library(name: "MapKitSearchView", targets: ["MapKitSearchView"]),
8 | ],
9 | targets: [
10 | .target(name: "MapKitSearchView", path: "Sources")
11 | ]
12 | )
13 |
--------------------------------------------------------------------------------
/Example/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - MapKitSearchView (2.0.0)
3 |
4 | DEPENDENCIES:
5 | - MapKitSearchView (from `../`)
6 |
7 | EXTERNAL SOURCES:
8 | MapKitSearchView:
9 | :path: "../"
10 |
11 | SPEC CHECKSUMS:
12 | MapKitSearchView: 27cd67b3033c31904b128cda9367cd94b48c062e
13 |
14 | PODFILE CHECKSUM: e3b5caeffdf4bee16cd0396738f30fb760f6492c
15 |
16 | COCOAPODS: 1.8.4
17 |
--------------------------------------------------------------------------------
/Sources/Extensions/CLLocationCoordinate2D.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Kevin Kieffer on 6/9/20.
6 | //
7 |
8 | import Foundation
9 | import MapKit
10 |
11 | extension CLLocationCoordinate2D: Equatable {
12 |
13 | static public func ==(lhs: CLLocationCoordinate2D, rhs: CLLocationCoordinate2D) -> Bool {
14 | return (lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude)
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/Sources/Objects/MapTabsView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MapTabsView.swift
3 | // MapKitSearchView
4 | //
5 | // Created by Philip on 10/11/18.
6 | // Copyright © 2018 Next Generation. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class MapTabsView: UIView {
12 | override func layoutSubviews() {
13 | super.layoutSubviews()
14 | layer.borderColor = UIColor(white: 0, alpha: 0.11).cgColor
15 | layer.borderWidth = 0.5
16 | layer.cornerRadius = 12
17 | layer.masksToBounds = true
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Sources/Objects/MapItemTableViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MapItemTableViewCell.swift
3 | // MapKitSearchView
4 | //
5 | // Created by Philip on 10/11/18.
6 | // Copyright © 2018 Next Generation. All rights reserved.
7 | //
8 |
9 | import MapKit
10 | import UIKit
11 |
12 | public class MapItemTableViewCell: UITableViewCell {
13 | func viewSetup(withMapItem mapItem: MKMapItem, tintColor: UIColor? = nil) {
14 | textLabel?.text = mapItem.name
15 | detailTextLabel?.text = mapItem.placemark.title
16 | imageView?.tintColor = tintColor
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Sources/Resources/Material.xcassets/round_close_black_24pt.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "round_close_black_24pt_1x.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "round_close_black_24pt_2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "round_close_black_24pt_3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | },
23 | "properties" : {
24 | "template-rendering-intent" : "template"
25 | }
26 | }
--------------------------------------------------------------------------------
/Sources/Resources/Material.xcassets/round_place_black_36pt.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "round_place_black_36pt_1x.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "round_place_black_36pt_2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "round_place_black_36pt_3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | },
23 | "properties" : {
24 | "template-rendering-intent" : "template"
25 | }
26 | }
--------------------------------------------------------------------------------
/Sources/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 |
22 |
23 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | os: osx
2 | osx_image: xcode11.6
3 | language: swift
4 | env:
5 | global:
6 | - LC_CTYPE=en_US.UTF-8
7 | - LANG=en_US.UTF-8
8 | - PROJECT=MapKitSearchView.xcodeproj
9 | - IOS_FRAMEWORK_SCHEME="MapKitSearchView"
10 | matrix:
11 | - DESTINATION="OS=12.4,name=iPhone Xʀ" SCHEME="$IOS_FRAMEWORK_SCHEME"
12 | - DESTINATION="OS=11.4,name=iPhone X" SCHEME="$IOS_FRAMEWORK_SCHEME"
13 | - DESTINATION="OS=11.0.1,name=iPhone 5s" SCHEME="$IOS_FRAMEWORK_SCHEME"
14 |
15 | script:
16 | - set -o pipefail
17 | - xcodebuild -version
18 | - xcodebuild -showsdks
19 |
20 | # Build Framework in Release and Run Tests if specified
21 | - if [ $RUN_TESTS == "YES" ]; then
22 | xcodebuild -project "$PROJECT" -scheme "$SCHEME" -destination "$DESTINATION" -configuration Release ONLY_ACTIVE_ARCH=NO ENABLE_TESTABILITY=YES test | xcpretty;
23 | else
24 | xcodebuild -project "$PROJECT" -scheme "$SCHEME" -destination "$DESTINATION" -configuration Release ONLY_ACTIVE_ARCH=NO build | xcpretty;
25 | fi
26 |
--------------------------------------------------------------------------------
/MapKitSearchView.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = 'MapKitSearchView'
3 | s.version = '2.0.0'
4 | s.license= { :type => 'MIT', :file => 'LICENSE' }
5 | s.summary = 'An implementation of Apples Map search view with bottom sheet gestures.'
6 | s.description = 'Standalone view controller for searching and finding places.'
7 | s.homepage = 'https://github.com/philip-bui/mapkit-search-view'
8 | s.author = { 'Philip Bui' => 'philip.bui.developer@gmail.com' }
9 | s.source = { :git => 'https://github.com/philip-bui/mapkit-search-view.git', :tag => s.version }
10 | s.documentation_url = 'https://github.com/philip-bui/mapkit-search-view'
11 |
12 | s.ios.deployment_target = '11.0'
13 |
14 | s.source_files = 'Sources/**/*.swift'
15 | s.resources = ['Sources/Resources/*', 'Sources/**/*.xib']
16 | s.screenshots = ['https://github.com/philip-bui/mapkit-search-view/raw/master/Images/MapKit.png', 'https://github.com/philip-bui/mapkit-search-view/raw/master/Images/MapKit_Completions.png', 'https://github.com/philip-bui/mapkit-search-view/raw/master/Images/MapKit_MapItems.png']
17 | s.swift_version = '5.0'
18 | end
19 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2017 Philip
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/Sources/Extensions/CLPlacemark.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CLPlacemark.swift
3 | // MapKitSearchView
4 | //
5 | // Created by Philip on 10/11/18.
6 | // Copyright © 2018 Next Generation. All rights reserved.
7 | //
8 |
9 | import MapKit
10 |
11 | public extension CLPlacemark {
12 | public var address: String {
13 | var address = ""
14 | if let subThoroughfare = self.subThoroughfare {
15 | address += subThoroughfare + " "
16 | }
17 | if let thoroughfare = self.thoroughfare {
18 | address += thoroughfare + ", "
19 | }
20 | if let locality = self.locality {
21 | address += locality + " "
22 | }
23 | if let administrativeArea = self.administrativeArea {
24 | address += administrativeArea
25 | }
26 | return address
27 | }
28 |
29 | public var mkPlacemark: MKPlacemark? {
30 | guard let coordinate = location?.coordinate, let addressDictionary = addressDictionary as? [String: Any] else {
31 | return nil
32 | }
33 | return MKPlacemark(coordinate: coordinate, addressDictionary: addressDictionary)
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Sources/Extensions/MKMapView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MKMapView.swift
3 | // MapKitSearchView
4 | //
5 | // Created by Philip on 10/11/18.
6 | // Copyright © 2018 Next Generation. All rights reserved.
7 | //
8 |
9 | import MapKit
10 |
11 | public typealias MKMapBounds = (northEast: CLLocationCoordinate2D, southWest: CLLocationCoordinate2D)
12 | public extension MKMapView {
13 | public var mapBounds: MKMapBounds {
14 | let northEastPoint = CGPoint(x: bounds.origin.x + bounds.size.width, y: bounds.origin.y)
15 | let southWestPoint = CGPoint(x: bounds.origin.x, y: bounds.origin.y + bounds.size.height)
16 | return (convert(northEastPoint, toCoordinateFrom: self), convert(southWestPoint, toCoordinateFrom: self))
17 | }
18 |
19 | public var mapBoundsDistance: CLLocationDistance {
20 | let mapBounds = self.mapBounds
21 | let northEastLocation = CLLocation(latitude: mapBounds.northEast.latitude, longitude: mapBounds.northEast.longitude)
22 | let southWestLocation = CLLocation(latitude: mapBounds.southWest.latitude, longitude: mapBounds.southWest.longitude)
23 | return northEastLocation.distance(from: southWestLocation)
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Example/Assets.xcassets/LaunchImage.launchimage/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "orientation" : "portrait",
5 | "idiom" : "ipad",
6 | "minimum-system-version" : "7.0",
7 | "extent" : "full-screen",
8 | "scale" : "2x"
9 | },
10 | {
11 | "orientation" : "landscape",
12 | "idiom" : "ipad",
13 | "minimum-system-version" : "7.0",
14 | "extent" : "full-screen",
15 | "scale" : "1x"
16 | },
17 | {
18 | "orientation" : "landscape",
19 | "idiom" : "ipad",
20 | "minimum-system-version" : "7.0",
21 | "extent" : "full-screen",
22 | "scale" : "2x"
23 | },
24 | {
25 | "orientation" : "portrait",
26 | "idiom" : "iphone",
27 | "minimum-system-version" : "7.0",
28 | "scale" : "2x"
29 | },
30 | {
31 | "orientation" : "portrait",
32 | "idiom" : "iphone",
33 | "minimum-system-version" : "7.0",
34 | "subtype" : "retina4",
35 | "scale" : "2x"
36 | },
37 | {
38 | "orientation" : "portrait",
39 | "idiom" : "ipad",
40 | "minimum-system-version" : "7.0",
41 | "extent" : "full-screen",
42 | "scale" : "1x"
43 | }
44 | ],
45 | "info" : {
46 | "version" : 1,
47 | "author" : "xcode"
48 | }
49 | }
--------------------------------------------------------------------------------
/Example/Sources/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // Example
4 | //
5 | // Created by Philip on 10/11/18.
6 | // Copyright © 2018 Next Generation. All rights reserved.
7 | //
8 |
9 | import MapKit
10 | import MapKitSearchView
11 | import UIKit
12 |
13 | class ViewController: UIViewController {
14 | override func viewDidAppear(_ animated: Bool) {
15 | super.viewDidAppear(animated)
16 | let mapKit = MapKitSearchViewController(delegate: self)
17 | present(mapKit, animated: true, completion: nil)
18 | }
19 | }
20 |
21 | extension ViewController: MapKitSearchDelegate {
22 | func mapKitSearch(_ mapKitSearchViewController: MapKitSearchViewController, mapItem: MKMapItem) {
23 | }
24 |
25 | func mapKitSearch(_ mapKitSearchViewController: MapKitSearchViewController, searchReturnedOneItem mapItem: MKMapItem) {
26 | }
27 |
28 | func mapKitSearch(_ mapKitSearchViewController: MapKitSearchViewController, userSelectedListItem mapItem: MKMapItem) {
29 |
30 | }
31 |
32 | func mapKitSearch(_ mapKitSearchViewController: MapKitSearchViewController, userSelectedGeocodeItem mapItem: MKMapItem) {
33 |
34 | }
35 |
36 | func mapKitSearch(_ mapKitSearchViewController: MapKitSearchViewController, userSelectedAnnotationFromMap mapItem: MKMapItem) {
37 |
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Sources/Objects/BottomSheetView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BottomSheetView.swift
3 | // MapKitSearchView
4 | //
5 | // Created by Philip on 10/11/18.
6 | // Copyright © 2018 Next Generation. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class BottomSheetView: UIView {
12 | override func layoutSubviews() {
13 | super.layoutSubviews()
14 | let path = UIBezierPath(
15 | roundedRect: bounds,
16 | byRoundingCorners: [.topRight, .topLeft],
17 | cornerRadii: CGSize(width: 16, height: 16))
18 | let maskLayer = CAShapeLayer()
19 | maskLayer.path = path.cgPath
20 | layer.mask = maskLayer
21 | layer.borderColor = UIColor(white: 0, alpha: 0.11).cgColor
22 | }
23 | }
24 |
25 | class BottomSheetShadowView: UIView {
26 | override func layoutSubviews() {
27 | super.layoutSubviews()
28 | layer.masksToBounds = false
29 | layer.shadowColor = UIColor.lightGray.cgColor
30 | layer.shadowOpacity = 0.35
31 | layer.shadowOffset = CGSize(width: -1, height: 1)
32 | layer.shadowRadius = 5
33 | layer.shadowPath = UIBezierPath(
34 | roundedRect: bounds,
35 | byRoundingCorners: [.topRight, .topLeft],
36 | cornerRadii: CGSize(width: 17, height: 17)).cgPath
37 | layer.shouldRasterize = true
38 | layer.rasterizationScale = UIScreen.main.scale
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Sources/Objects/SearchCompletionTableViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SearchCompletionTableViewCell.swift
3 | // MapKitSearchView
4 | //
5 | // Created by Philip on 10/11/18.
6 | // Copyright © 2018 Next Generation. All rights reserved.
7 | //
8 |
9 | import MapKit
10 | import UIKit
11 |
12 | public class SearchCompletionTableViewCell: UITableViewCell {
13 | func viewSetup(withSearchCompletion searchCompletion: MKLocalSearchCompletion) {
14 | let attributedString = NSMutableAttributedString(string: searchCompletion.title)
15 | for highlightRange in searchCompletion.titleHighlightRanges {
16 | attributedString.addAttribute(
17 | NSAttributedString.Key.font,
18 | value: UIFont.boldSystemFont(ofSize: textLabel?.font.pointSize ?? 14),
19 | range: highlightRange.rangeValue)
20 | }
21 | textLabel?.attributedText = attributedString
22 |
23 | let attributedStringDetail = NSMutableAttributedString(string: searchCompletion.subtitle)
24 | for highlightRange in searchCompletion.subtitleHighlightRanges {
25 | attributedStringDetail.addAttribute(
26 | NSAttributedString.Key.font,
27 | value: UIFont.boldSystemFont(ofSize: detailTextLabel?.font.pointSize ?? 13),
28 | range: highlightRange.rangeValue)
29 | }
30 | detailTextLabel?.attributedText = attributedStringDetail
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Example/Sources/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // Example
4 | //
5 | // Created by Philip on 10/11/18.
6 | // Copyright © 2018 Next Generation. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 | var window: UIWindow?
14 |
15 | func setRootViewController(viewController: UIViewController,
16 | duration: TimeInterval = 0.7,
17 | options: UIView.AnimationOptions? = .transitionCrossDissolve) {
18 | guard let window = UIApplication.shared.keyWindow else {
19 | fatalError("No window in app")
20 | }
21 | if let options = options, window.rootViewController != nil {
22 | UIView.transition(with: window, duration: duration,
23 | options: options, animations: {
24 | window.rootViewController = viewController
25 | })
26 | } else {
27 | window.rootViewController = viewController
28 | }
29 | }
30 |
31 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
32 | window = UIWindow(frame: UIScreen.main.bounds)
33 | window?.makeKeyAndVisible()
34 | setRootViewController(viewController: UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController()!)
35 | // Override point for customization after application launch.
36 | return true
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Example/Sources/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Example/Sources/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIcons
10 |
11 | CFBundleIcons~ipad
12 |
13 | CFBundleIdentifier
14 | $(PRODUCT_BUNDLE_IDENTIFIER)
15 | CFBundleInfoDictionaryVersion
16 | 6.0
17 | CFBundleName
18 | $(PRODUCT_NAME)
19 | CFBundlePackageType
20 | APPL
21 | CFBundleShortVersionString
22 | 1.0
23 | CFBundleVersion
24 | 1
25 | LSRequiresIPhoneOS
26 |
27 | UILaunchStoryboardName
28 | LaunchScreen
29 | UIMainStoryboardFile
30 | Main
31 | UIRequiredDeviceCapabilities
32 |
33 | armv7
34 |
35 | UISupportedInterfaceOrientations
36 |
37 | UIInterfaceOrientationPortrait
38 | UIInterfaceOrientationLandscapeLeft
39 | UIInterfaceOrientationLandscapeRight
40 |
41 | UISupportedInterfaceOrientations~ipad
42 |
43 | UIInterfaceOrientationPortrait
44 | UIInterfaceOrientationPortraitUpsideDown
45 | UIInterfaceOrientationLandscapeLeft
46 | UIInterfaceOrientationLandscapeRight
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/Example/Sources/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/Example/Sources/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "20x20",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "20x20",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "29x29",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "29x29",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "40x40",
66 | "scale" : "1x"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "size" : "40x40",
71 | "scale" : "2x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "76x76",
76 | "scale" : "1x"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "size" : "76x76",
81 | "scale" : "2x"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "size" : "83.5x83.5",
86 | "scale" : "2x"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "size" : "1024x1024",
91 | "scale" : "1x"
92 | }
93 | ],
94 | "info" : {
95 | "version" : 1,
96 | "author" : "xcode"
97 | }
98 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData/
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata/
19 |
20 | ## Other
21 | *.moved-aside
22 | *.xccheckout
23 | *.xcscmblueprint
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 | *.ipa
28 | *.dSYM.zip
29 | *.dSYM
30 |
31 | ## Playgrounds
32 | timeline.xctimeline
33 | playground.xcworkspace
34 |
35 | # Swift Package Manager
36 | #
37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
38 | # Packages/
39 | # Package.pins
40 | # Package.resolved
41 | .build/
42 |
43 | # CocoaPods
44 | #
45 | # We recommend against adding the Pods directory to your .gitignore. However
46 | # you should judge for yourself, the pros and cons are mentioned at:
47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
48 | #
49 | Pods/
50 | #
51 | # Add this line if you want to avoid checking in source code from the Xcode workspace
52 | *.xcworkspace
53 |
54 | # Carthage
55 | #
56 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
57 | # Carthage/Checkouts
58 |
59 | Carthage/Build
60 |
61 | # fastlane
62 | #
63 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
64 | # screenshots whenever they are needed.
65 | # For more information about the recommended setup visit:
66 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
67 |
68 | fastlane/report.xml
69 | fastlane/Preview.html
70 | fastlane/screenshots/**/*.png
71 | fastlane/test_output
72 |
73 | # Code Injection
74 | #
75 | # After new code Injection tools there's a generated folder /iOSInjectionProject
76 | # https://github.com/johnno1962/injectionforxcode
77 |
78 | iOSInjectionProject/
79 |
--------------------------------------------------------------------------------
/MapKitSearchView.xcodeproj/xcshareddata/xcschemes/MapKitSearchView.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
34 |
35 |
45 |
46 |
52 |
53 |
54 |
55 |
56 |
57 |
63 |
64 |
70 |
71 |
72 |
73 |
75 |
76 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/Sources/Objects/SearchCompletionTableViewCell.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
29 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/Sources/Objects/MapItemTableViewCell.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
29 |
30 |
31 |
32 |
33 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | # MapKit Search View
8 | [](https://travis-ci.org/philip-bui/mapkit-search-view)
9 | [](https://codecov.io/gh/philip-bui/mapkit-search-view)
10 | [](http://cocoapods.org/pods/MapKitSearchView)
11 | [](https://github.com/Carthage/Carthage)
12 | [](http://cocoapods.org/pods/MapKitSearchView)
13 | [](https://github.com/philip-bui/mapkit-search-view/blob/master/LICENSE)
14 |
15 | An implementation of Apple's Map search view.
16 |
17 | - Animation between states and keyboard events.
18 | - Single gesture to scroll table view or drag down sheet.
19 | - Map user tracking (Follow, Follow with Heading).
20 | - Compass on non-north headings.
21 | - Customizable colors, search options.
22 |
23 | ## Requirements
24 |
25 | - iOS 11.0+
26 | - Xcode 10.3+
27 | - Swift 4.2+
28 |
29 | ## Installation
30 |
31 | ### CocoaPods
32 |
33 | [CocoaPods](https://cocoapods.org) is a dependency manager for Cocoa projects. For usage and installation instructions, visit their website. To integrate MapKit Search View into your Xcode project using CocoaPods, specify it in your `Podfile`:
34 |
35 | ```ruby
36 | pod 'MapKitSearchView'
37 | ```
38 |
39 | ### Carthage
40 |
41 | [Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks. To integrate MapKit Search View into your Xcode project using Carthage, specify it in your `Cartfile`:
42 |
43 | ```ogdl
44 | github "philip-bui/mapkit-search-view"
45 | ```
46 |
47 | ### Swift Package Manager
48 |
49 | The [Swift Package Manager](https://swift.org/package-manager/) is a tool for automating the distribution of Swift code and is integrated into the `swift` compiler. It is in early development, but MapKit Search View does support its use on supported platforms.
50 |
51 | Once you have your Swift package set up, adding MapKit Search View as a dependency is as easy as adding it to the `dependencies` value of your `Package.swift`.
52 |
53 | ```swift
54 | dependencies: [
55 | .package(url: "https://github.com/philip-bui/mapkit-search-view.git", from: "1.0.0"))
56 | ]
57 | ```
58 |
59 | ## Usage
60 |
61 | ```swift
62 | import MapKitSearchView
63 |
64 | let mapKitSearch = MapKitSearchViewController(delegate: self)
65 | mapKitSearch.tintColor = nil // Tints the close, userTracking and searchBar cursor colors.
66 | mapKitSearch.markerTintColor = nil // Tints map annotations and mapItem results.
67 | mapKitSearch.completionEnabled = true // Enables search completions as you type.
68 | mapKitSearch.geocodeEnabled = true // Enables geocoding when tapping on a map at street levels.
69 | mapKitSearch.userLocationRequest = .authorizedAlways // Requests location permission on view load.
70 | ```
71 |
72 | ## Improvements
73 |
74 | - Tablet / Landscape UI.
75 | - Additional information on duplicate place names.
76 | - Strings Localization.
77 | - Optional delegate methods to customize UI views (Search Bar, Table View rows).
78 | - Add Bottom Sheet states (collapsed, peek, expanded) for users to choose.
79 |
80 | ## License
81 |
82 | MapKit Search View is available under the MIT license. [See LICENSE](https://github.com/philip-bui/mapkit-search-view/blob/master/LICENSE) for details.
83 |
--------------------------------------------------------------------------------
/MapKitSearchView.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | D4127ECC2217C5B500CF4C72 /* Material.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D4732E4622118C6500CB838C /* Material.xcassets */; };
11 | D4127ECD2217C5B500CF4C72 /* SearchCompletionTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D4732DF8221145C100CB838C /* SearchCompletionTableViewCell.xib */; };
12 | D4127ECE2217C5B500CF4C72 /* MapItemTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D4732DFC2211461500CB838C /* MapItemTableViewCell.xib */; };
13 | D4127ECF2217C5B500CF4C72 /* MapKitSearchViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D4732DEF221072A700CB838C /* MapKitSearchViewController.xib */; };
14 | D4732DF0221072A700CB838C /* MapKitSearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4732DEE221072A700CB838C /* MapKitSearchViewController.swift */; };
15 | D4732DF42210733800CB838C /* MapTabsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4732DF32210733800CB838C /* MapTabsView.swift */; };
16 | D4732DF62210760900CB838C /* BottomSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4732DF52210760900CB838C /* BottomSheetView.swift */; };
17 | D4732DF9221145C100CB838C /* SearchCompletionTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4732DF7221145C100CB838C /* SearchCompletionTableViewCell.swift */; };
18 | D4732DFD2211461500CB838C /* MapItemTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4732DFB2211461500CB838C /* MapItemTableViewCell.swift */; };
19 | D4732E492211989400CB838C /* CLPlacemark.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4732E482211989400CB838C /* CLPlacemark.swift */; };
20 | D4732E4B2211A1BF00CB838C /* MKMapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4732E4A2211A1BF00CB838C /* MKMapView.swift */; };
21 | /* End PBXBuildFile section */
22 |
23 | /* Begin PBXFileReference section */
24 | D4732DE122106F5600CB838C /* MapKitSearchView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MapKitSearchView.framework; sourceTree = BUILT_PRODUCTS_DIR; };
25 | D4732DE522106F5600CB838C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
26 | D4732DEE221072A700CB838C /* MapKitSearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapKitSearchViewController.swift; sourceTree = ""; };
27 | D4732DEF221072A700CB838C /* MapKitSearchViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MapKitSearchViewController.xib; sourceTree = ""; };
28 | D4732DF32210733800CB838C /* MapTabsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapTabsView.swift; sourceTree = ""; };
29 | D4732DF52210760900CB838C /* BottomSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomSheetView.swift; sourceTree = ""; };
30 | D4732DF7221145C100CB838C /* SearchCompletionTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchCompletionTableViewCell.swift; sourceTree = ""; };
31 | D4732DF8221145C100CB838C /* SearchCompletionTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SearchCompletionTableViewCell.xib; sourceTree = ""; };
32 | D4732DFB2211461500CB838C /* MapItemTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapItemTableViewCell.swift; sourceTree = ""; };
33 | D4732DFC2211461500CB838C /* MapItemTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MapItemTableViewCell.xib; sourceTree = ""; };
34 | D4732E4622118C6500CB838C /* Material.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Material.xcassets; sourceTree = ""; };
35 | D4732E482211989400CB838C /* CLPlacemark.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLPlacemark.swift; sourceTree = ""; };
36 | D4732E4A2211A1BF00CB838C /* MKMapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MKMapView.swift; sourceTree = ""; };
37 | /* End PBXFileReference section */
38 |
39 | /* Begin PBXFrameworksBuildPhase section */
40 | D4732DDE22106F5600CB838C /* Frameworks */ = {
41 | isa = PBXFrameworksBuildPhase;
42 | buildActionMask = 2147483647;
43 | files = (
44 | );
45 | runOnlyForDeploymentPostprocessing = 0;
46 | };
47 | /* End PBXFrameworksBuildPhase section */
48 |
49 | /* Begin PBXGroup section */
50 | D4732DD722106F5600CB838C = {
51 | isa = PBXGroup;
52 | children = (
53 | D4732DE322106F5600CB838C /* Sources */,
54 | D4732DE222106F5600CB838C /* Products */,
55 | );
56 | sourceTree = "";
57 | };
58 | D4732DE222106F5600CB838C /* Products */ = {
59 | isa = PBXGroup;
60 | children = (
61 | D4732DE122106F5600CB838C /* MapKitSearchView.framework */,
62 | );
63 | name = Products;
64 | sourceTree = "";
65 | };
66 | D4732DE322106F5600CB838C /* Sources */ = {
67 | isa = PBXGroup;
68 | children = (
69 | D4732E4522118C4200CB838C /* Resources */,
70 | D4732E4422118C3C00CB838C /* Extensions */,
71 | D4732DF22210732100CB838C /* Objects */,
72 | D4732DE522106F5600CB838C /* Info.plist */,
73 | D4732DEE221072A700CB838C /* MapKitSearchViewController.swift */,
74 | D4732DEF221072A700CB838C /* MapKitSearchViewController.xib */,
75 | );
76 | path = Sources;
77 | sourceTree = "";
78 | };
79 | D4732DF22210732100CB838C /* Objects */ = {
80 | isa = PBXGroup;
81 | children = (
82 | D4732DF32210733800CB838C /* MapTabsView.swift */,
83 | D4732DF52210760900CB838C /* BottomSheetView.swift */,
84 | D4732DF7221145C100CB838C /* SearchCompletionTableViewCell.swift */,
85 | D4732DF8221145C100CB838C /* SearchCompletionTableViewCell.xib */,
86 | D4732DFB2211461500CB838C /* MapItemTableViewCell.swift */,
87 | D4732DFC2211461500CB838C /* MapItemTableViewCell.xib */,
88 | );
89 | path = Objects;
90 | sourceTree = "";
91 | };
92 | D4732E4422118C3C00CB838C /* Extensions */ = {
93 | isa = PBXGroup;
94 | children = (
95 | D4732E482211989400CB838C /* CLPlacemark.swift */,
96 | D4732E4A2211A1BF00CB838C /* MKMapView.swift */,
97 | );
98 | path = Extensions;
99 | sourceTree = "";
100 | };
101 | D4732E4522118C4200CB838C /* Resources */ = {
102 | isa = PBXGroup;
103 | children = (
104 | D4732E4622118C6500CB838C /* Material.xcassets */,
105 | );
106 | path = Resources;
107 | sourceTree = "";
108 | };
109 | /* End PBXGroup section */
110 |
111 | /* Begin PBXHeadersBuildPhase section */
112 | D4732DDC22106F5600CB838C /* Headers */ = {
113 | isa = PBXHeadersBuildPhase;
114 | buildActionMask = 2147483647;
115 | files = (
116 | );
117 | runOnlyForDeploymentPostprocessing = 0;
118 | };
119 | /* End PBXHeadersBuildPhase section */
120 |
121 | /* Begin PBXNativeTarget section */
122 | D4732DE022106F5600CB838C /* MapKitSearchView */ = {
123 | isa = PBXNativeTarget;
124 | buildConfigurationList = D4732DE922106F5600CB838C /* Build configuration list for PBXNativeTarget "MapKitSearchView" */;
125 | buildPhases = (
126 | D4732DDC22106F5600CB838C /* Headers */,
127 | D4732DDD22106F5600CB838C /* Sources */,
128 | D4732DDE22106F5600CB838C /* Frameworks */,
129 | D4732DDF22106F5600CB838C /* Resources */,
130 | );
131 | buildRules = (
132 | );
133 | dependencies = (
134 | );
135 | name = MapKitSearchView;
136 | productName = "MapKit Search View";
137 | productReference = D4732DE122106F5600CB838C /* MapKitSearchView.framework */;
138 | productType = "com.apple.product-type.framework";
139 | };
140 | /* End PBXNativeTarget section */
141 |
142 | /* Begin PBXProject section */
143 | D4732DD822106F5600CB838C /* Project object */ = {
144 | isa = PBXProject;
145 | attributes = {
146 | LastUpgradeCheck = 1010;
147 | ORGANIZATIONNAME = "Next Generation";
148 | TargetAttributes = {
149 | D4732DE022106F5600CB838C = {
150 | CreatedOnToolsVersion = 10.1;
151 | LastSwiftMigration = 1130;
152 | };
153 | };
154 | };
155 | buildConfigurationList = D4732DDB22106F5600CB838C /* Build configuration list for PBXProject "MapKitSearchView" */;
156 | compatibilityVersion = "Xcode 9.3";
157 | developmentRegion = en;
158 | hasScannedForEncodings = 0;
159 | knownRegions = (
160 | en,
161 | Base,
162 | );
163 | mainGroup = D4732DD722106F5600CB838C;
164 | productRefGroup = D4732DE222106F5600CB838C /* Products */;
165 | projectDirPath = "";
166 | projectRoot = "";
167 | targets = (
168 | D4732DE022106F5600CB838C /* MapKitSearchView */,
169 | );
170 | };
171 | /* End PBXProject section */
172 |
173 | /* Begin PBXResourcesBuildPhase section */
174 | D4732DDF22106F5600CB838C /* Resources */ = {
175 | isa = PBXResourcesBuildPhase;
176 | buildActionMask = 2147483647;
177 | files = (
178 | D4127ECC2217C5B500CF4C72 /* Material.xcassets in Resources */,
179 | D4127ECD2217C5B500CF4C72 /* SearchCompletionTableViewCell.xib in Resources */,
180 | D4127ECE2217C5B500CF4C72 /* MapItemTableViewCell.xib in Resources */,
181 | D4127ECF2217C5B500CF4C72 /* MapKitSearchViewController.xib in Resources */,
182 | );
183 | runOnlyForDeploymentPostprocessing = 0;
184 | };
185 | /* End PBXResourcesBuildPhase section */
186 |
187 | /* Begin PBXSourcesBuildPhase section */
188 | D4732DDD22106F5600CB838C /* Sources */ = {
189 | isa = PBXSourcesBuildPhase;
190 | buildActionMask = 2147483647;
191 | files = (
192 | D4732DF42210733800CB838C /* MapTabsView.swift in Sources */,
193 | D4732DF62210760900CB838C /* BottomSheetView.swift in Sources */,
194 | D4732E492211989400CB838C /* CLPlacemark.swift in Sources */,
195 | D4732E4B2211A1BF00CB838C /* MKMapView.swift in Sources */,
196 | D4732DF9221145C100CB838C /* SearchCompletionTableViewCell.swift in Sources */,
197 | D4732DFD2211461500CB838C /* MapItemTableViewCell.swift in Sources */,
198 | D4732DF0221072A700CB838C /* MapKitSearchViewController.swift in Sources */,
199 | );
200 | runOnlyForDeploymentPostprocessing = 0;
201 | };
202 | /* End PBXSourcesBuildPhase section */
203 |
204 | /* Begin XCBuildConfiguration section */
205 | D4732DE722106F5600CB838C /* Debug */ = {
206 | isa = XCBuildConfiguration;
207 | buildSettings = {
208 | ALWAYS_SEARCH_USER_PATHS = NO;
209 | CLANG_ANALYZER_NONNULL = YES;
210 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
211 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
212 | CLANG_CXX_LIBRARY = "libc++";
213 | CLANG_ENABLE_MODULES = YES;
214 | CLANG_ENABLE_OBJC_ARC = YES;
215 | CLANG_ENABLE_OBJC_WEAK = YES;
216 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
217 | CLANG_WARN_BOOL_CONVERSION = YES;
218 | CLANG_WARN_COMMA = YES;
219 | CLANG_WARN_CONSTANT_CONVERSION = YES;
220 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
221 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
222 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
223 | CLANG_WARN_EMPTY_BODY = YES;
224 | CLANG_WARN_ENUM_CONVERSION = YES;
225 | CLANG_WARN_INFINITE_RECURSION = YES;
226 | CLANG_WARN_INT_CONVERSION = YES;
227 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
228 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
229 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
230 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
231 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
232 | CLANG_WARN_STRICT_PROTOTYPES = YES;
233 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
234 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
235 | CLANG_WARN_UNREACHABLE_CODE = YES;
236 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
237 | CODE_SIGN_IDENTITY = "iPhone Developer";
238 | COPY_PHASE_STRIP = NO;
239 | CURRENT_PROJECT_VERSION = 1;
240 | DEBUG_INFORMATION_FORMAT = dwarf;
241 | ENABLE_STRICT_OBJC_MSGSEND = YES;
242 | ENABLE_TESTABILITY = YES;
243 | GCC_C_LANGUAGE_STANDARD = gnu11;
244 | GCC_DYNAMIC_NO_PIC = NO;
245 | GCC_NO_COMMON_BLOCKS = YES;
246 | GCC_OPTIMIZATION_LEVEL = 0;
247 | GCC_PREPROCESSOR_DEFINITIONS = (
248 | "DEBUG=1",
249 | "$(inherited)",
250 | );
251 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
252 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
253 | GCC_WARN_UNDECLARED_SELECTOR = YES;
254 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
255 | GCC_WARN_UNUSED_FUNCTION = YES;
256 | GCC_WARN_UNUSED_VARIABLE = YES;
257 | IPHONEOS_DEPLOYMENT_TARGET = 11.0;
258 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
259 | MTL_FAST_MATH = YES;
260 | ONLY_ACTIVE_ARCH = YES;
261 | SDKROOT = iphoneos;
262 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
263 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
264 | VERSIONING_SYSTEM = "apple-generic";
265 | VERSION_INFO_PREFIX = "";
266 | };
267 | name = Debug;
268 | };
269 | D4732DE822106F5600CB838C /* Release */ = {
270 | isa = XCBuildConfiguration;
271 | buildSettings = {
272 | ALWAYS_SEARCH_USER_PATHS = NO;
273 | CLANG_ANALYZER_NONNULL = YES;
274 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
275 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
276 | CLANG_CXX_LIBRARY = "libc++";
277 | CLANG_ENABLE_MODULES = YES;
278 | CLANG_ENABLE_OBJC_ARC = YES;
279 | CLANG_ENABLE_OBJC_WEAK = YES;
280 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
281 | CLANG_WARN_BOOL_CONVERSION = YES;
282 | CLANG_WARN_COMMA = YES;
283 | CLANG_WARN_CONSTANT_CONVERSION = YES;
284 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
285 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
286 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
287 | CLANG_WARN_EMPTY_BODY = YES;
288 | CLANG_WARN_ENUM_CONVERSION = YES;
289 | CLANG_WARN_INFINITE_RECURSION = YES;
290 | CLANG_WARN_INT_CONVERSION = YES;
291 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
292 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
293 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
294 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
295 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
296 | CLANG_WARN_STRICT_PROTOTYPES = YES;
297 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
298 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
299 | CLANG_WARN_UNREACHABLE_CODE = YES;
300 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
301 | CODE_SIGN_IDENTITY = "iPhone Developer";
302 | COPY_PHASE_STRIP = NO;
303 | CURRENT_PROJECT_VERSION = 1;
304 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
305 | ENABLE_NS_ASSERTIONS = NO;
306 | ENABLE_STRICT_OBJC_MSGSEND = YES;
307 | GCC_C_LANGUAGE_STANDARD = gnu11;
308 | GCC_NO_COMMON_BLOCKS = YES;
309 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
310 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
311 | GCC_WARN_UNDECLARED_SELECTOR = YES;
312 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
313 | GCC_WARN_UNUSED_FUNCTION = YES;
314 | GCC_WARN_UNUSED_VARIABLE = YES;
315 | IPHONEOS_DEPLOYMENT_TARGET = 11.0;
316 | MTL_ENABLE_DEBUG_INFO = NO;
317 | MTL_FAST_MATH = YES;
318 | SDKROOT = iphoneos;
319 | SWIFT_COMPILATION_MODE = wholemodule;
320 | SWIFT_OPTIMIZATION_LEVEL = "-O";
321 | VALIDATE_PRODUCT = YES;
322 | VERSIONING_SYSTEM = "apple-generic";
323 | VERSION_INFO_PREFIX = "";
324 | };
325 | name = Release;
326 | };
327 | D4732DEA22106F5600CB838C /* Debug */ = {
328 | isa = XCBuildConfiguration;
329 | buildSettings = {
330 | CLANG_ENABLE_MODULES = YES;
331 | CODE_SIGN_IDENTITY = "";
332 | CODE_SIGN_STYLE = Automatic;
333 | DEFINES_MODULE = YES;
334 | DYLIB_COMPATIBILITY_VERSION = 1;
335 | DYLIB_CURRENT_VERSION = 1;
336 | DYLIB_INSTALL_NAME_BASE = "@rpath";
337 | INFOPLIST_FILE = "$(SRCROOT)/Sources/Info.plist";
338 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
339 | IPHONEOS_DEPLOYMENT_TARGET = 11.0;
340 | LD_RUNPATH_SEARCH_PATHS = (
341 | "$(inherited)",
342 | "@executable_path/Frameworks",
343 | "@loader_path/Frameworks",
344 | );
345 | PRODUCT_BUNDLE_IDENTIFIER = com.nextgeneration.MapKitSearchView;
346 | PRODUCT_MODULE_NAME = "$(PRODUCT_NAME:c99extidentifier)";
347 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
348 | SKIP_INSTALL = YES;
349 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
350 | SWIFT_VERSION = 5.0;
351 | TARGETED_DEVICE_FAMILY = "1,2";
352 | };
353 | name = Debug;
354 | };
355 | D4732DEB22106F5600CB838C /* Release */ = {
356 | isa = XCBuildConfiguration;
357 | buildSettings = {
358 | CLANG_ENABLE_MODULES = YES;
359 | CODE_SIGN_IDENTITY = "";
360 | CODE_SIGN_STYLE = Automatic;
361 | DEFINES_MODULE = YES;
362 | DYLIB_COMPATIBILITY_VERSION = 1;
363 | DYLIB_CURRENT_VERSION = 1;
364 | DYLIB_INSTALL_NAME_BASE = "@rpath";
365 | INFOPLIST_FILE = "$(SRCROOT)/Sources/Info.plist";
366 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
367 | IPHONEOS_DEPLOYMENT_TARGET = 11.0;
368 | LD_RUNPATH_SEARCH_PATHS = (
369 | "$(inherited)",
370 | "@executable_path/Frameworks",
371 | "@loader_path/Frameworks",
372 | );
373 | PRODUCT_BUNDLE_IDENTIFIER = com.nextgeneration.MapKitSearchView;
374 | PRODUCT_MODULE_NAME = "$(PRODUCT_NAME:c99extidentifier)";
375 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
376 | SKIP_INSTALL = YES;
377 | SWIFT_VERSION = 5.0;
378 | TARGETED_DEVICE_FAMILY = "1,2";
379 | };
380 | name = Release;
381 | };
382 | /* End XCBuildConfiguration section */
383 |
384 | /* Begin XCConfigurationList section */
385 | D4732DDB22106F5600CB838C /* Build configuration list for PBXProject "MapKitSearchView" */ = {
386 | isa = XCConfigurationList;
387 | buildConfigurations = (
388 | D4732DE722106F5600CB838C /* Debug */,
389 | D4732DE822106F5600CB838C /* Release */,
390 | );
391 | defaultConfigurationIsVisible = 0;
392 | defaultConfigurationName = Release;
393 | };
394 | D4732DE922106F5600CB838C /* Build configuration list for PBXNativeTarget "MapKitSearchView" */ = {
395 | isa = XCConfigurationList;
396 | buildConfigurations = (
397 | D4732DEA22106F5600CB838C /* Debug */,
398 | D4732DEB22106F5600CB838C /* Release */,
399 | );
400 | defaultConfigurationIsVisible = 0;
401 | defaultConfigurationName = Release;
402 | };
403 | /* End XCConfigurationList section */
404 | };
405 | rootObject = D4732DD822106F5600CB838C /* Project object */;
406 | }
407 |
--------------------------------------------------------------------------------
/Sources/MapKitSearchViewController.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
--------------------------------------------------------------------------------
/Example/Example.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | D4732E382211883E00CB838C /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4732E302211883E00CB838C /* ViewController.swift */; };
11 | D4732E392211883E00CB838C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D4732E312211883E00CB838C /* Assets.xcassets */; };
12 | D4732E3A2211883E00CB838C /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D4732E322211883E00CB838C /* LaunchScreen.storyboard */; };
13 | D4732E3B2211883E00CB838C /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D4732E342211883E00CB838C /* Main.storyboard */; };
14 | D4732E3C2211883E00CB838C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4732E362211883E00CB838C /* AppDelegate.swift */; };
15 | E7D6B92717697B1E1B8AAA5A /* Pods_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E903782E0682BEF0C5F4105D /* Pods_Example.framework */; };
16 | /* End PBXBuildFile section */
17 |
18 | /* Begin PBXContainerItemProxy section */
19 | D4732E2822115EC900CB838C /* PBXContainerItemProxy */ = {
20 | isa = PBXContainerItemProxy;
21 | containerPortal = D4732E2422115EC900CB838C /* MapKit Search View.xcodeproj */;
22 | proxyType = 2;
23 | remoteGlobalIDString = D4732DE122106F5600CB838C;
24 | remoteInfo = "MapKit Search View";
25 | };
26 | D4732E2B22115F3F00CB838C /* PBXContainerItemProxy */ = {
27 | isa = PBXContainerItemProxy;
28 | containerPortal = D4732E2422115EC900CB838C /* MapKit Search View.xcodeproj */;
29 | proxyType = 1;
30 | remoteGlobalIDString = D4732DE022106F5600CB838C;
31 | remoteInfo = "MapKit Search View";
32 | };
33 | /* End PBXContainerItemProxy section */
34 |
35 | /* Begin PBXFileReference section */
36 | BBE95EB94C5931CA7BC5BD61 /* Pods-Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Example/Pods-Example.debug.xcconfig"; sourceTree = ""; };
37 | D4127ECA2217C50B00CF4C72 /* MapKitSearchView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MapKitSearchView.framework; sourceTree = BUILT_PRODUCTS_DIR; };
38 | D4732E0822115E7E00CB838C /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
39 | D4732E2422115EC900CB838C /* MapKit Search View.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = "MapKit Search View.xcodeproj"; path = "../../MapKit Search View.xcodeproj"; sourceTree = ""; };
40 | D4732E302211883E00CB838C /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
41 | D4732E312211883E00CB838C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
42 | D4732E332211883E00CB838C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
43 | D4732E352211883E00CB838C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
44 | D4732E362211883E00CB838C /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
45 | D4732E372211883E00CB838C /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
46 | D4732E40221188C600CB838C /* MapKitSearchView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MapKitSearchView.framework; sourceTree = BUILT_PRODUCTS_DIR; };
47 | D4732E422211890B00CB838C /* MapKitSearchView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MapKitSearchView.framework; sourceTree = BUILT_PRODUCTS_DIR; };
48 | E903782E0682BEF0C5F4105D /* Pods_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; };
49 | E97B4F795ABCC660B1ED204C /* Pods-Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example.release.xcconfig"; path = "Pods/Target Support Files/Pods-Example/Pods-Example.release.xcconfig"; sourceTree = ""; };
50 | /* End PBXFileReference section */
51 |
52 | /* Begin PBXFrameworksBuildPhase section */
53 | D4732E0522115E7E00CB838C /* Frameworks */ = {
54 | isa = PBXFrameworksBuildPhase;
55 | buildActionMask = 2147483647;
56 | files = (
57 | E7D6B92717697B1E1B8AAA5A /* Pods_Example.framework in Frameworks */,
58 | );
59 | runOnlyForDeploymentPostprocessing = 0;
60 | };
61 | /* End PBXFrameworksBuildPhase section */
62 |
63 | /* Begin PBXGroup section */
64 | A1EEDBFA630BC8E178EB4F98 /* Pods */ = {
65 | isa = PBXGroup;
66 | children = (
67 | BBE95EB94C5931CA7BC5BD61 /* Pods-Example.debug.xcconfig */,
68 | E97B4F795ABCC660B1ED204C /* Pods-Example.release.xcconfig */,
69 | );
70 | name = Pods;
71 | sourceTree = "";
72 | };
73 | D4732DFF22115E7E00CB838C = {
74 | isa = PBXGroup;
75 | children = (
76 | D4732E2F2211883E00CB838C /* Sources */,
77 | D4732E0922115E7E00CB838C /* Products */,
78 | D4732E2322115EC900CB838C /* Frameworks */,
79 | A1EEDBFA630BC8E178EB4F98 /* Pods */,
80 | );
81 | sourceTree = "";
82 | };
83 | D4732E0922115E7E00CB838C /* Products */ = {
84 | isa = PBXGroup;
85 | children = (
86 | D4732E0822115E7E00CB838C /* Example.app */,
87 | );
88 | name = Products;
89 | sourceTree = "";
90 | };
91 | D4732E2322115EC900CB838C /* Frameworks */ = {
92 | isa = PBXGroup;
93 | children = (
94 | D4127ECA2217C50B00CF4C72 /* MapKitSearchView.framework */,
95 | D4732E422211890B00CB838C /* MapKitSearchView.framework */,
96 | D4732E40221188C600CB838C /* MapKitSearchView.framework */,
97 | D4732E2422115EC900CB838C /* MapKit Search View.xcodeproj */,
98 | E903782E0682BEF0C5F4105D /* Pods_Example.framework */,
99 | );
100 | name = Frameworks;
101 | sourceTree = "";
102 | };
103 | D4732E2522115EC900CB838C /* Products */ = {
104 | isa = PBXGroup;
105 | children = (
106 | D4732E2922115EC900CB838C /* MapKit_Search_View.framework */,
107 | );
108 | name = Products;
109 | sourceTree = "";
110 | };
111 | D4732E2F2211883E00CB838C /* Sources */ = {
112 | isa = PBXGroup;
113 | children = (
114 | D4732E302211883E00CB838C /* ViewController.swift */,
115 | D4732E312211883E00CB838C /* Assets.xcassets */,
116 | D4732E322211883E00CB838C /* LaunchScreen.storyboard */,
117 | D4732E342211883E00CB838C /* Main.storyboard */,
118 | D4732E362211883E00CB838C /* AppDelegate.swift */,
119 | D4732E372211883E00CB838C /* Info.plist */,
120 | );
121 | path = Sources;
122 | sourceTree = "";
123 | };
124 | /* End PBXGroup section */
125 |
126 | /* Begin PBXNativeTarget section */
127 | D4732E0722115E7E00CB838C /* Example */ = {
128 | isa = PBXNativeTarget;
129 | buildConfigurationList = D4732E1A22115E8100CB838C /* Build configuration list for PBXNativeTarget "Example" */;
130 | buildPhases = (
131 | 4CD975D931FCA0B490768C04 /* [CP] Check Pods Manifest.lock */,
132 | D4732E0422115E7E00CB838C /* Sources */,
133 | D4732E0522115E7E00CB838C /* Frameworks */,
134 | D4732E0622115E7E00CB838C /* Resources */,
135 | D9661FC85311E967B4C0BE75 /* [CP] Embed Pods Frameworks */,
136 | );
137 | buildRules = (
138 | );
139 | dependencies = (
140 | D4732E2C22115F3F00CB838C /* PBXTargetDependency */,
141 | );
142 | name = Example;
143 | productName = Example;
144 | productReference = D4732E0822115E7E00CB838C /* Example.app */;
145 | productType = "com.apple.product-type.application";
146 | };
147 | /* End PBXNativeTarget section */
148 |
149 | /* Begin PBXProject section */
150 | D4732E0022115E7E00CB838C /* Project object */ = {
151 | isa = PBXProject;
152 | attributes = {
153 | LastSwiftUpdateCheck = 1010;
154 | LastUpgradeCheck = 1010;
155 | ORGANIZATIONNAME = "Next Generation";
156 | TargetAttributes = {
157 | D4732E0722115E7E00CB838C = {
158 | CreatedOnToolsVersion = 10.1;
159 | };
160 | };
161 | };
162 | buildConfigurationList = D4732E0322115E7E00CB838C /* Build configuration list for PBXProject "Example" */;
163 | compatibilityVersion = "Xcode 9.3";
164 | developmentRegion = en;
165 | hasScannedForEncodings = 0;
166 | knownRegions = (
167 | en,
168 | Base,
169 | );
170 | mainGroup = D4732DFF22115E7E00CB838C;
171 | productRefGroup = D4732E0922115E7E00CB838C /* Products */;
172 | projectDirPath = "";
173 | projectReferences = (
174 | {
175 | ProductGroup = D4732E2522115EC900CB838C /* Products */;
176 | ProjectRef = D4732E2422115EC900CB838C /* MapKit Search View.xcodeproj */;
177 | },
178 | );
179 | projectRoot = "";
180 | targets = (
181 | D4732E0722115E7E00CB838C /* Example */,
182 | );
183 | };
184 | /* End PBXProject section */
185 |
186 | /* Begin PBXReferenceProxy section */
187 | D4732E2922115EC900CB838C /* MapKit_Search_View.framework */ = {
188 | isa = PBXReferenceProxy;
189 | fileType = wrapper.framework;
190 | path = MapKit_Search_View.framework;
191 | remoteRef = D4732E2822115EC900CB838C /* PBXContainerItemProxy */;
192 | sourceTree = BUILT_PRODUCTS_DIR;
193 | };
194 | /* End PBXReferenceProxy section */
195 |
196 | /* Begin PBXResourcesBuildPhase section */
197 | D4732E0622115E7E00CB838C /* Resources */ = {
198 | isa = PBXResourcesBuildPhase;
199 | buildActionMask = 2147483647;
200 | files = (
201 | D4732E3B2211883E00CB838C /* Main.storyboard in Resources */,
202 | D4732E392211883E00CB838C /* Assets.xcassets in Resources */,
203 | D4732E3A2211883E00CB838C /* LaunchScreen.storyboard in Resources */,
204 | );
205 | runOnlyForDeploymentPostprocessing = 0;
206 | };
207 | /* End PBXResourcesBuildPhase section */
208 |
209 | /* Begin PBXShellScriptBuildPhase section */
210 | 4CD975D931FCA0B490768C04 /* [CP] Check Pods Manifest.lock */ = {
211 | isa = PBXShellScriptBuildPhase;
212 | buildActionMask = 2147483647;
213 | files = (
214 | );
215 | inputPaths = (
216 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
217 | "${PODS_ROOT}/Manifest.lock",
218 | );
219 | name = "[CP] Check Pods Manifest.lock";
220 | outputPaths = (
221 | "$(DERIVED_FILE_DIR)/Pods-Example-checkManifestLockResult.txt",
222 | );
223 | runOnlyForDeploymentPostprocessing = 0;
224 | shellPath = /bin/sh;
225 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
226 | showEnvVarsInLog = 0;
227 | };
228 | D9661FC85311E967B4C0BE75 /* [CP] Embed Pods Frameworks */ = {
229 | isa = PBXShellScriptBuildPhase;
230 | buildActionMask = 2147483647;
231 | files = (
232 | );
233 | inputFileListPaths = (
234 | "${PODS_ROOT}/Target Support Files/Pods-Example/Pods-Example-frameworks-${CONFIGURATION}-input-files.xcfilelist",
235 | );
236 | name = "[CP] Embed Pods Frameworks";
237 | outputFileListPaths = (
238 | "${PODS_ROOT}/Target Support Files/Pods-Example/Pods-Example-frameworks-${CONFIGURATION}-output-files.xcfilelist",
239 | );
240 | runOnlyForDeploymentPostprocessing = 0;
241 | shellPath = /bin/sh;
242 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Example/Pods-Example-frameworks.sh\"\n";
243 | showEnvVarsInLog = 0;
244 | };
245 | /* End PBXShellScriptBuildPhase section */
246 |
247 | /* Begin PBXSourcesBuildPhase section */
248 | D4732E0422115E7E00CB838C /* Sources */ = {
249 | isa = PBXSourcesBuildPhase;
250 | buildActionMask = 2147483647;
251 | files = (
252 | D4732E3C2211883E00CB838C /* AppDelegate.swift in Sources */,
253 | D4732E382211883E00CB838C /* ViewController.swift in Sources */,
254 | );
255 | runOnlyForDeploymentPostprocessing = 0;
256 | };
257 | /* End PBXSourcesBuildPhase section */
258 |
259 | /* Begin PBXTargetDependency section */
260 | D4732E2C22115F3F00CB838C /* PBXTargetDependency */ = {
261 | isa = PBXTargetDependency;
262 | name = "MapKit Search View";
263 | targetProxy = D4732E2B22115F3F00CB838C /* PBXContainerItemProxy */;
264 | };
265 | /* End PBXTargetDependency section */
266 |
267 | /* Begin PBXVariantGroup section */
268 | D4732E322211883E00CB838C /* LaunchScreen.storyboard */ = {
269 | isa = PBXVariantGroup;
270 | children = (
271 | D4732E332211883E00CB838C /* Base */,
272 | );
273 | name = LaunchScreen.storyboard;
274 | sourceTree = "";
275 | };
276 | D4732E342211883E00CB838C /* Main.storyboard */ = {
277 | isa = PBXVariantGroup;
278 | children = (
279 | D4732E352211883E00CB838C /* Base */,
280 | );
281 | name = Main.storyboard;
282 | sourceTree = "";
283 | };
284 | /* End PBXVariantGroup section */
285 |
286 | /* Begin XCBuildConfiguration section */
287 | D4732E1822115E8100CB838C /* Debug */ = {
288 | isa = XCBuildConfiguration;
289 | buildSettings = {
290 | ALWAYS_SEARCH_USER_PATHS = NO;
291 | CLANG_ANALYZER_NONNULL = YES;
292 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
293 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
294 | CLANG_CXX_LIBRARY = "libc++";
295 | CLANG_ENABLE_MODULES = YES;
296 | CLANG_ENABLE_OBJC_ARC = YES;
297 | CLANG_ENABLE_OBJC_WEAK = YES;
298 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
299 | CLANG_WARN_BOOL_CONVERSION = YES;
300 | CLANG_WARN_COMMA = YES;
301 | CLANG_WARN_CONSTANT_CONVERSION = YES;
302 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
303 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
304 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
305 | CLANG_WARN_EMPTY_BODY = YES;
306 | CLANG_WARN_ENUM_CONVERSION = YES;
307 | CLANG_WARN_INFINITE_RECURSION = YES;
308 | CLANG_WARN_INT_CONVERSION = YES;
309 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
310 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
311 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
312 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
313 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
314 | CLANG_WARN_STRICT_PROTOTYPES = YES;
315 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
316 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
317 | CLANG_WARN_UNREACHABLE_CODE = YES;
318 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
319 | CODE_SIGN_IDENTITY = "iPhone Developer";
320 | COPY_PHASE_STRIP = NO;
321 | DEBUG_INFORMATION_FORMAT = dwarf;
322 | ENABLE_STRICT_OBJC_MSGSEND = YES;
323 | ENABLE_TESTABILITY = YES;
324 | GCC_C_LANGUAGE_STANDARD = gnu11;
325 | GCC_DYNAMIC_NO_PIC = NO;
326 | GCC_NO_COMMON_BLOCKS = YES;
327 | GCC_OPTIMIZATION_LEVEL = 0;
328 | GCC_PREPROCESSOR_DEFINITIONS = (
329 | "DEBUG=1",
330 | "$(inherited)",
331 | );
332 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
333 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
334 | GCC_WARN_UNDECLARED_SELECTOR = YES;
335 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
336 | GCC_WARN_UNUSED_FUNCTION = YES;
337 | GCC_WARN_UNUSED_VARIABLE = YES;
338 | IPHONEOS_DEPLOYMENT_TARGET = 12.1;
339 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
340 | MTL_FAST_MATH = YES;
341 | ONLY_ACTIVE_ARCH = YES;
342 | SDKROOT = iphoneos;
343 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
344 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
345 | };
346 | name = Debug;
347 | };
348 | D4732E1922115E8100CB838C /* Release */ = {
349 | isa = XCBuildConfiguration;
350 | buildSettings = {
351 | ALWAYS_SEARCH_USER_PATHS = NO;
352 | CLANG_ANALYZER_NONNULL = YES;
353 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
354 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
355 | CLANG_CXX_LIBRARY = "libc++";
356 | CLANG_ENABLE_MODULES = YES;
357 | CLANG_ENABLE_OBJC_ARC = YES;
358 | CLANG_ENABLE_OBJC_WEAK = YES;
359 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
360 | CLANG_WARN_BOOL_CONVERSION = YES;
361 | CLANG_WARN_COMMA = YES;
362 | CLANG_WARN_CONSTANT_CONVERSION = YES;
363 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
364 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
365 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
366 | CLANG_WARN_EMPTY_BODY = YES;
367 | CLANG_WARN_ENUM_CONVERSION = YES;
368 | CLANG_WARN_INFINITE_RECURSION = YES;
369 | CLANG_WARN_INT_CONVERSION = YES;
370 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
371 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
372 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
373 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
374 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
375 | CLANG_WARN_STRICT_PROTOTYPES = YES;
376 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
377 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
378 | CLANG_WARN_UNREACHABLE_CODE = YES;
379 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
380 | CODE_SIGN_IDENTITY = "iPhone Developer";
381 | COPY_PHASE_STRIP = NO;
382 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
383 | ENABLE_NS_ASSERTIONS = NO;
384 | ENABLE_STRICT_OBJC_MSGSEND = YES;
385 | GCC_C_LANGUAGE_STANDARD = gnu11;
386 | GCC_NO_COMMON_BLOCKS = YES;
387 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
388 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
389 | GCC_WARN_UNDECLARED_SELECTOR = YES;
390 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
391 | GCC_WARN_UNUSED_FUNCTION = YES;
392 | GCC_WARN_UNUSED_VARIABLE = YES;
393 | IPHONEOS_DEPLOYMENT_TARGET = 12.1;
394 | MTL_ENABLE_DEBUG_INFO = NO;
395 | MTL_FAST_MATH = YES;
396 | SDKROOT = iphoneos;
397 | SWIFT_COMPILATION_MODE = wholemodule;
398 | SWIFT_OPTIMIZATION_LEVEL = "-O";
399 | VALIDATE_PRODUCT = YES;
400 | };
401 | name = Release;
402 | };
403 | D4732E1B22115E8100CB838C /* Debug */ = {
404 | isa = XCBuildConfiguration;
405 | baseConfigurationReference = BBE95EB94C5931CA7BC5BD61 /* Pods-Example.debug.xcconfig */;
406 | buildSettings = {
407 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
408 | CODE_SIGN_STYLE = Automatic;
409 | INFOPLIST_FILE = "$(SRCROOT)/Sources/Info.plist";
410 | LD_RUNPATH_SEARCH_PATHS = (
411 | "$(inherited)",
412 | "@executable_path/Frameworks",
413 | );
414 | PRODUCT_BUNDLE_IDENTIFIER = com.nextgeneration.Example;
415 | PRODUCT_NAME = "$(TARGET_NAME)";
416 | SWIFT_VERSION = 4.2;
417 | TARGETED_DEVICE_FAMILY = "1,2";
418 | };
419 | name = Debug;
420 | };
421 | D4732E1C22115E8100CB838C /* Release */ = {
422 | isa = XCBuildConfiguration;
423 | baseConfigurationReference = E97B4F795ABCC660B1ED204C /* Pods-Example.release.xcconfig */;
424 | buildSettings = {
425 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
426 | CODE_SIGN_STYLE = Automatic;
427 | INFOPLIST_FILE = "$(SRCROOT)/Sources/Info.plist";
428 | LD_RUNPATH_SEARCH_PATHS = (
429 | "$(inherited)",
430 | "@executable_path/Frameworks",
431 | );
432 | PRODUCT_BUNDLE_IDENTIFIER = com.nextgeneration.Example;
433 | PRODUCT_NAME = "$(TARGET_NAME)";
434 | SWIFT_VERSION = 4.2;
435 | TARGETED_DEVICE_FAMILY = "1,2";
436 | };
437 | name = Release;
438 | };
439 | /* End XCBuildConfiguration section */
440 |
441 | /* Begin XCConfigurationList section */
442 | D4732E0322115E7E00CB838C /* Build configuration list for PBXProject "Example" */ = {
443 | isa = XCConfigurationList;
444 | buildConfigurations = (
445 | D4732E1822115E8100CB838C /* Debug */,
446 | D4732E1922115E8100CB838C /* Release */,
447 | );
448 | defaultConfigurationIsVisible = 0;
449 | defaultConfigurationName = Release;
450 | };
451 | D4732E1A22115E8100CB838C /* Build configuration list for PBXNativeTarget "Example" */ = {
452 | isa = XCConfigurationList;
453 | buildConfigurations = (
454 | D4732E1B22115E8100CB838C /* Debug */,
455 | D4732E1C22115E8100CB838C /* Release */,
456 | );
457 | defaultConfigurationIsVisible = 0;
458 | defaultConfigurationName = Release;
459 | };
460 | /* End XCConfigurationList section */
461 | };
462 | rootObject = D4732E0022115E7E00CB838C /* Project object */;
463 | }
464 |
--------------------------------------------------------------------------------
/Sources/MapKitSearchViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MapKitSearchViewController.swift
3 | // MapKitSearchView
4 | //
5 | // Created by Philip on 10/11/18.
6 | // Copyright © 2018 Next Generation. All rights reserved.
7 | //
8 |
9 | import MapKit
10 | import UIKit
11 |
12 | public class MapKitSearchViewController: UIViewController, UIGestureRecognizerDelegate {
13 | @IBOutlet public var mapView: MKMapView!
14 | // MARK: - Tab Views
15 | @IBOutlet public var close: UIButton!
16 | @IBAction public func closeDidTap() {
17 | if let navigationController = navigationController {
18 | navigationController.popViewController(animated: true)
19 | } else {
20 | dismiss(animated: true)
21 | }
22 | }
23 | @IBOutlet private var nearMeParent: UIView!
24 | public var nearMe: MKUserTrackingButton?
25 | @IBOutlet private var compassParent: UIView!
26 | public var compass: MKCompassButton?
27 | @IBOutlet public var tabView: UIView!
28 |
29 | // MARK: - Bottom Sheet Views & Variables
30 | @IBOutlet public var searchBar: UISearchBar!
31 | @IBOutlet public var tableView: UITableView!
32 | // Stack view current layout height.
33 | @IBOutlet private var stackViewHeight: NSLayoutConstraint!
34 | // Stack view height if expanded.
35 | private var stackViewExpandedHeight: CGFloat?
36 |
37 | ///This ratio controls how large the expanded stack can be relative to the height of the view. A value of >= 1.0 will consume the entire height. A value of 0.5 is
38 | ///half the height, a value of 0 will not take up any height (except allowing for the search bar and keyboard). The default is 0.45, just slightly less than half to show the
39 | ///current position marker. Note: the expanded height can grow when scrolling through the list.
40 | public var expandedRatio : CGFloat = 0.45
41 |
42 | // Maximum height for expanded stack view (when scrolling the list)
43 | private var stackViewMaxExpandedHeight: CGFloat {
44 | return view.frame.height - 160
45 | }
46 | // Maximum height for stack view when navigating map.
47 | private var stackViewMaxMapInteractedHeight: CGFloat {
48 |
49 | let ratio = min(expandedRatio, 1.0)
50 | return max((view.frame.height) * ratio, searchBarHeight)
51 | }
52 | // Initial table view y offset when beginning pan.
53 | private var tableViewPanInitialOffset: CGFloat?
54 | private var tableViewHeight: CGFloat {
55 | return tableView.frame.height
56 | }
57 | private var tableViewContentHeight: CGFloat {
58 | return tableView.backgroundView?.bounds.size.height ?? tableView.contentSize.height
59 | }
60 | private var searchBarHeight: CGFloat {
61 | return searchBar.frame.height
62 | }
63 | private var searchBarText: String {
64 | return searchBar.text ?? ""
65 | }
66 | private var safeAreaInsetsBottom: CGFloat {
67 | return view.safeAreaInsets.bottom
68 | }
69 | private var keyboardHeight: CGFloat = 0
70 | private var isExpanded = false
71 | private var isDragged = false {
72 | didSet {
73 | if isDragged {
74 | searchBar.resignFirstResponder() // On drag, dismiss Keyboard
75 | }
76 | }
77 | }
78 | private var isUserMapInteracted = false {
79 | didSet {
80 | if isUserMapInteracted {
81 | userDidMapInteract()
82 | }
83 | }
84 | }
85 | private var isUserInteracted: Bool {
86 | // User interacted if dragging gesture or map interaction.
87 | return isDragged || isUserMapInteracted
88 | }
89 |
90 | // MARK: - Search Variables
91 | private var searchCompletionRequest: MKLocalSearchCompleter? = MKLocalSearchCompleter()
92 | private var searchCompletions = [MKLocalSearchCompletion]()
93 |
94 | private var searchRequestFuture: Timer?
95 | private var searchRequest: MKLocalSearch?
96 | private var searchMapItems = [MKMapItem]()
97 |
98 | private var tableViewType: TableType = .searchCompletion {
99 | didSet {
100 | switch tableViewType {
101 | case .searchCompletion:
102 | tableView.separatorInset = UIEdgeInsets(top: 0, left: 15, bottom: 0, right: 0)
103 | case .mapItem:
104 | tableView.separatorInset = UIEdgeInsets(top: 0, left: 67, bottom: 0, right: 0)
105 | }
106 | tableView.reloadData()
107 | }
108 | }
109 | private enum TableType {
110 | case searchCompletion
111 | case mapItem
112 | }
113 |
114 | private var geocodeRequestFuture: Timer?
115 | private var geocodeRequest: CLGeocoder? = CLGeocoder()
116 |
117 | private var mapAnnotations = Set()
118 |
119 | private let locationManager = CLLocationManager()
120 |
121 | open var delegate: MapKitSearchDelegate?
122 |
123 | open var tintColor: UIColor? {
124 | didSet {
125 | guard tintColor != oldValue else {
126 | return
127 | }
128 | close.tintColor = tintColor
129 | nearMe?.tintColor = tintColor
130 | searchBarTextField?.tintColor = tintColor
131 | }
132 | }
133 | open var markerTintColor: UIColor? {
134 | didSet {
135 | guard markerTintColor != oldValue else {
136 | return
137 | }
138 | if tableViewType == .mapItem {
139 | tableView.reloadData()
140 | }
141 | }
142 | }
143 | open var searchBarTextField: UITextField? {
144 | return searchBar.value(forKey: "searchField") as? UITextField
145 | }
146 | open var completionEnabled = true {
147 | didSet {
148 | if !completionEnabled {
149 | searchCompletionRequest = nil
150 | } else if searchCompletionRequest == nil {
151 | searchCompletionRequest = MKLocalSearchCompleter()
152 | }
153 | }
154 | }
155 | open var geocodeEnabled = true {
156 | didSet {
157 | if !geocodeEnabled {
158 | geocodeRequest = nil
159 | } else if geocodeRequest == nil {
160 | geocodeRequest = CLGeocoder()
161 | }
162 | }
163 | }
164 | open var userLocationRequest: CLAuthorizationStatus?
165 | open var alertSubtitle: String?
166 |
167 | convenience public init(delegate: MapKitSearchDelegate?) {
168 | self.init(nibName: "MapKitSearchViewController", bundle: Bundle(for: MapKitSearchViewController.self))
169 | self.delegate = delegate
170 | }
171 |
172 | // MARK: - Setup
173 | override public func viewDidLoad() {
174 | super.viewDidLoad()
175 | nearMe = MKUserTrackingButton(mapView: mapView)
176 | nearMe!.frame.size = CGSize(width: 24, height: 24)
177 | nearMeParent.addSubview(nearMe!)
178 | compass = MKCompassButton(mapView: mapView)
179 | compassParent.addSubview(compass!)
180 | let pan = UIPanGestureRecognizer(target: self, action: #selector(mapView(isPan:)))
181 | pan.delegate = self
182 | mapView.addGestureRecognizer(pan)
183 | let pinch = UIPinchGestureRecognizer(target: self, action: #selector(mapView(isPinch:)))
184 | pinch.delegate = self
185 | mapView.addGestureRecognizer(pinch)
186 | mapView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(mapView(isTap:))))
187 | searchBar.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(searchBar(isPan:))))
188 | tableView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(tableView(isPan:))))
189 | tableView.register(UINib(nibName: "SearchCompletionTableViewCell", bundle: Bundle(for: SearchCompletionTableViewCell.self)), forCellReuseIdentifier: "SearchCompletion")
190 | tableView.register(UINib(nibName: "MapItemTableViewCell", bundle: Bundle(for: MapItemTableViewCell.self)), forCellReuseIdentifier: "MapItem")
191 | mapView.delegate = self
192 | searchBar.delegate = self
193 | searchCompletionRequest?.region = mapView.region
194 | searchCompletionRequest?.delegate = self
195 | tableView.isScrollEnabled = false
196 | tableView.dataSource = self
197 | tableView.delegate = self
198 | tableView.tableFooterView = UIView()
199 | locationManager.delegate = self
200 | NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil)
201 | NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil)
202 | // Invoke didSet of respective properties.
203 | self.tintColor = { tintColor }()
204 | self.markerTintColor = { markerTintColor }()
205 | if let userLocationRequest = userLocationRequest {
206 | locationManagerRequestLocation(withPermission: userLocationRequest)
207 | }
208 | if let searchBarTextField = searchBarTextField {
209 | searchBarTextField.font = UIFont.systemFont(ofSize: 15)
210 | }
211 | }
212 |
213 | deinit {
214 | NotificationCenter.default.removeObserver(self)
215 | }
216 |
217 | override public var supportedInterfaceOrientations: UIInterfaceOrientationMask {
218 | return .portrait
219 | }
220 |
221 | // Recognize added Gesture Recognizer with existing MapView Gesture Recognizers.
222 | public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
223 | return true
224 | }
225 |
226 | // MARK: - Map Gestures
227 | @objc private func mapView(isPan gesture: UIPanGestureRecognizer) {
228 | switch gesture.state {
229 | case .began:
230 | searchBar.resignFirstResponder()
231 | isUserMapInteracted = true
232 | break
233 | case .ended:
234 | // Add more results on mapView
235 | searchRequestInFuture(isMapPan: true)
236 | isUserMapInteracted = false
237 | break
238 | default:
239 | break
240 | }
241 | }
242 |
243 | @objc private func mapView(isPinch gesture: UIPinchGestureRecognizer) {
244 | switch gesture.state {
245 | case .began:
246 | searchBar.resignFirstResponder()
247 | isUserMapInteracted = true
248 | break
249 | case .ended:
250 | // Add more results on mapView
251 | searchRequestInFuture(isMapPan: true)
252 | isUserMapInteracted = false
253 | break
254 | default:
255 | break
256 | }
257 | }
258 |
259 | @objc func mapView(isTap gesture: UITapGestureRecognizer) {
260 | // If tap is coinciding with pan or pinch gesture, don't geocode.
261 | guard !isUserMapInteracted else {
262 | geocodeRequestCancel()
263 | return
264 | }
265 | // If typing or tableView scrolled, only resize bottom sheet.
266 | guard !searchBar.isFirstResponder && tableView.contentOffset.y == 0 else {
267 | geocodeRequestCancel()
268 | isUserMapInteracted = true
269 | isUserMapInteracted = false
270 | return
271 | }
272 | let coordinate = mapView.convert(gesture.location(in: mapView), toCoordinateFrom: mapView)
273 | let location = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
274 | geocodeRequestInFuture(withLocation: location)
275 | }
276 |
277 | private func userDidMapInteract() {
278 | tableView.setContentOffset(CGPoint(x: 0, y: 0), animated: false)
279 | searchBar.resignFirstResponder()
280 | if isExpanded, stackViewHeight.constant > stackViewMaxMapInteractedHeight {
281 | tableViewShow()
282 | }
283 | }
284 |
285 | // MARK: - Geocode
286 | private func geocodeRequestInFuture(withLocation location: CLLocation, timeInterval: Double = 1.5, repeats: Bool = false) {
287 | guard geocodeEnabled else {
288 | return
289 | }
290 | guard mapView.mapBoundsDistance <= 20000 else {
291 | // Less than 20KM (Street Level) otherwise don't geocode.
292 | return
293 | }
294 | geocodeRequestCancel()
295 | geocodeRequestFuture = Timer.scheduledTimer(withTimeInterval: timeInterval, repeats: repeats) { [weak self] _ in
296 | guard let self = self, !self.isUserMapInteracted else {
297 | return
298 | }
299 | self.geocodeRequest?.reverseGeocodeLocation(location) { [weak self] (placemarks, error) in
300 | self?.geocodeRequestDidComplete(withPlacemarks: placemarks, error: error)
301 | }
302 | }
303 | }
304 |
305 | public func geocodeRequestDidComplete(withPlacemarks placemarks: [CLPlacemark]?, error: Error?) {
306 | guard let originalPlacemark = placemarks?.first, let placemark = originalPlacemark.mkPlacemark else {
307 | return
308 | }
309 | let mapItem = MKMapItem(placemark: placemark)
310 |
311 | delegate?.mapKitSearch(self, mapItem: mapItem)
312 | delegate?.mapKitSearch(self, userSelectedGeocodeItem: mapItem)
313 | }
314 |
315 | private func geocodeRequestCancel() {
316 | geocodeRequestFuture?.invalidate()
317 | geocodeRequest?.cancelGeocode()
318 | }
319 |
320 | // MARK: - Bottom Sheet Gestures
321 | @objc private func searchBar(isPan gesture: UIPanGestureRecognizer) {
322 | guard tableView.numberOfRows(inSection: 0) > 0 else {
323 | return
324 | }
325 | let translationY = gesture.translation(in: view).y
326 | switch gesture.state {
327 | case .began:
328 | isDragged = true
329 | case .ended:
330 | bottomSheetDidDrag(completedTranslationY: translationY)
331 | break
332 | default:
333 | if translationY > 0 , let stackViewExpandedHeight = stackViewExpandedHeight {
334 | // Drag down. Can't drag below searchBarHeight
335 | stackViewHeight.constant = max(stackViewExpandedHeight - translationY, searchBarHeight)
336 | } else if translationY < 0 {
337 | // Drag up. Can't drag above stackViewMaxDraggableHeight
338 | if let stackViewExpandedHeight = stackViewExpandedHeight, isExpanded {
339 | // stackViewExpandedHeight always contains keyboardHeight
340 | stackViewHeight.constant = min(stackViewMaxExpandedHeight, stackViewExpandedHeight - translationY)
341 | } else {
342 | stackViewHeight.constant = min(stackViewMaxExpandedHeight, searchBarHeight + keyboardHeight - translationY)
343 | }
344 | }
345 | break
346 | }
347 | }
348 |
349 | @objc func tableView(isPan gesture: UIPanGestureRecognizer) {
350 | guard tableView.numberOfRows(inSection: 0) > 0 else {
351 | return
352 | }
353 | let translationY = gesture.translation(in: view).y
354 | switch gesture.state {
355 | case .began:
356 | isDragged = true
357 | tableViewPanInitialOffset = tableView.contentOffset.y
358 | break
359 | case .ended:
360 | bottomSheetDidDrag(completedTranslationY: translationY)
361 | // If bounced bottom, rebounce upwards
362 | if tableView.contentOffset.y > tableViewContentHeight - tableView.frame.size.height {
363 | tableView.setContentOffset(CGPoint(x: 0, y: max(0, tableViewContentHeight - tableView.frame.size.height)), animated: true)
364 | }
365 | tableViewPanInitialOffset = nil
366 | break
367 | default:
368 | guard let tableViewPanInitialOffset = tableViewPanInitialOffset else {
369 | return
370 | }
371 | let stackViewTranslation = tableViewPanInitialOffset - translationY
372 | tableView.contentOffset.y = max(0, stackViewTranslation)
373 |
374 | //Removed this code because scroll down in the table view hides the table when hitting the top, undesired behavior
375 | //if stackViewTranslation < 0, let stackViewExpandedHeight = stackViewExpandedHeight {
376 | // stackViewHeight.constant = max(searchBarHeight, stackViewExpandedHeight + stackViewTranslation)
377 | //}
378 | }
379 | }
380 |
381 | private func bottomSheetDidDrag(completedTranslationY translationY: CGFloat) {
382 | isDragged = false
383 | if let stackViewExpandedHeight = stackViewExpandedHeight { // Has expanded.
384 | if isExpanded { // If already expanded
385 | if stackViewExpandedHeight < 100 && translationY > 5 {
386 | tableViewHide() // If bottom sheet height <100 and dragged down 5 pixels
387 | } else if stackViewHeight.constant > (stackViewExpandedHeight * 0.85) {
388 | tableViewShow() // If dragged down < 15%
389 | } else {
390 | tableViewHide() // If dragged down >= 15%
391 | }
392 | } else {
393 | if stackViewExpandedHeight < 100 && translationY < -5 {
394 | tableViewShow() // If bottom sheet height <100 and dragged up 5 pixels
395 | } else if stackViewHeight.constant > (stackViewExpandedHeight * 0.15) {
396 | tableViewShow() // If dragged up > 15%
397 | } else {
398 | tableViewHide() // If dragged up <= 15%
399 | }
400 | }
401 | } else {
402 | tableViewHide()
403 | }
404 | }
405 |
406 | // MARK: - Search Completions
407 | // Search Completions Request are invoked on textDidChange in searchBar,
408 | // and region is updated upon regionDidChange in mapView.
409 | private func searchCompletionRequest(didComplete searchCompletions: [MKLocalSearchCompletion]) {
410 | searchRequestCancel()
411 | self.searchCompletions = searchCompletions
412 | tableViewType = .searchCompletion
413 | tableViewShow()
414 | }
415 |
416 | private func searchCompletionRequestCancel() {
417 | searchCompletionRequest?.delegate = nil
418 | searchCompletionRequest?.region = mapView.region
419 | searchCompletionRequest?.delegate = self
420 | }
421 |
422 | // MARK: - Search Map Item
423 | // TODO: Function too coupled with map gestures, create two functions or rename.
424 | private func searchRequestInFuture(withTimeInterval timeInterval: Double = 2.5, repeats: Bool = false, dismissKeyboard: Bool = false, isMapPan: Bool = false) {
425 | searchRequestCancel()
426 | // We use count of 1, as we predict search results won't change.
427 | if isExpanded, searchMapItems.count > 1, !searchBarText.isEmpty {
428 | searchRequestFuture = Timer.scheduledTimer(withTimeInterval: timeInterval, repeats: repeats) { [weak self] _ in
429 | self?.searchRequestStart(dismissKeyboard: dismissKeyboard, isMapPan: isMapPan)
430 | }
431 | }
432 | }
433 |
434 | private func searchRequestCancel() {
435 | searchCompletionRequest?.cancel()
436 | searchRequestFuture?.invalidate()
437 | searchRequest?.cancel()
438 | }
439 |
440 | private func searchRequestStart(dismissKeyboard: Bool = false, isMapPan: Bool = false) {
441 | searchRequestCancel()
442 | guard !searchBarText.isEmpty else {
443 | searchBar.resignFirstResponder()
444 | searchMapItems.removeAll()
445 | tableView.reloadData()
446 | tableViewHide()
447 | return
448 | }
449 | let request = MKLocalSearch.Request()
450 | request.naturalLanguageQuery = searchBarText
451 | request.region = mapView.region
452 | let search = MKLocalSearch(request: request)
453 | search.start { [weak self] (response, error) in
454 | self?.searchRequestDidComplete(withResponse: response, error, dismissKeyboard: dismissKeyboard, isMapPan: isMapPan)
455 | }
456 | self.searchRequest = search
457 | }
458 |
459 | private func searchRequestDidComplete(withResponse response: MKLocalSearch.Response?, _ error: Error?, dismissKeyboard: Bool = false, isMapPan: Bool = false) {
460 | guard let response = response else {
461 | return
462 | }
463 | self.searchMapItems = response.mapItems
464 | self.tableViewType = .mapItem
465 | if isMapPan { // Add new annotations from dragging and searching new areas.
466 | var newAnnotations = [PlaceAnnotation]()
467 | for mapItem in response.mapItems {
468 | if !mapAnnotations.contains(mapItem.placemark) {
469 | mapAnnotations.insert(mapItem.placemark)
470 | newAnnotations.append(PlaceAnnotation(mapItem))
471 | }
472 | }
473 | mapView.addAnnotations(newAnnotations)
474 | } else { // Remove annotations, and resize mapView to new annotations.
475 | tableViewShow()
476 | mapAnnotations.removeAll()
477 | mapView.removeAnnotations(mapView.annotations) //remove all annotations from map
478 | var annotations = [PlaceAnnotation]()
479 | for mapItem in response.mapItems {
480 | mapAnnotations.insert(mapItem.placemark)
481 | annotations.append(PlaceAnnotation(mapItem))
482 | }
483 | // 1 Search Result. Refer to delegate.
484 | if response.mapItems.count == 1, let mapItem = response.mapItems.first {
485 | delegate?.mapKitSearch(self, mapItem: mapItem)
486 | delegate?.mapKitSearch(self, searchReturnedOneItem: mapItem)
487 |
488 | }
489 | mapView.showAnnotations(annotations, animated: true)
490 | if dismissKeyboard {
491 | searchBar.resignFirstResponder()
492 | }
493 | }
494 | }
495 |
496 | // MARK: - Bottom Sheet Animations
497 | func tableViewHide(duration: TimeInterval = 0.5,
498 | options: UIView.AnimationOptions = [.curveEaseOut]) {
499 | if keyboardHeight > 0 { // If there was a previous keyboard height from dragging
500 | if stackViewExpandedHeight != nil, stackViewExpandedHeight! > 0 {
501 | stackViewExpandedHeight! -= keyboardHeight
502 | }
503 | keyboardHeight = 0
504 | }
505 | isExpanded = false
506 | if mapView.frame.size.height > CGFloat(searchBarHeight) {
507 | UIView.animate(withDuration: duration, delay: 0.0, options: options, animations: {
508 | self.stackViewHeight.constant = CGFloat(self.searchBarHeight)
509 | if self.searchMapItems.isEmpty {
510 | self.stackViewExpandedHeight = nil
511 | }
512 | self.tableView.superview?.layoutIfNeeded()
513 | self.view.layoutIfNeeded()
514 | })
515 | }
516 | }
517 |
518 | func tableViewShow(duration: TimeInterval = 0.5,
519 | options: UIView.AnimationOptions = [.curveEaseInOut]) {
520 | isExpanded = true
521 | // If user is interacting with map, or showing mapItems without searching or scrolling tableView, expand bottomSheet to maxMapInteractedHeight.
522 | let stackViewMaxExpandedHeight = isUserMapInteracted || (tableViewType == .mapItem && !searchBar.isFirstResponder && tableView.contentOffset.y == 0) ? stackViewMaxMapInteractedHeight : self.stackViewMaxExpandedHeight
523 | UIView.animate(withDuration: duration, delay: 0.0, options: options, animations: {
524 | let safeAreaInsetsBottom = self.keyboardHeight > 0 ? self.safeAreaInsetsBottom : 0
525 | // Remove safeAreaInsets bottom if keyboard opened due to overlap.
526 | self.stackViewHeight.constant = min(stackViewMaxExpandedHeight, self.searchBarHeight + self.keyboardHeight + self.tableViewContentHeight - safeAreaInsetsBottom)
527 | self.stackViewExpandedHeight = self.stackViewHeight.constant
528 | self.view.layoutIfNeeded()
529 | })
530 | }
531 |
532 | // MARK: - Keyboard Animations
533 | @objc private func keyboardWillShow(_ notification: NSNotification) {
534 | guard let userInfo = notification.userInfo,
535 | let keyboardFrame = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue,
536 | let duration = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double,
537 | let curve = userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as? UInt else {
538 | return
539 | }
540 | keyboardHeight = keyboardFrame.cgRectValue.size.height
541 | tableViewShow(duration: duration, options: UIView.AnimationOptions(rawValue: curve))
542 | }
543 |
544 | @objc private func keyboardWillHide(_ notification: NSNotification) {
545 | guard !isDragged,
546 | let userInfo = notification.userInfo,
547 | let duration = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double,
548 | let curve = userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as? UInt else {
549 | return
550 | }
551 | keyboardHeight = 0
552 | if isExpanded { // Maintain expanded state, but lower sheet if needed.
553 | tableViewShow(duration: duration, options: UIView.AnimationOptions(rawValue: curve))
554 | } else {
555 | tableViewHide(duration: duration, options: UIView.AnimationOptions(rawValue: curve))
556 | }
557 | }
558 | }
559 | // MARK: - Map Delegate
560 | extension MapKitSearchViewController: MKMapViewDelegate {
561 | public func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
562 | geocodeRequestCancel()
563 | }
564 |
565 | public func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
566 | if annotation is MKUserLocation {
567 | return nil
568 | }
569 | var view = mapView.dequeueReusableAnnotationView(withIdentifier: "Pin")
570 | if view == nil {
571 | let marker = MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: "Pin")
572 | marker.markerTintColor = markerTintColor
573 | marker.clusteringIdentifier = "MapItem"
574 | view = marker
575 | }
576 | return view
577 | }
578 |
579 | public func mapView(_ mapView: MKMapView, didChange mode: MKUserTrackingMode, animated: Bool) {
580 | switch mode {
581 | case .follow:
582 | locationManagerRequestLocation()
583 | break
584 | default:
585 | break
586 | }
587 | }
588 |
589 | //Locates the PlaceAnnotation from an item on the map
590 | private func findPlaceAnnotation(from mapItem: MKMapItem) -> PlaceAnnotation? {
591 | for annotation in mapView.annotations {
592 | if let placeAnnotation = annotation as? PlaceAnnotation {
593 | if placeAnnotation.mapItem == mapItem {
594 | return placeAnnotation
595 | }
596 | }
597 | }
598 | return nil
599 | }
600 |
601 | public func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
602 |
603 | geocodeRequestCancel()
604 |
605 | //If pressed on one of the annotation, let the delegate know
606 | if let annotation = view.annotation as? PlaceAnnotation {
607 |
608 | delegate?.mapKitSearch(self, userSelectedAnnotationFromMap: annotation.mapItem)
609 | }
610 | }
611 |
612 |
613 | private func centerAndZoomMapOnLocation(_ location: CLLocationCoordinate2D) {
614 |
615 | let coordinateRegion = MKCoordinateRegion(center: location,
616 | latitudinalMeters: 1000,
617 | longitudinalMeters: 1000)
618 |
619 | mapView.setRegion(coordinateRegion, animated: true)
620 | }
621 |
622 | ///Deselect all annotations on map
623 | public func deselectAnnotations() {
624 | for annotation in mapView.selectedAnnotations {
625 | mapView.deselectAnnotation(annotation, animated: true)
626 | }
627 | }
628 |
629 |
630 | }
631 |
632 | // MARK: - Search Delegate
633 | extension MapKitSearchViewController: UISearchBarDelegate, MKLocalSearchCompleterDelegate {
634 | public func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
635 |
636 | }
637 |
638 | public func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
639 | searchCompletionRequest(didComplete: completer.results)
640 | }
641 |
642 | public func completer(_ completer: MKLocalSearchCompleter, didFailWithError error: Error) {
643 | }
644 |
645 | public func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
646 | searchRequestFuture?.invalidate()
647 | if !searchText.isEmpty {
648 | searchCompletionRequest?.queryFragment = searchText
649 | }
650 | }
651 |
652 | public func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
653 | searchCompletionRequest?.cancel()
654 | searchRequestFuture?.invalidate()
655 | // User interactions can dismiss keyboard, we prevent another search.
656 | if !isUserInteracted {
657 | searchRequestStart(dismissKeyboard: true)
658 | }
659 | }
660 |
661 | public func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
662 | searchRequestStart(dismissKeyboard: true)
663 | }
664 | }
665 |
666 | // MARK: - Table Data Source
667 | extension MapKitSearchViewController: UITableViewDataSource {
668 | public func numberOfSections(in tableView: UITableView) -> Int {
669 | return 1
670 | }
671 |
672 | public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
673 | switch tableViewType {
674 | case .searchCompletion:
675 | return searchCompletions.count
676 | case .mapItem:
677 | return searchMapItems.count
678 | }
679 | }
680 |
681 | public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
682 | switch tableViewType {
683 | case .searchCompletion:
684 | let cell = tableView.dequeueReusableCell(withIdentifier: "SearchCompletion", for: indexPath) as! SearchCompletionTableViewCell
685 | cell.viewSetup(withSearchCompletion: searchCompletions[indexPath.row])
686 | return cell
687 | case .mapItem:
688 | let cell = tableView.dequeueReusableCell(withIdentifier: "MapItem", for: indexPath) as! MapItemTableViewCell
689 | cell.viewSetup(withMapItem: searchMapItems[indexPath.row], tintColor: markerTintColor ?? UIColor.red)
690 | return cell
691 | }
692 | }
693 |
694 | public func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
695 | cell.backgroundColor = UIColor.clear
696 | }
697 | }
698 |
699 | // MARK: - Table View Delegate
700 | extension MapKitSearchViewController: UITableViewDelegate {
701 | public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
702 | switch tableViewType {
703 | case .searchCompletion:
704 | guard searchCompletions.count > indexPath.row else {
705 | return
706 | }
707 | searchBar.text = searchCompletions[indexPath.row].title
708 | searchBarSearchButtonClicked(searchBar)
709 | break
710 | case .mapItem:
711 | guard searchMapItems.count > indexPath.row else {
712 | return
713 | }
714 | let selectedMapItem = searchMapItems[indexPath.row]
715 |
716 | //Find the annotation on the map from the selected table entry, zoom to it, hide the table, and let delegate know
717 | if let placeAnnotation = findPlaceAnnotation(from: selectedMapItem) {
718 | centerAndZoomMapOnLocation(placeAnnotation.coordinate)
719 | tableViewHide()
720 | delegate?.mapKitSearch(self, mapItem: selectedMapItem)
721 | delegate?.mapKitSearch(self, userSelectedListItem: selectedMapItem)
722 | }
723 |
724 | break
725 | }
726 | }
727 | }
728 | extension MapKitSearchViewController: CLLocationManagerDelegate {
729 | public func locationManagerRequestLocation(withPermission permission: CLAuthorizationStatus? = nil) {
730 | guard CLLocationManager.locationServicesEnabled() else {
731 | return
732 | }
733 | switch CLLocationManager.authorizationStatus() {
734 | case .authorizedAlways, .authorizedWhenInUse:
735 | locationManager.requestLocation()
736 | break
737 | case .notDetermined:
738 | guard let permission = permission else {
739 | return
740 | }
741 | switch permission {
742 | case .authorizedAlways:
743 | locationManager.requestAlwaysAuthorization()
744 | break
745 | case .authorizedWhenInUse:
746 | locationManager.requestWhenInUseAuthorization()
747 | break
748 | default:
749 | break
750 | }
751 | break
752 | case .denied:
753 | UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, options: [:]) { [weak self] _ in
754 | // TODO: Check if conflicts with locationManager(manager: didChangeAuthorization:)
755 | switch CLLocationManager.authorizationStatus() {
756 | case .authorizedAlways, .authorizedWhenInUse:
757 | self?.locationManager.requestLocation()
758 | break
759 | default:
760 | break
761 | }
762 | }
763 | break
764 | default:
765 | break
766 | }
767 | }
768 |
769 | public func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
770 | switch status {
771 | case .authorizedAlways, .authorizedWhenInUse:
772 | manager.requestLocation()
773 | break
774 | default:
775 | break
776 | }
777 | }
778 |
779 | public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
780 | guard let location = locations.first else {
781 | return
782 | }
783 | mapView.setCenter(location.coordinate, animated: true)
784 | manager.stopUpdatingLocation()
785 | }
786 |
787 | public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
788 | print(error)
789 | }
790 |
791 | }
792 |
793 | // MARK: - Protocol
794 | public protocol MapKitSearchDelegate {
795 |
796 | @available(*, deprecated, message: "Other protocol functions provide finer detail on user action")
797 | func mapKitSearch(_ mapKitSearchViewController: MapKitSearchViewController, mapItem: MKMapItem)
798 |
799 | ///Called on the delegate when the search results returned exactly one matching item
800 | func mapKitSearch(_ mapKitSearchViewController: MapKitSearchViewController, searchReturnedOneItem mapItem: MKMapItem)
801 |
802 | ///Called on the delegate when the user taps on one of the items on the list
803 | func mapKitSearch(_ mapKitSearchViewController: MapKitSearchViewController, userSelectedListItem mapItem: MKMapItem)
804 |
805 | ///Called on the delegate when the user taps on the map, and the geocode returns a matching entry
806 | func mapKitSearch(_ mapKitSearchViewController: MapKitSearchViewController, userSelectedGeocodeItem mapItem: MKMapItem)
807 |
808 | ///Called on the delegate when the user selects an annotation on the map that was added to the map by the search.
809 | func mapKitSearch(_ mapKitSearchViewController: MapKitSearchViewController, userSelectedAnnotationFromMap mapItem: MKMapItem)
810 |
811 |
812 | }
813 |
814 | // MARK: - MKAnnotation
815 | class PlaceAnnotation: NSObject, MKAnnotation {
816 | let mapItem: MKMapItem
817 | let coordinate: CLLocationCoordinate2D
818 | let title, subtitle: String?
819 |
820 | init(_ mapItem: MKMapItem) {
821 | self.mapItem = mapItem
822 | coordinate = mapItem.placemark.coordinate
823 | title = mapItem.name
824 | subtitle = nil
825 | }
826 | }
827 |
828 |
--------------------------------------------------------------------------------