Skip to content

Commit

Permalink
Initial Commit
Browse files Browse the repository at this point in the history
  • Loading branch information
James Wolfe committed Aug 14, 2020
0 parents commit bc16283
Show file tree
Hide file tree
Showing 19 changed files with 491 additions and 0 deletions.
Binary file added .DS_Store
Binary file not shown.
7 changes: 7 additions & 0 deletions .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file not shown.
24 changes: 24 additions & 0 deletions AtlasKit.podspec
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
21 changes: 21 additions & 0 deletions LICENSE
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.
31 changes: 31 additions & 0 deletions Package.swift
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"]),
]
)
32 changes: 32 additions & 0 deletions README.md
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 added Sources/.DS_Store
Binary file not shown.
Binary file added Sources/AtlasKit/.DS_Store
Binary file not shown.
167 changes: 167 additions & 0 deletions Sources/AtlasKit/AtlasKit.swift
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
}

}
30 changes: 30 additions & 0 deletions Sources/AtlasKit/Enums/AtlasKitDatasource.swift
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
}
}
}
30 changes: 30 additions & 0 deletions Sources/AtlasKit/Errors/AtlasKitError.swift
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"
}
}
}
Loading

0 comments on commit bc16283

Please sign in to comment.