├── 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 | [![CI Status](http://img.shields.io/travis/philip-bui/mapkit-search-view.svg?style=flat)](https://travis-ci.org/philip-bui/mapkit-search-view) 9 | [![CodeCov](https://codecov.io/gh/philip-bui/mapkit-search-view/branch/master/graph/badge.svg)](https://codecov.io/gh/philip-bui/mapkit-search-view) 10 | [![Version](https://img.shields.io/cocoapods/v/MapKitSearchView.svg?style=flat)](http://cocoapods.org/pods/MapKitSearchView) 11 | [![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 12 | [![Platform](https://img.shields.io/cocoapods/p/MapKitSearchView.svg?style=flat)](http://cocoapods.org/pods/MapKitSearchView) 13 | [![License](https://img.shields.io/cocoapods/l/MapKitSearchView.svg?style=flat)](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 | --------------------------------------------------------------------------------