├── .codeclimate.yml ├── .gitignore ├── .swift-version ├── Example ├── HLFMapViewController.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── HLFMapViewController-Example.xcscheme ├── HLFMapViewController.xcworkspace │ └── contents.xcworkspacedata ├── HLFMapViewController │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Images.xcassets │ │ └── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── id_x-20@2x.png │ │ │ ├── id_x-20@3x.png │ │ │ ├── id_x-29@2x.png │ │ │ ├── id_x-29@3x.png │ │ │ ├── id_x-40@2x.png │ │ │ ├── id_x-40@3x.png │ │ │ ├── id_x-60@2x.png │ │ │ └── id_x-60@3x.png │ ├── Info.plist │ └── ViewController.swift ├── HLFMapViewController_ExampleUITests │ ├── HLFMapViewController_Example_UITests.swift │ └── Info.plist ├── Podfile └── Podfile.lock ├── HLFMapViewController.podspec ├── LICENSE ├── Pod ├── Assets │ ├── .gitkeep │ ├── MapViewController.xib │ ├── SearchResultsViewCell.xib │ └── SearchResultsViewController.xib └── Classes │ ├── .gitkeep │ ├── MapViewController.swift │ ├── SearchResultsViewCell.swift │ └── SearchResultsViewController.swift ├── README.md └── _Pods.xcodeproj /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | engines: 2 | fixme: 3 | enabled: true 4 | tailor: 5 | enabled: true 6 | ratings: 7 | paths: 8 | - "**.swift" 9 | exclude_paths: [] 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata 15 | *.xccheckout 16 | profile 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | 22 | # Bundler 23 | .bundle 24 | 25 | Carthage 26 | Pods 27 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 3.0 2 | -------------------------------------------------------------------------------- /Example/HLFMapViewController.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD51AFB9204008FA782 /* AppDelegate.swift */; }; 11 | 607FACD81AFB9204008FA782 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD71AFB9204008FA782 /* ViewController.swift */; }; 12 | 607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 607FACD91AFB9204008FA782 /* Main.storyboard */; }; 13 | 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDC1AFB9204008FA782 /* Images.xcassets */; }; 14 | 961E9FBEA59074DEAA8078EC /* Pods_HLFMapViewController_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 278DE6817E4F53AA1A619335 /* Pods_HLFMapViewController_Example.framework */; }; 15 | E3A6ADE21C3A5C6A005CA7FD /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E3A6ADE01C3A5C6A005CA7FD /* LaunchScreen.storyboard */; }; 16 | E3A6ADE41C3A76C5005CA7FD /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E3A6ADE31C3A76C5005CA7FD /* MapKit.framework */; }; 17 | E3A6ADEE1C3A7FFE005CA7FD /* HLFMapViewController_Example_UITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3A6ADED1C3A7FFE005CA7FD /* HLFMapViewController_Example_UITests.swift */; }; 18 | /* End PBXBuildFile section */ 19 | 20 | /* Begin PBXContainerItemProxy section */ 21 | E3A6ADF01C3A7FFE005CA7FD /* PBXContainerItemProxy */ = { 22 | isa = PBXContainerItemProxy; 23 | containerPortal = 607FACC81AFB9204008FA782 /* Project object */; 24 | proxyType = 1; 25 | remoteGlobalIDString = 607FACCF1AFB9204008FA782; 26 | remoteInfo = HLFMapViewController_Example; 27 | }; 28 | /* End PBXContainerItemProxy section */ 29 | 30 | /* Begin PBXFileReference section */ 31 | 0852533C263774063D299346 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; 32 | 278DE6817E4F53AA1A619335 /* Pods_HLFMapViewController_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_HLFMapViewController_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 33 | 473E83DAFE5712ADF69FBD07 /* Pods-HLFMapViewController_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-HLFMapViewController_Example.release.xcconfig"; path = "Pods/Target Support Files/Pods-HLFMapViewController_Example/Pods-HLFMapViewController_Example.release.xcconfig"; sourceTree = ""; }; 34 | 607FACD01AFB9204008FA782 /* HLFMapViewController_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = HLFMapViewController_Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 35 | 607FACD41AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 36 | 607FACD51AFB9204008FA782 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 37 | 607FACD71AFB9204008FA782 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 38 | 607FACDA1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 39 | 607FACDC1AFB9204008FA782 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 40 | 72B6CE054EF3022550495340 /* Pods_HLFMapViewController_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_HLFMapViewController_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | 900C580ABE6FE7B32C5DD88D /* Pods-HLFMapViewController_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-HLFMapViewController_Tests.release.xcconfig"; path = "Pods/Target Support Files/Pods-HLFMapViewController_Tests/Pods-HLFMapViewController_Tests.release.xcconfig"; sourceTree = ""; }; 42 | B3959CD749204590FAEC22EF /* Pods-HLFMapViewController_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-HLFMapViewController_Example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-HLFMapViewController_Example/Pods-HLFMapViewController_Example.debug.xcconfig"; sourceTree = ""; }; 43 | DB61CC2737FF70E1CF700FCE /* HLFMapViewController.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = HLFMapViewController.podspec; path = ../HLFMapViewController.podspec; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; 44 | E3A6ADE11C3A5C6A005CA7FD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 45 | E3A6ADE31C3A76C5005CA7FD /* MapKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MapKit.framework; path = System/Library/Frameworks/MapKit.framework; sourceTree = SDKROOT; }; 46 | E3A6ADEB1C3A7FFE005CA7FD /* HLFMapViewController_Example_UITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = HLFMapViewController_Example_UITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 47 | E3A6ADED1C3A7FFE005CA7FD /* HLFMapViewController_Example_UITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HLFMapViewController_Example_UITests.swift; sourceTree = ""; }; 48 | E3A6ADEF1C3A7FFE005CA7FD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 49 | F84243DB4F58EE736F67F533 /* Pods-HLFMapViewController_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-HLFMapViewController_Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-HLFMapViewController_Tests/Pods-HLFMapViewController_Tests.debug.xcconfig"; sourceTree = ""; }; 50 | FFBBF3688DA15B97AF1F8592 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; 51 | /* End PBXFileReference section */ 52 | 53 | /* Begin PBXFrameworksBuildPhase section */ 54 | 607FACCD1AFB9204008FA782 /* Frameworks */ = { 55 | isa = PBXFrameworksBuildPhase; 56 | buildActionMask = 2147483647; 57 | files = ( 58 | E3A6ADE41C3A76C5005CA7FD /* MapKit.framework in Frameworks */, 59 | 961E9FBEA59074DEAA8078EC /* Pods_HLFMapViewController_Example.framework in Frameworks */, 60 | ); 61 | runOnlyForDeploymentPostprocessing = 0; 62 | }; 63 | E3A6ADE81C3A7FFE005CA7FD /* Frameworks */ = { 64 | isa = PBXFrameworksBuildPhase; 65 | buildActionMask = 2147483647; 66 | files = ( 67 | ); 68 | runOnlyForDeploymentPostprocessing = 0; 69 | }; 70 | /* End PBXFrameworksBuildPhase section */ 71 | 72 | /* Begin PBXGroup section */ 73 | 1AA0C0CAD3625A11479A1ED0 /* Frameworks */ = { 74 | isa = PBXGroup; 75 | children = ( 76 | E3A6ADE31C3A76C5005CA7FD /* MapKit.framework */, 77 | 278DE6817E4F53AA1A619335 /* Pods_HLFMapViewController_Example.framework */, 78 | 72B6CE054EF3022550495340 /* Pods_HLFMapViewController_Tests.framework */, 79 | ); 80 | name = Frameworks; 81 | sourceTree = ""; 82 | }; 83 | 607FACC71AFB9204008FA782 = { 84 | isa = PBXGroup; 85 | children = ( 86 | 607FACF51AFB993E008FA782 /* Podspec Metadata */, 87 | 607FACD21AFB9204008FA782 /* Example for HLFMapViewController */, 88 | E3A6ADEC1C3A7FFE005CA7FD /* HLFMapViewController_Example_UITests */, 89 | 607FACD11AFB9204008FA782 /* Products */, 90 | 85FC1438CA5E691BF3C537FA /* Pods */, 91 | 1AA0C0CAD3625A11479A1ED0 /* Frameworks */, 92 | ); 93 | sourceTree = ""; 94 | }; 95 | 607FACD11AFB9204008FA782 /* Products */ = { 96 | isa = PBXGroup; 97 | children = ( 98 | 607FACD01AFB9204008FA782 /* HLFMapViewController_Example.app */, 99 | E3A6ADEB1C3A7FFE005CA7FD /* HLFMapViewController_Example_UITests.xctest */, 100 | ); 101 | name = Products; 102 | sourceTree = ""; 103 | }; 104 | 607FACD21AFB9204008FA782 /* Example for HLFMapViewController */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | 607FACD51AFB9204008FA782 /* AppDelegate.swift */, 108 | 607FACD71AFB9204008FA782 /* ViewController.swift */, 109 | 607FACD91AFB9204008FA782 /* Main.storyboard */, 110 | E3A6ADE01C3A5C6A005CA7FD /* LaunchScreen.storyboard */, 111 | 607FACDC1AFB9204008FA782 /* Images.xcassets */, 112 | 607FACD31AFB9204008FA782 /* Supporting Files */, 113 | ); 114 | name = "Example for HLFMapViewController"; 115 | path = HLFMapViewController; 116 | sourceTree = ""; 117 | }; 118 | 607FACD31AFB9204008FA782 /* Supporting Files */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | 607FACD41AFB9204008FA782 /* Info.plist */, 122 | ); 123 | name = "Supporting Files"; 124 | sourceTree = ""; 125 | }; 126 | 607FACF51AFB993E008FA782 /* Podspec Metadata */ = { 127 | isa = PBXGroup; 128 | children = ( 129 | DB61CC2737FF70E1CF700FCE /* HLFMapViewController.podspec */, 130 | FFBBF3688DA15B97AF1F8592 /* README.md */, 131 | 0852533C263774063D299346 /* LICENSE */, 132 | ); 133 | name = "Podspec Metadata"; 134 | sourceTree = ""; 135 | }; 136 | 85FC1438CA5E691BF3C537FA /* Pods */ = { 137 | isa = PBXGroup; 138 | children = ( 139 | B3959CD749204590FAEC22EF /* Pods-HLFMapViewController_Example.debug.xcconfig */, 140 | 473E83DAFE5712ADF69FBD07 /* Pods-HLFMapViewController_Example.release.xcconfig */, 141 | F84243DB4F58EE736F67F533 /* Pods-HLFMapViewController_Tests.debug.xcconfig */, 142 | 900C580ABE6FE7B32C5DD88D /* Pods-HLFMapViewController_Tests.release.xcconfig */, 143 | ); 144 | name = Pods; 145 | sourceTree = ""; 146 | }; 147 | E3A6ADEC1C3A7FFE005CA7FD /* HLFMapViewController_Example_UITests */ = { 148 | isa = PBXGroup; 149 | children = ( 150 | E3A6ADED1C3A7FFE005CA7FD /* HLFMapViewController_Example_UITests.swift */, 151 | E3A6ADEF1C3A7FFE005CA7FD /* Info.plist */, 152 | ); 153 | name = HLFMapViewController_Example_UITests; 154 | path = HLFMapViewController_ExampleUITests; 155 | sourceTree = ""; 156 | }; 157 | /* End PBXGroup section */ 158 | 159 | /* Begin PBXNativeTarget section */ 160 | 607FACCF1AFB9204008FA782 /* HLFMapViewController_Example */ = { 161 | isa = PBXNativeTarget; 162 | buildConfigurationList = 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "HLFMapViewController_Example" */; 163 | buildPhases = ( 164 | 9D0B9AF8C879FD103B54829A /* Check Pods Manifest.lock */, 165 | 607FACCC1AFB9204008FA782 /* Sources */, 166 | 607FACCD1AFB9204008FA782 /* Frameworks */, 167 | 607FACCE1AFB9204008FA782 /* Resources */, 168 | 2494311384E903F82B6785FF /* Embed Pods Frameworks */, 169 | 93691F42853DF5FFB95EBC3F /* Copy Pods Resources */, 170 | ); 171 | buildRules = ( 172 | ); 173 | dependencies = ( 174 | ); 175 | name = HLFMapViewController_Example; 176 | productName = HLFMapViewController; 177 | productReference = 607FACD01AFB9204008FA782 /* HLFMapViewController_Example.app */; 178 | productType = "com.apple.product-type.application"; 179 | }; 180 | E3A6ADEA1C3A7FFE005CA7FD /* HLFMapViewController_Example_UITests */ = { 181 | isa = PBXNativeTarget; 182 | buildConfigurationList = E3A6ADF21C3A7FFE005CA7FD /* Build configuration list for PBXNativeTarget "HLFMapViewController_Example_UITests" */; 183 | buildPhases = ( 184 | E3A6ADE71C3A7FFE005CA7FD /* Sources */, 185 | E3A6ADE81C3A7FFE005CA7FD /* Frameworks */, 186 | E3A6ADE91C3A7FFE005CA7FD /* Resources */, 187 | ); 188 | buildRules = ( 189 | ); 190 | dependencies = ( 191 | E3A6ADF11C3A7FFE005CA7FD /* PBXTargetDependency */, 192 | ); 193 | name = HLFMapViewController_Example_UITests; 194 | productName = HLFMapViewController_ExampleUITests; 195 | productReference = E3A6ADEB1C3A7FFE005CA7FD /* HLFMapViewController_Example_UITests.xctest */; 196 | productType = "com.apple.product-type.bundle.ui-testing"; 197 | }; 198 | /* End PBXNativeTarget section */ 199 | 200 | /* Begin PBXProject section */ 201 | 607FACC81AFB9204008FA782 /* Project object */ = { 202 | isa = PBXProject; 203 | attributes = { 204 | LastSwiftUpdateCheck = 0720; 205 | LastUpgradeCheck = 0810; 206 | ORGANIZATIONNAME = CocoaPods; 207 | TargetAttributes = { 208 | 607FACCF1AFB9204008FA782 = { 209 | CreatedOnToolsVersion = 6.3.1; 210 | DevelopmentTeam = G9TR83TNAE; 211 | LastSwiftMigration = 0800; 212 | SystemCapabilities = { 213 | com.apple.Maps.iOS = { 214 | enabled = 1; 215 | }; 216 | }; 217 | }; 218 | E3A6ADEA1C3A7FFE005CA7FD = { 219 | CreatedOnToolsVersion = 7.2; 220 | LastSwiftMigration = 0800; 221 | TestTargetID = 607FACCF1AFB9204008FA782; 222 | }; 223 | }; 224 | }; 225 | buildConfigurationList = 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "HLFMapViewController" */; 226 | compatibilityVersion = "Xcode 3.2"; 227 | developmentRegion = English; 228 | hasScannedForEncodings = 0; 229 | knownRegions = ( 230 | en, 231 | Base, 232 | ); 233 | mainGroup = 607FACC71AFB9204008FA782; 234 | productRefGroup = 607FACD11AFB9204008FA782 /* Products */; 235 | projectDirPath = ""; 236 | projectRoot = ""; 237 | targets = ( 238 | 607FACCF1AFB9204008FA782 /* HLFMapViewController_Example */, 239 | E3A6ADEA1C3A7FFE005CA7FD /* HLFMapViewController_Example_UITests */, 240 | ); 241 | }; 242 | /* End PBXProject section */ 243 | 244 | /* Begin PBXResourcesBuildPhase section */ 245 | 607FACCE1AFB9204008FA782 /* Resources */ = { 246 | isa = PBXResourcesBuildPhase; 247 | buildActionMask = 2147483647; 248 | files = ( 249 | E3A6ADE21C3A5C6A005CA7FD /* LaunchScreen.storyboard in Resources */, 250 | 607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */, 251 | 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */, 252 | ); 253 | runOnlyForDeploymentPostprocessing = 0; 254 | }; 255 | E3A6ADE91C3A7FFE005CA7FD /* Resources */ = { 256 | isa = PBXResourcesBuildPhase; 257 | buildActionMask = 2147483647; 258 | files = ( 259 | ); 260 | runOnlyForDeploymentPostprocessing = 0; 261 | }; 262 | /* End PBXResourcesBuildPhase section */ 263 | 264 | /* Begin PBXShellScriptBuildPhase section */ 265 | 2494311384E903F82B6785FF /* Embed Pods Frameworks */ = { 266 | isa = PBXShellScriptBuildPhase; 267 | buildActionMask = 2147483647; 268 | files = ( 269 | ); 270 | inputPaths = ( 271 | ); 272 | name = "Embed Pods Frameworks"; 273 | outputPaths = ( 274 | ); 275 | runOnlyForDeploymentPostprocessing = 0; 276 | shellPath = /bin/sh; 277 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-HLFMapViewController_Example/Pods-HLFMapViewController_Example-frameworks.sh\"\n"; 278 | showEnvVarsInLog = 0; 279 | }; 280 | 93691F42853DF5FFB95EBC3F /* Copy Pods Resources */ = { 281 | isa = PBXShellScriptBuildPhase; 282 | buildActionMask = 2147483647; 283 | files = ( 284 | ); 285 | inputPaths = ( 286 | ); 287 | name = "Copy Pods Resources"; 288 | outputPaths = ( 289 | ); 290 | runOnlyForDeploymentPostprocessing = 0; 291 | shellPath = /bin/sh; 292 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-HLFMapViewController_Example/Pods-HLFMapViewController_Example-resources.sh\"\n"; 293 | showEnvVarsInLog = 0; 294 | }; 295 | 9D0B9AF8C879FD103B54829A /* Check Pods Manifest.lock */ = { 296 | isa = PBXShellScriptBuildPhase; 297 | buildActionMask = 2147483647; 298 | files = ( 299 | ); 300 | inputPaths = ( 301 | ); 302 | name = "Check Pods Manifest.lock"; 303 | outputPaths = ( 304 | ); 305 | runOnlyForDeploymentPostprocessing = 0; 306 | shellPath = /bin/sh; 307 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; 308 | showEnvVarsInLog = 0; 309 | }; 310 | /* End PBXShellScriptBuildPhase section */ 311 | 312 | /* Begin PBXSourcesBuildPhase section */ 313 | 607FACCC1AFB9204008FA782 /* Sources */ = { 314 | isa = PBXSourcesBuildPhase; 315 | buildActionMask = 2147483647; 316 | files = ( 317 | 607FACD81AFB9204008FA782 /* ViewController.swift in Sources */, 318 | 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */, 319 | ); 320 | runOnlyForDeploymentPostprocessing = 0; 321 | }; 322 | E3A6ADE71C3A7FFE005CA7FD /* Sources */ = { 323 | isa = PBXSourcesBuildPhase; 324 | buildActionMask = 2147483647; 325 | files = ( 326 | E3A6ADEE1C3A7FFE005CA7FD /* HLFMapViewController_Example_UITests.swift in Sources */, 327 | ); 328 | runOnlyForDeploymentPostprocessing = 0; 329 | }; 330 | /* End PBXSourcesBuildPhase section */ 331 | 332 | /* Begin PBXTargetDependency section */ 333 | E3A6ADF11C3A7FFE005CA7FD /* PBXTargetDependency */ = { 334 | isa = PBXTargetDependency; 335 | target = 607FACCF1AFB9204008FA782 /* HLFMapViewController_Example */; 336 | targetProxy = E3A6ADF01C3A7FFE005CA7FD /* PBXContainerItemProxy */; 337 | }; 338 | /* End PBXTargetDependency section */ 339 | 340 | /* Begin PBXVariantGroup section */ 341 | 607FACD91AFB9204008FA782 /* Main.storyboard */ = { 342 | isa = PBXVariantGroup; 343 | children = ( 344 | 607FACDA1AFB9204008FA782 /* Base */, 345 | ); 346 | name = Main.storyboard; 347 | sourceTree = ""; 348 | }; 349 | E3A6ADE01C3A5C6A005CA7FD /* LaunchScreen.storyboard */ = { 350 | isa = PBXVariantGroup; 351 | children = ( 352 | E3A6ADE11C3A5C6A005CA7FD /* Base */, 353 | ); 354 | name = LaunchScreen.storyboard; 355 | sourceTree = ""; 356 | }; 357 | /* End PBXVariantGroup section */ 358 | 359 | /* Begin XCBuildConfiguration section */ 360 | 607FACED1AFB9204008FA782 /* Debug */ = { 361 | isa = XCBuildConfiguration; 362 | buildSettings = { 363 | ALWAYS_SEARCH_USER_PATHS = NO; 364 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 365 | CLANG_CXX_LIBRARY = "libc++"; 366 | CLANG_ENABLE_MODULES = YES; 367 | CLANG_ENABLE_OBJC_ARC = YES; 368 | CLANG_WARN_BOOL_CONVERSION = YES; 369 | CLANG_WARN_CONSTANT_CONVERSION = YES; 370 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 371 | CLANG_WARN_EMPTY_BODY = YES; 372 | CLANG_WARN_ENUM_CONVERSION = YES; 373 | CLANG_WARN_INFINITE_RECURSION = YES; 374 | CLANG_WARN_INT_CONVERSION = YES; 375 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 376 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 377 | CLANG_WARN_UNREACHABLE_CODE = YES; 378 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 379 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 380 | COPY_PHASE_STRIP = NO; 381 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 382 | ENABLE_STRICT_OBJC_MSGSEND = YES; 383 | ENABLE_TESTABILITY = YES; 384 | GCC_C_LANGUAGE_STANDARD = gnu99; 385 | GCC_DYNAMIC_NO_PIC = NO; 386 | GCC_NO_COMMON_BLOCKS = YES; 387 | GCC_OPTIMIZATION_LEVEL = 0; 388 | GCC_PREPROCESSOR_DEFINITIONS = ( 389 | "DEBUG=1", 390 | "$(inherited)", 391 | ); 392 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 393 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 394 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 395 | GCC_WARN_UNDECLARED_SELECTOR = YES; 396 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 397 | GCC_WARN_UNUSED_FUNCTION = YES; 398 | GCC_WARN_UNUSED_VARIABLE = YES; 399 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 400 | MTL_ENABLE_DEBUG_INFO = YES; 401 | ONLY_ACTIVE_ARCH = YES; 402 | SDKROOT = iphoneos; 403 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 404 | }; 405 | name = Debug; 406 | }; 407 | 607FACEE1AFB9204008FA782 /* Release */ = { 408 | isa = XCBuildConfiguration; 409 | buildSettings = { 410 | ALWAYS_SEARCH_USER_PATHS = NO; 411 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 412 | CLANG_CXX_LIBRARY = "libc++"; 413 | CLANG_ENABLE_MODULES = YES; 414 | CLANG_ENABLE_OBJC_ARC = YES; 415 | CLANG_WARN_BOOL_CONVERSION = YES; 416 | CLANG_WARN_CONSTANT_CONVERSION = YES; 417 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 418 | CLANG_WARN_EMPTY_BODY = YES; 419 | CLANG_WARN_ENUM_CONVERSION = YES; 420 | CLANG_WARN_INFINITE_RECURSION = YES; 421 | CLANG_WARN_INT_CONVERSION = YES; 422 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 423 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 424 | CLANG_WARN_UNREACHABLE_CODE = YES; 425 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 426 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 427 | COPY_PHASE_STRIP = NO; 428 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 429 | ENABLE_NS_ASSERTIONS = NO; 430 | ENABLE_STRICT_OBJC_MSGSEND = YES; 431 | GCC_C_LANGUAGE_STANDARD = gnu99; 432 | GCC_NO_COMMON_BLOCKS = YES; 433 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 434 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 435 | GCC_WARN_UNDECLARED_SELECTOR = YES; 436 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 437 | GCC_WARN_UNUSED_FUNCTION = YES; 438 | GCC_WARN_UNUSED_VARIABLE = YES; 439 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 440 | MTL_ENABLE_DEBUG_INFO = NO; 441 | SDKROOT = iphoneos; 442 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 443 | VALIDATE_PRODUCT = YES; 444 | }; 445 | name = Release; 446 | }; 447 | 607FACF01AFB9204008FA782 /* Debug */ = { 448 | isa = XCBuildConfiguration; 449 | baseConfigurationReference = B3959CD749204590FAEC22EF /* Pods-HLFMapViewController_Example.debug.xcconfig */; 450 | buildSettings = { 451 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 452 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 453 | DEVELOPMENT_TEAM = G9TR83TNAE; 454 | INFOPLIST_FILE = HLFMapViewController/Info.plist; 455 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 456 | MODULE_NAME = ExampleApp; 457 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)"; 458 | PRODUCT_NAME = "$(TARGET_NAME)"; 459 | SWIFT_VERSION = 3.0; 460 | TARGETED_DEVICE_FAMILY = 1; 461 | }; 462 | name = Debug; 463 | }; 464 | 607FACF11AFB9204008FA782 /* Release */ = { 465 | isa = XCBuildConfiguration; 466 | baseConfigurationReference = 473E83DAFE5712ADF69FBD07 /* Pods-HLFMapViewController_Example.release.xcconfig */; 467 | buildSettings = { 468 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 469 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 470 | DEVELOPMENT_TEAM = G9TR83TNAE; 471 | INFOPLIST_FILE = HLFMapViewController/Info.plist; 472 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 473 | MODULE_NAME = ExampleApp; 474 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)"; 475 | PRODUCT_NAME = "$(TARGET_NAME)"; 476 | SWIFT_VERSION = 3.0; 477 | TARGETED_DEVICE_FAMILY = 1; 478 | }; 479 | name = Release; 480 | }; 481 | E3A6ADF31C3A7FFE005CA7FD /* Debug */ = { 482 | isa = XCBuildConfiguration; 483 | buildSettings = { 484 | DEBUG_INFORMATION_FORMAT = dwarf; 485 | ENABLE_TESTABILITY = YES; 486 | INFOPLIST_FILE = HLFMapViewController_ExampleUITests/Info.plist; 487 | IPHONEOS_DEPLOYMENT_TARGET = 9.2; 488 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 489 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.HLFMapViewController-ExampleUITests"; 490 | PRODUCT_NAME = "$(TARGET_NAME)"; 491 | SWIFT_VERSION = 3.0; 492 | TEST_TARGET_NAME = HLFMapViewController_Example; 493 | USES_XCTRUNNER = YES; 494 | }; 495 | name = Debug; 496 | }; 497 | E3A6ADF41C3A7FFE005CA7FD /* Release */ = { 498 | isa = XCBuildConfiguration; 499 | buildSettings = { 500 | INFOPLIST_FILE = HLFMapViewController_ExampleUITests/Info.plist; 501 | IPHONEOS_DEPLOYMENT_TARGET = 9.2; 502 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 503 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.HLFMapViewController-ExampleUITests"; 504 | PRODUCT_NAME = "$(TARGET_NAME)"; 505 | SWIFT_VERSION = 3.0; 506 | TEST_TARGET_NAME = HLFMapViewController_Example; 507 | USES_XCTRUNNER = YES; 508 | }; 509 | name = Release; 510 | }; 511 | /* End XCBuildConfiguration section */ 512 | 513 | /* Begin XCConfigurationList section */ 514 | 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "HLFMapViewController" */ = { 515 | isa = XCConfigurationList; 516 | buildConfigurations = ( 517 | 607FACED1AFB9204008FA782 /* Debug */, 518 | 607FACEE1AFB9204008FA782 /* Release */, 519 | ); 520 | defaultConfigurationIsVisible = 0; 521 | defaultConfigurationName = Release; 522 | }; 523 | 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "HLFMapViewController_Example" */ = { 524 | isa = XCConfigurationList; 525 | buildConfigurations = ( 526 | 607FACF01AFB9204008FA782 /* Debug */, 527 | 607FACF11AFB9204008FA782 /* Release */, 528 | ); 529 | defaultConfigurationIsVisible = 0; 530 | defaultConfigurationName = Release; 531 | }; 532 | E3A6ADF21C3A7FFE005CA7FD /* Build configuration list for PBXNativeTarget "HLFMapViewController_Example_UITests" */ = { 533 | isa = XCConfigurationList; 534 | buildConfigurations = ( 535 | E3A6ADF31C3A7FFE005CA7FD /* Debug */, 536 | E3A6ADF41C3A7FFE005CA7FD /* Release */, 537 | ); 538 | defaultConfigurationIsVisible = 0; 539 | defaultConfigurationName = Release; 540 | }; 541 | /* End XCConfigurationList section */ 542 | }; 543 | rootObject = 607FACC81AFB9204008FA782 /* Project object */; 544 | } 545 | -------------------------------------------------------------------------------- /Example/HLFMapViewController.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/HLFMapViewController.xcodeproj/xcshareddata/xcschemes/HLFMapViewController-Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 85 | 91 | 92 | 93 | 94 | 96 | 97 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /Example/HLFMapViewController.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/HLFMapViewController/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // HLFMapViewController 4 | // 5 | // Created by Peng Wang on 8/3/2015. 6 | // Copyright (c) 2015 Peng Wang. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 17 | return true 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /Example/HLFMapViewController/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 | 27 | 28 | -------------------------------------------------------------------------------- /Example/HLFMapViewController/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 | 34 | 35 | 36 | 37 | 44 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 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 | -------------------------------------------------------------------------------- /Example/HLFMapViewController/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "id_x-20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "id_x-20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "id_x-29@2x.png", 19 | "scale" : "2x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "id_x-29@3x.png", 25 | "scale" : "3x" 26 | }, 27 | { 28 | "size" : "40x40", 29 | "idiom" : "iphone", 30 | "filename" : "id_x-40@2x.png", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "id_x-40@3x.png", 37 | "scale" : "3x" 38 | }, 39 | { 40 | "size" : "60x60", 41 | "idiom" : "iphone", 42 | "filename" : "id_x-60@2x.png", 43 | "scale" : "2x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "id_x-60@3x.png", 49 | "scale" : "3x" 50 | } 51 | ], 52 | "info" : { 53 | "version" : 1, 54 | "author" : "xcode" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Example/HLFMapViewController/Images.xcassets/AppIcon.appiconset/id_x-20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hlfcoding/HLFMapViewController/60b276f0f3c15d611e584a2aef13fbbeba69e57b/Example/HLFMapViewController/Images.xcassets/AppIcon.appiconset/id_x-20@2x.png -------------------------------------------------------------------------------- /Example/HLFMapViewController/Images.xcassets/AppIcon.appiconset/id_x-20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hlfcoding/HLFMapViewController/60b276f0f3c15d611e584a2aef13fbbeba69e57b/Example/HLFMapViewController/Images.xcassets/AppIcon.appiconset/id_x-20@3x.png -------------------------------------------------------------------------------- /Example/HLFMapViewController/Images.xcassets/AppIcon.appiconset/id_x-29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hlfcoding/HLFMapViewController/60b276f0f3c15d611e584a2aef13fbbeba69e57b/Example/HLFMapViewController/Images.xcassets/AppIcon.appiconset/id_x-29@2x.png -------------------------------------------------------------------------------- /Example/HLFMapViewController/Images.xcassets/AppIcon.appiconset/id_x-29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hlfcoding/HLFMapViewController/60b276f0f3c15d611e584a2aef13fbbeba69e57b/Example/HLFMapViewController/Images.xcassets/AppIcon.appiconset/id_x-29@3x.png -------------------------------------------------------------------------------- /Example/HLFMapViewController/Images.xcassets/AppIcon.appiconset/id_x-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hlfcoding/HLFMapViewController/60b276f0f3c15d611e584a2aef13fbbeba69e57b/Example/HLFMapViewController/Images.xcassets/AppIcon.appiconset/id_x-40@2x.png -------------------------------------------------------------------------------- /Example/HLFMapViewController/Images.xcassets/AppIcon.appiconset/id_x-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hlfcoding/HLFMapViewController/60b276f0f3c15d611e584a2aef13fbbeba69e57b/Example/HLFMapViewController/Images.xcassets/AppIcon.appiconset/id_x-40@3x.png -------------------------------------------------------------------------------- /Example/HLFMapViewController/Images.xcassets/AppIcon.appiconset/id_x-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hlfcoding/HLFMapViewController/60b276f0f3c15d611e584a2aef13fbbeba69e57b/Example/HLFMapViewController/Images.xcassets/AppIcon.appiconset/id_x-60@2x.png -------------------------------------------------------------------------------- /Example/HLFMapViewController/Images.xcassets/AppIcon.appiconset/id_x-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hlfcoding/HLFMapViewController/60b276f0f3c15d611e584a2aef13fbbeba69e57b/Example/HLFMapViewController/Images.xcassets/AppIcon.appiconset/id_x-60@3x.png -------------------------------------------------------------------------------- /Example/HLFMapViewController/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | MapViewController 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 0.4.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSRequiresIPhoneOS 26 | 27 | NSLocationWhenInUseUsageDescription 28 | HLFMapViewController Example would like to use your location. 29 | UILaunchStoryboardName 30 | LaunchScreen 31 | UIMainStoryboardFile 32 | Main 33 | UIRequiredDeviceCapabilities 34 | 35 | armv7 36 | location-services 37 | 38 | UISupportedInterfaceOrientations 39 | 40 | UIInterfaceOrientationPortrait 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Example/HLFMapViewController/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // HLFMapViewController 4 | // 5 | // Created by Peng Wang on 8/3/2015. 6 | // Copyright (c) 2015 Peng Wang. All rights reserved. 7 | // 8 | 9 | import MapKit 10 | import UIKit 11 | 12 | import HLFMapViewController 13 | 14 | class ViewController: UIViewController, MapViewControllerDelegate { 15 | 16 | @IBOutlet var addressLabel: UILabel! 17 | @IBOutlet var nameLabel: UILabel! 18 | 19 | var selectedMapItem: MKMapItem? { 20 | didSet { 21 | guard selectedMapItem != oldValue else { return } 22 | updateLocationLabels() 23 | } 24 | } 25 | 26 | func updateLocationLabels() { 27 | if let addressLines = selectedMapItem?.placemark.addressDictionary?["FormattedAddressLines"] as? [String] { 28 | addressLabel.text = addressLines.joined(separator: "\n") 29 | addressLabel.textColor = UIColor.darkText 30 | } else { 31 | addressLabel.text = "Address" 32 | addressLabel.textColor = UIColor.lightGray 33 | } 34 | if let name = selectedMapItem?.name { 35 | nameLabel.text = name 36 | nameLabel.textColor = UIColor.darkText 37 | } else { 38 | nameLabel.text = "Name" 39 | nameLabel.textColor = UIColor.lightGray 40 | } 41 | } 42 | 43 | @IBAction func showMap(_ sender: Any) { 44 | let mapViewController = MapViewController(nibName: "MapViewController", bundle: MapViewController.bundle) 45 | mapViewController.title = NSLocalizedString("Select Nearby Location", comment: "") 46 | mapViewController.delegate = self 47 | mapViewController.selectedMapItem = selectedMapItem 48 | 49 | navigationController?.pushViewController(mapViewController, animated: true) 50 | } 51 | 52 | // MARK: - 53 | 54 | override func viewDidLoad() { 55 | super.viewDidLoad() 56 | updateLocationLabels() 57 | } 58 | 59 | // MARK: - 60 | 61 | func mapViewController(_ mapViewController: MapViewController, didSelectMapItem mapItem: MKMapItem) { 62 | selectedMapItem = mapItem 63 | 64 | DispatchQueue.main.asyncAfter(deadline: .now() + 1) { 65 | let _ = self.navigationController?.popToViewController(self, animated: true) 66 | } 67 | } 68 | 69 | func mapViewController(_ mapViewController: MapViewController, didDeselectMapItem mapItem: MKMapItem) { 70 | selectedMapItem = nil 71 | 72 | if !mapViewController.hasResults { 73 | let _ = navigationController?.popToViewController(self, animated: true) 74 | } 75 | } 76 | 77 | var customRowHeight: CGFloat? 78 | 79 | func resultsViewController(_ resultsViewController: SearchResultsViewController, 80 | didConfigureResultViewCell cell: SearchResultsViewCell, withMapItem mapItem: MKMapItem) { 81 | let margins = cell.contentView.layoutMargins 82 | var customMargins = margins 83 | customMargins.top = 15 84 | customMargins.bottom = 15 85 | cell.contentView.layoutMargins = customMargins 86 | 87 | if customRowHeight == nil { 88 | customRowHeight = resultsViewController.tableView.rowHeight + ( 89 | (customMargins.top - margins.top) + (customMargins.bottom - margins.bottom) 90 | ) 91 | } 92 | resultsViewController.tableView.rowHeight = customRowHeight! 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /Example/HLFMapViewController_ExampleUITests/HLFMapViewController_Example_UITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HLFMapViewController_Example_UITests.swift 3 | // HLFMapViewController_Example_UITests 4 | // 5 | // Created by Peng Wang on 1/4/16. 6 | // Copyright © 2016 CocoaPods. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class HLFMapViewController_Example_UITests: XCTestCase { 12 | 13 | let existsPredicate = NSPredicate(format: "exists == 1") 14 | 15 | override func setUp() { 16 | super.setUp() 17 | 18 | continueAfterFailure = false 19 | XCUIApplication().launch() 20 | } 21 | 22 | override func tearDown() { 23 | super.tearDown() 24 | } 25 | 26 | func testBasicUserFlow() { 27 | let app = XCUIApplication() 28 | let presentButton = app.buttons["Show Map"] 29 | let userLocation = app.otherElements["My Location"] 30 | let searchField = app.navigationBars["Select Nearby Location"].searchFields["Search for place or address"] 31 | let searchResult = app.tables["Search results"].staticTexts["Apple Inc., 1 Infinite Loop, Cupertino, CA 95014-2083, United States"] 32 | let selectButton = app.buttons["Select address in callout view"] 33 | let deselectButton = app.buttons["Deselect address in callout view"] 34 | let calloutTitle = app.staticTexts["Apple Inc., 1 Infinite Loop, Cupertino, CA 95014-2083, United States"] 35 | 36 | presentButton.tap() 37 | 38 | expectation(for: existsPredicate, evaluatedWith: userLocation, handler: nil) 39 | waitForExpectations(timeout: 10, handler: nil) 40 | 41 | searchField.tap() 42 | searchField.typeText("Apple Inc., Cupertino") 43 | searchResult.tap() 44 | 45 | expectation(for: existsPredicate, evaluatedWith: calloutTitle, handler: nil) 46 | waitForExpectations(timeout: 2, handler: nil) 47 | 48 | selectButton.tap() 49 | 50 | expectation(for: existsPredicate, evaluatedWith: presentButton, handler: nil) 51 | waitForExpectations(timeout: 2, handler: nil) 52 | 53 | presentButton.tap() 54 | 55 | expectation(for: existsPredicate, evaluatedWith: calloutTitle, handler: nil) 56 | expectation(for: existsPredicate, evaluatedWith: deselectButton, handler: nil) 57 | waitForExpectations(timeout: 10, handler: nil) 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /Example/HLFMapViewController_ExampleUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | source 'https://github.com/CocoaPods/Specs.git' 2 | 3 | platform :ios, '9.0' 4 | use_frameworks! 5 | 6 | target 'HLFMapViewController_Example', :exclusive => true do 7 | pod "HLFMapViewController", :path => "../" 8 | end -------------------------------------------------------------------------------- /Example/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - HLFMapViewController (0.2.0) 3 | 4 | DEPENDENCIES: 5 | - HLFMapViewController (from `../`) 6 | 7 | EXTERNAL SOURCES: 8 | HLFMapViewController: 9 | :path: ../ 10 | 11 | SPEC CHECKSUMS: 12 | HLFMapViewController: 7dd18eeac0dbefc51cdd066cac6498193c1775da 13 | 14 | COCOAPODS: 0.39.0 15 | -------------------------------------------------------------------------------- /HLFMapViewController.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'HLFMapViewController' 3 | s.version = '0.4.0' 4 | s.summary = 'Map modal for searching and selecting a location.' 5 | s.description = <<-DESC 6 | A generic implementation of a common feature: searching 7 | and selecting a nearby location from an MKMapView. 8 | DESC 9 | s.screenshots = [ 'https://dl.dropboxusercontent.com/u/305699/hlf-map-view-controller-1-2.png', 10 | 'https://dl.dropboxusercontent.com/u/305699/hlf-map-view-controller-2-2.png' ] 11 | s.homepage = 'https://github.com/hlfcoding/HLFMapViewController' 12 | s.license = { :type => 'MIT', :file => 'LICENSE' } 13 | s.author = { 'Peng Wang' => 'peng@pengxwang.com' } 14 | s.source = { :git => 'https://github.com/hlfcoding/HLFMapViewController.git', :tag => s.version.to_s } 15 | s.social_media_url = 'https://twitter.com/hlfcoding' 16 | 17 | s.ios.deployment_target = '9.0' 18 | 19 | s.source_files = 'Pod/Classes/**/*' 20 | s.resources = 'Pod/Assets/**/*' 21 | 22 | s.frameworks = 'UIKit', 'CoreLocation', 'MapKit' 23 | end 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT NON-AI License 2 | 3 | Copyright (c) 2015 Peng Wang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions. 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | In addition, the following restrictions apply: 16 | 17 | 1. The Software and any modifications made to it may not be used for the 18 | purpose of training or improving machine learning algorithms, including but 19 | not limited to artificial intelligence, natural language processing, or 20 | data mining. This condition applies to any derivatives, modifications, or 21 | updates based on the Software code. Any usage of the Software in an AI- 22 | training dataset is considered a breach of this License. 23 | 24 | 2. The Software may not be included in any dataset used for training or 25 | improving machine learning algorithms, including but not limited to 26 | artificial intelligence, natural language processing, or data mining. 27 | 28 | 3. Any person or organization found to be in violation of these 29 | restrictions will be subject to legal action and may be held liable for 30 | any damages resulting from such use. 31 | 32 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 33 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 34 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 35 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 36 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 37 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 38 | THE SOFTWARE. 39 | -------------------------------------------------------------------------------- /Pod/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hlfcoding/HLFMapViewController/60b276f0f3c15d611e584a2aef13fbbeba69e57b/Pod/Assets/.gitkeep -------------------------------------------------------------------------------- /Pod/Assets/MapViewController.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 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /Pod/Assets/SearchResultsViewCell.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 | 30 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /Pod/Assets/SearchResultsViewController.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 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /Pod/Classes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hlfcoding/HLFMapViewController/60b276f0f3c15d611e584a2aef13fbbeba69e57b/Pod/Classes/.gitkeep -------------------------------------------------------------------------------- /Pod/Classes/MapViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MapViewController.swift 3 | // MapViewController 4 | // 5 | // Created by Peng Wang on 8/3/2015. 6 | // Copyright (c) 2016 Peng Wang. All rights reserved. 7 | // 8 | 9 | import CoreLocation 10 | import MapKit 11 | import UIKit 12 | 13 | @objc(HLFMapViewControllerDelegate) 14 | public protocol MapViewControllerDelegate: SearchResultsViewControllerDelegate { 15 | 16 | /** 17 | When the 'add' button on a MKAnnotationView callout view is tapped, 18 | the location represented by the annotation counts as being selected. 19 | An `MKMapItem` is wrapped around the annotation. 20 | */ 21 | func mapViewController(_ mapViewController: MapViewController, didSelectMapItem mapItem: MKMapItem) 22 | func mapViewController(_ mapViewController: MapViewController, didDeselectMapItem mapItem: MKMapItem) 23 | 24 | } 25 | 26 | /** 27 | Map modal for searching and selecting a nearby location. This means it 28 | combines several different Apple API's into one specific (but very 29 | common) solution: `UISearchController`, `MapKit`, `CoreLocation`. 30 | */ 31 | @objc(HLFMapViewController) 32 | open class MapViewController: UIViewController { 33 | 34 | open static var bundle: Bundle { return Bundle(for: self) } 35 | 36 | /** Not required, but this view controller is pretty useless without a delegate. */ 37 | open weak var delegate: MapViewControllerDelegate? 38 | 39 | @available(*, deprecated, renamed: "mapIndicator") 40 | open var mapLoadingIndicator: UIActivityIndicatorView! { 41 | get { return mapIndicator } 42 | set { mapIndicator = newValue } 43 | } 44 | @IBOutlet open var mapIndicator: UIActivityIndicatorView! 45 | @IBOutlet open var mapView: MKMapView! 46 | 47 | open fileprivate(set) var locationManager: CLLocationManager! 48 | open fileprivate(set) var searchController: UISearchController! 49 | open fileprivate(set) var resultsViewController: SearchResultsViewController! 50 | 51 | open var pinColor: UIColor? 52 | open var searchDebounceDuration: TimeInterval = 0.6 53 | open var selectedMapItem: MKMapItem? 54 | open var shouldRedoSearchOnPan = true 55 | open var userLocationDebounceDuration: TimeInterval = 0.6 56 | open var zoomedInSpan: CLLocationDegrees = 0.01 57 | 58 | public var hasResults: Bool { return !resultsViewController.mapItems.isEmpty } 59 | 60 | override open func viewDidLoad() { 61 | super.viewDidLoad() 62 | 63 | initLocationManager() 64 | initSearchController() 65 | mapIndicator.color = view.tintColor 66 | mapIndicator.layer.shadowOffset = .zero 67 | mapIndicator.layer.shadowOpacity = 0.5 68 | mapIndicator.layer.shadowRadius = 3 69 | 70 | // TODO: Handle location loading timeout. 71 | } 72 | 73 | override open func viewDidAppear(_ animated: Bool) { 74 | super.viewDidAppear(animated) 75 | 76 | // TODO: Restore searchController state by implementing UIStateRestoring. 77 | } 78 | 79 | override open func viewDidDisappear(_ animated: Bool) { 80 | super.viewDidDisappear(animated) 81 | 82 | search?.cancel() 83 | } 84 | 85 | override open func didReceiveMemoryWarning() { 86 | super.didReceiveMemoryWarning() 87 | 88 | mapView.removeAnnotations(removableAnnotations) 89 | } 90 | 91 | // MARK: Implementation 92 | 93 | @IBOutlet fileprivate var mapIndicatorLoadingConstraints: [NSLayoutConstraint]! 94 | fileprivate lazy var mapIndicatorSearchingConstraints: [NSLayoutConstraint] = { 95 | let container = self.mapIndicator.superview! 96 | return [ 97 | self.mapIndicator.topAnchor.constraint( 98 | equalTo: self.topLayoutGuide.bottomAnchor, constant: container.layoutMargins.top), 99 | self.mapIndicator.trailingAnchor.constraint( 100 | equalTo: container.layoutMarginsGuide.trailingAnchor), 101 | ] 102 | }() 103 | 104 | fileprivate var hasSearch: Bool { 105 | return !preparedSearchQuery.isEmpty 106 | } 107 | fileprivate var hasSelectedPin: Bool { 108 | return !mapView.selectedAnnotations.isEmpty 109 | } 110 | fileprivate var isQuerying: Bool { 111 | return searchController.searchBar.isFirstResponder 112 | } 113 | fileprivate var isRedoingSearch = false 114 | fileprivate var preparedSearchQuery: String { 115 | return searchController.searchBar.text?.trimmingCharacters(in: .whitespaces) ?? "" 116 | } 117 | fileprivate var previousVisibleRect: MKMapRect? 118 | fileprivate var removableAnnotations: [MKPlacemark] { 119 | return mapView.annotations.filter(isNonSelectedPlacemark) as! [MKPlacemark] 120 | } 121 | fileprivate var search: MKLocalSearch? 122 | fileprivate var searchQuery = "" { 123 | didSet { 124 | updateSearchRequest() 125 | } 126 | } 127 | fileprivate var searchRegion: MKCoordinateRegion? { 128 | didSet { 129 | updateSearchRequest() 130 | } 131 | } 132 | fileprivate var searchRequest = MKLocalSearchRequest() 133 | fileprivate var shouldDebounceNextSearch = true 134 | fileprivate var wasMapPanned: Bool { 135 | let size = mapView.visibleMapRect.size 136 | defer { 137 | previousVisibleRect = mapView.visibleMapRect 138 | } 139 | if let previousSize = previousVisibleRect?.size, abs(size.width - previousSize.width) < 1 { 140 | return true 141 | } 142 | return false 143 | } 144 | 145 | /** 146 | Small helper necessitated by `CLLocationCoordinate2D` not being 147 | equatable. 148 | */ 149 | fileprivate func arePlacemarksEqual(_ a: MKPlacemark, _ b: MKPlacemark) -> Bool { 150 | return ( 151 | abs(a.coordinate.latitude - b.coordinate.latitude) < .ulpOfOne && 152 | abs(a.coordinate.longitude - b.coordinate.longitude) < .ulpOfOne 153 | ) 154 | } 155 | 156 | fileprivate func isNonSelectedPlacemark(_ annotation: MKAnnotation) -> Bool { 157 | guard let placemark = annotation as? MKPlacemark else { return false } 158 | guard let selectedPlacemark = self.selectedMapItem?.placemark else { return true } 159 | return !self.arePlacemarksEqual(placemark, selectedPlacemark) 160 | } 161 | 162 | /** 163 | Initializing `locationManager` means getting user location and 164 | setting `showsUserLocation` to true. Request authorization or 165 | `handleLocationAuthorizationDenial` if needed. 166 | 167 | See [Getting the User's Location](https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/LocationAwarenessPG/CoreLocation/CoreLocation.html). 168 | */ 169 | fileprivate func initLocationManager() { 170 | locationManager = CLLocationManager() 171 | locationManager.delegate = self 172 | 173 | DispatchQueue.main.async(execute: revealSelectedPlacemark) 174 | 175 | let status = CLLocationManager.authorizationStatus() 176 | switch status { 177 | case .notDetermined: 178 | locationManager.requestWhenInUseAuthorization() 179 | 180 | case .authorizedAlways, .authorizedWhenInUse: 181 | mapView.showsUserLocation = true 182 | 183 | case .denied, .restricted: 184 | handleLocationAuthorizationDenial(with: status) 185 | } 186 | } 187 | 188 | /** 189 | Initializing `searchController` means also setting up and providing 190 | it our custom `resultsViewController`, as well as updating this view 191 | controller to handle be the presentation context for it. 192 | 193 | See [Apple docs](https://developer.apple.com/library/ios/samplecode/TableSearch_UISearchController). 194 | */ 195 | fileprivate func initSearchController() { 196 | resultsViewController = SearchResultsViewController( 197 | nibName: "SearchResultsViewController", bundle: MapViewController.bundle 198 | ) 199 | // resultsViewController.debug = true 200 | resultsViewController.delegate = delegate 201 | resultsViewController.tableView.delegate = self 202 | 203 | searchController = UISearchController(searchResultsController: resultsViewController) 204 | searchController.delegate = self 205 | searchController.dimsBackgroundDuringPresentation = false 206 | searchController.hidesNavigationBarDuringPresentation = false 207 | searchController.searchResultsUpdater = self 208 | searchController.searchBar.delegate = self 209 | searchController.searchBar.placeholder = "Search for place or address" 210 | searchController.searchBar.searchBarStyle = .minimal 211 | searchController.searchBar.sizeToFit() 212 | searchController.loadViewIfNeeded() 213 | 214 | definesPresentationContext = true 215 | navigationItem.titleView = searchController.searchBar 216 | } 217 | 218 | /** 219 | If user denies current location, just present an alert to notify 220 | them, and show the map in its default state and require manual zoom. 221 | */ 222 | fileprivate func handleLocationAuthorizationDenial(with status: CLAuthorizationStatus) { 223 | let message = { 224 | switch status { 225 | case .denied: return "You've denied sharing your location (can be changed in Settings)." 226 | case .restricted: return "You're restricted from sharing your location." 227 | default: fatalError("Unsupported status.") 228 | } 229 | }() as String + " You can still find your location manually." 230 | 231 | let alertController = UIAlertController( 232 | title: "Location Unavailable", 233 | message: message, 234 | preferredStyle: .alert 235 | ) 236 | alertController.addAction(UIAlertAction(title: "OK", style: .default) { (action) in 237 | alertController.dismiss(animated: true, completion: nil) 238 | }) 239 | 240 | present(alertController, animated: true, completion: nil) 241 | revealMapView() 242 | // TODO: Test usability of search results in this state. 243 | } 244 | 245 | @objc fileprivate func redoSearch() { 246 | isRedoingSearch = true 247 | search?.cancel() 248 | mapIndicator.startAnimating() 249 | searchRegion = mapView.region 250 | } 251 | 252 | /** 253 | Stop any indicators and fade in `mapView`, but only if needed. 254 | */ 255 | fileprivate func revealMapView(completion: (() -> Void)? = nil) { 256 | guard mapView.isHidden else { return } 257 | 258 | mapIndicator.stopAnimating() 259 | NSLayoutConstraint.deactivate(mapIndicatorLoadingConstraints) 260 | NSLayoutConstraint.activate(mapIndicatorSearchingConstraints) 261 | 262 | mapView.alpha = 0 263 | mapView.isHidden = false 264 | UIView.animate(withDuration: 0.3, animations: { 265 | self.mapView.alpha = 1 266 | completion?() 267 | }) 268 | } 269 | 270 | fileprivate func revealSelectedPlacemark() { 271 | guard let placemark = selectedMapItem?.placemark, let location = placemark.location 272 | else { return } 273 | isDeferredSelectionEnabled = false 274 | zoomIn(to: location, animated: false) 275 | mapView.showAnnotations([placemark], animated: false) 276 | mapView.selectAnnotation(placemark, animated: false) 277 | } 278 | 279 | /** 280 | Main search handler that makes a `MKLocalSearch` based on `searchRequest` and updates 281 | `resultsViewController`. 282 | 283 | See [Apple docs](https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/LocationAwarenessPG/EnablingSearch/EnablingSearch.html). 284 | */ 285 | @objc fileprivate func searchMapItems() { 286 | let search = MKLocalSearch(request: searchRequest) 287 | search.start { [unowned self] (searchResponse, error) in 288 | guard let mapItems = searchResponse?.mapItems else { 289 | print("MKLocalSearch error: \(String(describing: error))") 290 | return 291 | } 292 | 293 | guard mapItems != self.resultsViewController.mapItems else { return } 294 | self.resultsViewController.mapItems = mapItems 295 | if self.isRedoingSearch { 296 | let _ = self.updateAnnotations() 297 | self.mapIndicator.stopAnimating() 298 | } 299 | self.search = nil 300 | } 301 | self.search = search 302 | } 303 | 304 | /** 305 | Selectively updates `mapView` annotations, adding only what's needed and removing as 306 | needed. 307 | 308 | But it returns the new annotations because using `MKMapView.annotations` or 309 | `MKMapItem.placemark` seems to yield equal but new references unsuited for selecting. 310 | Doing so would fail with a warning about 'un-added' annotations. 311 | */ 312 | fileprivate func updateAnnotations() -> [MKAnnotation] { 313 | let existing = mapView.annotations.filter { $0 is MKPlacemark } as! [MKPlacemark] 314 | let new = resultsViewController.mapItems.map { $0.placemark } 315 | let toAdd = new.filter { self.isNonSelectedPlacemark($0) && !existing.contains($0) } 316 | let toRemove = removableAnnotations.filter { !new.contains($0) } 317 | 318 | mapView.removeAnnotations(toRemove) 319 | mapView.addAnnotations(toAdd) 320 | return new 321 | } 322 | 323 | fileprivate func updateSearchRequest() { 324 | searchRequest.naturalLanguageQuery = searchQuery 325 | searchRequest.region = mapView.region 326 | NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(searchMapItems), object: nil) 327 | if shouldDebounceNextSearch { 328 | perform(#selector(searchMapItems), with: nil, afterDelay: searchDebounceDuration) 329 | } else { 330 | searchMapItems() 331 | shouldDebounceNextSearch = true 332 | } 333 | } 334 | 335 | /** 336 | Basically converts a location to a region with a hard-coded span no larger than 337 | `zoomedInSpan`, and sets it on `mapView`. Yet another helper missing from `MKMapView`. 338 | */ 339 | fileprivate func zoomIn(to location: CLLocation, animated: Bool) { 340 | revealMapView() 341 | 342 | let degrees = min(mapView.region.span.latitudeDelta, zoomedInSpan) 343 | let region = MKCoordinateRegion( 344 | center: location.coordinate, 345 | span: MKCoordinateSpanMake(degrees, degrees) 346 | ) 347 | mapView.setRegion(region, animated: animated) 348 | } 349 | 350 | fileprivate func zoomOut(animated: Bool) { 351 | let annotations = mapView.annotations.filter(isNonSelectedPlacemark) 352 | mapView.showAnnotations(annotations, animated: true) 353 | } 354 | 355 | @objc fileprivate func zoomToUserLocation() { 356 | zoomIn(to: mapView.userLocation.location!, animated: false) 357 | } 358 | 359 | // MARK: Hack: http://stackoverflow.com/a/38155566/65465 360 | 361 | fileprivate var deferredSelectedPinView: MKPinAnnotationView? 362 | fileprivate let fragileAssumptiveSelectionDuration: TimeInterval = 0.3 363 | fileprivate var isDeferredSelectionEnabled = true 364 | fileprivate var isDeferringSelection: Bool { return deferredSelectedPinView != nil } 365 | 366 | fileprivate func performDeferredSelection(animated: Bool) { 367 | let pinView = deferredSelectedPinView! 368 | pinView.canShowCallout = true 369 | let delay = fragileAssumptiveSelectionDuration 370 | DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [unowned self] in 371 | self.mapView.deselectAnnotation(pinView.annotation, animated: false) 372 | DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [unowned self] in 373 | self.mapView.selectAnnotation(pinView.annotation!, animated: animated) 374 | } 375 | } 376 | } 377 | 378 | fileprivate func prepareViewForDeferredSelection(view: MKPinAnnotationView) { 379 | // canShowCallout is false by default. 380 | if (!isDeferredSelectionEnabled) { 381 | view.canShowCallout = true 382 | } 383 | } 384 | 385 | fileprivate func setUpDeferredSelection(view: MKPinAnnotationView) -> Bool { 386 | if isDeferredSelectionEnabled { 387 | deferredSelectedPinView = view 388 | mapView.isUserInteractionEnabled = false 389 | return true 390 | } else { 391 | isDeferredSelectionEnabled = true // Restore to default. 392 | return false 393 | } 394 | } 395 | 396 | fileprivate func tearDownDeferredSelection() -> Bool { 397 | guard isDeferringSelection else { return false } 398 | deferredSelectedPinView!.canShowCallout = false 399 | deferredSelectedPinView = nil 400 | mapView.isUserInteractionEnabled = true 401 | return true 402 | } 403 | 404 | // MARK: Actions 405 | 406 | @IBAction func dismiss(_ sender: Any) { 407 | dismiss(animated: true, completion: nil) 408 | } 409 | 410 | } 411 | 412 | // MARK: CLLocationManagerDelegate 413 | 414 | extension MapViewController: CLLocationManagerDelegate { 415 | 416 | open func locationManager(_ manager: CLLocationManager, 417 | didChangeAuthorization status: CLAuthorizationStatus) { 418 | switch status { 419 | case .notDetermined: return 420 | 421 | case .authorizedAlways, .authorizedWhenInUse: 422 | mapView.showsUserLocation = true 423 | mapView.setUserTrackingMode(.none, animated: false) 424 | 425 | case .denied, .restricted: 426 | handleLocationAuthorizationDenial(with: status) 427 | } 428 | } 429 | 430 | } 431 | 432 | final fileprivate class MapPinView: MKPinAnnotationView { 433 | 434 | static let iconFont = UIFont.systemFont(ofSize: 24, weight: UIFontWeightMedium) 435 | static let reuseIdentifier = "MapPinView" 436 | 437 | var defaultColor = MKPinAnnotationView.redPinColor() 438 | var isPlacemarkSelected = false { 439 | didSet { 440 | if isPlacemarkSelected { 441 | selectButton.accessibilityLabel = "Deselect address in callout view" 442 | selectButton.setTitle("-", for: .normal) 443 | pinTintColor = tintColor 444 | } else { 445 | selectButton.accessibilityLabel = "Select address in callout view" 446 | selectButton.setTitle("+", for: .normal) 447 | pinTintColor = defaultColor 448 | } 449 | selectButton.sizeToFit() 450 | } 451 | } 452 | var placemark: MKPlacemark { return annotation as! MKPlacemark } 453 | 454 | lazy var detailsButton: UIButton = { 455 | let button = UIButton(type: .detailDisclosure) 456 | button.accessibilityLabel = "Show address details in Maps application" 457 | return button 458 | }() 459 | 460 | lazy var selectButton: UIButton = { 461 | let button = UIButton(type: .system) 462 | button.titleLabel?.font = iconFont 463 | return button 464 | }() 465 | 466 | override init(annotation: MKAnnotation?, reuseIdentifier: String?) { 467 | super.init(annotation: annotation, reuseIdentifier: reuseIdentifier) 468 | setUp() 469 | } 470 | 471 | required init?(coder aDecoder: NSCoder) { 472 | super.init(coder: aDecoder) 473 | setUp() 474 | } 475 | 476 | override func prepareForReuse() { 477 | isPlacemarkSelected = false 478 | } 479 | 480 | func setUp() { 481 | accessibilityValue = placemark.title ?? "An unknown location" 482 | leftCalloutAccessoryView = detailsButton 483 | rightCalloutAccessoryView = selectButton 484 | isPlacemarkSelected = false 485 | } 486 | 487 | } 488 | 489 | // MARK: MKMapViewDelegate 490 | 491 | extension MapViewController: MKMapViewDelegate { 492 | 493 | open func mapView(_ mapView: MKMapView, didUpdate userLocation: MKUserLocation) { 494 | guard selectedMapItem == nil else { return } 495 | guard removableAnnotations.isEmpty else { return } 496 | guard let _ = userLocation.location else { return } 497 | 498 | NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(zoomToUserLocation), object: nil) 499 | self.perform(#selector(zoomToUserLocation), with: nil, afterDelay: userLocationDebounceDuration) 500 | } 501 | 502 | open func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { 503 | guard !annotation.isEqual(mapView.userLocation) else { return nil } 504 | 505 | let pinView: MapPinView! 506 | let reuseIdentifier = String(describing: MapPinView.self) 507 | if let dequeued = mapView.dequeueReusableAnnotationView(withIdentifier: reuseIdentifier) as? MapPinView { 508 | pinView = dequeued 509 | pinView.annotation = annotation 510 | } else { 511 | pinView = MapPinView(annotation: annotation, reuseIdentifier: reuseIdentifier) 512 | } 513 | 514 | prepareViewForDeferredSelection(view: pinView) 515 | if let pinColor = pinColor { 516 | pinView.defaultColor = pinColor 517 | } 518 | if let placemark = annotation as? MKPlacemark, let selectedPlacemark = selectedMapItem?.placemark, 519 | arePlacemarksEqual(placemark, selectedPlacemark) { 520 | pinView.isPlacemarkSelected = true 521 | } 522 | 523 | return pinView 524 | } 525 | 526 | open func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, 527 | calloutAccessoryControlTapped control: UIControl) { 528 | guard let button = control as? UIButton else { return } 529 | guard let view = view as? MapPinView else { return } 530 | let mapItem = MKMapItem(placemark: view.placemark) 531 | switch button { 532 | case view.selectButton: 533 | if view.isPlacemarkSelected { 534 | selectedMapItem = nil 535 | delegate?.mapViewController(self, didDeselectMapItem: mapItem) 536 | } else { 537 | selectedMapItem = mapItem 538 | delegate?.mapViewController(self, didSelectMapItem: mapItem) 539 | } 540 | view.isPlacemarkSelected = !view.isPlacemarkSelected 541 | case view.detailsButton: 542 | mapItem.openInMaps(launchOptions: nil) 543 | default: return 544 | } 545 | } 546 | 547 | open func mapView(_ mapView: MKMapView, didDeselect view: MKAnnotationView) { 548 | guard view is MKPinAnnotationView else { return } 549 | guard !isDeferringSelection else { return } 550 | zoomOut(animated: true) 551 | } 552 | 553 | open func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) { 554 | guard let view = view as? MKPinAnnotationView else { return } 555 | guard !tearDownDeferredSelection() else { return } 556 | guard setUpDeferredSelection(view: view) else { return } 557 | guard let placemark = view.annotation as? MKPlacemark else { return } 558 | zoomIn(to: placemark.location!, animated: true) 559 | } 560 | 561 | open func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) { 562 | if isDeferringSelection { 563 | performDeferredSelection(animated: animated) 564 | } else if shouldRedoSearchOnPan { 565 | guard !hasSelectedPin && !isQuerying else { return } 566 | guard hasSearch && (wasMapPanned && !animated) else { return } 567 | NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(redoSearch), object: nil) 568 | self.perform(#selector(redoSearch), with: nil, afterDelay: searchDebounceDuration) 569 | } 570 | } 571 | 572 | } 573 | 574 | // MARK: UISearchBarDelegate 575 | 576 | extension MapViewController: UISearchBarDelegate { 577 | 578 | open func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { 579 | searchBar.resignFirstResponder() 580 | guard hasSearch else { return } 581 | shouldDebounceNextSearch = false 582 | searchQuery = preparedSearchQuery 583 | } 584 | 585 | open func searchBarCancelButtonClicked(_ searchBar: UISearchBar) { 586 | mapView.removeAnnotations(removableAnnotations) 587 | } 588 | 589 | } 590 | 591 | // MARK: UISearchControllerDelegate 592 | 593 | extension MapViewController: UISearchControllerDelegate {} 594 | 595 | // MARK: UISearchResultsUpdating 596 | 597 | extension MapViewController: UISearchResultsUpdating { 598 | 599 | open func updateSearchResults(for searchController: UISearchController) { 600 | guard hasSearch else { 601 | mapView.removeAnnotations(removableAnnotations) 602 | return 603 | } 604 | searchQuery = preparedSearchQuery 605 | } 606 | 607 | } 608 | 609 | // MARK: UITableViewDelegate 610 | 611 | extension MapViewController: UITableViewDelegate { 612 | 613 | open func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 614 | guard tableView === resultsViewController.tableView else { return } 615 | 616 | let annotationToSelect = updateAnnotations()[indexPath.row] 617 | resultsViewController.dismiss(animated: true) { 618 | self.mapView.selectAnnotation(annotationToSelect, animated: false) 619 | } 620 | } 621 | 622 | } 623 | -------------------------------------------------------------------------------- /Pod/Classes/SearchResultsViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SearchResultsViewCell.swift 3 | // MapViewController 4 | // 5 | // Created by Peng Wang on 8/7/2015. 6 | // Copyright © 2015 Peng Wang. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @objc(HLFSearchResultsViewCell) 12 | open class SearchResultsViewCell: UITableViewCell { 13 | 14 | static let reuseIdentifier = "SearchResult" 15 | 16 | @IBOutlet open weak var customTextLabel: UILabel! 17 | @IBOutlet open weak var customDetailTextLabel: UILabel! 18 | 19 | } 20 | -------------------------------------------------------------------------------- /Pod/Classes/SearchResultsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SearchResultsViewController.swift 3 | // MapViewController 4 | // 5 | // Created by Peng Wang on 8/6/2015. 6 | // Copyright © 2015 Peng Wang. All rights reserved. 7 | // 8 | 9 | import MapKit 10 | import UIKit 11 | 12 | @objc(HLFSearchResultsViewControllerDelegate) 13 | public protocol SearchResultsViewControllerDelegate: NSObjectProtocol { 14 | 15 | /** 16 | When the cell is dequeued and initially given content, this method 17 | will be called to allow additional customization of the cell, for 18 | example, its `layoutMargins` and label colors and font sizes. 19 | */ 20 | @objc optional func resultsViewController(_ resultsViewController: SearchResultsViewController, 21 | didConfigureResultViewCell cell: SearchResultsViewCell, 22 | withMapItem mapItem: MKMapItem) 23 | 24 | /** 25 | Blur style defaults to `light`. This method allows customizing that. 26 | */ 27 | @objc optional func resultsViewControllerBlurEffect(_ resultsViewController: SearchResultsViewController) -> UIBlurEffect 28 | 29 | } 30 | 31 | @objc(HLFSearchResultsViewController) 32 | open class SearchResultsViewController: UITableViewController { 33 | 34 | /** Not required, but this view controller is pretty useless without a delegate. */ 35 | open weak var delegate: SearchResultsViewControllerDelegate? 36 | 37 | @IBOutlet open weak var blurView: UIVisualEffectView! 38 | @IBOutlet open weak var vibrancyView: UIVisualEffectView! 39 | 40 | open var mapItems: [MKMapItem] = [] { 41 | didSet { 42 | if debug { print("Reloading with \(mapItems.count) items") } 43 | tableView.reloadData() 44 | } 45 | } 46 | 47 | open var debug = false 48 | 49 | override open func viewDidLoad() { 50 | super.viewDidLoad() 51 | 52 | if #available(iOS 10.0, *) { 53 | automaticallyAdjustsScrollViewInsets = false 54 | } 55 | 56 | tableView.register( 57 | SearchResultsViewCell.self, forCellReuseIdentifier: SearchResultsViewCell.reuseIdentifier 58 | ) 59 | tableView.register( 60 | UINib(nibName: "SearchResultsViewCell", bundle: MapViewController.bundle), 61 | forCellReuseIdentifier: SearchResultsViewCell.reuseIdentifier 62 | ) 63 | 64 | if let effect = delegate?.resultsViewControllerBlurEffect?(self) { 65 | blurView.effect = effect 66 | vibrancyView.effect = UIVibrancyEffect(blurEffect: effect) 67 | } 68 | tableView.backgroundView = blurView 69 | tableView.separatorEffect = vibrancyView.effect 70 | } 71 | 72 | override open func viewDidLayoutSubviews() { 73 | super.viewDidLayoutSubviews() 74 | 75 | if #available(iOS 10.0, *) { 76 | let correctOffset = presentingViewController!.topLayoutGuide.length 77 | tableView.contentInset.top = correctOffset 78 | tableView.scrollIndicatorInsets.top = correctOffset 79 | } 80 | } 81 | 82 | override open func didReceiveMemoryWarning() { 83 | super.didReceiveMemoryWarning() 84 | 85 | mapItems = [] 86 | } 87 | 88 | // MARK: UITableViewDataSource 89 | 90 | override open func numberOfSections(in tableView: UITableView) -> Int { 91 | return 1 92 | } 93 | 94 | override open func tableView(_ tableView: UITableView, 95 | numberOfRowsInSection section: Int) -> Int { 96 | return mapItems.count 97 | } 98 | 99 | override open func tableView(_ tableView: UITableView, 100 | cellForRowAt indexPath: IndexPath) -> UITableViewCell { 101 | let someCell = tableView.dequeueReusableCell( 102 | withIdentifier: SearchResultsViewCell.reuseIdentifier, for: indexPath 103 | ) 104 | guard let cell = someCell as? SearchResultsViewCell else { return someCell } 105 | 106 | let mapItem = mapItems[indexPath.row] 107 | cell.customTextLabel.text = mapItem.name 108 | if let addressLines = mapItem.placemark.addressDictionary?["FormattedAddressLines"] as? [String] { 109 | cell.customDetailTextLabel.text = addressLines.joined(separator: ", ") 110 | } 111 | 112 | delegate?.resultsViewController?( 113 | self, didConfigureResultViewCell: cell, withMapItem: mapItem 114 | ) 115 | 116 | return cell 117 | } 118 | 119 | } 120 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HLFMapViewController 2 | 3 | [![Version](https://img.shields.io/cocoapods/v/HLFMapViewController.svg?style=flat)](http://cocoapods.org/pods/HLFMapViewController) 4 | [![License](https://img.shields.io/cocoapods/l/HLFMapViewController.svg?style=flat)](http://cocoapods.org/pods/HLFMapViewController) 5 | [![Platform](https://img.shields.io/cocoapods/p/HLFMapViewController.svg?style=flat)](http://cocoapods.org/pods/HLFMapViewController) 6 | [![Code Climate](https://codeclimate.com/github/hlfcoding/HLFMapViewController/badges/gpa.svg)](https://codeclimate.com/github/hlfcoding/HLFMapViewController) 7 | 8 | > A generic implementation of a common feature: searching and selecting a nearby location from an `MKMapView`. 9 | 10 | ![screenshot-1](https://pengxwang.s3.amazonaws.com/files/hlf-map-view-controller-1-2.png)   ![screenshot-2](https://pengxwang.s3.amazonaws.com/files/hlf-map-view-controller-2-2.png) 11 | 12 | ## Usage 13 | 14 | This version uses Swift 3. The final Swift 2 version is 0.2.5. 15 | 16 | To run the example project, clone the repo, and run `pod install` from the Example directory first. 17 | 18 | In addition to turning on the 'Maps' capability, you'll need to add `location-services` to `UIRequiredDeviceCapabilities`and fill in `NSLocationAlwaysUsageDescription` in your Info.plist. 19 | 20 | Other than that just set up the view controller and implement the delegate method: 21 | 22 | ```swift 23 | // ... 24 | let mapViewController = MapViewController(nibName: "MapViewController", bundle: MapViewController.bundle) 25 | mapViewController.delegate = self 26 | mapViewController.selectedMapItem = self.selectedMapItem // Optional. 27 | // ... 28 | 29 | func mapViewController(_ mapViewController: MapViewController, didSelectMapItem mapItem: MKMapItem) { 30 | self.selectedMapItem = mapItem // Save, submit, etc. 31 | mapViewController.dismissViewController(animated: true, completion: nil) 32 | } 33 | ``` 34 | 35 | See [example app](//github.com/hlfcoding/HLFMapViewController/blob/master/Example/HLFMapViewController/ViewController.swift) for more details. 36 | 37 | ## Installation 38 | 39 | HLFMapViewController is available through [CocoaPods](http://cocoapods.org). To install 40 | it, simply add the following line to your Podfile: 41 | 42 | ```ruby 43 | pod "HLFMapViewController" 44 | ``` 45 | 46 | ## License 47 | 48 | HLFMapViewController is available under the MIT license. See the LICENSE file for more info. 49 | -------------------------------------------------------------------------------- /_Pods.xcodeproj: -------------------------------------------------------------------------------- 1 | Example/Pods/Pods.xcodeproj --------------------------------------------------------------------------------