-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
James Wolfe
committed
Aug 14, 2020
0 parents
commit bc16283
Showing
19 changed files
with
491 additions
and
0 deletions.
There are no files selected for viewing
Binary file not shown.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Binary file added
BIN
+10.8 KB
...code/package.xcworkspace/xcuserdata/jameswolfe.xcuserdatad/UserInterfaceState.xcuserstate
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
Pod::Spec.new do |spec| | ||
|
||
spec.name = "AtlasKit" | ||
spec.version = "0.1" | ||
spec.license = "MIT" | ||
spec.summary = "A swift library for quickly integrating a location search in your app." | ||
spec.homepage = "https://github.com/appoly/AtlasKit" | ||
spec.authors = "James Wolfe" | ||
spec.source = { :git => 'https://github.com/appoly/AtlasKit.git', :tag => spec.version } | ||
|
||
spec.ios.deployment_target = "11.4" | ||
spec.framework = "Foundation" | ||
spec.framework = "CoreLocation" | ||
spec.framework = "MapKit" | ||
spec.framework = "Contacts" | ||
|
||
spec.dependency 'Alamofire', '~> 4.9.0' | ||
|
||
spec.swift_versions = ["5.0", "5.1"] | ||
|
||
spec.source_files = "Sources/AtlasKit/*.swift" | ||
|
||
|
||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2020 Appoly | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
// swift-tools-version:5.1 | ||
// The swift-tools-version declares the minimum version of Swift required to build this package. | ||
|
||
import PackageDescription | ||
|
||
let package = Package( | ||
name: "PhotographyKit", | ||
platforms: [ | ||
.iOS(.v11), | ||
], | ||
products: [ | ||
// Products define the executables and libraries produced by a package, and make them visible to other packages. | ||
.library( | ||
name: "PhotographyKit", | ||
targets: ["PhotographyKit"]), | ||
], | ||
dependencies: [ | ||
// Dependencies declare other packages that this package depends on. | ||
// .package(url: /* package url */, from: "1.0.0"), | ||
], | ||
targets: [ | ||
// Targets are the basic building blocks of a package. A target can define a module or a test suite. | ||
// Targets can depend on other targets in this package, and on products in packages which this package depends on. | ||
.target( | ||
name: "PhotographyKit", | ||
dependencies: []), | ||
.testTarget( | ||
name: "PhotographyKitTests", | ||
dependencies: ["PhotographyKit"]), | ||
] | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
# AtlasKit | ||
A swift library for quickly integrating a location search in your app. | ||
|
||
**Installing with cocoapods** | ||
``` | ||
pod 'AtlasKit' | ||
``` | ||
|
||
**Quick start** | ||
|
||
First start by creating a `AtlasKit` object with a datasource: Apple or Google. An AtlasKit instance with an apple datasource will try to use Apple Maps to get locations, whereas one with a Google datasource will take an API key when initializing and try to make use of the Google Places API. | ||
|
||
Once you have your instance setup you can begin making search requests, you can do this with or without a delay (for use with textfields that search as you type). | ||
|
||
``` | ||
atlasKit.performSearchWithDelay(term, delay: 0.5) { predictions, error in | ||
... | ||
} | ||
atlasKit.performSearch(term) { { predictions, error in | ||
... | ||
} | ||
``` | ||
|
||
You can also cancel all pending searches with; | ||
``` | ||
atlasKit.cancelSearch() | ||
``` | ||
|
||
Search predictions are instances of the `AtlasKitPlace` object, which consists of; a street address, a city, a postcode, a state, a country, a location (coordinates). Be aware that each of these values are optional and may not always be available. | ||
|
||
**Warning - this library was only intended to be used for UK locations, as a result the postcode value is not supported outside of the UK.** |
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
// | ||
// GoogleNetworkController.swift | ||
// AtlasKit | ||
// | ||
// Created by James Wolfe on 13/08/2020. | ||
// Copyright © 2020 Appoly. All rights reserved. | ||
// | ||
|
||
|
||
|
||
import Foundation | ||
import MapKit | ||
import Contacts | ||
import Alamofire | ||
|
||
|
||
|
||
public class AtlasKit { | ||
|
||
// MARK: - Variables | ||
|
||
private let datasource: AtlasKitDataSource | ||
private let sessionManager = SessionManager() | ||
private var searchTimer: Timer? | ||
private var search: MKLocalSearch? | ||
|
||
|
||
|
||
// MARK: - Initializers | ||
|
||
init(_ datasource: AtlasKitDataSource) { | ||
self.datasource = datasource | ||
} | ||
|
||
|
||
|
||
// MARK: - Actions | ||
|
||
/// Performs a delayed local search with Apple Mapkit (to be used when updating search results based on textfield editingChanged action) | ||
/// - Parameters: | ||
/// - term: Search term | ||
/// - delay: How long you wish to delay the serach for | ||
/// - completion: Code to be ran when a result is received (Places, Error) | ||
public func performSearchWithDelay(_ term: String, delay: TimeInterval, completion: @escaping ([AtlasKitPlace]?, AtlasKitError?) -> Void) { | ||
searchTimer?.invalidate() | ||
searchTimer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false, block: { [weak self] timer in | ||
self?.performSearch(term, completion: completion) | ||
}) | ||
} | ||
|
||
|
||
/// Performs a location search using the specified type given in the initializer | ||
/// - Parameters: | ||
/// - term: Search term | ||
/// - completion: Code to be ran when a result is received (Places, Error) | ||
public func performSearch(_ term: String, completion: @escaping ([AtlasKitPlace]?, AtlasKitError?) -> Void) { | ||
guard isNetworkAvailable() else { | ||
completion(nil, .networkUnavailable) | ||
return | ||
} | ||
switch datasource { | ||
case .apple: | ||
performMapKitSearch(term, completion: completion) | ||
case .google: | ||
performGooglePlacesSearch(term, completion: completion) | ||
} | ||
} | ||
|
||
|
||
|
||
// MARK: - Lookup Functions | ||
|
||
private func performMapKitSearch(_ term: String, completion: @escaping ([AtlasKitPlace]?, AtlasKitError?) -> Void) { | ||
search?.cancel() | ||
let request = MKLocalSearch.Request() | ||
request.naturalLanguageQuery = term | ||
|
||
search = MKLocalSearch(request: request) | ||
search?.start(completionHandler: { (response, error) in | ||
DispatchQueue.main.async { [weak self] in | ||
guard error == nil else { | ||
completion(nil, .generic) | ||
return | ||
} | ||
|
||
guard let items = response?.mapItems.filter({ $0.placemark.postalAddress != nil }).map({ $0.placemark }) else { | ||
completion([], nil) | ||
return | ||
} | ||
|
||
completion(self?.formatResults(items) ?? [], nil) | ||
} | ||
}) | ||
} | ||
|
||
|
||
/// Cancels all pending searches | ||
public func cancelSearch() { | ||
searchTimer?.invalidate() | ||
} | ||
|
||
|
||
private func performGooglePlacesSearch(_ term: String, completion: @escaping ([AtlasKitPlace]?, AtlasKitError?) -> Void) { | ||
let parameters = [ | ||
"key": datasource.apiKey!, | ||
"inputtype": "textquery", | ||
"locationbias": "ipbias", | ||
"fields": "formatted_address,name,geometry", | ||
"input": term | ||
] | ||
|
||
let request = sessionManager.request(URL(string: "https://maps.googleapis.com/maps/api/place/findplacefromtext/json")!, | ||
method: .get, | ||
parameters: parameters, | ||
encoding: URLEncoding.methodDependent, | ||
headers: nil) | ||
|
||
request.validate(statusCode: HTTPStatusCode.ok.rawValue..<HTTPStatusCode.multipleChoices.rawValue).responseJSON { [weak self] (response) in | ||
switch response.result { | ||
case .failure(_): | ||
completion(nil, .generic) | ||
case .success(let value): | ||
guard let json = value as? [String: Any] else { | ||
completion(nil, .generic) | ||
return | ||
} | ||
|
||
guard let data = json["candidates"] as? [[String: Any]] else { | ||
completion(nil, .generic) | ||
return | ||
} | ||
|
||
completion(self?.formatResults(data), nil) | ||
} | ||
} | ||
} | ||
|
||
|
||
|
||
// MARK: - Utilities | ||
|
||
private func formatResults(_ items: [MKPlacemark]) -> [AtlasKitPlace] { | ||
return items.map({ AtlasKitPlace(streetAddress: $0.postalAddress!.street, city: $0.postalAddress!.city, postcode: $0.postalAddress!.postalCode, state: $0.postalAddress!.state, country: $0.postalAddress!.country, location: $0.coordinate) }) | ||
} | ||
|
||
|
||
private func formatResults(_ items: [[String: Any]]) -> [AtlasKitPlace] { | ||
return items.map { item -> AtlasKitPlace? in | ||
|
||
guard let address = item["formatted_address"] as? String, | ||
let geometry = item["geometry"] as? [String: Any], | ||
let location = geometry["location"] as? [String: Any], | ||
let latitude = location["lat"] as? Double, | ||
let longitude = location["lng"] as? Double else { | ||
return nil | ||
} | ||
|
||
return AtlasKitPlace(streetAddress: GoogleHelper.extractStreetAddress(from: address), city: GoogleHelper.extractCity(from: address), postcode: GoogleHelper.extractPostcode(from: address), state: GoogleHelper.extractState(from: address), country: GoogleHelper.extractCountry(from: address), location: CLLocationCoordinate2D(latitude: latitude, longitude: longitude)) | ||
}.filter({ $0 != nil && ($0!.formattedAddress.first) != nil }) as! [AtlasKitPlace] | ||
} | ||
|
||
|
||
private func isNetworkAvailable() -> Bool { | ||
return NetworkReachabilityManager()!.isReachable | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
// | ||
// AtlasKitDatasource.swift | ||
// AtlasKit | ||
// | ||
// Created by James Wolfe on 13/08/2020. | ||
// Copyright © 2020 Appoly. All rights reserved. | ||
// | ||
|
||
|
||
|
||
import Foundation | ||
|
||
|
||
|
||
public enum AtlasKitDataSource { | ||
case google(key: String) | ||
case apple | ||
} | ||
|
||
|
||
extension AtlasKitDataSource { | ||
public var apiKey: String? { | ||
switch self { | ||
case .google(let key): | ||
return key | ||
case .apple: | ||
return nil | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
// | ||
// AtlasKitError.swift | ||
// AtlasKit | ||
// | ||
// Created by James Wolfe on 13/08/2020. | ||
// Copyright © 2020 Appoly. All rights reserved. | ||
// | ||
|
||
|
||
|
||
import Foundation | ||
|
||
|
||
|
||
public enum AtlasKitError: Error { | ||
case generic | ||
case networkUnavailable | ||
} | ||
|
||
|
||
extension AtlasKitError { | ||
var localizedDescription: String { | ||
switch self { | ||
case .generic: | ||
return "Failed to lookup address" | ||
case .networkUnavailable: | ||
return "Please check your network connection and try again" | ||
} | ||
} | ||
} |
Oops, something went wrong.