Commit e973957f by Mobile Roshka

primer commit del proyecto

parents
# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'
target 'ProductosPY' do
# Comment the next line if you don't want to use dynamic frameworks
use_frameworks!
pod 'GoogleMaps'
pod 'GooglePlaces'
pod 'Google-Maps-iOS-Utils'
pod 'Alamofire', '~> 5.0'
end
PODS:
- Alamofire (5.0.2)
- Google-Maps-iOS-Utils (3.1.1):
- Google-Maps-iOS-Utils/Clustering (= 3.1.1)
- Google-Maps-iOS-Utils/Geometry (= 3.1.1)
- Google-Maps-iOS-Utils/Heatmap (= 3.1.1)
- Google-Maps-iOS-Utils/QuadTree (= 3.1.1)
- GoogleMaps
- Google-Maps-iOS-Utils/Clustering (3.1.1):
- Google-Maps-iOS-Utils/QuadTree
- GoogleMaps
- Google-Maps-iOS-Utils/Geometry (3.1.1):
- GoogleMaps
- Google-Maps-iOS-Utils/Heatmap (3.1.1):
- Google-Maps-iOS-Utils/QuadTree
- GoogleMaps
- Google-Maps-iOS-Utils/QuadTree (3.1.1):
- GoogleMaps
- GoogleMaps (3.8.0):
- GoogleMaps/Maps (= 3.8.0)
- GoogleMaps/Base (3.8.0)
- GoogleMaps/Maps (3.8.0):
- GoogleMaps/Base
- GooglePlaces (3.8.0):
- GoogleMaps/Base (= 3.8.0)
DEPENDENCIES:
- Alamofire (~> 5.0)
- Google-Maps-iOS-Utils
- GoogleMaps
- GooglePlaces
SPEC REPOS:
trunk:
- Alamofire
- Google-Maps-iOS-Utils
- GoogleMaps
- GooglePlaces
SPEC CHECKSUMS:
Alamofire: 3ba7a4db18b4f62c4a1c0e1cb39d7f3d52e10ada
Google-Maps-iOS-Utils: f5d6ea55799d2ef47c113e1351b48e21cddd593c
GoogleMaps: 7c8d66d70e4e8c300f43a7219d8fdaad7b325a9a
GooglePlaces: d5f70c3e9e427964fdeca1301a665d276ccd8754
PODFILE CHECKSUM: 191e0d5d3ef49627c13ab8be970e982c2afabb4d
COCOAPODS: 1.9.0
Copyright (c) 2014-2020 Alamofire Software Foundation (http://alamofire.org/)
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.
//
// Alamofire.swift
//
// Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/)
//
// 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.
//
/// Reference to `Session.default` for quick bootstrapping and examples.
public let AF = Session.default
/// Current Alamofire version. Necessary since SPM doesn't use dynamic libraries. Plus this will be more accurate.
let version = "5.0.2"
//
// AlamofireExtended.swift
//
// Copyright (c) 2019 Alamofire Software Foundation (http://alamofire.org/)
//
// 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.
//
/// Type that acts as a generic extension point for all `AlamofireExtended` types.
public struct AlamofireExtension<ExtendedType> {
/// Stores the type or meta-type of any extended type.
public private(set) var type: ExtendedType
/// Create an instance from the provided value.
///
/// - Parameter type: Instance being extended.
public init(_ type: ExtendedType) {
self.type = type
}
}
/// Protocol describing the `af` extension points for Alamofire extended types.
public protocol AlamofireExtended {
/// Type being extended.
associatedtype ExtendedType
/// Static Alamofire extension point.
static var af: AlamofireExtension<ExtendedType>.Type { get set }
/// Instance Alamofire extension point.
var af: AlamofireExtension<ExtendedType> { get set }
}
public extension AlamofireExtended {
/// Static Alamofire extension point.
static var af: AlamofireExtension<Self>.Type {
get { return AlamofireExtension<Self>.self }
set {}
}
/// Instance Alamofire extension point.
var af: AlamofireExtension<Self> {
get { return AlamofireExtension(self) }
set {}
}
}
//
// CachedResponseHandler.swift
//
// Copyright (c) 2019 Alamofire Software Foundation (http://alamofire.org/)
//
// 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.
//
import Foundation
/// A type that handles whether the data task should store the HTTP response in the cache.
public protocol CachedResponseHandler {
/// Determines whether the HTTP response should be stored in the cache.
///
/// The `completion` closure should be passed one of three possible options:
///
/// 1. The cached response provided by the server (this is the most common use case).
/// 2. A modified version of the cached response (you may want to modify it in some way before caching).
/// 3. A `nil` value to prevent the cached response from being stored in the cache.
///
/// - Parameters:
/// - task: The data task whose request resulted in the cached response.
/// - response: The cached response to potentially store in the cache.
/// - completion: The closure to execute containing cached response, a modified response, or `nil`.
func dataTask(_ task: URLSessionDataTask,
willCacheResponse response: CachedURLResponse,
completion: @escaping (CachedURLResponse?) -> Void)
}
// MARK: -
/// `ResponseCacher` is a convenience `CachedResponseHandler` making it easy to cache, not cache, or modify a cached
/// response.
public struct ResponseCacher {
/// Defines the behavior of the `ResponseCacher` type.
public enum Behavior {
/// Stores the cached response in the cache.
case cache
/// Prevents the cached response from being stored in the cache.
case doNotCache
/// Modifies the cached response before storing it in the cache.
case modify((URLSessionDataTask, CachedURLResponse) -> CachedURLResponse?)
}
/// Returns a `ResponseCacher` with a follow `Behavior`.
public static let cache = ResponseCacher(behavior: .cache)
/// Returns a `ResponseCacher` with a do not follow `Behavior`.
public static let doNotCache = ResponseCacher(behavior: .doNotCache)
/// The `Behavior` of the `ResponseCacher`.
public let behavior: Behavior
/// Creates a `ResponseCacher` instance from the `Behavior`.
///
/// - Parameter behavior: The `Behavior`.
public init(behavior: Behavior) {
self.behavior = behavior
}
}
extension ResponseCacher: CachedResponseHandler {
public func dataTask(_ task: URLSessionDataTask,
willCacheResponse response: CachedURLResponse,
completion: @escaping (CachedURLResponse?) -> Void) {
switch behavior {
case .cache:
completion(response)
case .doNotCache:
completion(nil)
case let .modify(closure):
let response = closure(task, response)
completion(response)
}
}
}
//
// DispatchQueue+Alamofire.swift
//
// Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/)
//
// 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.
//
import Dispatch
import Foundation
extension DispatchQueue {
/// Execute the provided closure after a `TimeInterval`.
///
/// - Parameters:
/// - delay: `TimeInterval` to delay execution.
/// - closure: Closure to execute.
func after(_ delay: TimeInterval, execute closure: @escaping () -> Void) {
asyncAfter(deadline: .now() + delay, execute: closure)
}
}
//
// HTTPMethod.swift
//
// Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/)
//
// 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.
//
/// Type representing HTTP methods. Raw `String` value is stored and compared case-sensitively, so
/// `HTTPMethod.get != HTTPMethod(rawValue: "get")`.
///
/// See https://tools.ietf.org/html/rfc7231#section-4.3
public struct HTTPMethod: RawRepresentable, Equatable, Hashable {
/// `CONNECT` method.
public static let connect = HTTPMethod(rawValue: "CONNECT")
/// `DELETE` method.
public static let delete = HTTPMethod(rawValue: "DELETE")
/// `GET` method.
public static let get = HTTPMethod(rawValue: "GET")
/// `HEAD` method.
public static let head = HTTPMethod(rawValue: "HEAD")
/// `OPTIONS` method.
public static let options = HTTPMethod(rawValue: "OPTIONS")
/// `PATCH` method.
public static let patch = HTTPMethod(rawValue: "PATCH")
/// `POST` method.
public static let post = HTTPMethod(rawValue: "POST")
/// `PUT` method.
public static let put = HTTPMethod(rawValue: "PUT")
/// `TRACE` method.
public static let trace = HTTPMethod(rawValue: "TRACE")
public let rawValue: String
public init(rawValue: String) {
self.rawValue = rawValue
}
}
//
// MultipartUpload.swift
//
// Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/)
//
// 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.
//
import Foundation
/// Internal type which encapsulates a `MultipartFormData` upload.
final class MultipartUpload {
lazy var result = Result { try build() }
let isInBackgroundSession: Bool
let multipartFormData: MultipartFormData
let encodingMemoryThreshold: UInt64
let request: URLRequestConvertible
let fileManager: FileManager
init(isInBackgroundSession: Bool,
encodingMemoryThreshold: UInt64,
request: URLRequestConvertible,
multipartFormData: MultipartFormData) {
self.isInBackgroundSession = isInBackgroundSession
self.encodingMemoryThreshold = encodingMemoryThreshold
self.request = request
fileManager = multipartFormData.fileManager
self.multipartFormData = multipartFormData
}
func build() throws -> (request: URLRequest, uploadable: UploadRequest.Uploadable) {
var urlRequest = try request.asURLRequest()
urlRequest.setValue(multipartFormData.contentType, forHTTPHeaderField: "Content-Type")
let uploadable: UploadRequest.Uploadable
if multipartFormData.contentLength < encodingMemoryThreshold && !isInBackgroundSession {
let data = try multipartFormData.encode()
uploadable = .data(data)
} else {
let tempDirectoryURL = fileManager.temporaryDirectory
let directoryURL = tempDirectoryURL.appendingPathComponent("org.alamofire.manager/multipart.form.data")
let fileName = UUID().uuidString
let fileURL = directoryURL.appendingPathComponent(fileName)
try fileManager.createDirectory(at: directoryURL, withIntermediateDirectories: true, attributes: nil)
do {
try multipartFormData.writeEncodedData(to: fileURL)
} catch {
// Cleanup after attempted write if it fails.
try? fileManager.removeItem(at: fileURL)
}
uploadable = .file(fileURL, shouldRemove: true)
}
return (request: urlRequest, uploadable: uploadable)
}
}
extension MultipartUpload: UploadConvertible {
func asURLRequest() throws -> URLRequest {
return try result.get().request
}
func createUploadable() throws -> UploadRequest.Uploadable {
return try result.get().uploadable
}
}
//
// Notifications.swift
//
// Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/)
//
// 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.
//
import Foundation
public extension Request {
/// Posted when a `Request` is resumed. The `Notification` contains the resumed `Request`.
static let didResumeNotification = Notification.Name(rawValue: "org.alamofire.notification.name.request.didResume")
/// Posted when a `Request` is suspended. The `Notification` contains the suspended `Request`.
static let didSuspendNotification = Notification.Name(rawValue: "org.alamofire.notification.name.request.didSuspend")
/// Posted when a `Request` is cancelled. The `Notification` contains the cancelled `Request`.
static let didCancelNotification = Notification.Name(rawValue: "org.alamofire.notification.name.request.didCancel")
/// Posted when a `Request` is finished. The `Notification` contains the completed `Request`.
static let didFinishNotification = Notification.Name(rawValue: "org.alamofire.notification.name.request.didFinish")
/// Posted when a `URLSessionTask` is resumed. The `Notification` contains the `Request` associated with the `URLSessionTask`.
static let didResumeTaskNotification = Notification.Name(rawValue: "org.alamofire.notification.name.request.didResumeTask")
/// Posted when a `URLSessionTask` is suspended. The `Notification` contains the `Request` associated with the `URLSessionTask`.
static let didSuspendTaskNotification = Notification.Name(rawValue: "org.alamofire.notification.name.request.didSuspendTask")
/// Posted when a `URLSessionTask` is cancelled. The `Notification` contains the `Request` associated with the `URLSessionTask`.
static let didCancelTaskNotification = Notification.Name(rawValue: "org.alamofire.notification.name.request.didCancelTask")
/// Posted when a `URLSessionTask` is completed. The `Notification` contains the `Request` associated with the `URLSessionTask`.
static let didCompleteTaskNotification = Notification.Name(rawValue: "org.alamofire.notification.name.request.didCompleteTask")
}
// MARK: -
extension Notification {
/// The `Request` contained by the instance's `userInfo`, `nil` otherwise.
public var request: Request? {
return userInfo?[String.requestKey] as? Request
}
/// Convenience initializer for a `Notification` containing a `Request` payload.
///
/// - Parameters:
/// - name: The name of the notification.
/// - request: The `Request` payload.
init(name: Notification.Name, request: Request) {
self.init(name: name, object: nil, userInfo: [String.requestKey: request])
}
}
extension NotificationCenter {
/// Convenience function for posting notifications with `Request` payloads.
///
/// - Parameters:
/// - name: The name of the notification.
/// - request: The `Request` payload.
func postNotification(named name: Notification.Name, with request: Request) {
let notification = Notification(name: name, request: request)
post(notification)
}
}
extension String {
/// User info dictionary key representing the `Request` associated with the notification.
fileprivate static let requestKey = "org.alamofire.notification.key.request"
}
/// `EventMonitor` that provides Alamofire's notifications.
public final class AlamofireNotifications: EventMonitor {
public func requestDidResume(_ request: Request) {
NotificationCenter.default.postNotification(named: Request.didResumeNotification, with: request)
}
public func requestDidSuspend(_ request: Request) {
NotificationCenter.default.postNotification(named: Request.didSuspendNotification, with: request)
}
public func requestDidCancel(_ request: Request) {
NotificationCenter.default.postNotification(named: Request.didCancelNotification, with: request)
}
public func requestDidFinish(_ request: Request) {
NotificationCenter.default.postNotification(named: Request.didFinishNotification, with: request)
}
public func request(_ request: Request, didResumeTask task: URLSessionTask) {
NotificationCenter.default.postNotification(named: Request.didResumeTaskNotification, with: request)
}
public func request(_ request: Request, didSuspendTask task: URLSessionTask) {
NotificationCenter.default.postNotification(named: Request.didSuspendTaskNotification, with: request)
}
public func request(_ request: Request, didCancelTask task: URLSessionTask) {
NotificationCenter.default.postNotification(named: Request.didCancelTaskNotification, with: request)
}
public func request(_ request: Request, didCompleteTask task: URLSessionTask, with error: AFError?) {
NotificationCenter.default.postNotification(named: Request.didCompleteTaskNotification, with: request)
}
}
//
// OperationQueue+Alamofire.swift
//
// Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/)
//
// 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.
//
import Foundation
extension OperationQueue {
/// Creates an instance using the provided parameters.
///
/// - Parameters:
/// - qualityOfService: `QualityOfService` to be applied to the queue. `.default` by default.
/// - maxConcurrentOperationCount: Maximum concurrent operations.
/// `OperationQueue.defaultMaxConcurrentOperationCount` by default.
/// - underlyingQueue: Underlying `DispatchQueue`. `nil` by default.
/// - name: Name for the queue. `nil` by default.
/// - startSuspended: Whether the queue starts suspended. `false` by default.
convenience init(qualityOfService: QualityOfService = .default,
maxConcurrentOperationCount: Int = OperationQueue.defaultMaxConcurrentOperationCount,
underlyingQueue: DispatchQueue? = nil,
name: String? = nil,
startSuspended: Bool = false) {
self.init()
self.qualityOfService = qualityOfService
self.maxConcurrentOperationCount = maxConcurrentOperationCount
self.underlyingQueue = underlyingQueue
self.name = name
isSuspended = startSuspended
}
}
//
// ParameterEncoder.swift
//
// Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/)
//
// 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.
//
import Foundation
/// A type that can encode any `Encodable` type into a `URLRequest`.
public protocol ParameterEncoder {
/// Encode the provided `Encodable` parameters into `request`.
///
/// - Parameters:
/// - parameters: The `Encodable` parameter value.
/// - request: The `URLRequest` into which to encode the parameters.
///
/// - Returns: A `URLRequest` with the result of the encoding.
/// - Throws: An `Error` when encoding fails. For Alamofire provided encoders, this will be an instance of
/// `AFError.parameterEncoderFailed` with an associated `ParameterEncoderFailureReason`.
func encode<Parameters: Encodable>(_ parameters: Parameters?, into request: URLRequest) throws -> URLRequest
}
/// A `ParameterEncoder` that encodes types as JSON body data.
///
/// If no `Content-Type` header is already set on the provided `URLRequest`s, it's set to `application/json`.
open class JSONParameterEncoder: ParameterEncoder {
/// Returns an encoder with default parameters.
public static var `default`: JSONParameterEncoder { return JSONParameterEncoder() }
/// Returns an encoder with `JSONEncoder.outputFormatting` set to `.prettyPrinted`.
public static var prettyPrinted: JSONParameterEncoder {
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
return JSONParameterEncoder(encoder: encoder)
}
/// Returns an encoder with `JSONEncoder.outputFormatting` set to `.sortedKeys`.
@available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4.0, *)
public static var sortedKeys: JSONParameterEncoder {
let encoder = JSONEncoder()
encoder.outputFormatting = .sortedKeys
return JSONParameterEncoder(encoder: encoder)
}
/// `JSONEncoder` used to encode parameters.
public let encoder: JSONEncoder
/// Creates an instance with the provided `JSONEncoder`.
///
/// - Parameter encoder: The `JSONEncoder`. `JSONEncoder()` by default.
public init(encoder: JSONEncoder = JSONEncoder()) {
self.encoder = encoder
}
open func encode<Parameters: Encodable>(_ parameters: Parameters?,
into request: URLRequest) throws -> URLRequest {
guard let parameters = parameters else { return request }
var request = request
do {
let data = try encoder.encode(parameters)
request.httpBody = data
if request.headers["Content-Type"] == nil {
request.headers.update(.contentType("application/json"))
}
} catch {
throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error))
}
return request
}
}
/// A `ParameterEncoder` that encodes types as URL-encoded query strings to be set on the URL or as body data, depending
/// on the `Destination` set.
///
/// If no `Content-Type` header is already set on the provided `URLRequest`s, it will be set to
/// `application/x-www-form-urlencoded; charset=utf-8`.
///
/// Encoding behavior can be customized by passing an instance of `URLEncodedFormEncoder` to the initializer.
open class URLEncodedFormParameterEncoder: ParameterEncoder {
/// Defines where the URL-encoded string should be set for each `URLRequest`.
public enum Destination {
/// Applies the encoded query string to any existing query string for `.get`, `.head`, and `.delete` request.
/// Sets it to the `httpBody` for all other methods.
case methodDependent
/// Applies the encoded query string to any existing query string from the `URLRequest`.
case queryString
/// Applies the encoded query string to the `httpBody` of the `URLRequest`.
case httpBody
/// Determines whether the URL-encoded string should be applied to the `URLRequest`'s `url`.
///
/// - Parameter method: The `HTTPMethod`.
///
/// - Returns: Whether the URL-encoded string should be applied to a `URL`.
func encodesParametersInURL(for method: HTTPMethod) -> Bool {
switch self {
case .methodDependent: return [.get, .head, .delete].contains(method)
case .queryString: return true
case .httpBody: return false
}
}
}
/// Returns an encoder with default parameters.
public static var `default`: URLEncodedFormParameterEncoder { return URLEncodedFormParameterEncoder() }
/// The `URLEncodedFormEncoder` to use.
public let encoder: URLEncodedFormEncoder
/// The `Destination` for the URL-encoded string.
public let destination: Destination
/// Creates an instance with the provided `URLEncodedFormEncoder` instance and `Destination` value.
///
/// - Parameters:
/// - encoder: The `URLEncodedFormEncoder`. `URLEncodedFormEncoder()` by default.
/// - destination: The `Destination`. `.methodDependent` by default.
public init(encoder: URLEncodedFormEncoder = URLEncodedFormEncoder(), destination: Destination = .methodDependent) {
self.encoder = encoder
self.destination = destination
}
open func encode<Parameters: Encodable>(_ parameters: Parameters?,
into request: URLRequest) throws -> URLRequest {
guard let parameters = parameters else { return request }
var request = request
guard let url = request.url else {
throw AFError.parameterEncoderFailed(reason: .missingRequiredComponent(.url))
}
guard let method = request.method else {
let rawValue = request.method?.rawValue ?? "nil"
throw AFError.parameterEncoderFailed(reason: .missingRequiredComponent(.httpMethod(rawValue: rawValue)))
}
if destination.encodesParametersInURL(for: method),
var components = URLComponents(url: url, resolvingAgainstBaseURL: false) {
let query: String = try Result<String, Error> { try encoder.encode(parameters) }
.mapError { AFError.parameterEncoderFailed(reason: .encoderFailed(error: $0)) }.get()
let newQueryString = [components.percentEncodedQuery, query].compactMap { $0 }.joinedWithAmpersands()
components.percentEncodedQuery = newQueryString.isEmpty ? nil : newQueryString
guard let newURL = components.url else {
throw AFError.parameterEncoderFailed(reason: .missingRequiredComponent(.url))
}
request.url = newURL
} else {
if request.headers["Content-Type"] == nil {
request.headers.update(.contentType("application/x-www-form-urlencoded; charset=utf-8"))
}
request.httpBody = try Result<Data, Error> { try encoder.encode(parameters) }
.mapError { AFError.parameterEncoderFailed(reason: .encoderFailed(error: $0)) }.get()
}
return request
}
}
//
// Protector.swift
//
// Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/)
//
// 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.
//
import Foundation
// MARK: -
/// An `os_unfair_lock` wrapper.
final class UnfairLock {
private let unfairLock: os_unfair_lock_t
init() {
unfairLock = .allocate(capacity: 1)
unfairLock.initialize(to: os_unfair_lock())
}
deinit {
unfairLock.deinitialize(count: 1)
unfairLock.deallocate()
}
private func lock() {
os_unfair_lock_lock(unfairLock)
}
private func unlock() {
os_unfair_lock_unlock(unfairLock)
}
/// Executes a closure returning a value while acquiring the lock.
///
/// - Parameter closure: The closure to run.
///
/// - Returns: The value the closure generated.
func around<T>(_ closure: () -> T) -> T {
lock(); defer { unlock() }
return closure()
}
/// Execute a closure while acquiring the lock.
///
/// - Parameter closure: The closure to run.
func around(_ closure: () -> Void) {
lock(); defer { unlock() }
return closure()
}
}
/// A thread-safe wrapper around a value.
final class Protector<T> {
private let lock = UnfairLock()
private var value: T
init(_ value: T) {
self.value = value
}
/// The contained value. Unsafe for anything more than direct read or write.
var directValue: T {
get { return lock.around { value } }
set { lock.around { value = newValue } }
}
/// Synchronously read or transform the contained value.
///
/// - Parameter closure: The closure to execute.
///
/// - Returns: The return value of the closure passed.
func read<U>(_ closure: (T) -> U) -> U {
return lock.around { closure(self.value) }
}
/// Synchronously modify the protected value.
///
/// - Parameter closure: The closure to execute.
///
/// - Returns: The modified value.
@discardableResult
func write<U>(_ closure: (inout T) -> U) -> U {
return lock.around { closure(&self.value) }
}
}
extension Protector where T: RangeReplaceableCollection {
/// Adds a new element to the end of this protected collection.
///
/// - Parameter newElement: The `Element` to append.
func append(_ newElement: T.Element) {
write { (ward: inout T) in
ward.append(newElement)
}
}
/// Adds the elements of a sequence to the end of this protected collection.
///
/// - Parameter newElements: The `Sequence` to append.
func append<S: Sequence>(contentsOf newElements: S) where S.Element == T.Element {
write { (ward: inout T) in
ward.append(contentsOf: newElements)
}
}
/// Add the elements of a collection to the end of the protected collection.
///
/// - Parameter newElements: The `Collection` to append.
func append<C: Collection>(contentsOf newElements: C) where C.Element == T.Element {
write { (ward: inout T) in
ward.append(contentsOf: newElements)
}
}
}
extension Protector where T == Data? {
/// Adds the contents of a `Data` value to the end of the protected `Data`.
///
/// - Parameter data: The `Data` to be appended.
func append(_ data: Data) {
write { (ward: inout T) in
ward?.append(data)
}
}
}
extension Protector where T == Request.MutableState {
/// Attempts to transition to the passed `State`.
///
/// - Parameter state: The `State` to attempt transition to.
///
/// - Returns: Whether the transition occurred.
func attemptToTransitionTo(_ state: Request.State) -> Bool {
return lock.around {
guard value.state.canTransitionTo(state) else { return false }
value.state = state
return true
}
}
/// Perform a closure while locked with the provided `Request.State`.
///
/// - Parameter perform: The closure to perform while locked.
func withState(perform: (Request.State) -> Void) {
lock.around { perform(value.state) }
}
}
//
// RedirectHandler.swift
//
// Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/)
//
// 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.
//
import Foundation
/// A type that handles how an HTTP redirect response from a remote server should be redirected to the new request.
public protocol RedirectHandler {
/// Determines how the HTTP redirect response should be redirected to the new request.
///
/// The `completion` closure should be passed one of three possible options:
///
/// 1. The new request specified by the redirect (this is the most common use case).
/// 2. A modified version of the new request (you may want to route it somewhere else).
/// 3. A `nil` value to deny the redirect request and return the body of the redirect response.
///
/// - Parameters:
/// - task: The `URLSessionTask` whose request resulted in a redirect.
/// - request: The `URLRequest` to the new location specified by the redirect response.
/// - response: The `HTTPURLResponse` containing the server's response to the original request.
/// - completion: The closure to execute containing the new `URLRequest`, a modified `URLRequest`, or `nil`.
func task(_ task: URLSessionTask,
willBeRedirectedTo request: URLRequest,
for response: HTTPURLResponse,
completion: @escaping (URLRequest?) -> Void)
}
// MARK: -
/// `Redirector` is a convenience `RedirectHandler` making it easy to follow, not follow, or modify a redirect.
public struct Redirector {
/// Defines the behavior of the `Redirector` type.
public enum Behavior {
/// Follow the redirect as defined in the response.
case follow
/// Do not follow the redirect defined in the response.
case doNotFollow
/// Modify the redirect request defined in the response.
case modify((URLSessionTask, URLRequest, HTTPURLResponse) -> URLRequest?)
}
/// Returns a `Redirector` with a `.follow` `Behavior`.
public static let follow = Redirector(behavior: .follow)
/// Returns a `Redirector` with a `.doNotFollow` `Behavior`.
public static let doNotFollow = Redirector(behavior: .doNotFollow)
/// The `Behavior` of the `Redirector`.
public let behavior: Behavior
/// Creates a `Redirector` instance from the `Behavior`.
///
/// - Parameter behavior: The `Behavior`.
public init(behavior: Behavior) {
self.behavior = behavior
}
}
// MARK: -
extension Redirector: RedirectHandler {
public func task(_ task: URLSessionTask,
willBeRedirectedTo request: URLRequest,
for response: HTTPURLResponse,
completion: @escaping (URLRequest?) -> Void) {
switch behavior {
case .follow:
completion(request)
case .doNotFollow:
completion(nil)
case let .modify(closure):
let request = closure(task, request, response)
completion(request)
}
}
}
//
// RequestInterceptor.swift
//
// Copyright (c) 2019 Alamofire Software Foundation (http://alamofire.org/)
//
// 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.
//
import Foundation
/// A type that can inspect and optionally adapt a `URLRequest` in some manner if necessary.
public protocol RequestAdapter {
/// Inspects and adapts the specified `URLRequest` in some manner and calls the completion handler with the Result.
///
/// - Parameters:
/// - urlRequest: The `URLRequest` to adapt.
/// - session: The `Session` that will execute the `URLRequest`.
/// - completion: The completion handler that must be called when adaptation is complete.
func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void)
}
// MARK: -
/// Outcome of determination whether retry is necessary.
public enum RetryResult {
/// Retry should be attempted immediately.
case retry
/// Retry should be attempted after the associated `TimeInterval`.
case retryWithDelay(TimeInterval)
/// Do not retry.
case doNotRetry
/// Do not retry due to the associated `Error`.
case doNotRetryWithError(Error)
}
extension RetryResult {
var retryRequired: Bool {
switch self {
case .retry, .retryWithDelay: return true
default: return false
}
}
var delay: TimeInterval? {
switch self {
case let .retryWithDelay(delay): return delay
default: return nil
}
}
var error: Error? {
guard case let .doNotRetryWithError(error) = self else { return nil }
return error
}
}
/// A type that determines whether a request should be retried after being executed by the specified session manager
/// and encountering an error.
public protocol RequestRetrier {
/// Determines whether the `Request` should be retried by calling the `completion` closure.
///
/// This operation is fully asynchronous. Any amount of time can be taken to determine whether the request needs
/// to be retried. The one requirement is that the completion closure is called to ensure the request is properly
/// cleaned up after.
///
/// - Parameters:
/// - request: `Request` that failed due to the provided `Error`.
/// - session: `Session` that produced the `Request`.
/// - error: `Error` encountered while executing the `Request`.
/// - completion: Completion closure to be executed when a retry decision has been determined.
func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void)
}
// MARK: -
/// Type that provides both `RequestAdapter` and `RequestRetrier` functionality.
public protocol RequestInterceptor: RequestAdapter, RequestRetrier {}
extension RequestInterceptor {
public func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
completion(.success(urlRequest))
}
public func retry(_ request: Request,
for session: Session,
dueTo error: Error,
completion: @escaping (RetryResult) -> Void) {
completion(.doNotRetry)
}
}
/// `RequestAdapter` closure definition.
public typealias AdaptHandler = (URLRequest, Session, _ completion: @escaping (Result<URLRequest, Error>) -> Void) -> Void
/// `RequestRetrier` closure definition.
public typealias RetryHandler = (Request, Session, Error, _ completion: @escaping (RetryResult) -> Void) -> Void
// MARK: -
/// Closure-based `RequestAdapter`.
open class Adapter: RequestInterceptor {
private let adaptHandler: AdaptHandler
/// Creates an instance using the provided closure.
///
/// - Parameter adaptHandler: `AdaptHandler` closure to be executed when handling request adaptation.
public init(_ adaptHandler: @escaping AdaptHandler) {
self.adaptHandler = adaptHandler
}
open func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
adaptHandler(urlRequest, session, completion)
}
}
// MARK: -
/// Closure-based `RequestRetrier`.
open class Retrier: RequestInterceptor {
private let retryHandler: RetryHandler
/// Creates an instance using the provided closure.
///
/// - Parameter retryHandler: `RetryHandler` closure to be executed when handling request retry.
public init(_ retryHandler: @escaping RetryHandler) {
self.retryHandler = retryHandler
}
open func retry(_ request: Request,
for session: Session,
dueTo error: Error,
completion: @escaping (RetryResult) -> Void) {
retryHandler(request, session, error, completion)
}
}
// MARK: -
/// `RequestInterceptor` which can use multiple `RequestAdapter` and `RequestRetrier` values.
open class Interceptor: RequestInterceptor {
/// All `RequestAdapter`s associated with the instance. These adapters will be run until one fails.
public let adapters: [RequestAdapter]
/// All `RequestRetrier`s associated with the instance. These retriers will be run one at a time until one triggers retry.
public let retriers: [RequestRetrier]
/// Creates an instance from `AdaptHandler` and `RetryHandler` closures.
///
/// - Parameters:
/// - adaptHandler: `AdaptHandler` closure to be used.
/// - retryHandler: `RetryHandler` closure to be used.
public init(adaptHandler: @escaping AdaptHandler, retryHandler: @escaping RetryHandler) {
adapters = [Adapter(adaptHandler)]
retriers = [Retrier(retryHandler)]
}
/// Creates an instance from `RequestAdapter` and `RequestRetrier` values.
///
/// - Parameters:
/// - adapter: `RequestAdapter` value to be used.
/// - retrier: `RequestRetrier` value to be used.
public init(adapter: RequestAdapter, retrier: RequestRetrier) {
adapters = [adapter]
retriers = [retrier]
}
/// Creates an instance from the arrays of `RequestAdapter` and `RequestRetrier` values.
///
/// - Parameters:
/// - adapters: `RequestAdapter` values to be used.
/// - retriers: `RequestRetrier` values to be used.
public init(adapters: [RequestAdapter] = [], retriers: [RequestRetrier] = []) {
self.adapters = adapters
self.retriers = retriers
}
open func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
adapt(urlRequest, for: session, using: adapters, completion: completion)
}
private func adapt(_ urlRequest: URLRequest,
for session: Session,
using adapters: [RequestAdapter],
completion: @escaping (Result<URLRequest, Error>) -> Void) {
var pendingAdapters = adapters
guard !pendingAdapters.isEmpty else { completion(.success(urlRequest)); return }
let adapter = pendingAdapters.removeFirst()
adapter.adapt(urlRequest, for: session) { result in
switch result {
case let .success(urlRequest):
self.adapt(urlRequest, for: session, using: pendingAdapters, completion: completion)
case .failure:
completion(result)
}
}
}
open func retry(_ request: Request,
for session: Session,
dueTo error: Error,
completion: @escaping (RetryResult) -> Void) {
retry(request, for: session, dueTo: error, using: retriers, completion: completion)
}
private func retry(_ request: Request,
for session: Session,
dueTo error: Error,
using retriers: [RequestRetrier],
completion: @escaping (RetryResult) -> Void) {
var pendingRetriers = retriers
guard !pendingRetriers.isEmpty else { completion(.doNotRetry); return }
let retrier = pendingRetriers.removeFirst()
retrier.retry(request, for: session, dueTo: error) { result in
switch result {
case .retry, .retryWithDelay, .doNotRetryWithError:
completion(result)
case .doNotRetry:
// Only continue to the next retrier if retry was not triggered and no error was encountered
self.retry(request, for: session, dueTo: error, using: pendingRetriers, completion: completion)
}
}
}
}
//
// RequestTaskMap.swift
//
// Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/)
//
// 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.
//
import Foundation
/// A type that maintains a two way, one to one map of `URLSessionTask`s to `Request`s.
struct RequestTaskMap {
private var tasksToRequests: [URLSessionTask: Request]
private var requestsToTasks: [Request: URLSessionTask]
private var taskEvents: [URLSessionTask: (completed: Bool, metricsGathered: Bool)]
var requests: [Request] {
return Array(tasksToRequests.values)
}
init(tasksToRequests: [URLSessionTask: Request] = [:],
requestsToTasks: [Request: URLSessionTask] = [:],
taskEvents: [URLSessionTask: (completed: Bool, metricsGathered: Bool)] = [:]) {
self.tasksToRequests = tasksToRequests
self.requestsToTasks = requestsToTasks
self.taskEvents = taskEvents
}
subscript(_ request: Request) -> URLSessionTask? {
get { return requestsToTasks[request] }
set {
guard let newValue = newValue else {
guard let task = requestsToTasks[request] else {
fatalError("RequestTaskMap consistency error: no task corresponding to request found.")
}
requestsToTasks.removeValue(forKey: request)
tasksToRequests.removeValue(forKey: task)
taskEvents.removeValue(forKey: task)
return
}
requestsToTasks[request] = newValue
tasksToRequests[newValue] = request
taskEvents[newValue] = (completed: false, metricsGathered: false)
}
}
subscript(_ task: URLSessionTask) -> Request? {
get { return tasksToRequests[task] }
set {
guard let newValue = newValue else {
guard let request = tasksToRequests[task] else {
fatalError("RequestTaskMap consistency error: no request corresponding to task found.")
}
tasksToRequests.removeValue(forKey: task)
requestsToTasks.removeValue(forKey: request)
taskEvents.removeValue(forKey: task)
return
}
tasksToRequests[task] = newValue
requestsToTasks[newValue] = task
taskEvents[task] = (completed: false, metricsGathered: false)
}
}
var count: Int {
precondition(tasksToRequests.count == requestsToTasks.count,
"RequestTaskMap.count invalid, requests.count: \(tasksToRequests.count) != tasks.count: \(requestsToTasks.count)")
return tasksToRequests.count
}
var eventCount: Int {
precondition(taskEvents.count == count, "RequestTaskMap.eventCount invalid, count: \(count) != taskEvents.count: \(taskEvents.count)")
return taskEvents.count
}
var isEmpty: Bool {
precondition(tasksToRequests.isEmpty == requestsToTasks.isEmpty,
"RequestTaskMap.isEmpty invalid, requests.isEmpty: \(tasksToRequests.isEmpty) != tasks.isEmpty: \(requestsToTasks.isEmpty)")
return tasksToRequests.isEmpty
}
var isEventsEmpty: Bool {
precondition(taskEvents.isEmpty == isEmpty, "RequestTaskMap.isEventsEmpty invalid, isEmpty: \(isEmpty) != taskEvents.isEmpty: \(taskEvents.isEmpty)")
return taskEvents.isEmpty
}
mutating func disassociateIfNecessaryAfterGatheringMetricsForTask(_ task: URLSessionTask) {
guard let events = taskEvents[task] else {
fatalError("RequestTaskMap consistency error: no events corresponding to task found.")
}
switch (events.completed, events.metricsGathered) {
case (_, true): fatalError("RequestTaskMap consistency error: duplicate metricsGatheredForTask call.")
case (false, false): taskEvents[task] = (completed: false, metricsGathered: true)
case (true, false): self[task] = nil
}
}
mutating func disassociateIfNecessaryAfterCompletingTask(_ task: URLSessionTask) {
guard let events = taskEvents[task] else {
fatalError("RequestTaskMap consistency error: no events corresponding to task found.")
}
switch (events.completed, events.metricsGathered) {
case (true, _): fatalError("RequestTaskMap consistency error: duplicate completionReceivedForTask call.")
case (false, false): taskEvents[task] = (completed: true, metricsGathered: false)
case (false, true): self[task] = nil
}
}
}
//
// Result+Alamofire.swift
//
// Copyright (c) 2019 Alamofire Software Foundation (http://alamofire.org/)
//
// 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.
//
import Foundation
/// Default type of `Result` returned by Alamofire, with an `AFError` `Failure` type.
public typealias AFResult<Success> = Result<Success, AFError>
// MARK: - Internal APIs
extension Result {
/// Returns the associated value if the result is a success, `nil` otherwise.
var success: Success? {
guard case let .success(value) = self else { return nil }
return value
}
/// Returns the associated error value if the result is a failure, `nil` otherwise.
var failure: Failure? {
guard case let .failure(error) = self else { return nil }
return error
}
/// Initializes a `Result` from value or error. Returns `.failure` if the error is non-nil, `.success` otherwise.
///
/// - Parameters:
/// - value: A value.
/// - error: An `Error`.
init(value: Success, error: Failure?) {
if let error = error {
self = .failure(error)
} else {
self = .success(value)
}
}
/// Evaluates the specified closure when the `Result` is a success, passing the unwrapped value as a parameter.
///
/// Use the `tryMap` method with a closure that may throw an error. For example:
///
/// let possibleData: Result<Data, Error> = .success(Data(...))
/// let possibleObject = possibleData.tryMap {
/// try JSONSerialization.jsonObject(with: $0)
/// }
///
/// - parameter transform: A closure that takes the success value of the instance.
///
/// - returns: A `Result` containing the result of the given closure. If this instance is a failure, returns the
/// same failure.
func tryMap<NewSuccess>(_ transform: (Success) throws -> NewSuccess) -> Result<NewSuccess, Error> {
switch self {
case let .success(value):
do {
return try .success(transform(value))
} catch {
return .failure(error)
}
case let .failure(error):
return .failure(error)
}
}
/// Evaluates the specified closure when the `Result` is a failure, passing the unwrapped error as a parameter.
///
/// Use the `tryMapError` function with a closure that may throw an error. For example:
///
/// let possibleData: Result<Data, Error> = .success(Data(...))
/// let possibleObject = possibleData.tryMapError {
/// try someFailableFunction(taking: $0)
/// }
///
/// - Parameter transform: A throwing closure that takes the error of the instance.
///
/// - Returns: A `Result` instance containing the result of the transform. If this instance is a success, returns
/// the same success.
func tryMapError<NewFailure: Error>(_ transform: (Failure) throws -> NewFailure) -> Result<Success, Error> {
switch self {
case let .failure(error):
do {
return try .failure(transform(error))
} catch {
return .failure(error)
}
case let .success(value):
return .success(value)
}
}
}
//
// URLConvertible+URLRequestConvertible.swift
//
// Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/)
//
// 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.
//
import Foundation
/// Types adopting the `URLConvertible` protocol can be used to construct `URL`s, which can then be used to construct
/// `URLRequests`.
public protocol URLConvertible {
/// Returns a `URL` from the conforming instance or throws.
///
/// - Returns: The `URL` created from the instance.
/// - Throws: Any error thrown while creating the `URL`.
func asURL() throws -> URL
}
extension String: URLConvertible {
/// Returns a `URL` if `self` can be used to initialize a `URL` instance, otherwise throws.
///
/// - Returns: The `URL` initialized with `self`.
/// - Throws: An `AFError.invalidURL` instance.
public func asURL() throws -> URL {
guard let url = URL(string: self) else { throw AFError.invalidURL(url: self) }
return url
}
}
extension URL: URLConvertible {
/// Returns `self`.
public func asURL() throws -> URL { return self }
}
extension URLComponents: URLConvertible {
/// Returns a `URL` if the `self`'s `url` is not nil, otherwise throws.
///
/// - Returns: The `URL` from the `url` property.
/// - Throws: An `AFError.invalidURL` instance.
public func asURL() throws -> URL {
guard let url = url else { throw AFError.invalidURL(url: self) }
return url
}
}
// MARK: -
/// Types adopting the `URLRequestConvertible` protocol can be used to safely construct `URLRequest`s.
public protocol URLRequestConvertible {
/// Returns a `URLRequest` or throws if an `Error` was encountered.
///
/// - Returns: A `URLRequest`.
/// - Throws: Any error thrown while constructing the `URLRequest`.
func asURLRequest() throws -> URLRequest
}
extension URLRequestConvertible {
/// The `URLRequest` returned by discarding any `Error` encountered.
public var urlRequest: URLRequest? { return try? asURLRequest() }
}
extension URLRequest: URLRequestConvertible {
/// Returns `self`.
public func asURLRequest() throws -> URLRequest { return self }
}
// MARK: -
extension URLRequest {
/// Creates an instance with the specified `url`, `method`, and `headers`.
///
/// - Parameters:
/// - url: The `URLConvertible` value.
/// - method: The `HTTPMethod`.
/// - headers: The `HTTPHeaders`, `nil` by default.
/// - Throws: Any error thrown while converting the `URLConvertible` to a `URL`.
public init(url: URLConvertible, method: HTTPMethod, headers: HTTPHeaders? = nil) throws {
let url = try url.asURL()
self.init(url: url)
httpMethod = method.rawValue
allHTTPHeaderFields = headers?.dictionary
}
}
//
// URLRequest+Alamofire.swift
//
// Copyright (c) 2019 Alamofire Software Foundation (http://alamofire.org/)
//
// 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.
//
import Foundation
public extension URLRequest {
/// Returns the `httpMethod` as Alamofire's `HTTPMethod` type.
var method: HTTPMethod? {
get { return httpMethod.flatMap(HTTPMethod.init) }
set { httpMethod = newValue?.rawValue }
}
func validate() throws {
if method == .get, let bodyData = httpBody {
throw AFError.urlRequestValidationFailed(reason: .bodyDataInGETRequest(bodyData))
}
}
}
//
// URLSessionConfiguration+Alamofire.swift
//
// Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/)
//
// 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.
//
import Foundation
extension URLSessionConfiguration: AlamofireExtended {}
extension AlamofireExtension where ExtendedType: URLSessionConfiguration {
/// Alamofire's default configuration. Same as `URLSessionConfiguration.default` but adds Alamofire default
/// `Accept-Language`, `Accept-Encoding`, and `User-Agent` headers.
public static var `default`: URLSessionConfiguration {
let configuration = URLSessionConfiguration.default
configuration.headers = .default
return configuration
}
}
//
// Validation.swift
//
// Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/)
//
// 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.
//
import Foundation
extension Request {
// MARK: Helper Types
fileprivate typealias ErrorReason = AFError.ResponseValidationFailureReason
/// Used to represent whether a validation succeeded or failed.
public typealias ValidationResult = Result<Void, Error>
fileprivate struct MIMEType {
let type: String
let subtype: String
var isWildcard: Bool { return type == "*" && subtype == "*" }
init?(_ string: String) {
let components: [String] = {
let stripped = string.trimmingCharacters(in: .whitespacesAndNewlines)
let split = stripped[..<(stripped.range(of: ";")?.lowerBound ?? stripped.endIndex)]
return split.components(separatedBy: "/")
}()
if let type = components.first, let subtype = components.last {
self.type = type
self.subtype = subtype
} else {
return nil
}
}
func matches(_ mime: MIMEType) -> Bool {
switch (type, subtype) {
case (mime.type, mime.subtype), (mime.type, "*"), ("*", mime.subtype), ("*", "*"):
return true
default:
return false
}
}
}
// MARK: Properties
fileprivate var acceptableStatusCodes: Range<Int> { return 200..<300 }
fileprivate var acceptableContentTypes: [String] {
if let accept = request?.value(forHTTPHeaderField: "Accept") {
return accept.components(separatedBy: ",")
}
return ["*/*"]
}
// MARK: Status Code
fileprivate func validate<S: Sequence>(statusCode acceptableStatusCodes: S,
response: HTTPURLResponse)
-> ValidationResult
where S.Iterator.Element == Int {
if acceptableStatusCodes.contains(response.statusCode) {
return .success(Void())
} else {
let reason: ErrorReason = .unacceptableStatusCode(code: response.statusCode)
return .failure(AFError.responseValidationFailed(reason: reason))
}
}
// MARK: Content Type
fileprivate func validate<S: Sequence>(contentType acceptableContentTypes: S,
response: HTTPURLResponse,
data: Data?)
-> ValidationResult
where S.Iterator.Element == String {
guard let data = data, !data.isEmpty else { return .success(Void()) }
guard
let responseContentType = response.mimeType,
let responseMIMEType = MIMEType(responseContentType)
else {
for contentType in acceptableContentTypes {
if let mimeType = MIMEType(contentType), mimeType.isWildcard {
return .success(Void())
}
}
let error: AFError = {
let reason: ErrorReason = .missingContentType(acceptableContentTypes: Array(acceptableContentTypes))
return AFError.responseValidationFailed(reason: reason)
}()
return .failure(error)
}
for contentType in acceptableContentTypes {
if let acceptableMIMEType = MIMEType(contentType), acceptableMIMEType.matches(responseMIMEType) {
return .success(Void())
}
}
let error: AFError = {
let reason: ErrorReason = .unacceptableContentType(acceptableContentTypes: Array(acceptableContentTypes),
responseContentType: responseContentType)
return AFError.responseValidationFailed(reason: reason)
}()
return .failure(error)
}
}
// MARK: -
extension DataRequest {
/// A closure used to validate a request that takes a URL request, a URL response and data, and returns whether the
/// request was valid.
public typealias Validation = (URLRequest?, HTTPURLResponse, Data?) -> ValidationResult
/// Validates that the response has a status code in the specified sequence.
///
/// If validation fails, subsequent calls to response handlers will have an associated error.
///
/// - parameter range: The range of acceptable status codes.
///
/// - returns: The request.
@discardableResult
public func validate<S: Sequence>(statusCode acceptableStatusCodes: S) -> Self where S.Iterator.Element == Int {
return validate { [unowned self] _, response, _ in
self.validate(statusCode: acceptableStatusCodes, response: response)
}
}
/// Validates that the response has a content type in the specified sequence.
///
/// If validation fails, subsequent calls to response handlers will have an associated error.
///
/// - parameter contentType: The acceptable content types, which may specify wildcard types and/or subtypes.
///
/// - returns: The request.
@discardableResult
public func validate<S: Sequence>(contentType acceptableContentTypes: @escaping @autoclosure () -> S) -> Self where S.Iterator.Element == String {
return validate { [unowned self] _, response, data in
self.validate(contentType: acceptableContentTypes(), response: response, data: data)
}
}
/// Validates that the response has a status code in the default acceptable range of 200...299, and that the content
/// type matches any specified in the Accept HTTP header field.
///
/// If validation fails, subsequent calls to response handlers will have an associated error.
///
/// - returns: The request.
@discardableResult
public func validate() -> Self {
let contentTypes: () -> [String] = { [unowned self] in
self.acceptableContentTypes
}
return validate(statusCode: acceptableStatusCodes).validate(contentType: contentTypes())
}
}
// MARK: -
extension DownloadRequest {
/// A closure used to validate a request that takes a URL request, a URL response, a temporary URL and a
/// destination URL, and returns whether the request was valid.
public typealias Validation = (_ request: URLRequest?,
_ response: HTTPURLResponse,
_ fileURL: URL?)
-> ValidationResult
/// Validates that the response has a status code in the specified sequence.
///
/// If validation fails, subsequent calls to response handlers will have an associated error.
///
/// - parameter range: The range of acceptable status codes.
///
/// - returns: The request.
@discardableResult
public func validate<S: Sequence>(statusCode acceptableStatusCodes: S) -> Self where S.Iterator.Element == Int {
return validate { [unowned self] _, response, _ in
self.validate(statusCode: acceptableStatusCodes, response: response)
}
}
/// Validates that the response has a content type in the specified sequence.
///
/// If validation fails, subsequent calls to response handlers will have an associated error.
///
/// - parameter contentType: The acceptable content types, which may specify wildcard types and/or subtypes.
///
/// - returns: The request.
@discardableResult
public func validate<S: Sequence>(contentType acceptableContentTypes: @escaping @autoclosure () -> S) -> Self where S.Iterator.Element == String {
return validate { [unowned self] _, response, fileURL in
guard let validFileURL = fileURL else {
return .failure(AFError.responseValidationFailed(reason: .dataFileNil))
}
do {
let data = try Data(contentsOf: validFileURL)
return self.validate(contentType: acceptableContentTypes(), response: response, data: data)
} catch {
return .failure(AFError.responseValidationFailed(reason: .dataFileReadFailed(at: validFileURL)))
}
}
}
/// Validates that the response has a status code in the default acceptable range of 200...299, and that the content
/// type matches any specified in the Accept HTTP header field.
///
/// If validation fails, subsequent calls to response handlers will have an associated error.
///
/// - returns: The request.
@discardableResult
public func validate() -> Self {
let contentTypes = { [unowned self] in
self.acceptableContentTypes
}
return validate(statusCode: acceptableStatusCodes).validate(contentType: contentTypes())
}
}
[![Build Status](https://travis-ci.org/googlemaps/google-maps-ios-utils.svg?branch=master)](https://travis-ci.org/googlemaps/google-maps-ios-utils)
[![pod](https://img.shields.io/cocoapods/v/Google-Maps-iOS-Utils.svg)](https://cocoapods.org/pods/Google-Maps-iOS-Utils)
![GitHub contributors](https://img.shields.io/github/contributors/googlemaps/google-maps-ios-utils)
![Apache-2.0](https://img.shields.io/badge/license-Apache-blue)
Google Maps SDK for iOS Utility Library
=======================================
## Description
This open-source library contains classes that are useful for a wide
range of applications using the [Google Maps SDK for iOS][sdk].
- **Marker clustering** — handles the display of a large number of points
- **Marker customization** - [display custom markers][customizing-markers]
- **Quadtree data structure** - indexes 2D geometry points and performs
2D range queries
- **Geometry libraries** - [KML and GeoJSON rendering][geometry-rendering]
- **Heatmaps** - [Heatmap rendering][heatmap-rendering]
<p align="center"><img width=“80%" vspace=“10" src="https://cloud.githubusercontent.com/assets/16808355/16646253/77feeb96-446c-11e6-9ec1-19e12a7fb3ae.png"></p>
## Requirements
* iOS 9.0+
* CocoaPods
## Installation
### [CocoaPods](https://guides.cocoapods.org/using/using-cocoapods.html)
In your `Podfile`:
```ruby
use_frameworks!
target 'TARGET_NAME' do
pod 'Google-Maps-iOS-Utils', '~> 3.1.1'
end
```
Replace `TARGET_NAME` and then, in the `Podfile` directory, type:
```bash
$ pod install
```
### [Carthage](https://github.com/Carthage/Carthage)
Coming soon! See [#249].
## Samples and Example Usage
e.g. Displaying KML data
```swift
import GoogleMapsUtils
func renderKml() {
// Parse KML
let path: String = // Path to your KML file...
let kmlUrl = URL(fileURLWithPath: path)
let kmlParser = GMUKmlParser(url: kmlUrl)
kmlParser.parse()
// Render parsed KML
let renderer = GMUGeometryRenderer(
map: mapView,
geometries: kmlParser.placemarks,
styles: kmlParser.styles,
styleMaps: kmlParser.styleMaps
)
renderer.render()
}
```
You can see more example usages in our [sample][samples] projects.
## Support
Encounter an issue while using this library?
If you find a bug or have a feature request, please file an [issue].
Or, if you'd like to contribute, please refer to our [contributing guide][contributing] and our [code of conduct].
You can also reach us on our [Discord channel].
For more information, check out the detailed guide on the
[Google Developers site][devsite-guide].
[#249]: https://github.com/googlemaps/google-maps-ios-utils/issues/249
[Discord channel]: https://discord.gg/9fwRNWg
[contributing]: CONTRIBUTING.md
[code of conduct]: CODE_OF_CONDUCT.md
[devsite-guide]: https://developers.google.com/maps/documentation/ios-sdk/utility/
[sdk]: https://developers.google.com/maps/documentation/ios-sdk
[issue]: https://github.com/googlemaps/google-maps-ios-utils/issues
[customizing-markers]: CustomMarkers.md
[geometry-rendering]: GeometryRendering.md
[heatmap-rendering]: HeatmapRendering.md
[samples]: https://github.com/googlemaps/google-maps-ios-utils/tree/master/samples
/* Copyright (c) 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
#import "GMUCluster.h"
#import "GMUClusterItem.h"
NS_ASSUME_NONNULL_BEGIN
/**
* Generic protocol for arranging cluster items into groups.
*/
@protocol GMUClusterAlgorithm<NSObject>
- (void)addItems:(NSArray<id<GMUClusterItem>> *)items;
/**
* Removes an item.
*/
- (void)removeItem:(id<GMUClusterItem>)item;
/**
* Clears all items.
*/
- (void)clearItems;
/**
* Returns the set of clusters of the added items.
*/
- (NSArray<id<GMUCluster>> *)clustersAtZoom:(float)zoom;
@end
NS_ASSUME_NONNULL_END
/* Copyright (c) 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
#import "GMUClusterAlgorithm.h"
/**
* A simple algorithm which devides the map into a grid where a cell has fixed dimension in
* screen space.
*/
@interface GMUGridBasedClusterAlgorithm : NSObject<GMUClusterAlgorithm>
@end
/* Copyright (c) 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
#import "GMUGridBasedClusterAlgorithm.h"
#import <GoogleMaps/GMSGeometryUtils.h>
#import "GMUStaticCluster.h"
#import "GMUClusterItem.h"
// Grid cell dimension in pixels to keep clusters about 100 pixels apart on screen.
static const NSUInteger kGMUGridCellSizePoints = 100;
@implementation GMUGridBasedClusterAlgorithm {
NSMutableArray<id<GMUClusterItem>> *_items;
}
- (instancetype)init {
if ((self = [super init])) {
_items = [[NSMutableArray alloc] init];
}
return self;
}
- (void)addItems:(NSArray<id<GMUClusterItem>> *)items {
[_items addObjectsFromArray:items];
}
- (void)removeItem:(id<GMUClusterItem>)item {
[_items removeObject:item];
}
- (void)clearItems {
[_items removeAllObjects];
}
- (NSArray<id<GMUCluster>> *)clustersAtZoom:(float)zoom {
NSMutableDictionary<NSNumber *, id<GMUCluster>> *clusters = [[NSMutableDictionary alloc] init];
// Divide the whole map into a numCells x numCells grid and assign items to them.
long numCells = (long)ceil(256 * pow(2, zoom) / kGMUGridCellSizePoints);
for (id<GMUClusterItem> item in _items) {
GMSMapPoint point = GMSProject(item.position);
long col = (long)(numCells * (1.0 + point.x) / 2); // point.x is in [-1, 1] range
long row = (long)(numCells * (1.0 + point.y) / 2); // point.y is in [-1, 1] range
long index = numCells * row + col;
NSNumber *cellKey = [NSNumber numberWithLong:index];
GMUStaticCluster *cluster = clusters[cellKey];
if (cluster == nil) {
// Normalize cluster's centroid to center of the cell.
GMSMapPoint point2 = {(double)(col + 0.5) * 2.0 / numCells - 1,
(double)(row + 0.5) * 2.0 / numCells - 1};
CLLocationCoordinate2D position = GMSUnproject(point2);
cluster = [[GMUStaticCluster alloc] initWithPosition:position];
clusters[cellKey] = cluster;
}
[cluster addItem:item];
}
return [clusters allValues];
}
@end
/* Copyright (c) 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
#import "GMUClusterAlgorithm.h"
/**
* A simple clustering algorithm with O(nlog n) performance. Resulting clusters are not
* hierarchical.
* High level algorithm:
* 1. Iterate over items in the order they were added (candidate clusters).
* 2. Create a cluster with the center of the item.
* 3. Add all items that are within a certain distance to the cluster.
* 4. Move any items out of an existing cluster if they are closer to another cluster.
* 5. Remove those items from the list of candidate clusters.
* Clusters have the center of the first element (not the centroid of the items within it).
*/
@interface GMUNonHierarchicalDistanceBasedAlgorithm : NSObject<GMUClusterAlgorithm>
/**
* Initializes this GMUNonHierarchicalDistanceBasedAlgorithm with clusterDistancePoints for
* the distance it uses to cluster items (default is 100).
*/
- (instancetype)initWithClusterDistancePoints:(NSUInteger)clusterDistancePoints;
@end
/* Copyright (c) 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
#import "GMUNonHierarchicalDistanceBasedAlgorithm.h"
#import <GoogleMaps/GMSGeometryUtils.h>
#import "GMUStaticCluster.h"
#import "GMUClusterItem.h"
#import "GMUWrappingDictionaryKey.h"
#import "GQTPointQuadTree.h"
static const NSUInteger kGMUDefaultClusterDistancePoints = 100;
static const double kGMUMapPointWidth = 2.0; // MapPoint is in a [-1,1]x[-1,1] space.
#pragma mark Utilities Classes
@interface GMUClusterItemQuadItem : NSObject<GQTPointQuadTreeItem>
@property(nonatomic, readonly) id<GMUClusterItem> clusterItem;
- (instancetype)initWithClusterItem:(id<GMUClusterItem>)clusterItem;
@end
@implementation GMUClusterItemQuadItem {
id<GMUClusterItem> _clusterItem;
GQTPoint _clusterItemPoint;
}
- (instancetype)initWithClusterItem:(id<GMUClusterItem>)clusterItem {
if ((self = [super init])) {
_clusterItem = clusterItem;
GMSMapPoint point = GMSProject(clusterItem.position);
_clusterItemPoint.x = point.x;
_clusterItemPoint.y = point.y;
}
return self;
}
- (GQTPoint)point {
return _clusterItemPoint;
}
// Forwards hash to clusterItem.
- (NSUInteger)hash {
return [_clusterItem hash];
}
// Forwards isEqual to clusterItem.
- (BOOL)isEqual:(id)object {
if (self == object) return YES;
if ([object class] != [self class]) return NO;
GMUClusterItemQuadItem *other = (GMUClusterItemQuadItem *)object;
return [_clusterItem isEqual:other->_clusterItem];
}
@end
#pragma mark GMUNonHierarchicalDistanceBasedAlgorithm
@implementation GMUNonHierarchicalDistanceBasedAlgorithm {
NSMutableArray<id<GMUClusterItem>> *_items;
GQTPointQuadTree *_quadTree;
NSUInteger _clusterDistancePoints;
}
- (instancetype)init {
return [self initWithClusterDistancePoints:kGMUDefaultClusterDistancePoints];
}
- (instancetype)initWithClusterDistancePoints:(NSUInteger)clusterDistancePoints {
if ((self = [super init])) {
_items = [[NSMutableArray alloc] init];
GQTBounds bounds = {-1, -1, 1, 1};
_quadTree = [[GQTPointQuadTree alloc] initWithBounds:bounds];
_clusterDistancePoints = clusterDistancePoints;
}
return self;
}
- (void)addItems:(NSArray<id<GMUClusterItem>> *)items {
[_items addObjectsFromArray:items];
for (id<GMUClusterItem> item in items) {
GMUClusterItemQuadItem *quadItem = [[GMUClusterItemQuadItem alloc] initWithClusterItem:item];
[_quadTree add:quadItem];
}
}
/**
* Removes an item.
*/
- (void)removeItem:(id<GMUClusterItem>)item {
[_items removeObject:item];
GMUClusterItemQuadItem *quadItem = [[GMUClusterItemQuadItem alloc] initWithClusterItem:item];
// This should remove the corresponding quad item since GMUClusterItemQuadItem forwards its hash
// and isEqual to the underlying item.
[_quadTree remove:quadItem];
}
/**
* Clears all items.
*/
- (void)clearItems {
[_items removeAllObjects];
[_quadTree clear];
}
/**
* Returns the set of clusters of the added items.
*/
- (NSArray<id<GMUCluster>> *)clustersAtZoom:(float)zoom {
NSMutableArray<id<GMUCluster>> *clusters = [[NSMutableArray alloc] init];
NSMutableDictionary<GMUWrappingDictionaryKey *, id<GMUCluster>> *itemToClusterMap =
[[NSMutableDictionary alloc] init];
NSMutableDictionary<GMUWrappingDictionaryKey *, NSNumber *> *itemToClusterDistanceMap =
[[NSMutableDictionary alloc] init];
NSMutableSet<id<GMUClusterItem>> *processedItems = [[NSMutableSet alloc] init];
for (id<GMUClusterItem> item in _items) {
if ([processedItems containsObject:item]) continue;
GMUStaticCluster *cluster = [[GMUStaticCluster alloc] initWithPosition:item.position];
GMSMapPoint point = GMSProject(item.position);
// Query for items within a fixed point distance from the current item to make up a cluster
// around it.
double radius = _clusterDistancePoints * kGMUMapPointWidth / pow(2.0, zoom + 8.0);
GQTBounds bounds = {point.x - radius, point.y - radius, point.x + radius, point.y + radius};
NSArray *nearbyItems = [_quadTree searchWithBounds:bounds];
for (GMUClusterItemQuadItem *quadItem in nearbyItems) {
id<GMUClusterItem> nearbyItem = quadItem.clusterItem;
[processedItems addObject:nearbyItem];
GMSMapPoint nearbyItemPoint = GMSProject(nearbyItem.position);
GMUWrappingDictionaryKey *key = [[GMUWrappingDictionaryKey alloc] initWithObject:nearbyItem];
NSNumber *existingDistance = [itemToClusterDistanceMap objectForKey:key];
double distanceSquared = [self distanceSquaredBetweenPointA:point andPointB:nearbyItemPoint];
if (existingDistance != nil) {
if ([existingDistance doubleValue] < distanceSquared) {
// Already belongs to a closer cluster.
continue;
}
GMUStaticCluster *existingCluster = [itemToClusterMap objectForKey:key];
[existingCluster removeItem:nearbyItem];
}
NSNumber *number = [NSNumber numberWithDouble:distanceSquared];
[itemToClusterDistanceMap setObject:number forKey:key];
[itemToClusterMap setObject:cluster forKey:key];
[cluster addItem:nearbyItem];
}
[clusters addObject:cluster];
}
NSAssert(itemToClusterDistanceMap.count == _items.count,
@"All items should be mapped to a distance");
NSAssert(itemToClusterMap.count == _items.count,
@"All items should be mapped to a cluster");
#if DEBUG
NSUInteger totalCount = 0;
for (id<GMUCluster> cluster in clusters) {
totalCount += cluster.count;
}
NSAssert(_items.count == totalCount, @"All clusters combined should make up original item set");
#endif
return clusters;
}
#pragma mark Private
- (double)distanceSquaredBetweenPointA:(GMSMapPoint)pointA andPointB:(GMSMapPoint)pointB {
double deltaX = pointA.x - pointB.x;
double deltaY = pointA.y - pointB.y;
return deltaX * deltaX + deltaY * deltaY;
}
@end
/* Copyright (c) 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
#import "GMUClusterAlgorithm.h"
/**
* Not for production: used for experimenting with new clustering algorithms only.
*/
@interface GMUSimpleClusterAlgorithm : NSObject<GMUClusterAlgorithm>
@end
/* Copyright (c) 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
#import "GMUSimpleClusterAlgorithm.h"
#import "GMUStaticCluster.h"
#import "GMUClusterItem.h"
static const NSUInteger kClusterCount = 10;
@implementation GMUSimpleClusterAlgorithm {
NSMutableArray<id<GMUClusterItem>> *_items;
}
- (instancetype)init {
if ((self = [super init])) {
_items = [[NSMutableArray alloc] init];
}
return self;
}
- (void)addItems:(NSArray<id<GMUClusterItem>> *)items {
[_items addObjectsFromArray:items];
}
- (void)removeItem:(id<GMUClusterItem>)item {
[_items removeObject:item];
}
- (void)clearItems {
[_items removeAllObjects];
}
- (NSArray<id<GMUCluster>> *)clustersAtZoom:(float)zoom {
NSMutableArray<id<GMUCluster>> *clusters =
[[NSMutableArray alloc] initWithCapacity:kClusterCount];
for (int i = 0; i < kClusterCount; ++i) {
if (i >= _items.count) break;
id<GMUClusterItem> item = _items[i];
[clusters addObject:[[GMUStaticCluster alloc] initWithPosition:item.position]];
}
NSUInteger clusterIndex = 0;
for (int i = kClusterCount; i < _items.count; ++i) {
id<GMUClusterItem> item = _items[i];
GMUStaticCluster *cluster = clusters[clusterIndex % kClusterCount];
[cluster addItem:item];
++clusterIndex;
}
return clusters;
}
@end
/* Copyright (c) 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
/**
* Wraps an object which does not implement NSCopying to be used as NSDictionary keys.
* This class will forward -hash and -isEqual methods to the underlying object.
*/
@interface GMUWrappingDictionaryKey : NSObject<NSCopying>
- (instancetype)initWithObject:(id)object;
@end
/* Copyright (c) 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
#import "GMUWrappingDictionaryKey.h"
@implementation GMUWrappingDictionaryKey {
id _object;
}
- (instancetype)initWithObject:(id)object {
if ((self = [super init])) {
_object = object;
}
return self;
}
- (id)copyWithZone:(NSZone *)zone {
GMUWrappingDictionaryKey *newKey = [[self class] allocWithZone:zone];
if (newKey) {
newKey->_object = _object;
}
return newKey;
}
// Forwards hash to _object.
- (NSUInteger)hash {
return [_object hash];
}
// Forwards isEqual to _object.
- (BOOL)isEqual:(id)object {
if (self == object) return YES;
if ([object class] != [self class]) return NO;
GMUWrappingDictionaryKey *other = (GMUWrappingDictionaryKey *)object;
return [_object isEqual:other->_object];
}
@end
/* Copyright (c) 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <CoreLocation/CoreLocation.h>
#import <Foundation/Foundation.h>
#import "GMUClusterItem.h"
NS_ASSUME_NONNULL_BEGIN
/**
* Defines a generic cluster object.
*/
@protocol GMUCluster <NSObject>
/**
* Returns the position of the cluster.
*/
@property(nonatomic, readonly) CLLocationCoordinate2D position;
/**
* Returns the number of items in the cluster.
*/
@property(nonatomic, readonly) NSUInteger count;
/**
* Returns a copy of the list of items in the cluster.
*/
@property(nonatomic, readonly) NSArray<id<GMUClusterItem>> *items;
@end
NS_ASSUME_NONNULL_END
/* Copyright (c) 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <CoreLocation/CoreLocation.h>
/**
* This protocol defines the contract for a cluster item.
*/
@protocol GMUClusterItem <NSObject>
/**
* Returns the position of the item.
*/
@property(nonatomic, readonly) CLLocationCoordinate2D position;
@end
/* Copyright (c) 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "GMUClusterManager.h"
/**
* Extensions for testing purposes only.
*/
@interface GMUClusterManager (Testing)
/**
* Returns in number of cluster requests.
*/
- (NSUInteger)clusterRequestCount;
@end
/* Copyright (c) 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
#import <GoogleMaps/GoogleMaps.h>
#import "GMUClusterAlgorithm.h"
#import "GMUClusterItem.h"
#import "GMUClusterRenderer.h"
NS_ASSUME_NONNULL_BEGIN
@class GMUClusterManager;
/**
* Delegate for events on the GMUClusterManager.
*/
@protocol GMUClusterManagerDelegate <NSObject>
@optional
/**
* Called when the user taps on a cluster marker.
* @return YES if this delegate handled the tap event,
* and NO to pass this tap event to other handlers.
*/
- (BOOL)clusterManager:(GMUClusterManager *)clusterManager didTapCluster:(id<GMUCluster>)cluster;
/**
* Called when the user taps on a cluster item marker.
* @return YES if this delegate handled the tap event,
* and NO to pass this tap event to other handlers.
*/
- (BOOL)clusterManager:(GMUClusterManager *)clusterManager
didTapClusterItem:(id<GMUClusterItem>)clusterItem;
@end
/**
* This class groups many items on a map based on zoom level.
* Cluster items should be added to the map via this class.
*/
@interface GMUClusterManager : NSObject <GMSMapViewDelegate>
/**
* The default initializer is not available. Use initWithMap:algorithm:renderer instead.
*/
- (instancetype)init NS_UNAVAILABLE;
/**
* Returns a new instance of the GMUClusterManager class defined by it's |algorithm| and |renderer|.
*/
- (instancetype)initWithMap:(GMSMapView *)mapView
algorithm:(id<GMUClusterAlgorithm>)algorithm
renderer:(id<GMUClusterRenderer>)renderer NS_DESIGNATED_INITIALIZER;
/**
* Returns the clustering algorithm.
*/
@property(nonatomic, readonly) id<GMUClusterAlgorithm> algorithm;
/**
* GMUClusterManager |delegate|.
* To set it use the setDelegate:mapDelegate: method.
*/
@property(nonatomic, readonly, weak, nullable) id<GMUClusterManagerDelegate> delegate;
/**
* The GMSMapViewDelegate delegate that map events are being forwarded to.
* To set it use the setDelegate:mapDelegate: method.
*/
@property(nonatomic, readonly, weak, nullable) id<GMSMapViewDelegate> mapDelegate;
/**
* Sets GMUClusterManagerDelegate |delegate| and optionally
* provides a |mapDelegate| to listen to forwarded map events.
*
* NOTES: This method changes the |delegate| property of the
* managed |mapView| to this object, intercepting events that
* the GMUClusterManager wants to action or rebroadcast
* to the GMUClusterManagerDelegate. Any remaining events are
* then forwarded to the new |mapDelegate| provided here.
*
* EXAMPLE: [clusterManager setDelegate:self mapDelegate:_map.delegate];
* In this example self will receive type-safe GMUClusterManagerDelegate
* events and other map events will be forwarded to the current map delegate.
*/
- (void)setDelegate:(id<GMUClusterManagerDelegate> _Nullable)delegate
mapDelegate:(id<GMSMapViewDelegate> _Nullable)mapDelegate;
/**
* Adds a cluster item to the collection.
*/
- (void)addItem:(id<GMUClusterItem>)item;
/**
* Adds multiple cluster items to the collection.
*/
- (void)addItems:(NSArray<id<GMUClusterItem>> *)items;
/**
* Removes a cluster item from the collection.
*/
- (void)removeItem:(id<GMUClusterItem>)item;
/**
* Removes all items from the collection.
*/
- (void)clearItems;
/**
* Called to arrange items into groups.
* - This method will be automatically invoked when the map's zoom level changes.
* - Manually invoke this method when new items have been added to rearrange items.
*/
- (void)cluster;
@end
NS_ASSUME_NONNULL_END
/* Copyright (c) 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "GMUClusterManager+Testing.h"
#import "GMUClusterRenderer.h"
#import "GMUSimpleClusterAlgorithm.h"
static NSString *const kGMUCameraKeyPath = @"camera";
// How long to wait for a cluster request before actually performing the clustering operation
// to avoid continuous clustering when the camera is moving which can affect performance.
static const double kGMUClusterWaitIntervalSeconds = 0.2;
@implementation GMUClusterManager {
// The map view that this object is associated with.
GMSMapView *_mapView;
// Position of the camera on the previous cluster invocation.
GMSCameraPosition *_previousCamera;
// Tracks number of cluster requests so that we can safely ignore stale (redundant) ones.
NSUInteger _clusterRequestCount;
// Renderer.
id<GMUClusterRenderer> _renderer;
}
- (instancetype)initWithMap:(GMSMapView *)mapView
algorithm:(id<GMUClusterAlgorithm>)algorithm
renderer:(id<GMUClusterRenderer>)renderer {
if ((self = [super init])) {
_algorithm = [[GMUSimpleClusterAlgorithm alloc] init];
_mapView = mapView;
_previousCamera = _mapView.camera;
_algorithm = algorithm;
_renderer = renderer;
[_mapView addObserver:self
forKeyPath:kGMUCameraKeyPath
options:NSKeyValueObservingOptionNew
context:nil];
}
return self;
}
- (void)dealloc {
[_mapView removeObserver:self forKeyPath:kGMUCameraKeyPath];
}
- (void)setDelegate:(id<GMUClusterManagerDelegate>)delegate
mapDelegate:(id<GMSMapViewDelegate> _Nullable)mapDelegate {
_delegate = delegate;
_mapView.delegate = self;
_mapDelegate = mapDelegate;
}
- (void)addItem:(id<GMUClusterItem>)item {
[_algorithm addItems:[[NSMutableArray alloc] initWithObjects:item, nil]];
}
- (void)addItems:(NSArray<id<GMUClusterItem>> *)items {
[_algorithm addItems:items];
}
- (void)removeItem:(id<GMUClusterItem>)item {
[_algorithm removeItem:item];
}
- (void)clearItems {
[_algorithm clearItems];
[self requestCluster];
}
- (void)cluster {
NSUInteger integralZoom = (NSUInteger)floorf(_mapView.camera.zoom + 0.5f);
NSArray<id<GMUCluster>> *clusters = [_algorithm clustersAtZoom:integralZoom];
[_renderer renderClusters:clusters];
_previousCamera = _mapView.camera;
}
#pragma mark GMSMapViewDelegate
- (BOOL)mapView:(GMSMapView *)mapView didTapMarker:(GMSMarker *)marker {
if ([_delegate respondsToSelector:@selector(clusterManager:didTapCluster:)] &&
[marker.userData conformsToProtocol:@protocol(GMUCluster)]) {
id<GMUCluster> cluster = marker.userData;
if ([_delegate clusterManager:self didTapCluster:cluster]) {
return YES;
}
}
if ([_delegate respondsToSelector:@selector(clusterManager:didTapClusterItem:)] &&
[marker.userData conformsToProtocol:@protocol(GMUClusterItem)]) {
id<GMUClusterItem> clusterItem = marker.userData;
if ([_delegate clusterManager:self didTapClusterItem:clusterItem]) {
return YES;
}
}
// Forward to _mapDelegate as a fallback.
if ([_mapDelegate respondsToSelector:@selector(mapView:didTapMarker:)]) {
return [_mapDelegate mapView:mapView didTapMarker:marker];
}
return NO;
}
#pragma mark Delegate Forwards
- (void)mapView:(GMSMapView *)mapView willMove:(BOOL)gesture {
if ([_mapDelegate respondsToSelector:@selector(mapView:willMove:)]) {
[_mapDelegate mapView:mapView willMove:gesture];
}
}
- (void)mapView:(GMSMapView *)mapView didChangeCameraPosition:(GMSCameraPosition *)position {
if ([_mapDelegate respondsToSelector:@selector(mapView:didChangeCameraPosition:)]) {
[_mapDelegate mapView:mapView didChangeCameraPosition:position];
}
}
- (void)mapView:(GMSMapView *)mapView idleAtCameraPosition:(GMSCameraPosition *)position {
if ([_mapDelegate respondsToSelector:@selector(mapView:idleAtCameraPosition:)]) {
[_mapDelegate mapView:mapView idleAtCameraPosition:position];
}
}
- (void)mapView:(GMSMapView *)mapView didTapAtCoordinate:(CLLocationCoordinate2D)coordinate {
if ([_mapDelegate respondsToSelector:@selector(mapView:didTapAtCoordinate:)]) {
[_mapDelegate mapView:mapView didTapAtCoordinate:coordinate];
}
}
- (void)mapView:(GMSMapView *)mapView didLongPressAtCoordinate:(CLLocationCoordinate2D)coordinate {
if ([_mapDelegate respondsToSelector:@selector(mapView:didLongPressAtCoordinate:)]) {
[_mapDelegate mapView:mapView didLongPressAtCoordinate:coordinate];
}
}
- (void)mapView:(GMSMapView *)mapView didTapInfoWindowOfMarker:(GMSMarker *)marker {
if ([_mapDelegate respondsToSelector:@selector(mapView:didTapInfoWindowOfMarker:)]) {
[_mapDelegate mapView:mapView didTapInfoWindowOfMarker:marker];
}
}
- (void)mapView:(GMSMapView *)mapView didLongPressInfoWindowOfMarker:(GMSMarker *)marker {
if ([_mapDelegate respondsToSelector:@selector(mapView:didLongPressInfoWindowOfMarker:)]) {
[_mapDelegate mapView:mapView didLongPressInfoWindowOfMarker:marker];
}
}
- (void)mapView:(GMSMapView *)mapView didTapOverlay:(GMSOverlay *)overlay {
if ([_mapDelegate respondsToSelector:@selector(mapView:didTapOverlay:)]) {
[_mapDelegate mapView:mapView didTapOverlay:overlay];
}
}
- (UIView *)mapView:(GMSMapView *)mapView markerInfoWindow:(GMSMarker *)marker {
if ([_mapDelegate respondsToSelector:@selector(mapView:markerInfoWindow:)]) {
return [_mapDelegate mapView:mapView markerInfoWindow:marker];
}
return nil;
}
- (void)mapView:(GMSMapView *)mapView
didTapPOIWithPlaceID:(NSString *)placeID
name:(NSString *)name
location:(CLLocationCoordinate2D)location {
if ([_mapDelegate respondsToSelector:@selector(mapView:didTapPOIWithPlaceID:name:location:)]) {
[_mapDelegate mapView:mapView didTapPOIWithPlaceID:placeID name:name location:location];
}
}
- (UIView *)mapView:(GMSMapView *)mapView markerInfoContents:(GMSMarker *)marker {
if ([_mapDelegate respondsToSelector:@selector(mapView:markerInfoContents:)]) {
return [_mapDelegate mapView:mapView markerInfoContents:marker];
}
return nil;
}
- (void)mapView:(GMSMapView *)mapView didCloseInfoWindowOfMarker:(GMSMarker *)marker {
if ([_mapDelegate respondsToSelector:@selector(mapView:didCloseInfoWindowOfMarker:)]) {
[_mapDelegate mapView:mapView didCloseInfoWindowOfMarker:marker];
}
}
- (void)mapView:(GMSMapView *)mapView didBeginDraggingMarker:(GMSMarker *)marker {
if ([_mapDelegate respondsToSelector:@selector(mapView:didBeginDraggingMarker:)]) {
[_mapDelegate mapView:mapView didBeginDraggingMarker:marker];
}
}
- (void)mapView:(GMSMapView *)mapView didEndDraggingMarker:(GMSMarker *)marker {
if ([_mapDelegate respondsToSelector:@selector(mapView:didEndDraggingMarker:)]) {
[_mapDelegate mapView:mapView didEndDraggingMarker:marker];
}
}
- (void)mapView:(GMSMapView *)mapView didDragMarker:(GMSMarker *)marker {
if ([_mapDelegate respondsToSelector:@selector(mapView:didDragMarker:)]) {
[_mapDelegate mapView:mapView didDragMarker:marker];
}
}
- (BOOL)didTapMyLocationButtonForMapView:(GMSMapView *)mapView {
if ([_mapDelegate respondsToSelector:@selector(didTapMyLocationButtonForMapView:)]) {
return [_mapDelegate didTapMyLocationButtonForMapView:mapView];
}
return NO;
}
- (void)mapViewDidStartTileRendering:(GMSMapView *)mapView {
if ([_mapDelegate respondsToSelector:@selector(mapViewDidStartTileRendering:)]) {
[_mapDelegate mapViewDidStartTileRendering:mapView];
}
}
- (void)mapViewDidFinishTileRendering:(GMSMapView *)mapView {
if ([_mapDelegate respondsToSelector:@selector(mapViewDidFinishTileRendering:)]) {
[_mapDelegate mapViewDidFinishTileRendering:mapView];
}
}
#pragma mark Testing
- (NSUInteger)clusterRequestCount {
return _clusterRequestCount;
}
#pragma mark Private
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSString *, id> *)change
context:(void *)context {
GMSCameraPosition *camera = _mapView.camera;
NSUInteger previousIntegralZoom = (NSUInteger)floorf(_previousCamera.zoom + 0.5f);
NSUInteger currentIntegralZoom = (NSUInteger)floorf(camera.zoom + 0.5f);
if (previousIntegralZoom != currentIntegralZoom) {
[self requestCluster];
} else {
[_renderer update];
}
}
- (void)requestCluster {
__weak GMUClusterManager *weakSelf = self;
++_clusterRequestCount;
NSUInteger requestNumber = _clusterRequestCount;
dispatch_after(
dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kGMUClusterWaitIntervalSeconds * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
GMUClusterManager *strongSelf = weakSelf;
if (strongSelf == nil) {
return;
}
// Ignore if there are newer requests.
if (requestNumber != strongSelf->_clusterRequestCount) {
return;
}
[strongSelf cluster];
});
}
@end
/* Copyright (c) 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
#import "GMUCluster.h"
NS_ASSUME_NONNULL_BEGIN
/**
* Defines a cluster where its position is fixed upon construction.
*/
@interface GMUStaticCluster : NSObject <GMUCluster>
/**
* The default initializer is not available. Use initWithPosition: instead.
*/
- (instancetype)init NS_UNAVAILABLE;
/**
* Returns a new instance of the GMUStaticCluster class defined by it's position.
*/
- (instancetype)initWithPosition:(CLLocationCoordinate2D)position NS_DESIGNATED_INITIALIZER;
/**
* Returns the position of the cluster.
*/
@property(nonatomic, readonly) CLLocationCoordinate2D position;
/**
* Returns the number of items in the cluster.
*/
@property(nonatomic, readonly) NSUInteger count;
/**
* Returns a copy of the list of items in the cluster.
*/
@property(nonatomic, readonly) NSArray<id<GMUClusterItem>> *items;
/**
* Adds an item to the cluster.
*/
- (void)addItem:(id<GMUClusterItem>)item;
/**
* Removes an item to the cluster.
*/
- (void)removeItem:(id<GMUClusterItem>)item;
@end
NS_ASSUME_NONNULL_END
/* Copyright (c) 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
#import "GMUStaticCluster.h"
@implementation GMUStaticCluster {
NSMutableArray<id<GMUClusterItem>> *_items;
}
- (instancetype)initWithPosition:(CLLocationCoordinate2D)position {
if ((self = [super init])) {
_items = [[NSMutableArray alloc] init];
_position = position;
}
return self;
}
- (NSUInteger)count {
return _items.count;
}
- (NSArray<id<GMUClusterItem>> *)items {
return [_items copy];
}
- (void)addItem:(id<GMUClusterItem>)item {
[_items addObject:item];
}
- (void)removeItem:(id<GMUClusterItem>)item {
[_items removeObject:item];
}
@end
/* Copyright (c) 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
/**
* Defines a contract for cluster icon generation.
*/
@protocol GMUClusterIconGenerator<NSObject>
/**
* Generates an icon with the given size.
*/
- (UIImage *)iconForSize:(NSUInteger)size;
@end
/* Copyright (c) 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
#import "GMUCluster.h"
NS_ASSUME_NONNULL_BEGIN
/**
* Defines a common contract for a cluster renderer.
*/
@protocol GMUClusterRenderer<NSObject>
// Renders a list of clusters.
- (void)renderClusters:(NSArray<id<GMUCluster>> *)clusters;
// Notifies renderer that the viewport has changed and renderer needs to update.
// For example new clusters may become visible and need to be shown on map.
- (void)update;
@end
NS_ASSUME_NONNULL_END
/* Copyright (c) 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "GMUDefaultClusterIconGenerator.h"
/* Extensions for testing purposes only. */
@interface GMUDefaultClusterIconGenerator (Testing)
/* Draws |text| on top of an |image| and returns the resultant image. */
- (UIImage *)iconForText:(NSString *)text withBaseImage:(UIImage *)image;
/**
* Draws |text| on top of a circle whose background color is determined by |bucketIndex|
* and returns the resultant image.
*/
- (UIImage *)iconForText:(NSString *)text withBucketIndex:(NSUInteger)bucketIndex;
@end
/* Copyright (c) 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "GMUClusterIconGenerator.h"
NS_ASSUME_NONNULL_BEGIN
/**
* This class places clusters into range-based buckets of size to avoid having too many distinct
* cluster icons. For example a small cluster of 1 to 9 items will have a icon with a text label
* of 1 to 9. Whereas clusters with a size of 100 to 199 items will be placed in the 100+ bucket
* and have the '100+' icon shown.
* This caches already generated icons for performance reasons.
*/
@interface GMUDefaultClusterIconGenerator : NSObject<GMUClusterIconGenerator>
/**
* Initializes the object with default buckets and auto generated background images.
*/
- (instancetype)init;
/**
* Initializes the object with given |buckets| and auto generated background images.
*/
- (instancetype)initWithBuckets:(NSArray<NSNumber *> *)buckets;
/**
* Initializes the class with a list of buckets and the corresponding background images.
* The backgroundImages array should ideally be big enough to hold the cluster label.
* Notes:
* - |buckets| should be strictly increasing. For example: @[@10, @20, @100, @1000].
* - |buckets| and |backgroundImages| must have equal non zero lengths.
*/
- (instancetype)initWithBuckets:(NSArray<NSNumber *> *)buckets
backgroundImages:(NSArray<UIImage *> *)backgroundImages;
/**
* Initializes the class with a list of buckets and the corresponding background colors.
*
* Notes:
* - |buckets| should be strictly increasing. For example: @[@10, @20, @100, @1000].
* - |buckets| and |backgroundColors| must have equal non zero lengths.
*/
- (instancetype)initWithBuckets:(NSArray<NSNumber *> *)buckets
backgroundColors:(NSArray<UIColor *> *)backgroundColors;
/**
* Generates an icon with the given size.
*/
- (UIImage *)iconForSize:(NSUInteger)size;
@end
NS_ASSUME_NONNULL_END
/* Copyright (c) 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
#import "GMUDefaultClusterIconGenerator+Testing.h"
#define UIColorFromHEX(hexValue) \
[UIColor colorWithRed:((CGFloat)((hexValue & 0xff0000) >> 16)) / 255.0 \
green:((CGFloat)((hexValue & 0x00ff00) >> 8)) / 255.0 \
blue:((CGFloat)((hexValue & 0x0000ff) >> 0)) / 255.0 \
alpha:1.0]
// Default bucket background colors when no background images are set.
static NSArray<UIColor *> *kGMUBucketBackgroundColors;
@implementation GMUDefaultClusterIconGenerator {
NSCache *_iconCache;
NSArray<NSNumber *> *_buckets;
NSArray<UIImage *> *_backgroundImages;
NSArray<UIColor *> *_backgroundColors;
}
+ (void)initialize {
kGMUBucketBackgroundColors = @[
UIColorFromHEX(0x0099cc),
UIColorFromHEX(0x669900),
UIColorFromHEX(0xff8800),
UIColorFromHEX(0xcc0000),
UIColorFromHEX(0x9933cc),
];
}
- (instancetype)init {
if ((self = [super init]) != nil) {
_iconCache = [[NSCache alloc] init];
_buckets = @[ @10, @50, @100, @200, @1000 ];
_backgroundColors = [kGMUBucketBackgroundColors copy];
}
return self;
}
- (instancetype)initWithBuckets:(NSArray<NSNumber *> *)buckets
backgroundImages:(NSArray<UIImage *> *)backgroundImages {
if ((self = [self initWithBuckets:buckets]) != nil) {
if (buckets.count != backgroundImages.count) {
[NSException raise:NSInvalidArgumentException
format:@"buckets' size: %lu is not equal to backgroundImages' size: %lu",
(unsigned long)buckets.count, (unsigned long)backgroundImages.count];
}
_backgroundImages = [backgroundImages copy];
}
return self;
}
- (instancetype)initWithBuckets:(NSArray<NSNumber *> *)buckets
backgroundColors:(NSArray<UIColor *> *)backgroundColors {
if ((self = [self initWithBuckets:buckets]) != nil) {
if (buckets.count != backgroundColors.count) {
[NSException raise:NSInvalidArgumentException
format:@"buckets' size: %lu is not equal to backgroundColors' size: %lu",
(unsigned long) buckets.count, (unsigned long) backgroundColors.count];
}
_backgroundColors = [backgroundColors copy];
}
return self;
}
- (instancetype)initWithBuckets:(NSArray<NSNumber *> *)buckets {
if ((self = [self init]) != nil) {
if (buckets.count == 0) {
[NSException raise:NSInvalidArgumentException format:@"buckets are empty"];
}
for (int i = 0; i < buckets.count; ++i) {
if (buckets[i].longLongValue <= 0) {
[NSException raise:NSInvalidArgumentException
format:@"buckets have non positive values"];
}
}
for (int i = 0; i < buckets.count - 1; ++i) {
if (buckets[i].longLongValue >= buckets[i+1].longLongValue) {
[NSException raise:NSInvalidArgumentException
format:@"buckets are not strictly increasing"];
}
}
_buckets = [buckets copy];
}
return self;
}
- (UIImage *)iconForSize:(NSUInteger)size {
NSUInteger bucketIndex = [self bucketIndexForSize:size];
NSString *text;
// If size is smaller to first bucket size, use the size as is otherwise round it down to the
// nearest bucket to limit the number of cluster icons we need to generate.
if (size < _buckets[0].unsignedLongValue) {
text = [NSString stringWithFormat:@"%ld", (unsigned long)size];
} else {
text = [NSString stringWithFormat:@"%ld+", _buckets[bucketIndex].unsignedLongValue];
}
if (_backgroundImages != nil) {
UIImage *image = _backgroundImages[bucketIndex];
return [self iconForText:text withBaseImage:image];
}
return [self iconForText:text withBucketIndex:bucketIndex];
}
#pragma mark Private
// Finds the smallest bucket which is greater than |size|. If none exists return the last bucket
// index (i.e |_buckets.count - 1|).
- (NSUInteger)bucketIndexForSize:(NSUInteger)size {
NSUInteger index = 0;
while (index + 1 < _buckets.count && _buckets[index + 1].unsignedLongValue <= size) {
++index;
}
return index;
}
- (UIImage *)iconForText:(NSString *)text withBaseImage:(UIImage *)image {
UIImage *icon = [_iconCache objectForKey:text];
if (icon != nil) {
return icon;
}
UIFont *font = [UIFont boldSystemFontOfSize:12];
CGSize size = image.size;
UIGraphicsBeginImageContextWithOptions(size, NO, 0.0f);
[image drawInRect:CGRectMake(0, 0, size.width, size.height)];
CGRect rect = CGRectMake(0, 0, image.size.width, image.size.height);
NSMutableParagraphStyle *paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
paragraphStyle.alignment = NSTextAlignmentCenter;
NSDictionary *attributes = @{
NSFontAttributeName : font,
NSParagraphStyleAttributeName : paragraphStyle,
NSForegroundColorAttributeName : [UIColor whiteColor]
};
CGSize textSize = [text sizeWithAttributes:attributes];
CGRect textRect = CGRectInset(rect, (rect.size.width - textSize.width) / 2,
(rect.size.height - textSize.height) / 2);
[text drawInRect:CGRectIntegral(textRect) withAttributes:attributes];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[_iconCache setObject:newImage forKey:text];
return newImage;
}
- (UIImage *)iconForText:(NSString *)text withBucketIndex:(NSUInteger)bucketIndex {
UIImage *icon = [_iconCache objectForKey:text];
if (icon != nil) {
return icon;
}
UIFont *font = [UIFont boldSystemFontOfSize:14];
NSMutableParagraphStyle *paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
paragraphStyle.alignment = NSTextAlignmentCenter;
NSDictionary *attributes = @{
NSFontAttributeName : font,
NSParagraphStyleAttributeName : paragraphStyle,
NSForegroundColorAttributeName : [UIColor whiteColor]
};
CGSize textSize = [text sizeWithAttributes:attributes];
// Create an image context with a square shape to contain the text (with more padding for
// larger buckets).
CGFloat rectDimension = MAX(20, MAX(textSize.width, textSize.height)) + 3 * bucketIndex + 6;
CGRect rect = CGRectMake(0.f, 0.f, rectDimension, rectDimension);
UIGraphicsBeginImageContext(rect.size);
// Draw background circle.
UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0.0f);
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSaveGState(ctx);
bucketIndex = MIN(bucketIndex, _backgroundColors.count - 1);
UIColor *backColor = _backgroundColors[bucketIndex];
CGContextSetFillColorWithColor(ctx, backColor.CGColor);
CGContextFillEllipseInRect(ctx, rect);
CGContextRestoreGState(ctx);
// Draw text.
[[UIColor whiteColor] set];
CGRect textRect = CGRectInset(rect, (rect.size.width - textSize.width) / 2,
(rect.size.height - textSize.height) / 2);
[text drawInRect:CGRectIntegral(textRect) withAttributes:attributes];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[_iconCache setObject:newImage forKey:text];
return newImage;
}
@end
/* Copyright (c) 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
#import "GMUClusterRenderer.h"
NS_ASSUME_NONNULL_BEGIN
@class GMSMapView;
@class GMSMarker;
@protocol GMUCluster;
@protocol GMUClusterIconGenerator;
@protocol GMUClusterRenderer;
/**
* Delegate for id<GMUClusterRenderer> to provide extra functionality to the default
* renderer.
*/
@protocol GMUClusterRendererDelegate<NSObject>
@optional
/**
* Returns a marker for an |object|. The |object| can be either an id<GMUCluster>
* or an id<GMUClusterItem>. Use this delegate to control of the life cycle of
* the marker. Any properties set on the returned marker will be honoured except
* for: .position, .icon, .groundAnchor, .zIndex and .userData. To customize
* these properties use renderer:willRenderMarker.
* Note that changing a marker's position is not recommended because it will
* interfere with the marker animation.
*/
- (nullable GMSMarker *)renderer:(id<GMUClusterRenderer>)renderer markerForObject:(id)object;
/**
* Raised when a marker (for a cluster or an item) is about to be added to the map.
* Use the marker.userData property to check whether it is a cluster marker or an
* item marker.
*/
- (void)renderer:(id<GMUClusterRenderer>)renderer willRenderMarker:(GMSMarker *)marker;
/**
* Raised when a marker (for a cluster or an item) has just been added to the map
* and animation has been added.
* Use the marker.userData property to check whether it is a cluster marker or an
* item marker.
*/
- (void)renderer:(id<GMUClusterRenderer>)renderer didRenderMarker:(GMSMarker *)marker;
@end
/**
* Default cluster renderer which shows clusters as markers with specialized icons.
* There is logic to decide whether to expand a cluster or not depending on the number of
* items or the zoom level.
* There is also some performance optimization where only clusters within the visisble
* region are shown.
*/
@interface GMUDefaultClusterRenderer : NSObject<GMUClusterRenderer>
/**
* Creates a new cluster renderer with a given map view and icon generator.
*
* @param mapView The map view to use.
* @param iconGenerator The icon generator to use. Can be subclassed if required.
*/
- (instancetype)initWithMapView:(GMSMapView *)mapView
clusterIconGenerator:(id<GMUClusterIconGenerator>)iconGenerator;
/**
* Animates the clusters to achieve splitting (when zooming in) and merging
* (when zooming out) effects:
* - splitting large clusters into smaller ones when zooming in.
* - merging small clusters into bigger ones when zooming out.
*
* NOTES: the position to animate to/from for each cluster is heuristically
* calculated by finding the first overlapping cluster. This means that:
* - when zooming in:
* if a cluster on a higher zoom level is made from multiple clusters on
* a lower zoom level the split will only animate the new cluster from
* one of them.
* - when zooming out:
* if a cluster on a higher zoom level is split into multiple parts to join
* multiple clusters at a lower zoom level, the merge will only animate
* the old cluster into one of them.
* Because of these limitations, the actual cluster sizes may not add up, for
* example people may see 3 clusters of size 3, 4, 5 joining to make up a cluster
* of only 8 for non-hierachical clusters. And vice versa, a cluster of 8 may
* split into 3 clusters of size 3, 4, 5. For hierarchical clusters, the numbers
* should add up however.
*
* Defaults to YES.
*/
@property(nonatomic) BOOL animatesClusters;
/**
* Determines the minimum number of cluster items inside a cluster.
* Clusters smaller than this threshold will be expanded.
*
* Defaults to 4.
*/
@property(nonatomic) NSUInteger minimumClusterSize;
/**
* Sets the maximium zoom level of the map on which the clustering
* should be applied. At zooms above this level, clusters will be expanded.
* This is to prevent cases where items are so close to each other than they
* are always grouped.
*
* Defaults to 20.
*/
@property(nonatomic) NSUInteger maximumClusterZoom;
/**
* Sets the animation duration for marker splitting/merging effects.
* Measured in seconds.
*
* Defaults to 0.5.
*/
@property(nonatomic) double animationDuration;
/**
* Allows setting a zIndex value for the clusters. This becomes useful
* when using multiple cluster data sets on the map and require a predictable
* way of displaying multiple sets with a predictable layering order.
*
* If no specific zIndex is not specified during the initialization, the
* default zIndex is '1'. Larger zIndex values are drawn over lower ones
* similar to the zIndex value of GMSMarkers.
*/
@property(nonatomic) int zIndex;
/** Sets to further customize the renderer. */
@property(nonatomic, nullable, weak) id<GMUClusterRendererDelegate> delegate;
/**
* Returns currently active markers.
*/
@property(nonatomic, readonly) NSArray<GMSMarker *> *markers;
/**
* If returns NO, cluster items will be expanded and rendered as normal markers.
* Subclass can override this method to provide custom logic.
*/
- (BOOL)shouldRenderAsCluster:(id<GMUCluster>)cluster atZoom:(float)zoom;
@end
NS_ASSUME_NONNULL_END
/* Copyright (c) 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@protocol GMUGeometryContainer;
/**
* Instances of this class parse GeoJSON data. The parsed features are stored in NSArray objects
* which can then be passed to a GMUGeometryRenderer to display on a Google Map.
*/
@interface GMUGeoJSONParser : NSObject
/**
* The features parsed from the GeoJSON file.
*/
@property(nonatomic, readonly) NSArray<id<GMUGeometryContainer>> *features;
/**
* Initializes a GMUGeoJSONParser with GeoJSON data contained in a URL.
*
* @param url The url containing GeoJSON data.
*/
- (instancetype)initWithURL:(NSURL *)url;
/**
* Initializes a GMUGeoJSONParser with GeoJSON data.
*
* @param data The GeoJSON data.
*/
- (instancetype)initWithData:(NSData *)data;
/**
* Initializes a GMUGeoJSONParser with GeoJSON data contained in an input stream.
*
* @param stream The stream to use to access GeoJSON data.
*/
- (instancetype)initWithStream:(NSInputStream *)stream;
/**
* Parses the stored GeoJSON data.
*/
- (void)parse;
@end
NS_ASSUME_NONNULL_END
/* Copyright (c) 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "GMUGeometryRenderer.h"
/* Extensions for testing purposes only. */
@interface GMUGeometryRenderer (Testing)
- (NSArray<GMSOverlay *> *)mapOverlays;
@end
/* Copyright (c) 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
#import <GoogleMaps/GoogleMaps.h>
#import "GMUGeometryContainer.h"
#import "GMUStyle.h"
#import "GMUStyleMap.h"
NS_ASSUME_NONNULL_BEGIN
/**
* Instances of this class render geometries generated by a GMUKMLParser or
* GMUGeoJSONParser object. These geometries can have specified style information
* applied to them when being rendered.
*/
@interface GMUGeometryRenderer : NSObject
/**
* Initializes a new renderer.
*
* @param map the Google Map layer to render the geometries onto.
* @param geometries the geometries to be rendered.
*/
- (instancetype)initWithMap:(GMSMapView *)map
geometries:(NSArray<id<GMUGeometryContainer>> *)geometries;
/**
* Initializes a new renderer.
*
* @param map the Google Map layer to render the geometries onto.
* @param geometries the geometries to be rendered.
* @param styles the styles to be applied to the geometries.
*/
- (instancetype)initWithMap:(GMSMapView *)map
geometries:(NSArray<id<GMUGeometryContainer>> *)geometries
styles:(NSArray<GMUStyle *> *_Nullable)styles;
/**
* Initializes a new renderer.
*
* @param map the Google Map layer to render the geometries onto.
* @param geometries the geometries to be rendered.
* @param styles the styles to be applied to the geometries.
* @param styleMaps the styleMaps to be applied to the geometries
*/
- (instancetype)initWithMap:(GMSMapView *)map
geometries:(NSArray<id<GMUGeometryContainer>> *)geometries
styles:(NSArray<GMUStyle *> * _Nullable)styles
styleMaps:(NSArray<GMUStyleMap *> *_Nullable)styleMaps;
/**
* Renders the geometries onto the Google Map.
*/
- (void)render;
/**
* Removes the rendered geometries from the Google Map. Markup that was not added by the renderer is
* preserved.
*/
- (void)clear;
@end
NS_ASSUME_NONNULL_END
/* Copyright (c) 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <CoreLocation/CoreLocation.h>
#import <Foundation/Foundation.h>
#import <GoogleMaps/GoogleMaps.h>
#import "GMUStyleMap.h"
NS_ASSUME_NONNULL_BEGIN
@class GMUStyle;
@protocol GMUGeometryContainer;
/**
* Instances of this class parse KML documents in an event-driven manner. The
* parsed placemarks and styles are stored in NSArray objects which can then be
* passed to a GMUGeometryRenderer to display on a Google Map.
*/
@interface GMUKMLParser : NSObject
/**
* The placemarks parsed from the KML file.
*/
@property(nonatomic, readonly) NSArray<id<GMUGeometryContainer>> *placemarks;
/**
* The styles parsed from the KML file.
*/
@property(nonatomic, readonly) NSArray<GMUStyle *> *styles;
@property(nonatomic, readonly) NSArray<GMUStyleMap *> *styleMaps;
/**
* Parses the stored KML document.
*/
- (void)parse;
/**
* Initializes a KMLParser with a KML file contained in a URL.
*
* @param url The url containing the KML file.
*/
- (instancetype)initWithURL:(NSURL *)url;
/**
* Initializes a KMLParser with a KML file contained in a data file.
*
* @param data The data file containing the contents of a KML file.
*/
- (instancetype)initWithData:(NSData *)data;
/**
* Initializes a KMLParser with a KML file contained in an input stream.
*
* @param stream The stream to use to access the KML file.
*/
- (instancetype)initWithStream:(NSInputStream *)stream;
@end
NS_ASSUME_NONNULL_END
/* Copyright (c) 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
#import <GoogleMaps/GoogleMaps.h>
#import "GMUGeometryContainer.h"
NS_ASSUME_NONNULL_BEGIN
/**
* Instances of this class represent a GeoJSON Feature object.
*/
@interface GMUFeature : NSObject<GMUGeometryContainer>
/**
* The identifier of the feature.
*/
@property(nonatomic, nullable, readonly) NSString *identifier;
/**
* The properties of the geometry in the feature.
*/
@property(nonatomic, nullable, readonly) NSDictionary<NSString *, NSObject *> *properties;
/**
* The bounding box of the geometry in the feature.
*/
@property(nonatomic, nullable, readonly) GMSCoordinateBounds *boundingBox;
/**
*
* @param geometry The geometry object in the feature.
* @param identifier The identifier of the feature.
* @param properties The properties of the geometry in the feature.
* @param boundingBox The bounding box of the geometry in the feature.
*/
- (instancetype)initWithGeometry:(id<GMUGeometry>)geometry
identifier:(NSString * _Nullable)identifier
properties:(NSDictionary<NSString *, NSObject *> * _Nullable)properties
boundingBox:(GMSCoordinateBounds * _Nullable)boundingBox;
@end
NS_ASSUME_NONNULL_END
/* Copyright (c) 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "GMUFeature.h"
@implementation GMUFeature
@synthesize geometry = _geometry;
@synthesize style = _style;
- (instancetype)initWithGeometry:(id<GMUGeometry>)geometry
identifier:(NSString * _Nullable)identifier
properties:(NSDictionary<NSString *, NSObject *> * _Nullable)properties
boundingBox:(GMSCoordinateBounds * _Nullable)boundingBox {
if (self = [super init]) {
_geometry = geometry;
_identifier = identifier;
_boundingBox = boundingBox;
_properties = properties ?: [[[NSDictionary alloc] init] copy];
}
return self;
}
@end
/* Copyright (c) 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
* Defines a generic geometry object.
*/
@protocol GMUGeometry<NSObject>
/**
* The type of the geometry.
*/
@property(nonatomic, readonly) NSString *type;
@end
NS_ASSUME_NONNULL_END
/* Copyright (c) 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
#import "GMUGeometry.h"
NS_ASSUME_NONNULL_BEGIN
/**
* Instances of this class represent a GeometryCollection object.
*/
@interface GMUGeometryCollection : NSObject<GMUGeometry>
/**
* The array of geometry objects for the GeometryCollection.
*/
@property(nonatomic, readonly) NSArray<id<GMUGeometry>> *geometries;
/**
* Initializes a GMUGeometryCollection object with a set of geometry objects.
*
* @param geometries The array of geometry objects.
*/
- (instancetype)initWithGeometries:(NSArray<id<GMUGeometry>> *)geometries;
@end
NS_ASSUME_NONNULL_END
/* Copyright (c) 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "GMUGeometryCollection.h"
@implementation GMUGeometryCollection
@synthesize type = _type;
- (instancetype)initWithGeometries:(NSArray<id<GMUGeometry>> *)geometries {
if (self = [super init]) {
_type = @"GeometryCollection";
_geometries = geometries;
}
return self;
}
@end
/* Copyright (c) 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
#import "GMUGeometry.h"
#import "GMUStyle.h"
NS_ASSUME_NONNULL_BEGIN
/**
* Defines a generic geometry container.
*/
@protocol GMUGeometryContainer<NSObject>
/**
* The geometry object in the container.
*/
@property(nonatomic, readonly) id<GMUGeometry> geometry;
/**
* Style information that should be applied to the contained geometry object.
*/
@property(nonatomic, nullable) GMUStyle *style;
@end
NS_ASSUME_NONNULL_END
/* Copyright (c) 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <CoreLocation/CoreLocation.h>
#import <Foundation/Foundation.h>
#import "GMUGeometry.h"
NS_ASSUME_NONNULL_BEGIN
/**
* Instances of this class represent a Ground Overlay object.
*/
@interface GMUGroundOverlay : NSObject<GMUGeometry>
/**
* The North-East corner of the overlay.
*/
@property(nonatomic, readonly) CLLocationCoordinate2D northEast;
/**
* The South-West corner of the overlay.
*/
@property(nonatomic, readonly) CLLocationCoordinate2D southWest;
/**
* The Z-Index of the overlay.
*/
@property(nonatomic, readonly) int zIndex;
/**
* The rotation of the overlay on the map.
*/
@property(nonatomic, readonly) double rotation;
/**
* The image to be rendered on the overlay.
*/
@property(nonatomic, readonly) NSString *href;
/**
* Initializes a GMUGroundOverlay object.
*
* @param northEast The North-East corner of the overlay.
* @param southWest The South-West corner of the overlay.
* @param zIndex The Z-Index of the overlay.
* @param rotation The rotation of the overlay.
* @param href The image to be rendered on the overlay.
*/
- (instancetype)initWithCoordinate:(CLLocationCoordinate2D)northEast
southWest:(CLLocationCoordinate2D)southWest
zIndex:(int)zIndex
rotation:(double)rotation
href:(NSString *)href;
@end
NS_ASSUME_NONNULL_END
/* Copyright (c) 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "GMUGroundOverlay.h"
@implementation GMUGroundOverlay
@synthesize type = _type;
- (instancetype)initWithCoordinate:(CLLocationCoordinate2D)northEast
southWest:(CLLocationCoordinate2D)southWest
zIndex:(int)zIndex
rotation:(double)rotation
href:(NSString *)href {
if (self = [super init]) {
_type = @"GroundOverlay";
_northEast = northEast;
_southWest = southWest;
_zIndex = zIndex;
_rotation = rotation;
_href = href;
}
return self;
}
@end
/* Copyright (c) 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <CoreLocation/CoreLocation.h>
#import <Foundation/Foundation.h>
#import <GoogleMaps/GoogleMaps.h>
#import "GMUGeometry.h"
NS_ASSUME_NONNULL_BEGIN
/**
* Instances of this class represent a LineString object.
*/
@interface GMULineString : NSObject<GMUGeometry>
/**
* The path of the LineString.
*/
@property(nonatomic, readonly) GMSPath *path;
/**
* Initializes a GMULineString object with a path.
*
* @param path The path of the LineString.
*/
- (instancetype)initWithPath:(GMSPath *)path;
@end
NS_ASSUME_NONNULL_END
/* Copyright (c) 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "GMULineString.h"
@implementation GMULineString
@synthesize type = _type;
- (instancetype)initWithPath:(GMSPath *)path {
if (self = [super init]) {
_type = @"LineString";
_path = path;
}
return self;
}
@end
/* Copyright (c) 2018 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
* Instances of this class represent a geometry Style. It is used to define the
* stylings of any number of GMUGeometry objects.
*/
@interface GMUPair : NSObject
@property(nonatomic, readonly) NSString *key;
@property(nonatomic, readonly) NSString *styleUrl;
- (instancetype)initWithKey:(NSString *)styleID
styleUrl:(NSString *)strokeColor;
@end
NS_ASSUME_NONNULL_END
/* Copyright (c) 2018 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "GMUPair.h"
@implementation GMUPair
@synthesize key = _key;
@synthesize styleUrl = _styleUrl;
- (instancetype)initWithKey:(NSString *)key
styleUrl:(NSString *)styleUrl {
if (self = [super init]) {
_key = key;
_styleUrl = styleUrl;
}
return self;
}
@end
/* Copyright (c) 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
#import "GMUGeometryContainer.h"
NS_ASSUME_NONNULL_BEGIN
/**
* Represents a placemark which is either a Point, LineString, Polygon, or MultiGeometry. Contains
* the properties and styles of the place.
*/
@interface GMUPlacemark : NSObject<GMUGeometryContainer>
/**
* The name element of the placemark.
*/
@property(nonatomic, nullable, readonly) NSString *title;
/**
* The description element of the placemark.
*/
@property(nonatomic, nullable, readonly) NSString *snippet;
/**
* The StyleUrl element of the placemark; used to reference a style defined in the file.
*/
@property(nonatomic, nullable, readonly) NSString *styleUrl;
/**
* Initializes a new KMLPlacemark object.
*
* @param geometry The geometry of the placemark.
* @param title The title of the placemark.
* @param snippet The snippet text of the placemark.
* @param style The inline style of the placemark.
* @param styleUrl The url to the style of the placemark.
*/
- (instancetype)initWithGeometry:(id<GMUGeometry> _Nullable)geometry
title:(NSString *_Nullable)title
snippet:(NSString *_Nullable)snippet
style:(GMUStyle *_Nullable)style
styleUrl:(NSString *_Nullable)styleUrl;
@end
NS_ASSUME_NONNULL_END
/* Copyright (c) 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "GMUPlacemark.h"
@implementation GMUPlacemark
@synthesize geometry = _geometry;
@synthesize style = _style;
- (instancetype)initWithGeometry:(id<GMUGeometry>)geometry
title:(NSString *)title
snippet:(NSString *)snippet
style:(GMUStyle *)style
styleUrl:(NSString *)styleUrl {
if (self = [super init]) {
_geometry = geometry;
_title = [title copy];
_snippet = [snippet copy];
_style = style;
_styleUrl = [styleUrl copy];
}
return self;
}
@end
/* Copyright (c) 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <CoreLocation/CoreLocation.h>
#import <Foundation/Foundation.h>
#import "GMUGeometry.h"
NS_ASSUME_NONNULL_BEGIN
/**
* Instances of this class represent a Point object.
*/
@interface GMUPoint : NSObject<GMUGeometry>
/**
* The 2D coordinate of the Point, containing a latitude and longitude.
*/
@property(nonatomic, readonly) CLLocationCoordinate2D coordinate;
/**
* Initializes a GMUPoint object with a coordinate.
*
* @param coordinate A location with a latitude and longitude.
*/
- (instancetype)initWithCoordinate:(CLLocationCoordinate2D)coordinate;
@end
NS_ASSUME_NONNULL_END
/* Copyright (c) 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "GMUPoint.h"
@implementation GMUPoint
@synthesize type = _type;
- (instancetype)initWithCoordinate:(CLLocationCoordinate2D)coordinate {
if (self = [super init]) {
_type = @"Point";
_coordinate = coordinate;
}
return self;
}
@end
/* Copyright (c) 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <CoreLocation/CoreLocation.h>
#import <Foundation/Foundation.h>
#import <GoogleMaps/GoogleMaps.h>
#import "GMUGeometry.h"
NS_ASSUME_NONNULL_BEGIN
/**
* Instances of this class represent a Polygon object.
*/
@interface GMUPolygon : NSObject<GMUGeometry>
/**
* The array of LinearRing paths for the Polygon. The first is the exterior ring of the Polygon; any
* subsequent rings are holes.
*/
@property(nonatomic, readonly) NSArray<GMSPath *> *paths;
/**
* Initializes a GMUGeoJSONPolygon object with a set of paths.
*
* @param paths The paths of the Polygon.
*/
- (instancetype)initWithPaths:(NSArray<GMSPath *> *)paths;
@end
NS_ASSUME_NONNULL_END
/* Copyright (c) 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "GMUPolygon.h"
@implementation GMUPolygon
@synthesize type = _type;
- (instancetype)initWithPaths:(NSArray<GMSPath *> *)paths {
if (self = [super init]) {
_type = @"Polygon";
_paths = [paths copy];
}
return self;
}
@end
/* Copyright (c) 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
#import <UIKit/UIColor.h>
NS_ASSUME_NONNULL_BEGIN
/**
* Instances of this class represent a geometry Style. It is used to define the
* stylings of any number of GMUGeometry objects.
*/
@interface GMUStyle : NSObject
/**
* The unique identifier of the style
*/
@property(nonatomic, readonly) NSString *styleID;
/**
* The color for the stroke of a LineString or Polygon.
*/
@property(nonatomic, nullable, readonly) UIColor *strokeColor;
/**
* The color for the fill of a Polygon.
*/
@property(nonatomic, nullable, readonly) UIColor *fillColor;
/**
* The width of a LineString
*/
@property(nonatomic, readonly) CGFloat width;
/**
* The scale that a Point's icon should be rendered at.
*/
@property(nonatomic, readonly) CGFloat scale;
/**
* The direction, in degrees, that a Point's icon should be rendered at.
*/
@property(nonatomic, readonly) CGFloat heading;
/**
* The position within an icon that is anchored to the Point.
*/
@property(nonatomic, readonly) CGPoint anchor;
/**
* The href for the icon to be used for a Point.
*/
@property(nonatomic, nullable, readonly) NSString *iconUrl;
/**
* The title to use for a Point.
*/
@property(nonatomic, nullable, readonly) NSString *title;
/**
* Whether the Polygon has a defined fill color.
*/
@property(nonatomic, readonly) BOOL hasFill;
/**
* Whether the LineString or Polygon has a defined stroke color.
*/
@property(nonatomic, readonly) BOOL hasStroke;
/**
* Initalizer that defines the style's identifier.
*
* @param styleID The unique identifier string for the style.
* @param strokeColor The color of the geometry stroke.
* @param fillColor The color of the geometry fill.
* @param width The width of the geometry stroke.
* @param scale The scale at which point icons will be rendered
* @param heading The heading of the point icon.
* @param anchor The anchor coordinate of the point icon.
* @param iconUrl The reference url to the point icon image.
* @param title The title of the point.
* @param hasFill Whether the geometry should be filled.
* @param hasStroke Whether the geometry has a stroke.
*/
- (instancetype)initWithStyleID:(NSString *)styleID
strokeColor:(UIColor *_Nullable)strokeColor
fillColor:(UIColor *_Nullable)fillColor
width:(CGFloat)width
scale:(CGFloat)scale
heading:(CGFloat)heading
anchor:(CGPoint)anchor
iconUrl:(NSString *_Nullable)iconUrl
title:(NSString *_Nullable)title
hasFill:(BOOL)hasFill
hasStroke:(BOOL)hasStroke;
@end
NS_ASSUME_NONNULL_END
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment