diff --git b/Podfile a/Podfile new file mode 100644 index 0000000..3324ade --- /dev/null +++ a/Podfile @@ -0,0 +1,16 @@ +# Uncomment the next line to define a global platform for your project +# platform :ios, '9.0' + + # Comment the next line if you don't want to use dynamic frameworks + use_frameworks! + + # Pods for ayudapy + source 'https://github.com/CocoaPods/Specs.git' + target 'ayudapy' do + pod 'GoogleMaps' + pod 'GooglePlaces' + pod 'Alamofire', '~> 5.0' + pod "Sniffer", '~> 2.0' + pod 'Kingfisher', '~> 5.0' + pod 'SwiftyJSON' +end diff --git b/Podfile.lock a/Podfile.lock new file mode 100644 index 0000000..e326977 --- /dev/null +++ a/Podfile.lock @@ -0,0 +1,43 @@ +PODS: + - Alamofire (5.1.0) + - 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) + - Kingfisher (5.13.4): + - Kingfisher/Core (= 5.13.4) + - Kingfisher/Core (5.13.4) + - Sniffer (2.0.0) + - SwiftyJSON (5.0.0) + +DEPENDENCIES: + - Alamofire (~> 5.0) + - GoogleMaps + - GooglePlaces + - Kingfisher (~> 5.0) + - Sniffer (~> 2.0) + - SwiftyJSON + +SPEC REPOS: + https://github.com/CocoaPods/Specs.git: + - Alamofire + - GoogleMaps + - GooglePlaces + - Kingfisher + - Sniffer + - SwiftyJSON + +SPEC CHECKSUMS: + Alamofire: 9d5c5f602928e512395b30950c5984eca840093c + GoogleMaps: 7c8d66d70e4e8c300f43a7219d8fdaad7b325a9a + GooglePlaces: d5f70c3e9e427964fdeca1301a665d276ccd8754 + Kingfisher: d2279a7abece3c7f25a80cd2b7f363ca5cf3f44c + Sniffer: 8818aff78371938472e70121eff907f5b0928049 + SwiftyJSON: 36413e04c44ee145039d332b4f4e2d3e8d6c4db7 + +PODFILE CHECKSUM: fd91a353c468991f2507662fbc21f9faeaba13db + +COCOAPODS: 1.9.1 diff --git b/Pods/Alamofire/LICENSE a/Pods/Alamofire/LICENSE new file mode 100644 index 0000000..ccafad5 --- /dev/null +++ a/Pods/Alamofire/LICENSE @@ -0,0 +1,19 @@ +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. diff --git b/Pods/Alamofire/README.md a/Pods/Alamofire/README.md new file mode 100644 index 0000000..0d730d1 --- /dev/null +++ a/Pods/Alamofire/README.md @@ -0,0 +1,205 @@ +![Alamofire: Elegant Networking in Swift](https://raw.githubusercontent.com/Alamofire/Alamofire/master/alamofire.png) + +[![Build Status](https://github.com/Alamofire/Alamofire/workflows/Alamofire%20CI/badge.svg?branch=master)](https://github.com/Alamofire/Alamofire/actions) +[![CocoaPods Compatible](https://img.shields.io/cocoapods/v/Alamofire.svg)](https://img.shields.io/cocoapods/v/Alamofire.svg) +[![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) +[![Platform](https://img.shields.io/cocoapods/p/Alamofire.svg?style=flat)](https://alamofire.github.io/Alamofire) +[![Twitter](https://img.shields.io/badge/twitter-@AlamofireSF-blue.svg?style=flat)](https://twitter.com/AlamofireSF) +[![Gitter](https://badges.gitter.im/Alamofire/Alamofire.svg)](https://gitter.im/Alamofire/Alamofire?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) +[![Open Source Helpers](https://www.codetriage.com/alamofire/alamofire/badges/users.svg)](https://www.codetriage.com/alamofire/alamofire) + +Alamofire is an HTTP networking library written in Swift. + +- [Features](#features) +- [Component Libraries](#component-libraries) +- [Requirements](#requirements) +- [Migration Guides](#migration-guides) +- [Communication](#communication) +- [Installation](#installation) +- [Usage](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#using-alamofire) + - [**Introduction -**](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#introduction) [Making Requests](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#making-requests), [Response Handling](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#response-handling), [Response Validation](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#response-validation), [Response Caching](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#response-caching) + - **HTTP -** [HTTP Methods](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#http-methods), [Parameters and Parameter Encoder](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md##request-parameters-and-parameter-encoders), [HTTP Headers](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#http-headers), [Authentication](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#authentication) + - **Large Data -** [Downloading Data to a File](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#downloading-data-to-a-file), [Uploading Data to a Server](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#uploading-data-to-a-server) + - **Tools -** [Statistical Metrics](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#statistical-metrics), [cURL Command Output](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#curl-command-output) +- [Advanced Usage](https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md) + - **URL Session -** [Session Manager](https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#session), [Session Delegate](https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#sessiondelegate), [Request](https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#request) + - **Routing -** [Routing Requests](https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#routing-requests), [Adapting and Retrying Requests](https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#adapting-and-retrying-requests) + - **Model Objects -** [Custom Response Serialization](https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#custom-response-serialization) + - **Connection -** [Security](https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#security), [Network Reachability](https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#network-reachability) +- [Open Radars](#open-radars) +- [FAQ](#faq) +- [Credits](#credits) +- [Donations](#donations) +- [License](#license) + +## Features + +- [x] Chainable Request / Response Methods +- [x] URL / JSON Parameter Encoding +- [x] Upload File / Data / Stream / MultipartFormData +- [x] Download File using Request or Resume Data +- [x] Authentication with URLCredential +- [x] HTTP Response Validation +- [x] Upload and Download Progress Closures with Progress +- [x] cURL Command Output +- [x] Dynamically Adapt and Retry Requests +- [x] TLS Certificate and Public Key Pinning +- [x] Network Reachability +- [x] Comprehensive Unit and Integration Test Coverage +- [x] [Complete Documentation](https://alamofire.github.io/Alamofire) + +## Component Libraries + +In order to keep Alamofire focused specifically on core networking implementations, additional component libraries have been created by the [Alamofire Software Foundation](https://github.com/Alamofire/Foundation) to bring additional functionality to the Alamofire ecosystem. + +- [AlamofireImage](https://github.com/Alamofire/AlamofireImage) - An image library including image response serializers, `UIImage` and `UIImageView` extensions, custom image filters, an auto-purging in-memory cache and a priority-based image downloading system. +- [AlamofireNetworkActivityIndicator](https://github.com/Alamofire/AlamofireNetworkActivityIndicator) - Controls the visibility of the network activity indicator on iOS using Alamofire. It contains configurable delay timers to help mitigate flicker and can support `URLSession` instances not managed by Alamofire. + +## Requirements + +- iOS 10.0+ / macOS 10.12+ / tvOS 10.0+ / watchOS 3.0+ +- Xcode 11+ +- Swift 5.1+ + +## Migration Guides + +- [Alamofire 5.0 Migration Guide](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Alamofire%205.0%20Migration%20Guide.md) +- [Alamofire 4.0 Migration Guide](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Alamofire%204.0%20Migration%20Guide.md) +- [Alamofire 3.0 Migration Guide](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Alamofire%203.0%20Migration%20Guide.md) +- [Alamofire 2.0 Migration Guide](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Alamofire%202.0%20Migration%20Guide.md) + +## Communication +- If you **need help with making network requests** using Alamofire, use [Stack Overflow](https://stackoverflow.com/questions/tagged/alamofire) and tag `alamofire`. +- If you need to **find or understand an API**, check [our documentation](http://alamofire.github.io/Alamofire/) or [Apple's documentation for `URLSession`](https://developer.apple.com/documentation/foundation/url_loading_system), on top of which Alamofire is built. +- If you need **help with an Alamofire feature**, use [our forum on swift.org](https://forums.swift.org/c/related-projects/alamofire). +- If you'd like to **discuss Alamofire best practices**, use [our forum on swift.org](https://forums.swift.org/c/related-projects/alamofire). +- If you'd like to **discuss a feature request**, use [our forum on swift.org](https://forums.swift.org/c/related-projects/alamofire). +- If you **found a bug**, open an issue here on GitHub and follow the guide. The more detail the better! +- If you **want to contribute**, submit a pull request! + +## Installation + +### CocoaPods + +[CocoaPods](https://cocoapods.org) is a dependency manager for Cocoa projects. For usage and installation instructions, visit their website. To integrate Alamofire into your Xcode project using CocoaPods, specify it in your `Podfile`: + +```ruby +pod 'Alamofire', '~> 5.1' +``` + +### Carthage + +[Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks. To integrate Alamofire into your Xcode project using Carthage, specify it in your `Cartfile`: + +```ogdl +github "Alamofire/Alamofire" ~> 5.1 +``` + +### Swift Package Manager + +The [Swift Package Manager](https://swift.org/package-manager/) is a tool for automating the distribution of Swift code and is integrated into the `swift` compiler. It is in early development, but Alamofire does support its use on supported platforms. + +Once you have your Swift package set up, adding Alamofire as a dependency is as easy as adding it to the `dependencies` value of your `Package.swift`. + +```swift +dependencies: [ + .package(url: "https://github.com/Alamofire/Alamofire.git", .upToNextMajor(from: "5.1.0")) +] +``` + +### Manually + +If you prefer not to use any of the aforementioned dependency managers, you can integrate Alamofire into your project manually. + +#### Embedded Framework + +- Open up Terminal, `cd` into your top-level project directory, and run the following command "if" your project is not initialized as a git repository: + + ```bash + $ git init + ``` + +- Add Alamofire as a git [submodule](https://git-scm.com/docs/git-submodule) by running the following command: + + ```bash + $ git submodule add https://github.com/Alamofire/Alamofire.git + ``` + +- Open the new `Alamofire` folder, and drag the `Alamofire.xcodeproj` into the Project Navigator of your application's Xcode project. + + > It should appear nested underneath your application's blue project icon. Whether it is above or below all the other Xcode groups does not matter. + +- Select the `Alamofire.xcodeproj` in the Project Navigator and verify the deployment target matches that of your application target. +- Next, select your application project in the Project Navigator (blue project icon) to navigate to the target configuration window and select the application target under the "Targets" heading in the sidebar. +- In the tab bar at the top of that window, open the "General" panel. +- Click on the `+` button under the "Embedded Binaries" section. +- You will see two different `Alamofire.xcodeproj` folders each with two different versions of the `Alamofire.framework` nested inside a `Products` folder. + + > It does not matter which `Products` folder you choose from, but it does matter whether you choose the top or bottom `Alamofire.framework`. + +- Select the top `Alamofire.framework` for iOS and the bottom one for macOS. + + > You can verify which one you selected by inspecting the build log for your project. The build target for `Alamofire` will be listed as either `Alamofire iOS`, `Alamofire macOS`, `Alamofire tvOS` or `Alamofire watchOS`. + +- And that's it! + + > The `Alamofire.framework` is automagically added as a target dependency, linked framework and embedded framework in a copy files build phase which is all you need to build on the simulator and a device. + +## Open Radars + +The following radars have some effect on the current implementation of Alamofire. + +- [`rdar://21349340`](http://www.openradar.me/radar?id=5517037090635776) - Compiler throwing warning due to toll-free bridging issue in test case +- `rdar://26870455` - Background URL Session Configurations do not work in the simulator +- `rdar://26849668` - Some URLProtocol APIs do not properly handle `URLRequest` +- `FB7624529` - `urlSession(_:task:didFinishCollecting:)` never called on watchOS + +## Resolved Radars + +The following radars have been resolved over time after being filed against the Alamofire project. + +- [`rdar://26761490`](http://www.openradar.me/radar?id=5010235949318144) - Swift string interpolation causing memory leak with common usage. + - (Resolved): 9/1/17 in Xcode 9 beta 6. +- [`rdar://36082113`](http://openradar.appspot.com/radar?id=4942308441063424) - `URLSessionTaskMetrics` failing to link on watchOS 3.0+ + - (Resolved): Just add `CFNetwork` to your linked frameworks. + +## Workarounds + +- Collection of `URLSessionTaskMetrics` is currently disabled on watchOS due to `FB7624529`. + +## FAQ + +### What's the origin of the name Alamofire? + +Alamofire is named after the [Alamo Fire flower](https://aggie-horticulture.tamu.edu/wildseed/alamofire.html), a hybrid variant of the Bluebonnet, the official state flower of Texas. + +## Credits + +Alamofire is owned and maintained by the [Alamofire Software Foundation](http://alamofire.org). You can follow them on Twitter at [@AlamofireSF](https://twitter.com/AlamofireSF) for project updates and releases. + +### Security Disclosure + +If you believe you have identified a security vulnerability with Alamofire, you should report it as soon as possible via email to security@alamofire.org. Please do not post it to a public issue tracker. + +## Donations + +The [ASF](https://github.com/Alamofire/Foundation#members) is looking to raise money to officially stay registered as a federal non-profit organization. +Registering will allow us members to gain some legal protections and also allow us to put donations to use, tax-free. +Donating to the ASF will enable us to: + +- Pay our yearly legal fees to keep the non-profit in good status +- Pay for our mail servers to help us stay on top of all questions and security issues +- Potentially fund test servers to make it easier for us to test the edge cases +- Potentially fund developers to work on one of our projects full-time + +The community adoption of the ASF libraries has been amazing. +We are greatly humbled by your enthusiasm around the projects and want to continue to do everything we can to move the needle forward. +With your continued support, the ASF will be able to improve its reach and also provide better legal safety for the core members. +If you use any of our libraries for work, see if your employers would be interested in donating. +Any amount you can donate today to help us reach our goal would be greatly appreciated. + +[![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=W34WPEE74APJQ) + +## License + +Alamofire is released under the MIT license. [See LICENSE](https://github.com/Alamofire/Alamofire/blob/master/LICENSE) for details. diff --git b/Pods/Alamofire/Source/AFError.swift a/Pods/Alamofire/Source/AFError.swift new file mode 100644 index 0000000..59c8deb --- /dev/null +++ a/Pods/Alamofire/Source/AFError.swift @@ -0,0 +1,840 @@ +// +// AFError.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 + +/// `AFError` is the error type returned by Alamofire. It encompasses a few different types of errors, each with +/// their own associated reasons. +public enum AFError: Error { + /// The underlying reason the `.multipartEncodingFailed` error occurred. + public enum MultipartEncodingFailureReason { + /// The `fileURL` provided for reading an encodable body part isn't a file `URL`. + case bodyPartURLInvalid(url: URL) + /// The filename of the `fileURL` provided has either an empty `lastPathComponent` or `pathExtension. + case bodyPartFilenameInvalid(in: URL) + /// The file at the `fileURL` provided was not reachable. + case bodyPartFileNotReachable(at: URL) + /// Attempting to check the reachability of the `fileURL` provided threw an error. + case bodyPartFileNotReachableWithError(atURL: URL, error: Error) + /// The file at the `fileURL` provided is actually a directory. + case bodyPartFileIsDirectory(at: URL) + /// The size of the file at the `fileURL` provided was not returned by the system. + case bodyPartFileSizeNotAvailable(at: URL) + /// The attempt to find the size of the file at the `fileURL` provided threw an error. + case bodyPartFileSizeQueryFailedWithError(forURL: URL, error: Error) + /// An `InputStream` could not be created for the provided `fileURL`. + case bodyPartInputStreamCreationFailed(for: URL) + /// An `OutputStream` could not be created when attempting to write the encoded data to disk. + case outputStreamCreationFailed(for: URL) + /// The encoded body data could not be written to disk because a file already exists at the provided `fileURL`. + case outputStreamFileAlreadyExists(at: URL) + /// The `fileURL` provided for writing the encoded body data to disk is not a file `URL`. + case outputStreamURLInvalid(url: URL) + /// The attempt to write the encoded body data to disk failed with an underlying error. + case outputStreamWriteFailed(error: Error) + /// The attempt to read an encoded body part `InputStream` failed with underlying system error. + case inputStreamReadFailed(error: Error) + } + + /// The underlying reason the `.parameterEncodingFailed` error occurred. + public enum ParameterEncodingFailureReason { + /// The `URLRequest` did not have a `URL` to encode. + case missingURL + /// JSON serialization failed with an underlying system error during the encoding process. + case jsonEncodingFailed(error: Error) + /// Custom parameter encoding failed due to the associated `Error`. + case customEncodingFailed(error: Error) + } + + /// The underlying reason the `.parameterEncoderFailed` error occurred. + public enum ParameterEncoderFailureReason { + /// Possible missing components. + public enum RequiredComponent { + /// The `URL` was missing or unable to be extracted from the passed `URLRequest` or during encoding. + case url + /// The `HTTPMethod` could not be extracted from the passed `URLRequest`. + case httpMethod(rawValue: String) + } + + /// A `RequiredComponent` was missing during encoding. + case missingRequiredComponent(RequiredComponent) + /// The underlying encoder failed with the associated error. + case encoderFailed(error: Error) + } + + /// The underlying reason the `.responseValidationFailed` error occurred. + public enum ResponseValidationFailureReason { + /// The data file containing the server response did not exist. + case dataFileNil + /// The data file containing the server response at the associated `URL` could not be read. + case dataFileReadFailed(at: URL) + /// The response did not contain a `Content-Type` and the `acceptableContentTypes` provided did not contain a + /// wildcard type. + case missingContentType(acceptableContentTypes: [String]) + /// The response `Content-Type` did not match any type in the provided `acceptableContentTypes`. + case unacceptableContentType(acceptableContentTypes: [String], responseContentType: String) + /// The response status code was not acceptable. + case unacceptableStatusCode(code: Int) + /// Custom response validation failed due to the associated `Error`. + case customValidationFailed(error: Error) + } + + /// The underlying reason the response serialization error occurred. + public enum ResponseSerializationFailureReason { + /// The server response contained no data or the data was zero length. + case inputDataNilOrZeroLength + /// The file containing the server response did not exist. + case inputFileNil + /// The file containing the server response could not be read from the associated `URL`. + case inputFileReadFailed(at: URL) + /// String serialization failed using the provided `String.Encoding`. + case stringSerializationFailed(encoding: String.Encoding) + /// JSON serialization failed with an underlying system error. + case jsonSerializationFailed(error: Error) + /// A `DataDecoder` failed to decode the response due to the associated `Error`. + case decodingFailed(error: Error) + /// A custom response serializer failed due to the associated `Error`. + case customSerializationFailed(error: Error) + /// Generic serialization failed for an empty response that wasn't type `Empty` but instead the associated type. + case invalidEmptyResponse(type: String) + } + + /// Underlying reason a server trust evaluation error occurred. + public enum ServerTrustFailureReason { + /// The output of a server trust evaluation. + public struct Output { + /// The host for which the evaluation was performed. + public let host: String + /// The `SecTrust` value which was evaluated. + public let trust: SecTrust + /// The `OSStatus` of evaluation operation. + public let status: OSStatus + /// The result of the evaluation operation. + public let result: SecTrustResultType + + /// Creates an `Output` value from the provided values. + init(_ host: String, _ trust: SecTrust, _ status: OSStatus, _ result: SecTrustResultType) { + self.host = host + self.trust = trust + self.status = status + self.result = result + } + } + + /// No `ServerTrustEvaluator` was found for the associated host. + case noRequiredEvaluator(host: String) + /// No certificates were found with which to perform the trust evaluation. + case noCertificatesFound + /// No public keys were found with which to perform the trust evaluation. + case noPublicKeysFound + /// During evaluation, application of the associated `SecPolicy` failed. + case policyApplicationFailed(trust: SecTrust, policy: SecPolicy, status: OSStatus) + /// During evaluation, setting the associated anchor certificates failed. + case settingAnchorCertificatesFailed(status: OSStatus, certificates: [SecCertificate]) + /// During evaluation, creation of the revocation policy failed. + case revocationPolicyCreationFailed + /// `SecTrust` evaluation failed with the associated `Error`, if one was produced. + case trustEvaluationFailed(error: Error?) + /// Default evaluation failed with the associated `Output`. + case defaultEvaluationFailed(output: Output) + /// Host validation failed with the associated `Output`. + case hostValidationFailed(output: Output) + /// Revocation check failed with the associated `Output` and options. + case revocationCheckFailed(output: Output, options: RevocationTrustEvaluator.Options) + /// Certificate pinning failed. + case certificatePinningFailed(host: String, trust: SecTrust, pinnedCertificates: [SecCertificate], serverCertificates: [SecCertificate]) + /// Public key pinning failed. + case publicKeyPinningFailed(host: String, trust: SecTrust, pinnedKeys: [SecKey], serverKeys: [SecKey]) + /// Custom server trust evaluation failed due to the associated `Error`. + case customEvaluationFailed(error: Error) + } + + /// The underlying reason the `.urlRequestValidationFailed` + public enum URLRequestValidationFailureReason { + /// URLRequest with GET method had body data. + case bodyDataInGETRequest(Data) + } + + /// `UploadableConvertible` threw an error in `createUploadable()`. + case createUploadableFailed(error: Error) + /// `URLRequestConvertible` threw an error in `asURLRequest()`. + case createURLRequestFailed(error: Error) + /// `SessionDelegate` threw an error while attempting to move downloaded file to destination URL. + case downloadedFileMoveFailed(error: Error, source: URL, destination: URL) + /// `Request` was explicitly cancelled. + case explicitlyCancelled + /// `URLConvertible` type failed to create a valid `URL`. + case invalidURL(url: URLConvertible) + /// Multipart form encoding failed. + case multipartEncodingFailed(reason: MultipartEncodingFailureReason) + /// `ParameterEncoding` threw an error during the encoding process. + case parameterEncodingFailed(reason: ParameterEncodingFailureReason) + /// `ParameterEncoder` threw an error while running the encoder. + case parameterEncoderFailed(reason: ParameterEncoderFailureReason) + /// `RequestAdapter` threw an error during adaptation. + case requestAdaptationFailed(error: Error) + /// `RequestRetrier` threw an error during the request retry process. + case requestRetryFailed(retryError: Error, originalError: Error) + /// Response validation failed. + case responseValidationFailed(reason: ResponseValidationFailureReason) + /// Response serialization failed. + case responseSerializationFailed(reason: ResponseSerializationFailureReason) + /// `ServerTrustEvaluating` instance threw an error during trust evaluation. + case serverTrustEvaluationFailed(reason: ServerTrustFailureReason) + /// `Session` which issued the `Request` was deinitialized, most likely because its reference went out of scope. + case sessionDeinitialized + /// `Session` was explicitly invalidated, possibly with the `Error` produced by the underlying `URLSession`. + case sessionInvalidated(error: Error?) + /// `URLSessionTask` completed with error. + case sessionTaskFailed(error: Error) + /// `URLRequest` failed validation. + case urlRequestValidationFailed(reason: URLRequestValidationFailureReason) +} + +extension Error { + /// Returns the instance cast as an `AFError`. + public var asAFError: AFError? { + self as? AFError + } + + /// Returns the instance cast as an `AFError`. If casting fails, a `fatalError` with the specified `message` is thrown. + public func asAFError(orFailWith message: @autoclosure () -> String, file: StaticString = #file, line: UInt = #line) -> AFError { + guard let afError = self as? AFError else { + fatalError(message(), file: file, line: line) + } + return afError + } + + /// Casts the instance as `AFError` or returns `defaultAFError` + func asAFError(or defaultAFError: @autoclosure () -> AFError) -> AFError { + self as? AFError ?? defaultAFError() + } +} + +// MARK: - Error Booleans + +extension AFError { + /// Returns whether the instance is `.sessionDeinitialized`. + public var isSessionDeinitializedError: Bool { + if case .sessionDeinitialized = self { return true } + return false + } + + /// Returns whether the instance is `.sessionInvalidated`. + public var isSessionInvalidatedError: Bool { + if case .sessionInvalidated = self { return true } + return false + } + + /// Returns whether the instance is `.explicitlyCancelled`. + public var isExplicitlyCancelledError: Bool { + if case .explicitlyCancelled = self { return true } + return false + } + + /// Returns whether the instance is `.invalidURL`. + public var isInvalidURLError: Bool { + if case .invalidURL = self { return true } + return false + } + + /// Returns whether the instance is `.parameterEncodingFailed`. When `true`, the `underlyingError` property will + /// contain the associated value. + public var isParameterEncodingError: Bool { + if case .parameterEncodingFailed = self { return true } + return false + } + + /// Returns whether the instance is `.parameterEncoderFailed`. When `true`, the `underlyingError` property will + /// contain the associated value. + public var isParameterEncoderError: Bool { + if case .parameterEncoderFailed = self { return true } + return false + } + + /// Returns whether the instance is `.multipartEncodingFailed`. When `true`, the `url` and `underlyingError` + /// properties will contain the associated values. + public var isMultipartEncodingError: Bool { + if case .multipartEncodingFailed = self { return true } + return false + } + + /// Returns whether the instance is `.requestAdaptationFailed`. When `true`, the `underlyingError` property will + /// contain the associated value. + public var isRequestAdaptationError: Bool { + if case .requestAdaptationFailed = self { return true } + return false + } + + /// Returns whether the instance is `.responseValidationFailed`. When `true`, the `acceptableContentTypes`, + /// `responseContentType`, `responseCode`, and `underlyingError` properties will contain the associated values. + public var isResponseValidationError: Bool { + if case .responseValidationFailed = self { return true } + return false + } + + /// Returns whether the instance is `.responseSerializationFailed`. When `true`, the `failedStringEncoding` and + /// `underlyingError` properties will contain the associated values. + public var isResponseSerializationError: Bool { + if case .responseSerializationFailed = self { return true } + return false + } + + /// Returns whether the instance is `.serverTrustEvaluationFailed`. When `true`, the `underlyingError` property will + /// contain the associated value. + public var isServerTrustEvaluationError: Bool { + if case .serverTrustEvaluationFailed = self { return true } + return false + } + + /// Returns whether the instance is `requestRetryFailed`. When `true`, the `underlyingError` property will + /// contain the associated value. + public var isRequestRetryError: Bool { + if case .requestRetryFailed = self { return true } + return false + } + + /// Returns whether the instance is `createUploadableFailed`. When `true`, the `underlyingError` property will + /// contain the associated value. + public var isCreateUploadableError: Bool { + if case .createUploadableFailed = self { return true } + return false + } + + /// Returns whether the instance is `createURLRequestFailed`. When `true`, the `underlyingError` property will + /// contain the associated value. + public var isCreateURLRequestError: Bool { + if case .createURLRequestFailed = self { return true } + return false + } + + /// Returns whether the instance is `downloadedFileMoveFailed`. When `true`, the `destination` and `underlyingError` properties will + /// contain the associated values. + public var isDownloadedFileMoveError: Bool { + if case .downloadedFileMoveFailed = self { return true } + return false + } + + /// Returns whether the instance is `createURLRequestFailed`. When `true`, the `underlyingError` property will + /// contain the associated value. + public var isSessionTaskError: Bool { + if case .sessionTaskFailed = self { return true } + return false + } +} + +// MARK: - Convenience Properties + +extension AFError { + /// The `URLConvertible` associated with the error. + public var urlConvertible: URLConvertible? { + guard case let .invalidURL(url) = self else { return nil } + return url + } + + /// The `URL` associated with the error. + public var url: URL? { + guard case let .multipartEncodingFailed(reason) = self else { return nil } + return reason.url + } + + /// The underlying `Error` responsible for generating the failure associated with `.sessionInvalidated`, + /// `.parameterEncodingFailed`, `.parameterEncoderFailed`, `.multipartEncodingFailed`, `.requestAdaptationFailed`, + /// `.responseSerializationFailed`, `.requestRetryFailed` errors. + public var underlyingError: Error? { + switch self { + case let .multipartEncodingFailed(reason): + return reason.underlyingError + case let .parameterEncodingFailed(reason): + return reason.underlyingError + case let .parameterEncoderFailed(reason): + return reason.underlyingError + case let .requestAdaptationFailed(error): + return error + case let .requestRetryFailed(retryError, _): + return retryError + case let .responseValidationFailed(reason): + return reason.underlyingError + case let .responseSerializationFailed(reason): + return reason.underlyingError + case let .serverTrustEvaluationFailed(reason): + return reason.underlyingError + case let .sessionInvalidated(error): + return error + case let .createUploadableFailed(error): + return error + case let .createURLRequestFailed(error): + return error + case let .downloadedFileMoveFailed(error, _, _): + return error + case let .sessionTaskFailed(error): + return error + case .explicitlyCancelled, + .invalidURL, + .sessionDeinitialized, + .urlRequestValidationFailed: + return nil + } + } + + /// The acceptable `Content-Type`s of a `.responseValidationFailed` error. + public var acceptableContentTypes: [String]? { + guard case let .responseValidationFailed(reason) = self else { return nil } + return reason.acceptableContentTypes + } + + /// The response `Content-Type` of a `.responseValidationFailed` error. + public var responseContentType: String? { + guard case let .responseValidationFailed(reason) = self else { return nil } + return reason.responseContentType + } + + /// The response code of a `.responseValidationFailed` error. + public var responseCode: Int? { + guard case let .responseValidationFailed(reason) = self else { return nil } + return reason.responseCode + } + + /// The `String.Encoding` associated with a failed `.stringResponse()` call. + public var failedStringEncoding: String.Encoding? { + guard case let .responseSerializationFailed(reason) = self else { return nil } + return reason.failedStringEncoding + } + + /// The `source` URL of a `.downloadedFileMoveFailed` error. + public var sourceURL: URL? { + guard case let .downloadedFileMoveFailed(_, source, _) = self else { return nil } + return source + } + + /// The `destination` URL of a `.downloadedFileMoveFailed` error. + public var destinationURL: URL? { + guard case let .downloadedFileMoveFailed(_, _, destination) = self else { return nil } + return destination + } +} + +extension AFError.ParameterEncodingFailureReason { + var underlyingError: Error? { + switch self { + case let .jsonEncodingFailed(error), + let .customEncodingFailed(error): + return error + case .missingURL: + return nil + } + } +} + +extension AFError.ParameterEncoderFailureReason { + var underlyingError: Error? { + switch self { + case let .encoderFailed(error): + return error + case .missingRequiredComponent: + return nil + } + } +} + +extension AFError.MultipartEncodingFailureReason { + var url: URL? { + switch self { + case let .bodyPartURLInvalid(url), + let .bodyPartFilenameInvalid(url), + let .bodyPartFileNotReachable(url), + let .bodyPartFileIsDirectory(url), + let .bodyPartFileSizeNotAvailable(url), + let .bodyPartInputStreamCreationFailed(url), + let .outputStreamCreationFailed(url), + let .outputStreamFileAlreadyExists(url), + let .outputStreamURLInvalid(url), + let .bodyPartFileNotReachableWithError(url, _), + let .bodyPartFileSizeQueryFailedWithError(url, _): + return url + case .outputStreamWriteFailed, + .inputStreamReadFailed: + return nil + } + } + + var underlyingError: Error? { + switch self { + case let .bodyPartFileNotReachableWithError(_, error), + let .bodyPartFileSizeQueryFailedWithError(_, error), + let .outputStreamWriteFailed(error), + let .inputStreamReadFailed(error): + return error + case .bodyPartURLInvalid, + .bodyPartFilenameInvalid, + .bodyPartFileNotReachable, + .bodyPartFileIsDirectory, + .bodyPartFileSizeNotAvailable, + .bodyPartInputStreamCreationFailed, + .outputStreamCreationFailed, + .outputStreamFileAlreadyExists, + .outputStreamURLInvalid: + return nil + } + } +} + +extension AFError.ResponseValidationFailureReason { + var acceptableContentTypes: [String]? { + switch self { + case let .missingContentType(types), + let .unacceptableContentType(types, _): + return types + case .dataFileNil, + .dataFileReadFailed, + .unacceptableStatusCode, + .customValidationFailed: + return nil + } + } + + var responseContentType: String? { + switch self { + case let .unacceptableContentType(_, responseType): + return responseType + case .dataFileNil, + .dataFileReadFailed, + .missingContentType, + .unacceptableStatusCode, + .customValidationFailed: + return nil + } + } + + var responseCode: Int? { + switch self { + case let .unacceptableStatusCode(code): + return code + case .dataFileNil, + .dataFileReadFailed, + .missingContentType, + .unacceptableContentType, + .customValidationFailed: + return nil + } + } + + var underlyingError: Error? { + switch self { + case let .customValidationFailed(error): + return error + case .dataFileNil, + .dataFileReadFailed, + .missingContentType, + .unacceptableContentType, + .unacceptableStatusCode: + return nil + } + } +} + +extension AFError.ResponseSerializationFailureReason { + var failedStringEncoding: String.Encoding? { + switch self { + case let .stringSerializationFailed(encoding): + return encoding + case .inputDataNilOrZeroLength, + .inputFileNil, + .inputFileReadFailed(_), + .jsonSerializationFailed(_), + .decodingFailed(_), + .customSerializationFailed(_), + .invalidEmptyResponse: + return nil + } + } + + var underlyingError: Error? { + switch self { + case let .jsonSerializationFailed(error), + let .decodingFailed(error), + let .customSerializationFailed(error): + return error + case .inputDataNilOrZeroLength, + .inputFileNil, + .inputFileReadFailed, + .stringSerializationFailed, + .invalidEmptyResponse: + return nil + } + } +} + +extension AFError.ServerTrustFailureReason { + var output: AFError.ServerTrustFailureReason.Output? { + switch self { + case let .defaultEvaluationFailed(output), + let .hostValidationFailed(output), + let .revocationCheckFailed(output, _): + return output + case .noRequiredEvaluator, + .noCertificatesFound, + .noPublicKeysFound, + .policyApplicationFailed, + .settingAnchorCertificatesFailed, + .revocationPolicyCreationFailed, + .trustEvaluationFailed, + .certificatePinningFailed, + .publicKeyPinningFailed, + .customEvaluationFailed: + return nil + } + } + + var underlyingError: Error? { + switch self { + case let .customEvaluationFailed(error): + return error + case let .trustEvaluationFailed(error): + return error + case .noRequiredEvaluator, + .noCertificatesFound, + .noPublicKeysFound, + .policyApplicationFailed, + .settingAnchorCertificatesFailed, + .revocationPolicyCreationFailed, + .defaultEvaluationFailed, + .hostValidationFailed, + .revocationCheckFailed, + .certificatePinningFailed, + .publicKeyPinningFailed: + return nil + } + } +} + +// MARK: - Error Descriptions + +extension AFError: LocalizedError { + public var errorDescription: String? { + switch self { + case .explicitlyCancelled: + return "Request explicitly cancelled." + case let .invalidURL(url): + return "URL is not valid: \(url)" + case let .parameterEncodingFailed(reason): + return reason.localizedDescription + case let .parameterEncoderFailed(reason): + return reason.localizedDescription + case let .multipartEncodingFailed(reason): + return reason.localizedDescription + case let .requestAdaptationFailed(error): + return "Request adaption failed with error: \(error.localizedDescription)" + case let .responseValidationFailed(reason): + return reason.localizedDescription + case let .responseSerializationFailed(reason): + return reason.localizedDescription + case let .requestRetryFailed(retryError, originalError): + return """ + Request retry failed with retry error: \(retryError.localizedDescription), \ + original error: \(originalError.localizedDescription) + """ + case .sessionDeinitialized: + return """ + Session was invalidated without error, so it was likely deinitialized unexpectedly. \ + Be sure to retain a reference to your Session for the duration of your requests. + """ + case let .sessionInvalidated(error): + return "Session was invalidated with error: \(error?.localizedDescription ?? "No description.")" + case let .serverTrustEvaluationFailed(reason): + return "Server trust evaluation failed due to reason: \(reason.localizedDescription)" + case let .urlRequestValidationFailed(reason): + return "URLRequest validation failed due to reason: \(reason.localizedDescription)" + case let .createUploadableFailed(error): + return "Uploadable creation failed with error: \(error.localizedDescription)" + case let .createURLRequestFailed(error): + return "URLRequest creation failed with error: \(error.localizedDescription)" + case let .downloadedFileMoveFailed(error, source, destination): + return "Moving downloaded file from: \(source) to: \(destination) failed with error: \(error.localizedDescription)" + case let .sessionTaskFailed(error): + return "URLSessionTask failed with error: \(error.localizedDescription)" + } + } +} + +extension AFError.ParameterEncodingFailureReason { + var localizedDescription: String { + switch self { + case .missingURL: + return "URL request to encode was missing a URL" + case let .jsonEncodingFailed(error): + return "JSON could not be encoded because of error:\n\(error.localizedDescription)" + case let .customEncodingFailed(error): + return "Custom parameter encoder failed with error: \(error.localizedDescription)" + } + } +} + +extension AFError.ParameterEncoderFailureReason { + var localizedDescription: String { + switch self { + case let .missingRequiredComponent(component): + return "Encoding failed due to a missing request component: \(component)" + case let .encoderFailed(error): + return "The underlying encoder failed with the error: \(error)" + } + } +} + +extension AFError.MultipartEncodingFailureReason { + var localizedDescription: String { + switch self { + case let .bodyPartURLInvalid(url): + return "The URL provided is not a file URL: \(url)" + case let .bodyPartFilenameInvalid(url): + return "The URL provided does not have a valid filename: \(url)" + case let .bodyPartFileNotReachable(url): + return "The URL provided is not reachable: \(url)" + case let .bodyPartFileNotReachableWithError(url, error): + return """ + The system returned an error while checking the provided URL for reachability. + URL: \(url) + Error: \(error) + """ + case let .bodyPartFileIsDirectory(url): + return "The URL provided is a directory: \(url)" + case let .bodyPartFileSizeNotAvailable(url): + return "Could not fetch the file size from the provided URL: \(url)" + case let .bodyPartFileSizeQueryFailedWithError(url, error): + return """ + The system returned an error while attempting to fetch the file size from the provided URL. + URL: \(url) + Error: \(error) + """ + case let .bodyPartInputStreamCreationFailed(url): + return "Failed to create an InputStream for the provided URL: \(url)" + case let .outputStreamCreationFailed(url): + return "Failed to create an OutputStream for URL: \(url)" + case let .outputStreamFileAlreadyExists(url): + return "A file already exists at the provided URL: \(url)" + case let .outputStreamURLInvalid(url): + return "The provided OutputStream URL is invalid: \(url)" + case let .outputStreamWriteFailed(error): + return "OutputStream write failed with error: \(error)" + case let .inputStreamReadFailed(error): + return "InputStream read failed with error: \(error)" + } + } +} + +extension AFError.ResponseSerializationFailureReason { + var localizedDescription: String { + switch self { + case .inputDataNilOrZeroLength: + return "Response could not be serialized, input data was nil or zero length." + case .inputFileNil: + return "Response could not be serialized, input file was nil." + case let .inputFileReadFailed(url): + return "Response could not be serialized, input file could not be read: \(url)." + case let .stringSerializationFailed(encoding): + return "String could not be serialized with encoding: \(encoding)." + case let .jsonSerializationFailed(error): + return "JSON could not be serialized because of error:\n\(error.localizedDescription)" + case let .invalidEmptyResponse(type): + return """ + Empty response could not be serialized to type: \(type). \ + Use Empty as the expected type for such responses. + """ + case let .decodingFailed(error): + return "Response could not be decoded because of error:\n\(error.localizedDescription)" + case let .customSerializationFailed(error): + return "Custom response serializer failed with error:\n\(error.localizedDescription)" + } + } +} + +extension AFError.ResponseValidationFailureReason { + var localizedDescription: String { + switch self { + case .dataFileNil: + return "Response could not be validated, data file was nil." + case let .dataFileReadFailed(url): + return "Response could not be validated, data file could not be read: \(url)." + case let .missingContentType(types): + return """ + Response Content-Type was missing and acceptable content types \ + (\(types.joined(separator: ","))) do not match "*/*". + """ + case let .unacceptableContentType(acceptableTypes, responseType): + return """ + Response Content-Type "\(responseType)" does not match any acceptable types: \ + \(acceptableTypes.joined(separator: ",")). + """ + case let .unacceptableStatusCode(code): + return "Response status code was unacceptable: \(code)." + case let .customValidationFailed(error): + return "Custom response validation failed with error: \(error.localizedDescription)" + } + } +} + +extension AFError.ServerTrustFailureReason { + var localizedDescription: String { + switch self { + case let .noRequiredEvaluator(host): + return "A ServerTrustEvaluating value is required for host \(host) but none was found." + case .noCertificatesFound: + return "No certificates were found or provided for evaluation." + case .noPublicKeysFound: + return "No public keys were found or provided for evaluation." + case .policyApplicationFailed: + return "Attempting to set a SecPolicy failed." + case .settingAnchorCertificatesFailed: + return "Attempting to set the provided certificates as anchor certificates failed." + case .revocationPolicyCreationFailed: + return "Attempting to create a revocation policy failed." + case let .trustEvaluationFailed(error): + return "SecTrust evaluation failed with error: \(error?.localizedDescription ?? "None")" + case let .defaultEvaluationFailed(output): + return "Default evaluation failed for host \(output.host)." + case let .hostValidationFailed(output): + return "Host validation failed for host \(output.host)." + case let .revocationCheckFailed(output, _): + return "Revocation check failed for host \(output.host)." + case let .certificatePinningFailed(host, _, _, _): + return "Certificate pinning failed for host \(host)." + case let .publicKeyPinningFailed(host, _, _, _): + return "Public key pinning failed for host \(host)." + case let .customEvaluationFailed(error): + return "Custom trust evaluation failed with error: \(error.localizedDescription)" + } + } +} + +extension AFError.URLRequestValidationFailureReason { + var localizedDescription: String { + switch self { + case let .bodyDataInGETRequest(data): + return """ + Invalid URLRequest: Requests with GET method cannot have body data: + \(String(decoding: data, as: UTF8.self)) + """ + } + } +} diff --git b/Pods/Alamofire/Source/Alamofire.swift a/Pods/Alamofire/Source/Alamofire.swift new file mode 100644 index 0000000..21166ad --- /dev/null +++ a/Pods/Alamofire/Source/Alamofire.swift @@ -0,0 +1,29 @@ +// +// 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.1.0" diff --git b/Pods/Alamofire/Source/AlamofireExtended.swift a/Pods/Alamofire/Source/AlamofireExtended.swift new file mode 100644 index 0000000..f1a281d --- /dev/null +++ a/Pods/Alamofire/Source/AlamofireExtended.swift @@ -0,0 +1,61 @@ +// +// 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 { + /// 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.Type { get set } + /// Instance Alamofire extension point. + var af: AlamofireExtension { get set } +} + +public extension AlamofireExtended { + /// Static Alamofire extension point. + static var af: AlamofireExtension.Type { + get { AlamofireExtension.self } + set {} + } + + /// Instance Alamofire extension point. + var af: AlamofireExtension { + get { AlamofireExtension(self) } + set {} + } +} diff --git b/Pods/Alamofire/Source/CachedResponseHandler.swift a/Pods/Alamofire/Source/CachedResponseHandler.swift new file mode 100644 index 0000000..b6e0d4b --- /dev/null +++ a/Pods/Alamofire/Source/CachedResponseHandler.swift @@ -0,0 +1,91 @@ +// +// 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) + } + } +} diff --git b/Pods/Alamofire/Source/DispatchQueue+Alamofire.swift a/Pods/Alamofire/Source/DispatchQueue+Alamofire.swift new file mode 100644 index 0000000..10cd273 --- /dev/null +++ a/Pods/Alamofire/Source/DispatchQueue+Alamofire.swift @@ -0,0 +1,37 @@ +// +// 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) + } +} diff --git b/Pods/Alamofire/Source/EventMonitor.swift a/Pods/Alamofire/Source/EventMonitor.swift new file mode 100644 index 0000000..3a6c777 --- /dev/null +++ a/Pods/Alamofire/Source/EventMonitor.swift @@ -0,0 +1,892 @@ +// +// EventMonitor.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 + +/// Protocol outlining the lifetime events inside Alamofire. It includes both events received from the various +/// `URLSession` delegate protocols as well as various events from the lifetime of `Request` and its subclasses. +public protocol EventMonitor { + /// The `DispatchQueue` onto which Alamofire's root `CompositeEventMonitor` will dispatch events. `.main` by default. + var queue: DispatchQueue { get } + + // MARK: - URLSession Events + + // MARK: URLSessionDelegate Events + + /// Event called during `URLSessionDelegate`'s `urlSession(_:didBecomeInvalidWithError:)` method. + func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) + + // MARK: URLSessionTaskDelegate Events + + /// Event called during `URLSessionTaskDelegate`'s `urlSession(_:task:didReceive:completionHandler:)` method. + func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge) + + /// Event called during `URLSessionTaskDelegate`'s `urlSession(_:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:)` method. + func urlSession(_ session: URLSession, + task: URLSessionTask, + didSendBodyData bytesSent: Int64, + totalBytesSent: Int64, + totalBytesExpectedToSend: Int64) + + /// Event called during `URLSessionTaskDelegate`'s `urlSession(_:task:needNewBodyStream:)` method. + func urlSession(_ session: URLSession, taskNeedsNewBodyStream task: URLSessionTask) + + /// Event called during `URLSessionTaskDelegate`'s `urlSession(_:task:willPerformHTTPRedirection:newRequest:completionHandler:)` method. + func urlSession(_ session: URLSession, + task: URLSessionTask, + willPerformHTTPRedirection response: HTTPURLResponse, + newRequest request: URLRequest) + + /// Event called during `URLSessionTaskDelegate`'s `urlSession(_:task:didFinishCollecting:)` method. + func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) + + /// Event called during `URLSessionTaskDelegate`'s `urlSession(_:task:didCompleteWithError:)` method. + func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) + + /// Event called during `URLSessionTaskDelegate`'s `urlSession(_:taskIsWaitingForConnectivity:)` method. + @available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4.0, *) + func urlSession(_ session: URLSession, taskIsWaitingForConnectivity task: URLSessionTask) + + // MARK: URLSessionDataDelegate Events + + /// Event called during `URLSessionDataDelegate`'s `urlSession(_:dataTask:didReceive:)` method. + func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) + + /// Event called during `URLSessionDataDelegate`'s `urlSession(_:dataTask:willCacheResponse:completionHandler:)` method. + func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, willCacheResponse proposedResponse: CachedURLResponse) + + // MARK: URLSessionDownloadDelegate Events + + /// Event called during `URLSessionDownloadDelegate`'s `urlSession(_:downloadTask:didResumeAtOffset:expectedTotalBytes:)` method. + func urlSession(_ session: URLSession, + downloadTask: URLSessionDownloadTask, + didResumeAtOffset fileOffset: Int64, + expectedTotalBytes: Int64) + + /// Event called during `URLSessionDownloadDelegate`'s `urlSession(_:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:)` method. + func urlSession(_ session: URLSession, + downloadTask: URLSessionDownloadTask, + didWriteData bytesWritten: Int64, + totalBytesWritten: Int64, + totalBytesExpectedToWrite: Int64) + + /// Event called during `URLSessionDownloadDelegate`'s `urlSession(_:downloadTask:didFinishDownloadingTo:)` method. + func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) + + // MARK: - Request Events + + /// Event called when a `URLRequest` is first created for a `Request`. If a `RequestAdapter` is active, the + /// `URLRequest` will be adapted before being issued. + func request(_ request: Request, didCreateInitialURLRequest urlRequest: URLRequest) + + /// Event called when the attempt to create a `URLRequest` from a `Request`'s original `URLRequestConvertible` value fails. + func request(_ request: Request, didFailToCreateURLRequestWithError error: AFError) + + /// Event called when a `RequestAdapter` adapts the `Request`'s initial `URLRequest`. + func request(_ request: Request, didAdaptInitialRequest initialRequest: URLRequest, to adaptedRequest: URLRequest) + + /// Event called when a `RequestAdapter` fails to adapt the `Request`'s initial `URLRequest`. + func request(_ request: Request, didFailToAdaptURLRequest initialRequest: URLRequest, withError error: AFError) + + /// Event called when a final `URLRequest` is created for a `Request`. + func request(_ request: Request, didCreateURLRequest urlRequest: URLRequest) + + /// Event called when a `URLSessionTask` subclass instance is created for a `Request`. + func request(_ request: Request, didCreateTask task: URLSessionTask) + + /// Event called when a `Request` receives a `URLSessionTaskMetrics` value. + func request(_ request: Request, didGatherMetrics metrics: URLSessionTaskMetrics) + + /// Event called when a `Request` fails due to an error created by Alamofire. e.g. When certificate pinning fails. + func request(_ request: Request, didFailTask task: URLSessionTask, earlyWithError error: AFError) + + /// Event called when a `Request`'s task completes, possibly with an error. A `Request` may receive this event + /// multiple times if it is retried. + func request(_ request: Request, didCompleteTask task: URLSessionTask, with error: AFError?) + + /// Event called when a `Request` is about to be retried. + func requestIsRetrying(_ request: Request) + + /// Event called when a `Request` finishes and response serializers are being called. + func requestDidFinish(_ request: Request) + + /// Event called when a `Request` receives a `resume` call. + func requestDidResume(_ request: Request) + + /// Event called when a `Request`'s associated `URLSessionTask` is resumed. + func request(_ request: Request, didResumeTask task: URLSessionTask) + + /// Event called when a `Request` receives a `suspend` call. + func requestDidSuspend(_ request: Request) + + /// Event called when a `Request`'s associated `URLSessionTask` is suspended. + func request(_ request: Request, didSuspendTask task: URLSessionTask) + + /// Event called when a `Request` receives a `cancel` call. + func requestDidCancel(_ request: Request) + + /// Event called when a `Request`'s associated `URLSessionTask` is cancelled. + func request(_ request: Request, didCancelTask task: URLSessionTask) + + // MARK: DataRequest Events + + /// Event called when a `DataRequest` calls a `Validation`. + func request(_ request: DataRequest, + didValidateRequest urlRequest: URLRequest?, + response: HTTPURLResponse, + data: Data?, + withResult result: Request.ValidationResult) + + /// Event called when a `DataRequest` creates a `DataResponse` value without calling a `ResponseSerializer`. + func request(_ request: DataRequest, didParseResponse response: DataResponse) + + /// Event called when a `DataRequest` calls a `ResponseSerializer` and creates a generic `DataResponse`. + func request(_ request: DataRequest, didParseResponse response: DataResponse) + + // MARK: DataStreamRequest Events + + /// Event called when a `DataStreamRequest` calls a `Validation` closure. + /// + /// - Parameters: + /// - request: `DataStreamRequest` which is calling the `Validation`. + /// - urlRequest: `URLRequest` of the request being validated. + /// - response: `HTTPURLResponse` of the request being validated. + /// - result: Produced `ValidationResult`. + func request(_ request: DataStreamRequest, + didValidateRequest urlRequest: URLRequest?, + response: HTTPURLResponse, + withResult result: Request.ValidationResult) + + /// Event called when a `DataStreamSerializer` produces a value from streamed `Data`. + /// + /// - Parameters: + /// - request: `DataStreamRequest` for which the value was serialized. + /// - result: `Result` of the serialization attempt. + func request(_ request: DataStreamRequest, didParseStream result: Result) + + // MARK: UploadRequest Events + + /// Event called when an `UploadRequest` creates its `Uploadable` value, indicating the type of upload it represents. + func request(_ request: UploadRequest, didCreateUploadable uploadable: UploadRequest.Uploadable) + + /// Event called when an `UploadRequest` failed to create its `Uploadable` value due to an error. + func request(_ request: UploadRequest, didFailToCreateUploadableWithError error: AFError) + + /// Event called when an `UploadRequest` provides the `InputStream` from its `Uploadable` value. This only occurs if + /// the `InputStream` does not wrap a `Data` value or file `URL`. + func request(_ request: UploadRequest, didProvideInputStream stream: InputStream) + + // MARK: DownloadRequest Events + + /// Event called when a `DownloadRequest`'s `URLSessionDownloadTask` finishes and the temporary file has been moved. + func request(_ request: DownloadRequest, didFinishDownloadingUsing task: URLSessionTask, with result: Result) + + /// Event called when a `DownloadRequest`'s `Destination` closure is called and creates the destination URL the + /// downloaded file will be moved to. + func request(_ request: DownloadRequest, didCreateDestinationURL url: URL) + + /// Event called when a `DownloadRequest` calls a `Validation`. + func request(_ request: DownloadRequest, + didValidateRequest urlRequest: URLRequest?, + response: HTTPURLResponse, + fileURL: URL?, + withResult result: Request.ValidationResult) + + /// Event called when a `DownloadRequest` creates a `DownloadResponse` without calling a `ResponseSerializer`. + func request(_ request: DownloadRequest, didParseResponse response: DownloadResponse) + + /// Event called when a `DownloadRequest` calls a `DownloadResponseSerializer` and creates a generic `DownloadResponse` + func request(_ request: DownloadRequest, didParseResponse response: DownloadResponse) +} + +extension EventMonitor { + /// The default queue on which `CompositeEventMonitor`s will call the `EventMonitor` methods. `.main` by default. + public var queue: DispatchQueue { .main } + + // MARK: Default Implementations + + public func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) {} + public func urlSession(_ session: URLSession, + task: URLSessionTask, + didReceive challenge: URLAuthenticationChallenge) {} + public func urlSession(_ session: URLSession, + task: URLSessionTask, + didSendBodyData bytesSent: Int64, + totalBytesSent: Int64, + totalBytesExpectedToSend: Int64) {} + public func urlSession(_ session: URLSession, taskNeedsNewBodyStream task: URLSessionTask) {} + public func urlSession(_ session: URLSession, + task: URLSessionTask, + willPerformHTTPRedirection response: HTTPURLResponse, + newRequest request: URLRequest) {} + public func urlSession(_ session: URLSession, + task: URLSessionTask, + didFinishCollecting metrics: URLSessionTaskMetrics) {} + public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {} + public func urlSession(_ session: URLSession, taskIsWaitingForConnectivity task: URLSessionTask) {} + public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {} + public func urlSession(_ session: URLSession, + dataTask: URLSessionDataTask, + willCacheResponse proposedResponse: CachedURLResponse) {} + public func urlSession(_ session: URLSession, + downloadTask: URLSessionDownloadTask, + didResumeAtOffset fileOffset: Int64, + expectedTotalBytes: Int64) {} + public func urlSession(_ session: URLSession, + downloadTask: URLSessionDownloadTask, + didWriteData bytesWritten: Int64, + totalBytesWritten: Int64, + totalBytesExpectedToWrite: Int64) {} + public func urlSession(_ session: URLSession, + downloadTask: URLSessionDownloadTask, + didFinishDownloadingTo location: URL) {} + public func request(_ request: Request, didCreateInitialURLRequest urlRequest: URLRequest) {} + public func request(_ request: Request, didFailToCreateURLRequestWithError error: AFError) {} + public func request(_ request: Request, + didAdaptInitialRequest initialRequest: URLRequest, + to adaptedRequest: URLRequest) {} + public func request(_ request: Request, + didFailToAdaptURLRequest initialRequest: URLRequest, + withError error: AFError) {} + public func request(_ request: Request, didCreateURLRequest urlRequest: URLRequest) {} + public func request(_ request: Request, didCreateTask task: URLSessionTask) {} + public func request(_ request: Request, didGatherMetrics metrics: URLSessionTaskMetrics) {} + public func request(_ request: Request, didFailTask task: URLSessionTask, earlyWithError error: AFError) {} + public func request(_ request: Request, didCompleteTask task: URLSessionTask, with error: AFError?) {} + public func requestIsRetrying(_ request: Request) {} + public func requestDidFinish(_ request: Request) {} + public func requestDidResume(_ request: Request) {} + public func request(_ request: Request, didResumeTask task: URLSessionTask) {} + public func requestDidSuspend(_ request: Request) {} + public func request(_ request: Request, didSuspendTask task: URLSessionTask) {} + public func requestDidCancel(_ request: Request) {} + public func request(_ request: Request, didCancelTask task: URLSessionTask) {} + public func request(_ request: DataRequest, + didValidateRequest urlRequest: URLRequest?, + response: HTTPURLResponse, + data: Data?, + withResult result: Request.ValidationResult) {} + public func request(_ request: DataRequest, didParseResponse response: DataResponse) {} + public func request(_ request: DataRequest, didParseResponse response: DataResponse) {} + public func request(_ request: DataStreamRequest, + didValidateRequest urlRequest: URLRequest?, + response: HTTPURLResponse, + withResult result: Request.ValidationResult) {} + public func request(_ request: DataStreamRequest, didParseStream result: Result) {} + public func request(_ request: UploadRequest, didCreateUploadable uploadable: UploadRequest.Uploadable) {} + public func request(_ request: UploadRequest, didFailToCreateUploadableWithError error: AFError) {} + public func request(_ request: UploadRequest, didProvideInputStream stream: InputStream) {} + public func request(_ request: DownloadRequest, didFinishDownloadingUsing task: URLSessionTask, with result: Result) {} + public func request(_ request: DownloadRequest, didCreateDestinationURL url: URL) {} + public func request(_ request: DownloadRequest, + didValidateRequest urlRequest: URLRequest?, + response: HTTPURLResponse, + fileURL: URL?, + withResult result: Request.ValidationResult) {} + public func request(_ request: DownloadRequest, didParseResponse response: DownloadResponse) {} + public func request(_ request: DownloadRequest, didParseResponse response: DownloadResponse) {} +} + +/// An `EventMonitor` which can contain multiple `EventMonitor`s and calls their methods on their queues. +public final class CompositeEventMonitor: EventMonitor { + public let queue = DispatchQueue(label: "org.alamofire.compositeEventMonitor", qos: .background) + + let monitors: [EventMonitor] + + init(monitors: [EventMonitor]) { + self.monitors = monitors + } + + func performEvent(_ event: @escaping (EventMonitor) -> Void) { + queue.async { + for monitor in self.monitors { + monitor.queue.async { event(monitor) } + } + } + } + + public func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) { + performEvent { $0.urlSession(session, didBecomeInvalidWithError: error) } + } + + public func urlSession(_ session: URLSession, + task: URLSessionTask, + didReceive challenge: URLAuthenticationChallenge) { + performEvent { $0.urlSession(session, task: task, didReceive: challenge) } + } + + public func urlSession(_ session: URLSession, + task: URLSessionTask, + didSendBodyData bytesSent: Int64, + totalBytesSent: Int64, + totalBytesExpectedToSend: Int64) { + performEvent { + $0.urlSession(session, + task: task, + didSendBodyData: bytesSent, + totalBytesSent: totalBytesSent, + totalBytesExpectedToSend: totalBytesExpectedToSend) + } + } + + public func urlSession(_ session: URLSession, taskNeedsNewBodyStream task: URLSessionTask) { + performEvent { + $0.urlSession(session, taskNeedsNewBodyStream: task) + } + } + + public func urlSession(_ session: URLSession, + task: URLSessionTask, + willPerformHTTPRedirection response: HTTPURLResponse, + newRequest request: URLRequest) { + performEvent { + $0.urlSession(session, + task: task, + willPerformHTTPRedirection: response, + newRequest: request) + } + } + + public func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) { + performEvent { $0.urlSession(session, task: task, didFinishCollecting: metrics) } + } + + public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { + performEvent { $0.urlSession(session, task: task, didCompleteWithError: error) } + } + + @available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4.0, *) + public func urlSession(_ session: URLSession, taskIsWaitingForConnectivity task: URLSessionTask) { + performEvent { $0.urlSession(session, taskIsWaitingForConnectivity: task) } + } + + public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { + performEvent { $0.urlSession(session, dataTask: dataTask, didReceive: data) } + } + + public func urlSession(_ session: URLSession, + dataTask: URLSessionDataTask, + willCacheResponse proposedResponse: CachedURLResponse) { + performEvent { $0.urlSession(session, dataTask: dataTask, willCacheResponse: proposedResponse) } + } + + public func urlSession(_ session: URLSession, + downloadTask: URLSessionDownloadTask, + didResumeAtOffset fileOffset: Int64, + expectedTotalBytes: Int64) { + performEvent { + $0.urlSession(session, + downloadTask: downloadTask, + didResumeAtOffset: fileOffset, + expectedTotalBytes: expectedTotalBytes) + } + } + + public func urlSession(_ session: URLSession, + downloadTask: URLSessionDownloadTask, + didWriteData bytesWritten: Int64, + totalBytesWritten: Int64, + totalBytesExpectedToWrite: Int64) { + performEvent { + $0.urlSession(session, + downloadTask: downloadTask, + didWriteData: bytesWritten, + totalBytesWritten: totalBytesWritten, + totalBytesExpectedToWrite: totalBytesExpectedToWrite) + } + } + + public func urlSession(_ session: URLSession, + downloadTask: URLSessionDownloadTask, + didFinishDownloadingTo location: URL) { + performEvent { $0.urlSession(session, downloadTask: downloadTask, didFinishDownloadingTo: location) } + } + + public func request(_ request: Request, didCreateInitialURLRequest urlRequest: URLRequest) { + performEvent { $0.request(request, didCreateInitialURLRequest: urlRequest) } + } + + public func request(_ request: Request, didFailToCreateURLRequestWithError error: AFError) { + performEvent { $0.request(request, didFailToCreateURLRequestWithError: error) } + } + + public func request(_ request: Request, didAdaptInitialRequest initialRequest: URLRequest, to adaptedRequest: URLRequest) { + performEvent { $0.request(request, didAdaptInitialRequest: initialRequest, to: adaptedRequest) } + } + + public func request(_ request: Request, didFailToAdaptURLRequest initialRequest: URLRequest, withError error: AFError) { + performEvent { $0.request(request, didFailToAdaptURLRequest: initialRequest, withError: error) } + } + + public func request(_ request: Request, didCreateURLRequest urlRequest: URLRequest) { + performEvent { $0.request(request, didCreateURLRequest: urlRequest) } + } + + public func request(_ request: Request, didCreateTask task: URLSessionTask) { + performEvent { $0.request(request, didCreateTask: task) } + } + + public func request(_ request: Request, didGatherMetrics metrics: URLSessionTaskMetrics) { + performEvent { $0.request(request, didGatherMetrics: metrics) } + } + + public func request(_ request: Request, didFailTask task: URLSessionTask, earlyWithError error: AFError) { + performEvent { $0.request(request, didFailTask: task, earlyWithError: error) } + } + + public func request(_ request: Request, didCompleteTask task: URLSessionTask, with error: AFError?) { + performEvent { $0.request(request, didCompleteTask: task, with: error) } + } + + public func requestIsRetrying(_ request: Request) { + performEvent { $0.requestIsRetrying(request) } + } + + public func requestDidFinish(_ request: Request) { + performEvent { $0.requestDidFinish(request) } + } + + public func requestDidResume(_ request: Request) { + performEvent { $0.requestDidResume(request) } + } + + public func request(_ request: Request, didResumeTask task: URLSessionTask) { + performEvent { $0.request(request, didResumeTask: task) } + } + + public func requestDidSuspend(_ request: Request) { + performEvent { $0.requestDidSuspend(request) } + } + + public func request(_ request: Request, didSuspendTask task: URLSessionTask) { + performEvent { $0.request(request, didSuspendTask: task) } + } + + public func requestDidCancel(_ request: Request) { + performEvent { $0.requestDidCancel(request) } + } + + public func request(_ request: Request, didCancelTask task: URLSessionTask) { + performEvent { $0.request(request, didCancelTask: task) } + } + + public func request(_ request: DataRequest, + didValidateRequest urlRequest: URLRequest?, + response: HTTPURLResponse, + data: Data?, + withResult result: Request.ValidationResult) { + performEvent { $0.request(request, + didValidateRequest: urlRequest, + response: response, + data: data, + withResult: result) + } + } + + public func request(_ request: DataRequest, didParseResponse response: DataResponse) { + performEvent { $0.request(request, didParseResponse: response) } + } + + public func request(_ request: DataRequest, didParseResponse response: DataResponse) { + performEvent { $0.request(request, didParseResponse: response) } + } + + public func request(_ request: DataStreamRequest, + didValidateRequest urlRequest: URLRequest?, + response: HTTPURLResponse, + withResult result: Request.ValidationResult) { + performEvent { $0.request(request, + didValidateRequest: urlRequest, + response: response, + withResult: result) + } + } + + public func request(_ request: DataStreamRequest, didParseStream result: Result) { + performEvent { $0.request(request, didParseStream: result) } + } + + public func request(_ request: UploadRequest, didCreateUploadable uploadable: UploadRequest.Uploadable) { + performEvent { $0.request(request, didCreateUploadable: uploadable) } + } + + public func request(_ request: UploadRequest, didFailToCreateUploadableWithError error: AFError) { + performEvent { $0.request(request, didFailToCreateUploadableWithError: error) } + } + + public func request(_ request: UploadRequest, didProvideInputStream stream: InputStream) { + performEvent { $0.request(request, didProvideInputStream: stream) } + } + + public func request(_ request: DownloadRequest, didFinishDownloadingUsing task: URLSessionTask, with result: Result) { + performEvent { $0.request(request, didFinishDownloadingUsing: task, with: result) } + } + + public func request(_ request: DownloadRequest, didCreateDestinationURL url: URL) { + performEvent { $0.request(request, didCreateDestinationURL: url) } + } + + public func request(_ request: DownloadRequest, + didValidateRequest urlRequest: URLRequest?, + response: HTTPURLResponse, + fileURL: URL?, + withResult result: Request.ValidationResult) { + performEvent { $0.request(request, + didValidateRequest: urlRequest, + response: response, + fileURL: fileURL, + withResult: result) } + } + + public func request(_ request: DownloadRequest, didParseResponse response: DownloadResponse) { + performEvent { $0.request(request, didParseResponse: response) } + } + + public func request(_ request: DownloadRequest, didParseResponse response: DownloadResponse) { + performEvent { $0.request(request, didParseResponse: response) } + } +} + +/// `EventMonitor` that allows optional closures to be set to receive events. +open class ClosureEventMonitor: EventMonitor { + /// Closure called on the `urlSession(_:didBecomeInvalidWithError:)` event. + open var sessionDidBecomeInvalidWithError: ((URLSession, Error?) -> Void)? + + /// Closure called on the `urlSession(_:task:didReceive:completionHandler:)`. + open var taskDidReceiveChallenge: ((URLSession, URLSessionTask, URLAuthenticationChallenge) -> Void)? + + /// Closure that receives `urlSession(_:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:)` event. + open var taskDidSendBodyData: ((URLSession, URLSessionTask, Int64, Int64, Int64) -> Void)? + + /// Closure called on the `urlSession(_:task:needNewBodyStream:)` event. + open var taskNeedNewBodyStream: ((URLSession, URLSessionTask) -> Void)? + + /// Closure called on the `urlSession(_:task:willPerformHTTPRedirection:newRequest:completionHandler:)` event. + open var taskWillPerformHTTPRedirection: ((URLSession, URLSessionTask, HTTPURLResponse, URLRequest) -> Void)? + + /// Closure called on the `urlSession(_:task:didFinishCollecting:)` event. + open var taskDidFinishCollectingMetrics: ((URLSession, URLSessionTask, URLSessionTaskMetrics) -> Void)? + + /// Closure called on the `urlSession(_:task:didCompleteWithError:)` event. + open var taskDidComplete: ((URLSession, URLSessionTask, Error?) -> Void)? + + /// Closure called on the `urlSession(_:taskIsWaitingForConnectivity:)` event. + open var taskIsWaitingForConnectivity: ((URLSession, URLSessionTask) -> Void)? + + /// Closure that receives the `urlSession(_:dataTask:didReceive:)` event. + open var dataTaskDidReceiveData: ((URLSession, URLSessionDataTask, Data) -> Void)? + + /// Closure called on the `urlSession(_:dataTask:willCacheResponse:completionHandler:)` event. + open var dataTaskWillCacheResponse: ((URLSession, URLSessionDataTask, CachedURLResponse) -> Void)? + + /// Closure called on the `urlSession(_:downloadTask:didFinishDownloadingTo:)` event. + open var downloadTaskDidFinishDownloadingToURL: ((URLSession, URLSessionDownloadTask, URL) -> Void)? + + /// Closure called on the `urlSession(_:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:)` + /// event. + open var downloadTaskDidWriteData: ((URLSession, URLSessionDownloadTask, Int64, Int64, Int64) -> Void)? + + /// Closure called on the `urlSession(_:downloadTask:didResumeAtOffset:expectedTotalBytes:)` event. + open var downloadTaskDidResumeAtOffset: ((URLSession, URLSessionDownloadTask, Int64, Int64) -> Void)? + + // MARK: - Request Events + + /// Closure called on the `request(_:didCreateInitialURLRequest:)` event. + open var requestDidCreateInitialURLRequest: ((Request, URLRequest) -> Void)? + + /// Closure called on the `request(_:didFailToCreateURLRequestWithError:)` event. + open var requestDidFailToCreateURLRequestWithError: ((Request, AFError) -> Void)? + + /// Closure called on the `request(_:didAdaptInitialRequest:to:)` event. + open var requestDidAdaptInitialRequestToAdaptedRequest: ((Request, URLRequest, URLRequest) -> Void)? + + /// Closure called on the `request(_:didFailToAdaptURLRequest:withError:)` event. + open var requestDidFailToAdaptURLRequestWithError: ((Request, URLRequest, AFError) -> Void)? + + /// Closure called on the `request(_:didCreateURLRequest:)` event. + open var requestDidCreateURLRequest: ((Request, URLRequest) -> Void)? + + /// Closure called on the `request(_:didCreateTask:)` event. + open var requestDidCreateTask: ((Request, URLSessionTask) -> Void)? + + /// Closure called on the `request(_:didGatherMetrics:)` event. + open var requestDidGatherMetrics: ((Request, URLSessionTaskMetrics) -> Void)? + + /// Closure called on the `request(_:didFailTask:earlyWithError:)` event. + open var requestDidFailTaskEarlyWithError: ((Request, URLSessionTask, AFError) -> Void)? + + /// Closure called on the `request(_:didCompleteTask:with:)` event. + open var requestDidCompleteTaskWithError: ((Request, URLSessionTask, AFError?) -> Void)? + + /// Closure called on the `requestIsRetrying(_:)` event. + open var requestIsRetrying: ((Request) -> Void)? + + /// Closure called on the `requestDidFinish(_:)` event. + open var requestDidFinish: ((Request) -> Void)? + + /// Closure called on the `requestDidResume(_:)` event. + open var requestDidResume: ((Request) -> Void)? + + /// Closure called on the `request(_:didResumeTask:)` event. + open var requestDidResumeTask: ((Request, URLSessionTask) -> Void)? + + /// Closure called on the `requestDidSuspend(_:)` event. + open var requestDidSuspend: ((Request) -> Void)? + + /// Closure called on the `request(_:didSuspendTask:)` event. + open var requestDidSuspendTask: ((Request, URLSessionTask) -> Void)? + + /// Closure called on the `requestDidCancel(_:)` event. + open var requestDidCancel: ((Request) -> Void)? + + /// Closure called on the `request(_:didCancelTask:)` event. + open var requestDidCancelTask: ((Request, URLSessionTask) -> Void)? + + /// Closure called on the `request(_:didValidateRequest:response:data:withResult:)` event. + open var requestDidValidateRequestResponseDataWithResult: ((DataRequest, URLRequest?, HTTPURLResponse, Data?, Request.ValidationResult) -> Void)? + + /// Closure called on the `request(_:didParseResponse:)` event. + open var requestDidParseResponse: ((DataRequest, DataResponse) -> Void)? + + /// Closure called on the `request(_:didValidateRequest:response:withResult:)` event. + open var requestDidValidateRequestResponseWithResult: ((DataStreamRequest, URLRequest?, HTTPURLResponse, Request.ValidationResult) -> Void)? + + /// Closure called on the `request(_:didCreateUploadable:)` event. + open var requestDidCreateUploadable: ((UploadRequest, UploadRequest.Uploadable) -> Void)? + + /// Closure called on the `request(_:didFailToCreateUploadableWithError:)` event. + open var requestDidFailToCreateUploadableWithError: ((UploadRequest, AFError) -> Void)? + + /// Closure called on the `request(_:didProvideInputStream:)` event. + open var requestDidProvideInputStream: ((UploadRequest, InputStream) -> Void)? + + /// Closure called on the `request(_:didFinishDownloadingUsing:with:)` event. + open var requestDidFinishDownloadingUsingTaskWithResult: ((DownloadRequest, URLSessionTask, Result) -> Void)? + + /// Closure called on the `request(_:didCreateDestinationURL:)` event. + open var requestDidCreateDestinationURL: ((DownloadRequest, URL) -> Void)? + + /// Closure called on the `request(_:didValidateRequest:response:temporaryURL:destinationURL:withResult:)` event. + open var requestDidValidateRequestResponseFileURLWithResult: ((DownloadRequest, URLRequest?, HTTPURLResponse, URL?, Request.ValidationResult) -> Void)? + + /// Closure called on the `request(_:didParseResponse:)` event. + open var requestDidParseDownloadResponse: ((DownloadRequest, DownloadResponse) -> Void)? + + public let queue: DispatchQueue + + /// Creates an instance using the provided queue. + /// + /// - Parameter queue: `DispatchQueue` on which events will fired. `.main` by default. + public init(queue: DispatchQueue = .main) { + self.queue = queue + } + + open func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) { + sessionDidBecomeInvalidWithError?(session, error) + } + + open func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge) { + taskDidReceiveChallenge?(session, task, challenge) + } + + open func urlSession(_ session: URLSession, + task: URLSessionTask, + didSendBodyData bytesSent: Int64, + totalBytesSent: Int64, + totalBytesExpectedToSend: Int64) { + taskDidSendBodyData?(session, task, bytesSent, totalBytesSent, totalBytesExpectedToSend) + } + + open func urlSession(_ session: URLSession, taskNeedsNewBodyStream task: URLSessionTask) { + taskNeedNewBodyStream?(session, task) + } + + open func urlSession(_ session: URLSession, + task: URLSessionTask, + willPerformHTTPRedirection response: HTTPURLResponse, + newRequest request: URLRequest) { + taskWillPerformHTTPRedirection?(session, task, response, request) + } + + open func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) { + taskDidFinishCollectingMetrics?(session, task, metrics) + } + + open func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { + taskDidComplete?(session, task, error) + } + + open func urlSession(_ session: URLSession, taskIsWaitingForConnectivity task: URLSessionTask) { + taskIsWaitingForConnectivity?(session, task) + } + + open func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { + dataTaskDidReceiveData?(session, dataTask, data) + } + + open func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, willCacheResponse proposedResponse: CachedURLResponse) { + dataTaskWillCacheResponse?(session, dataTask, proposedResponse) + } + + open func urlSession(_ session: URLSession, + downloadTask: URLSessionDownloadTask, + didResumeAtOffset fileOffset: Int64, + expectedTotalBytes: Int64) { + downloadTaskDidResumeAtOffset?(session, downloadTask, fileOffset, expectedTotalBytes) + } + + open func urlSession(_ session: URLSession, + downloadTask: URLSessionDownloadTask, + didWriteData bytesWritten: Int64, + totalBytesWritten: Int64, + totalBytesExpectedToWrite: Int64) { + downloadTaskDidWriteData?(session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite) + } + + open func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) { + downloadTaskDidFinishDownloadingToURL?(session, downloadTask, location) + } + + // MARK: Request Events + + open func request(_ request: Request, didCreateInitialURLRequest urlRequest: URLRequest) { + requestDidCreateInitialURLRequest?(request, urlRequest) + } + + open func request(_ request: Request, didFailToCreateURLRequestWithError error: AFError) { + requestDidFailToCreateURLRequestWithError?(request, error) + } + + open func request(_ request: Request, didAdaptInitialRequest initialRequest: URLRequest, to adaptedRequest: URLRequest) { + requestDidAdaptInitialRequestToAdaptedRequest?(request, initialRequest, adaptedRequest) + } + + open func request(_ request: Request, didFailToAdaptURLRequest initialRequest: URLRequest, withError error: AFError) { + requestDidFailToAdaptURLRequestWithError?(request, initialRequest, error) + } + + open func request(_ request: Request, didCreateURLRequest urlRequest: URLRequest) { + requestDidCreateURLRequest?(request, urlRequest) + } + + open func request(_ request: Request, didCreateTask task: URLSessionTask) { + requestDidCreateTask?(request, task) + } + + open func request(_ request: Request, didGatherMetrics metrics: URLSessionTaskMetrics) { + requestDidGatherMetrics?(request, metrics) + } + + open func request(_ request: Request, didFailTask task: URLSessionTask, earlyWithError error: AFError) { + requestDidFailTaskEarlyWithError?(request, task, error) + } + + open func request(_ request: Request, didCompleteTask task: URLSessionTask, with error: AFError?) { + requestDidCompleteTaskWithError?(request, task, error) + } + + open func requestIsRetrying(_ request: Request) { + requestIsRetrying?(request) + } + + open func requestDidFinish(_ request: Request) { + requestDidFinish?(request) + } + + open func requestDidResume(_ request: Request) { + requestDidResume?(request) + } + + public func request(_ request: Request, didResumeTask task: URLSessionTask) { + requestDidResumeTask?(request, task) + } + + open func requestDidSuspend(_ request: Request) { + requestDidSuspend?(request) + } + + public func request(_ request: Request, didSuspendTask task: URLSessionTask) { + requestDidSuspendTask?(request, task) + } + + open func requestDidCancel(_ request: Request) { + requestDidCancel?(request) + } + + public func request(_ request: Request, didCancelTask task: URLSessionTask) { + requestDidCancelTask?(request, task) + } + + open func request(_ request: DataRequest, + didValidateRequest urlRequest: URLRequest?, + response: HTTPURLResponse, + data: Data?, + withResult result: Request.ValidationResult) { + requestDidValidateRequestResponseDataWithResult?(request, urlRequest, response, data, result) + } + + open func request(_ request: DataRequest, didParseResponse response: DataResponse) { + requestDidParseResponse?(request, response) + } + + public func request(_ request: DataStreamRequest, didValidateRequest urlRequest: URLRequest?, response: HTTPURLResponse, withResult result: Request.ValidationResult) { + requestDidValidateRequestResponseWithResult?(request, urlRequest, response, result) + } + + open func request(_ request: UploadRequest, didCreateUploadable uploadable: UploadRequest.Uploadable) { + requestDidCreateUploadable?(request, uploadable) + } + + open func request(_ request: UploadRequest, didFailToCreateUploadableWithError error: AFError) { + requestDidFailToCreateUploadableWithError?(request, error) + } + + open func request(_ request: UploadRequest, didProvideInputStream stream: InputStream) { + requestDidProvideInputStream?(request, stream) + } + + open func request(_ request: DownloadRequest, didFinishDownloadingUsing task: URLSessionTask, with result: Result) { + requestDidFinishDownloadingUsingTaskWithResult?(request, task, result) + } + + open func request(_ request: DownloadRequest, didCreateDestinationURL url: URL) { + requestDidCreateDestinationURL?(request, url) + } + + open func request(_ request: DownloadRequest, + didValidateRequest urlRequest: URLRequest?, + response: HTTPURLResponse, + fileURL: URL?, + withResult result: Request.ValidationResult) { + requestDidValidateRequestResponseFileURLWithResult?(request, + urlRequest, + response, + fileURL, + result) + } + + open func request(_ request: DownloadRequest, didParseResponse response: DownloadResponse) { + requestDidParseDownloadResponse?(request, response) + } +} diff --git b/Pods/Alamofire/Source/HTTPHeaders.swift a/Pods/Alamofire/Source/HTTPHeaders.swift new file mode 100644 index 0000000..4c32704 --- /dev/null +++ a/Pods/Alamofire/Source/HTTPHeaders.swift @@ -0,0 +1,442 @@ +// +// HTTPHeaders.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 + +/// An order-preserving and case-insensitive representation of HTTP headers. +public struct HTTPHeaders { + private var headers: [HTTPHeader] = [] + + /// Creates an empty instance. + public init() {} + + /// Creates an instance from an array of `HTTPHeader`s. Duplicate case-insensitive names are collapsed into the last + /// name and value encountered. + public init(_ headers: [HTTPHeader]) { + self.init() + + headers.forEach { update($0) } + } + + /// Creates an instance from a `[String: String]`. Duplicate case-insensitive names are collapsed into the last name + /// and value encountered. + public init(_ dictionary: [String: String]) { + self.init() + + dictionary.forEach { update(HTTPHeader(name: $0.key, value: $0.value)) } + } + + /// Case-insensitively updates or appends an `HTTPHeader` into the instance using the provided `name` and `value`. + /// + /// - Parameters: + /// - name: The `HTTPHeader` name. + /// - value: The `HTTPHeader value. + public mutating func add(name: String, value: String) { + update(HTTPHeader(name: name, value: value)) + } + + /// Case-insensitively updates or appends the provided `HTTPHeader` into the instance. + /// + /// - Parameter header: The `HTTPHeader` to update or append. + public mutating func add(_ header: HTTPHeader) { + update(header) + } + + /// Case-insensitively updates or appends an `HTTPHeader` into the instance using the provided `name` and `value`. + /// + /// - Parameters: + /// - name: The `HTTPHeader` name. + /// - value: The `HTTPHeader value. + public mutating func update(name: String, value: String) { + update(HTTPHeader(name: name, value: value)) + } + + /// Case-insensitively updates or appends the provided `HTTPHeader` into the instance. + /// + /// - Parameter header: The `HTTPHeader` to update or append. + public mutating func update(_ header: HTTPHeader) { + guard let index = headers.index(of: header.name) else { + headers.append(header) + return + } + + headers.replaceSubrange(index...index, with: [header]) + } + + /// Case-insensitively removes an `HTTPHeader`, if it exists, from the instance. + /// + /// - Parameter name: The name of the `HTTPHeader` to remove. + public mutating func remove(name: String) { + guard let index = headers.index(of: name) else { return } + + headers.remove(at: index) + } + + /// Sort the current instance by header name. + public mutating func sort() { + headers.sort { $0.name < $1.name } + } + + /// Returns an instance sorted by header name. + /// + /// - Returns: A copy of the current instance sorted by name. + public func sorted() -> HTTPHeaders { + HTTPHeaders(headers.sorted { $0.name < $1.name }) + } + + /// Case-insensitively find a header's value by name. + /// + /// - Parameter name: The name of the header to search for, case-insensitively. + /// + /// - Returns: The value of header, if it exists. + public func value(for name: String) -> String? { + guard let index = headers.index(of: name) else { return nil } + + return headers[index].value + } + + /// Case-insensitively access the header with the given name. + /// + /// - Parameter name: The name of the header. + public subscript(_ name: String) -> String? { + get { value(for: name) } + set { + if let value = newValue { + update(name: name, value: value) + } else { + remove(name: name) + } + } + } + + /// The dictionary representation of all headers. + /// + /// This representation does not preserve the current order of the instance. + public var dictionary: [String: String] { + let namesAndValues = headers.map { ($0.name, $0.value) } + + return Dictionary(namesAndValues, uniquingKeysWith: { _, last in last }) + } +} + +extension HTTPHeaders: ExpressibleByDictionaryLiteral { + public init(dictionaryLiteral elements: (String, String)...) { + self.init() + + elements.forEach { update(name: $0.0, value: $0.1) } + } +} + +extension HTTPHeaders: ExpressibleByArrayLiteral { + public init(arrayLiteral elements: HTTPHeader...) { + self.init(elements) + } +} + +extension HTTPHeaders: Sequence { + public func makeIterator() -> IndexingIterator<[HTTPHeader]> { + headers.makeIterator() + } +} + +extension HTTPHeaders: Collection { + public var startIndex: Int { + headers.startIndex + } + + public var endIndex: Int { + headers.endIndex + } + + public subscript(position: Int) -> HTTPHeader { + headers[position] + } + + public func index(after i: Int) -> Int { + headers.index(after: i) + } +} + +extension HTTPHeaders: CustomStringConvertible { + public var description: String { + headers.map { $0.description } + .joined(separator: "\n") + } +} + +// MARK: - HTTPHeader + +/// A representation of a single HTTP header's name / value pair. +public struct HTTPHeader: Hashable { + /// Name of the header. + public let name: String + + /// Value of the header. + public let value: String + + /// Creates an instance from the given `name` and `value`. + /// + /// - Parameters: + /// - name: The name of the header. + /// - value: The value of the header. + public init(name: String, value: String) { + self.name = name + self.value = value + } +} + +extension HTTPHeader: CustomStringConvertible { + public var description: String { + "\(name): \(value)" + } +} + +extension HTTPHeader { + /// Returns an `Accept` header. + /// + /// - Parameter value: The `Accept` value. + /// - Returns: The header. + public static func accept(_ value: String) -> HTTPHeader { + HTTPHeader(name: "Accept", value: value) + } + + /// Returns an `Accept-Charset` header. + /// + /// - Parameter value: The `Accept-Charset` value. + /// - Returns: The header. + public static func acceptCharset(_ value: String) -> HTTPHeader { + HTTPHeader(name: "Accept-Charset", value: value) + } + + /// Returns an `Accept-Language` header. + /// + /// Alamofire offers a default Accept-Language header that accumulates and encodes the system's preferred languages. + /// Use `HTTPHeader.defaultAcceptLanguage`. + /// + /// - Parameter value: The `Accept-Language` value. + /// + /// - Returns: The header. + public static func acceptLanguage(_ value: String) -> HTTPHeader { + HTTPHeader(name: "Accept-Language", value: value) + } + + /// Returns an `Accept-Encoding` header. + /// + /// Alamofire offers a default accept encoding value that provides the most common values. Use + /// `HTTPHeader.defaultAcceptEncoding`. + /// + /// - Parameter value: The `Accept-Encoding` value. + /// + /// - Returns: The header + public static func acceptEncoding(_ value: String) -> HTTPHeader { + HTTPHeader(name: "Accept-Encoding", value: value) + } + + /// Returns a `Basic` `Authorization` header using the `username` and `password` provided. + /// + /// - Parameters: + /// - username: The username of the header. + /// - password: The password of the header. + /// + /// - Returns: The header. + public static func authorization(username: String, password: String) -> HTTPHeader { + let credential = Data("\(username):\(password)".utf8).base64EncodedString() + + return authorization("Basic \(credential)") + } + + /// Returns a `Bearer` `Authorization` header using the `bearerToken` provided + /// + /// - Parameter bearerToken: The bearer token. + /// + /// - Returns: The header. + public static func authorization(bearerToken: String) -> HTTPHeader { + authorization("Bearer \(bearerToken)") + } + + /// Returns an `Authorization` header. + /// + /// Alamofire provides built-in methods to produce `Authorization` headers. For a Basic `Authorization` header use + /// `HTTPHeader.authorization(username:password:)`. For a Bearer `Authorization` header, use + /// `HTTPHeader.authorization(bearerToken:)`. + /// + /// - Parameter value: The `Authorization` value. + /// + /// - Returns: The header. + public static func authorization(_ value: String) -> HTTPHeader { + HTTPHeader(name: "Authorization", value: value) + } + + /// Returns a `Content-Disposition` header. + /// + /// - Parameter value: The `Content-Disposition` value. + /// + /// - Returns: The header. + public static func contentDisposition(_ value: String) -> HTTPHeader { + HTTPHeader(name: "Content-Disposition", value: value) + } + + /// Returns a `Content-Type` header. + /// + /// All Alamofire `ParameterEncoding`s and `ParameterEncoder`s set the `Content-Type` of the request, so it may not be necessary to manually + /// set this value. + /// + /// - Parameter value: The `Content-Type` value. + /// + /// - Returns: The header. + public static func contentType(_ value: String) -> HTTPHeader { + HTTPHeader(name: "Content-Type", value: value) + } + + /// Returns a `User-Agent` header. + /// + /// - Parameter value: The `User-Agent` value. + /// + /// - Returns: The header. + public static func userAgent(_ value: String) -> HTTPHeader { + HTTPHeader(name: "User-Agent", value: value) + } +} + +extension Array where Element == HTTPHeader { + /// Case-insensitively finds the index of an `HTTPHeader` with the provided name, if it exists. + func index(of name: String) -> Int? { + let lowercasedName = name.lowercased() + return firstIndex { $0.name.lowercased() == lowercasedName } + } +} + +// MARK: - Defaults + +public extension HTTPHeaders { + /// The default set of `HTTPHeaders` used by Alamofire. Includes `Accept-Encoding`, `Accept-Language`, and + /// `User-Agent`. + static let `default`: HTTPHeaders = [.defaultAcceptEncoding, + .defaultAcceptLanguage, + .defaultUserAgent] +} + +extension HTTPHeader { + /// Returns Alamofire's default `Accept-Encoding` header, appropriate for the encodings supported by particular OS + /// versions. + /// + /// See the [Accept-Encoding HTTP header documentation](https://tools.ietf.org/html/rfc7230#section-4.2.3) . + public static let defaultAcceptEncoding: HTTPHeader = { + let encodings: [String] + if #available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, *) { + encodings = ["br", "gzip", "deflate"] + } else { + encodings = ["gzip", "deflate"] + } + + return .acceptEncoding(encodings.qualityEncoded()) + }() + + /// Returns Alamofire's default `Accept-Language` header, generated by querying `Locale` for the user's + /// `preferredLanguages`. + /// + /// See the [Accept-Language HTTP header documentation](https://tools.ietf.org/html/rfc7231#section-5.3.5). + public static let defaultAcceptLanguage: HTTPHeader = { + .acceptLanguage(Locale.preferredLanguages.prefix(6).qualityEncoded()) + }() + + /// Returns Alamofire's default `User-Agent` header. + /// + /// See the [User-Agent header documentation](https://tools.ietf.org/html/rfc7231#section-5.5.3). + /// + /// Example: `iOS Example/1.0 (org.alamofire.iOS-Example; build:1; iOS 13.0.0) Alamofire/5.0.0` + public static let defaultUserAgent: HTTPHeader = { + let info = Bundle.main.infoDictionary + let executable = (info?[kCFBundleExecutableKey as String] as? String) ?? + (ProcessInfo.processInfo.arguments.first?.split(separator: "/").last.map(String.init)) ?? + "Unknown" + let bundle = info?[kCFBundleIdentifierKey as String] as? String ?? "Unknown" + let appVersion = info?["CFBundleShortVersionString"] as? String ?? "Unknown" + let appBuild = info?[kCFBundleVersionKey as String] as? String ?? "Unknown" + + let osNameVersion: String = { + let version = ProcessInfo.processInfo.operatingSystemVersion + let versionString = "\(version.majorVersion).\(version.minorVersion).\(version.patchVersion)" + let osName: String = { + #if os(iOS) + return "iOS" + #elseif os(watchOS) + return "watchOS" + #elseif os(tvOS) + return "tvOS" + #elseif os(macOS) + return "macOS" + #elseif os(Linux) + return "Linux" + #elseif os(Windows) + return "Windows" + #else + return "Unknown" + #endif + }() + + return "\(osName) \(versionString)" + }() + + let alamofireVersion = "Alamofire/\(version)" + + let userAgent = "\(executable)/\(appVersion) (\(bundle); build:\(appBuild); \(osNameVersion)) \(alamofireVersion)" + + return .userAgent(userAgent) + }() +} + +extension Collection where Element == String { + func qualityEncoded() -> String { + enumerated().map { index, encoding in + let quality = 1.0 - (Double(index) * 0.1) + return "\(encoding);q=\(quality)" + }.joined(separator: ", ") + } +} + +// MARK: - System Type Extensions + +extension URLRequest { + /// Returns `allHTTPHeaderFields` as `HTTPHeaders`. + public var headers: HTTPHeaders { + get { allHTTPHeaderFields.map(HTTPHeaders.init) ?? HTTPHeaders() } + set { allHTTPHeaderFields = newValue.dictionary } + } +} + +extension HTTPURLResponse { + /// Returns `allHeaderFields` as `HTTPHeaders`. + public var headers: HTTPHeaders { + (allHeaderFields as? [String: String]).map(HTTPHeaders.init) ?? HTTPHeaders() + } +} + +public extension URLSessionConfiguration { + /// Returns `httpAdditionalHeaders` as `HTTPHeaders`. + var headers: HTTPHeaders { + get { (httpAdditionalHeaders as? [String: String]).map(HTTPHeaders.init) ?? HTTPHeaders() } + set { httpAdditionalHeaders = newValue.dictionary } + } +} diff --git b/Pods/Alamofire/Source/HTTPMethod.swift a/Pods/Alamofire/Source/HTTPMethod.swift new file mode 100644 index 0000000..4867c1e --- /dev/null +++ a/Pods/Alamofire/Source/HTTPMethod.swift @@ -0,0 +1,54 @@ +// +// 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 + } +} diff --git b/Pods/Alamofire/Source/MultipartFormData.swift a/Pods/Alamofire/Source/MultipartFormData.swift new file mode 100644 index 0000000..e9ef6a6 --- /dev/null +++ a/Pods/Alamofire/Source/MultipartFormData.swift @@ -0,0 +1,547 @@ +// +// MultipartFormData.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 + +#if os(iOS) || os(watchOS) || os(tvOS) +import MobileCoreServices +#elseif os(macOS) +import CoreServices +#endif + +/// Constructs `multipart/form-data` for uploads within an HTTP or HTTPS body. There are currently two ways to encode +/// multipart form data. The first way is to encode the data directly in memory. This is very efficient, but can lead +/// to memory issues if the dataset is too large. The second way is designed for larger datasets and will write all the +/// data to a single file on disk with all the proper boundary segmentation. The second approach MUST be used for +/// larger datasets such as video content, otherwise your app may run out of memory when trying to encode the dataset. +/// +/// For more information on `multipart/form-data` in general, please refer to the RFC-2388 and RFC-2045 specs as well +/// and the w3 form documentation. +/// +/// - https://www.ietf.org/rfc/rfc2388.txt +/// - https://www.ietf.org/rfc/rfc2045.txt +/// - https://www.w3.org/TR/html401/interact/forms.html#h-17.13 +open class MultipartFormData { + // MARK: - Helper Types + + struct EncodingCharacters { + static let crlf = "\r\n" + } + + struct BoundaryGenerator { + enum BoundaryType { + case initial, encapsulated, final + } + + static func randomBoundary() -> String { + let first = UInt32.random(in: UInt32.min...UInt32.max) + let second = UInt32.random(in: UInt32.min...UInt32.max) + + return String(format: "alamofire.boundary.%08x%08x", first, second) + } + + static func boundaryData(forBoundaryType boundaryType: BoundaryType, boundary: String) -> Data { + let boundaryText: String + + switch boundaryType { + case .initial: + boundaryText = "--\(boundary)\(EncodingCharacters.crlf)" + case .encapsulated: + boundaryText = "\(EncodingCharacters.crlf)--\(boundary)\(EncodingCharacters.crlf)" + case .final: + boundaryText = "\(EncodingCharacters.crlf)--\(boundary)--\(EncodingCharacters.crlf)" + } + + return Data(boundaryText.utf8) + } + } + + class BodyPart { + let headers: HTTPHeaders + let bodyStream: InputStream + let bodyContentLength: UInt64 + var hasInitialBoundary = false + var hasFinalBoundary = false + + init(headers: HTTPHeaders, bodyStream: InputStream, bodyContentLength: UInt64) { + self.headers = headers + self.bodyStream = bodyStream + self.bodyContentLength = bodyContentLength + } + } + + // MARK: - Properties + + /// Default memory threshold used when encoding `MultipartFormData`, in bytes. + public static let encodingMemoryThreshold: UInt64 = 10_000_000 + + /// The `Content-Type` header value containing the boundary used to generate the `multipart/form-data`. + open lazy var contentType: String = "multipart/form-data; boundary=\(self.boundary)" + + /// The content length of all body parts used to generate the `multipart/form-data` not including the boundaries. + public var contentLength: UInt64 { bodyParts.reduce(0) { $0 + $1.bodyContentLength } } + + /// The boundary used to separate the body parts in the encoded form data. + public let boundary: String + + let fileManager: FileManager + + private var bodyParts: [BodyPart] + private var bodyPartError: AFError? + private let streamBufferSize: Int + + // MARK: - Lifecycle + + /// Creates an instance. + /// + /// - Parameters: + /// - fileManager: `FileManager` to use for file operations, if needed. + /// - boundary: Boundary `String` used to separate body parts. + public init(fileManager: FileManager = .default, boundary: String? = nil) { + self.fileManager = fileManager + self.boundary = boundary ?? BoundaryGenerator.randomBoundary() + bodyParts = [] + + // + // The optimal read/write buffer size in bytes for input and output streams is 1024 (1KB). For more + // information, please refer to the following article: + // - https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Streams/Articles/ReadingInputStreams.html + // + streamBufferSize = 1024 + } + + // MARK: - Body Parts + + /// Creates a body part from the data and appends it to the instance. + /// + /// The body part data will be encoded using the following format: + /// + /// - `Content-Disposition: form-data; name=#{name}; filename=#{filename}` (HTTP Header) + /// - `Content-Type: #{mimeType}` (HTTP Header) + /// - Encoded file data + /// - Multipart form boundary + /// + /// - Parameters: + /// - data: `Data` to encoding into the instance. + /// - name: Name to associate with the `Data` in the `Content-Disposition` HTTP header. + /// - fileName: Filename to associate with the `Data` in the `Content-Disposition` HTTP header. + /// - mimeType: MIME type to associate with the data in the `Content-Type` HTTP header. + public func append(_ data: Data, withName name: String, fileName: String? = nil, mimeType: String? = nil) { + let headers = contentHeaders(withName: name, fileName: fileName, mimeType: mimeType) + let stream = InputStream(data: data) + let length = UInt64(data.count) + + append(stream, withLength: length, headers: headers) + } + + /// Creates a body part from the file and appends it to the instance. + /// + /// The body part data will be encoded using the following format: + /// + /// - `Content-Disposition: form-data; name=#{name}; filename=#{generated filename}` (HTTP Header) + /// - `Content-Type: #{generated mimeType}` (HTTP Header) + /// - Encoded file data + /// - Multipart form boundary + /// + /// The filename in the `Content-Disposition` HTTP header is generated from the last path component of the + /// `fileURL`. The `Content-Type` HTTP header MIME type is generated by mapping the `fileURL` extension to the + /// system associated MIME type. + /// + /// - Parameters: + /// - fileURL: `URL` of the file whose content will be encoded into the instance. + /// - name: Name to associate with the file content in the `Content-Disposition` HTTP header. + public func append(_ fileURL: URL, withName name: String) { + let fileName = fileURL.lastPathComponent + let pathExtension = fileURL.pathExtension + + if !fileName.isEmpty && !pathExtension.isEmpty { + let mime = mimeType(forPathExtension: pathExtension) + append(fileURL, withName: name, fileName: fileName, mimeType: mime) + } else { + setBodyPartError(withReason: .bodyPartFilenameInvalid(in: fileURL)) + } + } + + /// Creates a body part from the file and appends it to the instance. + /// + /// The body part data will be encoded using the following format: + /// + /// - Content-Disposition: form-data; name=#{name}; filename=#{filename} (HTTP Header) + /// - Content-Type: #{mimeType} (HTTP Header) + /// - Encoded file data + /// - Multipart form boundary + /// + /// - Parameters: + /// - fileURL: `URL` of the file whose content will be encoded into the instance. + /// - name: Name to associate with the file content in the `Content-Disposition` HTTP header. + /// - fileName: Filename to associate with the file content in the `Content-Disposition` HTTP header. + /// - mimeType: MIME type to associate with the file content in the `Content-Type` HTTP header. + public func append(_ fileURL: URL, withName name: String, fileName: String, mimeType: String) { + let headers = contentHeaders(withName: name, fileName: fileName, mimeType: mimeType) + + //============================================================ + // Check 1 - is file URL? + //============================================================ + + guard fileURL.isFileURL else { + setBodyPartError(withReason: .bodyPartURLInvalid(url: fileURL)) + return + } + + //============================================================ + // Check 2 - is file URL reachable? + //============================================================ + + do { + let isReachable = try fileURL.checkPromisedItemIsReachable() + guard isReachable else { + setBodyPartError(withReason: .bodyPartFileNotReachable(at: fileURL)) + return + } + } catch { + setBodyPartError(withReason: .bodyPartFileNotReachableWithError(atURL: fileURL, error: error)) + return + } + + //============================================================ + // Check 3 - is file URL a directory? + //============================================================ + + var isDirectory: ObjCBool = false + let path = fileURL.path + + guard fileManager.fileExists(atPath: path, isDirectory: &isDirectory) && !isDirectory.boolValue else { + setBodyPartError(withReason: .bodyPartFileIsDirectory(at: fileURL)) + return + } + + //============================================================ + // Check 4 - can the file size be extracted? + //============================================================ + + let bodyContentLength: UInt64 + + do { + guard let fileSize = try fileManager.attributesOfItem(atPath: path)[.size] as? NSNumber else { + setBodyPartError(withReason: .bodyPartFileSizeNotAvailable(at: fileURL)) + return + } + + bodyContentLength = fileSize.uint64Value + } catch { + setBodyPartError(withReason: .bodyPartFileSizeQueryFailedWithError(forURL: fileURL, error: error)) + return + } + + //============================================================ + // Check 5 - can a stream be created from file URL? + //============================================================ + + guard let stream = InputStream(url: fileURL) else { + setBodyPartError(withReason: .bodyPartInputStreamCreationFailed(for: fileURL)) + return + } + + append(stream, withLength: bodyContentLength, headers: headers) + } + + /// Creates a body part from the stream and appends it to the instance. + /// + /// The body part data will be encoded using the following format: + /// + /// - `Content-Disposition: form-data; name=#{name}; filename=#{filename}` (HTTP Header) + /// - `Content-Type: #{mimeType}` (HTTP Header) + /// - Encoded stream data + /// - Multipart form boundary + /// + /// - Parameters: + /// - stream: `InputStream` to encode into the instance. + /// - length: Length, in bytes, of the stream. + /// - name: Name to associate with the stream content in the `Content-Disposition` HTTP header. + /// - fileName: Filename to associate with the stream content in the `Content-Disposition` HTTP header. + /// - mimeType: MIME type to associate with the stream content in the `Content-Type` HTTP header. + public func append(_ stream: InputStream, + withLength length: UInt64, + name: String, + fileName: String, + mimeType: String) { + let headers = contentHeaders(withName: name, fileName: fileName, mimeType: mimeType) + append(stream, withLength: length, headers: headers) + } + + /// Creates a body part with the stream, length, and headers and appends it to the instance. + /// + /// The body part data will be encoded using the following format: + /// + /// - HTTP headers + /// - Encoded stream data + /// - Multipart form boundary + /// + /// - Parameters: + /// - stream: `InputStream` to encode into the instance. + /// - length: Length, in bytes, of the stream. + /// - headers: `HTTPHeaders` for the body part. + public func append(_ stream: InputStream, withLength length: UInt64, headers: HTTPHeaders) { + let bodyPart = BodyPart(headers: headers, bodyStream: stream, bodyContentLength: length) + bodyParts.append(bodyPart) + } + + // MARK: - Data Encoding + + /// Encodes all appended body parts into a single `Data` value. + /// + /// - Note: This method will load all the appended body parts into memory all at the same time. This method should + /// only be used when the encoded data will have a small memory footprint. For large data cases, please use + /// the `writeEncodedData(to:))` method. + /// + /// - Returns: The encoded `Data`, if encoding is successful. + /// - Throws: An `AFError` if encoding encounters an error. + public func encode() throws -> Data { + if let bodyPartError = bodyPartError { + throw bodyPartError + } + + var encoded = Data() + + bodyParts.first?.hasInitialBoundary = true + bodyParts.last?.hasFinalBoundary = true + + for bodyPart in bodyParts { + let encodedData = try encode(bodyPart) + encoded.append(encodedData) + } + + return encoded + } + + /// Writes all appended body parts to the given file `URL`. + /// + /// This process is facilitated by reading and writing with input and output streams, respectively. Thus, + /// this approach is very memory efficient and should be used for large body part data. + /// + /// - Parameter fileURL: File `URL` to which to write the form data. + /// - Throws: An `AFError` if encoding encounters an error. + public func writeEncodedData(to fileURL: URL) throws { + if let bodyPartError = bodyPartError { + throw bodyPartError + } + + if fileManager.fileExists(atPath: fileURL.path) { + throw AFError.multipartEncodingFailed(reason: .outputStreamFileAlreadyExists(at: fileURL)) + } else if !fileURL.isFileURL { + throw AFError.multipartEncodingFailed(reason: .outputStreamURLInvalid(url: fileURL)) + } + + guard let outputStream = OutputStream(url: fileURL, append: false) else { + throw AFError.multipartEncodingFailed(reason: .outputStreamCreationFailed(for: fileURL)) + } + + outputStream.open() + defer { outputStream.close() } + + bodyParts.first?.hasInitialBoundary = true + bodyParts.last?.hasFinalBoundary = true + + for bodyPart in bodyParts { + try write(bodyPart, to: outputStream) + } + } + + // MARK: - Private - Body Part Encoding + + private func encode(_ bodyPart: BodyPart) throws -> Data { + var encoded = Data() + + let initialData = bodyPart.hasInitialBoundary ? initialBoundaryData() : encapsulatedBoundaryData() + encoded.append(initialData) + + let headerData = encodeHeaders(for: bodyPart) + encoded.append(headerData) + + let bodyStreamData = try encodeBodyStream(for: bodyPart) + encoded.append(bodyStreamData) + + if bodyPart.hasFinalBoundary { + encoded.append(finalBoundaryData()) + } + + return encoded + } + + private func encodeHeaders(for bodyPart: BodyPart) -> Data { + let headerText = bodyPart.headers.map { "\($0.name): \($0.value)\(EncodingCharacters.crlf)" } + .joined() + + EncodingCharacters.crlf + + return Data(headerText.utf8) + } + + private func encodeBodyStream(for bodyPart: BodyPart) throws -> Data { + let inputStream = bodyPart.bodyStream + inputStream.open() + defer { inputStream.close() } + + var encoded = Data() + + while inputStream.hasBytesAvailable { + var buffer = [UInt8](repeating: 0, count: streamBufferSize) + let bytesRead = inputStream.read(&buffer, maxLength: streamBufferSize) + + if let error = inputStream.streamError { + throw AFError.multipartEncodingFailed(reason: .inputStreamReadFailed(error: error)) + } + + if bytesRead > 0 { + encoded.append(buffer, count: bytesRead) + } else { + break + } + } + + return encoded + } + + // MARK: - Private - Writing Body Part to Output Stream + + private func write(_ bodyPart: BodyPart, to outputStream: OutputStream) throws { + try writeInitialBoundaryData(for: bodyPart, to: outputStream) + try writeHeaderData(for: bodyPart, to: outputStream) + try writeBodyStream(for: bodyPart, to: outputStream) + try writeFinalBoundaryData(for: bodyPart, to: outputStream) + } + + private func writeInitialBoundaryData(for bodyPart: BodyPart, to outputStream: OutputStream) throws { + let initialData = bodyPart.hasInitialBoundary ? initialBoundaryData() : encapsulatedBoundaryData() + return try write(initialData, to: outputStream) + } + + private func writeHeaderData(for bodyPart: BodyPart, to outputStream: OutputStream) throws { + let headerData = encodeHeaders(for: bodyPart) + return try write(headerData, to: outputStream) + } + + private func writeBodyStream(for bodyPart: BodyPart, to outputStream: OutputStream) throws { + let inputStream = bodyPart.bodyStream + + inputStream.open() + defer { inputStream.close() } + + while inputStream.hasBytesAvailable { + var buffer = [UInt8](repeating: 0, count: streamBufferSize) + let bytesRead = inputStream.read(&buffer, maxLength: streamBufferSize) + + if let streamError = inputStream.streamError { + throw AFError.multipartEncodingFailed(reason: .inputStreamReadFailed(error: streamError)) + } + + if bytesRead > 0 { + if buffer.count != bytesRead { + buffer = Array(buffer[0.. 0, outputStream.hasSpaceAvailable { + let bytesWritten = outputStream.write(buffer, maxLength: bytesToWrite) + + if let error = outputStream.streamError { + throw AFError.multipartEncodingFailed(reason: .outputStreamWriteFailed(error: error)) + } + + bytesToWrite -= bytesWritten + + if bytesToWrite > 0 { + buffer = Array(buffer[bytesWritten.. String { + if + let id = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, pathExtension as CFString, nil)?.takeRetainedValue(), + let contentType = UTTypeCopyPreferredTagWithClass(id, kUTTagClassMIMEType)?.takeRetainedValue() { + return contentType as String + } + + return "application/octet-stream" + } + + // MARK: - Private - Content Headers + + private func contentHeaders(withName name: String, fileName: String? = nil, mimeType: String? = nil) -> HTTPHeaders { + var disposition = "form-data; name=\"\(name)\"" + if let fileName = fileName { disposition += "; filename=\"\(fileName)\"" } + + var headers: HTTPHeaders = [.contentDisposition(disposition)] + if let mimeType = mimeType { headers.add(.contentType(mimeType)) } + + return headers + } + + // MARK: - Private - Boundary Encoding + + private func initialBoundaryData() -> Data { + BoundaryGenerator.boundaryData(forBoundaryType: .initial, boundary: boundary) + } + + private func encapsulatedBoundaryData() -> Data { + BoundaryGenerator.boundaryData(forBoundaryType: .encapsulated, boundary: boundary) + } + + private func finalBoundaryData() -> Data { + BoundaryGenerator.boundaryData(forBoundaryType: .final, boundary: boundary) + } + + // MARK: - Private - Errors + + private func setBodyPartError(withReason reason: AFError.MultipartEncodingFailureReason) { + guard bodyPartError == nil else { return } + bodyPartError = AFError.multipartEncodingFailed(reason: reason) + } +} diff --git b/Pods/Alamofire/Source/MultipartUpload.swift a/Pods/Alamofire/Source/MultipartUpload.swift new file mode 100644 index 0000000..ff129be --- /dev/null +++ a/Pods/Alamofire/Source/MultipartUpload.swift @@ -0,0 +1,87 @@ +// +// 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 { + try result.get().request + } + + func createUploadable() throws -> UploadRequest.Uploadable { + try result.get().uploadable + } +} diff --git b/Pods/Alamofire/Source/NetworkReachabilityManager.swift a/Pods/Alamofire/Source/NetworkReachabilityManager.swift new file mode 100644 index 0000000..fe3728e --- /dev/null +++ a/Pods/Alamofire/Source/NetworkReachabilityManager.swift @@ -0,0 +1,267 @@ +// +// NetworkReachabilityManager.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. +// + +#if !(os(watchOS) || os(Linux)) + +import Foundation +import SystemConfiguration + +/// The `NetworkReachabilityManager` class listens for reachability changes of hosts and addresses for both cellular and +/// WiFi network interfaces. +/// +/// Reachability can be used to determine background information about why a network operation failed, or to retry +/// network requests when a connection is established. It should not be used to prevent a user from initiating a network +/// request, as it's possible that an initial request may be required to establish reachability. +open class NetworkReachabilityManager { + /// Defines the various states of network reachability. + public enum NetworkReachabilityStatus { + /// It is unknown whether the network is reachable. + case unknown + /// The network is not reachable. + case notReachable + /// The network is reachable on the associated `ConnectionType`. + case reachable(ConnectionType) + + init(_ flags: SCNetworkReachabilityFlags) { + guard flags.isActuallyReachable else { self = .notReachable; return } + + var networkStatus: NetworkReachabilityStatus = .reachable(.ethernetOrWiFi) + + if flags.isCellular { networkStatus = .reachable(.cellular) } + + self = networkStatus + } + + /// Defines the various connection types detected by reachability flags. + public enum ConnectionType { + /// The connection type is either over Ethernet or WiFi. + case ethernetOrWiFi + /// The connection type is a cellular connection. + case cellular + } + } + + /// A closure executed when the network reachability status changes. The closure takes a single argument: the + /// network reachability status. + public typealias Listener = (NetworkReachabilityStatus) -> Void + + /// Default `NetworkReachabilityManager` for the zero address and a `listenerQueue` of `.main`. + public static let `default` = NetworkReachabilityManager() + + // MARK: - Properties + + /// Whether the network is currently reachable. + open var isReachable: Bool { isReachableOnCellular || isReachableOnEthernetOrWiFi } + + /// Whether the network is currently reachable over the cellular interface. + /// + /// - Note: Using this property to decide whether to make a high or low bandwidth request is not recommended. + /// Instead, set the `allowsCellularAccess` on any `URLRequest`s being issued. + /// + open var isReachableOnCellular: Bool { status == .reachable(.cellular) } + + /// Whether the network is currently reachable over Ethernet or WiFi interface. + open var isReachableOnEthernetOrWiFi: Bool { status == .reachable(.ethernetOrWiFi) } + + /// `DispatchQueue` on which reachability will update. + public let reachabilityQueue = DispatchQueue(label: "org.alamofire.reachabilityQueue") + + /// Flags of the current reachability type, if any. + open var flags: SCNetworkReachabilityFlags? { + var flags = SCNetworkReachabilityFlags() + + return (SCNetworkReachabilityGetFlags(reachability, &flags)) ? flags : nil + } + + /// The current network reachability status. + open var status: NetworkReachabilityStatus { + flags.map(NetworkReachabilityStatus.init) ?? .unknown + } + + /// Mutable state storage. + struct MutableState { + /// A closure executed when the network reachability status changes. + var listener: Listener? + /// `DispatchQueue` on which listeners will be called. + var listenerQueue: DispatchQueue? + /// Previously calculated status. + var previousStatus: NetworkReachabilityStatus? + } + + /// `SCNetworkReachability` instance providing notifications. + private let reachability: SCNetworkReachability + + /// Protected storage for mutable state. + @Protected + private var mutableState = MutableState() + + // MARK: - Initialization + + /// Creates an instance with the specified host. + /// + /// - Note: The `host` value must *not* contain a scheme, just the hostname. + /// + /// - Parameters: + /// - host: Host used to evaluate network reachability. Must *not* include the scheme (e.g. `https`). + public convenience init?(host: String) { + guard let reachability = SCNetworkReachabilityCreateWithName(nil, host) else { return nil } + + self.init(reachability: reachability) + } + + /// Creates an instance that monitors the address 0.0.0.0. + /// + /// Reachability treats the 0.0.0.0 address as a special token that causes it to monitor the general routing + /// status of the device, both IPv4 and IPv6. + public convenience init?() { + var zero = sockaddr() + zero.sa_len = UInt8(MemoryLayout.size) + zero.sa_family = sa_family_t(AF_INET) + + guard let reachability = SCNetworkReachabilityCreateWithAddress(nil, &zero) else { return nil } + + self.init(reachability: reachability) + } + + private init(reachability: SCNetworkReachability) { + self.reachability = reachability + } + + deinit { + stopListening() + } + + // MARK: - Listening + + /// Starts listening for changes in network reachability status. + /// + /// - Note: Stops and removes any existing listener. + /// + /// - Parameters: + /// - queue: `DispatchQueue` on which to call the `listener` closure. `.main` by default. + /// - listener: `Listener` closure called when reachability changes. + /// + /// - Returns: `true` if listening was started successfully, `false` otherwise. + @discardableResult + open func startListening(onQueue queue: DispatchQueue = .main, + onUpdatePerforming listener: @escaping Listener) -> Bool { + stopListening() + + $mutableState.write { state in + state.listenerQueue = queue + state.listener = listener + } + + var context = SCNetworkReachabilityContext(version: 0, + info: Unmanaged.passRetained(self).toOpaque(), + retain: nil, + release: nil, + copyDescription: nil) + let callback: SCNetworkReachabilityCallBack = { _, flags, info in + guard let info = info else { return } + + let instance = Unmanaged.fromOpaque(info).takeUnretainedValue() + instance.notifyListener(flags) + } + + let queueAdded = SCNetworkReachabilitySetDispatchQueue(reachability, reachabilityQueue) + let callbackAdded = SCNetworkReachabilitySetCallback(reachability, callback, &context) + + // Manually call listener to give initial state, since the framework may not. + if let currentFlags = flags { + reachabilityQueue.async { + self.notifyListener(currentFlags) + } + } + + return callbackAdded && queueAdded + } + + /// Stops listening for changes in network reachability status. + open func stopListening() { + SCNetworkReachabilitySetCallback(reachability, nil, nil) + SCNetworkReachabilitySetDispatchQueue(reachability, nil) + $mutableState.write { state in + state.listener = nil + state.listenerQueue = nil + state.previousStatus = nil + } + } + + // MARK: - Internal - Listener Notification + + /// Calls the `listener` closure of the `listenerQueue` if the computed status hasn't changed. + /// + /// - Note: Should only be called from the `reachabilityQueue`. + /// + /// - Parameter flags: `SCNetworkReachabilityFlags` to use to calculate the status. + func notifyListener(_ flags: SCNetworkReachabilityFlags) { + let newStatus = NetworkReachabilityStatus(flags) + + $mutableState.write { state in + guard state.previousStatus != newStatus else { return } + + state.previousStatus = newStatus + + let listener = state.listener + state.listenerQueue?.async { listener?(newStatus) } + } + } +} + +// MARK: - + +extension NetworkReachabilityManager.NetworkReachabilityStatus: Equatable {} + +extension SCNetworkReachabilityFlags { + var isReachable: Bool { contains(.reachable) } + var isConnectionRequired: Bool { contains(.connectionRequired) } + var canConnectAutomatically: Bool { contains(.connectionOnDemand) || contains(.connectionOnTraffic) } + var canConnectWithoutUserInteraction: Bool { canConnectAutomatically && !contains(.interventionRequired) } + var isActuallyReachable: Bool { isReachable && (!isConnectionRequired || canConnectWithoutUserInteraction) } + var isCellular: Bool { + #if os(iOS) || os(tvOS) + return contains(.isWWAN) + #else + return false + #endif + } + + /// Human readable `String` for all states, to help with debugging. + var readableDescription: String { + let W = isCellular ? "W" : "-" + let R = isReachable ? "R" : "-" + let c = isConnectionRequired ? "c" : "-" + let t = contains(.transientConnection) ? "t" : "-" + let i = contains(.interventionRequired) ? "i" : "-" + let C = contains(.connectionOnTraffic) ? "C" : "-" + let D = contains(.connectionOnDemand) ? "D" : "-" + let l = contains(.isLocalAddress) ? "l" : "-" + let d = contains(.isDirect) ? "d" : "-" + let a = contains(.connectionAutomatic) ? "a" : "-" + + return "\(W)\(R) \(c)\(t)\(i)\(C)\(D)\(l)\(d)\(a)" + } +} +#endif diff --git b/Pods/Alamofire/Source/Notifications.swift a/Pods/Alamofire/Source/Notifications.swift new file mode 100644 index 0000000..3c0f192 --- /dev/null +++ a/Pods/Alamofire/Source/Notifications.swift @@ -0,0 +1,115 @@ +// +// 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? { + 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) + } +} diff --git b/Pods/Alamofire/Source/OperationQueue+Alamofire.swift a/Pods/Alamofire/Source/OperationQueue+Alamofire.swift new file mode 100644 index 0000000..b06a0cc --- /dev/null +++ a/Pods/Alamofire/Source/OperationQueue+Alamofire.swift @@ -0,0 +1,49 @@ +// +// 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 + } +} diff --git b/Pods/Alamofire/Source/ParameterEncoder.swift a/Pods/Alamofire/Source/ParameterEncoder.swift new file mode 100644 index 0000000..94f09bf --- /dev/null +++ a/Pods/Alamofire/Source/ParameterEncoder.swift @@ -0,0 +1,184 @@ +// +// 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: 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 { 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: 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 { 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: 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 { 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 { try encoder.encode(parameters) } + .mapError { AFError.parameterEncoderFailed(reason: .encoderFailed(error: $0)) }.get() + } + + return request + } +} diff --git b/Pods/Alamofire/Source/ParameterEncoding.swift a/Pods/Alamofire/Source/ParameterEncoding.swift new file mode 100644 index 0000000..32fd061 --- /dev/null +++ a/Pods/Alamofire/Source/ParameterEncoding.swift @@ -0,0 +1,318 @@ +// +// ParameterEncoding.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 dictionary of parameters to apply to a `URLRequest`. +public typealias Parameters = [String: Any] + +/// A type used to define how a set of parameters are applied to a `URLRequest`. +public protocol ParameterEncoding { + /// Creates a `URLRequest` by encoding parameters and applying them on the passed request. + /// + /// - Parameters: + /// - urlRequest: `URLRequestConvertible` value onto which parameters will be encoded. + /// - parameters: `Parameters` to encode onto the request. + /// + /// - Returns: The encoded `URLRequest`. + /// - Throws: Any `Error` produced during parameter encoding. + func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest +} + +// MARK: - + +/// Creates a url-encoded query string to be set as or appended to any existing URL query string or set as the HTTP +/// body of the URL request. Whether the query string is set or appended to any existing URL query string or set as +/// the HTTP body depends on the destination of the encoding. +/// +/// The `Content-Type` HTTP header field of an encoded request with HTTP body is set to +/// `application/x-www-form-urlencoded; charset=utf-8`. +/// +/// There is no published specification for how to encode collection types. By default the convention of appending +/// `[]` to the key for array values (`foo[]=1&foo[]=2`), and appending the key surrounded by square brackets for +/// nested dictionary values (`foo[bar]=baz`) is used. Optionally, `ArrayEncoding` can be used to omit the +/// square brackets appended to array keys. +/// +/// `BoolEncoding` can be used to configure how boolean values are encoded. The default behavior is to encode +/// `true` as 1 and `false` as 0. +public struct URLEncoding: ParameterEncoding { + // MARK: Helper Types + + /// Defines whether the url-encoded query string is applied to the existing query string or HTTP body of the + /// resulting URL request. + public enum Destination { + /// Applies encoded query string result to existing query string for `GET`, `HEAD` and `DELETE` requests and + /// sets as the HTTP body for requests with any other HTTP method. + case methodDependent + /// Sets or appends encoded query string result to existing query string. + case queryString + /// Sets encoded query string result as the HTTP body of the URL request. + case httpBody + + func encodesParametersInURL(for method: HTTPMethod) -> Bool { + switch self { + case .methodDependent: return [.get, .head, .delete].contains(method) + case .queryString: return true + case .httpBody: return false + } + } + } + + /// Configures how `Array` parameters are encoded. + public enum ArrayEncoding { + /// An empty set of square brackets is appended to the key for every value. This is the default behavior. + case brackets + /// No brackets are appended. The key is encoded as is. + case noBrackets + + func encode(key: String) -> String { + switch self { + case .brackets: + return "\(key)[]" + case .noBrackets: + return key + } + } + } + + /// Configures how `Bool` parameters are encoded. + public enum BoolEncoding { + /// Encode `true` as `1` and `false` as `0`. This is the default behavior. + case numeric + /// Encode `true` and `false` as string literals. + case literal + + func encode(value: Bool) -> String { + switch self { + case .numeric: + return value ? "1" : "0" + case .literal: + return value ? "true" : "false" + } + } + } + + // MARK: Properties + + /// Returns a default `URLEncoding` instance with a `.methodDependent` destination. + public static var `default`: URLEncoding { URLEncoding() } + + /// Returns a `URLEncoding` instance with a `.queryString` destination. + public static var queryString: URLEncoding { URLEncoding(destination: .queryString) } + + /// Returns a `URLEncoding` instance with an `.httpBody` destination. + public static var httpBody: URLEncoding { URLEncoding(destination: .httpBody) } + + /// The destination defining where the encoded query string is to be applied to the URL request. + public let destination: Destination + + /// The encoding to use for `Array` parameters. + public let arrayEncoding: ArrayEncoding + + /// The encoding to use for `Bool` parameters. + public let boolEncoding: BoolEncoding + + // MARK: Initialization + + /// Creates an instance using the specified parameters. + /// + /// - Parameters: + /// - destination: `Destination` defining where the encoded query string will be applied. `.methodDependent` by + /// default. + /// - arrayEncoding: `ArrayEncoding` to use. `.brackets` by default. + /// - boolEncoding: `BoolEncoding` to use. `.numeric` by default. + public init(destination: Destination = .methodDependent, + arrayEncoding: ArrayEncoding = .brackets, + boolEncoding: BoolEncoding = .numeric) { + self.destination = destination + self.arrayEncoding = arrayEncoding + self.boolEncoding = boolEncoding + } + + // MARK: Encoding + + public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest { + var urlRequest = try urlRequest.asURLRequest() + + guard let parameters = parameters else { return urlRequest } + + if let method = urlRequest.method, destination.encodesParametersInURL(for: method) { + guard let url = urlRequest.url else { + throw AFError.parameterEncodingFailed(reason: .missingURL) + } + + if var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false), !parameters.isEmpty { + let percentEncodedQuery = (urlComponents.percentEncodedQuery.map { $0 + "&" } ?? "") + query(parameters) + urlComponents.percentEncodedQuery = percentEncodedQuery + urlRequest.url = urlComponents.url + } + } else { + if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil { + urlRequest.setValue("application/x-www-form-urlencoded; charset=utf-8", forHTTPHeaderField: "Content-Type") + } + + urlRequest.httpBody = Data(query(parameters).utf8) + } + + return urlRequest + } + + /// Creates a percent-escaped, URL encoded query string components from the given key-value pair recursively. + /// + /// - Parameters: + /// - key: Key of the query component. + /// - value: Value of the query component. + /// + /// - Returns: The percent-escaped, URL encoded query string components. + public func queryComponents(fromKey key: String, value: Any) -> [(String, String)] { + var components: [(String, String)] = [] + + if let dictionary = value as? [String: Any] { + for (nestedKey, value) in dictionary { + components += queryComponents(fromKey: "\(key)[\(nestedKey)]", value: value) + } + } else if let array = value as? [Any] { + for value in array { + components += queryComponents(fromKey: arrayEncoding.encode(key: key), value: value) + } + } else if let value = value as? NSNumber { + if value.isBool { + components.append((escape(key), escape(boolEncoding.encode(value: value.boolValue)))) + } else { + components.append((escape(key), escape("\(value)"))) + } + } else if let bool = value as? Bool { + components.append((escape(key), escape(boolEncoding.encode(value: bool)))) + } else { + components.append((escape(key), escape("\(value)"))) + } + + return components + } + + /// Creates a percent-escaped string following RFC 3986 for a query string key or value. + /// + /// - Parameter string: `String` to be percent-escaped. + /// + /// - Returns: The percent-escaped `String`. + public func escape(_ string: String) -> String { + string.addingPercentEncoding(withAllowedCharacters: .afURLQueryAllowed) ?? string + } + + private func query(_ parameters: [String: Any]) -> String { + var components: [(String, String)] = [] + + for key in parameters.keys.sorted(by: <) { + let value = parameters[key]! + components += queryComponents(fromKey: key, value: value) + } + return components.map { "\($0)=\($1)" }.joined(separator: "&") + } +} + +// MARK: - + +/// Uses `JSONSerialization` to create a JSON representation of the parameters object, which is set as the body of the +/// request. The `Content-Type` HTTP header field of an encoded request is set to `application/json`. +public struct JSONEncoding: ParameterEncoding { + // MARK: Properties + + /// Returns a `JSONEncoding` instance with default writing options. + public static var `default`: JSONEncoding { JSONEncoding() } + + /// Returns a `JSONEncoding` instance with `.prettyPrinted` writing options. + public static var prettyPrinted: JSONEncoding { JSONEncoding(options: .prettyPrinted) } + + /// The options for writing the parameters as JSON data. + public let options: JSONSerialization.WritingOptions + + // MARK: Initialization + + /// Creates an instance using the specified `WritingOptions`. + /// + /// - Parameter options: `JSONSerialization.WritingOptions` to use. + public init(options: JSONSerialization.WritingOptions = []) { + self.options = options + } + + // MARK: Encoding + + public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest { + var urlRequest = try urlRequest.asURLRequest() + + guard let parameters = parameters else { return urlRequest } + + do { + let data = try JSONSerialization.data(withJSONObject: parameters, options: options) + + if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil { + urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type") + } + + urlRequest.httpBody = data + } catch { + throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error)) + } + + return urlRequest + } + + /// Encodes any JSON compatible object into a `URLRequest`. + /// + /// - Parameters: + /// - urlRequest: `URLRequestConvertible` value into which the object will be encoded. + /// - jsonObject: `Any` value (must be JSON compatible` to be encoded into the `URLRequest`. `nil` by default. + /// + /// - Returns: The encoded `URLRequest`. + /// - Throws: Any `Error` produced during encoding. + public func encode(_ urlRequest: URLRequestConvertible, withJSONObject jsonObject: Any? = nil) throws -> URLRequest { + var urlRequest = try urlRequest.asURLRequest() + + guard let jsonObject = jsonObject else { return urlRequest } + + do { + let data = try JSONSerialization.data(withJSONObject: jsonObject, options: options) + + if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil { + urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type") + } + + urlRequest.httpBody = data + } catch { + throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error)) + } + + return urlRequest + } +} + +// MARK: - + +extension NSNumber { + fileprivate var isBool: Bool { + // Use Obj-C type encoding to check whether the underlying type is a `Bool`, as it's guaranteed as part of + // swift-corelibs-foundation, per [this discussion on the Swift forums](https://forums.swift.org/t/alamofire-on-linux-possible-but-not-release-ready/34553/22). + String(cString: objCType) == "c" + } +} diff --git b/Pods/Alamofire/Source/Protected.swift a/Pods/Alamofire/Source/Protected.swift new file mode 100644 index 0000000..6288205 --- /dev/null +++ a/Pods/Alamofire/Source/Protected.swift @@ -0,0 +1,224 @@ +// +// Protected.swift +// +// 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. +// + +import Foundation + +private protocol Lock { + func lock() + func unlock() +} + +extension Lock { + /// Executes a closure returning a value while acquiring the lock. + /// + /// - Parameter closure: The closure to run. + /// + /// - Returns: The value the closure generated. + func around(_ 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() } + closure() + } +} + +#if os(Linux) +/// A `pthread_mutex_t` wrapper. +final class MutexLock: Lock { + private var mutex: UnsafeMutablePointer + + init() { + mutex = .allocate(capacity: 1) + + var attr = pthread_mutexattr_t() + pthread_mutexattr_init(&attr) + pthread_mutexattr_settype(&attr, .init(PTHREAD_MUTEX_ERRORCHECK)) + + let error = pthread_mutex_init(mutex, &attr) + precondition(error == 0, "Failed to create pthread_mutex") + } + + deinit { + let error = pthread_mutex_destroy(mutex) + precondition(error == 0, "Failed to destroy pthread_mutex") + } + + fileprivate func lock() { + let error = pthread_mutex_lock(mutex) + precondition(error == 0, "Failed to lock pthread_mutex") + } + + fileprivate func unlock() { + let error = pthread_mutex_unlock(mutex) + precondition(error == 0, "Failed to unlock pthread_mutex") + } +} +#endif + +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +/// An `os_unfair_lock` wrapper. +final class UnfairLock: Lock { + 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() + } + + fileprivate func lock() { + os_unfair_lock_lock(unfairLock) + } + + fileprivate func unlock() { + os_unfair_lock_unlock(unfairLock) + } +} +#endif + +/// A thread-safe wrapper around a value. +@propertyWrapper +@dynamicMemberLookup +final class Protected { + #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) + private let lock = UnfairLock() + #elseif os(Linux) + private let lock = MutexLock() + #endif + private var value: T + + init(_ value: T) { + self.value = value + } + + /// The contained value. Unsafe for anything more than direct read or write. + var wrappedValue: T { + get { lock.around { value } } + set { lock.around { value = newValue } } + } + + var projectedValue: Protected { self } + + init(wrappedValue: T) { + value = wrappedValue + } + + /// Synchronously read or transform the contained value. + /// + /// - Parameter closure: The closure to execute. + /// + /// - Returns: The return value of the closure passed. + func read(_ closure: (T) -> U) -> U { + lock.around { closure(self.value) } + } + + /// Synchronously modify the protected value. + /// + /// - Parameter closure: The closure to execute. + /// + /// - Returns: The modified value. + @discardableResult + func write(_ closure: (inout T) -> U) -> U { + lock.around { closure(&self.value) } + } + + subscript(dynamicMember keyPath: WritableKeyPath) -> Property { + get { lock.around { value[keyPath: keyPath] } } + set { lock.around { value[keyPath: keyPath] = newValue } } + } +} + +extension Protected 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(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(contentsOf newElements: C) where C.Element == T.Element { + write { (ward: inout T) in + ward.append(contentsOf: newElements) + } + } +} + +extension Protected 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 Protected 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 { + 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) } + } +} diff --git b/Pods/Alamofire/Source/RedirectHandler.swift a/Pods/Alamofire/Source/RedirectHandler.swift new file mode 100644 index 0000000..b6c069c --- /dev/null +++ a/Pods/Alamofire/Source/RedirectHandler.swift @@ -0,0 +1,95 @@ +// +// 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) + } + } +} diff --git b/Pods/Alamofire/Source/Request.swift a/Pods/Alamofire/Source/Request.swift new file mode 100644 index 0000000..1187a66 --- /dev/null +++ a/Pods/Alamofire/Source/Request.swift @@ -0,0 +1,1757 @@ +// +// Request.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 + +/// `Request` is the common superclass of all Alamofire request types and provides common state, delegate, and callback +/// handling. +public class Request { + /// State of the `Request`, with managed transitions between states set when calling `resume()`, `suspend()`, or + /// `cancel()` on the `Request`. + public enum State { + /// Initial state of the `Request`. + case initialized + /// `State` set when `resume()` is called. Any tasks created for the `Request` will have `resume()` called on + /// them in this state. + case resumed + /// `State` set when `suspend()` is called. Any tasks created for the `Request` will have `suspend()` called on + /// them in this state. + case suspended + /// `State` set when `cancel()` is called. Any tasks created for the `Request` will have `cancel()` called on + /// them. Unlike `resumed` or `suspended`, once in the `cancelled` state, the `Request` can no longer transition + /// to any other state. + case cancelled + /// `State` set when all response serialization completion closures have been cleared on the `Request` and + /// enqueued on their respective queues. + case finished + + /// Determines whether `self` can be transitioned to the provided `State`. + func canTransitionTo(_ state: State) -> Bool { + switch (self, state) { + case (.initialized, _): + return true + case (_, .initialized), (.cancelled, _), (.finished, _): + return false + case (.resumed, .cancelled), (.suspended, .cancelled), (.resumed, .suspended), (.suspended, .resumed): + return true + case (.suspended, .suspended), (.resumed, .resumed): + return false + case (_, .finished): + return true + } + } + } + + // MARK: - Initial State + + /// `UUID` providing a unique identifier for the `Request`, used in the `Hashable` and `Equatable` conformances. + public let id: UUID + /// The serial queue for all internal async actions. + public let underlyingQueue: DispatchQueue + /// The queue used for all serialization actions. By default it's a serial queue that targets `underlyingQueue`. + public let serializationQueue: DispatchQueue + /// `EventMonitor` used for event callbacks. + public let eventMonitor: EventMonitor? + /// The `Request`'s interceptor. + public let interceptor: RequestInterceptor? + /// The `Request`'s delegate. + public private(set) weak var delegate: RequestDelegate? + + // MARK: - Mutable State + + /// Type encapsulating all mutable state that may need to be accessed from anything other than the `underlyingQueue`. + struct MutableState { + /// State of the `Request`. + var state: State = .initialized + /// `ProgressHandler` and `DispatchQueue` provided for upload progress callbacks. + var uploadProgressHandler: (handler: ProgressHandler, queue: DispatchQueue)? + /// `ProgressHandler` and `DispatchQueue` provided for download progress callbacks. + var downloadProgressHandler: (handler: ProgressHandler, queue: DispatchQueue)? + /// `RedirectHandler` provided for to handle request redirection. + var redirectHandler: RedirectHandler? + /// `CachedResponseHandler` provided to handle response caching. + var cachedResponseHandler: CachedResponseHandler? + /// Closure called when the `Request` is able to create a cURL description of itself. + var cURLHandler: ((String) -> Void)? + /// Response serialization closures that handle response parsing. + var responseSerializers: [() -> Void] = [] + /// Response serialization completion closures executed once all response serializers are complete. + var responseSerializerCompletions: [() -> Void] = [] + /// Whether response serializer processing is finished. + var responseSerializerProcessingFinished = false + /// `URLCredential` used for authentication challenges. + var credential: URLCredential? + /// All `URLRequest`s created by Alamofire on behalf of the `Request`. + var requests: [URLRequest] = [] + /// All `URLSessionTask`s created by Alamofire on behalf of the `Request`. + var tasks: [URLSessionTask] = [] + /// All `URLSessionTaskMetrics` values gathered by Alamofire on behalf of the `Request`. Should correspond + /// exactly the the `tasks` created. + var metrics: [URLSessionTaskMetrics] = [] + /// Number of times any retriers provided retried the `Request`. + var retryCount = 0 + /// Final `AFError` for the `Request`, whether from various internal Alamofire calls or as a result of a `task`. + var error: AFError? + /// Whether the instance has had `finish()` called and is running the serializers. Should be replaced with a + /// representation in the state machine in the future. + var isFinishing = false + } + + /// Protected `MutableState` value that provides thread-safe access to state values. + @Protected + fileprivate var mutableState = MutableState() + + /// `State` of the `Request`. + public var state: State { mutableState.state } + /// Returns whether `state` is `.initialized`. + public var isInitialized: Bool { state == .initialized } + /// Returns whether `state is `.resumed`. + public var isResumed: Bool { state == .resumed } + /// Returns whether `state` is `.suspended`. + public var isSuspended: Bool { state == .suspended } + /// Returns whether `state` is `.cancelled`. + public var isCancelled: Bool { state == .cancelled } + /// Returns whether `state` is `.finished`. + public var isFinished: Bool { state == .finished } + + // MARK: Progress + + /// Closure type executed when monitoring the upload or download progress of a request. + public typealias ProgressHandler = (Progress) -> Void + + /// `Progress` of the upload of the body of the executed `URLRequest`. Reset to `0` if the `Request` is retried. + public let uploadProgress = Progress(totalUnitCount: 0) + /// `Progress` of the download of any response data. Reset to `0` if the `Request` is retried. + public let downloadProgress = Progress(totalUnitCount: 0) + /// `ProgressHandler` called when `uploadProgress` is updated, on the provided `DispatchQueue`. + fileprivate var uploadProgressHandler: (handler: ProgressHandler, queue: DispatchQueue)? { + get { mutableState.uploadProgressHandler } + set { mutableState.uploadProgressHandler = newValue } + } + + /// `ProgressHandler` called when `downloadProgress` is updated, on the provided `DispatchQueue`. + fileprivate var downloadProgressHandler: (handler: ProgressHandler, queue: DispatchQueue)? { + get { mutableState.downloadProgressHandler } + set { mutableState.downloadProgressHandler = newValue } + } + + // MARK: Redirect Handling + + /// `RedirectHandler` set on the instance. + public private(set) var redirectHandler: RedirectHandler? { + get { mutableState.redirectHandler } + set { mutableState.redirectHandler = newValue } + } + + // MARK: Cached Response Handling + + /// `CachedResponseHandler` set on the instance. + public private(set) var cachedResponseHandler: CachedResponseHandler? { + get { mutableState.cachedResponseHandler } + set { mutableState.cachedResponseHandler = newValue } + } + + // MARK: URLCredential + + /// `URLCredential` used for authentication challenges. Created by calling one of the `authenticate` methods. + public private(set) var credential: URLCredential? { + get { mutableState.credential } + set { mutableState.credential = newValue } + } + + // MARK: Validators + + /// `Validator` callback closures that store the validation calls enqueued. + @Protected + fileprivate var validators: [() -> Void] = [] + + // MARK: URLRequests + + /// All `URLRequests` created on behalf of the `Request`, including original and adapted requests. + public var requests: [URLRequest] { mutableState.requests } + /// First `URLRequest` created on behalf of the `Request`. May not be the first one actually executed. + public var firstRequest: URLRequest? { requests.first } + /// Last `URLRequest` created on behalf of the `Request`. + public var lastRequest: URLRequest? { requests.last } + /// Current `URLRequest` created on behalf of the `Request`. + public var request: URLRequest? { lastRequest } + + /// `URLRequest`s from all of the `URLSessionTask`s executed on behalf of the `Request`. May be different from + /// `requests` due to `URLSession` manipulation. + public var performedRequests: [URLRequest] { $mutableState.read { $0.tasks.compactMap { $0.currentRequest } } } + + // MARK: HTTPURLResponse + + /// `HTTPURLResponse` received from the server, if any. If the `Request` was retried, this is the response of the + /// last `URLSessionTask`. + public var response: HTTPURLResponse? { lastTask?.response as? HTTPURLResponse } + + // MARK: Tasks + + /// All `URLSessionTask`s created on behalf of the `Request`. + public var tasks: [URLSessionTask] { mutableState.tasks } + /// First `URLSessionTask` created on behalf of the `Request`. + public var firstTask: URLSessionTask? { tasks.first } + /// Last `URLSessionTask` crated on behalf of the `Request`. + public var lastTask: URLSessionTask? { tasks.last } + /// Current `URLSessionTask` created on behalf of the `Request`. + public var task: URLSessionTask? { lastTask } + + // MARK: Metrics + + /// All `URLSessionTaskMetrics` gathered on behalf of the `Request`. Should correspond to the `tasks` created. + public var allMetrics: [URLSessionTaskMetrics] { mutableState.metrics } + /// First `URLSessionTaskMetrics` gathered on behalf of the `Request`. + public var firstMetrics: URLSessionTaskMetrics? { allMetrics.first } + /// Last `URLSessionTaskMetrics` gathered on behalf of the `Request`. + public var lastMetrics: URLSessionTaskMetrics? { allMetrics.last } + /// Current `URLSessionTaskMetrics` gathered on behalf of the `Request`. + public var metrics: URLSessionTaskMetrics? { lastMetrics } + + // MARK: Retry Count + + /// Number of times the `Request` has been retried. + public var retryCount: Int { mutableState.retryCount } + + // MARK: Error + + /// `Error` returned from Alamofire internally, from the network request directly, or any validators executed. + public fileprivate(set) var error: AFError? { + get { mutableState.error } + set { mutableState.error = newValue } + } + + /// Default initializer for the `Request` superclass. + /// + /// - Parameters: + /// - id: `UUID` used for the `Hashable` and `Equatable` implementations. `UUID()` by default. + /// - underlyingQueue: `DispatchQueue` on which all internal `Request` work is performed. + /// - serializationQueue: `DispatchQueue` on which all serialization work is performed. By default targets + /// `underlyingQueue`, but can be passed another queue from a `Session`. + /// - eventMonitor: `EventMonitor` called for event callbacks from internal `Request` actions. + /// - interceptor: `RequestInterceptor` used throughout the request lifecycle. + /// - delegate: `RequestDelegate` that provides an interface to actions not performed by the `Request`. + init(id: UUID = UUID(), + underlyingQueue: DispatchQueue, + serializationQueue: DispatchQueue, + eventMonitor: EventMonitor?, + interceptor: RequestInterceptor?, + delegate: RequestDelegate) { + self.id = id + self.underlyingQueue = underlyingQueue + self.serializationQueue = serializationQueue + self.eventMonitor = eventMonitor + self.interceptor = interceptor + self.delegate = delegate + } + + // MARK: - Internal Event API + + // All API must be called from underlyingQueue. + + /// Called when an initial `URLRequest` has been created on behalf of the instance. If a `RequestAdapter` is active, + /// the `URLRequest` will be adapted before being issued. + /// + /// - Parameter request: The `URLRequest` created. + func didCreateInitialURLRequest(_ request: URLRequest) { + dispatchPrecondition(condition: .onQueue(underlyingQueue)) + + $mutableState.write { $0.requests.append(request) } + + eventMonitor?.request(self, didCreateInitialURLRequest: request) + } + + /// Called when initial `URLRequest` creation has failed, typically through a `URLRequestConvertible`. + /// + /// - Note: Triggers retry. + /// + /// - Parameter error: `AFError` thrown from the failed creation. + func didFailToCreateURLRequest(with error: AFError) { + dispatchPrecondition(condition: .onQueue(underlyingQueue)) + + self.error = error + + eventMonitor?.request(self, didFailToCreateURLRequestWithError: error) + + callCURLHandlerIfNecessary() + + retryOrFinish(error: error) + } + + /// Called when a `RequestAdapter` has successfully adapted a `URLRequest`. + /// + /// - Parameters: + /// - initialRequest: The `URLRequest` that was adapted. + /// - adaptedRequest: The `URLRequest` returned by the `RequestAdapter`. + func didAdaptInitialRequest(_ initialRequest: URLRequest, to adaptedRequest: URLRequest) { + dispatchPrecondition(condition: .onQueue(underlyingQueue)) + + $mutableState.write { $0.requests.append(adaptedRequest) } + + eventMonitor?.request(self, didAdaptInitialRequest: initialRequest, to: adaptedRequest) + } + + /// Called when a `RequestAdapter` fails to adapt a `URLRequest`. + /// + /// - Note: Triggers retry. + /// + /// - Parameters: + /// - request: The `URLRequest` the adapter was called with. + /// - error: The `AFError` returned by the `RequestAdapter`. + func didFailToAdaptURLRequest(_ request: URLRequest, withError error: AFError) { + dispatchPrecondition(condition: .onQueue(underlyingQueue)) + + self.error = error + + eventMonitor?.request(self, didFailToAdaptURLRequest: request, withError: error) + + callCURLHandlerIfNecessary() + + retryOrFinish(error: error) + } + + /// Final `URLRequest` has been created for the instance. + /// + /// - Parameter request: The `URLRequest` created. + func didCreateURLRequest(_ request: URLRequest) { + dispatchPrecondition(condition: .onQueue(underlyingQueue)) + + eventMonitor?.request(self, didCreateURLRequest: request) + + callCURLHandlerIfNecessary() + } + + /// Asynchronously calls any stored `cURLHandler` and then removes it from `mutableState`. + private func callCURLHandlerIfNecessary() { + $mutableState.write { mutableState in + guard let cURLHandler = mutableState.cURLHandler else { return } + + self.underlyingQueue.async { cURLHandler(self.cURLDescription()) } + mutableState.cURLHandler = nil + } + } + + /// Called when a `URLSessionTask` is created on behalf of the instance. + /// + /// - Parameter task: The `URLSessionTask` created. + func didCreateTask(_ task: URLSessionTask) { + dispatchPrecondition(condition: .onQueue(underlyingQueue)) + + $mutableState.write { $0.tasks.append(task) } + + eventMonitor?.request(self, didCreateTask: task) + } + + /// Called when resumption is completed. + func didResume() { + dispatchPrecondition(condition: .onQueue(underlyingQueue)) + + eventMonitor?.requestDidResume(self) + } + + /// Called when a `URLSessionTask` is resumed on behalf of the instance. + /// + /// - Parameter task: The `URLSessionTask` resumed. + func didResumeTask(_ task: URLSessionTask) { + dispatchPrecondition(condition: .onQueue(underlyingQueue)) + + eventMonitor?.request(self, didResumeTask: task) + } + + /// Called when suspension is completed. + func didSuspend() { + dispatchPrecondition(condition: .onQueue(underlyingQueue)) + + eventMonitor?.requestDidSuspend(self) + } + + /// Called when a `URLSessionTask` is suspended on behalf of the instance. + /// + /// - Parameter task: The `URLSessionTask` suspended. + func didSuspendTask(_ task: URLSessionTask) { + dispatchPrecondition(condition: .onQueue(underlyingQueue)) + + eventMonitor?.request(self, didSuspendTask: task) + } + + /// Called when cancellation is completed, sets `error` to `AFError.explicitlyCancelled`. + func didCancel() { + dispatchPrecondition(condition: .onQueue(underlyingQueue)) + + error = error ?? AFError.explicitlyCancelled + + eventMonitor?.requestDidCancel(self) + } + + /// Called when a `URLSessionTask` is cancelled on behalf of the instance. + /// + /// - Parameter task: The `URLSessionTask` cancelled. + func didCancelTask(_ task: URLSessionTask) { + dispatchPrecondition(condition: .onQueue(underlyingQueue)) + + eventMonitor?.request(self, didCancelTask: task) + } + + /// Called when a `URLSessionTaskMetrics` value is gathered on behalf of the instance. + /// + /// - Parameter metrics: The `URLSessionTaskMetrics` gathered. + func didGatherMetrics(_ metrics: URLSessionTaskMetrics) { + dispatchPrecondition(condition: .onQueue(underlyingQueue)) + + $mutableState.write { $0.metrics.append(metrics) } + + eventMonitor?.request(self, didGatherMetrics: metrics) + } + + /// Called when a `URLSessionTask` fails before it is finished, typically during certificate pinning. + /// + /// - Parameters: + /// - task: The `URLSessionTask` which failed. + /// - error: The early failure `AFError`. + func didFailTask(_ task: URLSessionTask, earlyWithError error: AFError) { + dispatchPrecondition(condition: .onQueue(underlyingQueue)) + + self.error = error + + // Task will still complete, so didCompleteTask(_:with:) will handle retry. + eventMonitor?.request(self, didFailTask: task, earlyWithError: error) + } + + /// Called when a `URLSessionTask` completes. All tasks will eventually call this method. + /// + /// - Note: Response validation is synchronously triggered in this step. + /// + /// - Parameters: + /// - task: The `URLSessionTask` which completed. + /// - error: The `AFError` `task` may have completed with. If `error` has already been set on the instance, this + /// value is ignored. + func didCompleteTask(_ task: URLSessionTask, with error: AFError?) { + dispatchPrecondition(condition: .onQueue(underlyingQueue)) + + self.error = self.error ?? error + + validators.forEach { $0() } + + eventMonitor?.request(self, didCompleteTask: task, with: error) + + retryOrFinish(error: self.error) + } + + /// Called when the `RequestDelegate` is going to retry this `Request`. Calls `reset()`. + func prepareForRetry() { + dispatchPrecondition(condition: .onQueue(underlyingQueue)) + + $mutableState.write { $0.retryCount += 1 } + + reset() + + eventMonitor?.requestIsRetrying(self) + } + + /// Called to determine whether retry will be triggered for the particular error, or whether the instance should + /// call `finish()`. + /// + /// - Parameter error: The possible `AFError` which may trigger retry. + func retryOrFinish(error: AFError?) { + dispatchPrecondition(condition: .onQueue(underlyingQueue)) + + guard let error = error, let delegate = delegate else { finish(); return } + + delegate.retryResult(for: self, dueTo: error) { retryResult in + switch retryResult { + case .doNotRetry: + self.finish() + case let .doNotRetryWithError(retryError): + self.finish(error: retryError.asAFError(orFailWith: "Received retryError was not already AFError")) + case .retry, .retryWithDelay: + delegate.retryRequest(self, withDelay: retryResult.delay) + } + } + } + + /// Finishes this `Request` and starts the response serializers. + /// + /// - Parameter error: The possible `Error` with which the instance will finish. + func finish(error: AFError? = nil) { + dispatchPrecondition(condition: .onQueue(underlyingQueue)) + + guard !mutableState.isFinishing else { return } + + mutableState.isFinishing = true + + if let error = error { self.error = error } + + // Start response handlers + processNextResponseSerializer() + + eventMonitor?.requestDidFinish(self) + } + + /// Appends the response serialization closure to the instance. + /// + /// - Note: This method will also `resume` the instance if `delegate.startImmediately` returns `true`. + /// + /// - Parameter closure: The closure containing the response serialization call. + func appendResponseSerializer(_ closure: @escaping () -> Void) { + $mutableState.write { mutableState in + mutableState.responseSerializers.append(closure) + + if mutableState.state == .finished { + mutableState.state = .resumed + } + + if mutableState.responseSerializerProcessingFinished { + underlyingQueue.async { self.processNextResponseSerializer() } + } + + if mutableState.state.canTransitionTo(.resumed) { + underlyingQueue.async { if self.delegate?.startImmediately == true { self.resume() } } + } + } + } + + /// Returns the next response serializer closure to execute if there's one left. + /// + /// - Returns: The next response serialization closure, if there is one. + func nextResponseSerializer() -> (() -> Void)? { + var responseSerializer: (() -> Void)? + + $mutableState.write { mutableState in + let responseSerializerIndex = mutableState.responseSerializerCompletions.count + + if responseSerializerIndex < mutableState.responseSerializers.count { + responseSerializer = mutableState.responseSerializers[responseSerializerIndex] + } + } + + return responseSerializer + } + + /// Processes the next response serializer and calls all completions if response serialization is complete. + func processNextResponseSerializer() { + guard let responseSerializer = nextResponseSerializer() else { + // Execute all response serializer completions and clear them + var completions: [() -> Void] = [] + + $mutableState.write { mutableState in + completions = mutableState.responseSerializerCompletions + + // Clear out all response serializers and response serializer completions in mutable state since the + // request is complete. It's important to do this prior to calling the completion closures in case + // the completions call back into the request triggering a re-processing of the response serializers. + // An example of how this can happen is by calling cancel inside a response completion closure. + mutableState.responseSerializers.removeAll() + mutableState.responseSerializerCompletions.removeAll() + + if mutableState.state.canTransitionTo(.finished) { + mutableState.state = .finished + } + + mutableState.responseSerializerProcessingFinished = true + mutableState.isFinishing = false + } + + completions.forEach { $0() } + + // Cleanup the request + cleanup() + + return + } + + serializationQueue.async { responseSerializer() } + } + + /// Notifies the `Request` that the response serializer is complete. + /// + /// - Parameter completion: The completion handler provided with the response serializer, called when all serializers + /// are complete. + func responseSerializerDidComplete(completion: @escaping () -> Void) { + $mutableState.write { $0.responseSerializerCompletions.append(completion) } + processNextResponseSerializer() + } + + /// Resets all task and response serializer related state for retry. + func reset() { + error = nil + + uploadProgress.totalUnitCount = 0 + uploadProgress.completedUnitCount = 0 + downloadProgress.totalUnitCount = 0 + downloadProgress.completedUnitCount = 0 + + $mutableState.write { state in + state.isFinishing = false + state.responseSerializerCompletions = [] + } + } + + /// Called when updating the upload progress. + /// + /// - Parameters: + /// - totalBytesSent: Total bytes sent so far. + /// - totalBytesExpectedToSend: Total bytes expected to send. + func updateUploadProgress(totalBytesSent: Int64, totalBytesExpectedToSend: Int64) { + uploadProgress.totalUnitCount = totalBytesExpectedToSend + uploadProgress.completedUnitCount = totalBytesSent + + uploadProgressHandler?.queue.async { self.uploadProgressHandler?.handler(self.uploadProgress) } + } + + /// Perform a closure on the current `state` while locked. + /// + /// - Parameter perform: The closure to perform. + func withState(perform: (State) -> Void) { + $mutableState.withState(perform: perform) + } + + // MARK: Task Creation + + /// Called when creating a `URLSessionTask` for this `Request`. Subclasses must override. + /// + /// - Parameters: + /// - request: `URLRequest` to use to create the `URLSessionTask`. + /// - session: `URLSession` which creates the `URLSessionTask`. + /// + /// - Returns: The `URLSessionTask` created. + func task(for request: URLRequest, using session: URLSession) -> URLSessionTask { + fatalError("Subclasses must override.") + } + + // MARK: - Public API + + // These APIs are callable from any queue. + + // MARK: State + + /// Cancels the instance. Once cancelled, a `Request` can no longer be resumed or suspended. + /// + /// - Returns: The instance. + @discardableResult + public func cancel() -> Self { + $mutableState.write { mutableState in + guard mutableState.state.canTransitionTo(.cancelled) else { return } + + mutableState.state = .cancelled + + underlyingQueue.async { self.didCancel() } + + guard let task = mutableState.tasks.last, task.state != .completed else { + underlyingQueue.async { self.finish() } + return + } + + // Resume to ensure metrics are gathered. + task.resume() + task.cancel() + underlyingQueue.async { self.didCancelTask(task) } + } + + return self + } + + /// Suspends the instance. + /// + /// - Returns: The instance. + @discardableResult + public func suspend() -> Self { + $mutableState.write { mutableState in + guard mutableState.state.canTransitionTo(.suspended) else { return } + + mutableState.state = .suspended + + underlyingQueue.async { self.didSuspend() } + + guard let task = mutableState.tasks.last, task.state != .completed else { return } + + task.suspend() + underlyingQueue.async { self.didSuspendTask(task) } + } + + return self + } + + /// Resumes the instance. + /// + /// - Returns: The instance. + @discardableResult + public func resume() -> Self { + $mutableState.write { mutableState in + guard mutableState.state.canTransitionTo(.resumed) else { return } + + mutableState.state = .resumed + + underlyingQueue.async { self.didResume() } + + guard let task = mutableState.tasks.last, task.state != .completed else { return } + + task.resume() + underlyingQueue.async { self.didResumeTask(task) } + } + + return self + } + + // MARK: - Closure API + + /// Associates a credential using the provided values with the instance. + /// + /// - Parameters: + /// - username: The username. + /// - password: The password. + /// - persistence: The `URLCredential.Persistence` for the created `URLCredential`. `.forSession` by default. + /// + /// - Returns: The instance. + @discardableResult + public func authenticate(username: String, password: String, persistence: URLCredential.Persistence = .forSession) -> Self { + let credential = URLCredential(user: username, password: password, persistence: persistence) + + return authenticate(with: credential) + } + + /// Associates the provided credential with the instance. + /// + /// - Parameter credential: The `URLCredential`. + /// + /// - Returns: The instance. + @discardableResult + public func authenticate(with credential: URLCredential) -> Self { + mutableState.credential = credential + + return self + } + + /// Sets a closure to be called periodically during the lifecycle of the instance as data is read from the server. + /// + /// - Note: Only the last closure provided is used. + /// + /// - Parameters: + /// - queue: The `DispatchQueue` to execute the closure on. `.main` by default. + /// - closure: The closure to be executed periodically as data is read from the server. + /// + /// - Returns: The instance. + @discardableResult + public func downloadProgress(queue: DispatchQueue = .main, closure: @escaping ProgressHandler) -> Self { + mutableState.downloadProgressHandler = (handler: closure, queue: queue) + + return self + } + + /// Sets a closure to be called periodically during the lifecycle of the instance as data is sent to the server. + /// + /// - Note: Only the last closure provided is used. + /// + /// - Parameters: + /// - queue: The `DispatchQueue` to execute the closure on. `.main` by default. + /// - closure: The closure to be executed periodically as data is sent to the server. + /// + /// - Returns: The instance. + @discardableResult + public func uploadProgress(queue: DispatchQueue = .main, closure: @escaping ProgressHandler) -> Self { + mutableState.uploadProgressHandler = (handler: closure, queue: queue) + + return self + } + + // MARK: Redirects + + /// Sets the redirect handler for the instance which will be used if a redirect response is encountered. + /// + /// - Note: Attempting to set the redirect handler more than once is a logic error and will crash. + /// + /// - Parameter handler: The `RedirectHandler`. + /// + /// - Returns: The instance. + @discardableResult + public func redirect(using handler: RedirectHandler) -> Self { + $mutableState.write { mutableState in + precondition(mutableState.redirectHandler == nil, "Redirect handler has already been set.") + mutableState.redirectHandler = handler + } + + return self + } + + // MARK: Cached Responses + + /// Sets the cached response handler for the `Request` which will be used when attempting to cache a response. + /// + /// - Note: Attempting to set the cache handler more than once is a logic error and will crash. + /// + /// - Parameter handler: The `CachedResponseHandler`. + /// + /// - Returns: The instance. + @discardableResult + public func cacheResponse(using handler: CachedResponseHandler) -> Self { + $mutableState.write { mutableState in + precondition(mutableState.cachedResponseHandler == nil, "Cached response handler has already been set.") + mutableState.cachedResponseHandler = handler + } + + return self + } + + /// Sets a handler to be called when the cURL description of the request is available. + /// + /// - Note: When waiting for a `Request`'s `URLRequest` to be created, only the last `handler` will be called. + /// + /// - Parameter handler: Closure to be called when the cURL description is available. + /// + /// - Returns: The instance. + @discardableResult + public func cURLDescription(calling handler: @escaping (String) -> Void) -> Self { + $mutableState.write { mutableState in + if mutableState.requests.last != nil { + underlyingQueue.async { handler(self.cURLDescription()) } + } else { + mutableState.cURLHandler = handler + } + } + + return self + } + + // MARK: Cleanup + + /// Final cleanup step executed when the instance finishes response serialization. + func cleanup() { + delegate?.cleanup(after: self) + // No-op: override in subclass + } +} + +// MARK: - Protocol Conformances + +extension Request: Equatable { + public static func ==(lhs: Request, rhs: Request) -> Bool { + lhs.id == rhs.id + } +} + +extension Request: Hashable { + public func hash(into hasher: inout Hasher) { + hasher.combine(id) + } +} + +extension Request: CustomStringConvertible { + /// A textual representation of this instance, including the `HTTPMethod` and `URL` if the `URLRequest` has been + /// created, as well as the response status code, if a response has been received. + public var description: String { + guard let request = performedRequests.last ?? lastRequest, + let url = request.url, + let method = request.httpMethod else { return "No request created yet." } + + let requestDescription = "\(method) \(url.absoluteString)" + + return response.map { "\(requestDescription) (\($0.statusCode))" } ?? requestDescription + } +} + +extension Request { + /// cURL representation of the instance. + /// + /// - Returns: The cURL equivalent of the instance. + public func cURLDescription() -> String { + guard + let request = lastRequest, + let url = request.url, + let host = url.host, + let method = request.httpMethod else { return "$ curl command could not be created" } + + var components = ["$ curl -v"] + + components.append("-X \(method)") + + if let credentialStorage = delegate?.sessionConfiguration.urlCredentialStorage { + let protectionSpace = URLProtectionSpace(host: host, + port: url.port ?? 0, + protocol: url.scheme, + realm: host, + authenticationMethod: NSURLAuthenticationMethodHTTPBasic) + + if let credentials = credentialStorage.credentials(for: protectionSpace)?.values { + for credential in credentials { + guard let user = credential.user, let password = credential.password else { continue } + components.append("-u \(user):\(password)") + } + } else { + if let credential = credential, let user = credential.user, let password = credential.password { + components.append("-u \(user):\(password)") + } + } + } + + if let configuration = delegate?.sessionConfiguration, configuration.httpShouldSetCookies { + if + let cookieStorage = configuration.httpCookieStorage, + let cookies = cookieStorage.cookies(for: url), !cookies.isEmpty { + let allCookies = cookies.map { "\($0.name)=\($0.value)" }.joined(separator: ";") + + components.append("-b \"\(allCookies)\"") + } + } + + var headers = HTTPHeaders() + + if let sessionHeaders = delegate?.sessionConfiguration.headers { + for header in sessionHeaders where header.name != "Cookie" { + headers[header.name] = header.value + } + } + + for header in request.headers where header.name != "Cookie" { + headers[header.name] = header.value + } + + for header in headers { + let escapedValue = header.value.replacingOccurrences(of: "\"", with: "\\\"") + components.append("-H \"\(header.name): \(escapedValue)\"") + } + + if let httpBodyData = request.httpBody { + let httpBody = String(decoding: httpBodyData, as: UTF8.self) + var escapedBody = httpBody.replacingOccurrences(of: "\\\"", with: "\\\\\"") + escapedBody = escapedBody.replacingOccurrences(of: "\"", with: "\\\"") + + components.append("-d \"\(escapedBody)\"") + } + + components.append("\"\(url.absoluteString)\"") + + return components.joined(separator: " \\\n\t") + } +} + +/// Protocol abstraction for `Request`'s communication back to the `SessionDelegate`. +public protocol RequestDelegate: AnyObject { + /// `URLSessionConfiguration` used to create the underlying `URLSessionTask`s. + var sessionConfiguration: URLSessionConfiguration { get } + + /// Determines whether the `Request` should automatically call `resume()` when adding the first response handler. + var startImmediately: Bool { get } + + /// Notifies the delegate the `Request` has reached a point where it needs cleanup. + /// + /// - Parameter request: The `Request` to cleanup after. + func cleanup(after request: Request) + + /// Asynchronously ask the delegate whether a `Request` will be retried. + /// + /// - Parameters: + /// - request: `Request` which failed. + /// - error: `Error` which produced the failure. + /// - completion: Closure taking the `RetryResult` for evaluation. + func retryResult(for request: Request, dueTo error: AFError, completion: @escaping (RetryResult) -> Void) + + /// Asynchronously retry the `Request`. + /// + /// - Parameters: + /// - request: `Request` which will be retried. + /// - timeDelay: `TimeInterval` after which the retry will be triggered. + func retryRequest(_ request: Request, withDelay timeDelay: TimeInterval?) +} + +// MARK: - Subclasses + +// MARK: - DataRequest + +/// `Request` subclass which handles in-memory `Data` download using `URLSessionDataTask`. +public class DataRequest: Request { + /// `URLRequestConvertible` value used to create `URLRequest`s for this instance. + public let convertible: URLRequestConvertible + /// `Data` read from the server so far. + public var data: Data? { mutableData } + + /// Protected storage for the `Data` read by the instance. + @Protected + private var mutableData: Data? = nil + + /// Creates a `DataRequest` using the provided parameters. + /// + /// - Parameters: + /// - id: `UUID` used for the `Hashable` and `Equatable` implementations. `UUID()` by default. + /// - convertible: `URLRequestConvertible` value used to create `URLRequest`s for this instance. + /// - underlyingQueue: `DispatchQueue` on which all internal `Request` work is performed. + /// - serializationQueue: `DispatchQueue` on which all serialization work is performed. By default targets + /// `underlyingQueue`, but can be passed another queue from a `Session`. + /// - eventMonitor: `EventMonitor` called for event callbacks from internal `Request` actions. + /// - interceptor: `RequestInterceptor` used throughout the request lifecycle. + /// - delegate: `RequestDelegate` that provides an interface to actions not performed by the `Request`. + init(id: UUID = UUID(), + convertible: URLRequestConvertible, + underlyingQueue: DispatchQueue, + serializationQueue: DispatchQueue, + eventMonitor: EventMonitor?, + interceptor: RequestInterceptor?, + delegate: RequestDelegate) { + self.convertible = convertible + + super.init(id: id, + underlyingQueue: underlyingQueue, + serializationQueue: serializationQueue, + eventMonitor: eventMonitor, + interceptor: interceptor, + delegate: delegate) + } + + override func reset() { + super.reset() + + mutableData = nil + } + + /// Called when `Data` is received by this instance. + /// + /// - Note: Also calls `updateDownloadProgress`. + /// + /// - Parameter data: The `Data` received. + func didReceive(data: Data) { + if self.data == nil { + mutableData = data + } else { + $mutableData.write { $0?.append(data) } + } + + updateDownloadProgress() + } + + override func task(for request: URLRequest, using session: URLSession) -> URLSessionTask { + let copiedRequest = request + return session.dataTask(with: copiedRequest) + } + + /// Called to updated the `downloadProgress` of the instance. + func updateDownloadProgress() { + let totalBytesReceived = Int64(data?.count ?? 0) + let totalBytesExpected = task?.response?.expectedContentLength ?? NSURLSessionTransferSizeUnknown + + downloadProgress.totalUnitCount = totalBytesExpected + downloadProgress.completedUnitCount = totalBytesReceived + + downloadProgressHandler?.queue.async { self.downloadProgressHandler?.handler(self.downloadProgress) } + } + + /// Validates the request, using the specified closure. + /// + /// - Note: If validation fails, subsequent calls to response handlers will have an associated error. + /// + /// - Parameter validation: `Validation` closure used to validate the response. + /// + /// - Returns: The instance. + @discardableResult + public func validate(_ validation: @escaping Validation) -> Self { + let validator: () -> Void = { [unowned self] in + guard self.error == nil, let response = self.response else { return } + + let result = validation(self.request, response, self.data) + + if case let .failure(error) = result { self.error = error.asAFError(or: .responseValidationFailed(reason: .customValidationFailed(error: error))) } + + self.eventMonitor?.request(self, + didValidateRequest: self.request, + response: response, + data: self.data, + withResult: result) + } + + $validators.write { $0.append(validator) } + + return self + } +} + +// MARK: - DataStreamRequest + +/// `Request` subclass which streams HTTP response `Data` through a `Handler` closure. +public final class DataStreamRequest: Request { + /// Closure type handling `DataStreamRequest.Stream` values. + public typealias Handler = (Stream) throws -> Void + + /// Type encapsulating an `Event` as it flows through the stream, as well as a `CancellationToken` which can be used + /// to stop the stream at any time. + public struct Stream { + /// Latest `Event` from the stream. + public let event: Event + /// Token used to cancel the stream. + public let token: CancellationToken + + /// Cancel the ongoing stream by canceling the underlying `DataStreamRequest`. + public func cancel() { + token.cancel() + } + } + + /// Type representing an event flowing through the stream. Contains either the `Result` of processing streamed + /// `Data` or the completion of the stream. + public enum Event { + /// Output produced every time the instance receives additional `Data`. The associated value contains the + /// `Result` of processing the incoming `Data`. + case stream(Result) + /// Output produced when the instance has completed, whether due to stream end, cancellation, or an error. + /// Associated `Completion` value contains the final state. + case complete(Completion) + } + + /// Value containing the state of a `DataStreamRequest` when the stream was completed. + public struct Completion { + /// Last `URLRequest` issued by the instance. + public let request: URLRequest? + /// Last `HTTPURLResponse` received by the instance. + public let response: HTTPURLResponse? + /// Last `URLSessionTaskMetrics` produced for the instance. + public let metrics: URLSessionTaskMetrics? + /// `AFError` produced for the instance, if any. + public let error: AFError? + } + + /// Type used to cancel an ongoing stream. + public struct CancellationToken { + let request: DataStreamRequest + + init(_ request: DataStreamRequest) { + self.request = request + } + + /// Cancel the ongoing stream by canceling the underlying `DataStreamRequest`. + public func cancel() { + request.cancel() + } + } + + /// `URLRequestConvertible` value used to create `URLRequest`s for this instance. + public let convertible: URLRequestConvertible + /// Whether or not the instance will be cancelled if stream parsing encounters an error. + public let automaticallyCancelOnStreamError: Bool + + /// Internal mutable state specific to this type. + struct StreamMutableState { + /// `OutputStream` bound to the `InputStream` produced by `asInputStream`, if it has been called. + var outputStream: OutputStream? + /// `DispatchQueue`s and stream closures associated called as `Data` is received. + var streams: [(queue: DispatchQueue, stream: (_ data: Data) -> Void)] = [] + } + + @Protected + var streamMutableState = StreamMutableState() + + /// Creates a `DataStreamRequest` using the provided parameters. + /// + /// - Parameters: + /// - id: `UUID` used for the `Hashable` and `Equatable` implementations. `UUID()` + /// by default. + /// - convertible: `URLRequestConvertible` value used to create `URLRequest`s for this + /// instance. + /// - automaticallyCancelOnStreamError: `Bool` indicating whether the instance will be cancelled when an `Error` + /// is thrown while serializing stream `Data`. + /// - underlyingQueue: `DispatchQueue` on which all internal `Request` work is performed. + /// - serializationQueue: `DispatchQueue` on which all serialization work is performed. By default + /// targets + /// `underlyingQueue`, but can be passed another queue from a `Session`. + /// - eventMonitor: `EventMonitor` called for event callbacks from internal `Request` actions. + /// - interceptor: `RequestInterceptor` used throughout the request lifecycle. + /// - delegate: `RequestDelegate` that provides an interface to actions not performed by + /// the `Request`. + init(id: UUID = UUID(), + convertible: URLRequestConvertible, + automaticallyCancelOnStreamError: Bool, + underlyingQueue: DispatchQueue, + serializationQueue: DispatchQueue, + eventMonitor: EventMonitor?, + interceptor: RequestInterceptor?, + delegate: RequestDelegate) { + self.convertible = convertible + self.automaticallyCancelOnStreamError = automaticallyCancelOnStreamError + + super.init(id: id, + underlyingQueue: underlyingQueue, + serializationQueue: serializationQueue, + eventMonitor: eventMonitor, + interceptor: interceptor, + delegate: delegate) + } + + override func task(for request: URLRequest, using session: URLSession) -> URLSessionTask { + let copiedRequest = request + return session.dataTask(with: copiedRequest) + } + + override func finish(error: AFError? = nil) { + $streamMutableState.write { state in + state.outputStream?.close() + } + + super.finish(error: error) + } + + func didReceive(data: Data) { + $streamMutableState.read { state in + if let stream = state.outputStream { + underlyingQueue.async { + var bytes = Array(data) + stream.write(&bytes, maxLength: bytes.count) + } + } + + underlyingQueue.async { state.streams.forEach { stream in stream.queue.async { stream.stream(data) } } } + } + } + + /// Validates the `URLRequest` and `HTTPURLResponse` received for the instance using the provided `Validation` closure. + /// + /// - Parameter validation: `Validation` closure used to validate the request and response. + /// + /// - Returns: The `DataStreamRequest`. + @discardableResult + public func validate(_ validation: @escaping Validation) -> Self { + let validator: () -> Void = { [unowned self] in + guard self.error == nil, let response = self.response else { return } + + let result = validation(self.request, response) + + if case let .failure(error) = result { + self.error = error.asAFError(or: .responseValidationFailed(reason: .customValidationFailed(error: error))) + } + + self.eventMonitor?.request(self, + didValidateRequest: self.request, + response: response, + withResult: result) + } + + $validators.write { $0.append(validator) } + + return self + } + + /// Produces an `InputStream` that receives the `Data` received by the instance. + /// + /// - Note: The `InputStream` produced by this method must have `open()` called before being able to read `Data`. + /// Additionally, this method will automatically call `resume()` on the instance, regardless of whether or + /// not the creating session has `startRequestsImmediately` set to `true`. + /// + /// - Parameter bufferSize: Size, in bytes, of the buffer between the `OutputStream` and `InputStream`. + /// + /// - Returns: The `InputStream` bound to the internal `OutboundStream`. + public func asInputStream(bufferSize: Int = 1024) -> InputStream? { + defer { resume() } + + var inputStream: InputStream? + $streamMutableState.write { state in + Foundation.Stream.getBoundStreams(withBufferSize: bufferSize, + inputStream: &inputStream, + outputStream: &state.outputStream) + state.outputStream?.open() + } + + return inputStream + } + + func capturingError(from closure: () throws -> Void) { + do { + try closure() + } catch { + self.error = error.asAFError(or: .responseSerializationFailed(reason: .customSerializationFailed(error: error))) + cancel() + } + } + + func appendStreamCompletion(on queue: DispatchQueue, + stream: @escaping Handler) { + appendResponseSerializer { + self.underlyingQueue.async { + self.responseSerializerDidComplete { + queue.async { + do { + let completion = Completion(request: self.request, + response: self.response, + metrics: self.metrics, + error: self.error) + try stream(.init(event: .complete(completion), token: .init(self))) + } catch { + // Ignore error, as errors on Completion can't be handled anyway. + } + } + } + } + } + } +} + +extension DataStreamRequest.Stream { + /// `Success` value of the instance, if any. + public var value: Success? { + guard case let .stream(result) = event, case let .success(value) = result else { return nil } + + return value + } + + /// `Failure` value of the instance, if any. + public var error: Failure? { + guard case let .stream(result) = event, case let .failure(error) = result else { return nil } + + return error + } + + /// `Completion` value of the instance, if any. + public var completion: DataStreamRequest.Completion? { + guard case let .complete(completion) = event else { return nil } + + return completion + } +} + +// MARK: - DownloadRequest + +/// `Request` subclass which downloads `Data` to a file on disk using `URLSessionDownloadTask`. +public class DownloadRequest: Request { + /// A set of options to be executed prior to moving a downloaded file from the temporary `URL` to the destination + /// `URL`. + public struct Options: OptionSet { + /// Specifies that intermediate directories for the destination URL should be created. + public static let createIntermediateDirectories = Options(rawValue: 1 << 0) + /// Specifies that any previous file at the destination `URL` should be removed. + public static let removePreviousFile = Options(rawValue: 1 << 1) + + public let rawValue: Int + + public init(rawValue: Int) { + self.rawValue = rawValue + } + } + + // MARK: Destination + + /// A closure executed once a `DownloadRequest` has successfully completed in order to determine where to move the + /// temporary file written to during the download process. The closure takes two arguments: the temporary file URL + /// and the URL response, and returns a two arguments: the file URL where the temporary file should be moved and + /// the options defining how the file should be moved. + public typealias Destination = (_ temporaryURL: URL, + _ response: HTTPURLResponse) -> (destinationURL: URL, options: Options) + + /// Creates a download file destination closure which uses the default file manager to move the temporary file to a + /// file URL in the first available directory with the specified search path directory and search path domain mask. + /// + /// - Parameters: + /// - directory: The search path directory. `.documentDirectory` by default. + /// - domain: The search path domain mask. `.userDomainMask` by default. + /// - options: `DownloadRequest.Options` used when moving the downloaded file to its destination. None by + /// default. + /// - Returns: The `Destination` closure. + public class func suggestedDownloadDestination(for directory: FileManager.SearchPathDirectory = .documentDirectory, + in domain: FileManager.SearchPathDomainMask = .userDomainMask, + options: Options = []) -> Destination { + { temporaryURL, response in + let directoryURLs = FileManager.default.urls(for: directory, in: domain) + let url = directoryURLs.first?.appendingPathComponent(response.suggestedFilename!) ?? temporaryURL + + return (url, options) + } + } + + /// Default `Destination` used by Alamofire to ensure all downloads persist. This `Destination` prepends + /// `Alamofire_` to the automatically generated download name and moves it within the temporary directory. Files + /// with this destination must be additionally moved if they should survive the system reclamation of temporary + /// space. + static let defaultDestination: Destination = { url, _ in + let filename = "Alamofire_\(url.lastPathComponent)" + let destination = url.deletingLastPathComponent().appendingPathComponent(filename) + + return (destination, []) + } + + // MARK: Downloadable + + /// Type describing the source used to create the underlying `URLSessionDownloadTask`. + public enum Downloadable { + /// Download should be started from the `URLRequest` produced by the associated `URLRequestConvertible` value. + case request(URLRequestConvertible) + /// Download should be started from the associated resume `Data` value. + case resumeData(Data) + } + + // MARK: Mutable State + + /// Type containing all mutable state for `DownloadRequest` instances. + private struct DownloadRequestMutableState { + /// Possible resume `Data` produced when cancelling the instance. + var resumeData: Data? + /// `URL` to which `Data` is being downloaded. + var fileURL: URL? + } + + /// Protected mutable state specific to `DownloadRequest`. + @Protected + private var mutableDownloadState = DownloadRequestMutableState() + + /// If the download is resumable and eventually cancelled, this value may be used to resume the download using the + /// `download(resumingWith data:)` API. + /// + /// - Note: For more information about `resumeData`, see [Apple's documentation](https://developer.apple.com/documentation/foundation/urlsessiondownloadtask/1411634-cancel). + public var resumeData: Data? { mutableDownloadState.resumeData } + /// If the download is successful, the `URL` where the file was downloaded. + public var fileURL: URL? { mutableDownloadState.fileURL } + + // MARK: Initial State + + /// `Downloadable` value used for this instance. + public let downloadable: Downloadable + /// The `Destination` to which the downloaded file is moved. + let destination: Destination + + /// Creates a `DownloadRequest` using the provided parameters. + /// + /// - Parameters: + /// - id: `UUID` used for the `Hashable` and `Equatable` implementations. `UUID()` by default. + /// - downloadable: `Downloadable` value used to create `URLSessionDownloadTasks` for the instance. + /// - underlyingQueue: `DispatchQueue` on which all internal `Request` work is performed. + /// - serializationQueue: `DispatchQueue` on which all serialization work is performed. By default targets + /// `underlyingQueue`, but can be passed another queue from a `Session`. + /// - eventMonitor: `EventMonitor` called for event callbacks from internal `Request` actions. + /// - interceptor: `RequestInterceptor` used throughout the request lifecycle. + /// - delegate: `RequestDelegate` that provides an interface to actions not performed by the `Request` + /// - destination: `Destination` closure used to move the downloaded file to its final location. + init(id: UUID = UUID(), + downloadable: Downloadable, + underlyingQueue: DispatchQueue, + serializationQueue: DispatchQueue, + eventMonitor: EventMonitor?, + interceptor: RequestInterceptor?, + delegate: RequestDelegate, + destination: @escaping Destination) { + self.downloadable = downloadable + self.destination = destination + + super.init(id: id, + underlyingQueue: underlyingQueue, + serializationQueue: serializationQueue, + eventMonitor: eventMonitor, + interceptor: interceptor, + delegate: delegate) + } + + override func reset() { + super.reset() + + $mutableDownloadState.write { + $0.resumeData = nil + $0.fileURL = nil + } + } + + /// Called when a download has finished. + /// + /// - Parameters: + /// - task: `URLSessionTask` that finished the download. + /// - result: `Result` of the automatic move to `destination`. + func didFinishDownloading(using task: URLSessionTask, with result: Result) { + eventMonitor?.request(self, didFinishDownloadingUsing: task, with: result) + + switch result { + case let .success(url): mutableDownloadState.fileURL = url + case let .failure(error): self.error = error + } + } + + /// Updates the `downloadProgress` using the provided values. + /// + /// - Parameters: + /// - bytesWritten: Total bytes written so far. + /// - totalBytesExpectedToWrite: Total bytes expected to write. + func updateDownloadProgress(bytesWritten: Int64, totalBytesExpectedToWrite: Int64) { + downloadProgress.totalUnitCount = totalBytesExpectedToWrite + downloadProgress.completedUnitCount += bytesWritten + + downloadProgressHandler?.queue.async { self.downloadProgressHandler?.handler(self.downloadProgress) } + } + + override func task(for request: URLRequest, using session: URLSession) -> URLSessionTask { + session.downloadTask(with: request) + } + + /// Creates a `URLSessionTask` from the provided resume data. + /// + /// - Parameters: + /// - data: `Data` used to resume the download. + /// - session: `URLSession` used to create the `URLSessionTask`. + /// + /// - Returns: The `URLSessionTask` created. + public func task(forResumeData data: Data, using session: URLSession) -> URLSessionTask { + session.downloadTask(withResumeData: data) + } + + /// Cancels the instance. Once cancelled, a `DownloadRequest` can no longer be resumed or suspended. + /// + /// - Note: This method will NOT produce resume data. If you wish to cancel and produce resume data, use + /// `cancel(producingResumeData:)` or `cancel(byProducingResumeData:)`. + /// + /// - Returns: The instance. + @discardableResult + public override func cancel() -> Self { + cancel(producingResumeData: false) + } + + /// Cancels the instance, optionally producing resume data. Once cancelled, a `DownloadRequest` can no longer be + /// resumed or suspended. + /// + /// - Note: If `producingResumeData` is `true`, the `resumeData` property will be populated with any resume data, if + /// available. + /// + /// - Returns: The instance. + @discardableResult + public func cancel(producingResumeData shouldProduceResumeData: Bool) -> Self { + cancel(optionallyProducingResumeData: shouldProduceResumeData ? { _ in } : nil) + } + + /// Cancels the instance while producing resume data. Once cancelled, a `DownloadRequest` can no longer be resumed + /// or suspended. + /// + /// - Note: The resume data passed to the completion handler will also be available on the instance's `resumeData` + /// property. + /// + /// - Parameter completionHandler: The completion handler that is called when the download has been successfully + /// cancelled. It is not guaranteed to be called on a particular queue, so you may + /// want use an appropriate queue to perform your work. + /// + /// - Returns: The instance. + @discardableResult + public func cancel(byProducingResumeData completionHandler: @escaping (_ data: Data?) -> Void) -> Self { + cancel(optionallyProducingResumeData: completionHandler) + } + + /// Internal implementation of cancellation that optionally takes a resume data handler. If no handler is passed, + /// cancellation is performed without producing resume data. + /// + /// - Parameter completionHandler: Optional resume data handler. + /// + /// - Returns: The instance. + private func cancel(optionallyProducingResumeData completionHandler: ((_ resumeData: Data?) -> Void)?) -> Self { + $mutableState.write { mutableState in + guard mutableState.state.canTransitionTo(.cancelled) else { return } + + mutableState.state = .cancelled + + underlyingQueue.async { self.didCancel() } + + guard let task = mutableState.tasks.last as? URLSessionDownloadTask, task.state != .completed else { + underlyingQueue.async { self.finish() } + return + } + + if let completionHandler = completionHandler { + // Resume to ensure metrics are gathered. + task.resume() + task.cancel { resumeData in + self.mutableDownloadState.resumeData = resumeData + self.underlyingQueue.async { self.didCancelTask(task) } + completionHandler(resumeData) + } + } else { + // Resume to ensure metrics are gathered. + task.resume() + task.cancel(byProducingResumeData: { _ in }) + self.underlyingQueue.async { self.didCancelTask(task) } + } + } + + return self + } + + /// Validates the request, using the specified closure. + /// + /// - Note: If validation fails, subsequent calls to response handlers will have an associated error. + /// + /// - Parameter validation: `Validation` closure to validate the response. + /// + /// - Returns: The instance. + @discardableResult + public func validate(_ validation: @escaping Validation) -> Self { + let validator: () -> Void = { [unowned self] in + guard self.error == nil, let response = self.response else { return } + + let result = validation(self.request, response, self.fileURL) + + if case let .failure(error) = result { self.error = error.asAFError(or: .responseValidationFailed(reason: .customValidationFailed(error: error))) } + + self.eventMonitor?.request(self, + didValidateRequest: self.request, + response: response, + fileURL: self.fileURL, + withResult: result) + } + + $validators.write { $0.append(validator) } + + return self + } +} + +// MARK: - UploadRequest + +/// `DataRequest` subclass which handles `Data` upload from memory, file, or stream using `URLSessionUploadTask`. +public class UploadRequest: DataRequest { + /// Type describing the origin of the upload, whether `Data`, file, or stream. + public enum Uploadable { + /// Upload from the provided `Data` value. + case data(Data) + /// Upload from the provided file `URL`, as well as a `Bool` determining whether the source file should be + /// automatically removed once uploaded. + case file(URL, shouldRemove: Bool) + /// Upload from the provided `InputStream`. + case stream(InputStream) + } + + // MARK: Initial State + + /// The `UploadableConvertible` value used to produce the `Uploadable` value for this instance. + public let upload: UploadableConvertible + + /// `FileManager` used to perform cleanup tasks, including the removal of multipart form encoded payloads written + /// to disk. + public let fileManager: FileManager + + // MARK: Mutable State + + /// `Uploadable` value used by the instance. + public var uploadable: Uploadable? + + /// Creates an `UploadRequest` using the provided parameters. + /// + /// - Parameters: + /// - id: `UUID` used for the `Hashable` and `Equatable` implementations. `UUID()` by default. + /// - convertible: `UploadConvertible` value used to determine the type of upload to be performed. + /// - underlyingQueue: `DispatchQueue` on which all internal `Request` work is performed. + /// - serializationQueue: `DispatchQueue` on which all serialization work is performed. By default targets + /// `underlyingQueue`, but can be passed another queue from a `Session`. + /// - eventMonitor: `EventMonitor` called for event callbacks from internal `Request` actions. + /// - interceptor: `RequestInterceptor` used throughout the request lifecycle. + /// - delegate: `RequestDelegate` that provides an interface to actions not performed by the `Request`. + init(id: UUID = UUID(), + convertible: UploadConvertible, + underlyingQueue: DispatchQueue, + serializationQueue: DispatchQueue, + eventMonitor: EventMonitor?, + interceptor: RequestInterceptor?, + fileManager: FileManager, + delegate: RequestDelegate) { + upload = convertible + self.fileManager = fileManager + + super.init(id: id, + convertible: convertible, + underlyingQueue: underlyingQueue, + serializationQueue: serializationQueue, + eventMonitor: eventMonitor, + interceptor: interceptor, + delegate: delegate) + } + + /// Called when the `Uploadable` value has been created from the `UploadConvertible`. + /// + /// - Parameter uploadable: The `Uploadable` that was created. + func didCreateUploadable(_ uploadable: Uploadable) { + self.uploadable = uploadable + + eventMonitor?.request(self, didCreateUploadable: uploadable) + } + + /// Called when the `Uploadable` value could not be created. + /// + /// - Parameter error: `AFError` produced by the failure. + func didFailToCreateUploadable(with error: AFError) { + self.error = error + + eventMonitor?.request(self, didFailToCreateUploadableWithError: error) + + retryOrFinish(error: error) + } + + override func task(for request: URLRequest, using session: URLSession) -> URLSessionTask { + guard let uploadable = uploadable else { + fatalError("Attempting to create a URLSessionUploadTask when Uploadable value doesn't exist.") + } + + switch uploadable { + case let .data(data): return session.uploadTask(with: request, from: data) + case let .file(url, _): return session.uploadTask(with: request, fromFile: url) + case .stream: return session.uploadTask(withStreamedRequest: request) + } + } + + override func reset() { + // Uploadable must be recreated on every retry. + uploadable = nil + + super.reset() + } + + /// Produces the `InputStream` from `uploadable`, if it can. + /// + /// - Note: Calling this method with a non-`.stream` `Uploadable` is a logic error and will crash. + /// + /// - Returns: The `InputStream`. + func inputStream() -> InputStream { + guard let uploadable = uploadable else { + fatalError("Attempting to access the input stream but the uploadable doesn't exist.") + } + + guard case let .stream(stream) = uploadable else { + fatalError("Attempted to access the stream of an UploadRequest that wasn't created with one.") + } + + eventMonitor?.request(self, didProvideInputStream: stream) + + return stream + } + + public override func cleanup() { + defer { super.cleanup() } + + guard + let uploadable = self.uploadable, + case let .file(url, shouldRemove) = uploadable, + shouldRemove + else { return } + + try? fileManager.removeItem(at: url) + } +} + +/// A type that can produce an `UploadRequest.Uploadable` value. +public protocol UploadableConvertible { + /// Produces an `UploadRequest.Uploadable` value from the instance. + /// + /// - Returns: The `UploadRequest.Uploadable`. + /// - Throws: Any `Error` produced during creation. + func createUploadable() throws -> UploadRequest.Uploadable +} + +extension UploadRequest.Uploadable: UploadableConvertible { + public func createUploadable() throws -> UploadRequest.Uploadable { + self + } +} + +/// A type that can be converted to an upload, whether from an `UploadRequest.Uploadable` or `URLRequestConvertible`. +public protocol UploadConvertible: UploadableConvertible & URLRequestConvertible {} diff --git b/Pods/Alamofire/Source/RequestInterceptor.swift a/Pods/Alamofire/Source/RequestInterceptor.swift new file mode 100644 index 0000000..09ba4ee --- /dev/null +++ a/Pods/Alamofire/Source/RequestInterceptor.swift @@ -0,0 +1,243 @@ +// +// 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) -> 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) -> 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) -> 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) -> 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) -> Void) { + adapt(urlRequest, for: session, using: adapters, completion: completion) + } + + private func adapt(_ urlRequest: URLRequest, + for session: Session, + using adapters: [RequestAdapter], + completion: @escaping (Result) -> 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) + } + } + } +} diff --git b/Pods/Alamofire/Source/RequestTaskMap.swift a/Pods/Alamofire/Source/RequestTaskMap.swift new file mode 100644 index 0000000..617536d --- /dev/null +++ a/Pods/Alamofire/Source/RequestTaskMap.swift @@ -0,0 +1,142 @@ +// +// 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 typealias Events = (completed: Bool, metricsGathered: Bool) + + private var tasksToRequests: [URLSessionTask: Request] + private var requestsToTasks: [Request: URLSessionTask] + private var taskEvents: [URLSessionTask: Events] + + var requests: [Request] { + 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 { 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 { 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) -> Bool { + 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); return false + case (true, false): self[task] = nil; return true + } + } + + mutating func disassociateIfNecessaryAfterCompletingTask(_ task: URLSessionTask) -> Bool { + 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.") + #if os(watchOS) // watchOS doesn't gather metrics, so unconditionally remove the reference and return true. + default: self[task] = nil; return true + #else + case (false, false): taskEvents[task] = (completed: true, metricsGathered: false); return false + case (false, true): self[task] = nil; return true + #endif + } + } +} diff --git b/Pods/Alamofire/Source/Response.swift a/Pods/Alamofire/Source/Response.swift new file mode 100644 index 0000000..530fc4e --- /dev/null +++ a/Pods/Alamofire/Source/Response.swift @@ -0,0 +1,405 @@ +// +// Response.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 + +/// Default type of `DataResponse` returned by Alamofire, with an `AFError` `Failure` type. +public typealias AFDataResponse = DataResponse +/// Default type of `DownloadResponse` returned by Alamofire, with an `AFError` `Failure` type. +public typealias AFDownloadResponse = DownloadResponse + +/// Type used to store all values associated with a serialized response of a `DataRequest` or `UploadRequest`. +public struct DataResponse { + /// The URL request sent to the server. + public let request: URLRequest? + + /// The server's response to the URL request. + public let response: HTTPURLResponse? + + /// The data returned by the server. + public let data: Data? + + /// The final metrics of the response. + /// + /// - Note: Due to `FB7624529`, collection of `URLSessionTaskMetrics` on watchOS is currently disabled.` + /// + public let metrics: URLSessionTaskMetrics? + + /// The time taken to serialize the response. + public let serializationDuration: TimeInterval + + /// The result of response serialization. + public let result: Result + + /// Returns the associated value of the result if it is a success, `nil` otherwise. + public var value: Success? { result.success } + + /// Returns the associated error value if the result if it is a failure, `nil` otherwise. + public var error: Failure? { result.failure } + + /// Creates a `DataResponse` instance with the specified parameters derived from the response serialization. + /// + /// - Parameters: + /// - request: The `URLRequest` sent to the server. + /// - response: The `HTTPURLResponse` from the server. + /// - data: The `Data` returned by the server. + /// - metrics: The `URLSessionTaskMetrics` of the `DataRequest` or `UploadRequest`. + /// - serializationDuration: The duration taken by serialization. + /// - result: The `Result` of response serialization. + public init(request: URLRequest?, + response: HTTPURLResponse?, + data: Data?, + metrics: URLSessionTaskMetrics?, + serializationDuration: TimeInterval, + result: Result) { + self.request = request + self.response = response + self.data = data + self.metrics = metrics + self.serializationDuration = serializationDuration + self.result = result + } +} + +// MARK: - + +extension DataResponse: CustomStringConvertible, CustomDebugStringConvertible { + /// The textual representation used when written to an output stream, which includes whether the result was a + /// success or failure. + public var description: String { + "\(result)" + } + + /// The debug textual representation used when written to an output stream, which includes the URL request, the URL + /// response, the server data, the duration of the network and serialization actions, and the response serialization + /// result. + public var debugDescription: String { + let requestDescription = request.map { "\($0.httpMethod!) \($0)" } ?? "nil" + let requestBody = request?.httpBody.map { String(decoding: $0, as: UTF8.self) } ?? "None" + let responseDescription = response.map { response in + let sortedHeaders = response.headers.sorted() + + return """ + [Status Code]: \(response.statusCode) + [Headers]: + \(sortedHeaders) + """ + } ?? "nil" + let responseBody = data.map { String(decoding: $0, as: UTF8.self) } ?? "None" + let metricsDescription = metrics.map { "\($0.taskInterval.duration)s" } ?? "None" + + return """ + [Request]: \(requestDescription) + [Request Body]: \n\(requestBody) + [Response]: \n\(responseDescription) + [Response Body]: \n\(responseBody) + [Data]: \(data?.description ?? "None") + [Network Duration]: \(metricsDescription) + [Serialization Duration]: \(serializationDuration)s + [Result]: \(result) + """ + } +} + +// MARK: - + +extension DataResponse { + /// Evaluates the specified closure when the result of this `DataResponse` is a success, passing the unwrapped + /// result value as a parameter. + /// + /// Use the `map` method with a closure that does not throw. For example: + /// + /// let possibleData: DataResponse = ... + /// let possibleInt = possibleData.map { $0.count } + /// + /// - parameter transform: A closure that takes the success value of the instance's result. + /// + /// - returns: A `DataResponse` whose result wraps the value returned by the given closure. If this instance's + /// result is a failure, returns a response wrapping the same failure. + public func map(_ transform: (Success) -> NewSuccess) -> DataResponse { + DataResponse(request: request, + response: response, + data: data, + metrics: metrics, + serializationDuration: serializationDuration, + result: result.map(transform)) + } + + /// Evaluates the given closure when the result of this `DataResponse` is a success, passing the unwrapped result + /// value as a parameter. + /// + /// Use the `tryMap` method with a closure that may throw an error. For example: + /// + /// let possibleData: DataResponse = ... + /// let possibleObject = possibleData.tryMap { + /// try JSONSerialization.jsonObject(with: $0) + /// } + /// + /// - parameter transform: A closure that takes the success value of the instance's result. + /// + /// - returns: A success or failure `DataResponse` depending on the result of the given closure. If this instance's + /// result is a failure, returns the same failure. + public func tryMap(_ transform: (Success) throws -> NewSuccess) -> DataResponse { + DataResponse(request: request, + response: response, + data: data, + metrics: metrics, + serializationDuration: serializationDuration, + result: result.tryMap(transform)) + } + + /// Evaluates the specified closure when the `DataResponse` is a failure, passing the unwrapped error as a parameter. + /// + /// Use the `mapError` function with a closure that does not throw. For example: + /// + /// let possibleData: DataResponse = ... + /// let withMyError = possibleData.mapError { MyError.error($0) } + /// + /// - Parameter transform: A closure that takes the error of the instance. + /// + /// - Returns: A `DataResponse` instance containing the result of the transform. + public func mapError(_ transform: (Failure) -> NewFailure) -> DataResponse { + DataResponse(request: request, + response: response, + data: data, + metrics: metrics, + serializationDuration: serializationDuration, + result: result.mapError(transform)) + } + + /// Evaluates the specified closure when the `DataResponse` 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: DataResponse = ... + /// let possibleObject = possibleData.tryMapError { + /// try someFailableFunction(taking: $0) + /// } + /// + /// - Parameter transform: A throwing closure that takes the error of the instance. + /// + /// - Returns: A `DataResponse` instance containing the result of the transform. + public func tryMapError(_ transform: (Failure) throws -> NewFailure) -> DataResponse { + DataResponse(request: request, + response: response, + data: data, + metrics: metrics, + serializationDuration: serializationDuration, + result: result.tryMapError(transform)) + } +} + +// MARK: - + +/// Used to store all data associated with a serialized response of a download request. +public struct DownloadResponse { + /// The URL request sent to the server. + public let request: URLRequest? + + /// The server's response to the URL request. + public let response: HTTPURLResponse? + + /// The final destination URL of the data returned from the server after it is moved. + public let fileURL: URL? + + /// The resume data generated if the request was cancelled. + public let resumeData: Data? + + /// The final metrics of the response. + /// + /// - Note: Due to `FB7624529`, collection of `URLSessionTaskMetrics` on watchOS is currently disabled.` + /// + public let metrics: URLSessionTaskMetrics? + + /// The time taken to serialize the response. + public let serializationDuration: TimeInterval + + /// The result of response serialization. + public let result: Result + + /// Returns the associated value of the result if it is a success, `nil` otherwise. + public var value: Success? { result.success } + + /// Returns the associated error value if the result if it is a failure, `nil` otherwise. + public var error: Failure? { result.failure } + + /// Creates a `DownloadResponse` instance with the specified parameters derived from response serialization. + /// + /// - Parameters: + /// - request: The `URLRequest` sent to the server. + /// - response: The `HTTPURLResponse` from the server. + /// - temporaryURL: The temporary destination `URL` of the data returned from the server. + /// - destinationURL: The final destination `URL` of the data returned from the server, if it was moved. + /// - resumeData: The resume `Data` generated if the request was cancelled. + /// - metrics: The `URLSessionTaskMetrics` of the `DownloadRequest`. + /// - serializationDuration: The duration taken by serialization. + /// - result: The `Result` of response serialization. + public init(request: URLRequest?, + response: HTTPURLResponse?, + fileURL: URL?, + resumeData: Data?, + metrics: URLSessionTaskMetrics?, + serializationDuration: TimeInterval, + result: Result) { + self.request = request + self.response = response + self.fileURL = fileURL + self.resumeData = resumeData + self.metrics = metrics + self.serializationDuration = serializationDuration + self.result = result + } +} + +// MARK: - + +extension DownloadResponse: CustomStringConvertible, CustomDebugStringConvertible { + /// The textual representation used when written to an output stream, which includes whether the result was a + /// success or failure. + public var description: String { + "\(result)" + } + + /// The debug textual representation used when written to an output stream, which includes the URL request, the URL + /// response, the temporary and destination URLs, the resume data, the durations of the network and serialization + /// actions, and the response serialization result. + public var debugDescription: String { + let requestDescription = request.map { "\($0.httpMethod!) \($0)" } ?? "nil" + let requestBody = request?.httpBody.map { String(decoding: $0, as: UTF8.self) } ?? "None" + let responseDescription = response.map { response in + let sortedHeaders = response.headers.sorted() + + return """ + [Status Code]: \(response.statusCode) + [Headers]: + \(sortedHeaders) + """ + } ?? "nil" + let metricsDescription = metrics.map { "\($0.taskInterval.duration)s" } ?? "None" + let resumeDataDescription = resumeData.map { "\($0)" } ?? "None" + + return """ + [Request]: \(requestDescription) + [Request Body]: \n\(requestBody) + [Response]: \n\(responseDescription) + [File URL]: \(fileURL?.path ?? "nil") + [ResumeData]: \(resumeDataDescription) + [Network Duration]: \(metricsDescription) + [Serialization Duration]: \(serializationDuration)s + [Result]: \(result) + """ + } +} + +// MARK: - + +extension DownloadResponse { + /// Evaluates the given closure when the result of this `DownloadResponse` is a success, passing the unwrapped + /// result value as a parameter. + /// + /// Use the `map` method with a closure that does not throw. For example: + /// + /// let possibleData: DownloadResponse = ... + /// let possibleInt = possibleData.map { $0.count } + /// + /// - parameter transform: A closure that takes the success value of the instance's result. + /// + /// - returns: A `DownloadResponse` whose result wraps the value returned by the given closure. If this instance's + /// result is a failure, returns a response wrapping the same failure. + public func map(_ transform: (Success) -> NewSuccess) -> DownloadResponse { + DownloadResponse(request: request, + response: response, + fileURL: fileURL, + resumeData: resumeData, + metrics: metrics, + serializationDuration: serializationDuration, + result: result.map(transform)) + } + + /// Evaluates the given closure when the result of this `DownloadResponse` is a success, passing the unwrapped + /// result value as a parameter. + /// + /// Use the `tryMap` method with a closure that may throw an error. For example: + /// + /// let possibleData: DownloadResponse = ... + /// let possibleObject = possibleData.tryMap { + /// try JSONSerialization.jsonObject(with: $0) + /// } + /// + /// - parameter transform: A closure that takes the success value of the instance's result. + /// + /// - returns: A success or failure `DownloadResponse` depending on the result of the given closure. If this + /// instance's result is a failure, returns the same failure. + public func tryMap(_ transform: (Success) throws -> NewSuccess) -> DownloadResponse { + DownloadResponse(request: request, + response: response, + fileURL: fileURL, + resumeData: resumeData, + metrics: metrics, + serializationDuration: serializationDuration, + result: result.tryMap(transform)) + } + + /// Evaluates the specified closure when the `DownloadResponse` is a failure, passing the unwrapped error as a parameter. + /// + /// Use the `mapError` function with a closure that does not throw. For example: + /// + /// let possibleData: DownloadResponse = ... + /// let withMyError = possibleData.mapError { MyError.error($0) } + /// + /// - Parameter transform: A closure that takes the error of the instance. + /// + /// - Returns: A `DownloadResponse` instance containing the result of the transform. + public func mapError(_ transform: (Failure) -> NewFailure) -> DownloadResponse { + DownloadResponse(request: request, + response: response, + fileURL: fileURL, + resumeData: resumeData, + metrics: metrics, + serializationDuration: serializationDuration, + result: result.mapError(transform)) + } + + /// Evaluates the specified closure when the `DownloadResponse` 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: DownloadResponse = ... + /// let possibleObject = possibleData.tryMapError { + /// try someFailableFunction(taking: $0) + /// } + /// + /// - Parameter transform: A throwing closure that takes the error of the instance. + /// + /// - Returns: A `DownloadResponse` instance containing the result of the transform. + public func tryMapError(_ transform: (Failure) throws -> NewFailure) -> DownloadResponse { + DownloadResponse(request: request, + response: response, + fileURL: fileURL, + resumeData: resumeData, + metrics: metrics, + serializationDuration: serializationDuration, + result: result.tryMapError(transform)) + } +} diff --git b/Pods/Alamofire/Source/ResponseSerialization.swift a/Pods/Alamofire/Source/ResponseSerialization.swift new file mode 100644 index 0000000..2de9d05 --- /dev/null +++ a/Pods/Alamofire/Source/ResponseSerialization.swift @@ -0,0 +1,948 @@ +// +// ResponseSerialization.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: Protocols + +/// The type to which all data response serializers must conform in order to serialize a response. +public protocol DataResponseSerializerProtocol { + /// The type of serialized object to be created. + associatedtype SerializedObject + + /// Serialize the response `Data` into the provided type.. + /// + /// - Parameters: + /// - request: `URLRequest` which was used to perform the request, if any. + /// - response: `HTTPURLResponse` received from the server, if any. + /// - data: `Data` returned from the server, if any. + /// - error: `Error` produced by Alamofire or the underlying `URLSession` during the request. + /// + /// - Returns: The `SerializedObject`. + /// - Throws: Any `Error` produced during serialization. + func serialize(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?) throws -> SerializedObject +} + +/// The type to which all download response serializers must conform in order to serialize a response. +public protocol DownloadResponseSerializerProtocol { + /// The type of serialized object to be created. + associatedtype SerializedObject + + /// Serialize the downloaded response `Data` from disk into the provided type.. + /// + /// - Parameters: + /// - request: `URLRequest` which was used to perform the request, if any. + /// - response: `HTTPURLResponse` received from the server, if any. + /// - fileURL: File `URL` to which the response data was downloaded. + /// - error: `Error` produced by Alamofire or the underlying `URLSession` during the request. + /// + /// - Returns: The `SerializedObject`. + /// - Throws: Any `Error` produced during serialization. + func serializeDownload(request: URLRequest?, response: HTTPURLResponse?, fileURL: URL?, error: Error?) throws -> SerializedObject +} + +/// A serializer that can handle both data and download responses. +public protocol ResponseSerializer: DataResponseSerializerProtocol & DownloadResponseSerializerProtocol { + /// `DataPreprocessor` used to prepare incoming `Data` for serialization. + var dataPreprocessor: DataPreprocessor { get } + /// `HTTPMethod`s for which empty response bodies are considered appropriate. + var emptyRequestMethods: Set { get } + /// HTTP response codes for which empty response bodies are considered appropriate. + var emptyResponseCodes: Set { get } +} + +/// Type used to preprocess `Data` before it handled by a serializer. +public protocol DataPreprocessor { + /// Process `Data` before it's handled by a serializer. + /// - Parameter data: The raw `Data` to process. + func preprocess(_ data: Data) throws -> Data +} + +/// `DataPreprocessor` that returns passed `Data` without any transform. +public struct PassthroughPreprocessor: DataPreprocessor { + public init() {} + + public func preprocess(_ data: Data) throws -> Data { data } +} + +/// `DataPreprocessor` that trims Google's typical `)]}',\n` XSSI JSON header. +public struct GoogleXSSIPreprocessor: DataPreprocessor { + public init() {} + + public func preprocess(_ data: Data) throws -> Data { + (data.prefix(6) == Data(")]}',\n".utf8)) ? data.dropFirst(6) : data + } +} + +extension ResponseSerializer { + /// Default `DataPreprocessor`. `PassthroughPreprocessor` by default. + public static var defaultDataPreprocessor: DataPreprocessor { PassthroughPreprocessor() } + /// Default `HTTPMethod`s for which empty response bodies are considered appropriate. `[.head]` by default. + public static var defaultEmptyRequestMethods: Set { [.head] } + /// HTTP response codes for which empty response bodies are considered appropriate. `[204, 205]` by default. + public static var defaultEmptyResponseCodes: Set { [204, 205] } + + public var dataPreprocessor: DataPreprocessor { Self.defaultDataPreprocessor } + public var emptyRequestMethods: Set { Self.defaultEmptyRequestMethods } + public var emptyResponseCodes: Set { Self.defaultEmptyResponseCodes } + + /// Determines whether the `request` allows empty response bodies, if `request` exists. + /// + /// - Parameter request: `URLRequest` to evaluate. + /// + /// - Returns: `Bool` representing the outcome of the evaluation, or `nil` if `request` was `nil`. + public func requestAllowsEmptyResponseData(_ request: URLRequest?) -> Bool? { + request.flatMap { $0.httpMethod } + .flatMap(HTTPMethod.init) + .map { emptyRequestMethods.contains($0) } + } + + /// Determines whether the `response` allows empty response bodies, if `response` exists`. + /// + /// - Parameter response: `HTTPURLResponse` to evaluate. + /// + /// - Returns: `Bool` representing the outcome of the evaluation, or `nil` if `response` was `nil`. + public func responseAllowsEmptyResponseData(_ response: HTTPURLResponse?) -> Bool? { + response.flatMap { $0.statusCode } + .map { emptyResponseCodes.contains($0) } + } + + /// Determines whether `request` and `response` allow empty response bodies. + /// + /// - Parameters: + /// - request: `URLRequest` to evaluate. + /// - response: `HTTPURLResponse` to evaluate. + /// + /// - Returns: `true` if `request` or `response` allow empty bodies, `false` otherwise. + public func emptyResponseAllowed(forRequest request: URLRequest?, response: HTTPURLResponse?) -> Bool { + (requestAllowsEmptyResponseData(request) == true) || (responseAllowsEmptyResponseData(response) == true) + } +} + +/// By default, any serializer declared to conform to both types will get file serialization for free, as it just feeds +/// the data read from disk into the data response serializer. +public extension DownloadResponseSerializerProtocol where Self: DataResponseSerializerProtocol { + func serializeDownload(request: URLRequest?, response: HTTPURLResponse?, fileURL: URL?, error: Error?) throws -> Self.SerializedObject { + guard error == nil else { throw error! } + + guard let fileURL = fileURL else { + throw AFError.responseSerializationFailed(reason: .inputFileNil) + } + + let data: Data + do { + data = try Data(contentsOf: fileURL) + } catch { + throw AFError.responseSerializationFailed(reason: .inputFileReadFailed(at: fileURL)) + } + + do { + return try serialize(request: request, response: response, data: data, error: error) + } catch { + throw error + } + } +} + +// MARK: - Default + +extension DataRequest { + /// Adds a handler to be called once the request has finished. + /// + /// - Parameters: + /// - queue: The queue on which the completion handler is dispatched. `.main` by default. + /// - completionHandler: The code to be executed once the request has finished. + /// + /// - Returns: The request. + @discardableResult + public func response(queue: DispatchQueue = .main, completionHandler: @escaping (AFDataResponse) -> Void) -> Self { + appendResponseSerializer { + // Start work that should be on the serialization queue. + let result = AFResult(value: self.data, error: self.error) + // End work that should be on the serialization queue. + + self.underlyingQueue.async { + let response = DataResponse(request: self.request, + response: self.response, + data: self.data, + metrics: self.metrics, + serializationDuration: 0, + result: result) + + self.eventMonitor?.request(self, didParseResponse: response) + + self.responseSerializerDidComplete { queue.async { completionHandler(response) } } + } + } + + return self + } + + /// Adds a handler to be called once the request has finished. + /// + /// - Parameters: + /// - queue: The queue on which the completion handler is dispatched. `.main` by default + /// - responseSerializer: The response serializer responsible for serializing the request, response, and data. + /// - completionHandler: The code to be executed once the request has finished. + /// + /// - Returns: The request. + @discardableResult + public func response(queue: DispatchQueue = .main, + responseSerializer: Serializer, + completionHandler: @escaping (AFDataResponse) -> Void) + -> Self { + appendResponseSerializer { + // Start work that should be on the serialization queue. + let start = ProcessInfo.processInfo.systemUptime + let result: AFResult = Result { + try responseSerializer.serialize(request: self.request, + response: self.response, + data: self.data, + error: self.error) + }.mapError { error in + error.asAFError(or: .responseSerializationFailed(reason: .customSerializationFailed(error: error))) + } + + let end = ProcessInfo.processInfo.systemUptime + // End work that should be on the serialization queue. + + self.underlyingQueue.async { + let response = DataResponse(request: self.request, + response: self.response, + data: self.data, + metrics: self.metrics, + serializationDuration: end - start, + result: result) + + self.eventMonitor?.request(self, didParseResponse: response) + + guard let serializerError = result.failure, let delegate = self.delegate else { + self.responseSerializerDidComplete { queue.async { completionHandler(response) } } + return + } + + delegate.retryResult(for: self, dueTo: serializerError) { retryResult in + var didComplete: (() -> Void)? + + defer { + if let didComplete = didComplete { + self.responseSerializerDidComplete { queue.async { didComplete() } } + } + } + + switch retryResult { + case .doNotRetry: + didComplete = { completionHandler(response) } + + case let .doNotRetryWithError(retryError): + let result: AFResult = .failure(retryError.asAFError(orFailWith: "Received retryError was not already AFError")) + + let response = DataResponse(request: self.request, + response: self.response, + data: self.data, + metrics: self.metrics, + serializationDuration: end - start, + result: result) + + didComplete = { completionHandler(response) } + + case .retry, .retryWithDelay: + delegate.retryRequest(self, withDelay: retryResult.delay) + } + } + } + } + + return self + } +} + +extension DownloadRequest { + /// Adds a handler to be called once the request has finished. + /// + /// - Parameters: + /// - queue: The queue on which the completion handler is dispatched. `.main` by default. + /// - completionHandler: The code to be executed once the request has finished. + /// + /// - Returns: The request. + @discardableResult + public func response(queue: DispatchQueue = .main, + completionHandler: @escaping (AFDownloadResponse) -> Void) + -> Self { + appendResponseSerializer { + // Start work that should be on the serialization queue. + let result = AFResult(value: self.fileURL, error: self.error) + // End work that should be on the serialization queue. + + self.underlyingQueue.async { + let response = DownloadResponse(request: self.request, + response: self.response, + fileURL: self.fileURL, + resumeData: self.resumeData, + metrics: self.metrics, + serializationDuration: 0, + result: result) + + self.eventMonitor?.request(self, didParseResponse: response) + + self.responseSerializerDidComplete { queue.async { completionHandler(response) } } + } + } + + return self + } + + /// Adds a handler to be called once the request has finished. + /// + /// - Parameters: + /// - queue: The queue on which the completion handler is dispatched. `.main` by default. + /// - responseSerializer: The response serializer responsible for serializing the request, response, and data + /// contained in the destination `URL`. + /// - completionHandler: The code to be executed once the request has finished. + /// + /// - Returns: The request. + @discardableResult + public func response(queue: DispatchQueue = .main, + responseSerializer: Serializer, + completionHandler: @escaping (AFDownloadResponse) -> Void) + -> Self { + appendResponseSerializer { + // Start work that should be on the serialization queue. + let start = ProcessInfo.processInfo.systemUptime + let result: AFResult = Result { + try responseSerializer.serializeDownload(request: self.request, + response: self.response, + fileURL: self.fileURL, + error: self.error) + }.mapError { error in + error.asAFError(or: .responseSerializationFailed(reason: .customSerializationFailed(error: error))) + } + let end = ProcessInfo.processInfo.systemUptime + // End work that should be on the serialization queue. + + self.underlyingQueue.async { + let response = DownloadResponse(request: self.request, + response: self.response, + fileURL: self.fileURL, + resumeData: self.resumeData, + metrics: self.metrics, + serializationDuration: end - start, + result: result) + + self.eventMonitor?.request(self, didParseResponse: response) + + guard let serializerError = result.failure, let delegate = self.delegate else { + self.responseSerializerDidComplete { queue.async { completionHandler(response) } } + return + } + + delegate.retryResult(for: self, dueTo: serializerError) { retryResult in + var didComplete: (() -> Void)? + + defer { + if let didComplete = didComplete { + self.responseSerializerDidComplete { queue.async { didComplete() } } + } + } + + switch retryResult { + case .doNotRetry: + didComplete = { completionHandler(response) } + + case let .doNotRetryWithError(retryError): + let result: AFResult = .failure(retryError.asAFError(orFailWith: "Received retryError was not already AFError")) + + let response = DownloadResponse(request: self.request, + response: self.response, + fileURL: self.fileURL, + resumeData: self.resumeData, + metrics: self.metrics, + serializationDuration: end - start, + result: result) + + didComplete = { completionHandler(response) } + + case .retry, .retryWithDelay: + delegate.retryRequest(self, withDelay: retryResult.delay) + } + } + } + } + + return self + } +} + +// MARK: - Data + +extension DataRequest { + /// Adds a handler to be called once the request has finished. + /// + /// - Parameters: + /// - queue: The queue on which the completion handler is dispatched. `.main` by default. + /// - completionHandler: The code to be executed once the request has finished. + /// + /// - Returns: The request. + @discardableResult + public func responseData(queue: DispatchQueue = .main, + completionHandler: @escaping (AFDataResponse) -> Void) + -> Self { + response(queue: queue, + responseSerializer: DataResponseSerializer(), + completionHandler: completionHandler) + } +} + +/// A `ResponseSerializer` that performs minimal response checking and returns any response data as-is. By default, a +/// request returning `nil` or no data is considered an error. However, if the response is has a status code valid for +/// empty responses (`204`, `205`), then an empty `Data` value is returned. +public final class DataResponseSerializer: ResponseSerializer { + public let dataPreprocessor: DataPreprocessor + public let emptyResponseCodes: Set + public let emptyRequestMethods: Set + + /// Creates an instance using the provided values. + /// + /// - Parameters: + /// - dataPreprocessor: `DataPreprocessor` used to prepare the received `Data` for serialization. + /// - emptyResponseCodes: The HTTP response codes for which empty responses are allowed. `[204, 205]` by default. + /// - emptyRequestMethods: The HTTP request methods for which empty responses are allowed. `[.head]` by default. + public init(dataPreprocessor: DataPreprocessor = DataResponseSerializer.defaultDataPreprocessor, + emptyResponseCodes: Set = DataResponseSerializer.defaultEmptyResponseCodes, + emptyRequestMethods: Set = DataResponseSerializer.defaultEmptyRequestMethods) { + self.dataPreprocessor = dataPreprocessor + self.emptyResponseCodes = emptyResponseCodes + self.emptyRequestMethods = emptyRequestMethods + } + + public func serialize(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?) throws -> Data { + guard error == nil else { throw error! } + + guard var data = data, !data.isEmpty else { + guard emptyResponseAllowed(forRequest: request, response: response) else { + throw AFError.responseSerializationFailed(reason: .inputDataNilOrZeroLength) + } + + return Data() + } + + data = try dataPreprocessor.preprocess(data) + + return data + } +} + +extension DownloadRequest { + /// Adds a handler to be called once the request has finished. + /// + /// - Parameters: + /// - queue: The queue on which the completion handler is dispatched. `.main` by default. + /// - completionHandler: The code to be executed once the request has finished. + /// + /// - Returns: The request. + @discardableResult + public func responseData(queue: DispatchQueue = .main, + completionHandler: @escaping (AFDownloadResponse) -> Void) + -> Self { + response(queue: queue, + responseSerializer: DataResponseSerializer(), + completionHandler: completionHandler) + } +} + +// MARK: - String + +/// A `ResponseSerializer` that decodes the response data as a `String`. By default, a request returning `nil` or no +/// data is considered an error. However, if the response is has a status code valid for empty responses (`204`, `205`), +/// then an empty `String` is returned. +public final class StringResponseSerializer: ResponseSerializer { + public let dataPreprocessor: DataPreprocessor + /// Optional string encoding used to validate the response. + public let encoding: String.Encoding? + public let emptyResponseCodes: Set + public let emptyRequestMethods: Set + + /// Creates an instance with the provided values. + /// + /// - Parameters: + /// - dataPreprocessor: `DataPreprocessor` used to prepare the received `Data` for serialization. + /// - encoding: A string encoding. Defaults to `nil`, in which case the encoding will be determined + /// from the server response, falling back to the default HTTP character set, `ISO-8859-1`. + /// - emptyResponseCodes: The HTTP response codes for which empty responses are allowed. `[204, 205]` by default. + /// - emptyRequestMethods: The HTTP request methods for which empty responses are allowed. `[.head]` by default. + public init(dataPreprocessor: DataPreprocessor = StringResponseSerializer.defaultDataPreprocessor, + encoding: String.Encoding? = nil, + emptyResponseCodes: Set = StringResponseSerializer.defaultEmptyResponseCodes, + emptyRequestMethods: Set = StringResponseSerializer.defaultEmptyRequestMethods) { + self.dataPreprocessor = dataPreprocessor + self.encoding = encoding + self.emptyResponseCodes = emptyResponseCodes + self.emptyRequestMethods = emptyRequestMethods + } + + public func serialize(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?) throws -> String { + guard error == nil else { throw error! } + + guard var data = data, !data.isEmpty else { + guard emptyResponseAllowed(forRequest: request, response: response) else { + throw AFError.responseSerializationFailed(reason: .inputDataNilOrZeroLength) + } + + return "" + } + + data = try dataPreprocessor.preprocess(data) + + var convertedEncoding = encoding + + if let encodingName = response?.textEncodingName, convertedEncoding == nil { + convertedEncoding = String.Encoding(ianaCharsetName: encodingName) + } + + let actualEncoding = convertedEncoding ?? .isoLatin1 + + guard let string = String(data: data, encoding: actualEncoding) else { + throw AFError.responseSerializationFailed(reason: .stringSerializationFailed(encoding: actualEncoding)) + } + + return string + } +} + +extension DataRequest { + /// Adds a handler to be called once the request has finished. + /// + /// - Parameters: + /// - queue: The queue on which the completion handler is dispatched. `.main` by default. + /// - encoding: The string encoding. Defaults to `nil`, in which case the encoding will be determined from + /// the server response, falling back to the default HTTP character set, `ISO-8859-1`. + /// - completionHandler: A closure to be executed once the request has finished. + /// + /// - Returns: The request. + @discardableResult + public func responseString(queue: DispatchQueue = .main, + encoding: String.Encoding? = nil, + completionHandler: @escaping (AFDataResponse) -> Void) -> Self { + response(queue: queue, + responseSerializer: StringResponseSerializer(encoding: encoding), + completionHandler: completionHandler) + } +} + +extension DownloadRequest { + /// Adds a handler to be called once the request has finished. + /// + /// - Parameters: + /// - queue: The queue on which the completion handler is dispatched. `.main` by default. + /// - encoding: The string encoding. Defaults to `nil`, in which case the encoding will be determined from + /// the server response, falling back to the default HTTP character set, `ISO-8859-1`. + /// - completionHandler: A closure to be executed once the request has finished. + /// + /// - Returns: The request. + @discardableResult + public func responseString(queue: DispatchQueue = .main, + encoding: String.Encoding? = nil, + completionHandler: @escaping (AFDownloadResponse) -> Void) + -> Self { + response(queue: queue, + responseSerializer: StringResponseSerializer(encoding: encoding), + completionHandler: completionHandler) + } +} + +// MARK: - JSON + +/// A `ResponseSerializer` that decodes the response data using `JSONSerialization`. By default, a request returning +/// `nil` or no data is considered an error. However, if the response is has a status code valid for empty responses +/// (`204`, `205`), then an `NSNull` value is returned. +public final class JSONResponseSerializer: ResponseSerializer { + public let dataPreprocessor: DataPreprocessor + public let emptyResponseCodes: Set + public let emptyRequestMethods: Set + /// `JSONSerialization.ReadingOptions` used when serializing a response. + public let options: JSONSerialization.ReadingOptions + + /// Creates an instance with the provided values. + /// + /// - Parameters: + /// - dataPreprocessor: `DataPreprocessor` used to prepare the received `Data` for serialization. + /// - emptyResponseCodes: The HTTP response codes for which empty responses are allowed. `[204, 205]` by default. + /// - emptyRequestMethods: The HTTP request methods for which empty responses are allowed. `[.head]` by default. + /// - options: The options to use. `.allowFragments` by default. + public init(dataPreprocessor: DataPreprocessor = JSONResponseSerializer.defaultDataPreprocessor, + emptyResponseCodes: Set = JSONResponseSerializer.defaultEmptyResponseCodes, + emptyRequestMethods: Set = JSONResponseSerializer.defaultEmptyRequestMethods, + options: JSONSerialization.ReadingOptions = .allowFragments) { + self.dataPreprocessor = dataPreprocessor + self.emptyResponseCodes = emptyResponseCodes + self.emptyRequestMethods = emptyRequestMethods + self.options = options + } + + public func serialize(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?) throws -> Any { + guard error == nil else { throw error! } + + guard var data = data, !data.isEmpty else { + guard emptyResponseAllowed(forRequest: request, response: response) else { + throw AFError.responseSerializationFailed(reason: .inputDataNilOrZeroLength) + } + + return NSNull() + } + + data = try dataPreprocessor.preprocess(data) + + do { + return try JSONSerialization.jsonObject(with: data, options: options) + } catch { + throw AFError.responseSerializationFailed(reason: .jsonSerializationFailed(error: error)) + } + } +} + +extension DataRequest { + /// Adds a handler to be called once the request has finished. + /// + /// - Parameters: + /// - queue: The queue on which the completion handler is dispatched. `.main` by default. + /// - options: The JSON serialization reading options. `.allowFragments` by default. + /// - completionHandler: A closure to be executed once the request has finished. + /// + /// - Returns: The request. + @discardableResult + public func responseJSON(queue: DispatchQueue = .main, + options: JSONSerialization.ReadingOptions = .allowFragments, + completionHandler: @escaping (AFDataResponse) -> Void) -> Self { + response(queue: queue, + responseSerializer: JSONResponseSerializer(options: options), + completionHandler: completionHandler) + } +} + +extension DownloadRequest { + /// Adds a handler to be called once the request has finished. + /// + /// - Parameters: + /// - queue: The queue on which the completion handler is dispatched. `.main` by default. + /// - options: The JSON serialization reading options. `.allowFragments` by default. + /// - completionHandler: A closure to be executed once the request has finished. + /// + /// - Returns: The request. + @discardableResult + public func responseJSON(queue: DispatchQueue = .main, + options: JSONSerialization.ReadingOptions = .allowFragments, + completionHandler: @escaping (AFDownloadResponse) -> Void) + -> Self { + response(queue: queue, + responseSerializer: JSONResponseSerializer(options: options), + completionHandler: completionHandler) + } +} + +// MARK: - Empty + +/// Protocol representing an empty response. Use `T.emptyValue()` to get an instance. +public protocol EmptyResponse { + /// Empty value for the conforming type. + /// + /// - Returns: Value of `Self` to use for empty values. + static func emptyValue() -> Self +} + +/// Type representing an empty value. Use `Empty.value` to get the static instance. +public struct Empty: Codable { + /// Static `Empty` instance used for all `Empty` responses. + public static let value = Empty() +} + +extension Empty: EmptyResponse { + public static func emptyValue() -> Empty { + value + } +} + +// MARK: - DataDecoder Protocol + +/// Any type which can decode `Data` into a `Decodable` type. +public protocol DataDecoder { + /// Decode `Data` into the provided type. + /// + /// - Parameters: + /// - type: The `Type` to be decoded. + /// - data: The `Data` to be decoded. + /// + /// - Returns: The decoded value of type `D`. + /// - Throws: Any error that occurs during decode. + func decode(_ type: D.Type, from data: Data) throws -> D +} + +/// `JSONDecoder` automatically conforms to `DataDecoder`. +extension JSONDecoder: DataDecoder {} + +// MARK: - Decodable + +/// A `ResponseSerializer` that decodes the response data as a generic value using any type that conforms to +/// `DataDecoder`. By default, this is an instance of `JSONDecoder`. Additionally, a request returning `nil` or no data +/// is considered an error. However, if the response is has a status code valid for empty responses (`204`, `205`), then +/// the `Empty.value` value is returned. +public final class DecodableResponseSerializer: ResponseSerializer { + public let dataPreprocessor: DataPreprocessor + /// The `DataDecoder` instance used to decode responses. + public let decoder: DataDecoder + public let emptyResponseCodes: Set + public let emptyRequestMethods: Set + + /// Creates an instance using the values provided. + /// + /// - Parameters: + /// - dataPreprocessor: `DataPreprocessor` used to prepare the received `Data` for serialization. + /// - decoder: The `DataDecoder`. `JSONDecoder()` by default. + /// - emptyResponseCodes: The HTTP response codes for which empty responses are allowed. `[204, 205]` by default. + /// - emptyRequestMethods: The HTTP request methods for which empty responses are allowed. `[.head]` by default. + public init(dataPreprocessor: DataPreprocessor = DecodableResponseSerializer.defaultDataPreprocessor, + decoder: DataDecoder = JSONDecoder(), + emptyResponseCodes: Set = DecodableResponseSerializer.defaultEmptyResponseCodes, + emptyRequestMethods: Set = DecodableResponseSerializer.defaultEmptyRequestMethods) { + self.dataPreprocessor = dataPreprocessor + self.decoder = decoder + self.emptyResponseCodes = emptyResponseCodes + self.emptyRequestMethods = emptyRequestMethods + } + + public func serialize(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?) throws -> T { + guard error == nil else { throw error! } + + guard var data = data, !data.isEmpty else { + guard emptyResponseAllowed(forRequest: request, response: response) else { + throw AFError.responseSerializationFailed(reason: .inputDataNilOrZeroLength) + } + + guard let emptyResponseType = T.self as? EmptyResponse.Type, let emptyValue = emptyResponseType.emptyValue() as? T else { + throw AFError.responseSerializationFailed(reason: .invalidEmptyResponse(type: "\(T.self)")) + } + + return emptyValue + } + + data = try dataPreprocessor.preprocess(data) + + do { + return try decoder.decode(T.self, from: data) + } catch { + throw AFError.responseSerializationFailed(reason: .decodingFailed(error: error)) + } + } +} + +extension DataRequest { + /// Adds a handler to be called once the request has finished. + /// + /// - Parameters: + /// - type: `Decodable` type to decode from response data. + /// - queue: The queue on which the completion handler is dispatched. `.main` by default. + /// - decoder: `DataDecoder` to use to decode the response. `JSONDecoder()` by default. + /// - completionHandler: A closure to be executed once the request has finished. + /// + /// - Returns: The request. + @discardableResult + public func responseDecodable(of type: T.Type = T.self, + queue: DispatchQueue = .main, + decoder: DataDecoder = JSONDecoder(), + completionHandler: @escaping (AFDataResponse) -> Void) -> Self { + response(queue: queue, + responseSerializer: DecodableResponseSerializer(decoder: decoder), + completionHandler: completionHandler) + } +} + +extension DownloadRequest { + /// Adds a handler to be called once the request has finished. + /// + /// - Parameters: + /// - type: `Decodable` type to decode from response data. + /// - queue: The queue on which the completion handler is dispatched. `.main` by default. + /// - decoder: `DataDecoder` to use to decode the response. `JSONDecoder()` by default. + /// - completionHandler: A closure to be executed once the request has finished. + /// + /// - Returns: The request. + @discardableResult + public func responseDecodable(of type: T.Type = T.self, + queue: DispatchQueue = .main, + decoder: DataDecoder = JSONDecoder(), + completionHandler: @escaping (AFDownloadResponse) -> Void) -> Self { + response(queue: queue, + responseSerializer: DecodableResponseSerializer(decoder: decoder), + completionHandler: completionHandler) + } +} + +// MARK: - DataStreamRequest + +/// A type which can serialize incoming `Data`. +public protocol DataStreamSerializer { + /// Type produced from the serialized `Data`. + associatedtype SerializedObject + + /// Serializes incoming `Data` into a `SerializedObject` value. + /// + /// - Parameter data: `Data` to be serialized. + /// + /// - Throws: Any error produced during serialization. + func serialize(_ data: Data) throws -> SerializedObject +} + +/// `DataStreamSerializer` which uses the provided `DataPreprocessor` and `DataDecoder` to serialize the incoming `Data`. +public struct DecodableStreamSerializer: DataStreamSerializer { + /// `DataDecoder` used to decode incoming `Data`. + public let decoder: DataDecoder + /// `DataPreprocessor` incoming `Data` is passed through before being passed to the `DataDecoder`. + public let dataPreprocessor: DataPreprocessor + + /// Creates an instance with the provided `DataDecoder` and `DataPreprocessor`. + /// - Parameters: + /// - decoder: ` DataDecoder` used to decode incoming `Data`. + /// - dataPreprocessor: `DataPreprocessor` used to process incoming `Data` before it's passed through the `decoder`. + public init(decoder: DataDecoder = JSONDecoder(), dataPreprocessor: DataPreprocessor = PassthroughPreprocessor()) { + self.decoder = decoder + self.dataPreprocessor = dataPreprocessor + } + + public func serialize(_ data: Data) throws -> T { + let processedData = try dataPreprocessor.preprocess(data) + do { + return try decoder.decode(T.self, from: processedData) + } catch { + throw AFError.responseSerializationFailed(reason: .decodingFailed(error: error)) + } + } +} + +extension DataStreamRequest { + /// Adds a stream handler which performs no parsing on incoming `Data`. + /// + /// - Parameters: + /// - queue: `DispatchQueue` on which to perform `StreamHandler` closure. + /// - stream: `StreamHandler` closure called as `Data` is received. May be called multiple times. + /// + /// - Returns: The `DataStreamRequest`. + @discardableResult + public func responseStream(on queue: DispatchQueue = .main, stream: @escaping Handler) -> Self { + $streamMutableState.write { state in + let capture = (queue, { data in + self.capturingError { + try stream(.init(event: .stream(.success(data)), token: .init(self))) + } + }) + state.streams.append(capture) + } + appendStreamCompletion(on: queue, stream: stream) + + return self + } + + /// Adds a `StreamHandler` which uses the provided `DataStreamSerializer` to process incoming `Data`. + /// + /// - Parameters: + /// - serializer: `DataStreamSerializer` used to process incoming `Data`. Its work is done on the `serializationQueue`. + /// - queue: `DispatchQueue` on which to perform `StreamHandler` closure. + /// - stream: `StreamHandler` closure called as `Data` is received. May be called multiple times. + /// + /// - Returns: The `DataStreamRequest`. + @discardableResult + public func responseStream(using serializer: Serializer, + on queue: DispatchQueue = .main, + stream: @escaping Handler) -> Self { + let parser = { (data: Data) in + self.serializationQueue.async { + // Start work on serialization queue. + let result = Result { try serializer.serialize(data) } + .mapError { $0.asAFError(or: .responseSerializationFailed(reason: .customSerializationFailed(error: $0))) } + // End work on serialization queue. + queue.async { + self.eventMonitor?.request(self, didParseStream: result) + if result.isFailure, self.automaticallyCancelOnStreamError { + queue.async { self.cancel() } + } + self.capturingError { + try stream(.init(event: .stream(result), token: .init(self))) + } + } + } + } + + $streamMutableState.write { $0.streams.append((queue, parser)) } + appendStreamCompletion(on: queue, stream: stream) + + return self + } + + /// Adds a `StreamHandler` which parses incoming `Data` as a UTF8 `String`. + /// + /// - Parameters: + /// - queue: `DispatchQueue` on which to perform `StreamHandler` closure. + /// - stream: `StreamHandler` closure called as `Data` is received. May be called multiple times. + /// + /// - Returns: The `DataStreamRequest`. + @discardableResult + public func responseStreamString(on queue: DispatchQueue = .main, + stream: @escaping Handler) -> Self { + let parser = { (data: Data) in + self.serializationQueue.async { + // Start work on serialization queue. + let string = String(decoding: data, as: UTF8.self) + // End work on serialization queue. + queue.async { + self.capturingError { + try stream(.init(event: .stream(.success(string)), token: .init(self))) + } + } + } + } + + $streamMutableState.write { $0.streams.append((queue, parser)) } + appendStreamCompletion(on: queue, stream: stream) + + return self + } + + /// Adds a `StreamHandler` which parses incoming `Data` using the provided `DataDecoder`. + /// + /// - Parameters: + /// - type: `Decodable` type to parse incoming `Data` into. + /// - queue: `DispatchQueue` on which to perform `StreamHandler` closure. + /// - decoder: `DataDecoder` used to decode the incoming `Data`. + /// - preprocessor: `DataPreprocessor` used to process the incoming `Data` before it's passed to the `decoder`. + /// - stream: `StreamHandler` closure called as `Data` is received. May be called multiple times. + /// + /// - Returns: The `DataStreamRequest`. + @discardableResult + public func responseStreamDecodable(of type: T.Type = T.self, + on queue: DispatchQueue = .main, + using decoder: DataDecoder = JSONDecoder(), + preprocessor: DataPreprocessor = PassthroughPreprocessor(), + stream: @escaping Handler) -> Self { + responseStream(using: DecodableStreamSerializer(decoder: decoder, dataPreprocessor: preprocessor), + stream: stream) + } +} diff --git b/Pods/Alamofire/Source/Result+Alamofire.swift a/Pods/Alamofire/Source/Result+Alamofire.swift new file mode 100644 index 0000000..39ac286 --- /dev/null +++ a/Pods/Alamofire/Source/Result+Alamofire.swift @@ -0,0 +1,120 @@ +// +// 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 = Result + +// MARK: - Internal APIs + +extension Result { + /// Returns whether the instance is `.success`. + var isSuccess: Bool { + guard case .success = self else { return false } + return true + } + + /// Returns whether the instance is `.failure`. + var isFailure: Bool { + !isSuccess + } + + /// 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 = .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(_ transform: (Success) throws -> NewSuccess) -> Result { + 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 = .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(_ transform: (Failure) throws -> NewFailure) -> Result { + switch self { + case let .failure(error): + do { + return try .failure(transform(error)) + } catch { + return .failure(error) + } + case let .success(value): + return .success(value) + } + } +} diff --git b/Pods/Alamofire/Source/RetryPolicy.swift a/Pods/Alamofire/Source/RetryPolicy.swift new file mode 100644 index 0000000..988342b --- /dev/null +++ a/Pods/Alamofire/Source/RetryPolicy.swift @@ -0,0 +1,367 @@ +// +// RetryPolicy.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 retry policy that retries requests using an exponential backoff for allowed HTTP methods and HTTP status codes +/// as well as certain types of networking errors. +open class RetryPolicy: RequestInterceptor { + /// The default retry limit for retry policies. + public static let defaultRetryLimit: UInt = 2 + + /// The default exponential backoff base for retry policies (must be a minimum of 2). + public static let defaultExponentialBackoffBase: UInt = 2 + + /// The default exponential backoff scale for retry policies. + public static let defaultExponentialBackoffScale: Double = 0.5 + + /// The default HTTP methods to retry. + /// See [RFC 2616 - Section 9.1.2](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html) for more information. + public static let defaultRetryableHTTPMethods: Set = [.delete, // [Delete](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.7) - not always idempotent + .get, // [GET](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.3) - generally idempotent + .head, // [HEAD](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.4) - generally idempotent + .options, // [OPTIONS](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.2) - inherently idempotent + .put, // [PUT](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.6) - not always idempotent + .trace // [TRACE](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.8) - inherently idempotent + ] + + /// The default HTTP status codes to retry. + /// See [RFC 2616 - Section 10](https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10) for more information. + public static let defaultRetryableHTTPStatusCodes: Set = [408, // [Request Timeout](https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.9) + 500, // [Internal Server Error](https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5.1) + 502, // [Bad Gateway](https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5.3) + 503, // [Service Unavailable](https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5.4) + 504 // [Gateway Timeout](https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5.5) + ] + + /// The default URL error codes to retry. + public static let defaultRetryableURLErrorCodes: Set = [// [Security] App Transport Security disallowed a connection because there is no secure network connection. + // - [Disabled] ATS settings do not change at runtime. + // .appTransportSecurityRequiresSecureConnection, + + // [System] An app or app extension attempted to connect to a background session that is already connected to a + // process. + // - [Enabled] The other process could release the background session. + .backgroundSessionInUseByAnotherProcess, + + // [System] The shared container identifier of the URL session configuration is needed but has not been set. + // - [Disabled] Cannot change at runtime. + // .backgroundSessionRequiresSharedContainer, + + // [System] The app is suspended or exits while a background data task is processing. + // - [Enabled] App can be foregrounded or launched to recover. + .backgroundSessionWasDisconnected, + + // [Network] The URL Loading system received bad data from the server. + // - [Enabled] Server could return valid data when retrying. + .badServerResponse, + + // [Resource] A malformed URL prevented a URL request from being initiated. + // - [Disabled] URL was most likely constructed incorrectly. + // .badURL, + + // [System] A connection was attempted while a phone call is active on a network that does not support + // simultaneous phone and data communication (EDGE or GPRS). + // - [Enabled] Phone call could be ended to allow request to recover. + .callIsActive, + + // [Client] An asynchronous load has been canceled. + // - [Disabled] Request was cancelled by the client. + // .cancelled, + + // [File System] A download task couldn’t close the downloaded file on disk. + // - [Disabled] File system error is unlikely to recover with retry. + // .cannotCloseFile, + + // [Network] An attempt to connect to a host failed. + // - [Enabled] Server or DNS lookup could recover during retry. + .cannotConnectToHost, + + // [File System] A download task couldn’t create the downloaded file on disk because of an I/O failure. + // - [Disabled] File system error is unlikely to recover with retry. + // .cannotCreateFile, + + // [Data] Content data received during a connection request had an unknown content encoding. + // - [Disabled] Server is unlikely to modify the content encoding during a retry. + // .cannotDecodeContentData, + + // [Data] Content data received during a connection request could not be decoded for a known content encoding. + // - [Disabled] Server is unlikely to modify the content encoding during a retry. + // .cannotDecodeRawData, + + // [Network] The host name for a URL could not be resolved. + // - [Enabled] Server or DNS lookup could recover during retry. + .cannotFindHost, + + // [Network] A request to load an item only from the cache could not be satisfied. + // - [Enabled] Cache could be populated during a retry. + .cannotLoadFromNetwork, + + // [File System] A download task was unable to move a downloaded file on disk. + // - [Disabled] File system error is unlikely to recover with retry. + // .cannotMoveFile, + + // [File System] A download task was unable to open the downloaded file on disk. + // - [Disabled] File system error is unlikely to recover with retry. + // .cannotOpenFile, + + // [Data] A task could not parse a response. + // - [Disabled] Invalid response is unlikely to recover with retry. + // .cannotParseResponse, + + // [File System] A download task was unable to remove a downloaded file from disk. + // - [Disabled] File system error is unlikely to recover with retry. + // .cannotRemoveFile, + + // [File System] A download task was unable to write to the downloaded file on disk. + // - [Disabled] File system error is unlikely to recover with retry. + // .cannotWriteToFile, + + // [Security] A client certificate was rejected. + // - [Disabled] Client certificate is unlikely to change with retry. + // .clientCertificateRejected, + + // [Security] A client certificate was required to authenticate an SSL connection during a request. + // - [Disabled] Client certificate is unlikely to be provided with retry. + // .clientCertificateRequired, + + // [Data] The length of the resource data exceeds the maximum allowed. + // - [Disabled] Resource will likely still exceed the length maximum on retry. + // .dataLengthExceedsMaximum, + + // [System] The cellular network disallowed a connection. + // - [Enabled] WiFi connection could be established during retry. + .dataNotAllowed, + + // [Network] The host address could not be found via DNS lookup. + // - [Enabled] DNS lookup could succeed during retry. + .dnsLookupFailed, + + // [Data] A download task failed to decode an encoded file during the download. + // - [Enabled] Server could correct the decoding issue with retry. + .downloadDecodingFailedMidStream, + + // [Data] A download task failed to decode an encoded file after downloading. + // - [Enabled] Server could correct the decoding issue with retry. + .downloadDecodingFailedToComplete, + + // [File System] A file does not exist. + // - [Disabled] File system error is unlikely to recover with retry. + // .fileDoesNotExist, + + // [File System] A request for an FTP file resulted in the server responding that the file is not a plain file, + // but a directory. + // - [Disabled] FTP directory is not likely to change to a file during a retry. + // .fileIsDirectory, + + // [Network] A redirect loop has been detected or the threshold for number of allowable redirects has been + // exceeded (currently 16). + // - [Disabled] The redirect loop is unlikely to be resolved within the retry window. + // .httpTooManyRedirects, + + // [System] The attempted connection required activating a data context while roaming, but international roaming + // is disabled. + // - [Enabled] WiFi connection could be established during retry. + .internationalRoamingOff, + + // [Connectivity] A client or server connection was severed in the middle of an in-progress load. + // - [Enabled] A network connection could be established during retry. + .networkConnectionLost, + + // [File System] A resource couldn’t be read because of insufficient permissions. + // - [Disabled] Permissions are unlikely to be granted during retry. + // .noPermissionsToReadFile, + + // [Connectivity] A network resource was requested, but an internet connection has not been established and + // cannot be established automatically. + // - [Enabled] A network connection could be established during retry. + .notConnectedToInternet, + + // [Resource] A redirect was specified by way of server response code, but the server did not accompany this + // code with a redirect URL. + // - [Disabled] The redirect URL is unlikely to be supplied during a retry. + // .redirectToNonExistentLocation, + + // [Client] A body stream is needed but the client did not provide one. + // - [Disabled] The client will be unlikely to supply a body stream during retry. + // .requestBodyStreamExhausted, + + // [Resource] A requested resource couldn’t be retrieved. + // - [Disabled] The resource is unlikely to become available during the retry window. + // .resourceUnavailable, + + // [Security] An attempt to establish a secure connection failed for reasons that can’t be expressed more + // specifically. + // - [Enabled] The secure connection could be established during a retry given the lack of specificity + // provided by the error. + .secureConnectionFailed, + + // [Security] A server certificate had a date which indicates it has expired, or is not yet valid. + // - [Enabled] The server certificate could become valid within the retry window. + .serverCertificateHasBadDate, + + // [Security] A server certificate was not signed by any root server. + // - [Disabled] The server certificate is unlikely to change during the retry window. + // .serverCertificateHasUnknownRoot, + + // [Security] A server certificate is not yet valid. + // - [Enabled] The server certificate could become valid within the retry window. + .serverCertificateNotYetValid, + + // [Security] A server certificate was signed by a root server that isn’t trusted. + // - [Disabled] The server certificate is unlikely to become trusted within the retry window. + // .serverCertificateUntrusted, + + // [Network] An asynchronous operation timed out. + // - [Enabled] The request timed out for an unknown reason and should be retried. + .timedOut + + // [System] The URL Loading System encountered an error that it can’t interpret. + // - [Disabled] The error could not be interpreted and is unlikely to be recovered from during a retry. + // .unknown, + + // [Resource] A properly formed URL couldn’t be handled by the framework. + // - [Disabled] The URL is unlikely to change during a retry. + // .unsupportedURL, + + // [Client] Authentication is required to access a resource. + // - [Disabled] The user authentication is unlikely to be provided by retrying. + // .userAuthenticationRequired, + + // [Client] An asynchronous request for authentication has been canceled by the user. + // - [Disabled] The user cancelled authentication and explicitly took action to not retry. + // .userCancelledAuthentication, + + // [Resource] A server reported that a URL has a non-zero content length, but terminated the network connection + // gracefully without sending any data. + // - [Disabled] The server is unlikely to provide data during the retry window. + // .zeroByteResource, + ] + + /// The total number of times the request is allowed to be retried. + public let retryLimit: UInt + + /// The base of the exponential backoff policy (should always be greater than or equal to 2). + public let exponentialBackoffBase: UInt + + /// The scale of the exponential backoff. + public let exponentialBackoffScale: Double + + /// The HTTP methods that are allowed to be retried. + public let retryableHTTPMethods: Set + + /// The HTTP status codes that are automatically retried by the policy. + public let retryableHTTPStatusCodes: Set + + /// The URL error codes that are automatically retried by the policy. + public let retryableURLErrorCodes: Set + + /// Creates an `ExponentialBackoffRetryPolicy` from the specified parameters. + /// + /// - Parameters: + /// - retryLimit: The total number of times the request is allowed to be retried. `2` by default. + /// - exponentialBackoffBase: The base of the exponential backoff policy. `2` by default. + /// - exponentialBackoffScale: The scale of the exponential backoff. `0.5` by default. + /// - retryableHTTPMethods: The HTTP methods that are allowed to be retried. + /// `RetryPolicy.defaultRetryableHTTPMethods` by default. + /// - retryableHTTPStatusCodes: The HTTP status codes that are automatically retried by the policy. + /// `RetryPolicy.defaultRetryableHTTPStatusCodes` by default. + /// - retryableURLErrorCodes: The URL error codes that are automatically retried by the policy. + /// `RetryPolicy.defaultRetryableURLErrorCodes` by default. + public init(retryLimit: UInt = RetryPolicy.defaultRetryLimit, + exponentialBackoffBase: UInt = RetryPolicy.defaultExponentialBackoffBase, + exponentialBackoffScale: Double = RetryPolicy.defaultExponentialBackoffScale, + retryableHTTPMethods: Set = RetryPolicy.defaultRetryableHTTPMethods, + retryableHTTPStatusCodes: Set = RetryPolicy.defaultRetryableHTTPStatusCodes, + retryableURLErrorCodes: Set = RetryPolicy.defaultRetryableURLErrorCodes) { + precondition(exponentialBackoffBase >= 2, "The `exponentialBackoffBase` must be a minimum of 2.") + + self.retryLimit = retryLimit + self.exponentialBackoffBase = exponentialBackoffBase + self.exponentialBackoffScale = exponentialBackoffScale + self.retryableHTTPMethods = retryableHTTPMethods + self.retryableHTTPStatusCodes = retryableHTTPStatusCodes + self.retryableURLErrorCodes = retryableURLErrorCodes + } + + open func retry(_ request: Request, + for session: Session, + dueTo error: Error, + completion: @escaping (RetryResult) -> Void) { + if + request.retryCount < retryLimit, + let httpMethod = request.request?.method, + retryableHTTPMethods.contains(httpMethod), + shouldRetry(response: request.response, error: error) { + let timeDelay = pow(Double(exponentialBackoffBase), Double(request.retryCount)) * exponentialBackoffScale + completion(.retryWithDelay(timeDelay)) + } else { + completion(.doNotRetry) + } + } + + private func shouldRetry(response: HTTPURLResponse?, error: Error) -> Bool { + if let statusCode = response?.statusCode, retryableHTTPStatusCodes.contains(statusCode) { + return true + } else { + let errorCode = (error as? URLError)?.code + let afErrorCode = (error.asAFError?.underlyingError as? URLError)?.code + if let code = errorCode ?? afErrorCode, retryableURLErrorCodes.contains(code) { + return true + } else { + return false + } + } + } +} + +// MARK: - + +/// A retry policy that automatically retries idempotent requests for network connection lost errors. For more +/// information about retrying network connection lost errors, please refer to Apple's +/// [technical document](https://developer.apple.com/library/content/qa/qa1941/_index.html). +open class ConnectionLostRetryPolicy: RetryPolicy { + /// Creates a `ConnectionLostRetryPolicy` instance from the specified parameters. + /// + /// - Parameters: + /// - retryLimit: The total number of times the request is allowed to be retried. + /// `RetryPolicy.defaultRetryLimit` by default. + /// - exponentialBackoffBase: The base of the exponential backoff policy. + /// `RetryPolicy.defaultExponentialBackoffBase` by default. + /// - exponentialBackoffScale: The scale of the exponential backoff. + /// `RetryPolicy.defaultExponentialBackoffScale` by default. + /// - retryableHTTPMethods: The idempotent http methods to retry. + /// `RetryPolicy.defaultRetryableHTTPMethods` by default. + public init(retryLimit: UInt = RetryPolicy.defaultRetryLimit, + exponentialBackoffBase: UInt = RetryPolicy.defaultExponentialBackoffBase, + exponentialBackoffScale: Double = RetryPolicy.defaultExponentialBackoffScale, + retryableHTTPMethods: Set = RetryPolicy.defaultRetryableHTTPMethods) { + super.init(retryLimit: retryLimit, + exponentialBackoffBase: exponentialBackoffBase, + exponentialBackoffScale: exponentialBackoffScale, + retryableHTTPMethods: retryableHTTPMethods, + retryableHTTPStatusCodes: [], + retryableURLErrorCodes: [.networkConnectionLost]) + } +} diff --git b/Pods/Alamofire/Source/ServerTrustEvaluation.swift a/Pods/Alamofire/Source/ServerTrustEvaluation.swift new file mode 100644 index 0000000..ba3e083 --- /dev/null +++ a/Pods/Alamofire/Source/ServerTrustEvaluation.swift @@ -0,0 +1,606 @@ +// +// ServerTrustPolicy.swift +// +// Copyright (c) 2014-2016 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 + +/// Responsible for managing the mapping of `ServerTrustEvaluating` values to given hosts. +open class ServerTrustManager { + /// Determines whether all hosts for this `ServerTrustManager` must be evaluated. `true` by default. + public let allHostsMustBeEvaluated: Bool + + /// The dictionary of policies mapped to a particular host. + public let evaluators: [String: ServerTrustEvaluating] + + /// Initializes the `ServerTrustManager` instance with the given evaluators. + /// + /// Since different servers and web services can have different leaf certificates, intermediate and even root + /// certificates, it is important to have the flexibility to specify evaluation policies on a per host basis. This + /// allows for scenarios such as using default evaluation for host1, certificate pinning for host2, public key + /// pinning for host3 and disabling evaluation for host4. + /// + /// - Parameters: + /// - allHostsMustBeEvaluated: The value determining whether all hosts for this instance must be evaluated. `true` + /// by default. + /// - evaluators: A dictionary of evaluators mapped to hosts. + public init(allHostsMustBeEvaluated: Bool = true, evaluators: [String: ServerTrustEvaluating]) { + self.allHostsMustBeEvaluated = allHostsMustBeEvaluated + self.evaluators = evaluators + } + + /// Returns the `ServerTrustEvaluating` value for the given host, if one is set. + /// + /// By default, this method will return the policy that perfectly matches the given host. Subclasses could override + /// this method and implement more complex mapping implementations such as wildcards. + /// + /// - Parameter host: The host to use when searching for a matching policy. + /// + /// - Returns: The `ServerTrustEvaluating` value for the given host if found, `nil` otherwise. + /// - Throws: `AFError.serverTrustEvaluationFailed` if `allHostsMustBeEvaluated` is `true` and no matching + /// evaluators are found. + open func serverTrustEvaluator(forHost host: String) throws -> ServerTrustEvaluating? { + guard let evaluator = evaluators[host] else { + if allHostsMustBeEvaluated { + throw AFError.serverTrustEvaluationFailed(reason: .noRequiredEvaluator(host: host)) + } + + return nil + } + + return evaluator + } +} + +/// A protocol describing the API used to evaluate server trusts. +public protocol ServerTrustEvaluating { + #if os(Linux) + // Implement this once Linux has API for evaluating server trusts. + #else + /// Evaluates the given `SecTrust` value for the given `host`. + /// + /// - Parameters: + /// - trust: The `SecTrust` value to evaluate. + /// - host: The host for which to evaluate the `SecTrust` value. + /// + /// - Returns: A `Bool` indicating whether the evaluator considers the `SecTrust` value valid for `host`. + func evaluate(_ trust: SecTrust, forHost host: String) throws + #endif +} + +// MARK: - Server Trust Evaluators + +/// An evaluator which uses the default server trust evaluation while allowing you to control whether to validate the +/// host provided by the challenge. Applications are encouraged to always validate the host in production environments +/// to guarantee the validity of the server's certificate chain. +public final class DefaultTrustEvaluator: ServerTrustEvaluating { + private let validateHost: Bool + + /// Creates a `DefaultTrustEvaluator`. + /// + /// - Parameter validateHost: Determines whether or not the evaluator should validate the host. `true` by default. + public init(validateHost: Bool = true) { + self.validateHost = validateHost + } + + public func evaluate(_ trust: SecTrust, forHost host: String) throws { + if validateHost { + try trust.af.performValidation(forHost: host) + } + + try trust.af.performDefaultValidation(forHost: host) + } +} + +/// An evaluator which Uses the default and revoked server trust evaluations allowing you to control whether to validate +/// the host provided by the challenge as well as specify the revocation flags for testing for revoked certificates. +/// Apple platforms did not start testing for revoked certificates automatically until iOS 10.1, macOS 10.12 and tvOS +/// 10.1 which is demonstrated in our TLS tests. Applications are encouraged to always validate the host in production +/// environments to guarantee the validity of the server's certificate chain. +public final class RevocationTrustEvaluator: ServerTrustEvaluating { + /// Represents the options to be use when evaluating the status of a certificate. + /// Only Revocation Policy Constants are valid, and can be found in [Apple's documentation](https://developer.apple.com/documentation/security/certificate_key_and_trust_services/policies/1563600-revocation_policy_constants). + public struct Options: OptionSet { + /// Perform revocation checking using the CRL (Certification Revocation List) method. + public static let crl = Options(rawValue: kSecRevocationCRLMethod) + /// Consult only locally cached replies; do not use network access. + public static let networkAccessDisabled = Options(rawValue: kSecRevocationNetworkAccessDisabled) + /// Perform revocation checking using OCSP (Online Certificate Status Protocol). + public static let ocsp = Options(rawValue: kSecRevocationOCSPMethod) + /// Prefer CRL revocation checking over OCSP; by default, OCSP is preferred. + public static let preferCRL = Options(rawValue: kSecRevocationPreferCRL) + /// Require a positive response to pass the policy. If the flag is not set, revocation checking is done on a + /// "best attempt" basis, where failure to reach the server is not considered fatal. + public static let requirePositiveResponse = Options(rawValue: kSecRevocationRequirePositiveResponse) + /// Perform either OCSP or CRL checking. The checking is performed according to the method(s) specified in the + /// certificate and the value of `preferCRL`. + public static let any = Options(rawValue: kSecRevocationUseAnyAvailableMethod) + + /// The raw value of the option. + public let rawValue: CFOptionFlags + + /// Creates an `Options` value with the given `CFOptionFlags`. + /// + /// - Parameter rawValue: The `CFOptionFlags` value to initialize with. + public init(rawValue: CFOptionFlags) { + self.rawValue = rawValue + } + } + + private let performDefaultValidation: Bool + private let validateHost: Bool + private let options: Options + + /// Creates a `RevocationTrustEvaluator`. + /// + /// - Note: Default and host validation will fail when using this evaluator with self-signed certificates. Use + /// `PinnedCertificatesTrustEvaluator` if you need to use self-signed certificates. + /// + /// - Parameters: + /// - performDefaultValidation: Determines whether default validation should be performed in addition to + /// evaluating the pinned certificates. `true` by default. + /// - validateHost: Determines whether or not the evaluator should validate the host, in addition + /// to performing the default evaluation, even if `performDefaultValidation` is + /// `false`. `true` by default. + /// - options: The `Options` to use to check the revocation status of the certificate. `.any` + /// by default. + public init(performDefaultValidation: Bool = true, validateHost: Bool = true, options: Options = .any) { + self.performDefaultValidation = performDefaultValidation + self.validateHost = validateHost + self.options = options + } + + public func evaluate(_ trust: SecTrust, forHost host: String) throws { + if performDefaultValidation { + try trust.af.performDefaultValidation(forHost: host) + } + + if validateHost { + try trust.af.performValidation(forHost: host) + } + + if #available(iOS 12, macOS 10.14, tvOS 12, watchOS 5, *) { + try trust.af.evaluate(afterApplying: SecPolicy.af.revocation(options: options)) + } else { + try trust.af.validate(policy: SecPolicy.af.revocation(options: options)) { status, result in + AFError.serverTrustEvaluationFailed(reason: .revocationCheckFailed(output: .init(host, trust, status, result), options: options)) + } + } + } +} + +/// Uses the pinned certificates to validate the server trust. The server trust is considered valid if one of the pinned +/// certificates match one of the server certificates. By validating both the certificate chain and host, certificate +/// pinning provides a very secure form of server trust validation mitigating most, if not all, MITM attacks. +/// Applications are encouraged to always validate the host and require a valid certificate chain in production +/// environments. +public final class PinnedCertificatesTrustEvaluator: ServerTrustEvaluating { + private let certificates: [SecCertificate] + private let acceptSelfSignedCertificates: Bool + private let performDefaultValidation: Bool + private let validateHost: Bool + + /// Creates a `PinnedCertificatesTrustEvaluator`. + /// + /// - Parameters: + /// - certificates: The certificates to use to evaluate the trust. All `cer`, `crt`, and `der` + /// certificates in `Bundle.main` by default. + /// - acceptSelfSignedCertificates: Adds the provided certificates as anchors for the trust evaluation, allowing + /// self-signed certificates to pass. `false` by default. THIS SETTING SHOULD BE + /// FALSE IN PRODUCTION! + /// - performDefaultValidation: Determines whether default validation should be performed in addition to + /// evaluating the pinned certificates. `true` by default. + /// - validateHost: Determines whether or not the evaluator should validate the host, in addition + /// to performing the default evaluation, even if `performDefaultValidation` is + /// `false`. `true` by default. + public init(certificates: [SecCertificate] = Bundle.main.af.certificates, + acceptSelfSignedCertificates: Bool = false, + performDefaultValidation: Bool = true, + validateHost: Bool = true) { + self.certificates = certificates + self.acceptSelfSignedCertificates = acceptSelfSignedCertificates + self.performDefaultValidation = performDefaultValidation + self.validateHost = validateHost + } + + public func evaluate(_ trust: SecTrust, forHost host: String) throws { + guard !certificates.isEmpty else { + throw AFError.serverTrustEvaluationFailed(reason: .noCertificatesFound) + } + + if acceptSelfSignedCertificates { + try trust.af.setAnchorCertificates(certificates) + } + + if performDefaultValidation { + try trust.af.performDefaultValidation(forHost: host) + } + + if validateHost { + try trust.af.performValidation(forHost: host) + } + + let serverCertificatesData = Set(trust.af.certificateData) + let pinnedCertificatesData = Set(certificates.af.data) + let pinnedCertificatesInServerData = !serverCertificatesData.isDisjoint(with: pinnedCertificatesData) + if !pinnedCertificatesInServerData { + throw AFError.serverTrustEvaluationFailed(reason: .certificatePinningFailed(host: host, + trust: trust, + pinnedCertificates: certificates, + serverCertificates: trust.af.certificates)) + } + } +} + +/// Uses the pinned public keys to validate the server trust. The server trust is considered valid if one of the pinned +/// public keys match one of the server certificate public keys. By validating both the certificate chain and host, +/// public key pinning provides a very secure form of server trust validation mitigating most, if not all, MITM attacks. +/// Applications are encouraged to always validate the host and require a valid certificate chain in production +/// environments. +public final class PublicKeysTrustEvaluator: ServerTrustEvaluating { + private let keys: [SecKey] + private let performDefaultValidation: Bool + private let validateHost: Bool + + /// Creates a `PublicKeysTrustEvaluator`. + /// + /// - Note: Default and host validation will fail when using this evaluator with self-signed certificates. Use + /// `PinnedCertificatesTrustEvaluator` if you need to use self-signed certificates. + /// + /// - Parameters: + /// - keys: The `SecKey`s to use to validate public keys. Defaults to the public keys of all + /// certificates included in the main bundle. + /// - performDefaultValidation: Determines whether default validation should be performed in addition to + /// evaluating the pinned certificates. `true` by default. + /// - validateHost: Determines whether or not the evaluator should validate the host, in addition to + /// performing the default evaluation, even if `performDefaultValidation` is `false`. + /// `true` by default. + public init(keys: [SecKey] = Bundle.main.af.publicKeys, + performDefaultValidation: Bool = true, + validateHost: Bool = true) { + self.keys = keys + self.performDefaultValidation = performDefaultValidation + self.validateHost = validateHost + } + + public func evaluate(_ trust: SecTrust, forHost host: String) throws { + guard !keys.isEmpty else { + throw AFError.serverTrustEvaluationFailed(reason: .noPublicKeysFound) + } + + if performDefaultValidation { + try trust.af.performDefaultValidation(forHost: host) + } + + if validateHost { + try trust.af.performValidation(forHost: host) + } + + let pinnedKeysInServerKeys: Bool = { + for serverPublicKey in trust.af.publicKeys { + for pinnedPublicKey in keys { + if serverPublicKey == pinnedPublicKey { + return true + } + } + } + return false + }() + + if !pinnedKeysInServerKeys { + throw AFError.serverTrustEvaluationFailed(reason: .publicKeyPinningFailed(host: host, + trust: trust, + pinnedKeys: keys, + serverKeys: trust.af.publicKeys)) + } + } +} + +/// Uses the provided evaluators to validate the server trust. The trust is only considered valid if all of the +/// evaluators consider it valid. +public final class CompositeTrustEvaluator: ServerTrustEvaluating { + private let evaluators: [ServerTrustEvaluating] + + /// Creates a `CompositeTrustEvaluator`. + /// + /// - Parameter evaluators: The `ServerTrustEvaluating` values used to evaluate the server trust. + public init(evaluators: [ServerTrustEvaluating]) { + self.evaluators = evaluators + } + + public func evaluate(_ trust: SecTrust, forHost host: String) throws { + try evaluators.evaluate(trust, forHost: host) + } +} + +/// Disables all evaluation which in turn will always consider any server trust as valid. +/// +/// **THIS EVALUATOR SHOULD NEVER BE USED IN PRODUCTION!** +public final class DisabledEvaluator: ServerTrustEvaluating { + /// Creates an instance. + public init() {} + + public func evaluate(_ trust: SecTrust, forHost host: String) throws {} +} + +// MARK: - Extensions + +public extension Array where Element == ServerTrustEvaluating { + #if os(Linux) + // Add this same convenience method for Linux. + #else + /// Evaluates the given `SecTrust` value for the given `host`. + /// + /// - Parameters: + /// - trust: The `SecTrust` value to evaluate. + /// - host: The host for which to evaluate the `SecTrust` value. + /// + /// - Returns: Whether or not the evaluator considers the `SecTrust` value valid for `host`. + func evaluate(_ trust: SecTrust, forHost host: String) throws { + for evaluator in self { + try evaluator.evaluate(trust, forHost: host) + } + } + #endif +} + +extension Bundle: AlamofireExtended {} +public extension AlamofireExtension where ExtendedType: Bundle { + /// Returns all valid `cer`, `crt`, and `der` certificates in the bundle. + var certificates: [SecCertificate] { + paths(forResourcesOfTypes: [".cer", ".CER", ".crt", ".CRT", ".der", ".DER"]).compactMap { path in + guard + let certificateData = try? Data(contentsOf: URL(fileURLWithPath: path)) as CFData, + let certificate = SecCertificateCreateWithData(nil, certificateData) else { return nil } + + return certificate + } + } + + /// Returns all public keys for the valid certificates in the bundle. + var publicKeys: [SecKey] { + certificates.af.publicKeys + } + + /// Returns all pathnames for the resources identified by the provided file extensions. + /// + /// - Parameter types: The filename extensions locate. + /// + /// - Returns: All pathnames for the given filename extensions. + func paths(forResourcesOfTypes types: [String]) -> [String] { + Array(Set(types.flatMap { type.paths(forResourcesOfType: $0, inDirectory: nil) })) + } +} + +extension SecTrust: AlamofireExtended {} +public extension AlamofireExtension where ExtendedType == SecTrust { + /// Evaluates `self` after applying the `SecPolicy` value provided. + /// + /// - Parameter policy: The `SecPolicy` to apply to `self` before evaluation. + /// + /// - Throws: Any `Error` from applying the `SecPolicy` or from evaluation. + @available(iOS 12, macOS 10.14, tvOS 12, watchOS 5, *) + func evaluate(afterApplying policy: SecPolicy) throws { + try apply(policy: policy).af.evaluate() + } + + /// Attempts to validate `self` using the `SecPolicy` provided and transforming any error produced using the closure passed. + /// + /// - Parameters: + /// - policy: The `SecPolicy` used to evaluate `self`. + /// - errorProducer: The closure used transform the failed `OSStatus` and `SecTrustResultType`. + /// - Throws: Any `Error` from applying the `policy`, or the result of `errorProducer` if validation fails. + @available(iOS, introduced: 10, deprecated: 12, renamed: "evaluate(afterApplying:)") + @available(macOS, introduced: 10.12, deprecated: 10.14, renamed: "evaluate(afterApplying:)") + @available(tvOS, introduced: 10, deprecated: 12, renamed: "evaluate(afterApplying:)") + @available(watchOS, introduced: 3, deprecated: 5, renamed: "evaluate(afterApplying:)") + func validate(policy: SecPolicy, errorProducer: (_ status: OSStatus, _ result: SecTrustResultType) -> Error) throws { + try apply(policy: policy).af.validate(errorProducer: errorProducer) + } + + /// Applies a `SecPolicy` to `self`, throwing if it fails. + /// + /// - Parameter policy: The `SecPolicy`. + /// + /// - Returns: `self`, with the policy applied. + /// - Throws: An `AFError.serverTrustEvaluationFailed` instance with a `.policyApplicationFailed` reason. + func apply(policy: SecPolicy) throws -> SecTrust { + let status = SecTrustSetPolicies(type, policy) + + guard status.af.isSuccess else { + throw AFError.serverTrustEvaluationFailed(reason: .policyApplicationFailed(trust: type, + policy: policy, + status: status)) + } + + return type + } + + /// Evaluate `self`, throwing an `Error` if evaluation fails. + /// + /// - Throws: `AFError.serverTrustEvaluationFailed` with reason `.trustValidationFailed` and associated error from + /// the underlying evaluation. + @available(iOS 12, macOS 10.14, tvOS 12, watchOS 5, *) + func evaluate() throws { + var error: CFError? + let evaluationSucceeded = SecTrustEvaluateWithError(type, &error) + + if !evaluationSucceeded { + throw AFError.serverTrustEvaluationFailed(reason: .trustEvaluationFailed(error: error)) + } + } + + /// Validate `self`, passing any failure values through `errorProducer`. + /// + /// - Parameter errorProducer: The closure used to transform the failed `OSStatus` and `SecTrustResultType` into an + /// `Error`. + /// - Throws: The `Error` produced by the `errorProducer` closure. + @available(iOS, introduced: 10, deprecated: 12, renamed: "evaluate()") + @available(macOS, introduced: 10.12, deprecated: 10.14, renamed: "evaluate()") + @available(tvOS, introduced: 10, deprecated: 12, renamed: "evaluate()") + @available(watchOS, introduced: 3, deprecated: 5, renamed: "evaluate()") + func validate(errorProducer: (_ status: OSStatus, _ result: SecTrustResultType) -> Error) throws { + var result = SecTrustResultType.invalid + let status = SecTrustEvaluate(type, &result) + + guard status.af.isSuccess && result.af.isSuccess else { + throw errorProducer(status, result) + } + } + + /// Sets a custom certificate chain on `self`, allowing full validation of a self-signed certificate and its chain. + /// + /// - Parameter certificates: The `SecCertificate`s to add to the chain. + /// - Throws: Any error produced when applying the new certificate chain. + func setAnchorCertificates(_ certificates: [SecCertificate]) throws { + // Add additional anchor certificates. + let status = SecTrustSetAnchorCertificates(type, certificates as CFArray) + guard status.af.isSuccess else { + throw AFError.serverTrustEvaluationFailed(reason: .settingAnchorCertificatesFailed(status: status, + certificates: certificates)) + } + + // Reenable system anchor certificates. + let systemStatus = SecTrustSetAnchorCertificatesOnly(type, true) + guard systemStatus.af.isSuccess else { + throw AFError.serverTrustEvaluationFailed(reason: .settingAnchorCertificatesFailed(status: systemStatus, + certificates: certificates)) + } + } + + /// The public keys contained in `self`. + var publicKeys: [SecKey] { + certificates.af.publicKeys + } + + /// The `SecCertificate`s contained i `self`. + var certificates: [SecCertificate] { + (0.. SecPolicy { + SecPolicyCreateSSL(true, hostname as CFString) + } + + /// Creates a `SecPolicy` which checks the revocation of certificates. + /// + /// - Parameter options: The `RevocationTrustEvaluator.Options` for evaluation. + /// + /// - Returns: The `SecPolicy`. + /// - Throws: An `AFError.serverTrustEvaluationFailed` error with reason `.revocationPolicyCreationFailed` + /// if the policy cannot be created. + static func revocation(options: RevocationTrustEvaluator.Options) throws -> SecPolicy { + guard let policy = SecPolicyCreateRevocation(options.rawValue) else { + throw AFError.serverTrustEvaluationFailed(reason: .revocationPolicyCreationFailed) + } + + return policy + } +} + +extension Array: AlamofireExtended {} +public extension AlamofireExtension where ExtendedType == [SecCertificate] { + /// All `Data` values for the contained `SecCertificate`s. + var data: [Data] { + type.map { SecCertificateCopyData($0) as Data } + } + + /// All public `SecKey` values for the contained `SecCertificate`s. + var publicKeys: [SecKey] { + type.compactMap { $0.af.publicKey } + } +} + +extension SecCertificate: AlamofireExtended {} +public extension AlamofireExtension where ExtendedType == SecCertificate { + /// The public key for `self`, if it can be extracted. + var publicKey: SecKey? { + let policy = SecPolicyCreateBasicX509() + var trust: SecTrust? + let trustCreationStatus = SecTrustCreateWithCertificates(type, policy, &trust) + + guard let createdTrust = trust, trustCreationStatus == errSecSuccess else { return nil } + + return SecTrustCopyPublicKey(createdTrust) + } +} + +extension OSStatus: AlamofireExtended {} +public extension AlamofireExtension where ExtendedType == OSStatus { + /// Returns whether `self` is `errSecSuccess`. + var isSuccess: Bool { type == errSecSuccess } +} + +extension SecTrustResultType: AlamofireExtended {} +public extension AlamofireExtension where ExtendedType == SecTrustResultType { + /// Returns whether `self is `.unspecified` or `.proceed`. + var isSuccess: Bool { + (type == .unspecified || type == .proceed) + } +} diff --git b/Pods/Alamofire/Source/Session.swift a/Pods/Alamofire/Source/Session.swift new file mode 100644 index 0000000..d78c621 --- /dev/null +++ a/Pods/Alamofire/Source/Session.swift @@ -0,0 +1,1234 @@ +// +// Session.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 + +/// `Session` creates and manages Alamofire's `Request` types during their lifetimes. It also provides common +/// functionality for all `Request`s, including queuing, interception, trust management, redirect handling, and response +/// cache handling. +open class Session { + /// Shared singleton instance used by all `AF.request` APIs. Cannot be modified. + public static let `default` = Session() + + /// Underlying `URLSession` used to create `URLSessionTasks` for this instance, and for which this instance's + /// `delegate` handles `URLSessionDelegate` callbacks. + public let session: URLSession + /// Instance's `SessionDelegate`, which handles the `URLSessionDelegate` methods and `Request` interaction. + public let delegate: SessionDelegate + /// Root `DispatchQueue` for all internal callbacks and state update. **MUST** be a serial queue. + public let rootQueue: DispatchQueue + /// Value determining whether this instance automatically calls `resume()` on all created `Request`s. + public let startRequestsImmediately: Bool + /// `DispatchQueue` on which `URLRequest`s are created asynchronously. By default this queue uses `rootQueue` as its + /// `target`, but a separate queue can be used if request creation is determined to be a bottleneck. Always profile + /// and test before introducing an additional queue. + public let requestQueue: DispatchQueue + /// `DispatchQueue` passed to all `Request`s on which they perform their response serialization. By default this + /// queue uses `rootQueue` as its `target` but a separate queue can be used if response serialization is determined + /// to be a bottleneck. Always profile and test before introducing an additional queue. + public let serializationQueue: DispatchQueue + /// `RequestInterceptor` used for all `Request` created by the instance. `RequestInterceptor`s can also be set on a + /// per-`Request` basis, in which case the `Request`'s interceptor takes precedence over this value. + public let interceptor: RequestInterceptor? + /// `ServerTrustManager` instance used to evaluate all trust challenges and provide certificate and key pinning. + public let serverTrustManager: ServerTrustManager? + /// `RedirectHandler` instance used to provide customization for request redirection. + public let redirectHandler: RedirectHandler? + /// `CachedResponseHandler` instance used to provide customization of cached response handling. + public let cachedResponseHandler: CachedResponseHandler? + /// `CompositeEventMonitor` used to compose Alamofire's `defaultEventMonitors` and any passed `EventMonitor`s. + public let eventMonitor: CompositeEventMonitor + /// `EventMonitor`s included in all instances. `[AlamofireNotifications()]` by default. + public let defaultEventMonitors: [EventMonitor] = [AlamofireNotifications()] + + /// Internal map between `Request`s and any `URLSessionTasks` that may be in flight for them. + var requestTaskMap = RequestTaskMap() + /// `Set` of currently active `Request`s. + var activeRequests: Set = [] + /// Completion events awaiting `URLSessionTaskMetrics`. + var waitingCompletions: [URLSessionTask: () -> Void] = [:] + + /// Creates a `Session` from a `URLSession` and other parameters. + /// + /// - Note: When passing a `URLSession`, you must create the `URLSession` with a specific `delegateQueue` value and + /// pass the `delegateQueue`'s `underlyingQueue` as the `rootQueue` parameter of this initializer. + /// + /// - Parameters: + /// - session: Underlying `URLSession` for this instance. + /// - delegate: `SessionDelegate` that handles `session`'s delegate callbacks as well as `Request` + /// interaction. + /// - rootQueue: Root `DispatchQueue` for all internal callbacks and state updates. **MUST** be a + /// serial queue. + /// - startRequestsImmediately: Determines whether this instance will automatically start all `Request`s. `true` + /// by default. If set to `false`, all `Request`s created must have `.resume()` called. + /// on them for them to start. + /// - requestQueue: `DispatchQueue` on which to perform `URLRequest` creation. By default this queue + /// will use the `rootQueue` as its `target`. A separate queue can be used if it's + /// determined request creation is a bottleneck, but that should only be done after + /// careful testing and profiling. `nil` by default. + /// - serializationQueue: `DispatchQueue` on which to perform all response serialization. By default this + /// queue will use the `rootQueue` as its `target`. A separate queue can be used if + /// it's determined response serialization is a bottleneck, but that should only be + /// done after careful testing and profiling. `nil` by default. + /// - interceptor: `RequestInterceptor` to be used for all `Request`s created by this instance. `nil` + /// by default. + /// - serverTrustManager: `ServerTrustManager` to be used for all trust evaluations by this instance. `nil` + /// by default. + /// - redirectHandler: `RedirectHandler` to be used by all `Request`s created by this instance. `nil` by + /// default. + /// - cachedResponseHandler: `CachedResponseHandler` to be used by all `Request`s created by this instance. + /// `nil` by default. + /// - eventMonitors: Additional `EventMonitor`s used by the instance. Alamofire always adds a + /// `AlamofireNotifications` `EventMonitor` to the array passed here. `[]` by default. + public init(session: URLSession, + delegate: SessionDelegate, + rootQueue: DispatchQueue, + startRequestsImmediately: Bool = true, + requestQueue: DispatchQueue? = nil, + serializationQueue: DispatchQueue? = nil, + interceptor: RequestInterceptor? = nil, + serverTrustManager: ServerTrustManager? = nil, + redirectHandler: RedirectHandler? = nil, + cachedResponseHandler: CachedResponseHandler? = nil, + eventMonitors: [EventMonitor] = []) { + precondition(session.configuration.identifier == nil, + "Alamofire does not support background URLSessionConfigurations.") + precondition(session.delegateQueue.underlyingQueue === rootQueue, + "Session(session:) initializer must be passed the DispatchQueue used as the delegateQueue's underlyingQueue as rootQueue.") + + self.session = session + self.delegate = delegate + self.rootQueue = rootQueue + self.startRequestsImmediately = startRequestsImmediately + self.requestQueue = requestQueue ?? DispatchQueue(label: "\(rootQueue.label).requestQueue", target: rootQueue) + self.serializationQueue = serializationQueue ?? DispatchQueue(label: "\(rootQueue.label).serializationQueue", target: rootQueue) + self.interceptor = interceptor + self.serverTrustManager = serverTrustManager + self.redirectHandler = redirectHandler + self.cachedResponseHandler = cachedResponseHandler + eventMonitor = CompositeEventMonitor(monitors: defaultEventMonitors + eventMonitors) + delegate.eventMonitor = eventMonitor + delegate.stateProvider = self + } + + /// Creates a `Session` from a `URLSessionConfiguration`. + /// + /// - Note: This initializer lets Alamofire handle the creation of the underlying `URLSession` and its + /// `delegateQueue`, and is the recommended initializer for most uses. + /// + /// - Parameters: + /// - configuration: `URLSessionConfiguration` to be used to create the underlying `URLSession`. Changes + /// to this value after being passed to this initializer will have no effect. + /// `URLSessionConfiguration.af.default` by default. + /// - delegate: `SessionDelegate` that handles `session`'s delegate callbacks as well as `Request` + /// interaction. `SessionDelegate()` by default. + /// - rootQueue: Root `DispatchQueue` for all internal callbacks and state updates. **MUST** be a + /// serial queue. `DispatchQueue(label: "org.alamofire.session.rootQueue")` by default. + /// - startRequestsImmediately: Determines whether this instance will automatically start all `Request`s. `true` + /// by default. If set to `false`, all `Request`s created must have `.resume()` called. + /// on them for them to start. + /// - requestQueue: `DispatchQueue` on which to perform `URLRequest` creation. By default this queue + /// will use the `rootQueue` as its `target`. A separate queue can be used if it's + /// determined request creation is a bottleneck, but that should only be done after + /// careful testing and profiling. `nil` by default. + /// - serializationQueue: `DispatchQueue` on which to perform all response serialization. By default this + /// queue will use the `rootQueue` as its `target`. A separate queue can be used if + /// it's determined response serialization is a bottleneck, but that should only be + /// done after careful testing and profiling. `nil` by default. + /// - interceptor: `RequestInterceptor` to be used for all `Request`s created by this instance. `nil` + /// by default. + /// - serverTrustManager: `ServerTrustManager` to be used for all trust evaluations by this instance. `nil` + /// by default. + /// - redirectHandler: `RedirectHandler` to be used by all `Request`s created by this instance. `nil` by + /// default. + /// - cachedResponseHandler: `CachedResponseHandler` to be used by all `Request`s created by this instance. + /// `nil` by default. + /// - eventMonitors: Additional `EventMonitor`s used by the instance. Alamofire always adds a + /// `AlamofireNotifications` `EventMonitor` to the array passed here. `[]` by default. + public convenience init(configuration: URLSessionConfiguration = URLSessionConfiguration.af.default, + delegate: SessionDelegate = SessionDelegate(), + rootQueue: DispatchQueue = DispatchQueue(label: "org.alamofire.session.rootQueue"), + startRequestsImmediately: Bool = true, + requestQueue: DispatchQueue? = nil, + serializationQueue: DispatchQueue? = nil, + interceptor: RequestInterceptor? = nil, + serverTrustManager: ServerTrustManager? = nil, + redirectHandler: RedirectHandler? = nil, + cachedResponseHandler: CachedResponseHandler? = nil, + eventMonitors: [EventMonitor] = []) { + precondition(configuration.identifier == nil, "Alamofire does not support background URLSessionConfigurations.") + + let delegateQueue = OperationQueue(maxConcurrentOperationCount: 1, underlyingQueue: rootQueue, name: "org.alamofire.session.sessionDelegateQueue") + let session = URLSession(configuration: configuration, delegate: delegate, delegateQueue: delegateQueue) + + self.init(session: session, + delegate: delegate, + rootQueue: rootQueue, + startRequestsImmediately: startRequestsImmediately, + requestQueue: requestQueue, + serializationQueue: serializationQueue, + interceptor: interceptor, + serverTrustManager: serverTrustManager, + redirectHandler: redirectHandler, + cachedResponseHandler: cachedResponseHandler, + eventMonitors: eventMonitors) + } + + deinit { + finishRequestsForDeinit() + session.invalidateAndCancel() + } + + // MARK: - Cancellation + + /// Cancel all active `Request`s, optionally calling a completion handler when complete. + /// + /// - Note: This is an asynchronous operation and does not block the creation of future `Request`s. Cancelled + /// `Request`s may not cancel immediately due internal work, and may not cancel at all if they are close to + /// completion when cancelled. + /// + /// - Parameters: + /// - queue: `DispatchQueue` on which the completion handler is run. `.main` by default. + /// - completion: Closure to be called when all `Request`s have been cancelled. + public func cancelAllRequests(completingOnQueue queue: DispatchQueue = .main, completion: (() -> Void)? = nil) { + rootQueue.async { + self.activeRequests.forEach { $0.cancel() } + queue.async { completion?() } + } + } + + // MARK: - DataRequest + + /// Closure which provides a `URLRequest` for mutation. + public typealias RequestModifier = (inout URLRequest) throws -> Void + + struct RequestConvertible: URLRequestConvertible { + let url: URLConvertible + let method: HTTPMethod + let parameters: Parameters? + let encoding: ParameterEncoding + let headers: HTTPHeaders? + let requestModifier: RequestModifier? + + func asURLRequest() throws -> URLRequest { + var request = try URLRequest(url: url, method: method, headers: headers) + try requestModifier?(&request) + + return try encoding.encode(request, with: parameters) + } + } + + /// Creates a `DataRequest` from a `URLRequest` created using the passed components and a `RequestInterceptor`. + /// + /// - Parameters: + /// - convertible: `URLConvertible` value to be used as the `URLRequest`'s `URL`. + /// - method: `HTTPMethod` for the `URLRequest`. `.get` by default. + /// - parameters: `Parameters` (a.k.a. `[String: Any]`) value to be encoded into the `URLRequest`. `nil` by + /// default. + /// - encoding: `ParameterEncoding` to be used to encode the `parameters` value into the `URLRequest`. + /// `URLEncoding.default` by default. + /// - headers: `HTTPHeaders` value to be added to the `URLRequest`. `nil` by default. + /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default. + /// - requestModifier: `RequestModifier` which will be applied to the `URLRequest` created from the provided + /// parameters. `nil` by default. + /// + /// - Returns: The created `DataRequest`. + open func request(_ convertible: URLConvertible, + method: HTTPMethod = .get, + parameters: Parameters? = nil, + encoding: ParameterEncoding = URLEncoding.default, + headers: HTTPHeaders? = nil, + interceptor: RequestInterceptor? = nil, + requestModifier: RequestModifier? = nil) -> DataRequest { + let convertible = RequestConvertible(url: convertible, + method: method, + parameters: parameters, + encoding: encoding, + headers: headers, + requestModifier: requestModifier) + + return request(convertible, interceptor: interceptor) + } + + struct RequestEncodableConvertible: URLRequestConvertible { + let url: URLConvertible + let method: HTTPMethod + let parameters: Parameters? + let encoder: ParameterEncoder + let headers: HTTPHeaders? + let requestModifier: RequestModifier? + + func asURLRequest() throws -> URLRequest { + var request = try URLRequest(url: url, method: method, headers: headers) + try requestModifier?(&request) + + return try parameters.map { try encoder.encode($0, into: request) } ?? request + } + } + + /// Creates a `DataRequest` from a `URLRequest` created using the passed components, `Encodable` parameters, and a + /// `RequestInterceptor`. + /// + /// - Parameters: + /// - convertible: `URLConvertible` value to be used as the `URLRequest`'s `URL`. + /// - method: `HTTPMethod` for the `URLRequest`. `.get` by default. + /// - parameters: `Encodable` value to be encoded into the `URLRequest`. `nil` by default. + /// - encoder: `ParameterEncoder` to be used to encode the `parameters` value into the `URLRequest`. + /// `URLEncodedFormParameterEncoder.default` by default. + /// - headers: `HTTPHeaders` value to be added to the `URLRequest`. `nil` by default. + /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default. + /// + /// - Returns: The created `DataRequest`. + open func request(_ convertible: URLConvertible, + method: HTTPMethod = .get, + parameters: Parameters? = nil, + encoder: ParameterEncoder = URLEncodedFormParameterEncoder.default, + headers: HTTPHeaders? = nil, + interceptor: RequestInterceptor? = nil, + requestModifier: RequestModifier? = nil) -> DataRequest { + let convertible = RequestEncodableConvertible(url: convertible, + method: method, + parameters: parameters, + encoder: encoder, + headers: headers, + requestModifier: requestModifier) + + return request(convertible, interceptor: interceptor) + } + + /// Creates a `DataRequest` from a `URLRequestConvertible` value and a `RequestInterceptor`. + /// + /// - Parameters: + /// - convertible: `URLRequestConvertible` value to be used to create the `URLRequest`. + /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default. + /// + /// - Returns: The created `DataRequest`. + open func request(_ convertible: URLRequestConvertible, interceptor: RequestInterceptor? = nil) -> DataRequest { + let request = DataRequest(convertible: convertible, + underlyingQueue: rootQueue, + serializationQueue: serializationQueue, + eventMonitor: eventMonitor, + interceptor: interceptor, + delegate: self) + + perform(request) + + return request + } + + // MARK: - DataStreamRequest + + /// Creates a `DataStreamRequest` from the passed components, `Encodable` parameters, and `RequestInterceptor`. + /// + /// - Parameters: + /// - convertible: `URLConvertible` value to be used as the `URLRequest`'s `URL`. + /// - method: `HTTPMethod` for the `URLRequest`. `.get` by default. + /// - parameters: `Encodable` value to be encoded into the `URLRequest`. `nil` by default. + /// - encoder: `ParameterEncoder` to be used to encode the `parameters` value into the + /// `URLRequest`. + /// `URLEncodedFormParameterEncoder.default` by default. + /// - headers: `HTTPHeaders` value to be added to the `URLRequest`. `nil` by default. + /// - automaticallyCancelOnStreamError: `Bool` indicating whether the instance should be canceled when an `Error` + /// is thrown while serializing stream `Data`. `false` by default. + /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` + /// by default. + /// - requestModifier: `RequestModifier` which will be applied to the `URLRequest` created from + /// the provided parameters. `nil` by default. + /// + /// - Returns: The created `DataStream` request. + open func streamRequest(_ convertible: URLConvertible, + method: HTTPMethod = .get, + parameters: Parameters? = nil, + encoder: ParameterEncoder = URLEncodedFormParameterEncoder.default, + headers: HTTPHeaders? = nil, + automaticallyCancelOnStreamError: Bool = false, + interceptor: RequestInterceptor? = nil, + requestModifier: RequestModifier? = nil) -> DataStreamRequest { + let convertible = RequestEncodableConvertible(url: convertible, + method: method, + parameters: parameters, + encoder: encoder, + headers: headers, + requestModifier: requestModifier) + + return streamRequest(convertible, + automaticallyCancelOnStreamError: automaticallyCancelOnStreamError, + interceptor: interceptor) + } + + /// Creates a `DataStreamRequest` from the passed components and `RequestInterceptor`. + /// + /// - Parameters: + /// - convertible: `URLConvertible` value to be used as the `URLRequest`'s `URL`. + /// - method: `HTTPMethod` for the `URLRequest`. `.get` by default. + /// - headers: `HTTPHeaders` value to be added to the `URLRequest`. `nil` by default. + /// - automaticallyCancelOnStreamError: `Bool` indicating whether the instance should be canceled when an `Error` + /// is thrown while serializing stream `Data`. `false` by default. + /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` + /// by default. + /// - requestModifier: `RequestModifier` which will be applied to the `URLRequest` created from + /// the provided parameters. `nil` by default. + /// + /// - Returns: The created `DataStream` request. + open func streamRequest(_ convertible: URLConvertible, + method: HTTPMethod = .get, + headers: HTTPHeaders? = nil, + automaticallyCancelOnStreamError: Bool = false, + interceptor: RequestInterceptor? = nil, + requestModifier: RequestModifier? = nil) -> DataStreamRequest { + let convertible = RequestEncodableConvertible(url: convertible, + method: method, + parameters: Optional.none, + encoder: URLEncodedFormParameterEncoder.default, + headers: headers, + requestModifier: requestModifier) + + return streamRequest(convertible, + automaticallyCancelOnStreamError: automaticallyCancelOnStreamError, + interceptor: interceptor) + } + + /// Creates a `DataStreamRequest` from the passed `URLRequestConvertible` value and `RequestInterceptor`. + /// + /// - Parameters: + /// - convertible: `URLRequestConvertible` value to be used to create the `URLRequest`. + /// - automaticallyCancelOnStreamError: `Bool` indicating whether the instance should be canceled when an `Error` + /// is thrown while serializing stream `Data`. `false` by default. + /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` + /// by default. + /// + /// - Returns: The created `DataStreamRequest`. + open func streamRequest(_ convertible: URLRequestConvertible, + automaticallyCancelOnStreamError: Bool = false, + interceptor: RequestInterceptor? = nil) -> DataStreamRequest { + let request = DataStreamRequest(convertible: convertible, + automaticallyCancelOnStreamError: automaticallyCancelOnStreamError, + underlyingQueue: rootQueue, + serializationQueue: serializationQueue, + eventMonitor: eventMonitor, + interceptor: interceptor, + delegate: self) + + perform(request) + + return request + } + + // MARK: - DownloadRequest + + /// Creates a `DownloadRequest` using a `URLRequest` created using the passed components, `RequestInterceptor`, and + /// `Destination`. + /// + /// - Parameters: + /// - convertible: `URLConvertible` value to be used as the `URLRequest`'s `URL`. + /// - method: `HTTPMethod` for the `URLRequest`. `.get` by default. + /// - parameters: `Parameters` (a.k.a. `[String: Any]`) value to be encoded into the `URLRequest`. `nil` by + /// default. + /// - encoding: `ParameterEncoding` to be used to encode the `parameters` value into the `URLRequest`. + /// Defaults to `URLEncoding.default`. + /// - headers: `HTTPHeaders` value to be added to the `URLRequest`. `nil` by default. + /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default. + /// - requestModifier: `RequestModifier` which will be applied to the `URLRequest` created from the provided + /// parameters. `nil` by default. + /// - destination: `DownloadRequest.Destination` closure used to determine how and where the downloaded file + /// should be moved. `nil` by default. + /// + /// - Returns: The created `DownloadRequest`. + open func download(_ convertible: URLConvertible, + method: HTTPMethod = .get, + parameters: Parameters? = nil, + encoding: ParameterEncoding = URLEncoding.default, + headers: HTTPHeaders? = nil, + interceptor: RequestInterceptor? = nil, + requestModifier: RequestModifier? = nil, + to destination: DownloadRequest.Destination? = nil) -> DownloadRequest { + let convertible = RequestConvertible(url: convertible, + method: method, + parameters: parameters, + encoding: encoding, + headers: headers, + requestModifier: requestModifier) + + return download(convertible, interceptor: interceptor, to: destination) + } + + /// Creates a `DownloadRequest` from a `URLRequest` created using the passed components, `Encodable` parameters, and + /// a `RequestInterceptor`. + /// + /// - Parameters: + /// - convertible: `URLConvertible` value to be used as the `URLRequest`'s `URL`. + /// - method: `HTTPMethod` for the `URLRequest`. `.get` by default. + /// - parameters: Value conforming to `Encodable` to be encoded into the `URLRequest`. `nil` by default. + /// - encoder: `ParameterEncoder` to be used to encode the `parameters` value into the `URLRequest`. + /// Defaults to `URLEncodedFormParameterEncoder.default`. + /// - headers: `HTTPHeaders` value to be added to the `URLRequest`. `nil` by default. + /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default. + /// - requestModifier: `RequestModifier` which will be applied to the `URLRequest` created from the provided + /// parameters. `nil` by default. + /// - destination: `DownloadRequest.Destination` closure used to determine how and where the downloaded file + /// should be moved. `nil` by default. + /// + /// - Returns: The created `DownloadRequest`. + open func download(_ convertible: URLConvertible, + method: HTTPMethod = .get, + parameters: Parameters? = nil, + encoder: ParameterEncoder = URLEncodedFormParameterEncoder.default, + headers: HTTPHeaders? = nil, + interceptor: RequestInterceptor? = nil, + requestModifier: RequestModifier? = nil, + to destination: DownloadRequest.Destination? = nil) -> DownloadRequest { + let convertible = RequestEncodableConvertible(url: convertible, + method: method, + parameters: parameters, + encoder: encoder, + headers: headers, + requestModifier: requestModifier) + + return download(convertible, interceptor: interceptor, to: destination) + } + + /// Creates a `DownloadRequest` from a `URLRequestConvertible` value, a `RequestInterceptor`, and a `Destination`. + /// + /// - Parameters: + /// - convertible: `URLRequestConvertible` value to be used to create the `URLRequest`. + /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default. + /// - destination: `DownloadRequest.Destination` closure used to determine how and where the downloaded file + /// should be moved. `nil` by default. + /// + /// - Returns: The created `DownloadRequest`. + open func download(_ convertible: URLRequestConvertible, + interceptor: RequestInterceptor? = nil, + to destination: DownloadRequest.Destination? = nil) -> DownloadRequest { + let request = DownloadRequest(downloadable: .request(convertible), + underlyingQueue: rootQueue, + serializationQueue: serializationQueue, + eventMonitor: eventMonitor, + interceptor: interceptor, + delegate: self, + destination: destination ?? DownloadRequest.defaultDestination) + + perform(request) + + return request + } + + /// Creates a `DownloadRequest` from the `resumeData` produced from a previously cancelled `DownloadRequest`, as + /// well as a `RequestInterceptor`, and a `Destination`. + /// + /// - Note: If `destination` is not specified, the download will be moved to a temporary location determined by + /// Alamofire. The file will not be deleted until the system purges the temporary files. + /// + /// - Note: On some versions of all Apple platforms (iOS 10 - 10.2, macOS 10.12 - 10.12.2, tvOS 10 - 10.1, watchOS 3 - 3.1.1), + /// `resumeData` is broken on background URL session configurations. There's an underlying bug in the `resumeData` + /// generation logic where the data is written incorrectly and will always fail to resume the download. For more + /// information about the bug and possible workarounds, please refer to the [this Stack Overflow post](http://stackoverflow.com/a/39347461/1342462). + /// + /// - Parameters: + /// - data: The resume data from a previously cancelled `DownloadRequest` or `URLSessionDownloadTask`. + /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default. + /// - destination: `DownloadRequest.Destination` closure used to determine how and where the downloaded file + /// should be moved. `nil` by default. + /// + /// - Returns: The created `DownloadRequest`. + open func download(resumingWith data: Data, + interceptor: RequestInterceptor? = nil, + to destination: DownloadRequest.Destination? = nil) -> DownloadRequest { + let request = DownloadRequest(downloadable: .resumeData(data), + underlyingQueue: rootQueue, + serializationQueue: serializationQueue, + eventMonitor: eventMonitor, + interceptor: interceptor, + delegate: self, + destination: destination ?? DownloadRequest.defaultDestination) + + perform(request) + + return request + } + + // MARK: - UploadRequest + + struct ParameterlessRequestConvertible: URLRequestConvertible { + let url: URLConvertible + let method: HTTPMethod + let headers: HTTPHeaders? + let requestModifier: RequestModifier? + + func asURLRequest() throws -> URLRequest { + var request = try URLRequest(url: url, method: method, headers: headers) + try requestModifier?(&request) + + return request + } + } + + struct Upload: UploadConvertible { + let request: URLRequestConvertible + let uploadable: UploadableConvertible + + func createUploadable() throws -> UploadRequest.Uploadable { + try uploadable.createUploadable() + } + + func asURLRequest() throws -> URLRequest { + try request.asURLRequest() + } + } + + // MARK: Data + + /// Creates an `UploadRequest` for the given `Data`, `URLRequest` components, and `RequestInterceptor`. + /// + /// - Parameters: + /// - data: The `Data` to upload. + /// - convertible: `URLConvertible` value to be used as the `URLRequest`'s `URL`. + /// - method: `HTTPMethod` for the `URLRequest`. `.post` by default. + /// - headers: `HTTPHeaders` value to be added to the `URLRequest`. `nil` by default. + /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default. + /// - fileManager: `FileManager` instance to be used by the returned `UploadRequest`. `.default` instance by + /// default. + /// - requestModifier: `RequestModifier` which will be applied to the `URLRequest` created from the provided + /// parameters. `nil` by default. + /// + /// - Returns: The created `UploadRequest`. + open func upload(_ data: Data, + to convertible: URLConvertible, + method: HTTPMethod = .post, + headers: HTTPHeaders? = nil, + interceptor: RequestInterceptor? = nil, + fileManager: FileManager = .default, + requestModifier: RequestModifier? = nil) -> UploadRequest { + let convertible = ParameterlessRequestConvertible(url: convertible, + method: method, + headers: headers, + requestModifier: requestModifier) + + return upload(data, with: convertible, interceptor: interceptor, fileManager: fileManager) + } + + /// Creates an `UploadRequest` for the given `Data` using the `URLRequestConvertible` value and `RequestInterceptor`. + /// + /// - Parameters: + /// - data: The `Data` to upload. + /// - convertible: `URLRequestConvertible` value to be used to create the `URLRequest`. + /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default. + /// - fileManager: `FileManager` instance to be used by the returned `UploadRequest`. `.default` instance by + /// default. + /// + /// - Returns: The created `UploadRequest`. + open func upload(_ data: Data, + with convertible: URLRequestConvertible, + interceptor: RequestInterceptor? = nil, + fileManager: FileManager = .default) -> UploadRequest { + upload(.data(data), with: convertible, interceptor: interceptor, fileManager: fileManager) + } + + // MARK: File + + /// Creates an `UploadRequest` for the file at the given file `URL`, using a `URLRequest` from the provided + /// components and `RequestInterceptor`. + /// + /// - Parameters: + /// - fileURL: The `URL` of the file to upload. + /// - convertible: `URLConvertible` value to be used as the `URLRequest`'s `URL`. + /// - method: `HTTPMethod` for the `URLRequest`. `.post` by default. + /// - headers: `HTTPHeaders` value to be added to the `URLRequest`. `nil` by default. + /// - interceptor: `RequestInterceptor` value to be used by the returned `UploadRequest`. `nil` by default. + /// - fileManager: `FileManager` instance to be used by the returned `UploadRequest`. `.default` instance by + /// default. + /// - requestModifier: `RequestModifier` which will be applied to the `URLRequest` created from the provided + /// parameters. `nil` by default. + /// + /// - Returns: The created `UploadRequest`. + open func upload(_ fileURL: URL, + to convertible: URLConvertible, + method: HTTPMethod = .post, + headers: HTTPHeaders? = nil, + interceptor: RequestInterceptor? = nil, + fileManager: FileManager = .default, + requestModifier: RequestModifier? = nil) -> UploadRequest { + let convertible = ParameterlessRequestConvertible(url: convertible, + method: method, + headers: headers, + requestModifier: requestModifier) + + return upload(fileURL, with: convertible, interceptor: interceptor, fileManager: fileManager) + } + + /// Creates an `UploadRequest` for the file at the given file `URL` using the `URLRequestConvertible` value and + /// `RequestInterceptor`. + /// + /// - Parameters: + /// - fileURL: The `URL` of the file to upload. + /// - convertible: `URLRequestConvertible` value to be used to create the `URLRequest`. + /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default. + /// - fileManager: `FileManager` instance to be used by the returned `UploadRequest`. `.default` instance by + /// default. + /// + /// - Returns: The created `UploadRequest`. + open func upload(_ fileURL: URL, + with convertible: URLRequestConvertible, + interceptor: RequestInterceptor? = nil, + fileManager: FileManager = .default) -> UploadRequest { + upload(.file(fileURL, shouldRemove: false), with: convertible, interceptor: interceptor, fileManager: fileManager) + } + + // MARK: InputStream + + /// Creates an `UploadRequest` from the `InputStream` provided using a `URLRequest` from the provided components and + /// `RequestInterceptor`. + /// + /// - Parameters: + /// - stream: The `InputStream` that provides the data to upload. + /// - convertible: `URLConvertible` value to be used as the `URLRequest`'s `URL`. + /// - method: `HTTPMethod` for the `URLRequest`. `.post` by default. + /// - headers: `HTTPHeaders` value to be added to the `URLRequest`. `nil` by default. + /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default. + /// - fileManager: `FileManager` instance to be used by the returned `UploadRequest`. `.default` instance by + /// default. + /// - requestModifier: `RequestModifier` which will be applied to the `URLRequest` created from the provided + /// parameters. `nil` by default. + /// + /// - Returns: The created `UploadRequest`. + open func upload(_ stream: InputStream, + to convertible: URLConvertible, + method: HTTPMethod = .post, + headers: HTTPHeaders? = nil, + interceptor: RequestInterceptor? = nil, + fileManager: FileManager = .default, + requestModifier: RequestModifier? = nil) -> UploadRequest { + let convertible = ParameterlessRequestConvertible(url: convertible, + method: method, + headers: headers, + requestModifier: requestModifier) + + return upload(stream, with: convertible, interceptor: interceptor, fileManager: fileManager) + } + + /// Creates an `UploadRequest` from the provided `InputStream` using the `URLRequestConvertible` value and + /// `RequestInterceptor`. + /// + /// - Parameters: + /// - stream: The `InputStream` that provides the data to upload. + /// - convertible: `URLRequestConvertible` value to be used to create the `URLRequest`. + /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default. + /// - fileManager: `FileManager` instance to be used by the returned `UploadRequest`. `.default` instance by + /// default. + /// + /// - Returns: The created `UploadRequest`. + open func upload(_ stream: InputStream, + with convertible: URLRequestConvertible, + interceptor: RequestInterceptor? = nil, + fileManager: FileManager = .default) -> UploadRequest { + upload(.stream(stream), with: convertible, interceptor: interceptor, fileManager: fileManager) + } + + // MARK: MultipartFormData + + /// Creates an `UploadRequest` for the multipart form data built using a closure and sent using the provided + /// `URLRequest` components and `RequestInterceptor`. + /// + /// It is important to understand the memory implications of uploading `MultipartFormData`. If the cumulative + /// payload is small, encoding the data in-memory and directly uploading to a server is the by far the most + /// efficient approach. However, if the payload is too large, encoding the data in-memory could cause your app to + /// be terminated. Larger payloads must first be written to disk using input and output streams to keep the memory + /// footprint low, then the data can be uploaded as a stream from the resulting file. Streaming from disk MUST be + /// used for larger payloads such as video content. + /// + /// The `encodingMemoryThreshold` parameter allows Alamofire to automatically determine whether to encode in-memory + /// or stream from disk. If the content length of the `MultipartFormData` is below the `encodingMemoryThreshold`, + /// encoding takes place in-memory. If the content length exceeds the threshold, the data is streamed to disk + /// during the encoding process. Then the result is uploaded as data or as a stream depending on which encoding + /// technique was used. + /// + /// - Parameters: + /// - multipartFormData: `MultipartFormData` building closure. + /// - convertible: `URLConvertible` value to be used as the `URLRequest`'s `URL`. + /// - encodingMemoryThreshold: Byte threshold used to determine whether the form data is encoded into memory or + /// onto disk before being uploaded. `MultipartFormData.encodingMemoryThreshold` by + /// default. + /// - method: `HTTPMethod` for the `URLRequest`. `.post` by default. + /// - headers: `HTTPHeaders` value to be added to the `URLRequest`. `nil` by default. + /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default. + /// - fileManager: `FileManager` to be used if the form data exceeds the memory threshold and is + /// written to disk before being uploaded. `.default` instance by default. + /// - requestModifier: `RequestModifier` which will be applied to the `URLRequest` created from the + /// provided parameters. `nil` by default. + /// + /// - Returns: The created `UploadRequest`. + open func upload(multipartFormData: @escaping (MultipartFormData) -> Void, + to url: URLConvertible, + usingThreshold encodingMemoryThreshold: UInt64 = MultipartFormData.encodingMemoryThreshold, + method: HTTPMethod = .post, + headers: HTTPHeaders? = nil, + interceptor: RequestInterceptor? = nil, + fileManager: FileManager = .default, + requestModifier: RequestModifier? = nil) -> UploadRequest { + let convertible = ParameterlessRequestConvertible(url: url, + method: method, + headers: headers, + requestModifier: requestModifier) + + let formData = MultipartFormData(fileManager: fileManager) + multipartFormData(formData) + + return upload(multipartFormData: formData, + with: convertible, + usingThreshold: encodingMemoryThreshold, + interceptor: interceptor, + fileManager: fileManager) + } + + /// Creates an `UploadRequest` using a `MultipartFormData` building closure, the provided `URLRequestConvertible` + /// value, and a `RequestInterceptor`. + /// + /// It is important to understand the memory implications of uploading `MultipartFormData`. If the cumulative + /// payload is small, encoding the data in-memory and directly uploading to a server is the by far the most + /// efficient approach. However, if the payload is too large, encoding the data in-memory could cause your app to + /// be terminated. Larger payloads must first be written to disk using input and output streams to keep the memory + /// footprint low, then the data can be uploaded as a stream from the resulting file. Streaming from disk MUST be + /// used for larger payloads such as video content. + /// + /// The `encodingMemoryThreshold` parameter allows Alamofire to automatically determine whether to encode in-memory + /// or stream from disk. If the content length of the `MultipartFormData` is below the `encodingMemoryThreshold`, + /// encoding takes place in-memory. If the content length exceeds the threshold, the data is streamed to disk + /// during the encoding process. Then the result is uploaded as data or as a stream depending on which encoding + /// technique was used. + /// + /// - Parameters: + /// - multipartFormData: `MultipartFormData` building closure. + /// - request: `URLRequestConvertible` value to be used to create the `URLRequest`. + /// - encodingMemoryThreshold: Byte threshold used to determine whether the form data is encoded into memory or + /// onto disk before being uploaded. `MultipartFormData.encodingMemoryThreshold` by + /// default. + /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default. + /// - fileManager: `FileManager` to be used if the form data exceeds the memory threshold and is + /// written to disk before being uploaded. `.default` instance by default. + /// + /// - Returns: The created `UploadRequest`. + open func upload(multipartFormData: @escaping (MultipartFormData) -> Void, + with request: URLRequestConvertible, + usingThreshold encodingMemoryThreshold: UInt64 = MultipartFormData.encodingMemoryThreshold, + interceptor: RequestInterceptor? = nil, + fileManager: FileManager = .default) -> UploadRequest { + let formData = MultipartFormData(fileManager: fileManager) + multipartFormData(formData) + + return upload(multipartFormData: formData, + with: request, + usingThreshold: encodingMemoryThreshold, + interceptor: interceptor, + fileManager: fileManager) + } + + /// Creates an `UploadRequest` for the prebuilt `MultipartFormData` value using the provided `URLRequest` components + /// and `RequestInterceptor`. + /// + /// It is important to understand the memory implications of uploading `MultipartFormData`. If the cumulative + /// payload is small, encoding the data in-memory and directly uploading to a server is the by far the most + /// efficient approach. However, if the payload is too large, encoding the data in-memory could cause your app to + /// be terminated. Larger payloads must first be written to disk using input and output streams to keep the memory + /// footprint low, then the data can be uploaded as a stream from the resulting file. Streaming from disk MUST be + /// used for larger payloads such as video content. + /// + /// The `encodingMemoryThreshold` parameter allows Alamofire to automatically determine whether to encode in-memory + /// or stream from disk. If the content length of the `MultipartFormData` is below the `encodingMemoryThreshold`, + /// encoding takes place in-memory. If the content length exceeds the threshold, the data is streamed to disk + /// during the encoding process. Then the result is uploaded as data or as a stream depending on which encoding + /// technique was used. + /// + /// - Parameters: + /// - multipartFormData: `MultipartFormData` instance to upload. + /// - url: `URLConvertible` value to be used as the `URLRequest`'s `URL`. + /// - encodingMemoryThreshold: Byte threshold used to determine whether the form data is encoded into memory or + /// onto disk before being uploaded. `MultipartFormData.encodingMemoryThreshold` by + /// default. + /// - method: `HTTPMethod` for the `URLRequest`. `.post` by default. + /// - headers: `HTTPHeaders` value to be added to the `URLRequest`. `nil` by default. + /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default. + /// - fileManager: `FileManager` to be used if the form data exceeds the memory threshold and is + /// written to disk before being uploaded. `.default` instance by default. + /// - requestModifier: `RequestModifier` which will be applied to the `URLRequest` created from the + /// provided parameters. `nil` by default. + /// + /// - Returns: The created `UploadRequest`. + open func upload(multipartFormData: MultipartFormData, + to url: URLConvertible, + usingThreshold encodingMemoryThreshold: UInt64 = MultipartFormData.encodingMemoryThreshold, + method: HTTPMethod = .post, + headers: HTTPHeaders? = nil, + interceptor: RequestInterceptor? = nil, + fileManager: FileManager = .default, + requestModifier: RequestModifier? = nil) -> UploadRequest { + let convertible = ParameterlessRequestConvertible(url: url, + method: method, + headers: headers, + requestModifier: requestModifier) + + let multipartUpload = MultipartUpload(isInBackgroundSession: session.configuration.identifier != nil, + encodingMemoryThreshold: encodingMemoryThreshold, + request: convertible, + multipartFormData: multipartFormData) + + return upload(multipartUpload, interceptor: interceptor, fileManager: fileManager) + } + + /// Creates an `UploadRequest` for the prebuilt `MultipartFormData` value using the providing `URLRequestConvertible` + /// value and `RequestInterceptor`. + /// + /// It is important to understand the memory implications of uploading `MultipartFormData`. If the cumulative + /// payload is small, encoding the data in-memory and directly uploading to a server is the by far the most + /// efficient approach. However, if the payload is too large, encoding the data in-memory could cause your app to + /// be terminated. Larger payloads must first be written to disk using input and output streams to keep the memory + /// footprint low, then the data can be uploaded as a stream from the resulting file. Streaming from disk MUST be + /// used for larger payloads such as video content. + /// + /// The `encodingMemoryThreshold` parameter allows Alamofire to automatically determine whether to encode in-memory + /// or stream from disk. If the content length of the `MultipartFormData` is below the `encodingMemoryThreshold`, + /// encoding takes place in-memory. If the content length exceeds the threshold, the data is streamed to disk + /// during the encoding process. Then the result is uploaded as data or as a stream depending on which encoding + /// technique was used. + /// + /// - Parameters: + /// - multipartFormData: `MultipartFormData` instance to upload. + /// - request: `URLRequestConvertible` value to be used to create the `URLRequest`. + /// - encodingMemoryThreshold: Byte threshold used to determine whether the form data is encoded into memory or + /// onto disk before being uploaded. `MultipartFormData.encodingMemoryThreshold` by + /// default. + /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default. + /// - fileManager: `FileManager` instance to be used by the returned `UploadRequest`. `.default` instance by + /// default. + /// + /// - Returns: The created `UploadRequest`. + open func upload(multipartFormData: MultipartFormData, + with request: URLRequestConvertible, + usingThreshold encodingMemoryThreshold: UInt64 = MultipartFormData.encodingMemoryThreshold, + interceptor: RequestInterceptor? = nil, + fileManager: FileManager = .default) -> UploadRequest { + let multipartUpload = MultipartUpload(isInBackgroundSession: session.configuration.identifier != nil, + encodingMemoryThreshold: encodingMemoryThreshold, + request: request, + multipartFormData: multipartFormData) + + return upload(multipartUpload, interceptor: interceptor, fileManager: fileManager) + } + + // MARK: - Internal API + + // MARK: Uploadable + + func upload(_ uploadable: UploadRequest.Uploadable, + with convertible: URLRequestConvertible, + interceptor: RequestInterceptor?, + fileManager: FileManager) -> UploadRequest { + let uploadable = Upload(request: convertible, uploadable: uploadable) + + return upload(uploadable, interceptor: interceptor, fileManager: fileManager) + } + + func upload(_ upload: UploadConvertible, interceptor: RequestInterceptor?, fileManager: FileManager) -> UploadRequest { + let request = UploadRequest(convertible: upload, + underlyingQueue: rootQueue, + serializationQueue: serializationQueue, + eventMonitor: eventMonitor, + interceptor: interceptor, + fileManager: fileManager, + delegate: self) + + perform(request) + + return request + } + + // MARK: Perform + + /// Perform `Request`. + /// + /// - Note: Called during retry. + /// + /// - Parameter request: The `Request` to perform. + func perform(_ request: Request) { + // Leaf types must come first, otherwise they will cast as their superclass. + switch request { + case let r as UploadRequest: perform(r) // UploadRequest must come before DataRequest due to subtype relationship. + case let r as DataRequest: perform(r) + case let r as DownloadRequest: perform(r) + case let r as DataStreamRequest: perform(r) + default: fatalError("Attempted to perform unsupported Request subclass: \(type(of: request))") + } + } + + func perform(_ request: DataRequest) { + requestQueue.async { + guard !request.isCancelled else { return } + + self.activeRequests.insert(request) + + self.performSetupOperations(for: request, convertible: request.convertible) + } + } + + func perform(_ request: DataStreamRequest) { + requestQueue.async { + guard !request.isCancelled else { return } + + self.activeRequests.insert(request) + + self.performSetupOperations(for: request, convertible: request.convertible) + } + } + + func perform(_ request: UploadRequest) { + requestQueue.async { + guard !request.isCancelled else { return } + + self.activeRequests.insert(request) + + do { + let uploadable = try request.upload.createUploadable() + self.rootQueue.async { request.didCreateUploadable(uploadable) } + + self.performSetupOperations(for: request, convertible: request.convertible) + } catch { + self.rootQueue.async { request.didFailToCreateUploadable(with: error.asAFError(or: .createUploadableFailed(error: error))) } + } + } + } + + func perform(_ request: DownloadRequest) { + requestQueue.async { + guard !request.isCancelled else { return } + + self.activeRequests.insert(request) + + switch request.downloadable { + case let .request(convertible): + self.performSetupOperations(for: request, convertible: convertible) + case let .resumeData(resumeData): + self.rootQueue.async { self.didReceiveResumeData(resumeData, for: request) } + } + } + } + + func performSetupOperations(for request: Request, convertible: URLRequestConvertible) { + let initialRequest: URLRequest + + do { + initialRequest = try convertible.asURLRequest() + try initialRequest.validate() + } catch { + rootQueue.async { request.didFailToCreateURLRequest(with: error.asAFError(or: .createURLRequestFailed(error: error))) } + return + } + + rootQueue.async { request.didCreateInitialURLRequest(initialRequest) } + + guard !request.isCancelled else { return } + + guard let adapter = adapter(for: request) else { + rootQueue.async { self.didCreateURLRequest(initialRequest, for: request) } + return + } + + adapter.adapt(initialRequest, for: self) { result in + do { + let adaptedRequest = try result.get() + try adaptedRequest.validate() + + self.rootQueue.async { + request.didAdaptInitialRequest(initialRequest, to: adaptedRequest) + self.didCreateURLRequest(adaptedRequest, for: request) + } + } catch { + self.rootQueue.async { request.didFailToAdaptURLRequest(initialRequest, withError: .requestAdaptationFailed(error: error)) } + } + } + } + + // MARK: - Task Handling + + func didCreateURLRequest(_ urlRequest: URLRequest, for request: Request) { + request.didCreateURLRequest(urlRequest) + + guard !request.isCancelled else { return } + + let task = request.task(for: urlRequest, using: session) + requestTaskMap[request] = task + request.didCreateTask(task) + + updateStatesForTask(task, request: request) + } + + func didReceiveResumeData(_ data: Data, for request: DownloadRequest) { + guard !request.isCancelled else { return } + + let task = request.task(forResumeData: data, using: session) + requestTaskMap[request] = task + request.didCreateTask(task) + + updateStatesForTask(task, request: request) + } + + func updateStatesForTask(_ task: URLSessionTask, request: Request) { + request.withState { state in + switch state { + case .initialized, .finished: + // Do nothing. + break + case .resumed: + task.resume() + rootQueue.async { request.didResumeTask(task) } + case .suspended: + task.suspend() + rootQueue.async { request.didSuspendTask(task) } + case .cancelled: + // Resume to ensure metrics are gathered. + task.resume() + task.cancel() + rootQueue.async { request.didCancelTask(task) } + } + } + } + + // MARK: - Adapters and Retriers + + func adapter(for request: Request) -> RequestAdapter? { + if let requestInterceptor = request.interceptor, let sessionInterceptor = interceptor { + return Interceptor(adapters: [requestInterceptor, sessionInterceptor]) + } else { + return request.interceptor ?? interceptor + } + } + + func retrier(for request: Request) -> RequestRetrier? { + if let requestInterceptor = request.interceptor, let sessionInterceptor = interceptor { + return Interceptor(retriers: [requestInterceptor, sessionInterceptor]) + } else { + return request.interceptor ?? interceptor + } + } + + // MARK: - Invalidation + + func finishRequestsForDeinit() { + requestTaskMap.requests.forEach { request in + rootQueue.async { + request.finish(error: AFError.sessionDeinitialized) + } + } + } +} + +// MARK: - RequestDelegate + +extension Session: RequestDelegate { + public var sessionConfiguration: URLSessionConfiguration { + session.configuration + } + + public var startImmediately: Bool { startRequestsImmediately } + + public func cleanup(after request: Request) { + activeRequests.remove(request) + } + + public func retryResult(for request: Request, dueTo error: AFError, completion: @escaping (RetryResult) -> Void) { + guard let retrier = retrier(for: request) else { + rootQueue.async { completion(.doNotRetry) } + return + } + + retrier.retry(request, for: self, dueTo: error) { retryResult in + self.rootQueue.async { + guard let retryResultError = retryResult.error else { completion(retryResult); return } + + let retryError = AFError.requestRetryFailed(retryError: retryResultError, originalError: error) + completion(.doNotRetryWithError(retryError)) + } + } + } + + public func retryRequest(_ request: Request, withDelay timeDelay: TimeInterval?) { + rootQueue.async { + let retry: () -> Void = { + guard !request.isCancelled else { return } + + request.prepareForRetry() + self.perform(request) + } + + if let retryDelay = timeDelay { + self.rootQueue.after(retryDelay) { retry() } + } else { + retry() + } + } + } +} + +// MARK: - SessionStateProvider + +extension Session: SessionStateProvider { + func request(for task: URLSessionTask) -> Request? { + dispatchPrecondition(condition: .onQueue(rootQueue)) + + return requestTaskMap[task] + } + + func didGatherMetricsForTask(_ task: URLSessionTask) { + dispatchPrecondition(condition: .onQueue(rootQueue)) + + let didDisassociate = requestTaskMap.disassociateIfNecessaryAfterGatheringMetricsForTask(task) + + if didDisassociate { + waitingCompletions[task]?() + waitingCompletions[task] = nil + } + } + + func didCompleteTask(_ task: URLSessionTask, completion: @escaping () -> Void) { + dispatchPrecondition(condition: .onQueue(rootQueue)) + + let didDisassociate = requestTaskMap.disassociateIfNecessaryAfterCompletingTask(task) + + if didDisassociate { + completion() + } else { + waitingCompletions[task] = completion + } + } + + func credential(for task: URLSessionTask, in protectionSpace: URLProtectionSpace) -> URLCredential? { + dispatchPrecondition(condition: .onQueue(rootQueue)) + + return requestTaskMap[task]?.credential ?? + session.configuration.urlCredentialStorage?.defaultCredential(for: protectionSpace) + } + + func cancelRequestsForSessionInvalidation(with error: Error?) { + dispatchPrecondition(condition: .onQueue(rootQueue)) + + requestTaskMap.requests.forEach { $0.finish(error: AFError.sessionInvalidated(error: error)) } + } +} diff --git b/Pods/Alamofire/Source/SessionDelegate.swift a/Pods/Alamofire/Source/SessionDelegate.swift new file mode 100644 index 0000000..7e6604c --- /dev/null +++ a/Pods/Alamofire/Source/SessionDelegate.swift @@ -0,0 +1,328 @@ +// +// SessionDelegate.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 + +/// Class which implements the various `URLSessionDelegate` methods to connect various Alamofire features. +open class SessionDelegate: NSObject { + private let fileManager: FileManager + + weak var stateProvider: SessionStateProvider? + var eventMonitor: EventMonitor? + + /// Creates an instance from the given `FileManager`. + /// + /// - Parameter fileManager: `FileManager` to use for underlying file management, such as moving downloaded files. + /// `.default` by default. + public init(fileManager: FileManager = .default) { + self.fileManager = fileManager + } + + /// Internal method to find and cast requests while maintaining some integrity checking. + /// + /// - Parameters: + /// - task: The `URLSessionTask` for which to find the associated `Request`. + /// - type: The `Request` subclass type to cast any `Request` associate with `task`. + func request(for task: URLSessionTask, as type: R.Type) -> R? { + guard let provider = stateProvider else { + assertionFailure("StateProvider is nil.") + return nil + } + + return provider.request(for: task) as? R + } +} + +/// Type which provides various `Session` state values. +protocol SessionStateProvider: AnyObject { + var serverTrustManager: ServerTrustManager? { get } + var redirectHandler: RedirectHandler? { get } + var cachedResponseHandler: CachedResponseHandler? { get } + + func request(for task: URLSessionTask) -> Request? + func didGatherMetricsForTask(_ task: URLSessionTask) + func didCompleteTask(_ task: URLSessionTask, completion: @escaping () -> Void) + func credential(for task: URLSessionTask, in protectionSpace: URLProtectionSpace) -> URLCredential? + func cancelRequestsForSessionInvalidation(with error: Error?) +} + +// MARK: URLSessionDelegate + +extension SessionDelegate: URLSessionDelegate { + open func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) { + eventMonitor?.urlSession(session, didBecomeInvalidWithError: error) + + stateProvider?.cancelRequestsForSessionInvalidation(with: error) + } +} + +// MARK: URLSessionTaskDelegate + +extension SessionDelegate: URLSessionTaskDelegate { + /// Result of a `URLAuthenticationChallenge` evaluation. + typealias ChallengeEvaluation = (disposition: URLSession.AuthChallengeDisposition, credential: URLCredential?, error: AFError?) + + open func urlSession(_ session: URLSession, + task: URLSessionTask, + didReceive challenge: URLAuthenticationChallenge, + completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { + eventMonitor?.urlSession(session, task: task, didReceive: challenge) + + let evaluation: ChallengeEvaluation + switch challenge.protectionSpace.authenticationMethod { + case NSURLAuthenticationMethodServerTrust: + evaluation = attemptServerTrustAuthentication(with: challenge) + case NSURLAuthenticationMethodHTTPBasic, NSURLAuthenticationMethodHTTPDigest, NSURLAuthenticationMethodNTLM, + NSURLAuthenticationMethodNegotiate, NSURLAuthenticationMethodClientCertificate: + evaluation = attemptCredentialAuthentication(for: challenge, belongingTo: task) + default: + evaluation = (.performDefaultHandling, nil, nil) + } + + if let error = evaluation.error { + stateProvider?.request(for: task)?.didFailTask(task, earlyWithError: error) + } + + completionHandler(evaluation.disposition, evaluation.credential) + } + + /// Evaluates the server trust `URLAuthenticationChallenge` received. + /// + /// - Parameter challenge: The `URLAuthenticationChallenge`. + /// + /// - Returns: The `ChallengeEvaluation`. + func attemptServerTrustAuthentication(with challenge: URLAuthenticationChallenge) -> ChallengeEvaluation { + let host = challenge.protectionSpace.host + + guard challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust, + let trust = challenge.protectionSpace.serverTrust + else { + return (.performDefaultHandling, nil, nil) + } + + do { + guard let evaluator = try stateProvider?.serverTrustManager?.serverTrustEvaluator(forHost: host) else { + return (.performDefaultHandling, nil, nil) + } + + try evaluator.evaluate(trust, forHost: host) + + return (.useCredential, URLCredential(trust: trust), nil) + } catch { + return (.cancelAuthenticationChallenge, nil, error.asAFError(or: .serverTrustEvaluationFailed(reason: .customEvaluationFailed(error: error)))) + } + } + + /// Evaluates the credential-based authentication `URLAuthenticationChallenge` received for `task`. + /// + /// - Parameters: + /// - challenge: The `URLAuthenticationChallenge`. + /// - task: The `URLSessionTask` which received the challenge. + /// + /// - Returns: The `ChallengeEvaluation`. + func attemptCredentialAuthentication(for challenge: URLAuthenticationChallenge, + belongingTo task: URLSessionTask) -> ChallengeEvaluation { + guard challenge.previousFailureCount == 0 else { + return (.rejectProtectionSpace, nil, nil) + } + + guard let credential = stateProvider?.credential(for: task, in: challenge.protectionSpace) else { + return (.performDefaultHandling, nil, nil) + } + + return (.useCredential, credential, nil) + } + + open func urlSession(_ session: URLSession, + task: URLSessionTask, + didSendBodyData bytesSent: Int64, + totalBytesSent: Int64, + totalBytesExpectedToSend: Int64) { + eventMonitor?.urlSession(session, + task: task, + didSendBodyData: bytesSent, + totalBytesSent: totalBytesSent, + totalBytesExpectedToSend: totalBytesExpectedToSend) + + stateProvider?.request(for: task)?.updateUploadProgress(totalBytesSent: totalBytesSent, + totalBytesExpectedToSend: totalBytesExpectedToSend) + } + + open func urlSession(_ session: URLSession, + task: URLSessionTask, + needNewBodyStream completionHandler: @escaping (InputStream?) -> Void) { + eventMonitor?.urlSession(session, taskNeedsNewBodyStream: task) + + guard let request = request(for: task, as: UploadRequest.self) else { + assertionFailure("needNewBodyStream did not find UploadRequest.") + completionHandler(nil) + return + } + + completionHandler(request.inputStream()) + } + + open func urlSession(_ session: URLSession, + task: URLSessionTask, + willPerformHTTPRedirection response: HTTPURLResponse, + newRequest request: URLRequest, + completionHandler: @escaping (URLRequest?) -> Void) { + eventMonitor?.urlSession(session, task: task, willPerformHTTPRedirection: response, newRequest: request) + + if let redirectHandler = stateProvider?.request(for: task)?.redirectHandler ?? stateProvider?.redirectHandler { + redirectHandler.task(task, willBeRedirectedTo: request, for: response, completion: completionHandler) + } else { + completionHandler(request) + } + } + + open func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) { + eventMonitor?.urlSession(session, task: task, didFinishCollecting: metrics) + + stateProvider?.request(for: task)?.didGatherMetrics(metrics) + + stateProvider?.didGatherMetricsForTask(task) + } + + open func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { + eventMonitor?.urlSession(session, task: task, didCompleteWithError: error) + + let request = stateProvider?.request(for: task) + + stateProvider?.didCompleteTask(task) { + request?.didCompleteTask(task, with: error.map { $0.asAFError(or: .sessionTaskFailed(error: $0)) }) + } + } + + @available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4.0, *) + open func urlSession(_ session: URLSession, taskIsWaitingForConnectivity task: URLSessionTask) { + eventMonitor?.urlSession(session, taskIsWaitingForConnectivity: task) + } +} + +// MARK: URLSessionDataDelegate + +extension SessionDelegate: URLSessionDataDelegate { + open func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { + eventMonitor?.urlSession(session, dataTask: dataTask, didReceive: data) + + if let request = request(for: dataTask, as: DataRequest.self) { + request.didReceive(data: data) + } else if let request = request(for: dataTask, as: DataStreamRequest.self) { + request.didReceive(data: data) + } else { + assertionFailure("dataTask did not find DataRequest or DataStreamRequest in didReceive") + return + } + } + + open func urlSession(_ session: URLSession, + dataTask: URLSessionDataTask, + willCacheResponse proposedResponse: CachedURLResponse, + completionHandler: @escaping (CachedURLResponse?) -> Void) { + eventMonitor?.urlSession(session, dataTask: dataTask, willCacheResponse: proposedResponse) + + if let handler = stateProvider?.request(for: dataTask)?.cachedResponseHandler ?? stateProvider?.cachedResponseHandler { + handler.dataTask(dataTask, willCacheResponse: proposedResponse, completion: completionHandler) + } else { + completionHandler(proposedResponse) + } + } +} + +// MARK: URLSessionDownloadDelegate + +extension SessionDelegate: URLSessionDownloadDelegate { + open func urlSession(_ session: URLSession, + downloadTask: URLSessionDownloadTask, + didResumeAtOffset fileOffset: Int64, + expectedTotalBytes: Int64) { + eventMonitor?.urlSession(session, + downloadTask: downloadTask, + didResumeAtOffset: fileOffset, + expectedTotalBytes: expectedTotalBytes) + guard let downloadRequest = request(for: downloadTask, as: DownloadRequest.self) else { + assertionFailure("downloadTask did not find DownloadRequest.") + return + } + + downloadRequest.updateDownloadProgress(bytesWritten: fileOffset, + totalBytesExpectedToWrite: expectedTotalBytes) + } + + open func urlSession(_ session: URLSession, + downloadTask: URLSessionDownloadTask, + didWriteData bytesWritten: Int64, + totalBytesWritten: Int64, + totalBytesExpectedToWrite: Int64) { + eventMonitor?.urlSession(session, + downloadTask: downloadTask, + didWriteData: bytesWritten, + totalBytesWritten: totalBytesWritten, + totalBytesExpectedToWrite: totalBytesExpectedToWrite) + guard let downloadRequest = request(for: downloadTask, as: DownloadRequest.self) else { + assertionFailure("downloadTask did not find DownloadRequest.") + return + } + + downloadRequest.updateDownloadProgress(bytesWritten: bytesWritten, + totalBytesExpectedToWrite: totalBytesExpectedToWrite) + } + + open func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) { + eventMonitor?.urlSession(session, downloadTask: downloadTask, didFinishDownloadingTo: location) + + guard let request = request(for: downloadTask, as: DownloadRequest.self) else { + assertionFailure("downloadTask did not find DownloadRequest.") + return + } + + guard let response = request.response else { + fatalError("URLSessionDownloadTask finished downloading with no response.") + } + + let (destination, options) = (request.destination)(location, response) + + eventMonitor?.request(request, didCreateDestinationURL: destination) + + do { + if options.contains(.removePreviousFile), fileManager.fileExists(atPath: destination.path) { + try fileManager.removeItem(at: destination) + } + + if options.contains(.createIntermediateDirectories) { + let directory = destination.deletingLastPathComponent() + try fileManager.createDirectory(at: directory, withIntermediateDirectories: true) + } + + try fileManager.moveItem(at: location, to: destination) + + request.didFinishDownloading(using: downloadTask, with: .success(destination)) + } catch { + request.didFinishDownloading(using: downloadTask, with: .failure(.downloadedFileMoveFailed(error: error, + source: location, + destination: destination))) + } + } +} diff --git b/Pods/Alamofire/Source/StringEncoding+Alamofire.swift a/Pods/Alamofire/Source/StringEncoding+Alamofire.swift new file mode 100644 index 0000000..8fa6133 --- /dev/null +++ a/Pods/Alamofire/Source/StringEncoding+Alamofire.swift @@ -0,0 +1,55 @@ +// +// StringEncoding+Alamofire.swift +// +// Copyright (c) 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. +// + +import Foundation + +extension String.Encoding { + /// Creates an encoding from the IANA charset name. + /// + /// - Notes: These mappings match those [provided by CoreFoundation](https://opensource.apple.com/source/CF/CF-476.18/CFStringUtilities.c.auto.html) + /// + /// - Parameter name: IANA charset name. + init?(ianaCharsetName name: String) { + switch name.lowercased() { + case "utf-8": + self = .utf8 + case "iso-8859-1": + self = .isoLatin1 + case "unicode-1-1", "iso-10646-ucs-2", "utf-16": + self = .utf16 + case "utf-16be": + self = .utf16BigEndian + case "utf-16le": + self = .utf16LittleEndian + case "utf-32": + self = .utf32 + case "utf-32be": + self = .utf32BigEndian + case "utf-32le": + self = .utf32LittleEndian + default: + return nil + } + } +} diff --git b/Pods/Alamofire/Source/URLConvertible+URLRequestConvertible.swift a/Pods/Alamofire/Source/URLConvertible+URLRequestConvertible.swift new file mode 100644 index 0000000..455c4bc --- /dev/null +++ a/Pods/Alamofire/Source/URLConvertible+URLRequestConvertible.swift @@ -0,0 +1,105 @@ +// +// 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 { 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? { try? asURLRequest() } +} + +extension URLRequest: URLRequestConvertible { + /// Returns `self`. + public func asURLRequest() throws -> URLRequest { 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 + } +} diff --git b/Pods/Alamofire/Source/URLEncodedFormEncoder.swift a/Pods/Alamofire/Source/URLEncodedFormEncoder.swift new file mode 100644 index 0000000..61cfce5 --- /dev/null +++ a/Pods/Alamofire/Source/URLEncodedFormEncoder.swift @@ -0,0 +1,973 @@ +// +// URLEncodedFormEncoder.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 + +/// An object that encodes instances into URL-encoded query strings. +/// +/// There is no published specification for how to encode collection types. By default, the convention of appending +/// `[]` to the key for array values (`foo[]=1&foo[]=2`), and appending the key surrounded by square brackets for +/// nested dictionary values (`foo[bar]=baz`) is used. Optionally, `ArrayEncoding` can be used to omit the +/// square brackets appended to array keys. +/// +/// `BoolEncoding` can be used to configure how `Bool` values are encoded. The default behavior is to encode +/// `true` as 1 and `false` as 0. +/// +/// `DateEncoding` can be used to configure how `Date` values are encoded. By default, the `.deferredToDate` +/// strategy is used, which formats dates from their structure. +/// +/// `SpaceEncoding` can be used to configure how spaces are encoded. Modern encodings use percent replacement (`%20`), +/// while older encodings may expect spaces to be replaced with `+`. +/// +/// This type is largely based on Vapor's [`url-encoded-form`](https://github.com/vapor/url-encoded-form) project. +public final class URLEncodedFormEncoder { + /// Encoding to use for `Array` values. + public enum ArrayEncoding { + /// An empty set of square brackets ("[]") are appended to the key for every value. This is the default encoding. + case brackets + /// No brackets are appended to the key and the key is encoded as is. + case noBrackets + + /// Encodes the key according to the encoding. + /// + /// - Parameter key: The `key` to encode. + /// - Returns: The encoded key. + func encode(_ key: String) -> String { + switch self { + case .brackets: return "\(key)[]" + case .noBrackets: return key + } + } + } + + /// Encoding to use for `Bool` values. + public enum BoolEncoding { + /// Encodes `true` as `1`, `false` as `0`. + case numeric + /// Encodes `true` as "true", `false` as "false". This is the default encoding. + case literal + + /// Encodes the given `Bool` as a `String`. + /// + /// - Parameter value: The `Bool` to encode. + /// + /// - Returns: The encoded `String`. + func encode(_ value: Bool) -> String { + switch self { + case .numeric: return value ? "1" : "0" + case .literal: return value ? "true" : "false" + } + } + } + + /// Encoding to use for `Data` values. + public enum DataEncoding { + /// Defers encoding to the `Data` type. + case deferredToData + /// Encodes `Data` as a Base64-encoded string. This is the default encoding. + case base64 + /// Encode the `Data` as a custom value encoded by the given closure. + case custom((Data) throws -> String) + + /// Encodes `Data` according to the encoding. + /// + /// - Parameter data: The `Data` to encode. + /// + /// - Returns: The encoded `String`, or `nil` if the `Data` should be encoded according to its + /// `Encodable` implementation. + func encode(_ data: Data) throws -> String? { + switch self { + case .deferredToData: return nil + case .base64: return data.base64EncodedString() + case let .custom(encoding): return try encoding(data) + } + } + } + + /// Encoding to use for `Date` values. + public enum DateEncoding { + /// ISO8601 and RFC3339 formatter. + private static let iso8601Formatter: ISO8601DateFormatter = { + let formatter = ISO8601DateFormatter() + formatter.formatOptions = .withInternetDateTime + return formatter + }() + + /// Defers encoding to the `Date` type. This is the default encoding. + case deferredToDate + /// Encodes `Date`s as seconds since midnight UTC on January 1, 1970. + case secondsSince1970 + /// Encodes `Date`s as milliseconds since midnight UTC on January 1, 1970. + case millisecondsSince1970 + /// Encodes `Date`s according to the ISO8601 and RFC3339 standards. + case iso8601 + /// Encodes `Date`s using the given `DateFormatter`. + case formatted(DateFormatter) + /// Encodes `Date`s using the given closure. + case custom((Date) throws -> String) + + /// Encodes the date according to the encoding. + /// + /// - Parameter date: The `Date` to encode. + /// + /// - Returns: The encoded `String`, or `nil` if the `Date` should be encoded according to its + /// `Encodable` implementation. + func encode(_ date: Date) throws -> String? { + switch self { + case .deferredToDate: + return nil + case .secondsSince1970: + return String(date.timeIntervalSince1970) + case .millisecondsSince1970: + return String(date.timeIntervalSince1970 * 1000.0) + case .iso8601: + return DateEncoding.iso8601Formatter.string(from: date) + case let .formatted(formatter): + return formatter.string(from: date) + case let .custom(closure): + return try closure(date) + } + } + } + + /// Encoding to use for keys. + /// + /// This type is derived from [`JSONEncoder`'s `KeyEncodingStrategy`](https://github.com/apple/swift/blob/6aa313b8dd5f05135f7f878eccc1db6f9fbe34ff/stdlib/public/Darwin/Foundation/JSONEncoder.swift#L128) + /// and [`XMLEncoder`s `KeyEncodingStrategy`](https://github.com/MaxDesiatov/XMLCoder/blob/master/Sources/XMLCoder/Encoder/XMLEncoder.swift#L102). + public enum KeyEncoding { + /// Use the keys specified by each type. This is the default encoding. + case useDefaultKeys + /// Convert from "camelCaseKeys" to "snake_case_keys" before writing a key. + /// + /// Capital characters are determined by testing membership in + /// `CharacterSet.uppercaseLetters` and `CharacterSet.lowercaseLetters` + /// (Unicode General Categories Lu and Lt). + /// The conversion to lower case uses `Locale.system`, also known as + /// the ICU "root" locale. This means the result is consistent + /// regardless of the current user's locale and language preferences. + /// + /// Converting from camel case to snake case: + /// 1. Splits words at the boundary of lower-case to upper-case + /// 2. Inserts `_` between words + /// 3. Lowercases the entire string + /// 4. Preserves starting and ending `_`. + /// + /// For example, `oneTwoThree` becomes `one_two_three`. `_oneTwoThree_` becomes `_one_two_three_`. + /// + /// - Note: Using a key encoding strategy has a nominal performance cost, as each string key has to be converted. + case convertToSnakeCase + /// Same as convertToSnakeCase, but using `-` instead of `_`. + /// For example `oneTwoThree` becomes `one-two-three`. + case convertToKebabCase + /// Capitalize the first letter only. + /// For example `oneTwoThree` becomes `OneTwoThree`. + case capitalized + /// Uppercase all letters. + /// For example `oneTwoThree` becomes `ONETWOTHREE`. + case uppercased + /// Lowercase all letters. + /// For example `oneTwoThree` becomes `onetwothree`. + case lowercased + /// A custom encoding using the provided closure. + case custom((String) -> String) + + func encode(_ key: String) -> String { + switch self { + case .useDefaultKeys: return key + case .convertToSnakeCase: return convertToSnakeCase(key) + case .convertToKebabCase: return convertToKebabCase(key) + case .capitalized: return String(key.prefix(1).uppercased() + key.dropFirst()) + case .uppercased: return key.uppercased() + case .lowercased: return key.lowercased() + case let .custom(encoding): return encoding(key) + } + } + + private func convertToSnakeCase(_ key: String) -> String { + convert(key, usingSeparator: "_") + } + + private func convertToKebabCase(_ key: String) -> String { + convert(key, usingSeparator: "-") + } + + private func convert(_ key: String, usingSeparator separator: String) -> String { + guard !key.isEmpty else { return key } + + var words: [Range] = [] + // The general idea of this algorithm is to split words on + // transition from lower to upper case, then on transition of >1 + // upper case characters to lowercase + // + // myProperty -> my_property + // myURLProperty -> my_url_property + // + // It is assumed, per Swift naming conventions, that the first character of the key is lowercase. + var wordStart = key.startIndex + var searchRange = key.index(after: wordStart)..1 capital letters. Turn those into a word, stopping at the capital before the lower case character. + let beforeLowerIndex = key.index(before: lowerCaseRange.lowerBound) + words.append(upperCaseRange.lowerBound.. String { + switch self { + case .percentEscaped: return string.replacingOccurrences(of: " ", with: "%20") + case .plusReplaced: return string.replacingOccurrences(of: " ", with: "+") + } + } + } + + /// `URLEncodedFormEncoder` error. + public enum Error: Swift.Error { + /// An invalid root object was created by the encoder. Only keyed values are valid. + case invalidRootObject(String) + + var localizedDescription: String { + switch self { + case let .invalidRootObject(object): + return "URLEncodedFormEncoder requires keyed root object. Received \(object) instead." + } + } + } + + /// Whether or not to sort the encoded key value pairs. + /// + /// - Note: This setting ensures a consistent ordering for all encodings of the same parameters. When set to `false`, + /// encoded `Dictionary` values may have a different encoded order each time they're encoded due to + /// ` Dictionary`'s random storage order, but `Encodable` types will maintain their encoded order. + public let alphabetizeKeyValuePairs: Bool + /// The `ArrayEncoding` to use. + public let arrayEncoding: ArrayEncoding + /// The `BoolEncoding` to use. + public let boolEncoding: BoolEncoding + /// THe `DataEncoding` to use. + public let dataEncoding: DataEncoding + /// The `DateEncoding` to use. + public let dateEncoding: DateEncoding + /// The `KeyEncoding` to use. + public let keyEncoding: KeyEncoding + /// The `SpaceEncoding` to use. + public let spaceEncoding: SpaceEncoding + /// The `CharacterSet` of allowed (non-escaped) characters. + public var allowedCharacters: CharacterSet + + /// Creates an instance from the supplied parameters. + /// + /// - Parameters: + /// - alphabetizeKeyValuePairs: Whether or not to sort the encoded key value pairs. `true` by default. + /// - arrayEncoding: The `ArrayEncoding` to use. `.brackets` by default. + /// - boolEncoding: The `BoolEncoding` to use. `.numeric` by default. + /// - dataEncoding: The `DataEncoding` to use. `.base64` by default. + /// - dateEncoding: The `DateEncoding` to use. `.deferredToDate` by default. + /// - keyEncoding: The `KeyEncoding` to use. `.useDefaultKeys` by default. + /// - spaceEncoding: The `SpaceEncoding` to use. `.percentEscaped` by default. + /// - allowedCharacters: The `CharacterSet` of allowed (non-escaped) characters. `.afURLQueryAllowed` by + /// default. + public init(alphabetizeKeyValuePairs: Bool = true, + arrayEncoding: ArrayEncoding = .brackets, + boolEncoding: BoolEncoding = .numeric, + dataEncoding: DataEncoding = .base64, + dateEncoding: DateEncoding = .deferredToDate, + keyEncoding: KeyEncoding = .useDefaultKeys, + spaceEncoding: SpaceEncoding = .percentEscaped, + allowedCharacters: CharacterSet = .afURLQueryAllowed) { + self.alphabetizeKeyValuePairs = alphabetizeKeyValuePairs + self.arrayEncoding = arrayEncoding + self.boolEncoding = boolEncoding + self.dataEncoding = dataEncoding + self.dateEncoding = dateEncoding + self.keyEncoding = keyEncoding + self.spaceEncoding = spaceEncoding + self.allowedCharacters = allowedCharacters + } + + func encode(_ value: Encodable) throws -> URLEncodedFormComponent { + let context = URLEncodedFormContext(.object([])) + let encoder = _URLEncodedFormEncoder(context: context, + boolEncoding: boolEncoding, + dataEncoding: dataEncoding, + dateEncoding: dateEncoding) + try value.encode(to: encoder) + + return context.component + } + + /// Encodes the `value` as a URL form encoded `String`. + /// + /// - Parameter value: The `Encodable` value.` + /// + /// - Returns: The encoded `String`. + /// - Throws: An `Error` or `EncodingError` instance if encoding fails. + public func encode(_ value: Encodable) throws -> String { + let component: URLEncodedFormComponent = try encode(value) + + guard case let .object(object) = component else { + throw Error.invalidRootObject("\(component)") + } + + let serializer = URLEncodedFormSerializer(alphabetizeKeyValuePairs: alphabetizeKeyValuePairs, + arrayEncoding: arrayEncoding, + keyEncoding: keyEncoding, + spaceEncoding: spaceEncoding, + allowedCharacters: allowedCharacters) + let query = serializer.serialize(object) + + return query + } + + /// Encodes the value as `Data`. This is performed by first creating an encoded `String` and then returning the + /// `.utf8` data. + /// + /// - Parameter value: The `Encodable` value. + /// + /// - Returns: The encoded `Data`. + /// + /// - Throws: An `Error` or `EncodingError` instance if encoding fails. + public func encode(_ value: Encodable) throws -> Data { + let string: String = try encode(value) + + return Data(string.utf8) + } +} + +final class _URLEncodedFormEncoder { + var codingPath: [CodingKey] + // Returns an empty dictionary, as this encoder doesn't support userInfo. + var userInfo: [CodingUserInfoKey: Any] { [:] } + + let context: URLEncodedFormContext + + private let boolEncoding: URLEncodedFormEncoder.BoolEncoding + private let dataEncoding: URLEncodedFormEncoder.DataEncoding + private let dateEncoding: URLEncodedFormEncoder.DateEncoding + + init(context: URLEncodedFormContext, + codingPath: [CodingKey] = [], + boolEncoding: URLEncodedFormEncoder.BoolEncoding, + dataEncoding: URLEncodedFormEncoder.DataEncoding, + dateEncoding: URLEncodedFormEncoder.DateEncoding) { + self.context = context + self.codingPath = codingPath + self.boolEncoding = boolEncoding + self.dataEncoding = dataEncoding + self.dateEncoding = dateEncoding + } +} + +extension _URLEncodedFormEncoder: Encoder { + func container(keyedBy type: Key.Type) -> KeyedEncodingContainer where Key: CodingKey { + let container = _URLEncodedFormEncoder.KeyedContainer(context: context, + codingPath: codingPath, + boolEncoding: boolEncoding, + dataEncoding: dataEncoding, + dateEncoding: dateEncoding) + return KeyedEncodingContainer(container) + } + + func unkeyedContainer() -> UnkeyedEncodingContainer { + _URLEncodedFormEncoder.UnkeyedContainer(context: context, + codingPath: codingPath, + boolEncoding: boolEncoding, + dataEncoding: dataEncoding, + dateEncoding: dateEncoding) + } + + func singleValueContainer() -> SingleValueEncodingContainer { + _URLEncodedFormEncoder.SingleValueContainer(context: context, + codingPath: codingPath, + boolEncoding: boolEncoding, + dataEncoding: dataEncoding, + dateEncoding: dateEncoding) + } +} + +final class URLEncodedFormContext { + var component: URLEncodedFormComponent + + init(_ component: URLEncodedFormComponent) { + self.component = component + } +} + +enum URLEncodedFormComponent { + typealias Object = [(key: String, value: URLEncodedFormComponent)] + + case string(String) + case array([URLEncodedFormComponent]) + case object(Object) + + /// Converts self to an `[URLEncodedFormData]` or returns `nil` if not convertible. + var array: [URLEncodedFormComponent]? { + switch self { + case let .array(array): return array + default: return nil + } + } + + /// Converts self to an `Object` or returns `nil` if not convertible. + var object: Object? { + switch self { + case let .object(object): return object + default: return nil + } + } + + /// Sets self to the supplied value at a given path. + /// + /// data.set(to: "hello", at: ["path", "to", "value"]) + /// + /// - parameters: + /// - value: Value of `Self` to set at the supplied path. + /// - path: `CodingKey` path to update with the supplied value. + public mutating func set(to value: URLEncodedFormComponent, at path: [CodingKey]) { + set(&self, to: value, at: path) + } + + /// Recursive backing method to `set(to:at:)`. + private func set(_ context: inout URLEncodedFormComponent, to value: URLEncodedFormComponent, at path: [CodingKey]) { + guard path.count >= 1 else { + context = value + return + } + + let end = path[0] + var child: URLEncodedFormComponent + switch path.count { + case 1: + child = value + case 2...: + if let index = end.intValue { + let array = context.array ?? [] + if array.count > index { + child = array[index] + } else { + child = .array([]) + } + set(&child, to: value, at: Array(path[1...])) + } else { + child = context.object?.first { $0.key == end.stringValue }?.value ?? .object(.init()) + set(&child, to: value, at: Array(path[1...])) + } + default: fatalError("Unreachable") + } + + if let index = end.intValue { + if var array = context.array { + if array.count > index { + array[index] = child + } else { + array.append(child) + } + context = .array(array) + } else { + context = .array([child]) + } + } else { + if var object = context.object { + if let index = object.firstIndex(where: { $0.key == end.stringValue }) { + object[index] = (key: end.stringValue, value: child) + } else { + object.append((key: end.stringValue, value: child)) + } + context = .object(object) + } else { + context = .object([(key: end.stringValue, value: child)]) + } + } + } +} + +struct AnyCodingKey: CodingKey, Hashable { + let stringValue: String + let intValue: Int? + + init?(stringValue: String) { + self.stringValue = stringValue + intValue = nil + } + + init?(intValue: Int) { + stringValue = "\(intValue)" + self.intValue = intValue + } + + init(_ base: Key) where Key: CodingKey { + if let intValue = base.intValue { + self.init(intValue: intValue)! + } else { + self.init(stringValue: base.stringValue)! + } + } +} + +extension _URLEncodedFormEncoder { + final class KeyedContainer where Key: CodingKey { + var codingPath: [CodingKey] + + private let context: URLEncodedFormContext + private let boolEncoding: URLEncodedFormEncoder.BoolEncoding + private let dataEncoding: URLEncodedFormEncoder.DataEncoding + private let dateEncoding: URLEncodedFormEncoder.DateEncoding + + init(context: URLEncodedFormContext, + codingPath: [CodingKey], + boolEncoding: URLEncodedFormEncoder.BoolEncoding, + dataEncoding: URLEncodedFormEncoder.DataEncoding, + dateEncoding: URLEncodedFormEncoder.DateEncoding) { + self.context = context + self.codingPath = codingPath + self.boolEncoding = boolEncoding + self.dataEncoding = dataEncoding + self.dateEncoding = dateEncoding + } + + private func nestedCodingPath(for key: CodingKey) -> [CodingKey] { + codingPath + [key] + } + } +} + +extension _URLEncodedFormEncoder.KeyedContainer: KeyedEncodingContainerProtocol { + func encodeNil(forKey key: Key) throws { + let context = EncodingError.Context(codingPath: codingPath, + debugDescription: "URLEncodedFormEncoder cannot encode nil values.") + throw EncodingError.invalidValue("\(key): nil", context) + } + + func encode(_ value: T, forKey key: Key) throws where T: Encodable { + var container = nestedSingleValueEncoder(for: key) + try container.encode(value) + } + + func nestedSingleValueEncoder(for key: Key) -> SingleValueEncodingContainer { + let container = _URLEncodedFormEncoder.SingleValueContainer(context: context, + codingPath: nestedCodingPath(for: key), + boolEncoding: boolEncoding, + dataEncoding: dataEncoding, + dateEncoding: dateEncoding) + + return container + } + + func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer { + let container = _URLEncodedFormEncoder.UnkeyedContainer(context: context, + codingPath: nestedCodingPath(for: key), + boolEncoding: boolEncoding, + dataEncoding: dataEncoding, + dateEncoding: dateEncoding) + + return container + } + + func nestedContainer(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer where NestedKey: CodingKey { + let container = _URLEncodedFormEncoder.KeyedContainer(context: context, + codingPath: nestedCodingPath(for: key), + boolEncoding: boolEncoding, + dataEncoding: dataEncoding, + dateEncoding: dateEncoding) + + return KeyedEncodingContainer(container) + } + + func superEncoder() -> Encoder { + _URLEncodedFormEncoder(context: context, + codingPath: codingPath, + boolEncoding: boolEncoding, + dataEncoding: dataEncoding, + dateEncoding: dateEncoding) + } + + func superEncoder(forKey key: Key) -> Encoder { + _URLEncodedFormEncoder(context: context, + codingPath: nestedCodingPath(for: key), + boolEncoding: boolEncoding, + dataEncoding: dataEncoding, + dateEncoding: dateEncoding) + } +} + +extension _URLEncodedFormEncoder { + final class SingleValueContainer { + var codingPath: [CodingKey] + + private var canEncodeNewValue = true + + private let context: URLEncodedFormContext + private let boolEncoding: URLEncodedFormEncoder.BoolEncoding + private let dataEncoding: URLEncodedFormEncoder.DataEncoding + private let dateEncoding: URLEncodedFormEncoder.DateEncoding + + init(context: URLEncodedFormContext, + codingPath: [CodingKey], + boolEncoding: URLEncodedFormEncoder.BoolEncoding, + dataEncoding: URLEncodedFormEncoder.DataEncoding, + dateEncoding: URLEncodedFormEncoder.DateEncoding) { + self.context = context + self.codingPath = codingPath + self.boolEncoding = boolEncoding + self.dataEncoding = dataEncoding + self.dateEncoding = dateEncoding + } + + private func checkCanEncode(value: Any?) throws { + guard canEncodeNewValue else { + let context = EncodingError.Context(codingPath: codingPath, + debugDescription: "Attempt to encode value through single value container when previously value already encoded.") + throw EncodingError.invalidValue(value as Any, context) + } + } + } +} + +extension _URLEncodedFormEncoder.SingleValueContainer: SingleValueEncodingContainer { + func encodeNil() throws { + try checkCanEncode(value: nil) + defer { canEncodeNewValue = false } + + let context = EncodingError.Context(codingPath: codingPath, + debugDescription: "URLEncodedFormEncoder cannot encode nil values.") + throw EncodingError.invalidValue("nil", context) + } + + func encode(_ value: Bool) throws { + try encode(value, as: String(boolEncoding.encode(value))) + } + + func encode(_ value: String) throws { + try encode(value, as: value) + } + + func encode(_ value: Double) throws { + try encode(value, as: String(value)) + } + + func encode(_ value: Float) throws { + try encode(value, as: String(value)) + } + + func encode(_ value: Int) throws { + try encode(value, as: String(value)) + } + + func encode(_ value: Int8) throws { + try encode(value, as: String(value)) + } + + func encode(_ value: Int16) throws { + try encode(value, as: String(value)) + } + + func encode(_ value: Int32) throws { + try encode(value, as: String(value)) + } + + func encode(_ value: Int64) throws { + try encode(value, as: String(value)) + } + + func encode(_ value: UInt) throws { + try encode(value, as: String(value)) + } + + func encode(_ value: UInt8) throws { + try encode(value, as: String(value)) + } + + func encode(_ value: UInt16) throws { + try encode(value, as: String(value)) + } + + func encode(_ value: UInt32) throws { + try encode(value, as: String(value)) + } + + func encode(_ value: UInt64) throws { + try encode(value, as: String(value)) + } + + private func encode(_ value: T, as string: String) throws where T: Encodable { + try checkCanEncode(value: value) + defer { canEncodeNewValue = false } + + context.component.set(to: .string(string), at: codingPath) + } + + func encode(_ value: T) throws where T: Encodable { + switch value { + case let date as Date: + guard let string = try dateEncoding.encode(date) else { + try attemptToEncode(value) + return + } + + try encode(value, as: string) + case let data as Data: + guard let string = try dataEncoding.encode(data) else { + try attemptToEncode(value) + return + } + + try encode(value, as: string) + default: + try attemptToEncode(value) + } + } + + private func attemptToEncode(_ value: T) throws where T: Encodable { + try checkCanEncode(value: value) + defer { canEncodeNewValue = false } + + let encoder = _URLEncodedFormEncoder(context: context, + codingPath: codingPath, + boolEncoding: boolEncoding, + dataEncoding: dataEncoding, + dateEncoding: dateEncoding) + try value.encode(to: encoder) + } +} + +extension _URLEncodedFormEncoder { + final class UnkeyedContainer { + var codingPath: [CodingKey] + + var count = 0 + var nestedCodingPath: [CodingKey] { + codingPath + [AnyCodingKey(intValue: count)!] + } + + private let context: URLEncodedFormContext + private let boolEncoding: URLEncodedFormEncoder.BoolEncoding + private let dataEncoding: URLEncodedFormEncoder.DataEncoding + private let dateEncoding: URLEncodedFormEncoder.DateEncoding + + init(context: URLEncodedFormContext, + codingPath: [CodingKey], + boolEncoding: URLEncodedFormEncoder.BoolEncoding, + dataEncoding: URLEncodedFormEncoder.DataEncoding, + dateEncoding: URLEncodedFormEncoder.DateEncoding) { + self.context = context + self.codingPath = codingPath + self.boolEncoding = boolEncoding + self.dataEncoding = dataEncoding + self.dateEncoding = dateEncoding + } + } +} + +extension _URLEncodedFormEncoder.UnkeyedContainer: UnkeyedEncodingContainer { + func encodeNil() throws { + let context = EncodingError.Context(codingPath: codingPath, + debugDescription: "URLEncodedFormEncoder cannot encode nil values.") + throw EncodingError.invalidValue("nil", context) + } + + func encode(_ value: T) throws where T: Encodable { + var container = nestedSingleValueContainer() + try container.encode(value) + } + + func nestedSingleValueContainer() -> SingleValueEncodingContainer { + defer { count += 1 } + + return _URLEncodedFormEncoder.SingleValueContainer(context: context, + codingPath: nestedCodingPath, + boolEncoding: boolEncoding, + dataEncoding: dataEncoding, + dateEncoding: dateEncoding) + } + + func nestedContainer(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer where NestedKey: CodingKey { + defer { count += 1 } + let container = _URLEncodedFormEncoder.KeyedContainer(context: context, + codingPath: nestedCodingPath, + boolEncoding: boolEncoding, + dataEncoding: dataEncoding, + dateEncoding: dateEncoding) + + return KeyedEncodingContainer(container) + } + + func nestedUnkeyedContainer() -> UnkeyedEncodingContainer { + defer { count += 1 } + + return _URLEncodedFormEncoder.UnkeyedContainer(context: context, + codingPath: nestedCodingPath, + boolEncoding: boolEncoding, + dataEncoding: dataEncoding, + dateEncoding: dateEncoding) + } + + func superEncoder() -> Encoder { + defer { count += 1 } + + return _URLEncodedFormEncoder(context: context, + codingPath: codingPath, + boolEncoding: boolEncoding, + dataEncoding: dataEncoding, + dateEncoding: dateEncoding) + } +} + +final class URLEncodedFormSerializer { + private let alphabetizeKeyValuePairs: Bool + private let arrayEncoding: URLEncodedFormEncoder.ArrayEncoding + private let keyEncoding: URLEncodedFormEncoder.KeyEncoding + private let spaceEncoding: URLEncodedFormEncoder.SpaceEncoding + private let allowedCharacters: CharacterSet + + init(alphabetizeKeyValuePairs: Bool, + arrayEncoding: URLEncodedFormEncoder.ArrayEncoding, + keyEncoding: URLEncodedFormEncoder.KeyEncoding, + spaceEncoding: URLEncodedFormEncoder.SpaceEncoding, + allowedCharacters: CharacterSet) { + self.alphabetizeKeyValuePairs = alphabetizeKeyValuePairs + self.arrayEncoding = arrayEncoding + self.keyEncoding = keyEncoding + self.spaceEncoding = spaceEncoding + self.allowedCharacters = allowedCharacters + } + + func serialize(_ object: URLEncodedFormComponent.Object) -> String { + var output: [String] = [] + for (key, component) in object { + let value = serialize(component, forKey: key) + output.append(value) + } + output = alphabetizeKeyValuePairs ? output.sorted() : output + + return output.joinedWithAmpersands() + } + + func serialize(_ component: URLEncodedFormComponent, forKey key: String) -> String { + switch component { + case let .string(string): return "\(escape(keyEncoding.encode(key)))=\(escape(string))" + case let .array(array): return serialize(array, forKey: key) + case let .object(object): return serialize(object, forKey: key) + } + } + + func serialize(_ object: URLEncodedFormComponent.Object, forKey key: String) -> String { + var segments: [String] = object.map { subKey, value in + let keyPath = "[\(subKey)]" + return serialize(value, forKey: key + keyPath) + } + segments = alphabetizeKeyValuePairs ? segments.sorted() : segments + + return segments.joinedWithAmpersands() + } + + func serialize(_ array: [URLEncodedFormComponent], forKey key: String) -> String { + var segments: [String] = array.map { component in + let keyPath = arrayEncoding.encode(key) + return serialize(component, forKey: keyPath) + } + segments = alphabetizeKeyValuePairs ? segments.sorted() : segments + + return segments.joinedWithAmpersands() + } + + func escape(_ query: String) -> String { + var allowedCharactersWithSpace = allowedCharacters + allowedCharactersWithSpace.insert(charactersIn: " ") + let escapedQuery = query.addingPercentEncoding(withAllowedCharacters: allowedCharactersWithSpace) ?? query + let spaceEncodedQuery = spaceEncoding.encode(escapedQuery) + + return spaceEncodedQuery + } +} + +extension Array where Element == String { + func joinedWithAmpersands() -> String { + joined(separator: "&") + } +} + +public extension CharacterSet { + /// Creates a CharacterSet from RFC 3986 allowed characters. + /// + /// RFC 3986 states that the following characters are "reserved" characters. + /// + /// - General Delimiters: ":", "#", "[", "]", "@", "?", "/" + /// - Sub-Delimiters: "!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "=" + /// + /// In RFC 3986 - Section 3.4, it states that the "?" and "/" characters should not be escaped to allow + /// query strings to include a URL. Therefore, all "reserved" characters with the exception of "?" and "/" + /// should be percent-escaped in the query string. + static let afURLQueryAllowed: CharacterSet = { + let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.4 + let subDelimitersToEncode = "!$&'()*+,;=" + let encodableDelimiters = CharacterSet(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)") + + return CharacterSet.urlQueryAllowed.subtracting(encodableDelimiters) + }() +} diff --git b/Pods/Alamofire/Source/URLRequest+Alamofire.swift a/Pods/Alamofire/Source/URLRequest+Alamofire.swift new file mode 100644 index 0000000..e5e6f61 --- /dev/null +++ a/Pods/Alamofire/Source/URLRequest+Alamofire.swift @@ -0,0 +1,39 @@ +// +// 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 { httpMethod.flatMap(HTTPMethod.init) } + set { httpMethod = newValue?.rawValue } + } + + func validate() throws { + if method == .get, let bodyData = httpBody { + throw AFError.urlRequestValidationFailed(reason: .bodyDataInGETRequest(bodyData)) + } + } +} diff --git b/Pods/Alamofire/Source/URLSessionConfiguration+Alamofire.swift a/Pods/Alamofire/Source/URLSessionConfiguration+Alamofire.swift new file mode 100644 index 0000000..de3e290 --- /dev/null +++ a/Pods/Alamofire/Source/URLSessionConfiguration+Alamofire.swift @@ -0,0 +1,37 @@ +// +// 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 + } +} diff --git b/Pods/Alamofire/Source/Validation.swift a/Pods/Alamofire/Source/Validation.swift new file mode 100644 index 0000000..1e307bf --- /dev/null +++ a/Pods/Alamofire/Source/Validation.swift @@ -0,0 +1,299 @@ +// +// 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 + + fileprivate struct MIMEType { + let type: String + let subtype: String + + var isWildcard: Bool { 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 { 200..<300 } + + fileprivate var acceptableContentTypes: [String] { + if let accept = request?.value(forHTTPHeaderField: "Accept") { + return accept.components(separatedBy: ",") + } + + return ["*/*"] + } + + // MARK: Status Code + + fileprivate func validate(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(contentType acceptableContentTypes: S, + response: HTTPURLResponse, + data: Data?) + -> ValidationResult + where S.Iterator.Element == String { + guard let data = data, !data.isEmpty else { return .success(Void()) } + + return validate(contentType: acceptableContentTypes, response: response) + } + + fileprivate func validate(contentType acceptableContentTypes: S, + response: HTTPURLResponse) + -> ValidationResult + where S.Iterator.Element == String { + 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 statusCode: `Sequence` of acceptable response status codes. + /// + /// - Returns: The instance. + @discardableResult + public func validate(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(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()) + } +} + +extension DataStreamRequest { + /// A closure used to validate a request that takes a `URLRequest` and `HTTPURLResponse` and returns whether the + /// request was valid. + public typealias Validation = (_ request: URLRequest?, _ response: HTTPURLResponse) -> 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 statusCode: `Sequence` of acceptable response status codes. + /// + /// - Returns: The instance. + @discardableResult + public func validate(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(contentType acceptableContentTypes: @escaping @autoclosure () -> S) -> Self where S.Iterator.Element == String { + return validate { [unowned self] _, response in + self.validate(contentType: acceptableContentTypes(), response: response) + } + } + + /// 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 instance. + @discardableResult + public func validate() -> Self { + validate(statusCode: acceptableStatusCodes).validate(contentType: self.acceptableContentTypes) + } +} + +// 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 statusCode: `Sequence` of acceptable response status codes. + /// + /// - Returns: The instance. + @discardableResult + public func validate(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(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()) + } +} diff --git b/Pods/GoogleMaps/Base/Frameworks/GoogleMapsBase.framework/GoogleMapsBase a/Pods/GoogleMaps/Base/Frameworks/GoogleMapsBase.framework/GoogleMapsBase new file mode 100755 index 0000000..a34d76a Binary files /dev/null and a/Pods/GoogleMaps/Base/Frameworks/GoogleMapsBase.framework/GoogleMapsBase differ diff --git b/Pods/GoogleMaps/Base/Frameworks/GoogleMapsBase.framework/Headers/GMSCompatabilityMacros.h a/Pods/GoogleMaps/Base/Frameworks/GoogleMapsBase.framework/Headers/GMSCompatabilityMacros.h new file mode 100755 index 0000000..ea51991 --- /dev/null +++ a/Pods/GoogleMaps/Base/Frameworks/GoogleMapsBase.framework/Headers/GMSCompatabilityMacros.h @@ -0,0 +1,17 @@ +// +// GMSCompatabilityMacros.h +// Google Maps SDK for iOS +// +// Copyright 2015 Google LLC +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import + +#if defined(SWIFT_SDK_OVERLAY_UIKIT_EPOCH) +#define GMS_SWIFT_NAME_2_0_3_0(name_swift_2, name_swift_3) NS_SWIFT_NAME(name_swift_3) +#else +#define GMS_SWIFT_NAME_2_0_3_0(name_swift_2, name_swift_3) NS_SWIFT_NAME(name_swift_2) +#endif diff --git b/Pods/GoogleMaps/Base/Frameworks/GoogleMapsBase.framework/Headers/GMSCoordinateBounds.h a/Pods/GoogleMaps/Base/Frameworks/GoogleMapsBase.framework/Headers/GMSCoordinateBounds.h new file mode 100755 index 0000000..0f6c2cd --- /dev/null +++ a/Pods/GoogleMaps/Base/Frameworks/GoogleMapsBase.framework/Headers/GMSCoordinateBounds.h @@ -0,0 +1,75 @@ +// +// GMSCoordinateBounds.h +// Google Maps SDK for iOS +// +// Copyright 2013 Google LLC +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * GMSCoordinateBounds represents a rectangular bounding box on the Earth's surface. + * GMSCoordinateBounds is immutable and can't be modified after construction. + */ +@interface GMSCoordinateBounds : NSObject + +/** The North-East corner of these bounds. */ +@property(nonatomic, readonly) CLLocationCoordinate2D northEast; + +/** The South-West corner of these bounds. */ +@property(nonatomic, readonly) CLLocationCoordinate2D southWest; + +/** + * Returns NO if this bounds does not contain any points. For example, + * [[GMSCoordinateBounds alloc] init].valid == NO. + * + * When an invalid bounds is expanded with valid coordinates via includingCoordinate: or + * includingBounds:, the resulting bounds will be valid but contain only the new coordinates. + */ +@property(nonatomic, readonly, getter=isValid) BOOL valid; + +/** + * Inits the northEast and southWest bounds corresponding to the rectangular region defined by the + * two corners. + * + * It is ambiguous whether the longitude of the box extends from |coord1| to |coord2| or vice-versa; + * the box is constructed as the smaller of the two variants, eliminating the ambiguity. + */ +- (id)initWithCoordinate:(CLLocationCoordinate2D)coord1 coordinate:(CLLocationCoordinate2D)coord2; + +/** + * Returns a GMSCoordinateBounds representing the current bounds extended to include the passed-in + * coordinate. + * + * If the current bounds is invalid, the result is a valid bounds containing only |coordinate|. + */ +- (GMSCoordinateBounds *)includingCoordinate:(CLLocationCoordinate2D)coordinate; + +/** + * Returns a GMSCoordinateBounds representing the current bounds extended to include the entire + * other bounds. + * + * If the current bounds is invalid, the result is a valid bounds equal to |other|. + */ +- (GMSCoordinateBounds *)includingBounds:(GMSCoordinateBounds *)other; + +/** + * Returns YES if |coordinate| is contained within this bounds. This includes points that lie + * exactly on the edge of the bounds. + */ +- (BOOL)containsCoordinate:(CLLocationCoordinate2D)coordinate; + +/** + * Returns YES if |other| overlaps with this bounds. Two bounds are overlapping if there is at least + * one coordinate point contained by both. + */ +- (BOOL)intersectsBounds:(GMSCoordinateBounds *)other; + +@end + +NS_ASSUME_NONNULL_END diff --git b/Pods/GoogleMaps/Base/Frameworks/GoogleMapsBase.framework/Headers/GMSDeprecationMacros.h a/Pods/GoogleMaps/Base/Frameworks/GoogleMapsBase.framework/Headers/GMSDeprecationMacros.h new file mode 100755 index 0000000..07da74e --- /dev/null +++ a/Pods/GoogleMaps/Base/Frameworks/GoogleMapsBase.framework/Headers/GMSDeprecationMacros.h @@ -0,0 +1,22 @@ +// +// GMSDeprecationMacros.h +// Google Maps SDK for iOS +// +// Copyright 2015 Google LLC +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#ifndef IPHONE_MAPS_SDK_BASE_GMSDEPRECATIONMACROS_H_ +#define IPHONE_MAPS_SDK_BASE_GMSDEPRECATIONMACROS_H_ + +#ifndef __GMS_AVAILABLE_BUT_DEPRECATED +#define __GMS_AVAILABLE_BUT_DEPRECATED __deprecated +#endif + +#ifndef __GMS_AVAILABLE_BUT_DEPRECATED_MSG +#define __GMS_AVAILABLE_BUT_DEPRECATED_MSG(msg) __deprecated_msg(msg) +#endif + +#endif diff --git b/Pods/GoogleMaps/Base/Frameworks/GoogleMapsBase.framework/Headers/GoogleMapsBase.h a/Pods/GoogleMaps/Base/Frameworks/GoogleMapsBase.framework/Headers/GoogleMapsBase.h new file mode 100755 index 0000000..95ec44c --- /dev/null +++ a/Pods/GoogleMaps/Base/Frameworks/GoogleMapsBase.framework/Headers/GoogleMapsBase.h @@ -0,0 +1,3 @@ +#import "GMSCompatabilityMacros.h" +#import "GMSCoordinateBounds.h" +#import "GMSDeprecationMacros.h" diff --git b/Pods/GoogleMaps/Base/Frameworks/GoogleMapsBase.framework/Modules/module.modulemap a/Pods/GoogleMaps/Base/Frameworks/GoogleMapsBase.framework/Modules/module.modulemap new file mode 100755 index 0000000..ab89f82 --- /dev/null +++ a/Pods/GoogleMaps/Base/Frameworks/GoogleMapsBase.framework/Modules/module.modulemap @@ -0,0 +1,14 @@ +framework module GoogleMapsBase { + umbrella header "GoogleMapsBase.h" + export * + module * { export * } + link "z" + link framework "CoreFoundation" + link framework "CoreLocation" + link framework "CoreTelephony" + link framework "CoreText" + link framework "Foundation" + link framework "QuartzCore" + link framework "Security" + link framework "UIKit" +} diff --git b/Pods/GoogleMaps/CHANGELOG.md a/Pods/GoogleMaps/CHANGELOG.md new file mode 100755 index 0000000..45a3b9f --- /dev/null +++ a/Pods/GoogleMaps/CHANGELOG.md @@ -0,0 +1,2 @@ +Please go to https://developers.google.com/maps/documentation/ios-sdk/releases +to view the Maps iOS release notes. diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos.xcodeproj/project.pbxproj a/Pods/GoogleMaps/Example/GoogleMapsDemos.xcodeproj/project.pbxproj new file mode 100755 index 0000000..7867b54 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos.xcodeproj/project.pbxproj @@ -0,0 +1,648 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 01C8D1488E89B8A200C8582E /* australia-large@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = A1D521E4FF1F8F85E084ED60 /* australia-large@2x.png */; }; + 023D5C0EB12D723906E5318C /* mapstyle-silver.json in Resources */ = {isa = PBXBuildFile; fileRef = B4C89E62C05816B590F45D57 /* mapstyle-silver.json */; }; + 02EC898A205A1E81940406E5 /* step8@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D5E5AF68BBC86BA530755A20 /* step8@2x.png */; }; + 06A69C6EAF7D7E8FF2197EC3 /* arrow@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 66DE8D273E896D7FF5B86DC9 /* arrow@2x.png */; }; + 0B07C58295A7BE58E96F34A3 /* aeroplane@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3F716156C215C436D55F5696 /* aeroplane@2x.png */; }; + 0D9B023D6E709FAB165A29C9 /* MasterViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = EE1D504741871952487BB5BE /* MasterViewController.m */; }; + 0E2E093D159271A7FB585759 /* GroundOverlayViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 8038AEC0AF9CBD2EBB975F54 /* GroundOverlayViewController.m */; }; + 141E2BE7C525DC36CB1FE54F /* Samples.m in Sources */ = {isa = PBXBuildFile; fileRef = 115963CCE082409262D179F3 /* Samples.m */; }; + 1511EFFE20A68F00B873A930 /* GeocoderViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A77C64C5A8B8109E6AFC41F /* GeocoderViewController.m */; }; + 162AF3F1216374886C841719 /* MarkerInfoWindowViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 89DFB3350D98DA617A2ECE85 /* MarkerInfoWindowViewController.m */; }; + 16717F377D18DD00AA691F18 /* argentina-large.png in Resources */ = {isa = PBXBuildFile; fileRef = 4D8E4A10394122CD9C9CD157 /* argentina-large.png */; }; + 16B31F7A9AF98310188109DC /* popup_santa.png in Resources */ = {isa = PBXBuildFile; fileRef = EF01EFEF0FC77447C02FE86C /* popup_santa.png */; }; + 19BA16967B625E9962C0EDCC /* UIViewController+GMSToastMessages.m in Sources */ = {isa = PBXBuildFile; fileRef = 141DC0E7977DE53BB4C0ACE6 /* UIViewController+GMSToastMessages.m */; }; + 1E38E739DB90F691064AB0A9 /* FitBoundsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 721936633CF79A85B5EF0E92 /* FitBoundsViewController.m */; }; + 212AC00650E55AC8C5EE5C02 /* PanoramaViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = A9ECA1CA359738BB8B17657F /* PanoramaViewController.m */; }; + 22EB466DA6512BAA95E23367 /* MarkerEventsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B9450C49DDD49AF9182779EB /* MarkerEventsViewController.m */; }; + 2522616BC4EFE2F27AA3C31D /* step8.png in Resources */ = {isa = PBXBuildFile; fileRef = B7B684AAFC3CA2232721C6D0 /* step8.png */; }; + 2BB7EAB853CF12FF8B728EB0 /* step1@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C6A156792608F06437E5931 /* step1@2x.png */; }; + 2BB91ECAAD82A6BD9AA5A31C /* GestureControlViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 71CE23C2A26FD3C29E6AEEA7 /* GestureControlViewController.m */; }; + 2C6A65E1DD60EA63447C3263 /* spitfire.png in Resources */ = {isa = PBXBuildFile; fileRef = 91BCD28A8D5451665C5B3FDE /* spitfire.png */; }; + 2D5C0726D35B29E07C3C45F4 /* step6.png in Resources */ = {isa = PBXBuildFile; fileRef = F64D45825D2647898331A1FF /* step6.png */; }; + 30CE808B1E98D7A20A2AB60E /* aeroplane.png in Resources */ = {isa = PBXBuildFile; fileRef = 937B2FC602E80ADC11CF364F /* aeroplane.png */; }; + 30EA088C289C5AE5A564CB7B /* AnimatedCurrentLocationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C37F2216D5D94A3F8B698AF2 /* AnimatedCurrentLocationViewController.m */; }; + 37AA7110FDDEA5425209ADDA /* step5@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AB708C911CD9B99ADBB07D08 /* step5@2x.png */; }; + 37FEE000C69584FD7194FF6B /* PaddingBehaviorViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3679BC5053A9702AE4FC85B3 /* PaddingBehaviorViewController.m */; }; + 3863ECD2CA69536521AD365E /* VisibleRegionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = A0518A8C9E543FFBA7F8305E /* VisibleRegionViewController.m */; }; + 3AC5F611874DBEBD37627398 /* MyLocationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = BF0878372A9A7274B74BCBE0 /* MyLocationViewController.m */; }; + 3ADDFA115AB009524ED62A24 /* step6@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = C0E7585025B57F969D8C3CCD /* step6@2x.png */; }; + 43F1D3292C4928A90E1F2ECC /* voyager.png in Resources */ = {isa = PBXBuildFile; fileRef = 5B3B4C44092471CA56ACFD4C /* voyager.png */; }; + 465832A7B8B8E273B905F029 /* IndoorMuseumNavigationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 1318E0A2BA2A41F2E8759CFE /* IndoorMuseumNavigationViewController.m */; }; + 4921EFA284C51B00D03795C3 /* glow-marker@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 430314741C873551A75A6748 /* glow-marker@2x.png */; }; + 4A2C42ED6FE29D017AE4A947 /* MapZoomViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B3D673FA7036FA5D07CB287 /* MapZoomViewController.m */; }; + 4EEE7F0A05EA0200D46C3158 /* x29.png in Resources */ = {isa = PBXBuildFile; fileRef = 77E4A5AC4E17EB252D617792 /* x29.png */; }; + 4FA6BC3F01C6B9CEDA448E26 /* spitfire@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = B15768E1EE7E314DF1B0A395 /* spitfire@2x.png */; }; + 51E192046D546D886F450F79 /* FixedPanoramaViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C762E469D796AFF83D3516EF /* FixedPanoramaViewController.m */; }; + 52964EB41C2A98E1B3B67C8D /* track.json in Resources */ = {isa = PBXBuildFile; fileRef = AD6B1AF49A87CC079E9CA862 /* track.json */; }; + 52D6B181D0B7A7439CCDB3D1 /* IndoorViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 8DA013030BC14C994AEA2428 /* IndoorViewController.m */; }; + 53A6B686C7620ACB24603D89 /* CameraViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 09A36E26A0818D4F0DF21051 /* CameraViewController.m */; }; + 5746390C8A605B40841AE0F5 /* australia-large.png in Resources */ = {isa = PBXBuildFile; fileRef = 9F4CB893BCACB294FBCEC850 /* australia-large.png */; }; + 58522D14C5843238B3B684B7 /* mapstyle-retro.json in Resources */ = {isa = PBXBuildFile; fileRef = 118A0FE8CA1D110B3C694A8D /* mapstyle-retro.json */; }; + 5C91A963E5BF4ECDD5ADA417 /* h1@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 16C4B08486C9905568685983 /* h1@2x.png */; }; + 5E4A600A723EAD16564CE99A /* MarkerLayerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C3E84ED0331DFB2A7082CA36 /* MarkerLayerViewController.m */; }; + 67BB1551A52FE25B519698E7 /* PolylinesViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FA4145303F340FC3675AABA5 /* PolylinesViewController.m */; }; + 6C605BC471B85B1304719717 /* DoubleMapViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = F8DFCC9D1D1F70D492149D65 /* DoubleMapViewController.m */; }; + 6CCF16F2F014A3E746833201 /* MapTypesViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = F371BFD608DCF4DA1671DB03 /* MapTypesViewController.m */; }; + 6FD2C9C23995654D55280DCC /* MapLayerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3E7BBD64FA078FC4259FCBD2 /* MapLayerViewController.m */; }; + 70C609604B6E16310749BEE1 /* step3.png in Resources */ = {isa = PBXBuildFile; fileRef = 6533FA4161283F8F6A5F2CC3 /* step3.png */; }; + 7130108058B7212F6C7FD1FE /* CustomMarkersViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DC77B503E93E88F6E254A170 /* CustomMarkersViewController.m */; }; + 78251F0B2F404EDD815FF5C5 /* boat@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 03E00AE555F58259DD31C383 /* boat@2x.png */; }; + 7C990DA60F735AF48D770DC5 /* h1.png in Resources */ = {isa = PBXBuildFile; fileRef = 6B05E27C8BB92E89D2DC78AC /* h1.png */; }; + 7D840080EF0D790EA1F194A8 /* australia.png in Resources */ = {isa = PBXBuildFile; fileRef = 304A2B780A4A9B2D32261355 /* australia.png */; }; + 85D9288290263792177A1F62 /* MarkersViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D4CFDA7D44E2CF33EB10072B /* MarkersViewController.m */; }; + 870CD271BCF4C0A11F72DF98 /* TileLayerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CEA86A42848E5BAA7B523FB /* TileLayerViewController.m */; }; + 8BE208DD6AC74D59735122F6 /* newark_nj_1922.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 2B0884C720D5AE0BDCC1903D /* newark_nj_1922.jpg */; }; + 8CD768E0261BFDCCCA116EFC /* step1.png in Resources */ = {isa = PBXBuildFile; fileRef = D66EF243066A58A0D10280D4 /* step1.png */; }; + 8F5472876D59BE14593D0FD2 /* boat.png in Resources */ = {isa = PBXBuildFile; fileRef = 2B049CFC81E59949D0184B8B /* boat.png */; }; + 9201EE4D315D2663EE849ACB /* step4@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 60944672BB9D460BE315ECB8 /* step4@2x.png */; }; + 966D736517085B56D65BFF5A /* StructuredGeocoderViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AAFE928C9FDAA1B5703AE31 /* StructuredGeocoderViewController.m */; }; + 98DEEDA226C3C3AFD58A0ED9 /* step2@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 4F955F74654756E7F7DBD9C1 /* step2@2x.png */; }; + A0E8E3B87D9EEC0D785B1DC5 /* FrameRateViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DB963F858613E50A64CFAE6 /* FrameRateViewController.m */; }; + A182EF7F4411ABF7BF943036 /* StyledMapViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3408578DDDF6D8B183D9B91E /* StyledMapViewController.m */; }; + A335061AB0909CB860ED2D91 /* step2.png in Resources */ = {isa = PBXBuildFile; fileRef = BB85B60A794D982F001E5D1D /* step2.png */; }; + A64080332C06F6DD1B1D8FFD /* DemoAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = C7AD415580B4A6B35532456A /* DemoAppDelegate.m */; }; + A6E90219EA860C586F543A4A /* glow-marker.png in Resources */ = {isa = PBXBuildFile; fileRef = 57D0C4A29B66857C926387F0 /* glow-marker.png */; }; + A7177552BD808915E2D171C6 /* MapsDemoAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B53B4C2097A6AD4A98746495 /* MapsDemoAssets.xcassets */; }; + AB5636020377CA4EEDDD54E2 /* arrow.png in Resources */ = {isa = PBXBuildFile; fileRef = 6614BE28012C7A7508437047 /* arrow.png */; }; + B28BAAE9307E182E27F1971D /* step7.png in Resources */ = {isa = PBXBuildFile; fileRef = 3B0B37F0669CF34418A263F9 /* step7.png */; }; + B94EC3B15FAA49100E840A46 /* step7@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D0E3C8118FBF6DF1D4996693 /* step7@2x.png */; }; + B96B00239590850A49AC33AD /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6F4C119D42CF37317FA0DFA9 /* UIKit.framework */; }; + BCDF7FA5469D1D2344028550 /* SnapshotReadyViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 15E621C7788D69B720052601 /* SnapshotReadyViewController.m */; }; + BE9A04B467C3AEFF9F055139 /* argentina.png in Resources */ = {isa = PBXBuildFile; fileRef = A6B80BDCF78F436799462F76 /* argentina.png */; }; + BEF92FBE50408C13E57D677C /* step4.png in Resources */ = {isa = PBXBuildFile; fileRef = 99C6A64731FB5249BA53255D /* step4.png */; }; + C24E551B4F0F381D7F6E4A67 /* x29@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DD1DD5592D028FDD5E527FDF /* x29@2x.png */; }; + C29A6A1CD63D938B4A8CDCC0 /* botswana.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C186E84CB691DD6239C5E57 /* botswana.png */; }; + C9767A9B1C49EE0C94893D7F /* botswana-large.png in Resources */ = {isa = PBXBuildFile; fileRef = 440513A7769094565EA82DD2 /* botswana-large.png */; }; + CDAFCD46473B6ED74EF20AB8 /* bulgaria-large.png in Resources */ = {isa = PBXBuildFile; fileRef = DA6BF21C0287E5828A770C09 /* bulgaria-large.png */; }; + CEA937D53AEA30A22D2AF43A /* AnimatedUIViewMarkerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2EB950AA302B77F16CF18088 /* AnimatedUIViewMarkerViewController.m */; }; + D07832F6FE5AD1A2170EFFEC /* GradientPolylinesViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 1B4378F4DBE15BB0E32690CE /* GradientPolylinesViewController.m */; }; + DDA4B1B496975C801B592AFD /* TrafficMapViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 611E9F57BD5274878732C0BE /* TrafficMapViewController.m */; }; + E2EBC4612218EA7304B27935 /* voyager@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = C56541EDCEFDB922783385B5 /* voyager@2x.png */; }; + EA1C6FDB09359F6A2E98414F /* CustomIndoorViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5563ED82E4E77A76707DAF12 /* CustomIndoorViewController.m */; }; + EB1E6BF7EC8A504C23509548 /* BasicMapViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FC5AD93CD72DE8F5B004A621 /* BasicMapViewController.m */; }; + EFB6792DEB7FB555FFB13CB0 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 21267D205F7EC280F26D97ED /* main.m */; }; + F0DB8FF9FBF256B3CB85A8E0 /* mapstyle-night.json in Resources */ = {isa = PBXBuildFile; fileRef = FFEED4DDB04F69C84A1924F1 /* mapstyle-night.json */; }; + F5B169917A264494AF0CF434 /* step5.png in Resources */ = {isa = PBXBuildFile; fileRef = B1763EA929EC73C50D294134 /* step5.png */; }; + FCA2894A159A2C5995A921BC /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DBB8C9AD891A00824AE32343 /* LaunchScreen.storyboard */; }; + FD207FD6DF50468AD7A901D7 /* PolygonsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ED9C84A592EB6464725BB9C /* PolygonsViewController.m */; }; + FEEB324EBD6CD86B7E2F3D23 /* popup_santa@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 5E2E2B19EF6846F007A7A909 /* popup_santa@2x.png */; }; + FF9DA2C517D5682ADD75D0AF /* museum-exhibits.json in Resources */ = {isa = PBXBuildFile; fileRef = 346BE94E21267BA7E00E1B94 /* museum-exhibits.json */; }; + FFB50913C7C92104C44FC4E6 /* step3@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = A3AE3B168663EA43D3F2F259 /* step3@2x.png */; }; + FFBC92DCB78E89B548CBEFCB /* bulgaria.png in Resources */ = {isa = PBXBuildFile; fileRef = 2BC67BDB51522BC85EBFED8E /* bulgaria.png */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 02A7C56A409A0CB977458251 /* DemoAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DemoAppDelegate.h; sourceTree = ""; }; + 03E00AE555F58259DD31C383 /* boat@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "boat@2x.png"; sourceTree = ""; }; + 09A36E26A0818D4F0DF21051 /* CameraViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CameraViewController.m; sourceTree = ""; }; + 0C5E03510539F570D757CAEE /* GestureControlViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GestureControlViewController.h; sourceTree = ""; }; + 0CEA86A42848E5BAA7B523FB /* TileLayerViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TileLayerViewController.m; sourceTree = ""; }; + 115963CCE082409262D179F3 /* Samples.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Samples.m; sourceTree = ""; }; + 118A0FE8CA1D110B3C694A8D /* mapstyle-retro.json */ = {isa = PBXFileReference; lastKnownFileType = text; path = "mapstyle-retro.json"; sourceTree = ""; }; + 1318E0A2BA2A41F2E8759CFE /* IndoorMuseumNavigationViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = IndoorMuseumNavigationViewController.m; sourceTree = ""; }; + 141DC0E7977DE53BB4C0ACE6 /* UIViewController+GMSToastMessages.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIViewController+GMSToastMessages.m"; sourceTree = ""; }; + 15E621C7788D69B720052601 /* SnapshotReadyViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SnapshotReadyViewController.m; sourceTree = ""; }; + 15E6F2AA55437CFAC24BCFEE /* MarkerInfoWindowViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MarkerInfoWindowViewController.h; sourceTree = ""; }; + 16C4B08486C9905568685983 /* h1@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "h1@2x.png"; sourceTree = ""; }; + 1B4378F4DBE15BB0E32690CE /* GradientPolylinesViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GradientPolylinesViewController.m; sourceTree = ""; }; + 21267D205F7EC280F26D97ED /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 281FB700EEAC3323C52CB587 /* AnimatedCurrentLocationViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AnimatedCurrentLocationViewController.h; sourceTree = ""; }; + 2A77C64C5A8B8109E6AFC41F /* GeocoderViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GeocoderViewController.m; sourceTree = ""; }; + 2AAFE928C9FDAA1B5703AE31 /* StructuredGeocoderViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = StructuredGeocoderViewController.m; sourceTree = ""; }; + 2B049CFC81E59949D0184B8B /* boat.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = boat.png; sourceTree = ""; }; + 2B0884C720D5AE0BDCC1903D /* newark_nj_1922.jpg */ = {isa = PBXFileReference; lastKnownFileType = text; path = newark_nj_1922.jpg; sourceTree = ""; }; + 2BC67BDB51522BC85EBFED8E /* bulgaria.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = bulgaria.png; sourceTree = ""; }; + 2D030F81AEB6D3278B8EA303 /* VisibleRegionViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VisibleRegionViewController.h; sourceTree = ""; }; + 2EB950AA302B77F16CF18088 /* AnimatedUIViewMarkerViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AnimatedUIViewMarkerViewController.m; sourceTree = ""; }; + 2ED9C84A592EB6464725BB9C /* PolygonsViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PolygonsViewController.m; sourceTree = ""; }; + 304A2B780A4A9B2D32261355 /* australia.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = australia.png; sourceTree = ""; }; + 33BC04FF4445AA4649D16101 /* TileLayerViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TileLayerViewController.h; sourceTree = ""; }; + 3408578DDDF6D8B183D9B91E /* StyledMapViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = StyledMapViewController.m; sourceTree = ""; }; + 346BE94E21267BA7E00E1B94 /* museum-exhibits.json */ = {isa = PBXFileReference; lastKnownFileType = text; path = "museum-exhibits.json"; sourceTree = ""; }; + 3679BC5053A9702AE4FC85B3 /* PaddingBehaviorViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PaddingBehaviorViewController.m; sourceTree = ""; }; + 3B0B37F0669CF34418A263F9 /* step7.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = step7.png; sourceTree = ""; }; + 3B3D673FA7036FA5D07CB287 /* MapZoomViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MapZoomViewController.m; sourceTree = ""; }; + 3E7BBD64FA078FC4259FCBD2 /* MapLayerViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MapLayerViewController.m; sourceTree = ""; }; + 3F716156C215C436D55F5696 /* aeroplane@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "aeroplane@2x.png"; sourceTree = ""; }; + 430314741C873551A75A6748 /* glow-marker@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "glow-marker@2x.png"; sourceTree = ""; }; + 440513A7769094565EA82DD2 /* botswana-large.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "botswana-large.png"; sourceTree = ""; }; + 4B436F286FDDDE125BA44AEE /* MapTypesViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MapTypesViewController.h; sourceTree = ""; }; + 4D8E4A10394122CD9C9CD157 /* argentina-large.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "argentina-large.png"; sourceTree = ""; }; + 4F955F74654756E7F7DBD9C1 /* step2@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "step2@2x.png"; sourceTree = ""; }; + 51B3E8FEDF5AFB146C099C21 /* MapLayerViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MapLayerViewController.h; sourceTree = ""; }; + 5563ED82E4E77A76707DAF12 /* CustomIndoorViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CustomIndoorViewController.m; sourceTree = ""; }; + 56200E1874E08933C65BFE11 /* PaddingBehaviorViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PaddingBehaviorViewController.h; sourceTree = ""; }; + 57D0C4A29B66857C926387F0 /* glow-marker.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "glow-marker.png"; sourceTree = ""; }; + 5B3B4C44092471CA56ACFD4C /* voyager.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = voyager.png; sourceTree = ""; }; + 5C9035433ADBDA49B4FF973F /* StructuredGeocoderViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StructuredGeocoderViewController.h; sourceTree = ""; }; + 5E2E2B19EF6846F007A7A909 /* popup_santa@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "popup_santa@2x.png"; sourceTree = ""; }; + 5F34640A0EABB50A24705F03 /* MyLocationViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MyLocationViewController.h; sourceTree = ""; }; + 60944672BB9D460BE315ECB8 /* step4@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "step4@2x.png"; sourceTree = ""; }; + 611E9F57BD5274878732C0BE /* TrafficMapViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TrafficMapViewController.m; sourceTree = ""; }; + 6140C36D94B102F62C26B713 /* IndoorMuseumNavigationViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IndoorMuseumNavigationViewController.h; sourceTree = ""; }; + 6533FA4161283F8F6A5F2CC3 /* step3.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = step3.png; sourceTree = ""; }; + 658307DB8F671E9CA0B1A2E6 /* GoogleMapsDemos.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GoogleMapsDemos.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 6614BE28012C7A7508437047 /* arrow.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = arrow.png; sourceTree = ""; }; + 66DE8D273E896D7FF5B86DC9 /* arrow@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "arrow@2x.png"; sourceTree = ""; }; + 6B05E27C8BB92E89D2DC78AC /* h1.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = h1.png; sourceTree = ""; }; + 6C186E84CB691DD6239C5E57 /* botswana.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = botswana.png; sourceTree = ""; }; + 6C6A156792608F06437E5931 /* step1@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "step1@2x.png"; sourceTree = ""; }; + 6F4C119D42CF37317FA0DFA9 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + 71CE23C2A26FD3C29E6AEEA7 /* GestureControlViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GestureControlViewController.m; sourceTree = ""; }; + 721936633CF79A85B5EF0E92 /* FitBoundsViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FitBoundsViewController.m; sourceTree = ""; }; + 726F8CE865076A20EE4C4408 /* PanoramaViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PanoramaViewController.h; sourceTree = ""; }; + 77E4A5AC4E17EB252D617792 /* x29.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = x29.png; sourceTree = ""; }; + 7BAA2BF38E0B41197BB6CA14 /* GroundOverlayViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GroundOverlayViewController.h; sourceTree = ""; }; + 7DB963F858613E50A64CFAE6 /* FrameRateViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FrameRateViewController.m; sourceTree = ""; }; + 8038AEC0AF9CBD2EBB975F54 /* GroundOverlayViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GroundOverlayViewController.m; sourceTree = ""; }; + 805803DEB0E29A1395C6ABB6 /* TrafficMapViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TrafficMapViewController.h; sourceTree = ""; }; + 8348F1BC33E5DCAAB85BBA43 /* DoubleMapViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DoubleMapViewController.h; sourceTree = ""; }; + 87C50C8E191C32391FCDE143 /* CameraViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CameraViewController.h; sourceTree = ""; }; + 89DFB3350D98DA617A2ECE85 /* MarkerInfoWindowViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MarkerInfoWindowViewController.m; sourceTree = ""; }; + 8BEC50A442A8A75604D7007D /* FixedPanoramaViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FixedPanoramaViewController.h; sourceTree = ""; }; + 8DA013030BC14C994AEA2428 /* IndoorViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = IndoorViewController.m; sourceTree = ""; }; + 90AFC572AAEC55CB23E0EDB4 /* BasicMapViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BasicMapViewController.h; sourceTree = ""; }; + 91BCD28A8D5451665C5B3FDE /* spitfire.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = spitfire.png; sourceTree = ""; }; + 937B2FC602E80ADC11CF364F /* aeroplane.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = aeroplane.png; sourceTree = ""; }; + 99C6A64731FB5249BA53255D /* step4.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = step4.png; sourceTree = ""; }; + 9F4CB893BCACB294FBCEC850 /* australia-large.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "australia-large.png"; sourceTree = ""; }; + A0518A8C9E543FFBA7F8305E /* VisibleRegionViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VisibleRegionViewController.m; sourceTree = ""; }; + A1D521E4FF1F8F85E084ED60 /* australia-large@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "australia-large@2x.png"; sourceTree = ""; }; + A3AE3B168663EA43D3F2F259 /* step3@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "step3@2x.png"; sourceTree = ""; }; + A6B80BDCF78F436799462F76 /* argentina.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = argentina.png; sourceTree = ""; }; + A9ECA1CA359738BB8B17657F /* PanoramaViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PanoramaViewController.m; sourceTree = ""; }; + AA7FD859248BB356AA73A407 /* FrameRateViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FrameRateViewController.h; sourceTree = ""; }; + AAB5CC69AA4A14C0B11D0F2B /* MapZoomViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MapZoomViewController.h; sourceTree = ""; }; + AB6D8FD2BBEB798B9D0A33DE /* IndoorViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IndoorViewController.h; sourceTree = ""; }; + AB708C911CD9B99ADBB07D08 /* step5@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "step5@2x.png"; sourceTree = ""; }; + AD6B1AF49A87CC079E9CA862 /* track.json */ = {isa = PBXFileReference; lastKnownFileType = text; path = track.json; sourceTree = ""; }; + B15768E1EE7E314DF1B0A395 /* spitfire@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "spitfire@2x.png"; sourceTree = ""; }; + B1763EA929EC73C50D294134 /* step5.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = step5.png; sourceTree = ""; }; + B4C89E62C05816B590F45D57 /* mapstyle-silver.json */ = {isa = PBXFileReference; lastKnownFileType = text; path = "mapstyle-silver.json"; sourceTree = ""; }; + B53058D6D185A08DDEF65436 /* Samples.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Samples.h; sourceTree = ""; }; + B53B4C2097A6AD4A98746495 /* MapsDemoAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = MapsDemoAssets.xcassets; sourceTree = ""; }; + B7B684AAFC3CA2232721C6D0 /* step8.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = step8.png; sourceTree = ""; }; + B9450C49DDD49AF9182779EB /* MarkerEventsViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MarkerEventsViewController.m; sourceTree = ""; }; + BB653990DAD6FDE28A9E4158 /* FitBoundsViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FitBoundsViewController.h; sourceTree = ""; }; + BB85B60A794D982F001E5D1D /* step2.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = step2.png; sourceTree = ""; }; + BEDC49669DBDA8BE37BFAB29 /* GeocoderViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeocoderViewController.h; sourceTree = ""; }; + BF0878372A9A7274B74BCBE0 /* MyLocationViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MyLocationViewController.m; sourceTree = ""; }; + C0E7585025B57F969D8C3CCD /* step6@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "step6@2x.png"; sourceTree = ""; }; + C37F2216D5D94A3F8B698AF2 /* AnimatedCurrentLocationViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AnimatedCurrentLocationViewController.m; sourceTree = ""; }; + C3E84ED0331DFB2A7082CA36 /* MarkerLayerViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MarkerLayerViewController.m; sourceTree = ""; }; + C4C3F5556675D1A9408F7C5F /* MasterViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MasterViewController.h; sourceTree = ""; }; + C56541EDCEFDB922783385B5 /* voyager@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "voyager@2x.png"; sourceTree = ""; }; + C762E469D796AFF83D3516EF /* FixedPanoramaViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FixedPanoramaViewController.m; sourceTree = ""; }; + C7AD415580B4A6B35532456A /* DemoAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DemoAppDelegate.m; sourceTree = ""; }; + CF8997BED409E528D34BA344 /* PolylinesViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PolylinesViewController.h; sourceTree = ""; }; + D0E3C8118FBF6DF1D4996693 /* step7@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "step7@2x.png"; sourceTree = ""; }; + D30CF0DFCD652D6DF10187D7 /* MarkerLayerViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MarkerLayerViewController.h; sourceTree = ""; }; + D4CFDA7D44E2CF33EB10072B /* MarkersViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MarkersViewController.m; sourceTree = ""; }; + D5E5AF68BBC86BA530755A20 /* step8@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "step8@2x.png"; sourceTree = ""; }; + D66EF243066A58A0D10280D4 /* step1.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = step1.png; sourceTree = ""; }; + D78B4ACC4D102CC9EC32C8A3 /* SnapshotReadyViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SnapshotReadyViewController.h; sourceTree = ""; }; + DA6BF21C0287E5828A770C09 /* bulgaria-large.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bulgaria-large.png"; sourceTree = ""; }; + DB481265195ED47C9D3588AB /* CustomIndoorViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CustomIndoorViewController.h; sourceTree = ""; }; + DBB8C9AD891A00824AE32343 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; + DC77B503E93E88F6E254A170 /* CustomMarkersViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CustomMarkersViewController.m; sourceTree = ""; }; + DD1DD5592D028FDD5E527FDF /* x29@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "x29@2x.png"; sourceTree = ""; }; + DD87868F23D05E5CFADEAB4F /* MarkerEventsViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MarkerEventsViewController.h; sourceTree = ""; }; + E40A76286C824B75258FDF5E /* StyledMapViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StyledMapViewController.h; sourceTree = ""; }; + E4F81E0232539CC089AE8893 /* CustomMarkersViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CustomMarkersViewController.h; sourceTree = ""; }; + E85063B5DC5D93C8875E3EBC /* PolygonsViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PolygonsViewController.h; sourceTree = ""; }; + EE1D504741871952487BB5BE /* MasterViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MasterViewController.m; sourceTree = ""; }; + EF01EFEF0FC77447C02FE86C /* popup_santa.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = popup_santa.png; sourceTree = ""; }; + F371BFD608DCF4DA1671DB03 /* MapTypesViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MapTypesViewController.m; sourceTree = ""; }; + F59393018D5AC385BE80BE57 /* MarkersViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MarkersViewController.h; sourceTree = ""; }; + F63E5BE6BF5B9E3D7AF192E0 /* UIViewController+GMSToastMessages.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIViewController+GMSToastMessages.h"; sourceTree = ""; }; + F64D45825D2647898331A1FF /* step6.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = step6.png; sourceTree = ""; }; + F8DFCC9D1D1F70D492149D65 /* DoubleMapViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DoubleMapViewController.m; sourceTree = ""; }; + FA4145303F340FC3675AABA5 /* PolylinesViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PolylinesViewController.m; sourceTree = ""; }; + FAF7CEAC34C5EBC5F05412EC /* GradientPolylinesViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GradientPolylinesViewController.h; sourceTree = ""; }; + FC2C54DF6BAAE16F877D8A66 /* AnimatedUIViewMarkerViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AnimatedUIViewMarkerViewController.h; sourceTree = ""; }; + FC5AD93CD72DE8F5B004A621 /* BasicMapViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BasicMapViewController.m; sourceTree = ""; }; + FCA64CCA86165B04BDDE0A93 /* SDKDemoAPIKey.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDKDemoAPIKey.h; sourceTree = ""; }; + FFEED4DDB04F69C84A1924F1 /* mapstyle-night.json */ = {isa = PBXFileReference; lastKnownFileType = text; path = "mapstyle-night.json"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 8B9B135C626B0FF8CC12CB4D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + B96B00239590850A49AC33AD /* UIKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 1D27564BB990E3F72332953D = { + isa = PBXGroup; + children = ( + 3F84FF5646DF0F79669A6A34 /* Source */, + 89188BA529E9696AD04AD794 /* Frameworks */, + 9C667AD7285A7D36180D29BE /* Products */, + ); + sourceTree = ""; + }; + 3F84FF5646DF0F79669A6A34 /* Source */ = { + isa = PBXGroup; + children = ( + E430A2BC04AE9A99EA6DACA3 /* GoogleMapsDemos */, + ); + name = Source; + sourceTree = ""; + }; + 89188BA529E9696AD04AD794 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 6F4C119D42CF37317FA0DFA9 /* UIKit.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 8AEE26F54DD8496915CC250C /* Museum-Icons */ = { + isa = PBXGroup; + children = ( + 6B05E27C8BB92E89D2DC78AC /* h1.png */, + 16C4B08486C9905568685983 /* h1@2x.png */, + 91BCD28A8D5451665C5B3FDE /* spitfire.png */, + B15768E1EE7E314DF1B0A395 /* spitfire@2x.png */, + 5B3B4C44092471CA56ACFD4C /* voyager.png */, + C56541EDCEFDB922783385B5 /* voyager@2x.png */, + 77E4A5AC4E17EB252D617792 /* x29.png */, + DD1DD5592D028FDD5E527FDF /* x29@2x.png */, + ); + path = "Museum-Icons"; + sourceTree = ""; + }; + 9C667AD7285A7D36180D29BE /* Products */ = { + isa = PBXGroup; + children = ( + 658307DB8F671E9CA0B1A2E6 /* GoogleMapsDemos.app */, + ); + name = Products; + sourceTree = ""; + }; + BB93A9CC7ED94895B527E2F2 /* Samples */ = { + isa = PBXGroup; + children = ( + 281FB700EEAC3323C52CB587 /* AnimatedCurrentLocationViewController.h */, + C37F2216D5D94A3F8B698AF2 /* AnimatedCurrentLocationViewController.m */, + FC2C54DF6BAAE16F877D8A66 /* AnimatedUIViewMarkerViewController.h */, + 2EB950AA302B77F16CF18088 /* AnimatedUIViewMarkerViewController.m */, + 90AFC572AAEC55CB23E0EDB4 /* BasicMapViewController.h */, + FC5AD93CD72DE8F5B004A621 /* BasicMapViewController.m */, + 87C50C8E191C32391FCDE143 /* CameraViewController.h */, + 09A36E26A0818D4F0DF21051 /* CameraViewController.m */, + DB481265195ED47C9D3588AB /* CustomIndoorViewController.h */, + 5563ED82E4E77A76707DAF12 /* CustomIndoorViewController.m */, + E4F81E0232539CC089AE8893 /* CustomMarkersViewController.h */, + DC77B503E93E88F6E254A170 /* CustomMarkersViewController.m */, + 8348F1BC33E5DCAAB85BBA43 /* DoubleMapViewController.h */, + F8DFCC9D1D1F70D492149D65 /* DoubleMapViewController.m */, + BB653990DAD6FDE28A9E4158 /* FitBoundsViewController.h */, + 721936633CF79A85B5EF0E92 /* FitBoundsViewController.m */, + 8BEC50A442A8A75604D7007D /* FixedPanoramaViewController.h */, + C762E469D796AFF83D3516EF /* FixedPanoramaViewController.m */, + AA7FD859248BB356AA73A407 /* FrameRateViewController.h */, + 7DB963F858613E50A64CFAE6 /* FrameRateViewController.m */, + BEDC49669DBDA8BE37BFAB29 /* GeocoderViewController.h */, + 2A77C64C5A8B8109E6AFC41F /* GeocoderViewController.m */, + 0C5E03510539F570D757CAEE /* GestureControlViewController.h */, + 71CE23C2A26FD3C29E6AEEA7 /* GestureControlViewController.m */, + FAF7CEAC34C5EBC5F05412EC /* GradientPolylinesViewController.h */, + 1B4378F4DBE15BB0E32690CE /* GradientPolylinesViewController.m */, + 7BAA2BF38E0B41197BB6CA14 /* GroundOverlayViewController.h */, + 8038AEC0AF9CBD2EBB975F54 /* GroundOverlayViewController.m */, + 6140C36D94B102F62C26B713 /* IndoorMuseumNavigationViewController.h */, + 1318E0A2BA2A41F2E8759CFE /* IndoorMuseumNavigationViewController.m */, + AB6D8FD2BBEB798B9D0A33DE /* IndoorViewController.h */, + 8DA013030BC14C994AEA2428 /* IndoorViewController.m */, + 51B3E8FEDF5AFB146C099C21 /* MapLayerViewController.h */, + 3E7BBD64FA078FC4259FCBD2 /* MapLayerViewController.m */, + 4B436F286FDDDE125BA44AEE /* MapTypesViewController.h */, + F371BFD608DCF4DA1671DB03 /* MapTypesViewController.m */, + AAB5CC69AA4A14C0B11D0F2B /* MapZoomViewController.h */, + 3B3D673FA7036FA5D07CB287 /* MapZoomViewController.m */, + DD87868F23D05E5CFADEAB4F /* MarkerEventsViewController.h */, + B9450C49DDD49AF9182779EB /* MarkerEventsViewController.m */, + 15E6F2AA55437CFAC24BCFEE /* MarkerInfoWindowViewController.h */, + 89DFB3350D98DA617A2ECE85 /* MarkerInfoWindowViewController.m */, + D30CF0DFCD652D6DF10187D7 /* MarkerLayerViewController.h */, + C3E84ED0331DFB2A7082CA36 /* MarkerLayerViewController.m */, + F59393018D5AC385BE80BE57 /* MarkersViewController.h */, + D4CFDA7D44E2CF33EB10072B /* MarkersViewController.m */, + 5F34640A0EABB50A24705F03 /* MyLocationViewController.h */, + BF0878372A9A7274B74BCBE0 /* MyLocationViewController.m */, + 56200E1874E08933C65BFE11 /* PaddingBehaviorViewController.h */, + 3679BC5053A9702AE4FC85B3 /* PaddingBehaviorViewController.m */, + 726F8CE865076A20EE4C4408 /* PanoramaViewController.h */, + A9ECA1CA359738BB8B17657F /* PanoramaViewController.m */, + E85063B5DC5D93C8875E3EBC /* PolygonsViewController.h */, + 2ED9C84A592EB6464725BB9C /* PolygonsViewController.m */, + CF8997BED409E528D34BA344 /* PolylinesViewController.h */, + FA4145303F340FC3675AABA5 /* PolylinesViewController.m */, + B53058D6D185A08DDEF65436 /* Samples.h */, + 115963CCE082409262D179F3 /* Samples.m */, + D78B4ACC4D102CC9EC32C8A3 /* SnapshotReadyViewController.h */, + 15E621C7788D69B720052601 /* SnapshotReadyViewController.m */, + 5C9035433ADBDA49B4FF973F /* StructuredGeocoderViewController.h */, + 2AAFE928C9FDAA1B5703AE31 /* StructuredGeocoderViewController.m */, + E40A76286C824B75258FDF5E /* StyledMapViewController.h */, + 3408578DDDF6D8B183D9B91E /* StyledMapViewController.m */, + 33BC04FF4445AA4649D16101 /* TileLayerViewController.h */, + 0CEA86A42848E5BAA7B523FB /* TileLayerViewController.m */, + 805803DEB0E29A1395C6ABB6 /* TrafficMapViewController.h */, + 611E9F57BD5274878732C0BE /* TrafficMapViewController.m */, + 2D030F81AEB6D3278B8EA303 /* VisibleRegionViewController.h */, + A0518A8C9E543FFBA7F8305E /* VisibleRegionViewController.m */, + ); + path = Samples; + sourceTree = ""; + }; + E430A2BC04AE9A99EA6DACA3 /* GoogleMapsDemos */ = { + isa = PBXGroup; + children = ( + E9C287B2214772B64F89F90B /* Resources */, + BB93A9CC7ED94895B527E2F2 /* Samples */, + 02A7C56A409A0CB977458251 /* DemoAppDelegate.h */, + C7AD415580B4A6B35532456A /* DemoAppDelegate.m */, + B53B4C2097A6AD4A98746495 /* MapsDemoAssets.xcassets */, + C4C3F5556675D1A9408F7C5F /* MasterViewController.h */, + EE1D504741871952487BB5BE /* MasterViewController.m */, + FCA64CCA86165B04BDDE0A93 /* SDKDemoAPIKey.h */, + F63E5BE6BF5B9E3D7AF192E0 /* UIViewController+GMSToastMessages.h */, + 141DC0E7977DE53BB4C0ACE6 /* UIViewController+GMSToastMessages.m */, + 21267D205F7EC280F26D97ED /* main.m */, + ); + path = GoogleMapsDemos; + sourceTree = ""; + }; + E9C287B2214772B64F89F90B /* Resources */ = { + isa = PBXGroup; + children = ( + 8AEE26F54DD8496915CC250C /* Museum-Icons */, + DBB8C9AD891A00824AE32343 /* LaunchScreen.storyboard */, + 937B2FC602E80ADC11CF364F /* aeroplane.png */, + 3F716156C215C436D55F5696 /* aeroplane@2x.png */, + 4D8E4A10394122CD9C9CD157 /* argentina-large.png */, + A6B80BDCF78F436799462F76 /* argentina.png */, + 6614BE28012C7A7508437047 /* arrow.png */, + 66DE8D273E896D7FF5B86DC9 /* arrow@2x.png */, + 9F4CB893BCACB294FBCEC850 /* australia-large.png */, + A1D521E4FF1F8F85E084ED60 /* australia-large@2x.png */, + 304A2B780A4A9B2D32261355 /* australia.png */, + 2B049CFC81E59949D0184B8B /* boat.png */, + 03E00AE555F58259DD31C383 /* boat@2x.png */, + 440513A7769094565EA82DD2 /* botswana-large.png */, + 6C186E84CB691DD6239C5E57 /* botswana.png */, + DA6BF21C0287E5828A770C09 /* bulgaria-large.png */, + 2BC67BDB51522BC85EBFED8E /* bulgaria.png */, + 57D0C4A29B66857C926387F0 /* glow-marker.png */, + 430314741C873551A75A6748 /* glow-marker@2x.png */, + FFEED4DDB04F69C84A1924F1 /* mapstyle-night.json */, + 118A0FE8CA1D110B3C694A8D /* mapstyle-retro.json */, + B4C89E62C05816B590F45D57 /* mapstyle-silver.json */, + 346BE94E21267BA7E00E1B94 /* museum-exhibits.json */, + 2B0884C720D5AE0BDCC1903D /* newark_nj_1922.jpg */, + EF01EFEF0FC77447C02FE86C /* popup_santa.png */, + 5E2E2B19EF6846F007A7A909 /* popup_santa@2x.png */, + D66EF243066A58A0D10280D4 /* step1.png */, + 6C6A156792608F06437E5931 /* step1@2x.png */, + BB85B60A794D982F001E5D1D /* step2.png */, + 4F955F74654756E7F7DBD9C1 /* step2@2x.png */, + 6533FA4161283F8F6A5F2CC3 /* step3.png */, + A3AE3B168663EA43D3F2F259 /* step3@2x.png */, + 99C6A64731FB5249BA53255D /* step4.png */, + 60944672BB9D460BE315ECB8 /* step4@2x.png */, + B1763EA929EC73C50D294134 /* step5.png */, + AB708C911CD9B99ADBB07D08 /* step5@2x.png */, + F64D45825D2647898331A1FF /* step6.png */, + C0E7585025B57F969D8C3CCD /* step6@2x.png */, + 3B0B37F0669CF34418A263F9 /* step7.png */, + D0E3C8118FBF6DF1D4996693 /* step7@2x.png */, + B7B684AAFC3CA2232721C6D0 /* step8.png */, + D5E5AF68BBC86BA530755A20 /* step8@2x.png */, + AD6B1AF49A87CC079E9CA862 /* track.json */, + ); + path = Resources; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + F934453EF090BCCB93E78E83 /* GoogleMapsDemos */ = { + isa = PBXNativeTarget; + buildConfigurationList = 56D55B92E97A2AAB3D261D4A /* Build configuration list for PBXNativeTarget "GoogleMapsDemos" */; + buildPhases = ( + C434996613C0A2FED463783D /* Resources */, + 06DE6F27AD1DD8E5163683B6 /* Sources */, + 8B9B135C626B0FF8CC12CB4D /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = GoogleMapsDemos; + productName = GoogleMapsDemos; + productReference = 658307DB8F671E9CA0B1A2E6 /* GoogleMapsDemos.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + E1E34A01E38954F1DD4BCD39 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + }; + buildConfigurationList = 80C3BB6A64A9375104C7ACB9 /* Build configuration list for PBXProject "GoogleMapsDemos" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = en; + hasScannedForEncodings = 1; + knownRegions = ( + en, + ); + mainGroup = 1D27564BB990E3F72332953D; + projectDirPath = ""; + projectRoot = ""; + targets = ( + F934453EF090BCCB93E78E83 /* GoogleMapsDemos */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + C434996613C0A2FED463783D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 30CE808B1E98D7A20A2AB60E /* aeroplane.png in Resources */, + 0B07C58295A7BE58E96F34A3 /* aeroplane@2x.png in Resources */, + 16717F377D18DD00AA691F18 /* argentina-large.png in Resources */, + BE9A04B467C3AEFF9F055139 /* argentina.png in Resources */, + AB5636020377CA4EEDDD54E2 /* arrow.png in Resources */, + 06A69C6EAF7D7E8FF2197EC3 /* arrow@2x.png in Resources */, + 5746390C8A605B40841AE0F5 /* australia-large.png in Resources */, + 01C8D1488E89B8A200C8582E /* australia-large@2x.png in Resources */, + 7D840080EF0D790EA1F194A8 /* australia.png in Resources */, + 8F5472876D59BE14593D0FD2 /* boat.png in Resources */, + 78251F0B2F404EDD815FF5C5 /* boat@2x.png in Resources */, + C9767A9B1C49EE0C94893D7F /* botswana-large.png in Resources */, + C29A6A1CD63D938B4A8CDCC0 /* botswana.png in Resources */, + CDAFCD46473B6ED74EF20AB8 /* bulgaria-large.png in Resources */, + FFBC92DCB78E89B548CBEFCB /* bulgaria.png in Resources */, + A6E90219EA860C586F543A4A /* glow-marker.png in Resources */, + 4921EFA284C51B00D03795C3 /* glow-marker@2x.png in Resources */, + 7C990DA60F735AF48D770DC5 /* h1.png in Resources */, + 5C91A963E5BF4ECDD5ADA417 /* h1@2x.png in Resources */, + 2C6A65E1DD60EA63447C3263 /* spitfire.png in Resources */, + 4FA6BC3F01C6B9CEDA448E26 /* spitfire@2x.png in Resources */, + 43F1D3292C4928A90E1F2ECC /* voyager.png in Resources */, + E2EBC4612218EA7304B27935 /* voyager@2x.png in Resources */, + 4EEE7F0A05EA0200D46C3158 /* x29.png in Resources */, + C24E551B4F0F381D7F6E4A67 /* x29@2x.png in Resources */, + 16B31F7A9AF98310188109DC /* popup_santa.png in Resources */, + FEEB324EBD6CD86B7E2F3D23 /* popup_santa@2x.png in Resources */, + 8CD768E0261BFDCCCA116EFC /* step1.png in Resources */, + 2BB7EAB853CF12FF8B728EB0 /* step1@2x.png in Resources */, + A335061AB0909CB860ED2D91 /* step2.png in Resources */, + 98DEEDA226C3C3AFD58A0ED9 /* step2@2x.png in Resources */, + 70C609604B6E16310749BEE1 /* step3.png in Resources */, + FFB50913C7C92104C44FC4E6 /* step3@2x.png in Resources */, + BEF92FBE50408C13E57D677C /* step4.png in Resources */, + 9201EE4D315D2663EE849ACB /* step4@2x.png in Resources */, + F5B169917A264494AF0CF434 /* step5.png in Resources */, + 37AA7110FDDEA5425209ADDA /* step5@2x.png in Resources */, + 2D5C0726D35B29E07C3C45F4 /* step6.png in Resources */, + 3ADDFA115AB009524ED62A24 /* step6@2x.png in Resources */, + B28BAAE9307E182E27F1971D /* step7.png in Resources */, + B94EC3B15FAA49100E840A46 /* step7@2x.png in Resources */, + 2522616BC4EFE2F27AA3C31D /* step8.png in Resources */, + 02EC898A205A1E81940406E5 /* step8@2x.png in Resources */, + 8BE208DD6AC74D59735122F6 /* newark_nj_1922.jpg in Resources */, + F0DB8FF9FBF256B3CB85A8E0 /* mapstyle-night.json in Resources */, + 58522D14C5843238B3B684B7 /* mapstyle-retro.json in Resources */, + 023D5C0EB12D723906E5318C /* mapstyle-silver.json in Resources */, + FF9DA2C517D5682ADD75D0AF /* museum-exhibits.json in Resources */, + 52964EB41C2A98E1B3B67C8D /* track.json in Resources */, + FCA2894A159A2C5995A921BC /* LaunchScreen.storyboard in Resources */, + A7177552BD808915E2D171C6 /* MapsDemoAssets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 06DE6F27AD1DD8E5163683B6 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A64080332C06F6DD1B1D8FFD /* DemoAppDelegate.m in Sources */, + EFB6792DEB7FB555FFB13CB0 /* main.m in Sources */, + 0D9B023D6E709FAB165A29C9 /* MasterViewController.m in Sources */, + 30EA088C289C5AE5A564CB7B /* AnimatedCurrentLocationViewController.m in Sources */, + CEA937D53AEA30A22D2AF43A /* AnimatedUIViewMarkerViewController.m in Sources */, + EB1E6BF7EC8A504C23509548 /* BasicMapViewController.m in Sources */, + 53A6B686C7620ACB24603D89 /* CameraViewController.m in Sources */, + EA1C6FDB09359F6A2E98414F /* CustomIndoorViewController.m in Sources */, + 7130108058B7212F6C7FD1FE /* CustomMarkersViewController.m in Sources */, + 6C605BC471B85B1304719717 /* DoubleMapViewController.m in Sources */, + 1E38E739DB90F691064AB0A9 /* FitBoundsViewController.m in Sources */, + 51E192046D546D886F450F79 /* FixedPanoramaViewController.m in Sources */, + A0E8E3B87D9EEC0D785B1DC5 /* FrameRateViewController.m in Sources */, + 1511EFFE20A68F00B873A930 /* GeocoderViewController.m in Sources */, + 2BB91ECAAD82A6BD9AA5A31C /* GestureControlViewController.m in Sources */, + D07832F6FE5AD1A2170EFFEC /* GradientPolylinesViewController.m in Sources */, + 0E2E093D159271A7FB585759 /* GroundOverlayViewController.m in Sources */, + 465832A7B8B8E273B905F029 /* IndoorMuseumNavigationViewController.m in Sources */, + 52D6B181D0B7A7439CCDB3D1 /* IndoorViewController.m in Sources */, + 6FD2C9C23995654D55280DCC /* MapLayerViewController.m in Sources */, + 6CCF16F2F014A3E746833201 /* MapTypesViewController.m in Sources */, + 4A2C42ED6FE29D017AE4A947 /* MapZoomViewController.m in Sources */, + 22EB466DA6512BAA95E23367 /* MarkerEventsViewController.m in Sources */, + 162AF3F1216374886C841719 /* MarkerInfoWindowViewController.m in Sources */, + 5E4A600A723EAD16564CE99A /* MarkerLayerViewController.m in Sources */, + 85D9288290263792177A1F62 /* MarkersViewController.m in Sources */, + 3AC5F611874DBEBD37627398 /* MyLocationViewController.m in Sources */, + 37FEE000C69584FD7194FF6B /* PaddingBehaviorViewController.m in Sources */, + 212AC00650E55AC8C5EE5C02 /* PanoramaViewController.m in Sources */, + FD207FD6DF50468AD7A901D7 /* PolygonsViewController.m in Sources */, + 67BB1551A52FE25B519698E7 /* PolylinesViewController.m in Sources */, + 141E2BE7C525DC36CB1FE54F /* Samples.m in Sources */, + BCDF7FA5469D1D2344028550 /* SnapshotReadyViewController.m in Sources */, + 966D736517085B56D65BFF5A /* StructuredGeocoderViewController.m in Sources */, + A182EF7F4411ABF7BF943036 /* StyledMapViewController.m in Sources */, + 870CD271BCF4C0A11F72DF98 /* TileLayerViewController.m in Sources */, + DDA4B1B496975C801B592AFD /* TrafficMapViewController.m in Sources */, + 3863ECD2CA69536521AD365E /* VisibleRegionViewController.m in Sources */, + 19BA16967B625E9962C0EDCC /* UIViewController+GMSToastMessages.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 2723CC697F2C564456B7C001 /* Default */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_OBJC_ARC = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + INFOPLIST_FILE = ./GoogleMapsDemos/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + LIBRARY_SEARCH_PATHS = ( + ., + "$(SDKROOT)/System/Library/Frameworks", + ); + PRODUCT_NAME = GoogleMapsDemos; + TARGETED_DEVICE_FAMILY = "1,2"; + USER_HEADER_SEARCH_PATHS = "$(SRCROOT)"; + USE_HEADERMAP = NO; + WRAPPER_PREFIX = ""; + }; + name = Default; + }; + C39588D2DC04B7B9CBE846E5 /* Default */ = { + isa = XCBuildConfiguration; + buildSettings = { + INTERMEDIATE_DIR = "$(PROJECT_DERIVED_FILE_DIR)/$(CONFIGURATION)"; + SDKROOT = iphoneos; + SHARED_INTERMEDIATE_DIR = "$(SYMROOT)/DerivedSources/$(CONFIGURATION)"; + }; + name = Default; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 56D55B92E97A2AAB3D261D4A /* Build configuration list for PBXNativeTarget "GoogleMapsDemos" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 2723CC697F2C564456B7C001 /* Default */, + ); + defaultConfigurationIsVisible = 1; + defaultConfigurationName = Default; + }; + 80C3BB6A64A9375104C7ACB9 /* Build configuration list for PBXProject "GoogleMapsDemos" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C39588D2DC04B7B9CBE846E5 /* Default */, + ); + defaultConfigurationIsVisible = 1; + defaultConfigurationName = Default; + }; +/* End XCConfigurationList section */ + }; + rootObject = E1E34A01E38954F1DD4BCD39 /* Project object */; +} diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/DemoAppDelegate.h a/Pods/GoogleMaps/Example/GoogleMapsDemos/DemoAppDelegate.h new file mode 100755 index 0000000..4a51db5 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/DemoAppDelegate.h @@ -0,0 +1,32 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 + +@interface DemoAppDelegate : UIResponder < + UIApplicationDelegate, + UISplitViewControllerDelegate> + +@property(nonatomic) UIWindow *window; +@property(nonatomic) UINavigationController *navigationController; +@property(nonatomic) UISplitViewController *splitViewController; + +/** + * If the device is an iPad, this property controls the sample displayed in the + * right side of its split view controller. + */ +@property(nonatomic) UIViewController *sample; + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/DemoAppDelegate.m a/Pods/GoogleMaps/Example/GoogleMapsDemos/DemoAppDelegate.m new file mode 100755 index 0000000..0366000 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/DemoAppDelegate.m @@ -0,0 +1,113 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 "GoogleMapsDemos/DemoAppDelegate.h" + +#import "GoogleMapsDemos/MasterViewController.h" +#import "GoogleMapsDemos/SDKDemoAPIKey.h" +#import + +@implementation DemoAppDelegate { + id _services; +} + +@synthesize window = _window; + +- (BOOL)application:(UIApplication *)application + didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + NSLog(@"Build version: %s", __VERSION__); + + if (kAPIKey.length == 0) { + // Blow up if APIKey has not yet been set. + NSString *bundleId = [[NSBundle mainBundle] bundleIdentifier]; + NSString *format = @"Configure APIKey inside SDKDemoAPIKey.h for your " + @"bundle `%@`, see README.GoogleMapsDemos for more information"; + @throw [NSException exceptionWithName:@"DemoAppDelegate" + reason:[NSString stringWithFormat:format, bundleId] + userInfo:nil]; + } + [GMSServices provideAPIKey:kAPIKey]; + _services = [GMSServices sharedServices]; + + // Log the required open source licenses! Yes, just NSLog-ing them is not enough but is good for + // a demo. + NSLog(@"Open source licenses:\n%@", [GMSServices openSourceLicenseInfo]); + + self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + MasterViewController *master = [[MasterViewController alloc] init]; + master.appDelegate = self; + + if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) { + // This is an iPhone; configure the top-level navigation controller as the + // rootViewController, which contains the 'master' list of samples. + self.navigationController = + [[UINavigationController alloc] initWithRootViewController:master]; + + // Force non-translucent navigation bar for consistency of demo between + // iOS 6 and iOS 7. + self.navigationController.navigationBar.translucent = NO; + + self.window.rootViewController = self.navigationController; + } else { + // This is an iPad; configure a split-view controller that contains the + // the 'master' list of samples on the left side, and the current displayed + // sample on the right (begins empty). + UINavigationController *masterNavigationController = + [[UINavigationController alloc] initWithRootViewController:master]; + + UIViewController *empty = [[UIViewController alloc] init]; + UINavigationController *detailNavigationController = + [[UINavigationController alloc] initWithRootViewController:empty]; + + // Force non-translucent navigation bar for consistency of demo between + // iOS 6 and iOS 7. + detailNavigationController.navigationBar.translucent = NO; + + self.splitViewController = [[UISplitViewController alloc] init]; + self.splitViewController.delegate = master; + self.splitViewController.viewControllers = + @[masterNavigationController, detailNavigationController]; + self.splitViewController.presentsWithGesture = NO; + + self.window.rootViewController = self.splitViewController; + } + + [self.window makeKeyAndVisible]; + return YES; +} + +- (void)setSample:(UIViewController *)sample { + NSAssert([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad, + @"Expected device to be iPad inside setSample:"); + + // Finds the UINavigationController in the right side of the sample, and + // replace its displayed controller with the new sample. + UINavigationController *nav = + [self.splitViewController.viewControllers objectAtIndex:1]; + [nav setViewControllers:[NSArray arrayWithObject:sample] animated:NO]; +} + +- (UIViewController *)sample { + NSAssert([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad, + @"Expected device to be iPad inside sample"); + + // The current sample is the top-most VC in the right-hand pane of the + // splitViewController. + UINavigationController *nav = + [self.splitViewController.viewControllers objectAtIndex:1]; + return [[nav viewControllers] objectAtIndex:0]; +} + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Info.plist a/Pods/GoogleMaps/Example/GoogleMapsDemos/Info.plist new file mode 100755 index 0000000..d5172fe --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + ${PRODUCT_NAME} + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + com.example.GoogleMapsDemos + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1.0 + LSRequiresIPhoneOS + + NSLocationWhenInUseUsageDescription + Show your location on the map + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UIStatusBarHidden + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/MapsDemoAssets.xcassets/AppIcon.appiconset/Contents.json a/Pods/GoogleMaps/Example/GoogleMapsDemos/MapsDemoAssets.xcassets/AppIcon.appiconset/Contents.json new file mode 100755 index 0000000..5fce6cf --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/MapsDemoAssets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Maps-SDK-Demo-App_120.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Maps-SDK-Demo-App_180.png", + "scale" : "3x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Maps-SDK-Demo-App_76.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Maps-SDK-Demo-App_152.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Maps-SDK-Demo-App_167.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/MapsDemoAssets.xcassets/AppIcon.appiconset/Maps-SDK-Demo-App_120.png a/Pods/GoogleMaps/Example/GoogleMapsDemos/MapsDemoAssets.xcassets/AppIcon.appiconset/Maps-SDK-Demo-App_120.png new file mode 100755 index 0000000..46d9c3f Binary files /dev/null and a/Pods/GoogleMaps/Example/GoogleMapsDemos/MapsDemoAssets.xcassets/AppIcon.appiconset/Maps-SDK-Demo-App_120.png differ diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/MapsDemoAssets.xcassets/AppIcon.appiconset/Maps-SDK-Demo-App_152.png a/Pods/GoogleMaps/Example/GoogleMapsDemos/MapsDemoAssets.xcassets/AppIcon.appiconset/Maps-SDK-Demo-App_152.png new file mode 100755 index 0000000..237bba1 Binary files /dev/null and a/Pods/GoogleMaps/Example/GoogleMapsDemos/MapsDemoAssets.xcassets/AppIcon.appiconset/Maps-SDK-Demo-App_152.png differ diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/MapsDemoAssets.xcassets/AppIcon.appiconset/Maps-SDK-Demo-App_167.png a/Pods/GoogleMaps/Example/GoogleMapsDemos/MapsDemoAssets.xcassets/AppIcon.appiconset/Maps-SDK-Demo-App_167.png new file mode 100755 index 0000000..8bee0dd Binary files /dev/null and a/Pods/GoogleMaps/Example/GoogleMapsDemos/MapsDemoAssets.xcassets/AppIcon.appiconset/Maps-SDK-Demo-App_167.png differ diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/MapsDemoAssets.xcassets/AppIcon.appiconset/Maps-SDK-Demo-App_180.png a/Pods/GoogleMaps/Example/GoogleMapsDemos/MapsDemoAssets.xcassets/AppIcon.appiconset/Maps-SDK-Demo-App_180.png new file mode 100755 index 0000000..0299a1d Binary files /dev/null and a/Pods/GoogleMaps/Example/GoogleMapsDemos/MapsDemoAssets.xcassets/AppIcon.appiconset/Maps-SDK-Demo-App_180.png differ diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/MapsDemoAssets.xcassets/AppIcon.appiconset/Maps-SDK-Demo-App_76.png a/Pods/GoogleMaps/Example/GoogleMapsDemos/MapsDemoAssets.xcassets/AppIcon.appiconset/Maps-SDK-Demo-App_76.png new file mode 100755 index 0000000..db89154 Binary files /dev/null and a/Pods/GoogleMaps/Example/GoogleMapsDemos/MapsDemoAssets.xcassets/AppIcon.appiconset/Maps-SDK-Demo-App_76.png differ diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/MapsDemoAssets.xcassets/Contents.json a/Pods/GoogleMaps/Example/GoogleMapsDemos/MapsDemoAssets.xcassets/Contents.json new file mode 100755 index 0000000..da4a164 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/MapsDemoAssets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/MasterViewController.h a/Pods/GoogleMaps/Example/GoogleMapsDemos/MasterViewController.h new file mode 100755 index 0000000..68a8fbe --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/MasterViewController.h @@ -0,0 +1,27 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 + +@class DemoAppDelegate; + +@interface MasterViewController : UITableViewController < + UISplitViewControllerDelegate, + UITableViewDataSource, + UITableViewDelegate> + +@property(nonatomic, weak) DemoAppDelegate *appDelegate; + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/MasterViewController.m a/Pods/GoogleMaps/Example/GoogleMapsDemos/MasterViewController.m new file mode 100755 index 0000000..02fe812 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/MasterViewController.m @@ -0,0 +1,160 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 "GoogleMapsDemos/MasterViewController.h" + +#import "GoogleMapsDemos/DemoAppDelegate.h" +#import "GoogleMapsDemos/Samples/Samples.h" +#import + + +@implementation MasterViewController { + NSArray *_demos; + NSArray *_demoSections; + BOOL _isPhone; + UIPopoverController *_popover; + UIBarButtonItem *_samplesButton; + __weak UIViewController *_controller; + CLLocationManager *_locationManager; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + _isPhone = [[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone; + + if (!_isPhone) { + self.clearsSelectionOnViewWillAppear = NO; + } else { + UIBarButtonItem *backButton = + [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"Back", @"Back") + style:UIBarButtonItemStylePlain + target:nil + action:nil]; + [self.navigationItem setBackBarButtonItem:backButton]; + } + + self.title = NSLocalizedString(@"Maps SDK Demos", @"Maps SDK Demos"); + self.title = [NSString stringWithFormat:@"%@: %@", self.title, [GMSServices SDKLongVersion]]; + + self.tableView.autoresizingMask = + UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; + self.tableView.delegate = self; + self.tableView.dataSource = self; + + _demoSections = [Samples loadSections]; + _demos = [Samples loadDemos]; + + if (!_isPhone) { + [self loadDemo:0 atIndex:0]; + } +} + +#pragma mark - UITableViewController + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return _demoSections.count; +} + +- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { + return 35.0; +} + +- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { + return [_demoSections objectAtIndex:section]; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + NSArray *demosInSection = [_demos objectAtIndex:section]; + return demosInSection.count; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView + cellForRowAtIndexPath:(NSIndexPath *)indexPath { + static NSString *cellIdentifier = @"Cell"; + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; + if (cell == nil) { + cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle + reuseIdentifier:cellIdentifier]; + + if (_isPhone) { + [cell setAccessoryType:UITableViewCellAccessoryDisclosureIndicator]; + } + } + + NSDictionary *demo = [[_demos objectAtIndex:indexPath.section] objectAtIndex:indexPath.row]; + cell.textLabel.text = [demo objectForKey:@"title"]; + cell.detailTextLabel.text = [demo objectForKey:@"description"]; + + return cell; +} + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + // The user has chosen a sample; load it and clear the selection! + [self loadDemo:indexPath.section atIndex:indexPath.row]; + [tableView deselectRowAtIndexPath:indexPath animated:YES]; +} + +#pragma mark - Split view + +- (void)splitViewController:(UISplitViewController *)splitController + willHideViewController:(UIViewController *)viewController + withBarButtonItem:(UIBarButtonItem *)barButtonItem + forPopoverController:(UIPopoverController *)popoverController { + _popover = popoverController; + _samplesButton = barButtonItem; + _samplesButton.title = NSLocalizedString(@"Samples", @"Samples"); + _samplesButton.style = UIBarButtonItemStyleDone; + [self updateSamplesButton]; +} + +- (void)splitViewController:(UISplitViewController *)splitController + willShowViewController:(UIViewController *)viewController + invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem { + _popover = nil; + _samplesButton = nil; + [self updateSamplesButton]; +} + +#pragma mark - Private methods + +- (void)loadDemo:(NSUInteger)section atIndex:(NSUInteger)index { + NSDictionary *demo = [[_demos objectAtIndex:section] objectAtIndex:index]; + UIViewController *controller = [[[demo objectForKey:@"controller"] alloc] init]; + _controller = controller; + + if (controller != nil) { + controller.title = [demo objectForKey:@"title"]; + + if (_isPhone) { + [self.navigationController pushViewController:controller animated:YES]; + } else { + [self.appDelegate setSample:controller]; + [_popover dismissPopoverAnimated:YES]; + } + + [self updateSamplesButton]; + } +} + +// This method is invoked when the left 'back' button in the split view +// controller on iPad should be updated (either made visible or hidden). +// It assumes that the left bar button item may be safely modified to contain +// the samples button. +- (void)updateSamplesButton { + _controller.navigationItem.leftBarButtonItem = _samplesButton; +} + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/LaunchScreen.storyboard a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/LaunchScreen.storyboard new file mode 100755 index 0000000..8c740c7 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/LaunchScreen.storyboard @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/Museum-Icons/h1.png a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/Museum-Icons/h1.png new file mode 100755 index 0000000..fff8197 Binary files /dev/null and a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/Museum-Icons/h1.png differ diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/Museum-Icons/h1@2x.png a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/Museum-Icons/h1@2x.png new file mode 100755 index 0000000..ce36c63 Binary files /dev/null and a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/Museum-Icons/h1@2x.png differ diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/Museum-Icons/spitfire.png a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/Museum-Icons/spitfire.png new file mode 100755 index 0000000..5c76dc9 Binary files /dev/null and a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/Museum-Icons/spitfire.png differ diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/Museum-Icons/spitfire@2x.png a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/Museum-Icons/spitfire@2x.png new file mode 100755 index 0000000..a09b75c Binary files /dev/null and a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/Museum-Icons/spitfire@2x.png differ diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/Museum-Icons/voyager.png a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/Museum-Icons/voyager.png new file mode 100755 index 0000000..d657f22 Binary files /dev/null and a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/Museum-Icons/voyager.png differ diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/Museum-Icons/voyager@2x.png a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/Museum-Icons/voyager@2x.png new file mode 100755 index 0000000..b2a668e Binary files /dev/null and a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/Museum-Icons/voyager@2x.png differ diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/Museum-Icons/x29.png a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/Museum-Icons/x29.png new file mode 100755 index 0000000..0edd3f1 Binary files /dev/null and a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/Museum-Icons/x29.png differ diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/Museum-Icons/x29@2x.png a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/Museum-Icons/x29@2x.png new file mode 100755 index 0000000..eda3d15 Binary files /dev/null and a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/Museum-Icons/x29@2x.png differ diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/aeroplane.png a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/aeroplane.png new file mode 100755 index 0000000..5114ee4 Binary files /dev/null and a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/aeroplane.png differ diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/aeroplane@2x.png a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/aeroplane@2x.png new file mode 100755 index 0000000..5c5012c Binary files /dev/null and a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/aeroplane@2x.png differ diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/ar.lproj/InfoPlist.strings a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/ar.lproj/InfoPlist.strings new file mode 100755 index 0000000..477b28f --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/ar.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/argentina-large.png a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/argentina-large.png new file mode 100755 index 0000000..b75247c Binary files /dev/null and a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/argentina-large.png differ diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/argentina.png a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/argentina.png new file mode 100755 index 0000000..9095376 Binary files /dev/null and a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/argentina.png differ diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/arrow.png a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/arrow.png new file mode 100755 index 0000000..8d8c3f7 Binary files /dev/null and a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/arrow.png differ diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/arrow@2x.png a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/arrow@2x.png new file mode 100755 index 0000000..4b0ff7c Binary files /dev/null and a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/arrow@2x.png differ diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/australia-large.png a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/australia-large.png new file mode 100755 index 0000000..787aed5 Binary files /dev/null and a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/australia-large.png differ diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/australia-large@2x.png a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/australia-large@2x.png new file mode 100755 index 0000000..15d4d2a Binary files /dev/null and a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/australia-large@2x.png differ diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/australia.png a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/australia.png new file mode 100755 index 0000000..12afbb9 Binary files /dev/null and a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/australia.png differ diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/boat.png a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/boat.png new file mode 100755 index 0000000..67221da Binary files /dev/null and a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/boat.png differ diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/boat@2x.png a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/boat@2x.png new file mode 100755 index 0000000..3f316d3 Binary files /dev/null and a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/boat@2x.png differ diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/botswana-large.png a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/botswana-large.png new file mode 100755 index 0000000..c150491 Binary files /dev/null and a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/botswana-large.png differ diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/botswana.png a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/botswana.png new file mode 100755 index 0000000..a006d99 Binary files /dev/null and a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/botswana.png differ diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/bulgaria-large.png a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/bulgaria-large.png new file mode 100755 index 0000000..0107da0 Binary files /dev/null and a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/bulgaria-large.png differ diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/bulgaria.png a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/bulgaria.png new file mode 100755 index 0000000..04cdb29 Binary files /dev/null and a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/bulgaria.png differ diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/ca.lproj/InfoPlist.strings a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/ca.lproj/InfoPlist.strings new file mode 100755 index 0000000..477b28f --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/ca.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/cs.lproj/InfoPlist.strings a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/cs.lproj/InfoPlist.strings new file mode 100755 index 0000000..477b28f --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/cs.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/da.lproj/InfoPlist.strings a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/da.lproj/InfoPlist.strings new file mode 100755 index 0000000..477b28f --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/da.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/de.lproj/InfoPlist.strings a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/de.lproj/InfoPlist.strings new file mode 100755 index 0000000..477b28f --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/de.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/el.lproj/InfoPlist.strings a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/el.lproj/InfoPlist.strings new file mode 100755 index 0000000..477b28f --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/el.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/en.lproj/InfoPlist.strings a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/en.lproj/InfoPlist.strings new file mode 100755 index 0000000..477b28f --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/en.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/en_GB.lproj/InfoPlist.strings a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/en_GB.lproj/InfoPlist.strings new file mode 100755 index 0000000..477b28f --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/en_GB.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/es.lproj/InfoPlist.strings a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/es.lproj/InfoPlist.strings new file mode 100755 index 0000000..477b28f --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/es.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/fi.lproj/InfoPlist.strings a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/fi.lproj/InfoPlist.strings new file mode 100755 index 0000000..477b28f --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/fi.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/fr.lproj/InfoPlist.strings a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/fr.lproj/InfoPlist.strings new file mode 100755 index 0000000..477b28f --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/fr.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/glow-marker.png a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/glow-marker.png new file mode 100755 index 0000000..eb0f596 Binary files /dev/null and a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/glow-marker.png differ diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/glow-marker@2x.png a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/glow-marker@2x.png new file mode 100755 index 0000000..e415c3f Binary files /dev/null and a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/glow-marker@2x.png differ diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/he.lproj/InfoPlist.strings a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/he.lproj/InfoPlist.strings new file mode 100755 index 0000000..477b28f --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/he.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/hr.lproj/InfoPlist.strings a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/hr.lproj/InfoPlist.strings new file mode 100755 index 0000000..477b28f --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/hr.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/hu.lproj/InfoPlist.strings a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/hu.lproj/InfoPlist.strings new file mode 100755 index 0000000..477b28f --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/hu.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/id.lproj/InfoPlist.strings a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/id.lproj/InfoPlist.strings new file mode 100755 index 0000000..477b28f --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/id.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/it.lproj/InfoPlist.strings a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/it.lproj/InfoPlist.strings new file mode 100755 index 0000000..477b28f --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/it.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/ja.lproj/InfoPlist.strings a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/ja.lproj/InfoPlist.strings new file mode 100755 index 0000000..477b28f --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/ja.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/ko.lproj/InfoPlist.strings a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/ko.lproj/InfoPlist.strings new file mode 100755 index 0000000..477b28f --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/ko.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/mapstyle-night.json a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/mapstyle-night.json new file mode 100755 index 0000000..1cbd616 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/mapstyle-night.json @@ -0,0 +1,191 @@ +[ + { + "featureType": "all", + "elementType": "geometry", + "stylers": [ + { + "color": "#242f3e" + } + ] + }, + { + "featureType": "all", + "elementType": "labels.text.stroke", + "stylers": [ + { + "lightness": -80 + } + ] + }, + { + "featureType": "administrative", + "elementType": "labels.text.fill", + "stylers": [ + { + "color": "#746855" + } + ] + }, + { + "featureType": "administrative.locality", + "elementType": "labels.text.fill", + "stylers": [ + { + "color": "#d59563" + } + ] + }, + { + "featureType": "poi", + "elementType": "labels.text.fill", + "stylers": [ + { + "color": "#d59563" + } + ] + }, + { + "featureType": "poi.park", + "elementType": "geometry", + "stylers": [ + { + "color": "#263c3f" + } + ] + }, + { + "featureType": "poi.park", + "elementType": "labels.text.fill", + "stylers": [ + { + "color": "#6b9a76" + } + ] + }, + { + "featureType": "road", + "elementType": "geometry.fill", + "stylers": [ + { + "color": "#2b3544" + } + ] + }, + { + "featureType": "road", + "elementType": "labels.text.fill", + "stylers": [ + { + "color": "#9ca5b3" + } + ] + }, + { + "featureType": "road.arterial", + "elementType": "geometry.fill", + "stylers": [ + { + "color": "#38414e" + } + ] + }, + { + "featureType": "road.arterial", + "elementType": "geometry.stroke", + "stylers": [ + { + "color": "#212a37" + } + ] + }, + { + "featureType": "road.highway", + "elementType": "geometry.fill", + "stylers": [ + { + "color": "#746855" + } + ] + }, + { + "featureType": "road.highway", + "elementType": "geometry.stroke", + "stylers": [ + { + "color": "#1f2835" + } + ] + }, + { + "featureType": "road.highway", + "elementType": "labels.text.fill", + "stylers": [ + { + "color": "#f3d19c" + } + ] + }, + { + "featureType": "road.local", + "elementType": "geometry.fill", + "stylers": [ + { + "color": "#38414e" + } + ] + }, + { + "featureType": "road.local", + "elementType": "geometry.stroke", + "stylers": [ + { + "color": "#212a37" + } + ] + }, + { + "featureType": "transit", + "elementType": "geometry", + "stylers": [ + { + "color": "#2f3948" + } + ] + }, + { + "featureType": "transit.station", + "elementType": "labels.text.fill", + "stylers": [ + { + "color": "#d59563" + } + ] + }, + { + "featureType": "water", + "elementType": "geometry", + "stylers": [ + { + "color": "#17263c" + } + ] + }, + { + "featureType": "water", + "elementType": "labels.text.fill", + "stylers": [ + { + "color": "#515c6d" + } + ] + }, + { + "featureType": "water", + "elementType": "labels.text.stroke", + "stylers": [ + { + "lightness": -20 + } + ] + } +] diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/mapstyle-retro.json a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/mapstyle-retro.json new file mode 100755 index 0000000..f6a16e8 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/mapstyle-retro.json @@ -0,0 +1,191 @@ +[ + { + "featureType": "all", + "elementType": "labels.text.fill", + "stylers": [ + { + "color": "#755f5d" + } + ] + }, + { + "featureType": "administrative", + "elementType": "geometry.fill", + "stylers": [ + { + "color": "#d4ccb9" + } + ] + }, + { + "featureType": "administrative.country", + "elementType": "geometry.stroke", + "stylers": [ + { + "color": "#baafae" + } + ] + }, + { + "featureType": "administrative.land_parcel", + "elementType": "geometry.stroke", + "stylers": [ + { + "color": "#d4ccb9" + } + ] + }, + { + "featureType": "landscape.man_made", + "elementType": "geometry.fill", + "stylers": [ + { + "color": "#ebe3cd" + } + ] + }, + { + "featureType": "landscape.natural", + "elementType": "geometry", + "stylers": [ + { + "color": "#ebe3cd" + } + ] + }, + { + "featureType": "landscape.natural", + "elementType": "geometry.fill", + "stylers": [ + { + "lightness": -10 + } + ] + }, + { + "featureType": "poi", + "elementType": "geometry.fill", + "stylers": [ + { + "color": "#d4ccb9" + } + ] + }, + { + "featureType": "poi", + "elementType": "labels.icon", + "stylers": [ + { + "hue": "#ff7f00" + } + ] + }, + { + "featureType": "poi.park", + "elementType": "geometry.fill", + "stylers": [ + { + "color": "#9ba56f" + } + ] + }, + { + "featureType": "road", + "elementType": "geometry.fill", + "stylers": [ + { + "color": "#f5f1e6" + } + ] + }, + { + "featureType": "road", + "elementType": "geometry.stroke", + "stylers": [ + { + "color": "#dfd8c3" + } + ] + }, + { + "featureType": "road.arterial", + "elementType": "geometry.fill", + "stylers": [ + { + "color": "#fdfcf8" + } + ] + }, + { + "featureType": "road.arterial", + "elementType": "geometry.stroke", + "stylers": [ + { + "color": "#e4e3df" + } + ] + }, + { + "featureType": "road.highway", + "elementType": "geometry.fill", + "stylers": [ + { + "color": "#f2cb77" + } + ] + }, + { + "featureType": "road.highway", + "elementType": "geometry.stroke", + "stylers": [ + { + "color": "#ecb43d" + } + ] + }, + { + "featureType": "road.highway.controlled_access", + "elementType": "geometry.fill", + "stylers": [ + { + "color": "#e98d58" + } + ] + }, + { + "featureType": "road.highway.controlled_access", + "elementType": "geometry.stroke", + "stylers": [ + { + "color": "#d27f4f" + } + ] + }, + { + "featureType": "transit.line", + "elementType": "geometry", + "stylers": [ + { + "color": "#d4ccb9" + } + ] + }, + { + "featureType": "transit.station.airport", + "elementType": "geometry.fill", + "stylers": [ + { + "color": "#d4ccb9" + } + ] + }, + { + "featureType": "water", + "elementType": "geometry.fill", + "stylers": [ + { + "color": "#b9d3c2" + } + ] + } +] diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/mapstyle-silver.json a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/mapstyle-silver.json new file mode 100755 index 0000000..340e5d6 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/mapstyle-silver.json @@ -0,0 +1,101 @@ +[ + { + "featureType": "all", + "elementType": "geometry", + "stylers": [ + { + "color": "#f5f5f5" + } + ] + }, + { + "featureType": "all", + "elementType": "labels.icon", + "stylers": [ + { + "saturation": -100 + } + ] + }, + { + "featureType": "all", + "elementType": "labels.text", + "stylers": [ + { + "saturation": -100 + } + ] + }, + { + "featureType": "poi", + "elementType": "geometry", + "stylers": [ + { + "color": "#eeeeee" + } + ] + }, + { + "featureType": "poi.park", + "elementType": "geometry", + "stylers": [ + { + "color": "#e5e5e5" + } + ] + }, + { + "featureType": "road", + "elementType": "geometry", + "stylers": [ + { + "color": "#ffffff" + } + ] + }, + { + "featureType": "road.highway", + "elementType": "geometry", + "stylers": [ + { + "color": "#dadada" + } + ] + }, + { + "featureType": "road.highway", + "elementType": "labels.icon", + "stylers": [ + { + "lightness": 30 + } + ] + }, + { + "featureType": "transit.line", + "elementType": "geometry", + "stylers": [ + { + "color": "#e5e5e5" + } + ] + }, + { + "featureType": "transit.station", + "elementType": "geometry", + "stylers": [ + { + "color": "#eeeeee" + } + ] + }, + { + "featureType": "water", + "elementType": "geometry", + "stylers": [ + { + "color": "#c9c9c9" + } + ] + } +] diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/ms.lproj/InfoPlist.strings a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/ms.lproj/InfoPlist.strings new file mode 100755 index 0000000..477b28f --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/ms.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/museum-exhibits.json a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/museum-exhibits.json new file mode 100755 index 0000000..25b0a5d --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/museum-exhibits.json @@ -0,0 +1,30 @@ +[ + { + "key": "h1", + "name": "Hughes H-1", + "lat": 38.8879, + "lng": -77.02085, + "level": "1" + }, + { + "key": "voyager", + "name": "Rutan Voyager", + "lat": 38.8880, + "lng": -77.0199, + "level": "1" + }, + { + "key": "spitfire", + "name": "Supermarine Spitfire", + "lat": 38.8879, + "lng": -77.0208, + "level": "2" + }, + { + "key": "x29", + "name": "Grumman X-29", + "lat": 38.88845, + "lng": -77.01875, + "level": "2" + } +] diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/nb.lproj/InfoPlist.strings a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/nb.lproj/InfoPlist.strings new file mode 100755 index 0000000..477b28f --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/nb.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/newark_nj_1922.jpg a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/newark_nj_1922.jpg new file mode 100755 index 0000000..1f4ae59 Binary files /dev/null and a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/newark_nj_1922.jpg differ diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/nl.lproj/InfoPlist.strings a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/nl.lproj/InfoPlist.strings new file mode 100755 index 0000000..477b28f --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/nl.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/pl.lproj/InfoPlist.strings a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/pl.lproj/InfoPlist.strings new file mode 100755 index 0000000..477b28f --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/pl.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/popup_santa.png a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/popup_santa.png new file mode 100755 index 0000000..f2968ef Binary files /dev/null and a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/popup_santa.png differ diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/popup_santa@2x.png a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/popup_santa@2x.png new file mode 100755 index 0000000..3f90828 Binary files /dev/null and a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/popup_santa@2x.png differ diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/pt.lproj/InfoPlist.strings a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/pt.lproj/InfoPlist.strings new file mode 100755 index 0000000..477b28f --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/pt.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/pt_PT.lproj/InfoPlist.strings a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/pt_PT.lproj/InfoPlist.strings new file mode 100755 index 0000000..477b28f --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/pt_PT.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/ro.lproj/InfoPlist.strings a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/ro.lproj/InfoPlist.strings new file mode 100755 index 0000000..477b28f --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/ro.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/ru.lproj/InfoPlist.strings a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/ru.lproj/InfoPlist.strings new file mode 100755 index 0000000..477b28f --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/ru.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/sk.lproj/InfoPlist.strings a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/sk.lproj/InfoPlist.strings new file mode 100755 index 0000000..477b28f --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/sk.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/step1.png a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/step1.png new file mode 100755 index 0000000..1cac697 Binary files /dev/null and a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/step1.png differ diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/step1@2x.png a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/step1@2x.png new file mode 100755 index 0000000..8d99108 Binary files /dev/null and a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/step1@2x.png differ diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/step2.png a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/step2.png new file mode 100755 index 0000000..18ee7f2 Binary files /dev/null and a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/step2.png differ diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/step2@2x.png a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/step2@2x.png new file mode 100755 index 0000000..5c37b1d Binary files /dev/null and a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/step2@2x.png differ diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/step3.png a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/step3.png new file mode 100755 index 0000000..795b90f Binary files /dev/null and a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/step3.png differ diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/step3@2x.png a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/step3@2x.png new file mode 100755 index 0000000..950d754 Binary files /dev/null and a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/step3@2x.png differ diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/step4.png a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/step4.png new file mode 100755 index 0000000..3d7416b Binary files /dev/null and a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/step4.png differ diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/step4@2x.png a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/step4@2x.png new file mode 100755 index 0000000..7ae50e5 Binary files /dev/null and a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/step4@2x.png differ diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/step5.png a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/step5.png new file mode 100755 index 0000000..3a8bd1e Binary files /dev/null and a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/step5.png differ diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/step5@2x.png a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/step5@2x.png new file mode 100755 index 0000000..236f3a0 Binary files /dev/null and a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/step5@2x.png differ diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/step6.png a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/step6.png new file mode 100755 index 0000000..c6580e6 Binary files /dev/null and a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/step6.png differ diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/step6@2x.png a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/step6@2x.png new file mode 100755 index 0000000..2bed812 Binary files /dev/null and a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/step6@2x.png differ diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/step7.png a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/step7.png new file mode 100755 index 0000000..4e17178 Binary files /dev/null and a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/step7.png differ diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/step7@2x.png a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/step7@2x.png new file mode 100755 index 0000000..88932e6 Binary files /dev/null and a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/step7@2x.png differ diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/step8.png a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/step8.png new file mode 100755 index 0000000..7eefde7 Binary files /dev/null and a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/step8.png differ diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/step8@2x.png a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/step8@2x.png new file mode 100755 index 0000000..6f49e3d Binary files /dev/null and a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/step8@2x.png differ diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/sv.lproj/InfoPlist.strings a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/sv.lproj/InfoPlist.strings new file mode 100755 index 0000000..477b28f --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/sv.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/th.lproj/InfoPlist.strings a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/th.lproj/InfoPlist.strings new file mode 100755 index 0000000..477b28f --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/th.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/tr.lproj/InfoPlist.strings a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/tr.lproj/InfoPlist.strings new file mode 100755 index 0000000..477b28f --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/tr.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/track.json a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/track.json new file mode 100755 index 0000000..6a0ed63 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/track.json @@ -0,0 +1 @@ +[{"lat": "44.145331", "lng": "9.661942", "elevation": "173.8000030517578", "time": "2013-09-20T08:40:00.855Z"}, {"lat": "44.145157", "lng": "9.661917", "elevation": "177.3000030517578", "time": "2013-09-20T08:40:01.824Z"}, {"lat": "44.14505", "lng": "9.662049", "elevation": "170.60000610351563", "time": "2013-09-20T08:40:02.945Z"}, {"lat": "44.145", "lng": "9.662165", "elevation": "156.5", "time": "2013-09-20T08:40:03.828Z"}, {"lat": "44.144918", "lng": "9.662227", "elevation": "130.6999969482422", "time": "2013-09-20T08:40:04.823Z"}, {"lat": "44.144945", "lng": "9.662122", "elevation": "149.5", "time": "2013-09-20T08:40:06.123Z"}, {"lat": "44.14503", "lng": "9.662141", "elevation": "152.89999389648438", "time": "2013-09-20T08:40:07.122Z"}, {"lat": "44.144943", "lng": "9.662169", "elevation": "155.3000030517578", "time": "2013-09-20T08:40:19.117Z"}, {"lat": "44.144937", "lng": "9.66217", "elevation": "155.5", "time": "2013-09-20T08:40:20.157Z"}, {"lat": "44.144933", "lng": "9.662171", "elevation": "154.8000030517578", "time": "2013-09-20T08:40:22.132Z"}, {"lat": "44.144933", "lng": "9.662173", "elevation": "155.0", "time": "2013-09-20T08:40:23.141Z"}, {"lat": "44.144937", "lng": "9.662186", "elevation": "155.8000030517578", "time": "2013-09-20T08:40:45.224Z"}, {"lat": "44.144934", "lng": "9.66219", "elevation": "158.5", "time": "2013-09-20T08:40:46.191Z"}, {"lat": "44.144911", "lng": "9.662248", "elevation": "161.6999969482422", "time": "2013-09-20T08:40:59.133Z"}, {"lat": "44.144911", "lng": "9.662249", "elevation": "161.8000030517578", "time": "2013-09-20T08:41:00.124Z"}, {"lat": "44.14491", "lng": "9.662258", "elevation": "161.6999969482422", "time": "2013-09-20T08:41:09.127Z"}, {"lat": "44.144907", "lng": "9.662263", "elevation": "162.0", "time": "2013-09-20T08:41:10.185Z"}, {"lat": "44.144884", "lng": "9.662378", "elevation": "161.3000030517578", "time": "2013-09-20T08:41:17.137Z"}, {"lat": "44.144879", "lng": "9.662397", "elevation": "161.1999969482422", "time": "2013-09-20T08:41:18.211Z"}, {"lat": "44.144874", "lng": "9.662517", "elevation": "163.0", "time": "2013-09-20T08:41:26.217Z"}, {"lat": "44.144877", "lng": "9.66253", "elevation": "163.39999389648438", "time": "2013-09-20T08:41:27.220Z"}, {"lat": "44.144812", "lng": "9.662617", "elevation": "166.8000030517578", "time": "2013-09-20T08:41:36.137Z"}, {"lat": "44.144806", "lng": "9.662625", "elevation": "166.89999389648438", "time": "2013-09-20T08:41:37.146Z"}, {"lat": "44.14477", "lng": "9.662604", "elevation": "167.10000610351563", "time": "2013-09-20T08:41:49.143Z"}, {"lat": "44.14477", "lng": "9.662607", "elevation": "167.1999969482422", "time": "2013-09-20T08:41:50.138Z"}, {"lat": "44.144763", "lng": "9.662619", "elevation": "168.0", "time": "2013-09-20T08:41:58.146Z"}, {"lat": "44.14476", "lng": "9.662618", "elevation": "168.3000030517578", "time": "2013-09-20T08:41:59.133Z"}, {"lat": "44.144755", "lng": "9.662616", "elevation": "168.5", "time": "2013-09-20T08:42:01.147Z"}, {"lat": "44.144755", "lng": "9.662616", "elevation": "168.6999969482422", "time": "2013-09-20T08:42:02.133Z"}, {"lat": "44.144754", "lng": "9.662623", "elevation": "169.8000030517578", "time": "2013-09-20T08:43:18.202Z"}, {"lat": "44.144753", "lng": "9.662633", "elevation": "169.39999389648438", "time": "2013-09-20T08:43:19.274Z"}, {"lat": "44.144768", "lng": "9.662683", "elevation": "173.8000030517578", "time": "2013-09-20T08:43:28.140Z"}, {"lat": "44.144768", "lng": "9.662684", "elevation": "174.0", "time": "2013-09-20T08:43:29.177Z"}, {"lat": "44.144764", "lng": "9.662687", "elevation": "172.89999389648438", "time": "2013-09-20T08:43:33.140Z"}, {"lat": "44.144761", "lng": "9.662692", "elevation": "173.3000030517578", "time": "2013-09-20T08:43:34.147Z"}, {"lat": "44.144755", "lng": "9.662699", "elevation": "173.1999969482422", "time": "2013-09-20T08:43:37.220Z"}, {"lat": "44.144754", "lng": "9.6627", "elevation": "173.1999969482422", "time": "2013-09-20T08:43:38.164Z"}, {"lat": "44.144755", "lng": "9.662702", "elevation": "173.3000030517578", "time": "2013-09-20T08:43:43.148Z"}, {"lat": "44.144756", "lng": "9.662709", "elevation": "172.6999969482422", "time": "2013-09-20T08:43:44.141Z"}, {"lat": "44.144716", "lng": "9.662816", "elevation": "179.5", "time": "2013-09-20T08:43:51.157Z"}, {"lat": "44.144717", "lng": "9.662831", "elevation": "180.8000030517578", "time": "2013-09-20T08:43:52.141Z"}, {"lat": "44.1447", "lng": "9.662945", "elevation": "182.3000030517578", "time": "2013-09-20T08:44:01.165Z"}, {"lat": "44.144696", "lng": "9.662956", "elevation": "181.89999389648438", "time": "2013-09-20T08:44:02.153Z"}, {"lat": "44.144679", "lng": "9.662965", "elevation": "181.6999969482422", "time": "2013-09-20T08:44:08.135Z"}, {"lat": "44.144679", "lng": "9.662966", "elevation": "181.60000610351563", "time": "2013-09-20T08:44:09.139Z"}, {"lat": "44.14469", "lng": "9.66299", "elevation": "183.1999969482422", "time": "2013-09-20T08:44:26.146Z"}, {"lat": "44.144687", "lng": "9.662998", "elevation": "182.89999389648438", "time": "2013-09-20T08:44:27.145Z"}, {"lat": "44.144661", "lng": "9.663117", "elevation": "193.1999969482422", "time": "2013-09-20T08:44:38.177Z"}, {"lat": "44.144658", "lng": "9.66312", "elevation": "193.1999969482422", "time": "2013-09-20T08:44:39.232Z"}, {"lat": "44.144581", "lng": "9.663173", "elevation": "199.3000030517578", "time": "2013-09-20T08:44:51.156Z"}, {"lat": "44.144572", "lng": "9.66319", "elevation": "199.39999389648438", "time": "2013-09-20T08:44:52.153Z"}, {"lat": "44.144518", "lng": "9.663271", "elevation": "201.1999969482422", "time": "2013-09-20T08:44:57.156Z"}, {"lat": "44.144506", "lng": "9.663276", "elevation": "202.5", "time": "2013-09-20T08:44:58.141Z"}, {"lat": "44.144498", "lng": "9.663277", "elevation": "202.3000030517578", "time": "2013-09-20T08:45:02.212Z"}, {"lat": "44.144506", "lng": "9.663277", "elevation": "201.8000030517578", "time": "2013-09-20T08:45:03.249Z"}, {"lat": "44.144513", "lng": "9.66328", "elevation": "201.1999969482422", "time": "2013-09-20T08:45:04.186Z"}, {"lat": "44.144526", "lng": "9.663302", "elevation": "199.5", "time": "2013-09-20T08:45:09.163Z"}, {"lat": "44.144526", "lng": "9.663298", "elevation": "199.89999389648438", "time": "2013-09-20T08:45:10.157Z"}, {"lat": "44.144527", "lng": "9.663291", "elevation": "200.6999969482422", "time": "2013-09-20T08:45:11.229Z"}, {"lat": "44.144527", "lng": "9.663281", "elevation": "201.8000030517578", "time": "2013-09-20T08:45:12.229Z"}, {"lat": "44.144522", "lng": "9.663257", "elevation": "202.0", "time": "2013-09-20T08:45:17.165Z"}, {"lat": "44.14452", "lng": "9.663259", "elevation": "201.60000610351563", "time": "2013-09-20T08:45:18.220Z"}, {"lat": "44.144511", "lng": "9.663258", "elevation": "202.0", "time": "2013-09-20T08:45:27.262Z"}, {"lat": "44.144503", "lng": "9.663259", "elevation": "200.39999389648438", "time": "2013-09-20T08:45:28.141Z"}, {"lat": "44.144419", "lng": "9.663262", "elevation": "198.3000030517578", "time": "2013-09-20T08:45:33.164Z"}, {"lat": "44.144404", "lng": "9.663262", "elevation": "197.3000030517578", "time": "2013-09-20T08:45:34.204Z"}, {"lat": "44.144364", "lng": "9.663282", "elevation": "198.3000030517578", "time": "2013-09-20T08:45:42.142Z"}, {"lat": "44.144366", "lng": "9.663283", "elevation": "198.10000610351563", "time": "2013-09-20T08:45:43.149Z"}, {"lat": "44.144362", "lng": "9.663275", "elevation": "199.3000030517578", "time": "2013-09-20T08:46:03.152Z"}, {"lat": "44.144358", "lng": "9.663284", "elevation": "199.1999969482422", "time": "2013-09-20T08:46:04.142Z"}, {"lat": "44.144319", "lng": "9.663392", "elevation": "201.60000610351563", "time": "2013-09-20T08:46:12.160Z"}, {"lat": "44.144313", "lng": "9.663404", "elevation": "201.0", "time": "2013-09-20T08:46:13.153Z"}, {"lat": "44.144264", "lng": "9.663501", "elevation": "204.89999389648438", "time": "2013-09-20T08:46:20.144Z"}, {"lat": "44.144256", "lng": "9.663513", "elevation": "206.60000610351563", "time": "2013-09-20T08:46:21.170Z"}, {"lat": "44.144207", "lng": "9.663617", "elevation": "207.89999389648438", "time": "2013-09-20T08:46:31.257Z"}, {"lat": "44.144203", "lng": "9.663625", "elevation": "208.6999969482422", "time": "2013-09-20T08:46:32.221Z"}, {"lat": "44.144194", "lng": "9.6637", "elevation": "210.10000610351563", "time": "2013-09-20T08:46:44.148Z"}, {"lat": "44.144195", "lng": "9.663701", "elevation": "210.0", "time": "2013-09-20T08:46:45.162Z"}, {"lat": "44.144193", "lng": "9.663706", "elevation": "210.0", "time": "2013-09-20T08:47:02.176Z"}, {"lat": "44.144194", "lng": "9.663712", "elevation": "209.39999389648438", "time": "2013-09-20T08:47:03.180Z"}, {"lat": "44.144242", "lng": "9.663813", "elevation": "205.8000030517578", "time": "2013-09-20T08:47:19.246Z"}, {"lat": "44.144247", "lng": "9.663822", "elevation": "205.1999969482422", "time": "2013-09-20T08:47:20.183Z"}, {"lat": "44.144316", "lng": "9.663899", "elevation": "202.10000610351563", "time": "2013-09-20T08:47:34.231Z"}, {"lat": "44.14432", "lng": "9.663909", "elevation": "201.89999389648438", "time": "2013-09-20T08:47:35.229Z"}, {"lat": "44.144355", "lng": "9.66397", "elevation": "205.89999389648438", "time": "2013-09-20T08:47:43.176Z"}, {"lat": "44.144354", "lng": "9.663968", "elevation": "205.8000030517578", "time": "2013-09-20T08:47:44.172Z"}, {"lat": "44.144359", "lng": "9.663989", "elevation": "207.8000030517578", "time": "2013-09-20T08:47:53.213Z"}, {"lat": "44.14436", "lng": "9.663996", "elevation": "207.89999389648438", "time": "2013-09-20T08:47:54.162Z"}, {"lat": "44.144404", "lng": "9.664094", "elevation": "210.10000610351563", "time": "2013-09-20T08:48:01.203Z"}, {"lat": "44.14441", "lng": "9.664112", "elevation": "209.89999389648438", "time": "2013-09-20T08:48:02.167Z"}, {"lat": "44.144445", "lng": "9.664217", "elevation": "208.39999389648438", "time": "2013-09-20T08:48:09.225Z"}, {"lat": "44.14445", "lng": "9.664226", "elevation": "207.39999389648438", "time": "2013-09-20T08:48:10.169Z"}, {"lat": "44.14451", "lng": "9.664318", "elevation": "207.6999969482422", "time": "2013-09-20T08:48:19.190Z"}, {"lat": "44.144516", "lng": "9.664334", "elevation": "206.0", "time": "2013-09-20T08:48:20.177Z"}, {"lat": "44.144565", "lng": "9.664426", "elevation": "205.0", "time": "2013-09-20T08:48:27.171Z"}, {"lat": "44.144574", "lng": "9.664434", "elevation": "205.10000610351563", "time": "2013-09-20T08:48:28.180Z"}, {"lat": "44.144609", "lng": "9.664543", "elevation": "206.6999969482422", "time": "2013-09-20T08:48:40.184Z"}, {"lat": "44.14461", "lng": "9.664554", "elevation": "206.39999389648438", "time": "2013-09-20T08:48:41.182Z"}, {"lat": "44.144638", "lng": "9.664672", "elevation": "205.10000610351563", "time": "2013-09-20T08:48:51.188Z"}, {"lat": "44.144642", "lng": "9.664682", "elevation": "205.60000610351563", "time": "2013-09-20T08:48:52.230Z"}, {"lat": "44.144682", "lng": "9.664781", "elevation": "205.8000030517578", "time": "2013-09-20T08:49:02.254Z"}, {"lat": "44.144687", "lng": "9.664793", "elevation": "206.0", "time": "2013-09-20T08:49:03.262Z"}, {"lat": "44.144653", "lng": "9.664906", "elevation": "206.60000610351563", "time": "2013-09-20T08:49:15.287Z"}, {"lat": "44.14465", "lng": "9.664912", "elevation": "207.10000610351563", "time": "2013-09-20T08:49:16.261Z"}, {"lat": "44.144651", "lng": "9.664916", "elevation": "205.89999389648438", "time": "2013-09-20T08:49:18.271Z"}, {"lat": "44.144656", "lng": "9.664914", "elevation": "205.89999389648438", "time": "2013-09-20T08:49:19.343Z"}, {"lat": "44.144661", "lng": "9.664911", "elevation": "206.0", "time": "2013-09-20T08:49:20.304Z"}, {"lat": "44.144685", "lng": "9.664912", "elevation": "205.89999389648438", "time": "2013-09-20T08:49:28.388Z"}, {"lat": "44.144686", "lng": "9.664914", "elevation": "206.0", "time": "2013-09-20T08:49:29.371Z"}, {"lat": "44.144687", "lng": "9.66492", "elevation": "205.89999389648438", "time": "2013-09-20T08:49:35.323Z"}, {"lat": "44.144691", "lng": "9.664926", "elevation": "205.39999389648438", "time": "2013-09-20T08:49:36.247Z"}, {"lat": "44.144753", "lng": "9.665007", "elevation": "203.6999969482422", "time": "2013-09-20T08:49:42.194Z"}, {"lat": "44.144764", "lng": "9.665024", "elevation": "203.89999389648438", "time": "2013-09-20T08:49:43.371Z"}, {"lat": "44.144819", "lng": "9.66512", "elevation": "204.10000610351563", "time": "2013-09-20T08:49:51.386Z"}, {"lat": "44.14482", "lng": "9.665126", "elevation": "204.3000030517578", "time": "2013-09-20T08:49:52.321Z"}, {"lat": "44.144856", "lng": "9.665239", "elevation": "205.89999389648438", "time": "2013-09-20T08:50:03.402Z"}, {"lat": "44.144859", "lng": "9.665241", "elevation": "205.60000610351563", "time": "2013-09-20T08:50:04.370Z"}, {"lat": "44.144862", "lng": "9.665246", "elevation": "205.5", "time": "2013-09-20T08:50:07.377Z"}, {"lat": "44.144862", "lng": "9.665247", "elevation": "205.5", "time": "2013-09-20T08:50:08.322Z"}, {"lat": "44.144864", "lng": "9.665254", "elevation": "206.1999969482422", "time": "2013-09-20T08:50:17.332Z"}, {"lat": "44.144867", "lng": "9.665261", "elevation": "206.10000610351563", "time": "2013-09-20T08:50:18.349Z"}, {"lat": "44.144931", "lng": "9.665342", "elevation": "207.6999969482422", "time": "2013-09-20T08:50:23.347Z"}, {"lat": "44.144945", "lng": "9.66536", "elevation": "208.0", "time": "2013-09-20T08:50:24.325Z"}, {"lat": "44.144995", "lng": "9.665457", "elevation": "206.39999389648438", "time": "2013-09-20T08:50:30.244Z"}, {"lat": "44.144997", "lng": "9.665466", "elevation": "206.3000030517578", "time": "2013-09-20T08:50:31.187Z"}, {"lat": "44.144991", "lng": "9.6655", "elevation": "206.1999969482422", "time": "2013-09-20T08:50:41.277Z"}, {"lat": "44.144991", "lng": "9.665502", "elevation": "205.8000030517578", "time": "2013-09-20T08:50:42.244Z"}, {"lat": "44.144995", "lng": "9.665519", "elevation": "204.1999969482422", "time": "2013-09-20T08:50:54.344Z"}, {"lat": "44.144995", "lng": "9.665528", "elevation": "204.1999969482422", "time": "2013-09-20T08:50:55.360Z"}, {"lat": "44.144992", "lng": "9.665644", "elevation": "206.8000030517578", "time": "2013-09-20T08:51:02.176Z"}, {"lat": "44.14499", "lng": "9.665659", "elevation": "206.6999969482422", "time": "2013-09-20T08:51:03.176Z"}, {"lat": "44.145013", "lng": "9.665772", "elevation": "204.60000610351563", "time": "2013-09-20T08:51:12.336Z"}, {"lat": "44.145022", "lng": "9.665786", "elevation": "204.10000610351563", "time": "2013-09-20T08:51:13.305Z"}, {"lat": "44.14507", "lng": "9.665875", "elevation": "204.3000030517578", "time": "2013-09-20T08:51:20.280Z"}, {"lat": "44.145072", "lng": "9.665891", "elevation": "202.89999389648438", "time": "2013-09-20T08:51:21.363Z"}, {"lat": "44.145067", "lng": "9.666001", "elevation": "197.8000030517578", "time": "2013-09-20T08:51:37.323Z"}, {"lat": "44.145074", "lng": "9.666025", "elevation": "197.5", "time": "2013-09-20T08:51:38.322Z"}, {"lat": "44.145099", "lng": "9.666122", "elevation": "196.3000030517578", "time": "2013-09-20T08:51:41.330Z"}, {"lat": "44.145112", "lng": "9.666149", "elevation": "196.3000030517578", "time": "2013-09-20T08:51:42.355Z"}, {"lat": "44.14516", "lng": "9.666228", "elevation": "197.6999969482422", "time": "2013-09-20T08:51:46.256Z"}, {"lat": "44.14517", "lng": "9.666247", "elevation": "197.3000030517578", "time": "2013-09-20T08:51:47.227Z"}, {"lat": "44.145223", "lng": "9.666331", "elevation": "199.89999389648438", "time": "2013-09-20T08:51:54.211Z"}, {"lat": "44.145231", "lng": "9.666343", "elevation": "201.0", "time": "2013-09-20T08:51:55.178Z"}, {"lat": "44.145287", "lng": "9.666436", "elevation": "202.60000610351563", "time": "2013-09-20T08:52:02.194Z"}, {"lat": "44.145294", "lng": "9.666447", "elevation": "202.89999389648438", "time": "2013-09-20T08:52:03.228Z"}, {"lat": "44.145377", "lng": "9.666465", "elevation": "201.0", "time": "2013-09-20T08:52:13.181Z"}, {"lat": "44.145386", "lng": "9.666461", "elevation": "201.10000610351563", "time": "2013-09-20T08:52:14.212Z"}, {"lat": "44.14542", "lng": "9.666575", "elevation": "199.1999969482422", "time": "2013-09-20T08:52:29.348Z"}, {"lat": "44.145421", "lng": "9.666594", "elevation": "199.0", "time": "2013-09-20T08:52:30.327Z"}, {"lat": "44.145417", "lng": "9.666709", "elevation": "195.39999389648438", "time": "2013-09-20T08:52:36.199Z"}, {"lat": "44.145418", "lng": "9.666721", "elevation": "196.10000610351563", "time": "2013-09-20T08:52:37.197Z"}, {"lat": "44.145423", "lng": "9.666843", "elevation": "195.6999969482422", "time": "2013-09-20T08:52:49.192Z"}, {"lat": "44.145426", "lng": "9.666855", "elevation": "195.10000610351563", "time": "2013-09-20T08:52:50.233Z"}, {"lat": "44.145455", "lng": "9.666967", "elevation": "194.1999969482422", "time": "2013-09-20T08:52:58.191Z"}, {"lat": "44.145459", "lng": "9.66698", "elevation": "194.0", "time": "2013-09-20T08:52:59.184Z"}, {"lat": "44.145496", "lng": "9.667082", "elevation": "191.10000610351563", "time": "2013-09-20T08:53:09.183Z"}, {"lat": "44.1455", "lng": "9.667098", "elevation": "191.1999969482422", "time": "2013-09-20T08:53:10.200Z"}, {"lat": "44.145552", "lng": "9.667184", "elevation": "191.8000030517578", "time": "2013-09-20T08:53:16.329Z"}, {"lat": "44.145557", "lng": "9.667196", "elevation": "191.8000030517578", "time": "2013-09-20T08:53:17.356Z"}, {"lat": "44.145562", "lng": "9.667214", "elevation": "189.60000610351563", "time": "2013-09-20T08:53:22.291Z"}, {"lat": "44.14556", "lng": "9.667212", "elevation": "189.6999969482422", "time": "2013-09-20T08:53:23.241Z"}, {"lat": "44.145553", "lng": "9.66721", "elevation": "188.6999969482422", "time": "2013-09-20T08:53:50.175Z"}, {"lat": "44.145559", "lng": "9.66721", "elevation": "189.1999969482422", "time": "2013-09-20T08:53:51.175Z"}, {"lat": "44.145641", "lng": "9.667257", "elevation": "192.10000610351563", "time": "2013-09-20T08:53:58.197Z"}, {"lat": "44.14565", "lng": "9.667267", "elevation": "192.5", "time": "2013-09-20T08:53:59.181Z"}, {"lat": "44.145691", "lng": "9.66735", "elevation": "193.1999969482422", "time": "2013-09-20T08:54:05.205Z"}, {"lat": "44.145695", "lng": "9.667379", "elevation": "193.39999389648438", "time": "2013-09-20T08:54:06.190Z"}, {"lat": "44.145706", "lng": "9.66749", "elevation": "194.60000610351563", "time": "2013-09-20T08:54:09.182Z"}, {"lat": "44.145712", "lng": "9.667534", "elevation": "195.3000030517578", "time": "2013-09-20T08:54:10.213Z"}, {"lat": "44.145739", "lng": "9.667573", "elevation": "194.89999389648438", "time": "2013-09-20T08:54:19.207Z"}, {"lat": "44.145739", "lng": "9.667574", "elevation": "194.0", "time": "2013-09-20T08:54:20.196Z"}, {"lat": "44.14574", "lng": "9.667582", "elevation": "195.39999389648438", "time": "2013-09-20T08:54:22.213Z"}, {"lat": "44.145741", "lng": "9.667587", "elevation": "194.5", "time": "2013-09-20T08:54:23.191Z"}, {"lat": "44.145733", "lng": "9.667644", "elevation": "198.1999969482422", "time": "2013-09-20T08:54:32.207Z"}, {"lat": "44.145733", "lng": "9.667643", "elevation": "198.89999389648438", "time": "2013-09-20T08:54:33.214Z"}, {"lat": "44.145739", "lng": "9.667633", "elevation": "198.10000610351563", "time": "2013-09-20T08:54:42.192Z"}, {"lat": "44.145741", "lng": "9.667637", "elevation": "198.39999389648438", "time": "2013-09-20T08:54:43.214Z"}, {"lat": "44.145724", "lng": "9.667754", "elevation": "199.8000030517578", "time": "2013-09-20T08:54:52.188Z"}, {"lat": "44.145723", "lng": "9.667775", "elevation": "198.5", "time": "2013-09-20T08:54:53.202Z"}, {"lat": "44.145703", "lng": "9.667889", "elevation": "197.0", "time": "2013-09-20T08:55:07.208Z"}, {"lat": "44.145707", "lng": "9.667901", "elevation": "196.6999969482422", "time": "2013-09-20T08:55:08.242Z"}, {"lat": "44.14571", "lng": "9.667922", "elevation": "195.6999969482422", "time": "2013-09-20T08:55:13.217Z"}, {"lat": "44.145707", "lng": "9.667921", "elevation": "196.6999969482422", "time": "2013-09-20T08:55:14.251Z"}, {"lat": "44.145704", "lng": "9.66792", "elevation": "196.1999969482422", "time": "2013-09-20T08:55:15.210Z"}, {"lat": "44.1457", "lng": "9.667919", "elevation": "196.60000610351563", "time": "2013-09-20T08:55:16.230Z"}, {"lat": "44.145617", "lng": "9.667918", "elevation": "196.3000030517578", "time": "2013-09-20T08:55:29.211Z"}, {"lat": "44.145603", "lng": "9.667908", "elevation": "197.39999389648438", "time": "2013-09-20T08:55:30.197Z"}, {"lat": "44.145516", "lng": "9.667888", "elevation": "197.10000610351563", "time": "2013-09-20T08:55:37.203Z"}, {"lat": "44.145508", "lng": "9.667883", "elevation": "198.60000610351563", "time": "2013-09-20T08:55:38.212Z"}, {"lat": "44.14545", "lng": "9.667852", "elevation": "196.8000030517578", "time": "2013-09-20T08:55:56.193Z"}, {"lat": "44.14545", "lng": "9.667852", "elevation": "197.0", "time": "2013-09-20T08:55:57.198Z"}, {"lat": "44.145443", "lng": "9.667863", "elevation": "195.6999969482422", "time": "2013-09-20T08:56:10.210Z"}, {"lat": "44.145437", "lng": "9.667863", "elevation": "198.1999969482422", "time": "2013-09-20T08:56:11.230Z"}, {"lat": "44.145349", "lng": "9.667869", "elevation": "197.10000610351563", "time": "2013-09-20T08:56:18.200Z"}, {"lat": "44.145335", "lng": "9.66787", "elevation": "198.0", "time": "2013-09-20T08:56:19.231Z"}, {"lat": "44.145254", "lng": "9.667841", "elevation": "193.89999389648438", "time": "2013-09-20T08:56:25.279Z"}, {"lat": "44.145241", "lng": "9.667831", "elevation": "192.6999969482422", "time": "2013-09-20T08:56:26.230Z"}, {"lat": "44.145155", "lng": "9.667803", "elevation": "194.10000610351563", "time": "2013-09-20T08:56:32.207Z"}, {"lat": "44.145141", "lng": "9.667805", "elevation": "194.3000030517578", "time": "2013-09-20T08:56:33.233Z"}, {"lat": "44.145086", "lng": "9.667807", "elevation": "191.8000030517578", "time": "2013-09-20T08:56:46.216Z"}, {"lat": "44.145085", "lng": "9.667808", "elevation": "191.8000030517578", "time": "2013-09-20T08:56:47.207Z"}, {"lat": "44.145082", "lng": "9.667807", "elevation": "192.1999969482422", "time": "2013-09-20T08:56:48.217Z"}, {"lat": "44.145076", "lng": "9.667807", "elevation": "192.39999389648438", "time": "2013-09-20T08:56:49.217Z"}, {"lat": "44.144992", "lng": "9.667778", "elevation": "194.0", "time": "2013-09-20T08:56:55.208Z"}, {"lat": "44.144977", "lng": "9.667771", "elevation": "194.10000610351563", "time": "2013-09-20T08:56:56.234Z"}, {"lat": "44.1449", "lng": "9.66773", "elevation": "195.39999389648438", "time": "2013-09-20T08:57:02.217Z"}, {"lat": "44.144888", "lng": "9.667724", "elevation": "196.10000610351563", "time": "2013-09-20T08:57:03.267Z"}, {"lat": "44.144801", "lng": "9.667719", "elevation": "193.3000030517578", "time": "2013-09-20T08:57:15.224Z"}, {"lat": "44.144792", "lng": "9.667717", "elevation": "193.10000610351563", "time": "2013-09-20T08:57:16.310Z"}, {"lat": "44.144702", "lng": "9.667699", "elevation": "189.5", "time": "2013-09-20T08:57:30.220Z"}, {"lat": "44.144698", "lng": "9.667704", "elevation": "189.5", "time": "2013-09-20T08:57:31.220Z"}, {"lat": "44.144612", "lng": "9.667714", "elevation": "184.1999969482422", "time": "2013-09-20T08:57:41.244Z"}, {"lat": "44.144597", "lng": "9.667713", "elevation": "184.39999389648438", "time": "2013-09-20T08:57:42.215Z"}, {"lat": "44.144547", "lng": "9.667816", "elevation": "194.1999969482422", "time": "2013-09-20T08:57:57.230Z"}, {"lat": "44.144544", "lng": "9.667823", "elevation": "195.39999389648438", "time": "2013-09-20T08:57:58.256Z"}, {"lat": "44.144581", "lng": "9.667931", "elevation": "200.8000030517578", "time": "2013-09-20T08:58:12.304Z"}, {"lat": "44.144579", "lng": "9.667938", "elevation": "201.10000610351563", "time": "2013-09-20T08:58:13.264Z"}, {"lat": "44.144543", "lng": "9.668047", "elevation": "200.6999969482422", "time": "2013-09-20T08:58:22.288Z"}, {"lat": "44.144541", "lng": "9.668063", "elevation": "201.10000610351563", "time": "2013-09-20T08:58:23.381Z"}, {"lat": "44.144542", "lng": "9.668181", "elevation": "200.39999389648438", "time": "2013-09-20T08:58:32.226Z"}, {"lat": "44.144542", "lng": "9.66819", "elevation": "201.89999389648438", "time": "2013-09-20T08:58:33.213Z"}, {"lat": "44.144476", "lng": "9.668256", "elevation": "198.6999969482422", "time": "2013-09-20T08:58:44.323Z"}, {"lat": "44.14447", "lng": "9.668272", "elevation": "199.3000030517578", "time": "2013-09-20T08:58:45.291Z"}, {"lat": "44.144473", "lng": "9.668395", "elevation": "207.10000610351563", "time": "2013-09-20T08:58:59.284Z"}, {"lat": "44.144475", "lng": "9.668399", "elevation": "207.5", "time": "2013-09-20T08:59:00.355Z"}, {"lat": "44.144447", "lng": "9.668515", "elevation": "205.5", "time": "2013-09-20T08:59:12.285Z"}, {"lat": "44.144445", "lng": "9.668528", "elevation": "205.8000030517578", "time": "2013-09-20T08:59:13.231Z"}, {"lat": "44.144438", "lng": "9.668644", "elevation": "205.3000030517578", "time": "2013-09-20T08:59:25.359Z"}, {"lat": "44.144429", "lng": "9.668653", "elevation": "205.3000030517578", "time": "2013-09-20T08:59:26.367Z"}, {"lat": "44.144408", "lng": "9.668772", "elevation": "207.5", "time": "2013-09-20T08:59:39.319Z"}, {"lat": "44.144411", "lng": "9.668783", "elevation": "208.10000610351563", "time": "2013-09-20T08:59:40.365Z"}, {"lat": "44.144481", "lng": "9.668861", "elevation": "211.1999969482422", "time": "2013-09-20T08:59:52.223Z"}, {"lat": "44.144485", "lng": "9.66887", "elevation": "211.39999389648438", "time": "2013-09-20T08:59:53.240Z"}, {"lat": "44.144481", "lng": "9.668992", "elevation": "210.39999389648438", "time": "2013-09-20T09:00:04.345Z"}, {"lat": "44.144482", "lng": "9.669003", "elevation": "210.60000610351563", "time": "2013-09-20T09:00:05.306Z"}, {"lat": "44.144454", "lng": "9.66906", "elevation": "210.39999389648438", "time": "2013-09-20T09:00:15.349Z"}, {"lat": "44.144453", "lng": "9.66906", "elevation": "210.3000030517578", "time": "2013-09-20T09:00:16.373Z"}, {"lat": "44.144451", "lng": "9.669059", "elevation": "210.1999969482422", "time": "2013-09-20T09:00:17.328Z"}, {"lat": "44.144447", "lng": "9.669058", "elevation": "210.1999969482422", "time": "2013-09-20T09:00:18.393Z"}, {"lat": "44.144438", "lng": "9.669054", "elevation": "210.10000610351563", "time": "2013-09-20T09:00:22.266Z"}, {"lat": "44.144438", "lng": "9.669054", "elevation": "210.0", "time": "2013-09-20T09:00:23.234Z"}, {"lat": "44.144439", "lng": "9.669063", "elevation": "210.1999969482422", "time": "2013-09-20T09:00:41.226Z"}, {"lat": "44.144439", "lng": "9.669074", "elevation": "210.60000610351563", "time": "2013-09-20T09:00:42.241Z"}, {"lat": "44.144431", "lng": "9.669184", "elevation": "213.1999969482422", "time": "2013-09-20T09:00:48.323Z"}, {"lat": "44.144428", "lng": "9.669204", "elevation": "213.6999969482422", "time": "2013-09-20T09:00:49.323Z"}, {"lat": "44.144437", "lng": "9.669318", "elevation": "212.39999389648438", "time": "2013-09-20T09:00:54.282Z"}, {"lat": "44.14444", "lng": "9.669341", "elevation": "212.0", "time": "2013-09-20T09:00:55.227Z"}, {"lat": "44.144402", "lng": "9.669447", "elevation": "211.3000030517578", "time": "2013-09-20T09:01:02.394Z"}, {"lat": "44.144399", "lng": "9.669458", "elevation": "211.3000030517578", "time": "2013-09-20T09:01:03.344Z"}, {"lat": "44.144371", "lng": "9.669565", "elevation": "213.89999389648438", "time": "2013-09-20T09:01:13.236Z"}, {"lat": "44.144368", "lng": "9.669583", "elevation": "214.8000030517578", "time": "2013-09-20T09:01:14.244Z"}, {"lat": "44.144391", "lng": "9.669694", "elevation": "215.0", "time": "2013-09-20T09:01:21.336Z"}, {"lat": "44.144397", "lng": "9.669703", "elevation": "214.6999969482422", "time": "2013-09-20T09:01:22.334Z"}, {"lat": "44.144386", "lng": "9.66982", "elevation": "215.1999969482422", "time": "2013-09-20T09:01:31.282Z"}, {"lat": "44.144379", "lng": "9.669826", "elevation": "216.3000030517578", "time": "2013-09-20T09:01:32.321Z"}, {"lat": "44.144351", "lng": "9.669856", "elevation": "217.0", "time": "2013-09-20T09:01:41.344Z"}, {"lat": "44.144351", "lng": "9.669857", "elevation": "216.6999969482422", "time": "2013-09-20T09:01:42.295Z"}, {"lat": "44.144337", "lng": "9.66986", "elevation": "214.39999389648438", "time": "2013-09-20T09:01:55.249Z"}, {"lat": "44.144331", "lng": "9.669859", "elevation": "213.3000030517578", "time": "2013-09-20T09:01:56.251Z"}, {"lat": "44.144244", "lng": "9.669859", "elevation": "210.6999969482422", "time": "2013-09-20T09:02:04.326Z"}, {"lat": "44.144229", "lng": "9.669855", "elevation": "209.6999969482422", "time": "2013-09-20T09:02:05.266Z"}, {"lat": "44.144145", "lng": "9.669813", "elevation": "210.3000030517578", "time": "2013-09-20T09:02:12.254Z"}, {"lat": "44.144133", "lng": "9.669806", "elevation": "211.60000610351563", "time": "2013-09-20T09:02:13.307Z"}, {"lat": "44.144084", "lng": "9.669726", "elevation": "211.8000030517578", "time": "2013-09-20T09:02:18.230Z"}, {"lat": "44.144075", "lng": "9.669703", "elevation": "211.60000610351563", "time": "2013-09-20T09:02:19.260Z"}, {"lat": "44.144018", "lng": "9.669627", "elevation": "213.5", "time": "2013-09-20T09:02:24.285Z"}, {"lat": "44.144005", "lng": "9.669616", "elevation": "214.3000030517578", "time": "2013-09-20T09:02:25.259Z"}, {"lat": "44.143925", "lng": "9.669578", "elevation": "214.0", "time": "2013-09-20T09:02:30.279Z"}, {"lat": "44.143911", "lng": "9.669575", "elevation": "213.5", "time": "2013-09-20T09:02:31.326Z"}, {"lat": "44.143833", "lng": "9.669571", "elevation": "214.3000030517578", "time": "2013-09-20T09:02:37.235Z"}, {"lat": "44.143818", "lng": "9.669571", "elevation": "214.10000610351563", "time": "2013-09-20T09:02:38.282Z"}, {"lat": "44.143755", "lng": "9.669483", "elevation": "213.1999969482422", "time": "2013-09-20T09:02:46.284Z"}, {"lat": "44.143748", "lng": "9.669477", "elevation": "212.3000030517578", "time": "2013-09-20T09:02:47.326Z"}, {"lat": "44.143667", "lng": "9.669444", "elevation": "211.1999969482422", "time": "2013-09-20T09:02:57.271Z"}, {"lat": "44.143659", "lng": "9.669438", "elevation": "211.39999389648438", "time": "2013-09-20T09:02:58.247Z"}, {"lat": "44.143598", "lng": "9.669348", "elevation": "216.1999969482422", "time": "2013-09-20T09:03:06.234Z"}, {"lat": "44.143589", "lng": "9.669334", "elevation": "217.0", "time": "2013-09-20T09:03:07.235Z"}, {"lat": "44.143531", "lng": "9.669243", "elevation": "218.8000030517578", "time": "2013-09-20T09:03:13.238Z"}, {"lat": "44.143523", "lng": "9.66923", "elevation": "219.0", "time": "2013-09-20T09:03:14.235Z"}, {"lat": "44.143485", "lng": "9.669128", "elevation": "219.1999969482422", "time": "2013-09-20T09:03:22.241Z"}, {"lat": "44.143479", "lng": "9.66912", "elevation": "219.10000610351563", "time": "2013-09-20T09:03:23.255Z"}, {"lat": "44.143393", "lng": "9.669141", "elevation": "220.3000030517578", "time": "2013-09-20T09:03:42.332Z"}, {"lat": "44.143389", "lng": "9.66914", "elevation": "220.8000030517578", "time": "2013-09-20T09:03:43.343Z"}, {"lat": "44.143316", "lng": "9.669112", "elevation": "224.60000610351563", "time": "2013-09-20T09:03:57.267Z"}, {"lat": "44.143317", "lng": "9.669111", "elevation": "224.6999969482422", "time": "2013-09-20T09:03:58.315Z"}, {"lat": "44.143317", "lng": "9.669118", "elevation": "224.10000610351563", "time": "2013-09-20T09:04:03.241Z"}, {"lat": "44.143314", "lng": "9.669121", "elevation": "224.1999969482422", "time": "2013-09-20T09:04:04.306Z"}, {"lat": "44.143311", "lng": "9.669125", "elevation": "224.6999969482422", "time": "2013-09-20T09:04:06.251Z"}, {"lat": "44.14331", "lng": "9.669126", "elevation": "225.10000610351563", "time": "2013-09-20T09:04:07.261Z"}, {"lat": "44.143303", "lng": "9.66912", "elevation": "225.39999389648438", "time": "2013-09-20T09:04:14.248Z"}, {"lat": "44.1433", "lng": "9.669122", "elevation": "224.1999969482422", "time": "2013-09-20T09:04:15.253Z"}, {"lat": "44.143214", "lng": "9.669147", "elevation": "221.60000610351563", "time": "2013-09-20T09:04:23.285Z"}, {"lat": "44.143201", "lng": "9.669156", "elevation": "220.5", "time": "2013-09-20T09:04:24.292Z"}, {"lat": "44.143132", "lng": "9.669228", "elevation": "218.8000030517578", "time": "2013-09-20T09:04:31.331Z"}, {"lat": "44.143125", "lng": "9.669245", "elevation": "219.1999969482422", "time": "2013-09-20T09:04:32.334Z"}, {"lat": "44.143048", "lng": "9.669309", "elevation": "216.0", "time": "2013-09-20T09:04:40.320Z"}, {"lat": "44.143039", "lng": "9.669316", "elevation": "217.39999389648438", "time": "2013-09-20T09:04:41.273Z"}, {"lat": "44.14297", "lng": "9.669391", "elevation": "220.1999969482422", "time": "2013-09-20T09:04:52.254Z"}, {"lat": "44.142966", "lng": "9.669397", "elevation": "220.3000030517578", "time": "2013-09-20T09:04:53.262Z"}, {"lat": "44.14292", "lng": "9.669493", "elevation": "231.0", "time": "2013-09-20T09:05:08.249Z"}, {"lat": "44.142916", "lng": "9.669504", "elevation": "231.6999969482422", "time": "2013-09-20T09:05:09.270Z"}, {"lat": "44.142854", "lng": "9.669583", "elevation": "229.3000030517578", "time": "2013-09-20T09:05:17.264Z"}, {"lat": "44.142843", "lng": "9.669591", "elevation": "229.0", "time": "2013-09-20T09:05:18.267Z"}, {"lat": "44.142811", "lng": "9.669699", "elevation": "229.1999969482422", "time": "2013-09-20T09:05:38.291Z"}, {"lat": "44.142812", "lng": "9.6697", "elevation": "229.39999389648438", "time": "2013-09-20T09:05:39.265Z"}, {"lat": "44.142807", "lng": "9.669704", "elevation": "229.10000610351563", "time": "2013-09-20T09:05:53.343Z"}, {"lat": "44.142802", "lng": "9.66971", "elevation": "228.60000610351563", "time": "2013-09-20T09:05:54.266Z"}, {"lat": "44.142739", "lng": "9.669788", "elevation": "226.89999389648438", "time": "2013-09-20T09:06:00.365Z"}, {"lat": "44.142725", "lng": "9.669803", "elevation": "225.60000610351563", "time": "2013-09-20T09:06:01.348Z"}, {"lat": "44.142665", "lng": "9.669875", "elevation": "224.6999969482422", "time": "2013-09-20T09:06:06.260Z"}, {"lat": "44.142658", "lng": "9.669893", "elevation": "225.39999389648438", "time": "2013-09-20T09:06:07.262Z"}, {"lat": "44.142614", "lng": "9.669987", "elevation": "223.60000610351563", "time": "2013-09-20T09:06:12.262Z"}, {"lat": "44.1426", "lng": "9.670006", "elevation": "223.5", "time": "2013-09-20T09:06:13.255Z"}, {"lat": "44.142532", "lng": "9.670084", "elevation": "221.60000610351563", "time": "2013-09-20T09:06:18.269Z"}, {"lat": "44.142521", "lng": "9.670096", "elevation": "221.0", "time": "2013-09-20T09:06:19.292Z"}, {"lat": "44.142444", "lng": "9.670138", "elevation": "220.39999389648438", "time": "2013-09-20T09:06:28.263Z"}, {"lat": "44.142433", "lng": "9.670138", "elevation": "219.5", "time": "2013-09-20T09:06:29.275Z"}, {"lat": "44.142349", "lng": "9.67018", "elevation": "215.10000610351563", "time": "2013-09-20T09:06:37.272Z"}, {"lat": "44.14234", "lng": "9.670191", "elevation": "215.0", "time": "2013-09-20T09:06:38.255Z"}, {"lat": "44.142282", "lng": "9.670284", "elevation": "212.60000610351563", "time": "2013-09-20T09:06:47.259Z"}, {"lat": "44.142278", "lng": "9.670289", "elevation": "211.6999969482422", "time": "2013-09-20T09:06:48.281Z"}, {"lat": "44.142205", "lng": "9.670358", "elevation": "212.3000030517578", "time": "2013-09-20T09:06:58.282Z"}, {"lat": "44.142197", "lng": "9.670374", "elevation": "212.0", "time": "2013-09-20T09:06:59.264Z"}, {"lat": "44.142145", "lng": "9.670464", "elevation": "211.60000610351563", "time": "2013-09-20T09:07:07.267Z"}, {"lat": "44.142132", "lng": "9.670468", "elevation": "211.89999389648438", "time": "2013-09-20T09:07:08.264Z"}, {"lat": "44.142055", "lng": "9.670509", "elevation": "208.5", "time": "2013-09-20T09:07:19.283Z"}, {"lat": "44.142045", "lng": "9.670511", "elevation": "207.8000030517578", "time": "2013-09-20T09:07:20.310Z"}, {"lat": "44.141963", "lng": "9.670544", "elevation": "205.1999969482422", "time": "2013-09-20T09:07:28.260Z"}, {"lat": "44.141956", "lng": "9.670557", "elevation": "204.89999389648438", "time": "2013-09-20T09:07:29.274Z"}, {"lat": "44.141916", "lng": "9.670662", "elevation": "203.6999969482422", "time": "2013-09-20T09:07:37.269Z"}, {"lat": "44.141911", "lng": "9.670673", "elevation": "204.0", "time": "2013-09-20T09:07:38.292Z"}, {"lat": "44.141832", "lng": "9.670726", "elevation": "203.5", "time": "2013-09-20T09:07:56.270Z"}, {"lat": "44.141829", "lng": "9.670736", "elevation": "203.89999389648438", "time": "2013-09-20T09:07:57.278Z"}, {"lat": "44.141775", "lng": "9.67069", "elevation": "208.89999389648438", "time": "2013-09-20T09:08:14.343Z"}, {"lat": "44.141775", "lng": "9.670689", "elevation": "208.6999969482422", "time": "2013-09-20T09:08:15.377Z"}, {"lat": "44.141772", "lng": "9.670694", "elevation": "208.89999389648438", "time": "2013-09-20T09:08:29.334Z"}, {"lat": "44.141767", "lng": "9.6707", "elevation": "209.8000030517578", "time": "2013-09-20T09:08:30.313Z"}, {"lat": "44.141695", "lng": "9.670774", "elevation": "209.0", "time": "2013-09-20T09:08:36.290Z"}, {"lat": "44.141682", "lng": "9.670793", "elevation": "206.3000030517578", "time": "2013-09-20T09:08:37.303Z"}, {"lat": "44.141676", "lng": "9.670903", "elevation": "206.3000030517578", "time": "2013-09-20T09:08:42.272Z"}, {"lat": "44.141676", "lng": "9.670921", "elevation": "206.5", "time": "2013-09-20T09:08:43.264Z"}, {"lat": "44.141665", "lng": "9.671042", "elevation": "209.10000610351563", "time": "2013-09-20T09:08:51.284Z"}, {"lat": "44.141658", "lng": "9.671052", "elevation": "208.60000610351563", "time": "2013-09-20T09:08:52.281Z"}, {"lat": "44.141598", "lng": "9.671141", "elevation": "207.5", "time": "2013-09-20T09:09:01.265Z"}, {"lat": "44.141586", "lng": "9.671158", "elevation": "207.8000030517578", "time": "2013-09-20T09:09:02.296Z"}, {"lat": "44.141525", "lng": "9.671237", "elevation": "208.8000030517578", "time": "2013-09-20T09:09:07.275Z"}, {"lat": "44.141513", "lng": "9.67125", "elevation": "209.1999969482422", "time": "2013-09-20T09:09:08.265Z"}, {"lat": "44.141455", "lng": "9.671324", "elevation": "210.39999389648438", "time": "2013-09-20T09:09:13.267Z"}, {"lat": "44.141449", "lng": "9.671346", "elevation": "210.89999389648438", "time": "2013-09-20T09:09:14.305Z"}, {"lat": "44.141409", "lng": "9.671437", "elevation": "212.5", "time": "2013-09-20T09:09:19.330Z"}, {"lat": "44.141397", "lng": "9.67145", "elevation": "212.10000610351563", "time": "2013-09-20T09:09:20.267Z"}, {"lat": "44.141331", "lng": "9.671521", "elevation": "211.39999389648438", "time": "2013-09-20T09:09:27.267Z"}, {"lat": "44.141326", "lng": "9.671536", "elevation": "211.3000030517578", "time": "2013-09-20T09:09:28.285Z"}, {"lat": "44.141303", "lng": "9.671647", "elevation": "212.6999969482422", "time": "2013-09-20T09:09:34.347Z"}, {"lat": "44.141298", "lng": "9.671664", "elevation": "212.60000610351563", "time": "2013-09-20T09:09:35.362Z"}, {"lat": "44.141252", "lng": "9.671752", "elevation": "211.3000030517578", "time": "2013-09-20T09:09:41.277Z"}, {"lat": "44.141247", "lng": "9.671769", "elevation": "211.1999969482422", "time": "2013-09-20T09:09:42.325Z"}, {"lat": "44.141196", "lng": "9.671864", "elevation": "213.8000030517578", "time": "2013-09-20T09:09:48.287Z"}, {"lat": "44.141177", "lng": "9.671877", "elevation": "213.3000030517578", "time": "2013-09-20T09:09:49.287Z"}, {"lat": "44.141107", "lng": "9.671917", "elevation": "214.3000030517578", "time": "2013-09-20T09:09:53.318Z"}, {"lat": "44.141094", "lng": "9.671927", "elevation": "214.10000610351563", "time": "2013-09-20T09:09:54.286Z"}, {"lat": "44.141035", "lng": "9.672006", "elevation": "213.6999969482422", "time": "2013-09-20T09:10:00.393Z"}, {"lat": "44.141027", "lng": "9.672018", "elevation": "212.60000610351563", "time": "2013-09-20T09:10:01.294Z"}, {"lat": "44.14094", "lng": "9.672043", "elevation": "213.1999969482422", "time": "2013-09-20T09:10:07.286Z"}, {"lat": "44.140927", "lng": "9.672048", "elevation": "214.39999389648438", "time": "2013-09-20T09:10:08.301Z"}, {"lat": "44.140852", "lng": "9.672109", "elevation": "217.5", "time": "2013-09-20T09:10:15.367Z"}, {"lat": "44.140845", "lng": "9.672117", "elevation": "217.0", "time": "2013-09-20T09:10:16.345Z"}, {"lat": "44.140788", "lng": "9.672193", "elevation": "215.60000610351563", "time": "2013-09-20T09:10:28.273Z"}, {"lat": "44.140779", "lng": "9.672203", "elevation": "216.1999969482422", "time": "2013-09-20T09:10:29.281Z"}, {"lat": "44.140702", "lng": "9.672256", "elevation": "214.89999389648438", "time": "2013-09-20T09:10:45.297Z"}, {"lat": "44.140699", "lng": "9.672262", "elevation": "214.1999969482422", "time": "2013-09-20T09:10:46.331Z"}, {"lat": "44.140637", "lng": "9.672337", "elevation": "216.10000610351563", "time": "2013-09-20T09:10:57.306Z"}, {"lat": "44.140626", "lng": "9.672344", "elevation": "216.1999969482422", "time": "2013-09-20T09:10:58.274Z"}, {"lat": "44.140567", "lng": "9.67243", "elevation": "216.10000610351563", "time": "2013-09-20T09:11:05.308Z"}, {"lat": "44.140564", "lng": "9.672447", "elevation": "216.8000030517578", "time": "2013-09-20T09:11:06.283Z"}, {"lat": "44.140541", "lng": "9.672555", "elevation": "218.5", "time": "2013-09-20T09:11:12.293Z"}, {"lat": "44.140535", "lng": "9.672568", "elevation": "218.89999389648438", "time": "2013-09-20T09:11:13.309Z"}, {"lat": "44.14053", "lng": "9.672691", "elevation": "218.0", "time": "2013-09-20T09:11:23.294Z"}, {"lat": "44.140535", "lng": "9.672708", "elevation": "216.89999389648438", "time": "2013-09-20T09:11:24.308Z"}, {"lat": "44.140551", "lng": "9.672822", "elevation": "221.10000610351563", "time": "2013-09-20T09:11:36.304Z"}, {"lat": "44.140552", "lng": "9.672832", "elevation": "221.5", "time": "2013-09-20T09:11:37.287Z"}, {"lat": "44.140572", "lng": "9.672943", "elevation": "219.8000030517578", "time": "2013-09-20T09:11:47.311Z"}, {"lat": "44.140574", "lng": "9.672955", "elevation": "219.3000030517578", "time": "2013-09-20T09:11:48.303Z"}, {"lat": "44.140561", "lng": "9.673054", "elevation": "218.5", "time": "2013-09-20T09:12:00.309Z"}, {"lat": "44.140562", "lng": "9.673054", "elevation": "218.6999969482422", "time": "2013-09-20T09:12:01.281Z"}, {"lat": "44.140573", "lng": "9.673073", "elevation": "217.6999969482422", "time": "2013-09-20T09:12:42.300Z"}, {"lat": "44.140572", "lng": "9.673081", "elevation": "216.60000610351563", "time": "2013-09-20T09:12:43.380Z"}, {"lat": "44.140562", "lng": "9.673203", "elevation": "212.5", "time": "2013-09-20T09:12:50.300Z"}, {"lat": "44.140571", "lng": "9.673217", "elevation": "212.39999389648438", "time": "2013-09-20T09:12:51.357Z"}, {"lat": "44.140627", "lng": "9.673314", "elevation": "209.8000030517578", "time": "2013-09-20T09:12:59.363Z"}, {"lat": "44.140628", "lng": "9.673317", "elevation": "210.10000610351563", "time": "2013-09-20T09:13:00.451Z"}, {"lat": "44.140604", "lng": "9.673426", "elevation": "208.89999389648438", "time": "2013-09-20T09:13:09.349Z"}, {"lat": "44.140605", "lng": "9.673443", "elevation": "206.89999389648438", "time": "2013-09-20T09:13:10.332Z"}, {"lat": "44.140649", "lng": "9.67355", "elevation": "204.89999389648438", "time": "2013-09-20T09:13:18.320Z"}, {"lat": "44.140649", "lng": "9.673571", "elevation": "204.8000030517578", "time": "2013-09-20T09:13:19.320Z"}, {"lat": "44.140614", "lng": "9.673678", "elevation": "208.0", "time": "2013-09-20T09:13:24.313Z"}, {"lat": "44.140609", "lng": "9.673696", "elevation": "208.5", "time": "2013-09-20T09:13:25.307Z"}, {"lat": "44.140609", "lng": "9.673815", "elevation": "209.1999969482422", "time": "2013-09-20T09:13:33.316Z"}, {"lat": "44.140604", "lng": "9.673838", "elevation": "208.8000030517578", "time": "2013-09-20T09:13:34.347Z"}, {"lat": "44.140612", "lng": "9.673959", "elevation": "205.10000610351563", "time": "2013-09-20T09:13:41.294Z"}, {"lat": "44.140623", "lng": "9.673962", "elevation": "205.5", "time": "2013-09-20T09:13:42.294Z"}, {"lat": "44.140633", "lng": "9.67408", "elevation": "206.60000610351563", "time": "2013-09-20T09:13:56.327Z"}, {"lat": "44.140625", "lng": "9.674094", "elevation": "205.3000030517578", "time": "2013-09-20T09:13:57.367Z"}, {"lat": "44.14059", "lng": "9.674206", "elevation": "204.5", "time": "2013-09-20T09:14:04.403Z"}, {"lat": "44.140588", "lng": "9.674225", "elevation": "204.89999389648438", "time": "2013-09-20T09:14:05.377Z"}, {"lat": "44.140568", "lng": "9.674345", "elevation": "206.0", "time": "2013-09-20T09:14:19.314Z"}, {"lat": "44.140567", "lng": "9.674357", "elevation": "206.39999389648438", "time": "2013-09-20T09:14:20.324Z"}, {"lat": "44.140591", "lng": "9.674469", "elevation": "207.1999969482422", "time": "2013-09-20T09:14:28.305Z"}, {"lat": "44.140591", "lng": "9.674481", "elevation": "206.89999389648438", "time": "2013-09-20T09:14:29.331Z"}, {"lat": "44.140605", "lng": "9.674598", "elevation": "209.60000610351563", "time": "2013-09-20T09:14:41.301Z"}, {"lat": "44.140609", "lng": "9.674611", "elevation": "210.60000610351563", "time": "2013-09-20T09:14:42.300Z"}, {"lat": "44.140588", "lng": "9.674654", "elevation": "211.3000030517578", "time": "2013-09-20T09:14:52.377Z"}, {"lat": "44.140587", "lng": "9.674654", "elevation": "211.10000610351563", "time": "2013-09-20T09:14:53.377Z"}, {"lat": "44.140596", "lng": "9.674662", "elevation": "210.3000030517578", "time": "2013-09-20T09:15:08.326Z"}, {"lat": "44.140596", "lng": "9.674669", "elevation": "210.10000610351563", "time": "2013-09-20T09:15:09.303Z"}, {"lat": "44.140624", "lng": "9.674769", "elevation": "205.1999969482422", "time": "2013-09-20T09:15:15.295Z"}, {"lat": "44.140634", "lng": "9.67479", "elevation": "204.60000610351563", "time": "2013-09-20T09:15:16.295Z"}, {"lat": "44.140666", "lng": "9.67489", "elevation": "206.10000610351563", "time": "2013-09-20T09:15:21.336Z"}, {"lat": "44.140673", "lng": "9.67491", "elevation": "207.39999389648438", "time": "2013-09-20T09:15:22.319Z"}, {"lat": "44.140681", "lng": "9.67502", "elevation": "207.6999969482422", "time": "2013-09-20T09:15:26.308Z"}, {"lat": "44.140676", "lng": "9.675045", "elevation": "209.3000030517578", "time": "2013-09-20T09:15:27.294Z"}, {"lat": "44.140648", "lng": "9.675161", "elevation": "210.10000610351563", "time": "2013-09-20T09:15:33.304Z"}, {"lat": "44.140649", "lng": "9.675166", "elevation": "210.0", "time": "2013-09-20T09:15:34.317Z"}, {"lat": "44.140668", "lng": "9.675164", "elevation": "210.0", "time": "2013-09-20T09:15:41.295Z"}, {"lat": "44.140669", "lng": "9.675165", "elevation": "210.0", "time": "2013-09-20T09:15:42.312Z"}, {"lat": "44.140668", "lng": "9.675169", "elevation": "209.1999969482422", "time": "2013-09-20T09:16:01.315Z"}, {"lat": "44.140663", "lng": "9.675176", "elevation": "208.1999969482422", "time": "2013-09-20T09:16:02.333Z"}, {"lat": "44.140639", "lng": "9.675219", "elevation": "207.10000610351563", "time": "2013-09-20T09:16:06.354Z"}, {"lat": "44.140631", "lng": "9.675516", "elevation": "195.5", "time": "2013-09-20T09:16:40.254Z"}, {"lat": "44.1406", "lng": "9.675602", "elevation": "206.89999389648438", "time": "2013-09-20T09:16:44.253Z"}, {"lat": "44.140593", "lng": "9.675632", "elevation": "209.0", "time": "2013-09-20T09:16:45.231Z"}, {"lat": "44.140526", "lng": "9.675678", "elevation": "207.89999389648438", "time": "2013-09-20T09:16:50.222Z"}, {"lat": "44.140505", "lng": "9.675667", "elevation": "207.3000030517578", "time": "2013-09-20T09:16:51.262Z"}, {"lat": "44.140521", "lng": "9.675644", "elevation": "205.60000610351563", "time": "2013-09-20T09:16:59.234Z"}, {"lat": "44.140522", "lng": "9.675644", "elevation": "205.6999969482422", "time": "2013-09-20T09:17:00.270Z"}, {"lat": "44.140515", "lng": "9.675657", "elevation": "206.0", "time": "2013-09-20T09:17:55.237Z"}, {"lat": "44.140517", "lng": "9.675663", "elevation": "207.5", "time": "2013-09-20T09:17:56.239Z"}, {"lat": "44.14057", "lng": "9.675754", "elevation": "209.39999389648438", "time": "2013-09-20T09:18:03.245Z"}, {"lat": "44.14058", "lng": "9.675765", "elevation": "209.1999969482422", "time": "2013-09-20T09:18:04.238Z"}, {"lat": "44.140586", "lng": "9.675823", "elevation": "211.5", "time": "2013-09-20T09:18:16.245Z"}, {"lat": "44.140586", "lng": "9.675825", "elevation": "211.60000610351563", "time": "2013-09-20T09:18:17.255Z"}, {"lat": "44.140592", "lng": "9.675829", "elevation": "211.6999969482422", "time": "2013-09-20T09:18:27.265Z"}, {"lat": "44.140593", "lng": "9.675839", "elevation": "212.6999969482422", "time": "2013-09-20T09:18:28.239Z"}, {"lat": "44.140558", "lng": "9.675943", "elevation": "219.39999389648438", "time": "2013-09-20T09:18:35.254Z"}, {"lat": "44.140548", "lng": "9.675955", "elevation": "220.1999969482422", "time": "2013-09-20T09:18:36.272Z"}, {"lat": "44.140505", "lng": "9.676063", "elevation": "222.6999969482422", "time": "2013-09-20T09:18:41.262Z"}, {"lat": "44.140501", "lng": "9.676086", "elevation": "223.3000030517578", "time": "2013-09-20T09:18:42.242Z"}, {"lat": "44.140424", "lng": "9.676136", "elevation": "223.60000610351563", "time": "2013-09-20T09:18:48.329Z"}, {"lat": "44.140408", "lng": "9.676142", "elevation": "223.60000610351563", "time": "2013-09-20T09:18:49.258Z"}, {"lat": "44.14036", "lng": "9.676245", "elevation": "221.0", "time": "2013-09-20T09:18:57.256Z"}, {"lat": "44.140359", "lng": "9.67626", "elevation": "219.8000030517578", "time": "2013-09-20T09:18:58.264Z"}, {"lat": "44.140321", "lng": "9.676372", "elevation": "220.60000610351563", "time": "2013-09-20T09:19:08.242Z"}, {"lat": "44.140317", "lng": "9.676377", "elevation": "221.0", "time": "2013-09-20T09:19:09.241Z"}, {"lat": "44.140239", "lng": "9.676438", "elevation": "218.89999389648438", "time": "2013-09-20T09:19:25.242Z"}, {"lat": "44.140236", "lng": "9.676448", "elevation": "220.5", "time": "2013-09-20T09:19:26.252Z"}, {"lat": "44.140213", "lng": "9.676558", "elevation": "213.5", "time": "2013-09-20T09:19:36.244Z"}, {"lat": "44.140218", "lng": "9.67658", "elevation": "212.3000030517578", "time": "2013-09-20T09:19:37.252Z"}, {"lat": "44.140208", "lng": "9.676694", "elevation": "208.10000610351563", "time": "2013-09-20T09:19:44.317Z"}, {"lat": "44.140202", "lng": "9.676706", "elevation": "207.1999969482422", "time": "2013-09-20T09:19:45.245Z"}, {"lat": "44.140178", "lng": "9.67682", "elevation": "206.60000610351563", "time": "2013-09-20T09:19:53.350Z"}, {"lat": "44.140177", "lng": "9.676834", "elevation": "207.3000030517578", "time": "2013-09-20T09:19:54.269Z"}, {"lat": "44.140178", "lng": "9.676951", "elevation": "204.5", "time": "2013-09-20T09:20:07.247Z"}, {"lat": "44.140184", "lng": "9.676964", "elevation": "204.10000610351563", "time": "2013-09-20T09:20:08.249Z"}, {"lat": "44.140158", "lng": "9.677077", "elevation": "206.10000610351563", "time": "2013-09-20T09:20:17.254Z"}, {"lat": "44.140146", "lng": "9.677089", "elevation": "206.60000610351563", "time": "2013-09-20T09:20:18.257Z"}, {"lat": "44.140076", "lng": "9.677164", "elevation": "208.10000610351563", "time": "2013-09-20T09:20:24.263Z"}, {"lat": "44.140066", "lng": "9.677187", "elevation": "207.3000030517578", "time": "2013-09-20T09:20:25.240Z"}, {"lat": "44.140011", "lng": "9.677273", "elevation": "208.39999389648438", "time": "2013-09-20T09:20:31.239Z"}, {"lat": "44.140003", "lng": "9.677287", "elevation": "208.3000030517578", "time": "2013-09-20T09:20:32.354Z"}, {"lat": "44.139937", "lng": "9.677346", "elevation": "209.39999389648438", "time": "2013-09-20T09:20:38.400Z"}, {"lat": "44.139926", "lng": "9.677353", "elevation": "210.0", "time": "2013-09-20T09:20:39.352Z"}, {"lat": "44.139849", "lng": "9.677395", "elevation": "208.39999389648438", "time": "2013-09-20T09:20:46.251Z"}, {"lat": "44.139841", "lng": "9.677402", "elevation": "210.39999389648438", "time": "2013-09-20T09:20:47.272Z"}, {"lat": "44.13977", "lng": "9.677457", "elevation": "211.60000610351563", "time": "2013-09-20T09:20:54.275Z"}, {"lat": "44.139763", "lng": "9.677472", "elevation": "212.0", "time": "2013-09-20T09:20:55.243Z"}, {"lat": "44.139711", "lng": "9.677569", "elevation": "214.0", "time": "2013-09-20T09:21:04.245Z"}, {"lat": "44.139705", "lng": "9.677577", "elevation": "213.60000610351563", "time": "2013-09-20T09:21:05.251Z"}, {"lat": "44.139645", "lng": "9.677534", "elevation": "217.1999969482422", "time": "2013-09-20T09:21:20.247Z"}, {"lat": "44.139644", "lng": "9.677533", "elevation": "217.1999969482422", "time": "2013-09-20T09:21:21.252Z"}, {"lat": "44.139635", "lng": "9.677543", "elevation": "216.89999389648438", "time": "2013-09-20T09:21:30.277Z"}, {"lat": "44.139631", "lng": "9.677547", "elevation": "217.10000610351563", "time": "2013-09-20T09:21:31.253Z"}, {"lat": "44.139551", "lng": "9.677589", "elevation": "218.8000030517578", "time": "2013-09-20T09:21:35.259Z"}, {"lat": "44.139524", "lng": "9.67759", "elevation": "218.60000610351563", "time": "2013-09-20T09:21:36.248Z"}, {"lat": "44.139454", "lng": "9.677585", "elevation": "219.10000610351563", "time": "2013-09-20T09:21:39.283Z"}, {"lat": "44.139433", "lng": "9.677585", "elevation": "218.89999389648438", "time": "2013-09-20T09:21:40.253Z"}, {"lat": "44.13935", "lng": "9.677614", "elevation": "219.1999969482422", "time": "2013-09-20T09:21:44.252Z"}, {"lat": "44.139334", "lng": "9.677625", "elevation": "218.60000610351563", "time": "2013-09-20T09:21:45.262Z"}, {"lat": "44.139269", "lng": "9.677702", "elevation": "216.5", "time": "2013-09-20T09:21:49.284Z"}, {"lat": "44.139256", "lng": "9.677719", "elevation": "214.89999389648438", "time": "2013-09-20T09:21:50.277Z"}, {"lat": "44.13917", "lng": "9.677756", "elevation": "220.89999389648438", "time": "2013-09-20T09:21:57.255Z"}, {"lat": "44.139157", "lng": "9.677764", "elevation": "221.5", "time": "2013-09-20T09:21:58.270Z"}, {"lat": "44.13907", "lng": "9.677779", "elevation": "221.5", "time": "2013-09-20T09:22:06.319Z"}, {"lat": "44.139058", "lng": "9.677776", "elevation": "220.6999969482422", "time": "2013-09-20T09:22:07.350Z"}, {"lat": "44.139001", "lng": "9.677871", "elevation": "224.0", "time": "2013-09-20T09:22:15.265Z"}, {"lat": "44.138985", "lng": "9.677885", "elevation": "225.0", "time": "2013-09-20T09:22:16.321Z"}, {"lat": "44.138915", "lng": "9.677936", "elevation": "227.10000610351563", "time": "2013-09-20T09:22:20.255Z"}, {"lat": "44.138896", "lng": "9.677943", "elevation": "227.39999389648438", "time": "2013-09-20T09:22:21.280Z"}, {"lat": "44.138807", "lng": "9.677963", "elevation": "228.60000610351563", "time": "2013-09-20T09:22:27.258Z"}, {"lat": "44.138795", "lng": "9.677966", "elevation": "228.5", "time": "2013-09-20T09:22:28.288Z"}, {"lat": "44.13872", "lng": "9.677989", "elevation": "229.60000610351563", "time": "2013-09-20T09:22:33.282Z"}, {"lat": "44.138702", "lng": "9.677992", "elevation": "232.39999389648438", "time": "2013-09-20T09:22:34.279Z"}, {"lat": "44.138622", "lng": "9.677985", "elevation": "231.39999389648438", "time": "2013-09-20T09:22:38.282Z"}, {"lat": "44.138604", "lng": "9.677982", "elevation": "232.10000610351563", "time": "2013-09-20T09:22:39.282Z"}, {"lat": "44.138516", "lng": "9.677967", "elevation": "234.60000610351563", "time": "2013-09-20T09:22:45.267Z"}, {"lat": "44.138508", "lng": "9.677969", "elevation": "236.3000030517578", "time": "2013-09-20T09:22:46.285Z"}, {"lat": "44.138439", "lng": "9.678037", "elevation": "238.89999389648438", "time": "2013-09-20T09:22:54.343Z"}, {"lat": "44.138428", "lng": "9.678039", "elevation": "240.1999969482422", "time": "2013-09-20T09:22:55.326Z"}, {"lat": "44.138345", "lng": "9.678053", "elevation": "235.39999389648438", "time": "2013-09-20T09:23:07.297Z"}, {"lat": "44.138338", "lng": "9.678057", "elevation": "236.39999389648438", "time": "2013-09-20T09:23:08.282Z"}, {"lat": "44.138254", "lng": "9.678067", "elevation": "240.89999389648438", "time": "2013-09-20T09:23:17.285Z"}, {"lat": "44.138247", "lng": "9.678071", "elevation": "240.8000030517578", "time": "2013-09-20T09:23:18.359Z"}, {"lat": "44.138243", "lng": "9.678189", "elevation": "224.6999969482422", "time": "2013-09-20T09:23:38.272Z"}, {"lat": "44.138243", "lng": "9.678197", "elevation": "224.5", "time": "2013-09-20T09:23:39.280Z"}, {"lat": "44.138248", "lng": "9.678206", "elevation": "223.8000030517578", "time": "2013-09-20T09:23:43.366Z"}, {"lat": "44.138248", "lng": "9.678205", "elevation": "224.8000030517578", "time": "2013-09-20T09:23:44.326Z"}, {"lat": "44.13825", "lng": "9.678205", "elevation": "223.10000610351563", "time": "2013-09-20T09:23:46.278Z"}, {"lat": "44.138254", "lng": "9.678206", "elevation": "222.89999389648438", "time": "2013-09-20T09:23:47.294Z"}, {"lat": "44.138273", "lng": "9.678225", "elevation": "219.39999389648438", "time": "2013-09-20T09:23:55.289Z"}, {"lat": "44.138273", "lng": "9.678226", "elevation": "219.3000030517578", "time": "2013-09-20T09:23:56.295Z"}, {"lat": "44.138272", "lng": "9.678229", "elevation": "219.0", "time": "2013-09-20T09:23:59.366Z"}, {"lat": "44.138272", "lng": "9.678235", "elevation": "216.6999969482422", "time": "2013-09-20T09:24:00.358Z"}, {"lat": "44.13827", "lng": "9.678347", "elevation": "205.6999969482422", "time": "2013-09-20T09:24:06.311Z"}, {"lat": "44.138268", "lng": "9.678371", "elevation": "204.8000030517578", "time": "2013-09-20T09:24:07.289Z"}, {"lat": "44.138286", "lng": "9.678438", "elevation": "204.8000030517578", "time": "2013-09-20T09:24:17.283Z"}, {"lat": "44.138286", "lng": "9.678439", "elevation": "204.5", "time": "2013-09-20T09:24:18.321Z"}, {"lat": "44.138282", "lng": "9.678456", "elevation": "202.89999389648438", "time": "2013-09-20T09:24:32.284Z"}, {"lat": "44.13828", "lng": "9.678465", "elevation": "202.3000030517578", "time": "2013-09-20T09:24:33.275Z"}, {"lat": "44.138262", "lng": "9.678573", "elevation": "197.1999969482422", "time": "2013-09-20T09:24:39.282Z"}, {"lat": "44.138254", "lng": "9.678589", "elevation": "197.6999969482422", "time": "2013-09-20T09:24:40.350Z"}, {"lat": "44.1382", "lng": "9.678683", "elevation": "196.39999389648438", "time": "2013-09-20T09:24:52.299Z"}, {"lat": "44.138196", "lng": "9.678692", "elevation": "195.0", "time": "2013-09-20T09:24:53.310Z"}, {"lat": "44.138166", "lng": "9.678792", "elevation": "196.10000610351563", "time": "2013-09-20T09:25:00.368Z"}, {"lat": "44.138159", "lng": "9.678807", "elevation": "196.3000030517578", "time": "2013-09-20T09:25:01.268Z"}, {"lat": "44.138194", "lng": "9.678917", "elevation": "198.6999969482422", "time": "2013-09-20T09:25:12.278Z"}, {"lat": "44.138197", "lng": "9.678928", "elevation": "198.8000030517578", "time": "2013-09-20T09:25:13.270Z"}, {"lat": "44.138226", "lng": "9.678961", "elevation": "197.39999389648438", "time": "2013-09-20T09:25:22.286Z"}, {"lat": "44.138225", "lng": "9.678964", "elevation": "197.1999969482422", "time": "2013-09-20T09:25:23.295Z"}, {"lat": "44.13823", "lng": "9.678985", "elevation": "194.39999389648438", "time": "2013-09-20T09:25:41.296Z"}, {"lat": "44.138232", "lng": "9.678992", "elevation": "194.6999969482422", "time": "2013-09-20T09:25:42.279Z"}, {"lat": "44.138286", "lng": "9.679026", "elevation": "190.8000030517578", "time": "2013-09-20T09:25:53.328Z"}, {"lat": "44.138286", "lng": "9.679026", "elevation": "190.5", "time": "2013-09-20T09:25:54.291Z"}, {"lat": "44.138286", "lng": "9.679034", "elevation": "191.0", "time": "2013-09-20T09:25:58.330Z"}, {"lat": "44.138288", "lng": "9.679045", "elevation": "190.1999969482422", "time": "2013-09-20T09:25:59.291Z"}, {"lat": "44.138312", "lng": "9.679158", "elevation": "185.89999389648438", "time": "2013-09-20T09:26:07.275Z"}, {"lat": "44.138313", "lng": "9.679171", "elevation": "184.6999969482422", "time": "2013-09-20T09:26:08.298Z"}, {"lat": "44.138306", "lng": "9.679292", "elevation": "183.10000610351563", "time": "2013-09-20T09:26:21.294Z"}, {"lat": "44.138303", "lng": "9.679298", "elevation": "183.39999389648438", "time": "2013-09-20T09:26:22.282Z"}, {"lat": "44.138285", "lng": "9.679396", "elevation": "176.60000610351563", "time": "2013-09-20T09:26:40.299Z"}, {"lat": "44.138285", "lng": "9.679395", "elevation": "176.6999969482422", "time": "2013-09-20T09:26:41.292Z"}, {"lat": "44.138283", "lng": "9.679398", "elevation": "176.60000610351563", "time": "2013-09-20T09:26:43.291Z"}, {"lat": "44.138279", "lng": "9.679403", "elevation": "176.3000030517578", "time": "2013-09-20T09:26:44.301Z"}, {"lat": "44.138229", "lng": "9.679493", "elevation": "172.5", "time": "2013-09-20T09:26:55.358Z"}, {"lat": "44.138228", "lng": "9.679506", "elevation": "172.89999389648438", "time": "2013-09-20T09:26:56.326Z"}, {"lat": "44.138213", "lng": "9.679581", "elevation": "172.60000610351563", "time": "2013-09-20T09:27:06.325Z"}, {"lat": "44.138215", "lng": "9.679582", "elevation": "172.6999969482422", "time": "2013-09-20T09:27:07.294Z"}, {"lat": "44.138199", "lng": "9.679572", "elevation": "172.0", "time": "2013-09-20T09:27:23.288Z"}, {"lat": "44.138195", "lng": "9.679574", "elevation": "172.1999969482422", "time": "2013-09-20T09:27:24.287Z"}, {"lat": "44.138114", "lng": "9.679621", "elevation": "169.5", "time": "2013-09-20T09:27:32.315Z"}, {"lat": "44.138106", "lng": "9.679629", "elevation": "170.60000610351563", "time": "2013-09-20T09:27:33.290Z"}, {"lat": "44.138108", "lng": "9.67968", "elevation": "168.1999969482422", "time": "2013-09-20T09:27:41.305Z"}, {"lat": "44.138107", "lng": "9.679679", "elevation": "168.1999969482422", "time": "2013-09-20T09:27:42.305Z"}, {"lat": "44.138102", "lng": "9.679695", "elevation": "165.39999389648438", "time": "2013-09-20T09:27:54.314Z"}, {"lat": "44.138099", "lng": "9.6797", "elevation": "165.5", "time": "2013-09-20T09:27:55.296Z"}, {"lat": "44.138052", "lng": "9.679789", "elevation": "166.0", "time": "2013-09-20T09:28:04.304Z"}, {"lat": "44.138043", "lng": "9.679801", "elevation": "166.60000610351563", "time": "2013-09-20T09:28:05.322Z"}, {"lat": "44.137982", "lng": "9.679877", "elevation": "163.5", "time": "2013-09-20T09:28:14.289Z"}, {"lat": "44.137973", "lng": "9.679881", "elevation": "163.6999969482422", "time": "2013-09-20T09:28:15.376Z"}, {"lat": "44.137898", "lng": "9.679945", "elevation": "163.1999969482422", "time": "2013-09-20T09:28:22.307Z"}, {"lat": "44.137892", "lng": "9.679957", "elevation": "163.10000610351563", "time": "2013-09-20T09:28:23.299Z"}, {"lat": "44.137811", "lng": "9.680002", "elevation": "161.0", "time": "2013-09-20T09:28:33.301Z"}, {"lat": "44.137806", "lng": "9.680006", "elevation": "161.6999969482422", "time": "2013-09-20T09:28:34.301Z"}, {"lat": "44.137785", "lng": "9.680024", "elevation": "160.6999969482422", "time": "2013-09-20T09:28:40.318Z"}, {"lat": "44.137789", "lng": "9.680023", "elevation": "161.10000610351563", "time": "2013-09-20T09:28:41.318Z"}, {"lat": "44.137792", "lng": "9.680034", "elevation": "162.5", "time": "2013-09-20T09:28:49.325Z"}, {"lat": "44.137787", "lng": "9.680036", "elevation": "162.1999969482422", "time": "2013-09-20T09:28:50.318Z"}, {"lat": "44.137728", "lng": "9.680109", "elevation": "156.8000030517578", "time": "2013-09-20T09:28:58.302Z"}, {"lat": "44.137718", "lng": "9.680118", "elevation": "155.6999969482422", "time": "2013-09-20T09:28:59.309Z"}, {"lat": "44.13766", "lng": "9.680206", "elevation": "153.60000610351563", "time": "2013-09-20T09:29:07.310Z"}, {"lat": "44.137658", "lng": "9.680217", "elevation": "152.39999389648438", "time": "2013-09-20T09:29:08.311Z"}, {"lat": "44.137629", "lng": "9.68033", "elevation": "151.39999389648438", "time": "2013-09-20T09:29:25.294Z"}, {"lat": "44.137629", "lng": "9.680338", "elevation": "151.1999969482422", "time": "2013-09-20T09:29:26.322Z"}, {"lat": "44.137663", "lng": "9.680357", "elevation": "148.1999969482422", "time": "2013-09-20T09:29:35.304Z"}, {"lat": "44.137662", "lng": "9.680357", "elevation": "148.1999969482422", "time": "2013-09-20T09:29:36.298Z"}, {"lat": "44.137657", "lng": "9.680357", "elevation": "147.8000030517578", "time": "2013-09-20T09:29:38.260Z"}, {"lat": "44.137652", "lng": "9.680356", "elevation": "147.89999389648438", "time": "2013-09-20T09:29:38.294Z"}, {"lat": "44.137566", "lng": "9.680328", "elevation": "147.89999389648438", "time": "2013-09-20T09:29:49.322Z"}, {"lat": "44.137562", "lng": "9.680335", "elevation": "148.3000030517578", "time": "2013-09-20T09:29:50.307Z"}, {"lat": "44.137504", "lng": "9.680421", "elevation": "146.0", "time": "2013-09-20T09:30:01.319Z"}, {"lat": "44.137495", "lng": "9.680429", "elevation": "146.8000030517578", "time": "2013-09-20T09:30:02.322Z"}, {"lat": "44.137471", "lng": "9.68045", "elevation": "142.1999969482422", "time": "2013-09-20T09:30:08.322Z"}, {"lat": "44.137475", "lng": "9.680451", "elevation": "142.39999389648438", "time": "2013-09-20T09:30:09.305Z"}, {"lat": "44.13748", "lng": "9.68045", "elevation": "142.60000610351563", "time": "2013-09-20T09:30:10.322Z"}, {"lat": "44.137481", "lng": "9.68045", "elevation": "142.60000610351563", "time": "2013-09-20T09:30:11.308Z"}, {"lat": "44.137477", "lng": "9.680446", "elevation": "142.8000030517578", "time": "2013-09-20T09:30:13.300Z"}, {"lat": "44.137471", "lng": "9.680443", "elevation": "145.0", "time": "2013-09-20T09:30:14.308Z"}, {"lat": "44.137448", "lng": "9.680447", "elevation": "144.60000610351563", "time": "2013-09-20T09:30:35.325Z"}, {"lat": "44.137448", "lng": "9.680446", "elevation": "144.5", "time": "2013-09-20T09:30:36.319Z"}, {"lat": "44.137441", "lng": "9.680445", "elevation": "144.10000610351563", "time": "2013-09-20T09:30:41.373Z"}, {"lat": "44.137436", "lng": "9.680444", "elevation": "143.6999969482422", "time": "2013-09-20T09:30:42.333Z"}, {"lat": "44.137362", "lng": "9.680377", "elevation": "140.89999389648438", "time": "2013-09-20T09:30:58.336Z"}, {"lat": "44.137354", "lng": "9.680371", "elevation": "141.89999389648438", "time": "2013-09-20T09:30:59.326Z"}, {"lat": "44.137318", "lng": "9.680375", "elevation": "141.3000030517578", "time": "2013-09-20T09:31:07.407Z"}, {"lat": "44.137319", "lng": "9.680376", "elevation": "141.1999969482422", "time": "2013-09-20T09:31:08.446Z"}, {"lat": "44.137316", "lng": "9.680386", "elevation": "140.6999969482422", "time": "2013-09-20T09:31:12.376Z"}, {"lat": "44.137313", "lng": "9.680391", "elevation": "140.1999969482422", "time": "2013-09-20T09:31:13.304Z"}, {"lat": "44.137241", "lng": "9.680448", "elevation": "140.5", "time": "2013-09-20T09:31:22.335Z"}, {"lat": "44.137234", "lng": "9.680452", "elevation": "140.10000610351563", "time": "2013-09-20T09:31:23.426Z"}, {"lat": "44.137146", "lng": "9.680462", "elevation": "139.60000610351563", "time": "2013-09-20T09:31:38.435Z"}, {"lat": "44.137138", "lng": "9.680468", "elevation": "137.39999389648438", "time": "2013-09-20T09:31:39.355Z"}, {"lat": "44.137067", "lng": "9.680541", "elevation": "134.8000030517578", "time": "2013-09-20T09:31:48.350Z"}, {"lat": "44.137056", "lng": "9.680546", "elevation": "135.1999969482422", "time": "2013-09-20T09:31:48.378Z"}, {"lat": "44.136989", "lng": "9.680622", "elevation": "132.10000610351563", "time": "2013-09-20T09:31:58.072Z"}, {"lat": "44.136983", "lng": "9.680626", "elevation": "131.8000030517578", "time": "2013-09-20T09:31:58.411Z"}, {"lat": "44.13691", "lng": "9.680685", "elevation": "122.5", "time": "2013-09-20T09:32:14.311Z"}, {"lat": "44.1369", "lng": "9.680693", "elevation": "122.0999984741211", "time": "2013-09-20T09:32:15.324Z"}, {"lat": "44.136825", "lng": "9.680753", "elevation": "122.30000305175781", "time": "2013-09-20T09:32:24.311Z"}, {"lat": "44.13682", "lng": "9.680757", "elevation": "122.4000015258789", "time": "2013-09-20T09:32:25.318Z"}, {"lat": "44.13679", "lng": "9.680869", "elevation": "118.9000015258789", "time": "2013-09-20T09:32:35.310Z"}, {"lat": "44.136793", "lng": "9.680883", "elevation": "117.69999694824219", "time": "2013-09-20T09:32:36.325Z"}, {"lat": "44.136808", "lng": "9.680865", "elevation": "119.30000305175781", "time": "2013-09-20T09:32:43.487Z"}, {"lat": "44.136807", "lng": "9.680862", "elevation": "119.4000015258789", "time": "2013-09-20T09:32:44.399Z"}, {"lat": "44.136809", "lng": "9.680865", "elevation": "118.0", "time": "2013-09-20T09:32:46.322Z"}, {"lat": "44.136816", "lng": "9.680872", "elevation": "116.0", "time": "2013-09-20T09:32:47.375Z"}, {"lat": "44.136808", "lng": "9.680989", "elevation": "115.19999694824219", "time": "2013-09-20T09:33:01.335Z"}, {"lat": "44.136796", "lng": "9.680997", "elevation": "115.80000305175781", "time": "2013-09-20T09:33:02.329Z"}, {"lat": "44.136744", "lng": "9.681017", "elevation": "122.0", "time": "2013-09-20T09:33:15.387Z"}, {"lat": "44.136743", "lng": "9.681018", "elevation": "122.0", "time": "2013-09-20T09:33:16.440Z"}, {"lat": "44.136747", "lng": "9.681026", "elevation": "122.0", "time": "2013-09-20T09:33:34.434Z"}, {"lat": "44.136746", "lng": "9.681032", "elevation": "121.69999694824219", "time": "2013-09-20T09:33:35.447Z"}, {"lat": "44.136743", "lng": "9.681041", "elevation": "121.80000305175781", "time": "2013-09-20T09:33:38.809Z"}, {"lat": "44.136743", "lng": "9.681042", "elevation": "121.5999984741211", "time": "2013-09-20T09:33:39.348Z"}, {"lat": "44.136738", "lng": "9.681053", "elevation": "121.0999984741211", "time": "2013-09-20T09:33:55.372Z"}, {"lat": "44.136735", "lng": "9.68106", "elevation": "120.5", "time": "2013-09-20T09:33:56.339Z"}, {"lat": "44.13669", "lng": "9.681147", "elevation": "117.0", "time": "2013-09-20T09:34:09.499Z"}, {"lat": "44.136683", "lng": "9.681162", "elevation": "117.0", "time": "2013-09-20T09:34:10.421Z"}, {"lat": "44.136632", "lng": "9.681262", "elevation": "109.9000015258789", "time": "2013-09-20T09:34:18.314Z"}, {"lat": "44.13663", "lng": "9.681274", "elevation": "110.19999694824219", "time": "2013-09-20T09:34:18.356Z"}, {"lat": "44.136596", "lng": "9.681383", "elevation": "107.5", "time": "2013-09-20T09:34:43.441Z"}, {"lat": "44.136597", "lng": "9.681385", "elevation": "106.80000305175781", "time": "2013-09-20T09:34:43.473Z"}, {"lat": "44.136606", "lng": "9.681394", "elevation": "104.80000305175781", "time": "2013-09-20T09:34:48.412Z"}, {"lat": "44.136609", "lng": "9.681397", "elevation": "102.30000305175781", "time": "2013-09-20T09:34:48.444Z"}, {"lat": "44.13664", "lng": "9.681501", "elevation": "102.80000305175781", "time": "2013-09-20T09:35:01.438Z"}, {"lat": "44.13664", "lng": "9.6815", "elevation": "102.9000015258789", "time": "2013-09-20T09:35:02.371Z"}, {"lat": "44.13665", "lng": "9.681504", "elevation": "103.0", "time": "2013-09-20T09:35:31.443Z"}, {"lat": "44.13665", "lng": "9.681513", "elevation": "101.4000015258789", "time": "2013-09-20T09:35:32.341Z"}, {"lat": "44.13667", "lng": "9.681625", "elevation": "97.5", "time": "2013-09-20T09:35:38.483Z"}, {"lat": "44.136679", "lng": "9.681646", "elevation": "97.30000305175781", "time": "2013-09-20T09:35:39.341Z"}, {"lat": "44.13673", "lng": "9.681748", "elevation": "92.80000305175781", "time": "2013-09-20T09:35:50.359Z"}, {"lat": "44.136739", "lng": "9.681759", "elevation": "92.5999984741211", "time": "2013-09-20T09:35:51.437Z"}, {"lat": "44.136706", "lng": "9.681826", "elevation": "94.80000305175781", "time": "2013-09-20T09:36:08.146Z"}, {"lat": "44.136707", "lng": "9.681825", "elevation": "94.5", "time": "2013-09-20T09:36:08.390Z"}, {"lat": "44.136707", "lng": "9.681837", "elevation": "94.5", "time": "2013-09-20T09:36:39.352Z"}, {"lat": "44.136704", "lng": "9.681846", "elevation": "94.19999694824219", "time": "2013-09-20T09:36:40.378Z"}, {"lat": "44.136751", "lng": "9.68195", "elevation": "92.9000015258789", "time": "2013-09-20T09:36:48.422Z"}, {"lat": "44.136759", "lng": "9.681955", "elevation": "92.80000305175781", "time": "2013-09-20T09:36:49.442Z"}, {"lat": "44.136748", "lng": "9.682016", "elevation": "91.69999694824219", "time": "2013-09-20T09:37:00.429Z"}, {"lat": "44.136748", "lng": "9.682016", "elevation": "91.80000305175781", "time": "2013-09-20T09:37:01.458Z"}, {"lat": "44.136742", "lng": "9.682022", "elevation": "90.19999694824219", "time": "2013-09-20T09:37:20.330Z"}, {"lat": "44.136735", "lng": "9.68226", "elevation": "91.80000305175781", "time": "2013-09-20T09:37:38.350Z"}, {"lat": "44.136665", "lng": "9.682325", "elevation": "95.69999694824219", "time": "2013-09-20T09:37:52.361Z"}, {"lat": "44.136658", "lng": "9.682328", "elevation": "96.5", "time": "2013-09-20T09:37:53.354Z"}, {"lat": "44.136652", "lng": "9.682356", "elevation": "96.80000305175781", "time": "2013-09-20T09:38:01.402Z"}, {"lat": "44.136653", "lng": "9.682355", "elevation": "96.5", "time": "2013-09-20T09:38:02.353Z"}, {"lat": "44.136645", "lng": "9.682335", "elevation": "96.80000305175781", "time": "2013-09-20T09:38:21.350Z"}, {"lat": "44.136642", "lng": "9.682327", "elevation": "96.9000015258789", "time": "2013-09-20T09:38:22.352Z"}, {"lat": "44.136583", "lng": "9.682238", "elevation": "96.9000015258789", "time": "2013-09-20T09:38:29.385Z"}, {"lat": "44.136569", "lng": "9.682232", "elevation": "97.4000015258789", "time": "2013-09-20T09:38:30.362Z"}, {"lat": "44.136498", "lng": "9.682229", "elevation": "98.5", "time": "2013-09-20T09:38:34.355Z"}, {"lat": "44.136478", "lng": "9.68223", "elevation": "98.5999984741211", "time": "2013-09-20T09:38:35.346Z"}, {"lat": "44.136424", "lng": "9.682296", "elevation": "96.19999694824219", "time": "2013-09-20T09:38:50.350Z"}, {"lat": "44.136424", "lng": "9.682297", "elevation": "96.0999984741211", "time": "2013-09-20T09:38:51.347Z"}, {"lat": "44.136423", "lng": "9.682303", "elevation": "97.5999984741211", "time": "2013-09-20T09:40:53.088Z"}, {"lat": "44.136423", "lng": "9.682312", "elevation": "98.19999694824219", "time": "2013-09-20T09:40:53.412Z"}, {"lat": "44.1364", "lng": "9.682417", "elevation": "94.5", "time": "2013-09-20T09:40:58.346Z"}, {"lat": "44.136392", "lng": "9.682442", "elevation": "93.69999694824219", "time": "2013-09-20T09:40:59.357Z"}, {"lat": "44.136363", "lng": "9.682493", "elevation": "92.9000015258789", "time": "2013-09-20T09:41:02.357Z"}, {"lat": "44.136197", "lng": "9.682553", "elevation": "95.30000305175781", "time": "2013-09-20T09:41:56.360Z"}, {"lat": "44.136195", "lng": "9.682557", "elevation": "95.19999694824219", "time": "2013-09-20T09:41:57.381Z"}, {"lat": "44.136195", "lng": "9.682557", "elevation": "95.19999694824219", "time": "2013-09-20T09:41:58.201Z"}, {"lat": "44.136191", "lng": "9.682562", "elevation": "95.0999984741211", "time": "2013-09-20T09:41:58.415Z"}, {"lat": "44.136141", "lng": "9.682635", "elevation": "95.5", "time": "2013-09-20T09:42:01.418Z"}, {"lat": "44.136117", "lng": "9.68266", "elevation": "95.30000305175781", "time": "2013-09-20T09:42:02.411Z"}, {"lat": "44.136062", "lng": "9.682709", "elevation": "95.30000305175781", "time": "2013-09-20T09:42:04.388Z"}, {"lat": "44.136034", "lng": "9.682733", "elevation": "94.30000305175781", "time": "2013-09-20T09:42:05.350Z"}, {"lat": "44.135954", "lng": "9.682785", "elevation": "94.30000305175781", "time": "2013-09-20T09:42:08.350Z"}, {"lat": "44.135928", "lng": "9.682794", "elevation": "94.19999694824219", "time": "2013-09-20T09:42:09.434Z"}, {"lat": "44.135858", "lng": "9.682798", "elevation": "95.80000305175781", "time": "2013-09-20T09:42:17.378Z"}, {"lat": "44.135863", "lng": "9.682797", "elevation": "95.5999984741211", "time": "2013-09-20T09:42:18.394Z"}, {"lat": "44.135866", "lng": "9.682803", "elevation": "95.19999694824219", "time": "2013-09-20T09:42:27.370Z"}, {"lat": "44.135862", "lng": "9.682808", "elevation": "93.5999984741211", "time": "2013-09-20T09:42:28.402Z"}, {"lat": "44.135818", "lng": "9.682786", "elevation": "88.5999984741211", "time": "2013-09-20T09:42:38.527Z"}, {"lat": "44.135817", "lng": "9.682786", "elevation": "88.5999984741211", "time": "2013-09-20T09:42:39.437Z"}, {"lat": "44.135806", "lng": "9.682802", "elevation": "87.69999694824219", "time": "2013-09-20T09:42:49.407Z"}, {"lat": "44.1358", "lng": "9.682803", "elevation": "87.19999694824219", "time": "2013-09-20T09:42:50.384Z"}, {"lat": "44.135722", "lng": "9.682807", "elevation": "87.69999694824219", "time": "2013-09-20T09:42:55.448Z"}, {"lat": "44.135702", "lng": "9.682807", "elevation": "87.4000015258789", "time": "2013-09-20T09:42:56.430Z"}, {"lat": "44.13562", "lng": "9.682857", "elevation": "82.0", "time": "2013-09-20T09:43:03.346Z"}, {"lat": "44.135619", "lng": "9.682872", "elevation": "81.5", "time": "2013-09-20T09:43:03.379Z"}, {"lat": "44.13562", "lng": "9.682894", "elevation": "81.69999694824219", "time": "2013-09-20T09:43:08.423Z"}, {"lat": "44.135619", "lng": "9.68289", "elevation": "82.0", "time": "2013-09-20T09:43:09.374Z"}, {"lat": "44.135616", "lng": "9.682886", "elevation": "82.19999694824219", "time": "2013-09-20T09:43:10.438Z"}, {"lat": "44.135614", "lng": "9.682881", "elevation": "82.19999694824219", "time": "2013-09-20T09:43:11.446Z"}, {"lat": "44.135599", "lng": "9.682876", "elevation": "82.0999984741211", "time": "2013-09-20T09:43:17.377Z"}, {"lat": "44.135598", "lng": "9.682876", "elevation": "82.0999984741211", "time": "2013-09-20T09:43:18.486Z"}, {"lat": "44.135599", "lng": "9.682874", "elevation": "82.30000305175781", "time": "2013-09-20T09:44:03.157Z"}, {"lat": "44.135593", "lng": "9.682876", "elevation": "82.30000305175781", "time": "2013-09-20T09:44:03.443Z"}, {"lat": "44.135517", "lng": "9.682874", "elevation": "82.30000305175781", "time": "2013-09-20T09:44:09.412Z"}, {"lat": "44.1355", "lng": "9.682868", "elevation": "82.19999694824219", "time": "2013-09-20T09:44:10.395Z"}, {"lat": "44.135417", "lng": "9.682881", "elevation": "84.5999984741211", "time": "2013-09-20T09:44:14.495Z"}, {"lat": "44.135402", "lng": "9.682891", "elevation": "85.0", "time": "2013-09-20T09:44:15.483Z"}, {"lat": "44.135328", "lng": "9.682879", "elevation": "83.30000305175781", "time": "2013-09-20T09:44:28.361Z"}, {"lat": "44.135333", "lng": "9.682882", "elevation": "83.30000305175781", "time": "2013-09-20T09:44:29.374Z"}, {"lat": "44.135338", "lng": "9.682885", "elevation": "83.30000305175781", "time": "2013-09-20T09:44:30.412Z"}, {"lat": "44.135343", "lng": "9.682889", "elevation": "83.30000305175781", "time": "2013-09-20T09:44:31.399Z"}, {"lat": "44.135358", "lng": "9.682907", "elevation": "83.30000305175781", "time": "2013-09-20T09:44:37.373Z"}, {"lat": "44.134955", "lng": "9.683342", "elevation": "82.30000305175781", "time": "2013-09-20T09:45:28.402Z"}, {"lat": "44.135033", "lng": "9.683385", "elevation": "82.0999984741211", "time": "2013-09-20T09:45:33.739Z"}, {"lat": "44.135044", "lng": "9.683382", "elevation": "82.0999984741211", "time": "2013-09-20T09:45:34.380Z"}, {"lat": "44.135052", "lng": "9.68337", "elevation": "82.19999694824219", "time": "2013-09-20T09:45:39.418Z"}, {"lat": "44.135049", "lng": "9.683368", "elevation": "82.19999694824219", "time": "2013-09-20T09:45:40.395Z"}, {"lat": "44.135041", "lng": "9.683368", "elevation": "82.0999984741211", "time": "2013-09-20T09:45:42.379Z"}, {"lat": "44.135027", "lng": "9.683364", "elevation": "82.0999984741211", "time": "2013-09-20T09:45:43.366Z"}, {"lat": "44.134971", "lng": "9.683342", "elevation": "82.0999984741211", "time": "2013-09-20T09:45:53.383Z"}, {"lat": "44.134971", "lng": "9.683342", "elevation": "82.0999984741211", "time": "2013-09-20T09:45:54.394Z"}, {"lat": "44.134975", "lng": "9.683344", "elevation": "82.0999984741211", "time": "2013-09-20T09:46:03.372Z"}, {"lat": "44.134663", "lng": "9.685106", "elevation": "53.70000076293945", "time": "2013-09-20T11:43:28.836Z"}, {"lat": "44.134807", "lng": "9.685014", "elevation": "31.0", "time": "2013-09-20T11:43:29.744Z"}, {"lat": "44.134857", "lng": "9.68503", "elevation": "39.400001525878906", "time": "2013-09-20T11:43:30.744Z"}, {"lat": "44.134907", "lng": "9.685014", "elevation": "22.299999237060547", "time": "2013-09-20T11:43:31.765Z"}, {"lat": "44.134884", "lng": "9.685022", "elevation": "36.20000076293945", "time": "2013-09-20T11:43:32.744Z"}, {"lat": "44.134806", "lng": "9.684967", "elevation": "65.0", "time": "2013-09-20T11:43:33.745Z"}, {"lat": "44.134806", "lng": "9.684967", "elevation": "65.0", "time": "2013-09-20T11:43:33.845Z"}, {"lat": "44.134683", "lng": "9.685084", "elevation": "65.30000305175781", "time": "2013-09-20T11:43:34.761Z"}, {"lat": "44.134762", "lng": "9.685107", "elevation": "42.70000076293945", "time": "2013-09-20T11:43:35.764Z"}, {"lat": "44.134857", "lng": "9.685136", "elevation": "41.099998474121094", "time": "2013-09-20T11:43:36.749Z"}, {"lat": "44.134875", "lng": "9.685044", "elevation": "59.5", "time": "2013-09-20T11:43:40.751Z"}, {"lat": "44.134842", "lng": "9.68501", "elevation": "51.5", "time": "2013-09-20T11:43:41.759Z"}, {"lat": "44.134779", "lng": "9.684951", "elevation": "56.0", "time": "2013-09-20T11:43:44.756Z"}, {"lat": "44.13472", "lng": "9.684971", "elevation": "65.19999694824219", "time": "2013-09-20T11:43:45.758Z"}, {"lat": "44.13465", "lng": "9.684897", "elevation": "68.5", "time": "2013-09-20T11:44:01.151Z"}, {"lat": "44.134646", "lng": "9.684896", "elevation": "69.30000305175781", "time": "2013-09-20T11:44:02.144Z"}, {"lat": "44.134566", "lng": "9.684845", "elevation": "69.19999694824219", "time": "2013-09-20T11:44:31.143Z"}, {"lat": "44.134553", "lng": "9.684842", "elevation": "69.0999984741211", "time": "2013-09-20T11:44:32.151Z"}, {"lat": "44.134488", "lng": "9.684778", "elevation": "72.9000015258789", "time": "2013-09-20T11:44:39.145Z"}, {"lat": "44.134478", "lng": "9.684769", "elevation": "72.80000305175781", "time": "2013-09-20T11:44:40.148Z"}, {"lat": "44.134453", "lng": "9.684716", "elevation": "71.9000015258789", "time": "2013-09-20T11:44:49.178Z"}, {"lat": "44.134452", "lng": "9.684716", "elevation": "72.0999984741211", "time": "2013-09-20T11:44:50.145Z"}, {"lat": "44.134453", "lng": "9.684705", "elevation": "72.9000015258789", "time": "2013-09-20T11:45:05.201Z"}, {"lat": "44.134451", "lng": "9.684696", "elevation": "74.30000305175781", "time": "2013-09-20T11:45:06.153Z"}, {"lat": "44.134457", "lng": "9.684581", "elevation": "77.19999694824219", "time": "2013-09-20T11:45:19.243Z"}, {"lat": "44.134457", "lng": "9.684581", "elevation": "77.30000305175781", "time": "2013-09-20T11:45:20.223Z"}, {"lat": "44.134455", "lng": "9.684583", "elevation": "77.5", "time": "2013-09-20T11:45:36.233Z"}, {"lat": "44.134449", "lng": "9.684583", "elevation": "78.0999984741211", "time": "2013-09-20T11:45:37.173Z"}, {"lat": "44.134413", "lng": "9.684617", "elevation": "80.4000015258789", "time": "2013-09-20T11:45:43.284Z"}, {"lat": "44.134391", "lng": "9.684664", "elevation": "82.30000305175781", "time": "2013-09-20T11:45:48.165Z"}, {"lat": "44.134324", "lng": "9.684725", "elevation": "79.80000305175781", "time": "2013-09-20T11:46:00.155Z"}, {"lat": "44.134327", "lng": "9.684724", "elevation": "79.9000015258789", "time": "2013-09-20T11:46:01.160Z"}, {"lat": "44.134329", "lng": "9.684725", "elevation": "80.19999694824219", "time": "2013-09-20T11:46:02.155Z"}, {"lat": "44.13434", "lng": "9.68473", "elevation": "80.9000015258789", "time": "2013-09-20T11:46:03.152Z"}, {"lat": "44.134355", "lng": "9.684735", "elevation": "80.69999694824219", "time": "2013-09-20T11:46:06.161Z"}, {"lat": "44.134355", "lng": "9.684736", "elevation": "80.80000305175781", "time": "2013-09-20T11:46:07.157Z"}, {"lat": "44.134354", "lng": "9.684733", "elevation": "81.19999694824219", "time": "2013-09-20T11:46:12.212Z"}, {"lat": "44.134351", "lng": "9.68473", "elevation": "81.19999694824219", "time": "2013-09-20T11:46:13.160Z"}, {"lat": "44.134286", "lng": "9.684733", "elevation": "83.5", "time": "2013-09-20T11:46:27.161Z"}, {"lat": "44.134287", "lng": "9.684733", "elevation": "83.4000015258789", "time": "2013-09-20T11:46:28.168Z"}, {"lat": "44.134277", "lng": "9.684726", "elevation": "87.5999984741211", "time": "2013-09-20T11:48:06.183Z"}, {"lat": "44.134276", "lng": "9.684735", "elevation": "88.5", "time": "2013-09-20T11:48:07.274Z"}, {"lat": "44.134284", "lng": "9.684856", "elevation": "90.0", "time": "2013-09-20T11:48:16.232Z"}, {"lat": "44.134286", "lng": "9.684878", "elevation": "90.5", "time": "2013-09-20T11:48:17.215Z"}, {"lat": "44.134275", "lng": "9.684992", "elevation": "88.9000015258789", "time": "2013-09-20T11:48:23.341Z"}, {"lat": "44.134271", "lng": "9.685005", "elevation": "88.30000305175781", "time": "2013-09-20T11:48:24.244Z"}, {"lat": "44.134207", "lng": "9.685056", "elevation": "88.19999694824219", "time": "2013-09-20T11:48:37.186Z"}, {"lat": "44.134207", "lng": "9.685055", "elevation": "88.19999694824219", "time": "2013-09-20T11:48:38.203Z"}, {"lat": "44.134212", "lng": "9.685062", "elevation": "88.4000015258789", "time": "2013-09-20T11:48:45.212Z"}, {"lat": "44.134217", "lng": "9.685065", "elevation": "87.80000305175781", "time": "2013-09-20T11:48:46.171Z"}, {"lat": "44.134234", "lng": "9.68509", "elevation": "87.0999984741211", "time": "2013-09-20T11:48:53.125Z"}, {"lat": "44.134234", "lng": "9.68509", "elevation": "86.80000305175781", "time": "2013-09-20T11:48:53.208Z"}, {"lat": "44.13423", "lng": "9.685092", "elevation": "87.0", "time": "2013-09-20T11:48:56.175Z"}, {"lat": "44.134225", "lng": "9.685089", "elevation": "86.5", "time": "2013-09-20T11:48:57.241Z"}, {"lat": "44.134191", "lng": "9.685074", "elevation": "90.30000305175781", "time": "2013-09-20T11:49:07.183Z"}, {"lat": "44.13419", "lng": "9.685075", "elevation": "90.19999694824219", "time": "2013-09-20T11:49:08.182Z"}, {"lat": "44.134185", "lng": "9.685077", "elevation": "91.0999984741211", "time": "2013-09-20T11:49:14.181Z"}, {"lat": "44.134182", "lng": "9.685085", "elevation": "90.69999694824219", "time": "2013-09-20T11:49:15.193Z"}, {"lat": "44.134132", "lng": "9.685179", "elevation": "91.0999984741211", "time": "2013-09-20T11:49:23.282Z"}, {"lat": "44.13413", "lng": "9.685195", "elevation": "91.0", "time": "2013-09-20T11:49:24.179Z"}, {"lat": "44.134134", "lng": "9.685243", "elevation": "92.5", "time": "2013-09-20T11:49:30.174Z"}, {"lat": "44.134135", "lng": "9.685239", "elevation": "92.4000015258789", "time": "2013-09-20T11:49:31.178Z"}, {"lat": "44.134131", "lng": "9.685253", "elevation": "94.0", "time": "2013-09-20T11:50:03.199Z"}, {"lat": "44.134127", "lng": "9.685259", "elevation": "94.5", "time": "2013-09-20T11:50:04.179Z"}, {"lat": "44.134111", "lng": "9.685357", "elevation": "98.0999984741211", "time": "2013-09-20T11:50:16.243Z"}, {"lat": "44.13411", "lng": "9.685357", "elevation": "98.30000305175781", "time": "2013-09-20T11:50:17.221Z"}, {"lat": "44.134101", "lng": "9.685357", "elevation": "99.5", "time": "2013-09-20T11:50:28.418Z"}, {"lat": "44.134095", "lng": "9.685358", "elevation": "100.0999984741211", "time": "2013-09-20T11:50:29.194Z"}, {"lat": "44.13401", "lng": "9.685381", "elevation": "100.69999694824219", "time": "2013-09-20T11:50:38.677Z"}, {"lat": "44.134001", "lng": "9.685388", "elevation": "99.80000305175781", "time": "2013-09-20T11:50:39.276Z"}, {"lat": "44.133965", "lng": "9.685502", "elevation": "90.30000305175781", "time": "2013-09-20T11:50:44.181Z"}, {"lat": "44.133962", "lng": "9.685525", "elevation": "90.0", "time": "2013-09-20T11:50:45.215Z"}, {"lat": "44.133912", "lng": "9.68562", "elevation": "91.0", "time": "2013-09-20T11:50:53.178Z"}, {"lat": "44.133905", "lng": "9.685635", "elevation": "90.80000305175781", "time": "2013-09-20T11:50:54.180Z"}, {"lat": "44.133842", "lng": "9.685708", "elevation": "91.80000305175781", "time": "2013-09-20T11:51:01.181Z"}, {"lat": "44.133834", "lng": "9.685717", "elevation": "90.69999694824219", "time": "2013-09-20T11:51:02.196Z"}, {"lat": "44.133794", "lng": "9.685815", "elevation": "90.5999984741211", "time": "2013-09-20T11:51:10.184Z"}, {"lat": "44.133788", "lng": "9.685826", "elevation": "91.5999984741211", "time": "2013-09-20T11:51:11.186Z"}, {"lat": "44.133727", "lng": "9.685894", "elevation": "96.0999984741211", "time": "2013-09-20T11:51:17.190Z"}, {"lat": "44.133715", "lng": "9.685901", "elevation": "95.5", "time": "2013-09-20T11:51:18.182Z"}, {"lat": "44.133657", "lng": "9.685932", "elevation": "98.5", "time": "2013-09-20T11:51:28.938Z"}, {"lat": "44.133658", "lng": "9.68593", "elevation": "99.69999694824219", "time": "2013-09-20T11:51:29.213Z"}, {"lat": "44.133655", "lng": "9.685926", "elevation": "101.0", "time": "2013-09-20T11:51:30.182Z"}, {"lat": "44.133635", "lng": "9.685912", "elevation": "103.0", "time": "2013-09-20T11:51:37.203Z"}, {"lat": "44.133633", "lng": "9.685913", "elevation": "103.69999694824219", "time": "2013-09-20T11:51:38.197Z"}, {"lat": "44.133632", "lng": "9.685919", "elevation": "110.4000015258789", "time": "2013-09-20T11:51:47.194Z"}, {"lat": "44.133627", "lng": "9.685926", "elevation": "110.5", "time": "2013-09-20T11:51:48.197Z"}, {"lat": "44.133579", "lng": "9.686024", "elevation": "112.0", "time": "2013-09-20T11:51:58.253Z"}, {"lat": "44.133574", "lng": "9.68603", "elevation": "112.9000015258789", "time": "2013-09-20T11:51:59.286Z"}, {"lat": "44.13351", "lng": "9.686115", "elevation": "113.19999694824219", "time": "2013-09-20T11:52:15.260Z"}, {"lat": "44.133508", "lng": "9.686117", "elevation": "111.4000015258789", "time": "2013-09-20T11:52:16.195Z"}, {"lat": "44.133449", "lng": "9.6862", "elevation": "120.0999984741211", "time": "2013-09-20T11:52:30.195Z"}, {"lat": "44.133443", "lng": "9.686208", "elevation": "119.69999694824219", "time": "2013-09-20T11:52:31.202Z"}, {"lat": "44.133378", "lng": "9.68628", "elevation": "124.69999694824219", "time": "2013-09-20T11:52:41.234Z"}, {"lat": "44.133366", "lng": "9.686279", "elevation": "125.19999694824219", "time": "2013-09-20T11:52:42.203Z"}, {"lat": "44.133279", "lng": "9.68631", "elevation": "125.80000305175781", "time": "2013-09-20T11:52:51.199Z"}, {"lat": "44.133273", "lng": "9.686313", "elevation": "126.0999984741211", "time": "2013-09-20T11:52:52.207Z"}, {"lat": "44.133205", "lng": "9.686391", "elevation": "129.89999389648438", "time": "2013-09-20T11:53:05.190Z"}, {"lat": "44.133198", "lng": "9.686383", "elevation": "129.6999969482422", "time": "2013-09-20T11:53:06.199Z"}, {"lat": "44.133194", "lng": "9.686363", "elevation": "129.39999389648438", "time": "2013-09-20T11:53:11.197Z"}, {"lat": "44.133194", "lng": "9.686364", "elevation": "128.89999389648438", "time": "2013-09-20T11:53:12.211Z"}, {"lat": "44.133195", "lng": "9.686378", "elevation": "129.10000610351563", "time": "2013-09-20T11:53:15.348Z"}, {"lat": "44.133196", "lng": "9.686384", "elevation": "128.6999969482422", "time": "2013-09-20T11:53:16.197Z"}, {"lat": "44.133205", "lng": "9.68641", "elevation": "129.5", "time": "2013-09-20T11:53:22.202Z"}, {"lat": "44.133205", "lng": "9.68641", "elevation": "129.5", "time": "2013-09-20T11:53:23.205Z"}, {"lat": "44.133202", "lng": "9.686414", "elevation": "128.60000610351563", "time": "2013-09-20T11:53:49.203Z"}, {"lat": "44.133203", "lng": "9.686419", "elevation": "128.39999389648438", "time": "2013-09-20T11:53:50.325Z"}, {"lat": "44.133202", "lng": "9.686527", "elevation": "126.5999984741211", "time": "2013-09-20T11:54:00.263Z"}, {"lat": "44.133202", "lng": "9.686549", "elevation": "125.69999694824219", "time": "2013-09-20T11:54:01.240Z"}, {"lat": "44.133192", "lng": "9.686659", "elevation": "127.30000305175781", "time": "2013-09-20T11:54:06.214Z"}, {"lat": "44.133187", "lng": "9.686677", "elevation": "127.0", "time": "2013-09-20T11:54:07.201Z"}, {"lat": "44.133136", "lng": "9.686765", "elevation": "129.1999969482422", "time": "2013-09-20T11:54:14.205Z"}, {"lat": "44.133128", "lng": "9.686776", "elevation": "129.3000030517578", "time": "2013-09-20T11:54:15.298Z"}, {"lat": "44.133068", "lng": "9.686852", "elevation": "131.10000610351563", "time": "2013-09-20T11:54:23.211Z"}, {"lat": "44.133064", "lng": "9.686867", "elevation": "131.0", "time": "2013-09-20T11:54:24.209Z"}, {"lat": "44.133024", "lng": "9.686974", "elevation": "128.60000610351563", "time": "2013-09-20T11:54:33.237Z"}, {"lat": "44.133018", "lng": "9.686986", "elevation": "128.5", "time": "2013-09-20T11:54:34.206Z"}, {"lat": "44.132967", "lng": "9.687078", "elevation": "131.1999969482422", "time": "2013-09-20T11:54:47.329Z"}, {"lat": "44.132962", "lng": "9.687087", "elevation": "129.8000030517578", "time": "2013-09-20T11:54:48.300Z"}, {"lat": "44.132916", "lng": "9.68719", "elevation": "128.8000030517578", "time": "2013-09-20T11:54:55.296Z"}, {"lat": "44.132911", "lng": "9.6872", "elevation": "128.89999389648438", "time": "2013-09-20T11:54:56.281Z"}, {"lat": "44.132873", "lng": "9.6873", "elevation": "130.1999969482422", "time": "2013-09-20T11:55:06.211Z"}, {"lat": "44.132865", "lng": "9.687314", "elevation": "131.39999389648438", "time": "2013-09-20T11:55:07.215Z"}, {"lat": "44.13282", "lng": "9.687412", "elevation": "134.1999969482422", "time": "2013-09-20T11:55:14.225Z"}, {"lat": "44.132812", "lng": "9.687425", "elevation": "134.5", "time": "2013-09-20T11:55:15.211Z"}, {"lat": "44.132782", "lng": "9.687533", "elevation": "130.5", "time": "2013-09-20T11:55:22.210Z"}, {"lat": "44.132784", "lng": "9.687551", "elevation": "130.89999389648438", "time": "2013-09-20T11:55:23.215Z"}, {"lat": "44.132749", "lng": "9.687664", "elevation": "133.6999969482422", "time": "2013-09-20T11:55:31.210Z"}, {"lat": "44.132745", "lng": "9.68768", "elevation": "134.1999969482422", "time": "2013-09-20T11:55:32.223Z"}, {"lat": "44.132741", "lng": "9.687762", "elevation": "133.89999389648438", "time": "2013-09-20T11:55:43.210Z"}, {"lat": "44.132742", "lng": "9.687762", "elevation": "134.3000030517578", "time": "2013-09-20T11:55:44.224Z"}, {"lat": "44.13274", "lng": "9.687767", "elevation": "135.10000610351563", "time": "2013-09-20T11:55:47.211Z"}, {"lat": "44.132738", "lng": "9.687772", "elevation": "136.39999389648438", "time": "2013-09-20T11:55:48.204Z"}, {"lat": "44.132731", "lng": "9.687895", "elevation": "138.1999969482422", "time": "2013-09-20T11:56:00.256Z"}, {"lat": "44.132735", "lng": "9.687903", "elevation": "137.6999969482422", "time": "2013-09-20T11:56:01.214Z"}, {"lat": "44.132757", "lng": "9.688023", "elevation": "137.6999969482422", "time": "2013-09-20T11:56:13.312Z"}, {"lat": "44.132754", "lng": "9.688027", "elevation": "138.3000030517578", "time": "2013-09-20T11:56:14.220Z"}, {"lat": "44.132759", "lng": "9.688064", "elevation": "141.3000030517578", "time": "2013-09-20T11:56:24.216Z"}, {"lat": "44.132759", "lng": "9.688065", "elevation": "141.3000030517578", "time": "2013-09-20T11:56:25.213Z"}, {"lat": "44.13275", "lng": "9.688102", "elevation": "143.39999389648438", "time": "2013-09-20T11:56:49.219Z"}, {"lat": "44.13275", "lng": "9.688108", "elevation": "143.3000030517578", "time": "2013-09-20T11:56:50.290Z"}, {"lat": "44.132754", "lng": "9.688224", "elevation": "144.1999969482422", "time": "2013-09-20T11:56:57.274Z"}, {"lat": "44.132755", "lng": "9.688243", "elevation": "144.39999389648438", "time": "2013-09-20T11:56:58.229Z"}, {"lat": "44.132775", "lng": "9.688359", "elevation": "141.8000030517578", "time": "2013-09-20T11:57:05.219Z"}, {"lat": "44.132778", "lng": "9.688378", "elevation": "141.5", "time": "2013-09-20T11:57:06.218Z"}, {"lat": "44.132775", "lng": "9.688494", "elevation": "142.0", "time": "2013-09-20T11:57:12.226Z"}, {"lat": "44.132773", "lng": "9.688514", "elevation": "140.8000030517578", "time": "2013-09-20T11:57:13.327Z"}, {"lat": "44.132758", "lng": "9.688635", "elevation": "142.39999389648438", "time": "2013-09-20T11:57:19.222Z"}, {"lat": "44.132756", "lng": "9.688659", "elevation": "142.39999389648438", "time": "2013-09-20T11:57:20.225Z"}, {"lat": "44.132744", "lng": "9.688781", "elevation": "142.3000030517578", "time": "2013-09-20T11:57:25.257Z"}, {"lat": "44.132741", "lng": "9.688806", "elevation": "142.5", "time": "2013-09-20T11:57:26.309Z"}, {"lat": "44.132725", "lng": "9.688919", "elevation": "141.6999969482422", "time": "2013-09-20T11:57:31.226Z"}, {"lat": "44.132725", "lng": "9.688943", "elevation": "141.10000610351563", "time": "2013-09-20T11:57:32.270Z"}, {"lat": "44.132733", "lng": "9.689059", "elevation": "138.89999389648438", "time": "2013-09-20T11:57:37.226Z"}, {"lat": "44.132738", "lng": "9.689083", "elevation": "139.39999389648438", "time": "2013-09-20T11:57:38.218Z"}, {"lat": "44.132716", "lng": "9.689201", "elevation": "137.89999389648438", "time": "2013-09-20T11:57:45.262Z"}, {"lat": "44.132707", "lng": "9.689214", "elevation": "138.6999969482422", "time": "2013-09-20T11:57:46.421Z"}, {"lat": "44.13265", "lng": "9.689307", "elevation": "137.3000030517578", "time": "2013-09-20T11:58:02.222Z"}, {"lat": "44.132656", "lng": "9.689324", "elevation": "137.8000030517578", "time": "2013-09-20T11:58:03.306Z"}, {"lat": "44.13269", "lng": "9.689431", "elevation": "135.6999969482422", "time": "2013-09-20T11:58:11.338Z"}, {"lat": "44.132692", "lng": "9.689446", "elevation": "134.89999389648438", "time": "2013-09-20T11:58:12.383Z"}, {"lat": "44.132681", "lng": "9.689563", "elevation": "135.1999969482422", "time": "2013-09-20T11:58:20.266Z"}, {"lat": "44.132678", "lng": "9.689578", "elevation": "135.1999969482422", "time": "2013-09-20T11:58:21.231Z"}, {"lat": "44.13266", "lng": "9.689694", "elevation": "137.10000610351563", "time": "2013-09-20T11:58:30.227Z"}, {"lat": "44.132654", "lng": "9.689707", "elevation": "136.60000610351563", "time": "2013-09-20T11:58:31.226Z"}, {"lat": "44.132598", "lng": "9.689803", "elevation": "138.39999389648438", "time": "2013-09-20T11:58:39.221Z"}, {"lat": "44.132596", "lng": "9.689812", "elevation": "138.10000610351563", "time": "2013-09-20T11:58:40.275Z"}, {"lat": "44.132594", "lng": "9.689823", "elevation": "137.89999389648438", "time": "2013-09-20T11:58:45.241Z"}, {"lat": "44.132593", "lng": "9.689823", "elevation": "138.39999389648438", "time": "2013-09-20T11:58:46.230Z"}, {"lat": "44.132585", "lng": "9.689827", "elevation": "138.3000030517578", "time": "2013-09-20T11:58:55.225Z"}, {"lat": "44.132582", "lng": "9.689834", "elevation": "137.89999389648438", "time": "2013-09-20T11:58:56.231Z"}, {"lat": "44.132518", "lng": "9.689917", "elevation": "139.1999969482422", "time": "2013-09-20T11:59:03.223Z"}, {"lat": "44.132503", "lng": "9.689923", "elevation": "139.1999969482422", "time": "2013-09-20T11:59:04.391Z"}, {"lat": "44.132441", "lng": "9.689981", "elevation": "135.5", "time": "2013-09-20T11:59:10.226Z"}, {"lat": "44.13243", "lng": "9.689998", "elevation": "136.39999389648438", "time": "2013-09-20T11:59:11.227Z"}, {"lat": "44.132368", "lng": "9.690086", "elevation": "136.89999389648438", "time": "2013-09-20T11:59:16.265Z"}, {"lat": "44.132359", "lng": "9.690106", "elevation": "137.6999969482422", "time": "2013-09-20T11:59:17.276Z"}, {"lat": "44.132334", "lng": "9.690212", "elevation": "133.1999969482422", "time": "2013-09-20T11:59:22.229Z"}, {"lat": "44.132329", "lng": "9.690232", "elevation": "133.89999389648438", "time": "2013-09-20T11:59:23.234Z"}, {"lat": "44.132301", "lng": "9.690341", "elevation": "130.0", "time": "2013-09-20T11:59:30.317Z"}, {"lat": "44.132303", "lng": "9.690363", "elevation": "130.3000030517578", "time": "2013-09-20T11:59:31.239Z"}, {"lat": "44.132272", "lng": "9.690465", "elevation": "127.9000015258789", "time": "2013-09-20T11:59:38.243Z"}, {"lat": "44.132265", "lng": "9.690477", "elevation": "129.60000610351563", "time": "2013-09-20T11:59:39.334Z"}, {"lat": "44.132224", "lng": "9.690581", "elevation": "129.39999389648438", "time": "2013-09-20T11:59:48.453Z"}, {"lat": "44.132226", "lng": "9.690597", "elevation": "128.8000030517578", "time": "2013-09-20T11:59:49.327Z"}, {"lat": "44.132231", "lng": "9.690711", "elevation": "127.19999694824219", "time": "2013-09-20T11:59:56.311Z"}, {"lat": "44.132231", "lng": "9.690723", "elevation": "125.9000015258789", "time": "2013-09-20T11:59:57.255Z"}, {"lat": "44.13221", "lng": "9.690783", "elevation": "129.0", "time": "2013-09-20T12:00:11.240Z"}, {"lat": "44.132213", "lng": "9.690787", "elevation": "129.0", "time": "2013-09-20T12:00:12.304Z"}, {"lat": "44.132218", "lng": "9.690793", "elevation": "128.60000610351563", "time": "2013-09-20T12:00:13.262Z"}, {"lat": "44.132238", "lng": "9.690817", "elevation": "128.10000610351563", "time": "2013-09-20T12:00:20.231Z"}, {"lat": "44.132239", "lng": "9.690817", "elevation": "128.1999969482422", "time": "2013-09-20T12:00:21.233Z"}, {"lat": "44.132235", "lng": "9.690816", "elevation": "128.1999969482422", "time": "2013-09-20T12:00:31.234Z"}, {"lat": "44.132228", "lng": "9.690816", "elevation": "128.5", "time": "2013-09-20T12:00:32.233Z"}, {"lat": "44.132147", "lng": "9.690857", "elevation": "129.8000030517578", "time": "2013-09-20T12:00:38.262Z"}, {"lat": "44.132133", "lng": "9.690872", "elevation": "130.5", "time": "2013-09-20T12:00:39.241Z"}, {"lat": "44.132094", "lng": "9.690973", "elevation": "131.10000610351563", "time": "2013-09-20T12:00:44.244Z"}, {"lat": "44.132088", "lng": "9.690988", "elevation": "131.3000030517578", "time": "2013-09-20T12:00:45.234Z"}, {"lat": "44.132052", "lng": "9.691091", "elevation": "130.8000030517578", "time": "2013-09-20T12:00:55.233Z"}, {"lat": "44.132049", "lng": "9.691105", "elevation": "131.60000610351563", "time": "2013-09-20T12:00:56.232Z"}, {"lat": "44.132004", "lng": "9.691212", "elevation": "131.39999389648438", "time": "2013-09-20T12:01:03.239Z"}, {"lat": "44.131997", "lng": "9.691229", "elevation": "131.3000030517578", "time": "2013-09-20T12:01:04.244Z"}, {"lat": "44.13198", "lng": "9.69126", "elevation": "131.0", "time": "2013-09-20T12:01:06.256Z"}, {"lat": "44.131924", "lng": "9.691412", "elevation": "132.6999969482422", "time": "2013-09-20T12:01:16.223Z"}, {"lat": "44.131908", "lng": "9.691519", "elevation": "131.89999389648438", "time": "2013-09-20T12:01:22.282Z"}, {"lat": "44.131905", "lng": "9.691535", "elevation": "131.60000610351563", "time": "2013-09-20T12:01:23.256Z"}, {"lat": "44.131852", "lng": "9.691635", "elevation": "133.10000610351563", "time": "2013-09-20T12:01:32.270Z"}, {"lat": "44.131838", "lng": "9.691635", "elevation": "133.8000030517578", "time": "2013-09-20T12:01:33.237Z"}, {"lat": "44.131776", "lng": "9.691718", "elevation": "134.10000610351563", "time": "2013-09-20T12:01:42.268Z"}, {"lat": "44.131766", "lng": "9.691726", "elevation": "134.0", "time": "2013-09-20T12:01:43.254Z"}, {"lat": "44.131685", "lng": "9.691775", "elevation": "133.39999389648438", "time": "2013-09-20T12:01:50.249Z"}, {"lat": "44.131671", "lng": "9.691779", "elevation": "132.60000610351563", "time": "2013-09-20T12:01:51.242Z"}, {"lat": "44.131625", "lng": "9.691876", "elevation": "129.89999389648438", "time": "2013-09-20T12:01:59.401Z"}, {"lat": "44.13162", "lng": "9.691888", "elevation": "130.5", "time": "2013-09-20T12:02:00.452Z"}, {"lat": "44.13156", "lng": "9.691973", "elevation": "133.1999969482422", "time": "2013-09-20T12:02:09.242Z"}, {"lat": "44.131555", "lng": "9.691984", "elevation": "133.39999389648438", "time": "2013-09-20T12:02:10.252Z"}, {"lat": "44.13151", "lng": "9.692083", "elevation": "133.6999969482422", "time": "2013-09-20T12:02:19.252Z"}, {"lat": "44.131508", "lng": "9.692097", "elevation": "133.8000030517578", "time": "2013-09-20T12:02:20.245Z"}, {"lat": "44.13145", "lng": "9.692182", "elevation": "134.3000030517578", "time": "2013-09-20T12:02:29.238Z"}, {"lat": "44.131441", "lng": "9.692189", "elevation": "134.39999389648438", "time": "2013-09-20T12:02:30.245Z"}, {"lat": "44.131361", "lng": "9.692245", "elevation": "140.89999389648438", "time": "2013-09-20T12:02:40.358Z"}, {"lat": "44.131355", "lng": "9.69225", "elevation": "140.6999969482422", "time": "2013-09-20T12:02:41.279Z"}, {"lat": "44.131277", "lng": "9.692304", "elevation": "144.1999969482422", "time": "2013-09-20T12:02:53.277Z"}, {"lat": "44.131271", "lng": "9.692311", "elevation": "143.89999389648438", "time": "2013-09-20T12:02:54.368Z"}, {"lat": "44.131207", "lng": "9.692396", "elevation": "149.89999389648438", "time": "2013-09-20T12:03:07.248Z"}, {"lat": "44.131205", "lng": "9.692406", "elevation": "149.60000610351563", "time": "2013-09-20T12:03:08.265Z"}, {"lat": "44.1312", "lng": "9.692455", "elevation": "149.8000030517578", "time": "2013-09-20T12:03:15.265Z"}, {"lat": "44.131202", "lng": "9.692456", "elevation": "149.60000610351563", "time": "2013-09-20T12:03:16.256Z"}, {"lat": "44.131204", "lng": "9.692461", "elevation": "149.5", "time": "2013-09-20T12:03:19.361Z"}, {"lat": "44.131203", "lng": "9.692466", "elevation": "149.6999969482422", "time": "2013-09-20T12:03:20.312Z"}, {"lat": "44.131145", "lng": "9.692559", "elevation": "153.0", "time": "2013-09-20T12:03:32.242Z"}, {"lat": "44.131137", "lng": "9.692568", "elevation": "152.8000030517578", "time": "2013-09-20T12:03:33.275Z"}, {"lat": "44.131071", "lng": "9.692643", "elevation": "153.89999389648438", "time": "2013-09-20T12:03:45.243Z"}, {"lat": "44.131065", "lng": "9.692646", "elevation": "154.8000030517578", "time": "2013-09-20T12:03:46.251Z"}, {"lat": "44.131004", "lng": "9.692735", "elevation": "162.6999969482422", "time": "2013-09-20T12:04:00.249Z"}, {"lat": "44.130998", "lng": "9.692735", "elevation": "161.89999389648438", "time": "2013-09-20T12:04:01.252Z"}, {"lat": "44.130974", "lng": "9.69277", "elevation": "162.8000030517578", "time": "2013-09-20T12:04:10.244Z"}, {"lat": "44.130975", "lng": "9.692769", "elevation": "163.6999969482422", "time": "2013-09-20T12:04:11.252Z"}, {"lat": "44.13096", "lng": "9.692758", "elevation": "165.60000610351563", "time": "2013-09-20T12:05:04.248Z"}, {"lat": "44.130953", "lng": "9.692763", "elevation": "165.1999969482422", "time": "2013-09-20T12:05:05.256Z"}, {"lat": "44.130945", "lng": "9.692876", "elevation": "160.10000610351563", "time": "2013-09-20T12:05:14.265Z"}, {"lat": "44.130943", "lng": "9.692887", "elevation": "160.5", "time": "2013-09-20T12:05:15.256Z"}, {"lat": "44.130979", "lng": "9.692999", "elevation": "169.10000610351563", "time": "2013-09-20T12:05:39.270Z"}, {"lat": "44.130976", "lng": "9.693003", "elevation": "169.89999389648438", "time": "2013-09-20T12:05:40.264Z"}, {"lat": "44.130921", "lng": "9.693018", "elevation": "171.39999389648438", "time": "2013-09-20T12:05:50.347Z"}, {"lat": "44.13092", "lng": "9.693017", "elevation": "172.3000030517578", "time": "2013-09-20T12:05:51.355Z"}, {"lat": "44.130914", "lng": "9.693011", "elevation": "172.89999389648438", "time": "2013-09-20T12:06:20.277Z"}, {"lat": "44.130908", "lng": "9.693012", "elevation": "173.89999389648438", "time": "2013-09-20T12:06:21.281Z"}, {"lat": "44.130823", "lng": "9.693026", "elevation": "172.5", "time": "2013-09-20T12:06:28.273Z"}, {"lat": "44.13081", "lng": "9.693039", "elevation": "170.3000030517578", "time": "2013-09-20T12:06:29.270Z"}, {"lat": "44.130792", "lng": "9.693152", "elevation": "166.39999389648438", "time": "2013-09-20T12:06:33.302Z"}, {"lat": "44.130789", "lng": "9.693182", "elevation": "165.89999389648438", "time": "2013-09-20T12:06:34.265Z"}, {"lat": "44.130722", "lng": "9.693263", "elevation": "166.6999969482422", "time": "2013-09-20T12:06:42.283Z"}, {"lat": "44.130721", "lng": "9.69327", "elevation": "166.8000030517578", "time": "2013-09-20T12:06:43.292Z"}, {"lat": "44.130718", "lng": "9.69328", "elevation": "167.5", "time": "2013-09-20T12:06:48.361Z"}, {"lat": "44.130717", "lng": "9.693279", "elevation": "167.39999389648438", "time": "2013-09-20T12:06:49.323Z"}, {"lat": "44.130711", "lng": "9.693278", "elevation": "168.39999389648438", "time": "2013-09-20T12:06:52.273Z"}, {"lat": "44.130706", "lng": "9.693278", "elevation": "168.8000030517578", "time": "2013-09-20T12:06:53.377Z"}, {"lat": "44.130619", "lng": "9.69328", "elevation": "174.5", "time": "2013-09-20T12:07:06.282Z"}, {"lat": "44.130612", "lng": "9.693275", "elevation": "174.6999969482422", "time": "2013-09-20T12:07:07.273Z"}, {"lat": "44.130602", "lng": "9.693267", "elevation": "174.89999389648438", "time": "2013-09-20T12:07:12.271Z"}, {"lat": "44.130603", "lng": "9.693267", "elevation": "174.89999389648438", "time": "2013-09-20T12:07:13.263Z"}, {"lat": "44.130599", "lng": "9.693273", "elevation": "178.1999969482422", "time": "2013-09-20T12:07:58.269Z"}, {"lat": "44.130594", "lng": "9.693271", "elevation": "179.5", "time": "2013-09-20T12:07:59.267Z"}, {"lat": "44.130574", "lng": "9.693262", "elevation": "180.1999969482422", "time": "2013-09-20T12:08:06.271Z"}, {"lat": "44.130573", "lng": "9.693263", "elevation": "180.3000030517578", "time": "2013-09-20T12:08:07.271Z"}, {"lat": "44.130558", "lng": "9.693261", "elevation": "180.5", "time": "2013-09-20T12:09:44.286Z"}, {"lat": "44.130556", "lng": "9.69326", "elevation": "180.8000030517578", "time": "2013-09-20T12:09:45.282Z"}, {"lat": "44.13054", "lng": "9.693267", "elevation": "180.5", "time": "2013-09-20T12:09:51.288Z"}, {"lat": "44.130539", "lng": "9.693268", "elevation": "180.60000610351563", "time": "2013-09-20T12:09:52.279Z"}, {"lat": "44.130536", "lng": "9.693273", "elevation": "180.89999389648438", "time": "2013-09-20T12:10:40.324Z"}, {"lat": "44.130534", "lng": "9.693277", "elevation": "181.0", "time": "2013-09-20T12:10:41.273Z"}, {"lat": "44.130497", "lng": "9.693262", "elevation": "184.0", "time": "2013-09-20T12:10:49.271Z"}, {"lat": "44.130496", "lng": "9.693261", "elevation": "184.0", "time": "2013-09-20T12:10:50.323Z"}, {"lat": "44.130496", "lng": "9.693252", "elevation": "186.10000610351563", "time": "2013-09-20T12:11:13.302Z"}, {"lat": "44.130495", "lng": "9.693244", "elevation": "185.6999969482422", "time": "2013-09-20T12:11:14.285Z"}, {"lat": "44.13048", "lng": "9.693198", "elevation": "189.6999969482422", "time": "2013-09-20T12:11:24.287Z"}, {"lat": "44.13048", "lng": "9.693199", "elevation": "189.1999969482422", "time": "2013-09-20T12:11:25.286Z"}, {"lat": "44.130479", "lng": "9.693214", "elevation": "192.39999389648438", "time": "2013-09-20T12:11:39.289Z"}, {"lat": "44.130476", "lng": "9.693218", "elevation": "190.6999969482422", "time": "2013-09-20T12:11:40.286Z"}, {"lat": "44.130508", "lng": "9.693334", "elevation": "197.5", "time": "2013-09-20T12:11:52.324Z"}, {"lat": "44.130514", "lng": "9.693345", "elevation": "197.1999969482422", "time": "2013-09-20T12:11:53.332Z"}, {"lat": "44.130525", "lng": "9.693463", "elevation": "196.10000610351563", "time": "2013-09-20T12:12:04.317Z"}, {"lat": "44.130519", "lng": "9.693474", "elevation": "196.60000610351563", "time": "2013-09-20T12:12:05.373Z"}, {"lat": "44.13049", "lng": "9.693587", "elevation": "198.6999969482422", "time": "2013-09-20T12:12:19.306Z"}, {"lat": "44.13049", "lng": "9.693596", "elevation": "199.3000030517578", "time": "2013-09-20T12:12:20.297Z"}, {"lat": "44.130512", "lng": "9.693715", "elevation": "200.0", "time": "2013-09-20T12:12:34.299Z"}, {"lat": "44.130511", "lng": "9.693722", "elevation": "200.0", "time": "2013-09-20T12:12:35.307Z"}, {"lat": "44.130487", "lng": "9.693839", "elevation": "203.6999969482422", "time": "2013-09-20T12:12:51.308Z"}, {"lat": "44.130481", "lng": "9.693851", "elevation": "204.60000610351563", "time": "2013-09-20T12:12:52.298Z"}, {"lat": "44.130447", "lng": "9.693964", "elevation": "207.5", "time": "2013-09-20T12:13:03.396Z"}, {"lat": "44.130444", "lng": "9.693975", "elevation": "208.3000030517578", "time": "2013-09-20T12:13:04.382Z"}, {"lat": "44.130406", "lng": "9.69408", "elevation": "210.10000610351563", "time": "2013-09-20T12:13:12.317Z"}, {"lat": "44.130399", "lng": "9.694092", "elevation": "210.1999969482422", "time": "2013-09-20T12:13:13.334Z"}, {"lat": "44.130349", "lng": "9.694189", "elevation": "213.1999969482422", "time": "2013-09-20T12:13:22.314Z"}, {"lat": "44.130347", "lng": "9.694202", "elevation": "213.6999969482422", "time": "2013-09-20T12:13:23.311Z"}, {"lat": "44.130305", "lng": "9.6943", "elevation": "217.39999389648438", "time": "2013-09-20T12:13:33.398Z"}, {"lat": "44.130299", "lng": "9.694309", "elevation": "217.1999969482422", "time": "2013-09-20T12:13:34.295Z"}, {"lat": "44.13023", "lng": "9.694378", "elevation": "217.8000030517578", "time": "2013-09-20T12:13:42.425Z"}, {"lat": "44.130222", "lng": "9.694384", "elevation": "216.3000030517578", "time": "2013-09-20T12:13:43.388Z"}, {"lat": "44.130217", "lng": "9.694383", "elevation": "217.60000610351563", "time": "2013-09-20T12:13:47.297Z"}, {"lat": "44.130218", "lng": "9.694382", "elevation": "217.89999389648438", "time": "2013-09-20T12:13:48.305Z"}, {"lat": "44.130224", "lng": "9.694398", "elevation": "218.6999969482422", "time": "2013-09-20T12:14:10.330Z"}, {"lat": "44.13022", "lng": "9.694404", "elevation": "219.8000030517578", "time": "2013-09-20T12:14:11.311Z"}, {"lat": "44.130142", "lng": "9.694452", "elevation": "222.10000610351563", "time": "2013-09-20T12:14:17.301Z"}, {"lat": "44.130127", "lng": "9.694465", "elevation": "223.0", "time": "2013-09-20T12:14:18.305Z"}, {"lat": "44.130056", "lng": "9.694534", "elevation": "228.10000610351563", "time": "2013-09-20T12:14:23.410Z"}, {"lat": "44.130044", "lng": "9.694544", "elevation": "229.39999389648438", "time": "2013-09-20T12:14:24.362Z"}, {"lat": "44.130043", "lng": "9.694603", "elevation": "229.6999969482422", "time": "2013-09-20T12:14:34.310Z"}, {"lat": "44.130043", "lng": "9.694603", "elevation": "229.8000030517578", "time": "2013-09-20T12:14:35.301Z"}, {"lat": "44.13004", "lng": "9.694613", "elevation": "231.89999389648438", "time": "2013-09-20T12:14:48.355Z"}, {"lat": "44.130038", "lng": "9.694621", "elevation": "233.10000610351563", "time": "2013-09-20T12:14:49.341Z"}, {"lat": "44.13003", "lng": "9.69474", "elevation": "231.3000030517578", "time": "2013-09-20T12:14:56.306Z"}, {"lat": "44.130032", "lng": "9.694756", "elevation": "231.5", "time": "2013-09-20T12:14:57.298Z"}, {"lat": "44.130048", "lng": "9.694868", "elevation": "234.60000610351563", "time": "2013-09-20T12:15:09.300Z"}, {"lat": "44.13005", "lng": "9.694878", "elevation": "234.5", "time": "2013-09-20T12:15:10.323Z"}, {"lat": "44.130062", "lng": "9.695001", "elevation": "240.39999389648438", "time": "2013-09-20T12:15:24.409Z"}, {"lat": "44.130061", "lng": "9.695013", "elevation": "239.60000610351563", "time": "2013-09-20T12:15:25.381Z"}, {"lat": "44.130046", "lng": "9.695119", "elevation": "242.5", "time": "2013-09-20T12:15:32.423Z"}, {"lat": "44.130044", "lng": "9.695139", "elevation": "242.1999969482422", "time": "2013-09-20T12:15:33.383Z"}, {"lat": "44.130013", "lng": "9.695249", "elevation": "244.10000610351563", "time": "2013-09-20T12:15:44.327Z"}, {"lat": "44.130008", "lng": "9.69526", "elevation": "243.39999389648438", "time": "2013-09-20T12:15:45.378Z"}, {"lat": "44.129972", "lng": "9.695373", "elevation": "240.89999389648438", "time": "2013-09-20T12:15:53.316Z"}, {"lat": "44.129971", "lng": "9.695387", "elevation": "241.10000610351563", "time": "2013-09-20T12:15:54.306Z"}, {"lat": "44.129946", "lng": "9.695495", "elevation": "239.5", "time": "2013-09-20T12:16:00.317Z"}, {"lat": "44.129944", "lng": "9.695514", "elevation": "239.89999389648438", "time": "2013-09-20T12:16:01.319Z"}, {"lat": "44.129932", "lng": "9.695626", "elevation": "241.89999389648438", "time": "2013-09-20T12:16:08.311Z"}, {"lat": "44.129931", "lng": "9.695641", "elevation": "241.1999969482422", "time": "2013-09-20T12:16:09.307Z"}, {"lat": "44.129948", "lng": "9.695759", "elevation": "239.89999389648438", "time": "2013-09-20T12:16:18.411Z"}, {"lat": "44.129943", "lng": "9.69577", "elevation": "239.60000610351563", "time": "2013-09-20T12:16:19.441Z"}, {"lat": "44.129886", "lng": "9.695858", "elevation": "240.60000610351563", "time": "2013-09-20T12:16:27.455Z"}, {"lat": "44.129879", "lng": "9.695862", "elevation": "240.6999969482422", "time": "2013-09-20T12:16:28.430Z"}, {"lat": "44.129859", "lng": "9.69586", "elevation": "241.3000030517578", "time": "2013-09-20T12:16:34.437Z"}, {"lat": "44.129859", "lng": "9.695862", "elevation": "241.10000610351563", "time": "2013-09-20T12:16:35.468Z"}, {"lat": "44.129857", "lng": "9.695866", "elevation": "240.8000030517578", "time": "2013-09-20T12:16:37.404Z"}, {"lat": "44.129856", "lng": "9.695873", "elevation": "241.10000610351563", "time": "2013-09-20T12:16:38.370Z"}, {"lat": "44.12983", "lng": "9.695987", "elevation": "241.10000610351563", "time": "2013-09-20T12:16:47.307Z"}, {"lat": "44.129824", "lng": "9.696002", "elevation": "240.60000610351563", "time": "2013-09-20T12:16:48.301Z"}, {"lat": "44.129766", "lng": "9.696096", "elevation": "240.89999389648438", "time": "2013-09-20T12:16:56.316Z"}, {"lat": "44.129759", "lng": "9.696104", "elevation": "241.3000030517578", "time": "2013-09-20T12:16:57.311Z"}, {"lat": "44.129704", "lng": "9.696183", "elevation": "244.5", "time": "2013-09-20T12:17:06.415Z"}, {"lat": "44.129696", "lng": "9.696194", "elevation": "245.3000030517578", "time": "2013-09-20T12:17:07.464Z"}, {"lat": "44.129652", "lng": "9.6963", "elevation": "244.3000030517578", "time": "2013-09-20T12:17:14.400Z"}, {"lat": "44.129646", "lng": "9.696318", "elevation": "244.10000610351563", "time": "2013-09-20T12:17:15.472Z"}, {"lat": "44.129623", "lng": "9.696427", "elevation": "242.89999389648438", "time": "2013-09-20T12:17:22.314Z"}, {"lat": "44.12962", "lng": "9.696438", "elevation": "242.6999969482422", "time": "2013-09-20T12:17:23.344Z"}, {"lat": "44.129613", "lng": "9.696561", "elevation": "243.0", "time": "2013-09-20T12:17:33.319Z"}, {"lat": "44.129611", "lng": "9.696573", "elevation": "241.6999969482422", "time": "2013-09-20T12:17:34.316Z"}, {"lat": "44.129587", "lng": "9.696684", "elevation": "239.3000030517578", "time": "2013-09-20T12:17:42.359Z"}, {"lat": "44.129582", "lng": "9.696701", "elevation": "241.8000030517578", "time": "2013-09-20T12:17:43.346Z"}, {"lat": "44.129531", "lng": "9.696803", "elevation": "243.0", "time": "2013-09-20T12:17:49.396Z"}, {"lat": "44.129522", "lng": "9.696815", "elevation": "242.8000030517578", "time": "2013-09-20T12:17:50.363Z"}, {"lat": "44.12949", "lng": "9.696912", "elevation": "238.10000610351563", "time": "2013-09-20T12:17:57.345Z"}, {"lat": "44.129493", "lng": "9.696939", "elevation": "238.8000030517578", "time": "2013-09-20T12:17:58.320Z"}, {"lat": "44.129491", "lng": "9.697058", "elevation": "238.60000610351563", "time": "2013-09-20T12:18:03.318Z"}, {"lat": "44.129485", "lng": "9.697074", "elevation": "238.5", "time": "2013-09-20T12:18:04.317Z"}, {"lat": "44.129466", "lng": "9.697191", "elevation": "237.89999389648438", "time": "2013-09-20T12:18:15.317Z"}, {"lat": "44.129465", "lng": "9.697197", "elevation": "237.8000030517578", "time": "2013-09-20T12:18:16.318Z"}, {"lat": "44.129422", "lng": "9.697305", "elevation": "241.5", "time": "2013-09-20T12:18:24.319Z"}, {"lat": "44.129417", "lng": "9.697319", "elevation": "242.0", "time": "2013-09-20T12:18:25.318Z"}, {"lat": "44.129413", "lng": "9.697439", "elevation": "241.39999389648438", "time": "2013-09-20T12:18:34.320Z"}, {"lat": "44.129413", "lng": "9.697456", "elevation": "240.5", "time": "2013-09-20T12:18:35.318Z"}, {"lat": "44.129429", "lng": "9.697569", "elevation": "241.5", "time": "2013-09-20T12:18:43.485Z"}, {"lat": "44.12943", "lng": "9.697582", "elevation": "241.6999969482422", "time": "2013-09-20T12:18:44.328Z"}, {"lat": "44.129449", "lng": "9.697693", "elevation": "246.1999969482422", "time": "2013-09-20T12:18:51.343Z"}, {"lat": "44.129453", "lng": "9.697706", "elevation": "246.5", "time": "2013-09-20T12:18:52.324Z"}, {"lat": "44.129456", "lng": "9.697718", "elevation": "245.39999389648438", "time": "2013-09-20T12:18:56.432Z"}, {"lat": "44.129455", "lng": "9.697716", "elevation": "246.0", "time": "2013-09-20T12:18:57.403Z"}, {"lat": "44.129455", "lng": "9.697715", "elevation": "244.89999389648438", "time": "2013-09-20T12:19:34.330Z"}, {"lat": "44.12946", "lng": "9.697717", "elevation": "245.1999969482422", "time": "2013-09-20T12:19:35.323Z"}, {"lat": "44.129524", "lng": "9.697802", "elevation": "247.0", "time": "2013-09-20T12:19:49.325Z"}, {"lat": "44.129527", "lng": "9.697805", "elevation": "246.8000030517578", "time": "2013-09-20T12:19:50.325Z"}, {"lat": "44.129549", "lng": "9.697924", "elevation": "242.3000030517578", "time": "2013-09-20T12:19:58.338Z"}, {"lat": "44.129552", "lng": "9.697946", "elevation": "242.89999389648438", "time": "2013-09-20T12:19:59.326Z"}, {"lat": "44.129515", "lng": "9.698055", "elevation": "246.89999389648438", "time": "2013-09-20T12:20:06.398Z"}, {"lat": "44.129511", "lng": "9.698068", "elevation": "246.1999969482422", "time": "2013-09-20T12:20:07.425Z"}, {"lat": "44.129494", "lng": "9.698177", "elevation": "246.8000030517578", "time": "2013-09-20T12:20:14.370Z"}, {"lat": "44.12949", "lng": "9.698193", "elevation": "247.10000610351563", "time": "2013-09-20T12:20:15.431Z"}, {"lat": "44.129447", "lng": "9.698291", "elevation": "247.60000610351563", "time": "2013-09-20T12:20:22.367Z"}, {"lat": "44.129441", "lng": "9.698306", "elevation": "247.10000610351563", "time": "2013-09-20T12:20:23.420Z"}, {"lat": "44.12944", "lng": "9.698421", "elevation": "246.39999389648438", "time": "2013-09-20T12:20:32.440Z"}, {"lat": "44.129439", "lng": "9.698433", "elevation": "246.1999969482422", "time": "2013-09-20T12:20:33.453Z"}, {"lat": "44.129409", "lng": "9.698538", "elevation": "245.8000030517578", "time": "2013-09-20T12:20:40.449Z"}, {"lat": "44.129406", "lng": "9.698551", "elevation": "245.5", "time": "2013-09-20T12:20:41.411Z"}, {"lat": "44.129428", "lng": "9.698656", "elevation": "243.8000030517578", "time": "2013-09-20T12:21:03.330Z"}, {"lat": "44.129424", "lng": "9.698658", "elevation": "244.60000610351563", "time": "2013-09-20T12:21:04.335Z"}, {"lat": "44.129419", "lng": "9.698661", "elevation": "244.6999969482422", "time": "2013-09-20T12:21:05.334Z"}, {"lat": "44.129416", "lng": "9.698663", "elevation": "244.60000610351563", "time": "2013-09-20T12:21:06.334Z"}, {"lat": "44.129411", "lng": "9.698665", "elevation": "244.60000610351563", "time": "2013-09-20T12:21:08.368Z"}, {"lat": "44.12941", "lng": "9.698666", "elevation": "244.60000610351563", "time": "2013-09-20T12:21:09.331Z"}, {"lat": "44.129423", "lng": "9.698705", "elevation": "242.5", "time": "2013-09-20T12:21:36.334Z"}, {"lat": "44.129429", "lng": "9.698716", "elevation": "244.60000610351563", "time": "2013-09-20T12:21:37.334Z"}, {"lat": "44.12947", "lng": "9.698803", "elevation": "247.8000030517578", "time": "2013-09-20T12:21:41.350Z"}, {"lat": "44.129479", "lng": "9.698838", "elevation": "250.10000610351563", "time": "2013-09-20T12:21:42.377Z"}, {"lat": "44.129478", "lng": "9.698962", "elevation": "255.1999969482422", "time": "2013-09-20T12:21:47.462Z"}, {"lat": "44.129475", "lng": "9.698981", "elevation": "255.0", "time": "2013-09-20T12:21:48.471Z"}, {"lat": "44.129489", "lng": "9.6991", "elevation": "257.5", "time": "2013-09-20T12:21:55.461Z"}, {"lat": "44.129487", "lng": "9.699113", "elevation": "258.70001220703125", "time": "2013-09-20T12:21:56.350Z"}, {"lat": "44.129481", "lng": "9.699237", "elevation": "261.79998779296875", "time": "2013-09-20T12:22:06.390Z"}, {"lat": "44.129483", "lng": "9.699249", "elevation": "262.1000061035156", "time": "2013-09-20T12:22:07.378Z"}, {"lat": "44.129483", "lng": "9.69937", "elevation": "266.3999938964844", "time": "2013-09-20T12:22:16.346Z"}, {"lat": "44.129483", "lng": "9.699384", "elevation": "266.0", "time": "2013-09-20T12:22:17.341Z"}, {"lat": "44.129476", "lng": "9.699499", "elevation": "265.20001220703125", "time": "2013-09-20T12:22:26.354Z"}, {"lat": "44.129476", "lng": "9.699511", "elevation": "265.20001220703125", "time": "2013-09-20T12:22:27.362Z"}, {"lat": "44.129482", "lng": "9.699628", "elevation": "266.5", "time": "2013-09-20T12:22:35.343Z"}, {"lat": "44.129483", "lng": "9.699644", "elevation": "266.3999938964844", "time": "2013-09-20T12:22:36.344Z"}, {"lat": "44.129467", "lng": "9.69976", "elevation": "267.5", "time": "2013-09-20T12:22:49.347Z"}, {"lat": "44.129468", "lng": "9.699768", "elevation": "267.3999938964844", "time": "2013-09-20T12:22:50.393Z"}, {"lat": "44.129434", "lng": "9.699871", "elevation": "272.70001220703125", "time": "2013-09-20T12:23:01.424Z"}, {"lat": "44.129423", "lng": "9.699888", "elevation": "272.5", "time": "2013-09-20T12:23:02.388Z"}, {"lat": "44.129365", "lng": "9.699975", "elevation": "271.79998779296875", "time": "2013-09-20T12:23:08.350Z"}, {"lat": "44.12936", "lng": "9.699988", "elevation": "271.29998779296875", "time": "2013-09-20T12:23:09.347Z"}, {"lat": "44.12933", "lng": "9.700052", "elevation": "271.79998779296875", "time": "2013-09-20T12:23:20.350Z"}, {"lat": "44.12933", "lng": "9.700053", "elevation": "271.5", "time": "2013-09-20T12:23:21.346Z"}, {"lat": "44.129329", "lng": "9.700055", "elevation": "271.5", "time": "2013-09-20T12:23:22.414Z"}, {"lat": "44.129327", "lng": "9.700057", "elevation": "270.6000061035156", "time": "2013-09-20T12:23:23.346Z"}, {"lat": "44.129278", "lng": "9.700151", "elevation": "268.0", "time": "2013-09-20T12:23:32.453Z"}, {"lat": "44.129272", "lng": "9.70017", "elevation": "268.1000061035156", "time": "2013-09-20T12:23:33.429Z"}, {"lat": "44.129243", "lng": "9.700282", "elevation": "267.20001220703125", "time": "2013-09-20T12:23:39.480Z"}, {"lat": "44.129236", "lng": "9.700293", "elevation": "267.5", "time": "2013-09-20T12:23:40.440Z"}, {"lat": "44.129218", "lng": "9.700301", "elevation": "267.1000061035156", "time": "2013-09-20T12:23:46.423Z"}, {"lat": "44.129219", "lng": "9.700299", "elevation": "267.1000061035156", "time": "2013-09-20T12:23:47.492Z"}, {"lat": "44.129224", "lng": "9.700306", "elevation": "267.0", "time": "2013-09-20T12:23:51.366Z"}, {"lat": "44.129224", "lng": "9.700313", "elevation": "268.1000061035156", "time": "2013-09-20T12:23:52.360Z"}, {"lat": "44.129148", "lng": "9.70038", "elevation": "271.8999938964844", "time": "2013-09-20T12:24:00.359Z"}, {"lat": "44.129135", "lng": "9.700388", "elevation": "271.8999938964844", "time": "2013-09-20T12:24:01.357Z"}, {"lat": "44.129087", "lng": "9.700477", "elevation": "271.1000061035156", "time": "2013-09-20T12:24:08.351Z"}, {"lat": "44.12908", "lng": "9.700491", "elevation": "270.29998779296875", "time": "2013-09-20T12:24:09.370Z"}, {"lat": "44.129045", "lng": "9.700595", "elevation": "265.5", "time": "2013-09-20T12:24:17.351Z"}, {"lat": "44.129041", "lng": "9.70061", "elevation": "265.70001220703125", "time": "2013-09-20T12:24:18.484Z"}, {"lat": "44.129019", "lng": "9.700718", "elevation": "265.3999938964844", "time": "2013-09-20T12:24:25.357Z"}, {"lat": "44.129018", "lng": "9.700733", "elevation": "265.0", "time": "2013-09-20T12:24:26.436Z"}, {"lat": "44.128987", "lng": "9.700839", "elevation": "261.3999938964844", "time": "2013-09-20T12:24:34.489Z"}, {"lat": "44.128987", "lng": "9.700854", "elevation": "259.6000061035156", "time": "2013-09-20T12:24:35.475Z"}, {"lat": "44.128987", "lng": "9.700972", "elevation": "257.20001220703125", "time": "2013-09-20T12:24:43.506Z"}, {"lat": "44.128989", "lng": "9.700981", "elevation": "256.29998779296875", "time": "2013-09-20T12:24:44.354Z"}, {"lat": "44.128958", "lng": "9.701041", "elevation": "256.20001220703125", "time": "2013-09-20T12:25:03.298Z"}, {"lat": "44.128956", "lng": "9.701041", "elevation": "256.5", "time": "2013-09-20T12:25:03.369Z"}, {"lat": "44.128952", "lng": "9.70104", "elevation": "256.1000061035156", "time": "2013-09-20T12:25:04.359Z"}, {"lat": "44.128947", "lng": "9.701038", "elevation": "256.0", "time": "2013-09-20T12:25:05.360Z"}, {"lat": "44.128921", "lng": "9.701043", "elevation": "256.5", "time": "2013-09-20T12:25:11.363Z"}, {"lat": "44.128922", "lng": "9.701045", "elevation": "256.3999938964844", "time": "2013-09-20T12:25:12.357Z"}, {"lat": "44.128919", "lng": "9.701067", "elevation": "257.70001220703125", "time": "2013-09-20T12:25:46.355Z"}, {"lat": "44.128915", "lng": "9.701071", "elevation": "257.3999938964844", "time": "2013-09-20T12:25:47.359Z"}, {"lat": "44.128904", "lng": "9.701189", "elevation": "244.39999389648438", "time": "2013-09-20T12:26:03.357Z"}, {"lat": "44.128906", "lng": "9.701205", "elevation": "245.39999389648438", "time": "2013-09-20T12:26:04.361Z"}, {"lat": "44.128893", "lng": "9.701294", "elevation": "245.1999969482422", "time": "2013-09-20T12:26:13.437Z"}, {"lat": "44.128893", "lng": "9.701293", "elevation": "244.1999969482422", "time": "2013-09-20T12:26:14.473Z"}, {"lat": "44.128885", "lng": "9.701297", "elevation": "245.1999969482422", "time": "2013-09-20T12:26:26.363Z"}, {"lat": "44.128886", "lng": "9.701302", "elevation": "245.10000610351563", "time": "2013-09-20T12:26:27.362Z"}, {"lat": "44.128906", "lng": "9.701423", "elevation": "246.10000610351563", "time": "2013-09-20T12:26:42.371Z"}, {"lat": "44.128906", "lng": "9.701446", "elevation": "246.6999969482422", "time": "2013-09-20T12:26:43.362Z"}, {"lat": "44.128899", "lng": "9.701556", "elevation": "247.10000610351563", "time": "2013-09-20T12:26:48.363Z"}, {"lat": "44.128897", "lng": "9.701573", "elevation": "247.0", "time": "2013-09-20T12:26:49.367Z"}, {"lat": "44.128871", "lng": "9.701631", "elevation": "246.5", "time": "2013-09-20T12:26:59.367Z"}, {"lat": "44.128872", "lng": "9.701631", "elevation": "246.5", "time": "2013-09-20T12:27:00.366Z"}, {"lat": "44.128875", "lng": "9.701643", "elevation": "244.5", "time": "2013-09-20T12:28:08.370Z"}, {"lat": "44.128877", "lng": "9.70165", "elevation": "244.39999389648438", "time": "2013-09-20T12:28:09.380Z"}, {"lat": "44.12887", "lng": "9.701716", "elevation": "246.39999389648438", "time": "2013-09-20T12:28:23.361Z"}, {"lat": "44.12887", "lng": "9.701716", "elevation": "246.39999389648438", "time": "2013-09-20T12:28:23.393Z"}, {"lat": "44.128873", "lng": "9.701713", "elevation": "246.10000610351563", "time": "2013-09-20T12:28:52.380Z"}, {"lat": "44.128876", "lng": "9.70172", "elevation": "245.5", "time": "2013-09-20T12:28:53.441Z"}, {"lat": "44.128935", "lng": "9.701809", "elevation": "245.5", "time": "2013-09-20T12:28:59.379Z"}, {"lat": "44.128945", "lng": "9.701817", "elevation": "245.1999969482422", "time": "2013-09-20T12:29:00.380Z"}, {"lat": "44.129022", "lng": "9.701861", "elevation": "239.5", "time": "2013-09-20T12:29:09.381Z"}, {"lat": "44.129029", "lng": "9.701876", "elevation": "239.6999969482422", "time": "2013-09-20T12:29:10.387Z"}, {"lat": "44.129091", "lng": "9.701959", "elevation": "237.6999969482422", "time": "2013-09-20T12:29:19.455Z"}, {"lat": "44.129097", "lng": "9.701968", "elevation": "238.1999969482422", "time": "2013-09-20T12:29:20.479Z"}, {"lat": "44.129162", "lng": "9.702031", "elevation": "237.6999969482422", "time": "2013-09-20T12:29:29.432Z"}, {"lat": "44.129172", "lng": "9.702037", "elevation": "238.3000030517578", "time": "2013-09-20T12:29:30.416Z"}, {"lat": "44.129235", "lng": "9.702101", "elevation": "239.0", "time": "2013-09-20T12:29:36.433Z"}, {"lat": "44.129245", "lng": "9.702115", "elevation": "239.10000610351563", "time": "2013-09-20T12:29:37.451Z"}, {"lat": "44.129271", "lng": "9.702144", "elevation": "237.0", "time": "2013-09-20T12:29:43.470Z"}, {"lat": "44.129271", "lng": "9.70214", "elevation": "236.6999969482422", "time": "2013-09-20T12:29:44.482Z"}, {"lat": "44.129273", "lng": "9.70215", "elevation": "238.3000030517578", "time": "2013-09-20T12:31:03.379Z"}, {"lat": "44.129277", "lng": "9.702157", "elevation": "238.60000610351563", "time": "2013-09-20T12:31:04.389Z"}, {"lat": "44.129349", "lng": "9.702223", "elevation": "241.5", "time": "2013-09-20T12:31:13.393Z"}, {"lat": "44.129357", "lng": "9.702229", "elevation": "242.0", "time": "2013-09-20T12:31:14.389Z"}, {"lat": "44.129421", "lng": "9.7023", "elevation": "239.39999389648438", "time": "2013-09-20T12:31:21.384Z"}, {"lat": "44.129429", "lng": "9.702318", "elevation": "239.3000030517578", "time": "2013-09-20T12:31:22.388Z"}, {"lat": "44.129476", "lng": "9.702409", "elevation": "239.60000610351563", "time": "2013-09-20T12:31:26.384Z"}, {"lat": "44.129483", "lng": "9.70242", "elevation": "239.60000610351563", "time": "2013-09-20T12:31:27.386Z"}, {"lat": "44.129499", "lng": "9.702465", "elevation": "240.1999969482422", "time": "2013-09-20T12:31:37.386Z"}, {"lat": "44.129499", "lng": "9.702466", "elevation": "240.1999969482422", "time": "2013-09-20T12:31:38.430Z"}, {"lat": "44.129499", "lng": "9.702472", "elevation": "240.10000610351563", "time": "2013-09-20T12:31:44.390Z"}, {"lat": "44.1295", "lng": "9.702479", "elevation": "239.89999389648438", "time": "2013-09-20T12:31:45.392Z"}, {"lat": "44.129526", "lng": "9.702587", "elevation": "241.39999389648438", "time": "2013-09-20T12:31:58.392Z"}, {"lat": "44.129521", "lng": "9.7026", "elevation": "242.39999389648438", "time": "2013-09-20T12:31:59.517Z"}, {"lat": "44.129451", "lng": "9.70267", "elevation": "240.3000030517578", "time": "2013-09-20T12:32:11.391Z"}, {"lat": "44.129454", "lng": "9.702671", "elevation": "240.3000030517578", "time": "2013-09-20T12:32:12.394Z"}, {"lat": "44.129455", "lng": "9.702676", "elevation": "240.1999969482422", "time": "2013-09-20T12:32:16.399Z"}, {"lat": "44.129451", "lng": "9.702682", "elevation": "240.6999969482422", "time": "2013-09-20T12:32:17.395Z"}, {"lat": "44.129404", "lng": "9.702783", "elevation": "236.1999969482422", "time": "2013-09-20T12:32:28.460Z"}, {"lat": "44.1294", "lng": "9.702801", "elevation": "236.0", "time": "2013-09-20T12:32:29.594Z"}, {"lat": "44.129382", "lng": "9.702908", "elevation": "232.8000030517578", "time": "2013-09-20T12:32:40.557Z"}, {"lat": "44.129379", "lng": "9.702926", "elevation": "232.5", "time": "2013-09-20T12:32:41.438Z"}, {"lat": "44.129306", "lng": "9.703", "elevation": "236.0", "time": "2013-09-20T12:32:50.393Z"}, {"lat": "44.129296", "lng": "9.703005", "elevation": "235.89999389648438", "time": "2013-09-20T12:32:51.415Z"}, {"lat": "44.129223", "lng": "9.703072", "elevation": "233.3000030517578", "time": "2013-09-20T12:33:03.393Z"}, {"lat": "44.12922", "lng": "9.703076", "elevation": "233.3000030517578", "time": "2013-09-20T12:33:04.400Z"}, {"lat": "44.129218", "lng": "9.703082", "elevation": "232.8000030517578", "time": "2013-09-20T12:33:06.397Z"}, {"lat": "44.129217", "lng": "9.703084", "elevation": "232.6999969482422", "time": "2013-09-20T12:33:07.402Z"}, {"lat": "44.129212", "lng": "9.703099", "elevation": "232.39999389648438", "time": "2013-09-20T12:33:16.395Z"}, {"lat": "44.129208", "lng": "9.703101", "elevation": "232.1999969482422", "time": "2013-09-20T12:33:17.395Z"}, {"lat": "44.129155", "lng": "9.703195", "elevation": "229.8000030517578", "time": "2013-09-20T12:33:33.559Z"}, {"lat": "44.12915", "lng": "9.703203", "elevation": "229.3000030517578", "time": "2013-09-20T12:33:34.403Z"}, {"lat": "44.129095", "lng": "9.703298", "elevation": "224.39999389648438", "time": "2013-09-20T12:33:51.438Z"}, {"lat": "44.129092", "lng": "9.703302", "elevation": "224.5", "time": "2013-09-20T12:33:52.443Z"}, {"lat": "44.129028", "lng": "9.703389", "elevation": "223.60000610351563", "time": "2013-09-20T12:34:06.408Z"}, {"lat": "44.129018", "lng": "9.703398", "elevation": "223.6999969482422", "time": "2013-09-20T12:34:07.557Z"}, {"lat": "44.128966", "lng": "9.703497", "elevation": "219.0", "time": "2013-09-20T12:34:16.567Z"}, {"lat": "44.128962", "lng": "9.703506", "elevation": "218.39999389648438", "time": "2013-09-20T12:34:17.597Z"}, {"lat": "44.128888", "lng": "9.703562", "elevation": "214.39999389648438", "time": "2013-09-20T12:34:30.522Z"}, {"lat": "44.128883", "lng": "9.703575", "elevation": "214.10000610351563", "time": "2013-09-20T12:34:31.407Z"}, {"lat": "44.12883", "lng": "9.703668", "elevation": "213.10000610351563", "time": "2013-09-20T12:34:45.391Z"}, {"lat": "44.128823", "lng": "9.703683", "elevation": "213.0", "time": "2013-09-20T12:34:46.401Z"}, {"lat": "44.12877", "lng": "9.703782", "elevation": "212.60000610351563", "time": "2013-09-20T12:34:53.404Z"}, {"lat": "44.128767", "lng": "9.703785", "elevation": "212.5", "time": "2013-09-20T12:34:54.406Z"}, {"lat": "44.128728", "lng": "9.703793", "elevation": "212.3000030517578", "time": "2013-09-20T12:35:06.404Z"}, {"lat": "44.128728", "lng": "9.703795", "elevation": "212.3000030517578", "time": "2013-09-20T12:35:07.401Z"}, {"lat": "44.128729", "lng": "9.703799", "elevation": "212.1999969482422", "time": "2013-09-20T12:35:09.405Z"}, {"lat": "44.128728", "lng": "9.703805", "elevation": "211.10000610351563", "time": "2013-09-20T12:35:10.419Z"}, {"lat": "44.128703", "lng": "9.703918", "elevation": "208.3000030517578", "time": "2013-09-20T12:35:16.403Z"}, {"lat": "44.128695", "lng": "9.703941", "elevation": "209.60000610351563", "time": "2013-09-20T12:35:17.401Z"}, {"lat": "44.128657", "lng": "9.704026", "elevation": "209.0", "time": "2013-09-20T12:35:21.408Z"}, {"lat": "44.12865", "lng": "9.704052", "elevation": "208.1999969482422", "time": "2013-09-20T12:35:22.403Z"}, {"lat": "44.128642", "lng": "9.704164", "elevation": "204.89999389648438", "time": "2013-09-20T12:35:26.403Z"}, {"lat": "44.128642", "lng": "9.704191", "elevation": "203.8000030517578", "time": "2013-09-20T12:35:27.403Z"}, {"lat": "44.128601", "lng": "9.704301", "elevation": "204.60000610351563", "time": "2013-09-20T12:35:34.404Z"}, {"lat": "44.128592", "lng": "9.704309", "elevation": "204.60000610351563", "time": "2013-09-20T12:35:35.403Z"}, {"lat": "44.128533", "lng": "9.70439", "elevation": "202.10000610351563", "time": "2013-09-20T12:35:44.407Z"}, {"lat": "44.128522", "lng": "9.704393", "elevation": "202.3000030517578", "time": "2013-09-20T12:35:45.405Z"}, {"lat": "44.128457", "lng": "9.704467", "elevation": "202.1999969482422", "time": "2013-09-20T12:35:53.407Z"}, {"lat": "44.128454", "lng": "9.704479", "elevation": "202.10000610351563", "time": "2013-09-20T12:35:54.411Z"}, {"lat": "44.128382", "lng": "9.704547", "elevation": "199.8000030517578", "time": "2013-09-20T12:36:08.418Z"}, {"lat": "44.128374", "lng": "9.704549", "elevation": "199.0", "time": "2013-09-20T12:36:09.422Z"}, {"lat": "44.128295", "lng": "9.704591", "elevation": "203.3000030517578", "time": "2013-09-20T12:36:21.456Z"}, {"lat": "44.128287", "lng": "9.704589", "elevation": "203.10000610351563", "time": "2013-09-20T12:36:22.404Z"}, {"lat": "44.128277", "lng": "9.704572", "elevation": "204.5", "time": "2013-09-20T12:36:27.458Z"}, {"lat": "44.12828", "lng": "9.704571", "elevation": "204.10000610351563", "time": "2013-09-20T12:36:28.415Z"}, {"lat": "44.128278", "lng": "9.704576", "elevation": "203.8000030517578", "time": "2013-09-20T12:36:48.450Z"}, {"lat": "44.128273", "lng": "9.704577", "elevation": "203.6999969482422", "time": "2013-09-20T12:36:49.413Z"}, {"lat": "44.128198", "lng": "9.704595", "elevation": "201.39999389648438", "time": "2013-09-20T12:36:53.502Z"}, {"lat": "44.128176", "lng": "9.704605", "elevation": "201.0", "time": "2013-09-20T12:36:54.428Z"}, {"lat": "44.128098", "lng": "9.704642", "elevation": "201.10000610351563", "time": "2013-09-20T12:36:58.439Z"}, {"lat": "44.128086", "lng": "9.704649", "elevation": "200.60000610351563", "time": "2013-09-20T12:36:59.433Z"}, {"lat": "44.128024", "lng": "9.704725", "elevation": "201.0", "time": "2013-09-20T12:37:07.421Z"}, {"lat": "44.128015", "lng": "9.704735", "elevation": "200.3000030517578", "time": "2013-09-20T12:37:08.410Z"}, {"lat": "44.127939", "lng": "9.704802", "elevation": "201.5", "time": "2013-09-20T12:37:16.415Z"}, {"lat": "44.127932", "lng": "9.704812", "elevation": "200.39999389648438", "time": "2013-09-20T12:37:17.413Z"}, {"lat": "44.127851", "lng": "9.704862", "elevation": "199.60000610351563", "time": "2013-09-20T12:37:25.411Z"}, {"lat": "44.127842", "lng": "9.704868", "elevation": "198.0", "time": "2013-09-20T12:37:26.412Z"}, {"lat": "44.127764", "lng": "9.704929", "elevation": "196.89999389648438", "time": "2013-09-20T12:37:35.437Z"}, {"lat": "44.127755", "lng": "9.704934", "elevation": "196.60000610351563", "time": "2013-09-20T12:37:36.412Z"}, {"lat": "44.127692", "lng": "9.70502", "elevation": "195.60000610351563", "time": "2013-09-20T12:37:50.413Z"}, {"lat": "44.127682", "lng": "9.705018", "elevation": "196.0", "time": "2013-09-20T12:37:51.418Z"}, {"lat": "44.12761", "lng": "9.705085", "elevation": "197.8000030517578", "time": "2013-09-20T12:38:00.457Z"}, {"lat": "44.127601", "lng": "9.705095", "elevation": "198.10000610351563", "time": "2013-09-20T12:38:01.416Z"}, {"lat": "44.127524", "lng": "9.70515", "elevation": "201.1999969482422", "time": "2013-09-20T12:38:08.421Z"}, {"lat": "44.127507", "lng": "9.705155", "elevation": "201.60000610351563", "time": "2013-09-20T12:38:09.423Z"}, {"lat": "44.127428", "lng": "9.705203", "elevation": "199.8000030517578", "time": "2013-09-20T12:38:16.418Z"}, {"lat": "44.127425", "lng": "9.705221", "elevation": "200.5", "time": "2013-09-20T12:38:17.427Z"}, {"lat": "44.127347", "lng": "9.705267", "elevation": "200.10000610351563", "time": "2013-09-20T12:38:26.413Z"}, {"lat": "44.127337", "lng": "9.705264", "elevation": "199.8000030517578", "time": "2013-09-20T12:38:27.419Z"}, {"lat": "44.127269", "lng": "9.705196", "elevation": "199.0", "time": "2013-09-20T12:38:35.418Z"}, {"lat": "44.12726", "lng": "9.705196", "elevation": "198.8000030517578", "time": "2013-09-20T12:38:36.417Z"}, {"lat": "44.127177", "lng": "9.705226", "elevation": "198.6999969482422", "time": "2013-09-20T12:38:44.433Z"}, {"lat": "44.127162", "lng": "9.70522", "elevation": "199.1999969482422", "time": "2013-09-20T12:38:45.429Z"}, {"lat": "44.127075", "lng": "9.705187", "elevation": "201.60000610351563", "time": "2013-09-20T12:38:54.421Z"}, {"lat": "44.127073", "lng": "9.705186", "elevation": "200.6999969482422", "time": "2013-09-20T12:38:55.425Z"}, {"lat": "44.127074", "lng": "9.705191", "elevation": "202.39999389648438", "time": "2013-09-20T12:38:57.451Z"}, {"lat": "44.127075", "lng": "9.705194", "elevation": "202.6999969482422", "time": "2013-09-20T12:38:58.448Z"}, {"lat": "44.127073", "lng": "9.705195", "elevation": "202.6999969482422", "time": "2013-09-20T12:38:59.424Z"}, {"lat": "44.127069", "lng": "9.705196", "elevation": "202.39999389648438", "time": "2013-09-20T12:39:00.447Z"}, {"lat": "44.126983", "lng": "9.705228", "elevation": "200.39999389648438", "time": "2013-09-20T12:39:07.436Z"}, {"lat": "44.126973", "lng": "9.705236", "elevation": "199.89999389648438", "time": "2013-09-20T12:39:08.452Z"}, {"lat": "44.126891", "lng": "9.705271", "elevation": "200.8000030517578", "time": "2013-09-20T12:39:16.420Z"}, {"lat": "44.12688", "lng": "9.705274", "elevation": "201.39999389648438", "time": "2013-09-20T12:39:17.426Z"}, {"lat": "44.126794", "lng": "9.705289", "elevation": "201.8000030517578", "time": "2013-09-20T12:39:25.422Z"}, {"lat": "44.126784", "lng": "9.705294", "elevation": "203.5", "time": "2013-09-20T12:39:26.435Z"}, {"lat": "44.126701", "lng": "9.705321", "elevation": "204.1999969482422", "time": "2013-09-20T12:39:34.434Z"}, {"lat": "44.126692", "lng": "9.705322", "elevation": "203.3000030517578", "time": "2013-09-20T12:39:35.415Z"}, {"lat": "44.126611", "lng": "9.705346", "elevation": "204.39999389648438", "time": "2013-09-20T12:39:47.423Z"}, {"lat": "44.1266", "lng": "9.705351", "elevation": "203.8000030517578", "time": "2013-09-20T12:39:48.421Z"}, {"lat": "44.12651", "lng": "9.705339", "elevation": "204.5", "time": "2013-09-20T12:39:56.426Z"}, {"lat": "44.126498", "lng": "9.705339", "elevation": "204.89999389648438", "time": "2013-09-20T12:39:57.423Z"}, {"lat": "44.126414", "lng": "9.705331", "elevation": "203.60000610351563", "time": "2013-09-20T12:40:06.428Z"}, {"lat": "44.126406", "lng": "9.705328", "elevation": "204.5", "time": "2013-09-20T12:40:07.434Z"}, {"lat": "44.126321", "lng": "9.705322", "elevation": "205.1999969482422", "time": "2013-09-20T12:40:16.431Z"}, {"lat": "44.126312", "lng": "9.705322", "elevation": "205.39999389648438", "time": "2013-09-20T12:40:17.426Z"}, {"lat": "44.126223", "lng": "9.705316", "elevation": "205.5", "time": "2013-09-20T12:40:28.427Z"}, {"lat": "44.126218", "lng": "9.705313", "elevation": "205.3000030517578", "time": "2013-09-20T12:40:29.427Z"}, {"lat": "44.126131", "lng": "9.705326", "elevation": "205.39999389648438", "time": "2013-09-20T12:40:40.431Z"}, {"lat": "44.126122", "lng": "9.705322", "elevation": "205.1999969482422", "time": "2013-09-20T12:40:41.428Z"}, {"lat": "44.126033", "lng": "9.705315", "elevation": "207.89999389648438", "time": "2013-09-20T12:40:53.425Z"}, {"lat": "44.126028", "lng": "9.705317", "elevation": "209.0", "time": "2013-09-20T12:40:54.425Z"}, {"lat": "44.125964", "lng": "9.705384", "elevation": "205.60000610351563", "time": "2013-09-20T12:41:05.431Z"}, {"lat": "44.125953", "lng": "9.70539", "elevation": "205.39999389648438", "time": "2013-09-20T12:41:06.430Z"}, {"lat": "44.125871", "lng": "9.705406", "elevation": "206.8000030517578", "time": "2013-09-20T12:41:13.431Z"}, {"lat": "44.125863", "lng": "9.705412", "elevation": "207.60000610351563", "time": "2013-09-20T12:41:14.431Z"}, {"lat": "44.12584", "lng": "9.705447", "elevation": "209.60000610351563", "time": "2013-09-20T12:41:22.458Z"}, {"lat": "44.125839", "lng": "9.705442", "elevation": "209.6999969482422", "time": "2013-09-20T12:41:23.479Z"}, {"lat": "44.125834", "lng": "9.705434", "elevation": "209.6999969482422", "time": "2013-09-20T12:41:24.467Z"}, {"lat": "44.125754", "lng": "9.705474", "elevation": "210.0", "time": "2013-09-20T12:41:41.436Z"}, {"lat": "44.125746", "lng": "9.705478", "elevation": "209.60000610351563", "time": "2013-09-20T12:41:42.427Z"}, {"lat": "44.125667", "lng": "9.705535", "elevation": "210.89999389648438", "time": "2013-09-20T12:41:56.435Z"}, {"lat": "44.125661", "lng": "9.70553", "elevation": "211.10000610351563", "time": "2013-09-20T12:41:57.430Z"}, {"lat": "44.125642", "lng": "9.705512", "elevation": "211.1999969482422", "time": "2013-09-20T12:42:03.763Z"}, {"lat": "44.125642", "lng": "9.705512", "elevation": "211.10000610351563", "time": "2013-09-20T12:42:04.433Z"}, {"lat": "44.125641", "lng": "9.705517", "elevation": "210.10000610351563", "time": "2013-09-20T12:42:20.442Z"}, {"lat": "44.125637", "lng": "9.705523", "elevation": "209.3000030517578", "time": "2013-09-20T12:42:21.443Z"}, {"lat": "44.125573", "lng": "9.705609", "elevation": "208.6999969482422", "time": "2013-09-20T12:42:34.481Z"}, {"lat": "44.125564", "lng": "9.705608", "elevation": "208.5", "time": "2013-09-20T12:42:35.461Z"}, {"lat": "44.125502", "lng": "9.70569", "elevation": "204.10000610351563", "time": "2013-09-20T12:42:59.440Z"}, {"lat": "44.125494", "lng": "9.70569", "elevation": "205.3000030517578", "time": "2013-09-20T12:43:00.442Z"}, {"lat": "44.12542", "lng": "9.705738", "elevation": "203.89999389648438", "time": "2013-09-20T12:43:08.576Z"}, {"lat": "44.125415", "lng": "9.705751", "elevation": "204.60000610351563", "time": "2013-09-20T12:43:09.438Z"}, {"lat": "44.125409", "lng": "9.705799", "elevation": "203.1999969482422", "time": "2013-09-20T12:43:17.451Z"}, {"lat": "44.125409", "lng": "9.7058", "elevation": "203.1999969482422", "time": "2013-09-20T12:43:18.450Z"}, {"lat": "44.125401", "lng": "9.705805", "elevation": "202.6999969482422", "time": "2013-09-20T12:43:27.453Z"}, {"lat": "44.125398", "lng": "9.705807", "elevation": "203.0", "time": "2013-09-20T12:43:28.442Z"}, {"lat": "44.125308", "lng": "9.705818", "elevation": "200.39999389648438", "time": "2013-09-20T12:43:41.442Z"}, {"lat": "44.125303", "lng": "9.705818", "elevation": "200.39999389648438", "time": "2013-09-20T12:43:42.451Z"}, {"lat": "44.125296", "lng": "9.705823", "elevation": "201.1999969482422", "time": "2013-09-20T12:43:46.451Z"}, {"lat": "44.125297", "lng": "9.705825", "elevation": "200.89999389648438", "time": "2013-09-20T12:43:47.444Z"}, {"lat": "44.125301", "lng": "9.705845", "elevation": "200.8000030517578", "time": "2013-09-20T12:44:50.504Z"}, {"lat": "44.125303", "lng": "9.705852", "elevation": "200.6999969482422", "time": "2013-09-20T12:44:51.550Z"}, {"lat": "44.125306", "lng": "9.705857", "elevation": "200.1999969482422", "time": "2013-09-20T12:44:53.569Z"}, {"lat": "44.125306", "lng": "9.705857", "elevation": "200.1999969482422", "time": "2013-09-20T12:44:54.512Z"}, {"lat": "44.125297", "lng": "9.705855", "elevation": "200.10000610351563", "time": "2013-09-20T12:45:04.464Z"}, {"lat": "44.125293", "lng": "9.705857", "elevation": "200.60000610351563", "time": "2013-09-20T12:45:05.456Z"}, {"lat": "44.125211", "lng": "9.705871", "elevation": "200.5", "time": "2013-09-20T12:45:16.543Z"}, {"lat": "44.125203", "lng": "9.705875", "elevation": "200.1999969482422", "time": "2013-09-20T12:45:17.520Z"}, {"lat": "44.125137", "lng": "9.705898", "elevation": "198.8000030517578", "time": "2013-09-20T12:45:35.458Z"}, {"lat": "44.125138", "lng": "9.705901", "elevation": "198.89999389648438", "time": "2013-09-20T12:45:36.451Z"}, {"lat": "44.125132", "lng": "9.7059", "elevation": "198.8000030517578", "time": "2013-09-20T12:45:41.522Z"}, {"lat": "44.125126", "lng": "9.705904", "elevation": "198.39999389648438", "time": "2013-09-20T12:45:42.556Z"}, {"lat": "44.125051", "lng": "9.705964", "elevation": "196.3000030517578", "time": "2013-09-20T12:45:52.460Z"}, {"lat": "44.125046", "lng": "9.705967", "elevation": "195.89999389648438", "time": "2013-09-20T12:45:53.472Z"}, {"lat": "44.124957", "lng": "9.705985", "elevation": "193.5", "time": "2013-09-20T12:46:08.499Z"}, {"lat": "44.12495", "lng": "9.70599", "elevation": "193.0", "time": "2013-09-20T12:46:09.460Z"}, {"lat": "44.124887", "lng": "9.706078", "elevation": "191.10000610351563", "time": "2013-09-20T12:46:30.474Z"}, {"lat": "44.124885", "lng": "9.706087", "elevation": "190.8000030517578", "time": "2013-09-20T12:46:31.459Z"}, {"lat": "44.124875", "lng": "9.706202", "elevation": "187.1999969482422", "time": "2013-09-20T12:46:40.467Z"}, {"lat": "44.124873", "lng": "9.706214", "elevation": "186.3000030517578", "time": "2013-09-20T12:46:41.467Z"}, {"lat": "44.124862", "lng": "9.706334", "elevation": "182.1999969482422", "time": "2013-09-20T12:46:55.468Z"}, {"lat": "44.124861", "lng": "9.70634", "elevation": "181.8000030517578", "time": "2013-09-20T12:46:56.457Z"}, {"lat": "44.124781", "lng": "9.706388", "elevation": "180.60000610351563", "time": "2013-09-20T12:47:09.469Z"}, {"lat": "44.124774", "lng": "9.70639", "elevation": "180.5", "time": "2013-09-20T12:47:10.475Z"}, {"lat": "44.124711", "lng": "9.706452", "elevation": "181.0", "time": "2013-09-20T12:47:34.564Z"}, {"lat": "44.12471", "lng": "9.706451", "elevation": "181.39999389648438", "time": "2013-09-20T12:47:35.605Z"}, {"lat": "44.124705", "lng": "9.706454", "elevation": "181.3000030517578", "time": "2013-09-20T12:47:38.571Z"}, {"lat": "44.124702", "lng": "9.706456", "elevation": "181.1999969482422", "time": "2013-09-20T12:47:39.486Z"}, {"lat": "44.124628", "lng": "9.706508", "elevation": "178.3000030517578", "time": "2013-09-20T12:47:51.589Z"}, {"lat": "44.12462", "lng": "9.706511", "elevation": "178.3000030517578", "time": "2013-09-20T12:47:52.581Z"}, {"lat": "44.124535", "lng": "9.706541", "elevation": "178.0", "time": "2013-09-20T12:48:02.461Z"}, {"lat": "44.124525", "lng": "9.706549", "elevation": "178.10000610351563", "time": "2013-09-20T12:48:03.461Z"}, {"lat": "44.124455", "lng": "9.706625", "elevation": "176.6999969482422", "time": "2013-09-20T12:48:18.527Z"}, {"lat": "44.124454", "lng": "9.706628", "elevation": "176.1999969482422", "time": "2013-09-20T12:48:19.495Z"}, {"lat": "44.124452", "lng": "9.706645", "elevation": "174.10000610351563", "time": "2013-09-20T12:48:25.559Z"}, {"lat": "44.124451", "lng": "9.706647", "elevation": "174.0", "time": "2013-09-20T12:48:26.517Z"}, {"lat": "44.124445", "lng": "9.706657", "elevation": "172.89999389648438", "time": "2013-09-20T12:48:31.569Z"}, {"lat": "44.124444", "lng": "9.706659", "elevation": "173.0", "time": "2013-09-20T12:48:32.560Z"}, {"lat": "44.124443", "lng": "9.706661", "elevation": "172.6999969482422", "time": "2013-09-20T12:48:33.561Z"}, {"lat": "44.12444", "lng": "9.706667", "elevation": "171.8000030517578", "time": "2013-09-20T12:48:36.475Z"}, {"lat": "44.124437", "lng": "9.70667", "elevation": "171.60000610351563", "time": "2013-09-20T12:48:37.470Z"}, {"lat": "44.124409", "lng": "9.706687", "elevation": "170.5", "time": "2013-09-20T12:48:46.479Z"}, {"lat": "44.124408", "lng": "9.706689", "elevation": "169.8000030517578", "time": "2013-09-20T12:48:47.472Z"}, {"lat": "44.124407", "lng": "9.706693", "elevation": "169.60000610351563", "time": "2013-09-20T12:48:48.479Z"}, {"lat": "44.124402", "lng": "9.706717", "elevation": "169.6999969482422", "time": "2013-09-20T12:48:54.477Z"}, {"lat": "44.124401", "lng": "9.706718", "elevation": "169.6999969482422", "time": "2013-09-20T12:48:55.476Z"}, {"lat": "44.124399", "lng": "9.706725", "elevation": "169.60000610351563", "time": "2013-09-20T12:49:12.474Z"}, {"lat": "44.124399", "lng": "9.706731", "elevation": "169.60000610351563", "time": "2013-09-20T12:49:13.473Z"}, {"lat": "44.124433", "lng": "9.706832", "elevation": "162.3000030517578", "time": "2013-09-20T12:49:22.492Z"}, {"lat": "44.124436", "lng": "9.706845", "elevation": "161.8000030517578", "time": "2013-09-20T12:49:23.488Z"}, {"lat": "44.124378", "lng": "9.70694", "elevation": "160.6999969482422", "time": "2013-09-20T12:49:30.523Z"}, {"lat": "44.124371", "lng": "9.706949", "elevation": "160.6999969482422", "time": "2013-09-20T12:49:31.483Z"}, {"lat": "44.124356", "lng": "9.706976", "elevation": "160.8000030517578", "time": "2013-09-20T12:49:39.501Z"}, {"lat": "44.124356", "lng": "9.706977", "elevation": "160.89999389648438", "time": "2013-09-20T12:49:40.486Z"}, {"lat": "44.124355", "lng": "9.706983", "elevation": "159.60000610351563", "time": "2013-09-20T12:49:55.493Z"}, {"lat": "44.124357", "lng": "9.70699", "elevation": "159.1999969482422", "time": "2013-09-20T12:49:56.485Z"}, {"lat": "44.124397", "lng": "9.707086", "elevation": "159.1999969482422", "time": "2013-09-20T12:50:04.485Z"}, {"lat": "44.124403", "lng": "9.707097", "elevation": "159.60000610351563", "time": "2013-09-20T12:50:05.512Z"}, {"lat": "44.12445", "lng": "9.707187", "elevation": "157.5", "time": "2013-09-20T12:50:17.483Z"}, {"lat": "44.124449", "lng": "9.707187", "elevation": "157.8000030517578", "time": "2013-09-20T12:50:18.482Z"}, {"lat": "44.124453", "lng": "9.707198", "elevation": "158.10000610351563", "time": "2013-09-20T12:50:40.536Z"}, {"lat": "44.124457", "lng": "9.707203", "elevation": "156.89999389648438", "time": "2013-09-20T12:50:41.491Z"}, {"lat": "44.124518", "lng": "9.707285", "elevation": "158.10000610351563", "time": "2013-09-20T12:50:52.656Z"}, {"lat": "44.12452", "lng": "9.707295", "elevation": "158.60000610351563", "time": "2013-09-20T12:50:53.649Z"}, {"lat": "44.124515", "lng": "9.707414", "elevation": "158.0", "time": "2013-09-20T12:51:04.490Z"}, {"lat": "44.124513", "lng": "9.707434", "elevation": "157.39999389648438", "time": "2013-09-20T12:51:05.683Z"}, {"lat": "44.12446", "lng": "9.707516", "elevation": "159.60000610351563", "time": "2013-09-20T12:51:12.562Z"}, {"lat": "44.124451", "lng": "9.707528", "elevation": "161.10000610351563", "time": "2013-09-20T12:51:13.518Z"}, {"lat": "44.1244", "lng": "9.707616", "elevation": "162.1999969482422", "time": "2013-09-20T12:51:20.684Z"}, {"lat": "44.124394", "lng": "9.70763", "elevation": "162.0", "time": "2013-09-20T12:51:21.661Z"}, {"lat": "44.124326", "lng": "9.707697", "elevation": "159.5", "time": "2013-09-20T12:51:29.516Z"}, {"lat": "44.124322", "lng": "9.707712", "elevation": "158.39999389648438", "time": "2013-09-20T12:51:30.487Z"}, {"lat": "44.124296", "lng": "9.707822", "elevation": "157.5", "time": "2013-09-20T12:51:39.488Z"}, {"lat": "44.124289", "lng": "9.707829", "elevation": "158.10000610351563", "time": "2013-09-20T12:51:40.485Z"}, {"lat": "44.124213", "lng": "9.707878", "elevation": "160.10000610351563", "time": "2013-09-20T12:51:51.489Z"}, {"lat": "44.124205", "lng": "9.707883", "elevation": "159.89999389648438", "time": "2013-09-20T12:51:52.487Z"}, {"lat": "44.124127", "lng": "9.707928", "elevation": "159.8000030517578", "time": "2013-09-20T12:52:01.486Z"}, {"lat": "44.124119", "lng": "9.707933", "elevation": "160.39999389648438", "time": "2013-09-20T12:52:02.496Z"}, {"lat": "44.12404", "lng": "9.707982", "elevation": "160.3000030517578", "time": "2013-09-20T12:52:12.487Z"}, {"lat": "44.12403", "lng": "9.707983", "elevation": "160.60000610351563", "time": "2013-09-20T12:52:13.491Z"}, {"lat": "44.123946", "lng": "9.708006", "elevation": "160.5", "time": "2013-09-20T12:52:22.489Z"}, {"lat": "44.123936", "lng": "9.70801", "elevation": "160.6999969482422", "time": "2013-09-20T12:52:23.489Z"}, {"lat": "44.123856", "lng": "9.70806", "elevation": "159.0", "time": "2013-09-20T12:52:31.678Z"}, {"lat": "44.12385", "lng": "9.708069", "elevation": "158.89999389648438", "time": "2013-09-20T12:52:32.600Z"}, {"lat": "44.123791", "lng": "9.70815", "elevation": "156.0", "time": "2013-09-20T12:52:40.651Z"}, {"lat": "44.123784", "lng": "9.70816", "elevation": "156.8000030517578", "time": "2013-09-20T12:52:41.668Z"}, {"lat": "44.123718", "lng": "9.708225", "elevation": "155.5", "time": "2013-09-20T12:52:49.611Z"}, {"lat": "44.123711", "lng": "9.708235", "elevation": "154.8000030517578", "time": "2013-09-20T12:52:50.673Z"}, {"lat": "44.123644", "lng": "9.708297", "elevation": "154.60000610351563", "time": "2013-09-20T12:52:58.579Z"}, {"lat": "44.123633", "lng": "9.708301", "elevation": "154.5", "time": "2013-09-20T12:52:59.698Z"}, {"lat": "44.123547", "lng": "9.708334", "elevation": "151.1999969482422", "time": "2013-09-20T12:53:08.497Z"}, {"lat": "44.123539", "lng": "9.70834", "elevation": "150.60000610351563", "time": "2013-09-20T12:53:09.504Z"}, {"lat": "44.123486", "lng": "9.708428", "elevation": "147.60000610351563", "time": "2013-09-20T12:53:17.492Z"}, {"lat": "44.123481", "lng": "9.708442", "elevation": "147.39999389648438", "time": "2013-09-20T12:53:18.495Z"}, {"lat": "44.123423", "lng": "9.708533", "elevation": "148.8000030517578", "time": "2013-09-20T12:53:28.646Z"}, {"lat": "44.12342", "lng": "9.708543", "elevation": "148.89999389648438", "time": "2013-09-20T12:53:29.629Z"}, {"lat": "44.123342", "lng": "9.708599", "elevation": "147.1999969482422", "time": "2013-09-20T12:53:39.554Z"}, {"lat": "44.123333", "lng": "9.708604", "elevation": "145.5", "time": "2013-09-20T12:53:40.655Z"}, {"lat": "44.123279", "lng": "9.708686", "elevation": "144.39999389648438", "time": "2013-09-20T12:53:48.492Z"}, {"lat": "44.123273", "lng": "9.708699", "elevation": "143.6999969482422", "time": "2013-09-20T12:53:49.500Z"}, {"lat": "44.123238", "lng": "9.708806", "elevation": "143.1999969482422", "time": "2013-09-20T12:53:57.495Z"}, {"lat": "44.123227", "lng": "9.708813", "elevation": "144.10000610351563", "time": "2013-09-20T12:53:58.668Z"}, {"lat": "44.123139", "lng": "9.708827", "elevation": "143.8000030517578", "time": "2013-09-20T12:54:07.671Z"}, {"lat": "44.123132", "lng": "9.708825", "elevation": "143.89999389648438", "time": "2013-09-20T12:54:08.594Z"}, {"lat": "44.12305", "lng": "9.708873", "elevation": "145.5", "time": "2013-09-20T12:54:19.653Z"}, {"lat": "44.123044", "lng": "9.708883", "elevation": "146.89999389648438", "time": "2013-09-20T12:54:20.530Z"}, {"lat": "44.123033", "lng": "9.709003", "elevation": "142.1999969482422", "time": "2013-09-20T12:54:27.547Z"}, {"lat": "44.123037", "lng": "9.709016", "elevation": "142.0", "time": "2013-09-20T12:54:28.661Z"}, {"lat": "44.123044", "lng": "9.709024", "elevation": "141.6999969482422", "time": "2013-09-20T12:54:33.566Z"}, {"lat": "44.123044", "lng": "9.709024", "elevation": "141.39999389648438", "time": "2013-09-20T12:54:34.573Z"}, {"lat": "44.123045", "lng": "9.709027", "elevation": "140.8000030517578", "time": "2013-09-20T12:54:37.499Z"}, {"lat": "44.123046", "lng": "9.709033", "elevation": "140.0", "time": "2013-09-20T12:54:38.502Z"}, {"lat": "44.123049", "lng": "9.709143", "elevation": "141.89999389648438", "time": "2013-09-20T12:54:45.501Z"}, {"lat": "44.123042", "lng": "9.70916", "elevation": "143.6999969482422", "time": "2013-09-20T12:54:46.499Z"}, {"lat": "44.122993", "lng": "9.709248", "elevation": "144.10000610351563", "time": "2013-09-20T12:54:52.577Z"}, {"lat": "44.122987", "lng": "9.709261", "elevation": "144.39999389648438", "time": "2013-09-20T12:54:53.616Z"}, {"lat": "44.12295", "lng": "9.709362", "elevation": "143.60000610351563", "time": "2013-09-20T12:55:04.674Z"}, {"lat": "44.122939", "lng": "9.70937", "elevation": "144.1999969482422", "time": "2013-09-20T12:55:05.669Z"}, {"lat": "44.122904", "lng": "9.709478", "elevation": "141.1999969482422", "time": "2013-09-20T12:55:11.596Z"}, {"lat": "44.122901", "lng": "9.709496", "elevation": "140.8000030517578", "time": "2013-09-20T12:55:12.702Z"}, {"lat": "44.122878", "lng": "9.709545", "elevation": "136.0", "time": "2013-09-20T12:55:20.525Z"}, {"lat": "44.122881", "lng": "9.709537", "elevation": "135.8000030517578", "time": "2013-09-20T12:55:21.639Z"}, {"lat": "44.122882", "lng": "9.709527", "elevation": "136.3000030517578", "time": "2013-09-20T12:55:22.615Z"}, {"lat": "44.122811", "lng": "9.709459", "elevation": "139.8000030517578", "time": "2013-09-20T12:55:30.609Z"}, {"lat": "44.122801", "lng": "9.709451", "elevation": "139.1999969482422", "time": "2013-09-20T12:55:31.549Z"}, {"lat": "44.122742", "lng": "9.709389", "elevation": "138.60000610351563", "time": "2013-09-20T12:55:47.577Z"}, {"lat": "44.122741", "lng": "9.70939", "elevation": "138.6999969482422", "time": "2013-09-20T12:55:48.739Z"}, {"lat": "44.122741", "lng": "9.70938", "elevation": "138.89999389648438", "time": "2013-09-20T12:55:53.713Z"}, {"lat": "44.122739", "lng": "9.709376", "elevation": "137.6999969482422", "time": "2013-09-20T12:55:54.606Z"}, {"lat": "44.122705", "lng": "9.709331", "elevation": "139.0", "time": "2013-09-20T12:56:03.557Z"}, {"lat": "44.122706", "lng": "9.709335", "elevation": "138.89999389648438", "time": "2013-09-20T12:56:04.738Z"}] diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/uk.lproj/InfoPlist.strings a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/uk.lproj/InfoPlist.strings new file mode 100755 index 0000000..477b28f --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/uk.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/vi.lproj/InfoPlist.strings a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/vi.lproj/InfoPlist.strings new file mode 100755 index 0000000..477b28f --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/vi.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/zh_CN.lproj/InfoPlist.strings a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/zh_CN.lproj/InfoPlist.strings new file mode 100755 index 0000000..477b28f --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/zh_CN.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/zh_TW.lproj/InfoPlist.strings a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/zh_TW.lproj/InfoPlist.strings new file mode 100755 index 0000000..477b28f --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Resources/zh_TW.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/SDKDemoAPIKey.h a/Pods/GoogleMaps/Example/GoogleMapsDemos/SDKDemoAPIKey.h new file mode 100755 index 0000000..23ba5b0 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/SDKDemoAPIKey.h @@ -0,0 +1,25 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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. + */ + +/** + * To use GoogleMapsDemos, please register an API Key for your application and set it here. Your + * API Key should be kept private. + * + * See documentation on getting an API Key for your API Project here: + * https://developers.google.com/maps/documentation/ios/start#get-key + */ + +#error Register for API Key and insert here. Then delete this line. +static NSString *const kAPIKey = @""; diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/AnimatedCurrentLocationViewController.h a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/AnimatedCurrentLocationViewController.h new file mode 100755 index 0000000..6b7bd89 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/AnimatedCurrentLocationViewController.h @@ -0,0 +1,23 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 +#import + +#import + +@interface AnimatedCurrentLocationViewController : UIViewController + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/AnimatedCurrentLocationViewController.m a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/AnimatedCurrentLocationViewController.m new file mode 100755 index 0000000..306a8d1 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/AnimatedCurrentLocationViewController.m @@ -0,0 +1,105 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 "GoogleMapsDemos/Samples/AnimatedCurrentLocationViewController.h" + +@implementation AnimatedCurrentLocationViewController { + CLLocationManager *_manager; + GMSMapView *_mapView; + GMSMarker *_locationMarker; + +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + GMSCameraPosition *camera = [GMSCameraPosition cameraWithLatitude:38.8879 + longitude:-77.0200 + zoom:17]; + _mapView = [GMSMapView mapWithFrame:CGRectZero camera:camera]; + _mapView.settings.myLocationButton = NO; + _mapView.settings.indoorPicker = NO; + + self.view = _mapView; + + // Setup location services + if (![CLLocationManager locationServicesEnabled]) { + NSLog(@"Please enable location services"); + return; + } + + if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied) { + NSLog(@"Please authorize location services"); + return; + } + + _manager = [[CLLocationManager alloc] init]; + if ([_manager respondsToSelector:@selector(requestWhenInUseAuthorization)]) { + [_manager requestWhenInUseAuthorization]; + } + _manager.delegate = self; + _manager.desiredAccuracy = kCLLocationAccuracyBest; + _manager.distanceFilter = 5.0f; + [_manager startUpdatingLocation]; + +} + +#pragma mark - CLLocationManagerDelegate + +- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error { + if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied) { + NSLog(@"Please authorize location services"); + return; + } + + NSLog(@"CLLocationManager error: %@", error.localizedFailureReason); + return; +} + +- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations { + CLLocation *location = [locations lastObject]; + + if (_locationMarker == nil) { + _locationMarker = [[GMSMarker alloc] init]; + _locationMarker.position = location.coordinate; + + // Animated walker images derived from an www.angryanimator.com tutorial. + // See: http://www.angryanimator.com/word/2010/11/26/tutorial-2-walk-cycle/ + + NSArray *frames = @[[UIImage imageNamed:@"step1"], + [UIImage imageNamed:@"step2"], + [UIImage imageNamed:@"step3"], + [UIImage imageNamed:@"step4"], + [UIImage imageNamed:@"step5"], + [UIImage imageNamed:@"step6"], + [UIImage imageNamed:@"step7"], + [UIImage imageNamed:@"step8"]]; + + _locationMarker.icon = [UIImage animatedImageWithImages:frames duration:0.8]; + _locationMarker.groundAnchor = CGPointMake(0.5f, 0.97f); // Taking into account walker's shadow + _locationMarker.map = _mapView; + } else { + [CATransaction begin]; + [CATransaction setAnimationDuration:2.0]; + _locationMarker.position = location.coordinate; + [CATransaction commit]; + } + + GMSCameraUpdate *move = [GMSCameraUpdate setTarget:location.coordinate zoom:17]; + [_mapView animateWithCameraUpdate:move]; +} + + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/AnimatedUIViewMarkerViewController.h a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/AnimatedUIViewMarkerViewController.h new file mode 100755 index 0000000..0dce9e8 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/AnimatedUIViewMarkerViewController.h @@ -0,0 +1,20 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 + +@interface AnimatedUIViewMarkerViewController : UIViewController + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/AnimatedUIViewMarkerViewController.m a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/AnimatedUIViewMarkerViewController.m new file mode 100755 index 0000000..7392b51 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/AnimatedUIViewMarkerViewController.m @@ -0,0 +1,130 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 "GoogleMapsDemos/Samples/AnimatedUIViewMarkerViewController.h" + +#import + +// Returns a random value from 0-1.0f. +static CGFloat randf() { return (((float)arc4random() / 0x100000000) * 1.0f); } + +@interface AnimatedUIViewMarkerViewController () +@end + +@implementation AnimatedUIViewMarkerViewController { + GMSMapView *_mapView; + UIView *_infoView; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + GMSCameraPosition *camera = + [GMSCameraPosition cameraWithLatitude:-33.8683 longitude:151.2086 zoom:5]; + _mapView = [GMSMapView mapWithFrame:CGRectZero camera:camera]; + _mapView.delegate = self; + + self.view = _mapView; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(applicationWillEnterForeground) + name:UIApplicationWillEnterForegroundNotification + object:nil]; + [_mapView clear]; + [self addDefaultMarker]; +} + +- (void)applicationWillEnterForeground { + [_mapView clear]; + [self addDefaultMarker]; +} + +- (void)viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +- (UIView *)mapView:(GMSMapView *)mapView markerInfoContents:(GMSMarker *)marker { + // Show an info window with dynamic content - a simple background color animation. + _infoView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"arrow"]]; + UIView *infoView = _infoView; + marker.tracksInfoWindowChanges = YES; + UIColor *color = [UIColor colorWithHue:randf() saturation:1.f brightness:1.f alpha:1.0f]; + _infoView.backgroundColor = [UIColor clearColor]; + [UIView animateWithDuration:1.0 + delay:1.0 + options:UIViewAnimationOptionCurveLinear + animations:^{ + infoView.backgroundColor = color; + } + completion:^(BOOL finished) { + [UIView animateWithDuration:1.0 + delay:0.0 + options:UIViewAnimationOptionCurveLinear + animations:^{ + infoView.backgroundColor = [UIColor clearColor]; + } + completion:^(BOOL finished2) { + marker.tracksInfoWindowChanges = NO; + }]; + }]; + + return _infoView; +} + +- (void)mapView:(GMSMapView *)mapView didCloseInfoWindowOfMarker:(GMSMarker *)marker { + _infoView = nil; + marker.tracksInfoWindowChanges = NO; +} + +- (void)addDefaultMarker { + // Add a custom 'glow' marker with a pulsing blue shadow on Sydney. + GMSMarker *sydneyMarker = [[GMSMarker alloc] init]; + sydneyMarker.title = @"Sydney!"; + sydneyMarker.iconView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"glow-marker"]]; + sydneyMarker.position = CLLocationCoordinate2DMake(-33.8683, 151.2086); + sydneyMarker.iconView.contentMode = UIViewContentModeCenter; + CGRect oldBound = sydneyMarker.iconView.bounds; + CGRect bound = oldBound; + bound.size.width *= 2; + bound.size.height *= 2; + sydneyMarker.iconView.bounds = bound; + sydneyMarker.groundAnchor = CGPointMake(0.5, 0.75); + sydneyMarker.infoWindowAnchor = CGPointMake(0.5, 0.25); + UIView *sydneyGlow = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"glow-marker"]]; + sydneyGlow.layer.shadowColor = [UIColor blueColor].CGColor; + sydneyGlow.layer.shadowOffset = CGSizeZero; + sydneyGlow.layer.shadowRadius = 8.0; + sydneyGlow.layer.shadowOpacity = 1.0; + sydneyGlow.layer.opacity = 0.0; + [sydneyMarker.iconView addSubview:sydneyGlow]; + sydneyGlow.center = CGPointMake(oldBound.size.width, oldBound.size.height); + sydneyMarker.map = _mapView; + [UIView animateWithDuration:1.0 + delay:0.0 + options:UIViewAnimationOptionCurveEaseInOut | UIViewKeyframeAnimationOptionAutoreverse | + UIViewKeyframeAnimationOptionRepeat + animations:^{ + sydneyGlow.layer.opacity = 1.0; + } + completion:^(BOOL finished) { + // If the animation is ever terminated, no need to keep tracking the view for changes. + sydneyMarker.tracksViewChanges = NO; + }]; +} + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/BasicMapViewController.h a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/BasicMapViewController.h new file mode 100755 index 0000000..9edc891 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/BasicMapViewController.h @@ -0,0 +1,22 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 + +#import + +@interface BasicMapViewController : UIViewController + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/BasicMapViewController.m a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/BasicMapViewController.m new file mode 100755 index 0000000..558acce --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/BasicMapViewController.m @@ -0,0 +1,51 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 "GoogleMapsDemos/Samples/BasicMapViewController.h" + +@implementation BasicMapViewController { + UILabel *_statusLabel; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + GMSCameraPosition *camera = [GMSCameraPosition cameraWithLatitude:-33.868 + longitude:151.2086 + zoom:6]; + GMSMapView *view = [GMSMapView mapWithFrame:CGRectZero camera:camera]; + view.delegate = self; + self.view = view; + + // Add status label, initially hidden. + _statusLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 0, 30)]; + _statusLabel.alpha = 0.0f; + _statusLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth; + _statusLabel.backgroundColor = [UIColor blueColor]; + _statusLabel.textColor = [UIColor whiteColor]; + _statusLabel.textAlignment = NSTextAlignmentCenter; + + [view addSubview:_statusLabel]; +} + +- (void)mapViewDidStartTileRendering:(GMSMapView *)mapView { + _statusLabel.alpha = 0.8f; + _statusLabel.text = @"Rendering"; +} + +- (void)mapViewDidFinishTileRendering:(GMSMapView *)mapView { + _statusLabel.alpha = 0.0f; +} + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/CameraViewController.h a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/CameraViewController.h new file mode 100755 index 0000000..b8f9b1a --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/CameraViewController.h @@ -0,0 +1,20 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 + +@interface CameraViewController : UIViewController + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/CameraViewController.m a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/CameraViewController.m new file mode 100755 index 0000000..a2dc629 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/CameraViewController.m @@ -0,0 +1,72 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 "GoogleMapsDemos/Samples/CameraViewController.h" + +#import + +@implementation CameraViewController { + GMSMapView *_mapView; + NSTimer *timer; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + GMSCameraPosition *camera = [GMSCameraPosition cameraWithLatitude:-37.809487 + longitude:144.965699 + zoom:20 + bearing:0 + viewingAngle:0]; + _mapView = [GMSMapView mapWithFrame:CGRectZero camera:camera]; + _mapView.settings.zoomGestures = NO; + _mapView.settings.scrollGestures = NO; + _mapView.settings.rotateGestures = NO; + _mapView.settings.tiltGestures = NO; + + self.view = _mapView; +} + +- (void)moveCamera { + GMSCameraPosition *camera = _mapView.camera; + float zoom = fmaxf(camera.zoom - 0.1f, 17.5f); + + GMSCameraPosition *newCamera = + [[GMSCameraPosition alloc] initWithTarget:camera.target + zoom:zoom + bearing:camera.bearing + 10 + viewingAngle:camera.viewingAngle + 10]; + [_mapView animateToCameraPosition:newCamera]; +} + +- (void)viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; + timer = [NSTimer scheduledTimerWithTimeInterval:1.f/30.f + target:self + selector:@selector(moveCamera) + userInfo:nil + repeats:YES]; +} + +- (void)viewDidDisappear:(BOOL)animated { + [super viewDidDisappear:animated]; + [timer invalidate]; +} + +- (void)didReceiveMemoryWarning { + [super didReceiveMemoryWarning]; + [timer invalidate]; +} + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/CustomIndoorViewController.h a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/CustomIndoorViewController.h new file mode 100755 index 0000000..c546d30 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/CustomIndoorViewController.h @@ -0,0 +1,19 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 + +@interface CustomIndoorViewController : UIViewController +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/CustomIndoorViewController.m a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/CustomIndoorViewController.m new file mode 100755 index 0000000..f5b50c2 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/CustomIndoorViewController.m @@ -0,0 +1,150 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 "GoogleMapsDemos/Samples/CustomIndoorViewController.h" + +#import + +@interface CustomIndoorViewController () < + GMSIndoorDisplayDelegate, + UIPickerViewDelegate, + UIPickerViewDataSource> + +@end + +@implementation CustomIndoorViewController { + GMSMapView *_mapView; + UIPickerView *_levelPickerView; + NSArray *_levels; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + GMSCameraPosition *camera = [GMSCameraPosition cameraWithLatitude:37.78318 + longitude:-122.403874 + zoom:18]; + + // set backgroundColor, otherwise UIPickerView fades into the background + self.view.backgroundColor = [UIColor grayColor]; + + _mapView = [GMSMapView mapWithFrame:CGRectZero camera:camera]; + _mapView.settings.myLocationButton = NO; + _mapView.settings.indoorPicker = NO; // We are implementing a custom level picker. + + _mapView.indoorEnabled = YES; // Defaults to YES. Set to NO to hide indoor maps. + _mapView.indoorDisplay.delegate = self; + _mapView.translatesAutoresizingMaskIntoConstraints = NO; + [self.view addSubview:_mapView]; + + // This UIPickerView will be populated with the levels of the active building. + _levelPickerView = [[UIPickerView alloc] init]; + _levelPickerView.delegate = self; + _levelPickerView.dataSource = self; + _levelPickerView.showsSelectionIndicator = YES; + _levelPickerView.translatesAutoresizingMaskIntoConstraints = NO; + [self.view addSubview:_levelPickerView]; + + // The height of the UIPickerView, used below in the vertical constraint + NSDictionary *metrics = @{@"height": @180.0}; + NSDictionary *views = NSDictionaryOfVariableBindings(_mapView, _levelPickerView); + + // Constraining the map to the full width of the display. + // The |_levelPickerView| is constrained below with the NSLayoutFormatAlignAll* + // See http://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/Articles/formatLanguage.html + [self.view addConstraints:[NSLayoutConstraint + constraintsWithVisualFormat:@"|[_mapView]|" + options:0 + metrics:metrics + views:views]]; + + // Constraining the _mapView and the _levelPickerView as siblings taking + // the full height of the display, with _levelPickerView at 200 points high + [self.view addConstraints:[NSLayoutConstraint + constraintsWithVisualFormat:@"V:|[_mapView][_levelPickerView(height)]|" + options:NSLayoutFormatAlignAllLeft|NSLayoutFormatAlignAllRight + metrics:metrics + views:views]]; +} + +#pragma mark - GMSIndoorDisplayDelegate + +- (void)didChangeActiveBuilding:(GMSIndoorBuilding *)building { + // Everytime we change active building force the picker to re-display the labels. + + NSMutableArray *levels = [NSMutableArray array]; + if (building.underground) { + // If this building is completely underground, add a fake 'top' floor. This must be the 'boxed' + // nil, [NSNull null], as NSArray/NSMutableArray cannot contain nils. + [levels addObject:[NSNull null]]; + } + [levels addObjectsFromArray:building.levels]; + _levels = [levels copy]; + + [_levelPickerView reloadAllComponents]; + [_levelPickerView selectRow:-1 inComponent:0 animated:NO]; + + // UIPickerView insists on having some data; disable interaction if there's no levels. + _levelPickerView.userInteractionEnabled = (_levels.count > 0); +} + +- (void)didChangeActiveLevel:(GMSIndoorLevel *)level { + // On level change, sync our level picker's selection to the IndoorDisplay. + if (level == nil) { + level = (id)[NSNull null]; // box nil to NSNull for use in NSArray + } + NSUInteger index = [_levels indexOfObject:level]; + if (index != NSNotFound) { + NSInteger currentlySelectedLevel = [_levelPickerView selectedRowInComponent:0]; + if ((NSInteger)index != currentlySelectedLevel) { + [_levelPickerView selectRow:index inComponent:0 animated:NO]; + } + } +} + +#pragma mark - UIPickerViewDelegate + +- (void)pickerView:(UIPickerView *)pickerView + didSelectRow:(NSInteger)row + inComponent:(NSInteger)component { + // On user selection of a level in the picker, set the right level in IndoorDisplay + id level = _levels[row]; + if (level == [NSNull null]) { + level = nil; // unbox NSNull + } + [_mapView.indoorDisplay setActiveLevel:level]; +} + +- (NSString *)pickerView:(UIPickerView *)pickerView + titleForRow:(NSInteger)row + forComponent:(NSInteger)component { + id object = _levels[row]; + if (object == [NSNull null]) { + return @"\u2014"; // use an em dash for 'above ground' + } + GMSIndoorLevel *level = object; + return level.name; +} + +#pragma mark - UIPickerViewDataSource + +- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView { + return 1; +} + +- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component { + return _levels.count; +} + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/CustomMarkersViewController.h a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/CustomMarkersViewController.h new file mode 100755 index 0000000..c63fd37 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/CustomMarkersViewController.h @@ -0,0 +1,20 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 + +@interface CustomMarkersViewController : UIViewController + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/CustomMarkersViewController.m a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/CustomMarkersViewController.m new file mode 100755 index 0000000..cd0c5f4 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/CustomMarkersViewController.m @@ -0,0 +1,120 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 "GoogleMapsDemos/Samples/CustomMarkersViewController.h" + +#import + +static int kMarkerCount = 0; + +// Returns a random value from 0-1.0f. +static CGFloat randf() { + return (((float)arc4random() / 0x100000000) * 1.0f); +} + +@implementation CustomMarkersViewController { + GMSMapView *_mapView; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + GMSCameraPosition *camera = + [GMSCameraPosition cameraWithLatitude:-37.81969 longitude:144.966085 zoom:4]; + _mapView = [GMSMapView mapWithFrame:CGRectZero camera:camera]; + [self addDefaultMarkers]; + + // Add a button which adds random markers to the map. + UIBarButtonItem *addButton = + [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd + target:self + action:@selector(didTapAdd)]; + addButton.accessibilityLabel = @"Add Markers"; + UIBarButtonItem *clearButton = [[UIBarButtonItem alloc] initWithTitle:@"Clear Markers" + style:UIBarButtonItemStylePlain + target:self + action:@selector(didTapClear)]; + self.navigationItem.rightBarButtonItems = @[ addButton, clearButton ]; + + self.view = _mapView; +} + +- (void)addDefaultMarkers { + // Add a custom 'glow' marker around Sydney. + GMSMarker *sydneyMarker = [[GMSMarker alloc] init]; + sydneyMarker.title = @"Sydney!"; + sydneyMarker.icon = [UIImage imageNamed:@"glow-marker"]; + sydneyMarker.position = CLLocationCoordinate2DMake(-33.8683, 151.2086); + sydneyMarker.map = _mapView; + + // Add a custom 'arrow' marker pointing to Melbourne. + GMSMarker *melbourneMarker = [[GMSMarker alloc] init]; + melbourneMarker.title = @"Melbourne!"; + melbourneMarker.icon = [UIImage imageNamed:@"arrow"]; + melbourneMarker.position = CLLocationCoordinate2DMake(-37.81969, 144.966085); + melbourneMarker.map = _mapView; +} + +- (void)didTapAdd { + for (int i = 0; i < 10; ++i) { + // Add a marker every 0.25 seconds for the next ten markers, randomly + // within the bounds of the camera as it is at that point. + double delayInSeconds = (i * 0.25); + dispatch_time_t popTime = + dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); + __weak __typeof__(self) weakSelf = self; + dispatch_after(popTime, dispatch_get_main_queue(), ^(void) { + __typeof__(self) strongSelf = weakSelf; + if (strongSelf) { + GMSVisibleRegion region = [strongSelf->_mapView.projection visibleRegion]; + GMSCoordinateBounds *bounds = [[GMSCoordinateBounds alloc] initWithRegion:region]; + [strongSelf addMarkerInBounds:bounds]; + } + }); + } +} + +- (void)addMarkerInBounds:(GMSCoordinateBounds *)bounds { + CLLocationDegrees latitude = + bounds.southWest.latitude + randf() * (bounds.northEast.latitude - bounds.southWest.latitude); + + // If the visible region crosses the antimeridian (the right-most point is + // "smaller" than the left-most point), adjust the longitude accordingly. + BOOL offset = (bounds.northEast.longitude < bounds.southWest.longitude); + CLLocationDegrees longitude = + bounds.southWest.longitude + + randf() * (bounds.northEast.longitude - bounds.southWest.longitude + (offset ? 360 : 0)); + if (longitude > 180.f) { + longitude -= 360.f; + } + + UIColor *color = [UIColor colorWithHue:randf() saturation:1.f brightness:1.f alpha:1.0f]; + + CLLocationCoordinate2D position = CLLocationCoordinate2DMake(latitude, longitude); + GMSMarker *marker = [GMSMarker markerWithPosition:position]; + marker.title = [NSString stringWithFormat:@"Marker #%d", ++kMarkerCount]; + marker.appearAnimation = kGMSMarkerAnimationPop; + marker.icon = [GMSMarker markerImageWithColor:color]; + + marker.rotation = (randf() - 0.5f) * 20; // rotate between -10 and +10 degrees + + marker.map = _mapView; +} + +- (void)didTapClear { + [_mapView clear]; + [self addDefaultMarkers]; +} + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/DoubleMapViewController.h a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/DoubleMapViewController.h new file mode 100755 index 0000000..d612fbf --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/DoubleMapViewController.h @@ -0,0 +1,20 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 + +@interface DoubleMapViewController : UIViewController + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/DoubleMapViewController.m a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/DoubleMapViewController.m new file mode 100755 index 0000000..8fb1f25 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/DoubleMapViewController.m @@ -0,0 +1,77 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 "GoogleMapsDemos/Samples/DoubleMapViewController.h" + +#import + +@interface DoubleMapViewController () +@end + +@implementation DoubleMapViewController { + GMSMapView *_mapView; + GMSMapView *_boundMapView; +} + ++ (GMSCameraPosition *)defaultCamera { + return [GMSCameraPosition cameraWithLatitude:37.7847 + longitude:-122.41 + zoom:5]; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + // Two map views, second one has its camera target controlled by the first. + CGRect frame = self.view.bounds; + frame.size.height = frame.size.height / 2; + _mapView = [GMSMapView mapWithFrame:frame camera:[DoubleMapViewController defaultCamera]]; + _mapView.autoresizingMask = UIViewAutoresizingFlexibleWidth | + UIViewAutoresizingFlexibleHeight | + UIViewAutoresizingFlexibleBottomMargin; + + _mapView.delegate = self; + [self.view addSubview:_mapView]; + + frame = self.view.bounds; + frame.size.height = frame.size.height / 2; + frame.origin.y = frame.size.height; + _boundMapView = + [GMSMapView mapWithFrame:frame camera:[DoubleMapViewController defaultCamera]]; + _boundMapView.autoresizingMask = UIViewAutoresizingFlexibleWidth | + UIViewAutoresizingFlexibleHeight | + UIViewAutoresizingFlexibleTopMargin; + _boundMapView.settings.scrollGestures = NO; + + [self.view addSubview:_boundMapView]; +} + +- (void)viewWillTransitionToSize:(CGSize)size + withTransitionCoordinator:(id)coordinator { + [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; + CGRect frame = self.view.bounds; + frame.size.height = frame.size.height / 2; + _mapView.frame = frame; +} + +- (void)mapView:(GMSMapView *)mapView didChangeCameraPosition:(GMSCameraPosition *)position { + GMSCameraPosition *previousCamera = _boundMapView.camera; + _boundMapView.camera = [GMSCameraPosition cameraWithTarget:position.target + zoom:previousCamera.zoom + bearing:previousCamera.bearing + viewingAngle:previousCamera.viewingAngle]; +} + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/FitBoundsViewController.h a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/FitBoundsViewController.h new file mode 100755 index 0000000..b38b0f5 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/FitBoundsViewController.h @@ -0,0 +1,20 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 + +@interface FitBoundsViewController : UIViewController + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/FitBoundsViewController.m a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/FitBoundsViewController.m new file mode 100755 index 0000000..b0b4f28 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/FitBoundsViewController.m @@ -0,0 +1,91 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 "GoogleMapsDemos/Samples/FitBoundsViewController.h" + +#import + +@interface FitBoundsViewController () +@end + +@implementation FitBoundsViewController { + GMSMapView *_mapView; + NSMutableArray *_markers; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + GMSCameraPosition *camera = [GMSCameraPosition cameraWithLatitude:-37.81969 + longitude:144.966085 + zoom:4]; + _mapView = [GMSMapView mapWithFrame:CGRectZero camera:camera]; + _mapView.delegate = self; + self.view = _mapView; + + // Add a default marker around Sydney. + GMSMarker *sydneyMarker = [[GMSMarker alloc] init]; + sydneyMarker.title = @"Sydney!"; + sydneyMarker.icon = [UIImage imageNamed:@"glow-marker"]; + sydneyMarker.position = CLLocationCoordinate2DMake(-33.8683, 151.2086); + sydneyMarker.map = _mapView; + + GMSMarker *anotherSydneyMarker = [[GMSMarker alloc] init]; + anotherSydneyMarker.title = @"Sydney 2!"; + anotherSydneyMarker.icon = [UIImage imageNamed:@"glow-marker"]; + anotherSydneyMarker.position = CLLocationCoordinate2DMake(-33.8683, 149.2086); + anotherSydneyMarker.map = _mapView; + + // Create a list of markers, adding the Sydney marker. + _markers = [NSMutableArray arrayWithObject:sydneyMarker]; + [_markers addObject:anotherSydneyMarker]; + + // Create a button that, when pressed, updates the camera to fit the bounds + // of the specified markers. + UIBarButtonItem *fitBoundsButton = + [[UIBarButtonItem alloc] initWithTitle:@"Fit Bounds" + style:UIBarButtonItemStylePlain + target:self + action:@selector(didTapFitBounds)]; + self.navigationItem.rightBarButtonItem = fitBoundsButton; +} + +- (void)didTapFitBounds { + if (_markers.count == 0) return; + CLLocationCoordinate2D firstPos = ((GMSMarker *)_markers.firstObject).position; + GMSCoordinateBounds *bounds = + [[GMSCoordinateBounds alloc] initWithCoordinate:firstPos coordinate:firstPos]; + for (GMSMarker *marker in _markers) { + bounds = [bounds includingCoordinate:marker.position]; + } + GMSCameraUpdate *update = [GMSCameraUpdate fitBounds:bounds withPadding:50.0f]; + [_mapView moveCamera:update]; +} + +#pragma mark - GMSMapViewDelegate + +- (void)mapView:(GMSMapView *)mapView + didLongPressAtCoordinate:(CLLocationCoordinate2D)coordinate { + GMSMarker *marker = [[GMSMarker alloc] init]; + marker.title = [NSString stringWithFormat:@"Marker at: %.2f,%.2f", + coordinate.latitude, coordinate.longitude]; + marker.position = coordinate; + marker.appearAnimation = kGMSMarkerAnimationPop; + marker.map = _mapView; + + // Add the new marker to the list of markers. + [_markers addObject:marker]; +} + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/FixedPanoramaViewController.h a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/FixedPanoramaViewController.h new file mode 100755 index 0000000..900b02e --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/FixedPanoramaViewController.h @@ -0,0 +1,20 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 + +@interface FixedPanoramaViewController : UIViewController + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/FixedPanoramaViewController.m a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/FixedPanoramaViewController.m new file mode 100755 index 0000000..7074a93 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/FixedPanoramaViewController.m @@ -0,0 +1,44 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 "GoogleMapsDemos/Samples/FixedPanoramaViewController.h" + +#import + +static CLLocationCoordinate2D kPanoramaNear = {-33.732022, 150.312114}; + +@interface FixedPanoramaViewController () +@end + +@implementation FixedPanoramaViewController { + GMSPanoramaView *_view; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + _view = [GMSPanoramaView panoramaWithFrame:CGRectZero + nearCoordinate:kPanoramaNear]; + _view.camera = [GMSPanoramaCamera cameraWithHeading:180 + pitch:-10 + zoom:0]; + _view.delegate = self; + _view.orientationGestures = NO; + _view.navigationGestures = NO; + _view.navigationLinksHidden = YES; + self.view = _view; +} + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/FrameRateViewController.h a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/FrameRateViewController.h new file mode 100755 index 0000000..940dbb0 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/FrameRateViewController.h @@ -0,0 +1,20 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 + +@interface FrameRateViewController : UIViewController + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/FrameRateViewController.m a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/FrameRateViewController.m new file mode 100755 index 0000000..3367fe8 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/FrameRateViewController.m @@ -0,0 +1,88 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 "GoogleMapsDemos/Samples/FrameRateViewController.h" + +#import + +@interface FrameRateViewController () + +@end + +@implementation FrameRateViewController { + GMSMapView *_mapView; + UITextView *_statusTextView; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + GMSCameraPosition *camera = + [GMSCameraPosition cameraWithLatitude:-33.868 longitude:151.2086 zoom:6]; + _mapView = [GMSMapView mapWithFrame:CGRectZero camera:camera]; + self.view = _mapView; + + // Add a display for the current frame rate mode. + _statusTextView = [[UITextView alloc] init]; + _statusTextView.frame = CGRectMake(0, 0, CGRectGetWidth(self.view.frame), 0); + _statusTextView.text = @""; + _statusTextView.textAlignment = NSTextAlignmentCenter; + _statusTextView.backgroundColor = [UIColor colorWithWhite:1.0 alpha:0.8f]; + _statusTextView.autoresizingMask = UIViewAutoresizingFlexibleWidth; + _statusTextView.editable = NO; + [self.view addSubview:_statusTextView]; + [_statusTextView sizeToFit]; + + // Add a button toggling through modes. + self.navigationItem.rightBarButtonItem = + [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemPlay + target:self + action:@selector(didTapNext)]; + [self updateStatus]; +} + +- (void)didTapNext { + _mapView.preferredFrameRate = [self nextFrameRate]; + [self updateStatus]; +} + ++ (NSString *)nameForFrameRate:(GMSFrameRate)frameRate { + switch (frameRate) { + case kGMSFrameRatePowerSave: + return @"PowerSave"; + case kGMSFrameRateConservative: + return @"Conservative"; + case kGMSFrameRateMaximum: + return @"Maximum"; + } +} + +- (GMSFrameRate)nextFrameRate { + switch (_mapView.preferredFrameRate) { + case kGMSFrameRatePowerSave: + return kGMSFrameRateConservative; + case kGMSFrameRateConservative: + return kGMSFrameRateMaximum; + case kGMSFrameRateMaximum: + return kGMSFrameRatePowerSave; + } +} + +- (void)updateStatus { + _statusTextView.text = + [NSString stringWithFormat:@"Preferred frame rate: %@", + [[self class] nameForFrameRate:_mapView.preferredFrameRate]]; +} + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/GeocoderViewController.h a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/GeocoderViewController.h new file mode 100755 index 0000000..6fec749 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/GeocoderViewController.h @@ -0,0 +1,22 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 + +#import + +@interface GeocoderViewController : UIViewController + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/GeocoderViewController.m a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/GeocoderViewController.m new file mode 100755 index 0000000..62b5930 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/GeocoderViewController.m @@ -0,0 +1,71 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 "GoogleMapsDemos/Samples/GeocoderViewController.h" + +#import + +@implementation GeocoderViewController { + GMSMapView *_mapView; + GMSGeocoder *_geocoder; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + GMSCameraPosition *camera = [GMSCameraPosition cameraWithLatitude:-33.868 + longitude:151.2086 + zoom:12]; + + _mapView = [GMSMapView mapWithFrame:CGRectZero camera:camera]; + _mapView.delegate = self; + + _geocoder = [[GMSGeocoder alloc] init]; + + self.view = _mapView; +} + +- (void)mapView:(GMSMapView *)mapView didLongPressAtCoordinate:(CLLocationCoordinate2D)coordinate { + // On a long press, reverse geocode this location. + __weak __typeof__(self) weakSelf = self; + GMSReverseGeocodeCallback handler = ^(GMSReverseGeocodeResponse *response, NSError *error) { + [weakSelf handleResponse:response coordinate:coordinate error:error]; + }; + [_geocoder reverseGeocodeCoordinate:coordinate completionHandler:handler]; +} + +- (void)handleResponse:(nullable GMSReverseGeocodeResponse *)response + coordinate:(CLLocationCoordinate2D)coordinate + error:(nullable NSError *)error { + GMSAddress *address = response.firstResult; + if (address) { + NSLog(@"Geocoder result: %@", address); + + GMSMarker *marker = [GMSMarker markerWithPosition:address.coordinate]; + NSArray *lines = [address lines]; + + marker.title = [lines firstObject]; + if (lines.count > 1) { + marker.snippet = [lines objectAtIndex:1]; + } + + marker.appearAnimation = kGMSMarkerAnimationPop; + marker.map = _mapView; + } else { + NSLog(@"Could not reverse geocode point (%f,%f): %@", coordinate.latitude, coordinate.longitude, + error); + } +} + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/GestureControlViewController.h a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/GestureControlViewController.h new file mode 100755 index 0000000..f103e5d --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/GestureControlViewController.h @@ -0,0 +1,20 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 + +@interface GestureControlViewController : UIViewController + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/GestureControlViewController.m a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/GestureControlViewController.m new file mode 100755 index 0000000..1935c25 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/GestureControlViewController.m @@ -0,0 +1,70 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 "GoogleMapsDemos/Samples/GestureControlViewController.h" + +#import + +@implementation GestureControlViewController { + GMSMapView *_mapView; + UISwitch *_zoomSwitch; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + GMSCameraPosition *camera = [GMSCameraPosition cameraWithLatitude:-25.5605 + longitude:133.605097 + zoom:3]; + + _mapView = [GMSMapView mapWithFrame:CGRectZero camera:camera]; + _mapView.autoresizingMask = + UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + + self.view = [[UIView alloc] initWithFrame:CGRectZero]; + [self.view addSubview:_mapView]; + + UIView *holder = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 59)]; + holder.autoresizingMask = + UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleBottomMargin; + holder.backgroundColor = [UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:0.8f]; + [self.view addSubview:holder]; + + // Zoom label. + UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(16, 16, 200, 29)]; + label.text = @"Zooming?"; + label.font = [UIFont boldSystemFontOfSize:18.0f]; + label.textAlignment = NSTextAlignmentLeft; + label.backgroundColor = [UIColor clearColor]; + label.layer.shadowColor = [[UIColor whiteColor] CGColor]; + label.layer.shadowOffset = CGSizeMake(0.0f, 1.0f); + label.layer.shadowOpacity = 1.0f; + label.layer.shadowRadius = 0.0f; + [holder addSubview:label]; + + // Control zooming. + _zoomSwitch = [[UISwitch alloc] initWithFrame:CGRectMake(-90, 16, 0, 0)]; + _zoomSwitch.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin; + [_zoomSwitch addTarget:self + action:@selector(didChangeZoomSwitch) + forControlEvents:UIControlEventValueChanged]; + _zoomSwitch.on = YES; + [holder addSubview:_zoomSwitch]; +} + +- (void)didChangeZoomSwitch { + _mapView.settings.zoomGestures = _zoomSwitch.isOn; +} + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/GradientPolylinesViewController.h a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/GradientPolylinesViewController.h new file mode 100755 index 0000000..3142b72 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/GradientPolylinesViewController.h @@ -0,0 +1,20 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 + +@interface GradientPolylinesViewController : UIViewController + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/GradientPolylinesViewController.m a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/GradientPolylinesViewController.m new file mode 100755 index 0000000..60f875e --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/GradientPolylinesViewController.m @@ -0,0 +1,87 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 "GoogleMapsDemos/Samples/GradientPolylinesViewController.h" + +#import + +@implementation GradientPolylinesViewController { + GMSMapView *_mapView; + GMSPolyline *_polyline; + NSMutableArray *_trackData; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + GMSCameraPosition *camera = [GMSCameraPosition cameraWithLatitude:44.1314 + longitude:9.6921 + zoom:14.059f + bearing:328.f + viewingAngle:40.f]; + _mapView = [GMSMapView mapWithFrame:CGRectZero camera:camera]; + self.view = _mapView; + + [self parseTrackFile]; + [_polyline setSpans:[self gradientSpans]]; +} + +- (NSArray *)gradientSpans { + NSMutableArray *colorSpans = [NSMutableArray array]; + NSUInteger count = _trackData.count; + UIColor *prevColor; + for (NSUInteger i = 0; i < count; i++) { + NSDictionary *dict = [_trackData objectAtIndex:i]; + double elevation = [[dict objectForKey:@"elevation"] doubleValue]; + + UIColor *toColor = [UIColor colorWithHue:(float)elevation/700 + saturation:1.f + brightness:.9f + alpha:1.f]; + + if (prevColor == nil) { + prevColor = toColor; + } + + GMSStrokeStyle *style = [GMSStrokeStyle gradientFromColor:prevColor toColor:toColor]; + [colorSpans addObject:[GMSStyleSpan spanWithStyle:style]]; + + prevColor = toColor; + } + return colorSpans; +} + +- (void)parseTrackFile { + NSString *filePath = [[NSBundle mainBundle] pathForResource:@"track" ofType:@"json"]; + NSData *data = [NSData dataWithContentsOfFile:filePath]; + NSArray *json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil]; + _trackData = [[NSMutableArray alloc] init]; + GMSMutablePath *path = [GMSMutablePath path]; + + for (NSUInteger i = 0; i < json.count; i++) { + NSDictionary *info = [json objectAtIndex:i]; + NSNumber *elevation = [info objectForKey:@"elevation"]; + CLLocationDegrees lat = [[info objectForKey:@"lat"] doubleValue]; + CLLocationDegrees lng = [[info objectForKey:@"lng"] doubleValue]; + CLLocation *loc = [[CLLocation alloc] initWithLatitude:lat longitude:lng]; + [_trackData addObject:@{@"loc": loc, @"elevation": elevation}]; + [path addLatitude:lat longitude:lng]; + } + + _polyline = [GMSPolyline polylineWithPath:path]; + _polyline.strokeWidth = 6; + _polyline.map = _mapView; +} + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/GroundOverlayViewController.h a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/GroundOverlayViewController.h new file mode 100755 index 0000000..e220bf4 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/GroundOverlayViewController.h @@ -0,0 +1,20 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 + +@interface GroundOverlayViewController : UIViewController + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/GroundOverlayViewController.m a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/GroundOverlayViewController.m new file mode 100755 index 0000000..1341bf6 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/GroundOverlayViewController.m @@ -0,0 +1,60 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 "GoogleMapsDemos/Samples/GroundOverlayViewController.h" + +#import + +@interface GroundOverlayViewController () +@end + +@implementation GroundOverlayViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + + CLLocationCoordinate2D southWest = CLLocationCoordinate2DMake(40.712216, -74.22655); + CLLocationCoordinate2D northEast = CLLocationCoordinate2DMake(40.773941, -74.12544); + + GMSCoordinateBounds *overlayBounds = [[GMSCoordinateBounds alloc] initWithCoordinate:southWest + coordinate:northEast]; + + // Choose the midpoint of the coordinate to focus the camera on. + CLLocationCoordinate2D newark = GMSGeometryInterpolate(southWest, northEast, 0.5); + GMSCameraPosition *camera = [GMSCameraPosition cameraWithTarget:newark + zoom:12 + bearing:0 + viewingAngle:45]; + GMSMapView *mapView = [GMSMapView mapWithFrame:CGRectZero camera:camera]; + mapView.delegate = self; + + // Add the ground overlay, centered in Newark, NJ + GMSGroundOverlay *groundOverlay = [[GMSGroundOverlay alloc] init]; + // Image from http://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg + groundOverlay.icon = [UIImage imageNamed:@"newark_nj_1922.jpg"]; + groundOverlay.tappable = YES; + groundOverlay.position = newark; + groundOverlay.bounds = overlayBounds; + groundOverlay.map = mapView; + + self.view = mapView; +} + +- (void)mapView:(GMSMapView *)mapView didTapOverlay:(GMSOverlay *)overlay { + float opacity = (((float)arc4random()/0x100000000)*0.5f + 0.5f); + ((GMSGroundOverlay *)overlay).opacity = opacity; +} + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/IndoorMuseumNavigationViewController.h a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/IndoorMuseumNavigationViewController.h new file mode 100755 index 0000000..722f6ea --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/IndoorMuseumNavigationViewController.h @@ -0,0 +1,24 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 + +#import + +@interface IndoorMuseumNavigationViewController : UIViewController< + GMSMapViewDelegate, + GMSIndoorDisplayDelegate> + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/IndoorMuseumNavigationViewController.m a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/IndoorMuseumNavigationViewController.m new file mode 100755 index 0000000..efe13cf --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/IndoorMuseumNavigationViewController.m @@ -0,0 +1,126 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 "GoogleMapsDemos/Samples/IndoorMuseumNavigationViewController.h" + +@implementation IndoorMuseumNavigationViewController { + GMSMapView *_mapView; + NSArray *_exhibits; // Array of JSON exhibit data. + NSDictionary *_exhibit; // The currently selected exhibit. Will be nil initially. + GMSMarker *_marker; + NSDictionary *_levels; // The levels dictionary is updated when a new building is selected, and + // contains mapping from localized level name to GMSIndoorLevel. +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + GMSCameraPosition *camera = [GMSCameraPosition cameraWithLatitude:38.8879 + longitude:-77.0200 + zoom:17]; + _mapView = [GMSMapView mapWithFrame:CGRectZero camera:camera]; + _mapView.settings.myLocationButton = NO; + _mapView.settings.indoorPicker = NO; + _mapView.delegate = self; + _mapView.indoorDisplay.delegate = self; + + self.view = _mapView; + + // Load the exhibits configuration from JSON + NSString *jsonPath = [[NSBundle mainBundle] pathForResource:@"museum-exhibits" ofType:@"json"]; + NSData *data = [NSData dataWithContentsOfFile:jsonPath]; + _exhibits = [NSJSONSerialization JSONObjectWithData:data + options:kNilOptions + error:nil]; + + + UISegmentedControl *segmentedControl = [[UISegmentedControl alloc] init]; + [segmentedControl setTintColor:[UIColor colorWithRed:0.373f green:0.667f blue:0.882f alpha:1.0f]]; + + segmentedControl.translatesAutoresizingMaskIntoConstraints = NO; + [segmentedControl addTarget:self + action:@selector(exhibitSelected:) + forControlEvents:UIControlEventValueChanged]; + [self.view addSubview:segmentedControl]; + + for (NSDictionary *exhibit in _exhibits) { + [segmentedControl insertSegmentWithImage:[UIImage imageNamed:exhibit[@"key"]] + atIndex:[_exhibits indexOfObject:exhibit] + animated:NO]; + } + + NSDictionary *views = NSDictionaryOfVariableBindings(segmentedControl); + + [self.view addConstraints:[NSLayoutConstraint + constraintsWithVisualFormat:@"[segmentedControl]-|" + options:kNilOptions + metrics:nil + views:views]]; + [self.view addConstraints:[NSLayoutConstraint + constraintsWithVisualFormat:@"V:[segmentedControl]-|" + options:kNilOptions + metrics:nil + views:views]]; + +} + +- (void)moveMarker { + CLLocationCoordinate2D loc = CLLocationCoordinate2DMake([_exhibit[@"lat"] doubleValue], + [_exhibit[@"lng"] doubleValue]); + if (_marker == nil) { + _marker = [GMSMarker markerWithPosition:loc]; + _marker.map = _mapView; + } else { + _marker.position = loc; + } + _marker.title = _exhibit[@"name"]; + [_mapView animateToLocation:loc]; + [_mapView animateToZoom:19]; +} + +- (void)exhibitSelected:(UISegmentedControl *)segmentedControl { + _exhibit = _exhibits[[segmentedControl selectedSegmentIndex]]; + [self moveMarker]; +} + +#pragma mark - GMSMapViewDelegate + +- (void)mapView:(GMSMapView *)mapView idleAtCameraPosition:(GMSCameraPosition *)camera { + if (_exhibit != nil) { + CLLocationCoordinate2D loc = CLLocationCoordinate2DMake([_exhibit[@"lat"] doubleValue], + [_exhibit[@"lng"] doubleValue]); + if ([_mapView.projection containsCoordinate:loc] && _levels != nil) { + [mapView.indoorDisplay setActiveLevel:_levels[_exhibit[@"level"]]]; + } + } +} + +#pragma mark - GMSIndoorDisplayDelegate + +- (void)didChangeActiveBuilding:(GMSIndoorBuilding *)building { + if (building != nil) { + NSMutableDictionary *levels = [NSMutableDictionary dictionary]; + + for (GMSIndoorLevel *level in building.levels) { + [levels setObject:level forKey:level.shortName]; + } + + _levels = [NSDictionary dictionaryWithDictionary:levels]; + } else { + _levels = nil; + } +} + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/IndoorViewController.h a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/IndoorViewController.h new file mode 100755 index 0000000..d4f5a67 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/IndoorViewController.h @@ -0,0 +1,20 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 + +@interface IndoorViewController : UIViewController + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/IndoorViewController.m a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/IndoorViewController.m new file mode 100755 index 0000000..b97770d --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/IndoorViewController.m @@ -0,0 +1,94 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 "GoogleMapsDemos/Samples/IndoorViewController.h" + +#import + +static NSString *const kNormalType = @"Normal"; +static NSString *const kRetroType = @"Retro"; +static NSString *const kGrayscaleType = @"Grayscale"; +static NSString *const kNightType = @"Night"; + +@interface IndoorViewController () +@end + +@implementation IndoorViewController { + GMSMapView *_mapView; + UIBarButtonItem *_barButtonItem; + GMSMapStyle *_retroStyle; + GMSMapStyle *_grayscaleStyle; + GMSMapStyle *_nightStyle; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + NSURL *retroURL = [[NSBundle mainBundle] URLForResource:@"mapstyle-retro" withExtension:@"json"]; + _retroStyle = [GMSMapStyle styleWithContentsOfFileURL:retroURL error:NULL]; + + NSURL *grayscaleURL = [[NSBundle mainBundle] URLForResource:@"mapstyle-silver" + withExtension:@"json"]; + _grayscaleStyle = [GMSMapStyle styleWithContentsOfFileURL:grayscaleURL error:NULL]; + + NSURL *nightURL = [[NSBundle mainBundle] URLForResource:@"mapstyle-night" withExtension:@"json"]; + _nightStyle = [GMSMapStyle styleWithContentsOfFileURL:nightURL error:NULL]; + + GMSCameraPosition *camera = [GMSCameraPosition cameraWithLatitude:37.78318 + longitude:-122.403874 + zoom:18]; + + _mapView = [GMSMapView mapWithFrame:CGRectZero camera:camera]; + _mapView.settings.myLocationButton = YES; + + UIBarButtonItem *styleButton = [[UIBarButtonItem alloc] initWithTitle:@"Style" + style:UIBarButtonItemStylePlain + target:self + action:@selector(changeMapStyle:)]; + self.navigationItem.rightBarButtonItem = styleButton; + + self.view = _mapView; +} + +- (UIAlertAction *_Nonnull)actionWithTitle:(nonnull NSString *)title + style:(nullable GMSMapStyle *)style { + __weak __typeof__(self) weakSelf = self; + return [UIAlertAction actionWithTitle:title + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *_Nonnull action) { + __strong __typeof__(self) strongSelf = weakSelf; + if (strongSelf) { + strongSelf->_mapView.mapStyle = style; + } + }]; +} + +- (void)changeMapStyle:(UIBarButtonItem *)sender { + UIAlertController *alert = + [UIAlertController alertControllerWithTitle:@"Select map style" + message:nil + preferredStyle:UIAlertControllerStyleActionSheet]; + [alert addAction:[self actionWithTitle:kRetroType style:_retroStyle]]; + [alert addAction:[self actionWithTitle:kGrayscaleType style:_grayscaleStyle]]; + [alert addAction:[self actionWithTitle:kNightType style:_nightStyle]]; + [alert addAction:[self actionWithTitle:kNormalType style:nil]]; + [alert addAction:[UIAlertAction actionWithTitle:@"Cancel" + style:UIAlertActionStyleCancel + handler:nil]]; + alert.popoverPresentationController.barButtonItem = sender; + [self presentViewController:alert animated:YES completion:nil]; +} + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/MapLayerViewController.h a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/MapLayerViewController.h new file mode 100755 index 0000000..f2f9f6e --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/MapLayerViewController.h @@ -0,0 +1,20 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 + +@interface MapLayerViewController : UIViewController + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/MapLayerViewController.m a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/MapLayerViewController.m new file mode 100755 index 0000000..974a41c --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/MapLayerViewController.m @@ -0,0 +1,91 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 "GoogleMapsDemos/Samples/MapLayerViewController.h" + +#import + +@implementation MapLayerViewController { + GMSMapView *_mapView; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + GMSCameraPosition *camera = [GMSCameraPosition cameraWithLatitude:-37.81969 + longitude:144.966085 + zoom:4]; + _mapView = [GMSMapView mapWithFrame:CGRectZero camera:camera]; + self.view = _mapView; + + GMSMapView *mapView = _mapView; + dispatch_async(dispatch_get_main_queue(), ^{ + mapView.myLocationEnabled = YES; + }); + + UIBarButtonItem *myLocationButton = + [[UIBarButtonItem alloc] initWithTitle:@"Fly to My Location" + style:UIBarButtonItemStylePlain + target:self + action:@selector(didTapMyLocation)]; + self.navigationItem.rightBarButtonItem = myLocationButton; + +} + +- (void)didTapMyLocation { + CLLocation *location = _mapView.myLocation; + if (!location || !CLLocationCoordinate2DIsValid(location.coordinate)) { + return; + } + + _mapView.layer.cameraLatitude = location.coordinate.latitude; + _mapView.layer.cameraLongitude = location.coordinate.longitude; + _mapView.layer.cameraBearing = 0.0; + + // Access the GMSMapLayer directly to modify the following properties with a + // specified timing function and duration. + + CAMediaTimingFunction *curve = + [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; + CABasicAnimation *animation; + + animation = [CABasicAnimation animationWithKeyPath:kGMSLayerCameraLatitudeKey]; + animation.duration = 2.0f; + animation.timingFunction = curve; + animation.toValue = @(location.coordinate.latitude); + [_mapView.layer addAnimation:animation forKey:kGMSLayerCameraLatitudeKey]; + + animation = [CABasicAnimation animationWithKeyPath:kGMSLayerCameraLongitudeKey]; + animation.duration = 2.0f; + animation.timingFunction = curve; + animation.toValue = @(location.coordinate.longitude); + [_mapView.layer addAnimation:animation forKey:kGMSLayerCameraLongitudeKey]; + + animation = [CABasicAnimation animationWithKeyPath:kGMSLayerCameraBearingKey]; + animation.duration = 2.0f; + animation.timingFunction = curve; + animation.toValue = @0.0; + [_mapView.layer addAnimation:animation forKey:kGMSLayerCameraBearingKey]; + + // Fly out to the minimum zoom and then zoom back to the current zoom! + CGFloat zoom = _mapView.camera.zoom; + NSArray *keyValues = @[@(zoom), @(kGMSMinZoomLevel), @(zoom)]; + CAKeyframeAnimation *keyFrameAnimation = + [CAKeyframeAnimation animationWithKeyPath:kGMSLayerCameraZoomLevelKey]; + keyFrameAnimation.duration = 2.0f; + keyFrameAnimation.values = keyValues; + [_mapView.layer addAnimation:keyFrameAnimation forKey:kGMSLayerCameraZoomLevelKey]; +} + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/MapTypesViewController.h a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/MapTypesViewController.h new file mode 100755 index 0000000..c879adc --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/MapTypesViewController.h @@ -0,0 +1,20 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 + +@interface MapTypesViewController : UIViewController + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/MapTypesViewController.m a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/MapTypesViewController.m new file mode 100755 index 0000000..3bbd853 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/MapTypesViewController.m @@ -0,0 +1,71 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 "GoogleMapsDemos/Samples/MapTypesViewController.h" + +#import + +static NSString const * kNormalType = @"Normal"; +static NSString const * kSatelliteType = @"Satellite"; +static NSString const * kHybridType = @"Hybrid"; +static NSString const * kTerrainType = @"Terrain"; + +@implementation MapTypesViewController { + UISegmentedControl *_switcher; + GMSMapView *_mapView; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + GMSCameraPosition *camera = [GMSCameraPosition cameraWithLatitude:-33.868 + longitude:151.2086 + zoom:12]; + + _mapView = [GMSMapView mapWithFrame:CGRectZero camera:camera]; + self.view = _mapView; + + // The possible different types to show. + NSArray *types = @[kNormalType, kSatelliteType, kHybridType, kTerrainType]; + + // Create a UISegmentedControl that is the navigationItem's titleView. + _switcher = [[UISegmentedControl alloc] initWithItems:types]; + _switcher.autoresizingMask = UIViewAutoresizingFlexibleBottomMargin | + UIViewAutoresizingFlexibleWidth | + UIViewAutoresizingFlexibleBottomMargin; + _switcher.selectedSegmentIndex = 0; + self.navigationItem.titleView = _switcher; + + // Listen to touch events on the UISegmentedControl. + [_switcher addTarget:self action:@selector(didChangeSwitcher) + forControlEvents:UIControlEventValueChanged]; +} + +- (void)didChangeSwitcher { + // Switch to the type clicked on. + NSString *title = + [_switcher titleForSegmentAtIndex:_switcher.selectedSegmentIndex]; + if ([kNormalType isEqualToString:title]) { + _mapView.mapType = kGMSTypeNormal; + } else if ([kSatelliteType isEqualToString:title]) { + _mapView.mapType = kGMSTypeSatellite; + } else if ([kHybridType isEqualToString:title]) { + _mapView.mapType = kGMSTypeHybrid; + } else if ([kTerrainType isEqualToString:title]) { + _mapView.mapType = kGMSTypeTerrain; + } +} + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/MapZoomViewController.h a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/MapZoomViewController.h new file mode 100755 index 0000000..dadb9a1 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/MapZoomViewController.h @@ -0,0 +1,20 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 + +@interface MapZoomViewController : UIViewController + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/MapZoomViewController.m a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/MapZoomViewController.m new file mode 100755 index 0000000..4657d2f --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/MapZoomViewController.m @@ -0,0 +1,85 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 "GoogleMapsDemos/Samples/MapZoomViewController.h" + +#import + +@implementation MapZoomViewController { + GMSMapView *_mapView; + UITextView *_zoomRangeView; + NSUInteger _nextMode; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + GMSCameraPosition *camera = [GMSCameraPosition cameraWithLatitude:-33.868 + longitude:151.2086 + zoom:6]; + _mapView = [GMSMapView mapWithFrame:CGRectZero camera:camera]; + _mapView.settings.scrollGestures = NO; + self.view = _mapView; + + // Add a display for the current zoom range restriction. + _zoomRangeView = [[UITextView alloc] init]; + _zoomRangeView.frame = + CGRectMake(0, 0, CGRectGetWidth(self.view.frame), 0); + _zoomRangeView.text = @""; + _zoomRangeView.textAlignment = NSTextAlignmentCenter; + _zoomRangeView.backgroundColor = [UIColor colorWithWhite:1.0 alpha:0.8f]; + _zoomRangeView.autoresizingMask = UIViewAutoresizingFlexibleWidth; + _zoomRangeView.editable = NO; + [self.view addSubview:_zoomRangeView]; + [_zoomRangeView sizeToFit]; + [self didTapNext]; + + // Add a button toggling through modes. + self.navigationItem.rightBarButtonItem = + [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemPlay + target:self + action:@selector(didTapNext)]; +} + +- (void)didTapNext { + NSString *label = @""; + float minZoom = kGMSMinZoomLevel; + float maxZoom = kGMSMaxZoomLevel; + + switch (_nextMode) { + case 0: + label = @"Default"; + break; + case 1: + minZoom = 18; + label = @"Zoomed in"; + break; + case 2: + maxZoom = 8; + label = @"Zoomed out"; + break; + case 3: + minZoom = 10; + maxZoom = 11.5; + label = @"Small range"; + break; + } + _nextMode = (_nextMode + 1) % 4; + + [_mapView setMinZoom:minZoom maxZoom:maxZoom]; + _zoomRangeView.text = + [NSString stringWithFormat:@"%@ (%.2f - %.2f)", label, _mapView.minZoom, _mapView.maxZoom]; +} + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/MarkerEventsViewController.h a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/MarkerEventsViewController.h new file mode 100755 index 0000000..86ed130 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/MarkerEventsViewController.h @@ -0,0 +1,22 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 + +#import + +@interface MarkerEventsViewController : UIViewController + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/MarkerEventsViewController.m a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/MarkerEventsViewController.m new file mode 100755 index 0000000..31a304f --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/MarkerEventsViewController.m @@ -0,0 +1,81 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 "GoogleMapsDemos/Samples/MarkerEventsViewController.h" + +#import + +#import + +@implementation MarkerEventsViewController { + GMSMapView *_mapView; + GMSMarker *_melbourneMarker; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + GMSCameraPosition *camera = [GMSCameraPosition cameraWithLatitude:-37.81969 + longitude:144.966085 + zoom:4]; + _mapView = [GMSMapView mapWithFrame:CGRectZero camera:camera]; + + GMSMarker *sydneyMarker = [[GMSMarker alloc] init]; + sydneyMarker.position = CLLocationCoordinate2DMake(-33.8683, 151.2086); + sydneyMarker.map = _mapView; + + _melbourneMarker = [[GMSMarker alloc] init]; + _melbourneMarker.position = CLLocationCoordinate2DMake(-37.81969, 144.966085); + _melbourneMarker.map = _mapView; + + _mapView.delegate = self; + self.view = _mapView; +} + +#pragma mark - GMSMapViewDelegate + +- (UIView *)mapView:(GMSMapView *)mapView markerInfoWindow:(GMSMarker *)marker { + if (marker == _melbourneMarker) { + return [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"Icon"]]; + } + + return nil; +} + +- (BOOL)mapView:(GMSMapView *)mapView didTapMarker:(GMSMarker *)marker { + // Animate to the marker + [CATransaction begin]; + [CATransaction setAnimationDuration:3.f]; // 3 second animation + + GMSCameraPosition *camera = + [[GMSCameraPosition alloc] initWithTarget:marker.position + zoom:8 + bearing:50 + viewingAngle:60]; + [mapView animateToCameraPosition:camera]; + [CATransaction commit]; + + // Melbourne marker has a InfoWindow so return NO to allow markerInfoWindow to + // fire. Also check that the marker isn't already selected so that the + // InfoWindow doesn't close. + if (marker == _melbourneMarker && + mapView.selectedMarker != _melbourneMarker) { + return NO; + } + + // The Tap has been handled so return YES + return YES; +} + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/MarkerInfoWindowViewController.h a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/MarkerInfoWindowViewController.h new file mode 100755 index 0000000..299a9a2 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/MarkerInfoWindowViewController.h @@ -0,0 +1,20 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 + +@interface MarkerInfoWindowViewController : UIViewController + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/MarkerInfoWindowViewController.m a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/MarkerInfoWindowViewController.m new file mode 100755 index 0000000..14cc07a --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/MarkerInfoWindowViewController.m @@ -0,0 +1,97 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 "GoogleMapsDemos/Samples/MarkerInfoWindowViewController.h" + +#import "GoogleMapsDemos/UIViewController+GMSToastMessages.h" +#import + +@interface MarkerInfoWindowViewController () +@end + +@implementation MarkerInfoWindowViewController { + GMSMarker *_sydneyMarker; + GMSMarker *_melbourneMarker; + GMSMarker *_brisbaneMarker; + UIView *_contentView; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + GMSCameraPosition *camera = [GMSCameraPosition cameraWithLatitude:-37.81969 + longitude:144.966085 + zoom:4]; + GMSMapView *mapView = [GMSMapView mapWithFrame:CGRectZero camera:camera]; + + + _sydneyMarker = [[GMSMarker alloc] init]; + _sydneyMarker.title = @"Sydney"; + _sydneyMarker.snippet = @"Population: 4,605,992"; + _sydneyMarker.position = CLLocationCoordinate2DMake(-33.8683, 151.2086); + _sydneyMarker.map = mapView; + NSLog(@"sydneyMarker: %@", _sydneyMarker); + + + _melbourneMarker.map = nil; + _melbourneMarker = [[GMSMarker alloc] init]; + _melbourneMarker.title = @"Melbourne"; + _melbourneMarker.snippet = @"Population: 4,169,103"; + _melbourneMarker.position = CLLocationCoordinate2DMake(-37.81969, 144.966085); + _melbourneMarker.map = mapView; + NSLog(@"melbourneMarker: %@", _melbourneMarker); + + _brisbaneMarker.map = nil; + _brisbaneMarker = [[GMSMarker alloc] init]; + _brisbaneMarker.title = @"Brisbane"; + _brisbaneMarker.snippet = @"Population: 2,189,878"; + _brisbaneMarker.position = CLLocationCoordinate2DMake(-27.4710107, 153.0234489); + _brisbaneMarker.map = mapView; + NSLog(@"brisbaneMarker: %@", _brisbaneMarker); + + _contentView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"aeroplane"]]; + + mapView.delegate = self; + self.view = mapView; +} + +#pragma mark GMSMapViewDelegate + +- (UIView *)mapView:(GMSMapView *)mapView markerInfoWindow:(GMSMarker *)marker { + if (marker == _sydneyMarker) { + return _contentView; + } + return nil; +} + +- (UIView *)mapView:(GMSMapView *)mapView markerInfoContents:(GMSMarker *)marker { + if (marker == _brisbaneMarker) { + return _contentView; + } + return nil; +} + +- (void)mapView:(GMSMapView *)mapView didCloseInfoWindowOfMarker:(GMSMarker *)marker { + NSString *message = + [NSString stringWithFormat:@"Info window for marker %@ closed.", marker.title]; + [self gms_showToastWithMessage:message]; +} + +- (void)mapView:(GMSMapView *)mapView didLongPressInfoWindowOfMarker:(GMSMarker *)marker { + NSString *message = + [NSString stringWithFormat:@"Info window for marker %@ long pressed.", marker.title]; + [self gms_showToastWithMessage:message]; +} + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/MarkerLayerViewController.h a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/MarkerLayerViewController.h new file mode 100755 index 0000000..25b8088 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/MarkerLayerViewController.h @@ -0,0 +1,22 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 + +#import + +@interface MarkerLayerViewController : UIViewController + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/MarkerLayerViewController.m a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/MarkerLayerViewController.m new file mode 100755 index 0000000..39faf67 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/MarkerLayerViewController.m @@ -0,0 +1,148 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 "GoogleMapsDemos/Samples/MarkerLayerViewController.h" + +#import + +@interface CoordsList : NSObject +@property(nonatomic, readonly, copy) GMSPath *path; +@property(nonatomic, readonly) NSUInteger target; + +- (id)initWithPath:(GMSPath *)path; + +- (CLLocationCoordinate2D)next; + +@end + +@implementation CoordsList + +- (id)initWithPath:(GMSPath *)path { + if ((self = [super init])) { + _path = [path copy]; + _target = 0; + } + return self; +} + +- (CLLocationCoordinate2D)next { + ++_target; + if (_target == _path.count) { + _target = 0; + } + return [_path coordinateAtIndex:_target]; +} + +@end + +@implementation MarkerLayerViewController { + GMSMapView *_mapView; + GMSMarker *_fadedMarker; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + _mapView = [[GMSMapView alloc] init]; + _mapView.camera = [GMSCameraPosition cameraWithLatitude:50.6042 longitude:3.9599 zoom:5]; + _mapView.delegate = self; + self.view = _mapView; + + GMSMutablePath *coords; + GMSMarker *marker; + + // Create a plane that flies to several airports around western Europe. + coords = [GMSMutablePath path]; + [coords addLatitude:52.310683 longitude:4.765121]; + [coords addLatitude:51.471386 longitude:-0.457148]; + [coords addLatitude:49.01378 longitude:2.5542943]; + [coords addLatitude:50.036194 longitude:8.554519]; + marker = [GMSMarker markerWithPosition:[coords coordinateAtIndex:0]]; + marker.icon = [UIImage imageNamed:@"aeroplane"]; + marker.groundAnchor = CGPointMake(0.5f, 0.5f); + marker.flat = YES; + marker.map = _mapView; + marker.userData = [[CoordsList alloc] initWithPath:coords]; + [self animateToNextCoord:marker]; + + // Create a boat that moves around the Baltic Sea. + coords = [GMSMutablePath path]; + [coords addLatitude:57.598335 longitude:11.290512]; + [coords addLatitude:55.665193 longitude:10.741196]; + [coords addLatitude:55.065787 longitude:11.083488]; + [coords addLatitude:54.699234 longitude:10.863762]; + [coords addLatitude:54.482805 longitude:12.061272]; + [coords addLatitude:55.819802 longitude:16.148186]; // final point + [coords addLatitude:54.927142 longitude:16.455803]; // final point + [coords addLatitude:54.482805 longitude:12.061272]; // and back again + [coords addLatitude:54.699234 longitude:10.863762]; + [coords addLatitude:55.065787 longitude:11.083488]; + [coords addLatitude:55.665193 longitude:10.741196]; + marker = [GMSMarker markerWithPosition:[coords coordinateAtIndex:0]]; + marker.icon = [UIImage imageNamed:@"boat"]; + marker.map = _mapView; + marker.userData = [[CoordsList alloc] initWithPath:coords]; + [self animateToNextCoord:marker]; +} + +- (void)animateToNextCoord:(GMSMarker *)marker { + CoordsList *coords = marker.userData; + CLLocationCoordinate2D coord = [coords next]; + CLLocationCoordinate2D previous = marker.position; + + CLLocationDirection heading = GMSGeometryHeading(previous, coord); + CLLocationDistance distance = GMSGeometryDistance(previous, coord); + + // Use CATransaction to set a custom duration for this animation. By default, changes to the + // position are already animated, but with a very short default duration. When the animation is + // complete, trigger another animation step. + + [CATransaction begin]; + [CATransaction setAnimationDuration:(distance / (50 * 1000))]; // custom duration, 50km/sec + + __weak MarkerLayerViewController *weakSelf = self; + [CATransaction setCompletionBlock:^{ + [weakSelf animateToNextCoord:marker]; + }]; + + marker.position = coord; + + [CATransaction commit]; + + // If this marker is flat, implicitly trigger a change in rotation, which will finish quickly. + if (marker.flat) { + marker.rotation = heading; + } +} + +- (void)fadeMarker:(GMSMarker *)marker { + _fadedMarker.opacity = 1.0f; // reset previous faded marker + + // Fade this new marker. + _fadedMarker = marker; + _fadedMarker.opacity = 0.5f; +} + +#pragma mark - GMSMapViewDelegate + +- (BOOL)mapView:(GMSMapView *)mapView didTapMarker:(GMSMarker *)marker { + [self fadeMarker:marker]; + return YES; +} + +- (void)mapView:(GMSMapView *)mapView didTapAtCoordinate:(CLLocationCoordinate2D)coordinate { + [self fadeMarker:nil]; +} + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/MarkersViewController.h a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/MarkersViewController.h new file mode 100755 index 0000000..3cce920 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/MarkersViewController.h @@ -0,0 +1,20 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 + +@interface MarkersViewController : UIViewController + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/MarkersViewController.m a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/MarkersViewController.m new file mode 100755 index 0000000..ef19c48 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/MarkersViewController.m @@ -0,0 +1,74 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 "GoogleMapsDemos/Samples/MarkersViewController.h" + +#import + +@implementation MarkersViewController { + GMSMarker *_sydneyMarker; + GMSMarker *_melbourneMarker; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + GMSCameraPosition *camera = [GMSCameraPosition cameraWithLatitude:-37.81969 + longitude:144.966085 + zoom:4]; + GMSMapView *mapView = [GMSMapView mapWithFrame:CGRectZero camera:camera]; + + _sydneyMarker = [[GMSMarker alloc] init]; + _sydneyMarker.title = @"Sydney"; + _sydneyMarker.snippet = @"Population: 4,605,992"; + _sydneyMarker.position = CLLocationCoordinate2DMake(-33.8683, 151.2086); + _sydneyMarker.flat = NO; + _sydneyMarker.rotation = 30.0; + NSLog(@"sydneyMarker: %@", _sydneyMarker); + + GMSMarker *australiaMarker = [[GMSMarker alloc] init]; + australiaMarker.title = @"Australia"; + australiaMarker.position = CLLocationCoordinate2DMake(-27.994401,140.07019); + australiaMarker.appearAnimation = kGMSMarkerAnimationPop; + australiaMarker.flat = YES; + australiaMarker.draggable = YES; + australiaMarker.groundAnchor = CGPointMake(0.5, 0.5); + australiaMarker.icon = [UIImage imageNamed:@"australia"]; + australiaMarker.map = mapView; + + // Set the marker in Sydney to be selected + mapView.selectedMarker = _sydneyMarker; + + self.view = mapView; + self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(didTapAdd)]; +} + +- (void)didTapAdd { + if (_sydneyMarker.map == nil) { + _sydneyMarker.map = (GMSMapView *)self.view; +// _sydneyMarker.rotation += 45.0; + } else { + _sydneyMarker.map = nil; + } + + _melbourneMarker.map = nil; + _melbourneMarker = [[GMSMarker alloc] init]; + _melbourneMarker.title = @"Melbourne"; + _melbourneMarker.snippet = @"Population: 4,169,103"; + _melbourneMarker.position = CLLocationCoordinate2DMake(-37.81969, 144.966085); + _melbourneMarker.map = (GMSMapView *)self.view; +} + + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/MyLocationViewController.h a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/MyLocationViewController.h new file mode 100755 index 0000000..4a6a2c2 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/MyLocationViewController.h @@ -0,0 +1,22 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 + +#import + +@interface MyLocationViewController : UIViewController + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/MyLocationViewController.m a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/MyLocationViewController.m new file mode 100755 index 0000000..ae45e2d --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/MyLocationViewController.m @@ -0,0 +1,88 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 "GoogleMapsDemos/Samples/MyLocationViewController.h" + +#import + +@implementation MyLocationViewController { + GMSMapView *_mapView; + BOOL _firstLocationUpdate; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + GMSCameraPosition *camera = [GMSCameraPosition cameraWithLatitude:-33.868 + longitude:151.2086 + zoom:12]; + + _mapView = [GMSMapView mapWithFrame:CGRectZero camera:camera]; + _mapView.delegate = self; + _mapView.settings.compassButton = YES; + _mapView.settings.myLocationButton = YES; + + // Listen to the myLocation property of GMSMapView. + [_mapView addObserver:self + forKeyPath:@"myLocation" + options:NSKeyValueObservingOptionNew + context:NULL]; + + self.view = _mapView; + + // Ask for My Location data after the map has already been added to the UI. + GMSMapView *mapView = _mapView; + dispatch_async(dispatch_get_main_queue(), ^{ + mapView.myLocationEnabled = YES; + }); +} + +- (void)mapView:(GMSMapView *)mapView didTapMyLocation:(CLLocationCoordinate2D)location { + NSString *message = [NSString stringWithFormat:@"My Location Dot Tapped at: [lat: %f, lng: %f]", + location.latitude, location.longitude]; + UIAlertController *alertController = + [UIAlertController alertControllerWithTitle:@"Location Tapped" + message:message + preferredStyle:UIAlertControllerStyleAlert]; + UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *action){ + }]; + [alertController addAction:okAction]; + [self presentViewController:alertController animated:YES completion:nil]; +} + +- (void)dealloc { + [_mapView removeObserver:self + forKeyPath:@"myLocation" + context:NULL]; +} + +#pragma mark - KVO updates + +- (void)observeValueForKeyPath:(NSString *)keyPath + ofObject:(id)object + change:(NSDictionary *)change + context:(void *)context { + if (!_firstLocationUpdate) { + // If the first location update has not yet been received, then jump to that + // location. + _firstLocationUpdate = YES; + CLLocation *location = [change objectForKey:NSKeyValueChangeNewKey]; + _mapView.camera = [GMSCameraPosition cameraWithTarget:location.coordinate + zoom:14]; + } +} + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/PaddingBehaviorViewController.h a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/PaddingBehaviorViewController.h new file mode 100755 index 0000000..0b62aad --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/PaddingBehaviorViewController.h @@ -0,0 +1,20 @@ +/* + * Copyright 2017 Google LLC. All rights reserved. + * + * + * 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 + +@interface PaddingBehaviorViewController : UIViewController + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/PaddingBehaviorViewController.m a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/PaddingBehaviorViewController.m new file mode 100755 index 0000000..41f2c0e --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/PaddingBehaviorViewController.m @@ -0,0 +1,142 @@ +/* + * Copyright 2017 Google LLC. All rights reserved. + * + * + * 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 "GoogleMapsDemos/Samples/PaddingBehaviorViewController.h" + +#import + +static CLLocationCoordinate2D kPanoramaNear = {40.761388, -73.978133}; + +@interface PaddingBehaviorViewController () +@end + +@implementation PaddingBehaviorViewController { + GMSMapView *_mapView; + GMSPanoramaView *_panoramaView; + + UILabel *_statusLabel; + UIButton *_changeBehaviorButton; + UIButton *_toggleFrameButton; + UIBarButtonItem *_toggleViewButton; + + BOOL _hasShrunk; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + GMSCameraPosition *camera = + [GMSCameraPosition cameraWithLatitude:-33.868 longitude:151.2086 zoom:6]; + _mapView = [GMSMapView mapWithFrame:self.view.bounds camera:camera]; + _mapView.padding = UIEdgeInsetsMake(0, 20, 40, 60); + _mapView.delegate = self; + _mapView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + [self.view addSubview:_mapView]; + + // Add status label. + _statusLabel = [[UILabel alloc] initWithFrame:CGRectMake(30, 0, 0, 30)]; + _statusLabel.textColor = [UIColor brownColor]; + _statusLabel.textAlignment = NSTextAlignmentLeft; + _statusLabel.text = @"Behavior: Always"; + [_statusLabel sizeToFit]; + + // Add behavior modifier button. + _changeBehaviorButton = [UIButton buttonWithType:UIButtonTypeSystem]; + _changeBehaviorButton.frame = CGRectMake(30, 30, 0, 0); + [_changeBehaviorButton setTitle:@"Next Behavior" forState:UIControlStateNormal]; + [_changeBehaviorButton sizeToFit]; + [_changeBehaviorButton addTarget:self + action:@selector(nextBehavior) + forControlEvents:UIControlEventTouchUpInside]; + + // Add frame animation button. + _toggleViewButton = [[UIBarButtonItem alloc] initWithTitle:@"Toggle View" + style:UIBarButtonItemStylePlain + target:self + action:@selector(toggleViewType)]; + self.navigationItem.rightBarButtonItem = _toggleViewButton; + + // Add change view type button. + _toggleFrameButton = [UIButton buttonWithType:UIButtonTypeSystem]; + _toggleFrameButton.frame = CGRectMake(30, 60, 0, 0); + [_toggleFrameButton setTitle:@"Animate Frame" forState:UIControlStateNormal]; + [_toggleFrameButton sizeToFit]; + [_toggleFrameButton addTarget:self + action:@selector(toggleFrame) + forControlEvents:UIControlEventTouchUpInside]; + + [_mapView addSubview:_statusLabel]; + [_mapView addSubview:_changeBehaviorButton]; + [_mapView addSubview:_toggleFrameButton]; + + _hasShrunk = NO; + + // Pre-load PanoramaView + _panoramaView = [GMSPanoramaView panoramaWithFrame:self.view.bounds nearCoordinate:kPanoramaNear]; + _panoramaView.autoresizingMask = + UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; +} + +#pragma mark - Button Click Handlers + +- (void)toggleFrame { + CGSize size = self.view.bounds.size; + CGPoint point = self.view.bounds.origin; + [UIView animateWithDuration:2.0 + animations:^{ + if (self->_hasShrunk) { + self->_mapView.frame = self.view.bounds; + self->_panoramaView.frame = self->_mapView.frame; + } else { + self->_mapView.frame = + CGRectMake(point.x, point.y, size.width / 2, size.height / 2); + self->_panoramaView.frame = self->_mapView.frame; + } + self->_hasShrunk = !self->_hasShrunk; + [self.view setNeedsLayout]; + [self.view layoutIfNeeded]; + }]; +} + +- (void)toggleViewType { + if ([self.view.subviews containsObject:_mapView]) { + [_mapView removeFromSuperview]; + [self.view addSubview:_panoramaView]; + [_panoramaView addSubview:_toggleFrameButton]; + } else { + [_panoramaView removeFromSuperview]; + [self.view addSubview:_mapView]; + [_mapView addSubview:_toggleFrameButton]; + } + +} + +- (void)nextBehavior { + switch (_mapView.paddingAdjustmentBehavior) { + case kGMSMapViewPaddingAdjustmentBehaviorAlways: + _mapView.paddingAdjustmentBehavior = kGMSMapViewPaddingAdjustmentBehaviorAutomatic; + _statusLabel.text = @"Behavior: Automatic"; + break; + case kGMSMapViewPaddingAdjustmentBehaviorAutomatic: + _mapView.paddingAdjustmentBehavior = kGMSMapViewPaddingAdjustmentBehaviorNever; + _statusLabel.text = @"Behavior: Never"; + break; + case kGMSMapViewPaddingAdjustmentBehaviorNever: + _mapView.paddingAdjustmentBehavior = kGMSMapViewPaddingAdjustmentBehaviorAlways; + _statusLabel.text = @"Behavior: Always"; + break; + } +} + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/PanoramaViewController.h a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/PanoramaViewController.h new file mode 100755 index 0000000..1dfd285 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/PanoramaViewController.h @@ -0,0 +1,20 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 + +@interface PanoramaViewController : UIViewController + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/PanoramaViewController.m a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/PanoramaViewController.m new file mode 100755 index 0000000..eac9a2f --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/PanoramaViewController.m @@ -0,0 +1,84 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 "GoogleMapsDemos/Samples/PanoramaViewController.h" + +#import + +static CLLocationCoordinate2D kPanoramaNear = {40.761388, -73.978133}; +static CLLocationCoordinate2D kMarkerAt = {40.761455, -73.977814}; + +@interface PanoramaViewController () +@end + +@implementation PanoramaViewController { + GMSPanoramaView *_view; + BOOL _configured; + UILabel *_statusLabel; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + _view = [GMSPanoramaView panoramaWithFrame:CGRectZero + nearCoordinate:kPanoramaNear]; + _view.backgroundColor = [UIColor grayColor]; + _view.delegate = self; + self.view = _view; + + // Add status label, initially hidden. + _statusLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 0, 30)]; + _statusLabel.alpha = 0.0f; + _statusLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth; + _statusLabel.backgroundColor = [UIColor blueColor]; + _statusLabel.textColor = [UIColor whiteColor]; + _statusLabel.textAlignment = NSTextAlignmentCenter; + + [_view addSubview:_statusLabel]; +} + +#pragma mark - GMSPanoramaDelegate + +- (void)panoramaView:(GMSPanoramaView *)panoramaView + didMoveCamera:(GMSPanoramaCamera *)camera { + NSLog(@"Camera: (%f,%f,%f)", + camera.orientation.heading, camera.orientation.pitch, camera.zoom); +} + +- (void)panoramaView:(GMSPanoramaView *)view + didMoveToPanorama:(GMSPanorama *)panorama { + if (!_configured) { + GMSMarker *marker = [GMSMarker markerWithPosition:kMarkerAt]; + marker.icon = [GMSMarker markerImageWithColor:[UIColor purpleColor]]; + marker.panoramaView = _view; + + CLLocationDegrees heading = GMSGeometryHeading(kPanoramaNear, kMarkerAt); + _view.camera = + [GMSPanoramaCamera cameraWithHeading:heading pitch:0 zoom:1]; + + _configured = YES; + } +} + +- (void)panoramaViewDidStartRendering:(GMSPanoramaView *)panoramaView { + _statusLabel.alpha = 0.8f; + _statusLabel.text = @"Rendering"; +} + +- (void)panoramaViewDidFinishRendering:(GMSPanoramaView *)panoramaView { + _statusLabel.alpha = 0.0f; +} + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/PolygonsViewController.h a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/PolygonsViewController.h new file mode 100755 index 0000000..efdc2bd --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/PolygonsViewController.h @@ -0,0 +1,22 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 + +#import + +@interface PolygonsViewController : UIViewController + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/PolygonsViewController.m a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/PolygonsViewController.m new file mode 100755 index 0000000..e3df3d8 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/PolygonsViewController.m @@ -0,0 +1,274 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 "GoogleMapsDemos/Samples/PolygonsViewController.h" + +#import + +@implementation PolygonsViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + GMSCameraPosition *camera = [GMSCameraPosition cameraWithLatitude:39.13006 + longitude:-77.508545 + zoom:4]; + GMSMapView *mapView = [GMSMapView mapWithFrame:CGRectZero camera:camera]; + mapView.delegate = self; // needed for didTapOverlay delegate method + + self.view = mapView; +} + +- (void)viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; + + // Create renderer related objects after view appears, so a renderer will be available; + // otherwise, behavior is undefined (may result in null ptr derefs). + GMSMapView *mapView = (GMSMapView *)self.view; + + // Create the first polygon. + GMSPolygon *polygon = [[GMSPolygon alloc] init]; + polygon.path = [self pathOfNewYorkState]; + polygon.holes = @[ [self pathOfNewYorkStateHole] ]; + polygon.title = @"New York"; + polygon.fillColor = [UIColor colorWithRed:0.25 green:0 blue:0 alpha:0.2f]; + polygon.strokeColor = [UIColor blackColor]; + polygon.strokeWidth = 2; + polygon.tappable = YES; + polygon.map = mapView; + + // Copy the existing polygon and its settings and use it as a base for the + // second polygon. + polygon = [polygon copy]; + polygon.title = @"North Carolina"; + polygon.path = [self pathOfNorthCarolina]; + polygon.fillColor = [UIColor colorWithRed:0 green:0.25 blue:0 alpha:0.5]; + polygon.map = mapView; +} + +- (void)mapView:(GMSMapView *)mapView didTapOverlay:(GMSOverlay *)overlay { + // When a polygon is tapped, randomly change its fill color to a new hue. + if ([overlay isKindOfClass:[GMSPolygon class]]) { + GMSPolygon *polygon = (GMSPolygon *)overlay; + CGFloat hue = (((float)arc4random()/0x100000000)*1.0f); + polygon.fillColor = + [UIColor colorWithHue:hue saturation:1 brightness:1 alpha:0.5]; + } +} + +- (GMSPath *)pathOfNewYorkState { + GMSMutablePath *path = [GMSMutablePath path]; + [path addLatitude:42.5142 longitude:-79.7624]; + [path addLatitude:42.7783 longitude:-79.0672]; + [path addLatitude:42.8508 longitude:-78.9313]; + [path addLatitude:42.9061 longitude:-78.9024]; + [path addLatitude:42.9554 longitude:-78.9313]; + [path addLatitude:42.9584 longitude:-78.9656]; + [path addLatitude:42.9886 longitude:-79.0219]; + [path addLatitude:43.0568 longitude:-79.0027]; + [path addLatitude:43.0769 longitude:-79.0727]; + [path addLatitude:43.1220 longitude:-79.0713]; + [path addLatitude:43.1441 longitude:-79.0302]; + [path addLatitude:43.1801 longitude:-79.0576]; + [path addLatitude:43.2482 longitude:-79.0604]; + [path addLatitude:43.2812 longitude:-79.0837]; + [path addLatitude:43.4509 longitude:-79.2004]; + [path addLatitude:43.6311 longitude:-78.6909]; + [path addLatitude:43.6321 longitude:-76.7958]; + [path addLatitude:43.9987 longitude:-76.4978]; + [path addLatitude:44.0965 longitude:-76.4388]; + [path addLatitude:44.1349 longitude:-76.3536]; + [path addLatitude:44.1989 longitude:-76.3124]; + [path addLatitude:44.2049 longitude:-76.2437]; + [path addLatitude:44.2413 longitude:-76.1655]; + [path addLatitude:44.2973 longitude:-76.1353]; + [path addLatitude:44.3327 longitude:-76.0474]; + [path addLatitude:44.3553 longitude:-75.9856]; + [path addLatitude:44.3749 longitude:-75.9196]; + [path addLatitude:44.3994 longitude:-75.8730]; + [path addLatitude:44.4308 longitude:-75.8221]; + [path addLatitude:44.4740 longitude:-75.8098]; + [path addLatitude:44.5425 longitude:-75.7288]; + [path addLatitude:44.6647 longitude:-75.5585]; + [path addLatitude:44.7672 longitude:-75.4088]; + [path addLatitude:44.8101 longitude:-75.3442]; + [path addLatitude:44.8383 longitude:-75.3058]; + [path addLatitude:44.8676 longitude:-75.2399]; + [path addLatitude:44.9211 longitude:-75.1204]; + [path addLatitude:44.9609 longitude:-74.9995]; + [path addLatitude:44.9803 longitude:-74.9899]; + [path addLatitude:44.9852 longitude:-74.9103]; + [path addLatitude:45.0017 longitude:-74.8856]; + [path addLatitude:45.0153 longitude:-74.8306]; + [path addLatitude:45.0046 longitude:-74.7633]; + [path addLatitude:45.0027 longitude:-74.7070]; + [path addLatitude:45.0007 longitude:-74.5642]; + [path addLatitude:44.9920 longitude:-74.1467]; + [path addLatitude:45.0037 longitude:-73.7306]; + [path addLatitude:45.0085 longitude:-73.4203]; + [path addLatitude:45.0109 longitude:-73.3430]; + [path addLatitude:44.9874 longitude:-73.3547]; + [path addLatitude:44.9648 longitude:-73.3379]; + [path addLatitude:44.9160 longitude:-73.3396]; + [path addLatitude:44.8354 longitude:-73.3739]; + [path addLatitude:44.8013 longitude:-73.3324]; + [path addLatitude:44.7419 longitude:-73.3667]; + [path addLatitude:44.6139 longitude:-73.3873]; + [path addLatitude:44.5787 longitude:-73.3736]; + [path addLatitude:44.4916 longitude:-73.3049]; + [path addLatitude:44.4289 longitude:-73.2953]; + [path addLatitude:44.3513 longitude:-73.3365]; + [path addLatitude:44.2757 longitude:-73.3118]; + [path addLatitude:44.1980 longitude:-73.3818]; + [path addLatitude:44.1142 longitude:-73.4079]; + [path addLatitude:44.0511 longitude:-73.4367]; + [path addLatitude:44.0165 longitude:-73.4065]; + [path addLatitude:43.9375 longitude:-73.4079]; + [path addLatitude:43.8771 longitude:-73.3749]; + [path addLatitude:43.8167 longitude:-73.3914]; + [path addLatitude:43.7790 longitude:-73.3557]; + [path addLatitude:43.6460 longitude:-73.4244]; + [path addLatitude:43.5893 longitude:-73.4340]; + [path addLatitude:43.5655 longitude:-73.3969]; + [path addLatitude:43.6112 longitude:-73.3818]; + [path addLatitude:43.6271 longitude:-73.3049]; + [path addLatitude:43.5764 longitude:-73.3063]; + [path addLatitude:43.5675 longitude:-73.2582]; + [path addLatitude:43.5227 longitude:-73.2445]; + [path addLatitude:43.2582 longitude:-73.2582]; + [path addLatitude:42.9715 longitude:-73.2733]; + [path addLatitude:42.8004 longitude:-73.2898]; + [path addLatitude:42.7460 longitude:-73.2664]; + [path addLatitude:42.4630 longitude:-73.3708]; + [path addLatitude:42.0840 longitude:-73.5095]; + [path addLatitude:42.0218 longitude:-73.4903]; + [path addLatitude:41.8808 longitude:-73.4999]; + [path addLatitude:41.2953 longitude:-73.5535]; + [path addLatitude:41.2128 longitude:-73.4834]; + [path addLatitude:41.1011 longitude:-73.7275]; + [path addLatitude:41.0237 longitude:-73.6644]; + [path addLatitude:40.9851 longitude:-73.6578]; + [path addLatitude:40.9509 longitude:-73.6132]; + [path addLatitude:41.1869 longitude:-72.4823]; + [path addLatitude:41.2551 longitude:-72.0950]; + [path addLatitude:41.3005 longitude:-71.9714]; + [path addLatitude:41.3108 longitude:-71.9193]; + [path addLatitude:41.1838 longitude:-71.7915]; + [path addLatitude:41.1249 longitude:-71.7929]; + [path addLatitude:41.0462 longitude:-71.7517]; + [path addLatitude:40.6306 longitude:-72.9465]; + [path addLatitude:40.5368 longitude:-73.4628]; + [path addLatitude:40.4887 longitude:-73.8885]; + [path addLatitude:40.5232 longitude:-73.9490]; + [path addLatitude:40.4772 longitude:-74.2271]; + [path addLatitude:40.4861 longitude:-74.2532]; + [path addLatitude:40.6468 longitude:-74.1866]; + [path addLatitude:40.6556 longitude:-74.0547]; + [path addLatitude:40.7618 longitude:-74.0156]; + [path addLatitude:40.8699 longitude:-73.9421]; + [path addLatitude:40.9980 longitude:-73.8934]; + [path addLatitude:41.0343 longitude:-73.9854]; + [path addLatitude:41.3268 longitude:-74.6274]; + [path addLatitude:41.3583 longitude:-74.7084]; + [path addLatitude:41.3811 longitude:-74.7101]; + [path addLatitude:41.4386 longitude:-74.8265]; + [path addLatitude:41.5075 longitude:-74.9913]; + [path addLatitude:41.6000 longitude:-75.0668]; + [path addLatitude:41.6719 longitude:-75.0366]; + [path addLatitude:41.7672 longitude:-75.0545]; + [path addLatitude:41.8808 longitude:-75.1945]; + [path addLatitude:42.0013 longitude:-75.3552]; + [path addLatitude:42.0003 longitude:-75.4266]; + [path addLatitude:42.0013 longitude:-77.0306]; + [path addLatitude:41.9993 longitude:-79.7250]; + [path addLatitude:42.0003 longitude:-79.7621]; + [path addLatitude:42.1827 longitude:-79.7621]; + [path addLatitude:42.5146 longitude:-79.7621]; + return path; +} + +- (GMSPath *)pathOfNewYorkStateHole { + GMSMutablePath *path = [GMSMutablePath path]; + [path addLatitude:43.5000 longitude:-76.3651]; + [path addLatitude:43.5000 longitude:-74.3651]; + [path addLatitude:42.0000 longitude:-74.3651]; + return path; +} + +- (GMSPath *)pathOfNorthCarolina { + GMSMutablePath *path = [GMSMutablePath path]; + [path addLatitude:33.7963 longitude:-78.4850]; + [path addLatitude:34.8037 longitude:-79.6742]; + [path addLatitude:34.8206 longitude:-80.8003]; + [path addLatitude:34.9377 longitude:-80.7880]; + [path addLatitude:35.1019 longitude:-80.9377]; + [path addLatitude:35.0356 longitude:-81.0379]; + [path addLatitude:35.1457 longitude:-81.0324]; + [path addLatitude:35.1660 longitude:-81.3867]; + [path addLatitude:35.1985 longitude:-82.2739]; + [path addLatitude:35.2041 longitude:-82.3933]; + [path addLatitude:35.0637 longitude:-82.7765]; + [path addLatitude:35.0817 longitude:-82.7861]; + [path addLatitude:34.9996 longitude:-83.1075]; + [path addLatitude:34.9918 longitude:-83.6183]; + [path addLatitude:34.9918 longitude:-84.3201]; + [path addLatitude:35.2131 longitude:-84.2885]; + [path addLatitude:35.2680 longitude:-84.2226]; + [path addLatitude:35.2310 longitude:-84.1113]; + [path addLatitude:35.2815 longitude:-84.0454]; + [path addLatitude:35.4058 longitude:-84.0248]; + [path addLatitude:35.4719 longitude:-83.9424]; + [path addLatitude:35.5166 longitude:-83.8559]; + [path addLatitude:35.5512 longitude:-83.6938]; + [path addLatitude:35.5680 longitude:-83.5181]; + [path addLatitude:35.6327 longitude:-83.3849]; + [path addLatitude:35.7142 longitude:-83.2475]; + [path addLatitude:35.7799 longitude:-82.9962]; + [path addLatitude:35.8445 longitude:-82.9276]; + [path addLatitude:35.9224 longitude:-82.8191]; + [path addLatitude:35.9958 longitude:-82.7710]; + [path addLatitude:36.0613 longitude:-82.6419]; + [path addLatitude:35.9702 longitude:-82.6103]; + [path addLatitude:35.9547 longitude:-82.5677]; + [path addLatitude:36.0236 longitude:-82.4730]; + [path addLatitude:36.0669 longitude:-82.4194]; + [path addLatitude:36.1168 longitude:-82.3535]; + [path addLatitude:36.1345 longitude:-82.2862]; + [path addLatitude:36.1467 longitude:-82.1461]; + [path addLatitude:36.1035 longitude:-82.1228]; + [path addLatitude:36.1268 longitude:-82.0267]; + [path addLatitude:36.2797 longitude:-81.9360]; + [path addLatitude:36.3527 longitude:-81.7987]; + [path addLatitude:36.3361 longitude:-81.7081]; + [path addLatitude:36.5880 longitude:-81.6724]; + [path addLatitude:36.5659 longitude:-80.7234]; + [path addLatitude:36.5438 longitude:-80.2977]; + [path addLatitude:36.5449 longitude:-79.6729]; + [path addLatitude:36.5449 longitude:-77.2559]; + [path addLatitude:36.5505 longitude:-75.7562]; + [path addLatitude:36.3129 longitude:-75.7068]; + [path addLatitude:35.7131 longitude:-75.4129]; + [path addLatitude:35.2041 longitude:-75.4720]; + [path addLatitude:34.9794 longitude:-76.0748]; + [path addLatitude:34.5258 longitude:-76.4951]; + [path addLatitude:34.5880 longitude:-76.8109]; + [path addLatitude:34.5314 longitude:-77.1378]; + [path addLatitude:34.3910 longitude:-77.4481]; + [path addLatitude:34.0481 longitude:-77.7983]; + [path addLatitude:33.7666 longitude:-77.9260]; + [path addLatitude:33.7963 longitude:-78.4863]; + return path; +} + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/PolylinesViewController.h a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/PolylinesViewController.h new file mode 100755 index 0000000..6cb22f5 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/PolylinesViewController.h @@ -0,0 +1,20 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 + +@interface PolylinesViewController : UIViewController + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/PolylinesViewController.m a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/PolylinesViewController.m new file mode 100755 index 0000000..c75086f --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/PolylinesViewController.m @@ -0,0 +1,115 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 "GoogleMapsDemos/Samples/PolylinesViewController.h" + +#import + +@interface GMSPolyline (length) + +@property(nonatomic, readonly) double length; + +@end + +@implementation GMSPolyline (length) + +- (double)length { + GMSLengthKind kind = [self geodesic] ? kGMSLengthGeodesic : kGMSLengthRhumb; + return [[self path] lengthOfKind:kind]; +} + +@end + +static CLLocationCoordinate2D kSydneyAustralia = {-33.866901, 151.195988}; +static CLLocationCoordinate2D kHawaiiUSA = {21.291982, -157.821856}; +static CLLocationCoordinate2D kFiji = {-18, 179}; +static CLLocationCoordinate2D kMountainViewUSA = {37.423802, -122.091859}; +static CLLocationCoordinate2D kLimaPeru = {-12, -77}; +static bool kAnimate = true; + +@implementation PolylinesViewController { + NSArray *_styles; + NSArray *_lengths; + NSArray *_polys; + double _pos, _step; + GMSMapView *_mapView; +} + +- (void)tick { + for (GMSPolyline *poly in _polys) { + poly.spans = + GMSStyleSpansOffset(poly.path, _styles, _lengths, kGMSLengthGeodesic, _pos); + } + _pos -= _step; + if (kAnimate) { + __weak id weakSelf = self; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC / 10), + dispatch_get_main_queue(), + ^{ [weakSelf tick]; }); + } +} + +- (void)initLines { + if (!_polys) { + NSMutableArray *polys = [NSMutableArray array]; + GMSMutablePath *path = [GMSMutablePath path]; + [path addCoordinate:kSydneyAustralia]; + [path addCoordinate:kFiji]; + [path addCoordinate:kHawaiiUSA]; + [path addCoordinate:kMountainViewUSA]; + [path addCoordinate:kLimaPeru]; + [path addCoordinate:kSydneyAustralia]; + path = [path pathOffsetByLatitude:-30 longitude:0]; + _lengths = @[@([path lengthOfKind:kGMSLengthGeodesic] / 21)]; + for (int i = 0; i < 30; ++i) { + GMSPolyline *poly = [[GMSPolyline alloc] init]; + poly.path = [path pathOffsetByLatitude:(i * 1.5) longitude:0]; + poly.strokeWidth = 8; + poly.geodesic = YES; + poly.map = _mapView; + [polys addObject:poly]; + } + _polys = polys; + } +} + +- (void)viewDidLoad { + [super viewDidLoad]; + GMSCameraPosition *camera = [GMSCameraPosition cameraWithLatitude:-30 + longitude:-175 + zoom:3]; + GMSMapView *mapView = [GMSMapView mapWithFrame:CGRectZero camera:camera]; + mapView.accessibilityElementsHidden = YES; + self.view = mapView; + _mapView = mapView; + + CGFloat alpha = 1; + UIColor *green = [UIColor colorWithRed:0 green:1 blue: 0 alpha:alpha]; + UIColor *greenTransp = [UIColor colorWithRed:0 green:1 blue: 0 alpha:0]; + UIColor *red = [UIColor colorWithRed:1 green:0 blue: 0 alpha:alpha]; + UIColor *redTransp = [UIColor colorWithRed:1 green:0 blue: 0 alpha:0]; + GMSStrokeStyle *grad1 = [GMSStrokeStyle gradientFromColor:green toColor:greenTransp]; + GMSStrokeStyle *grad2 = [GMSStrokeStyle gradientFromColor:redTransp toColor:red]; + _styles = @[ + grad1, + grad2, + [GMSStrokeStyle solidColor:[UIColor colorWithWhite:0 alpha:0]], + ]; + _step = 50000; + [self initLines]; + [self tick]; +} + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/Samples.h a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/Samples.h new file mode 100755 index 0000000..5c2d54d --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/Samples.h @@ -0,0 +1,24 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 + +@interface Samples : NSObject ++ (NSArray *)loadSections; ++ (NSArray *)loadDemos; ++ (NSDictionary *)newDemo:(Class) class + withTitle:(NSString *)title + andDescription:(NSString *)description; +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/Samples.m a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/Samples.m new file mode 100755 index 0000000..cebd46a --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/Samples.m @@ -0,0 +1,183 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 "GoogleMapsDemos/Samples/Samples.h" + +#import "GoogleMapsDemos/Samples/AnimatedCurrentLocationViewController.h" +#import "GoogleMapsDemos/Samples/AnimatedUIViewMarkerViewController.h" +#import "GoogleMapsDemos/Samples/BasicMapViewController.h" +#import "GoogleMapsDemos/Samples/CameraViewController.h" +#import "GoogleMapsDemos/Samples/CustomIndoorViewController.h" +#import "GoogleMapsDemos/Samples/CustomMarkersViewController.h" +#import "GoogleMapsDemos/Samples/DoubleMapViewController.h" +#import "GoogleMapsDemos/Samples/FitBoundsViewController.h" +#import "GoogleMapsDemos/Samples/FixedPanoramaViewController.h" +#import "GoogleMapsDemos/Samples/FrameRateViewController.h" +#import "GoogleMapsDemos/Samples/GeocoderViewController.h" +#import "GoogleMapsDemos/Samples/GestureControlViewController.h" +#import "GoogleMapsDemos/Samples/GradientPolylinesViewController.h" +#import "GoogleMapsDemos/Samples/GroundOverlayViewController.h" +#import "GoogleMapsDemos/Samples/IndoorMuseumNavigationViewController.h" +#import "GoogleMapsDemos/Samples/IndoorViewController.h" +#import "GoogleMapsDemos/Samples/MapLayerViewController.h" +#import "GoogleMapsDemos/Samples/MapTypesViewController.h" +#import "GoogleMapsDemos/Samples/MapZoomViewController.h" +#import "GoogleMapsDemos/Samples/MarkerEventsViewController.h" +#import "GoogleMapsDemos/Samples/MarkerInfoWindowViewController.h" +#import "GoogleMapsDemos/Samples/MarkerLayerViewController.h" +#import "GoogleMapsDemos/Samples/MarkersViewController.h" +#import "GoogleMapsDemos/Samples/MyLocationViewController.h" +#import "GoogleMapsDemos/Samples/PaddingBehaviorViewController.h" +#import "GoogleMapsDemos/Samples/PanoramaViewController.h" +#import "GoogleMapsDemos/Samples/PolygonsViewController.h" +#import "GoogleMapsDemos/Samples/PolylinesViewController.h" +#import "GoogleMapsDemos/Samples/SnapshotReadyViewController.h" +#import "GoogleMapsDemos/Samples/StructuredGeocoderViewController.h" +#import "GoogleMapsDemos/Samples/StyledMapViewController.h" +#import "GoogleMapsDemos/Samples/TileLayerViewController.h" +#import "GoogleMapsDemos/Samples/TrafficMapViewController.h" +#import "GoogleMapsDemos/Samples/VisibleRegionViewController.h" + +@implementation Samples + ++ (NSArray *)loadSections { + return @[ @"Map", @"Panorama", @"Overlays", @"Camera", @"Services" ]; +} + ++ (NSArray *)loadDemos { + NSArray *mapDemos = + @[[self newDemo:[BasicMapViewController class] + withTitle:@"Basic Map" + andDescription:nil], + [self newDemo:[MapTypesViewController class] + withTitle:@"Map Types" + andDescription:nil], + [self newDemo:[StyledMapViewController class] + withTitle:@"Styled Map" + andDescription:nil], + [self newDemo:[TrafficMapViewController class] + withTitle:@"Traffic Layer" + andDescription:nil], + [self newDemo:[MyLocationViewController class] + withTitle:@"My Location" + andDescription:nil], + [self newDemo:[IndoorViewController class] + withTitle:@"Indoor" + andDescription:nil], + [self newDemo:[CustomIndoorViewController class] + withTitle:@"Indoor with Custom Level Select" + andDescription:nil], + [self newDemo:[IndoorMuseumNavigationViewController class] + withTitle:@"Indoor Museum Navigator" + andDescription:nil], + [self newDemo:[GestureControlViewController class] + withTitle:@"Gesture Control" + andDescription:nil], + [self newDemo:[SnapshotReadyViewController class] + withTitle:@"Snapshot Ready" + andDescription:nil], + [self newDemo:[DoubleMapViewController class] + withTitle:@"Two Maps" + andDescription:nil], + [self newDemo:[VisibleRegionViewController class] + withTitle:@"Visible Regions" + andDescription:nil], + [self newDemo:[MapZoomViewController class] + withTitle:@"Min/Max Zoom" + andDescription:nil], + [self newDemo:[FrameRateViewController class] + withTitle:@"Frame Rate" + andDescription:nil], + [self newDemo:[PaddingBehaviorViewController class] + withTitle:@"Padding Behavior" + andDescription:nil], + ]; + + NSArray *panoramaDemos = + @[[self newDemo:[PanoramaViewController class] + withTitle:@"Street View" + andDescription:nil], + [self newDemo:[FixedPanoramaViewController class] + withTitle:@"Fixed Street View" + andDescription:nil]]; + + NSArray *overlayDemos = + @[[self newDemo:[MarkersViewController class] + withTitle:@"Markers" + andDescription:nil], + [self newDemo:[CustomMarkersViewController class] + withTitle:@"Custom Markers" + andDescription:nil], + [self newDemo:[AnimatedUIViewMarkerViewController class] + withTitle:@"UIView Markers" + andDescription:nil], + [self newDemo:[MarkerEventsViewController class] + withTitle:@"Marker Events" + andDescription:nil], + [self newDemo:[MarkerLayerViewController class] + withTitle:@"Marker Layer" + andDescription:nil], + [self newDemo:[MarkerInfoWindowViewController class] + withTitle:@"Custom Info Windows" + andDescription:nil], + [self newDemo:[PolygonsViewController class] + withTitle:@"Polygons" + andDescription:nil], + [self newDemo:[PolylinesViewController class] + withTitle:@"Polylines" + andDescription:nil], + [self newDemo:[GroundOverlayViewController class] + withTitle:@"Ground Overlays" + andDescription:nil], + [self newDemo:[TileLayerViewController class] + withTitle:@"Tile Layers" + andDescription:nil], + [self newDemo:[AnimatedCurrentLocationViewController class] + withTitle:@"Animated Current Location" + andDescription:nil], + [self newDemo:[GradientPolylinesViewController class] + withTitle:@"Gradient Polylines" + andDescription:nil]]; + + NSArray *cameraDemos = + @[[self newDemo:[FitBoundsViewController class] + withTitle:@"Fit Bounds" + andDescription:nil], + [self newDemo:[CameraViewController class] + withTitle:@"Camera Animation" + andDescription:nil], + [self newDemo:[MapLayerViewController class] + withTitle:@"Map Layer" + andDescription:nil]]; + + NSArray *servicesDemos = + @[[self newDemo:[GeocoderViewController class] + withTitle:@"Geocoder" + andDescription:nil], + [self newDemo:[StructuredGeocoderViewController class] + withTitle:@"Structured Geocoder" + andDescription:nil], + ]; + + return @[mapDemos, panoramaDemos, overlayDemos, cameraDemos, servicesDemos]; +} + ++ (NSDictionary *)newDemo:(Class) class + withTitle:(NSString *)title + andDescription:(NSString *)description { + return [[NSDictionary alloc] initWithObjectsAndKeys:class, @"controller", + title, @"title", description, @"description", nil]; +} +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/SnapshotReadyViewController.h a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/SnapshotReadyViewController.h new file mode 100755 index 0000000..ceaa9c1 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/SnapshotReadyViewController.h @@ -0,0 +1,20 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 + +@interface SnapshotReadyViewController : UIViewController + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/SnapshotReadyViewController.m a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/SnapshotReadyViewController.m new file mode 100755 index 0000000..cb53732 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/SnapshotReadyViewController.m @@ -0,0 +1,108 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 "GoogleMapsDemos/Samples/SnapshotReadyViewController.h" + +#import + +@interface SnapshotReadyViewController () +@end + +@implementation SnapshotReadyViewController { + GMSMapView *_mapView; + UILabel *_statusLabel; + UIBarButtonItem *_waitButton; + BOOL _isAwaitingSnapshot; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + GMSCameraPosition *camera = + [GMSCameraPosition cameraWithLatitude:-33.868 longitude:151.2086 zoom:6]; + _mapView = [GMSMapView mapWithFrame:CGRectZero camera:camera]; + _mapView.delegate = self; + self.view = _mapView; + + // Add status label, initially hidden. + _statusLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 0, 30)]; + _statusLabel.alpha = 0.0f; + _statusLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth; + _statusLabel.backgroundColor = [UIColor blueColor]; + _statusLabel.textColor = [UIColor whiteColor]; + _statusLabel.textAlignment = NSTextAlignmentCenter; + + // Add a wait button to signify on the next SnapshotReady event, a screenshot of the map will + // be taken. + _waitButton = [[UIBarButtonItem alloc] initWithTitle:@"Wait for snapshot" + style:UIBarButtonItemStylePlain + target:self + action:@selector(didTapWait)]; + self.navigationItem.rightBarButtonItems = @[ _waitButton ]; + [_mapView addSubview:_statusLabel]; +} + +#pragma mark GMSMapViewDelegate + +- (void)mapViewSnapshotReady:(GMSMapView *)mapView { + if (_isAwaitingSnapshot) { + _isAwaitingSnapshot = NO; + _waitButton.enabled = YES; + _waitButton.title = @"Wait for snapshot"; + [self takeSnapshot]; + } + + _statusLabel.alpha = 0.8f; + _statusLabel.text = @"Snapshot Ready"; + // Remove status label after 1 second. + UILabel *statusLabel = _statusLabel; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ + statusLabel.alpha = 0.0f; + }); +} + +#pragma mark Private + +- (void)didTapWait { + _isAwaitingSnapshot = YES; + _waitButton.enabled = NO; + _waitButton.title = @"Waiting"; +} + +- (void)takeSnapshot { + // Take a snapshot of the map. + UIGraphicsBeginImageContextWithOptions(_mapView.bounds.size, YES, 0); + [_mapView drawViewHierarchyInRect:_mapView.bounds afterScreenUpdates:YES]; + UIImage *mapSnapShot = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + // Put snapshot image into an UIImageView and overlay on top of map. + UIImageView *imageView = [[UIImageView alloc] initWithImage:mapSnapShot]; + imageView.layer.borderColor = [UIColor redColor].CGColor; + imageView.layer.borderWidth = 10.0f; + [_mapView addSubview:imageView]; + + // Remove imageView after 1 second. + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ + [UIView animateWithDuration:1 + animations:^{ + imageView.alpha = 0.0f; + } + completion:^(BOOL finished) { + [imageView removeFromSuperview]; + }]; + }); +} + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/StructuredGeocoderViewController.h a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/StructuredGeocoderViewController.h new file mode 100755 index 0000000..4e655fc --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/StructuredGeocoderViewController.h @@ -0,0 +1,20 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 + +@interface StructuredGeocoderViewController : UIViewController + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/StructuredGeocoderViewController.m a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/StructuredGeocoderViewController.m new file mode 100755 index 0000000..d105a9c --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/StructuredGeocoderViewController.m @@ -0,0 +1,91 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 "GoogleMapsDemos/Samples/StructuredGeocoderViewController.h" + +#import + +@interface StructuredGeocoderViewController () + +@end + +@implementation StructuredGeocoderViewController { + GMSMapView *_mapView; + GMSGeocoder *_geocoder; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + GMSCameraPosition *camera = [GMSCameraPosition cameraWithLatitude:-33.868 + longitude:151.2086 + zoom:12]; + _mapView = [GMSMapView mapWithFrame:CGRectZero camera:camera]; + _mapView.delegate = self; + _geocoder = [[GMSGeocoder alloc] init]; + + self.view = _mapView; +} + +#pragma mark - GMSMapViewDelegate + +- (void)mapView:(GMSMapView *)mapView didLongPressAtCoordinate:(CLLocationCoordinate2D)coordinate { + // On a long press, reverse geocode this location. + __weak __typeof__(self) weakSelf = self; + GMSReverseGeocodeCallback handler = ^(GMSReverseGeocodeResponse *response, NSError *error) { + [weakSelf handleResponse:response coordinate:coordinate error:error]; + }; + [_geocoder reverseGeocodeCoordinate:coordinate completionHandler:handler]; +} + +#pragma mark - StructuredGeocoderViewController Private Category + +- (void)handleResponse:(nullable GMSReverseGeocodeResponse *)response + coordinate:(CLLocationCoordinate2D)coordinate + error:(nullable NSError *)error { + GMSAddress *address = response.firstResult; + if (address) { + NSLog(@"Geocoder result: %@", address); + + GMSMarker *marker = [GMSMarker markerWithPosition:address.coordinate]; + + marker.title = address.thoroughfare; + + NSMutableString *snippet = [[NSMutableString alloc] init]; + if (address.subLocality != NULL) { + [snippet appendString:[NSString stringWithFormat:@"subLocality: %@\n", address.subLocality]]; + } + if (address.locality != NULL) { + [snippet appendString:[NSString stringWithFormat:@"locality: %@\n", address.locality]]; + } + if (address.administrativeArea != NULL) { + [snippet appendString:[NSString stringWithFormat:@"administrativeArea: %@\n", + address.administrativeArea]]; + } + if (address.country != NULL) { + [snippet appendString:[NSString stringWithFormat:@"country: %@\n", address.country]]; + } + + marker.snippet = snippet; + + marker.appearAnimation = kGMSMarkerAnimationPop; + marker.map = _mapView; + _mapView.selectedMarker = marker; + } else { + NSLog(@"Could not reverse geocode point (%f,%f): %@", coordinate.latitude, coordinate.longitude, + error); + } +} + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/StyledMapViewController.h a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/StyledMapViewController.h new file mode 100755 index 0000000..c744eb5 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/StyledMapViewController.h @@ -0,0 +1,20 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 + +@interface StyledMapViewController : UIViewController + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/StyledMapViewController.m a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/StyledMapViewController.m new file mode 100755 index 0000000..9930aba --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/StyledMapViewController.m @@ -0,0 +1,123 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 "GoogleMapsDemos/Samples/StyledMapViewController.h" + +#import + +static NSString *const kNormalType = @"Normal"; +static NSString *const kRetroType = @"Retro"; +static NSString *const kGrayscaleType = @"Grayscale"; +static NSString *const kNightType = @"Night"; +static NSString *const kNoPOIsType = @"No business points of interest, no transit"; + +@implementation StyledMapViewController { + UIBarButtonItem *_barButtonItem; + GMSMapView *_mapView; + GMSMapStyle *_retroStyle; + GMSMapStyle *_grayscaleStyle; + GMSMapStyle *_nightStyle; + GMSMapStyle *_noPOIsStyle; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + // Error handling is skipped here for brevity, however it is recommended that you look at the + // error returned from |styleWithContentsOfFileURL:error:| if it returns nil. This error will + // provide information on why your style was not able to be loaded. + + NSURL *retroURL = [[NSBundle mainBundle] URLForResource:@"mapstyle-retro" + withExtension:@"json"]; + _retroStyle = [GMSMapStyle styleWithContentsOfFileURL:retroURL error:NULL]; + + NSURL *grayscaleURL = [[NSBundle mainBundle] URLForResource:@"mapstyle-silver" + withExtension:@"json"]; + _grayscaleStyle = [GMSMapStyle styleWithContentsOfFileURL:grayscaleURL error:NULL]; + + NSURL *nightURL = [[NSBundle mainBundle] URLForResource:@"mapstyle-night" + withExtension:@"json"]; + _nightStyle = [GMSMapStyle styleWithContentsOfFileURL:nightURL error:NULL]; + + NSString *noPOIsString = @" [\n" + " {\n" + " \"featureType\": \"poi.business\",\n" + " \"elementType\": \"all\",\n" + " \"stylers\": [\n" + " {\n" + " \"visibility\": \"off\"\n" + " }\n" + " ]\n" + " },\n" + " {\n" + " \"featureType\": \"transit\",\n" + " \"elementType\": \"all\",\n" + " \"stylers\": [\n" + " {\n" + " \"visibility\": \"off\"\n" + " }\n" + " ]\n" + " }\n" + " ]"; + _noPOIsStyle = [GMSMapStyle styleWithJSONString:noPOIsString error:NULL]; + + GMSCameraPosition *camera = + [GMSCameraPosition cameraWithLatitude:-33.868 longitude:151.2086 zoom:12]; + + _mapView = [GMSMapView mapWithFrame:CGRectZero camera:camera]; + self.view = _mapView; + + _mapView.mapStyle = _retroStyle; + + UIBarButtonItem *styleButton = [[UIBarButtonItem alloc] initWithTitle:@"Style" + style:UIBarButtonItemStylePlain + target:self + action:@selector(changeMapStyle:)]; + self.navigationItem.rightBarButtonItem = styleButton; + self.navigationItem.title = kRetroType; +} + +- (UIAlertAction *_Nonnull)actionWithTitle:(nonnull NSString *)title + style:(nullable GMSMapStyle *)style { + __weak __typeof__(self) weakSelf = self; + return [UIAlertAction actionWithTitle:title + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *_Nonnull action) { + __strong __typeof__(self) strongSelf = weakSelf; + if (strongSelf) { + strongSelf->_mapView.mapStyle = style; + strongSelf.navigationItem.title = title; + } + }]; +} + +- (void)changeMapStyle:(UIBarButtonItem *)sender { + UIAlertController *alert = + [UIAlertController alertControllerWithTitle:@"Select map style" + message:nil + preferredStyle:UIAlertControllerStyleActionSheet]; + [alert addAction:[self actionWithTitle:kRetroType style:_retroStyle]]; + [alert addAction:[self actionWithTitle:kGrayscaleType style:_grayscaleStyle]]; + [alert addAction:[self actionWithTitle:kNightType style:_nightStyle]]; + [alert addAction:[self actionWithTitle:kNormalType style:nil]]; + [alert addAction:[self actionWithTitle:kNoPOIsType style:_noPOIsStyle]]; + [alert addAction:[UIAlertAction actionWithTitle:@"Cancel" + style:UIAlertActionStyleCancel + handler:nil]]; + alert.popoverPresentationController.barButtonItem = sender; + [self presentViewController:alert animated:YES completion:nil]; +} + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/TileLayerViewController.h a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/TileLayerViewController.h new file mode 100755 index 0000000..adc4085 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/TileLayerViewController.h @@ -0,0 +1,20 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 + +@interface TileLayerViewController : UIViewController + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/TileLayerViewController.m a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/TileLayerViewController.m new file mode 100755 index 0000000..617db21 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/TileLayerViewController.m @@ -0,0 +1,76 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 "GoogleMapsDemos/Samples/TileLayerViewController.h" + +#import + +@implementation TileLayerViewController { + UISegmentedControl *_switcher; + GMSMapView *_mapView; + GMSTileLayer *_tileLayer; + NSInteger _floor; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + GMSCameraPosition *camera = [GMSCameraPosition cameraWithLatitude:37.78318 + longitude:-122.403874 + zoom:18]; + + _mapView = [GMSMapView mapWithFrame:CGRectZero camera:camera]; + _mapView.buildingsEnabled = NO; + _mapView.indoorEnabled = NO; + self.view = _mapView; + + // The possible floors that might be shown. + NSArray *types = @[ @"1", @"2", @"3" ]; + + // Create a UISegmentedControl that is the navigationItem's titleView. + _switcher = [[UISegmentedControl alloc] initWithItems:types]; + _switcher.selectedSegmentIndex = 0; + _switcher.autoresizingMask = UIViewAutoresizingFlexibleWidth; + _switcher.frame = + CGRectMake(0, 0, 300, _switcher.frame.size.height); + self.navigationItem.titleView = _switcher; + + // Listen to touch events on the UISegmentedControl, force initial update. + [_switcher addTarget:self action:@selector(didChangeSwitcher) + forControlEvents:UIControlEventValueChanged]; + [self didChangeSwitcher]; +} + +- (void)didChangeSwitcher { + NSString *title = + [_switcher titleForSegmentAtIndex:_switcher.selectedSegmentIndex]; + NSInteger floor = [title integerValue]; + if (_floor != floor) { + // Clear existing tileLayer, if any. + _tileLayer.map = nil; + + // Create a new GMSTileLayer with the new floor choice. + GMSTileURLConstructor urls = ^(NSUInteger x, NSUInteger y, NSUInteger zoom) { + NSString *url = [NSString + stringWithFormat:@"https://www.gstatic.com/io2010maps/tiles/9/L%ld_%lu_%lu_%lu.png", + (long)floor, (unsigned long)zoom, (unsigned long)x, (unsigned long)y]; + return [NSURL URLWithString:url]; + }; + _tileLayer = [GMSURLTileLayer tileLayerWithURLConstructor:urls]; + _tileLayer.map = _mapView; + _floor = floor; + } +} + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/TrafficMapViewController.h a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/TrafficMapViewController.h new file mode 100755 index 0000000..ddbc9c6 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/TrafficMapViewController.h @@ -0,0 +1,20 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 + +@interface TrafficMapViewController : UIViewController + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/TrafficMapViewController.m a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/TrafficMapViewController.m new file mode 100755 index 0000000..9140c41 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/TrafficMapViewController.m @@ -0,0 +1,33 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 "GoogleMapsDemos/Samples/TrafficMapViewController.h" + +#import + +@implementation TrafficMapViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + GMSCameraPosition *camera = [GMSCameraPosition cameraWithLatitude:-33.868 + longitude:151.2086 + zoom:12]; + + GMSMapView *mapView = [GMSMapView mapWithFrame:CGRectZero camera:camera]; + mapView.trafficEnabled = YES; + self.view = mapView; +} + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/VisibleRegionViewController.h a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/VisibleRegionViewController.h new file mode 100755 index 0000000..1f5c6b0 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/VisibleRegionViewController.h @@ -0,0 +1,20 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 + +@interface VisibleRegionViewController : UIViewController + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/VisibleRegionViewController.m a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/VisibleRegionViewController.m new file mode 100755 index 0000000..6bcc21f --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/Samples/VisibleRegionViewController.m @@ -0,0 +1,72 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 "GoogleMapsDemos/Samples/VisibleRegionViewController.h" + +#import + +static const CGFloat kOverlayHeight = 140.0f; + +@implementation VisibleRegionViewController { + GMSMapView *_mapView; + UIView *_overlay; + UIBarButtonItem *_flyInButton; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + GMSCameraPosition *camera = [GMSCameraPosition cameraWithLatitude:-37.81969 + longitude:144.966085 + zoom:4]; + _mapView = [GMSMapView mapWithFrame:CGRectZero camera:camera]; + + // Enable my location button to show more UI components updating. + _mapView.settings.myLocationButton = YES; + _mapView.myLocationEnabled = YES; + _mapView.padding = UIEdgeInsetsMake(0, 0, kOverlayHeight, 0); + self.view = _mapView; + + // Create a button that, when pressed, causes an overlaying view to fly-in/out. + _flyInButton = [[UIBarButtonItem alloc] initWithTitle:@"Toggle Overlay" + style:UIBarButtonItemStylePlain + target:self + action:@selector(didTapFlyIn)]; + self.navigationItem.rightBarButtonItem = _flyInButton; + + CGRect overlayFrame = CGRectMake(0, -kOverlayHeight, 0, kOverlayHeight); + _overlay = [[UIView alloc] initWithFrame:overlayFrame]; + _overlay.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth; + + _overlay.backgroundColor = [UIColor colorWithHue:0.0 saturation:1.0 brightness:1.0 alpha:0.5]; + [self.view addSubview:_overlay]; +} + +- (void)didTapFlyIn { + UIEdgeInsets padding = _mapView.padding; + + [UIView animateWithDuration:2.0 animations:^{ + CGSize size = self.view.bounds.size; + if (padding.bottom == 0.0f) { + self->_overlay.frame = + CGRectMake(0, size.height - kOverlayHeight, size.width, kOverlayHeight); + self->_mapView.padding = UIEdgeInsetsMake(0, 0, kOverlayHeight, 0); + } else { + self->_overlay.frame = CGRectMake(0, self->_mapView.bounds.size.height, size.width, 0); + self->_mapView.padding = UIEdgeInsetsZero; + } + }]; +} + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/UIViewController+GMSToastMessages.h a/Pods/GoogleMaps/Example/GoogleMapsDemos/UIViewController+GMSToastMessages.h new file mode 100755 index 0000000..93161f4 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/UIViewController+GMSToastMessages.h @@ -0,0 +1,26 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 + +NS_ASSUME_NONNULL_BEGIN + +@interface UIViewController (GMSToastMessages) + +- (void)gms_showToastWithMessage:(NSString*)message; + +@end + +NS_ASSUME_NONNULL_END diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/UIViewController+GMSToastMessages.m a/Pods/GoogleMaps/Example/GoogleMapsDemos/UIViewController+GMSToastMessages.m new file mode 100755 index 0000000..8e4072c --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/UIViewController+GMSToastMessages.m @@ -0,0 +1,36 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 "GoogleMapsDemos/UIViewController+GMSToastMessages.h" + +@implementation UIViewController (GMSToastMessages) + +- (void)gms_showToastWithMessage:(NSString *)message { + UIAlertController *toast = + [UIAlertController alertControllerWithTitle:nil + message:message + preferredStyle:UIAlertControllerStyleAlert]; + [self presentViewController:toast + animated:YES + completion:^{ + const int kDuration = 2; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, kDuration * NSEC_PER_SEC), + dispatch_get_main_queue(), ^{ + [toast dismissViewControllerAnimated:YES completion:nil]; + }); + }]; +} + +@end diff --git b/Pods/GoogleMaps/Example/GoogleMapsDemos/main.m a/Pods/GoogleMaps/Example/GoogleMapsDemos/main.m new file mode 100755 index 0000000..34d59f6 --- /dev/null +++ a/Pods/GoogleMaps/Example/GoogleMapsDemos/main.m @@ -0,0 +1,24 @@ +/* + * Copyright 2016 Google LLC. All rights reserved. + * + * + * 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 + +#import "GoogleMapsDemos/DemoAppDelegate.h" + +int main(int argc, char *argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([DemoAppDelegate class])); + } +} diff --git b/Pods/GoogleMaps/Example/Podfile a/Pods/GoogleMaps/Example/Podfile new file mode 100755 index 0000000..d9eb236 --- /dev/null +++ a/Pods/GoogleMaps/Example/Podfile @@ -0,0 +1,6 @@ +source 'https://github.com/CocoaPods/Specs.git' + +target 'GoogleMapsDemos' do + platform :ios, '9.0' + pod 'GoogleMaps', '= 3.8.0' +end diff --git b/Pods/GoogleMaps/Example/README.GoogleMapsDemos a/Pods/GoogleMaps/Example/README.GoogleMapsDemos new file mode 100755 index 0000000..96171d4 --- /dev/null +++ a/Pods/GoogleMaps/Example/README.GoogleMapsDemos @@ -0,0 +1,21 @@ +GoogleMapsDemos contains a demo application showcasing various features of +the Google Maps SDK for iOS. + +Before starting, please note that these demos are directed towards a technical +audience. You'll also need Xcode 9.0 or later, with the iOS SDK 9.3 or later. + +If you're new to the SDK, please read the Introduction section of the Google +Maps SDK for iOS documentation- + https://developers.google.com/maps/documentation/ios + +Once you've read the Introduction page, follow the first couple of steps on the +"Getting Started" page. Specifically; + + * Obtain an API key for the demo application, and specify the bundle ID of + this demo application as an an 'allowed iOS app'. By default, the bundle ID + is "com.example.GoogleMapsDemos". + + * Open the project in Xcode, and update `SDKDemoAPIKey.h` with this key. + +If you'd like to add a new sample, add a new subclass of `ViewController` and +add it to the samples definitions inside the `Samples.m`. diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/GoogleMaps a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/GoogleMaps new file mode 100755 index 0000000..7f9572b Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/GoogleMaps differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSAddress.h a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSAddress.h new file mode 100755 index 0000000..0540507 --- /dev/null +++ a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSAddress.h @@ -0,0 +1,75 @@ +// +// GMSAddress.h +// Google Maps SDK for iOS +// +// Copyright 2014 Google LLC +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import + +#if __has_feature(modules) +@import GoogleMapsBase; +#else +#import +#endif + +NS_ASSUME_NONNULL_BEGIN + +/** + * A result from a reverse geocode request, containing a human-readable address. This class is + * immutable and should be obtained via GMSGeocoder. + * + * Some of the fields may be nil, indicating they are not present. + */ +@interface GMSAddress : NSObject + +/** Location, or kLocationCoordinate2DInvalid if unknown. */ +@property(nonatomic, readonly) CLLocationCoordinate2D coordinate; + +/** Street number and name. */ +@property(nonatomic, copy, readonly, nullable) NSString *thoroughfare; + +/** Locality or city. */ +@property(nonatomic, copy, readonly, nullable) NSString *locality; + +/** Subdivision of locality, district or park. */ +@property(nonatomic, copy, readonly, nullable) NSString *subLocality; + +/** Region/State/Administrative area. */ +@property(nonatomic, copy, readonly, nullable) NSString *administrativeArea; + +/** Postal/Zip code. */ +@property(nonatomic, copy, readonly, nullable) NSString *postalCode; + +/** The country name. */ +@property(nonatomic, copy, readonly, nullable) NSString *country; + +/** An array of NSString containing formatted lines of the address. May be nil. */ +@property(nonatomic, copy, readonly, nullable) NSArray *lines; + +/** + * Returns the first line of the address. + */ +- (nullable NSString *)addressLine1 __GMS_AVAILABLE_BUT_DEPRECATED_MSG( + "This method is obsolete and will be removed in a future release. Use the lines property " + "instead."); + +/** + * Returns the second line of the address. + */ +- (nullable NSString *)addressLine2 __GMS_AVAILABLE_BUT_DEPRECATED_MSG( + "This method is obsolete and will be removed in a future release. Use the lines property " + "instead."); + +@end + +/** + * The former type of geocode results (pre-1.7). This remains here for migration and will be + * removed in future releases. + */ +@compatibility_alias GMSReverseGeocodeResult GMSAddress; + +NS_ASSUME_NONNULL_END diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSCALayer.h a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSCALayer.h new file mode 100755 index 0000000..2ab6da2 --- /dev/null +++ a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSCALayer.h @@ -0,0 +1,20 @@ +// +// GMSCALayer.h +// Google Maps SDK for iOS +// +// Copyright 2013 Google LLC +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import + +/** + * GMSCALayer is a superclass used by layers in the Google Maps SDK for iOS, such as GMSMapLayer and + * GMSPanoramaLayer. + * + * This is an implementation detail and it should not be instantiated directly. + */ +@interface GMSCALayer : CALayer +@end diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSCameraPosition.h a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSCameraPosition.h new file mode 100755 index 0000000..77a1d85 --- /dev/null +++ a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSCameraPosition.h @@ -0,0 +1,166 @@ +// +// GMSCameraPosition.h +// Google Maps SDK for iOS +// +// Copyright 2013 Google LLC +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** +* An immutable class that aggregates all camera position parameters. + */ +@interface GMSCameraPosition : NSObject + +/** + * Location on the Earth towards which the camera points. + */ +@property(nonatomic, readonly) CLLocationCoordinate2D target; + +/** + * Zoom level. Zoom uses an exponentional scale, where zoom 0 represents the entire world as a + * 256 x 256 square. Each successive zoom level increases magnification by a factor of 2. So at + * zoom level 1, the world is 512x512, and at zoom level 2, the entire world is 1024x1024. + */ +@property(nonatomic, readonly) float zoom; + +/** + * Bearing of the camera, in degrees clockwise from true north. + */ +@property(nonatomic, readonly) CLLocationDirection bearing; + +/** + * The angle, in degrees, of the camera from the nadir (directly facing the Earth). 0 is + * straight down, 90 is parallel to the ground. Note that the maximum angle allowed is dependent + * on the zoom. You can think of it as a series of line segments as a function of zoom, rather + * than a step function. For zoom 16 and above, the maximum angle is 65 degrees. For zoom 10 and + * below, the maximum angle is 30 degrees. + */ +@property(nonatomic, readonly) double viewingAngle; + +/** + * Designated initializer. Configures this GMSCameraPosition with all available camera properties. + * Building a GMSCameraPosition via this initializer (or by the following convenience constructors) + * will implicitly clamp camera values. + * + * @param target Location on the earth towards which the camera points. + * @param zoom The zoom level near the center of the screen. + * @param bearing Bearing of the camera in degrees clockwise from true north. + * @param viewingAngle The angle, in degrees, of the camera angle from the nadir (directly facing + * the Earth) + */ +- (instancetype)initWithTarget:(CLLocationCoordinate2D)target + zoom:(float)zoom + bearing:(CLLocationDirection)bearing + viewingAngle:(double)viewingAngle; + +/** + * Convenience initializer for GMSCameraPosition for a particular target and zoom level. This will + * set the bearing and viewingAngle properties of this camera to zero defaults (i.e., directly + * facing the Earth's surface, with the top of the screen pointing north). + * + * @param target Location on the earth towards which the camera points. + * @param zoom The zoom level near the center of the screen. + */ +- (instancetype)initWithTarget:(CLLocationCoordinate2D)target zoom:(float)zoom; + +/** + * Convenience initializer for GMSCameraPosition for a particular latitidue, longitude and zoom + * level. This will set the bearing and viewingAngle properties of this camera to zero defaults + * (i.e., directly facing the Earth's surface, with the top of the screen pointing north). + * + * @param latitude The latitude component of the location towards which the camera points. + * @param longitude The latitude component of the location towards which the camera points. + * @param zoom The zoom level near the center of the screen. + */ +- (instancetype)initWithLatitude:(CLLocationDegrees)latitude + longitude:(CLLocationDegrees)longitude + zoom:(float)zoom; + +/** + * Convenience initializer for GMSCameraPosition, with latitude/longitude and all other camera + * properties as per -initWithTarget:zoom:bearing:viewingAngle:. + * + * @param latitude The latitude component of the location towards which the camera points. + * @param longitude The latitude component of the location towards which the camera points. + * @param zoom The zoom level near the center of the screen. + * @param bearing Bearing of the camera in degrees clockwise from true north. + * @param viewingAngle The angle, in degrees, of the camera angle from the nadir (directly facing + * the Earth) + */ +- (instancetype)initWithLatitude:(CLLocationDegrees)latitude + longitude:(CLLocationDegrees)longitude + zoom:(float)zoom + bearing:(CLLocationDirection)bearing + viewingAngle:(double)viewingAngle; + +/** + * Convenience constructor for GMSCameraPosition for a particular target and zoom level. This will + * set the bearing and viewingAngle properties of this camera to zero defaults (i.e., directly + * facing the Earth's surface, with the top of the screen pointing north). + */ ++ (instancetype)cameraWithTarget:(CLLocationCoordinate2D)target zoom:(float)zoom; + +/** + * Convenience constructor for GMSCameraPosition, as per cameraWithTarget:zoom:. + */ ++ (instancetype)cameraWithLatitude:(CLLocationDegrees)latitude + longitude:(CLLocationDegrees)longitude + zoom:(float)zoom; + +/** + * Convenience constructor for GMSCameraPosition, with all camera properties as per + * initWithTarget:zoom:bearing:viewingAngle:. + */ ++ (instancetype)cameraWithTarget:(CLLocationCoordinate2D)target + zoom:(float)zoom + bearing:(CLLocationDirection)bearing + viewingAngle:(double)viewingAngle; + +/** + * Convenience constructor for GMSCameraPosition, with latitude/longitude and all other camera + * properties as per initWithTarget:zoom:bearing:viewingAngle:. + */ ++ (instancetype)cameraWithLatitude:(CLLocationDegrees)latitude + longitude:(CLLocationDegrees)longitude + zoom:(float)zoom + bearing:(CLLocationDirection)bearing + viewingAngle:(double)viewingAngle; + +/** + * Get the zoom level at which |meters| distance, at given |coord| on Earth, correspond to the + * specified number of screen |points|. + * + * For extremely large or small distances the returned zoom level may be smaller or larger than the + * minimum or maximum zoom level allowed on the camera. + * + * This helper method is useful for building camera positions that contain specific physical areas + * on Earth. + */ ++ (float)zoomAtCoordinate:(CLLocationCoordinate2D)coordinate + forMeters:(CLLocationDistance)meters + perPoints:(CGFloat)points; + +@end + +/** Mutable version of GMSCameraPosition. */ +@interface GMSMutableCameraPosition : GMSCameraPosition +@property(nonatomic) CLLocationCoordinate2D target; +@property(nonatomic) float zoom; +@property(nonatomic) CLLocationDirection bearing; +@property(nonatomic) double viewingAngle; +@end + +/** The maximum zoom (closest to the Earth's surface) permitted by the map camera. */ +FOUNDATION_EXTERN const float kGMSMaxZoomLevel; + +/** The minimum zoom (farthest from the Earth's surface) permitted by the map camera. */ +FOUNDATION_EXTERN const float kGMSMinZoomLevel; + +NS_ASSUME_NONNULL_END diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSCameraUpdate.h a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSCameraUpdate.h new file mode 100755 index 0000000..3996d01 --- /dev/null +++ a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSCameraUpdate.h @@ -0,0 +1,109 @@ +// +// GMSCameraUpdate.h +// Google Maps SDK for iOS +// +// Copyright 2013 Google LLC +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import +#import + +@class GMSCameraPosition; +@class GMSCoordinateBounds; + +NS_ASSUME_NONNULL_BEGIN + +/** + * GMSCameraUpdate represents an update that may be applied to a GMSMapView. + * + * It encapsulates some logic for modifying the current camera. + * + * It should only be constructed using the factory helper methods below. + */ +@interface GMSCameraUpdate : NSObject + +/** + * Returns a GMSCameraUpdate that zooms in on the map. + * + * The zoom increment is 1.0. + */ ++ (GMSCameraUpdate *)zoomIn; + +/** + * Returns a GMSCameraUpdate that zooms out on the map. + * + * The zoom increment is -1.0. + */ ++ (GMSCameraUpdate *)zoomOut; + +/** + * Returns a GMSCameraUpdate that changes the zoom by the specified amount. + */ ++ (GMSCameraUpdate *)zoomBy:(float)delta; + +/** + * Returns a GMSCameraUpdate that sets the zoom to the specified amount. + */ ++ (GMSCameraUpdate *)zoomTo:(float)zoom; + +/** + * Returns a GMSCameraUpdate that sets the camera target to the specified coordinate. + */ ++ (GMSCameraUpdate *)setTarget:(CLLocationCoordinate2D)target; + +/** + * Returns a GMSCameraUpdate that sets the camera target and zoom to the specified values. + */ ++ (GMSCameraUpdate *)setTarget:(CLLocationCoordinate2D)target zoom:(float)zoom; + +/** + * Returns a GMSCameraUpdate that sets the camera to the specified GMSCameraPosition. + */ ++ (GMSCameraUpdate *)setCamera:(GMSCameraPosition *)camera; + +/** + * Returns a GMSCameraUpdate that transforms the camera such that the specified bounds are centered + * on screen at the greatest possible zoom level. The bounds will have a default padding of 64 + * points. + * + * The returned camera update will set the camera's bearing and tilt to their default zero values + * (i.e., facing north and looking directly at the Earth). + */ ++ (GMSCameraUpdate *)fitBounds:(GMSCoordinateBounds *)bounds; + +/** + * This is similar to fitBounds: but allows specifying the padding (in points) in order to inset the + * bounding box from the view's edges. + * + * If the requested |padding| is larger than the view size in either the vertical or horizontal + * direction the map will be maximally zoomed out. + */ ++ (GMSCameraUpdate *)fitBounds:(GMSCoordinateBounds *)bounds withPadding:(CGFloat)padding; + +/** + * This is similar to fitBounds: but allows specifying edge insets in order to inset the bounding + * box from the view's edges. + * + * If the requested |edgeInsets| are larger than the view size in either the vertical or horizontal + * direction the map will be maximally zoomed out. + */ ++ (GMSCameraUpdate *)fitBounds:(GMSCoordinateBounds *)bounds + withEdgeInsets:(UIEdgeInsets)edgeInsets; + +/** + * Returns a GMSCameraUpdate that shifts the center of the view by the specified number of points in + * the x and y directions. X grows to the right, Y grows down. + */ ++ (GMSCameraUpdate *)scrollByX:(CGFloat)dX Y:(CGFloat)dY; + +/** + * Returns a GMSCameraUpdate that zooms with a focus point; the focus point stays fixed on screen. + */ ++ (GMSCameraUpdate *)zoomBy:(float)zoom atPoint:(CGPoint)point; + +@end + +NS_ASSUME_NONNULL_END diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSCircle.h a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSCircle.h new file mode 100755 index 0000000..d1995e1 --- /dev/null +++ a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSCircle.h @@ -0,0 +1,54 @@ +// +// GMSCircle.h +// Google Maps SDK for iOS +// +// Copyright 2013 Google LLC +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import + +#import "GMSOverlay.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * A circle on the Earth's surface (spherical cap). + */ +@interface GMSCircle : GMSOverlay + +/** Position on Earth of circle center. */ +@property(nonatomic) CLLocationCoordinate2D position; + +/** Radius of the circle in meters; must be positive. */ +@property(nonatomic) CLLocationDistance radius; + +/** + * The width of the circle's outline in screen points. Defaults to 1. As per GMSPolygon, the width + * does not scale when the map is zoomed. + * + * Setting strokeWidth to 0 results in no stroke. + */ +@property(nonatomic) CGFloat strokeWidth; + +/** The color of this circle's outline. The default value is black. */ +@property(nonatomic, nullable) UIColor *strokeColor; + +/** + * The interior of the circle is painted with fillColor. The default value is nil, resulting in no + * fill. + */ +@property(nonatomic, nullable) UIColor *fillColor; + +/** + * Convenience constructor for GMSCircle for a particular position and radius. Other properties will + * have default values. + */ ++ (instancetype)circleWithPosition:(CLLocationCoordinate2D)position + radius:(CLLocationDistance)radius; + +@end + +NS_ASSUME_NONNULL_END diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSCoordinateBounds+GoogleMaps.h a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSCoordinateBounds+GoogleMaps.h new file mode 100755 index 0000000..18f543f --- /dev/null +++ a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSCoordinateBounds+GoogleMaps.h @@ -0,0 +1,42 @@ +// +// GMSCoordinateBounds+GoogleMaps.h +// Google Maps SDK for iOS +// +// Copyright 2013 Google LLC +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + + +#if __has_feature(modules) +@import GoogleMapsBase; +#else +#import +#endif +#import "GMSProjection.h" + +@class GMSPath; + +NS_ASSUME_NONNULL_BEGIN + +@interface GMSCoordinateBounds (GoogleMaps) + +/** + * Inits with bounds that encompass |region|. + */ +- (id)initWithRegion:(GMSVisibleRegion)region; + +/** + * Inits with bounds that encompass |path|. + */ +- (id)initWithPath:(GMSPath *)path; + +/** + * Returns a GMSCoordinateBounds representing the current bounds extended to include |path|. + */ +- (GMSCoordinateBounds *)includingPath:(GMSPath *)path; + +@end + +NS_ASSUME_NONNULL_END diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSGeocoder.h a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSGeocoder.h new file mode 100755 index 0000000..c05b312 --- /dev/null +++ a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSGeocoder.h @@ -0,0 +1,74 @@ +// +// GMSGeocoder.h +// Google Maps SDK for iOS +// +// Copyright 2012 Google LLC +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import + +#import "GMSAddress.h" + +@class GMSReverseGeocodeResponse; + +NS_ASSUME_NONNULL_BEGIN + +/** + * \defgroup GeocoderErrorCode GMSGeocoderErrorCode + * @{ + */ + +/** + * GMSGeocoder error codes, embedded in NSError. + */ +typedef NS_ENUM(NSInteger, GMSGeocoderErrorCode) { + kGMSGeocoderErrorInvalidCoordinate = 1, + kGMSGeocoderErrorInternal, +}; + +/**@}*/ + +/** + * Handler that reports a reverse geocoding response, or error. + * + * @related GMSGeocoder + */ +typedef void (^GMSReverseGeocodeCallback)(GMSReverseGeocodeResponse *_Nullable, + NSError *_Nullable); + +/** + * Exposes a service for reverse geocoding. This maps Earth coordinates (latitude and longitude) to + * a collection of addresses near that coordinate. + */ +@interface GMSGeocoder : NSObject + +/* Convenience constructor for GMSGeocoder. */ ++ (GMSGeocoder *)geocoder; + +/** + * Reverse geocodes a coordinate on the Earth's surface. + * + * @param coordinate The coordinate to reverse geocode. + * @param handler The callback to invoke with the reverse geocode results. + * The callback will be invoked asynchronously from the main thread. + */ +- (void)reverseGeocodeCoordinate:(CLLocationCoordinate2D)coordinate + completionHandler:(GMSReverseGeocodeCallback)handler; + +@end + +/** A collection of results from a reverse geocode request. */ +@interface GMSReverseGeocodeResponse : NSObject + +/** Returns the first result, or nil if no results were available. */ +- (nullable GMSAddress *)firstResult; + +/** Returns an array of all the results (contains GMSAddress), including the first result. */ +- (nullable NSArray *)results; + +@end + +NS_ASSUME_NONNULL_END diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSGeometryUtils.h a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSGeometryUtils.h new file mode 100755 index 0000000..fc335c4 --- /dev/null +++ a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSGeometryUtils.h @@ -0,0 +1,243 @@ +// +// GMSGeometryUtils.h +// Google Maps SDK for iOS +// +// Copyright 2013 Google LLC +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +/** + * \defgroup GeometryUtils GMSGeometryUtils + * @{ + */ + +#import + +#import "GMSPath.h" + +@class GMSPath; +@class GMSStrokeStyle; +@class GMSStyleSpan; + +NS_ASSUME_NONNULL_BEGIN + +/** Average Earth radius in meters. */ +static const CLLocationDistance kGMSEarthRadius = 6371009.0; + +/** + * A point on the map. May represent a projected coordinate. + * + * x is in [-1, 1]. The axis direction is normal: y grows towards North, x grows towards East. (0, + * 0) is the center of the map. + * + * See GMSProject() and GMSUnproject(). + */ +typedef struct GMSMapPoint { + double x; + double y; +} GMSMapPoint; + +/** Projects |coordinate| to the map. |coordinate| must be valid. */ +FOUNDATION_EXPORT +GMSMapPoint GMSProject(CLLocationCoordinate2D coordinate); + +/** Unprojects |point| from the map. point.x must be in [-1, 1]. */ +FOUNDATION_EXPORT +CLLocationCoordinate2D GMSUnproject(GMSMapPoint point); + +/** + * Returns a linearly interpolated point on the segment [a, b], at the fraction |t| from |a|. |t|==0 + * corresponds to |a|, |t|==1 corresponds to |b|. + * + * The interpolation takes place along the short path between the points potentially crossing the + * date line. E.g. interpolating from San Francisco to Tokyo will pass north of Hawaii and cross the + * date line. + */ +FOUNDATION_EXPORT +GMSMapPoint GMSMapPointInterpolate(GMSMapPoint a, GMSMapPoint b, double t); + +/** + * Returns the length of the segment [a, b] in projected space. + * + * The length is computed along the short path between the points potentially crossing the date + * line. E.g. the distance between the points corresponding to San Francisco and Tokyo measures the + * segment that passes north of Hawaii crossing the date line. + */ +FOUNDATION_EXPORT +double GMSMapPointDistance(GMSMapPoint a, GMSMapPoint b); + +/** + * Returns whether |point| lies inside of path. The path is always considered closed, regardless of + * whether the last point equals the first or not. + * + * Inside is defined as not containing the South Pole -- the South Pole is always outside. + * + * |path| describes great circle segments if |geodesic| is YES, and rhumb (loxodromic) segments + * otherwise. + * + * If |point| is exactly equal to one of the vertices, the result is YES. A point that is not equal + * to a vertex is on one side or the other of any path segment -- it can never be "exactly on the + * border". + * + * See GMSGeometryIsLocationOnPath() for a border test with tolerance. + */ +FOUNDATION_EXPORT +BOOL GMSGeometryContainsLocation(CLLocationCoordinate2D point, GMSPath *path, BOOL geodesic); + +/** + * Returns whether |point| lies on or near |path|, within the specified |tolerance| in meters. + * |path| is composed of great circle segments if |geodesic| is YES, and of rhumb (loxodromic) + * segments if |geodesic| is NO. + * + * See also GMSGeometryIsLocationOnPath(point, path, geodesic). + * + * The tolerance, in meters, is relative to the spherical radius of the Earth. If you need to work + * on a sphere of different radius, you may compute the equivalent tolerance from the desired + * tolerance on the sphere of radius R: tolerance = toleranceR * (RadiusEarth / R), with + * RadiusEarth==6371009. + */ +FOUNDATION_EXPORT +BOOL GMSGeometryIsLocationOnPathTolerance(CLLocationCoordinate2D point, + GMSPath *path, + BOOL geodesic, + CLLocationDistance tolerance); + +/** + * Same as GMSGeometryIsLocationOnPath(point, path, geodesic, tolerance), with a default tolerance + * of 0.1 meters. + */ +FOUNDATION_EXPORT +BOOL GMSGeometryIsLocationOnPath(CLLocationCoordinate2D point, GMSPath *path, BOOL geodesic); + +/** + * Returns the great circle distance between two coordinates, in meters, on Earth. + * + * This is the shortest distance between the two coordinates on the sphere. + * + * Both coordinates must be valid. + */ +FOUNDATION_EXPORT +CLLocationDistance GMSGeometryDistance(CLLocationCoordinate2D from, CLLocationCoordinate2D to); + +/** + * Returns the great circle length of |path|, in meters, on Earth. + * + * This is the sum of GMSGeometryDistance() over the path segments. + * + * All the coordinates of the path must be valid. + */ +FOUNDATION_EXPORT +CLLocationDistance GMSGeometryLength(GMSPath *path); + +/** + * Returns the area of a geodesic polygon defined by |path| on Earth. + * + * The "inside" of the polygon is defined as not containing the South pole. + * + * If |path| is not closed, it is implicitly treated as a closed path nevertheless and the result is + * the same. + * + * All coordinates of the path must be valid. + * + * The polygon must be simple (not self-overlapping) and may be concave. + * + * If any segment of the path is a pair of antipodal points, the result is undefined -- because two + * antipodal points do not form a unique great circle segment on the sphere. + */ +FOUNDATION_EXPORT +double GMSGeometryArea(GMSPath *path); + +/** + * Returns the signed area of a geodesic polygon defined by |path| on Earth. + * + * The result has the same absolute value as GMSGeometryArea(); it is positive if the points of path + * are in counter-clockwise order, and negative otherwise. + * + * The same restrictions as on GMSGeometryArea() apply. + */ +FOUNDATION_EXPORT +double GMSGeometrySignedArea(GMSPath *path); + +/** + * Returns the initial heading (degrees clockwise of North) at |from| of the shortest path to |to|. + * + * The returned value is in the range [0, 360). + * + * Returns 0 if the two coordinates are the same. + * + * Both coordinates must be valid. + * + * To get the final heading at |to| one may use (GMSGeometryHeading(|to|, |from|) + 180) modulo 360. + */ +FOUNDATION_EXPORT +CLLocationDirection GMSGeometryHeading(CLLocationCoordinate2D from, CLLocationCoordinate2D to); + +/** + * Returns the destination coordinate, when starting at |from| with initial |heading|, travelling + * |distance| meters along a great circle arc, on Earth. + * + * The resulting longitude is in the range [-180, 180). + * + * Both coordinates must be valid. + */ +FOUNDATION_EXPORT +CLLocationCoordinate2D GMSGeometryOffset(CLLocationCoordinate2D from, + CLLocationDistance distance, + CLLocationDirection heading); + +/** + * Returns the coordinate that lies the given |fraction| of the way between the |from| and |to| + * coordinates on the shortest path between the two. + * + * The resulting longitude is in the range [-180, 180). + */ +FOUNDATION_EXPORT +CLLocationCoordinate2D GMSGeometryInterpolate(CLLocationCoordinate2D from, + CLLocationCoordinate2D to, + double fraction); + +/** + * Returns an NSArray of GMSStyleSpan constructed by repeated application of style and length + * information from |styles| and |lengths| along |path|. + * + * |path| the path along which the output spans are computed. + * |styles| an NSArray of GMSStrokeStyle. Wraps if consumed. Can't be empty. + * |lengths| an NSArray of NSNumber; each entry gives the length of the corresponding + * style from |styles|. Wraps if consumed. Can't be empty. + * |lengthKind| the interpretation of values from |lengths| (geodesic, rhumb or projected). + * + * Example: a polyline with alternating black and white spans: + * + *
+ * GMSMutablePath *path;
+ * NSArray *styles = @[[GMSStrokeStyle solidColor:[UIColor whiteColor]],
+ *                     [GMSStrokeStyle solidColor:[UIColor blackColor]]];
+ * NSArray *lengths = @[@100000, @50000];
+ * polyline.path = path;
+ * polyline.spans = GMSStyleSpans(path, styles, lengths, kGMSLengthRhumb);
+ * 
+ */ +FOUNDATION_EXPORT +NSArray *GMSStyleSpans(GMSPath *path, + NSArray *styles, + NSArray *lengths, + GMSLengthKind lengthKind); + +/** + * Similar to GMSStyleSpans(path, styles, lengths, lengthKind) but additionally takes an initial + * length offset that will be skipped over relative to the |lengths| array. + * + * |lengthOffset| the length (e.g. in meters) that should be skipped initially from |lengths|. + */ +FOUNDATION_EXPORT +NSArray *GMSStyleSpansOffset(GMSPath *path, + NSArray *styles, + NSArray *lengths, + GMSLengthKind lengthKind, + double lengthOffset); + +/**@}*/ + +NS_ASSUME_NONNULL_END diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSGroundOverlay.h a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSGroundOverlay.h new file mode 100755 index 0000000..a3a8304 --- /dev/null +++ a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSGroundOverlay.h @@ -0,0 +1,85 @@ +// +// GMSGroundOverlay.h +// Google Maps SDK for iOS +// +// Copyright 2013 Google LLC +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import + +#import "GMSOverlay.h" + +@class GMSCoordinateBounds; + +NS_ASSUME_NONNULL_BEGIN + +/** + * GMSGroundOverlay specifies the available options for a ground overlay that exists on the Earth's + * surface. Unlike a marker, the position of a ground overlay is specified explicitly and it does + * not face the camera. + */ +@interface GMSGroundOverlay : GMSOverlay + +/** + * The position of this GMSGroundOverlay, or more specifically, the physical position of its anchor. + * If this is changed, |bounds| will be moved around the new position. + */ +@property(nonatomic) CLLocationCoordinate2D position; + +/** + * The anchor specifies where this GMSGroundOverlay is anchored to the Earth in relation to + * |bounds|. If this is modified, |position| will be set to the corresponding new position within + * |bounds|. + */ +@property(nonatomic) CGPoint anchor; + +/** + * Icon to render within |bounds| on the Earth. If this is nil, the overlay will not be visible + * (unlike GMSMarker which has a default image). + */ +@property(nonatomic, nullable) UIImage *icon; + +/** + * Sets the opacity of the ground overlay, between 0 (completely transparent) and 1 (default) + * inclusive. + */ +@property(nonatomic) float opacity; + +/** + * Bearing of this ground overlay, in degrees. The default value, zero, points this ground overlay + * up/down along the normal Y axis of the earth. + */ +@property(nonatomic) CLLocationDirection bearing; + +/** + * The 2D bounds on the Earth in which |icon| is drawn. Changing this value will adjust |position| + * accordingly. + */ +@property(nonatomic, nullable) GMSCoordinateBounds *bounds; + +/** + * Convenience constructor for GMSGroundOverlay for a particular |bounds| and |icon|. Will set + * |position| accordingly. + */ ++ (instancetype)groundOverlayWithBounds:(nullable GMSCoordinateBounds *)bounds + icon:(nullable UIImage *)icon; + +/** + * Constructs a GMSGroundOverlay that renders the given |icon| at |position|, as if the image's + * actual size matches camera pixels at |zoomLevel|. + */ ++ (instancetype)groundOverlayWithPosition:(CLLocationCoordinate2D)position + icon:(nullable UIImage *)icon + zoomLevel:(CGFloat)zoomLevel; + +@end + +/** + * The default position of the ground anchor of a GMSGroundOverlay: the center point of the icon. + */ +FOUNDATION_EXTERN const CGPoint kGMSGroundOverlayDefaultAnchor; + +NS_ASSUME_NONNULL_END diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSIndoorBuilding.h a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSIndoorBuilding.h new file mode 100755 index 0000000..cfe70c2 --- /dev/null +++ a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSIndoorBuilding.h @@ -0,0 +1,43 @@ +// +// GMSIndoorBuilding.h +// Google Maps SDK for iOS +// +// Copyright 2013 Google Inc. +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + + +#import + +@class GMSIndoorLevel; + +NS_ASSUME_NONNULL_BEGIN + +/** + * Describes a building which contains levels. + */ +@interface GMSIndoorBuilding : NSObject + +/** + * Array of GMSIndoorLevel describing the levels which make up the building. + * The levels are in 'display order' from top to bottom. + */ +@property(nonatomic, strong, readonly) NSArray *levels; + +/** + * Index in the levels array of the default level. + */ +@property(nonatomic, assign, readonly) NSUInteger defaultLevelIndex; + +/** + * If YES, the building is entirely underground and supports being hidden. + */ +@property(nonatomic, assign, readonly, getter=isUnderground) BOOL underground; + +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSIndoorDisplay.h a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSIndoorDisplay.h new file mode 100755 index 0000000..a703838 --- /dev/null +++ a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSIndoorDisplay.h @@ -0,0 +1,66 @@ +// +// GMSIndoorDisplay.h +// Google Maps SDK for iOS +// +// Copyright 2013 Google LLC +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import + +@class GMSIndoorBuilding; +@class GMSIndoorLevel; + +NS_ASSUME_NONNULL_BEGIN + +/** Delegate for events on GMSIndoorDisplay. */ +@protocol GMSIndoorDisplayDelegate +@optional + +/** + * Raised when the activeBuilding has changed. The activeLevel will also have already been updated + * for the new building, but didChangeActiveLevel: will be raised after this method. + */ +- (void)didChangeActiveBuilding:(nullable GMSIndoorBuilding *)building; + +/** + * Raised when the activeLevel has changed. This event is raised for all changes, including + * explicit setting of the property. + */ +- (void)didChangeActiveLevel:(nullable GMSIndoorLevel *)level; + +@end + +/** + * Provides ability to observe or control the display of indoor level data. + * + * Like GMSMapView, GMSIndoorDisplay may only be used from the main thread. + */ +@interface GMSIndoorDisplay : NSObject + +/** GMSIndoorDisplay delegate */ +@property(nonatomic, weak, nullable) id delegate; + +/** + * Provides the currently focused building, will be nil if there is no building with indoor data + * currently under focus. + */ +@property(nonatomic, readonly, nullable) GMSIndoorBuilding *activeBuilding; + +/** + * Provides and controls the active level for activeBuilding. Will be updated whenever + * activeBuilding changes, and may be set to any member of activeBuilding's levels property. May + * also be set to nil if the building is underground, to stop showing the building (the building + * will remain active). + * + * Will always be nil if activeBuilding is nil. + * + * Any attempt to set it to an invalid value will be ignored. + */ +@property(nonatomic, nullable) GMSIndoorLevel *activeLevel; + +@end + +NS_ASSUME_NONNULL_END diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSIndoorLevel.h a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSIndoorLevel.h new file mode 100755 index 0000000..0604ccb --- /dev/null +++ a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSIndoorLevel.h @@ -0,0 +1,32 @@ +// +// GMSIndoorLevel.h +// Google Maps SDK for iOS +// +// Copyright 2013 Google Inc. +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * Describes a single level in a building. + * + * Multiple buildings can share a level - in this case the level instances will compare as equal, + * even though the level numbers/names may be different. + */ +@interface GMSIndoorLevel : NSObject + +- (instancetype)init NS_UNAVAILABLE; + +/** Localized display name for the level, e.g. "Ground floor". */ +@property(nonatomic, copy, readonly, nullable) NSString *name; + +/** Localized short display name for the level, e.g. "1". */ +@property(nonatomic, copy, readonly, nullable) NSString *shortName; + +@end + +NS_ASSUME_NONNULL_END diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSMapLayer.h a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSMapLayer.h new file mode 100755 index 0000000..21a0c2f --- /dev/null +++ a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSMapLayer.h @@ -0,0 +1,110 @@ +// +// GMSMapLayer.h +// Google Maps SDK for iOS +// +// Copyright 2013 Google LLC +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import +#import + +#import "GMSCALayer.h" + +NS_ASSUME_NONNULL_BEGIN + +/* + * The following layer properties and constants describe the camera properties that may be animated + * on the custom model layer of a GMSMapView with Core Animation. For simple camera control and + * animation, please see the helper methods in GMSMapView+Animation.h, and the camera object + * definition within GMSCameraPosition.h. + * + * Changing layer properties triggers an implicit animation, e.g.:- + * mapView_.layer.cameraBearing = 20; + * + * An explicit animation, replacing the implicit animation, may be added after changing the + * property, for example: + *
+ *   CAMediaTimingFunction *curve = [CAMediaTimingFunction functionWithName:
+ *                                   kCAMediaTimingFunctionEaseInEaseOut];
+ *   CABasicAnimation *animation =
+ *       [CABasicAnimation animationWithKeyPath:kGMSLayerCameraBearingKey];
+ *   animation.duration = 2.0f;
+ *   animation.timingFunction = curve;
+ *   animation.toValue = @20;
+ *   [mapView_.layer addAnimation:animation forKey:kGMSLayerCameraBearingKey];
+ * 
+ * + * To control several implicit animations, Core Animation's transaction support may be used, for + * example: + *
+ *   [CATransaction begin];
+ *   [CATransaction setAnimationDuration:2.0f];
+ *   mapView_.layer.cameraBearing = 20;
+ *   mapView_.layer.cameraViewingAngle = 30;
+ *   [CATransaction commit];
+ * 
+ * + * Note that these properties are not view-based. Please see "Animating View and Layer Changes + * Together" in the + * View Programming Guide for iOS. + */ + +/** + * kGMSLayerCameraLatitudeKey ranges from [-85, 85], and values outside this range will be clamped. + * + * @related GMSMapLayer + */ +extern NSString *const kGMSLayerCameraLatitudeKey; + +/** + * kGMSLayerCameraLongitudeKey ranges from [-180, 180), and values outside this range will be + * wrapped to within this range. + * + * @related GMSMapLayer + */ +extern NSString *const kGMSLayerCameraLongitudeKey; + +/** + * kGMSLayerCameraBearingKey ranges from [0, 360), and values are wrapped. + * + * @related GMSMapLayer + */ +extern NSString *const kGMSLayerCameraBearingKey; + +/** + * kGMSLayerCameraZoomLevelKey ranges from [kGMSMinZoomLevel, kGMSMaxZoomLevel], and values are + * clamped. + * + * @related GMSMapLayer + */ +extern NSString *const kGMSLayerCameraZoomLevelKey; + +/** + * kGMSLayerCameraViewingAngleKey ranges from zero (i.e., facing straight down) and to between 30 + * and 45 degrees towards the horizon, depending on the model zoom level. + * + * @related GMSMapLayer + */ +extern NSString *const kGMSLayerCameraViewingAngleKey; + +/** + * GMSMapLayer is a custom subclass of CALayer, provided as the layer class on GMSMapView. This + * layer should not be instantiated directly. It provides model access to the camera normally + * defined on GMSMapView. + * + * Modifying or animating these properties will typically interrupt any current gesture on + * GMSMapView, e.g., a user's pan or rotation. Similarly, if a user performs an enabled gesture + * during an animation, the animation will stop 'in-place' (at the current presentation value). + */ +@interface GMSMapLayer : GMSCALayer +@property(nonatomic) CLLocationDegrees cameraLatitude; +@property(nonatomic) CLLocationDegrees cameraLongitude; +@property(nonatomic) CLLocationDirection cameraBearing; +@property(nonatomic) float cameraZoomLevel; +@property(nonatomic) double cameraViewingAngle; +@end + +NS_ASSUME_NONNULL_END diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSMapStyle.h a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSMapStyle.h new file mode 100755 index 0000000..6c1e4b7 --- /dev/null +++ a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSMapStyle.h @@ -0,0 +1,55 @@ +// +// GMSMapStyle.h +// Google Maps SDK for iOS +// +// Copyright 2016 Google LLC +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * GMSMapStyle holds details about a style which can be applied to a map. + * + * With style options you can customize the presentation of the standard Google map styles, changing + * the visual display of features like roads, parks, and other points of interest. As well as + * changing the style of these features, you can also hide features entirely. This means that you + * can emphasize particular components of the map or make the map complement the content of your + * app. + * + * For more information see: https://developers.google.com/maps/documentation/ios-sdk/styling + */ +@interface GMSMapStyle : NSObject + +/** + * Creates a style using a string containing JSON. + * + * Returns nil and populates |error| (if provided) if |style| is invalid. + */ ++ (nullable instancetype)styleWithJSONString:(NSString *)style + error:(NSError *__autoreleasing _Nullable *)error; + +/** + * Creates a style using a file containing JSON. + * + * Returns nil and populates |error| (if provided) if |style| is invalid, the file cannot be read, + * or the URL is not a file URL. + */ ++ (nullable instancetype)styleWithContentsOfFileURL:(NSURL *)fileURL + error:(NSError *__autoreleasing _Nullable *)error; + +@end + +/** + * Calculates a hash value for the given string. + * @param string The string to use to calculate the hash value. + * @return The hash value. + * @note The current implementation uses an MD5 hash, which is sufficient for uniquifying styles. + */ +NSUInteger GMSStyleHashForString(NSString *string); + +NS_ASSUME_NONNULL_END diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSMapView+Animation.h a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSMapView+Animation.h new file mode 100755 index 0000000..41e5ff0 --- /dev/null +++ a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSMapView+Animation.h @@ -0,0 +1,61 @@ +// +// GMSMapView+Animation.h +// Google Maps SDK for iOS +// +// Copyright 2013 Google LLC +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import "GMSMapView.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * GMSMapView (Animation) offers several animation helper methods. + * + * During any animation, retrieving the camera position through the camera property on GMSMapView + * returns an intermediate immutable GMSCameraPosition. This camera position will typically + * represent the most recently drawn frame. + */ +@interface GMSMapView (Animation) + +/** Animates the camera of this map to |cameraPosition|. */ +- (void)animateToCameraPosition:(GMSCameraPosition *)cameraPosition; + +/** + * As animateToCameraPosition:, but changes only the location of the camera (i.e., from the current + * location to |location|). + */ +- (void)animateToLocation:(CLLocationCoordinate2D)location; + +/** + * As animateToCameraPosition:, but changes only the zoom level of the camera. + * + * This value is clamped by [kGMSMinZoomLevel, kGMSMaxZoomLevel]. + */ +- (void)animateToZoom:(float)zoom; + +/** + * As animateToCameraPosition:, but changes only the bearing of the camera (in degrees). Zero + * indicates true north. + */ +- (void)animateToBearing:(CLLocationDirection)bearing; + +/** + * As animateToCameraPosition:, but changes only the viewing angle of the camera (in degrees). This + * value will be clamped to a minimum of zero (i.e., facing straight down) and between 30 and 45 + * degrees towards the horizon, depending on the relative closeness to the earth. + */ +- (void)animateToViewingAngle:(double)viewingAngle; + +/** + * Applies |cameraUpdate| to the current camera, and then uses the result as per + * animateToCameraPosition:. + */ +- (void)animateWithCameraUpdate:(GMSCameraUpdate *)cameraUpdate; + +@end + +NS_ASSUME_NONNULL_END diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSMapView.h a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSMapView.h new file mode 100755 index 0000000..8e223d5 --- /dev/null +++ a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSMapView.h @@ -0,0 +1,520 @@ +// +// GMSMapView.h +// Google Maps SDK for iOS +// +// Copyright 2012 Google LLC +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import +#import + +#if __has_feature(modules) +@import GoogleMapsBase; +#else +#import +#endif +#if __has_feature(modules) +@import GoogleMapsBase; +#else +#import +#endif +#import "GMSMapLayer.h" +#import "GMSUISettings.h" + +@class GMSCameraPosition; +@class GMSCameraUpdate; +@class GMSCoordinateBounds; +@class GMSIndoorDisplay; +@class GMSMapLayer; +@class GMSMapStyle; +@class GMSMapView; +@class GMSMarker; +@class GMSOverlay; +@class GMSProjection; + +NS_ASSUME_NONNULL_BEGIN + +/** Delegate for events on GMSMapView. */ +@protocol GMSMapViewDelegate + +@optional + +/** + * Called before the camera on the map changes, either due to a gesture, animation (e.g., by a user + * tapping on the "My Location" button) or by being updated explicitly via the camera or a + * zero-length animation on layer. + * + * @param mapView The map view that was tapped. + * @param gesture If YES, this is occurring due to a user gesture. +*/ +- (void)mapView:(GMSMapView *)mapView willMove:(BOOL)gesture; + +/** + * Called repeatedly during any animations or gestures on the map (or once, if the camera is + * explicitly set). This may not be called for all intermediate camera positions. It is always + * called for the final position of an animation or gesture. + */ +- (void)mapView:(GMSMapView *)mapView didChangeCameraPosition:(GMSCameraPosition *)position; + +/** + * Called when the map becomes idle, after any outstanding gestures or animations have completed (or + * after the camera has been explicitly set). + */ +- (void)mapView:(GMSMapView *)mapView idleAtCameraPosition:(GMSCameraPosition *)position; + +/** + * Called after a tap gesture at a particular coordinate, but only if a marker was not tapped. This + * is called before deselecting any currently selected marker (the implicit action for tapping on + * the map). + */ +- (void)mapView:(GMSMapView *)mapView didTapAtCoordinate:(CLLocationCoordinate2D)coordinate; + +/** + * Called after a long-press gesture at a particular coordinate. + * + * @param mapView The map view that was tapped. + * @param coordinate The location that was tapped. + */ +- (void)mapView:(GMSMapView *)mapView didLongPressAtCoordinate:(CLLocationCoordinate2D)coordinate; + +/** + * Called after a marker has been tapped. + * + * @param mapView The map view that was tapped. + * @param marker The marker that was tapped. + * @return YES if this delegate handled the tap event, which prevents the map from performing its + * default selection behavior, and NO if the map should continue with its default selection + * behavior. + */ +- (BOOL)mapView:(GMSMapView *)mapView didTapMarker:(GMSMarker *)marker; + +/** + * Called after a marker's info window has been tapped. + */ +- (void)mapView:(GMSMapView *)mapView didTapInfoWindowOfMarker:(GMSMarker *)marker; + +/** + * Called after a marker's info window has been long pressed. + */ +- (void)mapView:(GMSMapView *)mapView didLongPressInfoWindowOfMarker:(GMSMarker *)marker; + +/** + * Called after an overlay has been tapped. + * + * This method is not called for taps on markers. + * + * @param mapView The map view that was tapped. + * @param overlay The overlay that was tapped. + */ +- (void)mapView:(GMSMapView *)mapView didTapOverlay:(GMSOverlay *)overlay; + +/** + * Called after a POI has been tapped. + * + * @param mapView The map view that was tapped. + * @param placeID The placeID of the POI that was tapped. + * @param name The name of the POI that was tapped. + * @param location The location of the POI that was tapped. + */ +- (void)mapView:(GMSMapView *)mapView + didTapPOIWithPlaceID:(NSString *)placeID + name:(NSString *)name + location:(CLLocationCoordinate2D)location; + +/** + * Called when a marker is about to become selected, and provides an optional custom info window to + * use for that marker if this method returns a UIView. + * + * If you change this view after this method is called, those changes will not necessarily be + * reflected in the rendered version. + * + * The returned UIView must not have bounds greater than 500 points on either dimension. As there + * is only one info window shown at any time, the returned view may be reused between other info + * windows. + * + * Removing the marker from the map or changing the map's selected marker during this call results + * in undefined behavior. + * + * @return The custom info window for the specified marker, or nil for default + */ +- (nullable UIView *)mapView:(GMSMapView *)mapView markerInfoWindow:(GMSMarker *)marker; + +/** + * Called when mapView:markerInfoWindow: returns nil. If this method returns a view, it will be + * placed within the default info window frame. If this method returns nil, then the default + * rendering will be used instead. + * + * @param mapView The map view that was pressed. + * @param marker The marker that was pressed. + * @return The custom view to display as contents in the info window, or nil to use the default + * content rendering instead + */ + +- (nullable UIView *)mapView:(GMSMapView *)mapView markerInfoContents:(GMSMarker *)marker; + +/** + * Called when the marker's info window is closed. + */ +- (void)mapView:(GMSMapView *)mapView didCloseInfoWindowOfMarker:(GMSMarker *)marker; + +/** + * Called when dragging has been initiated on a marker. + */ +- (void)mapView:(GMSMapView *)mapView didBeginDraggingMarker:(GMSMarker *)marker; + +/** + * Called after dragging of a marker ended. + */ +- (void)mapView:(GMSMapView *)mapView didEndDraggingMarker:(GMSMarker *)marker; + +/** + * Called while a marker is dragged. + */ +- (void)mapView:(GMSMapView *)mapView didDragMarker:(GMSMarker *)marker; + +/** + * Called when the My Location button is tapped. + * + * @return YES if the listener has consumed the event (i.e., the default behavior should not occur), + * NO otherwise (i.e., the default behavior should occur). The default behavior is for the + * camera to move such that it is centered on the user location. + */ +- (BOOL)didTapMyLocationButtonForMapView:(GMSMapView *)mapView; + +/** + * Called when the My Location Dot is tapped. + * + * @param mapView The map view that was tapped. + * @param location The location of the user when the location dot was tapped. + */ +- (void)mapView:(GMSMapView *)mapView didTapMyLocation:(CLLocationCoordinate2D)location; + +/** + * Called when tiles have just been requested or labels have just started rendering. + */ +- (void)mapViewDidStartTileRendering:(GMSMapView *)mapView; + +/** + * Called when all tiles have been loaded (or failed permanently) and labels have been rendered. + */ +- (void)mapViewDidFinishTileRendering:(GMSMapView *)mapView; + +/** + * Called when map is stable (tiles loaded, labels rendered, camera idle) and overlay objects have + * been rendered. + */ +- (void)mapViewSnapshotReady:(GMSMapView *)mapView; + +@end + +/** + * \defgroup MapViewType GMSMapViewType + * @{ + */ + +/** + * Display types for GMSMapView. + */ +typedef NS_ENUM(NSUInteger, GMSMapViewType) { + /** Basic maps. The default. */ + kGMSTypeNormal GMS_SWIFT_NAME_2_0_3_0(Normal, normal) = 1, + + /** Satellite maps with no labels. */ + kGMSTypeSatellite GMS_SWIFT_NAME_2_0_3_0(Satellite, satellite), + + /** Terrain maps. */ + kGMSTypeTerrain GMS_SWIFT_NAME_2_0_3_0(Terrain, terrain), + + /** Satellite maps with a transparent label overview. */ + kGMSTypeHybrid GMS_SWIFT_NAME_2_0_3_0(Hybrid, hybrid), + + /** No maps, no labels. Display of traffic data is not supported. */ + kGMSTypeNone GMS_SWIFT_NAME_2_0_3_0(None, none), + +}; + +/**@}*/ + +/** + * \defgroup FrameRate GMSFrameRate + * @{ + */ + +/** + * Rendering frame rates for GMSMapView. + */ +typedef NS_ENUM(NSUInteger, GMSFrameRate) { + /** Use the minimum frame rate to conserve battery usage. */ + kGMSFrameRatePowerSave, + + /** + * Use a median frame rate to provide smoother rendering and conserve processing cycles. + */ + kGMSFrameRateConservative, + + /** + * Use the maximum frame rate for a device. For low end devices this will be 30 FPS, + * for high end devices 60 FPS. + */ + kGMSFrameRateMaximum, +}; + +/**@}*/ + +/** + * \defgroup MapViewPaddingAdjustmentBehavior GMSMapViewPaddingAdjustmentBehavior + * @{ + */ + +/** + * Constants indicating how safe area insets are added to padding. + */ +typedef NS_ENUM(NSUInteger, GMSMapViewPaddingAdjustmentBehavior) { + /** Always include the safe area insets in the padding. */ + kGMSMapViewPaddingAdjustmentBehaviorAlways, + + /** + * When the padding value is smaller than the safe area inset for a particular edge, use the safe + * area value for layout, else use padding. + */ + kGMSMapViewPaddingAdjustmentBehaviorAutomatic, + + /** + * Never include the safe area insets in the padding. This was the behavior prior to version 2.5. + */ + kGMSMapViewPaddingAdjustmentBehaviorNever, +}; + +/**@}*/ + +/** + * This is the main class of the Google Maps SDK for iOS and is the entry point for all methods + * related to the map. + * + * The map should be instantiated via the convenience constructor [GMSMapView mapWithFrame:camera:]. + * It may also be created with the default [[GMSMapView alloc] initWithFrame:] method (wherein its + * camera will be set to a default location). + * + * GMSMapView can only be read and modified from the main thread, similar to all UIKit objects. + * Calling these methods from another thread will result in an exception or undefined behavior. + */ +@interface GMSMapView : UIView + +/** GMSMapView delegate. */ +@property(nonatomic, weak, nullable) IBOutlet id delegate; + +/** + * Controls the camera, which defines how the map is oriented. Modification of this property is + * instantaneous. + */ +@property(nonatomic, copy) GMSCameraPosition *camera; + +/** + * Returns a GMSProjection object that you can use to convert between screen coordinates and + * latitude/longitude coordinates. + * + * This is a snapshot of the current projection, and will not automatically update when the camera + * moves. It represents either the projection of the last drawn GMSMapView frame, or; where the + * camera has been explicitly set or the map just created, the upcoming frame. It will never be nil. + */ +@property(nonatomic, readonly) GMSProjection *projection; + +/** + * Controls whether the My Location dot and accuracy circle is enabled. Defaults to NO. + */ +@property(nonatomic, getter=isMyLocationEnabled) BOOL myLocationEnabled; + +/** + * If My Location is enabled, reveals where the user location dot is being drawn. If it is disabled, + * or it is enabled but no location data is available, this will be nil. This property is + * observable using KVO. + */ +@property(nonatomic, readonly, nullable) CLLocation *myLocation; + +/** + * The marker that is selected. Setting this property selects a particular marker, showing an info + * window on it. If this property is non-nil, setting it to nil deselects the marker, hiding the + * info window. This property is observable using KVO. + */ +@property(nonatomic, nullable) GMSMarker *selectedMarker; + +/** + * Controls whether the map is drawing traffic data, if available. This is subject to the + * availability of traffic data. Defaults to NO. + */ +@property(nonatomic, getter=isTrafficEnabled) BOOL trafficEnabled; + +/** + * Controls the type of map tiles that should be displayed. Defaults to kGMSTypeNormal. + */ +@property(nonatomic) GMSMapViewType mapType; + +/** + * Controls the style of the map. + * + * A non-nil mapStyle will only apply if mapType is Normal. + */ +@property(nonatomic, nullable) GMSMapStyle *mapStyle; + +/** + * Minimum zoom (the farthest the camera may be zoomed out). Defaults to kGMSMinZoomLevel. Modified + * with -setMinZoom:maxZoom:. + */ +@property(nonatomic, readonly) float minZoom; + +/** + * Maximum zoom (the closest the camera may be to the Earth). Defaults to kGMSMaxZoomLevel. Modified + * with -setMinZoom:maxZoom:. + */ +@property(nonatomic, readonly) float maxZoom; + +/** + * If set, 3D buildings will be shown where available. Defaults to YES. + * + * This may be useful when adding a custom tile layer to the map, in order to make it clearer at + * high zoom levels. Changing this value will cause all tiles to be briefly invalidated. + */ +@property(nonatomic, getter=isBuildingsEnabled) BOOL buildingsEnabled; + +/** + * Sets whether indoor maps are shown, where available. Defaults to YES. + * + * If this is set to NO, caches for indoor data may be purged and any floor currently selected by + * the end-user may be reset. + */ +@property(nonatomic, getter=isIndoorEnabled) BOOL indoorEnabled; + +/** + * Gets the GMSIndoorDisplay instance which allows to observe or control aspects of indoor data + * display. + */ +@property(nonatomic, readonly) GMSIndoorDisplay *indoorDisplay; + +/** + * Gets the GMSUISettings object, which controls user interface settings for the map. + */ +@property(nonatomic, readonly) GMSUISettings *settings; + +/** + * Controls the 'visible' region of the view. By applying padding an area around the edge of the + * view can be created which will contain map data but will not contain UI controls. + * + * If the padding is not balanced, the visual center of the view will move as appropriate. Padding + * will also affect the |projection| property so the visible region will not include the padding + * area. GMSCameraUpdate fitToBounds will ensure that both this padding and any padding requested + * will be taken into account. + * + * This property may be animated within a UIView-based animation block. + */ +@property(nonatomic) UIEdgeInsets padding; + +/** + * Controls how safe area insets are added to the padding values. Like padding, safe area insets + * position map controls such as the compass, my location button and floor picker within the device + * safe area. + * + * Defaults to kGMSMapViewPaddingAdjustmentBehaviorAlways. + */ +@property(nonatomic) GMSMapViewPaddingAdjustmentBehavior paddingAdjustmentBehavior; + +/** + * Defaults to YES. If set to NO, GMSMapView will generate accessibility elements for overlay + * objects, such as GMSMarker and GMSPolyline. + * + * This property is as per the informal UIAcessibility protocol, except for the default value of + * YES. + */ +@property(nonatomic) BOOL accessibilityElementsHidden; + +/** + * Accessor for the custom CALayer type used for the layer. + */ +@property(nonatomic, readonly, retain) GMSMapLayer *layer; + +/** + * Controls the rendering frame rate. Default value is kGMSFrameRateMaximum. + */ +@property(nonatomic) GMSFrameRate preferredFrameRate; + +/** + * If not nil, constrains the camera target so that gestures cannot cause it to leave the specified + * bounds. + */ +@property(nonatomic, nullable) GMSCoordinateBounds *cameraTargetBounds; + +/** + * Convenience initializer that builds and returns a GMSMapView, with a frame and camera target. + */ +- (instancetype)initWithFrame:(CGRect)frame camera:(GMSCameraPosition *)camera; + +/** + * Builds and returns a GMSMapView, with a frame and camera target. + */ ++ (instancetype)mapWithFrame:(CGRect)frame camera:(GMSCameraPosition *)camera; + +/** + * Tells this map to power up its renderer. This is optional and idempotent. + */ +- (void)startRendering __GMS_AVAILABLE_BUT_DEPRECATED_MSG( + "This method is obsolete and will be removed in a future release."); + +/** + * Tells this map to power down its renderer. This is optional and idempotent. + */ +- (void)stopRendering __GMS_AVAILABLE_BUT_DEPRECATED_MSG( + "This method is obsolete and will be removed in a future release."); + +/** + * Clears all markup that has been added to the map, including markers, polylines and ground + * overlays. This will not clear the visible location dot or reset the current mapType. + */ +- (void)clear; + +/** + * Sets |minZoom| and |maxZoom|. This method expects the minimum to be less than or equal to the + * maximum, and will throw an exception with name NSRangeException otherwise. + */ +- (void)setMinZoom:(float)minZoom maxZoom:(float)maxZoom; + +/** + * Build a GMSCameraPosition that presents |bounds| with |padding|. The camera will have a zero + * bearing and tilt (i.e., facing north and looking directly at the Earth). This takes the frame and + * padding of this GMSMapView into account. + * + * If the bounds is invalid this method will return a nil camera. + */ +- (nullable GMSCameraPosition *)cameraForBounds:(GMSCoordinateBounds *)bounds + insets:(UIEdgeInsets)insets; + +/** + * Changes the camera according to |update|. The camera change is instantaneous (with no animation). + */ +- (void)moveCamera:(GMSCameraUpdate *)update; + +/** + * Check whether the given camera positions would practically cause the camera to be rendered the + * same, taking into account the level of precision and transformations used internally. + */ +- (BOOL)areEqualForRenderingPosition:(GMSCameraPosition *)position + position:(GMSCameraPosition *)otherPosition; + +@end + +/** + * Accessibility identifier for the compass button. + * + * @related GMSMapView + */ +extern NSString *const kGMSAccessibilityCompass; + +/** + * Accessibility identifier for the "my location" button. + * + * @related GMSMapView + */ +extern NSString *const kGMSAccessibilityMyLocation; + +NS_ASSUME_NONNULL_END diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSMarker.h a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSMarker.h new file mode 100755 index 0000000..e0ba822 --- /dev/null +++ a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSMarker.h @@ -0,0 +1,179 @@ +// +// GMSMarker.h +// Google Maps SDK for iOS +// +// Copyright 2012 Google LLC +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import + +#import "GMSOverlay.h" + +@class GMSMarkerLayer; +@class GMSPanoramaView; +@class UIImage; + +NS_ASSUME_NONNULL_BEGIN + +/** + * \defgroup MarkerAnimation GMSMarkerAnimation + * @{ + */ + +/** + * Animation types for GMSMarker. + */ +typedef NS_ENUM(NSUInteger, GMSMarkerAnimation) { + /** No animation (default). */ + kGMSMarkerAnimationNone = 0, + + /** The marker will pop from its groundAnchor when added. */ + kGMSMarkerAnimationPop, +}; + +/**@}*/ + +/** + * A marker is an icon placed at a particular point on the map's surface. A marker's icon is drawn + * oriented against the device's screen rather than the map's surface; i.e., it will not necessarily + * change orientation due to map rotations, tilting, or zooming. + */ +@interface GMSMarker : GMSOverlay + +/** Marker position. Animated. */ +@property(nonatomic) CLLocationCoordinate2D position; + +/** Snippet text, shown beneath the title in the info window when selected. */ +@property(nonatomic, copy, nullable) NSString *snippet; + +/** + * Marker icon to render. If left nil, uses a default SDK place marker. + * + * Supports animated images, but each frame must be the same size or the behavior is undefined. + * + * Supports the use of alignmentRectInsets to specify a reduced tap area. This also redefines how + * anchors are specified. For an animated image the value for the animation is used, not the + * individual frames. + */ +@property(nonatomic, nullable) UIImage *icon; + +/** + * Marker view to render. If left nil, falls back to the |icon| property instead. + * + * Supports animation of all animatable properties of UIView, except |frame| and |center|. Changing + * these properties or their corresponding CALayer version, including |position|, is not supported. + * + * Note that the view behaves as if |clipsToBounds| is set to YES, regardless of its actual value. + */ +@property(nonatomic, nullable) UIView *iconView; + +/** + * Controls whether the icon for this marker should be redrawn every frame. + * + * Note that when this changes from NO to YES, the icon is guaranteed to be redrawn next frame. + * + * Defaults to YES. + * Has no effect if |iconView| is nil. + */ +@property(nonatomic) BOOL tracksViewChanges; + +/** + * Controls whether the info window for this marker should be redrawn every frame. + * + * Note that when this changes from NO to YES, the info window is guaranteed to be redrawn next + * frame. + * + * Defaults to NO. + */ +@property(nonatomic) BOOL tracksInfoWindowChanges; + +/** + * The ground anchor specifies the point in the icon image that is anchored to the marker's position + * on the Earth's surface. This point is specified within the continuous space [0.0, 1.0] x [0.0, + * 1.0], where (0,0) is the top-left corner of the image, and (1,1) is the bottom-right corner. + * + * If the image has non-zero alignmentRectInsets, the top-left and bottom-right mentioned above + * refer to the inset section of the image. + */ +@property(nonatomic) CGPoint groundAnchor; + +/** + * The info window anchor specifies the point in the icon image at which to anchor the info window, + * which will be displayed directly above this point. This point is specified within the same space + * as groundAnchor. + */ +@property(nonatomic) CGPoint infoWindowAnchor; + +/** + * Controls the animation used when this marker is placed on a GMSMapView (default + * kGMSMarkerAnimationNone, no animation). + */ +@property(nonatomic) GMSMarkerAnimation appearAnimation; + +/** + * Controls whether this marker can be dragged interactively (default NO). + */ +@property(nonatomic, getter=isDraggable) BOOL draggable; + +/** + * Controls whether this marker should be flat against the Earth's surface (YES) or a billboard + * facing the camera (NO, default). + */ +@property(nonatomic, getter=isFlat) BOOL flat; + +/** + * Sets the rotation of the marker in degrees clockwise about the marker's anchor point. The axis of + * rotation is perpendicular to the marker. A rotation of 0 corresponds to the default position of + * the marker. Animated. + * + * When the marker is flat on the map, the default position is north aligned and the rotation is + * such that the marker always remains flat on the map. When the marker is a billboard, the default + * position is pointing up and the rotation is such that the marker is always facing the camera. + */ +@property(nonatomic) CLLocationDegrees rotation; + +/** + * Sets the opacity of the marker, between 0 (completely transparent) and 1 (default) inclusive. + */ +@property(nonatomic) float opacity; + +/** + * Provides the Core Animation layer for this GMSMarker. + */ +@property(nonatomic, readonly) GMSMarkerLayer *layer; + +/** + * The |panoramaView| specifies which panorama view will attempt to show this marker. Note that if + * the marker's |position| is too far away from the |panoramaView|'s current panorama location, it + * will not be displayed as it will be too small. + * + * Can be set to nil to remove the marker from any current panorama view it is attached to. + * + * A marker can be shown on both a panorama and a map at the same time. + */ +@property(nonatomic, weak, nullable) GMSPanoramaView *panoramaView; + +/** Convenience constructor for a default marker. */ ++ (instancetype)markerWithPosition:(CLLocationCoordinate2D)position; + +/** Creates a tinted version of the default marker image for use as an icon. */ ++ (UIImage *)markerImageWithColor:(nullable UIColor *)color; + +@end + +/** + * The default position of the ground anchor of a GMSMarker: the center bottom point of the marker + * icon. + */ +FOUNDATION_EXTERN const CGPoint kGMSMarkerDefaultGroundAnchor; + +/** + * The default position of the info window anchor of a GMSMarker: the center top point of the marker + * icon. + */ +FOUNDATION_EXTERN const CGPoint kGMSMarkerDefaultInfoWindowAnchor; + +NS_ASSUME_NONNULL_END diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSMarkerLayer.h a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSMarkerLayer.h new file mode 100755 index 0000000..0538955 --- /dev/null +++ a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSMarkerLayer.h @@ -0,0 +1,47 @@ +// +// GMSMarkerLayer.h +// Google Maps SDK for iOS +// +// Copyright 2013 Google LLC +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import +#import + +#import "GMSOverlayLayer.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * GMSMarkerLayer is a subclass of GMSOverlayLayer, available on a per-marker basis, that allows + * animation of several properties of its associated GMSMarker. + * + * Note that this CALayer is never actually rendered directly, as GMSMapView is provided entirely + * via an OpenGL layer. As such, adjustments or animations to 'default' properties of CALayer will + * not have any effect. + */ +@interface GMSMarkerLayer : GMSOverlayLayer + +/** Latitude, part of |position| on GMSMarker. */ +@property(nonatomic) CLLocationDegrees latitude; + +/** Longitude, part of |position| on GMSMarker. */ +@property(nonatomic) CLLocationDegrees longitude; + +/** Rotation, as per GMSMarker. */ +@property(nonatomic) CLLocationDegrees rotation; + +/** Opacity, as per GMSMarker. */ +@property(atomic) float opacity; + +@end + +extern NSString *const kGMSMarkerLayerLatitude; +extern NSString *const kGMSMarkerLayerLongitude; +extern NSString *const kGMSMarkerLayerRotation; +extern NSString *const kGMSMarkerLayerOpacity; + +NS_ASSUME_NONNULL_END diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSMutablePath.h a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSMutablePath.h new file mode 100755 index 0000000..a710b09 --- /dev/null +++ a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSMutablePath.h @@ -0,0 +1,60 @@ +// +// GMSMutablePath.h +// Google Maps SDK for iOS +// +// Copyright 2013 Google LLC +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import +#import + +#import "GMSPath.h" + +/** + * GMSMutablePath is a dynamic (resizable) array of CLLocationCoordinate2D. All coordinates must be + * valid. GMSMutablePath is the mutable counterpart to the immutable GMSPath. + */ +@interface GMSMutablePath : GMSPath + +/** Adds |coord| at the end of the path. */ +- (void)addCoordinate:(CLLocationCoordinate2D)coord; + +/** Adds a new CLLocationCoordinate2D instance with the given lat/lng. */ +- (void)addLatitude:(CLLocationDegrees)latitude longitude:(CLLocationDegrees)longitude; + +/** + * Inserts |coord| at |index|. + * + * If this is smaller than the size of the path, shifts all coordinates forward by one. Otherwise, + * behaves as replaceCoordinateAtIndex:withCoordinate:. + */ +- (void)insertCoordinate:(CLLocationCoordinate2D)coord atIndex:(NSUInteger)index; + +/** + * Replace the coordinate at |index| with |coord|. If |index| is after the end, grows the array with + * an undefined coordinate. + */ +- (void)replaceCoordinateAtIndex:(NSUInteger)index + withCoordinate:(CLLocationCoordinate2D)coord; + +/** + * Remove entry at |index|. + * + * If |index| < count decrements size. If |index| >= count this is a silent no-op. + */ +- (void)removeCoordinateAtIndex:(NSUInteger)index; + +/** + * Removes the last coordinate of the path. + * + * If the array is non-empty decrements size. If the array is empty, this is a silent no-op. + */ +- (void)removeLastCoordinate; + +/** Removes all coordinates in this path. */ +- (void)removeAllCoordinates; + +@end diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSOrientation.h a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSOrientation.h new file mode 100755 index 0000000..5d8822c --- /dev/null +++ a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSOrientation.h @@ -0,0 +1,44 @@ +// +// GMSOrientation.h +// Google Maps SDK for iOS +// +// Copyright 2013 Google LLC +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import + +/** + * GMSOrientation is a tuple of heading and pitch used to control the viewing direction of a + * GMSPanoramaCamera. + */ +typedef struct { + /** The camera heading (horizontal angle) in degrees. */ + const CLLocationDirection heading; + + /** + * The camera pitch (vertical angle), in degrees from the horizon. The |pitch| range is [-90,90], + * although it is possible that not the full range is supported. + */ + const double pitch; +} GMSOrientation; + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Returns a GMSOrientation with the given |heading| and |pitch|. + * + * @related GMSOrientation + */ +inline static GMSOrientation GMSOrientationMake(CLLocationDirection heading, double pitch) { + GMSOrientation orientation = {heading, pitch}; + return orientation; +} + +#ifdef __cplusplus +} +#endif diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSOverlay.h a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSOverlay.h new file mode 100755 index 0000000..f71f20f --- /dev/null +++ a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSOverlay.h @@ -0,0 +1,66 @@ +// +// GMSOverlay.h +// Google Maps SDK for iOS +// +// Copyright 2013 Google LLC +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import + +@class GMSMapView; + +NS_ASSUME_NONNULL_BEGIN + +/** + * GMSOverlay is an abstract class that represents some overlay that may be attached to a specific + * GMSMapView. It may not be instantiated directly; instead, instances of concrete overlay types + * should be created directly (such as GMSMarker, GMSPolyline, and GMSPolygon). + * + * This supports the NSCopying protocol; [overlay_ copy] will return a copy of the overlay type, but + * with |map| set to nil. + */ +@interface GMSOverlay : NSObject + +/** + * Title, a short description of the overlay. Some overlays, such as markers, will display the title + * on the map. The title is also the default accessibility text. + */ +@property(nonatomic, copy, nullable) NSString *title; + +/** + * The map this overlay is on. Setting this property will add the overlay to the map. Setting it to + * nil removes this overlay from the map. An overlay may be active on at most one map at any given + * time. + */ +@property(nonatomic, weak, nullable) GMSMapView *map; + +/** + * If this overlay should cause tap notifications. Some overlays, such as markers, will default to + * being tappable. + */ +@property(nonatomic, getter=isTappable) BOOL tappable; + +/** + * Higher |zIndex| value overlays will be drawn on top of lower |zIndex| value tile layers and + * overlays. Equal values result in undefined draw ordering. Markers are an exception that + * regardless of |zIndex|, they will always be drawn above tile layers and other non-marker + * overlays; they are effectively considered to be in a separate z-index group compared to other + * overlays. + */ +@property(nonatomic) int zIndex; + +/** + * Overlay data. You can use this property to associate an arbitrary object with this overlay. + * Google Maps SDK for iOS neither reads nor writes this property. + * + * Note that userData should not hold any strong references to any Maps objects, otherwise a retain + * cycle may be created (preventing objects from being released). + */ +@property(nonatomic, nullable) id userData; + +@end + +NS_ASSUME_NONNULL_END diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSOverlayLayer.h a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSOverlayLayer.h new file mode 100755 index 0000000..b0b8cf2 --- /dev/null +++ a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSOverlayLayer.h @@ -0,0 +1,30 @@ +// +// GMSOverlayLayer.h +// Google Maps SDK for iOS +// +// Copyright 2018 Google LLC +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * GMSOverlayLayer is a custom subclass of CALayer, and an abstract baseclass for GMSOverlay layers + * that allow custom animations. + * + * Note that this CALayer or any subclass are never actually rendered directly, as GMSMapView is + * provided entirely via an OpenGL layer. As such, adjustments or animations to 'default' properties + * of CALayer will not have any effect. + * + * This is an implementation detail and it should not be instantiated directly. + */ +@interface GMSOverlayLayer : CALayer + +@end + +NS_ASSUME_NONNULL_END diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSPanorama.h a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSPanorama.h new file mode 100755 index 0000000..70400f2 --- /dev/null +++ a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSPanorama.h @@ -0,0 +1,34 @@ +// +// GMSPanorama.h +// Google Maps SDK for iOS +// +// Copyright 2013 Google LLC +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import + +@class GMSPanoramaLink; + +NS_ASSUME_NONNULL_BEGIN + +/** + * GMSPanorama represents metadata for a specific panorama on the Earth. This class is not + * instantiable directly and is obtained via GMSPanoramaService or GMSPanoramaView. + */ +@interface GMSPanorama : NSObject + +/** The precise location of this panorama. */ +@property(nonatomic, readonly) CLLocationCoordinate2D coordinate; + +/** The ID of this panorama. Panoramas may change ID over time, so this should not be persisted */ +@property(nonatomic, copy, readonly) NSString *panoramaID; + +/** An array of GMSPanoramaLink describing the neighboring panoramas. */ +@property(nonatomic, copy, readonly) NSArray *links; + +@end + +NS_ASSUME_NONNULL_END diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSPanoramaCamera.h a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSPanoramaCamera.h new file mode 100755 index 0000000..4b9cc47 --- /dev/null +++ a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSPanoramaCamera.h @@ -0,0 +1,81 @@ +// +// GMSPanoramaCamera.h +// Google Maps SDK for iOS +// +// Copyright 2013 Google LLC +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import + +#import "GMSOrientation.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * GMSPanoramaCamera is used to control the viewing direction of a GMSPanoramaView. It does not + * contain information about which particular panorama should be displayed. + */ +@interface GMSPanoramaCamera : NSObject + +/** + * Designated initializer. Configures this GMSPanoramaCamera with |orientation|, |zoom| and |FOV|. + * These values will be clamped to acceptable ranges. + */ +- (id)initWithOrientation:(GMSOrientation)orientation zoom:(float)zoom FOV:(double)FOV; + +/** + * Convenience constructor specifying heading and pitch as part of |orientation|, plus |zoom| and + * default field of view (90 degrees). + */ ++ (instancetype)cameraWithOrientation:(GMSOrientation)orientation zoom:(float)zoom; + +/** + * Convenience constructor specifying |heading|, |pitch|, |zoom| with default field of view (90 + * degrees). + */ ++ (instancetype)cameraWithHeading:(CLLocationDirection)heading pitch:(double)pitch zoom:(float)zoom; + +/** + * Convenience constructor for GMSPanoramaCamera, specifying all camera properties with heading and + * pitch as part of |orientation|. + */ ++ (instancetype)cameraWithOrientation:(GMSOrientation)orientation zoom:(float)zoom FOV:(double)FOV; + +/** + * Convenience constructor for GMSPanoramaCamera, specifying all camera properties. + */ ++ (instancetype)cameraWithHeading:(CLLocationDirection)heading + pitch:(double)pitch + zoom:(float)zoom + FOV:(double)FOV; + +/** + * The field of view (FOV) encompassed by the larger dimension (width or height) of the view in + * degrees at zoom 1. This is clamped to the range [1, 160] degrees, and has a default value of 90. + * + * Lower FOV values produce a zooming in effect; larger FOV values produce an fisheye effect. + * + * Note: This is not the displayed FOV if zoom is anything other than 1. User zoom gestures + * control the zoom property, not this property. + */ +@property(nonatomic, readonly) double FOV; + +/** + * Adjusts the visible region of the screen. A zoom of N will show the same area as the central + * width/N height/N area of what is shown at zoom 1. + * + * Zoom is clamped to the implementation defined range [1, 5]. + */ +@property(nonatomic, readonly) float zoom; + +/** + * The camera orientation, which groups together heading and pitch. + */ +@property(nonatomic, readonly) GMSOrientation orientation; + +@end + +NS_ASSUME_NONNULL_END diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSPanoramaCameraUpdate.h a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSPanoramaCameraUpdate.h new file mode 100755 index 0000000..96dd4c2 --- /dev/null +++ a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSPanoramaCameraUpdate.h @@ -0,0 +1,37 @@ +// +// GMSPanoramaCameraUpdate.h +// Google Maps SDK for iOS +// +// Copyright 2013 Google LLC +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * GMSPanoramaCameraUpdate represents an update that may be applied to a GMSPanoramaView. + * It encapsulates some logic for modifying the current camera. + * It should only be constructed using the factory helper methods below. + */ +@interface GMSPanoramaCameraUpdate : NSObject + +/** Returns an update that increments the camera heading with |deltaHeading|. */ ++ (GMSPanoramaCameraUpdate *)rotateBy:(CGFloat)deltaHeading; + +/** Returns an update that sets the camera heading to the given value. */ ++ (GMSPanoramaCameraUpdate *)setHeading:(CGFloat)heading; + +/** Returns an update that sets the camera pitch to the given value. */ ++ (GMSPanoramaCameraUpdate *)setPitch:(CGFloat)pitch; + +/** Returns an update that sets the camera zoom to the given value. */ ++ (GMSPanoramaCameraUpdate *)setZoom:(CGFloat)zoom; + +@end + +NS_ASSUME_NONNULL_END diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSPanoramaLayer.h a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSPanoramaLayer.h new file mode 100755 index 0000000..4b18a87 --- /dev/null +++ a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSPanoramaLayer.h @@ -0,0 +1,57 @@ +// +// GMSPanoramaLayer.h +// Google Maps SDK for iOS +// +// Copyright 2013 Google LLC +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import +#import + +#import "GMSCALayer.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * kGMSLayerPanoramaHeadingKey ranges from [0, 360). + * + * @related GMSPanoramaLayer + */ +extern NSString *const kGMSLayerPanoramaHeadingKey; + +/** + * kGMSLayerPanoramaPitchKey ranges from [-90, 90]. + * + * @related GMSPanoramaLayer + */ +extern NSString *const kGMSLayerPanoramaPitchKey; + +/** + * kGMSLayerCameraZoomLevelKey ranges from [1, 5], default 1. + * + * @related GMSPanoramaLayer + */ +extern NSString *const kGMSLayerPanoramaZoomKey; + +/** + * kGMSLayerPanoramaFOVKey ranges from [1, 160] (in degrees), default 90. + * + * @related GMSPanoramaLayer + */ +extern NSString *const kGMSLayerPanoramaFOVKey; + +/** + * GMSPanoramaLayer is a custom subclass of CALayer, provided as the layer class on GMSPanoramaView. + * This layer should not be instantiated directly. + */ +@interface GMSPanoramaLayer : GMSCALayer +@property(nonatomic) CLLocationDirection cameraHeading; +@property(nonatomic) double cameraPitch; +@property(nonatomic) float cameraZoom; +@property(nonatomic) double cameraFOV; +@end + +NS_ASSUME_NONNULL_END diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSPanoramaLink.h a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSPanoramaLink.h new file mode 100755 index 0000000..419545f --- /dev/null +++ a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSPanoramaLink.h @@ -0,0 +1,30 @@ +// +// GMSPanoramaLink.h +// Google Maps SDK for iOS +// +// Copyright 2013 Google LLC +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** Links from a GMSPanorama to neighboring panoramas. */ +@interface GMSPanoramaLink : NSObject + +/** Angle of the neighboring panorama, clockwise from north in degrees. */ +@property(nonatomic) CGFloat heading; + +/** + * Panorama ID for the neighboring panorama. + * Do not store this persistenly, it changes in time. + */ +@property(nonatomic, copy) NSString *panoramaID; + +@end + +NS_ASSUME_NONNULL_END diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSPanoramaService.h a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSPanoramaService.h new file mode 100755 index 0000000..01ea837 --- /dev/null +++ a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSPanoramaService.h @@ -0,0 +1,83 @@ +// +// GMSPanoramaService.h +// Google Maps SDK for iOS +// +// Copyright 2013 Google LLC +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import + +#import "GMSPanoramaSource.h" + +@class GMSPanorama; + +NS_ASSUME_NONNULL_BEGIN + +/** + * Callback for when a panorama metadata becomes available. + * If an error occurred, |panorama| is nil and |error| is not nil. + * Otherwise, |panorama| is not nil and |error| is nil. + * + * @related GMSPanoramaService + */ +typedef void (^GMSPanoramaCallback)(GMSPanorama *_Nullable panorama, NSError *_Nullable error); + +/** + * GMSPanoramaService can be used to request panorama metadata even when a GMSPanoramaView is not + * active. + * + * Get an instance like this: [[GMSPanoramaService alloc] init]. + */ +@interface GMSPanoramaService : NSObject + +/** + * Retrieves information about a panorama near the given |coordinate|. + * + * This is an asynchronous request, |callback| will be called with the result. + */ +- (void)requestPanoramaNearCoordinate:(CLLocationCoordinate2D)coordinate + callback:(GMSPanoramaCallback)callback; + +/** + * Similar to requestPanoramaNearCoordinate:callback: but allows specifying a search radius (meters) + * around |coordinate|. + */ +- (void)requestPanoramaNearCoordinate:(CLLocationCoordinate2D)coordinate + radius:(NSUInteger)radius + callback:(GMSPanoramaCallback)callback; + +/** + * Similar to requestPanoramaNearCoordinate:callback: but allows specifying the panorama source type + * near the given |coordinate|. + * + * This API is experimental and may not always filter by source. + */ +- (void)requestPanoramaNearCoordinate:(CLLocationCoordinate2D)coordinate + source:(GMSPanoramaSource)source + callback:(GMSPanoramaCallback)callback; + +/** + * Similar to requestPanoramaNearCoordinate:callback: but allows specifying a search radius (meters) + * and the panorama source type near the given |coordinate|. + * + * This API is experimental and may not always filter by source. + */ +- (void)requestPanoramaNearCoordinate:(CLLocationCoordinate2D)coordinate + radius:(NSUInteger)radius + source:(GMSPanoramaSource)source + callback:(GMSPanoramaCallback)callback; + +/** + * Retrieves information about a panorama with the given |panoramaID|. + * + * |callback| will be called with the result. Only panoramaIDs obtained from the Google Maps SDK for + * iOS are supported. + */ +- (void)requestPanoramaWithID:(NSString *)panoramaID callback:(GMSPanoramaCallback)callback; + +@end + +NS_ASSUME_NONNULL_END diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSPanoramaSource.h a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSPanoramaSource.h new file mode 100755 index 0000000..f97d176 --- /dev/null +++ a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSPanoramaSource.h @@ -0,0 +1,30 @@ +// +// GMSPanoramaSource.h +// Google Maps SDK for iOS +// +// Copyright 2017 Google LLC +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import + +/** + * \defgroup PanoramaSource GMSPanoramaSource + * @{ + */ + +/** + * Source types for Panoramas. Used to specify the source of a StreetView Panorama. + * + * This API is experimental. Results may not always match expectations. + */ +typedef NS_ENUM(NSUInteger, GMSPanoramaSource) { + /** Panoramas of locations either inside or outside. */ + kGMSPanoramaSourceDefault = 0, + /** Panoramas of locations outside. */ + kGMSPanoramaSourceOutside, +}; + +/**@}*/ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSPanoramaView.h a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSPanoramaView.h new file mode 100755 index 0000000..9898bb1 --- /dev/null +++ a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSPanoramaView.h @@ -0,0 +1,300 @@ +// +// GMSPanoramaView.h +// Google Maps SDK for iOS +// +// Copyright 2013 Google LLC +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import +#import + +#import "GMSOrientation.h" +#import "GMSPanoramaLayer.h" +#import "GMSPanoramaSource.h" + +@class GMSMarker; +@class GMSPanorama; +@class GMSPanoramaCamera; +@class GMSPanoramaCameraUpdate; +@class GMSPanoramaView; + +NS_ASSUME_NONNULL_BEGIN + +/** Delegate for events on GMSPanoramaView. */ +@protocol GMSPanoramaViewDelegate +@optional + +/** + * Called when starting a move to another panorama. + * + * This can be the result of interactive navigation to a neighbouring panorama. + * + * At the moment this method is called, the |view|.panorama is still pointing to the old panorama, + * as the new panorama identified by |panoID| is not yet resolved. panoramaView:didMoveToPanorama: + * will be called when the new panorama is ready. + */ +- (void)panoramaView:(GMSPanoramaView *)view willMoveToPanoramaID:(NSString *)panoramaID; + +/** + * This is invoked every time the |view|.panorama property changes. + */ +- (void)panoramaView:(GMSPanoramaView *)view + didMoveToPanorama:(nullable GMSPanorama *)panorama; + +/** + * Called when the panorama change was caused by invoking moveToPanoramaNearCoordinate:. The + * coordinate passed to that method will also be passed here. + */ +- (void)panoramaView:(GMSPanoramaView *)view + didMoveToPanorama:(GMSPanorama *)panorama + nearCoordinate:(CLLocationCoordinate2D)coordinate; + +/** + * Called when moveNearCoordinate: produces an error. + */ +- (void)panoramaView:(GMSPanoramaView *)view + error:(NSError *)error + onMoveNearCoordinate:(CLLocationCoordinate2D)coordinate; + +/** + * Called when moveToPanoramaID: produces an error. + */ +- (void)panoramaView:(GMSPanoramaView *)view + error:(NSError *)error + onMoveToPanoramaID:(NSString *)panoramaID; + +/** + * Called repeatedly during changes to the camera on GMSPanoramaView. This may not be called for all + * intermediate camera values, but is always called for the final position of the camera after an + * animation or gesture. + */ +- (void)panoramaView:(GMSPanoramaView *)panoramaView didMoveCamera:(GMSPanoramaCamera *)camera; + +/** + * Called when a user has tapped on the GMSPanoramaView, but this tap was not consumed (taps may be + * consumed by e.g., tapping on a navigation arrow). + */ +- (void)panoramaView:(GMSPanoramaView *)panoramaView didTap:(CGPoint)point; + +/** + * Called after a marker has been tapped. May return YES to indicate the event has been fully + * handled and suppress any default behavior. + */ +- (BOOL)panoramaView:(GMSPanoramaView *)panoramaView didTapMarker:(GMSMarker *)marker; + +/** + * Called when the panorama tiles for the current view have just been requested and are beginning to + * load. + */ +- (void)panoramaViewDidStartRendering:(GMSPanoramaView *)panoramaView; + +/** + * Called when the panorama tiles have been loaded (or permanently failed to load) and rendered on + * screen. + */ +- (void)panoramaViewDidFinishRendering:(GMSPanoramaView *)panoramaView; + +@end + +/** + * A panorama is used to display Street View imagery. It should be constructed via [[GMSPanoramaView + * alloc] initWithFrame:], and configured post-initialization. + * + * All properties and methods should be accessed on the main thread, similar to all UIKit objects. + * The GMSPanoramaViewDelegate methods will also be called back only on the main thread. + * + * The backgroundColor of this view is shown while no panorama is visible, such as while it is + * loading or if the panorama is later set to nil. The alpha color of backgroundColor is not + * supported. + */ +@interface GMSPanoramaView : UIView + +/** + * The panorama to display; setting it will transition to a new panorama. This is animated, except + * for the initial panorama. + * + * Can be set to nil to clear the view. + */ +@property(nonatomic, nullable) GMSPanorama *panorama; + +/** GMSPanoramaView delegate. */ +@property(nonatomic, weak, nullable) IBOutlet id delegate; + +/** + * Sets the preference for whether all gestures should be enabled (default) or disabled. + * + * This does not limit programmatic movement of the camera or control of the panorama. + */ +- (void)setAllGesturesEnabled:(BOOL)enabled; + +/** + * Controls whether orientation gestures are enabled (default) or disabled. If enabled, users may + * use gestures to change the orientation of the camera. + * + * This does not limit programmatic movement of the camera. + */ +@property(nonatomic) BOOL orientationGestures; + +/** + * Controls whether zoom gestures are enabled (default) or disabled. If enabled, users may pinch to + * zoom the camera. + * + * This does not limit programmatic movement of the camera. + */ +@property(nonatomic) BOOL zoomGestures; + +/** + * Controls whether navigation gestures are enabled (default) or disabled. If enabled, users may use + * a single tap on navigation links or double tap the view to change panoramas. + * + * This does not limit programmatic control of the panorama. + */ +@property(nonatomic) BOOL navigationGestures; + +/** + * Controls whether the tappable navigation links are hidden or visible (default). Hidden navigation + * links cannot be tapped. + */ +@property(nonatomic) BOOL navigationLinksHidden; + +/** + * Controls whether the street name overlays are hidden or visible (default). + */ +@property(nonatomic) BOOL streetNamesHidden; + +/** + * Controls the panorama's camera. Setting a new camera here jumps to the new camera value, with no + * animation. + */ +@property(nonatomic) GMSPanoramaCamera *camera; + +/** + * Accessor for the custom CALayer type used for the layer. + */ +@property(nonatomic, readonly, retain) GMSPanoramaLayer *layer; + +/** + * Animates the camera of this GMSPanoramaView to |camera|, over |duration| (specified in seconds). + */ +- (void)animateToCamera:(GMSPanoramaCamera *)camera animationDuration:(NSTimeInterval)duration; + +/** + * Modifies the camera according to |cameraUpdate|, over |duration| (specified in seconds). + */ +- (void)updateCamera:(GMSPanoramaCameraUpdate *)cameraUpdate + animationDuration:(NSTimeInterval)duration; + +/** + * Requests a panorama near |coordinate|. + * + * Upon successful completion panoramaView:didMoveToPanorama: and + * panoramaView:didMoveToPanorama:nearCoordinate: will be sent to GMSPanoramaViewDelegate. + * + * On error panoramaView:error:onMoveNearCoordinate: will be sent. + * + * Repeated calls to moveNearCoordinate: result in the previous pending (incomplete) transitions + * being cancelled -- only the most recent of moveNearCoordinate: and moveToPanoramaId: will proceed + * and generate events. + */ +- (void)moveNearCoordinate:(CLLocationCoordinate2D)coordinate; + +/** + * Similar to moveNearCoordinate: but allows specifying a search radius (meters) around + * |coordinate|. + */ +- (void)moveNearCoordinate:(CLLocationCoordinate2D)coordinate radius:(NSUInteger)radius; + +/** + * Similar to moveNearCoordinate: but allows specifying a source near |coordinate|. + * + * This API is experimental and may not always filter by source. + */ +- (void)moveNearCoordinate:(CLLocationCoordinate2D)coordinate source:(GMSPanoramaSource)source; + +/** + * Similar to moveNearCoordinate: but allows specifying a search radius (meters) around + * |coordinate| and a source. + * + * This API is experimental and may not always filter by source. + */ +- (void)moveNearCoordinate:(CLLocationCoordinate2D)coordinate + radius:(NSUInteger)radius + source:(GMSPanoramaSource)source; + +/** + * Requests a panorama with |panoramaID|. + * + * Upon successful completion panoramaView:didMoveToPanorama: will be sent to + * GMSPanoramaViewDelegate. + * + * On error panoramaView:error:onMoveToPanoramaID: will be sent. + * + * Repeated calls to moveToPanoramaID: result in the previous pending (incomplete) transitions being + * cancelled -- only the most recent of moveNearCoordinate: and moveToPanoramaId: will proceed and + * generate events. + * + * Only panoramaIDs obtained from the Google Maps SDK for iOS are supported. + */ +- (void)moveToPanoramaID:(NSString *)panoramaID; + +/** + * For the current view, returns the screen point the |orientation| points through. This value may + * be outside the view for forward facing orientations which are far enough away from straight + * ahead. + * + * The result will contain NaNs for camera orientations which point away from the view, where the + * implied screen point would have had a negative distance from the camera in the direction of + * orientation. + */ +- (CGPoint)pointForOrientation:(GMSOrientation)orientation; + +/** + * Given a point for this view, returns the current camera orientation pointing through that screen + * location. At the center of this view, the returned GMSOrientation will be approximately equal to + * that of the current GMSPanoramaCamera. + */ +- (GMSOrientation)orientationForPoint:(CGPoint)point; + +/** + * Convenience constructor for GMSPanoramaView, which searches for and displays a GMSPanorama near + * |coordinate|. This performs a similar action to that of moveNearCoordinate:, and will call the + * same delegate methods. + */ ++ (instancetype)panoramaWithFrame:(CGRect)frame nearCoordinate:(CLLocationCoordinate2D)coordinate; + +/** + * Similar to panoramaWithFrame:nearCoordinate: but allows specifying a search radius (meters) + * around |coordinate|. + */ ++ (instancetype)panoramaWithFrame:(CGRect)frame + nearCoordinate:(CLLocationCoordinate2D)coordinate + radius:(NSUInteger)radius; + +/** + * Convenience constructor for GMSPanoramaView, which searches for and displays a GMSPanorama near + * |coordinate|. This performs a similar action to that of moveNearCoordinate:source, and will call + * the same delegate methods. + * + * This API is experimental and may not always filter by source. + */ ++ (instancetype)panoramaWithFrame:(CGRect)frame + nearCoordinate:(CLLocationCoordinate2D)coordinate + source:(GMSPanoramaSource)source; +/** + * Convenience constructor for GMSPanoramaView, which searches for and displays a GMSPanorama near + * |coordinate|. This performs a similar action to that of moveNearCoordinate:radius:source, and + * will call the same delegate methods. + * + * This API is experimental and may not always filter by source. + */ ++ (instancetype)panoramaWithFrame:(CGRect)frame + nearCoordinate:(CLLocationCoordinate2D)coordinate + radius:(NSUInteger)radius + source:(GMSPanoramaSource)source; + +@end + +NS_ASSUME_NONNULL_END diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSPath.h a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSPath.h new file mode 100755 index 0000000..15129b8 --- /dev/null +++ a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSPath.h @@ -0,0 +1,112 @@ +// +// GMSPath.h +// Google Maps SDK for iOS +// +// Copyright 2013 Google LLC +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * GMSPath encapsulates an immutable array of CLLocationCooordinate2D. All the coordinates of a + * GMSPath must be valid. The mutable counterpart is GMSMutablePath. + */ +@interface GMSPath : NSObject + +/** Convenience constructor for an empty path. */ ++ (instancetype)path; + +/** Initializes a newly allocated path with the contents of another GMSPath. */ +- (id)initWithPath:(GMSPath *)path; + +/** Get size of path. */ +- (NSUInteger)count; + +/** Returns kCLLocationCoordinate2DInvalid if |index| >= count. */ +- (CLLocationCoordinate2D)coordinateAtIndex:(NSUInteger)index; + +/** + * Initializes a newly allocated path from |encodedPath|. This format is described at: + * https://developers.google.com/maps/documentation/utilities/polylinealgorithm + */ ++ (nullable instancetype)pathFromEncodedPath:(NSString *)encodedPath; + +/** Returns an encoded string of the path in the format described above. */ +- (NSString *)encodedPath; + +/** + * Returns a new path obtained by adding |deltaLatitude| and |deltaLongitude| to each coordinate + * of the current path. Does not modify the current path. + */ +- (instancetype)pathOffsetByLatitude:(CLLocationDegrees)deltaLatitude + longitude:(CLLocationDegrees)deltaLongitude; + +@end + +/** + * kGMSEquatorProjectedMeter may be useful when specifying lengths for segment in "projected" units. + * The value of kGMSEquatorProjectedMeter, 1/(pi * EarthRadius), represents the length of one meter + * at the equator in projected units. For example to specify a projected length that corresponds + * to 100km at the equator use 100000 * kGMSEquatorProjectedMeter. + * See [GMSPath segmentsForLength:kind:], [GMSPath lengthOfKind:] and kGMSLengthProjected. + */ +extern const double kGMSEquatorProjectedMeter; + +/** + * \defgroup LengthKind GMSLengthKind + * @{ + */ + +/** + * GMSLengthKind indicates the type of a length value, which can be geodesic (in meters), rhumb + * length (in meters) and projected length (in GMSMapPoint units). + */ +typedef NS_ENUM(NSUInteger, GMSLengthKind) { + /* + * Geodesic length, in meters, along geodesic segments. May be useful, for example, to specify + * lengths along the the trajectory of airplanes or ships. + */ + kGMSLengthGeodesic, + + /* + * Rhumb length, in meters, along rhumb (straight line) segments. May be useful, for example, to + * draw a scale bar on a map. The visual size of a segment of a given length depens on the + * latitude. + */ + kGMSLengthRhumb, + + /* + * Length in projected space, along rhumb segments. Projected length uses the same units as + * GMSMapPoint - the Earth equator circumference has length 2. It is possible to specify projected + * length in units corresponding to 1 meter at the equator by multiplying with + * kGMSEquatorProjectedMeter, equal to 1/(pi * EarthRadius). + * + * Projected length may be useful, for example, to specify segments with the same visual length + * regardless of latitude. + */ + kGMSLengthProjected +}; + +/**@}*/ + +@interface GMSPath (GMSPathLength) + +/** + * Returns the fractional number of segments along the path that correspond to |length|, + * interpreted according to |kind|. See GMSLengthKind. + */ +- (double)segmentsForLength:(CLLocationDistance)length kind:(GMSLengthKind)kind; + +/** + * Returns the length of the path, according to |kind|. See GMSLengthKind. + */ +- (CLLocationDistance)lengthOfKind:(GMSLengthKind)kind; + +@end + +NS_ASSUME_NONNULL_END diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSPolygon.h a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSPolygon.h new file mode 100755 index 0000000..ff38e85 --- /dev/null +++ a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSPolygon.h @@ -0,0 +1,56 @@ +// +// GMSPolygon.h +// Google Maps SDK for iOS +// +// Copyright 2013 Google LLC +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import + +#import "GMSOverlay.h" + +@class GMSPath; +@class GMSPolygonLayer; + +NS_ASSUME_NONNULL_BEGIN + +/** + * GMSPolygon defines a polygon that appears on the map. A polygon (like a polyline) defines a + * series of connected coordinates in an ordered sequence; additionally, polygons form a closed loop + * and define a filled region. + */ +@interface GMSPolygon : GMSOverlay + +/** The path that describes this polygon. The coordinates composing the path must be valid. */ +@property(nonatomic, copy, nullable) GMSPath *path; + +/** + * The array of GMSPath instances that describes any holes in this polygon. The coordinates + * composing each path must be valid. + */ +@property(nonatomic, copy, nullable) NSArray *holes; + +/** The width of the polygon outline in screen points. Defaults to 1. */ +@property(nonatomic) CGFloat strokeWidth; + +/** The color of the polygon outline. Defaults to nil. */ +@property(nonatomic, nullable) UIColor *strokeColor; + +/** The fill color. Defaults to blueColor. */ +@property(nonatomic, nullable) UIColor *fillColor; + +/** Whether this polygon should be rendered with geodesic correction. */ +@property(nonatomic) BOOL geodesic; + +/** + * Convenience constructor for GMSPolygon for a particular path. Other properties will have default + * values. + */ ++ (instancetype)polygonWithPath:(nullable GMSPath *)path; + +@end + +NS_ASSUME_NONNULL_END diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSPolygonLayer.h a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSPolygonLayer.h new file mode 100755 index 0000000..f1860a5 --- /dev/null +++ a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSPolygonLayer.h @@ -0,0 +1,49 @@ +// +// GMSPolygonLayer.h +// Google Maps SDK for iOS +// +// Copyright 2018 Google LLC +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import +#import + +#import "GMSOverlayLayer.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * GMSPolygonLayer is a subclass of GMSOverlayLayer, available on a per-polygon basis, that allows + * animation of several properties of its associated GMSPolygon. + * + * Note that this CALayer is never actually rendered directly, as GMSMapView is provided entirely + * via an OpenGL layer. As such, adjustments or animations to 'default' properties of CALayer will + * not have any effect. + */ +@interface GMSPolygonLayer : GMSOverlayLayer + +/** The width of the polygon outline in screen points. */ +@property(nonatomic) CGFloat strokeWidth; + +/** + * The color of the polygon outline. This is an assign property, there is an expectation for the + * GMSPolygon to own the reference if necessary. + */ +@property(nonatomic, assign, nullable) CGColorRef strokeColor; + +/** + * The fill color. This is an assign property, there is an expectation for the GMSPolygon to own the + * reference if necessary. + */ +@property(nonatomic, assign, nullable) CGColorRef fillColor; + +@end + +extern NSString *const kGMSPolygonLayerStrokeWidth; +extern NSString *const kGMSPolygonLayerStrokeColor; +extern NSString *const kGMSPolygonLayerFillColor; + +NS_ASSUME_NONNULL_END diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSPolyline.h a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSPolyline.h new file mode 100755 index 0000000..9f67db1 --- /dev/null +++ a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSPolyline.h @@ -0,0 +1,61 @@ +// +// GMSPolyline.h +// Google Maps SDK for iOS +// +// Copyright 2012 Google LLC +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import + +#import "GMSOverlay.h" +#import "GMSStyleSpan.h" + +@class GMSPath; + +NS_ASSUME_NONNULL_BEGIN + +/** + * GMSPolyline specifies the available options for a polyline that exists on the Earth's surface. + * It is drawn as a physical line between the points specified in |path|. + */ +@interface GMSPolyline : GMSOverlay + +/** + * The path that describes this polyline. + */ +@property(nonatomic, copy, nullable) GMSPath *path; + +/** + * The width of the line in screen points. Defaults to 1. + */ +@property(nonatomic) CGFloat strokeWidth; + +/** + * The UIColor used to render the polyline. Defaults to [UIColor blueColor]. + */ +@property(nonatomic) UIColor *strokeColor; + +/** Whether this line should be rendered with geodesic correction. */ +@property(nonatomic) BOOL geodesic; + +/** + * Convenience constructor for GMSPolyline for a particular path. Other properties will have + * default values. + */ ++ (instancetype)polylineWithPath:(nullable GMSPath *)path; + +/** + * An array containing GMSStyleSpan, the spans used to render this polyline. + * + * If this array contains fewer segments than the polyline itself, the final segment will be applied + * over the remaining length. If this array is unset or empty, then |strokeColor| is used for the + * entire line instead. + */ +@property(nonatomic, copy, nullable) NSArray *spans; + +@end + +NS_ASSUME_NONNULL_END diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSProjection.h a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSProjection.h new file mode 100755 index 0000000..73f5b1d --- /dev/null +++ a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSProjection.h @@ -0,0 +1,76 @@ +// +// GMSProjection.h +// Google Maps SDK for iOS +// +// Copyright 2012 Google LLC +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import +#import + +/** + * GMSVisibleRegion contains the four points defining the polygon that is visible in a map's camera. + * + * This polygon can be a trapezoid instead of a rectangle, because a camera can have tilt. If the + * camera is directly over the center of the camera, the shape is rectangular, but if the camera is + * tilted, the shape will appear to be a trapezoid whose smallest side is closest to the point of + * view. + */ +typedef struct { + + /** Bottom left corner of the camera. */ + CLLocationCoordinate2D nearLeft; + + /** Bottom right corner of the camera. */ + CLLocationCoordinate2D nearRight; + + /** Far left corner of the camera. */ + CLLocationCoordinate2D farLeft; + + /** Far right corner of the camera. */ + CLLocationCoordinate2D farRight; +} GMSVisibleRegion; + +/** + * Defines a mapping between Earth coordinates (CLLocationCoordinate2D) and coordinates in the map's + * view (CGPoint). A projection is constant and immutable, in that the mapping it embodies never + * changes. The mapping is not necessarily linear. + * + * Passing invalid Earth coordinates (i.e., per CLLocationCoordinate2DIsValid) to this object may + * result in undefined behavior. + * + * This class should not be instantiated directly, instead, obtained via projection on GMSMapView. + */ +@interface GMSProjection : NSObject + +/** Maps an Earth coordinate to a point coordinate in the map's view. */ +- (CGPoint)pointForCoordinate:(CLLocationCoordinate2D)coordinate; + +/** Maps a point coordinate in the map's view to an Earth coordinate. */ +- (CLLocationCoordinate2D)coordinateForPoint:(CGPoint)point; + +/** + * Converts a distance in meters to content size. This is only accurate for small Earth distances, + * as it uses CGFloat for screen distances. + */ +- (CGFloat)pointsForMeters:(CLLocationDistance)meters + atCoordinate:(CLLocationCoordinate2D)coordinate; + +/** + * Returns whether a given coordinate (lat/lng) is contained within the projection. + */ +- (BOOL)containsCoordinate:(CLLocationCoordinate2D)coordinate; + +/** + * Returns the region (four location coordinates) that is visible according to the projection. If + * padding was set on GMSMapView, this region takes the padding into account. + * + * The visible region can be non-rectangular. The result is undefined if the projection includes + * points that do not map to anywhere on the map (e.g., camera sees outer space). + */ +- (GMSVisibleRegion)visibleRegion; + +@end diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSServices.h a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSServices.h new file mode 100755 index 0000000..8b360c8 --- /dev/null +++ a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSServices.h @@ -0,0 +1,77 @@ +// +// GMSServices.h +// Google Maps SDK for iOS +// +// Copyright 2012 Google LLC +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * Service class for the Google Maps SDK for iOS. + * + * This class is not thread safe. All methods should only be invoked on the main thread. + */ +@interface GMSServices : NSObject + +/** + * Provides the shared instance of GMSServices for the Google Maps SDK for iOS, creating it if + * necessary. Classes such as GMSMapView and GMSPanoramaView will hold this instance to provide + * their connection to Google. + * + * This is an opaque object. If your application often creates and destroys view or service classes + * provided by the Google Maps SDK for iOS, it may be useful to hold onto this object directly, as + * otherwise your connection to Google may be restarted on a regular basis. It also may be useful to + * take this object in advance of the first map creation, to reduce initial map creation performance + * cost. + * + * This method will throw an exception if provideAPIKey: has not been called. + */ ++ (id)sharedServices; + +/** + * Provides your API key to the Google Maps SDK for iOS. This key is generated for your application + * via the Google APIs Console, and is paired with your application's bundle ID to identify it. + * This must be called exactly once by your application before any iOS Maps SDK object is + * initialized. + * + * @return YES if the APIKey was successfully provided. + */ ++ (BOOL)provideAPIKey:(NSString *)APIKey; + +/** + * Provides your API options to the Google Maps SDK for iOS. Pass an array containing an NSString + * for each option. These options apply to all maps. + * + * This may be called exactly once by your application and must be called before any iOS Maps SDK + * object is initialized. + * + * @return YES if all the APIOptions were successfully provided. + */ ++ (BOOL)provideAPIOptions:(NSArray *)APIOptions; + +/** + * Returns the open source software license information for Google Maps SDK for iOS. This + * information must be made available within your application. + */ ++ (NSString *)openSourceLicenseInfo; + +/** + * Returns the version for this release of the Google Maps SDK for iOS. For example, "1.0.0" + */ ++ (NSString *)SDKVersion; + +/** + * Returns the long version for this release of the Google Maps SDK for iOS. For example, "1.0.0 + * (102.1)". + */ ++ (NSString *)SDKLongVersion; + +@end + +NS_ASSUME_NONNULL_END diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSStrokeStyle.h a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSStrokeStyle.h new file mode 100755 index 0000000..3bf1cf3 --- /dev/null +++ a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSStrokeStyle.h @@ -0,0 +1,26 @@ +// +// GMSStrokeStyle.h +// Google Maps SDK for iOS +// +// Copyright 2019 Google LLC +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** Describes the drawing style for one-dimensional entities such as polylines. */ +@interface GMSStrokeStyle : NSObject + +/** Creates a solid color stroke style. */ ++ (instancetype)solidColor:(UIColor *)color; + +/** Creates a gradient stroke style interpolating from |fromColor| to |toColor|. */ ++ (instancetype)gradientFromColor:(UIColor *)fromColor toColor:(UIColor *)toColor; + +@end + +NS_ASSUME_NONNULL_END diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSStyleSpan.h a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSStyleSpan.h new file mode 100755 index 0000000..08462bc --- /dev/null +++ a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSStyleSpan.h @@ -0,0 +1,51 @@ +// +// GMSStyleSpan.h +// Google Maps SDK for iOS +// +// Copyright 2019 Google LLC +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import + +#import "GMSStrokeStyle.h" + +NS_ASSUME_NONNULL_BEGIN +/** Describes the style for some region of a polyline. */ +@interface GMSStyleSpan : NSObject + +/** + * Factory returning a solid color span of length one segment. Equivalent to + * [GMSStyleSpan spanWithStyle:[GMSStrokeStyle solidColor:|color|] segments:1]. + */ ++ (instancetype)spanWithColor:(UIColor *)color; + +/** + * Factory returning a solid color span with a given number of segments. Equivalent to + * [GMSStyleSpan spanWithStyle:[GMSStrokeStyle solidColor:|color|] segments:|segments|]. + */ ++ (instancetype)spanWithColor:(UIColor *)color segments:(double)segments; + +/** + * Factory returning a span with the given |style| of length one segment. Equivalent to + * [GMSStyleSpan spanWithStyle:|style| segments:1]. + */ ++ (instancetype)spanWithStyle:(GMSStrokeStyle *)style; + +/** + * Factory returning a span with the given |style| and length in number of segments. + * |segments| must be greater than 0 (i.e. can't be 0). + */ ++ (instancetype)spanWithStyle:(GMSStrokeStyle *)style segments:(double)segments; + +/** The style of this span. */ +@property(nonatomic, readonly) GMSStrokeStyle *style; + +/** The length of this span in number of segments. */ +@property(nonatomic, readonly) double segments; + +@end + +NS_ASSUME_NONNULL_END diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSSyncTileLayer.h a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSSyncTileLayer.h new file mode 100755 index 0000000..ca3f91b --- /dev/null +++ a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSSyncTileLayer.h @@ -0,0 +1,35 @@ +// +// GMSSyncTileLayer.h +// Google Maps SDK for iOS +// +// Copyright 2013 Google LLC +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import + +#import "GMSTileLayer.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * GMSSyncTileLayer is an abstract subclass of GMSTileLayer that provides a sync interface to + * generate image tile data. + */ +@interface GMSSyncTileLayer : GMSTileLayer + +/** + * As per requestTileForX:y:zoom:receiver: on GMSTileLayer, but provides a synchronous interface to + * return tiles. This method may block or otherwise perform work, and is not called on the main + * thread. + * + * Calls to this method may also be made from multiple threads so implementations must be + * threadsafe. + */ +- (nullable UIImage *)tileForX:(NSUInteger)x y:(NSUInteger)y zoom:(NSUInteger)zoom; + +@end + +NS_ASSUME_NONNULL_END diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSTileLayer.h a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSTileLayer.h new file mode 100755 index 0000000..3ddb4e8 --- /dev/null +++ a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSTileLayer.h @@ -0,0 +1,105 @@ +// +// GMSTileLayer.h +// Google Maps SDK for iOS +// +// Copyright 2013 Google LLC +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import + +@class GMSMapView; + +NS_ASSUME_NONNULL_BEGIN + +/** + * Stub tile that is used to indicate that no tile exists for a specific tile coordinate. May be + * returned by tileForX:y:zoom: on GMSTileProvider. + */ +FOUNDATION_EXTERN UIImage *const kGMSTileLayerNoTile; + +/** + * GMSTileReceiver is provided to GMSTileLayer when a tile request is made, allowing the callback to + * be later (or immediately) invoked. + */ +@protocol GMSTileReceiver +- (void)receiveTileWithX:(NSUInteger)x + y:(NSUInteger)y + zoom:(NSUInteger)zoom + image:(nullable UIImage *)image; +@end + +/** + * GMSTileLayer is an abstract class that allows overlaying of custom image tiles on a specified + * GMSMapView. It may not be initialized directly, and subclasses must implement the + * tileForX:y:zoom: method to return tiles. + * + * At zoom level 0 the whole world is a square covered by a single tile, and the coordinates |x| and + * |y| are both 0 for that tile. At zoom level 1, the world is covered by 4 tiles with |x| and |y| + * being 0 or 1, and so on. + */ +@interface GMSTileLayer : NSObject + +/** + * requestTileForX:y:zoom:receiver: generates image tiles for GMSTileOverlay. It must be overridden + * by subclasses. The tile for the given |x|, |y| and |zoom| _must_ be later passed to |receiver|. + * + * Specify kGMSTileLayerNoTile if no tile is available for this location; or nil if a transient + * error occured and a tile may be available later. + * + * Calls to this method will be made on the main thread. See GMSSyncTileLayer for a base class that + * implements a blocking tile layer that does not run on your application's main thread. + */ +- (void)requestTileForX:(NSUInteger)x + y:(NSUInteger)y + zoom:(NSUInteger)zoom + receiver:(id)receiver; + +/** + * Clears the cache so that all tiles will be requested again. + */ +- (void)clearTileCache; + +/** + * The map this GMSTileOverlay is displayed on. Setting this property will add the layer to the map. + * Setting it to nil removes this layer from the map. A layer may be active on at most one map at + * any given time. + */ +@property(nonatomic, weak, nullable) GMSMapView *map; + +/** + * Higher |zIndex| value tile layers will be drawn on top of lower |zIndex| value tile layers and + * overlays. Equal values result in undefined draw ordering. + */ +@property(nonatomic) int zIndex; + +/** + * Specifies the number of pixels (not points) that the returned tile images will prefer to display + * as. For best results, this should be the edge length of your custom tiles. Defaults to 256, which + * is the traditional size of Google Maps tiles. + * + * Values less than the equivalent of 128 points (e.g. 256 pixels on retina devices) may not perform + * well and are not recommended. + * + * As an example, an application developer may wish to provide retina tiles (512 pixel edge length) + * on retina devices, to keep the same number of tiles + * per view as the default value of 256 would give on a non-retina device. + */ +@property(nonatomic) NSInteger tileSize; + +/** + * Specifies the opacity of the tile layer. This provides a multiplier for the alpha channel of tile + * images. + */ +@property(nonatomic) float opacity; + +/** + * Specifies whether the tiles should fade in. Default YES. + */ +@property(nonatomic) BOOL fadeIn; + +@end + +NS_ASSUME_NONNULL_END diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSUISettings.h a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSUISettings.h new file mode 100755 index 0000000..0b81e5c --- /dev/null +++ a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSUISettings.h @@ -0,0 +1,91 @@ +// +// GMSUISettings.h +// Google Maps SDK for iOS +// +// Copyright 2013 Google LLC +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import + +/** Settings for the user interface of a GMSMapView. */ +@interface GMSUISettings : NSObject + +/** + * Sets the preference for whether all gestures should be enabled (default) or disabled. This + * doesn't restrict users from tapping any on screen buttons to move the camera (e.g., compass or + * zoom controls), nor does it restrict programmatic movements and animation. + */ +- (void)setAllGesturesEnabled:(BOOL)enabled; + +/** + * Controls whether scroll gestures are enabled (default) or disabled. If enabled, users may drag to + * pan the camera. This does not limit programmatic movement of the camera. + */ +@property(nonatomic) BOOL scrollGestures; + +/** + * Controls whether zoom gestures are enabled (default) or disabled. If enabled, users may double + * tap/two-finger tap or pinch to zoom the camera. This does not limit programmatic movement of the + * camera. + */ +@property(nonatomic) BOOL zoomGestures; + +/** + * Controls whether tilt gestures are enabled (default) or disabled. If enabled, users may use a + * two-finger vertical down or up swipe to tilt the camera. This does not limit programmatic control + * of the camera's viewingAngle. + */ +@property(nonatomic) BOOL tiltGestures; + +/** + * Controls whether rotate gestures are enabled (default) or disabled. If enabled, users may use a + * two-finger rotate gesture to rotate the camera. This does not limit programmatic control of the + * camera's bearing. + */ +@property(nonatomic) BOOL rotateGestures; + +/** + * Controls whether gestures by users are completely consumed by the GMSMapView when gestures are + * enabled (default YES). This prevents these gestures from being received by parent views. + * + * When the GMSMapView is contained by a UIScrollView (or other scrollable area), this means that + * gestures on the map will not be additional consumed as scroll gestures. However, disabling this + * (set to NO) may be useful to support complex view hierarchies or requirements. + */ +@property(nonatomic) BOOL consumesGesturesInView; + +/** + * Enables or disables the compass. The compass is an icon on the map that indicates the direction + * of north on the map. + * + * If enabled, it is only shown when the camera is rotated away from its default orientation + * (bearing of 0). When a user taps the compass, the camera orients itself to its default + * orientation and fades away shortly after. If disabled, the compass will never be displayed. + */ +@property(nonatomic) BOOL compassButton; + +/** + * Enables or disables the My Location button. This is a button visible on the map that, when tapped + * by users, will center the map on the current user location. + */ +@property(nonatomic) BOOL myLocationButton; + +/** + * Enables (default) or disables the indoor floor picker. + * + * If enabled, it is only visible when the view is focused on a building with indoor floor data. If + * disabled, the selected floor can still be controlled programmatically via the indoorDisplay + * mapView property. + */ +@property(nonatomic) BOOL indoorPicker; + +/** + * Controls whether rotate and zoom gestures can be performed off-center and scrolled around + * (default YES). + */ +@property(nonatomic) BOOL allowScrollGesturesDuringRotateOrZoom; + +@end diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSURLTileLayer.h a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSURLTileLayer.h new file mode 100755 index 0000000..7167166 --- /dev/null +++ a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GMSURLTileLayer.h @@ -0,0 +1,56 @@ +// +// GMSURLTileLayer.h +// Google Maps SDK for iOS +// +// Copyright 2013 Google LLC +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import "GMSTileLayer.h" + +@class NSURL; + +NS_ASSUME_NONNULL_BEGIN + +/** + * |GMSTileURLConstructor| is a block taking |x|, |y| and |zoom| and returning an NSURL, or nil to + * indicate no tile for that location. + * + * @related GMSURLTileLayer + */ +typedef NSURL *_Nullable (^GMSTileURLConstructor)(NSUInteger x, NSUInteger y, NSUInteger zoom); + +/** + * GMSURLTileProvider fetches tiles based on the URLs returned from a GMSTileURLConstructor. For + * example: + *
+ *   GMSTileURLConstructor constructor = ^(NSUInteger x, NSUInteger y, NSUInteger zoom) {
+ *     NSString *URLStr =
+ *         [NSString stringWithFormat:@"https://example.com/%d/%d/%d.png", x, y, zoom];
+ *     return [NSURL URLWithString:URLStr];
+ *   };
+ *   GMSTileLayer *layer =
+ *       [GMSURLTileLayer tileLayerWithURLConstructor:constructor];
+ *   layer.userAgent = @"SDK user agent";
+ *   layer.map = map;
+ * 
+ * + * GMSURLTileProvider may not be subclassed and should only be created via its convenience + * constructor. + */ +@interface GMSURLTileLayer : GMSTileLayer + +/** Convenience constructor. |constructor| must be non-nil. */ ++ (instancetype)tileLayerWithURLConstructor:(GMSTileURLConstructor)constructor; + +/** + * Specify the user agent to describe your application. If this is nil (the default), the default + * iOS user agent is used for HTTP requests. + */ +@property(nonatomic, copy, nullable) NSString *userAgent; + +@end + +NS_ASSUME_NONNULL_END diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GoogleMaps.h a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GoogleMaps.h new file mode 100755 index 0000000..b617468 --- /dev/null +++ a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Headers/GoogleMaps.h @@ -0,0 +1,42 @@ +#import "GMSIndoorBuilding.h" +#import "GMSIndoorLevel.h" +#import "GMSAddress.h" +#import "GMSCALayer.h" +#import "GMSCameraPosition.h" +#import "GMSCameraUpdate.h" +#import "GMSCoordinateBounds+GoogleMaps.h" +#import "GMSGeocoder.h" +#import "GMSGeometryUtils.h" +#import "GMSIndoorDisplay.h" +#import "GMSMapLayer.h" +#import "GMSMapStyle.h" +#import "GMSMapView+Animation.h" +#import "GMSMapView.h" +#import "GMSMutablePath.h" +#import "GMSPath.h" +#import "GMSProjection.h" +#import "GMSServices.h" +#import "GMSUISettings.h" +#import "GMSCircle.h" +#import "GMSGroundOverlay.h" +#import "GMSMarker.h" +#import "GMSMarkerLayer.h" +#import "GMSOverlay.h" +#import "GMSOverlayLayer.h" +#import "GMSPolygon.h" +#import "GMSPolygonLayer.h" +#import "GMSPolyline.h" +#import "GMSStrokeStyle.h" +#import "GMSStyleSpan.h" +#import "GMSSyncTileLayer.h" +#import "GMSTileLayer.h" +#import "GMSURLTileLayer.h" +#import "GMSOrientation.h" +#import "GMSPanorama.h" +#import "GMSPanoramaCamera.h" +#import "GMSPanoramaCameraUpdate.h" +#import "GMSPanoramaLayer.h" +#import "GMSPanoramaLink.h" +#import "GMSPanoramaService.h" +#import "GMSPanoramaSource.h" +#import "GMSPanoramaView.h" diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Modules/module.modulemap a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Modules/module.modulemap new file mode 100755 index 0000000..d60ea20 --- /dev/null +++ a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Modules/module.modulemap @@ -0,0 +1,21 @@ +framework module GoogleMaps { + umbrella header "GoogleMaps.h" + export * + module * { export * } + link "z" + link framework "CoreData" + link framework "CoreFoundation" + link framework "CoreGraphics" + link framework "CoreImage" + link framework "CoreLocation" + link framework "CoreTelephony" + link framework "CoreText" + link framework "Foundation" + link framework "GLKit" + link framework "ImageIO" + link framework "OpenGLES" + link framework "QuartzCore" + link framework "Security" + link framework "SystemConfiguration" + link framework "UIKit" +} diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCacheStorage.momd/Storage.mom a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCacheStorage.momd/Storage.mom new file mode 100755 index 0000000..aa0d57a Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCacheStorage.momd/Storage.mom differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCacheStorage.momd/StorageWithTileProto.mom a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCacheStorage.momd/StorageWithTileProto.mom new file mode 100755 index 0000000..30b3d13 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCacheStorage.momd/StorageWithTileProto.mom differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCacheStorage.momd/StorageWithTileVersionID.mom a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCacheStorage.momd/StorageWithTileVersionID.mom new file mode 100755 index 0000000..fd72ead Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCacheStorage.momd/StorageWithTileVersionID.mom differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCacheStorage.momd/VersionInfo.plist a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCacheStorage.momd/VersionInfo.plist new file mode 100755 index 0000000..be304d9 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCacheStorage.momd/VersionInfo.plist differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/Assets.car a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/Assets.car new file mode 100755 index 0000000..d4ea703 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/Assets.car differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/DroidSansMerged-Regular.ttf a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/DroidSansMerged-Regular.ttf new file mode 100755 index 0000000..2aca5f5 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/DroidSansMerged-Regular.ttf differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/GMSNavNightModeSprites-0-1x.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/GMSNavNightModeSprites-0-1x.png new file mode 100755 index 0000000..852182e Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/GMSNavNightModeSprites-0-1x.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/GMSNavNightModeSprites-0-2x.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/GMSNavNightModeSprites-0-2x.png new file mode 100755 index 0000000..be8282a Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/GMSNavNightModeSprites-0-2x.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/GMSNavNightModeSprites-0-3x.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/GMSNavNightModeSprites-0-3x.png new file mode 100755 index 0000000..680ef5d Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/GMSNavNightModeSprites-0-3x.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/GMSNavSprites-0-1x.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/GMSNavSprites-0-1x.png new file mode 100755 index 0000000..af7815c Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/GMSNavSprites-0-1x.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/GMSNavSprites-0-2x.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/GMSNavSprites-0-2x.png new file mode 100755 index 0000000..d6e9e8f Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/GMSNavSprites-0-2x.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/GMSNavSprites-0-3x.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/GMSNavSprites-0-3x.png new file mode 100755 index 0000000..668d8fe Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/GMSNavSprites-0-3x.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/GMSSprites-0-1x.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/GMSSprites-0-1x.png new file mode 100755 index 0000000..97f63ad Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/GMSSprites-0-1x.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/GMSSprites-0-2x.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/GMSSprites-0-2x.png new file mode 100755 index 0000000..db95433 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/GMSSprites-0-2x.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/GMSSprites-0-3x.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/GMSSprites-0-3x.png new file mode 100755 index 0000000..50f9bfe Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/GMSSprites-0-3x.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/Info.plist a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/Info.plist new file mode 100755 index 0000000..569780c Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/Info.plist differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/Tharlon-Regular.ttf a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/Tharlon-Regular.ttf new file mode 100755 index 0000000..4717d70 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/Tharlon-Regular.ttf differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ar.lproj/GMSCore.strings a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ar.lproj/GMSCore.strings new file mode 100755 index 0000000..1d02d65 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ar.lproj/GMSCore.strings differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/button_background.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/button_background.png new file mode 100755 index 0000000..cec89b6 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/button_background.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/button_background@2x.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/button_background@2x.png new file mode 100755 index 0000000..7a3d29d Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/button_background@2x.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/button_background@3x.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/button_background@3x.png new file mode 100755 index 0000000..74eace5 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/button_background@3x.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/button_compass.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/button_compass.png new file mode 100755 index 0000000..11fee99 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/button_compass.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/button_compass@2x.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/button_compass@2x.png new file mode 100755 index 0000000..a73d1d6 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/button_compass@2x.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/button_compass_night.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/button_compass_night.png new file mode 100755 index 0000000..df8c234 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/button_compass_night.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/button_compass_night@2x.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/button_compass_night@2x.png new file mode 100755 index 0000000..dccbf03 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/button_compass_night@2x.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/button_my_location.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/button_my_location.png new file mode 100755 index 0000000..c09a65f Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/button_my_location.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/button_my_location@2x.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/button_my_location@2x.png new file mode 100755 index 0000000..379be62 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/button_my_location@2x.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ca.lproj/GMSCore.strings a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ca.lproj/GMSCore.strings new file mode 100755 index 0000000..8b00cef Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ca.lproj/GMSCore.strings differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/cs.lproj/GMSCore.strings a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/cs.lproj/GMSCore.strings new file mode 100755 index 0000000..219385c Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/cs.lproj/GMSCore.strings differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/da.lproj/GMSCore.strings a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/da.lproj/GMSCore.strings new file mode 100755 index 0000000..4d63559 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/da.lproj/GMSCore.strings differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/dav_one_way_16_256.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/dav_one_way_16_256.png new file mode 100755 index 0000000..7f7c2fe Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/dav_one_way_16_256.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/de.lproj/GMSCore.strings a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/de.lproj/GMSCore.strings new file mode 100755 index 0000000..b5e9cf0 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/de.lproj/GMSCore.strings differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/el.lproj/GMSCore.strings a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/el.lproj/GMSCore.strings new file mode 100755 index 0000000..48b01f2 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/el.lproj/GMSCore.strings differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/en.lproj/GMSCore.strings a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/en.lproj/GMSCore.strings new file mode 100755 index 0000000..8415be5 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/en.lproj/GMSCore.strings differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/en_AU.lproj/GMSCore.strings a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/en_AU.lproj/GMSCore.strings new file mode 100755 index 0000000..b187d5f Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/en_AU.lproj/GMSCore.strings differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/en_GB.lproj/GMSCore.strings a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/en_GB.lproj/GMSCore.strings new file mode 100755 index 0000000..b187d5f Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/en_GB.lproj/GMSCore.strings differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/en_IN.lproj/GMSCore.strings a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/en_IN.lproj/GMSCore.strings new file mode 100755 index 0000000..b187d5f Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/en_IN.lproj/GMSCore.strings differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/es.lproj/GMSCore.strings a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/es.lproj/GMSCore.strings new file mode 100755 index 0000000..96d13cf Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/es.lproj/GMSCore.strings differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/es_419.lproj/GMSCore.strings a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/es_419.lproj/GMSCore.strings new file mode 100755 index 0000000..ad91daf Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/es_419.lproj/GMSCore.strings differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/es_MX.lproj/GMSCore.strings a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/es_MX.lproj/GMSCore.strings new file mode 100755 index 0000000..ad91daf Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/es_MX.lproj/GMSCore.strings differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/fi.lproj/GMSCore.strings a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/fi.lproj/GMSCore.strings new file mode 100755 index 0000000..529b7f9 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/fi.lproj/GMSCore.strings differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/fr.lproj/GMSCore.strings a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/fr.lproj/GMSCore.strings new file mode 100755 index 0000000..6e3575c Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/fr.lproj/GMSCore.strings differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/fr_CA.lproj/GMSCore.strings a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/fr_CA.lproj/GMSCore.strings new file mode 100755 index 0000000..d6715c9 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/fr_CA.lproj/GMSCore.strings differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/he.lproj/GMSCore.strings a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/he.lproj/GMSCore.strings new file mode 100755 index 0000000..7fb5ae4 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/he.lproj/GMSCore.strings differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/hi.lproj/GMSCore.strings a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/hi.lproj/GMSCore.strings new file mode 100755 index 0000000..219a465 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/hi.lproj/GMSCore.strings differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/hr.lproj/GMSCore.strings a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/hr.lproj/GMSCore.strings new file mode 100755 index 0000000..92b7be3 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/hr.lproj/GMSCore.strings differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/hu.lproj/GMSCore.strings a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/hu.lproj/GMSCore.strings new file mode 100755 index 0000000..d07e3a7 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/hu.lproj/GMSCore.strings differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ic_compass_needle.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ic_compass_needle.png new file mode 100755 index 0000000..db933c8 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ic_compass_needle.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ic_compass_needle@2x.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ic_compass_needle@2x.png new file mode 100755 index 0000000..65fee67 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ic_compass_needle@2x.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ic_compass_needle_32pt.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ic_compass_needle_32pt.png new file mode 100755 index 0000000..a765b86 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ic_compass_needle_32pt.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ic_compass_needle_32pt@2x.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ic_compass_needle_32pt@2x.png new file mode 100755 index 0000000..01a79a6 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ic_compass_needle_32pt@2x.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ic_compass_needle_32pt@3x.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ic_compass_needle_32pt@3x.png new file mode 100755 index 0000000..90542c3 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ic_compass_needle_32pt@3x.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ic_compass_needle_large.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ic_compass_needle_large.png new file mode 100755 index 0000000..21d2e70 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ic_compass_needle_large.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ic_compass_needle_large@2x.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ic_compass_needle_large@2x.png new file mode 100755 index 0000000..3aaa92b Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ic_compass_needle_large@2x.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ic_compass_needle_large@3x.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ic_compass_needle_large@3x.png new file mode 100755 index 0000000..9cb93b7 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ic_compass_needle_large@3x.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ic_location_off.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ic_location_off.png new file mode 100755 index 0000000..2bf7984 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ic_location_off.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ic_location_off@2x.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ic_location_off@2x.png new file mode 100755 index 0000000..565195f Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ic_location_off@2x.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ic_location_off@3x.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ic_location_off@3x.png new file mode 100755 index 0000000..4be8cb3 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ic_location_off@3x.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ic_qu_direction_mylocation.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ic_qu_direction_mylocation.png new file mode 100755 index 0000000..dccdcfd Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ic_qu_direction_mylocation.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ic_qu_direction_mylocation@2x.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ic_qu_direction_mylocation@2x.png new file mode 100755 index 0000000..ccb840e Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ic_qu_direction_mylocation@2x.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ic_qu_direction_mylocation@3x.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ic_qu_direction_mylocation@3x.png new file mode 100755 index 0000000..0300f62 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ic_qu_direction_mylocation@3x.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/id.lproj/GMSCore.strings a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/id.lproj/GMSCore.strings new file mode 100755 index 0000000..d3a5ab4 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/id.lproj/GMSCore.strings differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/it.lproj/GMSCore.strings a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/it.lproj/GMSCore.strings new file mode 100755 index 0000000..df22d2d Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/it.lproj/GMSCore.strings differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ja.lproj/GMSCore.strings a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ja.lproj/GMSCore.strings new file mode 100755 index 0000000..f85cdeb Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ja.lproj/GMSCore.strings differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ko.lproj/GMSCore.strings a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ko.lproj/GMSCore.strings new file mode 100755 index 0000000..fb76068 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ko.lproj/GMSCore.strings differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/lt.lproj/GMSCore.strings a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/lt.lproj/GMSCore.strings new file mode 100755 index 0000000..1e2e24b Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/lt.lproj/GMSCore.strings differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/lv.lproj/GMSCore.strings a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/lv.lproj/GMSCore.strings new file mode 100755 index 0000000..643657d Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/lv.lproj/GMSCore.strings differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ms.lproj/GMSCore.strings a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ms.lproj/GMSCore.strings new file mode 100755 index 0000000..a663026 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ms.lproj/GMSCore.strings differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/nb.lproj/GMSCore.strings a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/nb.lproj/GMSCore.strings new file mode 100755 index 0000000..3556373 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/nb.lproj/GMSCore.strings differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/nl.lproj/GMSCore.strings a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/nl.lproj/GMSCore.strings new file mode 100755 index 0000000..e9af86b Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/nl.lproj/GMSCore.strings differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/pl.lproj/GMSCore.strings a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/pl.lproj/GMSCore.strings new file mode 100755 index 0000000..b85e6ae Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/pl.lproj/GMSCore.strings differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/polyline_colors_texture.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/polyline_colors_texture.png new file mode 100755 index 0000000..23c22ba Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/polyline_colors_texture.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/polyline_colors_texture_dim.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/polyline_colors_texture_dim.png new file mode 100755 index 0000000..0512020 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/polyline_colors_texture_dim.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/pt.lproj/GMSCore.strings a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/pt.lproj/GMSCore.strings new file mode 100755 index 0000000..febecaf Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/pt.lproj/GMSCore.strings differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/pt_BR.lproj/GMSCore.strings a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/pt_BR.lproj/GMSCore.strings new file mode 100755 index 0000000..febecaf Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/pt_BR.lproj/GMSCore.strings differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/pt_PT.lproj/GMSCore.strings a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/pt_PT.lproj/GMSCore.strings new file mode 100755 index 0000000..899875e Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/pt_PT.lproj/GMSCore.strings differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ro.lproj/GMSCore.strings a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ro.lproj/GMSCore.strings new file mode 100755 index 0000000..e1412e7 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ro.lproj/GMSCore.strings differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/road_1-1.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/road_1-1.png new file mode 100755 index 0000000..b6ba5ec Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/road_1-1.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/road_128-32.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/road_128-32.png new file mode 100755 index 0000000..08672e6 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/road_128-32.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/road_16-4.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/road_16-4.png new file mode 100755 index 0000000..ba0b0a5 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/road_16-4.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/road_2-1.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/road_2-1.png new file mode 100755 index 0000000..6317a5c Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/road_2-1.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/road_256-64.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/road_256-64.png new file mode 100755 index 0000000..45a66a4 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/road_256-64.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/road_32-8.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/road_32-8.png new file mode 100755 index 0000000..ed0424b Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/road_32-8.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/road_4-1.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/road_4-1.png new file mode 100755 index 0000000..b2efb5d Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/road_4-1.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/road_64-16.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/road_64-16.png new file mode 100755 index 0000000..664e9f6 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/road_64-16.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/road_8-2.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/road_8-2.png new file mode 100755 index 0000000..dabc352 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/road_8-2.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ru.lproj/GMSCore.strings a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ru.lproj/GMSCore.strings new file mode 100755 index 0000000..119ec97 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/ru.lproj/GMSCore.strings differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/sk.lproj/GMSCore.strings a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/sk.lproj/GMSCore.strings new file mode 100755 index 0000000..b2a5e97 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/sk.lproj/GMSCore.strings differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/sv.lproj/GMSCore.strings a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/sv.lproj/GMSCore.strings new file mode 100755 index 0000000..dc52f7d Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/sv.lproj/GMSCore.strings differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/th.lproj/GMSCore.strings a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/th.lproj/GMSCore.strings new file mode 100755 index 0000000..8a6a742 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/th.lproj/GMSCore.strings differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/tr.lproj/GMSCore.strings a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/tr.lproj/GMSCore.strings new file mode 100755 index 0000000..b737155 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/tr.lproj/GMSCore.strings differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/uk.lproj/GMSCore.strings a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/uk.lproj/GMSCore.strings new file mode 100755 index 0000000..601b80b Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/uk.lproj/GMSCore.strings differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/vi.lproj/GMSCore.strings a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/vi.lproj/GMSCore.strings new file mode 100755 index 0000000..64dc031 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/vi.lproj/GMSCore.strings differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/zh_CN.lproj/GMSCore.strings a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/zh_CN.lproj/GMSCore.strings new file mode 100755 index 0000000..6f768d6 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/zh_CN.lproj/GMSCore.strings differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/zh_HK.lproj/GMSCore.strings a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/zh_HK.lproj/GMSCore.strings new file mode 100755 index 0000000..95153ad Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/zh_HK.lproj/GMSCore.strings differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/zh_TW.lproj/GMSCore.strings a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/zh_TW.lproj/GMSCore.strings new file mode 100755 index 0000000..6fae671 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/GMSCoreResources.bundle/zh_TW.lproj/GMSCore.strings differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/Info.plist a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/Info.plist new file mode 100755 index 0000000..919fa75 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/Info.plist differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/bubble_left.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/bubble_left.png new file mode 100755 index 0000000..c8e4a41 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/bubble_left.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/bubble_left@2x.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/bubble_left@2x.png new file mode 100755 index 0000000..3e8fdca Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/bubble_left@2x.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/bubble_left@3x.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/bubble_left@3x.png new file mode 100755 index 0000000..1d8aee7 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/bubble_left@3x.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/bubble_right.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/bubble_right.png new file mode 100755 index 0000000..6189714 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/bubble_right.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/bubble_right@2x.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/bubble_right@2x.png new file mode 100755 index 0000000..8abc3f7 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/bubble_right@2x.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/bubble_right@3x.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/bubble_right@3x.png new file mode 100755 index 0000000..7c35f06 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/bubble_right@3x.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/default_marker.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/default_marker.png new file mode 100755 index 0000000..521414c Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/default_marker.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/default_marker@2x.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/default_marker@2x.png new file mode 100755 index 0000000..463657c Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/default_marker@2x.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/default_marker@3x.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/default_marker@3x.png new file mode 100755 index 0000000..d31aea6 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/default_marker@3x.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/ic_error.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/ic_error.png new file mode 100755 index 0000000..c74eda9 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/ic_error.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/ic_error@2x.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/ic_error@2x.png new file mode 100755 index 0000000..a640b5a Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/ic_error@2x.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/ic_error@3x.png a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/ic_error@3x.png new file mode 100755 index 0000000..a4ecc4b Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/ic_error@3x.png differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/oss_licenses_maps.txt.gz a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/oss_licenses_maps.txt.gz new file mode 100755 index 0000000..e302132 Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle/oss_licenses_maps.txt.gz differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMapsCore.framework/GoogleMapsCore a/Pods/GoogleMaps/Maps/Frameworks/GoogleMapsCore.framework/GoogleMapsCore new file mode 100755 index 0000000..442a75f Binary files /dev/null and a/Pods/GoogleMaps/Maps/Frameworks/GoogleMapsCore.framework/GoogleMapsCore differ diff --git b/Pods/GoogleMaps/Maps/Frameworks/GoogleMapsCore.framework/Modules/module.modulemap a/Pods/GoogleMaps/Maps/Frameworks/GoogleMapsCore.framework/Modules/module.modulemap new file mode 100755 index 0000000..28f52fc --- /dev/null +++ a/Pods/GoogleMaps/Maps/Frameworks/GoogleMapsCore.framework/Modules/module.modulemap @@ -0,0 +1,20 @@ +framework module GoogleMapsCore { + export * + module * { export * } + link "z" + link framework "CoreData" + link framework "CoreFoundation" + link framework "CoreGraphics" + link framework "CoreImage" + link framework "CoreLocation" + link framework "CoreTelephony" + link framework "CoreText" + link framework "Foundation" + link framework "GLKit" + link framework "ImageIO" + link framework "OpenGLES" + link framework "QuartzCore" + link framework "Security" + link framework "SystemConfiguration" + link framework "UIKit" +} diff --git b/Pods/GoogleMaps/README.md a/Pods/GoogleMaps/README.md new file mode 100755 index 0000000..c0c1128 --- /dev/null +++ a/Pods/GoogleMaps/README.md @@ -0,0 +1,78 @@ +# Google Maps SDK for iOS + +This pod contains the Google Maps SDK for iOS, supporting both Objective C and +Swift. + +Use the [Google Maps SDK for iOS](https://developers.google.com/maps/documentation/ios-sdk/) +to enrich your app with interactive maps and immersive street view panoramas, +and add your own custom elements such as markers, windows and polylines. + +# Getting Started + +* *Guides*: Read our [Getting Started guides](https://developers.google.com/maps/documentation/ios-sdk/intro). +* *Demo Videos*: View [pre-recorded online demos](https://developers.google.com/maps/documentation/ios-sdk/#demos). +* *Code samples*: In order to try out our demo app, use: + + ``` + $ pod try GoogleMaps + ``` + + and follow the instructions on our [developer pages](https://developers.google.com/maps/documentation/ios-sdk/code-samples). + +* *Support*: Find support from various channels and communities. + + * Support pages for [Google Maps SDK for iOS](https://developers.google.com/maps/documentation/ios-sdk/support). + * Stack Overflow, using the [google-maps](https://stackoverflow.com/questions/tagged/google-maps) + tag. + * [Google Maps APIs Premium Plan](https://developers.google.com/maps/premium/support) + customers have access to business-level support through Google's + [Enterprise Support Portal](https://google.secure.force.com/). + +* *Report issues*: Use our issue tracker to [file a bug](https://code.google.com/p/gmaps-api-issues/issues/entry?template=Maps%20SDK%20for%20iOS%20-%20Bug) + or a [feature request](https://code.google.com/p/gmaps-api-issues/issues/entry?template=Maps%20SDK%20for%20iOS%20-%20Feature%20Request). + +# Installation + +To integrate Google Maps SDK for iOS into your Xcode project using CocoaPods, +specify it in your `Podfile`: + +``` +source 'https://github.com/CocoaPods/Specs.git' +platform :ios, '9.0' +target 'YOUR_APPLICATION_TARGET_NAME_HERE' do + pod 'GoogleMaps' +end +``` + +Then, run the following command: + +``` +$ pod install +``` + +Before you can start using the API, you have to activate it in the [Google +Developer Console](https://console.developers.google.com/) and integrate the +respective API key in your project. For detailed installation instructions, +visit Google's Getting Started Guides for the [Google Maps SDK for iOS](https://developers.google.com/maps/documentation/ios-sdk/start). + +# Migration from version 1 + +If you are using the Google Places API for iOS as part of the Google Maps SDK +for iOS version 1 please check the [migration guide](https://developers.google.com/places/migrate-to-v2) +for more information on upgrading your project. + +# License and Terms of Service + +By using the Google Maps SDK for iOS you accept Google's Terms of Service and +Policies. Pay attention particularly to the following aspects: + +* Depending on your app and use case, you may be required to display + attribution. Read more about [attribution requirements](https://developers.google.com/maps/documentation/ios-sdk/intro#attribution_requirements). +* Your API usage is subject to quota limitations. Read more about [usage + limits](https://developers.google.com/maps/pricing-and-plans/). +* The [Terms of Service](https://developers.google.com/maps/terms) are a + comprehensive description of the legal contract that you enter with Google + by using the Google Maps SDK for iOS. You may want to pay special attention + to [section 10](https://developers.google.com/maps/terms#10-license-restrictions), + as it talks in detail about what you can do with the API, and what you + can't. diff --git b/Pods/GooglePlaces/CHANGELOG.md a/Pods/GooglePlaces/CHANGELOG.md new file mode 100755 index 0000000..b4ca345 --- /dev/null +++ a/Pods/GooglePlaces/CHANGELOG.md @@ -0,0 +1,2 @@ +Please go to https://developers.google.com/places/ios-sdk/releases to view the +Places iOS release notes. diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos.xcodeproj/project.pbxproj a/Pods/GooglePlaces/Example/GooglePlacesDemos.xcodeproj/project.pbxproj new file mode 100755 index 0000000..bcfcd42 --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos.xcodeproj/project.pbxproj @@ -0,0 +1,451 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 0166AF076A7926C3F6AF3E91 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F65D01DD8887E3B1B9731DAE /* LaunchScreen.storyboard */; }; + 19D03B4688CF7DF76076B137 /* AutocompleteWithTextFieldController.m in Sources */ = {isa = PBXBuildFile; fileRef = ADB9E4A950DB2EA454486E47 /* AutocompleteWithTextFieldController.m */; }; + 1F8E76499BD9AC1A594A09DF /* AutocompleteWithSearchViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 8F7419DDC82CA074FF0A508C /* AutocompleteWithSearchViewController.m */; }; + 2E76D90BDA04FEDA32FBC7EE /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D0589812EE1F702A7BBA34D /* main.m */; }; + 30DB731057D560A0F3017362 /* FindPlaceLikelihoodListViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 18AE29BAB2CC24B015F7754A /* FindPlaceLikelihoodListViewController.m */; }; + 3440EB14B9D0E24AAF030012 /* PlacesDemoAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 85EDEFE42680FF093454D1AB /* PlacesDemoAssets.xcassets */; }; + 44E1C3298B123A5107DBC3FA /* DemoAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 9B81B9326DD5B37C08B8CCAA /* DemoAppDelegate.m */; }; + 52C261A89C8177D081F0D666 /* DemoListViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 36C753B6637575A8CD81C699 /* DemoListViewController.m */; }; + 52FA2C79BAE0253BF65AD4DA /* AutocompleteBaseViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D0E6E2A1285D9A0BEBDCD503 /* AutocompleteBaseViewController.m */; }; + 68FD32D0AB7DA54A0617E79C /* DemoData.m in Sources */ = {isa = PBXBuildFile; fileRef = 4F2203BCCE0CFC3DC4F38B30 /* DemoData.m */; }; + 7DF1A5BA0B155A6414638BC6 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 97ADBE3D50D731A88C24F3F0 /* UIKit.framework */; }; + 893AFDBC37BAD181B89DBE05 /* AutocompletePushViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 1BAEA89ED8C397E73E2E9730 /* AutocompletePushViewController.m */; }; + 8C59A77A235FCAEEE2D7E6CB /* AutocompleteModalViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = E1502E6F1A86B1773138153B /* AutocompleteModalViewController.m */; }; + A3D84E176FACD02AA9FAC18C /* BaseDemoViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = A3D4BD9841268F97FD23E770 /* BaseDemoViewController.m */; }; + BDCD4957C53C15747F136768 /* AutocompleteWithCustomColors.m in Sources */ = {isa = PBXBuildFile; fileRef = FC03FEE150596E9432F612DF /* AutocompleteWithCustomColors.m */; }; + C31E5320A5DBA3B92410CC12 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 1D344C64A66F10D55590797E /* Localizable.strings */; }; + CCE1EEA311AF30C9E7273CA6 /* PagingPhotoView.m in Sources */ = {isa = PBXBuildFile; fileRef = 21153EB819432638638C2C26 /* PagingPhotoView.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 0D33295E02D9501C1D45D615 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; + 13C513E93D85CC7B765F9CDB /* AutocompleteWithCustomColors.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AutocompleteWithCustomColors.h; sourceTree = ""; }; + 16C539680ABA55CA7AFC3002 /* GooglePlacesDemos.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GooglePlacesDemos.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 18AE29BAB2CC24B015F7754A /* FindPlaceLikelihoodListViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FindPlaceLikelihoodListViewController.m; sourceTree = ""; }; + 1A982A54052D846FC64B7AB5 /* pt_BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt_BR; path = pt_BR.lproj/Localizable.strings; sourceTree = ""; }; + 1BAEA89ED8C397E73E2E9730 /* AutocompletePushViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AutocompletePushViewController.m; sourceTree = ""; }; + 1D0589812EE1F702A7BBA34D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 1D767BA5698A891C36ADF830 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = ""; }; + 1DB6FA3886B89186F55A12E3 /* ms */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ms; path = ms.lproj/Localizable.strings; sourceTree = ""; }; + 1FE971FB44A73F445279DA7F /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + 21153EB819432638638C2C26 /* PagingPhotoView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PagingPhotoView.m; sourceTree = ""; }; + 216CA36352B45FBC79C98B8F /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; + 273B1A3F7AA7EED9084B5033 /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/Localizable.strings; sourceTree = ""; }; + 2D52E39A30BD9D0AFC323F7F /* es_419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es_419; path = es_419.lproj/Localizable.strings; sourceTree = ""; }; + 2E4275BAD2E59FA193904FFC /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = pt.lproj/Localizable.strings; sourceTree = ""; }; + 31A6A624F6D9631C9BFECF9A /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = ""; }; + 36C753B6637575A8CD81C699 /* DemoListViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DemoListViewController.m; sourceTree = ""; }; + 37CA5C28547C65246AC4EAE6 /* zh_HK */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = zh_HK; path = zh_HK.lproj/Localizable.strings; sourceTree = ""; }; + 3A822A5CF223FF6ADFCA9458 /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = th.lproj/Localizable.strings; sourceTree = ""; }; + 3CB3E42FBF23DD0411D5EFB5 /* PagingPhotoView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PagingPhotoView.h; sourceTree = ""; }; + 4268AFE07F8787F0CA36AA3B /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; + 42C8FE938EFBD5D6633C190F /* hi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hi; path = hi.lproj/Localizable.strings; sourceTree = ""; }; + 4F2203BCCE0CFC3DC4F38B30 /* DemoData.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DemoData.m; sourceTree = ""; }; + 50E198C48B1A11E3B03DBE30 /* he */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/Localizable.strings; sourceTree = ""; }; + 55F92A67B1F0F18431DD759C /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/Localizable.strings; sourceTree = ""; }; + 611B5E7788AB0459C2AA1A54 /* DemoData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DemoData.h; sourceTree = ""; }; + 651835542B635C74A3D0F711 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Localizable.strings; sourceTree = ""; }; + 67BD5ABA8E74D336AE54C0B0 /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Localizable.strings; sourceTree = ""; }; + 69D781288A761B789E0F63B6 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Localizable.strings; sourceTree = ""; }; + 77688EB771E8262C6C224CE8 /* en_IN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en_IN; path = en_IN.lproj/Localizable.strings; sourceTree = ""; }; + 84E89BC0ADE5F983320B9730 /* AutocompletePushViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AutocompletePushViewController.h; sourceTree = ""; }; + 85EDEFE42680FF093454D1AB /* PlacesDemoAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = PlacesDemoAssets.xcassets; sourceTree = ""; }; + 88DC6C46CAE8787FB54161AF /* AutocompleteBaseViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AutocompleteBaseViewController.h; sourceTree = ""; }; + 8F7419DDC82CA074FF0A508C /* AutocompleteWithSearchViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AutocompleteWithSearchViewController.m; sourceTree = ""; }; + 91F10567E79D600B5D3A7385 /* DemoListViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DemoListViewController.h; sourceTree = ""; }; + 97ADBE3D50D731A88C24F3F0 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + 9B81B9326DD5B37C08B8CCAA /* DemoAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DemoAppDelegate.m; sourceTree = ""; }; + A00F12FEC785E9E287E42582 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + A06B8C341933D23425E2C178 /* AutocompleteModalViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AutocompleteModalViewController.h; sourceTree = ""; }; + A3D4BD9841268F97FD23E770 /* BaseDemoViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BaseDemoViewController.m; sourceTree = ""; }; + A8162D0DA3B592BBB6E3FA35 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; + A8233D7A6FCDAD9F3E3FFEFC /* pt_PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt_PT; path = pt_PT.lproj/Localizable.strings; sourceTree = ""; }; + A997870E831544227236DF00 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; + AADF2995CB717FE4D341C944 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Localizable.strings; sourceTree = ""; }; + ADB9E4A950DB2EA454486E47 /* AutocompleteWithTextFieldController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AutocompleteWithTextFieldController.m; sourceTree = ""; }; + AF5862DFC2F44C360067E8C5 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = ""; }; + B346F058E854F6546CE4A113 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; }; + B36F7F4AFB067A447A95D21C /* fr_CA */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr_CA; path = fr_CA.lproj/Localizable.strings; sourceTree = ""; }; + B4ABFD22EF8C141BB930DA79 /* es_MX */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es_MX; path = es_MX.lproj/Localizable.strings; sourceTree = ""; }; + B89AD97A10365521ED09C03F /* en_AU */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en_AU; path = en_AU.lproj/Localizable.strings; sourceTree = ""; }; + BB25E63BA7525B4F9C996E94 /* zh_CN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = zh_CN; path = zh_CN.lproj/Localizable.strings; sourceTree = ""; }; + C4230A88E51B60A9F4C0CF9B /* BaseDemoViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BaseDemoViewController.h; sourceTree = ""; }; + CBCF32F12AADFF34B1745273 /* zh_TW */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = zh_TW; path = zh_TW.lproj/Localizable.strings; sourceTree = ""; }; + CE0FA365A96C1D97CE01B652 /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/Localizable.strings; sourceTree = ""; }; + CF1B1E3D7B5C131B7DF1C10C /* AutocompleteWithSearchViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AutocompleteWithSearchViewController.h; sourceTree = ""; }; + D0E6E2A1285D9A0BEBDCD503 /* AutocompleteBaseViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AutocompleteBaseViewController.m; sourceTree = ""; }; + D1C229B5A414955539EBE107 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; + D5401C957612DEF87E02DC2C /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Localizable.strings; sourceTree = ""; }; + D979103A75E016981230D84D /* FindPlaceLikelihoodListViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FindPlaceLikelihoodListViewController.h; sourceTree = ""; }; + DAB6D5F45E10BD22F8FBB4BA /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/Localizable.strings; sourceTree = ""; }; + E008571503BEEFC5C2BF5D66 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = ""; }; + E0C040E0617744F5A55C8922 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/Localizable.strings; sourceTree = ""; }; + E1502E6F1A86B1773138153B /* AutocompleteModalViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AutocompleteModalViewController.m; sourceTree = ""; }; + E766ACF338BF4D9D9B3C86BA /* en_GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en_GB; path = en_GB.lproj/Localizable.strings; sourceTree = ""; }; + E8EBD6572BDD22DD2C8C37F0 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/Localizable.strings; sourceTree = ""; }; + F2689EA2B2A4F7DC9912D5DE /* AutocompleteWithTextFieldController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AutocompleteWithTextFieldController.h; sourceTree = ""; }; + F3A846055E79359BAF267E70 /* SDKDemoAPIKey.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDKDemoAPIKey.h; sourceTree = ""; }; + F7EFEB4946D6A093B9475CDD /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Localizable.strings; sourceTree = ""; }; + F8ABF15D04AF351D0A21A5CD /* DemoAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DemoAppDelegate.h; sourceTree = ""; }; + FA2B20801D4CBBF7439CF693 /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/Localizable.strings; sourceTree = ""; }; + FB0BACDCF9A220122AFB87B5 /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/Localizable.strings; sourceTree = ""; }; + FC03FEE150596E9432F612DF /* AutocompleteWithCustomColors.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AutocompleteWithCustomColors.m; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + A91F0EDBE05C0121150AEE46 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 7DF1A5BA0B155A6414638BC6 /* UIKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 5451EE1805656445BDA54845 /* Resources */ = { + isa = PBXGroup; + children = ( + F65D01DD8887E3B1B9731DAE /* LaunchScreen.storyboard */, + 1D344C64A66F10D55590797E /* Localizable.strings */, + 85EDEFE42680FF093454D1AB /* PlacesDemoAssets.xcassets */, + ); + path = Resources; + sourceTree = ""; + }; + 5EDD6C0FADBB29C5650216AA /* Autocomplete */ = { + isa = PBXGroup; + children = ( + 88DC6C46CAE8787FB54161AF /* AutocompleteBaseViewController.h */, + D0E6E2A1285D9A0BEBDCD503 /* AutocompleteBaseViewController.m */, + A06B8C341933D23425E2C178 /* AutocompleteModalViewController.h */, + E1502E6F1A86B1773138153B /* AutocompleteModalViewController.m */, + 84E89BC0ADE5F983320B9730 /* AutocompletePushViewController.h */, + 1BAEA89ED8C397E73E2E9730 /* AutocompletePushViewController.m */, + 13C513E93D85CC7B765F9CDB /* AutocompleteWithCustomColors.h */, + FC03FEE150596E9432F612DF /* AutocompleteWithCustomColors.m */, + CF1B1E3D7B5C131B7DF1C10C /* AutocompleteWithSearchViewController.h */, + 8F7419DDC82CA074FF0A508C /* AutocompleteWithSearchViewController.m */, + F2689EA2B2A4F7DC9912D5DE /* AutocompleteWithTextFieldController.h */, + ADB9E4A950DB2EA454486E47 /* AutocompleteWithTextFieldController.m */, + ); + path = Autocomplete; + sourceTree = ""; + }; + 6474F6E51B1B62FD7E19E38F /* Source */ = { + isa = PBXGroup; + children = ( + E437630B6C8A8B3D1C7679DE /* GooglePlacesDemos */, + ); + name = Source; + sourceTree = ""; + }; + 7C4A8D45D6D1B907A8C0521E = { + isa = PBXGroup; + children = ( + 6474F6E51B1B62FD7E19E38F /* Source */, + AB0178922FA6ECEABB9EFFE7 /* Frameworks */, + 87ED31F365F5BCA83FBDE8E8 /* Products */, + ); + sourceTree = ""; + }; + 7ECF3926AB3331C3F787BF0A /* Support */ = { + isa = PBXGroup; + children = ( + C4230A88E51B60A9F4C0CF9B /* BaseDemoViewController.h */, + A3D4BD9841268F97FD23E770 /* BaseDemoViewController.m */, + ); + path = Support; + sourceTree = ""; + }; + 87ED31F365F5BCA83FBDE8E8 /* Products */ = { + isa = PBXGroup; + children = ( + 16C539680ABA55CA7AFC3002 /* GooglePlacesDemos.app */, + ); + name = Products; + sourceTree = ""; + }; + AB0178922FA6ECEABB9EFFE7 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 97ADBE3D50D731A88C24F3F0 /* UIKit.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + D21615D02D1F3EB36750BF21 /* Samples */ = { + isa = PBXGroup; + children = ( + 5EDD6C0FADBB29C5650216AA /* Autocomplete */, + D979103A75E016981230D84D /* FindPlaceLikelihoodListViewController.h */, + 18AE29BAB2CC24B015F7754A /* FindPlaceLikelihoodListViewController.m */, + 3CB3E42FBF23DD0411D5EFB5 /* PagingPhotoView.h */, + 21153EB819432638638C2C26 /* PagingPhotoView.m */, + ); + path = Samples; + sourceTree = ""; + }; + E437630B6C8A8B3D1C7679DE /* GooglePlacesDemos */ = { + isa = PBXGroup; + children = ( + 5451EE1805656445BDA54845 /* Resources */, + D21615D02D1F3EB36750BF21 /* Samples */, + 7ECF3926AB3331C3F787BF0A /* Support */, + F8ABF15D04AF351D0A21A5CD /* DemoAppDelegate.h */, + 9B81B9326DD5B37C08B8CCAA /* DemoAppDelegate.m */, + 611B5E7788AB0459C2AA1A54 /* DemoData.h */, + 4F2203BCCE0CFC3DC4F38B30 /* DemoData.m */, + 91F10567E79D600B5D3A7385 /* DemoListViewController.h */, + 36C753B6637575A8CD81C699 /* DemoListViewController.m */, + F3A846055E79359BAF267E70 /* SDKDemoAPIKey.h */, + 1D0589812EE1F702A7BBA34D /* main.m */, + ); + path = GooglePlacesDemos; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 71EABCA8BFCB342F947AF4D2 /* GooglePlacesDemos */ = { + isa = PBXNativeTarget; + buildConfigurationList = E489F9A9DC7E458708AB572E /* Build configuration list for PBXNativeTarget "GooglePlacesDemos" */; + buildPhases = ( + 2A017491F6C5A9DBB4F6E6F6 /* Resources */, + 45EF04B6738E3486BE121AEC /* Sources */, + A91F0EDBE05C0121150AEE46 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = GooglePlacesDemos; + productName = GooglePlacesDemos; + productReference = 16C539680ABA55CA7AFC3002 /* GooglePlacesDemos.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + B0EA45FFFB815F6F5A3E1BDD /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + }; + buildConfigurationList = 63964CA36C9733EFF27C66CD /* Build configuration list for PBXProject "GooglePlacesDemos" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = en; + hasScannedForEncodings = 1; + knownRegions = ( + en, + Base, + ar, + ca, + cs, + da, + de, + el, + "en-AU", + "en-GB", + "en-IN", + es, + "es-419", + "es-MX", + fi, + fr, + "fr-CA", + he, + hi, + hr, + hu, + id, + it, + ja, + ko, + ms, + nb, + nl, + pl, + pt, + "pt-BR", + "pt-PT", + ro, + ru, + sk, + sv, + th, + tr, + uk, + vi, + "zh-Hans", + "zh-HK", + "zh-Hant", + ); + mainGroup = 7C4A8D45D6D1B907A8C0521E; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 71EABCA8BFCB342F947AF4D2 /* GooglePlacesDemos */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 2A017491F6C5A9DBB4F6E6F6 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0166AF076A7926C3F6AF3E91 /* LaunchScreen.storyboard in Resources */, + C31E5320A5DBA3B92410CC12 /* Localizable.strings in Resources */, + 3440EB14B9D0E24AAF030012 /* PlacesDemoAssets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 45EF04B6738E3486BE121AEC /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 44E1C3298B123A5107DBC3FA /* DemoAppDelegate.m in Sources */, + 68FD32D0AB7DA54A0617E79C /* DemoData.m in Sources */, + 52C261A89C8177D081F0D666 /* DemoListViewController.m in Sources */, + 2E76D90BDA04FEDA32FBC7EE /* main.m in Sources */, + 52FA2C79BAE0253BF65AD4DA /* AutocompleteBaseViewController.m in Sources */, + 8C59A77A235FCAEEE2D7E6CB /* AutocompleteModalViewController.m in Sources */, + 893AFDBC37BAD181B89DBE05 /* AutocompletePushViewController.m in Sources */, + BDCD4957C53C15747F136768 /* AutocompleteWithCustomColors.m in Sources */, + 1F8E76499BD9AC1A594A09DF /* AutocompleteWithSearchViewController.m in Sources */, + 19D03B4688CF7DF76076B137 /* AutocompleteWithTextFieldController.m in Sources */, + 30DB731057D560A0F3017362 /* FindPlaceLikelihoodListViewController.m in Sources */, + CCE1EEA311AF30C9E7273CA6 /* PagingPhotoView.m in Sources */, + A3D84E176FACD02AA9FAC18C /* BaseDemoViewController.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 1D344C64A66F10D55590797E /* Localizable.strings */ = { + isa = PBXVariantGroup; + children = ( + E0C040E0617744F5A55C8922 /* ar */, + FA2B20801D4CBBF7439CF693 /* ca */, + 31A6A624F6D9631C9BFECF9A /* cs */, + 69D781288A761B789E0F63B6 /* da */, + A8162D0DA3B592BBB6E3FA35 /* de */, + 273B1A3F7AA7EED9084B5033 /* el */, + 1FE971FB44A73F445279DA7F /* en */, + B89AD97A10365521ED09C03F /* en_AU */, + E766ACF338BF4D9D9B3C86BA /* en_GB */, + 77688EB771E8262C6C224CE8 /* en_IN */, + A997870E831544227236DF00 /* es */, + 2D52E39A30BD9D0AFC323F7F /* es_419 */, + B4ABFD22EF8C141BB930DA79 /* es_MX */, + D5401C957612DEF87E02DC2C /* fi */, + D1C229B5A414955539EBE107 /* fr */, + B36F7F4AFB067A447A95D21C /* fr_CA */, + 50E198C48B1A11E3B03DBE30 /* he */, + 42C8FE938EFBD5D6633C190F /* hi */, + FB0BACDCF9A220122AFB87B5 /* hr */, + DAB6D5F45E10BD22F8FBB4BA /* hu */, + 55F92A67B1F0F18431DD759C /* id */, + B346F058E854F6546CE4A113 /* it */, + 4268AFE07F8787F0CA36AA3B /* ja */, + E008571503BEEFC5C2BF5D66 /* ko */, + 1DB6FA3886B89186F55A12E3 /* ms */, + AADF2995CB717FE4D341C944 /* nb */, + 0D33295E02D9501C1D45D615 /* nl */, + AF5862DFC2F44C360067E8C5 /* pl */, + 2E4275BAD2E59FA193904FFC /* pt */, + 1A982A54052D846FC64B7AB5 /* pt_BR */, + A8233D7A6FCDAD9F3E3FFEFC /* pt_PT */, + E8EBD6572BDD22DD2C8C37F0 /* ro */, + 216CA36352B45FBC79C98B8F /* ru */, + CE0FA365A96C1D97CE01B652 /* sk */, + 1D767BA5698A891C36ADF830 /* sv */, + 3A822A5CF223FF6ADFCA9458 /* th */, + F7EFEB4946D6A093B9475CDD /* tr */, + 67BD5ABA8E74D336AE54C0B0 /* uk */, + 651835542B635C74A3D0F711 /* vi */, + BB25E63BA7525B4F9C996E94 /* zh_CN */, + 37CA5C28547C65246AC4EAE6 /* zh_HK */, + CBCF32F12AADFF34B1745273 /* zh_TW */, + ); + name = Localizable.strings; + sourceTree = ""; + }; + F65D01DD8887E3B1B9731DAE /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + A00F12FEC785E9E287E42582 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 1BA056F14489621B09D65C69 /* Default */ = { + isa = XCBuildConfiguration; + buildSettings = { + INTERMEDIATE_DIR = "$(PROJECT_DERIVED_FILE_DIR)/$(CONFIGURATION)"; + SDKROOT = iphoneos; + SHARED_INTERMEDIATE_DIR = "$(SYMROOT)/DerivedSources/$(CONFIGURATION)"; + }; + name = Default; + }; + C88A918BB441623AA489A34D /* Default */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_OBJC_ARC = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + INFOPLIST_FILE = ./GooglePlacesDemos/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + LIBRARY_SEARCH_PATHS = ( + ., + "$(SDKROOT)/System/Library/Frameworks", + ); + PRODUCT_NAME = GooglePlacesDemos; + TARGETED_DEVICE_FAMILY = "1,2"; + USER_HEADER_SEARCH_PATHS = "$(SRCROOT)"; + USE_HEADERMAP = NO; + WRAPPER_PREFIX = ""; + }; + name = Default; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 63964CA36C9733EFF27C66CD /* Build configuration list for PBXProject "GooglePlacesDemos" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1BA056F14489621B09D65C69 /* Default */, + ); + defaultConfigurationIsVisible = 1; + defaultConfigurationName = Default; + }; + E489F9A9DC7E458708AB572E /* Build configuration list for PBXNativeTarget "GooglePlacesDemos" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C88A918BB441623AA489A34D /* Default */, + ); + defaultConfigurationIsVisible = 1; + defaultConfigurationName = Default; + }; +/* End XCConfigurationList section */ + }; + rootObject = B0EA45FFFB815F6F5A3E1BDD /* Project object */; +} diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/DemoAppDelegate.h a/Pods/GooglePlaces/Example/GooglePlacesDemos/DemoAppDelegate.h new file mode 100755 index 0000000..37b7d28 --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/DemoAppDelegate.h @@ -0,0 +1,22 @@ +/* + * Copyright 2016 Google Inc. All rights reserved. + * + * + * 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 + +@interface DemoAppDelegate : UIResponder + +@property(strong, nonatomic) UIWindow *window; + +@end diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/DemoAppDelegate.m a/Pods/GooglePlaces/Example/GooglePlacesDemos/DemoAppDelegate.m new file mode 100755 index 0000000..dea71d7 --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/DemoAppDelegate.m @@ -0,0 +1,65 @@ +/* + * Copyright 2016 Google Inc. All rights reserved. + * + * + * 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 "GooglePlacesDemos/DemoAppDelegate.h" + +#import +#import "GooglePlacesDemos/DemoData.h" +#import "GooglePlacesDemos/DemoListViewController.h" +#import "GooglePlacesDemos/SDKDemoAPIKey.h" + +@implementation DemoAppDelegate + +- (BOOL)application:(UIApplication *)application + didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + NSLog(@"Build version: %s", __VERSION__); + + // Do a quick check to see if you've provided an API key, in a real app you wouldn't need this but + // for the demo it means we can provide a better error message. + if (!kAPIKey.length) { + // Blow up if APIKeys have not yet been set. + NSString *bundleId = [[NSBundle mainBundle] bundleIdentifier]; + NSString *format = @"Configure APIKeys inside SDKDemoAPIKey.h for your bundle `%@`, see " + @"README.GooglePlacesDemos for more information"; + @throw [NSException exceptionWithName:@"DemoAppDelegate" + reason:[NSString stringWithFormat:format, bundleId] + userInfo:nil]; + } + + // Provide the Places SDK with your API key. + [GMSPlacesClient provideAPIKey:kAPIKey]; + + // Log the required open source licenses! Yes, just NSLog-ing them is not enough but is good for + // a demo. + NSLog(@"Google Places open source licenses:\n%@", [GMSPlacesClient openSourceLicenseInfo]); + + // Manually create a window. If you are using a storyboard in your own app you can ignore the rest + // of this method. + self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; + + // Create our view controller with the list of demos. + DemoData *demoData = [[DemoData alloc] init]; + DemoListViewController *masterViewController = + [[DemoListViewController alloc] initWithDemoData:demoData]; + UINavigationController *masterNavigationController = + [[UINavigationController alloc] initWithRootViewController:masterViewController]; + self.window.rootViewController = masterNavigationController; + + [self.window makeKeyAndVisible]; + + return YES; +} + +@end diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/DemoData.h a/Pods/GooglePlaces/Example/GooglePlacesDemos/DemoData.h new file mode 100755 index 0000000..05f56fc --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/DemoData.h @@ -0,0 +1,97 @@ +/* + * Copyright 2016 Google Inc. All rights reserved. + * + * + * 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 + +#import + +/* + * This file contains a set of data objects which represent the list of demos which are provided by + * this sample app. + */ + +/** + * Represents a specific demo sample, stores the title and the name of the view controller which + * contains the demo code. + */ +@interface Demo : NSObject + +/** + * The title of the demo. This is displayed in the list of demos. + */ +@property(nonatomic, readonly) NSString *title; + +/** + * Construct a |Demo| object with the specified view controller which contains the demo code. + * + * @param viewControllerClass The class of the view controller to display when the demo is selected + * from the list. + */ +- (instancetype)initWithViewControllerClass:(Class)viewControllerClass; + +/** + * Construct and return a new UIViewController instance which contains the view to present when the + * demo is selected from the list. + * + * @param autocompleteFilter The |GMSAutocompleteFilter| that filters on types and countries. + * @param placeField The |GMSPlaceField| to request individual fields for the |GMSPlace| result. + */ +- (UIViewController *)createViewControllerWithAutocompleteFilter: + (GMSAutocompleteFilter *)autocompleteFilter + placeFields:(GMSPlaceField)placeField; + +@end + +/** + * A group of demos which comprise a section in the list of demos. + */ +@interface DemoSection : NSObject + +/** + * The title of the section. + */ +@property(nonatomic, readonly) NSString *title; + +/** + * The list of demos which are contained in the section. + */ +@property(nonatomic, readonly) NSArray *demos; + +/** + * Initialise a |DemoSection| with the specified title and list of demos. + * + * @param title The title of the section. + * @param demos The demos contained in the section. + */ +- (instancetype)initWithTitle:(NSString *)title demos:(NSArray *)demos; + +@end + +/** + * A class which encapsulates the data required to create and display demos. + */ +@interface DemoData : NSObject + +/** + * A list of sections to display. + */ +@property(nonatomic, readonly) NSArray *sections; + +/** + * The first demo to display when launched in side-by-side mode. + */ +@property(nonatomic, readonly) Demo *firstDemo; + +@end diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/DemoData.m a/Pods/GooglePlaces/Example/GooglePlacesDemos/DemoData.m new file mode 100755 index 0000000..b76b8ef --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/DemoData.m @@ -0,0 +1,102 @@ +/* + * Copyright 2016 Google Inc. All rights reserved. + * + * + * 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 "GooglePlacesDemos/DemoData.h" + +#import "GooglePlacesDemos/Samples/Autocomplete/AutocompleteModalViewController.h" +#import "GooglePlacesDemos/Samples/Autocomplete/AutocompletePushViewController.h" +#import "GooglePlacesDemos/Samples/Autocomplete/AutocompleteWithCustomColors.h" +#import "GooglePlacesDemos/Samples/Autocomplete/AutocompleteWithSearchViewController.h" +#import "GooglePlacesDemos/Samples/Autocomplete/AutocompleteWithTextFieldController.h" +#import "GooglePlacesDemos/Samples/FindPlaceLikelihoodListViewController.h" +#import "GooglePlacesDemos/Support/BaseDemoViewController.h" + +@implementation Demo { + Class _viewControllerClass; +} + +- (instancetype)initWithViewControllerClass:(Class)viewControllerClass { + if ((self = [self init])) { + _title = [viewControllerClass demoTitle]; + _viewControllerClass = viewControllerClass; + } + return self; +} + +- (UIViewController *)createViewControllerWithAutocompleteFilter: + (GMSAutocompleteFilter *)autocompleteFilter + placeFields:(GMSPlaceField)placeFields { + // Construct the demo view controller. + UIViewController *demoViewController = [[_viewControllerClass alloc] init]; + + // Pass the place fields to the view controller for these classes. + if ([demoViewController isKindOfClass:[AutocompleteBaseViewController class]]) { + AutocompleteBaseViewController *controller = + (AutocompleteBaseViewController *)demoViewController; + controller.autocompleteFilter = autocompleteFilter; + controller.placeFields = placeFields; + } + + return demoViewController; +} + +@end + +@implementation DemoSection + +- (instancetype)initWithTitle:(NSString *)title demos:(NSArray *)demos { + if ((self = [self init])) { + _title = [title copy]; + _demos = [demos copy]; + } + return self; +} + +@end + +@implementation DemoData + +- (instancetype)init { + if ((self = [super init])) { + NSArray *autocompleteDemos = @[ + [[Demo alloc] initWithViewControllerClass:[AutocompleteWithCustomColors class]], + [[Demo alloc] initWithViewControllerClass:[AutocompleteModalViewController class]], + [[Demo alloc] initWithViewControllerClass:[AutocompletePushViewController class]], + [[Demo alloc] initWithViewControllerClass:[AutocompleteWithSearchViewController class]], + [[Demo alloc] initWithViewControllerClass:[AutocompleteWithTextFieldController class]], + ]; + + NSArray *findPlaceLikelihoodDemos = @[ [[Demo alloc] + initWithViewControllerClass:[FindPlaceLikelihoodListViewController class]] ]; + + _sections = @[ + [[DemoSection alloc] + initWithTitle:NSLocalizedString(@"Demo.Section.Title.Autocomplete", + @"Title of the autocomplete demo section") + demos:autocompleteDemos], + [[DemoSection alloc] + initWithTitle:NSLocalizedString(@"Demo.Section.Title.FindPlaceLikelihood", + @"Title of the findPlaceLikelihood demo section") + demos:findPlaceLikelihoodDemos] + ]; + } + return self; +} + +- (Demo *)firstDemo { + return _sections[0].demos[0]; +} + +@end diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/DemoListViewController.h a/Pods/GooglePlaces/Example/GooglePlacesDemos/DemoListViewController.h new file mode 100755 index 0000000..6c1d776 --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/DemoListViewController.h @@ -0,0 +1,32 @@ +/* + * Copyright 2016 Google Inc. All rights reserved. + * + * + * 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 + +#import "GooglePlacesDemos/DemoData.h" + +/** + * The class which displays the list of demos. + */ +@interface DemoListViewController : UITableViewController + +/** + * Construct a new list controller using the provided demo data. + * + * @param demoData The demo data to display in the list. + */ +- (instancetype)initWithDemoData:(DemoData *)demoData; + +@end diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/DemoListViewController.m a/Pods/GooglePlaces/Example/GooglePlacesDemos/DemoListViewController.m new file mode 100755 index 0000000..ea066c0 --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/DemoListViewController.m @@ -0,0 +1,417 @@ +/* + * Copyright 2016 Google Inc. All rights reserved. + * + * + * 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 "GooglePlacesDemos/DemoListViewController.h" + +#import + +// The cell reuse identifier we are going to use. +static NSString *const kCellIdentifier = @"DemoCellIdentifier"; +static const CGFloat kSelectionHeight = 40; +static const CGFloat kSelectionSwitchWidth = 50; +static const CGFloat kEdgeBuffer = 8; + +@implementation DemoListViewController { + UIViewController *_editSelectionsViewController; + NSMutableDictionary *_autocompleteFiltersSelectionMap; + NSMutableDictionary *_placeFieldsSelectionMap; + NSMutableDictionary *_restrictionBoundsMap; + CGFloat _nextSelectionYPos; + DemoData *_demoData; +} + +- (instancetype)initWithDemoData:(DemoData *)demoData { + if ((self = [self init])) { + _demoData = demoData; + _autocompleteFiltersSelectionMap = [NSMutableDictionary dictionary]; + _placeFieldsSelectionMap = [NSMutableDictionary dictionary]; + _restrictionBoundsMap = [NSMutableDictionary dictionary]; + } + return self; +} + +- (void)viewWillAppear:(BOOL)animated { + // Set up the title for view to be displayed. + self.title = [DemoListViewController titleText]; + [super viewWillAppear:animated]; +} + +- (void)viewWillDisappear:(BOOL)animated { + // Clear the title to make room for next view to share the header space in splitsreen view. + self.title = nil; + [super viewWillDisappear:animated]; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + // Set up the edit selections UI. + [self setUpEditSelectionsUI]; + + // Add button to the header to edit the place field selections. + self.navigationItem.rightBarButtonItem = + [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemEdit + target:self + action:@selector(beginEditSelections)]; + + // Register a plain old UITableViewCell as this will be sufficient for our list. + [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:kCellIdentifier]; + + // [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(orientationChanged:) + name:UIDeviceOrientationDidChangeNotification + object:[UIDevice currentDevice]]; +} + +/** + * Private method which is called when a demo is selected. Constructs the demo view controller and + * displays it. + * + * @param demo The demo to show. + */ +- (void)showDemo:(Demo *)demo { + CLLocationCoordinate2D northEast = kCLLocationCoordinate2DInvalid; + CLLocationCoordinate2D southWest = kCLLocationCoordinate2DInvalid; + GMSAutocompleteFilter *autocompleteFilter = [self autcompleteFilter]; + + // Check for restriction bounds settings. + if (_restrictionBoundsMap[@"Kansas"].on) { + northEast = CLLocationCoordinate2DMake(39.0, -95.0); + southWest = CLLocationCoordinate2DMake(37.5, -100.0); + autocompleteFilter.origin = [[CLLocation alloc] initWithLatitude:northEast.latitude + longitude:northEast.longitude]; + autocompleteFilter.locationRestriction = + GMSPlaceRectangularLocationOption(northEast, southWest); + } else if (_restrictionBoundsMap[@"Canada"].on) { + northEast = CLLocationCoordinate2DMake(70.0, -60.0); + southWest = CLLocationCoordinate2DMake(50.0, -140.0); + autocompleteFilter.origin = [[CLLocation alloc] initWithLatitude:northEast.latitude + longitude:northEast.longitude]; + autocompleteFilter.locationRestriction = + GMSPlaceRectangularLocationOption(northEast, southWest); + } + + // Create view controller with the autocomplete filters, bounds and selected place fields. + UIViewController *viewController = + [demo createViewControllerWithAutocompleteFilter:autocompleteFilter + placeFields:[self selectedPlaceFields]]; + [self.navigationController pushViewController:viewController animated:YES]; +} + +#pragma mark - Edit Autocomplete Filters and Place Fields selections UI + +- (void)setUpEditSelectionsUI { + // Initialize the place fields selection UI. + UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:self.view.frame]; + scrollView.backgroundColor = [UIColor whiteColor]; + + // Add heading for the autocomplete type filters. + _nextSelectionYPos = [UIApplication sharedApplication].statusBarFrame.size.height; + [scrollView addSubview:[self headerLabelForTitle:@"Autcomplete Filters"]]; + + // Set up the individual autocomplete type filters we can limit the results to. + // Add a heading for the place fields that we can request. + _nextSelectionYPos += kSelectionHeight; + for (NSInteger autocompleteFilterType = kGMSPlacesAutocompleteTypeFilterGeocode; + autocompleteFilterType <= kGMSPlacesAutocompleteTypeFilterCity; ++autocompleteFilterType) { + [scrollView + addSubview:[self selectionButtonForAutocompleteFilterType:(GMSPlacesAutocompleteTypeFilter) + autocompleteFilterType]]; + } + + // Add heading for the autocomplete restriction bounds. + [scrollView addSubview:[self headerLabelForTitle:@"Autcomplete Restriction Bounds"]]; + + // Set up the restriction bounds for testing purposes. + _nextSelectionYPos += kSelectionHeight; + [scrollView addSubview:[self selectionButtonForRestrictionBoundsArea:@"Canada"]]; + + _nextSelectionYPos += kSelectionHeight; + [scrollView addSubview:[self selectionButtonForRestrictionBoundsArea:@"Kansas"]]; + + // Add heading for the place fields that we can request. + _nextSelectionYPos += kSelectionHeight; + [scrollView addSubview:[self headerLabelForTitle:@"Place Fields"]]; + + // Set up the individual place fields that we can request. + _nextSelectionYPos += kSelectionHeight; + for (NSUInteger placeField = GMSPlaceFieldName; placeField <= GMSPlaceFieldUTCOffsetMinutes; + placeField <<= 1) { + [scrollView addSubview:[self selectionButtonForPlaceField:(GMSPlaceField)placeField]]; + } + + // Add the close button to dismiss the selection UI. + UIButton *close = + [[UIButton alloc] initWithFrame:CGRectMake(0, _nextSelectionYPos, self.view.frame.size.width, + kSelectionHeight)]; + close.backgroundColor = [UIColor blueColor]; + [close setTitle:@"Close" forState:UIControlStateNormal]; + [close setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; + [close addTarget:self + action:@selector(endEditSelections:) + forControlEvents:UIControlEventTouchUpInside]; + [scrollView addSubview:close]; + + // Set the content size for the scroll view after we determine the height of all the contents. + scrollView.contentSize = CGSizeMake(scrollView.frame.size.width, + close.frame.origin.y + close.frame.size.height + kEdgeBuffer); + + // Initialize the edit selections view controller. + _editSelectionsViewController = [[UIViewController alloc] init]; + _editSelectionsViewController.view = scrollView; +} + +- (void)updateFrameForSelectionViews:(UIView *)view { + CGFloat horizontalInset = [self horizontalInset]; + view.frame = + CGRectMake(horizontalInset, view.frame.origin.y, + self.view.frame.size.width - (2 * horizontalInset), view.frame.size.height); + for (UIView *subView in view.subviews) { + if ([subView isKindOfClass:[UISwitch class]]) { + subView.frame = + CGRectMake(view.frame.size.width - kSelectionSwitchWidth, subView.frame.origin.y, + subView.frame.size.width, subView.frame.size.height); + } + } +} + +- (UILabel *)headerLabelForTitle:(NSString *)title { + UILabel *headerLabel = + [[UILabel alloc] initWithFrame:CGRectMake(0, _nextSelectionYPos, self.view.frame.size.width, + kSelectionHeight)]; + headerLabel.backgroundColor = [UIColor lightGrayColor]; + headerLabel.text = title; + headerLabel.textAlignment = NSTextAlignmentCenter; + headerLabel.textColor = [UIColor whiteColor]; + headerLabel.userInteractionEnabled = NO; + return headerLabel; +} + +- (UIButton *)selectionButtonForTitle:(NSString *)title { + UIButton *selectionButton = + [[UIButton alloc] initWithFrame:CGRectMake(0, _nextSelectionYPos, self.view.frame.size.width, + kSelectionHeight)]; + UISwitch *selectionSwitch = [[UISwitch alloc] + initWithFrame:CGRectMake(selectionButton.frame.size.width - kSelectionSwitchWidth, + kEdgeBuffer / 2, kSelectionSwitchWidth, kSelectionHeight)]; + selectionSwitch.userInteractionEnabled = NO; + [selectionButton addTarget:self + action:@selector(selectionButtonTapped:) + forControlEvents:UIControlEventTouchUpInside]; + [selectionButton setBackgroundColor:[UIColor whiteColor]]; + [selectionButton setTitle:title forState:UIControlStateNormal]; + [selectionButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; + selectionButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft; + [selectionButton addSubview:selectionSwitch]; + return selectionButton; +} + +- (UIButton *)selectionButtonForPlaceField:(GMSPlaceField)placeField { + NSDictionary *fieldsMapping = @{ + @(GMSPlaceFieldName) : @"Name", + @(GMSPlaceFieldPlaceID) : @"Place ID", + @(GMSPlaceFieldPlusCode) : @"Plus Code", + @(GMSPlaceFieldCoordinate) : @"Coordinate", + @(GMSPlaceFieldOpeningHours) : @"Opening Hours", + @(GMSPlaceFieldPhoneNumber) : @"Phone Number", + @(GMSPlaceFieldFormattedAddress) : @"Formatted Address", + @(GMSPlaceFieldRating) : @"Rating", + @(GMSPlaceFieldUserRatingsTotal) : @"User Ratings Total", + @(GMSPlaceFieldPriceLevel) : @"Price Level", + @(GMSPlaceFieldTypes) : @"Types", + @(GMSPlaceFieldWebsite) : @"Website", + @(GMSPlaceFieldViewport) : @"Viewport", + @(GMSPlaceFieldAddressComponents) : @"Address Components", + @(GMSPlaceFieldPhotos) : @"Photos", + @(GMSPlaceFieldUTCOffsetMinutes) : @"UTC Offset Minutes", + }; + UIButton *selectionButton = [self selectionButtonForTitle:fieldsMapping[@(placeField)]]; + UISwitch *selectionSwitch = [self switchFromButton:selectionButton]; + [selectionSwitch setOn:YES]; + _placeFieldsSelectionMap[@(placeField)] = selectionSwitch; + _nextSelectionYPos += selectionButton.frame.size.height; + return selectionButton; +} + +- (UIButton *)selectionButtonForAutocompleteFilterType: + (GMSPlacesAutocompleteTypeFilter)autocompleteFilter { + NSDictionary *fieldsMapping = @{ + @(kGMSPlacesAutocompleteTypeFilterGeocode) : @"Geocode", + @(kGMSPlacesAutocompleteTypeFilterAddress) : @"Address", + @(kGMSPlacesAutocompleteTypeFilterEstablishment) : @"Establishment", + @(kGMSPlacesAutocompleteTypeFilterRegion) : @"Region", + @(kGMSPlacesAutocompleteTypeFilterCity) : @"City", + }; + UIButton *selectionButton = [self selectionButtonForTitle:fieldsMapping[@(autocompleteFilter)]]; + [selectionButton addTarget:self + action:@selector(disableOtherAutocompleteFilterExceptForTapped:) + forControlEvents:UIControlEventTouchUpInside]; + UISwitch *selectionSwitch = [self switchFromButton:selectionButton]; + [selectionSwitch setOn:NO]; + _autocompleteFiltersSelectionMap[@(autocompleteFilter)] = selectionSwitch; + _nextSelectionYPos += selectionButton.frame.size.height; + return selectionButton; +} + +- (UIButton *)selectionButtonForRestrictionBoundsArea:(NSString *)area { + UIButton *selectionButton = [self selectionButtonForTitle:area]; + [selectionButton addTarget:self + action:@selector(disableOtherRestrictionBoundsExceptForTapped:) + forControlEvents:UIControlEventTouchUpInside]; + _restrictionBoundsMap[area] = [self switchFromButton:selectionButton]; + [_restrictionBoundsMap[area] setOn:NO]; + return selectionButton; +} + +- (UISwitch *)switchFromButton:(UIButton *)button { + for (UIView *subView in button.subviews) { + if ([subView isKindOfClass:[UISwitch class]]) { + return (UISwitch *)subView; + } + } + return nil; +} + +- (void)selectionButtonTapped:(UIButton *)sender { + UISwitch *selectionSwitch = [self switchFromButton:sender]; + [selectionSwitch setOn:selectionSwitch.on ? NO : YES animated:YES]; +} + +- (void)disableOtherAutocompleteFilterExceptForTapped:(UIButton *)sender { + UISwitch *tappedSwitch = [self switchFromButton:sender]; + for (NSNumber *number in _autocompleteFiltersSelectionMap) { + UISwitch *selectionSwitch = _autocompleteFiltersSelectionMap[number]; + if (selectionSwitch != tappedSwitch) { + [selectionSwitch setOn:NO animated:YES]; + } + } +} + +- (void)disableOtherRestrictionBoundsExceptForTapped:(UIButton *)sender { + UISwitch *tappedSwitch = [self switchFromButton:sender]; + for (NSString *key in [_restrictionBoundsMap allKeys]) { + UISwitch *selectionSwitch = _restrictionBoundsMap[key]; + if (selectionSwitch != tappedSwitch) { + [selectionSwitch setOn:NO animated:YES]; + } + } +} + +- (void)beginEditSelections { + // Update the selection views to fit the current device frame and orientation. + for (UIView *view in _editSelectionsViewController.view.subviews) { + [self updateFrameForSelectionViews:view]; + } + + // Scroll the contents to the top before presenting the selection UI. + UIScrollView *scrollView = (UIScrollView *)_editSelectionsViewController.view; + [scrollView setContentOffset:CGPointZero animated:NO]; + + // Present the selection UI to edit which place fields to request. + [self.navigationController presentViewController:_editSelectionsViewController + animated:YES + completion:nil]; +} + +- (void)endEditSelections:(UIButton *)sender { + [_editSelectionsViewController dismissViewControllerAnimated:YES completion:nil]; +} + +- (GMSAutocompleteFilter *)autcompleteFilter { + GMSAutocompleteFilter *filter = [[GMSAutocompleteFilter alloc] init]; + for (NSNumber *number in _autocompleteFiltersSelectionMap) { + UISwitch *selectionSwitch = _autocompleteFiltersSelectionMap[number]; + if ([selectionSwitch isOn]) { + filter.type = (GMSPlacesAutocompleteTypeFilter)[number integerValue]; + break; + } + } + return filter; +} + +- (GMSPlaceField)selectedPlaceFields { + GMSPlaceField placeFields = 0; + for (NSNumber *number in _placeFieldsSelectionMap) { + UISwitch *selectionSwitch = _placeFieldsSelectionMap[number]; + if ([selectionSwitch isOn]) { + placeFields |= [number integerValue]; + } + } + return placeFields; +} + +- (CGFloat)horizontalInset { +#if defined(__IPHONE_11_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0) + // Take into account the safe areas of the device screen and do not use that space. + if (@available(iOS 11.0, *)) { + return MAX(self.view.safeAreaInsets.left, self.view.safeAreaInsets.right) + kEdgeBuffer; + } +#endif + return kEdgeBuffer; +} + +#pragma mark - UITableViewDataSource/Delegate + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return _demoData.sections.count; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return _demoData.sections[section].demos.count; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView + cellForRowAtIndexPath:(NSIndexPath *)indexPath { + // Dequeue a table view cell to use. + UITableViewCell *cell = + [tableView dequeueReusableCellWithIdentifier:kCellIdentifier forIndexPath:indexPath]; + + // Grab the demo object. + Demo *demo = _demoData.sections[indexPath.section].demos[indexPath.row]; + + // Configure the demo title on the cell. + cell.textLabel.text = demo.title; + + return cell; +} + +- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { + return _demoData.sections[section].title; +} + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + // Get the demo which was selected. + Demo *demo = _demoData.sections[indexPath.section].demos[indexPath.row]; + [self showDemo:demo]; +} + ++ (NSString *)titleText { + NSString *titleFormat = NSLocalizedString( + @"App.NameAndVersion", @"The name of the app to display in a navigation bar along with a " + @"placeholder for the SDK version number"); + return [NSString stringWithFormat:titleFormat, [GMSPlacesClient SDKLongVersion]]; +} + +#pragma mark - Handle Orientation Changes + +- (void)orientationChanged:(NSNotification *)notification { + // Dismiss the selections UI if currently active. + if (_editSelectionsViewController.isViewLoaded && _editSelectionsViewController.view.window) { + [self endEditSelections:nil]; + } +} + +@end diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Info.plist a/Pods/GooglePlaces/Example/GooglePlacesDemos/Info.plist new file mode 100755 index 0000000..b6a4f96 --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Info.plist @@ -0,0 +1,51 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIcons + + CFBundleIcons~ipad + + CFBundleIdentifier + com.example.GooglePlacesDemos + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1.0 + LSApplicationCategoryType + + LSRequiresIPhoneOS + + NSLocationWhenInUseUsageDescription + Show your location on a map + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationLandscapeLeft + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/Base.lproj/LaunchScreen.storyboard a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/Base.lproj/LaunchScreen.storyboard new file mode 100755 index 0000000..1eb368c --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/PlacesDemoAssets.xcassets/AppIcon.appiconset/Contents.json a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/PlacesDemoAssets.xcassets/AppIcon.appiconset/Contents.json new file mode 100755 index 0000000..2cadbf3 --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/PlacesDemoAssets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Places-API-Demo-App_120.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Places-API-Demo-App_180.png", + "scale" : "3x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Places-API-Demo-App_76.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Places-API-Demo-App_152.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Places-API-Demo-App_167.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/PlacesDemoAssets.xcassets/AppIcon.appiconset/Places-API-Demo-App_120.png a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/PlacesDemoAssets.xcassets/AppIcon.appiconset/Places-API-Demo-App_120.png new file mode 100755 index 0000000..79d7dc9 Binary files /dev/null and a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/PlacesDemoAssets.xcassets/AppIcon.appiconset/Places-API-Demo-App_120.png differ diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/PlacesDemoAssets.xcassets/AppIcon.appiconset/Places-API-Demo-App_152.png a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/PlacesDemoAssets.xcassets/AppIcon.appiconset/Places-API-Demo-App_152.png new file mode 100755 index 0000000..0151e0d Binary files /dev/null and a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/PlacesDemoAssets.xcassets/AppIcon.appiconset/Places-API-Demo-App_152.png differ diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/PlacesDemoAssets.xcassets/AppIcon.appiconset/Places-API-Demo-App_167.png a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/PlacesDemoAssets.xcassets/AppIcon.appiconset/Places-API-Demo-App_167.png new file mode 100755 index 0000000..cee6f35 Binary files /dev/null and a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/PlacesDemoAssets.xcassets/AppIcon.appiconset/Places-API-Demo-App_167.png differ diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/PlacesDemoAssets.xcassets/AppIcon.appiconset/Places-API-Demo-App_180.png a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/PlacesDemoAssets.xcassets/AppIcon.appiconset/Places-API-Demo-App_180.png new file mode 100755 index 0000000..969960e Binary files /dev/null and a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/PlacesDemoAssets.xcassets/AppIcon.appiconset/Places-API-Demo-App_180.png differ diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/PlacesDemoAssets.xcassets/AppIcon.appiconset/Places-API-Demo-App_76.png a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/PlacesDemoAssets.xcassets/AppIcon.appiconset/Places-API-Demo-App_76.png new file mode 100755 index 0000000..94600cd Binary files /dev/null and a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/PlacesDemoAssets.xcassets/AppIcon.appiconset/Places-API-Demo-App_76.png differ diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/PlacesDemoAssets.xcassets/Contents.json a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/PlacesDemoAssets.xcassets/Contents.json new file mode 100755 index 0000000..da4a164 --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/PlacesDemoAssets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/ar.lproj/Localizable.strings a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/ar.lproj/Localizable.strings new file mode 100755 index 0000000..f01e41c --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/ar.lproj/Localizable.strings @@ -0,0 +1,57 @@ +// NOTE: This is the english localization and has been copied to all languages. The reason for doing +// this is it signals to iOS that the app "supports" all of these languages. This is helpful for a +// demo as it allows you to try out the localization features of the Places SDK. In a shipping app +// you should never do this, it is for demonstration purposes only. + + +// The name of the app to display in a navigation bar along with a placeholder for the SDK version number. +"App.NameAndVersion"="Places Demos: %1$@"; + +// Title of the full-screen autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.FullScreen"="Full-Screen Autocomplete"; +// Title of the pushed autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Push"="Push Autocomplete"; +// Title of the UISearchController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchController"="UISearchController"; +// Title of the UISearchDisplayController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchDisplayController"="UISearchDisplayController"; +// Title of the UITextField autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UITextField"="UITextField"; +// Title of the Styling autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Styling"="Custom Autocomplete Styling"; +// Title of the photos demo for display in a list or nav header +"Demo.Title.Photos"="Photos"; +// Title of the 'select place' button within the photos demo +"Demo.Title.Photos.SelectPlace"="Select Place"; +// Title of the Place Picker demo for displaying the picker in a popover, navigation controller, or modally. +"Demo.Title.PlacePicker.ViewController"="Place Picker View Controller"; +// Title of the autocomplete demo section +"Demo.Section.Title.Autocomplete"="Autocomplete"; +// Title of the 'Programmatic' demo section +"Demo.Section.Title.Programmatic"="Programmatic APIs"; +// Title of the findPlacesLikelihood demo section +"Demo.Section.Title.FindPlaceLikelihood"="Likelihoods"; + +// Button title for 'show autocomplete widget' +"Demo.Content.Autocomplete.ShowWidgetButton"="Show Autocomplete Widget"; +// Prompt to enter text for autocomplete demo +"Demo.Content.Autocomplete.EnterTextPrompt"="Enter Autocomplete Text Here"; +// Format string for 'autocomplete failed with error' message +"Demo.Content.Autocomplete.FailedErrorMessage"="Autocomplete failed with error: %1$@"; +// String for 'autocomplete canceled message' +"Demo.Content.Autocomplete.WasCanceledMessage"="Autocomplete was canceled"; +// Button title for the 'Yellow and Brown' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.YellowAndBrown"="Yellow and Brown"; +// Button title for the 'White on Black' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.WhiteOnBlack"="White on Black"; +// Button title for the 'Blue Colors' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.BlueColors"="Blue Colors"; +// Button title for the 'Hot Dog Stand' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.HotDogStand"="Hot Dog Stand"; +// Button title for the 'Popover' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Popover"="Popover"; +// Button title for the 'Navigation' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Navigation"="Navigation"; +// Button title for the 'Modal' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Modal"="Modal"; + diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/ca.lproj/Localizable.strings a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/ca.lproj/Localizable.strings new file mode 100755 index 0000000..f01e41c --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/ca.lproj/Localizable.strings @@ -0,0 +1,57 @@ +// NOTE: This is the english localization and has been copied to all languages. The reason for doing +// this is it signals to iOS that the app "supports" all of these languages. This is helpful for a +// demo as it allows you to try out the localization features of the Places SDK. In a shipping app +// you should never do this, it is for demonstration purposes only. + + +// The name of the app to display in a navigation bar along with a placeholder for the SDK version number. +"App.NameAndVersion"="Places Demos: %1$@"; + +// Title of the full-screen autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.FullScreen"="Full-Screen Autocomplete"; +// Title of the pushed autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Push"="Push Autocomplete"; +// Title of the UISearchController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchController"="UISearchController"; +// Title of the UISearchDisplayController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchDisplayController"="UISearchDisplayController"; +// Title of the UITextField autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UITextField"="UITextField"; +// Title of the Styling autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Styling"="Custom Autocomplete Styling"; +// Title of the photos demo for display in a list or nav header +"Demo.Title.Photos"="Photos"; +// Title of the 'select place' button within the photos demo +"Demo.Title.Photos.SelectPlace"="Select Place"; +// Title of the Place Picker demo for displaying the picker in a popover, navigation controller, or modally. +"Demo.Title.PlacePicker.ViewController"="Place Picker View Controller"; +// Title of the autocomplete demo section +"Demo.Section.Title.Autocomplete"="Autocomplete"; +// Title of the 'Programmatic' demo section +"Demo.Section.Title.Programmatic"="Programmatic APIs"; +// Title of the findPlacesLikelihood demo section +"Demo.Section.Title.FindPlaceLikelihood"="Likelihoods"; + +// Button title for 'show autocomplete widget' +"Demo.Content.Autocomplete.ShowWidgetButton"="Show Autocomplete Widget"; +// Prompt to enter text for autocomplete demo +"Demo.Content.Autocomplete.EnterTextPrompt"="Enter Autocomplete Text Here"; +// Format string for 'autocomplete failed with error' message +"Demo.Content.Autocomplete.FailedErrorMessage"="Autocomplete failed with error: %1$@"; +// String for 'autocomplete canceled message' +"Demo.Content.Autocomplete.WasCanceledMessage"="Autocomplete was canceled"; +// Button title for the 'Yellow and Brown' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.YellowAndBrown"="Yellow and Brown"; +// Button title for the 'White on Black' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.WhiteOnBlack"="White on Black"; +// Button title for the 'Blue Colors' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.BlueColors"="Blue Colors"; +// Button title for the 'Hot Dog Stand' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.HotDogStand"="Hot Dog Stand"; +// Button title for the 'Popover' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Popover"="Popover"; +// Button title for the 'Navigation' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Navigation"="Navigation"; +// Button title for the 'Modal' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Modal"="Modal"; + diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/cs.lproj/Localizable.strings a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/cs.lproj/Localizable.strings new file mode 100755 index 0000000..f01e41c --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/cs.lproj/Localizable.strings @@ -0,0 +1,57 @@ +// NOTE: This is the english localization and has been copied to all languages. The reason for doing +// this is it signals to iOS that the app "supports" all of these languages. This is helpful for a +// demo as it allows you to try out the localization features of the Places SDK. In a shipping app +// you should never do this, it is for demonstration purposes only. + + +// The name of the app to display in a navigation bar along with a placeholder for the SDK version number. +"App.NameAndVersion"="Places Demos: %1$@"; + +// Title of the full-screen autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.FullScreen"="Full-Screen Autocomplete"; +// Title of the pushed autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Push"="Push Autocomplete"; +// Title of the UISearchController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchController"="UISearchController"; +// Title of the UISearchDisplayController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchDisplayController"="UISearchDisplayController"; +// Title of the UITextField autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UITextField"="UITextField"; +// Title of the Styling autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Styling"="Custom Autocomplete Styling"; +// Title of the photos demo for display in a list or nav header +"Demo.Title.Photos"="Photos"; +// Title of the 'select place' button within the photos demo +"Demo.Title.Photos.SelectPlace"="Select Place"; +// Title of the Place Picker demo for displaying the picker in a popover, navigation controller, or modally. +"Demo.Title.PlacePicker.ViewController"="Place Picker View Controller"; +// Title of the autocomplete demo section +"Demo.Section.Title.Autocomplete"="Autocomplete"; +// Title of the 'Programmatic' demo section +"Demo.Section.Title.Programmatic"="Programmatic APIs"; +// Title of the findPlacesLikelihood demo section +"Demo.Section.Title.FindPlaceLikelihood"="Likelihoods"; + +// Button title for 'show autocomplete widget' +"Demo.Content.Autocomplete.ShowWidgetButton"="Show Autocomplete Widget"; +// Prompt to enter text for autocomplete demo +"Demo.Content.Autocomplete.EnterTextPrompt"="Enter Autocomplete Text Here"; +// Format string for 'autocomplete failed with error' message +"Demo.Content.Autocomplete.FailedErrorMessage"="Autocomplete failed with error: %1$@"; +// String for 'autocomplete canceled message' +"Demo.Content.Autocomplete.WasCanceledMessage"="Autocomplete was canceled"; +// Button title for the 'Yellow and Brown' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.YellowAndBrown"="Yellow and Brown"; +// Button title for the 'White on Black' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.WhiteOnBlack"="White on Black"; +// Button title for the 'Blue Colors' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.BlueColors"="Blue Colors"; +// Button title for the 'Hot Dog Stand' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.HotDogStand"="Hot Dog Stand"; +// Button title for the 'Popover' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Popover"="Popover"; +// Button title for the 'Navigation' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Navigation"="Navigation"; +// Button title for the 'Modal' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Modal"="Modal"; + diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/da.lproj/Localizable.strings a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/da.lproj/Localizable.strings new file mode 100755 index 0000000..f01e41c --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/da.lproj/Localizable.strings @@ -0,0 +1,57 @@ +// NOTE: This is the english localization and has been copied to all languages. The reason for doing +// this is it signals to iOS that the app "supports" all of these languages. This is helpful for a +// demo as it allows you to try out the localization features of the Places SDK. In a shipping app +// you should never do this, it is for demonstration purposes only. + + +// The name of the app to display in a navigation bar along with a placeholder for the SDK version number. +"App.NameAndVersion"="Places Demos: %1$@"; + +// Title of the full-screen autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.FullScreen"="Full-Screen Autocomplete"; +// Title of the pushed autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Push"="Push Autocomplete"; +// Title of the UISearchController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchController"="UISearchController"; +// Title of the UISearchDisplayController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchDisplayController"="UISearchDisplayController"; +// Title of the UITextField autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UITextField"="UITextField"; +// Title of the Styling autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Styling"="Custom Autocomplete Styling"; +// Title of the photos demo for display in a list or nav header +"Demo.Title.Photos"="Photos"; +// Title of the 'select place' button within the photos demo +"Demo.Title.Photos.SelectPlace"="Select Place"; +// Title of the Place Picker demo for displaying the picker in a popover, navigation controller, or modally. +"Demo.Title.PlacePicker.ViewController"="Place Picker View Controller"; +// Title of the autocomplete demo section +"Demo.Section.Title.Autocomplete"="Autocomplete"; +// Title of the 'Programmatic' demo section +"Demo.Section.Title.Programmatic"="Programmatic APIs"; +// Title of the findPlacesLikelihood demo section +"Demo.Section.Title.FindPlaceLikelihood"="Likelihoods"; + +// Button title for 'show autocomplete widget' +"Demo.Content.Autocomplete.ShowWidgetButton"="Show Autocomplete Widget"; +// Prompt to enter text for autocomplete demo +"Demo.Content.Autocomplete.EnterTextPrompt"="Enter Autocomplete Text Here"; +// Format string for 'autocomplete failed with error' message +"Demo.Content.Autocomplete.FailedErrorMessage"="Autocomplete failed with error: %1$@"; +// String for 'autocomplete canceled message' +"Demo.Content.Autocomplete.WasCanceledMessage"="Autocomplete was canceled"; +// Button title for the 'Yellow and Brown' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.YellowAndBrown"="Yellow and Brown"; +// Button title for the 'White on Black' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.WhiteOnBlack"="White on Black"; +// Button title for the 'Blue Colors' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.BlueColors"="Blue Colors"; +// Button title for the 'Hot Dog Stand' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.HotDogStand"="Hot Dog Stand"; +// Button title for the 'Popover' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Popover"="Popover"; +// Button title for the 'Navigation' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Navigation"="Navigation"; +// Button title for the 'Modal' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Modal"="Modal"; + diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/de.lproj/Localizable.strings a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/de.lproj/Localizable.strings new file mode 100755 index 0000000..f01e41c --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/de.lproj/Localizable.strings @@ -0,0 +1,57 @@ +// NOTE: This is the english localization and has been copied to all languages. The reason for doing +// this is it signals to iOS that the app "supports" all of these languages. This is helpful for a +// demo as it allows you to try out the localization features of the Places SDK. In a shipping app +// you should never do this, it is for demonstration purposes only. + + +// The name of the app to display in a navigation bar along with a placeholder for the SDK version number. +"App.NameAndVersion"="Places Demos: %1$@"; + +// Title of the full-screen autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.FullScreen"="Full-Screen Autocomplete"; +// Title of the pushed autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Push"="Push Autocomplete"; +// Title of the UISearchController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchController"="UISearchController"; +// Title of the UISearchDisplayController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchDisplayController"="UISearchDisplayController"; +// Title of the UITextField autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UITextField"="UITextField"; +// Title of the Styling autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Styling"="Custom Autocomplete Styling"; +// Title of the photos demo for display in a list or nav header +"Demo.Title.Photos"="Photos"; +// Title of the 'select place' button within the photos demo +"Demo.Title.Photos.SelectPlace"="Select Place"; +// Title of the Place Picker demo for displaying the picker in a popover, navigation controller, or modally. +"Demo.Title.PlacePicker.ViewController"="Place Picker View Controller"; +// Title of the autocomplete demo section +"Demo.Section.Title.Autocomplete"="Autocomplete"; +// Title of the 'Programmatic' demo section +"Demo.Section.Title.Programmatic"="Programmatic APIs"; +// Title of the findPlacesLikelihood demo section +"Demo.Section.Title.FindPlaceLikelihood"="Likelihoods"; + +// Button title for 'show autocomplete widget' +"Demo.Content.Autocomplete.ShowWidgetButton"="Show Autocomplete Widget"; +// Prompt to enter text for autocomplete demo +"Demo.Content.Autocomplete.EnterTextPrompt"="Enter Autocomplete Text Here"; +// Format string for 'autocomplete failed with error' message +"Demo.Content.Autocomplete.FailedErrorMessage"="Autocomplete failed with error: %1$@"; +// String for 'autocomplete canceled message' +"Demo.Content.Autocomplete.WasCanceledMessage"="Autocomplete was canceled"; +// Button title for the 'Yellow and Brown' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.YellowAndBrown"="Yellow and Brown"; +// Button title for the 'White on Black' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.WhiteOnBlack"="White on Black"; +// Button title for the 'Blue Colors' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.BlueColors"="Blue Colors"; +// Button title for the 'Hot Dog Stand' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.HotDogStand"="Hot Dog Stand"; +// Button title for the 'Popover' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Popover"="Popover"; +// Button title for the 'Navigation' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Navigation"="Navigation"; +// Button title for the 'Modal' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Modal"="Modal"; + diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/el.lproj/Localizable.strings a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/el.lproj/Localizable.strings new file mode 100755 index 0000000..f01e41c --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/el.lproj/Localizable.strings @@ -0,0 +1,57 @@ +// NOTE: This is the english localization and has been copied to all languages. The reason for doing +// this is it signals to iOS that the app "supports" all of these languages. This is helpful for a +// demo as it allows you to try out the localization features of the Places SDK. In a shipping app +// you should never do this, it is for demonstration purposes only. + + +// The name of the app to display in a navigation bar along with a placeholder for the SDK version number. +"App.NameAndVersion"="Places Demos: %1$@"; + +// Title of the full-screen autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.FullScreen"="Full-Screen Autocomplete"; +// Title of the pushed autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Push"="Push Autocomplete"; +// Title of the UISearchController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchController"="UISearchController"; +// Title of the UISearchDisplayController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchDisplayController"="UISearchDisplayController"; +// Title of the UITextField autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UITextField"="UITextField"; +// Title of the Styling autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Styling"="Custom Autocomplete Styling"; +// Title of the photos demo for display in a list or nav header +"Demo.Title.Photos"="Photos"; +// Title of the 'select place' button within the photos demo +"Demo.Title.Photos.SelectPlace"="Select Place"; +// Title of the Place Picker demo for displaying the picker in a popover, navigation controller, or modally. +"Demo.Title.PlacePicker.ViewController"="Place Picker View Controller"; +// Title of the autocomplete demo section +"Demo.Section.Title.Autocomplete"="Autocomplete"; +// Title of the 'Programmatic' demo section +"Demo.Section.Title.Programmatic"="Programmatic APIs"; +// Title of the findPlacesLikelihood demo section +"Demo.Section.Title.FindPlaceLikelihood"="Likelihoods"; + +// Button title for 'show autocomplete widget' +"Demo.Content.Autocomplete.ShowWidgetButton"="Show Autocomplete Widget"; +// Prompt to enter text for autocomplete demo +"Demo.Content.Autocomplete.EnterTextPrompt"="Enter Autocomplete Text Here"; +// Format string for 'autocomplete failed with error' message +"Demo.Content.Autocomplete.FailedErrorMessage"="Autocomplete failed with error: %1$@"; +// String for 'autocomplete canceled message' +"Demo.Content.Autocomplete.WasCanceledMessage"="Autocomplete was canceled"; +// Button title for the 'Yellow and Brown' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.YellowAndBrown"="Yellow and Brown"; +// Button title for the 'White on Black' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.WhiteOnBlack"="White on Black"; +// Button title for the 'Blue Colors' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.BlueColors"="Blue Colors"; +// Button title for the 'Hot Dog Stand' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.HotDogStand"="Hot Dog Stand"; +// Button title for the 'Popover' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Popover"="Popover"; +// Button title for the 'Navigation' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Navigation"="Navigation"; +// Button title for the 'Modal' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Modal"="Modal"; + diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/en.lproj/Localizable.strings a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/en.lproj/Localizable.strings new file mode 100755 index 0000000..f01e41c --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/en.lproj/Localizable.strings @@ -0,0 +1,57 @@ +// NOTE: This is the english localization and has been copied to all languages. The reason for doing +// this is it signals to iOS that the app "supports" all of these languages. This is helpful for a +// demo as it allows you to try out the localization features of the Places SDK. In a shipping app +// you should never do this, it is for demonstration purposes only. + + +// The name of the app to display in a navigation bar along with a placeholder for the SDK version number. +"App.NameAndVersion"="Places Demos: %1$@"; + +// Title of the full-screen autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.FullScreen"="Full-Screen Autocomplete"; +// Title of the pushed autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Push"="Push Autocomplete"; +// Title of the UISearchController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchController"="UISearchController"; +// Title of the UISearchDisplayController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchDisplayController"="UISearchDisplayController"; +// Title of the UITextField autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UITextField"="UITextField"; +// Title of the Styling autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Styling"="Custom Autocomplete Styling"; +// Title of the photos demo for display in a list or nav header +"Demo.Title.Photos"="Photos"; +// Title of the 'select place' button within the photos demo +"Demo.Title.Photos.SelectPlace"="Select Place"; +// Title of the Place Picker demo for displaying the picker in a popover, navigation controller, or modally. +"Demo.Title.PlacePicker.ViewController"="Place Picker View Controller"; +// Title of the autocomplete demo section +"Demo.Section.Title.Autocomplete"="Autocomplete"; +// Title of the 'Programmatic' demo section +"Demo.Section.Title.Programmatic"="Programmatic APIs"; +// Title of the findPlacesLikelihood demo section +"Demo.Section.Title.FindPlaceLikelihood"="Likelihoods"; + +// Button title for 'show autocomplete widget' +"Demo.Content.Autocomplete.ShowWidgetButton"="Show Autocomplete Widget"; +// Prompt to enter text for autocomplete demo +"Demo.Content.Autocomplete.EnterTextPrompt"="Enter Autocomplete Text Here"; +// Format string for 'autocomplete failed with error' message +"Demo.Content.Autocomplete.FailedErrorMessage"="Autocomplete failed with error: %1$@"; +// String for 'autocomplete canceled message' +"Demo.Content.Autocomplete.WasCanceledMessage"="Autocomplete was canceled"; +// Button title for the 'Yellow and Brown' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.YellowAndBrown"="Yellow and Brown"; +// Button title for the 'White on Black' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.WhiteOnBlack"="White on Black"; +// Button title for the 'Blue Colors' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.BlueColors"="Blue Colors"; +// Button title for the 'Hot Dog Stand' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.HotDogStand"="Hot Dog Stand"; +// Button title for the 'Popover' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Popover"="Popover"; +// Button title for the 'Navigation' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Navigation"="Navigation"; +// Button title for the 'Modal' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Modal"="Modal"; + diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/en_AU.lproj/Localizable.strings a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/en_AU.lproj/Localizable.strings new file mode 100755 index 0000000..f01e41c --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/en_AU.lproj/Localizable.strings @@ -0,0 +1,57 @@ +// NOTE: This is the english localization and has been copied to all languages. The reason for doing +// this is it signals to iOS that the app "supports" all of these languages. This is helpful for a +// demo as it allows you to try out the localization features of the Places SDK. In a shipping app +// you should never do this, it is for demonstration purposes only. + + +// The name of the app to display in a navigation bar along with a placeholder for the SDK version number. +"App.NameAndVersion"="Places Demos: %1$@"; + +// Title of the full-screen autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.FullScreen"="Full-Screen Autocomplete"; +// Title of the pushed autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Push"="Push Autocomplete"; +// Title of the UISearchController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchController"="UISearchController"; +// Title of the UISearchDisplayController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchDisplayController"="UISearchDisplayController"; +// Title of the UITextField autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UITextField"="UITextField"; +// Title of the Styling autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Styling"="Custom Autocomplete Styling"; +// Title of the photos demo for display in a list or nav header +"Demo.Title.Photos"="Photos"; +// Title of the 'select place' button within the photos demo +"Demo.Title.Photos.SelectPlace"="Select Place"; +// Title of the Place Picker demo for displaying the picker in a popover, navigation controller, or modally. +"Demo.Title.PlacePicker.ViewController"="Place Picker View Controller"; +// Title of the autocomplete demo section +"Demo.Section.Title.Autocomplete"="Autocomplete"; +// Title of the 'Programmatic' demo section +"Demo.Section.Title.Programmatic"="Programmatic APIs"; +// Title of the findPlacesLikelihood demo section +"Demo.Section.Title.FindPlaceLikelihood"="Likelihoods"; + +// Button title for 'show autocomplete widget' +"Demo.Content.Autocomplete.ShowWidgetButton"="Show Autocomplete Widget"; +// Prompt to enter text for autocomplete demo +"Demo.Content.Autocomplete.EnterTextPrompt"="Enter Autocomplete Text Here"; +// Format string for 'autocomplete failed with error' message +"Demo.Content.Autocomplete.FailedErrorMessage"="Autocomplete failed with error: %1$@"; +// String for 'autocomplete canceled message' +"Demo.Content.Autocomplete.WasCanceledMessage"="Autocomplete was canceled"; +// Button title for the 'Yellow and Brown' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.YellowAndBrown"="Yellow and Brown"; +// Button title for the 'White on Black' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.WhiteOnBlack"="White on Black"; +// Button title for the 'Blue Colors' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.BlueColors"="Blue Colors"; +// Button title for the 'Hot Dog Stand' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.HotDogStand"="Hot Dog Stand"; +// Button title for the 'Popover' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Popover"="Popover"; +// Button title for the 'Navigation' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Navigation"="Navigation"; +// Button title for the 'Modal' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Modal"="Modal"; + diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/en_GB.lproj/Localizable.strings a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/en_GB.lproj/Localizable.strings new file mode 100755 index 0000000..f01e41c --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/en_GB.lproj/Localizable.strings @@ -0,0 +1,57 @@ +// NOTE: This is the english localization and has been copied to all languages. The reason for doing +// this is it signals to iOS that the app "supports" all of these languages. This is helpful for a +// demo as it allows you to try out the localization features of the Places SDK. In a shipping app +// you should never do this, it is for demonstration purposes only. + + +// The name of the app to display in a navigation bar along with a placeholder for the SDK version number. +"App.NameAndVersion"="Places Demos: %1$@"; + +// Title of the full-screen autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.FullScreen"="Full-Screen Autocomplete"; +// Title of the pushed autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Push"="Push Autocomplete"; +// Title of the UISearchController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchController"="UISearchController"; +// Title of the UISearchDisplayController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchDisplayController"="UISearchDisplayController"; +// Title of the UITextField autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UITextField"="UITextField"; +// Title of the Styling autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Styling"="Custom Autocomplete Styling"; +// Title of the photos demo for display in a list or nav header +"Demo.Title.Photos"="Photos"; +// Title of the 'select place' button within the photos demo +"Demo.Title.Photos.SelectPlace"="Select Place"; +// Title of the Place Picker demo for displaying the picker in a popover, navigation controller, or modally. +"Demo.Title.PlacePicker.ViewController"="Place Picker View Controller"; +// Title of the autocomplete demo section +"Demo.Section.Title.Autocomplete"="Autocomplete"; +// Title of the 'Programmatic' demo section +"Demo.Section.Title.Programmatic"="Programmatic APIs"; +// Title of the findPlacesLikelihood demo section +"Demo.Section.Title.FindPlaceLikelihood"="Likelihoods"; + +// Button title for 'show autocomplete widget' +"Demo.Content.Autocomplete.ShowWidgetButton"="Show Autocomplete Widget"; +// Prompt to enter text for autocomplete demo +"Demo.Content.Autocomplete.EnterTextPrompt"="Enter Autocomplete Text Here"; +// Format string for 'autocomplete failed with error' message +"Demo.Content.Autocomplete.FailedErrorMessage"="Autocomplete failed with error: %1$@"; +// String for 'autocomplete canceled message' +"Demo.Content.Autocomplete.WasCanceledMessage"="Autocomplete was canceled"; +// Button title for the 'Yellow and Brown' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.YellowAndBrown"="Yellow and Brown"; +// Button title for the 'White on Black' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.WhiteOnBlack"="White on Black"; +// Button title for the 'Blue Colors' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.BlueColors"="Blue Colors"; +// Button title for the 'Hot Dog Stand' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.HotDogStand"="Hot Dog Stand"; +// Button title for the 'Popover' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Popover"="Popover"; +// Button title for the 'Navigation' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Navigation"="Navigation"; +// Button title for the 'Modal' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Modal"="Modal"; + diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/en_IN.lproj/Localizable.strings a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/en_IN.lproj/Localizable.strings new file mode 100755 index 0000000..f01e41c --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/en_IN.lproj/Localizable.strings @@ -0,0 +1,57 @@ +// NOTE: This is the english localization and has been copied to all languages. The reason for doing +// this is it signals to iOS that the app "supports" all of these languages. This is helpful for a +// demo as it allows you to try out the localization features of the Places SDK. In a shipping app +// you should never do this, it is for demonstration purposes only. + + +// The name of the app to display in a navigation bar along with a placeholder for the SDK version number. +"App.NameAndVersion"="Places Demos: %1$@"; + +// Title of the full-screen autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.FullScreen"="Full-Screen Autocomplete"; +// Title of the pushed autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Push"="Push Autocomplete"; +// Title of the UISearchController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchController"="UISearchController"; +// Title of the UISearchDisplayController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchDisplayController"="UISearchDisplayController"; +// Title of the UITextField autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UITextField"="UITextField"; +// Title of the Styling autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Styling"="Custom Autocomplete Styling"; +// Title of the photos demo for display in a list or nav header +"Demo.Title.Photos"="Photos"; +// Title of the 'select place' button within the photos demo +"Demo.Title.Photos.SelectPlace"="Select Place"; +// Title of the Place Picker demo for displaying the picker in a popover, navigation controller, or modally. +"Demo.Title.PlacePicker.ViewController"="Place Picker View Controller"; +// Title of the autocomplete demo section +"Demo.Section.Title.Autocomplete"="Autocomplete"; +// Title of the 'Programmatic' demo section +"Demo.Section.Title.Programmatic"="Programmatic APIs"; +// Title of the findPlacesLikelihood demo section +"Demo.Section.Title.FindPlaceLikelihood"="Likelihoods"; + +// Button title for 'show autocomplete widget' +"Demo.Content.Autocomplete.ShowWidgetButton"="Show Autocomplete Widget"; +// Prompt to enter text for autocomplete demo +"Demo.Content.Autocomplete.EnterTextPrompt"="Enter Autocomplete Text Here"; +// Format string for 'autocomplete failed with error' message +"Demo.Content.Autocomplete.FailedErrorMessage"="Autocomplete failed with error: %1$@"; +// String for 'autocomplete canceled message' +"Demo.Content.Autocomplete.WasCanceledMessage"="Autocomplete was canceled"; +// Button title for the 'Yellow and Brown' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.YellowAndBrown"="Yellow and Brown"; +// Button title for the 'White on Black' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.WhiteOnBlack"="White on Black"; +// Button title for the 'Blue Colors' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.BlueColors"="Blue Colors"; +// Button title for the 'Hot Dog Stand' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.HotDogStand"="Hot Dog Stand"; +// Button title for the 'Popover' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Popover"="Popover"; +// Button title for the 'Navigation' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Navigation"="Navigation"; +// Button title for the 'Modal' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Modal"="Modal"; + diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/es.lproj/Localizable.strings a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/es.lproj/Localizable.strings new file mode 100755 index 0000000..f01e41c --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/es.lproj/Localizable.strings @@ -0,0 +1,57 @@ +// NOTE: This is the english localization and has been copied to all languages. The reason for doing +// this is it signals to iOS that the app "supports" all of these languages. This is helpful for a +// demo as it allows you to try out the localization features of the Places SDK. In a shipping app +// you should never do this, it is for demonstration purposes only. + + +// The name of the app to display in a navigation bar along with a placeholder for the SDK version number. +"App.NameAndVersion"="Places Demos: %1$@"; + +// Title of the full-screen autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.FullScreen"="Full-Screen Autocomplete"; +// Title of the pushed autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Push"="Push Autocomplete"; +// Title of the UISearchController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchController"="UISearchController"; +// Title of the UISearchDisplayController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchDisplayController"="UISearchDisplayController"; +// Title of the UITextField autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UITextField"="UITextField"; +// Title of the Styling autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Styling"="Custom Autocomplete Styling"; +// Title of the photos demo for display in a list or nav header +"Demo.Title.Photos"="Photos"; +// Title of the 'select place' button within the photos demo +"Demo.Title.Photos.SelectPlace"="Select Place"; +// Title of the Place Picker demo for displaying the picker in a popover, navigation controller, or modally. +"Demo.Title.PlacePicker.ViewController"="Place Picker View Controller"; +// Title of the autocomplete demo section +"Demo.Section.Title.Autocomplete"="Autocomplete"; +// Title of the 'Programmatic' demo section +"Demo.Section.Title.Programmatic"="Programmatic APIs"; +// Title of the findPlacesLikelihood demo section +"Demo.Section.Title.FindPlaceLikelihood"="Likelihoods"; + +// Button title for 'show autocomplete widget' +"Demo.Content.Autocomplete.ShowWidgetButton"="Show Autocomplete Widget"; +// Prompt to enter text for autocomplete demo +"Demo.Content.Autocomplete.EnterTextPrompt"="Enter Autocomplete Text Here"; +// Format string for 'autocomplete failed with error' message +"Demo.Content.Autocomplete.FailedErrorMessage"="Autocomplete failed with error: %1$@"; +// String for 'autocomplete canceled message' +"Demo.Content.Autocomplete.WasCanceledMessage"="Autocomplete was canceled"; +// Button title for the 'Yellow and Brown' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.YellowAndBrown"="Yellow and Brown"; +// Button title for the 'White on Black' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.WhiteOnBlack"="White on Black"; +// Button title for the 'Blue Colors' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.BlueColors"="Blue Colors"; +// Button title for the 'Hot Dog Stand' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.HotDogStand"="Hot Dog Stand"; +// Button title for the 'Popover' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Popover"="Popover"; +// Button title for the 'Navigation' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Navigation"="Navigation"; +// Button title for the 'Modal' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Modal"="Modal"; + diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/es_419.lproj/Localizable.strings a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/es_419.lproj/Localizable.strings new file mode 100755 index 0000000..f01e41c --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/es_419.lproj/Localizable.strings @@ -0,0 +1,57 @@ +// NOTE: This is the english localization and has been copied to all languages. The reason for doing +// this is it signals to iOS that the app "supports" all of these languages. This is helpful for a +// demo as it allows you to try out the localization features of the Places SDK. In a shipping app +// you should never do this, it is for demonstration purposes only. + + +// The name of the app to display in a navigation bar along with a placeholder for the SDK version number. +"App.NameAndVersion"="Places Demos: %1$@"; + +// Title of the full-screen autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.FullScreen"="Full-Screen Autocomplete"; +// Title of the pushed autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Push"="Push Autocomplete"; +// Title of the UISearchController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchController"="UISearchController"; +// Title of the UISearchDisplayController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchDisplayController"="UISearchDisplayController"; +// Title of the UITextField autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UITextField"="UITextField"; +// Title of the Styling autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Styling"="Custom Autocomplete Styling"; +// Title of the photos demo for display in a list or nav header +"Demo.Title.Photos"="Photos"; +// Title of the 'select place' button within the photos demo +"Demo.Title.Photos.SelectPlace"="Select Place"; +// Title of the Place Picker demo for displaying the picker in a popover, navigation controller, or modally. +"Demo.Title.PlacePicker.ViewController"="Place Picker View Controller"; +// Title of the autocomplete demo section +"Demo.Section.Title.Autocomplete"="Autocomplete"; +// Title of the 'Programmatic' demo section +"Demo.Section.Title.Programmatic"="Programmatic APIs"; +// Title of the findPlacesLikelihood demo section +"Demo.Section.Title.FindPlaceLikelihood"="Likelihoods"; + +// Button title for 'show autocomplete widget' +"Demo.Content.Autocomplete.ShowWidgetButton"="Show Autocomplete Widget"; +// Prompt to enter text for autocomplete demo +"Demo.Content.Autocomplete.EnterTextPrompt"="Enter Autocomplete Text Here"; +// Format string for 'autocomplete failed with error' message +"Demo.Content.Autocomplete.FailedErrorMessage"="Autocomplete failed with error: %1$@"; +// String for 'autocomplete canceled message' +"Demo.Content.Autocomplete.WasCanceledMessage"="Autocomplete was canceled"; +// Button title for the 'Yellow and Brown' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.YellowAndBrown"="Yellow and Brown"; +// Button title for the 'White on Black' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.WhiteOnBlack"="White on Black"; +// Button title for the 'Blue Colors' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.BlueColors"="Blue Colors"; +// Button title for the 'Hot Dog Stand' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.HotDogStand"="Hot Dog Stand"; +// Button title for the 'Popover' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Popover"="Popover"; +// Button title for the 'Navigation' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Navigation"="Navigation"; +// Button title for the 'Modal' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Modal"="Modal"; + diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/es_MX.lproj/Localizable.strings a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/es_MX.lproj/Localizable.strings new file mode 100755 index 0000000..f01e41c --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/es_MX.lproj/Localizable.strings @@ -0,0 +1,57 @@ +// NOTE: This is the english localization and has been copied to all languages. The reason for doing +// this is it signals to iOS that the app "supports" all of these languages. This is helpful for a +// demo as it allows you to try out the localization features of the Places SDK. In a shipping app +// you should never do this, it is for demonstration purposes only. + + +// The name of the app to display in a navigation bar along with a placeholder for the SDK version number. +"App.NameAndVersion"="Places Demos: %1$@"; + +// Title of the full-screen autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.FullScreen"="Full-Screen Autocomplete"; +// Title of the pushed autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Push"="Push Autocomplete"; +// Title of the UISearchController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchController"="UISearchController"; +// Title of the UISearchDisplayController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchDisplayController"="UISearchDisplayController"; +// Title of the UITextField autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UITextField"="UITextField"; +// Title of the Styling autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Styling"="Custom Autocomplete Styling"; +// Title of the photos demo for display in a list or nav header +"Demo.Title.Photos"="Photos"; +// Title of the 'select place' button within the photos demo +"Demo.Title.Photos.SelectPlace"="Select Place"; +// Title of the Place Picker demo for displaying the picker in a popover, navigation controller, or modally. +"Demo.Title.PlacePicker.ViewController"="Place Picker View Controller"; +// Title of the autocomplete demo section +"Demo.Section.Title.Autocomplete"="Autocomplete"; +// Title of the 'Programmatic' demo section +"Demo.Section.Title.Programmatic"="Programmatic APIs"; +// Title of the findPlacesLikelihood demo section +"Demo.Section.Title.FindPlaceLikelihood"="Likelihoods"; + +// Button title for 'show autocomplete widget' +"Demo.Content.Autocomplete.ShowWidgetButton"="Show Autocomplete Widget"; +// Prompt to enter text for autocomplete demo +"Demo.Content.Autocomplete.EnterTextPrompt"="Enter Autocomplete Text Here"; +// Format string for 'autocomplete failed with error' message +"Demo.Content.Autocomplete.FailedErrorMessage"="Autocomplete failed with error: %1$@"; +// String for 'autocomplete canceled message' +"Demo.Content.Autocomplete.WasCanceledMessage"="Autocomplete was canceled"; +// Button title for the 'Yellow and Brown' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.YellowAndBrown"="Yellow and Brown"; +// Button title for the 'White on Black' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.WhiteOnBlack"="White on Black"; +// Button title for the 'Blue Colors' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.BlueColors"="Blue Colors"; +// Button title for the 'Hot Dog Stand' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.HotDogStand"="Hot Dog Stand"; +// Button title for the 'Popover' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Popover"="Popover"; +// Button title for the 'Navigation' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Navigation"="Navigation"; +// Button title for the 'Modal' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Modal"="Modal"; + diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/fi.lproj/Localizable.strings a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/fi.lproj/Localizable.strings new file mode 100755 index 0000000..f01e41c --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/fi.lproj/Localizable.strings @@ -0,0 +1,57 @@ +// NOTE: This is the english localization and has been copied to all languages. The reason for doing +// this is it signals to iOS that the app "supports" all of these languages. This is helpful for a +// demo as it allows you to try out the localization features of the Places SDK. In a shipping app +// you should never do this, it is for demonstration purposes only. + + +// The name of the app to display in a navigation bar along with a placeholder for the SDK version number. +"App.NameAndVersion"="Places Demos: %1$@"; + +// Title of the full-screen autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.FullScreen"="Full-Screen Autocomplete"; +// Title of the pushed autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Push"="Push Autocomplete"; +// Title of the UISearchController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchController"="UISearchController"; +// Title of the UISearchDisplayController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchDisplayController"="UISearchDisplayController"; +// Title of the UITextField autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UITextField"="UITextField"; +// Title of the Styling autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Styling"="Custom Autocomplete Styling"; +// Title of the photos demo for display in a list or nav header +"Demo.Title.Photos"="Photos"; +// Title of the 'select place' button within the photos demo +"Demo.Title.Photos.SelectPlace"="Select Place"; +// Title of the Place Picker demo for displaying the picker in a popover, navigation controller, or modally. +"Demo.Title.PlacePicker.ViewController"="Place Picker View Controller"; +// Title of the autocomplete demo section +"Demo.Section.Title.Autocomplete"="Autocomplete"; +// Title of the 'Programmatic' demo section +"Demo.Section.Title.Programmatic"="Programmatic APIs"; +// Title of the findPlacesLikelihood demo section +"Demo.Section.Title.FindPlaceLikelihood"="Likelihoods"; + +// Button title for 'show autocomplete widget' +"Demo.Content.Autocomplete.ShowWidgetButton"="Show Autocomplete Widget"; +// Prompt to enter text for autocomplete demo +"Demo.Content.Autocomplete.EnterTextPrompt"="Enter Autocomplete Text Here"; +// Format string for 'autocomplete failed with error' message +"Demo.Content.Autocomplete.FailedErrorMessage"="Autocomplete failed with error: %1$@"; +// String for 'autocomplete canceled message' +"Demo.Content.Autocomplete.WasCanceledMessage"="Autocomplete was canceled"; +// Button title for the 'Yellow and Brown' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.YellowAndBrown"="Yellow and Brown"; +// Button title for the 'White on Black' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.WhiteOnBlack"="White on Black"; +// Button title for the 'Blue Colors' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.BlueColors"="Blue Colors"; +// Button title for the 'Hot Dog Stand' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.HotDogStand"="Hot Dog Stand"; +// Button title for the 'Popover' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Popover"="Popover"; +// Button title for the 'Navigation' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Navigation"="Navigation"; +// Button title for the 'Modal' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Modal"="Modal"; + diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/fr.lproj/Localizable.strings a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/fr.lproj/Localizable.strings new file mode 100755 index 0000000..f01e41c --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/fr.lproj/Localizable.strings @@ -0,0 +1,57 @@ +// NOTE: This is the english localization and has been copied to all languages. The reason for doing +// this is it signals to iOS that the app "supports" all of these languages. This is helpful for a +// demo as it allows you to try out the localization features of the Places SDK. In a shipping app +// you should never do this, it is for demonstration purposes only. + + +// The name of the app to display in a navigation bar along with a placeholder for the SDK version number. +"App.NameAndVersion"="Places Demos: %1$@"; + +// Title of the full-screen autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.FullScreen"="Full-Screen Autocomplete"; +// Title of the pushed autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Push"="Push Autocomplete"; +// Title of the UISearchController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchController"="UISearchController"; +// Title of the UISearchDisplayController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchDisplayController"="UISearchDisplayController"; +// Title of the UITextField autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UITextField"="UITextField"; +// Title of the Styling autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Styling"="Custom Autocomplete Styling"; +// Title of the photos demo for display in a list or nav header +"Demo.Title.Photos"="Photos"; +// Title of the 'select place' button within the photos demo +"Demo.Title.Photos.SelectPlace"="Select Place"; +// Title of the Place Picker demo for displaying the picker in a popover, navigation controller, or modally. +"Demo.Title.PlacePicker.ViewController"="Place Picker View Controller"; +// Title of the autocomplete demo section +"Demo.Section.Title.Autocomplete"="Autocomplete"; +// Title of the 'Programmatic' demo section +"Demo.Section.Title.Programmatic"="Programmatic APIs"; +// Title of the findPlacesLikelihood demo section +"Demo.Section.Title.FindPlaceLikelihood"="Likelihoods"; + +// Button title for 'show autocomplete widget' +"Demo.Content.Autocomplete.ShowWidgetButton"="Show Autocomplete Widget"; +// Prompt to enter text for autocomplete demo +"Demo.Content.Autocomplete.EnterTextPrompt"="Enter Autocomplete Text Here"; +// Format string for 'autocomplete failed with error' message +"Demo.Content.Autocomplete.FailedErrorMessage"="Autocomplete failed with error: %1$@"; +// String for 'autocomplete canceled message' +"Demo.Content.Autocomplete.WasCanceledMessage"="Autocomplete was canceled"; +// Button title for the 'Yellow and Brown' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.YellowAndBrown"="Yellow and Brown"; +// Button title for the 'White on Black' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.WhiteOnBlack"="White on Black"; +// Button title for the 'Blue Colors' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.BlueColors"="Blue Colors"; +// Button title for the 'Hot Dog Stand' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.HotDogStand"="Hot Dog Stand"; +// Button title for the 'Popover' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Popover"="Popover"; +// Button title for the 'Navigation' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Navigation"="Navigation"; +// Button title for the 'Modal' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Modal"="Modal"; + diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/fr_CA.lproj/Localizable.strings a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/fr_CA.lproj/Localizable.strings new file mode 100755 index 0000000..f01e41c --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/fr_CA.lproj/Localizable.strings @@ -0,0 +1,57 @@ +// NOTE: This is the english localization and has been copied to all languages. The reason for doing +// this is it signals to iOS that the app "supports" all of these languages. This is helpful for a +// demo as it allows you to try out the localization features of the Places SDK. In a shipping app +// you should never do this, it is for demonstration purposes only. + + +// The name of the app to display in a navigation bar along with a placeholder for the SDK version number. +"App.NameAndVersion"="Places Demos: %1$@"; + +// Title of the full-screen autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.FullScreen"="Full-Screen Autocomplete"; +// Title of the pushed autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Push"="Push Autocomplete"; +// Title of the UISearchController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchController"="UISearchController"; +// Title of the UISearchDisplayController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchDisplayController"="UISearchDisplayController"; +// Title of the UITextField autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UITextField"="UITextField"; +// Title of the Styling autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Styling"="Custom Autocomplete Styling"; +// Title of the photos demo for display in a list or nav header +"Demo.Title.Photos"="Photos"; +// Title of the 'select place' button within the photos demo +"Demo.Title.Photos.SelectPlace"="Select Place"; +// Title of the Place Picker demo for displaying the picker in a popover, navigation controller, or modally. +"Demo.Title.PlacePicker.ViewController"="Place Picker View Controller"; +// Title of the autocomplete demo section +"Demo.Section.Title.Autocomplete"="Autocomplete"; +// Title of the 'Programmatic' demo section +"Demo.Section.Title.Programmatic"="Programmatic APIs"; +// Title of the findPlacesLikelihood demo section +"Demo.Section.Title.FindPlaceLikelihood"="Likelihoods"; + +// Button title for 'show autocomplete widget' +"Demo.Content.Autocomplete.ShowWidgetButton"="Show Autocomplete Widget"; +// Prompt to enter text for autocomplete demo +"Demo.Content.Autocomplete.EnterTextPrompt"="Enter Autocomplete Text Here"; +// Format string for 'autocomplete failed with error' message +"Demo.Content.Autocomplete.FailedErrorMessage"="Autocomplete failed with error: %1$@"; +// String for 'autocomplete canceled message' +"Demo.Content.Autocomplete.WasCanceledMessage"="Autocomplete was canceled"; +// Button title for the 'Yellow and Brown' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.YellowAndBrown"="Yellow and Brown"; +// Button title for the 'White on Black' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.WhiteOnBlack"="White on Black"; +// Button title for the 'Blue Colors' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.BlueColors"="Blue Colors"; +// Button title for the 'Hot Dog Stand' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.HotDogStand"="Hot Dog Stand"; +// Button title for the 'Popover' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Popover"="Popover"; +// Button title for the 'Navigation' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Navigation"="Navigation"; +// Button title for the 'Modal' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Modal"="Modal"; + diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/he.lproj/Localizable.strings a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/he.lproj/Localizable.strings new file mode 100755 index 0000000..f01e41c --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/he.lproj/Localizable.strings @@ -0,0 +1,57 @@ +// NOTE: This is the english localization and has been copied to all languages. The reason for doing +// this is it signals to iOS that the app "supports" all of these languages. This is helpful for a +// demo as it allows you to try out the localization features of the Places SDK. In a shipping app +// you should never do this, it is for demonstration purposes only. + + +// The name of the app to display in a navigation bar along with a placeholder for the SDK version number. +"App.NameAndVersion"="Places Demos: %1$@"; + +// Title of the full-screen autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.FullScreen"="Full-Screen Autocomplete"; +// Title of the pushed autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Push"="Push Autocomplete"; +// Title of the UISearchController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchController"="UISearchController"; +// Title of the UISearchDisplayController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchDisplayController"="UISearchDisplayController"; +// Title of the UITextField autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UITextField"="UITextField"; +// Title of the Styling autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Styling"="Custom Autocomplete Styling"; +// Title of the photos demo for display in a list or nav header +"Demo.Title.Photos"="Photos"; +// Title of the 'select place' button within the photos demo +"Demo.Title.Photos.SelectPlace"="Select Place"; +// Title of the Place Picker demo for displaying the picker in a popover, navigation controller, or modally. +"Demo.Title.PlacePicker.ViewController"="Place Picker View Controller"; +// Title of the autocomplete demo section +"Demo.Section.Title.Autocomplete"="Autocomplete"; +// Title of the 'Programmatic' demo section +"Demo.Section.Title.Programmatic"="Programmatic APIs"; +// Title of the findPlacesLikelihood demo section +"Demo.Section.Title.FindPlaceLikelihood"="Likelihoods"; + +// Button title for 'show autocomplete widget' +"Demo.Content.Autocomplete.ShowWidgetButton"="Show Autocomplete Widget"; +// Prompt to enter text for autocomplete demo +"Demo.Content.Autocomplete.EnterTextPrompt"="Enter Autocomplete Text Here"; +// Format string for 'autocomplete failed with error' message +"Demo.Content.Autocomplete.FailedErrorMessage"="Autocomplete failed with error: %1$@"; +// String for 'autocomplete canceled message' +"Demo.Content.Autocomplete.WasCanceledMessage"="Autocomplete was canceled"; +// Button title for the 'Yellow and Brown' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.YellowAndBrown"="Yellow and Brown"; +// Button title for the 'White on Black' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.WhiteOnBlack"="White on Black"; +// Button title for the 'Blue Colors' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.BlueColors"="Blue Colors"; +// Button title for the 'Hot Dog Stand' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.HotDogStand"="Hot Dog Stand"; +// Button title for the 'Popover' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Popover"="Popover"; +// Button title for the 'Navigation' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Navigation"="Navigation"; +// Button title for the 'Modal' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Modal"="Modal"; + diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/hi.lproj/Localizable.strings a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/hi.lproj/Localizable.strings new file mode 100755 index 0000000..f01e41c --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/hi.lproj/Localizable.strings @@ -0,0 +1,57 @@ +// NOTE: This is the english localization and has been copied to all languages. The reason for doing +// this is it signals to iOS that the app "supports" all of these languages. This is helpful for a +// demo as it allows you to try out the localization features of the Places SDK. In a shipping app +// you should never do this, it is for demonstration purposes only. + + +// The name of the app to display in a navigation bar along with a placeholder for the SDK version number. +"App.NameAndVersion"="Places Demos: %1$@"; + +// Title of the full-screen autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.FullScreen"="Full-Screen Autocomplete"; +// Title of the pushed autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Push"="Push Autocomplete"; +// Title of the UISearchController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchController"="UISearchController"; +// Title of the UISearchDisplayController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchDisplayController"="UISearchDisplayController"; +// Title of the UITextField autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UITextField"="UITextField"; +// Title of the Styling autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Styling"="Custom Autocomplete Styling"; +// Title of the photos demo for display in a list or nav header +"Demo.Title.Photos"="Photos"; +// Title of the 'select place' button within the photos demo +"Demo.Title.Photos.SelectPlace"="Select Place"; +// Title of the Place Picker demo for displaying the picker in a popover, navigation controller, or modally. +"Demo.Title.PlacePicker.ViewController"="Place Picker View Controller"; +// Title of the autocomplete demo section +"Demo.Section.Title.Autocomplete"="Autocomplete"; +// Title of the 'Programmatic' demo section +"Demo.Section.Title.Programmatic"="Programmatic APIs"; +// Title of the findPlacesLikelihood demo section +"Demo.Section.Title.FindPlaceLikelihood"="Likelihoods"; + +// Button title for 'show autocomplete widget' +"Demo.Content.Autocomplete.ShowWidgetButton"="Show Autocomplete Widget"; +// Prompt to enter text for autocomplete demo +"Demo.Content.Autocomplete.EnterTextPrompt"="Enter Autocomplete Text Here"; +// Format string for 'autocomplete failed with error' message +"Demo.Content.Autocomplete.FailedErrorMessage"="Autocomplete failed with error: %1$@"; +// String for 'autocomplete canceled message' +"Demo.Content.Autocomplete.WasCanceledMessage"="Autocomplete was canceled"; +// Button title for the 'Yellow and Brown' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.YellowAndBrown"="Yellow and Brown"; +// Button title for the 'White on Black' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.WhiteOnBlack"="White on Black"; +// Button title for the 'Blue Colors' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.BlueColors"="Blue Colors"; +// Button title for the 'Hot Dog Stand' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.HotDogStand"="Hot Dog Stand"; +// Button title for the 'Popover' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Popover"="Popover"; +// Button title for the 'Navigation' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Navigation"="Navigation"; +// Button title for the 'Modal' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Modal"="Modal"; + diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/hr.lproj/Localizable.strings a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/hr.lproj/Localizable.strings new file mode 100755 index 0000000..f01e41c --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/hr.lproj/Localizable.strings @@ -0,0 +1,57 @@ +// NOTE: This is the english localization and has been copied to all languages. The reason for doing +// this is it signals to iOS that the app "supports" all of these languages. This is helpful for a +// demo as it allows you to try out the localization features of the Places SDK. In a shipping app +// you should never do this, it is for demonstration purposes only. + + +// The name of the app to display in a navigation bar along with a placeholder for the SDK version number. +"App.NameAndVersion"="Places Demos: %1$@"; + +// Title of the full-screen autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.FullScreen"="Full-Screen Autocomplete"; +// Title of the pushed autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Push"="Push Autocomplete"; +// Title of the UISearchController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchController"="UISearchController"; +// Title of the UISearchDisplayController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchDisplayController"="UISearchDisplayController"; +// Title of the UITextField autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UITextField"="UITextField"; +// Title of the Styling autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Styling"="Custom Autocomplete Styling"; +// Title of the photos demo for display in a list or nav header +"Demo.Title.Photos"="Photos"; +// Title of the 'select place' button within the photos demo +"Demo.Title.Photos.SelectPlace"="Select Place"; +// Title of the Place Picker demo for displaying the picker in a popover, navigation controller, or modally. +"Demo.Title.PlacePicker.ViewController"="Place Picker View Controller"; +// Title of the autocomplete demo section +"Demo.Section.Title.Autocomplete"="Autocomplete"; +// Title of the 'Programmatic' demo section +"Demo.Section.Title.Programmatic"="Programmatic APIs"; +// Title of the findPlacesLikelihood demo section +"Demo.Section.Title.FindPlaceLikelihood"="Likelihoods"; + +// Button title for 'show autocomplete widget' +"Demo.Content.Autocomplete.ShowWidgetButton"="Show Autocomplete Widget"; +// Prompt to enter text for autocomplete demo +"Demo.Content.Autocomplete.EnterTextPrompt"="Enter Autocomplete Text Here"; +// Format string for 'autocomplete failed with error' message +"Demo.Content.Autocomplete.FailedErrorMessage"="Autocomplete failed with error: %1$@"; +// String for 'autocomplete canceled message' +"Demo.Content.Autocomplete.WasCanceledMessage"="Autocomplete was canceled"; +// Button title for the 'Yellow and Brown' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.YellowAndBrown"="Yellow and Brown"; +// Button title for the 'White on Black' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.WhiteOnBlack"="White on Black"; +// Button title for the 'Blue Colors' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.BlueColors"="Blue Colors"; +// Button title for the 'Hot Dog Stand' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.HotDogStand"="Hot Dog Stand"; +// Button title for the 'Popover' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Popover"="Popover"; +// Button title for the 'Navigation' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Navigation"="Navigation"; +// Button title for the 'Modal' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Modal"="Modal"; + diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/hu.lproj/Localizable.strings a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/hu.lproj/Localizable.strings new file mode 100755 index 0000000..f01e41c --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/hu.lproj/Localizable.strings @@ -0,0 +1,57 @@ +// NOTE: This is the english localization and has been copied to all languages. The reason for doing +// this is it signals to iOS that the app "supports" all of these languages. This is helpful for a +// demo as it allows you to try out the localization features of the Places SDK. In a shipping app +// you should never do this, it is for demonstration purposes only. + + +// The name of the app to display in a navigation bar along with a placeholder for the SDK version number. +"App.NameAndVersion"="Places Demos: %1$@"; + +// Title of the full-screen autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.FullScreen"="Full-Screen Autocomplete"; +// Title of the pushed autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Push"="Push Autocomplete"; +// Title of the UISearchController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchController"="UISearchController"; +// Title of the UISearchDisplayController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchDisplayController"="UISearchDisplayController"; +// Title of the UITextField autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UITextField"="UITextField"; +// Title of the Styling autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Styling"="Custom Autocomplete Styling"; +// Title of the photos demo for display in a list or nav header +"Demo.Title.Photos"="Photos"; +// Title of the 'select place' button within the photos demo +"Demo.Title.Photos.SelectPlace"="Select Place"; +// Title of the Place Picker demo for displaying the picker in a popover, navigation controller, or modally. +"Demo.Title.PlacePicker.ViewController"="Place Picker View Controller"; +// Title of the autocomplete demo section +"Demo.Section.Title.Autocomplete"="Autocomplete"; +// Title of the 'Programmatic' demo section +"Demo.Section.Title.Programmatic"="Programmatic APIs"; +// Title of the findPlacesLikelihood demo section +"Demo.Section.Title.FindPlaceLikelihood"="Likelihoods"; + +// Button title for 'show autocomplete widget' +"Demo.Content.Autocomplete.ShowWidgetButton"="Show Autocomplete Widget"; +// Prompt to enter text for autocomplete demo +"Demo.Content.Autocomplete.EnterTextPrompt"="Enter Autocomplete Text Here"; +// Format string for 'autocomplete failed with error' message +"Demo.Content.Autocomplete.FailedErrorMessage"="Autocomplete failed with error: %1$@"; +// String for 'autocomplete canceled message' +"Demo.Content.Autocomplete.WasCanceledMessage"="Autocomplete was canceled"; +// Button title for the 'Yellow and Brown' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.YellowAndBrown"="Yellow and Brown"; +// Button title for the 'White on Black' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.WhiteOnBlack"="White on Black"; +// Button title for the 'Blue Colors' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.BlueColors"="Blue Colors"; +// Button title for the 'Hot Dog Stand' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.HotDogStand"="Hot Dog Stand"; +// Button title for the 'Popover' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Popover"="Popover"; +// Button title for the 'Navigation' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Navigation"="Navigation"; +// Button title for the 'Modal' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Modal"="Modal"; + diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/id.lproj/Localizable.strings a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/id.lproj/Localizable.strings new file mode 100755 index 0000000..f01e41c --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/id.lproj/Localizable.strings @@ -0,0 +1,57 @@ +// NOTE: This is the english localization and has been copied to all languages. The reason for doing +// this is it signals to iOS that the app "supports" all of these languages. This is helpful for a +// demo as it allows you to try out the localization features of the Places SDK. In a shipping app +// you should never do this, it is for demonstration purposes only. + + +// The name of the app to display in a navigation bar along with a placeholder for the SDK version number. +"App.NameAndVersion"="Places Demos: %1$@"; + +// Title of the full-screen autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.FullScreen"="Full-Screen Autocomplete"; +// Title of the pushed autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Push"="Push Autocomplete"; +// Title of the UISearchController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchController"="UISearchController"; +// Title of the UISearchDisplayController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchDisplayController"="UISearchDisplayController"; +// Title of the UITextField autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UITextField"="UITextField"; +// Title of the Styling autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Styling"="Custom Autocomplete Styling"; +// Title of the photos demo for display in a list or nav header +"Demo.Title.Photos"="Photos"; +// Title of the 'select place' button within the photos demo +"Demo.Title.Photos.SelectPlace"="Select Place"; +// Title of the Place Picker demo for displaying the picker in a popover, navigation controller, or modally. +"Demo.Title.PlacePicker.ViewController"="Place Picker View Controller"; +// Title of the autocomplete demo section +"Demo.Section.Title.Autocomplete"="Autocomplete"; +// Title of the 'Programmatic' demo section +"Demo.Section.Title.Programmatic"="Programmatic APIs"; +// Title of the findPlacesLikelihood demo section +"Demo.Section.Title.FindPlaceLikelihood"="Likelihoods"; + +// Button title for 'show autocomplete widget' +"Demo.Content.Autocomplete.ShowWidgetButton"="Show Autocomplete Widget"; +// Prompt to enter text for autocomplete demo +"Demo.Content.Autocomplete.EnterTextPrompt"="Enter Autocomplete Text Here"; +// Format string for 'autocomplete failed with error' message +"Demo.Content.Autocomplete.FailedErrorMessage"="Autocomplete failed with error: %1$@"; +// String for 'autocomplete canceled message' +"Demo.Content.Autocomplete.WasCanceledMessage"="Autocomplete was canceled"; +// Button title for the 'Yellow and Brown' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.YellowAndBrown"="Yellow and Brown"; +// Button title for the 'White on Black' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.WhiteOnBlack"="White on Black"; +// Button title for the 'Blue Colors' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.BlueColors"="Blue Colors"; +// Button title for the 'Hot Dog Stand' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.HotDogStand"="Hot Dog Stand"; +// Button title for the 'Popover' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Popover"="Popover"; +// Button title for the 'Navigation' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Navigation"="Navigation"; +// Button title for the 'Modal' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Modal"="Modal"; + diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/it.lproj/Localizable.strings a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/it.lproj/Localizable.strings new file mode 100755 index 0000000..f01e41c --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/it.lproj/Localizable.strings @@ -0,0 +1,57 @@ +// NOTE: This is the english localization and has been copied to all languages. The reason for doing +// this is it signals to iOS that the app "supports" all of these languages. This is helpful for a +// demo as it allows you to try out the localization features of the Places SDK. In a shipping app +// you should never do this, it is for demonstration purposes only. + + +// The name of the app to display in a navigation bar along with a placeholder for the SDK version number. +"App.NameAndVersion"="Places Demos: %1$@"; + +// Title of the full-screen autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.FullScreen"="Full-Screen Autocomplete"; +// Title of the pushed autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Push"="Push Autocomplete"; +// Title of the UISearchController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchController"="UISearchController"; +// Title of the UISearchDisplayController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchDisplayController"="UISearchDisplayController"; +// Title of the UITextField autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UITextField"="UITextField"; +// Title of the Styling autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Styling"="Custom Autocomplete Styling"; +// Title of the photos demo for display in a list or nav header +"Demo.Title.Photos"="Photos"; +// Title of the 'select place' button within the photos demo +"Demo.Title.Photos.SelectPlace"="Select Place"; +// Title of the Place Picker demo for displaying the picker in a popover, navigation controller, or modally. +"Demo.Title.PlacePicker.ViewController"="Place Picker View Controller"; +// Title of the autocomplete demo section +"Demo.Section.Title.Autocomplete"="Autocomplete"; +// Title of the 'Programmatic' demo section +"Demo.Section.Title.Programmatic"="Programmatic APIs"; +// Title of the findPlacesLikelihood demo section +"Demo.Section.Title.FindPlaceLikelihood"="Likelihoods"; + +// Button title for 'show autocomplete widget' +"Demo.Content.Autocomplete.ShowWidgetButton"="Show Autocomplete Widget"; +// Prompt to enter text for autocomplete demo +"Demo.Content.Autocomplete.EnterTextPrompt"="Enter Autocomplete Text Here"; +// Format string for 'autocomplete failed with error' message +"Demo.Content.Autocomplete.FailedErrorMessage"="Autocomplete failed with error: %1$@"; +// String for 'autocomplete canceled message' +"Demo.Content.Autocomplete.WasCanceledMessage"="Autocomplete was canceled"; +// Button title for the 'Yellow and Brown' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.YellowAndBrown"="Yellow and Brown"; +// Button title for the 'White on Black' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.WhiteOnBlack"="White on Black"; +// Button title for the 'Blue Colors' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.BlueColors"="Blue Colors"; +// Button title for the 'Hot Dog Stand' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.HotDogStand"="Hot Dog Stand"; +// Button title for the 'Popover' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Popover"="Popover"; +// Button title for the 'Navigation' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Navigation"="Navigation"; +// Button title for the 'Modal' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Modal"="Modal"; + diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/ja.lproj/Localizable.strings a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/ja.lproj/Localizable.strings new file mode 100755 index 0000000..f01e41c --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/ja.lproj/Localizable.strings @@ -0,0 +1,57 @@ +// NOTE: This is the english localization and has been copied to all languages. The reason for doing +// this is it signals to iOS that the app "supports" all of these languages. This is helpful for a +// demo as it allows you to try out the localization features of the Places SDK. In a shipping app +// you should never do this, it is for demonstration purposes only. + + +// The name of the app to display in a navigation bar along with a placeholder for the SDK version number. +"App.NameAndVersion"="Places Demos: %1$@"; + +// Title of the full-screen autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.FullScreen"="Full-Screen Autocomplete"; +// Title of the pushed autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Push"="Push Autocomplete"; +// Title of the UISearchController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchController"="UISearchController"; +// Title of the UISearchDisplayController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchDisplayController"="UISearchDisplayController"; +// Title of the UITextField autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UITextField"="UITextField"; +// Title of the Styling autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Styling"="Custom Autocomplete Styling"; +// Title of the photos demo for display in a list or nav header +"Demo.Title.Photos"="Photos"; +// Title of the 'select place' button within the photos demo +"Demo.Title.Photos.SelectPlace"="Select Place"; +// Title of the Place Picker demo for displaying the picker in a popover, navigation controller, or modally. +"Demo.Title.PlacePicker.ViewController"="Place Picker View Controller"; +// Title of the autocomplete demo section +"Demo.Section.Title.Autocomplete"="Autocomplete"; +// Title of the 'Programmatic' demo section +"Demo.Section.Title.Programmatic"="Programmatic APIs"; +// Title of the findPlacesLikelihood demo section +"Demo.Section.Title.FindPlaceLikelihood"="Likelihoods"; + +// Button title for 'show autocomplete widget' +"Demo.Content.Autocomplete.ShowWidgetButton"="Show Autocomplete Widget"; +// Prompt to enter text for autocomplete demo +"Demo.Content.Autocomplete.EnterTextPrompt"="Enter Autocomplete Text Here"; +// Format string for 'autocomplete failed with error' message +"Demo.Content.Autocomplete.FailedErrorMessage"="Autocomplete failed with error: %1$@"; +// String for 'autocomplete canceled message' +"Demo.Content.Autocomplete.WasCanceledMessage"="Autocomplete was canceled"; +// Button title for the 'Yellow and Brown' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.YellowAndBrown"="Yellow and Brown"; +// Button title for the 'White on Black' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.WhiteOnBlack"="White on Black"; +// Button title for the 'Blue Colors' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.BlueColors"="Blue Colors"; +// Button title for the 'Hot Dog Stand' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.HotDogStand"="Hot Dog Stand"; +// Button title for the 'Popover' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Popover"="Popover"; +// Button title for the 'Navigation' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Navigation"="Navigation"; +// Button title for the 'Modal' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Modal"="Modal"; + diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/ko.lproj/Localizable.strings a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/ko.lproj/Localizable.strings new file mode 100755 index 0000000..f01e41c --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/ko.lproj/Localizable.strings @@ -0,0 +1,57 @@ +// NOTE: This is the english localization and has been copied to all languages. The reason for doing +// this is it signals to iOS that the app "supports" all of these languages. This is helpful for a +// demo as it allows you to try out the localization features of the Places SDK. In a shipping app +// you should never do this, it is for demonstration purposes only. + + +// The name of the app to display in a navigation bar along with a placeholder for the SDK version number. +"App.NameAndVersion"="Places Demos: %1$@"; + +// Title of the full-screen autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.FullScreen"="Full-Screen Autocomplete"; +// Title of the pushed autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Push"="Push Autocomplete"; +// Title of the UISearchController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchController"="UISearchController"; +// Title of the UISearchDisplayController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchDisplayController"="UISearchDisplayController"; +// Title of the UITextField autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UITextField"="UITextField"; +// Title of the Styling autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Styling"="Custom Autocomplete Styling"; +// Title of the photos demo for display in a list or nav header +"Demo.Title.Photos"="Photos"; +// Title of the 'select place' button within the photos demo +"Demo.Title.Photos.SelectPlace"="Select Place"; +// Title of the Place Picker demo for displaying the picker in a popover, navigation controller, or modally. +"Demo.Title.PlacePicker.ViewController"="Place Picker View Controller"; +// Title of the autocomplete demo section +"Demo.Section.Title.Autocomplete"="Autocomplete"; +// Title of the 'Programmatic' demo section +"Demo.Section.Title.Programmatic"="Programmatic APIs"; +// Title of the findPlacesLikelihood demo section +"Demo.Section.Title.FindPlaceLikelihood"="Likelihoods"; + +// Button title for 'show autocomplete widget' +"Demo.Content.Autocomplete.ShowWidgetButton"="Show Autocomplete Widget"; +// Prompt to enter text for autocomplete demo +"Demo.Content.Autocomplete.EnterTextPrompt"="Enter Autocomplete Text Here"; +// Format string for 'autocomplete failed with error' message +"Demo.Content.Autocomplete.FailedErrorMessage"="Autocomplete failed with error: %1$@"; +// String for 'autocomplete canceled message' +"Demo.Content.Autocomplete.WasCanceledMessage"="Autocomplete was canceled"; +// Button title for the 'Yellow and Brown' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.YellowAndBrown"="Yellow and Brown"; +// Button title for the 'White on Black' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.WhiteOnBlack"="White on Black"; +// Button title for the 'Blue Colors' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.BlueColors"="Blue Colors"; +// Button title for the 'Hot Dog Stand' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.HotDogStand"="Hot Dog Stand"; +// Button title for the 'Popover' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Popover"="Popover"; +// Button title for the 'Navigation' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Navigation"="Navigation"; +// Button title for the 'Modal' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Modal"="Modal"; + diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/ms.lproj/Localizable.strings a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/ms.lproj/Localizable.strings new file mode 100755 index 0000000..f01e41c --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/ms.lproj/Localizable.strings @@ -0,0 +1,57 @@ +// NOTE: This is the english localization and has been copied to all languages. The reason for doing +// this is it signals to iOS that the app "supports" all of these languages. This is helpful for a +// demo as it allows you to try out the localization features of the Places SDK. In a shipping app +// you should never do this, it is for demonstration purposes only. + + +// The name of the app to display in a navigation bar along with a placeholder for the SDK version number. +"App.NameAndVersion"="Places Demos: %1$@"; + +// Title of the full-screen autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.FullScreen"="Full-Screen Autocomplete"; +// Title of the pushed autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Push"="Push Autocomplete"; +// Title of the UISearchController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchController"="UISearchController"; +// Title of the UISearchDisplayController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchDisplayController"="UISearchDisplayController"; +// Title of the UITextField autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UITextField"="UITextField"; +// Title of the Styling autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Styling"="Custom Autocomplete Styling"; +// Title of the photos demo for display in a list or nav header +"Demo.Title.Photos"="Photos"; +// Title of the 'select place' button within the photos demo +"Demo.Title.Photos.SelectPlace"="Select Place"; +// Title of the Place Picker demo for displaying the picker in a popover, navigation controller, or modally. +"Demo.Title.PlacePicker.ViewController"="Place Picker View Controller"; +// Title of the autocomplete demo section +"Demo.Section.Title.Autocomplete"="Autocomplete"; +// Title of the 'Programmatic' demo section +"Demo.Section.Title.Programmatic"="Programmatic APIs"; +// Title of the findPlacesLikelihood demo section +"Demo.Section.Title.FindPlaceLikelihood"="Likelihoods"; + +// Button title for 'show autocomplete widget' +"Demo.Content.Autocomplete.ShowWidgetButton"="Show Autocomplete Widget"; +// Prompt to enter text for autocomplete demo +"Demo.Content.Autocomplete.EnterTextPrompt"="Enter Autocomplete Text Here"; +// Format string for 'autocomplete failed with error' message +"Demo.Content.Autocomplete.FailedErrorMessage"="Autocomplete failed with error: %1$@"; +// String for 'autocomplete canceled message' +"Demo.Content.Autocomplete.WasCanceledMessage"="Autocomplete was canceled"; +// Button title for the 'Yellow and Brown' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.YellowAndBrown"="Yellow and Brown"; +// Button title for the 'White on Black' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.WhiteOnBlack"="White on Black"; +// Button title for the 'Blue Colors' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.BlueColors"="Blue Colors"; +// Button title for the 'Hot Dog Stand' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.HotDogStand"="Hot Dog Stand"; +// Button title for the 'Popover' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Popover"="Popover"; +// Button title for the 'Navigation' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Navigation"="Navigation"; +// Button title for the 'Modal' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Modal"="Modal"; + diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/nb.lproj/Localizable.strings a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/nb.lproj/Localizable.strings new file mode 100755 index 0000000..f01e41c --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/nb.lproj/Localizable.strings @@ -0,0 +1,57 @@ +// NOTE: This is the english localization and has been copied to all languages. The reason for doing +// this is it signals to iOS that the app "supports" all of these languages. This is helpful for a +// demo as it allows you to try out the localization features of the Places SDK. In a shipping app +// you should never do this, it is for demonstration purposes only. + + +// The name of the app to display in a navigation bar along with a placeholder for the SDK version number. +"App.NameAndVersion"="Places Demos: %1$@"; + +// Title of the full-screen autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.FullScreen"="Full-Screen Autocomplete"; +// Title of the pushed autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Push"="Push Autocomplete"; +// Title of the UISearchController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchController"="UISearchController"; +// Title of the UISearchDisplayController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchDisplayController"="UISearchDisplayController"; +// Title of the UITextField autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UITextField"="UITextField"; +// Title of the Styling autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Styling"="Custom Autocomplete Styling"; +// Title of the photos demo for display in a list or nav header +"Demo.Title.Photos"="Photos"; +// Title of the 'select place' button within the photos demo +"Demo.Title.Photos.SelectPlace"="Select Place"; +// Title of the Place Picker demo for displaying the picker in a popover, navigation controller, or modally. +"Demo.Title.PlacePicker.ViewController"="Place Picker View Controller"; +// Title of the autocomplete demo section +"Demo.Section.Title.Autocomplete"="Autocomplete"; +// Title of the 'Programmatic' demo section +"Demo.Section.Title.Programmatic"="Programmatic APIs"; +// Title of the findPlacesLikelihood demo section +"Demo.Section.Title.FindPlaceLikelihood"="Likelihoods"; + +// Button title for 'show autocomplete widget' +"Demo.Content.Autocomplete.ShowWidgetButton"="Show Autocomplete Widget"; +// Prompt to enter text for autocomplete demo +"Demo.Content.Autocomplete.EnterTextPrompt"="Enter Autocomplete Text Here"; +// Format string for 'autocomplete failed with error' message +"Demo.Content.Autocomplete.FailedErrorMessage"="Autocomplete failed with error: %1$@"; +// String for 'autocomplete canceled message' +"Demo.Content.Autocomplete.WasCanceledMessage"="Autocomplete was canceled"; +// Button title for the 'Yellow and Brown' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.YellowAndBrown"="Yellow and Brown"; +// Button title for the 'White on Black' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.WhiteOnBlack"="White on Black"; +// Button title for the 'Blue Colors' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.BlueColors"="Blue Colors"; +// Button title for the 'Hot Dog Stand' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.HotDogStand"="Hot Dog Stand"; +// Button title for the 'Popover' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Popover"="Popover"; +// Button title for the 'Navigation' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Navigation"="Navigation"; +// Button title for the 'Modal' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Modal"="Modal"; + diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/nl.lproj/Localizable.strings a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/nl.lproj/Localizable.strings new file mode 100755 index 0000000..f01e41c --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/nl.lproj/Localizable.strings @@ -0,0 +1,57 @@ +// NOTE: This is the english localization and has been copied to all languages. The reason for doing +// this is it signals to iOS that the app "supports" all of these languages. This is helpful for a +// demo as it allows you to try out the localization features of the Places SDK. In a shipping app +// you should never do this, it is for demonstration purposes only. + + +// The name of the app to display in a navigation bar along with a placeholder for the SDK version number. +"App.NameAndVersion"="Places Demos: %1$@"; + +// Title of the full-screen autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.FullScreen"="Full-Screen Autocomplete"; +// Title of the pushed autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Push"="Push Autocomplete"; +// Title of the UISearchController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchController"="UISearchController"; +// Title of the UISearchDisplayController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchDisplayController"="UISearchDisplayController"; +// Title of the UITextField autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UITextField"="UITextField"; +// Title of the Styling autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Styling"="Custom Autocomplete Styling"; +// Title of the photos demo for display in a list or nav header +"Demo.Title.Photos"="Photos"; +// Title of the 'select place' button within the photos demo +"Demo.Title.Photos.SelectPlace"="Select Place"; +// Title of the Place Picker demo for displaying the picker in a popover, navigation controller, or modally. +"Demo.Title.PlacePicker.ViewController"="Place Picker View Controller"; +// Title of the autocomplete demo section +"Demo.Section.Title.Autocomplete"="Autocomplete"; +// Title of the 'Programmatic' demo section +"Demo.Section.Title.Programmatic"="Programmatic APIs"; +// Title of the findPlacesLikelihood demo section +"Demo.Section.Title.FindPlaceLikelihood"="Likelihoods"; + +// Button title for 'show autocomplete widget' +"Demo.Content.Autocomplete.ShowWidgetButton"="Show Autocomplete Widget"; +// Prompt to enter text for autocomplete demo +"Demo.Content.Autocomplete.EnterTextPrompt"="Enter Autocomplete Text Here"; +// Format string for 'autocomplete failed with error' message +"Demo.Content.Autocomplete.FailedErrorMessage"="Autocomplete failed with error: %1$@"; +// String for 'autocomplete canceled message' +"Demo.Content.Autocomplete.WasCanceledMessage"="Autocomplete was canceled"; +// Button title for the 'Yellow and Brown' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.YellowAndBrown"="Yellow and Brown"; +// Button title for the 'White on Black' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.WhiteOnBlack"="White on Black"; +// Button title for the 'Blue Colors' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.BlueColors"="Blue Colors"; +// Button title for the 'Hot Dog Stand' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.HotDogStand"="Hot Dog Stand"; +// Button title for the 'Popover' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Popover"="Popover"; +// Button title for the 'Navigation' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Navigation"="Navigation"; +// Button title for the 'Modal' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Modal"="Modal"; + diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/pl.lproj/Localizable.strings a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/pl.lproj/Localizable.strings new file mode 100755 index 0000000..f01e41c --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/pl.lproj/Localizable.strings @@ -0,0 +1,57 @@ +// NOTE: This is the english localization and has been copied to all languages. The reason for doing +// this is it signals to iOS that the app "supports" all of these languages. This is helpful for a +// demo as it allows you to try out the localization features of the Places SDK. In a shipping app +// you should never do this, it is for demonstration purposes only. + + +// The name of the app to display in a navigation bar along with a placeholder for the SDK version number. +"App.NameAndVersion"="Places Demos: %1$@"; + +// Title of the full-screen autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.FullScreen"="Full-Screen Autocomplete"; +// Title of the pushed autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Push"="Push Autocomplete"; +// Title of the UISearchController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchController"="UISearchController"; +// Title of the UISearchDisplayController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchDisplayController"="UISearchDisplayController"; +// Title of the UITextField autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UITextField"="UITextField"; +// Title of the Styling autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Styling"="Custom Autocomplete Styling"; +// Title of the photos demo for display in a list or nav header +"Demo.Title.Photos"="Photos"; +// Title of the 'select place' button within the photos demo +"Demo.Title.Photos.SelectPlace"="Select Place"; +// Title of the Place Picker demo for displaying the picker in a popover, navigation controller, or modally. +"Demo.Title.PlacePicker.ViewController"="Place Picker View Controller"; +// Title of the autocomplete demo section +"Demo.Section.Title.Autocomplete"="Autocomplete"; +// Title of the 'Programmatic' demo section +"Demo.Section.Title.Programmatic"="Programmatic APIs"; +// Title of the findPlacesLikelihood demo section +"Demo.Section.Title.FindPlaceLikelihood"="Likelihoods"; + +// Button title for 'show autocomplete widget' +"Demo.Content.Autocomplete.ShowWidgetButton"="Show Autocomplete Widget"; +// Prompt to enter text for autocomplete demo +"Demo.Content.Autocomplete.EnterTextPrompt"="Enter Autocomplete Text Here"; +// Format string for 'autocomplete failed with error' message +"Demo.Content.Autocomplete.FailedErrorMessage"="Autocomplete failed with error: %1$@"; +// String for 'autocomplete canceled message' +"Demo.Content.Autocomplete.WasCanceledMessage"="Autocomplete was canceled"; +// Button title for the 'Yellow and Brown' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.YellowAndBrown"="Yellow and Brown"; +// Button title for the 'White on Black' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.WhiteOnBlack"="White on Black"; +// Button title for the 'Blue Colors' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.BlueColors"="Blue Colors"; +// Button title for the 'Hot Dog Stand' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.HotDogStand"="Hot Dog Stand"; +// Button title for the 'Popover' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Popover"="Popover"; +// Button title for the 'Navigation' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Navigation"="Navigation"; +// Button title for the 'Modal' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Modal"="Modal"; + diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/pt.lproj/Localizable.strings a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/pt.lproj/Localizable.strings new file mode 100755 index 0000000..f01e41c --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/pt.lproj/Localizable.strings @@ -0,0 +1,57 @@ +// NOTE: This is the english localization and has been copied to all languages. The reason for doing +// this is it signals to iOS that the app "supports" all of these languages. This is helpful for a +// demo as it allows you to try out the localization features of the Places SDK. In a shipping app +// you should never do this, it is for demonstration purposes only. + + +// The name of the app to display in a navigation bar along with a placeholder for the SDK version number. +"App.NameAndVersion"="Places Demos: %1$@"; + +// Title of the full-screen autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.FullScreen"="Full-Screen Autocomplete"; +// Title of the pushed autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Push"="Push Autocomplete"; +// Title of the UISearchController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchController"="UISearchController"; +// Title of the UISearchDisplayController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchDisplayController"="UISearchDisplayController"; +// Title of the UITextField autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UITextField"="UITextField"; +// Title of the Styling autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Styling"="Custom Autocomplete Styling"; +// Title of the photos demo for display in a list or nav header +"Demo.Title.Photos"="Photos"; +// Title of the 'select place' button within the photos demo +"Demo.Title.Photos.SelectPlace"="Select Place"; +// Title of the Place Picker demo for displaying the picker in a popover, navigation controller, or modally. +"Demo.Title.PlacePicker.ViewController"="Place Picker View Controller"; +// Title of the autocomplete demo section +"Demo.Section.Title.Autocomplete"="Autocomplete"; +// Title of the 'Programmatic' demo section +"Demo.Section.Title.Programmatic"="Programmatic APIs"; +// Title of the findPlacesLikelihood demo section +"Demo.Section.Title.FindPlaceLikelihood"="Likelihoods"; + +// Button title for 'show autocomplete widget' +"Demo.Content.Autocomplete.ShowWidgetButton"="Show Autocomplete Widget"; +// Prompt to enter text for autocomplete demo +"Demo.Content.Autocomplete.EnterTextPrompt"="Enter Autocomplete Text Here"; +// Format string for 'autocomplete failed with error' message +"Demo.Content.Autocomplete.FailedErrorMessage"="Autocomplete failed with error: %1$@"; +// String for 'autocomplete canceled message' +"Demo.Content.Autocomplete.WasCanceledMessage"="Autocomplete was canceled"; +// Button title for the 'Yellow and Brown' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.YellowAndBrown"="Yellow and Brown"; +// Button title for the 'White on Black' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.WhiteOnBlack"="White on Black"; +// Button title for the 'Blue Colors' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.BlueColors"="Blue Colors"; +// Button title for the 'Hot Dog Stand' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.HotDogStand"="Hot Dog Stand"; +// Button title for the 'Popover' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Popover"="Popover"; +// Button title for the 'Navigation' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Navigation"="Navigation"; +// Button title for the 'Modal' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Modal"="Modal"; + diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/pt_BR.lproj/Localizable.strings a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/pt_BR.lproj/Localizable.strings new file mode 100755 index 0000000..f01e41c --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/pt_BR.lproj/Localizable.strings @@ -0,0 +1,57 @@ +// NOTE: This is the english localization and has been copied to all languages. The reason for doing +// this is it signals to iOS that the app "supports" all of these languages. This is helpful for a +// demo as it allows you to try out the localization features of the Places SDK. In a shipping app +// you should never do this, it is for demonstration purposes only. + + +// The name of the app to display in a navigation bar along with a placeholder for the SDK version number. +"App.NameAndVersion"="Places Demos: %1$@"; + +// Title of the full-screen autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.FullScreen"="Full-Screen Autocomplete"; +// Title of the pushed autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Push"="Push Autocomplete"; +// Title of the UISearchController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchController"="UISearchController"; +// Title of the UISearchDisplayController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchDisplayController"="UISearchDisplayController"; +// Title of the UITextField autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UITextField"="UITextField"; +// Title of the Styling autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Styling"="Custom Autocomplete Styling"; +// Title of the photos demo for display in a list or nav header +"Demo.Title.Photos"="Photos"; +// Title of the 'select place' button within the photos demo +"Demo.Title.Photos.SelectPlace"="Select Place"; +// Title of the Place Picker demo for displaying the picker in a popover, navigation controller, or modally. +"Demo.Title.PlacePicker.ViewController"="Place Picker View Controller"; +// Title of the autocomplete demo section +"Demo.Section.Title.Autocomplete"="Autocomplete"; +// Title of the 'Programmatic' demo section +"Demo.Section.Title.Programmatic"="Programmatic APIs"; +// Title of the findPlacesLikelihood demo section +"Demo.Section.Title.FindPlaceLikelihood"="Likelihoods"; + +// Button title for 'show autocomplete widget' +"Demo.Content.Autocomplete.ShowWidgetButton"="Show Autocomplete Widget"; +// Prompt to enter text for autocomplete demo +"Demo.Content.Autocomplete.EnterTextPrompt"="Enter Autocomplete Text Here"; +// Format string for 'autocomplete failed with error' message +"Demo.Content.Autocomplete.FailedErrorMessage"="Autocomplete failed with error: %1$@"; +// String for 'autocomplete canceled message' +"Demo.Content.Autocomplete.WasCanceledMessage"="Autocomplete was canceled"; +// Button title for the 'Yellow and Brown' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.YellowAndBrown"="Yellow and Brown"; +// Button title for the 'White on Black' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.WhiteOnBlack"="White on Black"; +// Button title for the 'Blue Colors' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.BlueColors"="Blue Colors"; +// Button title for the 'Hot Dog Stand' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.HotDogStand"="Hot Dog Stand"; +// Button title for the 'Popover' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Popover"="Popover"; +// Button title for the 'Navigation' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Navigation"="Navigation"; +// Button title for the 'Modal' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Modal"="Modal"; + diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/pt_PT.lproj/Localizable.strings a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/pt_PT.lproj/Localizable.strings new file mode 100755 index 0000000..f01e41c --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/pt_PT.lproj/Localizable.strings @@ -0,0 +1,57 @@ +// NOTE: This is the english localization and has been copied to all languages. The reason for doing +// this is it signals to iOS that the app "supports" all of these languages. This is helpful for a +// demo as it allows you to try out the localization features of the Places SDK. In a shipping app +// you should never do this, it is for demonstration purposes only. + + +// The name of the app to display in a navigation bar along with a placeholder for the SDK version number. +"App.NameAndVersion"="Places Demos: %1$@"; + +// Title of the full-screen autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.FullScreen"="Full-Screen Autocomplete"; +// Title of the pushed autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Push"="Push Autocomplete"; +// Title of the UISearchController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchController"="UISearchController"; +// Title of the UISearchDisplayController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchDisplayController"="UISearchDisplayController"; +// Title of the UITextField autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UITextField"="UITextField"; +// Title of the Styling autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Styling"="Custom Autocomplete Styling"; +// Title of the photos demo for display in a list or nav header +"Demo.Title.Photos"="Photos"; +// Title of the 'select place' button within the photos demo +"Demo.Title.Photos.SelectPlace"="Select Place"; +// Title of the Place Picker demo for displaying the picker in a popover, navigation controller, or modally. +"Demo.Title.PlacePicker.ViewController"="Place Picker View Controller"; +// Title of the autocomplete demo section +"Demo.Section.Title.Autocomplete"="Autocomplete"; +// Title of the 'Programmatic' demo section +"Demo.Section.Title.Programmatic"="Programmatic APIs"; +// Title of the findPlacesLikelihood demo section +"Demo.Section.Title.FindPlaceLikelihood"="Likelihoods"; + +// Button title for 'show autocomplete widget' +"Demo.Content.Autocomplete.ShowWidgetButton"="Show Autocomplete Widget"; +// Prompt to enter text for autocomplete demo +"Demo.Content.Autocomplete.EnterTextPrompt"="Enter Autocomplete Text Here"; +// Format string for 'autocomplete failed with error' message +"Demo.Content.Autocomplete.FailedErrorMessage"="Autocomplete failed with error: %1$@"; +// String for 'autocomplete canceled message' +"Demo.Content.Autocomplete.WasCanceledMessage"="Autocomplete was canceled"; +// Button title for the 'Yellow and Brown' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.YellowAndBrown"="Yellow and Brown"; +// Button title for the 'White on Black' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.WhiteOnBlack"="White on Black"; +// Button title for the 'Blue Colors' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.BlueColors"="Blue Colors"; +// Button title for the 'Hot Dog Stand' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.HotDogStand"="Hot Dog Stand"; +// Button title for the 'Popover' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Popover"="Popover"; +// Button title for the 'Navigation' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Navigation"="Navigation"; +// Button title for the 'Modal' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Modal"="Modal"; + diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/ro.lproj/Localizable.strings a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/ro.lproj/Localizable.strings new file mode 100755 index 0000000..f01e41c --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/ro.lproj/Localizable.strings @@ -0,0 +1,57 @@ +// NOTE: This is the english localization and has been copied to all languages. The reason for doing +// this is it signals to iOS that the app "supports" all of these languages. This is helpful for a +// demo as it allows you to try out the localization features of the Places SDK. In a shipping app +// you should never do this, it is for demonstration purposes only. + + +// The name of the app to display in a navigation bar along with a placeholder for the SDK version number. +"App.NameAndVersion"="Places Demos: %1$@"; + +// Title of the full-screen autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.FullScreen"="Full-Screen Autocomplete"; +// Title of the pushed autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Push"="Push Autocomplete"; +// Title of the UISearchController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchController"="UISearchController"; +// Title of the UISearchDisplayController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchDisplayController"="UISearchDisplayController"; +// Title of the UITextField autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UITextField"="UITextField"; +// Title of the Styling autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Styling"="Custom Autocomplete Styling"; +// Title of the photos demo for display in a list or nav header +"Demo.Title.Photos"="Photos"; +// Title of the 'select place' button within the photos demo +"Demo.Title.Photos.SelectPlace"="Select Place"; +// Title of the Place Picker demo for displaying the picker in a popover, navigation controller, or modally. +"Demo.Title.PlacePicker.ViewController"="Place Picker View Controller"; +// Title of the autocomplete demo section +"Demo.Section.Title.Autocomplete"="Autocomplete"; +// Title of the 'Programmatic' demo section +"Demo.Section.Title.Programmatic"="Programmatic APIs"; +// Title of the findPlacesLikelihood demo section +"Demo.Section.Title.FindPlaceLikelihood"="Likelihoods"; + +// Button title for 'show autocomplete widget' +"Demo.Content.Autocomplete.ShowWidgetButton"="Show Autocomplete Widget"; +// Prompt to enter text for autocomplete demo +"Demo.Content.Autocomplete.EnterTextPrompt"="Enter Autocomplete Text Here"; +// Format string for 'autocomplete failed with error' message +"Demo.Content.Autocomplete.FailedErrorMessage"="Autocomplete failed with error: %1$@"; +// String for 'autocomplete canceled message' +"Demo.Content.Autocomplete.WasCanceledMessage"="Autocomplete was canceled"; +// Button title for the 'Yellow and Brown' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.YellowAndBrown"="Yellow and Brown"; +// Button title for the 'White on Black' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.WhiteOnBlack"="White on Black"; +// Button title for the 'Blue Colors' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.BlueColors"="Blue Colors"; +// Button title for the 'Hot Dog Stand' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.HotDogStand"="Hot Dog Stand"; +// Button title for the 'Popover' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Popover"="Popover"; +// Button title for the 'Navigation' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Navigation"="Navigation"; +// Button title for the 'Modal' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Modal"="Modal"; + diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/ru.lproj/Localizable.strings a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/ru.lproj/Localizable.strings new file mode 100755 index 0000000..f01e41c --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/ru.lproj/Localizable.strings @@ -0,0 +1,57 @@ +// NOTE: This is the english localization and has been copied to all languages. The reason for doing +// this is it signals to iOS that the app "supports" all of these languages. This is helpful for a +// demo as it allows you to try out the localization features of the Places SDK. In a shipping app +// you should never do this, it is for demonstration purposes only. + + +// The name of the app to display in a navigation bar along with a placeholder for the SDK version number. +"App.NameAndVersion"="Places Demos: %1$@"; + +// Title of the full-screen autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.FullScreen"="Full-Screen Autocomplete"; +// Title of the pushed autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Push"="Push Autocomplete"; +// Title of the UISearchController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchController"="UISearchController"; +// Title of the UISearchDisplayController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchDisplayController"="UISearchDisplayController"; +// Title of the UITextField autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UITextField"="UITextField"; +// Title of the Styling autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Styling"="Custom Autocomplete Styling"; +// Title of the photos demo for display in a list or nav header +"Demo.Title.Photos"="Photos"; +// Title of the 'select place' button within the photos demo +"Demo.Title.Photos.SelectPlace"="Select Place"; +// Title of the Place Picker demo for displaying the picker in a popover, navigation controller, or modally. +"Demo.Title.PlacePicker.ViewController"="Place Picker View Controller"; +// Title of the autocomplete demo section +"Demo.Section.Title.Autocomplete"="Autocomplete"; +// Title of the 'Programmatic' demo section +"Demo.Section.Title.Programmatic"="Programmatic APIs"; +// Title of the findPlacesLikelihood demo section +"Demo.Section.Title.FindPlaceLikelihood"="Likelihoods"; + +// Button title for 'show autocomplete widget' +"Demo.Content.Autocomplete.ShowWidgetButton"="Show Autocomplete Widget"; +// Prompt to enter text for autocomplete demo +"Demo.Content.Autocomplete.EnterTextPrompt"="Enter Autocomplete Text Here"; +// Format string for 'autocomplete failed with error' message +"Demo.Content.Autocomplete.FailedErrorMessage"="Autocomplete failed with error: %1$@"; +// String for 'autocomplete canceled message' +"Demo.Content.Autocomplete.WasCanceledMessage"="Autocomplete was canceled"; +// Button title for the 'Yellow and Brown' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.YellowAndBrown"="Yellow and Brown"; +// Button title for the 'White on Black' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.WhiteOnBlack"="White on Black"; +// Button title for the 'Blue Colors' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.BlueColors"="Blue Colors"; +// Button title for the 'Hot Dog Stand' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.HotDogStand"="Hot Dog Stand"; +// Button title for the 'Popover' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Popover"="Popover"; +// Button title for the 'Navigation' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Navigation"="Navigation"; +// Button title for the 'Modal' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Modal"="Modal"; + diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/sk.lproj/Localizable.strings a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/sk.lproj/Localizable.strings new file mode 100755 index 0000000..f01e41c --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/sk.lproj/Localizable.strings @@ -0,0 +1,57 @@ +// NOTE: This is the english localization and has been copied to all languages. The reason for doing +// this is it signals to iOS that the app "supports" all of these languages. This is helpful for a +// demo as it allows you to try out the localization features of the Places SDK. In a shipping app +// you should never do this, it is for demonstration purposes only. + + +// The name of the app to display in a navigation bar along with a placeholder for the SDK version number. +"App.NameAndVersion"="Places Demos: %1$@"; + +// Title of the full-screen autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.FullScreen"="Full-Screen Autocomplete"; +// Title of the pushed autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Push"="Push Autocomplete"; +// Title of the UISearchController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchController"="UISearchController"; +// Title of the UISearchDisplayController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchDisplayController"="UISearchDisplayController"; +// Title of the UITextField autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UITextField"="UITextField"; +// Title of the Styling autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Styling"="Custom Autocomplete Styling"; +// Title of the photos demo for display in a list or nav header +"Demo.Title.Photos"="Photos"; +// Title of the 'select place' button within the photos demo +"Demo.Title.Photos.SelectPlace"="Select Place"; +// Title of the Place Picker demo for displaying the picker in a popover, navigation controller, or modally. +"Demo.Title.PlacePicker.ViewController"="Place Picker View Controller"; +// Title of the autocomplete demo section +"Demo.Section.Title.Autocomplete"="Autocomplete"; +// Title of the 'Programmatic' demo section +"Demo.Section.Title.Programmatic"="Programmatic APIs"; +// Title of the findPlacesLikelihood demo section +"Demo.Section.Title.FindPlaceLikelihood"="Likelihoods"; + +// Button title for 'show autocomplete widget' +"Demo.Content.Autocomplete.ShowWidgetButton"="Show Autocomplete Widget"; +// Prompt to enter text for autocomplete demo +"Demo.Content.Autocomplete.EnterTextPrompt"="Enter Autocomplete Text Here"; +// Format string for 'autocomplete failed with error' message +"Demo.Content.Autocomplete.FailedErrorMessage"="Autocomplete failed with error: %1$@"; +// String for 'autocomplete canceled message' +"Demo.Content.Autocomplete.WasCanceledMessage"="Autocomplete was canceled"; +// Button title for the 'Yellow and Brown' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.YellowAndBrown"="Yellow and Brown"; +// Button title for the 'White on Black' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.WhiteOnBlack"="White on Black"; +// Button title for the 'Blue Colors' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.BlueColors"="Blue Colors"; +// Button title for the 'Hot Dog Stand' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.HotDogStand"="Hot Dog Stand"; +// Button title for the 'Popover' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Popover"="Popover"; +// Button title for the 'Navigation' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Navigation"="Navigation"; +// Button title for the 'Modal' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Modal"="Modal"; + diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/sv.lproj/Localizable.strings a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/sv.lproj/Localizable.strings new file mode 100755 index 0000000..f01e41c --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/sv.lproj/Localizable.strings @@ -0,0 +1,57 @@ +// NOTE: This is the english localization and has been copied to all languages. The reason for doing +// this is it signals to iOS that the app "supports" all of these languages. This is helpful for a +// demo as it allows you to try out the localization features of the Places SDK. In a shipping app +// you should never do this, it is for demonstration purposes only. + + +// The name of the app to display in a navigation bar along with a placeholder for the SDK version number. +"App.NameAndVersion"="Places Demos: %1$@"; + +// Title of the full-screen autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.FullScreen"="Full-Screen Autocomplete"; +// Title of the pushed autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Push"="Push Autocomplete"; +// Title of the UISearchController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchController"="UISearchController"; +// Title of the UISearchDisplayController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchDisplayController"="UISearchDisplayController"; +// Title of the UITextField autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UITextField"="UITextField"; +// Title of the Styling autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Styling"="Custom Autocomplete Styling"; +// Title of the photos demo for display in a list or nav header +"Demo.Title.Photos"="Photos"; +// Title of the 'select place' button within the photos demo +"Demo.Title.Photos.SelectPlace"="Select Place"; +// Title of the Place Picker demo for displaying the picker in a popover, navigation controller, or modally. +"Demo.Title.PlacePicker.ViewController"="Place Picker View Controller"; +// Title of the autocomplete demo section +"Demo.Section.Title.Autocomplete"="Autocomplete"; +// Title of the 'Programmatic' demo section +"Demo.Section.Title.Programmatic"="Programmatic APIs"; +// Title of the findPlacesLikelihood demo section +"Demo.Section.Title.FindPlaceLikelihood"="Likelihoods"; + +// Button title for 'show autocomplete widget' +"Demo.Content.Autocomplete.ShowWidgetButton"="Show Autocomplete Widget"; +// Prompt to enter text for autocomplete demo +"Demo.Content.Autocomplete.EnterTextPrompt"="Enter Autocomplete Text Here"; +// Format string for 'autocomplete failed with error' message +"Demo.Content.Autocomplete.FailedErrorMessage"="Autocomplete failed with error: %1$@"; +// String for 'autocomplete canceled message' +"Demo.Content.Autocomplete.WasCanceledMessage"="Autocomplete was canceled"; +// Button title for the 'Yellow and Brown' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.YellowAndBrown"="Yellow and Brown"; +// Button title for the 'White on Black' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.WhiteOnBlack"="White on Black"; +// Button title for the 'Blue Colors' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.BlueColors"="Blue Colors"; +// Button title for the 'Hot Dog Stand' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.HotDogStand"="Hot Dog Stand"; +// Button title for the 'Popover' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Popover"="Popover"; +// Button title for the 'Navigation' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Navigation"="Navigation"; +// Button title for the 'Modal' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Modal"="Modal"; + diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/th.lproj/Localizable.strings a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/th.lproj/Localizable.strings new file mode 100755 index 0000000..f01e41c --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/th.lproj/Localizable.strings @@ -0,0 +1,57 @@ +// NOTE: This is the english localization and has been copied to all languages. The reason for doing +// this is it signals to iOS that the app "supports" all of these languages. This is helpful for a +// demo as it allows you to try out the localization features of the Places SDK. In a shipping app +// you should never do this, it is for demonstration purposes only. + + +// The name of the app to display in a navigation bar along with a placeholder for the SDK version number. +"App.NameAndVersion"="Places Demos: %1$@"; + +// Title of the full-screen autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.FullScreen"="Full-Screen Autocomplete"; +// Title of the pushed autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Push"="Push Autocomplete"; +// Title of the UISearchController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchController"="UISearchController"; +// Title of the UISearchDisplayController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchDisplayController"="UISearchDisplayController"; +// Title of the UITextField autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UITextField"="UITextField"; +// Title of the Styling autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Styling"="Custom Autocomplete Styling"; +// Title of the photos demo for display in a list or nav header +"Demo.Title.Photos"="Photos"; +// Title of the 'select place' button within the photos demo +"Demo.Title.Photos.SelectPlace"="Select Place"; +// Title of the Place Picker demo for displaying the picker in a popover, navigation controller, or modally. +"Demo.Title.PlacePicker.ViewController"="Place Picker View Controller"; +// Title of the autocomplete demo section +"Demo.Section.Title.Autocomplete"="Autocomplete"; +// Title of the 'Programmatic' demo section +"Demo.Section.Title.Programmatic"="Programmatic APIs"; +// Title of the findPlacesLikelihood demo section +"Demo.Section.Title.FindPlaceLikelihood"="Likelihoods"; + +// Button title for 'show autocomplete widget' +"Demo.Content.Autocomplete.ShowWidgetButton"="Show Autocomplete Widget"; +// Prompt to enter text for autocomplete demo +"Demo.Content.Autocomplete.EnterTextPrompt"="Enter Autocomplete Text Here"; +// Format string for 'autocomplete failed with error' message +"Demo.Content.Autocomplete.FailedErrorMessage"="Autocomplete failed with error: %1$@"; +// String for 'autocomplete canceled message' +"Demo.Content.Autocomplete.WasCanceledMessage"="Autocomplete was canceled"; +// Button title for the 'Yellow and Brown' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.YellowAndBrown"="Yellow and Brown"; +// Button title for the 'White on Black' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.WhiteOnBlack"="White on Black"; +// Button title for the 'Blue Colors' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.BlueColors"="Blue Colors"; +// Button title for the 'Hot Dog Stand' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.HotDogStand"="Hot Dog Stand"; +// Button title for the 'Popover' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Popover"="Popover"; +// Button title for the 'Navigation' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Navigation"="Navigation"; +// Button title for the 'Modal' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Modal"="Modal"; + diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/tr.lproj/Localizable.strings a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/tr.lproj/Localizable.strings new file mode 100755 index 0000000..f01e41c --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/tr.lproj/Localizable.strings @@ -0,0 +1,57 @@ +// NOTE: This is the english localization and has been copied to all languages. The reason for doing +// this is it signals to iOS that the app "supports" all of these languages. This is helpful for a +// demo as it allows you to try out the localization features of the Places SDK. In a shipping app +// you should never do this, it is for demonstration purposes only. + + +// The name of the app to display in a navigation bar along with a placeholder for the SDK version number. +"App.NameAndVersion"="Places Demos: %1$@"; + +// Title of the full-screen autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.FullScreen"="Full-Screen Autocomplete"; +// Title of the pushed autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Push"="Push Autocomplete"; +// Title of the UISearchController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchController"="UISearchController"; +// Title of the UISearchDisplayController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchDisplayController"="UISearchDisplayController"; +// Title of the UITextField autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UITextField"="UITextField"; +// Title of the Styling autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Styling"="Custom Autocomplete Styling"; +// Title of the photos demo for display in a list or nav header +"Demo.Title.Photos"="Photos"; +// Title of the 'select place' button within the photos demo +"Demo.Title.Photos.SelectPlace"="Select Place"; +// Title of the Place Picker demo for displaying the picker in a popover, navigation controller, or modally. +"Demo.Title.PlacePicker.ViewController"="Place Picker View Controller"; +// Title of the autocomplete demo section +"Demo.Section.Title.Autocomplete"="Autocomplete"; +// Title of the 'Programmatic' demo section +"Demo.Section.Title.Programmatic"="Programmatic APIs"; +// Title of the findPlacesLikelihood demo section +"Demo.Section.Title.FindPlaceLikelihood"="Likelihoods"; + +// Button title for 'show autocomplete widget' +"Demo.Content.Autocomplete.ShowWidgetButton"="Show Autocomplete Widget"; +// Prompt to enter text for autocomplete demo +"Demo.Content.Autocomplete.EnterTextPrompt"="Enter Autocomplete Text Here"; +// Format string for 'autocomplete failed with error' message +"Demo.Content.Autocomplete.FailedErrorMessage"="Autocomplete failed with error: %1$@"; +// String for 'autocomplete canceled message' +"Demo.Content.Autocomplete.WasCanceledMessage"="Autocomplete was canceled"; +// Button title for the 'Yellow and Brown' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.YellowAndBrown"="Yellow and Brown"; +// Button title for the 'White on Black' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.WhiteOnBlack"="White on Black"; +// Button title for the 'Blue Colors' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.BlueColors"="Blue Colors"; +// Button title for the 'Hot Dog Stand' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.HotDogStand"="Hot Dog Stand"; +// Button title for the 'Popover' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Popover"="Popover"; +// Button title for the 'Navigation' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Navigation"="Navigation"; +// Button title for the 'Modal' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Modal"="Modal"; + diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/uk.lproj/Localizable.strings a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/uk.lproj/Localizable.strings new file mode 100755 index 0000000..f01e41c --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/uk.lproj/Localizable.strings @@ -0,0 +1,57 @@ +// NOTE: This is the english localization and has been copied to all languages. The reason for doing +// this is it signals to iOS that the app "supports" all of these languages. This is helpful for a +// demo as it allows you to try out the localization features of the Places SDK. In a shipping app +// you should never do this, it is for demonstration purposes only. + + +// The name of the app to display in a navigation bar along with a placeholder for the SDK version number. +"App.NameAndVersion"="Places Demos: %1$@"; + +// Title of the full-screen autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.FullScreen"="Full-Screen Autocomplete"; +// Title of the pushed autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Push"="Push Autocomplete"; +// Title of the UISearchController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchController"="UISearchController"; +// Title of the UISearchDisplayController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchDisplayController"="UISearchDisplayController"; +// Title of the UITextField autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UITextField"="UITextField"; +// Title of the Styling autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Styling"="Custom Autocomplete Styling"; +// Title of the photos demo for display in a list or nav header +"Demo.Title.Photos"="Photos"; +// Title of the 'select place' button within the photos demo +"Demo.Title.Photos.SelectPlace"="Select Place"; +// Title of the Place Picker demo for displaying the picker in a popover, navigation controller, or modally. +"Demo.Title.PlacePicker.ViewController"="Place Picker View Controller"; +// Title of the autocomplete demo section +"Demo.Section.Title.Autocomplete"="Autocomplete"; +// Title of the 'Programmatic' demo section +"Demo.Section.Title.Programmatic"="Programmatic APIs"; +// Title of the findPlacesLikelihood demo section +"Demo.Section.Title.FindPlaceLikelihood"="Likelihoods"; + +// Button title for 'show autocomplete widget' +"Demo.Content.Autocomplete.ShowWidgetButton"="Show Autocomplete Widget"; +// Prompt to enter text for autocomplete demo +"Demo.Content.Autocomplete.EnterTextPrompt"="Enter Autocomplete Text Here"; +// Format string for 'autocomplete failed with error' message +"Demo.Content.Autocomplete.FailedErrorMessage"="Autocomplete failed with error: %1$@"; +// String for 'autocomplete canceled message' +"Demo.Content.Autocomplete.WasCanceledMessage"="Autocomplete was canceled"; +// Button title for the 'Yellow and Brown' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.YellowAndBrown"="Yellow and Brown"; +// Button title for the 'White on Black' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.WhiteOnBlack"="White on Black"; +// Button title for the 'Blue Colors' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.BlueColors"="Blue Colors"; +// Button title for the 'Hot Dog Stand' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.HotDogStand"="Hot Dog Stand"; +// Button title for the 'Popover' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Popover"="Popover"; +// Button title for the 'Navigation' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Navigation"="Navigation"; +// Button title for the 'Modal' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Modal"="Modal"; + diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/vi.lproj/Localizable.strings a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/vi.lproj/Localizable.strings new file mode 100755 index 0000000..f01e41c --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/vi.lproj/Localizable.strings @@ -0,0 +1,57 @@ +// NOTE: This is the english localization and has been copied to all languages. The reason for doing +// this is it signals to iOS that the app "supports" all of these languages. This is helpful for a +// demo as it allows you to try out the localization features of the Places SDK. In a shipping app +// you should never do this, it is for demonstration purposes only. + + +// The name of the app to display in a navigation bar along with a placeholder for the SDK version number. +"App.NameAndVersion"="Places Demos: %1$@"; + +// Title of the full-screen autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.FullScreen"="Full-Screen Autocomplete"; +// Title of the pushed autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Push"="Push Autocomplete"; +// Title of the UISearchController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchController"="UISearchController"; +// Title of the UISearchDisplayController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchDisplayController"="UISearchDisplayController"; +// Title of the UITextField autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UITextField"="UITextField"; +// Title of the Styling autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Styling"="Custom Autocomplete Styling"; +// Title of the photos demo for display in a list or nav header +"Demo.Title.Photos"="Photos"; +// Title of the 'select place' button within the photos demo +"Demo.Title.Photos.SelectPlace"="Select Place"; +// Title of the Place Picker demo for displaying the picker in a popover, navigation controller, or modally. +"Demo.Title.PlacePicker.ViewController"="Place Picker View Controller"; +// Title of the autocomplete demo section +"Demo.Section.Title.Autocomplete"="Autocomplete"; +// Title of the 'Programmatic' demo section +"Demo.Section.Title.Programmatic"="Programmatic APIs"; +// Title of the findPlacesLikelihood demo section +"Demo.Section.Title.FindPlaceLikelihood"="Likelihoods"; + +// Button title for 'show autocomplete widget' +"Demo.Content.Autocomplete.ShowWidgetButton"="Show Autocomplete Widget"; +// Prompt to enter text for autocomplete demo +"Demo.Content.Autocomplete.EnterTextPrompt"="Enter Autocomplete Text Here"; +// Format string for 'autocomplete failed with error' message +"Demo.Content.Autocomplete.FailedErrorMessage"="Autocomplete failed with error: %1$@"; +// String for 'autocomplete canceled message' +"Demo.Content.Autocomplete.WasCanceledMessage"="Autocomplete was canceled"; +// Button title for the 'Yellow and Brown' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.YellowAndBrown"="Yellow and Brown"; +// Button title for the 'White on Black' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.WhiteOnBlack"="White on Black"; +// Button title for the 'Blue Colors' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.BlueColors"="Blue Colors"; +// Button title for the 'Hot Dog Stand' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.HotDogStand"="Hot Dog Stand"; +// Button title for the 'Popover' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Popover"="Popover"; +// Button title for the 'Navigation' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Navigation"="Navigation"; +// Button title for the 'Modal' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Modal"="Modal"; + diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/zh_CN.lproj/Localizable.strings a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/zh_CN.lproj/Localizable.strings new file mode 100755 index 0000000..f01e41c --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/zh_CN.lproj/Localizable.strings @@ -0,0 +1,57 @@ +// NOTE: This is the english localization and has been copied to all languages. The reason for doing +// this is it signals to iOS that the app "supports" all of these languages. This is helpful for a +// demo as it allows you to try out the localization features of the Places SDK. In a shipping app +// you should never do this, it is for demonstration purposes only. + + +// The name of the app to display in a navigation bar along with a placeholder for the SDK version number. +"App.NameAndVersion"="Places Demos: %1$@"; + +// Title of the full-screen autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.FullScreen"="Full-Screen Autocomplete"; +// Title of the pushed autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Push"="Push Autocomplete"; +// Title of the UISearchController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchController"="UISearchController"; +// Title of the UISearchDisplayController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchDisplayController"="UISearchDisplayController"; +// Title of the UITextField autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UITextField"="UITextField"; +// Title of the Styling autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Styling"="Custom Autocomplete Styling"; +// Title of the photos demo for display in a list or nav header +"Demo.Title.Photos"="Photos"; +// Title of the 'select place' button within the photos demo +"Demo.Title.Photos.SelectPlace"="Select Place"; +// Title of the Place Picker demo for displaying the picker in a popover, navigation controller, or modally. +"Demo.Title.PlacePicker.ViewController"="Place Picker View Controller"; +// Title of the autocomplete demo section +"Demo.Section.Title.Autocomplete"="Autocomplete"; +// Title of the 'Programmatic' demo section +"Demo.Section.Title.Programmatic"="Programmatic APIs"; +// Title of the findPlacesLikelihood demo section +"Demo.Section.Title.FindPlaceLikelihood"="Likelihoods"; + +// Button title for 'show autocomplete widget' +"Demo.Content.Autocomplete.ShowWidgetButton"="Show Autocomplete Widget"; +// Prompt to enter text for autocomplete demo +"Demo.Content.Autocomplete.EnterTextPrompt"="Enter Autocomplete Text Here"; +// Format string for 'autocomplete failed with error' message +"Demo.Content.Autocomplete.FailedErrorMessage"="Autocomplete failed with error: %1$@"; +// String for 'autocomplete canceled message' +"Demo.Content.Autocomplete.WasCanceledMessage"="Autocomplete was canceled"; +// Button title for the 'Yellow and Brown' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.YellowAndBrown"="Yellow and Brown"; +// Button title for the 'White on Black' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.WhiteOnBlack"="White on Black"; +// Button title for the 'Blue Colors' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.BlueColors"="Blue Colors"; +// Button title for the 'Hot Dog Stand' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.HotDogStand"="Hot Dog Stand"; +// Button title for the 'Popover' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Popover"="Popover"; +// Button title for the 'Navigation' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Navigation"="Navigation"; +// Button title for the 'Modal' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Modal"="Modal"; + diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/zh_HK.lproj/Localizable.strings a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/zh_HK.lproj/Localizable.strings new file mode 100755 index 0000000..f01e41c --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/zh_HK.lproj/Localizable.strings @@ -0,0 +1,57 @@ +// NOTE: This is the english localization and has been copied to all languages. The reason for doing +// this is it signals to iOS that the app "supports" all of these languages. This is helpful for a +// demo as it allows you to try out the localization features of the Places SDK. In a shipping app +// you should never do this, it is for demonstration purposes only. + + +// The name of the app to display in a navigation bar along with a placeholder for the SDK version number. +"App.NameAndVersion"="Places Demos: %1$@"; + +// Title of the full-screen autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.FullScreen"="Full-Screen Autocomplete"; +// Title of the pushed autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Push"="Push Autocomplete"; +// Title of the UISearchController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchController"="UISearchController"; +// Title of the UISearchDisplayController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchDisplayController"="UISearchDisplayController"; +// Title of the UITextField autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UITextField"="UITextField"; +// Title of the Styling autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Styling"="Custom Autocomplete Styling"; +// Title of the photos demo for display in a list or nav header +"Demo.Title.Photos"="Photos"; +// Title of the 'select place' button within the photos demo +"Demo.Title.Photos.SelectPlace"="Select Place"; +// Title of the Place Picker demo for displaying the picker in a popover, navigation controller, or modally. +"Demo.Title.PlacePicker.ViewController"="Place Picker View Controller"; +// Title of the autocomplete demo section +"Demo.Section.Title.Autocomplete"="Autocomplete"; +// Title of the 'Programmatic' demo section +"Demo.Section.Title.Programmatic"="Programmatic APIs"; +// Title of the findPlacesLikelihood demo section +"Demo.Section.Title.FindPlaceLikelihood"="Likelihoods"; + +// Button title for 'show autocomplete widget' +"Demo.Content.Autocomplete.ShowWidgetButton"="Show Autocomplete Widget"; +// Prompt to enter text for autocomplete demo +"Demo.Content.Autocomplete.EnterTextPrompt"="Enter Autocomplete Text Here"; +// Format string for 'autocomplete failed with error' message +"Demo.Content.Autocomplete.FailedErrorMessage"="Autocomplete failed with error: %1$@"; +// String for 'autocomplete canceled message' +"Demo.Content.Autocomplete.WasCanceledMessage"="Autocomplete was canceled"; +// Button title for the 'Yellow and Brown' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.YellowAndBrown"="Yellow and Brown"; +// Button title for the 'White on Black' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.WhiteOnBlack"="White on Black"; +// Button title for the 'Blue Colors' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.BlueColors"="Blue Colors"; +// Button title for the 'Hot Dog Stand' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.HotDogStand"="Hot Dog Stand"; +// Button title for the 'Popover' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Popover"="Popover"; +// Button title for the 'Navigation' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Navigation"="Navigation"; +// Button title for the 'Modal' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Modal"="Modal"; + diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/zh_TW.lproj/Localizable.strings a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/zh_TW.lproj/Localizable.strings new file mode 100755 index 0000000..f01e41c --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Resources/zh_TW.lproj/Localizable.strings @@ -0,0 +1,57 @@ +// NOTE: This is the english localization and has been copied to all languages. The reason for doing +// this is it signals to iOS that the app "supports" all of these languages. This is helpful for a +// demo as it allows you to try out the localization features of the Places SDK. In a shipping app +// you should never do this, it is for demonstration purposes only. + + +// The name of the app to display in a navigation bar along with a placeholder for the SDK version number. +"App.NameAndVersion"="Places Demos: %1$@"; + +// Title of the full-screen autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.FullScreen"="Full-Screen Autocomplete"; +// Title of the pushed autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Push"="Push Autocomplete"; +// Title of the UISearchController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchController"="UISearchController"; +// Title of the UISearchDisplayController autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UISearchDisplayController"="UISearchDisplayController"; +// Title of the UITextField autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.UITextField"="UITextField"; +// Title of the Styling autocomplete demo for display in a list or nav header +"Demo.Title.Autocomplete.Styling"="Custom Autocomplete Styling"; +// Title of the photos demo for display in a list or nav header +"Demo.Title.Photos"="Photos"; +// Title of the 'select place' button within the photos demo +"Demo.Title.Photos.SelectPlace"="Select Place"; +// Title of the Place Picker demo for displaying the picker in a popover, navigation controller, or modally. +"Demo.Title.PlacePicker.ViewController"="Place Picker View Controller"; +// Title of the autocomplete demo section +"Demo.Section.Title.Autocomplete"="Autocomplete"; +// Title of the 'Programmatic' demo section +"Demo.Section.Title.Programmatic"="Programmatic APIs"; +// Title of the findPlacesLikelihood demo section +"Demo.Section.Title.FindPlaceLikelihood"="Likelihoods"; + +// Button title for 'show autocomplete widget' +"Demo.Content.Autocomplete.ShowWidgetButton"="Show Autocomplete Widget"; +// Prompt to enter text for autocomplete demo +"Demo.Content.Autocomplete.EnterTextPrompt"="Enter Autocomplete Text Here"; +// Format string for 'autocomplete failed with error' message +"Demo.Content.Autocomplete.FailedErrorMessage"="Autocomplete failed with error: %1$@"; +// String for 'autocomplete canceled message' +"Demo.Content.Autocomplete.WasCanceledMessage"="Autocomplete was canceled"; +// Button title for the 'Yellow and Brown' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.YellowAndBrown"="Yellow and Brown"; +// Button title for the 'White on Black' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.WhiteOnBlack"="White on Black"; +// Button title for the 'Blue Colors' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.BlueColors"="Blue Colors"; +// Button title for the 'Hot Dog Stand' styled autocomplete widget. +"Demo.Content.Autocomplete.Styling.Colors.HotDogStand"="Hot Dog Stand"; +// Button title for the 'Popover' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Popover"="Popover"; +// Button title for the 'Navigation' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Navigation"="Navigation"; +// Button title for the 'Modal' view of the place picker. +"Demo.Content.PlacePicker.ViewController.Modal"="Modal"; + diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/SDKDemoAPIKey.h a/Pods/GooglePlaces/Example/GooglePlacesDemos/SDKDemoAPIKey.h new file mode 100755 index 0000000..35da75b --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/SDKDemoAPIKey.h @@ -0,0 +1,25 @@ +/* + * Copyright 2016 Google Inc. All rights reserved. + * + * + * 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. + */ + +/** + * To use GooglePlacesDemos, please register an API Key for your application and set it here. Your + * API Key should be kept private. + * + * See documentation on getting an API Key for your API Project here: + * https://developers.google.com/places/ios-sdk/start#get-key + */ + +#error Register your API key and insert here, then delete this line. +static NSString *const kAPIKey = @""; diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Samples/Autocomplete/AutocompleteBaseViewController.h a/Pods/GooglePlaces/Example/GooglePlacesDemos/Samples/Autocomplete/AutocompleteBaseViewController.h new file mode 100755 index 0000000..0b2172c --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Samples/Autocomplete/AutocompleteBaseViewController.h @@ -0,0 +1,51 @@ +/* + * Copyright 2016 Google Inc. All rights reserved. + * + * + * 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 +#import "GooglePlacesDemos/Support/BaseDemoViewController.h" + +/** + * All other autocomplete demo classes inherit from this class. This class optionally adds a button + * to present the autocomplete widget, and displays the results when these are selected. + */ +@interface AutocompleteBaseViewController : BaseDemoViewController + +/** Filter to apply to autocomplete suggestions (can be nil). */ +@property(nonatomic, strong) GMSAutocompleteFilter *autocompleteFilter; + +/** + * The |GMSPlaceField| for specifying explicit place details to be requested for the |GMSPlace| + * result. + */ +@property(nonatomic, assign) GMSPlaceField placeFields; + +/** + * Build a UIButton to display the autocomplete widget and add it to the UI. This should be called + * only if the demo requires such a button, e.g. demos for modal presentation of widgets would use + * this, while a UITextField demo would not. + * + * @param selector The selector to send to self when the button is tapped. + * + * @return The UIButton which was added to the UI. + */ +- (UIButton *)createShowAutocompleteButton:(SEL)selector; + +- (void)autocompleteDidSelectPlace:(GMSPlace *)place; +- (void)autocompleteDidFail:(NSError *)error; +- (void)autocompleteDidCancel; +- (void)showCustomMessageInResultPane:(NSString *)message; +- (void)resetViews; + +@end diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Samples/Autocomplete/AutocompleteBaseViewController.m a/Pods/GooglePlaces/Example/GooglePlacesDemos/Samples/Autocomplete/AutocompleteBaseViewController.m new file mode 100755 index 0000000..ded5d78 --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Samples/Autocomplete/AutocompleteBaseViewController.m @@ -0,0 +1,220 @@ +/* + * Copyright 2016 Google Inc. All rights reserved. + * + * + * 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 "GooglePlacesDemos/Samples/Autocomplete/AutocompleteBaseViewController.h" + +#import "GooglePlacesDemos/Samples/PagingPhotoView.h" + +@implementation AutocompleteBaseViewController { + PagingPhotoView *_photoView; + UIButton *_photoButton; + UITextView *_textView; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + // Configure a background color. + self.view.backgroundColor = [UIColor whiteColor]; + + // Configure the UI. Tell our superclass we want a button and a result view below that. + _photoButton = + [self createButton:@selector(showPhotosButtonTapped) + title:NSLocalizedString(@"Demo.Title.Photos", @"Button title for 'Photos'")]; + + // Create a text view. + _textView = [[UITextView alloc] init]; + _textView.editable = NO; + _textView.translatesAutoresizingMaskIntoConstraints = NO; + [self addResultTextView]; + + // Configure the photo view where we are going to display the loaded photos. + _photoView = [[PagingPhotoView alloc] initWithFrame:self.view.bounds]; + _photoView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + [self.view addSubview:_photoView]; + + // Reset the various views to their initial states. + [self resetViews]; +} + +- (UIButton *)createShowAutocompleteButton:(SEL)selector { + return [self createButton:selector + title:NSLocalizedString(@"Demo.Content.Autocomplete.ShowWidgetButton", + @"Button title for 'show autocomplete widget'")]; +} + +- (NSString *)openStatusTextFromPlace:(GMSPlace *)place { + GMSPlaceOpenStatus openStatus = [place isOpen]; + switch (openStatus) { + case GMSPlaceOpenStatusOpen: + return @"Open"; + case GMSPlaceOpenStatusClosed: + return @"Closed"; + case GMSPlaceOpenStatusUnknown: + return @"Unknown"; + } +} + +- (void)autocompleteDidSelectPlace:(GMSPlace *)place { + NSMutableAttributedString *text = + [[NSMutableAttributedString alloc] initWithString:[place description]]; + [text appendAttributedString:[[NSAttributedString alloc] initWithString:@"\nPlace status: "]]; + NSString *openStatusText = [self openStatusTextFromPlace:place]; + [text appendAttributedString:[[NSAttributedString alloc] initWithString:openStatusText]]; + NSAttributedString *attributions = place.attributions; + if (attributions) { + NSAttributedString *doubleReturn = [[NSAttributedString alloc] initWithString:@"\n\n"]; + [text appendAttributedString:doubleReturn]; + [text appendAttributedString:attributions]; + } + _textView.attributedText = text; + [_textView setIsAccessibilityElement:YES]; + [_textView setHidden:NO]; + + // Show the photo button be start disabled until the photos have loaded. + [_photoButton setIsAccessibilityElement:YES]; + [_photoButton setHidden:NO]; + [_photoButton setEnabled:NO]; + if (place.photos.count > 0) { + [self preloadPhotoList:place.photos]; + } +} + +- (void)autocompleteDidFail:(NSError *)error { + NSString *formatString = + NSLocalizedString(@"Demo.Content.Autocomplete.FailedErrorMessage", + @"Format string for 'autocomplete failed with error' message"); + _textView.text = [NSString stringWithFormat:formatString, error]; +} + +- (void)autocompleteDidCancel { + _textView.text = NSLocalizedString(@"Demo.Content.Autocomplete.WasCanceledMessage", + @"String for 'autocomplete canceled message'"); +} + +- (void)showCustomMessageInResultPane:(NSString *)message { + _textView.text = message; +} + +- (void)resetViews { + _photoView.photoList = @[]; + [_textView setText:@""]; + [_textView setIsAccessibilityElement:NO]; + [_textView setHidden:NO]; + [_photoButton setIsAccessibilityElement:NO]; + [_photoButton setHidden:YES]; + [_photoView setHidden:YES]; +} + +#pragma mark - Private + +- (void)addResultTextView { + NSAssert(_textView.superview == nil, @"%s should not be called twice", sel_getName(_cmd)); + [self.view addSubview:_textView]; + + // Check to see if we can use readableContentGuide from iOS 9+ + if ([self.view respondsToSelector:@selector(readableContentGuide)]) { + // Position it horizontally so it fills the readableContentGuide. Use the new anchor-based + // system because we know this code will only run on iOS 9+. + [self.view.readableContentGuide.leadingAnchor constraintEqualToAnchor:_textView.leadingAnchor] + .active = YES; + [self.view.readableContentGuide.trailingAnchor constraintEqualToAnchor:_textView.trailingAnchor] + .active = YES; + // Set the textContainerInset to 0 because the readableContentGuide is already handling the + // inset. + _textView.textContainerInset = UIEdgeInsetsZero; + } else { + // Position it horizontally so it fills the parent. + [self.view + addConstraints:[NSLayoutConstraint + constraintsWithVisualFormat:@"H:|-(0)-[_textView]-(0)-|" + options:0 + metrics:nil + views:NSDictionaryOfVariableBindings(_textView)]]; + } + + // If we have a view place it below that. + [self.view addConstraints:[NSLayoutConstraint + constraintsWithVisualFormat:@"V:[_photoButton]-[_textView]-(0)-|" + options:0 + metrics:nil + views:NSDictionaryOfVariableBindings( + _photoButton, _textView)]]; +} + +- (UIButton *)createButton:(SEL)selector title:(NSString *)title { + // Create a button to show the autocomplete widget. + UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem]; + [button setTitle:title forState:UIControlStateNormal]; + [button addTarget:self action:selector forControlEvents:UIControlEventTouchUpInside]; + button.translatesAutoresizingMaskIntoConstraints = NO; + [self.view addSubview:button]; + // Position the button from the top of the view. + [NSLayoutConstraint constraintWithItem:button + attribute:NSLayoutAttributeTop + relatedBy:NSLayoutRelationEqual + toItem:self.topLayoutGuide + attribute:NSLayoutAttributeBottom + multiplier:1 + constant:8] + .active = YES; + // Centre it horizontally. + [NSLayoutConstraint constraintWithItem:button + attribute:NSLayoutAttributeCenterX + relatedBy:NSLayoutRelationEqual + toItem:self.view + attribute:NSLayoutAttributeCenterX + multiplier:1 + constant:0] + .active = YES; + + return button; +} + +- (void)showPhotosButtonTapped { + [_textView setIsAccessibilityElement:NO]; + [_textView setHidden:YES]; + [_photoButton setIsAccessibilityElement:NO]; + [_photoButton setHidden:YES]; + [_photoView setHidden:NO]; +} + +// Preload the photos to be displayed. +- (void)preloadPhotoList:(NSArray *)photos { + __block NSMutableArray *attributedPhotos = [NSMutableArray array]; + __block NSInteger photoRequestsInFlight = photos.count; + for (GMSPlacePhotoMetadata *photo in photos) { + [[GMSPlacesClient sharedClient] loadPlacePhoto:photo + callback:^(UIImage *photoImage, NSError *error) { + photoRequestsInFlight--; + if (photoImage == nil) { + NSLog(@"Photo request failed with error: %@", error); + } else { + AttributedPhoto *attributedPhoto = + [[AttributedPhoto alloc] init]; + attributedPhoto.image = photoImage; + attributedPhoto.attributions = photo.attributions; + [attributedPhotos addObject:attributedPhoto]; + } + + if (photoRequestsInFlight == 0) { + _photoView.photoList = attributedPhotos; + [_photoButton setEnabled:YES]; + } + }]; + } +} + +@end diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Samples/Autocomplete/AutocompleteModalViewController.h a/Pods/GooglePlaces/Example/GooglePlacesDemos/Samples/Autocomplete/AutocompleteModalViewController.h new file mode 100755 index 0000000..dceb663 --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Samples/Autocomplete/AutocompleteModalViewController.h @@ -0,0 +1,23 @@ +/* + * Copyright 2016 Google Inc. All rights reserved. + * + * + * 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 "GooglePlacesDemos/Samples/Autocomplete/AutocompleteBaseViewController.h" + +/** + * Demo showing a modally presented Autocomplete view controller. + */ +@interface AutocompleteModalViewController : AutocompleteBaseViewController + +@end diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Samples/Autocomplete/AutocompleteModalViewController.m a/Pods/GooglePlaces/Example/GooglePlacesDemos/Samples/Autocomplete/AutocompleteModalViewController.m new file mode 100755 index 0000000..963f772 --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Samples/Autocomplete/AutocompleteModalViewController.m @@ -0,0 +1,86 @@ +/* + * Copyright 2016 Google Inc. All rights reserved. + * + * + * 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 "GooglePlacesDemos/Samples/Autocomplete/AutocompleteModalViewController.h" + +#import + +@interface AutocompleteModalViewController () +@end + +@implementation AutocompleteModalViewController { + UIButton *_showAutocompleteWidgetButton; +} + ++ (NSString *)demoTitle { + return NSLocalizedString( + @"Demo.Title.Autocomplete.FullScreen", + @"Title of the full-screen autocomplete demo for display in a list or nav header"); +} + +#pragma mark - View Lifecycle + +- (void)viewDidLoad { + [super viewDidLoad]; + + // Configure the UI. Tell our superclass we want a button and a result view below that. + _showAutocompleteWidgetButton = + [self createShowAutocompleteButton:@selector(showAutocompleteWidgetButtonTapped)]; +} + +#pragma mark - Actions + +- (IBAction)showAutocompleteWidgetButtonTapped { + // When the button is pressed modally present the autocomplete view controller. + GMSAutocompleteViewController *autocompleteViewController = + [[GMSAutocompleteViewController alloc] init]; + autocompleteViewController.delegate = self; + autocompleteViewController.autocompleteFilter = self.autocompleteFilter; + autocompleteViewController.placeFields = self.placeFields; + [self presentViewController:autocompleteViewController animated:YES completion:nil]; + [_showAutocompleteWidgetButton setHidden:YES]; +} + +#pragma mark - GMSAutocompleteViewControllerDelegate + +- (void)viewController:(GMSAutocompleteViewController *)viewController + didAutocompleteWithPlace:(GMSPlace *)place { + // Dismiss the view controller and tell our superclass to populate the result view. + [viewController dismissViewControllerAnimated:YES completion:nil]; + [self autocompleteDidSelectPlace:place]; +} + +- (void)viewController:(GMSAutocompleteViewController *)viewController + didFailAutocompleteWithError:(NSError *)error { + // Dismiss the view controller and notify our superclass of the failure. + [viewController dismissViewControllerAnimated:YES completion:nil]; + [self autocompleteDidFail:error]; +} + +- (void)wasCancelled:(GMSAutocompleteViewController *)viewController { + // Dismiss the controller and show a message that it was canceled. + [viewController dismissViewControllerAnimated:YES completion:nil]; + [self autocompleteDidCancel]; +} + +- (void)didRequestAutocompletePredictions:(GMSAutocompleteViewController *)viewController { + [UIApplication sharedApplication].networkActivityIndicatorVisible = YES; +} + +- (void)didUpdateAutocompletePredictions:(GMSAutocompleteViewController *)viewController { + [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; +} + +@end diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Samples/Autocomplete/AutocompletePushViewController.h a/Pods/GooglePlaces/Example/GooglePlacesDemos/Samples/Autocomplete/AutocompletePushViewController.h new file mode 100755 index 0000000..64d87e1 --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Samples/Autocomplete/AutocompletePushViewController.h @@ -0,0 +1,23 @@ +/* + * Copyright 2016 Google Inc. All rights reserved. + * + * + * 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 "GooglePlacesDemos/Samples/Autocomplete/AutocompleteBaseViewController.h" + +/** + * Demo showing a Autocomplete view controller pushed on the navigation stack. + */ +@interface AutocompletePushViewController : AutocompleteBaseViewController + +@end diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Samples/Autocomplete/AutocompletePushViewController.m a/Pods/GooglePlaces/Example/GooglePlacesDemos/Samples/Autocomplete/AutocompletePushViewController.m new file mode 100755 index 0000000..2152cfe --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Samples/Autocomplete/AutocompletePushViewController.m @@ -0,0 +1,95 @@ +/* + * Copyright 2016 Google Inc. All rights reserved. + * + * + * 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 "GooglePlacesDemos/Samples/Autocomplete/AutocompletePushViewController.h" + +#import + +@interface AutocompletePushViewController () +@end + +@implementation AutocompletePushViewController { + UIButton *_showAutocompleteWidgetButton; +} + ++ (NSString *)demoTitle { + return NSLocalizedString( + @"Demo.Title.Autocomplete.Push", + @"Title of the pushed autocomplete demo for display in a list or nav header"); +} + +#pragma mark - View Lifecycle + +- (void)viewDidLoad { + [super viewDidLoad]; + + // Configure the UI. Tell our superclass we want a button and a result view below that. + _showAutocompleteWidgetButton = + [self createShowAutocompleteButton:@selector(showAutocompleteWidgetButtonTapped)]; +} + +#pragma mark - Creation of |GMSAutocompleteViewController| instance. + +- (GMSAutocompleteViewController *)autocompleteViewControllerInstance { + GMSAutocompleteViewController *autocompleteViewController = + [[GMSAutocompleteViewController alloc] init]; + autocompleteViewController.delegate = self; + autocompleteViewController.autocompleteFilter = self.autocompleteFilter; + autocompleteViewController.placeFields = self.placeFields; + + // Returns new GMSAutocompleteViewController instance. + return autocompleteViewController; +} + +#pragma mark - Actions + +- (IBAction)showAutocompleteWidgetButtonTapped { + // When the button is tapped just push a new autocomplete view controller onto the stack. + [self.navigationController pushViewController:[self autocompleteViewControllerInstance] + animated:YES]; + [_showAutocompleteWidgetButton setHidden:YES]; +} + +#pragma mark - GMSAutocompleteViewControllerDelegate + +- (void)viewController:(GMSAutocompleteViewController *)viewController + didAutocompleteWithPlace:(GMSPlace *)place { + // Dismiss the view controller and tell our superclass to populate the result view. + [self.navigationController popToViewController:self animated:YES]; + [self autocompleteDidSelectPlace:place]; +} + +- (void)viewController:(GMSAutocompleteViewController *)viewController + didFailAutocompleteWithError:(NSError *)error { + // Dismiss the view controller and notify our superclass of the failure. + [self.navigationController popToViewController:self animated:YES]; + [self autocompleteDidFail:error]; +} + +- (void)wasCancelled:(GMSAutocompleteViewController *)viewController { + // Dismiss the controller and show a message that it was canceled. + [self.navigationController popToViewController:self animated:YES]; + [self autocompleteDidCancel]; +} + +- (void)didRequestAutocompletePredictions:(GMSAutocompleteViewController *)viewController { + [UIApplication sharedApplication].networkActivityIndicatorVisible = YES; +} + +- (void)didUpdateAutocompletePredictions:(GMSAutocompleteViewController *)viewController { + [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; +} + +@end diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Samples/Autocomplete/AutocompleteWithCustomColors.h a/Pods/GooglePlaces/Example/GooglePlacesDemos/Samples/Autocomplete/AutocompleteWithCustomColors.h new file mode 100755 index 0000000..4f13d5f --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Samples/Autocomplete/AutocompleteWithCustomColors.h @@ -0,0 +1,23 @@ +/* + * Copyright 2016 Google Inc. All rights reserved. + * + * + * 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 "GooglePlacesDemos/Samples/Autocomplete/AutocompleteBaseViewController.h" + +/* + * SDK Demo showing how to customise colors in the full-screen Autocomplete Widget. + */ +@interface AutocompleteWithCustomColors : AutocompleteBaseViewController + +@end diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Samples/Autocomplete/AutocompleteWithCustomColors.m a/Pods/GooglePlaces/Example/GooglePlacesDemos/Samples/Autocomplete/AutocompleteWithCustomColors.m new file mode 100755 index 0000000..84fcace --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Samples/Autocomplete/AutocompleteWithCustomColors.m @@ -0,0 +1,389 @@ +/* + * Copyright 2016 Google Inc. All rights reserved. + * + * + * 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 "GooglePlacesDemos/Samples/Autocomplete/AutocompleteWithCustomColors.h" + +#import + +/** + * Simple subclass of GMSAutocompleteViewController solely for the purpose of localising appearance + * proxy changes to this part of the demo app. + */ +@interface GMSStyledAutocompleteViewController : GMSAutocompleteViewController +@end + +@implementation GMSStyledAutocompleteViewController +@end + +@interface AutocompleteWithCustomColors () +@end + +@implementation AutocompleteWithCustomColors { + NSMutableArray *_themeButtons; +} + ++ (NSString *)demoTitle { + return NSLocalizedString( + @"Demo.Title.Autocomplete.Styling", + @"Title of the Styling autocomplete demo for display in a list or nav header"); +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.view.backgroundColor = [UIColor whiteColor]; + + NSString *titleYellowAndBrown = + NSLocalizedString(@"Demo.Content.Autocomplete.Styling.Colors.YellowAndBrown", + @"Button title for the 'Yellow and Brown' styled autocomplete widget."); + NSString *titleWhiteOnBlack = + NSLocalizedString(@"Demo.Content.Autocomplete.Styling.Colors.WhiteOnBlack", + @"Button title for the 'WhiteOnBlack' styled autocomplete widget."); + NSString *titleBlueColors = + NSLocalizedString(@"Demo.Content.Autocomplete.Styling.Colors.BlueColors", + @"Button title for the 'BlueColors' styled autocomplete widget."); + NSString *titleHotDogStand = + NSLocalizedString(@"Demo.Content.Autocomplete.Styling.Colors.HotDogStand", + @"Button title for the 'Hot Dog Stand' styled autocomplete widget."); + + UIButton *brownThemeButton = [UIButton buttonWithType:UIButtonTypeSystem]; + [brownThemeButton setTitle:titleYellowAndBrown forState:UIControlStateNormal]; + [brownThemeButton addTarget:self + action:@selector(openBrownTheme:) + forControlEvents:UIControlEventTouchUpInside]; + brownThemeButton.translatesAutoresizingMaskIntoConstraints = NO; + [self.view addSubview:brownThemeButton]; + [NSLayoutConstraint constraintWithItem:brownThemeButton + attribute:NSLayoutAttributeTop + relatedBy:NSLayoutRelationEqual + toItem:self.topLayoutGuide + attribute:NSLayoutAttributeBottom + multiplier:1 + constant:8] + .active = YES; + [NSLayoutConstraint constraintWithItem:brownThemeButton + attribute:NSLayoutAttributeCenterX + relatedBy:NSLayoutRelationEqual + toItem:self.view + attribute:NSLayoutAttributeCenterX + multiplier:1 + constant:0] + .active = YES; + + UIButton *blackThemeButton = [UIButton buttonWithType:UIButtonTypeSystem]; + [blackThemeButton setTitle:titleWhiteOnBlack forState:UIControlStateNormal]; + [blackThemeButton addTarget:self + action:@selector(openBlackTheme:) + forControlEvents:UIControlEventTouchUpInside]; + blackThemeButton.translatesAutoresizingMaskIntoConstraints = NO; + [self.view addSubview:blackThemeButton]; + [NSLayoutConstraint constraintWithItem:blackThemeButton + attribute:NSLayoutAttributeTop + relatedBy:NSLayoutRelationEqual + toItem:brownThemeButton + attribute:NSLayoutAttributeBottom + multiplier:1 + constant:8] + .active = YES; + [NSLayoutConstraint constraintWithItem:blackThemeButton + attribute:NSLayoutAttributeCenterX + relatedBy:NSLayoutRelationEqual + toItem:self.view + attribute:NSLayoutAttributeCenterX + multiplier:1 + constant:0] + .active = YES; + + UIButton *blueThemeButton = [UIButton buttonWithType:UIButtonTypeSystem]; + [blueThemeButton setTitle:titleBlueColors forState:UIControlStateNormal]; + [blueThemeButton addTarget:self + action:@selector(openBlueTheme:) + forControlEvents:UIControlEventTouchUpInside]; + blueThemeButton.translatesAutoresizingMaskIntoConstraints = NO; + [self.view addSubview:blueThemeButton]; + [NSLayoutConstraint constraintWithItem:blueThemeButton + attribute:NSLayoutAttributeTop + relatedBy:NSLayoutRelationEqual + toItem:blackThemeButton + attribute:NSLayoutAttributeBottom + multiplier:1 + constant:8] + .active = YES; + [NSLayoutConstraint constraintWithItem:blueThemeButton + attribute:NSLayoutAttributeCenterX + relatedBy:NSLayoutRelationEqual + toItem:self.view + attribute:NSLayoutAttributeCenterX + multiplier:1 + constant:0] + .active = YES; + + UIButton *hotDogThemeButton = [UIButton buttonWithType:UIButtonTypeSystem]; + [hotDogThemeButton setTitle:titleHotDogStand forState:UIControlStateNormal]; + [hotDogThemeButton addTarget:self + action:@selector(openHotDogTheme:) + forControlEvents:UIControlEventTouchUpInside]; + hotDogThemeButton.translatesAutoresizingMaskIntoConstraints = NO; + [self.view addSubview:hotDogThemeButton]; + [NSLayoutConstraint constraintWithItem:hotDogThemeButton + attribute:NSLayoutAttributeTop + relatedBy:NSLayoutRelationEqual + toItem:blueThemeButton + attribute:NSLayoutAttributeBottom + multiplier:1 + constant:8] + .active = YES; + [NSLayoutConstraint constraintWithItem:hotDogThemeButton + attribute:NSLayoutAttributeCenterX + relatedBy:NSLayoutRelationEqual + toItem:self.view + attribute:NSLayoutAttributeCenterX + multiplier:1 + constant:0] + .active = YES; + + self.definesPresentationContext = YES; + + // Store the theme buttons into array. + _themeButtons = [NSMutableArray array]; + [_themeButtons addObject:brownThemeButton]; + [_themeButtons addObject:blackThemeButton]; + [_themeButtons addObject:blueThemeButton]; + [_themeButtons addObject:hotDogThemeButton]; +} + +- (void)openBrownTheme:(UIButton *)button { + UIColor *backgroundColor = + [UIColor colorWithRed:215.0f / 255.0f green:204.0f / 255.0f blue:200.0f / 255.0f alpha:1.0f]; + UIColor *selectedTableCellBackgroundColor = + [UIColor colorWithRed:236.0f / 255.0f green:225.0f / 255.0f blue:220.0f / 255.0f alpha:1.0f]; + UIColor *darkBackgroundColor = + [UIColor colorWithRed:93.0f / 255.0f green:64.0f / 255.0f blue:55.0f / 255.0f alpha:1.0f]; + UIColor *primaryTextColor = [UIColor colorWithWhite:0.33f alpha:1.0f]; + + UIColor *highlightColor = + [UIColor colorWithRed:255.0f / 255.0f green:235.0f / 255.0f blue:0.0f / 255.0f alpha:1.0f]; + UIColor *secondaryColor = [UIColor colorWithWhite:114.0f / 255.0f alpha:1.0f]; + UIColor *tintColor = + [UIColor colorWithRed:219 / 255.0f green:207 / 255.0f blue:28 / 255.0f alpha:1.0f]; + UIColor *searchBarTintColor = [UIColor yellowColor]; + UIColor *separatorColor = [UIColor colorWithWhite:182.0f / 255.0f alpha:1.0f]; + + [self presentAutocompleteControllerWithBackgroundColor:backgroundColor + selectedTableCellBackgroundColor:selectedTableCellBackgroundColor + darkBackgroundColor:darkBackgroundColor + primaryTextColor:primaryTextColor + highlightColor:highlightColor + secondaryColor:secondaryColor + tintColor:tintColor + searchBarTintColor:searchBarTintColor + separatorColor:separatorColor]; +} + +- (void)openBlueTheme:(UIButton *)button { + UIColor *backgroundColor = + [UIColor colorWithRed:225.0f / 255.0f green:241.0f / 255.0f blue:252.0f / 255.0f alpha:1.0f]; + UIColor *selectedTableCellBackgroundColor = + [UIColor colorWithRed:213.0f / 255.0f green:219.0f / 255.0f blue:230.0f / 255.0f alpha:1.0f]; + UIColor *darkBackgroundColor = + [UIColor colorWithRed:187.0f / 255.0f green:222.0f / 255.0f blue:248.0f / 255.0f alpha:1.0f]; + UIColor *primaryTextColor = [UIColor colorWithWhite:0.5f alpha:1.0f]; + UIColor *highlightColor = + [UIColor colorWithRed:76.0f / 255.0f green:175.0f / 255.0f blue:248.0f / 255.0f alpha:1.0f]; + UIColor *secondaryColor = [UIColor colorWithWhite:0.5f alpha:0.65f]; + UIColor *tintColor = + [UIColor colorWithRed:0 / 255.0f green:142 / 255.0f blue:248.0f / 255.0f alpha:1.0f]; + UIColor *searchBarTintColor = tintColor; + UIColor *separatorColor = [UIColor colorWithWhite:0.5f alpha:0.65f]; + + [self presentAutocompleteControllerWithBackgroundColor:backgroundColor + selectedTableCellBackgroundColor:selectedTableCellBackgroundColor + darkBackgroundColor:darkBackgroundColor + primaryTextColor:primaryTextColor + highlightColor:highlightColor + secondaryColor:secondaryColor + tintColor:tintColor + searchBarTintColor:searchBarTintColor + separatorColor:separatorColor]; +} + +- (void)openBlackTheme:(UIButton *)button { + UIColor *backgroundColor = [UIColor colorWithWhite:0.25f alpha:1.0f]; + UIColor *selectedTableCellBackgroundColor = [UIColor colorWithWhite:0.35f alpha:1.0f]; + UIColor *darkBackgroundColor = [UIColor colorWithWhite:0.2f alpha:1.0f]; + UIColor *primaryTextColor = [UIColor whiteColor]; + UIColor *highlightColor = [UIColor colorWithRed:0.75f green:1.0f blue:0.75f alpha:1.0f]; + UIColor *secondaryColor = [UIColor colorWithWhite:1.0f alpha:0.5f]; + UIColor *tintColor = [UIColor whiteColor]; + UIColor *searchBarTintColor = tintColor; + UIColor *separatorColor = [UIColor colorWithRed:0.5f green:0.75f blue:0.5f alpha:0.30f]; + + [self presentAutocompleteControllerWithBackgroundColor:backgroundColor + selectedTableCellBackgroundColor:selectedTableCellBackgroundColor + darkBackgroundColor:darkBackgroundColor + primaryTextColor:primaryTextColor + highlightColor:highlightColor + secondaryColor:secondaryColor + tintColor:tintColor + searchBarTintColor:searchBarTintColor + separatorColor:separatorColor]; +} + +- (void)openHotDogTheme:(UIButton *)button { + UIColor *backgroundColor = [UIColor yellowColor]; + UIColor *selectedTableCellBackgroundColor = [UIColor whiteColor]; + UIColor *darkBackgroundColor = [UIColor redColor]; + UIColor *primaryTextColor = [UIColor blackColor]; + UIColor *highlightColor = [UIColor redColor]; + UIColor *secondaryColor = [UIColor colorWithWhite:0.0f alpha:0.6f]; + UIColor *tintColor = [UIColor redColor]; + UIColor *searchBarTintColor = [UIColor whiteColor]; + UIColor *separatorColor = [UIColor redColor]; + + [self presentAutocompleteControllerWithBackgroundColor:backgroundColor + selectedTableCellBackgroundColor:selectedTableCellBackgroundColor + darkBackgroundColor:darkBackgroundColor + primaryTextColor:primaryTextColor + highlightColor:highlightColor + secondaryColor:secondaryColor + tintColor:tintColor + searchBarTintColor:searchBarTintColor + separatorColor:separatorColor]; +} + +- (void)presentAutocompleteControllerWithBackgroundColor:(UIColor *)backgroundColor + selectedTableCellBackgroundColor:(UIColor *)selectedTableCellBackgroundColor + darkBackgroundColor:(UIColor *)darkBackgroundColor + primaryTextColor:(UIColor *)primaryTextColor + highlightColor:(UIColor *)highlightColor + secondaryColor:(UIColor *)secondaryColor + tintColor:(UIColor *)tintColor + searchBarTintColor:(UIColor *)searchBarTintColor + separatorColor:(UIColor *)separatorColor { + // Use UIAppearance proxies to change the appearance of UI controls in + // GMSAutocompleteViewController. Here we use appearanceWhenContainedInInstancesOfClasses to + // localise changes to just this part of the Demo app. This will generally not be necessary in a + // real application as you will probably want the same theme to apply to all elements in your app. + UIActivityIndicatorView *appearance = [UIActivityIndicatorView + appearanceWhenContainedInInstancesOfClasses:@ [[GMSStyledAutocompleteViewController class]]]; + [appearance setColor:primaryTextColor]; + + [[UINavigationBar + appearanceWhenContainedInInstancesOfClasses:@ [[GMSStyledAutocompleteViewController class]]] + setBarTintColor:darkBackgroundColor]; + [[UINavigationBar + appearanceWhenContainedInInstancesOfClasses:@ [[GMSStyledAutocompleteViewController class]]] + setTintColor:searchBarTintColor]; + + // Color of typed text in search bar. + NSDictionary *searchBarTextAttributes = @{ + NSForegroundColorAttributeName : searchBarTintColor, + NSFontAttributeName : [UIFont systemFontOfSize:[UIFont systemFontSize]] + }; + [[UITextField + appearanceWhenContainedInInstancesOfClasses:@ [[GMSStyledAutocompleteViewController class]]] + setDefaultTextAttributes:searchBarTextAttributes]; + + // Color of the "Search" placeholder text in search bar. For this example, we'll make it the same + // as the bar tint color but with added transparency. + CGFloat increasedAlpha = CGColorGetAlpha(searchBarTintColor.CGColor) * 0.75f; + UIColor *placeHolderColor = [searchBarTintColor colorWithAlphaComponent:increasedAlpha]; + + NSDictionary *placeholderAttributes = @{ + NSForegroundColorAttributeName : placeHolderColor, + NSFontAttributeName : [UIFont systemFontOfSize:[UIFont systemFontSize]] + }; + NSAttributedString *attributedPlaceholder = + [[NSAttributedString alloc] initWithString:@"Search" attributes:placeholderAttributes]; + + [[UITextField + appearanceWhenContainedInInstancesOfClasses:@ [[GMSStyledAutocompleteViewController class]]] + setAttributedPlaceholder:attributedPlaceholder]; + + // Change the background color of selected table cells. + UIView *selectedBackgroundView = [[UIView alloc] init]; + selectedBackgroundView.backgroundColor = selectedTableCellBackgroundColor; + id tableCellAppearance = [UITableViewCell + appearanceWhenContainedInInstancesOfClasses:@ [[GMSStyledAutocompleteViewController class]]]; + [tableCellAppearance setSelectedBackgroundView:selectedBackgroundView]; + + // Depending on the navigation bar background color, it might also be necessary to customise the + // icons displayed in the search bar to something other than the default. The + // setupSearchBarCustomIcons method contains example code to do this. + + GMSAutocompleteViewController *acController = [[GMSStyledAutocompleteViewController alloc] init]; + acController.delegate = self; + acController.autocompleteFilter = self.autocompleteFilter; + acController.placeFields = self.placeFields; + acController.tableCellBackgroundColor = backgroundColor; + acController.tableCellSeparatorColor = separatorColor; + acController.primaryTextColor = primaryTextColor; + acController.primaryTextHighlightColor = highlightColor; + acController.secondaryTextColor = secondaryColor; + acController.tintColor = tintColor; + + [self presentViewController:acController animated:YES completion:nil]; + // Hide theme buttons. + for (UIButton *button in _themeButtons) { + [button setHidden:YES]; + } +} + +/* + * This method shows how to replace the "search" and "clear text" icons in the search bar with + * custom icons in the case where the default gray icons don't match a custom background. + */ +- (void)setupSearchBarCustomIcons { + id searchBarAppearanceProxy = [UISearchBar + appearanceWhenContainedInInstancesOfClasses:@ [[GMSStyledAutocompleteViewController class]]]; + [searchBarAppearanceProxy setImage:[UIImage imageNamed:@"custom_clear_x_high"] + forSearchBarIcon:UISearchBarIconClear + state:UIControlStateHighlighted]; + [searchBarAppearanceProxy setImage:[UIImage imageNamed:@"custom_clear_x"] + forSearchBarIcon:UISearchBarIconClear + state:UIControlStateNormal]; + [searchBarAppearanceProxy setImage:[UIImage imageNamed:@"custom_search"] + forSearchBarIcon:UISearchBarIconSearch + state:UIControlStateNormal]; +} + +#pragma mark - GMSAutocompleteViewControllerDelegate + +- (void)viewController:(GMSAutocompleteViewController *)viewController + didAutocompleteWithPlace:(GMSPlace *)place { + [self dismissViewControllerAnimated:YES completion:nil]; + [self autocompleteDidSelectPlace:place]; +} + +- (void)viewController:(GMSAutocompleteViewController *)viewController + didFailAutocompleteWithError:(NSError *)error { + [self dismissViewControllerAnimated:YES completion:nil]; + [self autocompleteDidFail:error]; +} + +- (void)wasCancelled:(GMSAutocompleteViewController *)viewController { + [self dismissViewControllerAnimated:YES completion:nil]; + [self autocompleteDidCancel]; +} + +- (void)didRequestAutocompletePredictions:(GMSAutocompleteViewController *)viewController { + [UIApplication sharedApplication].networkActivityIndicatorVisible = YES; +} + +- (void)didUpdateAutocompletePredictions:(GMSAutocompleteViewController *)viewController { + [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; +} + +@end diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Samples/Autocomplete/AutocompleteWithSearchViewController.h a/Pods/GooglePlaces/Example/GooglePlacesDemos/Samples/Autocomplete/AutocompleteWithSearchViewController.h new file mode 100755 index 0000000..5b0a4ca --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Samples/Autocomplete/AutocompleteWithSearchViewController.h @@ -0,0 +1,23 @@ +/* + * Copyright 2016 Google Inc. All rights reserved. + * + * + * 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 "GooglePlacesDemos/Samples/Autocomplete/AutocompleteBaseViewController.h" + +/** + * Demo showing the use of GMSAutocompleteViewController with a UISearchController. + */ +@interface AutocompleteWithSearchViewController : AutocompleteBaseViewController + +@end diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Samples/Autocomplete/AutocompleteWithSearchViewController.m a/Pods/GooglePlaces/Example/GooglePlacesDemos/Samples/Autocomplete/AutocompleteWithSearchViewController.m new file mode 100755 index 0000000..5ea5783 --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Samples/Autocomplete/AutocompleteWithSearchViewController.m @@ -0,0 +1,114 @@ +/* + * Copyright 2016 Google Inc. All rights reserved. + * + * + * 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 "GooglePlacesDemos/Samples/Autocomplete/AutocompleteWithSearchViewController.h" + +#import + +NSString *const kSearchBarAccessibilityIdentifier = @"searchBarAccessibilityIdentifier"; + +@interface AutocompleteWithSearchViewController () +@end + +@implementation AutocompleteWithSearchViewController { + UISearchController *_searchController; + GMSAutocompleteResultsViewController *_acViewController; +} + ++ (NSString *)demoTitle { + return NSLocalizedString( + @"Demo.Title.Autocomplete.UISearchController", + @"Title of the UISearchController autocomplete demo for display in a list or nav header"); +} + +#pragma mark - View Lifecycle + +- (void)viewDidLoad { + [super viewDidLoad]; + + _acViewController = [[GMSAutocompleteResultsViewController alloc] init]; + _acViewController.autocompleteFilter = self.autocompleteFilter; + _acViewController.placeFields = self.placeFields; + _acViewController.delegate = self; + + _searchController = + [[UISearchController alloc] initWithSearchResultsController:_acViewController]; + _searchController.hidesNavigationBarDuringPresentation = NO; + _searchController.dimsBackgroundDuringPresentation = YES; + + _searchController.searchBar.autoresizingMask = UIViewAutoresizingFlexibleWidth; + _searchController.searchBar.searchBarStyle = UISearchBarStyleMinimal; + _searchController.searchBar.delegate = self; + _searchController.searchBar.accessibilityIdentifier = kSearchBarAccessibilityIdentifier; + + [_searchController.searchBar sizeToFit]; + self.navigationItem.titleView = _searchController.searchBar; + self.definesPresentationContext = YES; + + // Work around a UISearchController bug that doesn't reposition the table view correctly when + // rotating to landscape. + self.edgesForExtendedLayout = UIRectEdgeAll; + self.extendedLayoutIncludesOpaqueBars = YES; + + _searchController.searchResultsUpdater = _acViewController; + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + _searchController.modalPresentationStyle = UIModalPresentationPopover; + } else { + _searchController.modalPresentationStyle = UIModalPresentationFullScreen; + } +} + +#pragma mark - UISearcBarDelegate + +- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar { + // Inform user that the autocomplete query has been cancelled and dismiss the search bar. + [_searchController setActive:NO]; + [_searchController.searchBar setHidden:YES]; + [self autocompleteDidCancel]; +} + +#pragma mark - GMSAutocompleteResultsViewControllerDelegate + +- (void)resultsController:(GMSAutocompleteResultsViewController *)resultsController + didAutocompleteWithPlace:(GMSPlace *)place { + // Display the results and dismiss the search controller. + [_searchController setActive:NO]; + [self autocompleteDidSelectPlace:place]; +} + +- (void)resultsController:(GMSAutocompleteResultsViewController *)resultsController + didFailAutocompleteWithError:(NSError *)error { + // Display the error and dismiss the search controller. + [_searchController setActive:NO]; + [self autocompleteDidFail:error]; +} + +// Show and hide the network activity indicator when we start/stop loading results. + +- (void)didRequestAutocompletePredictionsForResultsController: + (GMSAutocompleteResultsViewController *)resultsController { + [UIApplication sharedApplication].networkActivityIndicatorVisible = YES; + + // Reset the text and photos view when we are requesting for predictions. + [self resetViews]; +} + +- (void)didUpdateAutocompletePredictionsForResultsController: + (GMSAutocompleteResultsViewController *)resultsController { + [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; +} + +@end diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Samples/Autocomplete/AutocompleteWithTextFieldController.h a/Pods/GooglePlaces/Example/GooglePlacesDemos/Samples/Autocomplete/AutocompleteWithTextFieldController.h new file mode 100755 index 0000000..057fca2 --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Samples/Autocomplete/AutocompleteWithTextFieldController.h @@ -0,0 +1,24 @@ +/* + * Copyright 2016 Google Inc. All rights reserved. + * + * + * 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 "GooglePlacesDemos/Samples/Autocomplete/AutocompleteBaseViewController.h" + +/* + * This demo shows how to manually present a UITableViewController and supply it with autocomplete + * text from an arbitrary source, in this case a UITextField. + */ +@interface AutocompleteWithTextFieldController : AutocompleteBaseViewController + +@end diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Samples/Autocomplete/AutocompleteWithTextFieldController.m a/Pods/GooglePlaces/Example/GooglePlacesDemos/Samples/Autocomplete/AutocompleteWithTextFieldController.m new file mode 100755 index 0000000..b48ef9d --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Samples/Autocomplete/AutocompleteWithTextFieldController.m @@ -0,0 +1,194 @@ +/* + * Copyright 2016 Google Inc. All rights reserved. + * + * + * 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 "GooglePlacesDemos/Samples/Autocomplete/AutocompleteWithTextFieldController.h" + +#import + +@interface AutocompleteWithTextFieldController () +@end + +@implementation AutocompleteWithTextFieldController { + UITextField *_searchField; + UITableViewController *_resultsController; + GMSAutocompleteTableDataSource *_tableDataSource; +} + ++ (NSString *)demoTitle { + return NSLocalizedString( + @"Demo.Title.Autocomplete.UITextField", + @"Title of the UITextField autocomplete demo for display in a list or nav header"); +} + +#pragma mark - View Lifecycle + +- (void)viewDidLoad { + [super viewDidLoad]; + self.view.backgroundColor = [UIColor whiteColor]; + + // Configure the text field to our linking. + _searchField = [[UITextField alloc] initWithFrame:CGRectZero]; + _searchField.translatesAutoresizingMaskIntoConstraints = NO; + _searchField.borderStyle = UITextBorderStyleNone; + _searchField.backgroundColor = [UIColor whiteColor]; + _searchField.placeholder = NSLocalizedString(@"Demo.Content.Autocomplete.EnterTextPrompt", + @"Prompt to enter text for autocomplete demo"); + _searchField.autocorrectionType = UITextAutocorrectionTypeNo; + _searchField.keyboardType = UIKeyboardTypeDefault; + _searchField.returnKeyType = UIReturnKeyDone; + _searchField.clearButtonMode = UITextFieldViewModeWhileEditing; + _searchField.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter; + + [_searchField addTarget:self + action:@selector(textFieldDidChange:) + forControlEvents:UIControlEventEditingChanged]; + _searchField.delegate = self; + + // Setup the results view controller. + _tableDataSource = [[GMSAutocompleteTableDataSource alloc] init]; + _tableDataSource.delegate = self; + _tableDataSource.autocompleteFilter = self.autocompleteFilter; + _tableDataSource.placeFields = self.placeFields; + _tableDataSource.tableCellBackgroundColor = [UIColor whiteColor]; + _resultsController = [[UITableViewController alloc] initWithStyle:UITableViewStylePlain]; + _resultsController.tableView.delegate = _tableDataSource; + _resultsController.tableView.dataSource = _tableDataSource; + + [self.view addSubview:_searchField]; + // Use auto layout to place the text field, as we need to take the top layout guide into + // consideration. + [self.view + addConstraints:[NSLayoutConstraint + constraintsWithVisualFormat:@"H:|-[_searchField]-|" + options:0 + metrics:nil + views:NSDictionaryOfVariableBindings(_searchField)]]; + [NSLayoutConstraint constraintWithItem:_searchField + attribute:NSLayoutAttributeTop + relatedBy:NSLayoutRelationEqual + toItem:self.topLayoutGuide + attribute:NSLayoutAttributeBottom + multiplier:1 + constant:8] + .active = YES; +} + +#pragma mark - GMSAutocompleteTableDataSourceDelegate + +- (void)tableDataSource:(GMSAutocompleteTableDataSource *)tableDataSource + didAutocompleteWithPlace:(GMSPlace *)place { + [self dismissResultsController]; + [_searchField resignFirstResponder]; + [_searchField setHidden:YES]; + [self autocompleteDidSelectPlace:place]; +} + +- (void)tableDataSource:(GMSAutocompleteTableDataSource *)tableDataSource + didFailAutocompleteWithError:(NSError *)error { + [self dismissResultsController]; + [_searchField resignFirstResponder]; + [self autocompleteDidFail:error]; + _searchField.text = @""; +} + +- (void)didRequestAutocompletePredictionsForTableDataSource: + (GMSAutocompleteTableDataSource *)tableDataSource { + [UIApplication sharedApplication].networkActivityIndicatorVisible = YES; + [_resultsController.tableView reloadData]; +} + +- (void)didUpdateAutocompletePredictionsForTableDataSource: + (GMSAutocompleteTableDataSource *)tableDataSource { + [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; + [_resultsController.tableView reloadData]; +} + +#pragma mark - UITextFieldDelegate + +- (void)textFieldDidBeginEditing:(UITextField *)textField { + [self addChildViewController:_resultsController]; + + // Add the results controller. + _resultsController.view.translatesAutoresizingMaskIntoConstraints = NO; + _resultsController.view.alpha = 0.0f; + [self.view addSubview:_resultsController.view]; + + // Layout it out below the text field using auto layout. + [self.view addConstraints:[NSLayoutConstraint + constraintsWithVisualFormat:@"V:[_searchField]-[resultView]-(0)-|" + options:0 + metrics:nil + views:@{ + @"_searchField" : _searchField, + @"resultView" : _resultsController.view + }]]; + [self.view addConstraints:[NSLayoutConstraint + constraintsWithVisualFormat:@"H:|-(0)-[resultView]-(0)-|" + options:0 + metrics:nil + views:@{ + @"resultView" : _resultsController.view + }]]; + + // Force a layout pass otherwise the table will animate in weirdly. + [self.view layoutIfNeeded]; + + // Reload the data. + [_resultsController.tableView reloadData]; + + // Animate in the results. + [UIView animateWithDuration:0.5 + animations:^{ + _resultsController.view.alpha = 1.0f; + } + completion:^(BOOL finished) { + [_resultsController didMoveToParentViewController:self]; + }]; +} + +- (BOOL)textFieldShouldReturn:(UITextField *)textField { + [textField resignFirstResponder]; + return NO; +} + +- (BOOL)textFieldShouldClear:(UITextField *)textField { + [self dismissResultsController]; + [textField resignFirstResponder]; + textField.text = @""; + [_tableDataSource clearResults]; + return NO; +} + +#pragma mark - Private Methods + +- (void)textFieldDidChange:(UITextField *)textField { + [_tableDataSource sourceTextHasChanged:textField.text]; +} + +- (void)dismissResultsController { + // Dismiss the results. + [_resultsController willMoveToParentViewController:nil]; + [UIView animateWithDuration:0.5 + animations:^{ + _resultsController.view.alpha = 0.0f; + } + completion:^(BOOL finished) { + [_resultsController.view removeFromSuperview]; + [_resultsController removeFromParentViewController]; + }]; +} + +@end diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Samples/FindPlaceLikelihoodListViewController.h a/Pods/GooglePlaces/Example/GooglePlacesDemos/Samples/FindPlaceLikelihoodListViewController.h new file mode 100755 index 0000000..e5e26ba --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Samples/FindPlaceLikelihoodListViewController.h @@ -0,0 +1,25 @@ +/* + * Copyright 2019 Google Inc. All rights reserved. + * + * + * 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 +#import + +/** + * Demo that exposes the findPlaceLikelihoodsForLocation API. + */ +@interface FindPlaceLikelihoodListViewController + : UIViewController + +@end diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Samples/FindPlaceLikelihoodListViewController.m a/Pods/GooglePlaces/Example/GooglePlacesDemos/Samples/FindPlaceLikelihoodListViewController.m new file mode 100755 index 0000000..e7ea5c7 --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Samples/FindPlaceLikelihoodListViewController.m @@ -0,0 +1,274 @@ +/* + * Copyright 2019 Google Inc. All rights reserved. + * + * + * 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 "GooglePlacesDemos/Samples/FindPlaceLikelihoodListViewController.h" + +#import + + +static NSString *const kCellIdentifier = @"LikelihoodCellIdentifier"; + +#pragma mark - ButtonCoordinateView + +@interface ButtonCoordinateView : UIView + +// The button used to trigger the fetch likelihoods from coordinate action. +@property(nonatomic, strong) UIButton *button; + +@end + +@implementation ButtonCoordinateView { +} + +- (instancetype)init { + if (self = [super init]) { + [self setupUI]; + } + return self; +} + +- (void)setupUI { + self.layer.cornerRadius = 3; + self.layer.masksToBounds = YES; + self.layer.borderColor = [UIColor clearColor].CGColor; + self.layer.borderWidth = 1; + + UIStackView *stackView = [[UIStackView alloc] init]; + stackView.axis = UILayoutConstraintAxisHorizontal; + stackView.spacing = 15; + stackView.layoutMargins = UIEdgeInsetsMake(5, 0, 5, 0); + stackView.layoutMarginsRelativeArrangement = YES; + [self addSubview:stackView]; + + UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem]; + [button setTitle:@"Retrieve location" forState:UIControlStateNormal]; + [stackView addArrangedSubview:button]; + _button = button; + + UIStackView *labelsStackView = [[UIStackView alloc] init]; + labelsStackView.axis = UILayoutConstraintAxisVertical; + [stackView addArrangedSubview:labelsStackView]; + + [stackView setTranslatesAutoresizingMaskIntoConstraints:NO]; + [NSLayoutConstraint activateConstraints:@[ + [stackView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor], + [stackView.trailingAnchor constraintEqualToAnchor:self.trailingAnchor], + [stackView.topAnchor constraintEqualToAnchor:self.topAnchor], + [stackView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor] + ]]; +} + +// Sets the title and target for the button. +- (void)fillWithButtonTitle:(NSString *)title + target:(id)target + action:(SEL)action + forControlEvents:(UIControlEvents)controlEvents { + [_button setTitle:title forState:UIControlStateNormal]; + [_button addTarget:target action:action forControlEvents:controlEvents]; +} + + +@end + +#pragma mark - FindPlaceLikelihoodListViewController + +@interface FindPlaceLikelihoodListViewController () + +@property(nonatomic, strong) UITableView *tableView; +@property(nonatomic, strong) NSArray *placeLikelihoods; +@property(nonatomic, strong) UILabel *errorLabel; +@property(nonatomic, strong) ButtonCoordinateView *currentButtonCoordinateView; + +@end + +@implementation FindPlaceLikelihoodListViewController { + CLLocationManager *_locationManager; + GMSPlacesClient *_placesClient; +} + ++ (NSString *)demoTitle { + return @"Find Place Likelihoods"; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + _placesClient = [GMSPlacesClient sharedClient]; + + // Initializes the location manager to be used for current location. + CLLocationManager *locationManager = [[CLLocationManager alloc] init]; + locationManager.delegate = self; + _locationManager = locationManager; + + self.title = [NSString stringWithFormat:@"Find place likelihoods from location"]; + self.view.backgroundColor = [UIColor whiteColor]; + + UIStackView *mainStackView = [[UIStackView alloc] init]; + mainStackView.axis = UILayoutConstraintAxisVertical; + [self.view addSubview:mainStackView]; + + // Adds the location input section. + UIStackView *locationInputStackView = [[UIStackView alloc] initWithFrame:CGRectZero]; + locationInputStackView.axis = UILayoutConstraintAxisVertical; + locationInputStackView.layoutMargins = UIEdgeInsetsMake(20, 15, 0, 15); + locationInputStackView.layoutMarginsRelativeArrangement = YES; + locationInputStackView.distribution = UIStackViewDistributionFill; + locationInputStackView.alignment = UIStackViewAlignmentFill; + locationInputStackView.spacing = 10; + [mainStackView addArrangedSubview:locationInputStackView]; + + ButtonCoordinateView *currentView = [[ButtonCoordinateView alloc] init]; + [currentView fillWithButtonTitle:@"Find from current location" + target:self + action:@selector(onCurrentLocationTap) + forControlEvents:UIControlEventTouchUpInside]; + [locationInputStackView addArrangedSubview:currentView]; + _currentButtonCoordinateView = currentView; + + + UILabel *errorLabel = [[UILabel alloc] init]; + errorLabel.textColor = [UIColor redColor]; + errorLabel.numberOfLines = 0; + [locationInputStackView addArrangedSubview:errorLabel]; + _errorLabel = errorLabel; + + // Adds the likelihood list table. + UITableView *tableView = [[UITableView alloc] init]; + [tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:kCellIdentifier]; + tableView.delegate = self; + tableView.dataSource = self; + [mainStackView addArrangedSubview:tableView]; + _tableView = tableView; + + mainStackView.translatesAutoresizingMaskIntoConstraints = NO; + NSArray *stackViewConstraints = @[ + [mainStackView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], + [mainStackView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor], + [mainStackView.topAnchor constraintEqualToAnchor:self.topLayoutGuide.bottomAnchor], + [mainStackView.bottomAnchor constraintEqualToAnchor:self.bottomLayoutGuide.topAnchor] + ]; +#if defined(__IPHONE_11_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0) + if (@available(iOS 11.0, *)) { + stackViewConstraints = @[ + [mainStackView.leadingAnchor + constraintEqualToAnchor:self.view.safeAreaLayoutGuide.leadingAnchor], + [mainStackView.trailingAnchor + constraintEqualToAnchor:self.view.safeAreaLayoutGuide.trailingAnchor], + [mainStackView.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor], + [mainStackView.bottomAnchor + constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor] + ]; + } +#endif + [NSLayoutConstraint activateConstraints:stackViewConstraints]; + + [self onCurrentLocationTap]; +} + +#pragma mark - Button Handlers + +// Requests location services authorization if needed, and starts updating location. +- (void)onCurrentLocationTap { + if (![FindPlaceLikelihoodListViewController areLocationServicesEnabledAndAuthorized]) { + [_locationManager requestWhenInUseAuthorization]; + + return; + } + + [_locationManager startUpdatingLocation]; + + __block FindPlaceLikelihoodListViewController *weakSelf = self; + GMSPlaceLikelihoodsCallback fetcherCallback = + ^(NSArray *_Nullable likelihoods, NSError *_Nullable error) { + [weakSelf handleFindPlaceLikelihoodsResponse:likelihoods error:error]; + }; + + [_placesClient findPlaceLikelihoodsFromCurrentLocationWithPlaceFields:GMSPlaceFieldAll + callback:fetcherCallback]; +} + + +#pragma mark - CLLocationManagerDelegate + +// Retries retrieving current location if user has granted location services permission. +- (void)locationManager:(CLLocationManager *)manager + didChangeAuthorizationStatus:(CLAuthorizationStatus)status { + if (status == kCLAuthorizationStatusAuthorizedWhenInUse) { + // Retry current location fetch once user enables Location Services. + [self onCurrentLocationTap]; + } else { + _errorLabel.text = @"Please make sure location services are enabled."; + } +} + + +#pragma mark - UITableViewDataSource/Delegate + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + if (_placeLikelihoods == nil) { + return 0; + } + return _placeLikelihoods.count; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView + cellForRowAtIndexPath:(NSIndexPath *)indexPath { + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellIdentifier + forIndexPath:indexPath]; + cell.textLabel.numberOfLines = 0; + cell.selectionStyle = UITableViewCellSelectionStyleNone; + NSInteger row = indexPath.row; + NSInteger likelihoodCount = _placeLikelihoods.count; + if (likelihoodCount > 0 && row < likelihoodCount) { + GMSPlaceLikelihood *likelihood = _placeLikelihoods[row]; + cell.textLabel.text = likelihood.place.name; + } + + return cell; +} + +#pragma mark - Helpers + +// Checks if user has authorized location services required for retrieving device location. ++ (BOOL)areLocationServicesEnabledAndAuthorized { + if (![CLLocationManager locationServicesEnabled]) { + return NO; + } + + CLAuthorizationStatus status = [CLLocationManager authorizationStatus]; + return status == kCLAuthorizationStatusAuthorizedAlways || + status == kCLAuthorizationStatusAuthorizedWhenInUse; +} + +- (void)handleFindPlaceLikelihoodsResponse:(NSArray *)likelihoods + error:(NSError *)error { + if (error != nil) { + _errorLabel.text = @"There was an error fetching likelihoods."; + return; + } + + // Filters out Places that don't have a valid name. + NSPredicate *predicate = [NSPredicate + predicateWithBlock:^BOOL(GMSPlaceLikelihood *likelihood, NSDictionary *b) { + return likelihood.place.name != nil && [likelihood.place.name length] > 0; + }]; + _placeLikelihoods = [likelihoods filteredArrayUsingPredicate:predicate]; + _errorLabel.text = nil; + [_tableView reloadData]; + [self resignFirstResponder]; +} + + +@end diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Samples/PagingPhotoView.h a/Pods/GooglePlaces/Example/GooglePlacesDemos/Samples/PagingPhotoView.h new file mode 100755 index 0000000..0a52ba7 --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Samples/PagingPhotoView.h @@ -0,0 +1,40 @@ +/* + * Copyright 2016 Google Inc. All rights reserved. + * + * + * 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 + +/** + * Represents a place photo, along with the attributions which are required to be displayed along + * with it. + */ +@interface AttributedPhoto : NSObject + +@property(nonatomic, strong) UIImage *image; + +@property(nonatomic, strong) NSAttributedString *attributions; + +@end + +/* + * A horizontally-paging scroll view that displays a list of photo images and their attributions. + */ +@interface PagingPhotoView : UIScrollView + +/** + * An array of |AttributedPhoto| objects representing the photos to display. + */ +@property(nonatomic, copy) NSArray *photoList; + +@end diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Samples/PagingPhotoView.m a/Pods/GooglePlaces/Example/GooglePlacesDemos/Samples/PagingPhotoView.m new file mode 100755 index 0000000..b19c3fa --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Samples/PagingPhotoView.m @@ -0,0 +1,180 @@ +/* + * Copyright 2016 Google Inc. All rights reserved. + * + * + * 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 "GooglePlacesDemos/Samples/PagingPhotoView.h" + +/** + * Class to store the image and text views that display the image and attributions. + */ +@interface ImageViewAndAttribution : NSObject + +@property(nonatomic, strong) UIImageView *imageView; + +@property(nonatomic, strong) UITextView *attributionView; + +@end + +@implementation ImageViewAndAttribution +@end + +@implementation AttributedPhoto +@end + +@interface PagingPhotoView () +@end + +@implementation PagingPhotoView { + // An array of |ImageViewAndAttribution| objects representing the actual views that are + // being displayed. + NSMutableArray *_photoImageViews; + // Whether we should update the image and attribution view frames on the next |layoutSubviews| + // call. This should be set to YES whenever the frame is updated or the photos change. + BOOL _imageLayoutUpdateNeeded; +} + +- (instancetype)initWithFrame:(CGRect)frame { + if ((self = [super initWithFrame:frame])) { + _photoImageViews = [NSMutableArray array]; + self.backgroundColor = [UIColor whiteColor]; + self.pagingEnabled = YES; + } + return self; +} + +- (void)setPhotoList:(NSArray *)photoList { + // First, remove all of the existing image and attribution subviews. + for (ImageViewAndAttribution *photoView in _photoImageViews) { + [photoView.imageView removeFromSuperview]; + [photoView.attributionView removeFromSuperview]; + } + [_photoImageViews removeAllObjects]; + + // Add the new images and attributions as subviews. + _photoList = [photoList copy]; + for (AttributedPhoto *photo in photoList) { + UITextView *textView = [[UITextView alloc] initWithFrame:CGRectZero]; + textView.delegate = self; + textView.editable = NO; + textView.attributedText = photo.attributions; + [self addSubview:textView]; + + UIImageView *imageView = [[UIImageView alloc] initWithImage:photo.image]; + imageView.contentMode = UIViewContentModeScaleAspectFit; + imageView.clipsToBounds = YES; + [self addSubview:imageView]; + + ImageViewAndAttribution *attributedView = [[ImageViewAndAttribution alloc] init]; + attributedView.imageView = imageView; + attributedView.attributionView = textView; + [_photoImageViews addObject:attributedView]; + } + [self updateContentSize]; + _imageLayoutUpdateNeeded = YES; +} + +- (void)setFrame:(CGRect)frame { + _imageLayoutUpdateNeeded = YES; + + // We want to make sure that we are still scrolled to the same photo when the frame changes. + // Measure the current content offset and scroll to the same fraction along the content after the + // frame change. + CGFloat scrollOffsetFraction = 0; + if (self.contentSize.width != 0) { + scrollOffsetFraction = self.contentOffset.x / self.contentSize.width; + } + [super setFrame:frame]; + [UIView performWithoutAnimation:^{ + [self updateContentSize]; + self.contentOffset = + CGPointMake(scrollOffsetFraction * self.contentSize.width, -self.contentInset.top); + }]; +} + +- (void)layoutSubviews { + [super layoutSubviews]; + if (_imageLayoutUpdateNeeded) { + [self layoutImages]; + _imageLayoutUpdateNeeded = NO; + + // Re-adjust the content offset to ensure the photos are aligned properly horizontally. + if (self.contentSize.width != 0) { + CGFloat scrollOffset = + (CGFloat)round((self.contentOffset.x / self.contentSize.width) * 10.0f) / 10.0f; + self.contentOffset = + CGPointMake(scrollOffset * self.contentSize.width, -self.contentInset.top); + } + } +} + +#pragma mark - UITextViewDelegate + +- (BOOL)textView:(UITextView *)textView + shouldInteractWithURL:(NSURL *)url + inRange:(NSRange)characterRange { + // Make links clickable. + return YES; +} + +#pragma mark - Helper methods + +/** + * Update the content size of the scroll view based on the number of photos and the view's width. + * This should be called whenever the frame changes or the number of photos has changed. + */ +- (void)updateContentSize { + CGRect insetBounds = UIEdgeInsetsInsetRect(self.bounds, self.contentInset); + CGFloat usableScrollViewHeight = insetBounds.size.height; + + self.contentSize = + CGSizeMake(_photoImageViews.count * self.frame.size.width, usableScrollViewHeight); +} + +/** + * Updates the frames of the images and attributions. + */ +- (void)layoutImages { + CGFloat contentWidth = 0; + CGFloat scrollViewWidth = self.bounds.size.width; + CGRect insetBounds = UIEdgeInsetsInsetRect(self.bounds, self.contentInset); + CGFloat usableScrollViewHeight = insetBounds.size.height; + + // Lay out the images one after the other horizontally. + for (ImageViewAndAttribution *attributedImageView in _photoImageViews) { + UITextView *attributionView = attributedImageView.attributionView; + UIImageView *imageView = attributedImageView.imageView; + [attributionView sizeToFit]; + CGFloat attributionHeight = attributionView.frame.size.height; + CGFloat imageHeight = usableScrollViewHeight - attributionHeight; + CGFloat safeAreaX = 0.0f; + +#if defined(__IPHONE_11_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0) + // Take into account the safe areas of the device screen and do not use that space for the + // attribution text. + if (@available(iOS 11.0, *)) { + imageHeight -= self.safeAreaInsets.bottom; + safeAreaX = self.safeAreaInsets.left; + } +#endif + + // Put the attribution view aligned to the same left edge as the photo, in the bottom left + // corner of the screen. + attributionView.frame = CGRectMake(contentWidth + safeAreaX, imageHeight, + scrollViewWidth - (2 * safeAreaX), attributionHeight); + imageView.frame = CGRectMake(contentWidth, 0, scrollViewWidth, imageHeight); + contentWidth += imageView.frame.size.width; + } +} + +@end diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Support/BaseDemoViewController.h a/Pods/GooglePlaces/Example/GooglePlacesDemos/Support/BaseDemoViewController.h new file mode 100755 index 0000000..6e8759f --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Support/BaseDemoViewController.h @@ -0,0 +1,31 @@ +/* + * Copyright 2016 Google Inc. All rights reserved. + * + * + * 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 + +/** + * Base view controller for all demos in the Places Demo app. Provides some basic functionality + * which is common across demos. + */ +@interface BaseDemoViewController : UIViewController + +/** + * The title of the demo. Displayed in lists and navigation bars. + * + * NOTE: This must be overridden by subclasses. + */ ++ (NSString *)demoTitle; + +@end diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/Support/BaseDemoViewController.m a/Pods/GooglePlaces/Example/GooglePlacesDemos/Support/BaseDemoViewController.m new file mode 100755 index 0000000..9984cab --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/Support/BaseDemoViewController.m @@ -0,0 +1,32 @@ +/* + * Copyright 2016 Google Inc. All rights reserved. + * + * + * 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 "GooglePlacesDemos/Support/BaseDemoViewController.h" + +@implementation BaseDemoViewController + ++ (NSString *)demoTitle { + // This should be overridden by subclasses, so should not be called. + return nil; +} + +- (instancetype)initWithNibName:(NSString *)name bundle:(NSBundle *)bundle { + if ((self = [super initWithNibName:name bundle:bundle])) { + self.title = [[self class] demoTitle]; + } + return self; +} + +@end diff --git b/Pods/GooglePlaces/Example/GooglePlacesDemos/main.m a/Pods/GooglePlaces/Example/GooglePlacesDemos/main.m new file mode 100755 index 0000000..86bfe27 --- /dev/null +++ a/Pods/GooglePlaces/Example/GooglePlacesDemos/main.m @@ -0,0 +1,24 @@ +/* + * Copyright 2016 Google Inc. All rights reserved. + * + * + * 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 + +#import "GooglePlacesDemos/DemoAppDelegate.h" + +int main(int argc, char* argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([DemoAppDelegate class])); + } +} diff --git b/Pods/GooglePlaces/Example/Podfile a/Pods/GooglePlaces/Example/Podfile new file mode 100755 index 0000000..dd81691 --- /dev/null +++ a/Pods/GooglePlaces/Example/Podfile @@ -0,0 +1,7 @@ +source 'https://github.com/CocoaPods/Specs.git' + +target 'GooglePlacesDemos' do + platform :ios, '9.0' + pod 'GooglePlaces', '= 3.8.0' + pod 'GoogleMaps', '= 3.8.0' +end diff --git b/Pods/GooglePlaces/Example/README.GooglePlacesDemos a/Pods/GooglePlaces/Example/README.GooglePlacesDemos new file mode 100755 index 0000000..ecf5fdd --- /dev/null +++ a/Pods/GooglePlaces/Example/README.GooglePlacesDemos @@ -0,0 +1,18 @@ +GooglePlacesDemos contains a demo application showcasing various features of +the Google Places API for iOS. + +Before starting, please note that these demos are directed towards a technical +audience. You'll also need Xcode 9.0 or later, with the iOS SDK 9.3 or later. + +If you're new to the API, please read the Introduction section of the Google +Places API for iOS documentation- + https://developers.google.com/places/ios-api/ + +Once you've read the Introduction page, follow the first couple of steps on the +"Getting Started" page. Specifically; + + * Obtain an API key for the demo application, and specify the bundle ID of + this demo application as an an 'allowed iOS app'. By default, the bundle ID + is "com.example.GooglePlacesDemos". + + * Open the project in Xcode, and update `SDKDemoAPIKey.h` with this key. diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/GooglePlaces a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/GooglePlaces new file mode 100755 index 0000000..08c55eb Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/GooglePlaces differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSAddressComponent.h a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSAddressComponent.h new file mode 100755 index 0000000..21a1387 --- /dev/null +++ a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSAddressComponent.h @@ -0,0 +1,43 @@ +// +// GMSAddressComponent.h +// Google Places SDK for iOS +// +// Copyright 2016 Google Inc. +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * Represents a component of an address, e.g., street number, postcode, city, etc. + */ +@interface GMSAddressComponent : NSObject + +/** + * Type of the address component. For a list of supported types, see + * https://developers.google.com/places/ios-sdk/supported_types#table2. This string will be one + * of the constants defined in GMSPlaceTypes.h. + */ +@property(nonatomic, readonly, copy) + NSString *type __deprecated_msg("type property is deprecated in favor of types)"); + +/** + * Types associated with the address component. For a list of supported types, see + * https://developers.google.com/places/ios-sdk/supported_types#table2. This array will contain + * one or more of the constants strings defined in GMSPlaceTypes.h. + */ +@property(nonatomic, readonly, strong) NSArray *types; + +/** Name of the address component, e.g. "Sydney" */ +@property(nonatomic, readonly, copy) NSString *name; + +/** Short name of the address component, e.g. "AU" */ +@property(nonatomic, readonly, copy) NSString *_Nullable shortName; + +@end + +NS_ASSUME_NONNULL_END diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSAutocompleteBoundsMode.h a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSAutocompleteBoundsMode.h new file mode 100755 index 0000000..99c48c0 --- /dev/null +++ a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSAutocompleteBoundsMode.h @@ -0,0 +1,32 @@ +// +// GMSAutocompleteBoundsMode.h +// Google Places SDK for iOS +// +// Copyright 2017 Google Inc. +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +/** + * \defgroup AutocompleteBoundsMode GMSAutocompleteBoundsMode + * @{ + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * Specifies how autocomplete should interpret the |bounds| parameters. + */ +typedef NS_ENUM(NSUInteger, GMSAutocompleteBoundsMode) { + /** Interpret |bounds| as a bias. */ + kGMSAutocompleteBoundsModeBias, + /** Interpret |bounds| as a restrict. */ + kGMSAutocompleteBoundsModeRestrict +}; + +NS_ASSUME_NONNULL_END + +/**@}*/ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSAutocompleteFetcher.h a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSAutocompleteFetcher.h new file mode 100755 index 0000000..140ef57 --- /dev/null +++ a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSAutocompleteFetcher.h @@ -0,0 +1,103 @@ +// +// GMSAutocompleteFetcher.h +// Google Places SDK for iOS +// +// Copyright 2016 Google Inc. +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import "GMSAutocompleteBoundsMode.h" +#import "GMSAutocompleteFilter.h" + +@class GMSAutocompletePrediction; +@class GMSAutocompleteSessionToken; +@class GMSCoordinateBounds; + +NS_ASSUME_NONNULL_BEGIN + +/** + * Protocol for objects that can receive callbacks from GMSAutocompleteFetcher + */ +@protocol GMSAutocompleteFetcherDelegate + +@required + +/** + * Called when autocomplete predictions are available. + * @param predictions an array of GMSAutocompletePrediction objects. + */ +- (void)didAutocompleteWithPredictions:(NSArray *)predictions; + +/** + * Called when an autocomplete request returns an error. + * @param error the error that was received. + */ +- (void)didFailAutocompleteWithError:(NSError *)error; + +@end + +/** + * GMSAutocompleteFetcher is a wrapper around the lower-level autocomplete APIs that encapsulates + * some of the complexity of requesting autocomplete predictions as the user is typing. Calling + * sourceTextHasChanged will generally result in the provided delegate being called with + * autocomplete predictions for the queried text, with the following provisos: + * + * - The fetcher may not necessarily request predictions on every call of sourceTextHasChanged if + * several requests are made within a short amount of time. + * - The delegate will only be called with prediction results if those predictions are for the + * text supplied in the most recent call to sourceTextHasChanged. + */ +@interface GMSAutocompleteFetcher : NSObject + +/** + * Initialize the fetcher. + * + * @param bounds The bounds used to bias or restrict the results. Whether this biases or restricts + * is determined by the value of the |autocompleteBoundsMode| property. + * This parameter may be nil. + * @param filter The filter to apply to the results. This parameter may be nil. + */ +- (instancetype)initWithBounds:(nullable GMSCoordinateBounds *)bounds + filter:(nullable GMSAutocompleteFilter *)filter NS_DESIGNATED_INITIALIZER; + +/** Delegate to be notified with autocomplete prediction results. */ +@property(nonatomic, weak, nullable) id delegate; + +/** + * Bounds used to bias or restrict the autocomplete results depending on the value of + * |autocompleteBoundsMode| (can be nil). + */ +@property(nonatomic, strong, nullable) GMSCoordinateBounds *autocompleteBounds; + +/** + * How to treat the |autocompleteBounds| property. Defaults to |kGMSAutocompleteBoundsModeBias|. + * + * Has no effect if |autocompleteBounds| is nil. + */ +@property(nonatomic, assign) GMSAutocompleteBoundsMode autocompleteBoundsMode; + +/** Filter to apply to autocomplete suggestions (can be nil). */ +@property(nonatomic, strong, nullable) GMSAutocompleteFilter *autocompleteFilter; + +/** + * Provide a |GMSAutocompleteSessionToken| for tracking the specific autocomplete query flow. + */ +- (void)provideSessionToken:(nullable GMSAutocompleteSessionToken *)sessionToken; + +/** + * Notify the fetcher that the source text to autocomplete has changed. + * + * This method should only be called from the main thread. Calling this method from another thread + * will result in undefined behavior. Calls to |GMSAutocompleteFetcherDelegate| methods will also be + * called on the main thread. + * + * This method is non-blocking. + * @param text The partial text to autocomplete. + */ +- (void)sourceTextHasChanged:(nullable NSString *)text; + +@end + +NS_ASSUME_NONNULL_END diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSAutocompleteFilter.h a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSAutocompleteFilter.h new file mode 100755 index 0000000..8d69b02 --- /dev/null +++ a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSAutocompleteFilter.h @@ -0,0 +1,112 @@ +// +// GMSAutocompleteFilter.h +// Google Places SDK for iOS +// +// Copyright 2016 Google Inc. +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import +#import + +@protocol GMSPlaceLocationBias; +@protocol GMSPlaceLocationRestriction; + +NS_ASSUME_NONNULL_BEGIN + +/** + * \defgroup PlacesAutocompleteTypeFilter GMSPlacesAutocompleteTypeFilter + * @{ + */ + +/** + * The type filters that may be applied to an autocomplete request to restrict results to different + * types. + */ +typedef NS_ENUM(NSInteger, GMSPlacesAutocompleteTypeFilter) { + /** + * All results. + */ + kGMSPlacesAutocompleteTypeFilterNoFilter, + /** + * Geeocoding results, as opposed to business results. + */ + kGMSPlacesAutocompleteTypeFilterGeocode, + /** + * Geocoding results with a precise address. + */ + kGMSPlacesAutocompleteTypeFilterAddress, + /** + * Business results. + */ + kGMSPlacesAutocompleteTypeFilterEstablishment, + /** + * Results that match the following types: + * "locality", + * "sublocality" + * "postal_code", + * "country", + * "administrative_area_level_1", + * "administrative_area_level_2" + */ + kGMSPlacesAutocompleteTypeFilterRegion, + /** + * Results that match the following types: + * "locality", + * "administrative_area_level_3" + */ + kGMSPlacesAutocompleteTypeFilterCity, +}; + +/**@}*/ + +/** + * This class represents a set of restrictions that may be applied to autocomplete requests. This + * allows customization of autocomplete suggestions to only those places that are of interest. + */ +@interface GMSAutocompleteFilter : NSObject + +/** + * The type filter applied to an autocomplete request to restrict results to different types. + * Default value is kGMSPlacesAutocompleteTypeFilterNoFilter. + */ +@property(nonatomic, assign) GMSPlacesAutocompleteTypeFilter type; + +/** + * The country to restrict results to. This should be a ISO 3166-1 Alpha-2 country code (case + * insensitive). If nil, no country filtering will take place. + * + * NOTE: Ignored if the countries property is set. + */ +@property(nonatomic, copy, nullable) NSString *country; +; + +/** + * The countries to restrict results to. This should be a ISO 3166-1 Alpha-2 country code (case + * insensitive). Supports up to 5 countries to filter. If nil, no country filtering will take place. + * + * NOTE: Overrides the country property if that is set. + */ +@property(nonatomic, copy, nullable) NSArray *countries; + +/** + * The staight line distance origin location for measuring the straight line distance between the + * origin location and autocomplete predictions. + */ +@property(nonatomic, nullable) CLLocation *origin; + +/** + * The optional location bias to perfer place results near the location. + */ +@property(nonatomic, nullable) id locationBias; + +/** + * The optional location restriction to limit the place results to. + */ +@property(nonatomic, nullable) id locationRestriction; + +@end + +NS_ASSUME_NONNULL_END diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSAutocompleteMatchFragment.h a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSAutocompleteMatchFragment.h new file mode 100755 index 0000000..fb3e5a9 --- /dev/null +++ a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSAutocompleteMatchFragment.h @@ -0,0 +1,40 @@ +// +// GMSAutocompleteMatchFragment.h +// Google Places SDK for iOS +// +// Copyright 2016 Google Inc. +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * This class represents a matched fragment of a string. This is a contiguous range of characters + * in a string, suitable for highlighting in an autocompletion UI. + */ +@interface GMSAutocompleteMatchFragment : NSObject + +/** + * The offset of the matched fragment. This is an index into a string. The character at this index + * is the first matched character. + */ +@property(nonatomic, readonly) NSUInteger offset; + +/** + * The length of the matched fragment. + */ +@property(nonatomic, readonly) NSUInteger length; + +/** + * Initializer is not available. + */ +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSAutocompletePrediction.h a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSAutocompletePrediction.h new file mode 100755 index 0000000..2a74641 --- /dev/null +++ a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSAutocompletePrediction.h @@ -0,0 +1,106 @@ +// +// GMSAutocompletePrediction.h +// Google Places SDK for iOS +// +// Copyright 2016 Google Inc. +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + + +#import + +NS_ASSUME_NONNULL_BEGIN + +#if defined(__IPHONE_11_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0) +/** + * Attribute name for match fragments in |GMSAutocompletePrediction| attributedFullText. + * + * @related GMSAutocompletePrediction + */ +extern NSAttributedStringKey const kGMSAutocompleteMatchAttribute; +#else +/** + * Attribute name for match fragments in |GMSAutocompletePrediction| attributedFullText. + * + * @related GMSAutocompletePrediction + */ +extern NSString *const kGMSAutocompleteMatchAttribute; +#endif + +/** + * This class represents a prediction of a full query based on a partially typed string. + */ +@interface GMSAutocompletePrediction : NSObject + +/** + * The full description of the prediction as a NSAttributedString. E.g., "Sydney Opera House, + * Sydney, New South Wales, Australia". + * + * Every text range that matches the user input has a |kGMSAutocompleteMatchAttribute|. For + * example, you can make every match bold using enumerateAttribute: + *
+ *   UIFont *regularFont = [UIFont systemFontOfSize:[UIFont labelFontSize]];
+ *   UIFont *boldFont = [UIFont boldSystemFontOfSize:[UIFont labelFontSize]];
+ *
+ *   NSMutableAttributedString *bolded = [prediction.attributedFullText mutableCopy];
+ *   [bolded enumerateAttribute:kGMSAutocompleteMatchAttribute
+ *                      inRange:NSMakeRange(0, bolded.length)
+ *                      options:0
+ *                   usingBlock:^(id value, NSRange range, BOOL *stop) {
+ *                     UIFont *font = (value == nil) ? regularFont : boldFont;
+ *                     [bolded addAttribute:NSFontAttributeName value:font range:range];
+ *                   }];
+ *
+ *   label.attributedText = bolded;
+ * 
+ */ +@property(nonatomic, copy, readonly) NSAttributedString *attributedFullText; + +/** + * The main text of a prediction as a NSAttributedString, usually the name of the place. + * E.g. "Sydney Opera House". + * + * Text ranges that match user input are have a |kGMSAutocompleteMatchAttribute|, + * like |attributedFullText|. + */ +@property(nonatomic, copy, readonly) NSAttributedString *attributedPrimaryText; + +/** + * The secondary text of a prediction as a NSAttributedString, usually the location of the place. + * E.g. "Sydney, New South Wales, Australia". + * + * Text ranges that match user input are have a |kGMSAutocompleteMatchAttribute|, like + * |attributedFullText|. + * + * May be nil. + */ +@property(nonatomic, copy, readonly, nullable) NSAttributedString *attributedSecondaryText; + +/** + * A property representing the place ID of the prediction, suitable for use in a place details + * request. + */ +@property(nonatomic, copy, readonly) NSString *placeID; + +/** + * The types of this autocomplete result. Types are NSStrings, valid values are any types + * documented at . + */ +@property(nonatomic, copy, readonly) NSArray *types; + +/** + * The straight line distance in meters between the origin and this prediction if a valid origin is + * specified in the |GMSAutocompleteFilter| of the request. + */ +@property(nonatomic, readonly, nullable) NSNumber *distanceMeters; + +/** + * Initializer is not available. + */ +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSAutocompleteResultsViewController.h a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSAutocompleteResultsViewController.h new file mode 100755 index 0000000..38aedd6 --- /dev/null +++ a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSAutocompleteResultsViewController.h @@ -0,0 +1,152 @@ +// +// GMSAutocompleteResultsViewController.h +// Google Places SDK for iOS +// +// Copyright 2016 Google Inc. +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import + +#import "GMSAutocompleteBoundsMode.h" +#import "GMSAutocompleteFilter.h" +#import "GMSAutocompletePrediction.h" +#import "GMSPlace.h" +#import "GMSPlaceFieldMask.h" + +@class GMSAutocompleteResultsViewController; +@class GMSCoordinateBounds; + +NS_ASSUME_NONNULL_BEGIN + +/** + * Protocol used by |GMSAutocompleteResultsViewController|, to communicate the user's interaction + * with the controller to the application. + */ +@protocol GMSAutocompleteResultsViewControllerDelegate + +@required + +/** + * Called when a place has been selected from the available autocomplete predictions. + * @param resultsController The |GMSAutocompleteResultsViewController| that generated the event. + * @param place The |GMSPlace| that was returned. + */ +- (void)resultsController:(GMSAutocompleteResultsViewController *)resultsController + didAutocompleteWithPlace:(GMSPlace *)place; + +/** + * Called when a non-retryable error occurred when retrieving autocomplete predictions or place + * details. A non-retryable error is defined as one that is unlikely to be fixed by immediately + * retrying the operation. + *

+ * Only the following values of |GMSPlacesErrorCode| are retryable: + *

    + *
  • kGMSPlacesNetworkError + *
  • kGMSPlacesServerError + *
  • kGMSPlacesInternalError + *
+ * All other error codes are non-retryable. + * @param resultsController The |GMSAutocompleteResultsViewController| that generated the event. + * @param error The |NSError| that was returned. + */ +- (void)resultsController:(GMSAutocompleteResultsViewController *)resultsController + didFailAutocompleteWithError:(NSError *)error; + +@optional + +/** + * Called when the user selects an autocomplete prediction from the list but before requesting + * place details. Returning NO from this method will suppress the place details fetch and + * didAutocompleteWithPlace will not be called. + * @param resultsController The |GMSAutocompleteResultsViewController| that generated the event. + * @param prediction The |GMSAutocompletePrediction| that was selected. + */ +- (BOOL)resultsController:(GMSAutocompleteResultsViewController *)resultsController + didSelectPrediction:(GMSAutocompletePrediction *)prediction; + +/** + * Called once every time new autocomplete predictions are received. + * @param resultsController The |GMSAutocompleteResultsViewController| that generated the event. + */ +- (void)didUpdateAutocompletePredictionsForResultsController: + (GMSAutocompleteResultsViewController *)resultsController; + +/** + * Called once immediately after a request for autocomplete predictions is made. + * @param resultsController The |GMSAutocompleteResultsViewController| that generated the event. + */ +- (void)didRequestAutocompletePredictionsForResultsController: + (GMSAutocompleteResultsViewController *)resultsController; + +@end + +/** + * GMSAutocompleteResultsViewController provides an interface that displays place autocomplete + * predictions in a table view. The table view will be automatically updated as input text + * changes. + * + * This class is intended to be used as the search results controller of a UISearchController. Pass + * an instance of |GMSAutocompleteResultsViewController| to UISearchController's + * initWithSearchResultsController method, then set the controller as the UISearchController's + * searchResultsUpdater property. + * + * Use the |GMSAutocompleteResultsViewControllerDelegate| delegate protocol to be notified when a + * place is selected from the list. + */ +@interface GMSAutocompleteResultsViewController : UIViewController + +/** Delegate to be notified when a place is selected. */ +@property(nonatomic, weak, nullable) id delegate; + +/** + * Bounds used to bias or restrict the autocomplete results depending on the value of + * |autocompleteBoundsMode| (can be nil). + */ +@property(nonatomic, strong, nullable) GMSCoordinateBounds *autocompleteBounds; + +/** + * How to treat the |autocompleteBounds| property. Defaults to |kGMSAutocompleteBoundsModeBias|. + * + * Has no effect if |autocompleteBounds| is nil. + */ +@property(nonatomic, assign) GMSAutocompleteBoundsMode autocompleteBoundsMode; + +/** Filter to apply to autocomplete suggestions (can be nil). */ +@property(nonatomic, strong, nullable) GMSAutocompleteFilter *autocompleteFilter; + +/** The background color of table cells. */ +@property(nonatomic, strong) IBInspectable UIColor *tableCellBackgroundColor; + +/** The color of the separator line between table cells. */ +@property(nonatomic, strong) IBInspectable UIColor *tableCellSeparatorColor; + +/** The color of result name text in autocomplete results */ +@property(nonatomic, strong) IBInspectable UIColor *primaryTextColor; + +/** The color used to highlight matching text in autocomplete results */ +@property(nonatomic, strong) IBInspectable UIColor *primaryTextHighlightColor; + +/** The color of the second row of text in autocomplete results. */ +@property(nonatomic, strong) IBInspectable UIColor *secondaryTextColor; + +/** The tint color applied to controls in the Autocomplete view. */ +@property(nonatomic, strong, nullable) IBInspectable UIColor *tintColor; + +/** + * Specify individual place details to fetch for object |GMSPlace|. + * Defaults to returning all details if not overridden. + */ +@property(nonatomic, assign) GMSPlaceField placeFields; + +/** + * Sets up the autocomplete bounds using the NE and SW corner locations. + */ +- (void)setAutocompleteBoundsUsingNorthEastCorner:(CLLocationCoordinate2D)NorthEastCorner + SouthWestCorner:(CLLocationCoordinate2D)SouthWestCorner; + +@end + +NS_ASSUME_NONNULL_END diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSAutocompleteSessionToken.h a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSAutocompleteSessionToken.h new file mode 100755 index 0000000..cff3b7c --- /dev/null +++ a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSAutocompleteSessionToken.h @@ -0,0 +1,23 @@ +// +// GMSAutocompleteSessionToken.h +// Google Places SDK for iOS +// +// Copyright 2018 Google Inc. +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * This class represents an session token to uniquely identify an series of queries to the Google + * Places API Services for fetching place predictions for a partial search string. + */ +@interface GMSAutocompleteSessionToken : NSObject +@end + +NS_ASSUME_NONNULL_END diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSAutocompleteTableDataSource.h a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSAutocompleteTableDataSource.h new file mode 100755 index 0000000..66216b7 --- /dev/null +++ a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSAutocompleteTableDataSource.h @@ -0,0 +1,188 @@ +// +// GMSAutocompleteTableDataSource.h +// Google Places SDK for iOS +// +// Copyright 2016 Google Inc. +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import + +#import "GMSAutocompleteBoundsMode.h" +#import "GMSAutocompleteFilter.h" +#import "GMSAutocompletePrediction.h" +#import "GMSPlace.h" +#import "GMSPlaceFieldMask.h" + +@class GMSAutocompleteTableDataSource; +@class GMSCoordinateBounds; + +NS_ASSUME_NONNULL_BEGIN + +/** + * Protocol used by |GMSAutocompleteTableDataSource|, to communicate the user's interaction with the + * data source to the application. + */ +@protocol GMSAutocompleteTableDataSourceDelegate + +@required + +/** + * Called when a place has been selected from the available autocomplete predictions. + * @param tableDataSource The |GMSAutocompleteTableDataSource| that generated the event. + * @param place The |GMSPlace| that was returned. + */ +- (void)tableDataSource:(GMSAutocompleteTableDataSource *)tableDataSource + didAutocompleteWithPlace:(GMSPlace *)place; + +/** + * Called when a non-retryable error occurred when retrieving autocomplete predictions or place + * details. A non-retryable error is defined as one that is unlikely to be fixed by immediately + * retrying the operation. + *

+ * Only the following values of |GMSPlacesErrorCode| are retryable: + *

    + *
  • kGMSPlacesNetworkError + *
  • kGMSPlacesServerError + *
  • kGMSPlacesInternalError + *
+ * All other error codes are non-retryable. + * @param tableDataSource The |GMSAutocompleteTableDataSource| that generated the event. + * @param error The |NSError| that was returned. + */ +- (void)tableDataSource:(GMSAutocompleteTableDataSource *)tableDataSource + didFailAutocompleteWithError:(NSError *)error; + +@optional + +/** + * Called when the user selects an autocomplete prediction from the list but before requesting + * place details. Returning NO from this method will suppress the place details fetch and + * didAutocompleteWithPlace will not be called. + * @param tableDataSource The |GMSAutocompleteTableDataSource| that generated the event. + * @param prediction The |GMSAutocompletePrediction| that was selected. + */ +- (BOOL)tableDataSource:(GMSAutocompleteTableDataSource *)tableDataSource + didSelectPrediction:(GMSAutocompletePrediction *)prediction; + +/** + * Called once every time new autocomplete predictions are received. + * @param tableDataSource The |GMSAutocompleteTableDataSource| that generated the event. + */ +- (void)didUpdateAutocompletePredictionsForTableDataSource: + (GMSAutocompleteTableDataSource *)tableDataSource; + +/** + * Called once immediately after a request for autocomplete predictions is made. + * @param tableDataSource The |GMSAutocompleteTableDataSource| that generated the event. + */ +- (void)didRequestAutocompletePredictionsForTableDataSource: + (GMSAutocompleteTableDataSource *)tableDataSource; + +@end + +/** + * GMSAutocompleteTableDataSource provides an interface for providing place autocomplete + * predictions to populate a UITableView by implementing the UITableViewDataSource and + * UITableViewDelegate protocols. + * + * GMSAutocompleteTableDataSource is designed to be used as the data source for a + * UISearchDisplayController. + * + * NOTE: UISearchDisplayController has been deprecated since iOS 8. It is now recommended to use + * UISearchController with |GMSAutocompleteResultsViewController| to display autocomplete results + * using the iOS search UI. + * + * Set an instance of GMSAutocompleteTableDataSource as the searchResultsDataSource and + * searchResultsDelegate properties of UISearchDisplayController. In your implementation of + * shouldReloadTableForSearchString, call sourceTextHasChanged with the current search string. + * + * Use the |GMSAutocompleteTableDataSourceDelegate| delegate protocol to be notified when a place is + * selected from the list. Because autocomplete predictions load asynchronously, it is necessary + * to implement didUpdateAutocompletePredictions and call reloadData on the + * UISearchDisplayController's table view. + * + */ +@interface GMSAutocompleteTableDataSource : NSObject + +/** Delegate to be notified when a place is selected or picking is cancelled. */ +@property(nonatomic, weak, nullable) IBOutlet id delegate; + +/** + * Bounds used to bias or restrict the autocomplete results depending on the value of + * |autocompleteBoundsMode| (can be nil). + */ +@property(nonatomic, strong, nullable) GMSCoordinateBounds *autocompleteBounds; + +/** + * How to treat the |autocompleteBounds| property. Defaults to |kGMSAutocompleteBoundsModeBias|. + * + * Has no effect if |autocompleteBounds| is nil. + */ +@property(nonatomic, assign) GMSAutocompleteBoundsMode autocompleteBoundsMode; + +/** Filter to apply to autocomplete suggestions (can be nil). */ +@property(nonatomic, strong, nullable) GMSAutocompleteFilter *autocompleteFilter; + +/** The background color of table cells. */ +@property(nonatomic, strong) UIColor *tableCellBackgroundColor; + +/** The color of the separator line between table cells. */ +@property(nonatomic, strong) UIColor *tableCellSeparatorColor; + +/** The color of result name text in autocomplete results */ +@property(nonatomic, strong) UIColor *primaryTextColor; + +/** The color used to highlight matching text in autocomplete results */ +@property(nonatomic, strong) UIColor *primaryTextHighlightColor; + +/** The color of the second row of text in autocomplete results. */ +@property(nonatomic, strong) UIColor *secondaryTextColor; + +/** The tint color applied to controls in the Autocomplete view. */ +@property(nonatomic, strong, nullable) UIColor *tintColor; + +/** + * The |GMSPlaceField| for specifying explicit place details to be requested. Default returns + * all avilable fields. + */ +@property(nonatomic, assign) GMSPlaceField placeFields; + +/** Designated initializer */ +- (instancetype)init NS_DESIGNATED_INITIALIZER; + +/** + * Notify the data source that the source text to autocomplete has changed. + * + * This method should only be called from the main thread. Calling this method from another thread + * will result in undefined behavior. Calls to |GMSAutocompleteTableDataSourceDelegate| methods will + * also be called on the main thread. + * + * This method is non-blocking. + * @param text The partial text to autocomplete. + */ +- (void)sourceTextHasChanged:(nullable NSString *)text; + +/** + * Clear all predictions. + * + * NOTE: This will call the two delegate methods below: + * + * - |didUpdateAutocompletePredictionsForResultsController:| + * - |didRequestAutocompletePredictionsForResultsController:| + * + * The implementation of this method is guaranteed to call these synchronously and in-order. + */ +- (void)clearResults; + +/** + * Sets up the autocomplete bounds using the NE and SW corner locations. + */ +- (void)setAutocompleteBoundsUsingNorthEastCorner:(CLLocationCoordinate2D)NorthEastCorner + SouthWestCorner:(CLLocationCoordinate2D)SouthWestCorner; + +@end + +NS_ASSUME_NONNULL_END diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSAutocompleteViewController.h a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSAutocompleteViewController.h new file mode 100755 index 0000000..00433fb --- /dev/null +++ a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSAutocompleteViewController.h @@ -0,0 +1,167 @@ +// +// GMSAutocompleteViewController.h +// Google Places SDK for iOS +// +// Copyright 2016 Google Inc. +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import + +#import "GMSAutocompleteBoundsMode.h" +#import "GMSAutocompleteFilter.h" +#import "GMSAutocompletePrediction.h" +#import "GMSPlace.h" +#import "GMSPlaceFieldMask.h" + +@class GMSAutocompleteViewController; +@class GMSCoordinateBounds; + +NS_ASSUME_NONNULL_BEGIN + +/** + * Protocol used by |GMSAutocompleteViewController|, to communicate the user's interaction + * with the controller to the application. + */ +@protocol GMSAutocompleteViewControllerDelegate + +@required + +/** + * Called when a place has been selected from the available autocomplete predictions. + * + * Implementations of this method should dismiss the view controller as the view controller will not + * dismiss itself. + * + * @param viewController The |GMSAutocompleteViewController| that generated the event. + * @param place The |GMSPlace| that was returned. + */ +- (void)viewController:(GMSAutocompleteViewController *)viewController + didAutocompleteWithPlace:(GMSPlace *)place; + +/** + * Called when a non-retryable error occurred when retrieving autocomplete predictions or place + * details. A non-retryable error is defined as one that is unlikely to be fixed by immediately + * retrying the operation. + * + * Only the following values of |GMSPlacesErrorCode| are retryable: + *
    + *
  • kGMSPlacesNetworkError + *
  • kGMSPlacesServerError + *
  • kGMSPlacesInternalError + *
+ * All other error codes are non-retryable. + * + * @param viewController The |GMSAutocompleteViewController| that generated the event. + * @param error The |NSError| that was returned. + */ +- (void)viewController:(GMSAutocompleteViewController *)viewController + didFailAutocompleteWithError:(NSError *)error; + +/** + * Called when the user taps the Cancel button in a |GMSAutocompleteViewController|. + * + * Implementations of this method should dismiss the view controller as the view controller will not + * dismiss itself. + * + * @param viewController The |GMSAutocompleteViewController| that generated the event. + */ +- (void)wasCancelled:(GMSAutocompleteViewController *)viewController; + +@optional + +/** + * Called when the user selects an autocomplete prediction from the list but before requesting + * place details. + * + * Returning NO from this method will suppress the place details fetch and didAutocompleteWithPlace + * will not be called. + * + * @param viewController The |GMSAutocompleteViewController| that generated the event. + * @param prediction The |GMSAutocompletePrediction| that was selected. + */ +- (BOOL)viewController:(GMSAutocompleteViewController *)viewController + didSelectPrediction:(GMSAutocompletePrediction *)prediction; + +/** + * Called once every time new autocomplete predictions are received. + * + * @param viewController The |GMSAutocompleteViewController| that generated the event. + */ +- (void)didUpdateAutocompletePredictions:(GMSAutocompleteViewController *)viewController; + +/** + * Called once immediately after a request for autocomplete predictions is made. + * + * @param viewController The |GMSAutocompleteViewController| that generated the event. + */ +- (void)didRequestAutocompletePredictions:(GMSAutocompleteViewController *)viewController; + +@end + +/** + * GMSAutocompleteViewController provides an interface that displays a table of autocomplete + * predictions that updates as the user enters text. Place selections made by the user are + * returned to the app via the |GMSAutocompleteViewControllerResultsDelegate| protocol. + * + * To use GMSAutocompleteViewController, set its delegate to an object in your app that + * conforms to the |GMSAutocompleteViewControllerDelegate| protocol and present the controller + * (eg using presentViewController). The |GMSAutocompleteViewControllerDelegate| delegate methods + * can be used to determine when the user has selected a place or has cancelled selection. + */ +@interface GMSAutocompleteViewController : UIViewController + +/** Delegate to be notified when a place is selected or picking is cancelled. */ +@property(nonatomic, weak, nullable) IBOutlet id delegate; + +/** + * Bounds used to bias or restrict the autocomplete results depending on the value of + * |autocompleteBoundsMode| (can be nil). + */ +@property(nonatomic, strong, nullable) GMSCoordinateBounds *autocompleteBounds; + +/** + * How to treat the |autocompleteBounds| property. Defaults to |kGMSAutocompleteBoundsModeBias|. + * + * Has no effect if |autocompleteBounds| is nil. + */ +@property(nonatomic, assign) GMSAutocompleteBoundsMode autocompleteBoundsMode; + +/** Filter to apply to autocomplete suggestions (can be nil). */ +@property(nonatomic, strong, nullable) GMSAutocompleteFilter *autocompleteFilter; + +/** The background color of table cells. */ +@property(nonatomic, strong) IBInspectable UIColor *tableCellBackgroundColor; + +/** The color of the separator line between table cells. */ +@property(nonatomic, strong) IBInspectable UIColor *tableCellSeparatorColor; + +/** The color of result name text in autocomplete results */ +@property(nonatomic, strong) IBInspectable UIColor *primaryTextColor; + +/** The color used to highlight matching text in autocomplete results */ +@property(nonatomic, strong) IBInspectable UIColor *primaryTextHighlightColor; + +/** The color of the second row of text in autocomplete results. */ +@property(nonatomic, strong) IBInspectable UIColor *secondaryTextColor; + +/** The tint color applied to controls in the Autocomplete view. */ +@property(nonatomic, strong, nullable) IBInspectable UIColor *tintColor; + +/** + * Specify individual place details to fetch for object |GMSPlace|. + * Defaults to returning all details if not overridden. + */ +@property(nonatomic, assign) GMSPlaceField placeFields; + +/** + * Sets up the autocomplete bounds using the NE and SW corner locations. + */ +- (void)setAutocompleteBoundsUsingNorthEastCorner:(CLLocationCoordinate2D)NorthEastCorner + SouthWestCorner:(CLLocationCoordinate2D)SouthWestCorner; + +@end + +NS_ASSUME_NONNULL_END diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSOpeningHours.h a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSOpeningHours.h new file mode 100755 index 0000000..5f40f2e --- /dev/null +++ a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSOpeningHours.h @@ -0,0 +1,136 @@ +// +// GMSOpeningHours.h +// Google Places SDK for iOS +// +// Copyright 2018 Google Inc. +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * \defgroup OpenNowStatus GMSOpenNowStatus + * @{ + */ + +/** + * Describes the current open status of a place. + */ +typedef NS_ENUM(NSInteger, GMSOpenNowStatus) { + /** The place is open now. */ + GMSOpenNowStatusYes, + /** The place is not open now. */ + GMSOpenNowStatusNo, + /** Whether the place is open now is unknown. */ + GMSOpenNowStatusUnknown, +}; + +/**@}*/ + +/** + * \defgroup DayOfWeek GMSDayOfWeek + * @{ + */ + +/** + * The fields represent individual days of the week. Matches NSDateComponents.weekday index. + * Refer to https://developer.apple.com/documentation/foundation/nsdatecomponents/1410442-weekday + */ +typedef NS_ENUM(NSUInteger, GMSDayOfWeek) { + GMSDayOfWeekSunday = 1, + GMSDayOfWeekMonday = 2, + GMSDayOfWeekTuesday = 3, + GMSDayOfWeekWednesday = 4, + GMSDayOfWeekThursday = 5, + GMSDayOfWeekFriday = 6, + GMSDayOfWeekSaturday = 7, +}; + +/**@}*/ + +/** + * A class representing time in hours and minutes in a 24hr clock. + */ +@interface GMSTime : NSObject + +/** + * The hour representation of time in a day. (Range is between 0-23). + */ +@property(nonatomic, readonly, assign) NSUInteger hour; + +/** + * The minute representation of time in a 1 hr period. (Range is between 0-59). + */ +@property(nonatomic, readonly, assign) NSUInteger minute; + +@end + +/** + * A class representing a open/close event in |GMSPeriod|. + */ +@interface GMSEvent : NSObject + +/** + * Day of week the associated with the event. + */ +@property(nonatomic, readonly, assign) GMSDayOfWeek day; + +/** + * The representation of time of the event in 24hr clock. + */ +@property(nonatomic, readonly, strong) GMSTime *time; + +@end + +/** + * A class representing a period of time where the place is operating for a |GMSPlace|. + * It contains an open |GMSEvent| and an optional close |GMSEvent|. The close event will be nil + * if the period is open 24hrs. + */ +@interface GMSPeriod : NSObject + +/** + * The open event of this period. + * Each |GMSPeriod| is guaranteed to have an open event. + * If the period is representing open 24hrs, it will only have the openEvent with time as "0000". + */ +@property(nonatomic, readonly, strong) GMSEvent *openEvent; + +/** + * The close event of this period. + * Can be nil if period is open 24hrs. + */ +@property(nullable, nonatomic, readonly, strong) GMSEvent *closeEvent; + +@end + +/** + * A class to handle storing and accessing opening hours information for |GMSPlace|. + */ +@interface GMSOpeningHours : NSObject + +/** + * Contains all |GMSPeriod|s of open and close events for the week. + * + * Note: Multiple periods can be associated with a day (eg. Monday 7am - Monday 2pm, + * Monday 5pm - Monday 10pm). + * + * Periods may also span multiple days (eg Friday 7pm - Saturday 2am). + */ +@property(nullable, nonatomic, readonly, strong) NSArray *periods; + +/** + * Contains localized strings of the daily opening hours for the week. + * + * Note: The order of the text depends on the language and may begin on Monday or Sunday. + * Do not use the GMSDayOfWeek enum to index into the array. + */ +@property(nullable, nonatomic, readonly, strong) NSArray *weekdayText; + +@end + +NS_ASSUME_NONNULL_END diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSPlace.h a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSPlace.h new file mode 100755 index 0000000..9d40ce2 --- /dev/null +++ a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSPlace.h @@ -0,0 +1,227 @@ +// +// GMSPlace.h +// Google Places SDK for iOS +// +// Copyright 2016 Google Inc. +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import + +@class GMSAddressComponent; +@class GMSCoordinateBounds; +@class GMSOpeningHours; +@class GMSPlacePhotoMetadata; +@class GMSPlusCode; + +NS_ASSUME_NONNULL_BEGIN + + +/** + * \defgroup PlacesOpenNowStatus GMSPlacesOpenNowStatus + * @{ + */ + +/** + * Describes the current open status of a place. + * + * (Deprecated: This enum is currently not supported and should not be used. Use GMSPlaceOpenStatus + * instead.) + */ +typedef NS_ENUM(NSInteger, GMSPlacesOpenNowStatus) { + /** The place is open now. */ + kGMSPlacesOpenNowStatusYes, + /** The place is not open now. */ + kGMSPlacesOpenNowStatusNo, + /** We don't know whether the place is open now. */ + kGMSPlacesOpenNowStatusUnknown, +}; + +/**@}*/ + +/** + * \defgroup PlacesPriceLevel GMSPlacesPriceLevel + * @{ + */ + +/** + * Describes the price level of a place. + */ +typedef NS_ENUM(NSInteger, GMSPlacesPriceLevel) { + kGMSPlacesPriceLevelUnknown = -1, + kGMSPlacesPriceLevelFree = 0, + kGMSPlacesPriceLevelCheap = 1, + kGMSPlacesPriceLevelMedium = 2, + kGMSPlacesPriceLevelHigh = 3, + kGMSPlacesPriceLevelExpensive = 4, +}; + +/**@}*/ + +/** + * \defgroup PlaceOpenStatus GMSPlaceOpenStatus + * @{ + */ + +/** + * Describes the open status of a place. + */ +typedef NS_ENUM(NSInteger, GMSPlaceOpenStatus) { + /** The place's open status is unknown. */ + GMSPlaceOpenStatusUnknown, + /** The place is open. */ + GMSPlaceOpenStatusOpen, + /** The place is not open. */ + GMSPlaceOpenStatusClosed, +}; + +/**@}*/ + +/** + * Represents a particular physical place. A GMSPlace encapsulates information about a physical + * location, including its name, location, and any other information we might have about it. This + * class is immutable. + */ +@interface GMSPlace : NSObject + +/** Name of the place. */ +@property(nonatomic, copy, readonly, nullable) NSString *name; + +/** Place ID of this place. */ +@property(nonatomic, copy, readonly, nullable) NSString *placeID; + +/** + * Location of the place. The location is not necessarily the center of the Place, or any + * particular entry or exit point, but some arbitrarily chosen point within the geographic extent of + * the Place. + */ +@property(nonatomic, readonly, assign) CLLocationCoordinate2D coordinate; + +/** + * Represents the open now status of the place at the time that the place was created. + * + * (Deprecated: This property is currently not supported and should not be used) + */ +@property(nonatomic, readonly, assign) GMSPlacesOpenNowStatus openNowStatus __deprecated_msg( + "openNowStatus property is currently not supported and should not be used)"); + +/** + * Phone number of this place, in international format, i.e. including the country code prefixed + * with "+". For example, Google Sydney's phone number is "+61 2 9374 4000". + */ +@property(nonatomic, copy, readonly, nullable) NSString *phoneNumber; + +/** + * Address of the place as a simple string. + */ +@property(nonatomic, copy, readonly, nullable) NSString *formattedAddress; + +/** + * Five-star rating for this place based on user reviews. + * + * Ratings range from 1.0 to 5.0. 0.0 means we have no rating for this place (e.g. because not + * enough users have reviewed this place). + */ +@property(nonatomic, readonly, assign) float rating; + +/** + * Price level for this place, as integers from 0 to 4. + * + * e.g. A value of 4 means this place is "$$$$" (expensive). A value of 0 means free (such as a + * museum with free admission). + */ +@property(nonatomic, readonly, assign) GMSPlacesPriceLevel priceLevel; + +/** + * The types of this place. Types are NSStrings, valid values are any types documented at + * . + */ +@property(nonatomic, copy, readonly, nullable) NSArray *types; + +/** Website for this place. */ +@property(nonatomic, copy, readonly, nullable) NSURL *website; + +/** + * The data provider attribution string for this place. + * + * These are provided as a NSAttributedString, which may contain hyperlinks to the website of each + * provider. + * + * In general, these must be shown to the user if data from this GMSPlace is shown, as described in + * the Places SDK Terms of Service. + */ +@property(nonatomic, copy, readonly, nullable) NSAttributedString *attributions; + +/** + * The recommended viewport for this place. May be nil if the size of the place is not known. + * + * This returns a viewport of a size that is suitable for displaying this place. For example, a + * |GMSPlace| object representing a store may have a relatively small viewport, while a |GMSPlace| + * object representing a country may have a very large viewport. + */ +@property(nonatomic, strong, readonly, nullable) GMSCoordinateBounds *viewport; + +/** + * An array of |GMSAddressComponent| objects representing the components in the place's address. + * These components are provided for the purpose of extracting structured information about the + * place's address: for example, finding the city that a place is in. + * + * These components should not be used for address formatting. If a formatted address is required, + * use the |formattedAddress| property, which provides a localized formatted address. + */ +@property(nonatomic, copy, readonly, nullable) NSArray *addressComponents; + +/** + * The Plus code representation of location for this place. + */ +@property(nonatomic, strong, readonly, nullable) GMSPlusCode *plusCode; + +/** + * The Opening Hours information for this place. + * Includes open status, periods and weekday text when available. + */ +@property(nonatomic, strong, readonly, nullable) GMSOpeningHours *openingHours; + +/** + * Represents how many reviews make up this place's rating. + */ +@property(nonatomic, readonly, assign) NSUInteger userRatingsTotal; + +/** + * An array of |GMSPlacePhotoMetadata| objects representing the photos of the place. + */ +@property(nonatomic, copy, readonly, nullable) NSArray *photos; + +/** + * The timezone UTC offset of the place in minutes. + */ +@property(nonatomic, readonly, nullable) NSNumber *UTCOffsetMinutes; + +/** + * Default init is not available. + */ +- (instancetype)init NS_UNAVAILABLE; + +/** + * Calculates if a place is open based on |openingHours|, |UTCOffsetMinutes|, and |date|. + * + * @param date A reference point in time used to determine if the place is open. + * @return GMSPlaceOpenStatusOpen if the place is open, GMSPlaceOpenStatusClosed if the place is + * closed, and GMSPlaceOpenStatusUnknown if the open status is unknown. + */ +- (GMSPlaceOpenStatus)isOpenAtDate:(NSDate *)date; + +/** + * Calculates if a place is open based on |openingHours|, |UTCOffsetMinutes|, and current date + * and time obtained from |[NSDate date]|. + * + * @return GMSPlaceOpenStatusOpen if the place is open, GMSPlaceOpenStatusClosed if the place is + * closed, and GMSPlaceOpenStatusUnknown if the open status is unknown. + */ +- (GMSPlaceOpenStatus)isOpen; + +@end + +NS_ASSUME_NONNULL_END diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSPlaceFieldMask.h a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSPlaceFieldMask.h new file mode 100755 index 0000000..f12c1ee --- /dev/null +++ a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSPlaceFieldMask.h @@ -0,0 +1,50 @@ +// +// GMSPlaceFieldMask.h +// Google Places SDK for iOS +// +// Copyright 2018 Google Inc. +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * \defgroup PlaceField GMSPlaceField + * @{ + */ + +/** + * The fields represent individual information that can be requested for a |GMSPlace| object. + * If no request fields are set, the |GMSPlace| object will be empty with no useful information. + * + * Note: GMSPlaceFieldPhoneNumber, GMSPlaceFieldWebsite and GMSPlaceFieldAddressComponents are not + * supported for |GMSPlaceLikelihoodList| place objects. Please refer to + * https://developers.google.com/places/ios-sdk/place-data-fields for more details. + */ +typedef NS_ENUM(NSUInteger, GMSPlaceField) { + GMSPlaceFieldName = 1 << 0, + GMSPlaceFieldPlaceID = 1 << 1, + GMSPlaceFieldPlusCode = 1 << 2, + GMSPlaceFieldCoordinate = 1 << 3, + GMSPlaceFieldOpeningHours = 1 << 4, + GMSPlaceFieldPhoneNumber = 1 << 5, + GMSPlaceFieldFormattedAddress = 1 << 6, + GMSPlaceFieldRating = 1 << 7, + GMSPlaceFieldPriceLevel = 1 << 8, + GMSPlaceFieldTypes = 1 << 9, + GMSPlaceFieldWebsite = 1 << 10, + GMSPlaceFieldViewport = 1 << 11, + GMSPlaceFieldAddressComponents = 1 << 12, + GMSPlaceFieldPhotos = 1 << 13, + GMSPlaceFieldUserRatingsTotal = 1 << 14, + GMSPlaceFieldUTCOffsetMinutes = 1 << 15, + GMSPlaceFieldAll = NSUIntegerMax, +}; + +/**@}*/ + +NS_ASSUME_NONNULL_END diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSPlaceLikelihood.h a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSPlaceLikelihood.h new file mode 100755 index 0000000..9bba03c --- /dev/null +++ a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSPlaceLikelihood.h @@ -0,0 +1,47 @@ +// +// GMSPlaceLikelihood.h +// Google Places SDK for iOS +// +// Copyright 2016 Google Inc. +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + + +#import + +@class GMSPlace; + +NS_ASSUME_NONNULL_BEGIN + +/** + * Represents a |GMSPlace| and the relative likelihood of the place being the best match within the + * list of returned places for a single request. For more information about place likelihoods, see + * |GMSPlaceLikelihoodList|. + */ +@interface GMSPlaceLikelihood : NSObject + +/** + * The place contained in this place likelihood. + */ +@property(nonatomic, strong, readonly) GMSPlace *place; + +/** + * Returns a value from 0.0 to 1.0 indicating the confidence that the user is at this place. The + * larger the value the more confident we are of the place returned. For example, a likelihood of + * 0.75 means that the user is at least 75% likely to be at this place. + */ +@property(nonatomic, assign, readonly) double likelihood; + +- (instancetype)initWithPlace:(GMSPlace *)place + likelihood:(double)likelihood NS_DESIGNATED_INITIALIZER; + +/** + * Default init is not available. Please use the designated initializer. + */ +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSPlaceLikelihoodList.h a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSPlaceLikelihoodList.h new file mode 100755 index 0000000..5130626 --- /dev/null +++ a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSPlaceLikelihoodList.h @@ -0,0 +1,42 @@ +// +// GMSPlaceLikelihoodList.h +// Google Places SDK for iOS +// +// Copyright 2016 Google Inc. +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import + +@class GMSPlaceLikelihood; + +NS_ASSUME_NONNULL_BEGIN + +/** + * Represents a list of places with an associated likelihood for the place being the correct place. + * For example, the Places service may be uncertain what the true Place is, but think it 55% likely + * to be PlaceA, and 35% likely to be PlaceB. The corresponding likelihood list has two members, one + * with likelihood 0.55 and the other with likelihood 0.35. The likelihoods are not guaranteed to be + * correct, and in a given place likelihood list they may not sum to 1.0. + */ +@interface GMSPlaceLikelihoodList : NSObject + +/** An array of likelihoods, sorted in descending order. */ +@property(nonatomic, copy) NSArray *likelihoods; + +/** + * The data provider attribution strings for the likelihood list. + * + * These are provided as a NSAttributedString, which may contain hyperlinks to the website of each + * provider. + * + * In general, these must be shown to the user if data from this likelihood list is shown, as + * described in the Places SDK Terms of Service. + */ +@property(nonatomic, copy, readonly, nullable) NSAttributedString *attributions; + +@end + +NS_ASSUME_NONNULL_END diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSPlaceLocationOptions.h a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSPlaceLocationOptions.h new file mode 100755 index 0000000..5d9c9a0 --- /dev/null +++ a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSPlaceLocationOptions.h @@ -0,0 +1,31 @@ +// +// GMSPlaceLocationOptions.h +// Google Places SDK for iOS +// +// Copyright 2019 Google Inc. +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + + +#import + +@protocol GMSPlaceLocationBias; +@protocol GMSPlaceLocationRestriction; + +NS_ASSUME_NONNULL_BEGIN + +/* + * Returns a rectangular location to filter place results inside the boundaries. + * Supports filtering as a restriction where results must be inside the bounds, or as a bias where + * results in the bounds are preferred. + * + * @param northEastBounds The north east corner of the bounds. + * @param southWestBounds The south west corner of the bounds. + */ +FOUNDATION_EXTERN id +GMSPlaceRectangularLocationOption(CLLocationCoordinate2D northEastBounds, + CLLocationCoordinate2D southWestBounds); + +NS_ASSUME_NONNULL_END diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSPlacePhotoMetadata.h a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSPlacePhotoMetadata.h new file mode 100755 index 0000000..9c93eaa --- /dev/null +++ a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSPlacePhotoMetadata.h @@ -0,0 +1,43 @@ +// +// GMSPlacePhotoMetadata.h +// Google Places SDK for iOS +// +// Copyright 2016 Google Inc. +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * The metadata corresponding to a single photo associated with a place. + */ +@interface GMSPlacePhotoMetadata : NSObject + +/** + * The data provider attribution string for this photo. + * + * These are provided as a NSAttributedString, which may contain hyperlinks to the website of each + * provider. + * + * In general, these must be shown to the user if data from this GMSPlacePhotoMetadata is shown, as + * described in the Places SDK Terms of Service. + */ +@property(nonatomic, readonly, copy, nullable) NSAttributedString *attributions; + +/** + * The maximum pixel size in which this photo is available. + */ +@property(nonatomic, readonly, assign) CGSize maxSize; + +/** + * Initializer is not available. + */ +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSPlacePhotoMetadataList.h a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSPlacePhotoMetadataList.h new file mode 100755 index 0000000..cc58d4d --- /dev/null +++ a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSPlacePhotoMetadataList.h @@ -0,0 +1,29 @@ +// +// GMSPlacePhotoMetadataList.h +// Google Places SDK for iOS +// +// Copyright 2016 Google Inc. +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import + +#import "GMSPlacePhotoMetadata.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * A list of |GMSPlacePhotoMetadata| objects. + */ +@interface GMSPlacePhotoMetadataList : NSObject + +/** + * The array of |GMSPlacePhotoMetadata| objects. + */ +@property(nonatomic, readonly, copy) NSArray *results; + +@end + +NS_ASSUME_NONNULL_END diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSPlaceTypes.h a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSPlaceTypes.h new file mode 100755 index 0000000..461e482 --- /dev/null +++ a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSPlaceTypes.h @@ -0,0 +1,164 @@ +// +// GMSPlaceTypes.h +// Google Places SDK for iOS +// +// Copyright 2016 Google Inc. +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import + +/** + * \defgroup PlaceTypes GMSPlaceType + * @{ + */ + +/** + * The constants for the Table 1 place types for the API. Complete list can be found at + * https://developers.google.com/places/documentation/supported_types + */ +extern NSString *const kGMSPlaceTypeAccounting; +extern NSString *const kGMSPlaceTypeAirport; +extern NSString *const kGMSPlaceTypeAmusementPark; +extern NSString *const kGMSPlaceTypeAquarium; +extern NSString *const kGMSPlaceTypeArtGallery; +extern NSString *const kGMSPlaceTypeAtm; +extern NSString *const kGMSPlaceTypeBakery; +extern NSString *const kGMSPlaceTypeBank; +extern NSString *const kGMSPlaceTypeBar; +extern NSString *const kGMSPlaceTypeBeautySalon; +extern NSString *const kGMSPlaceTypeBicycleStore; +extern NSString *const kGMSPlaceTypeBookStore; +extern NSString *const kGMSPlaceTypeBowlingAlley; +extern NSString *const kGMSPlaceTypeBusStation; +extern NSString *const kGMSPlaceTypeCafe; +extern NSString *const kGMSPlaceTypeCampground; +extern NSString *const kGMSPlaceTypeCarDealer; +extern NSString *const kGMSPlaceTypeCarRental; +extern NSString *const kGMSPlaceTypeCarRepair; +extern NSString *const kGMSPlaceTypeCarWash; +extern NSString *const kGMSPlaceTypeCasino; +extern NSString *const kGMSPlaceTypeCemetery; +extern NSString *const kGMSPlaceTypeChurch; +extern NSString *const kGMSPlaceTypeCityHall; +extern NSString *const kGMSPlaceTypeClothingStore; +extern NSString *const kGMSPlaceTypeConvenienceStore; +extern NSString *const kGMSPlaceTypeCourthouse; +extern NSString *const kGMSPlaceTypeDentist; +extern NSString *const kGMSPlaceTypeDepartmentStore; +extern NSString *const kGMSPlaceTypeDoctor; +extern NSString *const kGMSPlaceTypeDrugstore; +extern NSString *const kGMSPlaceTypeElectrician; +extern NSString *const kGMSPlaceTypeElectronicsStore; +extern NSString *const kGMSPlaceTypeEmbassy; +extern NSString *const kGMSPlaceTypeFireStation; +extern NSString *const kGMSPlaceTypeFlorist; +extern NSString *const kGMSPlaceTypeFuneralHome; +extern NSString *const kGMSPlaceTypeFurnitureStore; +extern NSString *const kGMSPlaceTypeGasStation; +extern NSString *const kGMSPlaceTypeGroceryOrSupermarket; +extern NSString *const kGMSPlaceTypeGym; +extern NSString *const kGMSPlaceTypeHairCare; +extern NSString *const kGMSPlaceTypeHardwareStore; +extern NSString *const kGMSPlaceTypeHinduTemple; +extern NSString *const kGMSPlaceTypeHomeGoodsStore; +extern NSString *const kGMSPlaceTypeHospital; +extern NSString *const kGMSPlaceTypeInsuranceAgency; +extern NSString *const kGMSPlaceTypeJewelryStore; +extern NSString *const kGMSPlaceTypeLaundry; +extern NSString *const kGMSPlaceTypeLawyer; +extern NSString *const kGMSPlaceTypeLibrary; +extern NSString *const kGMSPlaceTypeLightRailStation; +extern NSString *const kGMSPlaceTypeLiquorStore; +extern NSString *const kGMSPlaceTypeLocalGovernmentOffice; +extern NSString *const kGMSPlaceTypeLocksmith; +extern NSString *const kGMSPlaceTypeLodging; +extern NSString *const kGMSPlaceTypeMealDelivery; +extern NSString *const kGMSPlaceTypeMealTakeaway; +extern NSString *const kGMSPlaceTypeMosque; +extern NSString *const kGMSPlaceTypeMovieRental; +extern NSString *const kGMSPlaceTypeMovieTheater; +extern NSString *const kGMSPlaceTypeMovingCompany; +extern NSString *const kGMSPlaceTypeMuseum; +extern NSString *const kGMSPlaceTypeNightClub; +extern NSString *const kGMSPlaceTypePainter; +extern NSString *const kGMSPlaceTypePark; +extern NSString *const kGMSPlaceTypeParking; +extern NSString *const kGMSPlaceTypePetStore; +extern NSString *const kGMSPlaceTypePharmacy; +extern NSString *const kGMSPlaceTypePhysiotherapist; +extern NSString *const kGMSPlaceTypePlumber; +extern NSString *const kGMSPlaceTypePrimarySchool; +extern NSString *const kGMSPlaceTypePolice; +extern NSString *const kGMSPlaceTypePostOffice; +extern NSString *const kGMSPlaceTypeRealEstateAgency; +extern NSString *const kGMSPlaceTypeRestaurant; +extern NSString *const kGMSPlaceTypeRoofingContractor; +extern NSString *const kGMSPlaceTypeRvPark; +extern NSString *const kGMSPlaceTypeSchool; +extern NSString *const kGMSPlaceTypeSecondarySchool; +extern NSString *const kGMSPlaceTypeShoeStore; +extern NSString *const kGMSPlaceTypeShoppingMall; +extern NSString *const kGMSPlaceTypeSpa; +extern NSString *const kGMSPlaceTypeStadium; +extern NSString *const kGMSPlaceTypeStorage; +extern NSString *const kGMSPlaceTypeStore; +extern NSString *const kGMSPlaceTypeSubwayStation; +extern NSString *const kGMSPlaceTypeSynagogue; +extern NSString *const kGMSPlaceTypeTaxiStand; +extern NSString *const kGMSPlaceTypeTrainStation; +extern NSString *const kGMSPlaceTypeTravelAgency; +extern NSString *const kGMSPlaceTypeTransitStation; +extern NSString *const kGMSPlaceTypeTouristAttraction; +extern NSString *const kGMSPlaceTypeUniversity; +extern NSString *const kGMSPlaceTypeVeterinaryCare; +extern NSString *const kGMSPlaceTypeZoo; + +/** + * The constants for Table 2 additional types returned by the Places service. Complete list can be + * found at https://developers.google.com/places/documentation/supported_types. + * Note: The types below are not supported in the type filter of a place search. + */ +extern NSString *const kGMSPlaceTypeAdministrativeAreaLevel1; +extern NSString *const kGMSPlaceTypeAdministrativeAreaLevel2; +extern NSString *const kGMSPlaceTypeAdministrativeAreaLevel3; +extern NSString *const kGMSPlaceTypeAdministrativeAreaLevel4; +extern NSString *const kGMSPlaceTypeAdministrativeAreaLevel5; +extern NSString *const kGMSPlaceTypeColloquialArea; +extern NSString *const kGMSPlaceTypeCountry; +extern NSString *const kGMSPlaceTypeEstablishment; +extern NSString *const kGMSPlaceTypeFinance; +extern NSString *const kGMSPlaceTypeFloor; +extern NSString *const kGMSPlaceTypeFood; +extern NSString *const kGMSPlaceTypeGeneralContractor; +extern NSString *const kGMSPlaceTypeGeocode; +extern NSString *const kGMSPlaceTypeHealth; +extern NSString *const kGMSPlaceTypeIntersection; +extern NSString *const kGMSPlaceTypeLocality; +extern NSString *const kGMSPlaceTypeNaturalFeature; +extern NSString *const kGMSPlaceTypeNeighborhood; +extern NSString *const kGMSPlaceTypePlaceOfWorship; +extern NSString *const kGMSPlaceTypePointOfInterest; +extern NSString *const kGMSPlaceTypePolitical; +extern NSString *const kGMSPlaceTypePostBox; +extern NSString *const kGMSPlaceTypePostalCode; +extern NSString *const kGMSPlaceTypePostalCodePrefix; +extern NSString *const kGMSPlaceTypePostalCodeSuffix; +extern NSString *const kGMSPlaceTypePostalTown; +extern NSString *const kGMSPlaceTypePremise; +extern NSString *const kGMSPlaceTypeRoom; +extern NSString *const kGMSPlaceTypeRoute; +extern NSString *const kGMSPlaceTypeStreetAddress; +extern NSString *const kGMSPlaceTypeStreetNumber; +extern NSString *const kGMSPlaceTypeSublocality; +extern NSString *const kGMSPlaceTypeSublocalityLevel1; +extern NSString *const kGMSPlaceTypeSublocalityLevel2; +extern NSString *const kGMSPlaceTypeSublocalityLevel3; +extern NSString *const kGMSPlaceTypeSublocalityLevel4; +extern NSString *const kGMSPlaceTypeSublocalityLevel5; +extern NSString *const kGMSPlaceTypeSubpremise; +extern NSString *const kGMSPlaceTypeTownSquare; + +/**@}*/ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSPlacesClient.h a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSPlacesClient.h new file mode 100755 index 0000000..378c249 --- /dev/null +++ a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSPlacesClient.h @@ -0,0 +1,327 @@ +// +// GMSPlacesClient.h +// Google Places SDK for iOS +// +// Copyright 2016 Google Inc. +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import +#import + +#import "GMSAutocompleteBoundsMode.h" +#import "GMSPlace.h" +#import "GMSPlaceFieldMask.h" +#import "GMSPlacesErrors.h" + +@class GMSAutocompleteFilter; +@class GMSAutocompletePrediction; +@class GMSAutocompleteSessionToken; +@class GMSPlaceLikelihood; +@class GMSPlaceLikelihoodList; +@class GMSPlacePhotoMetadata; +@class GMSPlacePhotoMetadataList; + + +NS_ASSUME_NONNULL_BEGIN + +/** + * Callback type for receiving place details lookups. If an error occurred, + * |result| will be nil and |error| will contain information about the error. + * @param result The |GMSPlace| that was returned. + * @param error The error that occurred, if any. + * + * @related GMSPlacesClient + */ +typedef void (^GMSPlaceResultCallback)(GMSPlace *_Nullable result, NSError *_Nullable error); + +/** + * Callback type for receiving place likelihood lists. If an error occurred, |likelihoodList| will + * be nil and |error| will contain information about the error. + * @param likelihoodList The list of place likelihoods. + * @param error The error that occurred, if any. + * + * @related GMSPlacesClient + */ +typedef void (^GMSPlaceLikelihoodListCallback)(GMSPlaceLikelihoodList *_Nullable likelihoodList, + NSError *_Nullable error); + +/** + * Callback type for receiving array of |GMSPlaceLikelihood|s. If an error occurred, the array will + * be nil and |error| will contain information about the error. + * + * @related GMSPlacesClient + */ +typedef void (^GMSPlaceLikelihoodsCallback)(NSArray *_Nullable likelihoods, + NSError *_Nullable error); + +/** + * Callback type for receiving autocompletion results. |results| is an array of + * GMSAutocompletePredictions representing candidate completions of the query. + * @param results An array of |GMSAutocompletePrediction|s. + * @param error The error that occurred, if any. + * + * @related GMSPlacesClient + */ +typedef void (^GMSAutocompletePredictionsCallback)( + NSArray *_Nullable results, NSError *_Nullable error); + +/** + * Callback type for receiving place photos results. If an error occurred, |photos| will be nil and + * |error| will contain information about the error. + * @param photos The result containing |GMSPlacePhotoMetadata| objects. + * @param error The error that occurred, if any. + * + * @related GMSPlacesClient + */ +typedef void (^GMSPlacePhotoMetadataResultCallback)(GMSPlacePhotoMetadataList *_Nullable photos, + NSError *_Nullable error); + +/** + * Callback type for receiving |UIImage| objects from a |GMSPlacePhotoMetadata| object. If an error + * occurred, |photo| will be nil and |error| will contain information about the error. + * @param photo The |UIImage| which was loaded. + * @param error The error that occurred, if any. + * + * @related GMSPlacesClient + */ +typedef void (^GMSPlacePhotoImageResultCallback)(UIImage *_Nullable photo, + NSError *_Nullable error); + +/** + * Main interface to the Places SDK. Used for searching and getting details about places. This class + * should be accessed through the [GMSPlacesClient sharedClient] method. + * + * GMSPlacesClient methods should only be called from the main thread. Calling these methods from + * another thread will result in an exception or undefined behavior. Unless otherwise specified, all + * callbacks will be invoked on the main thread. + */ +@interface GMSPlacesClient : NSObject + +/** + * Provides the shared instance of GMSPlacesClient for the Google Places SDK for iOS, creating it if + * necessary. + * + * If your application often uses methods of GMSPlacesClient it may want to hold onto this object + * directly, as otherwise your connection to Google may be restarted on a regular basis. + */ ++ (instancetype)sharedClient; + +/** + * Provides your API key to the Google Places SDK for iOS. This key is generated for your + * application via the Google APIs Console, and is paired with your application's bundle ID to + * identify it. This should be called by your application before using GMSPlacesClient. + * (e.g., in application:didFinishLaunchingWithOptions:). + * + * @return YES if the APIKey was successfully provided. + */ ++ (BOOL)provideAPIKey:(NSString *)key; + +/** + * Returns the open source software license information for the Google Places SDK for iOS. This + * information must be made available within your application. + */ ++ (NSString *)openSourceLicenseInfo; + +/** + * Returns the version for this release of the Google Places SDK for iOS.. For example, "1.0.0". + */ ++ (NSString *)SDKVersion; + +/** + * Returns the long version for this release of the Google Places SDK for iOS.. For example, "1.0.0 + * (102.1)". + */ ++ (NSString *)SDKLongVersion; + +/** + * Get details for a place. This method is non-blocking. + * @param placeID The place ID to lookup. + * @param callback The callback to invoke with the lookup result. + */ +- (void)lookUpPlaceID:(NSString *)placeID callback:(GMSPlaceResultCallback)callback; + +/** + * Gets the metadata for up to 10 photos associated with a place. + * + * Photos are sourced from a variety of locations, including business owners and photos contributed + * by Google+ users. In most cases, these photos can be used without attribution, or will have the + * required attribution included as a part of the image. However, you must use the |attributions| + * property in the response to retrieve any additional attributions required, and display those + * attributions in your application wherever you display the image. A maximum of 10 photos are + * returned. + * + * Multiple calls of this method will probably return the same photos each time. However, this is + * not guaranteed because the underlying data may have changed. + * + * This method performs a network lookup. + * + * @param placeID The place ID for which to lookup photos. + * @param callback The callback to invoke with the lookup result. + */ +- (void)lookUpPhotosForPlaceID:(NSString *)placeID + callback:(GMSPlacePhotoMetadataResultCallback)callback; + +/** + * Loads the image for a specific photo at its maximum size. + * + * Image data may be cached by the SDK. If the requested photo does not exist in the cache then a + * network lookup will be performed. + * + * @param photoMetadata The |GMSPlacePhotoMetadata| for which to load a |UIImage|. + * @param callback The callback to invoke with the loaded |UIImage|. + */ +- (void)loadPlacePhoto:(GMSPlacePhotoMetadata *)photoMetadata + callback:(GMSPlacePhotoImageResultCallback)callback; + +/** + * Loads the image for a specific photo, scaled to fit the given maximum dimensions. + * + * The image will be scaled to fit within the given dimensions while maintaining the aspect ratio of + * the original image. This scaling is performed server-side. + * + * If the scale parameter is not 1.0 maxSize will be multiplied by this value and the returned + * |UIImage| will be set to have the specified scale. This parameter should be set to the screen + * scale if you are loading images for display on screen. + * + * Image data may be cached by the SDK. If the requested photo does not exist in the cache then a + * network lookup will be performed. + * + * NOTE: After applying the scale factor the dimensions in maxSize will be rounded up to the nearest + * integer before use. If an image is requested which is larger than the maximum size available a + * smaller image may be returned. + * + * @param photoMetadata The |GMSPlacePhotoMetadata| for which to load a |UIImage|. + * @param maxSize The maximum size of the image. + * @param scale The scale to load the image at. + * @param callback The callback to invoke with the loaded |UIImage|. + */ +- (void)loadPlacePhoto:(GMSPlacePhotoMetadata *)photoMetadata + constrainedToSize:(CGSize)maxSize + scale:(CGFloat)scale + callback:(GMSPlacePhotoImageResultCallback)callback; + +/** + * Returns an estimate of the place where the device is currently known to be located. + * + * Generates a place likelihood list based on the device's last estimated location. The supplied + * callback will be invoked with this likelihood list upon success and an NSError upon an error. + * + * NOTE: This method requires that your app has permission to access the current device location. + * Before calling this make sure to request access to the users location using [CLLocationManager + * requestWhenInUseAuthorization] or [CLLocationManager requestAlwaysAuthorization]. If you do call + * this method and your app does not have the correct authorization status, the callback will be + * called with an error. + * + * @param callback The callback to invoke with the place likelihood list. + */ +- (void)currentPlaceWithCallback:(GMSPlaceLikelihoodListCallback)callback; + +/** + * Autocompletes a given text query. Results may optionally be biased towards a certain location. + * + * The supplied callback will be invoked with an array of autocompletion predictions upon success + * and an NSError upon an error. + * + * @param query The partial text to autocomplete. + * @param bounds The bounds used to bias the results. This is not a hard restrict - places may still + * be returned outside of these bounds. This parameter may be nil. + * @param filter The filter to apply to the results. This parameter may be nil. + * @param callback The callback to invoke with the predictions. + */ +- (void)autocompleteQuery:(NSString *)query + bounds:(nullable GMSCoordinateBounds *)bounds + filter:(nullable GMSAutocompleteFilter *)filter + callback:(GMSAutocompletePredictionsCallback)callback; + +/** + * Autocompletes a given text query. Results may optionally be biased towards a certain location, + * or restricted to an area. + * + * The supplied callback will be invoked with an array of autocompletion predictions upon success + * and an NSError upon an error. + * + * @param query The partial text to autocomplete. + * @param bounds The bounds used to bias or restrict the results. Whether this biases or restricts + * is determined by the value of the |boundsMode| parameter. This parameter may be + * nil. + * @param boundsMode How to treat the |bounds| parameter. Has no effect if |bounds| is nil. + * @param filter The filter to apply to the results. This parameter may be nil. + * @param callback The callback to invoke with the predictions. + */ +- (void)autocompleteQuery:(NSString *)query + bounds:(nullable GMSCoordinateBounds *)bounds + boundsMode:(GMSAutocompleteBoundsMode)boundsMode + filter:(nullable GMSAutocompleteFilter *)filter + callback:(GMSAutocompletePredictionsCallback)callback; + +/** + * Find Autocomplete predictions from text query. Results may optionally be biased towards a + * certain location or restricted to an area. This method is non-blocking. + * + * The supplied callback will be invoked with an array of autocompletion predictions upon success + * and an NSError upon an error. + * + * @param query The partial text to autocomplete. + * @param bounds The bounds used to bias or restrict the results. Whether this biases or restricts + * is determined by the value of the |boundsMode| parameter. This parameter may be + * nil. + * @param boundsMode How to treat the |bounds| parameter. Has no effect if |bounds| is nil. + * @param filter The filter to apply to the results. This parameter may be nil. + * @param sessionToken The |GMSAutocompleteSessionToken| to associate request to a billing session. + * @param callback The callback to invoke with the predictions. + */ +- (void)findAutocompletePredictionsFromQuery:(NSString *)query + bounds:(nullable GMSCoordinateBounds *)bounds + boundsMode:(GMSAutocompleteBoundsMode)boundsMode + filter:(nullable GMSAutocompleteFilter *)filter + sessionToken:(nullable GMSAutocompleteSessionToken *)sessionToken + callback:(GMSAutocompletePredictionsCallback)callback; + +/** + * Find Autocomplete predictions from text query. Results may optionally be biased towards a + * certain location or restricted to an area. This method is non-blocking. + * + * The supplied callback will be invoked with an array of autocompletion predictions upon success + * and an NSError upon an error. + * + * @param query The partial text to autocomplete. + * @param filter The filter to apply to the results. This parameter may be nil. + * @param sessionToken The |GMSAutocompleteSessionToken| to associate request to a billing session. + * @param callback The callback to invoke with the predictions. + */ +- (void)findAutocompletePredictionsFromQuery:(NSString *)query + filter:(nullable GMSAutocompleteFilter *)filter + sessionToken:(nullable GMSAutocompleteSessionToken *)sessionToken + callback:(GMSAutocompletePredictionsCallback)callback; + +/** + * Fetch details for a place. This method is non-blocking. + * @param placeID The place ID to lookup. + * @param placeFields The individual place fields requested for the place objects in the list. + * @param sessionToken The |GMSAutocompleteSessionToken| to associate request to a billing session. + * @param callback The callback to invoke with the lookup result. + */ +- (void)fetchPlaceFromPlaceID:(NSString *)placeID + placeFields:(GMSPlaceField)placeFields + sessionToken:(nullable GMSAutocompleteSessionToken *)sessionToken + callback:(GMSPlaceResultCallback)callback; + +/** + * Find place likelihoods using the user's current location. This method is non-blocking. + * + * The supplied callback will be invoked with an array of places with likelihood scores upon success + * and an NSError upon an error. + * + * @param placeFields The individual place fields requested for the place objects in the list. + * @param callback The callback to invoke with place likelihoods. + */ +- (void)findPlaceLikelihoodsFromCurrentLocationWithPlaceFields:(GMSPlaceField)placeFields + callback: + (GMSPlaceLikelihoodsCallback)callback; + +@end + +NS_ASSUME_NONNULL_END diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSPlacesErrors.h a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSPlacesErrors.h new file mode 100755 index 0000000..5b0c17c --- /dev/null +++ a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSPlacesErrors.h @@ -0,0 +1,119 @@ +// +// GMSPlacesErrors.h +// Google Places SDK for iOS +// +// Copyright 2016 Google Inc. +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +/** + * \defgroup PlacesErrors GMSPlacesErrors + * @{ + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * Error domain used for Places SDK errors. + */ +extern NSString *const kGMSPlacesErrorDomain; + +/** + * Error codes for |kGMSPlacesErrorDomain|. + */ +typedef NS_ENUM(NSInteger, GMSPlacesErrorCode) { + /** + * Something went wrong with the connection to the Places API server. + */ + kGMSPlacesNetworkError = -1, + /** + * The Places API server returned a response that we couldn't understand. + *

+ * If you believe this error represents a bug, please file a report using the instructions on our + * community and support page. + */ + kGMSPlacesServerError = -2, + /** + * An internal error occurred in the Places SDK library. + *

+ * If you believe this error represents a bug, please file a report using the instructions on our + * community and support page. + */ + kGMSPlacesInternalError = -3, + /** + * Operation failed due to an invalid (malformed or missing) API key. + *

+ * See the developer's guide + * for information on creating and using an API key. + */ + kGMSPlacesKeyInvalid = -4, + /** + * Operation failed due to an expired API key. + *

+ * See the developer's guide + * for information on creating and using an API key. + */ + kGMSPlacesKeyExpired = -5, + /** + * Operation failed due to exceeding the quota usage limit. + *

+ * See the usage limits guide + * for information on usage limits and how to request a higher limit. + */ + kGMSPlacesUsageLimitExceeded = -6, + /** + * Operation failed due to exceeding the usage rate limit for the API key. + *

+ * This status code shouldn't be returned during normal usage of the API. It relates to usage of + * the API that far exceeds normal request levels. See the usage limits guide for more + * information. + */ + kGMSPlacesRateLimitExceeded = -7, + /** + * Operation failed due to exceeding the per-device usage rate limit. + *

+ * This status code shouldn't be returned during normal usage of the API. It relates to usage of + * the API that far exceeds normal request levels. See the usage limits guide for more + * information. + */ + kGMSPlacesDeviceRateLimitExceeded = -8, + /** + * The Places API service for iOS is not enabled. + *

+ * See the developer's guide + * to learn how to set up the Places SDK for iOS or the + * migration guide + * if you are migrating from an earlier version. + */ + kGMSPlacesAccessNotConfigured = -9, + /** + * The application's bundle identifier does not match one of the allowed iOS applications for the + * API key. + *

+ * See the developer's guide + * for how to configure bundle restrictions on API keys. + */ + kGMSPlacesIncorrectBundleIdentifier = -10, + /** + * The Places SDK could not find the user's location. This may be because the user has not allowed + * the application to access location information. + */ + kGMSPlacesLocationError = -11, + /** + * The Places SDK could not process the invalid request. + *

+ * If you believe this error represents a bug, please file a report using the instructions on our + * community and support page. + */ + kGMSPlacesInvalidRequest = -12 +}; + +NS_ASSUME_NONNULL_END + +/**@}*/ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSPlusCode.h a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSPlusCode.h new file mode 100755 index 0000000..76e80fa --- /dev/null +++ a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GMSPlusCode.h @@ -0,0 +1,29 @@ +// +// GMSPlusCode.h +// Google Places SDK for iOS +// +// Copyright 2018 Google Inc. +// +// Usage of this SDK is subject to the Google Maps/Google Earth APIs Terms of +// Service: https://developers.google.com/maps/terms +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * A class containing the Plus codes representation for a location. + * See https://plus.codes/ for more details. + */ +@interface GMSPlusCode : NSObject + +/** Geo plus code, e.g. "8FVC9G8F+5W" */ +@property(nonatomic, readonly, copy) NSString *globalCode; + +/** Compound plus code, e.g. "9G8F+5W Zurich, Switzerland" */ +@property(nullable, nonatomic, readonly, copy) NSString *compoundCode; + +@end + +NS_ASSUME_NONNULL_END diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GooglePlaces.h a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GooglePlaces.h new file mode 100755 index 0000000..c635b44 --- /dev/null +++ a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Headers/GooglePlaces.h @@ -0,0 +1,22 @@ +#import "GMSAutocompleteBoundsMode.h" +#import "GMSAutocompleteFetcher.h" +#import "GMSAutocompleteFilter.h" +#import "GMSAutocompleteMatchFragment.h" +#import "GMSAutocompletePrediction.h" +#import "GMSAutocompleteResultsViewController.h" +#import "GMSAutocompleteSessionToken.h" +#import "GMSAutocompleteTableDataSource.h" +#import "GMSAutocompleteViewController.h" +#import "GMSAddressComponent.h" +#import "GMSOpeningHours.h" +#import "GMSPlace.h" +#import "GMSPlaceFieldMask.h" +#import "GMSPlaceLikelihood.h" +#import "GMSPlaceLikelihoodList.h" +#import "GMSPlaceLocationOptions.h" +#import "GMSPlacePhotoMetadata.h" +#import "GMSPlacePhotoMetadataList.h" +#import "GMSPlaceTypes.h" +#import "GMSPlacesClient.h" +#import "GMSPlacesErrors.h" +#import "GMSPlusCode.h" diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Modules/module.modulemap a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Modules/module.modulemap new file mode 100755 index 0000000..2e69b08 --- /dev/null +++ a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Modules/module.modulemap @@ -0,0 +1,17 @@ +framework module GooglePlaces { + umbrella header "GooglePlaces.h" + export * + module * { export * } + link "c++" + link "z" + link framework "CoreData" + link framework "CoreFoundation" + link framework "CoreGraphics" + link framework "CoreLocation" + link framework "CoreText" + link framework "Foundation" + link framework "QuartzCore" + link framework "Security" + link framework "SystemConfiguration" + link framework "UIKit" +} diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/Info.plist a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/Info.plist new file mode 100755 index 0000000..775e226 Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/Info.plist differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/ar.lproj/GooglePlaces.strings a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/ar.lproj/GooglePlaces.strings new file mode 100755 index 0000000..84e922e Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/ar.lproj/GooglePlaces.strings differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/build-with-google-black.png a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/build-with-google-black.png new file mode 100755 index 0000000..0cc45fc Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/build-with-google-black.png differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/build-with-google-black@2x.png a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/build-with-google-black@2x.png new file mode 100755 index 0000000..71f1c8e Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/build-with-google-black@2x.png differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/build-with-google-black@3x.png a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/build-with-google-black@3x.png new file mode 100755 index 0000000..6b8b9a6 Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/build-with-google-black@3x.png differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/build-with-google-white.png a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/build-with-google-white.png new file mode 100755 index 0000000..7e67463 Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/build-with-google-white.png differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/build-with-google-white@2x.png a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/build-with-google-white@2x.png new file mode 100755 index 0000000..50c3ace Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/build-with-google-white@2x.png differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/build-with-google-white@3x.png a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/build-with-google-white@3x.png new file mode 100755 index 0000000..bcafb09 Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/build-with-google-white@3x.png differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/ca.lproj/GooglePlaces.strings a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/ca.lproj/GooglePlaces.strings new file mode 100755 index 0000000..d8eaa9d Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/ca.lproj/GooglePlaces.strings differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/cs.lproj/GooglePlaces.strings a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/cs.lproj/GooglePlaces.strings new file mode 100755 index 0000000..3640071 Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/cs.lproj/GooglePlaces.strings differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/da.lproj/GooglePlaces.strings a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/da.lproj/GooglePlaces.strings new file mode 100755 index 0000000..9599d8e Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/da.lproj/GooglePlaces.strings differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/de.lproj/GooglePlaces.strings a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/de.lproj/GooglePlaces.strings new file mode 100755 index 0000000..d3bfc38 Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/de.lproj/GooglePlaces.strings differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/el.lproj/GooglePlaces.strings a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/el.lproj/GooglePlaces.strings new file mode 100755 index 0000000..074c670 Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/el.lproj/GooglePlaces.strings differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/en.lproj/GooglePlaces.strings a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/en.lproj/GooglePlaces.strings new file mode 100755 index 0000000..88b3692 Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/en.lproj/GooglePlaces.strings differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/en_AU.lproj/GooglePlaces.strings a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/en_AU.lproj/GooglePlaces.strings new file mode 100755 index 0000000..7709a87 Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/en_AU.lproj/GooglePlaces.strings differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/en_GB.lproj/GooglePlaces.strings a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/en_GB.lproj/GooglePlaces.strings new file mode 100755 index 0000000..7709a87 Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/en_GB.lproj/GooglePlaces.strings differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/en_IN.lproj/GooglePlaces.strings a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/en_IN.lproj/GooglePlaces.strings new file mode 100755 index 0000000..f7555c9 Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/en_IN.lproj/GooglePlaces.strings differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/es.lproj/GooglePlaces.strings a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/es.lproj/GooglePlaces.strings new file mode 100755 index 0000000..34cc70c Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/es.lproj/GooglePlaces.strings differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/es_419.lproj/GooglePlaces.strings a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/es_419.lproj/GooglePlaces.strings new file mode 100755 index 0000000..57d504e Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/es_419.lproj/GooglePlaces.strings differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/es_MX.lproj/GooglePlaces.strings a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/es_MX.lproj/GooglePlaces.strings new file mode 100755 index 0000000..57d504e Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/es_MX.lproj/GooglePlaces.strings differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/fi.lproj/GooglePlaces.strings a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/fi.lproj/GooglePlaces.strings new file mode 100755 index 0000000..095564f Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/fi.lproj/GooglePlaces.strings differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/fr.lproj/GooglePlaces.strings a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/fr.lproj/GooglePlaces.strings new file mode 100755 index 0000000..af51206 Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/fr.lproj/GooglePlaces.strings differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/fr_CA.lproj/GooglePlaces.strings a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/fr_CA.lproj/GooglePlaces.strings new file mode 100755 index 0000000..5c56634 Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/fr_CA.lproj/GooglePlaces.strings differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/he.lproj/GooglePlaces.strings a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/he.lproj/GooglePlaces.strings new file mode 100755 index 0000000..c716cc6 Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/he.lproj/GooglePlaces.strings differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/hi.lproj/GooglePlaces.strings a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/hi.lproj/GooglePlaces.strings new file mode 100755 index 0000000..47d9d90 Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/hi.lproj/GooglePlaces.strings differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/hr.lproj/GooglePlaces.strings a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/hr.lproj/GooglePlaces.strings new file mode 100755 index 0000000..469f18d Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/hr.lproj/GooglePlaces.strings differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/hu.lproj/GooglePlaces.strings a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/hu.lproj/GooglePlaces.strings new file mode 100755 index 0000000..2ca04ae Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/hu.lproj/GooglePlaces.strings differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/id.lproj/GooglePlaces.strings a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/id.lproj/GooglePlaces.strings new file mode 100755 index 0000000..601429a Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/id.lproj/GooglePlaces.strings differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/it.lproj/GooglePlaces.strings a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/it.lproj/GooglePlaces.strings new file mode 100755 index 0000000..fb73275 Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/it.lproj/GooglePlaces.strings differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/ja.lproj/GooglePlaces.strings a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/ja.lproj/GooglePlaces.strings new file mode 100755 index 0000000..7271741 Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/ja.lproj/GooglePlaces.strings differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/ko.lproj/GooglePlaces.strings a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/ko.lproj/GooglePlaces.strings new file mode 100755 index 0000000..b3b0236 Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/ko.lproj/GooglePlaces.strings differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/lt.lproj/GooglePlaces.strings a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/lt.lproj/GooglePlaces.strings new file mode 100755 index 0000000..717f6e4 Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/lt.lproj/GooglePlaces.strings differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/lv.lproj/GooglePlaces.strings a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/lv.lproj/GooglePlaces.strings new file mode 100755 index 0000000..7340d36 Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/lv.lproj/GooglePlaces.strings differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/ms.lproj/GooglePlaces.strings a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/ms.lproj/GooglePlaces.strings new file mode 100755 index 0000000..b6061bf Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/ms.lproj/GooglePlaces.strings differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/nb.lproj/GooglePlaces.strings a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/nb.lproj/GooglePlaces.strings new file mode 100755 index 0000000..e16393e Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/nb.lproj/GooglePlaces.strings differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/nl.lproj/GooglePlaces.strings a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/nl.lproj/GooglePlaces.strings new file mode 100755 index 0000000..da8766b Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/nl.lproj/GooglePlaces.strings differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/oss_licenses_places.txt.gz a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/oss_licenses_places.txt.gz new file mode 100755 index 0000000..80ed2c3 Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/oss_licenses_places.txt.gz differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/pl.lproj/GooglePlaces.strings a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/pl.lproj/GooglePlaces.strings new file mode 100755 index 0000000..2f515a9 Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/pl.lproj/GooglePlaces.strings differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/pt.lproj/GooglePlaces.strings a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/pt.lproj/GooglePlaces.strings new file mode 100755 index 0000000..709e17f Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/pt.lproj/GooglePlaces.strings differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/pt_BR.lproj/GooglePlaces.strings a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/pt_BR.lproj/GooglePlaces.strings new file mode 100755 index 0000000..709e17f Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/pt_BR.lproj/GooglePlaces.strings differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/pt_PT.lproj/GooglePlaces.strings a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/pt_PT.lproj/GooglePlaces.strings new file mode 100755 index 0000000..9f243f1 Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/pt_PT.lproj/GooglePlaces.strings differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/ro.lproj/GooglePlaces.strings a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/ro.lproj/GooglePlaces.strings new file mode 100755 index 0000000..9e27d58 Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/ro.lproj/GooglePlaces.strings differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/ru.lproj/GooglePlaces.strings a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/ru.lproj/GooglePlaces.strings new file mode 100755 index 0000000..3166e64 Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/ru.lproj/GooglePlaces.strings differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/sad_cloud.png a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/sad_cloud.png new file mode 100755 index 0000000..1c30882 Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/sad_cloud.png differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/sad_cloud@2x.png a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/sad_cloud@2x.png new file mode 100755 index 0000000..e199181 Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/sad_cloud@2x.png differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/sad_cloud@3x.png a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/sad_cloud@3x.png new file mode 100755 index 0000000..ce3e035 Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/sad_cloud@3x.png differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/sad_cloud_dark.png a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/sad_cloud_dark.png new file mode 100755 index 0000000..a16f5a2 Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/sad_cloud_dark.png differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/sad_cloud_dark@2x.png a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/sad_cloud_dark@2x.png new file mode 100755 index 0000000..e61cabd Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/sad_cloud_dark@2x.png differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/sad_cloud_dark@3x.png a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/sad_cloud_dark@3x.png new file mode 100755 index 0000000..90d2380 Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/sad_cloud_dark@3x.png differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/sk.lproj/GooglePlaces.strings a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/sk.lproj/GooglePlaces.strings new file mode 100755 index 0000000..3e2ab13 Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/sk.lproj/GooglePlaces.strings differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/sv.lproj/GooglePlaces.strings a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/sv.lproj/GooglePlaces.strings new file mode 100755 index 0000000..c1a2393 Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/sv.lproj/GooglePlaces.strings differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/th.lproj/GooglePlaces.strings a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/th.lproj/GooglePlaces.strings new file mode 100755 index 0000000..15d9a4f Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/th.lproj/GooglePlaces.strings differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/tr.lproj/GooglePlaces.strings a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/tr.lproj/GooglePlaces.strings new file mode 100755 index 0000000..42fc423 Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/tr.lproj/GooglePlaces.strings differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/uk.lproj/GooglePlaces.strings a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/uk.lproj/GooglePlaces.strings new file mode 100755 index 0000000..d39f99a Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/uk.lproj/GooglePlaces.strings differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/vi.lproj/GooglePlaces.strings a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/vi.lproj/GooglePlaces.strings new file mode 100755 index 0000000..c88d7ab Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/vi.lproj/GooglePlaces.strings differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/zh_CN.lproj/GooglePlaces.strings a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/zh_CN.lproj/GooglePlaces.strings new file mode 100755 index 0000000..290001e Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/zh_CN.lproj/GooglePlaces.strings differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/zh_HK.lproj/GooglePlaces.strings a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/zh_HK.lproj/GooglePlaces.strings new file mode 100755 index 0000000..d3686c6 Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/zh_HK.lproj/GooglePlaces.strings differ diff --git b/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/zh_TW.lproj/GooglePlaces.strings a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/zh_TW.lproj/GooglePlaces.strings new file mode 100755 index 0000000..f749ef8 Binary files /dev/null and a/Pods/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle/zh_TW.lproj/GooglePlaces.strings differ diff --git b/Pods/GooglePlaces/README.md a/Pods/GooglePlaces/README.md new file mode 100755 index 0000000..d2dc94a --- /dev/null +++ a/Pods/GooglePlaces/README.md @@ -0,0 +1,92 @@ +# Google Places API for iOS + +**NOTE:** This pod is the official pod for the Google Places API for iOS. +Previously this pod was used by another developer, his content has been moved to +[Swift Google Maps API](https://github.com/honghaoz/Swift-Google-Maps-API) on +GitHub. + +This pod contains the Google Places API for iOS, supporting both Objective C and +Swift. + +Use the [Google Places API for iOS] +(https://developers.google.com/places/ios-sdk/) for exciting features based +on the user's location and Google's Places database. You can enable users to +add a place, autocomplete place names, use a place picker widget, identify +the user's current place or retrieve full details and photos of a place. + +The Google Places API for iOS is distributed as a stand alone Pod. This Pod +contains all the Google Places API for iOS functionality which does not require +a map. + +# Getting Started + +* *Guides*: Read our [Getting Started guides] + (https://developers.google.com/places/ios-sdk/start). +* *Code samples*: In order to try out our demo app, run + + ``` + $ pod try GooglePlaces + ``` + + and follow the instructions on our [developer pages] + (https://developers.google.com/places/ios-api/code-samples). + +* *Support*: Find support from various channels and communities. + + * Support pages for [Google Places API for iOS] + (https://developers.google.com/places/support). + * Stack Overflow, using the [google-places-api] + (https://stackoverflow.com/questions/tagged/google-places-api) tag. + +* *Report issues*: Use our issue tracker to [file a bug] + (https://code.google.com/p/gmaps-api-issues/issues/entry?template=Places%20API%20for%20iOS%20-%20Bug) + or a [feature request] + (https://code.google.com/p/gmaps-api-issues/issues/entry?template=Places%20API%20for%20iOS%20-%20Feature%20Request) + +# Installation + +To integrate Google Places API for iOS into your Xcode project using CocoaPods, +specify it in your `Podfile`: + +``` +source 'https://github.com/CocoaPods/Specs.git' +platform :ios, '9.0' +target 'YOUR_APPLICATION_TARGET_NAME_HERE' do + pod 'GooglePlaces' +end +``` + +Then, run the following command: + +``` +$ pod install +``` + +Before you can start using the API, you have to activate it in the [Google +Developer Console](https://console.developers.google.com/) and integrate the +respective API key in your project. For detailed installation instructions, +visit Google's Getting Started Guides for the [Google Places API for iOS] +(https://developers.google.com/places/ios-api/start). + +# Migration from version 1 + +If you are using the Google Places API for iOS as part of the Google Maps SDK +for iOS version 1 please check the [migration guide](https://developers.google.com/places/migrate-to-v2) +for more information on upgrading your project. + +# License and Terms of Service + +By using the Google Places API for iOS, you accept Google's Terms of +Service and Policies. Pay attention particularly to the following aspects: + +* Depending on your app and use case, you may be required to display + attribution. Read more about [attribution requirements] + (https://developers.google.com/places/ios-api/attributions). +* Your API usage is subject to quota limitations. Read more about [usage + limits](https://developers.google.com/places/ios-api/usage). +* The [Terms of Service](https://developers.google.com/maps/terms) are a + comprehensive description of the legal contract that you enter with Google + by using the Google Places API for iOS. You may want to pay special + attention to [section 10] + (https://developers.google.com/maps/terms#10-license-restrictions), as it + talks in detail about what you can do with the API, and what you can't. diff --git b/Pods/Kingfisher/LICENSE a/Pods/Kingfisher/LICENSE new file mode 100644 index 0000000..80888ba --- /dev/null +++ a/Pods/Kingfisher/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2019 Wei Wang + +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. + diff --git b/Pods/Kingfisher/README.md a/Pods/Kingfisher/README.md new file mode 100644 index 0000000..13a216f --- /dev/null +++ a/Pods/Kingfisher/README.md @@ -0,0 +1,168 @@ +

+Kingfisher +

+ +

+ + + + + +
+ + + + +

+ +Kingfisher is a powerful, pure-Swift library for downloading and caching images from the web. It provides you a chance to use a pure-Swift way to work with remote images in your next app. + +## Features + +- [x] Asynchronous image downloading and caching. +- [x] Loading image from either `URLSession`-based networking or local provided data. +- [x] Useful image processors and filters provided. +- [x] Multiple-layer hybrid cache for both memory and disk. +- [x] Fine control on cache behavior. Customizable expiration date and size limit. +- [x] Cancelable downloading and auto-reusing previous downloaded content to improve performance. +- [x] Independent components. Use the downloader, caching system and image processors separately as you need. +- [x] Prefetching images and showing them from cache to boost your app. +- [x] View extensions for `UIImageView`, `NSImageView`, `NSButton` and `UIButton` to directly set an image from a URL. +- [x] Built-in transition animation when setting images. +- [x] Customizable placeholder and indicator while loading images. +- [x] Extensible image processing and image format easily. +- [x] SwiftUI support. + +### Kingfisher 101 + +The simplest use-case is setting an image to an image view with the `UIImageView` extension: + +```swift +let url = URL(string: "https://example.com/image.png") +imageView.kf.setImage(with: url) +``` + +Kingfisher will download the image from `url`, send it to both memory cache and disk cache, and display it in `imageView`. When you set with the same URL later, the image will be retrieved from cache and shown immediately. + +It also works if you use SwiftUI: + +```swift +import KingfisherSwiftUI + +var body: some View { + KFImage(URL(string: "https://example.com/image.png")!) +} +``` + +### A More Advanced Example + +With the powerful options, you can do hard tasks with Kingfisher in a simple way. For example, the code below: + +1. Downloads a high-resolution image. +2. Downsamples it to match the image view size. +3. Makes it round cornered with a given radius. +4. Shows a system indicator and a placeholder image while downloading. +5. When prepared, it animates the small thumbnail image with a "fade in" effect. +6. The original large image is also cached to disk for later use, to get rid of downloading it again in a detail view. +7. A console log is printed when the task finishes, either for success or failure. + +```swift +let url = URL(string: "https://example.com/high_resolution_image.png") +let processor = DownsamplingImageProcessor(size: imageView.bounds.size) + >> RoundCornerImageProcessor(cornerRadius: 20) +imageView.kf.indicatorType = .activity +imageView.kf.setImage( + with: url, + placeholder: UIImage(named: "placeholderImage"), + options: [ + .processor(processor), + .scaleFactor(UIScreen.main.scale), + .transition(.fade(1)), + .cacheOriginalImage + ]) +{ + result in + switch result { + case .success(let value): + print("Task done for: \(value.source.url?.absoluteString ?? "")") + case .failure(let error): + print("Job failed: \(error.localizedDescription)") + } +} +``` + +It is really a very common situation I can meet in my daily work. Think about how many lines you need to write without Kingfisher. You will fall in love with it if you give it a try! + +### Learn More + +To learn the using of Kingfisher by more examples, take a look at the [Cheat Sheet](https://github.com/onevcat/Kingfisher/wiki/Cheat-Sheet). There we summarized most common tasks in Kingfisher, you can get a better idea on what this framework can do. There are also some tips for performance in the same page, remember to check them too. + +## Requirements + +- iOS 10.0+ / macOS 10.12+ / tvOS 10.0+ / watchOS 3.0+ +- Swift 4.0+ + +[Kingfisher 5.0 Migration](https://github.com/onevcat/Kingfisher/wiki/Kingfisher-5.0-Migration-Guide) - Kingfisher 5.x is NOT fully compatible with version 4.x. However, the migration is not difficult. Depending on your use cases, it may take no effect or several minutes to modify your existing code for the new version. Please follow the [migration guide](https://github.com/onevcat/Kingfisher/wiki/Kingfisher-5.0-Migration-Guide) when you prepare to upgrade Kingfisher in your project. + +If you are using an even earlier version, see the guides below to know the steps for migrating. + +> - Kingfisher 4.0 Migration - Kingfisher 3.x should be source compatible to Kingfisher 4. The reason for a major update is that we need to specify the Swift version explicitly for Xcode. All deprecated methods in Kingfisher 3 has been removed, so please ensure you have no warning left before you migrate from Kingfisher 3 to Kingfisher 4. If you have any trouble in migrating, please open an issue to discuss. +> - [Kingfisher 3.0 Migration](https://github.com/onevcat/Kingfisher/wiki/Kingfisher-3.0-Migration-Guide) - If you are upgrading to Kingfisher 3.x from an earlier version, please read this for more information. + +## Next Steps + +We prepared a [wiki page](https://github.com/onevcat/Kingfisher/wiki). You can find tons of useful things there. + +* [Installation Guide](https://github.com/onevcat/Kingfisher/wiki/Installation-Guide) - Follow it to integrate Kingfisher into your project. +* [Cheat Sheet](https://github.com/onevcat/Kingfisher/wiki/Cheat-Sheet)- Curious about what Kingfisher could do and how would it look like when used in your project? See this page for useful code snippets. If you are already familiar with Kingfisher, you could also learn new tricks to improve the way you use Kingfisher! +* [API Reference](http://onevcat.github.io/Kingfisher/) - Lastly, please remember to read the full whenever you may need a more detailed reference. + +## Other + +### Future of Kingfisher + +I want to keep Kingfisher lightweight. This framework will focus on providing a simple solution for downloading and caching images. This doesn’t mean the framework can’t be improved. Kingfisher is far from perfect, so necessary and useful updates will be made to make it better. + +### Developments and Tests + +Any contributing and pull requests are warmly welcome. However, before you plan to implement some features or try to fix an uncertain issue, it is recommended to open a discussion first. It would be appreciated if your pull requests could build and with all tests green. :) + +### About the logo + +The logo of Kingfisher is inspired by [Tangram (七巧板)](http://en.wikipedia.org/wiki/Tangram), a dissection puzzle consisting of seven flat shapes from China. I believe she's a kingfisher bird instead of a swift, but someone insists that she is a pigeon. I guess I should give her a name. Hi, guys, do you have any suggestions? + +### Contact + +Follow and contact me on [Twitter](http://twitter.com/onevcat) or [Sina Weibo](http://weibo.com/onevcat). If you find an issue, just [open a ticket](https://github.com/onevcat/Kingfisher/issues/new). Pull requests are warmly welcome as well. + +## Contributors + +This project exists thanks to all the people who contribute. [[Contribute]](https://github.com/onevcat/Kingfisher/blob/master/CONTRIBUTING.md). + + + +## Backers + +Thank you to all our backers! Your support is really important for the project and encourages us to continue. 🙏 [[Become a backer](https://opencollective.com/kingfisher#backer)] + + + + +## Sponsors + +Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/kingfisher#sponsor)] + + + + + + + + + + + + +### License + +Kingfisher is released under the MIT license. See LICENSE for details. diff --git b/Pods/Kingfisher/Sources/Cache/CacheSerializer.swift a/Pods/Kingfisher/Sources/Cache/CacheSerializer.swift new file mode 100644 index 0000000..2290c16 --- /dev/null +++ a/Pods/Kingfisher/Sources/Cache/CacheSerializer.swift @@ -0,0 +1,137 @@ +// +// CacheSerializer.swift +// Kingfisher +// +// Created by Wei Wang on 2016/09/02. +// +// Copyright (c) 2019 Wei Wang +// +// 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 +import CoreGraphics + +/// An `CacheSerializer` is used to convert some data to an image object after +/// retrieving it from disk storage, and vice versa, to convert an image to data object +/// for storing to the disk storage. +public protocol CacheSerializer { + + /// Gets the serialized data from a provided image + /// and optional original data for caching to disk. + /// + /// - Parameters: + /// - image: The image needed to be serialized. + /// - original: The original data which is just downloaded. + /// If the image is retrieved from cache instead of + /// downloaded, it will be `nil`. + /// - Returns: The data object for storing to disk, or `nil` when no valid + /// data could be serialized. + func data(with image: KFCrossPlatformImage, original: Data?) -> Data? + + /// Gets an image from provided serialized data. + /// + /// - Parameters: + /// - data: The data from which an image should be deserialized. + /// - options: The parsed options for deserialization. + /// - Returns: An image deserialized or `nil` when no valid image + /// could be deserialized. + func image(with data: Data, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? + + /// Gets an image deserialized from provided data. + /// + /// - Parameters: + /// - data: The data from which an image should be deserialized. + /// - options: Options for deserialization. + /// - Returns: An image deserialized or `nil` when no valid image + /// could be deserialized. + /// - Note: + /// This method is deprecated. Please implement the version with + /// `KingfisherParsedOptionsInfo` as parameter instead. + @available(*, deprecated, + message: "Deprecated. Implement the method with same name but with `KingfisherParsedOptionsInfo` instead.") + func image(with data: Data, options: KingfisherOptionsInfo?) -> KFCrossPlatformImage? +} + +extension CacheSerializer { + public func image(with data: Data, options: KingfisherOptionsInfo?) -> KFCrossPlatformImage? { + return image(with: data, options: KingfisherParsedOptionsInfo(options)) + } +} + +/// Represents a basic and default `CacheSerializer` used in Kingfisher disk cache system. +/// It could serialize and deserialize images in PNG, JPEG and GIF format. For +/// image other than these formats, a normalized `pngRepresentation` will be used. +public struct DefaultCacheSerializer: CacheSerializer { + + /// The default general cache serializer used across Kingfisher's cache. + public static let `default` = DefaultCacheSerializer() + + /// The compression quality when converting image to a lossy format data. Default is 1.0. + public var compressionQuality: CGFloat = 1.0 + + /// Whether the original data should be preferred when serializing the image. + /// If `true`, the input original data will be checked first and used unless the data is `nil`. + /// In that case, the serialization will fall back to creating data from image. + public var preferCacheOriginalData: Bool = false + + /// Creates a cache serializer that serialize and deserialize images in PNG, JPEG and GIF format. + /// + /// - Note: + /// Use `DefaultCacheSerializer.default` unless you need to specify your own properties. + /// + public init() { } + + /// - Parameters: + /// - image: The image needed to be serialized. + /// - original: The original data which is just downloaded. + /// If the image is retrieved from cache instead of + /// downloaded, it will be `nil`. + /// - Returns: The data object for storing to disk, or `nil` when no valid + /// data could be serialized. + /// + /// - Note: + /// Only when `original` contains valid PNG, JPEG and GIF format data, the `image` will be + /// converted to the corresponding data type. Otherwise, if the `original` is provided but it is not + /// If `original` is `nil`, the input `image` will be encoded as PNG data. + public func data(with image: KFCrossPlatformImage, original: Data?) -> Data? { + if preferCacheOriginalData { + return original ?? + image.kf.data( + format: original?.kf.imageFormat ?? .unknown, + compressionQuality: compressionQuality + ) + } else { + return image.kf.data( + format: original?.kf.imageFormat ?? .unknown, + compressionQuality: compressionQuality + ) + } + } + + /// Gets an image deserialized from provided data. + /// + /// - Parameters: + /// - data: The data from which an image should be deserialized. + /// - options: Options for deserialization. + /// - Returns: An image deserialized or `nil` when no valid image + /// could be deserialized. + public func image(with data: Data, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { + return KingfisherWrapper.image(data: data, options: options.imageCreatingOptions) + } +} diff --git b/Pods/Kingfisher/Sources/Cache/DiskStorage.swift a/Pods/Kingfisher/Sources/Cache/DiskStorage.swift new file mode 100644 index 0000000..e538bd6 --- /dev/null +++ a/Pods/Kingfisher/Sources/Cache/DiskStorage.swift @@ -0,0 +1,466 @@ +// +// DiskStorage.swift +// Kingfisher +// +// Created by Wei Wang on 2018/10/15. +// +// Copyright (c) 2019 Wei Wang +// +// 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 + + +/// Represents a set of conception related to storage which stores a certain type of value in disk. +/// This is a namespace for the disk storage types. A `Backend` with a certain `Config` will be used to describe the +/// storage. See these composed types for more information. +public enum DiskStorage { + + /// Represents a storage back-end for the `DiskStorage`. The value is serialized to data + /// and stored as file in the file system under a specified location. + /// + /// You can config a `DiskStorage.Backend` in its initializer by passing a `DiskStorage.Config` value. + /// or modifying the `config` property after it being created. `DiskStorage` will use file's attributes to keep + /// track of a file for its expiration or size limitation. + public class Backend { + /// The config used for this disk storage. + public var config: Config + + // The final storage URL on disk, with `name` and `cachePathBlock` considered. + public let directoryURL: URL + + let metaChangingQueue: DispatchQueue + + /// Creates a disk storage with the given `DiskStorage.Config`. + /// + /// - Parameter config: The config used for this disk storage. + /// - Throws: An error if the folder for storage cannot be got or created. + public init(config: Config) throws { + + self.config = config + + let url: URL + if let directory = config.directory { + url = directory + } else { + url = try config.fileManager.url( + for: .cachesDirectory, + in: .userDomainMask, + appropriateFor: nil, + create: true) + } + + let cacheName = "com.onevcat.Kingfisher.ImageCache.\(config.name)" + directoryURL = config.cachePathBlock(url, cacheName) + + metaChangingQueue = DispatchQueue(label: cacheName) + + try prepareDirectory() + } + + // Creates the storage folder. + func prepareDirectory() throws { + let fileManager = config.fileManager + let path = directoryURL.path + + guard !fileManager.fileExists(atPath: path) else { return } + + do { + try fileManager.createDirectory( + atPath: path, + withIntermediateDirectories: true, + attributes: nil) + } catch { + throw KingfisherError.cacheError(reason: .cannotCreateDirectory(path: path, error: error)) + } + } + + func store( + value: T, + forKey key: String, + expiration: StorageExpiration? = nil) throws + { + let expiration = expiration ?? config.expiration + // The expiration indicates that already expired, no need to store. + guard !expiration.isExpired else { return } + + let data: Data + do { + data = try value.toData() + } catch { + throw KingfisherError.cacheError(reason: .cannotConvertToData(object: value, error: error)) + } + + let fileURL = cacheFileURL(forKey: key) + do { + try data.write(to: fileURL) + } catch { + throw KingfisherError.cacheError( + reason: .cannotCreateCacheFile(fileURL: fileURL, key: key, data: data, error: error) + ) + } + + let now = Date() + let attributes: [FileAttributeKey : Any] = [ + // The last access date. + .creationDate: now.fileAttributeDate, + // The estimated expiration date. + .modificationDate: expiration.estimatedExpirationSinceNow.fileAttributeDate + ] + do { + try config.fileManager.setAttributes(attributes, ofItemAtPath: fileURL.path) + } catch { + try? config.fileManager.removeItem(at: fileURL) + throw KingfisherError.cacheError( + reason: .cannotSetCacheFileAttribute( + filePath: fileURL.path, + attributes: attributes, + error: error + ) + ) + } + } + + func value(forKey key: String, extendingExpiration: ExpirationExtending = .cacheTime) throws -> T? { + return try value(forKey: key, referenceDate: Date(), actuallyLoad: true, extendingExpiration: extendingExpiration) + } + + func value( + forKey key: String, + referenceDate: Date, + actuallyLoad: Bool, + extendingExpiration: ExpirationExtending) throws -> T? + { + let fileManager = config.fileManager + let fileURL = cacheFileURL(forKey: key) + let filePath = fileURL.path + guard fileManager.fileExists(atPath: filePath) else { + return nil + } + + let meta: FileMeta + do { + let resourceKeys: Set = [.contentModificationDateKey, .creationDateKey] + meta = try FileMeta(fileURL: fileURL, resourceKeys: resourceKeys) + } catch { + throw KingfisherError.cacheError( + reason: .invalidURLResource(error: error, key: key, url: fileURL)) + } + + if meta.expired(referenceDate: referenceDate) { + return nil + } + if !actuallyLoad { return T.empty } + + do { + let data = try Data(contentsOf: fileURL) + let obj = try T.fromData(data) + metaChangingQueue.async { + meta.extendExpiration(with: fileManager, extendingExpiration: extendingExpiration) + } + return obj + } catch { + throw KingfisherError.cacheError(reason: .cannotLoadDataFromDisk(url: fileURL, error: error)) + } + } + + func isCached(forKey key: String) -> Bool { + return isCached(forKey: key, referenceDate: Date()) + } + + func isCached(forKey key: String, referenceDate: Date) -> Bool { + do { + let result = try value( + forKey: key, + referenceDate: referenceDate, + actuallyLoad: false, + extendingExpiration: .none + ) + return result != nil + } catch { + return false + } + } + + func remove(forKey key: String) throws { + let fileURL = cacheFileURL(forKey: key) + try removeFile(at: fileURL) + } + + func removeFile(at url: URL) throws { + try config.fileManager.removeItem(at: url) + } + + func removeAll() throws { + try removeAll(skipCreatingDirectory: false) + } + + func removeAll(skipCreatingDirectory: Bool) throws { + try config.fileManager.removeItem(at: directoryURL) + if !skipCreatingDirectory { + try prepareDirectory() + } + } + + /// The URL of the cached file with a given computed `key`. + /// + /// - Note: + /// This method does not guarantee there is an image already cached in the returned URL. It just gives your + /// the URL that the image should be if it exists in disk storage, with the give key. + /// + /// - Parameter key: The final computed key used when caching the image. Please note that usually this is not + /// the `cacheKey` of an image `Source`. It is the computed key with processor identifier considered. + public func cacheFileURL(forKey key: String) -> URL { + let fileName = cacheFileName(forKey: key) + return directoryURL.appendingPathComponent(fileName) + } + + func cacheFileName(forKey key: String) -> String { + if config.usesHashedFileName { + let hashedKey = key.kf.md5 + if let ext = config.pathExtension { + return "\(hashedKey).\(ext)" + } + return hashedKey + } else { + if let ext = config.pathExtension { + return "\(key).\(ext)" + } + return key + } + } + + func allFileURLs(for propertyKeys: [URLResourceKey]) throws -> [URL] { + let fileManager = config.fileManager + + guard let directoryEnumerator = fileManager.enumerator( + at: directoryURL, includingPropertiesForKeys: propertyKeys, options: .skipsHiddenFiles) else + { + throw KingfisherError.cacheError(reason: .fileEnumeratorCreationFailed(url: directoryURL)) + } + + guard let urls = directoryEnumerator.allObjects as? [URL] else { + throw KingfisherError.cacheError(reason: .invalidFileEnumeratorContent(url: directoryURL)) + } + return urls + } + + func removeExpiredValues(referenceDate: Date = Date()) throws -> [URL] { + let propertyKeys: [URLResourceKey] = [ + .isDirectoryKey, + .contentModificationDateKey + ] + + let urls = try allFileURLs(for: propertyKeys) + let keys = Set(propertyKeys) + let expiredFiles = urls.filter { fileURL in + do { + let meta = try FileMeta(fileURL: fileURL, resourceKeys: keys) + if meta.isDirectory { + return false + } + return meta.expired(referenceDate: referenceDate) + } catch { + return true + } + } + try expiredFiles.forEach { url in + try removeFile(at: url) + } + return expiredFiles + } + + func removeSizeExceededValues() throws -> [URL] { + + if config.sizeLimit == 0 { return [] } // Back compatible. 0 means no limit. + + var size = try totalSize() + if size < config.sizeLimit { return [] } + + let propertyKeys: [URLResourceKey] = [ + .isDirectoryKey, + .creationDateKey, + .fileSizeKey + ] + let keys = Set(propertyKeys) + + let urls = try allFileURLs(for: propertyKeys) + var pendings: [FileMeta] = urls.compactMap { fileURL in + guard let meta = try? FileMeta(fileURL: fileURL, resourceKeys: keys) else { + return nil + } + return meta + } + // Sort by last access date. Most recent file first. + pendings.sort(by: FileMeta.lastAccessDate) + + var removed: [URL] = [] + let target = config.sizeLimit / 2 + while size > target, let meta = pendings.popLast() { + size -= UInt(meta.fileSize) + try removeFile(at: meta.url) + removed.append(meta.url) + } + return removed + } + + /// Get the total file size of the folder in bytes. + func totalSize() throws -> UInt { + let propertyKeys: [URLResourceKey] = [.fileSizeKey] + let urls = try allFileURLs(for: propertyKeys) + let keys = Set(propertyKeys) + let totalSize: UInt = urls.reduce(0) { size, fileURL in + do { + let meta = try FileMeta(fileURL: fileURL, resourceKeys: keys) + return size + UInt(meta.fileSize) + } catch { + return size + } + } + return totalSize + } + } +} + +extension DiskStorage { + /// Represents the config used in a `DiskStorage`. + public struct Config { + + /// The file size limit on disk of the storage in bytes. 0 means no limit. + public var sizeLimit: UInt + + /// The `StorageExpiration` used in this disk storage. Default is `.days(7)`, + /// means that the disk cache would expire in one week. + public var expiration: StorageExpiration = .days(7) + + /// The preferred extension of cache item. It will be appended to the file name as its extension. + /// Default is `nil`, means that the cache file does not contain a file extension. + public var pathExtension: String? = nil + + /// Default is `true`, means that the cache file name will be hashed before storing. + public var usesHashedFileName = true + + let name: String + let fileManager: FileManager + let directory: URL? + + var cachePathBlock: ((_ directory: URL, _ cacheName: String) -> URL)! = { + (directory, cacheName) in + return directory.appendingPathComponent(cacheName, isDirectory: true) + } + + /// Creates a config value based on given parameters. + /// + /// - Parameters: + /// - name: The name of cache. It is used as a part of storage folder. It is used to identify the disk + /// storage. Two storages with the same `name` would share the same folder in disk, and it should + /// be prevented. + /// - sizeLimit: The size limit in bytes for all existing files in the disk storage. + /// - fileManager: The `FileManager` used to manipulate files on disk. Default is `FileManager.default`. + /// - directory: The URL where the disk storage should live. The storage will use this as the root folder, + /// and append a path which is constructed by input `name`. Default is `nil`, indicates that + /// the cache directory under user domain mask will be used. + public init( + name: String, + sizeLimit: UInt, + fileManager: FileManager = .default, + directory: URL? = nil) + { + self.name = name + self.fileManager = fileManager + self.directory = directory + self.sizeLimit = sizeLimit + } + } +} + +extension DiskStorage { + struct FileMeta { + + let url: URL + + let lastAccessDate: Date? + let estimatedExpirationDate: Date? + let isDirectory: Bool + let fileSize: Int + + static func lastAccessDate(lhs: FileMeta, rhs: FileMeta) -> Bool { + return lhs.lastAccessDate ?? .distantPast > rhs.lastAccessDate ?? .distantPast + } + + init(fileURL: URL, resourceKeys: Set) throws { + let meta = try fileURL.resourceValues(forKeys: resourceKeys) + self.init( + fileURL: fileURL, + lastAccessDate: meta.creationDate, + estimatedExpirationDate: meta.contentModificationDate, + isDirectory: meta.isDirectory ?? false, + fileSize: meta.fileSize ?? 0) + } + + init( + fileURL: URL, + lastAccessDate: Date?, + estimatedExpirationDate: Date?, + isDirectory: Bool, + fileSize: Int) + { + self.url = fileURL + self.lastAccessDate = lastAccessDate + self.estimatedExpirationDate = estimatedExpirationDate + self.isDirectory = isDirectory + self.fileSize = fileSize + } + + func expired(referenceDate: Date) -> Bool { + return estimatedExpirationDate?.isPast(referenceDate: referenceDate) ?? true + } + + func extendExpiration(with fileManager: FileManager, extendingExpiration: ExpirationExtending) { + guard let lastAccessDate = lastAccessDate, + let lastEstimatedExpiration = estimatedExpirationDate else + { + return + } + + let attributes: [FileAttributeKey : Any] + + switch extendingExpiration { + case .none: + // not extending expiration time here + return + case .cacheTime: + let originalExpiration: StorageExpiration = + .seconds(lastEstimatedExpiration.timeIntervalSince(lastAccessDate)) + attributes = [ + .creationDate: Date().fileAttributeDate, + .modificationDate: originalExpiration.estimatedExpirationSinceNow.fileAttributeDate + ] + case .expirationTime(let expirationTime): + attributes = [ + .creationDate: Date().fileAttributeDate, + .modificationDate: expirationTime.estimatedExpirationSinceNow.fileAttributeDate + ] + } + + try? fileManager.setAttributes(attributes, ofItemAtPath: url.path) + } + } +} + diff --git b/Pods/Kingfisher/Sources/Cache/FormatIndicatedCacheSerializer.swift a/Pods/Kingfisher/Sources/Cache/FormatIndicatedCacheSerializer.swift new file mode 100644 index 0000000..cdfb7c3 --- /dev/null +++ a/Pods/Kingfisher/Sources/Cache/FormatIndicatedCacheSerializer.swift @@ -0,0 +1,118 @@ +// +// RequestModifier.swift +// Kingfisher +// +// Created by Junyu Kuang on 5/28/17. +// +// Copyright (c) 2019 Wei Wang +// +// 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 +import CoreGraphics + +/// `FormatIndicatedCacheSerializer` lets you indicate an image format for serialized caches. +/// +/// It could serialize and deserialize PNG, JPEG and GIF images. For +/// image other than these formats, a normalized `pngRepresentation` will be used. +/// +/// Example: +/// ```` +/// let profileImageSize = CGSize(width: 44, height: 44) +/// +/// // A round corner image. +/// let imageProcessor = RoundCornerImageProcessor( +/// cornerRadius: profileImageSize.width / 2, targetSize: profileImageSize) +/// +/// let optionsInfo: KingfisherOptionsInfo = [ +/// .cacheSerializer(FormatIndicatedCacheSerializer.png), +/// .processor(imageProcessor)] +/// +/// A URL pointing to a JPEG image. +/// let url = URL(string: "https://example.com/image.jpg")! +/// +/// // Image will be always cached as PNG format to preserve alpha channel for round rectangle. +/// // So when you load it from cache again later, it will be still round cornered. +/// // Otherwise, the corner part would be filled by white color (since JPEG does not contain an alpha channel). +/// imageView.kf.setImage(with: url, options: optionsInfo) +/// ```` +public struct FormatIndicatedCacheSerializer: CacheSerializer { + + /// A `FormatIndicatedCacheSerializer` which converts image from and to PNG format. If the image cannot be + /// represented by PNG format, it will fallback to its real format which is determined by `original` data. + public static let png = FormatIndicatedCacheSerializer(imageFormat: .PNG, jpegCompressionQuality: nil) + + /// A `FormatIndicatedCacheSerializer` which converts image from and to JPEG format. If the image cannot be + /// represented by JPEG format, it will fallback to its real format which is determined by `original` data. + /// The compression quality is 1.0 when using this serializer. If you need to set a customized compression quality, + /// use `jpeg(compressionQuality:)`. + public static let jpeg = FormatIndicatedCacheSerializer(imageFormat: .JPEG, jpegCompressionQuality: 1.0) + + /// A `FormatIndicatedCacheSerializer` which converts image from and to JPEG format with a settable compression + /// quality. If the image cannot be represented by JPEG format, it will fallback to its real format which is + /// determined by `original` data. + /// - Parameter compressionQuality: The compression quality when converting image to JPEG data. + public static func jpeg(compressionQuality: CGFloat) -> FormatIndicatedCacheSerializer { + return FormatIndicatedCacheSerializer(imageFormat: .JPEG, jpegCompressionQuality: compressionQuality) + } + + /// A `FormatIndicatedCacheSerializer` which converts image from and to GIF format. If the image cannot be + /// represented by GIF format, it will fallback to its real format which is determined by `original` data. + public static let gif = FormatIndicatedCacheSerializer(imageFormat: .GIF, jpegCompressionQuality: nil) + + /// The indicated image format. + private let imageFormat: ImageFormat + + /// The compression quality used for loss image format (like JPEG). + private let jpegCompressionQuality: CGFloat? + + /// Creates data which represents the given `image` under a format. + public func data(with image: KFCrossPlatformImage, original: Data?) -> Data? { + + func imageData(withFormat imageFormat: ImageFormat) -> Data? { + return autoreleasepool { () -> Data? in + switch imageFormat { + case .PNG: return image.kf.pngRepresentation() + case .JPEG: return image.kf.jpegRepresentation(compressionQuality: jpegCompressionQuality ?? 1.0) + case .GIF: return image.kf.gifRepresentation() + case .unknown: return nil + } + } + } + + // generate data with indicated image format + if let data = imageData(withFormat: imageFormat) { + return data + } + + let originalFormat = original?.kf.imageFormat ?? .unknown + + // generate data with original image's format + if originalFormat != imageFormat, let data = imageData(withFormat: originalFormat) { + return data + } + + return original ?? image.kf.normalized.kf.pngRepresentation() + } + + /// Same implementation as `DefaultCacheSerializer`. + public func image(with data: Data, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { + return KingfisherWrapper.image(data: data, options: options.imageCreatingOptions) + } +} diff --git b/Pods/Kingfisher/Sources/Cache/ImageCache.swift a/Pods/Kingfisher/Sources/Cache/ImageCache.swift new file mode 100644 index 0000000..fde53b0 --- /dev/null +++ a/Pods/Kingfisher/Sources/Cache/ImageCache.swift @@ -0,0 +1,839 @@ +// +// ImageCache.swift +// Kingfisher +// +// Created by Wei Wang on 15/4/6. +// +// Copyright (c) 2019 Wei Wang +// +// 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. + +#if os(macOS) +import AppKit +#else +import UIKit +#endif + +extension Notification.Name { + /// This notification will be sent when the disk cache got cleaned either there are cached files expired or the + /// total size exceeding the max allowed size. The manually invoking of `clearDiskCache` method will not trigger + /// this notification. + /// + /// The `object` of this notification is the `ImageCache` object which sends the notification. + /// A list of removed hashes (files) could be retrieved by accessing the array under + /// `KingfisherDiskCacheCleanedHashKey` key in `userInfo` of the notification object you received. + /// By checking the array, you could know the hash codes of files are removed. + public static let KingfisherDidCleanDiskCache = + Notification.Name("com.onevcat.Kingfisher.KingfisherDidCleanDiskCache") +} + +/// Key for array of cleaned hashes in `userInfo` of `KingfisherDidCleanDiskCacheNotification`. +public let KingfisherDiskCacheCleanedHashKey = "com.onevcat.Kingfisher.cleanedHash" + +/// Cache type of a cached image. +/// - none: The image is not cached yet when retrieving it. +/// - memory: The image is cached in memory. +/// - disk: The image is cached in disk. +public enum CacheType { + /// The image is not cached yet when retrieving it. + case none + /// The image is cached in memory. + case memory + /// The image is cached in disk. + case disk + + /// Whether the cache type represents the image is already cached or not. + public var cached: Bool { + switch self { + case .memory, .disk: return true + case .none: return false + } + } +} + +/// Represents the caching operation result. +public struct CacheStoreResult { + + /// The cache result for memory cache. Caching an image to memory will never fail. + public let memoryCacheResult: Result<(), Never> + + /// The cache result for disk cache. If an error happens during caching operation, + /// you can get it from `.failure` case of this `diskCacheResult`. + public let diskCacheResult: Result<(), KingfisherError> +} + +extension KFCrossPlatformImage: CacheCostCalculable { + /// Cost of an image + public var cacheCost: Int { return kf.cost } +} + +extension Data: DataTransformable { + public func toData() throws -> Data { + return self + } + + public static func fromData(_ data: Data) throws -> Data { + return data + } + + public static let empty = Data() +} + + +/// Represents the getting image operation from the cache. +/// +/// - disk: The image can be retrieved from disk cache. +/// - memory: The image can be retrieved memory cache. +/// - none: The image does not exist in the cache. +public enum ImageCacheResult { + + /// The image can be retrieved from disk cache. + case disk(KFCrossPlatformImage) + + /// The image can be retrieved memory cache. + case memory(KFCrossPlatformImage) + + /// The image does not exist in the cache. + case none + + /// Extracts the image from cache result. It returns the associated `Image` value for + /// `.disk` and `.memory` case. For `.none` case, `nil` is returned. + public var image: KFCrossPlatformImage? { + switch self { + case .disk(let image): return image + case .memory(let image): return image + case .none: return nil + } + } + + /// Returns the corresponding `CacheType` value based on the result type of `self`. + public var cacheType: CacheType { + switch self { + case .disk: return .disk + case .memory: return .memory + case .none: return .none + } + } +} + +/// Represents a hybrid caching system which is composed by a `MemoryStorage.Backend` and a `DiskStorage.Backend`. +/// `ImageCache` is a high level abstract for storing an image as well as its data to disk memory and disk, and +/// retrieving them back. +/// +/// While a default image cache object will be used if you prefer the extension methods of Kingfisher, you can create +/// your own cache object and configure its storages as your need. This class also provide an interface for you to set +/// the memory and disk storage config. +open class ImageCache { + + // MARK: Singleton + /// The default `ImageCache` object. Kingfisher will use this cache for its related methods if there is no + /// other cache specified. The `name` of this default cache is "default", and you should not use this name + /// for any of your customize cache. + public static let `default` = ImageCache(name: "default") + + // MARK: Public Properties + /// The `MemoryStorage.Backend` object used in this cache. This storage holds loaded images in memory with a + /// reasonable expire duration and a maximum memory usage. To modify the configuration of a storage, just set + /// the storage `config` and its properties. + public let memoryStorage: MemoryStorage.Backend + + /// The `DiskStorage.Backend` object used in this cache. This storage stores loaded images in disk with a + /// reasonable expire duration and a maximum disk usage. To modify the configuration of a storage, just set + /// the storage `config` and its properties. + public let diskStorage: DiskStorage.Backend + + private let ioQueue: DispatchQueue + + /// Closure that defines the disk cache path from a given path and cacheName. + public typealias DiskCachePathClosure = (URL, String) -> URL + + // MARK: Initializers + + /// Creates an `ImageCache` from a customized `MemoryStorage` and `DiskStorage`. + /// + /// - Parameters: + /// - memoryStorage: The `MemoryStorage.Backend` object to use in the image cache. + /// - diskStorage: The `DiskStorage.Backend` object to use in the image cache. + public init( + memoryStorage: MemoryStorage.Backend, + diskStorage: DiskStorage.Backend) + { + self.memoryStorage = memoryStorage + self.diskStorage = diskStorage + let ioQueueName = "com.onevcat.Kingfisher.ImageCache.ioQueue.\(UUID().uuidString)" + ioQueue = DispatchQueue(label: ioQueueName) + + let notifications: [(Notification.Name, Selector)] + #if !os(macOS) && !os(watchOS) + #if swift(>=4.2) + notifications = [ + (UIApplication.didReceiveMemoryWarningNotification, #selector(clearMemoryCache)), + (UIApplication.willTerminateNotification, #selector(cleanExpiredDiskCache)), + (UIApplication.didEnterBackgroundNotification, #selector(backgroundCleanExpiredDiskCache)) + ] + #else + notifications = [ + (NSNotification.Name.UIApplicationDidReceiveMemoryWarning, #selector(clearMemoryCache)), + (NSNotification.Name.UIApplicationWillTerminate, #selector(cleanExpiredDiskCache)), + (NSNotification.Name.UIApplicationDidEnterBackground, #selector(backgroundCleanExpiredDiskCache)) + ] + #endif + #elseif os(macOS) + notifications = [ + (NSApplication.willResignActiveNotification, #selector(cleanExpiredDiskCache)), + ] + #else + notifications = [] + #endif + notifications.forEach { + NotificationCenter.default.addObserver(self, selector: $0.1, name: $0.0, object: nil) + } + } + + /// Creates an `ImageCache` with a given `name`. Both `MemoryStorage` and `DiskStorage` will be created + /// with a default config based on the `name`. + /// + /// - Parameter name: The name of cache object. It is used to setup disk cache directories and IO queue. + /// You should not use the same `name` for different caches, otherwise, the disk storage would + /// be conflicting to each other. The `name` should not be an empty string. + public convenience init(name: String) { + try! self.init(name: name, cacheDirectoryURL: nil, diskCachePathClosure: nil) + } + + /// Creates an `ImageCache` with a given `name`, cache directory `path` + /// and a closure to modify the cache directory. + /// + /// - Parameters: + /// - name: The name of cache object. It is used to setup disk cache directories and IO queue. + /// You should not use the same `name` for different caches, otherwise, the disk storage would + /// be conflicting to each other. + /// - cacheDirectoryURL: Location of cache directory URL on disk. It will be internally pass to the + /// initializer of `DiskStorage` as the disk cache directory. If `nil`, the cache + /// directory under user domain mask will be used. + /// - diskCachePathClosure: Closure that takes in an optional initial path string and generates + /// the final disk cache path. You could use it to fully customize your cache path. + /// - Throws: An error that happens during image cache creating, such as unable to create a directory at the given + /// path. + public convenience init( + name: String, + cacheDirectoryURL: URL?, + diskCachePathClosure: DiskCachePathClosure? = nil) throws + { + if name.isEmpty { + fatalError("[Kingfisher] You should specify a name for the cache. A cache with empty name is not permitted.") + } + + let totalMemory = ProcessInfo.processInfo.physicalMemory + let costLimit = totalMemory / 4 + let memoryStorage = MemoryStorage.Backend(config: + .init(totalCostLimit: (costLimit > Int.max) ? Int.max : Int(costLimit))) + + var diskConfig = DiskStorage.Config( + name: name, + sizeLimit: 0, + directory: cacheDirectoryURL + ) + if let closure = diskCachePathClosure { + diskConfig.cachePathBlock = closure + } + let diskStorage = try DiskStorage.Backend(config: diskConfig) + diskConfig.cachePathBlock = nil + + self.init(memoryStorage: memoryStorage, diskStorage: diskStorage) + } + + deinit { + NotificationCenter.default.removeObserver(self) + } + + // MARK: Storing Images + + open func store(_ image: KFCrossPlatformImage, + original: Data? = nil, + forKey key: String, + options: KingfisherParsedOptionsInfo, + toDisk: Bool = true, + completionHandler: ((CacheStoreResult) -> Void)? = nil) + { + let identifier = options.processor.identifier + let callbackQueue = options.callbackQueue + + let computedKey = key.computedKey(with: identifier) + // Memory storage should not throw. + memoryStorage.storeNoThrow(value: image, forKey: computedKey, expiration: options.memoryCacheExpiration) + + guard toDisk else { + if let completionHandler = completionHandler { + let result = CacheStoreResult(memoryCacheResult: .success(()), diskCacheResult: .success(())) + callbackQueue.execute { completionHandler(result) } + } + return + } + + ioQueue.async { + let serializer = options.cacheSerializer + if let data = serializer.data(with: image, original: original) { + self.syncStoreToDisk( + data, + forKey: key, + processorIdentifier: identifier, + callbackQueue: callbackQueue, + expiration: options.diskCacheExpiration, + completionHandler: completionHandler) + } else { + guard let completionHandler = completionHandler else { return } + + let diskError = KingfisherError.cacheError( + reason: .cannotSerializeImage(image: image, original: original, serializer: serializer)) + let result = CacheStoreResult( + memoryCacheResult: .success(()), + diskCacheResult: .failure(diskError)) + callbackQueue.execute { completionHandler(result) } + } + } + } + + /// Stores an image to the cache. + /// + /// - Parameters: + /// - image: The image to be stored. + /// - original: The original data of the image. This value will be forwarded to the provided `serializer` for + /// further use. By default, Kingfisher uses a `DefaultCacheSerializer` to serialize the image to + /// data for caching in disk, it checks the image format based on `original` data to determine in + /// which image format should be used. For other types of `serializer`, it depends on their + /// implementation detail on how to use this original data. + /// - key: The key used for caching the image. + /// - identifier: The identifier of processor being used for caching. If you are using a processor for the + /// image, pass the identifier of processor to this parameter. + /// - serializer: The `CacheSerializer` + /// - toDisk: Whether this image should be cached to disk or not. If `false`, the image is only cached in memory. + /// Otherwise, it is cached in both memory storage and disk storage. Default is `true`. + /// - callbackQueue: The callback queue on which `completionHandler` is invoked. Default is `.untouch`. For case + /// that `toDisk` is `false`, a `.untouch` queue means `callbackQueue` will be invoked from the + /// caller queue of this method. If `toDisk` is `true`, the `completionHandler` will be called + /// from an internal file IO queue. To change this behavior, specify another `CallbackQueue` + /// value. + /// - completionHandler: A closure which is invoked when the cache operation finishes. + open func store(_ image: KFCrossPlatformImage, + original: Data? = nil, + forKey key: String, + processorIdentifier identifier: String = "", + cacheSerializer serializer: CacheSerializer = DefaultCacheSerializer.default, + toDisk: Bool = true, + callbackQueue: CallbackQueue = .untouch, + completionHandler: ((CacheStoreResult) -> Void)? = nil) + { + struct TempProcessor: ImageProcessor { + let identifier: String + func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { + return nil + } + } + + let options = KingfisherParsedOptionsInfo([ + .processor(TempProcessor(identifier: identifier)), + .cacheSerializer(serializer), + .callbackQueue(callbackQueue) + ]) + store(image, original: original, forKey: key, options: options, + toDisk: toDisk, completionHandler: completionHandler) + } + + open func storeToDisk( + _ data: Data, + forKey key: String, + processorIdentifier identifier: String = "", + expiration: StorageExpiration? = nil, + callbackQueue: CallbackQueue = .untouch, + completionHandler: ((CacheStoreResult) -> Void)? = nil) + { + ioQueue.async { + self.syncStoreToDisk( + data, + forKey: key, + processorIdentifier: identifier, + callbackQueue: callbackQueue, + expiration: expiration, + completionHandler: completionHandler) + } + } + + private func syncStoreToDisk( + _ data: Data, + forKey key: String, + processorIdentifier identifier: String = "", + callbackQueue: CallbackQueue = .untouch, + expiration: StorageExpiration? = nil, + completionHandler: ((CacheStoreResult) -> Void)? = nil) + { + let computedKey = key.computedKey(with: identifier) + let result: CacheStoreResult + do { + try self.diskStorage.store(value: data, forKey: computedKey, expiration: expiration) + result = CacheStoreResult(memoryCacheResult: .success(()), diskCacheResult: .success(())) + } catch { + let diskError: KingfisherError + if let error = error as? KingfisherError { + diskError = error + } else { + diskError = .cacheError(reason: .cannotConvertToData(object: data, error: error)) + } + + result = CacheStoreResult( + memoryCacheResult: .success(()), + diskCacheResult: .failure(diskError) + ) + } + if let completionHandler = completionHandler { + callbackQueue.execute { completionHandler(result) } + } + } + + // MARK: Removing Images + + /// Removes the image for the given key from the cache. + /// + /// - Parameters: + /// - key: The key used for caching the image. + /// - identifier: The identifier of processor being used for caching. If you are using a processor for the + /// image, pass the identifier of processor to this parameter. + /// - fromMemory: Whether this image should be removed from memory storage or not. + /// If `false`, the image won't be removed from the memory storage. Default is `true`. + /// - fromDisk: Whether this image should be removed from disk storage or not. + /// If `false`, the image won't be removed from the disk storage. Default is `true`. + /// - callbackQueue: The callback queue on which `completionHandler` is invoked. Default is `.untouch`. + /// - completionHandler: A closure which is invoked when the cache removing operation finishes. + open func removeImage(forKey key: String, + processorIdentifier identifier: String = "", + fromMemory: Bool = true, + fromDisk: Bool = true, + callbackQueue: CallbackQueue = .untouch, + completionHandler: (() -> Void)? = nil) + { + let computedKey = key.computedKey(with: identifier) + + if fromMemory { + try? memoryStorage.remove(forKey: computedKey) + } + + if fromDisk { + ioQueue.async{ + try? self.diskStorage.remove(forKey: computedKey) + if let completionHandler = completionHandler { + callbackQueue.execute { completionHandler() } + } + } + } else { + if let completionHandler = completionHandler { + callbackQueue.execute { completionHandler() } + } + } + } + + func retrieveImage(forKey key: String, + options: KingfisherParsedOptionsInfo, + callbackQueue: CallbackQueue = .mainCurrentOrAsync, + completionHandler: ((Result) -> Void)?) + { + // No completion handler. No need to start working and early return. + guard let completionHandler = completionHandler else { return } + + // Try to check the image from memory cache first. + if let image = retrieveImageInMemoryCache(forKey: key, options: options) { + let image = options.imageModifier?.modify(image) ?? image + callbackQueue.execute { completionHandler(.success(.memory(image))) } + } else if options.fromMemoryCacheOrRefresh { + callbackQueue.execute { completionHandler(.success(.none)) } + } else { + + // Begin to disk search. + self.retrieveImageInDiskCache(forKey: key, options: options, callbackQueue: callbackQueue) { + result in + switch result { + case .success(let image): + + guard let image = image else { + // No image found in disk storage. + callbackQueue.execute { completionHandler(.success(.none)) } + return + } + + let finalImage = options.imageModifier?.modify(image) ?? image + // Cache the disk image to memory. + // We are passing `false` to `toDisk`, the memory cache does not change + // callback queue, we can call `completionHandler` without another dispatch. + var cacheOptions = options + cacheOptions.callbackQueue = .untouch + self.store( + finalImage, + forKey: key, + options: cacheOptions, + toDisk: false) + { + _ in + callbackQueue.execute { completionHandler(.success(.disk(finalImage))) } + } + case .failure(let error): + callbackQueue.execute { completionHandler(.failure(error)) } + } + } + } + } + + // MARK: Getting Images + + /// Gets an image for a given key from the cache, either from memory storage or disk storage. + /// + /// - Parameters: + /// - key: The key used for caching the image. + /// - options: The `KingfisherOptionsInfo` options setting used for retrieving the image. + /// - callbackQueue: The callback queue on which `completionHandler` is invoked. Default is `.mainCurrentOrAsync`. + /// - completionHandler: A closure which is invoked when the image getting operation finishes. If the + /// image retrieving operation finishes without problem, an `ImageCacheResult` value + /// will be sent to this closure as result. Otherwise, a `KingfisherError` result + /// with detail failing reason will be sent. + open func retrieveImage(forKey key: String, + options: KingfisherOptionsInfo? = nil, + callbackQueue: CallbackQueue = .mainCurrentOrAsync, + completionHandler: ((Result) -> Void)?) + { + retrieveImage( + forKey: key, + options: KingfisherParsedOptionsInfo(options), + callbackQueue: callbackQueue, + completionHandler: completionHandler) + } + + func retrieveImageInMemoryCache( + forKey key: String, + options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? + { + let computedKey = key.computedKey(with: options.processor.identifier) + return memoryStorage.value(forKey: computedKey, extendingExpiration: options.memoryCacheAccessExtendingExpiration) + } + + /// Gets an image for a given key from the memory storage. + /// + /// - Parameters: + /// - key: The key used for caching the image. + /// - options: The `KingfisherOptionsInfo` options setting used for retrieving the image. + /// - Returns: The image stored in memory cache, if exists and valid. Otherwise, if the image does not exist or + /// has already expired, `nil` is returned. + open func retrieveImageInMemoryCache( + forKey key: String, + options: KingfisherOptionsInfo? = nil) -> KFCrossPlatformImage? + { + return retrieveImageInMemoryCache(forKey: key, options: KingfisherParsedOptionsInfo(options)) + } + + func retrieveImageInDiskCache( + forKey key: String, + options: KingfisherParsedOptionsInfo, + callbackQueue: CallbackQueue = .untouch, + completionHandler: @escaping (Result) -> Void) + { + let computedKey = key.computedKey(with: options.processor.identifier) + let loadingQueue: CallbackQueue = options.loadDiskFileSynchronously ? .untouch : .dispatch(ioQueue) + loadingQueue.execute { + do { + var image: KFCrossPlatformImage? = nil + if let data = try self.diskStorage.value(forKey: computedKey, extendingExpiration: options.diskCacheAccessExtendingExpiration) { + image = options.cacheSerializer.image(with: data, options: options) + } + callbackQueue.execute { completionHandler(.success(image)) } + } catch { + if let error = error as? KingfisherError { + callbackQueue.execute { completionHandler(.failure(error)) } + } else { + assertionFailure("The internal thrown error should be a `KingfisherError`.") + } + } + } + } + + /// Gets an image for a given key from the disk storage. + /// + /// - Parameters: + /// - key: The key used for caching the image. + /// - options: The `KingfisherOptionsInfo` options setting used for retrieving the image. + /// - callbackQueue: The callback queue on which `completionHandler` is invoked. Default is `.untouch`. + /// - completionHandler: A closure which is invoked when the operation finishes. + open func retrieveImageInDiskCache( + forKey key: String, + options: KingfisherOptionsInfo? = nil, + callbackQueue: CallbackQueue = .untouch, + completionHandler: @escaping (Result) -> Void) + { + retrieveImageInDiskCache( + forKey: key, + options: KingfisherParsedOptionsInfo(options), + callbackQueue: callbackQueue, + completionHandler: completionHandler) + } + + // MARK: Cleaning + /// Clears the memory storage of this cache. + @objc public func clearMemoryCache() { + try? memoryStorage.removeAll() + } + + /// Clears the disk storage of this cache. This is an async operation. + /// + /// - Parameter handler: A closure which is invoked when the cache clearing operation finishes. + /// This `handler` will be called from the main queue. + open func clearDiskCache(completion handler: (()->())? = nil) { + ioQueue.async { + do { + try self.diskStorage.removeAll() + } catch _ { } + if let handler = handler { + DispatchQueue.main.async { handler() } + } + } + } + + /// Clears the expired images from disk storage. This is an async operation. + open func cleanExpiredMemoryCache() { + memoryStorage.removeExpired() + } + + /// Clears the expired images from disk storage. This is an async operation. + @objc func cleanExpiredDiskCache() { + cleanExpiredDiskCache(completion: nil) + } + + /// Clears the expired images from disk storage. This is an async operation. + /// + /// - Parameter handler: A closure which is invoked when the cache clearing operation finishes. + /// This `handler` will be called from the main queue. + open func cleanExpiredDiskCache(completion handler: (() -> Void)? = nil) { + ioQueue.async { + do { + var removed: [URL] = [] + let removedExpired = try self.diskStorage.removeExpiredValues() + removed.append(contentsOf: removedExpired) + + let removedSizeExceeded = try self.diskStorage.removeSizeExceededValues() + removed.append(contentsOf: removedSizeExceeded) + + if !removed.isEmpty { + DispatchQueue.main.async { + let cleanedHashes = removed.map { $0.lastPathComponent } + NotificationCenter.default.post( + name: .KingfisherDidCleanDiskCache, + object: self, + userInfo: [KingfisherDiskCacheCleanedHashKey: cleanedHashes]) + } + } + + if let handler = handler { + DispatchQueue.main.async { handler() } + } + } catch {} + } + } + +#if !os(macOS) && !os(watchOS) + /// Clears the expired images from disk storage when app is in background. This is an async operation. + /// In most cases, you should not call this method explicitly. + /// It will be called automatically when `UIApplicationDidEnterBackgroundNotification` received. + @objc public func backgroundCleanExpiredDiskCache() { + // if 'sharedApplication()' is unavailable, then return + guard let sharedApplication = KingfisherWrapper.shared else { return } + + func endBackgroundTask(_ task: inout UIBackgroundTaskIdentifier) { + sharedApplication.endBackgroundTask(task) + #if swift(>=4.2) + task = UIBackgroundTaskIdentifier.invalid + #else + task = UIBackgroundTaskInvalid + #endif + } + + var backgroundTask: UIBackgroundTaskIdentifier! + backgroundTask = sharedApplication.beginBackgroundTask { + endBackgroundTask(&backgroundTask!) + } + + cleanExpiredDiskCache { + endBackgroundTask(&backgroundTask!) + } + } +#endif + + // MARK: Image Cache State + + /// Returns the cache type for a given `key` and `identifier` combination. + /// This method is used for checking whether an image is cached in current cache. + /// It also provides information on which kind of cache can it be found in the return value. + /// + /// - Parameters: + /// - key: The key used for caching the image. + /// - identifier: Processor identifier which used for this image. Default is the `identifier` of + /// `DefaultImageProcessor.default`. + /// - Returns: A `CacheType` instance which indicates the cache status. + /// `.none` means the image is not in cache or it is already expired. + open func imageCachedType( + forKey key: String, + processorIdentifier identifier: String = DefaultImageProcessor.default.identifier) -> CacheType + { + let computedKey = key.computedKey(with: identifier) + if memoryStorage.isCached(forKey: computedKey) { return .memory } + if diskStorage.isCached(forKey: computedKey) { return .disk } + return .none + } + + /// Returns whether the file exists in cache for a given `key` and `identifier` combination. + /// + /// - Parameters: + /// - key: The key used for caching the image. + /// - identifier: Processor identifier which used for this image. Default is the `identifier` of + /// `DefaultImageProcessor.default`. + /// - Returns: A `Bool` which indicates whether a cache could match the given `key` and `identifier` combination. + /// + /// - Note: + /// The return value does not contain information about from which kind of storage the cache matches. + /// To get the information about cache type according `CacheType`, + /// use `imageCachedType(forKey:processorIdentifier:)` instead. + public func isCached( + forKey key: String, + processorIdentifier identifier: String = DefaultImageProcessor.default.identifier) -> Bool + { + return imageCachedType(forKey: key, processorIdentifier: identifier).cached + } + + /// Gets the hash used as cache file name for the key. + /// + /// - Parameters: + /// - key: The key used for caching the image. + /// - identifier: Processor identifier which used for this image. Default is the `identifier` of + /// `DefaultImageProcessor.default`. + /// - Returns: The hash which is used as the cache file name. + /// + /// - Note: + /// By default, for a given combination of `key` and `identifier`, `ImageCache` will use the value + /// returned by this method as the cache file name. You can use this value to check and match cache file + /// if you need. + open func hash( + forKey key: String, + processorIdentifier identifier: String = DefaultImageProcessor.default.identifier) -> String + { + let computedKey = key.computedKey(with: identifier) + return diskStorage.cacheFileName(forKey: computedKey) + } + + /// Calculates the size taken by the disk storage. + /// It is the total file size of all cached files in the `diskStorage` on disk in bytes. + /// + /// - Parameter handler: Called with the size calculating finishes. This closure is invoked from the main queue. + open func calculateDiskStorageSize(completion handler: @escaping ((Result) -> Void)) { + ioQueue.async { + do { + let size = try self.diskStorage.totalSize() + DispatchQueue.main.async { handler(.success(size)) } + } catch { + if let error = error as? KingfisherError { + DispatchQueue.main.async { handler(.failure(error)) } + } else { + assertionFailure("The internal thrown error should be a `KingfisherError`.") + } + + } + } + } + + /// Gets the cache path for the key. + /// It is useful for projects with web view or anyone that needs access to the local file path. + /// + /// i.e. Replacing the `` tag in your HTML. + /// + /// - Parameters: + /// - key: The key used for caching the image. + /// - identifier: Processor identifier which used for this image. Default is the `identifier` of + /// `DefaultImageProcessor.default`. + /// - Returns: The disk path of cached image under the given `key` and `identifier`. + /// + /// - Note: + /// This method does not guarantee there is an image already cached in the returned path. It just gives your + /// the path that the image should be, if it exists in disk storage. + /// + /// You could use `isCached(forKey:)` method to check whether the image is cached under that key in disk. + open func cachePath( + forKey key: String, + processorIdentifier identifier: String = DefaultImageProcessor.default.identifier) -> String + { + let computedKey = key.computedKey(with: identifier) + return diskStorage.cacheFileURL(forKey: computedKey).path + } +} + +extension Dictionary { + func keysSortedByValue(_ isOrderedBefore: (Value, Value) -> Bool) -> [Key] { + return Array(self).sorted{ isOrderedBefore($0.1, $1.1) }.map{ $0.0 } + } +} + +#if !os(macOS) && !os(watchOS) +// MARK: - For App Extensions +extension UIApplication: KingfisherCompatible { } +extension KingfisherWrapper where Base: UIApplication { + public static var shared: UIApplication? { + let selector = NSSelectorFromString("sharedApplication") + guard Base.responds(to: selector) else { return nil } + return Base.perform(selector).takeUnretainedValue() as? UIApplication + } +} +#endif + +extension String { + func computedKey(with identifier: String) -> String { + if identifier.isEmpty { + return self + } else { + return appending("@\(identifier)") + } + } +} + +extension ImageCache { + + /// Creates an `ImageCache` with a given `name`, cache directory `path` + /// and a closure to modify the cache directory. + /// + /// - Parameters: + /// - name: The name of cache object. It is used to setup disk cache directories and IO queue. + /// You should not use the same `name` for different caches, otherwise, the disk storage would + /// be conflicting to each other. + /// - path: Location of cache URL on disk. It will be internally pass to the initializer of `DiskStorage` as the + /// disk cache directory. + /// - diskCachePathClosure: Closure that takes in an optional initial path string and generates + /// the final disk cache path. You could use it to fully customize your cache path. + /// - Throws: An error that happens during image cache creating, such as unable to create a directory at the given + /// path. + @available(*, deprecated, message: "Use `init(name:cacheDirectoryURL:diskCachePathClosure:)` instead", + renamed: "init(name:cacheDirectoryURL:diskCachePathClosure:)") + public convenience init( + name: String, + path: String?, + diskCachePathClosure: DiskCachePathClosure? = nil) throws + { + let directoryURL = path.flatMap { URL(string: $0) } + try self.init(name: name, cacheDirectoryURL: directoryURL, diskCachePathClosure: diskCachePathClosure) + } +} diff --git b/Pods/Kingfisher/Sources/Cache/MemoryStorage.swift a/Pods/Kingfisher/Sources/Cache/MemoryStorage.swift new file mode 100644 index 0000000..fd0965b --- /dev/null +++ a/Pods/Kingfisher/Sources/Cache/MemoryStorage.swift @@ -0,0 +1,237 @@ +// +// MemoryStorage.swift +// Kingfisher +// +// Created by Wei Wang on 2018/10/15. +// +// Copyright (c) 2019 Wei Wang +// +// 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 + +/// Represents a set of conception related to storage which stores a certain type of value in memory. +/// This is a namespace for the memory storage types. A `Backend` with a certain `Config` will be used to describe the +/// storage. See these composed types for more information. +public enum MemoryStorage { + + /// Represents a storage which stores a certain type of value in memory. It provides fast access, + /// but limited storing size. The stored value type needs to conform to `CacheCostCalculable`, + /// and its `cacheCost` will be used to determine the cost of size for the cache item. + /// + /// You can config a `MemoryStorage.Backend` in its initializer by passing a `MemoryStorage.Config` value. + /// or modifying the `config` property after it being created. The backend of `MemoryStorage` has + /// upper limitation on cost size in memory and item count. All items in the storage has an expiration + /// date. When retrieved, if the target item is already expired, it will be recognized as it does not + /// exist in the storage. The `MemoryStorage` also contains a scheduled self clean task, to evict expired + /// items from memory. + public class Backend { + let storage = NSCache>() + + // Keys trackes the objects once inside the storage. For object removing triggered by user, the corresponding + // key would be also removed. However, for the object removing triggered by cache rule/policy of system, the + // key will be remained there until next `removeExpired` happens. + // + // Breaking the strict tracking could save additional locking behaviors. + // See https://github.com/onevcat/Kingfisher/issues/1233 + var keys = Set() + + private var cleanTimer: Timer? = nil + private let lock = NSLock() + + /// The config used in this storage. It is a value you can set and + /// use to config the storage in air. + public var config: Config { + didSet { + storage.totalCostLimit = config.totalCostLimit + storage.countLimit = config.countLimit + } + } + + /// Creates a `MemoryStorage` with a given `config`. + /// + /// - Parameter config: The config used to create the storage. It determines the max size limitation, + /// default expiration setting and more. + public init(config: Config) { + self.config = config + storage.totalCostLimit = config.totalCostLimit + storage.countLimit = config.countLimit + + cleanTimer = .scheduledTimer(withTimeInterval: config.cleanInterval, repeats: true) { [weak self] _ in + guard let self = self else { return } + self.removeExpired() + } + } + + func removeExpired() { + lock.lock() + defer { lock.unlock() } + for key in keys { + let nsKey = key as NSString + guard let object = storage.object(forKey: nsKey) else { + // This could happen if the object is moved by cache `totalCostLimit` or `countLimit` rule. + // We didn't remove the key yet until now, since we do not want to introduce additonal lock. + // See https://github.com/onevcat/Kingfisher/issues/1233 + keys.remove(key) + continue + } + if object.estimatedExpiration.isPast { + storage.removeObject(forKey: nsKey) + keys.remove(key) + } + } + } + + // Storing in memory will not throw. It is just for meeting protocol requirement and + // forwarding to no throwing method. + func store( + value: T, + forKey key: String, + expiration: StorageExpiration? = nil) throws + { + storeNoThrow(value: value, forKey: key, expiration: expiration) + } + + // The no throw version for storing value in cache. Kingfisher knows the detail so it + // could use this version to make syntax simpler internally. + func storeNoThrow( + value: T, + forKey key: String, + expiration: StorageExpiration? = nil) + { + lock.lock() + defer { lock.unlock() } + let expiration = expiration ?? config.expiration + // The expiration indicates that already expired, no need to store. + guard !expiration.isExpired else { return } + + let object = StorageObject(value, key: key, expiration: expiration) + storage.setObject(object, forKey: key as NSString, cost: value.cacheCost) + keys.insert(key) + } + + /// Use this when you actually access the memory cached item. + /// By default, this will extend the expired data for the accessed item. + /// + /// - Parameters: + /// - key: Cache Key + /// - extendingExpiration: expiration value to extend item expiration time: + /// * .none: The item expires after the original time, without extending after access. + /// * .cacheTime: The item expiration extends by the original cache time after each access. + /// * .expirationTime: The item expiration extends by the provided time after each access. + /// - Returns: cached object or nil + func value(forKey key: String, extendingExpiration: ExpirationExtending = .cacheTime) -> T? { + guard let object = storage.object(forKey: key as NSString) else { + return nil + } + if object.expired { + return nil + } + object.extendExpiration(extendingExpiration) + return object.value + } + + func isCached(forKey key: String) -> Bool { + guard let _ = value(forKey: key, extendingExpiration: .none) else { + return false + } + return true + } + + func remove(forKey key: String) throws { + lock.lock() + defer { lock.unlock() } + storage.removeObject(forKey: key as NSString) + keys.remove(key) + } + + func removeAll() throws { + lock.lock() + defer { lock.unlock() } + storage.removeAllObjects() + keys.removeAll() + } + } +} + +extension MemoryStorage { + /// Represents the config used in a `MemoryStorage`. + public struct Config { + + /// Total cost limit of the storage in bytes. + public var totalCostLimit: Int + + /// The item count limit of the memory storage. + public var countLimit: Int = .max + + /// The `StorageExpiration` used in this memory storage. Default is `.seconds(300)`, + /// means that the memory cache would expire in 5 minutes. + public var expiration: StorageExpiration = .seconds(300) + + /// The time interval between the storage do clean work for swiping expired items. + public let cleanInterval: TimeInterval + + /// Creates a config from a given `totalCostLimit` value. + /// + /// - Parameters: + /// - totalCostLimit: Total cost limit of the storage in bytes. + /// - cleanInterval: The time interval between the storage do clean work for swiping expired items. + /// Default is 120, means the auto eviction happens once per two minutes. + /// + /// - Note: + /// Other members of `MemoryStorage.Config` will use their default values when created. + public init(totalCostLimit: Int, cleanInterval: TimeInterval = 120) { + self.totalCostLimit = totalCostLimit + self.cleanInterval = cleanInterval + } + } +} + +extension MemoryStorage { + class StorageObject { + let value: T + let expiration: StorageExpiration + let key: String + + private(set) var estimatedExpiration: Date + + init(_ value: T, key: String, expiration: StorageExpiration) { + self.value = value + self.key = key + self.expiration = expiration + + self.estimatedExpiration = expiration.estimatedExpirationSinceNow + } + + func extendExpiration(_ extendingExpiration: ExpirationExtending = .cacheTime) { + switch extendingExpiration { + case .none: + return + case .cacheTime: + self.estimatedExpiration = expiration.estimatedExpirationSinceNow + case .expirationTime(let expirationTime): + self.estimatedExpiration = expirationTime.estimatedExpirationSinceNow + } + } + + var expired: Bool { + return estimatedExpiration.isPast + } + } +} diff --git b/Pods/Kingfisher/Sources/Cache/Storage.swift a/Pods/Kingfisher/Sources/Cache/Storage.swift new file mode 100644 index 0000000..fb2b423 --- /dev/null +++ a/Pods/Kingfisher/Sources/Cache/Storage.swift @@ -0,0 +1,113 @@ +// +// Storage.swift +// Kingfisher +// +// Created by Wei Wang on 2018/10/15. +// +// Copyright (c) 2019 Wei Wang +// +// 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 + +/// Constants for some time intervals +struct TimeConstants { + static let secondsInOneMinute = 60 + static let minutesInOneHour = 60 + static let hoursInOneDay = 24 + static let secondsInOneDay = 86_400 +} + +/// Represents the expiration strategy used in storage. +/// +/// - never: The item never expires. +/// - seconds: The item expires after a time duration of given seconds from now. +/// - days: The item expires after a time duration of given days from now. +/// - date: The item expires after a given date. +public enum StorageExpiration { + /// The item never expires. + case never + /// The item expires after a time duration of given seconds from now. + case seconds(TimeInterval) + /// The item expires after a time duration of given days from now. + case days(Int) + /// The item expires after a given date. + case date(Date) + /// Indicates the item is already expired. Use this to skip cache. + case expired + + func estimatedExpirationSince(_ date: Date) -> Date { + switch self { + case .never: return .distantFuture + case .seconds(let seconds): + return date.addingTimeInterval(seconds) + case .days(let days): + let duration = TimeInterval(TimeConstants.secondsInOneDay) * TimeInterval(days) + return date.addingTimeInterval(duration) + case .date(let ref): + return ref + case .expired: + return .distantPast + } + } + + var estimatedExpirationSinceNow: Date { + return estimatedExpirationSince(Date()) + } + + var isExpired: Bool { + return timeInterval <= 0 + } + + var timeInterval: TimeInterval { + switch self { + case .never: return .infinity + case .seconds(let seconds): return seconds + case .days(let days): return TimeInterval(TimeConstants.secondsInOneDay) * TimeInterval(days) + case .date(let ref): return ref.timeIntervalSinceNow + case .expired: return -(.infinity) + } + } +} + +/// Represents the expiration extending strategy used in storage to after access. +/// +/// - none: The item expires after the original time, without extending after access. +/// - cacheTime: The item expiration extends by the original cache time after each access. +/// - expirationTime: The item expiration extends by the provided time after each access. +public enum ExpirationExtending { + /// The item expires after the original time, without extending after access. + case none + /// The item expiration extends by the original cache time after each access. + case cacheTime + /// The item expiration extends by the provided time after each access. + case expirationTime(_ expiration: StorageExpiration) +} + +/// Represents types which cost in memory can be calculated. +public protocol CacheCostCalculable { + var cacheCost: Int { get } +} + +/// Represents types which can be converted to and from data. +public protocol DataTransformable { + func toData() throws -> Data + static func fromData(_ data: Data) throws -> Self + static var empty: Self { get } +} diff --git b/Pods/Kingfisher/Sources/Extensions/ImageView+Kingfisher.swift a/Pods/Kingfisher/Sources/Extensions/ImageView+Kingfisher.swift new file mode 100644 index 0000000..8066ba2 --- /dev/null +++ a/Pods/Kingfisher/Sources/Extensions/ImageView+Kingfisher.swift @@ -0,0 +1,419 @@ +// +// ImageView+Kingfisher.swift +// Kingfisher +// +// Created by Wei Wang on 15/4/6. +// +// Copyright (c) 2019 Wei Wang +// +// 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. + +#if !os(watchOS) + +#if os(macOS) +import AppKit +#else +import UIKit +#endif + +extension KingfisherWrapper where Base: KFCrossPlatformImageView { + + // MARK: Setting Image + + /// Sets an image to the image view with a `Source`. + /// + /// - Parameters: + /// - source: The `Source` object defines data information from network or a data provider. + /// - placeholder: A placeholder to show while retrieving the image from the given `resource`. + /// - options: An options set to define image setting behaviors. See `KingfisherOptionsInfo` for more. + /// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an + /// `expectedContentLength`, this block will not be called. + /// - completionHandler: Called when the image retrieved and set finished. + /// - Returns: A task represents the image downloading. + /// + /// - Note: + /// This is the easiest way to use Kingfisher to boost the image setting process from a source. Since all parameters + /// have a default value except the `source`, you can set an image from a certain URL to an image view like this: + /// + /// ``` + /// // Set image from a network source. + /// let url = URL(string: "https://example.com/image.png")! + /// imageView.kf.setImage(with: .network(url)) + /// + /// // Or set image from a data provider. + /// let provider = LocalFileImageDataProvider(fileURL: fileURL) + /// imageView.kf.setImage(with: .provider(provider)) + /// ``` + /// + /// For both `.network` and `.provider` source, there are corresponding view extension methods. So the code + /// above is equivalent to: + /// + /// ``` + /// imageView.kf.setImage(with: url) + /// imageView.kf.setImage(with: provider) + /// ``` + /// + /// Internally, this method will use `KingfisherManager` to get the source. + /// Since this method will perform UI changes, you must call it from the main thread. + /// Both `progressBlock` and `completionHandler` will be also executed in the main thread. + /// + @discardableResult + public func setImage( + with source: Source?, + placeholder: Placeholder? = nil, + options: KingfisherOptionsInfo? = nil, + progressBlock: DownloadProgressBlock? = nil, + completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? + { + var mutatingSelf = self + guard let source = source else { + mutatingSelf.placeholder = placeholder + mutatingSelf.taskIdentifier = nil + completionHandler?(.failure(KingfisherError.imageSettingError(reason: .emptySource))) + return nil + } + + var options = KingfisherParsedOptionsInfo(KingfisherManager.shared.defaultOptions + (options ?? .empty)) + let noImageOrPlaceholderSet = base.image == nil && self.placeholder == nil + if !options.keepCurrentImageWhileLoading || noImageOrPlaceholderSet { + // Always set placeholder while there is no image/placeholder yet. + mutatingSelf.placeholder = placeholder + } + + let maybeIndicator = indicator + maybeIndicator?.startAnimatingView() + + let issuedIdentifier = Source.Identifier.next() + mutatingSelf.taskIdentifier = issuedIdentifier + + if base.shouldPreloadAllAnimation() { + options.preloadAllAnimationData = true + } + + if let block = progressBlock { + options.onDataReceived = (options.onDataReceived ?? []) + [ImageLoadingProgressSideEffect(block)] + } + + if let provider = ImageProgressiveProvider(options, refresh: { image in + self.base.image = image + }) { + options.onDataReceived = (options.onDataReceived ?? []) + [provider] + } + + options.onDataReceived?.forEach { + $0.onShouldApply = { issuedIdentifier == self.taskIdentifier } + } + + let task = KingfisherManager.shared.retrieveImage( + with: source, + options: options, + downloadTaskUpdated: { mutatingSelf.imageTask = $0 }, + completionHandler: { result in + CallbackQueue.mainCurrentOrAsync.execute { + maybeIndicator?.stopAnimatingView() + guard issuedIdentifier == self.taskIdentifier else { + let reason: KingfisherError.ImageSettingErrorReason + do { + let value = try result.get() + reason = .notCurrentSourceTask(result: value, error: nil, source: source) + } catch { + reason = .notCurrentSourceTask(result: nil, error: error, source: source) + } + let error = KingfisherError.imageSettingError(reason: reason) + completionHandler?(.failure(error)) + return + } + + mutatingSelf.imageTask = nil + mutatingSelf.taskIdentifier = nil + + switch result { + case .success(let value): + guard self.needsTransition(options: options, cacheType: value.cacheType) else { + mutatingSelf.placeholder = nil + self.base.image = value.image + completionHandler?(result) + return + } + + self.makeTransition(image: value.image, transition: options.transition) { + completionHandler?(result) + } + + case .failure: + if let image = options.onFailureImage { + self.base.image = image + } + completionHandler?(result) + } + } + } + ) + mutatingSelf.imageTask = task + return task + } + + /// Sets an image to the image view with a requested resource. + /// + /// - Parameters: + /// - resource: The `Resource` object contains information about the resource. + /// - placeholder: A placeholder to show while retrieving the image from the given `resource`. + /// - options: An options set to define image setting behaviors. See `KingfisherOptionsInfo` for more. + /// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an + /// `expectedContentLength`, this block will not be called. + /// - completionHandler: Called when the image retrieved and set finished. + /// - Returns: A task represents the image downloading. + /// + /// - Note: + /// This is the easiest way to use Kingfisher to boost the image setting process from network. Since all parameters + /// have a default value except the `resource`, you can set an image from a certain URL to an image view like this: + /// + /// ``` + /// let url = URL(string: "https://example.com/image.png")! + /// imageView.kf.setImage(with: url) + /// ``` + /// + /// Internally, this method will use `KingfisherManager` to get the requested resource, from either cache + /// or network. Since this method will perform UI changes, you must call it from the main thread. + /// Both `progressBlock` and `completionHandler` will be also executed in the main thread. + /// + @discardableResult + public func setImage( + with resource: Resource?, + placeholder: Placeholder? = nil, + options: KingfisherOptionsInfo? = nil, + progressBlock: DownloadProgressBlock? = nil, + completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? + { + return setImage( + with: resource?.convertToSource(), + placeholder: placeholder, + options: options, + progressBlock: progressBlock, + completionHandler: completionHandler) + } + + /// Sets an image to the image view with a data provider. + /// + /// - Parameters: + /// - provider: The `ImageDataProvider` object contains information about the data. + /// - placeholder: A placeholder to show while retrieving the image from the given `resource`. + /// - options: An options set to define image setting behaviors. See `KingfisherOptionsInfo` for more. + /// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an + /// `expectedContentLength`, this block will not be called. + /// - completionHandler: Called when the image retrieved and set finished. + /// - Returns: A task represents the image downloading. + /// + /// Internally, this method will use `KingfisherManager` to get the image data, from either cache + /// or the data provider. Since this method will perform UI changes, you must call it from the main thread. + /// Both `progressBlock` and `completionHandler` will be also executed in the main thread. + /// + @discardableResult + public func setImage( + with provider: ImageDataProvider?, + placeholder: Placeholder? = nil, + options: KingfisherOptionsInfo? = nil, + progressBlock: DownloadProgressBlock? = nil, + completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? + { + return setImage( + with: provider.map { .provider($0) }, + placeholder: placeholder, + options: options, + progressBlock: progressBlock, + completionHandler: completionHandler) + } + + // MARK: Cancelling Downloading Task + + /// Cancels the image download task of the image view if it is running. + /// Nothing will happen if the downloading has already finished. + public func cancelDownloadTask() { + imageTask?.cancel() + } + + private func needsTransition(options: KingfisherParsedOptionsInfo, cacheType: CacheType) -> Bool { + switch options.transition { + case .none: + return false + #if !os(macOS) + default: + if options.forceTransition { return true } + if cacheType == .none { return true } + return false + #endif + } + } + + private func makeTransition(image: KFCrossPlatformImage, transition: ImageTransition, done: @escaping () -> Void) { + #if !os(macOS) + // Force hiding the indicator without transition first. + UIView.transition( + with: self.base, + duration: 0.0, + options: [], + animations: { self.indicator?.stopAnimatingView() }, + completion: { _ in + var mutatingSelf = self + mutatingSelf.placeholder = nil + UIView.transition( + with: self.base, + duration: transition.duration, + options: [transition.animationOptions, .allowUserInteraction], + animations: { transition.animations?(self.base, image) }, + completion: { finished in + transition.completion?(finished) + done() + } + ) + } + ) + #else + done() + #endif + } +} + +// MARK: - Associated Object +private var taskIdentifierKey: Void? +private var indicatorKey: Void? +private var indicatorTypeKey: Void? +private var placeholderKey: Void? +private var imageTaskKey: Void? + +extension KingfisherWrapper where Base: KFCrossPlatformImageView { + + // MARK: Properties + public private(set) var taskIdentifier: Source.Identifier.Value? { + get { + let box: Box? = getAssociatedObject(base, &taskIdentifierKey) + return box?.value + } + set { + let box = newValue.map { Box($0) } + setRetainedAssociatedObject(base, &taskIdentifierKey, box) + } + } + + /// Holds which indicator type is going to be used. + /// Default is `.none`, means no indicator will be shown while downloading. + public var indicatorType: IndicatorType { + get { + return getAssociatedObject(base, &indicatorTypeKey) ?? .none + } + + set { + switch newValue { + case .none: indicator = nil + case .activity: indicator = ActivityIndicator() + case .image(let data): indicator = ImageIndicator(imageData: data) + case .custom(let anIndicator): indicator = anIndicator + } + + setRetainedAssociatedObject(base, &indicatorTypeKey, newValue) + } + } + + /// Holds any type that conforms to the protocol `Indicator`. + /// The protocol `Indicator` has a `view` property that will be shown when loading an image. + /// It will be `nil` if `indicatorType` is `.none`. + public private(set) var indicator: Indicator? { + get { + let box: Box? = getAssociatedObject(base, &indicatorKey) + return box?.value + } + + set { + // Remove previous + if let previousIndicator = indicator { + previousIndicator.view.removeFromSuperview() + } + + // Add new + if let newIndicator = newValue { + // Set default indicator layout + let view = newIndicator.view + + base.addSubview(view) + view.translatesAutoresizingMaskIntoConstraints = false + view.centerXAnchor.constraint( + equalTo: base.centerXAnchor, constant: newIndicator.centerOffset.x).isActive = true + view.centerYAnchor.constraint( + equalTo: base.centerYAnchor, constant: newIndicator.centerOffset.y).isActive = true + + switch newIndicator.sizeStrategy(in: base) { + case .intrinsicSize: + break + case .full: + view.heightAnchor.constraint(equalTo: base.heightAnchor, constant: 0).isActive = true + view.widthAnchor.constraint(equalTo: base.widthAnchor, constant: 0).isActive = true + case .size(let size): + view.heightAnchor.constraint(equalToConstant: size.height).isActive = true + view.widthAnchor.constraint(equalToConstant: size.width).isActive = true + } + + newIndicator.view.isHidden = true + } + + // Save in associated object + // Wrap newValue with Box to workaround an issue that Swift does not recognize + // and casting protocol for associate object correctly. https://github.com/onevcat/Kingfisher/issues/872 + setRetainedAssociatedObject(base, &indicatorKey, newValue.map(Box.init)) + } + } + + private var imageTask: DownloadTask? { + get { return getAssociatedObject(base, &imageTaskKey) } + set { setRetainedAssociatedObject(base, &imageTaskKey, newValue)} + } + + /// Represents the `Placeholder` used for this image view. A `Placeholder` will be shown in the view while + /// it is downloading an image. + public private(set) var placeholder: Placeholder? { + get { return getAssociatedObject(base, &placeholderKey) } + set { + if let previousPlaceholder = placeholder { + previousPlaceholder.remove(from: base) + } + + if let newPlaceholder = newValue { + newPlaceholder.add(to: base) + } else { + base.image = nil + } + setRetainedAssociatedObject(base, &placeholderKey, newValue) + } + } +} + + +extension KFCrossPlatformImageView { + @objc func shouldPreloadAllAnimation() -> Bool { return true } +} + +extension KingfisherWrapper where Base: KFCrossPlatformImageView { + /// Gets the image URL bound to this image view. + @available(*, deprecated, message: "Use `taskIdentifier` instead to identify a setting task.") + public private(set) var webURL: URL? { + get { return nil } + set { } + } +} + +#endif diff --git b/Pods/Kingfisher/Sources/Extensions/NSButton+Kingfisher.swift a/Pods/Kingfisher/Sources/Extensions/NSButton+Kingfisher.swift new file mode 100644 index 0000000..79b9029 --- /dev/null +++ a/Pods/Kingfisher/Sources/Extensions/NSButton+Kingfisher.swift @@ -0,0 +1,355 @@ +// +// NSButton+Kingfisher.swift +// Kingfisher +// +// Created by Jie Zhang on 14/04/2016. +// +// Copyright (c) 2019 Wei Wang +// +// 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. + +#if canImport(AppKit) && !targetEnvironment(macCatalyst) + +import AppKit + +extension KingfisherWrapper where Base: NSButton { + + // MARK: Setting Image + + /// Sets an image to the button with a source. + /// + /// - Parameters: + /// - source: The `Source` object contains information about how to get the image. + /// - placeholder: A placeholder to show while retrieving the image from the given `resource`. + /// - options: An options set to define image setting behaviors. See `KingfisherOptionsInfo` for more. + /// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an + /// `expectedContentLength`, this block will not be called. + /// - completionHandler: Called when the image retrieved and set finished. + /// - Returns: A task represents the image downloading. + /// + /// - Note: + /// Internally, this method will use `KingfisherManager` to get the requested source. + /// Since this method will perform UI changes, you must call it from the main thread. + /// Both `progressBlock` and `completionHandler` will be also executed in the main thread. + /// + @discardableResult + public func setImage( + with source: Source?, + placeholder: KFCrossPlatformImage? = nil, + options: KingfisherOptionsInfo? = nil, + progressBlock: DownloadProgressBlock? = nil, + completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? + { + var mutatingSelf = self + guard let source = source else { + base.image = placeholder + mutatingSelf.taskIdentifier = nil + completionHandler?(.failure(KingfisherError.imageSettingError(reason: .emptySource))) + return nil + } + + var options = KingfisherParsedOptionsInfo(KingfisherManager.shared.defaultOptions + (options ?? .empty)) + if !options.keepCurrentImageWhileLoading { + base.image = placeholder + } + + let issuedIdentifier = Source.Identifier.next() + mutatingSelf.taskIdentifier = issuedIdentifier + + if let block = progressBlock { + options.onDataReceived = (options.onDataReceived ?? []) + [ImageLoadingProgressSideEffect(block)] + } + + if let provider = ImageProgressiveProvider(options, refresh: { image in + self.base.image = image + }) { + options.onDataReceived = (options.onDataReceived ?? []) + [provider] + } + + options.onDataReceived?.forEach { + $0.onShouldApply = { issuedIdentifier == self.taskIdentifier } + } + + let task = KingfisherManager.shared.retrieveImage( + with: source, + options: options, + downloadTaskUpdated: { mutatingSelf.imageTask = $0 }, + completionHandler: { result in + CallbackQueue.mainCurrentOrAsync.execute { + guard issuedIdentifier == self.taskIdentifier else { + let reason: KingfisherError.ImageSettingErrorReason + do { + let value = try result.get() + reason = .notCurrentSourceTask(result: value, error: nil, source: source) + } catch { + reason = .notCurrentSourceTask(result: nil, error: error, source: source) + } + let error = KingfisherError.imageSettingError(reason: reason) + completionHandler?(.failure(error)) + return + } + + mutatingSelf.imageTask = nil + mutatingSelf.taskIdentifier = nil + + switch result { + case .success(let value): + self.base.image = value.image + completionHandler?(result) + + case .failure: + if let image = options.onFailureImage { + self.base.image = image + } + completionHandler?(result) + } + } + } + ) + + mutatingSelf.imageTask = task + return task + } + + /// Sets an image to the button with a requested resource. + /// + /// - Parameters: + /// - resource: The `Resource` object contains information about the resource. + /// - placeholder: A placeholder to show while retrieving the image from the given `resource`. + /// - options: An options set to define image setting behaviors. See `KingfisherOptionsInfo` for more. + /// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an + /// `expectedContentLength`, this block will not be called. + /// - completionHandler: Called when the image retrieved and set finished. + /// - Returns: A task represents the image downloading. + /// + /// - Note: + /// Internally, this method will use `KingfisherManager` to get the requested resource, from either cache + /// or network. Since this method will perform UI changes, you must call it from the main thread. + /// Both `progressBlock` and `completionHandler` will be also executed in the main thread. + /// + @discardableResult + public func setImage( + with resource: Resource?, + placeholder: KFCrossPlatformImage? = nil, + options: KingfisherOptionsInfo? = nil, + progressBlock: DownloadProgressBlock? = nil, + completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? + { + return setImage( + with: resource?.convertToSource(), + placeholder: placeholder, + options: options, + progressBlock: progressBlock, + completionHandler: completionHandler) + } + + // MARK: Cancelling Downloading Task + + /// Cancels the image download task of the button if it is running. + /// Nothing will happen if the downloading has already finished. + public func cancelImageDownloadTask() { + imageTask?.cancel() + } + + // MARK: Setting Alternate Image + + @discardableResult + public func setAlternateImage( + with source: Source?, + placeholder: KFCrossPlatformImage? = nil, + options: KingfisherOptionsInfo? = nil, + progressBlock: DownloadProgressBlock? = nil, + completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? + { + var mutatingSelf = self + guard let source = source else { + base.alternateImage = placeholder + mutatingSelf.alternateTaskIdentifier = nil + completionHandler?(.failure(KingfisherError.imageSettingError(reason: .emptySource))) + return nil + } + + var options = KingfisherParsedOptionsInfo(KingfisherManager.shared.defaultOptions + (options ?? .empty)) + if !options.keepCurrentImageWhileLoading { + base.alternateImage = placeholder + } + + let issuedIdentifier = Source.Identifier.next() + mutatingSelf.alternateTaskIdentifier = issuedIdentifier + + if let block = progressBlock { + options.onDataReceived = (options.onDataReceived ?? []) + [ImageLoadingProgressSideEffect(block)] + } + + if let provider = ImageProgressiveProvider(options, refresh: { image in + self.base.alternateImage = image + }) { + options.onDataReceived = (options.onDataReceived ?? []) + [provider] + } + + options.onDataReceived?.forEach { + $0.onShouldApply = { issuedIdentifier == self.alternateTaskIdentifier } + } + + let task = KingfisherManager.shared.retrieveImage( + with: source, + options: options, + downloadTaskUpdated: { mutatingSelf.alternateImageTask = $0 }, + completionHandler: { result in + CallbackQueue.mainCurrentOrAsync.execute { + guard issuedIdentifier == self.alternateTaskIdentifier else { + let reason: KingfisherError.ImageSettingErrorReason + do { + let value = try result.get() + reason = .notCurrentSourceTask(result: value, error: nil, source: source) + } catch { + reason = .notCurrentSourceTask(result: nil, error: error, source: source) + } + let error = KingfisherError.imageSettingError(reason: reason) + completionHandler?(.failure(error)) + return + } + + mutatingSelf.alternateImageTask = nil + mutatingSelf.alternateTaskIdentifier = nil + + switch result { + case .success(let value): + self.base.alternateImage = value.image + completionHandler?(result) + + case .failure: + if let image = options.onFailureImage { + self.base.alternateImage = image + } + completionHandler?(result) + } + } + } + ) + + mutatingSelf.alternateImageTask = task + return task + } + + /// Sets an alternate image to the button with a requested resource. + /// + /// - Parameters: + /// - resource: The `Resource` object contains information about the resource. + /// - placeholder: A placeholder to show while retrieving the image from the given `resource`. + /// - options: An options set to define image setting behaviors. See `KingfisherOptionsInfo` for more. + /// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an + /// `expectedContentLength`, this block will not be called. + /// - completionHandler: Called when the image retrieved and set finished. + /// - Returns: A task represents the image downloading. + /// + /// - Note: + /// Internally, this method will use `KingfisherManager` to get the requested resource, from either cache + /// or network. Since this method will perform UI changes, you must call it from the main thread. + /// Both `progressBlock` and `completionHandler` will be also executed in the main thread. + /// + @discardableResult + public func setAlternateImage( + with resource: Resource?, + placeholder: KFCrossPlatformImage? = nil, + options: KingfisherOptionsInfo? = nil, + progressBlock: DownloadProgressBlock? = nil, + completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? + { + return setAlternateImage( + with: resource?.convertToSource(), + placeholder: placeholder, + options: options, + progressBlock: progressBlock, + completionHandler: completionHandler) + } + + // MARK: Cancelling Alternate Image Downloading Task + + /// Cancels the alternate image download task of the button if it is running. + /// Nothing will happen if the downloading has already finished. + public func cancelAlternateImageDownloadTask() { + alternateImageTask?.cancel() + } +} + + +// MARK: - Associated Object +private var taskIdentifierKey: Void? +private var imageTaskKey: Void? + +private var alternateTaskIdentifierKey: Void? +private var alternateImageTaskKey: Void? + +extension KingfisherWrapper where Base: NSButton { + + // MARK: Properties + + public private(set) var taskIdentifier: Source.Identifier.Value? { + get { + let box: Box? = getAssociatedObject(base, &taskIdentifierKey) + return box?.value + } + set { + let box = newValue.map { Box($0) } + setRetainedAssociatedObject(base, &taskIdentifierKey, box) + } + } + + private var imageTask: DownloadTask? { + get { return getAssociatedObject(base, &imageTaskKey) } + set { setRetainedAssociatedObject(base, &imageTaskKey, newValue)} + } + + public private(set) var alternateTaskIdentifier: Source.Identifier.Value? { + get { + let box: Box? = getAssociatedObject(base, &alternateTaskIdentifierKey) + return box?.value + } + set { + let box = newValue.map { Box($0) } + setRetainedAssociatedObject(base, &alternateTaskIdentifierKey, box) + } + } + + private var alternateImageTask: DownloadTask? { + get { return getAssociatedObject(base, &alternateImageTaskKey) } + set { setRetainedAssociatedObject(base, &alternateImageTaskKey, newValue)} + } +} + +extension KingfisherWrapper where Base: NSButton { + + /// Gets the image URL bound to this button. + @available(*, deprecated, message: "Use `taskIdentifier` instead to identify a setting task.") + public private(set) var webURL: URL? { + get { return nil } + set { } + } + + + /// Gets the image URL bound to this button. + @available(*, deprecated, message: "Use `alternateTaskIdentifier` instead to identify a setting task.") + public private(set) var alternateWebURL: URL? { + get { return nil } + set { } + } +} + +#endif diff --git b/Pods/Kingfisher/Sources/Extensions/UIButton+Kingfisher.swift a/Pods/Kingfisher/Sources/Extensions/UIButton+Kingfisher.swift new file mode 100644 index 0000000..3fda9a3 --- /dev/null +++ a/Pods/Kingfisher/Sources/Extensions/UIButton+Kingfisher.swift @@ -0,0 +1,398 @@ +// +// UIButton+Kingfisher.swift +// Kingfisher +// +// Created by Wei Wang on 15/4/13. +// +// Copyright (c) 2019 Wei Wang +// +// 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. + +#if !os(watchOS) + +#if canImport(UIKit) +import UIKit + +extension KingfisherWrapper where Base: UIButton { + + // MARK: Setting Image + /// Sets an image to the button for a specified state with a source. + /// + /// - Parameters: + /// - source: The `Source` object contains information about the image. + /// - state: The button state to which the image should be set. + /// - placeholder: A placeholder to show while retrieving the image from the given `resource`. + /// - options: An options set to define image setting behaviors. See `KingfisherOptionsInfo` for more. + /// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an + /// `expectedContentLength`, this block will not be called. + /// - completionHandler: Called when the image retrieved and set finished. + /// - Returns: A task represents the image downloading. + /// + /// - Note: + /// Internally, this method will use `KingfisherManager` to get the requested source, from either cache + /// or network. Since this method will perform UI changes, you must call it from the main thread. + /// Both `progressBlock` and `completionHandler` will be also executed in the main thread. + /// + @discardableResult + public func setImage( + with source: Source?, + for state: UIControl.State, + placeholder: UIImage? = nil, + options: KingfisherOptionsInfo? = nil, + progressBlock: DownloadProgressBlock? = nil, + completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? + { + guard let source = source else { + base.setImage(placeholder, for: state) + setTaskIdentifier(nil, for: state) + completionHandler?(.failure(KingfisherError.imageSettingError(reason: .emptySource))) + return nil + } + + var options = KingfisherParsedOptionsInfo(KingfisherManager.shared.defaultOptions + (options ?? .empty)) + if !options.keepCurrentImageWhileLoading { + base.setImage(placeholder, for: state) + } + + var mutatingSelf = self + let issuedIdentifier = Source.Identifier.next() + setTaskIdentifier(issuedIdentifier, for: state) + + if let block = progressBlock { + options.onDataReceived = (options.onDataReceived ?? []) + [ImageLoadingProgressSideEffect(block)] + } + + if let provider = ImageProgressiveProvider(options, refresh: { image in + self.base.setImage(image, for: state) + }) { + options.onDataReceived = (options.onDataReceived ?? []) + [provider] + } + + options.onDataReceived?.forEach { + $0.onShouldApply = { issuedIdentifier == self.taskIdentifier(for: state) } + } + + let task = KingfisherManager.shared.retrieveImage( + with: source, + options: options, + downloadTaskUpdated: { mutatingSelf.imageTask = $0 }, + completionHandler: { result in + CallbackQueue.mainCurrentOrAsync.execute { + guard issuedIdentifier == self.taskIdentifier(for: state) else { + let reason: KingfisherError.ImageSettingErrorReason + do { + let value = try result.get() + reason = .notCurrentSourceTask(result: value, error: nil, source: source) + } catch { + reason = .notCurrentSourceTask(result: nil, error: error, source: source) + } + let error = KingfisherError.imageSettingError(reason: reason) + completionHandler?(.failure(error)) + return + } + + mutatingSelf.imageTask = nil + mutatingSelf.setTaskIdentifier(nil, for: state) + + switch result { + case .success(let value): + self.base.setImage(value.image, for: state) + completionHandler?(result) + + case .failure: + if let image = options.onFailureImage { + self.base.setImage(image, for: state) + } + completionHandler?(result) + } + } + } + ) + + mutatingSelf.imageTask = task + return task + } + + /// Sets an image to the button for a specified state with a requested resource. + /// + /// - Parameters: + /// - resource: The `Resource` object contains information about the resource. + /// - state: The button state to which the image should be set. + /// - placeholder: A placeholder to show while retrieving the image from the given `resource`. + /// - options: An options set to define image setting behaviors. See `KingfisherOptionsInfo` for more. + /// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an + /// `expectedContentLength`, this block will not be called. + /// - completionHandler: Called when the image retrieved and set finished. + /// - Returns: A task represents the image downloading. + /// + /// - Note: + /// Internally, this method will use `KingfisherManager` to get the requested resource, from either cache + /// or network. Since this method will perform UI changes, you must call it from the main thread. + /// Both `progressBlock` and `completionHandler` will be also executed in the main thread. + /// + @discardableResult + public func setImage( + with resource: Resource?, + for state: UIControl.State, + placeholder: UIImage? = nil, + options: KingfisherOptionsInfo? = nil, + progressBlock: DownloadProgressBlock? = nil, + completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? + { + return setImage( + with: resource?.convertToSource(), + for: state, + placeholder: placeholder, + options: options, + progressBlock: progressBlock, + completionHandler: completionHandler) + } + + // MARK: Cancelling Downloading Task + + /// Cancels the image download task of the button if it is running. + /// Nothing will happen if the downloading has already finished. + public func cancelImageDownloadTask() { + imageTask?.cancel() + } + + // MARK: Setting Background Image + + /// Sets a background image to the button for a specified state with a source. + /// + /// - Parameters: + /// - source: The `Source` object contains information about the image. + /// - state: The button state to which the image should be set. + /// - placeholder: A placeholder to show while retrieving the image from the given `resource`. + /// - options: An options set to define image setting behaviors. See `KingfisherOptionsInfo` for more. + /// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an + /// `expectedContentLength`, this block will not be called. + /// - completionHandler: Called when the image retrieved and set finished. + /// - Returns: A task represents the image downloading. + /// + /// - Note: + /// Internally, this method will use `KingfisherManager` to get the requested source + /// Since this method will perform UI changes, you must call it from the main thread. + /// Both `progressBlock` and `completionHandler` will be also executed in the main thread. + /// + @discardableResult + public func setBackgroundImage( + with source: Source?, + for state: UIControl.State, + placeholder: UIImage? = nil, + options: KingfisherOptionsInfo? = nil, + progressBlock: DownloadProgressBlock? = nil, + completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? + { + guard let source = source else { + base.setBackgroundImage(placeholder, for: state) + setBackgroundTaskIdentifier(nil, for: state) + completionHandler?(.failure(KingfisherError.imageSettingError(reason: .emptySource))) + return nil + } + + var options = KingfisherParsedOptionsInfo(KingfisherManager.shared.defaultOptions + (options ?? .empty)) + if !options.keepCurrentImageWhileLoading { + base.setBackgroundImage(placeholder, for: state) + } + + var mutatingSelf = self + let issuedIdentifier = Source.Identifier.next() + setBackgroundTaskIdentifier(issuedIdentifier, for: state) + + if let block = progressBlock { + options.onDataReceived = (options.onDataReceived ?? []) + [ImageLoadingProgressSideEffect(block)] + } + + if let provider = ImageProgressiveProvider(options, refresh: { image in + self.base.setBackgroundImage(image, for: state) + }) { + options.onDataReceived = (options.onDataReceived ?? []) + [provider] + } + + options.onDataReceived?.forEach { + $0.onShouldApply = { issuedIdentifier == self.backgroundTaskIdentifier(for: state) } + } + + let task = KingfisherManager.shared.retrieveImage( + with: source, + options: options, + downloadTaskUpdated: { mutatingSelf.backgroundImageTask = $0 }, + completionHandler: { result in + CallbackQueue.mainCurrentOrAsync.execute { + guard issuedIdentifier == self.backgroundTaskIdentifier(for: state) else { + let reason: KingfisherError.ImageSettingErrorReason + do { + let value = try result.get() + reason = .notCurrentSourceTask(result: value, error: nil, source: source) + } catch { + reason = .notCurrentSourceTask(result: nil, error: error, source: source) + } + let error = KingfisherError.imageSettingError(reason: reason) + completionHandler?(.failure(error)) + return + } + + mutatingSelf.backgroundImageTask = nil + mutatingSelf.setBackgroundTaskIdentifier(nil, for: state) + + switch result { + case .success(let value): + self.base.setBackgroundImage(value.image, for: state) + completionHandler?(result) + + case .failure: + if let image = options.onFailureImage { + self.base.setBackgroundImage(image, for: state) + } + completionHandler?(result) + } + } + } + ) + + mutatingSelf.backgroundImageTask = task + return task + } + + /// Sets a background image to the button for a specified state with a requested resource. + /// + /// - Parameters: + /// - resource: The `Resource` object contains information about the resource. + /// - state: The button state to which the image should be set. + /// - placeholder: A placeholder to show while retrieving the image from the given `resource`. + /// - options: An options set to define image setting behaviors. See `KingfisherOptionsInfo` for more. + /// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an + /// `expectedContentLength`, this block will not be called. + /// - completionHandler: Called when the image retrieved and set finished. + /// - Returns: A task represents the image downloading. + /// + /// - Note: + /// Internally, this method will use `KingfisherManager` to get the requested resource, from either cache + /// or network. Since this method will perform UI changes, you must call it from the main thread. + /// Both `progressBlock` and `completionHandler` will be also executed in the main thread. + /// + @discardableResult + public func setBackgroundImage( + with resource: Resource?, + for state: UIControl.State, + placeholder: UIImage? = nil, + options: KingfisherOptionsInfo? = nil, + progressBlock: DownloadProgressBlock? = nil, + completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? + { + return setBackgroundImage( + with: resource?.convertToSource(), + for: state, + placeholder: placeholder, + options: options, + progressBlock: progressBlock, + completionHandler: completionHandler) + } + + // MARK: Cancelling Background Downloading Task + + /// Cancels the background image download task of the button if it is running. + /// Nothing will happen if the downloading has already finished. + public func cancelBackgroundImageDownloadTask() { + backgroundImageTask?.cancel() + } +} + +// MARK: - Associated Object +private var taskIdentifierKey: Void? +private var imageTaskKey: Void? + +// MARK: Properties +extension KingfisherWrapper where Base: UIButton { + + private typealias TaskIdentifier = Box<[UInt: Source.Identifier.Value]> + + public func taskIdentifier(for state: UIControl.State) -> Source.Identifier.Value? { + return taskIdentifierInfo.value[state.rawValue] + } + + private func setTaskIdentifier(_ identifier: Source.Identifier.Value?, for state: UIControl.State) { + taskIdentifierInfo.value[state.rawValue] = identifier + } + + private var taskIdentifierInfo: TaskIdentifier { + return getAssociatedObject(base, &taskIdentifierKey) ?? { + setRetainedAssociatedObject(base, &taskIdentifierKey, $0) + return $0 + } (TaskIdentifier([:])) + } + + private var imageTask: DownloadTask? { + get { return getAssociatedObject(base, &imageTaskKey) } + set { setRetainedAssociatedObject(base, &imageTaskKey, newValue)} + } +} + + +private var backgroundTaskIdentifierKey: Void? +private var backgroundImageTaskKey: Void? + +// MARK: Background Properties +extension KingfisherWrapper where Base: UIButton { + + public func backgroundTaskIdentifier(for state: UIControl.State) -> Source.Identifier.Value? { + return backgroundTaskIdentifierInfo.value[state.rawValue] + } + + private func setBackgroundTaskIdentifier(_ identifier: Source.Identifier.Value?, for state: UIControl.State) { + backgroundTaskIdentifierInfo.value[state.rawValue] = identifier + } + + private var backgroundTaskIdentifierInfo: TaskIdentifier { + return getAssociatedObject(base, &backgroundTaskIdentifierKey) ?? { + setRetainedAssociatedObject(base, &backgroundTaskIdentifierKey, $0) + return $0 + } (TaskIdentifier([:])) + } + + private var backgroundImageTask: DownloadTask? { + get { return getAssociatedObject(base, &backgroundImageTaskKey) } + mutating set { setRetainedAssociatedObject(base, &backgroundImageTaskKey, newValue) } + } +} + +extension KingfisherWrapper where Base: UIButton { + + /// Gets the image URL of this button for a specified state. + /// + /// - Parameter state: The state that uses the specified image. + /// - Returns: Current URL for image. + @available(*, deprecated, message: "Use `taskIdentifier` instead to identify a setting task.") + public func webURL(for state: UIControl.State) -> URL? { + return nil + } + + /// Gets the background image URL of this button for a specified state. + /// + /// - Parameter state: The state that uses the specified background image. + /// - Returns: Current URL for image. + @available(*, deprecated, message: "Use `backgroundTaskIdentifier` instead to identify a setting task.") + public func backgroundWebURL(for state: UIControl.State) -> URL? { + return nil + } +} +#endif + +#endif diff --git b/Pods/Kingfisher/Sources/Extensions/WKInterfaceImage+Kingfisher.swift a/Pods/Kingfisher/Sources/Extensions/WKInterfaceImage+Kingfisher.swift new file mode 100644 index 0000000..328facb --- /dev/null +++ a/Pods/Kingfisher/Sources/Extensions/WKInterfaceImage+Kingfisher.swift @@ -0,0 +1,205 @@ +// +// WKInterfaceImage+Kingfisher.swift +// Kingfisher +// +// Created by Rodrigo Borges Soares on 04/05/18. +// +// Copyright (c) 2019 Wei Wang +// +// 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. + +#if canImport(WatchKit) + +import WatchKit + +extension KingfisherWrapper where Base: WKInterfaceImage { + + // MARK: Setting Image + + /// Sets an image to the image view with a source. + /// + /// - Parameters: + /// - source: The `Source` object contains information about the image. + /// - placeholder: A placeholder to show while retrieving the image from the given `resource`. + /// - options: An options set to define image setting behaviors. See `KingfisherOptionsInfo` for more. + /// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an + /// `expectedContentLength`, this block will not be called. + /// - completionHandler: Called when the image retrieved and set finished. + /// - Returns: A task represents the image downloading. + /// + /// - Note: + /// + /// Internally, this method will use `KingfisherManager` to get the requested source + /// Since this method will perform UI changes, you must call it from the main thread. + /// Both `progressBlock` and `completionHandler` will be also executed in the main thread. + /// + @discardableResult + public func setImage( + with source: Source?, + placeholder: KFCrossPlatformImage? = nil, + options: KingfisherOptionsInfo? = nil, + progressBlock: DownloadProgressBlock? = nil, + completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? + { + var mutatingSelf = self + guard let source = source else { + base.setImage(placeholder) + mutatingSelf.taskIdentifier = nil + completionHandler?(.failure(KingfisherError.imageSettingError(reason: .emptySource))) + return nil + } + + var options = KingfisherParsedOptionsInfo(KingfisherManager.shared.defaultOptions + (options ?? .empty)) + if !options.keepCurrentImageWhileLoading { + base.setImage(placeholder) + } + + let issuedIdentifier = Source.Identifier.next() + mutatingSelf.taskIdentifier = issuedIdentifier + + if let block = progressBlock { + options.onDataReceived = (options.onDataReceived ?? []) + [ImageLoadingProgressSideEffect(block)] + } + + if let provider = ImageProgressiveProvider(options, refresh: { image in + self.base.setImage(image) + }) { + options.onDataReceived = (options.onDataReceived ?? []) + [provider] + } + + options.onDataReceived?.forEach { + $0.onShouldApply = { issuedIdentifier == self.taskIdentifier } + } + + let task = KingfisherManager.shared.retrieveImage( + with: source, + options: options, + downloadTaskUpdated: { mutatingSelf.imageTask = $0 }, + completionHandler: { result in + CallbackQueue.mainCurrentOrAsync.execute { + guard issuedIdentifier == self.taskIdentifier else { + let reason: KingfisherError.ImageSettingErrorReason + do { + let value = try result.get() + reason = .notCurrentSourceTask(result: value, error: nil, source: source) + } catch { + reason = .notCurrentSourceTask(result: nil, error: error, source: source) + } + let error = KingfisherError.imageSettingError(reason: reason) + completionHandler?(.failure(error)) + return + } + + mutatingSelf.imageTask = nil + mutatingSelf.taskIdentifier = nil + + switch result { + case .success(let value): + self.base.setImage(value.image) + completionHandler?(result) + + case .failure: + if let image = options.onFailureImage { + self.base.setImage(image) + } + completionHandler?(result) + } + } + } + ) + + mutatingSelf.imageTask = task + return task + } + + /// Sets an image to the image view with a requested resource. + /// + /// - Parameters: + /// - resource: The `Resource` object contains information about the image. + /// - placeholder: A placeholder to show while retrieving the image from the given `resource`. + /// - options: An options set to define image setting behaviors. See `KingfisherOptionsInfo` for more. + /// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an + /// `expectedContentLength`, this block will not be called. + /// - completionHandler: Called when the image retrieved and set finished. + /// - Returns: A task represents the image downloading. + /// + /// - Note: + /// + /// Internally, this method will use `KingfisherManager` to get the requested resource, from either cache + /// or network. Since this method will perform UI changes, you must call it from the main thread. + /// Both `progressBlock` and `completionHandler` will be also executed in the main thread. + /// + @discardableResult + public func setImage( + with resource: Resource?, + placeholder: KFCrossPlatformImage? = nil, + options: KingfisherOptionsInfo? = nil, + progressBlock: DownloadProgressBlock? = nil, + completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? + { + return setImage( + with: resource?.convertToSource(), + placeholder: placeholder, + options: options, + progressBlock: progressBlock, + completionHandler: completionHandler) + } + + // MARK: Cancelling Image + + /// Cancel the image download task bounded to the image view if it is running. + /// Nothing will happen if the downloading has already finished. + public func cancelDownloadTask() { + imageTask?.cancel() + } +} + +private var taskIdentifierKey: Void? +private var imageTaskKey: Void? + +// MARK: Properties +extension KingfisherWrapper where Base: WKInterfaceImage { + + public private(set) var taskIdentifier: Source.Identifier.Value? { + get { + let box: Box? = getAssociatedObject(base, &taskIdentifierKey) + return box?.value + } + set { + let box = newValue.map { Box($0) } + setRetainedAssociatedObject(base, &taskIdentifierKey, box) + } + } + + private var imageTask: DownloadTask? { + get { return getAssociatedObject(base, &imageTaskKey) } + set { setRetainedAssociatedObject(base, &imageTaskKey, newValue)} + } +} + +extension KingfisherWrapper where Base: WKInterfaceImage { + /// Gets the image URL bound to this image view. + @available(*, deprecated, message: "Use `taskIdentifier` instead to identify a setting task.") + public private(set) var webURL: URL? { + get { return nil } + set { } + } +} + +#endif diff --git b/Pods/Kingfisher/Sources/General/Deprecated.swift a/Pods/Kingfisher/Sources/General/Deprecated.swift new file mode 100644 index 0000000..e0724b5 --- /dev/null +++ a/Pods/Kingfisher/Sources/General/Deprecated.swift @@ -0,0 +1,681 @@ +// +// Deprecated.swift +// Kingfisher +// +// Created by onevcat on 2018/09/28. +// +// Copyright (c) 2019 Wei Wang +// +// 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. + +#if canImport(AppKit) && !targetEnvironment(macCatalyst) +import AppKit +#elseif canImport(UIKit) +import UIKit +#endif + +// MARK: - Deprecated +extension KingfisherWrapper where Base: KFCrossPlatformImage { + @available(*, deprecated, message: + "Will be removed soon. Pass parameters with `ImageCreatingOptions`, use `image(with:options:)` instead.") + public static func image( + data: Data, + scale: CGFloat, + preloadAllAnimationData: Bool, + onlyFirstFrame: Bool) -> KFCrossPlatformImage? + { + let options = ImageCreatingOptions( + scale: scale, + duration: 0.0, + preloadAll: preloadAllAnimationData, + onlyFirstFrame: onlyFirstFrame) + return KingfisherWrapper.image(data: data, options: options) + } + + @available(*, deprecated, message: + "Will be removed soon. Pass parameters with `ImageCreatingOptions`, use `animatedImage(with:options:)` instead.") + public static func animated( + with data: Data, + scale: CGFloat = 1.0, + duration: TimeInterval = 0.0, + preloadAll: Bool, + onlyFirstFrame: Bool = false) -> KFCrossPlatformImage? + { + let options = ImageCreatingOptions( + scale: scale, duration: duration, preloadAll: preloadAll, onlyFirstFrame: onlyFirstFrame) + return animatedImage(data: data, options: options) + } +} + +@available(*, deprecated, message: "Will be removed soon. Use `Result` based callback instead") +public typealias CompletionHandler = + ((_ image: KFCrossPlatformImage?, _ error: NSError?, _ cacheType: CacheType, _ imageURL: URL?) -> Void) + +@available(*, deprecated, message: "Will be removed soon. Use `Result` based callback instead") +public typealias ImageDownloaderCompletionHandler = + ((_ image: KFCrossPlatformImage?, _ error: NSError?, _ url: URL?, _ originalData: Data?) -> Void) + +// MARK: - Deprecated +@available(*, deprecated, message: "Will be removed soon. Use `DownloadTask` to cancel a task.") +extension RetrieveImageTask { + @available(*, deprecated, message: "RetrieveImageTask.empty will be removed soon. Use `nil` to represent a no task.") + public static let empty = RetrieveImageTask() +} + +// MARK: - Deprecated +extension KingfisherManager { + /// Get an image with resource. + /// If `.empty` is used as `options`, Kingfisher will seek the image in memory and disk first. + /// If not found, it will download the image at `resource.downloadURL` and cache it with `resource.cacheKey`. + /// These default behaviors could be adjusted by passing different options. See `KingfisherOptions` for more. + /// + /// - Parameters: + /// - resource: Resource object contains information such as `cacheKey` and `downloadURL`. + /// - options: A dictionary could control some behaviors. See `KingfisherOptionsInfo` for more. + /// - progressBlock: Called every time downloaded data changed. This could be used as a progress UI. + /// - completionHandler: Called when the whole retrieving process finished. + /// - Returns: A `RetrieveImageTask` task object. You can use this object to cancel the task. + @available(*, deprecated, message: "Use `Result` based callback instead.") + @discardableResult + public func retrieveImage(with resource: Resource, + options: KingfisherOptionsInfo?, + progressBlock: DownloadProgressBlock?, + completionHandler: CompletionHandler?) -> DownloadTask? + { + return retrieveImage(with: resource, options: options, progressBlock: progressBlock) { + result in + switch result { + case .success(let value): completionHandler?(value.image, nil, value.cacheType, value.source.url) + case .failure(let error): completionHandler?(nil, error as NSError, .none, resource.downloadURL) + } + } + } +} + +// MARK: - Deprecated +extension ImageDownloader { + @available(*, deprecated, message: "Use `Result` based callback instead.") + @discardableResult + open func downloadImage(with url: URL, + retrieveImageTask: RetrieveImageTask? = nil, + options: KingfisherOptionsInfo? = nil, + progressBlock: ImageDownloaderProgressBlock? = nil, + completionHandler: ImageDownloaderCompletionHandler?) -> DownloadTask? + { + return downloadImage(with: url, options: options, progressBlock: progressBlock) { + result in + switch result { + case .success(let value): completionHandler?(value.image, nil, value.url, value.originalData) + case .failure(let error): completionHandler?(nil, error as NSError, nil, nil) + } + } + } +} + +@available(*, deprecated, message: "RetrieveImageDownloadTask is removed. Use `DownloadTask` to cancel a task.") +public struct RetrieveImageDownloadTask { +} + +@available(*, deprecated, message: "RetrieveImageTask is removed. Use `DownloadTask` to cancel a task.") +public final class RetrieveImageTask { +} + +@available(*, deprecated, message: "Use `DownloadProgressBlock` instead.", renamed: "DownloadProgressBlock") +public typealias ImageDownloaderProgressBlock = DownloadProgressBlock + +#if !os(watchOS) +// MARK: - Deprecated +extension KingfisherWrapper where Base: KFCrossPlatformImageView { + @available(*, deprecated, message: "Use `Result` based callback instead.") + @discardableResult + public func setImage(with resource: Resource?, + placeholder: Placeholder? = nil, + options: KingfisherOptionsInfo? = nil, + progressBlock: DownloadProgressBlock? = nil, + completionHandler: CompletionHandler?) -> DownloadTask? + { + return setImage(with: resource, placeholder: placeholder, options: options, progressBlock: progressBlock) { + result in + switch result { + case .success(let value): + completionHandler?(value.image, nil, value.cacheType, value.source.url) + case .failure(let error): + completionHandler?(nil, error as NSError, .none, nil) + } + } + } +} +#endif + +#if canImport(UIKit) && !os(watchOS) +// MARK: - Deprecated +extension KingfisherWrapper where Base: UIButton { + @available(*, deprecated, message: "Use `Result` based callback instead.") + @discardableResult + public func setImage( + with resource: Resource?, + for state: UIControl.State, + placeholder: UIImage? = nil, + options: KingfisherOptionsInfo? = nil, + progressBlock: DownloadProgressBlock? = nil, + completionHandler: CompletionHandler?) -> DownloadTask? + { + return setImage( + with: resource, + for: state, + placeholder: placeholder, + options: options, + progressBlock: progressBlock) + { + result in + switch result { + case .success(let value): + completionHandler?(value.image, nil, value.cacheType, value.source.url) + case .failure(let error): + completionHandler?(nil, error as NSError, .none, nil) + } + } + } + + @available(*, deprecated, message: "Use `Result` based callback instead.") + @discardableResult + public func setBackgroundImage( + with resource: Resource?, + for state: UIControl.State, + placeholder: UIImage? = nil, + options: KingfisherOptionsInfo? = nil, + progressBlock: DownloadProgressBlock? = nil, + completionHandler: CompletionHandler?) -> DownloadTask? + { + return setBackgroundImage( + with: resource, + for: state, + placeholder: placeholder, + options: options, + progressBlock: progressBlock) + { + result in + switch result { + case .success(let value): + completionHandler?(value.image, nil, value.cacheType, value.source.url) + case .failure(let error): + completionHandler?(nil, error as NSError, .none, nil) + } + } + } +} +#endif + +#if os(watchOS) +import WatchKit +// MARK: - Deprecated +extension KingfisherWrapper where Base: WKInterfaceImage { + @available(*, deprecated, message: "Use `Result` based callback instead.") + @discardableResult + public func setImage(_ resource: Resource?, + placeholder: KFCrossPlatformImage? = nil, + options: KingfisherOptionsInfo? = nil, + progressBlock: DownloadProgressBlock? = nil, + completionHandler: CompletionHandler?) -> DownloadTask? + { + return setImage( + with: resource, + placeholder: placeholder, + options: options, + progressBlock: progressBlock) + { + result in + switch result { + case .success(let value): + completionHandler?(value.image, nil, value.cacheType, value.source.url) + case .failure(let error): + completionHandler?(nil, error as NSError, .none, nil) + } + } + } +} +#endif + +#if os(macOS) +// MARK: - Deprecated +extension KingfisherWrapper where Base: NSButton { + @discardableResult + @available(*, deprecated, message: "Use `Result` based callback instead.") + public func setImage(with resource: Resource?, + placeholder: KFCrossPlatformImage? = nil, + options: KingfisherOptionsInfo? = nil, + progressBlock: DownloadProgressBlock? = nil, + completionHandler: CompletionHandler?) -> DownloadTask? + { + return setImage( + with: resource, + placeholder: placeholder, + options: options, + progressBlock: progressBlock) + { + result in + switch result { + case .success(let value): + completionHandler?(value.image, nil, value.cacheType, value.source.url) + case .failure(let error): + completionHandler?(nil, error as NSError, .none, nil) + } + } + } + + @discardableResult + @available(*, deprecated, message: "Use `Result` based callback instead.") + public func setAlternateImage(with resource: Resource?, + placeholder: KFCrossPlatformImage? = nil, + options: KingfisherOptionsInfo? = nil, + progressBlock: DownloadProgressBlock? = nil, + completionHandler: CompletionHandler?) -> DownloadTask? + { + return setAlternateImage( + with: resource, + placeholder: placeholder, + options: options, + progressBlock: progressBlock) + { + result in + switch result { + case .success(let value): + completionHandler?(value.image, nil, value.cacheType, value.source.url) + case .failure(let error): + completionHandler?(nil, error as NSError, .none, nil) + } + } + } +} +#endif + +// MARK: - Deprecated +extension ImageCache { + /// The largest cache cost of memory cache. The total cost is pixel count of + /// all cached images in memory. + /// Default is unlimited. Memory cache will be purged automatically when a + /// memory warning notification is received. + @available(*, deprecated, message: "Use `memoryStorage.config.totalCostLimit` instead.", + renamed: "memoryStorage.config.totalCostLimit") + open var maxMemoryCost: Int { + get { return memoryStorage.config.totalCostLimit } + set { memoryStorage.config.totalCostLimit = newValue } + } + + /// The default DiskCachePathClosure + @available(*, deprecated, message: "Not needed anymore.") + public final class func defaultDiskCachePathClosure(path: String?, cacheName: String) -> String { + let dstPath = path ?? NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first! + return (dstPath as NSString).appendingPathComponent(cacheName) + } + + /// The default file extension appended to cached files. + @available(*, deprecated, message: "Use `diskStorage.config.pathExtension` instead.", + renamed: "diskStorage.config.pathExtension") + open var pathExtension: String? { + get { return diskStorage.config.pathExtension } + set { diskStorage.config.pathExtension = newValue } + } + + ///The disk cache location. + @available(*, deprecated, message: "Use `diskStorage.directoryURL.absoluteString` instead.", + renamed: "diskStorage.directoryURL.absoluteString") + public var diskCachePath: String { + return diskStorage.directoryURL.absoluteString + } + + /// The largest disk size can be taken for the cache. It is the total + /// allocated size of cached files in bytes. + /// Default is no limit. + @available(*, deprecated, message: "Use `diskStorage.config.sizeLimit` instead.", + renamed: "diskStorage.config.sizeLimit") + open var maxDiskCacheSize: UInt { + get { return UInt(diskStorage.config.sizeLimit) } + set { diskStorage.config.sizeLimit = newValue } + } + + @available(*, deprecated, message: "Use `diskStorage.cacheFileURL(forKey:).path` instead.", + renamed: "diskStorage.cacheFileURL(forKey:)") + open func cachePath(forComputedKey key: String) -> String { + return diskStorage.cacheFileURL(forKey: key).path + } + + /** + Get an image for a key from disk. + + - parameter key: Key for the image. + - parameter options: Options of retrieving image. If you need to retrieve an image which was + stored with a specified `ImageProcessor`, pass the processor in the option too. + + - returns: The image object if it is cached, or `nil` if there is no such key in the cache. + */ + @available(*, deprecated, + message: "Use `Result` based `retrieveImageInDiskCache(forKey:options:callbackQueue:completionHandler:)` instead.", + renamed: "retrieveImageInDiskCache(forKey:options:callbackQueue:completionHandler:)") + open func retrieveImageInDiskCache(forKey key: String, options: KingfisherOptionsInfo? = nil) -> KFCrossPlatformImage? { + let options = KingfisherParsedOptionsInfo(options ?? .empty) + let computedKey = key.computedKey(with: options.processor.identifier) + do { + if let data = try diskStorage.value(forKey: computedKey, extendingExpiration: options.diskCacheAccessExtendingExpiration) { + return options.cacheSerializer.image(with: data, options: options) + } + } catch {} + return nil + } + + @available(*, deprecated, + message: "Use `Result` based `retrieveImage(forKey:options:callbackQueue:completionHandler:)` instead.", + renamed: "retrieveImage(forKey:options:callbackQueue:completionHandler:)") + open func retrieveImage(forKey key: String, + options: KingfisherOptionsInfo?, + completionHandler: ((KFCrossPlatformImage?, CacheType) -> Void)?) + { + retrieveImage( + forKey: key, + options: options, + callbackQueue: .dispatch((options ?? .empty).callbackDispatchQueue)) + { + result in + do { + let value = try result.get() + completionHandler?(value.image, value.cacheType) + } catch { + completionHandler?(nil, .none) + } + } + } + + /// The longest time duration in second of the cache being stored in disk. + /// Default is 1 week (60 * 60 * 24 * 7 seconds). + /// Setting this to a negative value will make the disk cache never expiring. + @available(*, deprecated, message: "Deprecated. Use `diskStorage.config.expiration` instead") + open var maxCachePeriodInSecond: TimeInterval { + get { return diskStorage.config.expiration.timeInterval } + set { diskStorage.config.expiration = newValue < 0 ? .never : .seconds(newValue) } + } + + @available(*, deprecated, message: "Use `Result` based callback instead.") + open func store(_ image: KFCrossPlatformImage, + original: Data? = nil, + forKey key: String, + processorIdentifier identifier: String = "", + cacheSerializer serializer: CacheSerializer = DefaultCacheSerializer.default, + toDisk: Bool = true, + completionHandler: (() -> Void)?) + { + store( + image, + original: original, + forKey: key, + processorIdentifier: identifier, + cacheSerializer: serializer, + toDisk: toDisk) + { + _ in + completionHandler?() + } + } + + @available(*, deprecated, message: "Use the `Result`-based `calculateDiskStorageSize` instead.") + open func calculateDiskCacheSize(completion handler: @escaping ((_ size: UInt) -> Void)) { + calculateDiskStorageSize { result in + let size: UInt? = try? result.get() + handler(size ?? 0) + } + } +} + +// MARK: - Deprecated +extension Collection where Iterator.Element == KingfisherOptionsInfoItem { + /// The queue of callbacks should happen from Kingfisher. + @available(*, deprecated, message: "Use `callbackQueue` instead.", renamed: "callbackQueue") + public var callbackDispatchQueue: DispatchQueue { + return KingfisherParsedOptionsInfo(Array(self)).callbackQueue.queue + } +} + +/// Error domain of Kingfisher +@available(*, deprecated, message: "Use `KingfisherError.domain` instead.", renamed: "KingfisherError.domain") +public let KingfisherErrorDomain = "com.onevcat.Kingfisher.Error" + +/// Key will be used in the `userInfo` of `.invalidStatusCode` +@available(*, unavailable, +message: "Use `.invalidHTTPStatusCode` or `isInvalidResponseStatusCode` of `KingfisherError` instead for the status code.") +public let KingfisherErrorStatusCodeKey = "statusCode" + +// MARK: - Deprecated +extension Collection where Iterator.Element == KingfisherOptionsInfoItem { + /// The target `ImageCache` which is used. + @available(*, deprecated, + message: "Create a `KingfisherParsedOptionsInfo` from `KingfisherOptionsInfo` and use `targetCache` instead.") + public var targetCache: ImageCache? { + return KingfisherParsedOptionsInfo(Array(self)).targetCache + } + + /// The original `ImageCache` which is used. + @available(*, deprecated, + message: "Create a `KingfisherParsedOptionsInfo` from `KingfisherOptionsInfo` and use `originalCache` instead.") + public var originalCache: ImageCache? { + return KingfisherParsedOptionsInfo(Array(self)).originalCache + } + + /// The `ImageDownloader` which is specified. + @available(*, deprecated, + message: "Create a `KingfisherParsedOptionsInfo` from `KingfisherOptionsInfo` and use `downloader` instead.") + public var downloader: ImageDownloader? { + return KingfisherParsedOptionsInfo(Array(self)).downloader + } + + /// Member for animation transition when using UIImageView. + @available(*, deprecated, + message: "Create a `KingfisherParsedOptionsInfo` from `KingfisherOptionsInfo` and use `transition` instead.") + public var transition: ImageTransition { + return KingfisherParsedOptionsInfo(Array(self)).transition + } + + /// A `Float` value set as the priority of image download task. The value for it should be + /// between 0.0~1.0. + @available(*, deprecated, + message: "Create a `KingfisherParsedOptionsInfo` from `KingfisherOptionsInfo` and use `downloadPriority` instead.") + public var downloadPriority: Float { + return KingfisherParsedOptionsInfo(Array(self)).downloadPriority + } + + /// Whether an image will be always downloaded again or not. + @available(*, deprecated, + message: "Create a `KingfisherParsedOptionsInfo` from `KingfisherOptionsInfo` and use `forceRefresh` instead.") + public var forceRefresh: Bool { + return KingfisherParsedOptionsInfo(Array(self)).forceRefresh + } + + /// Whether an image should be got only from memory cache or download. + @available(*, deprecated, + message: "Create a `KingfisherParsedOptionsInfo` from `KingfisherOptionsInfo` and use `fromMemoryCacheOrRefresh` instead.") + public var fromMemoryCacheOrRefresh: Bool { + return KingfisherParsedOptionsInfo(Array(self)).fromMemoryCacheOrRefresh + } + + /// Whether the transition should always happen or not. + @available(*, deprecated, + message: "Create a `KingfisherParsedOptionsInfo` from `KingfisherOptionsInfo` and use `forceTransition` instead.") + public var forceTransition: Bool { + return KingfisherParsedOptionsInfo(Array(self)).forceTransition + } + + /// Whether cache the image only in memory or not. + @available(*, deprecated, + message: "Create a `KingfisherParsedOptionsInfo` from `KingfisherOptionsInfo` and use `cacheMemoryOnly` instead.") + public var cacheMemoryOnly: Bool { + return KingfisherParsedOptionsInfo(Array(self)).cacheMemoryOnly + } + + /// Whether the caching operation will be waited or not. + @available(*, deprecated, + message: "Create a `KingfisherParsedOptionsInfo` from `KingfisherOptionsInfo` and use `waitForCache` instead.") + public var waitForCache: Bool { + return KingfisherParsedOptionsInfo(Array(self)).waitForCache + } + + /// Whether only load the images from cache or not. + @available(*, deprecated, + message: "Create a `KingfisherParsedOptionsInfo` from `KingfisherOptionsInfo` and use `onlyFromCache` instead.") + public var onlyFromCache: Bool { + return KingfisherParsedOptionsInfo(Array(self)).onlyFromCache + } + + /// Whether the image should be decoded in background or not. + @available(*, deprecated, + message: "Create a `KingfisherParsedOptionsInfo` from `KingfisherOptionsInfo` and use `backgroundDecode` instead.") + public var backgroundDecode: Bool { + return KingfisherParsedOptionsInfo(Array(self)).backgroundDecode + } + + /// Whether the image data should be all loaded at once if it is an animated image. + @available(*, deprecated, + message: "Create a `KingfisherParsedOptionsInfo` from `KingfisherOptionsInfo` and use `preloadAllAnimationData` instead.") + public var preloadAllAnimationData: Bool { + return KingfisherParsedOptionsInfo(Array(self)).preloadAllAnimationData + } + + /// The `CallbackQueue` on which completion handler should be invoked. + /// If not set in the options, `.mainCurrentOrAsync` will be used. + @available(*, deprecated, + message: "Create a `KingfisherParsedOptionsInfo` from `KingfisherOptionsInfo` and use `callbackQueue` instead.") + public var callbackQueue: CallbackQueue { + return KingfisherParsedOptionsInfo(Array(self)).callbackQueue + } + + /// The scale factor which should be used for the image. + @available(*, deprecated, + message: "Create a `KingfisherParsedOptionsInfo` from `KingfisherOptionsInfo` and use `scaleFactor` instead.") + public var scaleFactor: CGFloat { + return KingfisherParsedOptionsInfo(Array(self)).scaleFactor + } + + /// The `ImageDownloadRequestModifier` will be used before sending a download request. + @available(*, deprecated, + message: "Create a `KingfisherParsedOptionsInfo` from `KingfisherOptionsInfo` and use `requestModifier` instead.") + public var modifier: ImageDownloadRequestModifier? { + return KingfisherParsedOptionsInfo(Array(self)).requestModifier + } + + /// `ImageProcessor` for processing when the downloading finishes. + @available(*, deprecated, + message: "Create a `KingfisherParsedOptionsInfo` from `KingfisherOptionsInfo` and use `processor` instead.") + public var processor: ImageProcessor { + return KingfisherParsedOptionsInfo(Array(self)).processor + } + + /// `ImageModifier` for modifying right before the image is displayed. + @available(*, deprecated, + message: "Create a `KingfisherParsedOptionsInfo` from `KingfisherOptionsInfo` and use `imageModifier` instead.") + public var imageModifier: ImageModifier? { + return KingfisherParsedOptionsInfo(Array(self)).imageModifier + } + + /// `CacheSerializer` to convert image to data for storing in cache. + @available(*, deprecated, + message: "Create a `KingfisherParsedOptionsInfo` from `KingfisherOptionsInfo` and use `cacheSerializer` instead.") + public var cacheSerializer: CacheSerializer { + return KingfisherParsedOptionsInfo(Array(self)).cacheSerializer + } + + /// Keep the existing image while setting another image to an image view. + /// Or the placeholder will be used while downloading. + @available(*, deprecated, + message: "Create a `KingfisherParsedOptionsInfo` from `KingfisherOptionsInfo` and use `keepCurrentImageWhileLoading` instead.") + public var keepCurrentImageWhileLoading: Bool { + return KingfisherParsedOptionsInfo(Array(self)).keepCurrentImageWhileLoading + } + + /// Whether the options contains `.onlyLoadFirstFrame`. + @available(*, deprecated, + message: "Create a `KingfisherParsedOptionsInfo` from `KingfisherOptionsInfo` and use `onlyLoadFirstFrame` instead.") + public var onlyLoadFirstFrame: Bool { + return KingfisherParsedOptionsInfo(Array(self)).onlyLoadFirstFrame + } + + /// Whether the options contains `.cacheOriginalImage`. + @available(*, deprecated, + message: "Create a `KingfisherParsedOptionsInfo` from `KingfisherOptionsInfo` and use `cacheOriginalImage` instead.") + public var cacheOriginalImage: Bool { + return KingfisherParsedOptionsInfo(Array(self)).cacheOriginalImage + } + + /// The image which should be used when download image request fails. + @available(*, deprecated, + message: "Create a `KingfisherParsedOptionsInfo` from `KingfisherOptionsInfo` and use `onFailureImage` instead.") + public var onFailureImage: Optional { + return KingfisherParsedOptionsInfo(Array(self)).onFailureImage + } + + /// Whether the `ImagePrefetcher` should load images to memory in an aggressive way or not. + @available(*, deprecated, + message: "Create a `KingfisherParsedOptionsInfo` from `KingfisherOptionsInfo` and use `alsoPrefetchToMemory` instead.") + public var alsoPrefetchToMemory: Bool { + return KingfisherParsedOptionsInfo(Array(self)).alsoPrefetchToMemory + } + + /// Whether the disk storage file loading should happen in a synchronous behavior or not. + @available(*, deprecated, + message: "Create a `KingfisherParsedOptionsInfo` from `KingfisherOptionsInfo` and use `loadDiskFileSynchronously` instead.") + public var loadDiskFileSynchronously: Bool { + return KingfisherParsedOptionsInfo(Array(self)).loadDiskFileSynchronously + } +} + +/// The default modifier. +/// It does nothing and returns the image as is. +@available(*, deprecated, message: "Use `nil` in KingfisherOptionsInfo to indicate no modifier.") +public struct DefaultImageModifier: ImageModifier { + + /// A default `DefaultImageModifier` which can be used everywhere. + public static let `default` = DefaultImageModifier() + private init() {} + + /// Modifies an input `Image`. See `ImageModifier` protocol for more. + public func modify(_ image: KFCrossPlatformImage) -> KFCrossPlatformImage { return image } +} + + +#if os(macOS) +@available(*, deprecated, message: "Use `KFCrossPlatformImage` instead.") +public typealias Image = KFCrossPlatformImage +@available(*, deprecated, message: "Use `KFCrossPlatformView` instead.") +public typealias View = KFCrossPlatformView +@available(*, deprecated, message: "Use `KFCrossPlatformColor` instead.") +public typealias Color = KFCrossPlatformColor +@available(*, deprecated, message: "Use `KFCrossPlatformImageView` instead.") +public typealias ImageView = KFCrossPlatformImageView +@available(*, deprecated, message: "Use `KFCrossPlatformButton` instead.") +public typealias Button = KFCrossPlatformButton +#else +@available(*, deprecated, message: "Use `KFCrossPlatformImage` instead.") +public typealias Image = KFCrossPlatformImage +@available(*, deprecated, message: "Use `KFCrossPlatformColor` instead.") +public typealias Color = KFCrossPlatformColor + #if !os(watchOS) + @available(*, deprecated, message: "Use `KFCrossPlatformImageView` instead.") + public typealias ImageView = KFCrossPlatformImageView + @available(*, deprecated, message: "Use `KFCrossPlatformView` instead.") + public typealias View = KFCrossPlatformView + @available(*, deprecated, message: "Use `KFCrossPlatformButton` instead.") + public typealias Button = KFCrossPlatformButton + #endif +#endif diff --git b/Pods/Kingfisher/Sources/General/ImageSource/ImageDataProvider.swift a/Pods/Kingfisher/Sources/General/ImageSource/ImageDataProvider.swift new file mode 100644 index 0000000..f303107 --- /dev/null +++ a/Pods/Kingfisher/Sources/General/ImageSource/ImageDataProvider.swift @@ -0,0 +1,157 @@ +// +// ImageDataProvider.swift +// Kingfisher +// +// Created by onevcat on 2018/11/13. +// +// Copyright (c) 2019 Wei Wang +// +// 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 + +/// Represents a data provider to provide image data to Kingfisher when setting with +/// `Source.provider` source. Compared to `Source.network` member, it gives a chance +/// to load some image data in your own way, as long as you can provide the data +/// representation for the image. +public protocol ImageDataProvider { + + /// The key used in cache. + var cacheKey: String { get } + + /// Provides the data which represents image. Kingfisher uses the data you pass in the + /// handler to process images and caches it for later use. + /// + /// - Parameter handler: The handler you should call when you prepared your data. + /// If the data is loaded successfully, call the handler with + /// a `.success` with the data associated. Otherwise, call it + /// with a `.failure` and pass the error. + /// + /// - Note: + /// If the `handler` is called with a `.failure` with error, a `dataProviderError` of + /// `ImageSettingErrorReason` will be finally thrown out to you as the `KingfisherError` + /// from the framework. + func data(handler: @escaping (Result) -> Void) + + /// The content URL represents this provider, if exists. + var contentURL: URL? { get } +} + +public extension ImageDataProvider { + var contentURL: URL? { return nil } +} + +/// Represents an image data provider for loading from a local file URL on disk. +/// Uses this type for adding a disk image to Kingfisher. Compared to loading it +/// directly, you can get benefit of using Kingfisher's extension methods, as well +/// as applying `ImageProcessor`s and storing the image to `ImageCache` of Kingfisher. +public struct LocalFileImageDataProvider: ImageDataProvider { + + // MARK: Public Properties + + /// The file URL from which the image be loaded. + public let fileURL: URL + + // MARK: Initializers + + /// Creates an image data provider by supplying the target local file URL. + /// + /// - Parameters: + /// - fileURL: The file URL from which the image be loaded. + /// - cacheKey: The key is used for caching the image data. By default, + /// the `absoluteString` of `fileURL` is used. + public init(fileURL: URL, cacheKey: String? = nil) { + self.fileURL = fileURL + self.cacheKey = cacheKey ?? fileURL.absoluteString + } + + // MARK: Protocol Conforming + + /// The key used in cache. + public var cacheKey: String + + public func data(handler: (Result) -> Void) { + handler(Result(catching: { try Data(contentsOf: fileURL) })) + } + + /// The URL of the local file on the disk. + public var contentURL: URL? { + return fileURL + } +} + +/// Represents an image data provider for loading image from a given Base64 encoded string. +public struct Base64ImageDataProvider: ImageDataProvider { + + // MARK: Public Properties + /// The encoded Base64 string for the image. + public let base64String: String + + // MARK: Initializers + + /// Creates an image data provider by supplying the Base64 encoded string. + /// + /// - Parameters: + /// - base64String: The Base64 encoded string for an image. + /// - cacheKey: The key is used for caching the image data. You need a different key for any different image. + public init(base64String: String, cacheKey: String) { + self.base64String = base64String + self.cacheKey = cacheKey + } + + // MARK: Protocol Conforming + + /// The key used in cache. + public var cacheKey: String + + public func data(handler: (Result) -> Void) { + let data = Data(base64Encoded: base64String)! + handler(.success(data)) + } +} + +/// Represents an image data provider for a raw data object. +public struct RawImageDataProvider: ImageDataProvider { + + // MARK: Public Properties + + /// The raw data object to provide to Kingfisher image loader. + public let data: Data + + // MARK: Initializers + + /// Creates an image data provider by the given raw `data` value and a `cacheKey` be used in Kingfisher cache. + /// + /// - Parameters: + /// - data: The raw data reprensents an image. + /// - cacheKey: The key is used for caching the image data. You need a different key for any different image. + public init(data: Data, cacheKey: String) { + self.data = data + self.cacheKey = cacheKey + } + + // MARK: Protocol Conforming + + /// The key used in cache. + public var cacheKey: String + + public func data(handler: @escaping (Result) -> Void) { + handler(.success(data)) + } +} diff --git b/Pods/Kingfisher/Sources/General/ImageSource/Resource.swift a/Pods/Kingfisher/Sources/General/ImageSource/Resource.swift new file mode 100644 index 0000000..8514384 --- /dev/null +++ a/Pods/Kingfisher/Sources/General/ImageSource/Resource.swift @@ -0,0 +1,86 @@ +// +// Resource.swift +// Kingfisher +// +// Created by Wei Wang on 15/4/6. +// +// Copyright (c) 2019 Wei Wang +// +// 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 + +/// Represents an image resource at a certain url and a given cache key. +/// Kingfisher will use a `Resource` to download a resource from network and cache it with the cache key when +/// using `Source.network` as its image setting source. +public protocol Resource { + + /// The key used in cache. + var cacheKey: String { get } + + /// The target image URL. + var downloadURL: URL { get } +} + +extension Resource { + + /// Converts `self` to a valid `Source` based on its `downloadURL` scheme. A `.provider` with + /// `LocalFileImageDataProvider` associated will be returned if the URL points to a local file. Otherwise, + /// `.network` is returned. + public func convertToSource() -> Source { + return downloadURL.isFileURL ? + .provider(LocalFileImageDataProvider(fileURL: downloadURL, cacheKey: cacheKey)) : + .network(self) + } +} + +/// ImageResource is a simple combination of `downloadURL` and `cacheKey`. +/// When passed to image view set methods, Kingfisher will try to download the target +/// image from the `downloadURL`, and then store it with the `cacheKey` as the key in cache. +public struct ImageResource: Resource { + + // MARK: - Initializers + + /// Creates an image resource. + /// + /// - Parameters: + /// - downloadURL: The target image URL from where the image can be downloaded. + /// - cacheKey: The cache key. If `nil`, Kingfisher will use the `absoluteString` of `downloadURL` as the key. + /// Default is `nil`. + public init(downloadURL: URL, cacheKey: String? = nil) { + self.downloadURL = downloadURL + self.cacheKey = cacheKey ?? downloadURL.absoluteString + } + + // MARK: Protocol Conforming + + /// The key used in cache. + public let cacheKey: String + + /// The target image URL. + public let downloadURL: URL +} + +/// URL conforms to `Resource` in Kingfisher. +/// The `absoluteString` of this URL is used as `cacheKey`. And the URL itself will be used as `downloadURL`. +/// If you need customize the url and/or cache key, use `ImageResource` instead. +extension URL: Resource { + public var cacheKey: String { return absoluteString } + public var downloadURL: URL { return self } +} diff --git b/Pods/Kingfisher/Sources/General/ImageSource/Source.swift a/Pods/Kingfisher/Sources/General/ImageSource/Source.swift new file mode 100644 index 0000000..79680ab --- /dev/null +++ a/Pods/Kingfisher/Sources/General/ImageSource/Source.swift @@ -0,0 +1,97 @@ +// +// Source.swift +// Kingfisher +// +// Created by onevcat on 2018/11/17. +// +// Copyright (c) 2019 Wei Wang +// +// 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 + +/// Represents an image setting source for Kingfisher methods. +/// +/// A `Source` value indicates the way how the target image can be retrieved and cached. +/// +/// - network: The target image should be got from network remotely. The associated `Resource` +/// value defines detail information like image URL and cache key. +/// - provider: The target image should be provided in a data format. Normally, it can be an image +/// from local storage or in any other encoding format (like Base64). +public enum Source { + + /// Represents the source task identifier when setting an image to a view with extension methods. + public enum Identifier { + + /// The underlying value type of source identifier. + public typealias Value = UInt + static var current: Value = 0 + static func next() -> Value { + current += 1 + return current + } + } + + // MARK: Member Cases + + /// The target image should be got from network remotely. The associated `Resource` + /// value defines detail information like image URL and cache key. + case network(Resource) + + /// The target image should be provided in a data format. Normally, it can be an image + /// from local storage or in any other encoding format (like Base64). + case provider(ImageDataProvider) + + // MARK: Getting Properties + + /// The cache key defined for this source value. + public var cacheKey: String { + switch self { + case .network(let resource): return resource.cacheKey + case .provider(let provider): return provider.cacheKey + } + } + + /// The URL defined for this source value. + /// + /// For a `.network` source, it is the `downloadURL` of associated `Resource` instance. + /// For a `.provider` value, it is always `nil`. + public var url: URL? { + switch self { + case .network(let resource): return resource.downloadURL + case .provider(let provider): return provider.contentURL + } + } +} + +extension Source { + var asResource: Resource? { + guard case .network(let resource) = self else { + return nil + } + return resource + } + + var asProvider: ImageDataProvider? { + guard case .provider(let provider) = self else { + return nil + } + return provider + } +} diff --git b/Pods/Kingfisher/Sources/General/Kingfisher.swift a/Pods/Kingfisher/Sources/General/Kingfisher.swift new file mode 100644 index 0000000..8143531 --- /dev/null +++ a/Pods/Kingfisher/Sources/General/Kingfisher.swift @@ -0,0 +1,89 @@ +// +// Kingfisher.swift +// Kingfisher +// +// Created by Wei Wang on 16/9/14. +// +// Copyright (c) 2019 Wei Wang +// +// 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 +import ImageIO + +#if os(macOS) +import AppKit +public typealias KFCrossPlatformImage = NSImage +public typealias KFCrossPlatformView = NSView +public typealias KFCrossPlatformColor = NSColor +public typealias KFCrossPlatformImageView = NSImageView +public typealias KFCrossPlatformButton = NSButton +#else +import UIKit +public typealias KFCrossPlatformImage = UIImage +public typealias KFCrossPlatformColor = UIColor +#if !os(watchOS) +public typealias KFCrossPlatformImageView = UIImageView +public typealias KFCrossPlatformView = UIView +public typealias KFCrossPlatformButton = UIButton +#else +import WatchKit +#endif +#endif + +/// Wrapper for Kingfisher compatible types. This type provides an extension point for +/// connivence methods in Kingfisher. +public struct KingfisherWrapper { + public let base: Base + public init(_ base: Base) { + self.base = base + } +} + +/// Represents an object type that is compatible with Kingfisher. You can use `kf` property to get a +/// value in the namespace of Kingfisher. +public protocol KingfisherCompatible: AnyObject { } + +/// Represents a value type that is compatible with Kingfisher. You can use `kf` property to get a +/// value in the namespace of Kingfisher. +public protocol KingfisherCompatibleValue {} + +extension KingfisherCompatible { + /// Gets a namespace holder for Kingfisher compatible types. + public var kf: KingfisherWrapper { + get { return KingfisherWrapper(self) } + set { } + } +} + +extension KingfisherCompatibleValue { + /// Gets a namespace holder for Kingfisher compatible types. + public var kf: KingfisherWrapper { + get { return KingfisherWrapper(self) } + set { } + } +} + +extension KFCrossPlatformImage: KingfisherCompatible { } +#if !os(watchOS) +extension KFCrossPlatformImageView: KingfisherCompatible { } +extension KFCrossPlatformButton: KingfisherCompatible { } +#else +extension WKInterfaceImage: KingfisherCompatible { } +#endif diff --git b/Pods/Kingfisher/Sources/General/KingfisherError.swift a/Pods/Kingfisher/Sources/General/KingfisherError.swift new file mode 100644 index 0000000..a3262f4 --- /dev/null +++ a/Pods/Kingfisher/Sources/General/KingfisherError.swift @@ -0,0 +1,437 @@ +// +// KingfisherError.swift +// Kingfisher +// +// Created by onevcat on 2018/09/26. +// +// Copyright (c) 2019 Wei Wang +// +// 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 Never {} + +/// Represents all the errors which can happen in Kingfisher framework. +/// Kingfisher related methods always throw a `KingfisherError` or invoke the callback with `KingfisherError` +/// as its error type. To handle errors from Kingfisher, you switch over the error to get a reason catalog, +/// then switch over the reason to know error detail. +public enum KingfisherError: Error { + + // MARK: Error Reason Types + + /// Represents the error reason during networking request phase. + /// + /// - emptyRequest: The request is empty. Code 1001. + /// - invalidURL: The URL of request is invalid. Code 1002. + /// - taskCancelled: The downloading task is cancelled by user. Code 1003. + public enum RequestErrorReason { + + /// The request is empty. Code 1001. + case emptyRequest + + /// The URL of request is invalid. Code 1002. + /// - request: The request is tend to be sent but its URL is invalid. + case invalidURL(request: URLRequest) + + /// The downloading task is cancelled by user. Code 1003. + /// - task: The session data task which is cancelled. + /// - token: The cancel token which is used for cancelling the task. + case taskCancelled(task: SessionDataTask, token: SessionDataTask.CancelToken) + } + + /// Represents the error reason during networking response phase. + /// + /// - invalidURLResponse: The response is not a valid URL response. Code 2001. + /// - invalidHTTPStatusCode: The response contains an invalid HTTP status code. Code 2002. + /// - URLSessionError: An error happens in the system URL session. Code 2003. + /// - dataModifyingFailed: Data modifying fails on returning a valid data. Code 2004. + /// - noURLResponse: The task is done but no URL response found. Code 2005. + public enum ResponseErrorReason { + + /// The response is not a valid URL response. Code 2001. + /// - response: The received invalid URL response. + /// The response is expected to be an HTTP response, but it is not. + case invalidURLResponse(response: URLResponse) + + /// The response contains an invalid HTTP status code. Code 2002. + /// - Note: + /// By default, status code 200..<400 is recognized as valid. You can override + /// this behavior by conforming to the `ImageDownloaderDelegate`. + /// - response: The received response. + case invalidHTTPStatusCode(response: HTTPURLResponse) + + /// An error happens in the system URL session. Code 2003. + /// - error: The underlying URLSession error object. + case URLSessionError(error: Error) + + /// Data modifying fails on returning a valid data. Code 2004. + /// - task: The failed task. + case dataModifyingFailed(task: SessionDataTask) + + /// The task is done but no URL response found. Code 2005. + /// - task: The failed task. + case noURLResponse(task: SessionDataTask) + } + + /// Represents the error reason during Kingfisher caching system. + /// + /// - fileEnumeratorCreationFailed: Cannot create a file enumerator for a certain disk URL. Code 3001. + /// - invalidFileEnumeratorContent: Cannot get correct file contents from a file enumerator. Code 3002. + /// - invalidURLResource: The file at target URL exists, but its URL resource is unavailable. Code 3003. + /// - cannotLoadDataFromDisk: The file at target URL exists, but the data cannot be loaded from it. Code 3004. + /// - cannotCreateDirectory: Cannot create a folder at a given path. Code 3005. + /// - imageNotExisting: The requested image does not exist in cache. Code 3006. + /// - cannotConvertToData: Cannot convert an object to data for storing. Code 3007. + /// - cannotSerializeImage: Cannot serialize an image to data for storing. Code 3008. + /// - cannotCreateCacheFile: Cannot create the cache file at a certain fileURL under a key. Code 3009. + /// - cannotSetCacheFileAttribute: Cannot set file attributes to a cached file. Code 3010. + public enum CacheErrorReason { + + /// Cannot create a file enumerator for a certain disk URL. Code 3001. + /// - url: The target disk URL from which the file enumerator should be created. + case fileEnumeratorCreationFailed(url: URL) + + /// Cannot get correct file contents from a file enumerator. Code 3002. + /// - url: The target disk URL from which the content of a file enumerator should be got. + case invalidFileEnumeratorContent(url: URL) + + /// The file at target URL exists, but its URL resource is unavailable. Code 3003. + /// - error: The underlying error thrown by file manager. + /// - key: The key used to getting the resource from cache. + /// - url: The disk URL where the target cached file exists. + case invalidURLResource(error: Error, key: String, url: URL) + + /// The file at target URL exists, but the data cannot be loaded from it. Code 3004. + /// - url: The disk URL where the target cached file exists. + /// - error: The underlying error which describes why this error happens. + case cannotLoadDataFromDisk(url: URL, error: Error) + + /// Cannot create a folder at a given path. Code 3005. + /// - path: The disk path where the directory creating operation fails. + /// - error: The underlying error which describes why this error happens. + case cannotCreateDirectory(path: String, error: Error) + + /// The requested image does not exist in cache. Code 3006. + /// - key: Key of the requested image in cache. + case imageNotExisting(key: String) + + /// Cannot convert an object to data for storing. Code 3007. + /// - object: The object which needs be convert to data. + case cannotConvertToData(object: Any, error: Error) + + /// Cannot serialize an image to data for storing. Code 3008. + /// - image: The input image needs to be serialized to cache. + /// - original: The original image data, if exists. + /// - serializer: The `CacheSerializer` used for the image serializing. + case cannotSerializeImage(image: KFCrossPlatformImage?, original: Data?, serializer: CacheSerializer) + + /// Cannot create the cache file at a certain fileURL under a key. Code 3009. + /// - fileURL: The url where the cache file should be created. + /// - key: The cache key used for the cache. When caching a file through `KingfisherManager` and Kingfisher's + /// extension method, it is the resolved cache key based on your input `Source` and the image processors. + /// - data: The data to be cached. + /// - error: The underlying error originally thrown by Foundation when writing the `data` to the disk file at + /// `fileURL`. + case cannotCreateCacheFile(fileURL: URL, key: String, data: Data, error: Error) + + /// Cannot set file attributes to a cached file. Code 3010. + /// - filePath: The path of target cache file. + /// - attributes: The file attribute to be set to the target file. + /// - error: The underlying error originally thrown by Foundation when setting the `attributes` to the disk + /// file at `filePath`. + case cannotSetCacheFileAttribute(filePath: String, attributes: [FileAttributeKey : Any], error: Error) + } + + + /// Represents the error reason during image processing phase. + /// + /// - processingFailed: Image processing fails. There is no valid output image from the processor. Code 4001. + public enum ProcessorErrorReason { + /// Image processing fails. There is no valid output image from the processor. Code 4001. + /// - processor: The `ImageProcessor` used to process the image or its data in `item`. + /// - item: The image or its data content. + case processingFailed(processor: ImageProcessor, item: ImageProcessItem) + } + + /// Represents the error reason during image setting in a view related class. + /// + /// - emptySource: The input resource is empty or `nil`. Code 5001. + /// - notCurrentSourceTask: The source task is finished, but it is not the one expected now. Code 5002. + /// - dataProviderError: An error happens during getting data from an `ImageDataProvider`. Code 5003. + public enum ImageSettingErrorReason { + + /// The input resource is empty or `nil`. Code 5001. + case emptySource + + /// The resource task is finished, but it is not the one expected now. This usually happens when you set another + /// resource on the view without cancelling the current on-going one. The previous setting task will fail with + /// this `.notCurrentSourceTask` error when a result got, regardless of it being successful or not for that task. + /// The result of this original task is contained in the associated value. + /// Code 5002. + /// - result: The `RetrieveImageResult` if the source task is finished without problem. `nil` if an error + /// happens. + /// - error: The `Error` if an issue happens during image setting task. `nil` if the task finishes without + /// problem. + /// - source: The original source value of the task. + case notCurrentSourceTask(result: RetrieveImageResult?, error: Error?, source: Source) + + /// An error happens during getting data from an `ImageDataProvider`. Code 5003. + case dataProviderError(provider: ImageDataProvider, error: Error) + + /// No more alternative `Source` can be used in current loading process. It means that the + /// `.alternativeSources` are used and Kingfisher tried to recovery from the original error, but still + /// fails for all the given alternative sources. The associated value holds all the errors encountered during + /// the load process, including the original source loading error and all the alternative sources errors. + /// Code 5004. + case alternativeSourcesExhausted([PropagationError]) + } + + // MARK: Member Cases + + /// Represents the error reason during networking request phase. + case requestError(reason: RequestErrorReason) + /// Represents the error reason during networking response phase. + case responseError(reason: ResponseErrorReason) + /// Represents the error reason during Kingfisher caching system. + case cacheError(reason: CacheErrorReason) + /// Represents the error reason during image processing phase. + case processorError(reason: ProcessorErrorReason) + /// Represents the error reason during image setting in a view related class. + case imageSettingError(reason: ImageSettingErrorReason) + + // MARK: Helper Properties & Methods + + /// Helper property to check whether this error is a `RequestErrorReason.taskCancelled` or not. + public var isTaskCancelled: Bool { + if case .requestError(reason: .taskCancelled) = self { + return true + } + return false + } + + /// Helper method to check whether this error is a `ResponseErrorReason.invalidHTTPStatusCode` and the + /// associated value is a given status code. + /// + /// - Parameter code: The given status code. + /// - Returns: If `self` is a `ResponseErrorReason.invalidHTTPStatusCode` error + /// and its status code equals to `code`, `true` is returned. Otherwise, `false`. + public func isInvalidResponseStatusCode(_ code: Int) -> Bool { + if case .responseError(reason: .invalidHTTPStatusCode(let response)) = self { + return response.statusCode == code + } + return false + } + + public var isInvalidResponseStatusCode: Bool { + if case .responseError(reason: .invalidHTTPStatusCode) = self { + return true + } + return false + } + + /// Helper property to check whether this error is a `ImageSettingErrorReason.notCurrentSourceTask` or not. + /// When a new image setting task starts while the old one is still running, the new task identifier will be + /// set and the old one is overwritten. A `.notCurrentSourceTask` error will be raised when the old task finishes + /// to let you know the setting process finishes with a certain result, but the image view or button is not set. + public var isNotCurrentTask: Bool { + if case .imageSettingError(reason: .notCurrentSourceTask(_, _, _)) = self { + return true + } + return false + } +} + +// MARK: - LocalizedError Conforming +extension KingfisherError: LocalizedError { + + /// A localized message describing what error occurred. + public var errorDescription: String? { + switch self { + case .requestError(let reason): return reason.errorDescription + case .responseError(let reason): return reason.errorDescription + case .cacheError(let reason): return reason.errorDescription + case .processorError(let reason): return reason.errorDescription + case .imageSettingError(let reason): return reason.errorDescription + } + } +} + + +// MARK: - CustomNSError Conforming +extension KingfisherError: CustomNSError { + + /// The error domain of `KingfisherError`. All errors from Kingfisher is under this domain. + public static let domain = "com.onevcat.Kingfisher.Error" + + /// The error code within the given domain. + public var errorCode: Int { + switch self { + case .requestError(let reason): return reason.errorCode + case .responseError(let reason): return reason.errorCode + case .cacheError(let reason): return reason.errorCode + case .processorError(let reason): return reason.errorCode + case .imageSettingError(let reason): return reason.errorCode + } + } +} + +extension KingfisherError.RequestErrorReason { + var errorDescription: String? { + switch self { + case .emptyRequest: + return "The request is empty or `nil`." + case .invalidURL(let request): + return "The request contains an invalid or empty URL. Request: \(request)." + case .taskCancelled(let task, let token): + return "The session task was cancelled. Task: \(task), cancel token: \(token)." + } + } + + var errorCode: Int { + switch self { + case .emptyRequest: return 1001 + case .invalidURL: return 1002 + case .taskCancelled: return 1003 + } + } +} + +extension KingfisherError.ResponseErrorReason { + var errorDescription: String? { + switch self { + case .invalidURLResponse(let response): + return "The URL response is invalid: \(response)" + case .invalidHTTPStatusCode(let response): + return "The HTTP status code in response is invalid. Code: \(response.statusCode), response: \(response)." + case .URLSessionError(let error): + return "A URL session error happened. The underlying error: \(error)" + case .dataModifyingFailed(let task): + return "The data modifying delegate returned `nil` for the downloaded data. Task: \(task)." + case .noURLResponse(let task): + return "No URL response received. Task: \(task)," + } + } + + var errorCode: Int { + switch self { + case .invalidURLResponse: return 2001 + case .invalidHTTPStatusCode: return 2002 + case .URLSessionError: return 2003 + case .dataModifyingFailed: return 2004 + case .noURLResponse: return 2005 + } + } +} + +extension KingfisherError.CacheErrorReason { + var errorDescription: String? { + switch self { + case .fileEnumeratorCreationFailed(let url): + return "Cannot create file enumerator for URL: \(url)." + case .invalidFileEnumeratorContent(let url): + return "Cannot get contents from the file enumerator at URL: \(url)." + case .invalidURLResource(let error, let key, let url): + return "Cannot get URL resource values or data for the given URL: \(url). " + + "Cache key: \(key). Underlying error: \(error)" + case .cannotLoadDataFromDisk(let url, let error): + return "Cannot load data from disk at URL: \(url). Underlying error: \(error)" + case .cannotCreateDirectory(let path, let error): + return "Cannot create directory at given path: Path: \(path). Underlying error: \(error)" + case .imageNotExisting(let key): + return "The image is not in cache, but you requires it should only be " + + "from cache by enabling the `.onlyFromCache` option. Key: \(key)." + case .cannotConvertToData(let object, let error): + return "Cannot convert the input object to a `Data` object when storing it to disk cache. " + + "Object: \(object). Underlying error: \(error)" + case .cannotSerializeImage(let image, let originalData, let serializer): + return "Cannot serialize an image due to the cache serializer returning `nil`. " + + "Image: \(String(describing:image)), original data: \(String(describing: originalData)), " + + "serializer: \(serializer)." + case .cannotCreateCacheFile(let fileURL, let key, let data, let error): + return "Cannot create cache file at url: \(fileURL), key: \(key), data length: \(data.count). " + + "Underlying foundation error: \(error)." + case .cannotSetCacheFileAttribute(let filePath, let attributes, let error): + return "Cannot set file attribute for the cache file at path: \(filePath), attributes: \(attributes)." + + "Underlying foundation error: \(error)." + } + } + + var errorCode: Int { + switch self { + case .fileEnumeratorCreationFailed: return 3001 + case .invalidFileEnumeratorContent: return 3002 + case .invalidURLResource: return 3003 + case .cannotLoadDataFromDisk: return 3004 + case .cannotCreateDirectory: return 3005 + case .imageNotExisting: return 3006 + case .cannotConvertToData: return 3007 + case .cannotSerializeImage: return 3008 + case .cannotCreateCacheFile: return 3009 + case .cannotSetCacheFileAttribute: return 3010 + } + } +} + +extension KingfisherError.ProcessorErrorReason { + var errorDescription: String? { + switch self { + case .processingFailed(let processor, let item): + return "Processing image failed. Processor: \(processor). Processing item: \(item)." + } + } + + var errorCode: Int { + switch self { + case .processingFailed: return 4001 + } + } +} + +extension KingfisherError.ImageSettingErrorReason { + var errorDescription: String? { + switch self { + case .emptySource: + return "The input resource is empty." + case .notCurrentSourceTask(let result, let error, let resource): + if let result = result { + return "Retrieving resource succeeded, but this source is " + + "not the one currently expected. Result: \(result). Resource: \(resource)." + } else if let error = error { + return "Retrieving resource failed, and this resource is " + + "not the one currently expected. Error: \(error). Resource: \(resource)." + } else { + return nil + } + case .dataProviderError(let provider, let error): + return "Image data provider fails to provide data. Provider: \(provider), error: \(error)" + case .alternativeSourcesExhausted(let errors): + return "Image setting from alternaive sources failed: \(errors)" + } + } + + var errorCode: Int { + switch self { + case .emptySource: return 5001 + case .notCurrentSourceTask: return 5002 + case .dataProviderError: return 5003 + case .alternativeSourcesExhausted: return 5004 + } + } +} diff --git b/Pods/Kingfisher/Sources/General/KingfisherManager.swift a/Pods/Kingfisher/Sources/General/KingfisherManager.swift new file mode 100644 index 0000000..c65762b --- /dev/null +++ a/Pods/Kingfisher/Sources/General/KingfisherManager.swift @@ -0,0 +1,680 @@ +// +// KingfisherManager.swift +// Kingfisher +// +// Created by Wei Wang on 15/4/6. +// +// Copyright (c) 2019 Wei Wang +// +// 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 + +/// The downloading progress block type. +/// The parameter value is the `receivedSize` of current response. +/// The second parameter is the total expected data length from response's "Content-Length" header. +/// If the expected length is not available, this block will not be called. +public typealias DownloadProgressBlock = ((_ receivedSize: Int64, _ totalSize: Int64) -> Void) + +/// Represents the result of a Kingfisher retrieving image task. +public struct RetrieveImageResult { + + /// Gets the image object of this result. + public let image: KFCrossPlatformImage + + /// Gets the cache source of the image. It indicates from which layer of cache this image is retrieved. + /// If the image is just downloaded from network, `.none` will be returned. + public let cacheType: CacheType + + /// The `Source` which this result is related to. This indicated where the `image` of `self` is referring. + public let source: Source + + /// The original `Source` from which the retrieve task begins. It can be different from the `source` property. + /// When an alternative source loading happened, the `source` will be the replacing loading target, while the + /// `originalSource` will be kept as the initial `source` which issued the image loading process. + public let originalSource: Source +} + +/// A struct that stores some related information of an `KingfisherError`. It provides some context information for +/// a pure error so you can identify the error easier. +public struct PropagationError { + + /// The `Source` to which current `error` is bound. + public let source: Source + + /// The actual error happens in framework. + public let error: KingfisherError +} + + +/// The downloading task updated block type. The parameter `newTask` is the updated new task of image setting process. +/// It is a `nil` if the image loading does not require an image downloading process. If an image downloading is issued, +/// this value will contain the actual `DownloadTask` for you to keep and cancel it later if you need. +public typealias DownloadTaskUpdatedBlock = ((_ newTask: DownloadTask?) -> Void) + +/// Main manager class of Kingfisher. It connects Kingfisher downloader and cache, +/// to provide a set of convenience methods to use Kingfisher for tasks. +/// You can use this class to retrieve an image via a specified URL from web or cache. +public class KingfisherManager { + + /// Represents a shared manager used across Kingfisher. + /// Use this instance for getting or storing images with Kingfisher. + public static let shared = KingfisherManager() + + // Mark: Public Properties + /// The `ImageCache` used by this manager. It is `ImageCache.default` by default. + /// If a cache is specified in `KingfisherManager.defaultOptions`, the value in `defaultOptions` will be + /// used instead. + public var cache: ImageCache + + /// The `ImageDownloader` used by this manager. It is `ImageDownloader.default` by default. + /// If a downloader is specified in `KingfisherManager.defaultOptions`, the value in `defaultOptions` will be + /// used instead. + public var downloader: ImageDownloader + + /// Default options used by the manager. This option will be used in + /// Kingfisher manager related methods, as well as all view extension methods. + /// You can also passing other options for each image task by sending an `options` parameter + /// to Kingfisher's APIs. The per image options will overwrite the default ones, + /// if the option exists in both. + public var defaultOptions = KingfisherOptionsInfo.empty + + // Use `defaultOptions` to overwrite the `downloader` and `cache`. + private var currentDefaultOptions: KingfisherOptionsInfo { + return [.downloader(downloader), .targetCache(cache)] + defaultOptions + } + + private let processingQueue: CallbackQueue + + private convenience init() { + self.init(downloader: .default, cache: .default) + } + + /// Creates an image setting manager with specified downloader and cache. + /// + /// - Parameters: + /// - downloader: The image downloader used to download images. + /// - cache: The image cache which stores memory and disk images. + public init(downloader: ImageDownloader, cache: ImageCache) { + self.downloader = downloader + self.cache = cache + + let processQueueName = "com.onevcat.Kingfisher.KingfisherManager.processQueue.\(UUID().uuidString)" + processingQueue = .dispatch(DispatchQueue(label: processQueueName)) + } + + // MARK: - Getting Images + + /// Gets an image from a given resource. + /// - Parameters: + /// - resource: The `Resource` object defines data information like key or URL. + /// - options: Options to use when creating the image. + /// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an + /// `expectedContentLength`, this block will not be called. `progressBlock` is always called in + /// main queue. + /// - downloadTaskUpdated: Called when a new image downloading task is created for current image retrieving. This + /// usually happens when an alternative source is used to replace the original (failed) + /// task. You can update your reference of `DownloadTask` if you want to manually `cancel` + /// the new task. + /// - completionHandler: Called when the image retrieved and set finished. This completion handler will be invoked + /// from the `options.callbackQueue`. If not specified, the main queue will be used. + /// - Returns: A task represents the image downloading. If there is a download task starts for `.network` resource, + /// the started `DownloadTask` is returned. Otherwise, `nil` is returned. + /// + /// - Note: + /// This method will first check whether the requested `resource` is already in cache or not. If cached, + /// it returns `nil` and invoke the `completionHandler` after the cached image retrieved. Otherwise, it + /// will download the `resource`, store it in cache, then call `completionHandler`. + @discardableResult + public func retrieveImage( + with resource: Resource, + options: KingfisherOptionsInfo? = nil, + progressBlock: DownloadProgressBlock? = nil, + downloadTaskUpdated: DownloadTaskUpdatedBlock? = nil, + completionHandler: ((Result) -> Void)?) -> DownloadTask? + { + return retrieveImage( + with: resource.convertToSource(), + options: options, + progressBlock: progressBlock, + downloadTaskUpdated: downloadTaskUpdated, + completionHandler: completionHandler + ) + } + + /// Gets an image from a given resource. + /// + /// - Parameters: + /// - source: The `Source` object defines data information from network or a data provider. + /// - options: Options to use when creating the image. + /// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an + /// `expectedContentLength`, this block will not be called. `progressBlock` is always called in + /// main queue. + /// - downloadTaskUpdated: Called when a new image downloading task is created for current image retrieving. This + /// usually happens when an alternative source is used to replace the original (failed) + /// task. You can update your reference of `DownloadTask` if you want to manually `cancel` + /// the new task. + /// - completionHandler: Called when the image retrieved and set finished. This completion handler will be invoked + /// from the `options.callbackQueue`. If not specified, the main queue will be used. + /// - Returns: A task represents the image downloading. If there is a download task starts for `.network` resource, + /// the started `DownloadTask` is returned. Otherwise, `nil` is returned. + /// + /// - Note: + /// This method will first check whether the requested `source` is already in cache or not. If cached, + /// it returns `nil` and invoke the `completionHandler` after the cached image retrieved. Otherwise, it + /// will try to load the `source`, store it in cache, then call `completionHandler`. + /// + public func retrieveImage( + with source: Source, + options: KingfisherOptionsInfo? = nil, + progressBlock: DownloadProgressBlock? = nil, + downloadTaskUpdated: DownloadTaskUpdatedBlock? = nil, + completionHandler: ((Result) -> Void)?) -> DownloadTask? + { + let options = currentDefaultOptions + (options ?? .empty) + var info = KingfisherParsedOptionsInfo(options) + if let block = progressBlock { + info.onDataReceived = (info.onDataReceived ?? []) + [ImageLoadingProgressSideEffect(block)] + } + return retrieveImage( + with: source, + options: info, + downloadTaskUpdated: downloadTaskUpdated, + completionHandler: completionHandler) + } + + func retrieveImage( + with source: Source, + options: KingfisherParsedOptionsInfo, + downloadTaskUpdated: DownloadTaskUpdatedBlock? = nil, + completionHandler: ((Result) -> Void)?) -> DownloadTask? + { + var context = RetrievingContext(options: options, originalSource: source) + + func handler(currentSource: Source, result: (Result)) -> Void { + switch result { + case .success: + completionHandler?(result) + case .failure(let error): + // Skip alternative sources if the user cancelled it. + guard !error.isTaskCancelled else { + completionHandler?(.failure(error)) + return + } + if let nextSource = context.popAlternativeSource() { + context.appendError(error, to: currentSource) + let newTask = self.retrieveImage(with: nextSource, context: context) { result in + handler(currentSource: nextSource, result: result) + } + downloadTaskUpdated?(newTask) + } else { + // No other alternative source. Finish with error. + if context.propagationErrors.isEmpty { + completionHandler?(.failure(error)) + } else { + context.appendError(error, to: currentSource) + let finalError = KingfisherError.imageSettingError( + reason: .alternativeSourcesExhausted(context.propagationErrors) + ) + completionHandler?(.failure(finalError)) + } + } + } + } + + return retrieveImage( + with: source, + context: context) + { + result in + handler(currentSource: source, result: result) + } + + } + + private func retrieveImage( + with source: Source, + context: RetrievingContext, + completionHandler: ((Result) -> Void)?) -> DownloadTask? + { + let options = context.options + if options.forceRefresh { + return loadAndCacheImage( + source: source, + context: context, + completionHandler: completionHandler)?.value + + } else { + let loadedFromCache = retrieveImageFromCache( + source: source, + context: context, + completionHandler: completionHandler) + + if loadedFromCache { + return nil + } + + if options.onlyFromCache { + let error = KingfisherError.cacheError(reason: .imageNotExisting(key: source.cacheKey)) + completionHandler?(.failure(error)) + return nil + } + + return loadAndCacheImage( + source: source, + context: context, + completionHandler: completionHandler)?.value + } + } + + func provideImage( + provider: ImageDataProvider, + options: KingfisherParsedOptionsInfo, + completionHandler: ((Result) -> Void)?) + { + guard let completionHandler = completionHandler else { return } + provider.data { result in + switch result { + case .success(let data): + (options.processingQueue ?? self.processingQueue).execute { + let processor = options.processor + let processingItem = ImageProcessItem.data(data) + guard let image = processor.process(item: processingItem, options: options) else { + options.callbackQueue.execute { + let error = KingfisherError.processorError( + reason: .processingFailed(processor: processor, item: processingItem)) + completionHandler(.failure(error)) + } + return + } + + options.callbackQueue.execute { + let result = ImageLoadingResult(image: image, url: nil, originalData: data) + completionHandler(.success(result)) + } + } + case .failure(let error): + options.callbackQueue.execute { + let error = KingfisherError.imageSettingError( + reason: .dataProviderError(provider: provider, error: error)) + completionHandler(.failure(error)) + } + + } + } + } + + private func cacheImage( + source: Source, + options: KingfisherParsedOptionsInfo, + context: RetrievingContext, + result: Result, + completionHandler: ((Result) -> Void)? + ) + { + switch result { + case .success(let value): + let needToCacheOriginalImage = options.cacheOriginalImage && + options.processor != DefaultImageProcessor.default + let coordinator = CacheCallbackCoordinator( + shouldWaitForCache: options.waitForCache, shouldCacheOriginal: needToCacheOriginalImage) + // Add image to cache. + let targetCache = options.targetCache ?? self.cache + targetCache.store( + value.image, + original: value.originalData, + forKey: source.cacheKey, + options: options, + toDisk: !options.cacheMemoryOnly) + { + _ in + coordinator.apply(.cachingImage) { + let result = RetrieveImageResult( + image: value.image, + cacheType: .none, + source: source, + originalSource: context.originalSource + ) + completionHandler?(.success(result)) + } + } + + // Add original image to cache if necessary. + + if needToCacheOriginalImage { + let originalCache = options.originalCache ?? targetCache + originalCache.storeToDisk( + value.originalData, + forKey: source.cacheKey, + processorIdentifier: DefaultImageProcessor.default.identifier, + expiration: options.diskCacheExpiration) + { + _ in + coordinator.apply(.cachingOriginalImage) { + let result = RetrieveImageResult( + image: value.image, + cacheType: .none, + source: source, + originalSource: context.originalSource + ) + completionHandler?(.success(result)) + } + } + } + + coordinator.apply(.cacheInitiated) { + let result = RetrieveImageResult( + image: value.image, + cacheType: .none, + source: source, + originalSource: context.originalSource + ) + completionHandler?(.success(result)) + } + + case .failure(let error): + completionHandler?(.failure(error)) + } + } + + @discardableResult + func loadAndCacheImage( + source: Source, + context: RetrievingContext, + completionHandler: ((Result) -> Void)?) -> DownloadTask.WrappedTask? + { + let options = context.options + func _cacheImage(_ result: Result) { + cacheImage( + source: source, + options: options, + context: context, + result: result, + completionHandler: completionHandler + ) + } + + switch source { + case .network(let resource): + let downloader = options.downloader ?? self.downloader + let task = downloader.downloadImage( + with: resource.downloadURL, options: options, completionHandler: _cacheImage + ) + return task.map(DownloadTask.WrappedTask.download) + + case .provider(let provider): + provideImage(provider: provider, options: options, completionHandler: _cacheImage) + return .dataProviding + } + } + + /// Retrieves image from memory or disk cache. + /// + /// - Parameters: + /// - source: The target source from which to get image. + /// - key: The key to use when caching the image. + /// - url: Image request URL. This is not used when retrieving image from cache. It is just used for + /// `RetrieveImageResult` callback compatibility. + /// - options: Options on how to get the image from image cache. + /// - completionHandler: Called when the image retrieving finishes, either with succeeded + /// `RetrieveImageResult` or an error. + /// - Returns: `true` if the requested image or the original image before being processed is existing in cache. + /// Otherwise, this method returns `false`. + /// + /// - Note: + /// The image retrieving could happen in either memory cache or disk cache. The `.processor` option in + /// `options` will be considered when searching in the cache. If no processed image is found, Kingfisher + /// will try to check whether an original version of that image is existing or not. If there is already an + /// original, Kingfisher retrieves it from cache and processes it. Then, the processed image will be store + /// back to cache for later use. + func retrieveImageFromCache( + source: Source, + context: RetrievingContext, + completionHandler: ((Result) -> Void)?) -> Bool + { + let options = context.options + // 1. Check whether the image was already in target cache. If so, just get it. + let targetCache = options.targetCache ?? cache + let key = source.cacheKey + let targetImageCached = targetCache.imageCachedType( + forKey: key, processorIdentifier: options.processor.identifier) + + let validCache = targetImageCached.cached && + (options.fromMemoryCacheOrRefresh == false || targetImageCached == .memory) + if validCache { + targetCache.retrieveImage(forKey: key, options: options) { result in + guard let completionHandler = completionHandler else { return } + options.callbackQueue.execute { + result.match( + onSuccess: { cacheResult in + let value: Result + if let image = cacheResult.image { + value = result.map { + RetrieveImageResult( + image: image, + cacheType: $0.cacheType, + source: source, + originalSource: context.originalSource + ) + } + } else { + value = .failure(KingfisherError.cacheError(reason: .imageNotExisting(key: key))) + } + completionHandler(value) + }, + onFailure: { _ in + completionHandler(.failure(KingfisherError.cacheError(reason: .imageNotExisting(key: key)))) + } + ) + } + } + return true + } + + // 2. Check whether the original image exists. If so, get it, process it, save to storage and return. + let originalCache = options.originalCache ?? targetCache + // No need to store the same file in the same cache again. + if originalCache === targetCache && options.processor == DefaultImageProcessor.default { + return false + } + + // Check whether the unprocessed image existing or not. + let originalImageCacheType = originalCache.imageCachedType( + forKey: key, processorIdentifier: DefaultImageProcessor.default.identifier) + let canAcceptDiskCache = !options.fromMemoryCacheOrRefresh + + let canUseOriginalImageCache = + (canAcceptDiskCache && originalImageCacheType.cached) || + (!canAcceptDiskCache && originalImageCacheType == .memory) + + if canUseOriginalImageCache { + // Now we are ready to get found the original image from cache. We need the unprocessed image, so remove + // any processor from options first. + var optionsWithoutProcessor = options + optionsWithoutProcessor.processor = DefaultImageProcessor.default + originalCache.retrieveImage(forKey: key, options: optionsWithoutProcessor) { result in + + result.match( + onSuccess: { cacheResult in + guard let image = cacheResult.image else { + assertionFailure("The image (under key: \(key) should be existing in the original cache.") + return + } + + let processor = options.processor + (options.processingQueue ?? self.processingQueue).execute { + let item = ImageProcessItem.image(image) + guard let processedImage = processor.process(item: item, options: options) else { + let error = KingfisherError.processorError( + reason: .processingFailed(processor: processor, item: item)) + options.callbackQueue.execute { completionHandler?(.failure(error)) } + return + } + + var cacheOptions = options + cacheOptions.callbackQueue = .untouch + + let coordinator = CacheCallbackCoordinator( + shouldWaitForCache: options.waitForCache, shouldCacheOriginal: false) + + targetCache.store( + processedImage, + forKey: key, + options: cacheOptions, + toDisk: !options.cacheMemoryOnly) + { + _ in + coordinator.apply(.cachingImage) { + let value = RetrieveImageResult( + image: processedImage, + cacheType: .none, + source: source, + originalSource: context.originalSource + ) + options.callbackQueue.execute { completionHandler?(.success(value)) } + } + } + + coordinator.apply(.cacheInitiated) { + let value = RetrieveImageResult( + image: processedImage, + cacheType: .none, + source: source, + originalSource: context.originalSource + ) + options.callbackQueue.execute { completionHandler?(.success(value)) } + } + } + }, + onFailure: { _ in + // This should not happen actually, since we already confirmed `originalImageCached` is `true`. + // Just in case... + options.callbackQueue.execute { + completionHandler?( + .failure(KingfisherError.cacheError(reason: .imageNotExisting(key: key))) + ) + } + } + ) + } + return true + } + + return false + } +} + +struct RetrievingContext { + + var options: KingfisherParsedOptionsInfo + + let originalSource: Source + var propagationErrors: [PropagationError] = [] + + init(options: KingfisherParsedOptionsInfo, originalSource: Source) { + self.originalSource = originalSource + self.options = options + } + + mutating func popAlternativeSource() -> Source? { + guard var alternativeSources = options.alternativeSources, !alternativeSources.isEmpty else { + return nil + } + let nextSource = alternativeSources.removeFirst() + options.alternativeSources = alternativeSources + return nextSource + } + + @discardableResult + mutating func appendError(_ error: KingfisherError, to source: Source) -> [PropagationError] { + let item = PropagationError(source: source, error: error) + propagationErrors.append(item) + return propagationErrors + } +} + +class CacheCallbackCoordinator { + + enum State { + case idle + case imageCached + case originalImageCached + case done + } + + enum Action { + case cacheInitiated + case cachingImage + case cachingOriginalImage + } + + private let shouldWaitForCache: Bool + private let shouldCacheOriginal: Bool + private let stateQueue: DispatchQueue + private var threadSafeState: State = .idle + + private (set) var state: State { + set { stateQueue.sync { threadSafeState = newValue } } + get { stateQueue.sync { threadSafeState } } + } + + init(shouldWaitForCache: Bool, shouldCacheOriginal: Bool) { + self.shouldWaitForCache = shouldWaitForCache + self.shouldCacheOriginal = shouldCacheOriginal + let stateQueueName = "com.onevcat.Kingfisher.CacheCallbackCoordinator.stateQueue.\(UUID().uuidString)" + self.stateQueue = DispatchQueue(label: stateQueueName) + } + + func apply(_ action: Action, trigger: () -> Void) { + switch (state, action) { + case (.done, _): + break + + // From .idle + case (.idle, .cacheInitiated): + if !shouldWaitForCache { + state = .done + trigger() + } + case (.idle, .cachingImage): + if shouldCacheOriginal { + state = .imageCached + } else { + state = .done + trigger() + } + case (.idle, .cachingOriginalImage): + state = .originalImageCached + + // From .imageCached + case (.imageCached, .cachingOriginalImage): + state = .done + trigger() + + // From .originalImageCached + case (.originalImageCached, .cachingImage): + state = .done + trigger() + + default: + assertionFailure("This case should not happen in CacheCallbackCoordinator: \(state) - \(action)") + } + } +} diff --git b/Pods/Kingfisher/Sources/General/KingfisherOptionsInfo.swift a/Pods/Kingfisher/Sources/General/KingfisherOptionsInfo.swift new file mode 100644 index 0000000..e77793f --- /dev/null +++ a/Pods/Kingfisher/Sources/General/KingfisherOptionsInfo.swift @@ -0,0 +1,373 @@ +// +// KingfisherOptionsInfo.swift +// Kingfisher +// +// Created by Wei Wang on 15/4/23. +// +// Copyright (c) 2019 Wei Wang +// +// 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. + +#if os(macOS) +import AppKit +#else +import UIKit +#endif + + +/// KingfisherOptionsInfo is a typealias for [KingfisherOptionsInfoItem]. +/// You can use the enum of option item with value to control some behaviors of Kingfisher. +public typealias KingfisherOptionsInfo = [KingfisherOptionsInfoItem] + +extension Array where Element == KingfisherOptionsInfoItem { + static let empty: KingfisherOptionsInfo = [] +} + +/// Represents the available option items could be used in `KingfisherOptionsInfo`. +public enum KingfisherOptionsInfoItem { + + /// Kingfisher will use the associated `ImageCache` object when handling related operations, + /// including trying to retrieve the cached images and store the downloaded image to it. + case targetCache(ImageCache) + + /// The `ImageCache` for storing and retrieving original images. If `originalCache` is + /// contained in the options, it will be preferred for storing and retrieving original images. + /// If there is no `.originalCache` in the options, `.targetCache` will be used to store original images. + /// + /// When using KingfisherManager to download and store an image, if `cacheOriginalImage` is + /// applied in the option, the original image will be stored to this `originalCache`. At the + /// same time, if a requested final image (with processor applied) cannot be found in `targetCache`, + /// Kingfisher will try to search the original image to check whether it is already there. If found, + /// it will be used and applied with the given processor. It is an optimization for not downloading + /// the same image for multiple times. + case originalCache(ImageCache) + + /// Kingfisher will use the associated `ImageDownloader` object to download the requested images. + case downloader(ImageDownloader) + + /// Member for animation transition when using `UIImageView`. Kingfisher will use the `ImageTransition` of + /// this enum to animate the image in if it is downloaded from web. The transition will not happen when the + /// image is retrieved from either memory or disk cache by default. If you need to do the transition even when + /// the image being retrieved from cache, set `.forceRefresh` as well. + case transition(ImageTransition) + + /// Associated `Float` value will be set as the priority of image download task. The value for it should be + /// between 0.0~1.0. If this option not set, the default value (`URLSessionTask.defaultPriority`) will be used. + case downloadPriority(Float) + + /// If set, Kingfisher will ignore the cache and try to fire a download task for the resource. + case forceRefresh + + /// If set, Kingfisher will try to retrieve the image from memory cache first. If the image is not in memory + /// cache, then it will ignore the disk cache but download the image again from network. This is useful when + /// you want to display a changeable image behind the same url at the same app session, while avoiding download + /// it for multiple times. + case fromMemoryCacheOrRefresh + + /// If set, setting the image to an image view will happen with transition even when retrieved from cache. + /// See `.transition` option for more. + case forceTransition + + /// If set, Kingfisher will only cache the value in memory but not in disk. + case cacheMemoryOnly + + /// If set, Kingfisher will wait for caching operation to be completed before calling the completion block. + case waitForCache + + /// If set, Kingfisher will only try to retrieve the image from cache, but not from network. If the image is + /// not in cache, the image retrieving will fail with an error. + case onlyFromCache + + /// Decode the image in background thread before using. It will decode the downloaded image data and do a off-screen + /// rendering to extract pixel information in background. This can speed up display, but will cost more time to + /// prepare the image for using. + case backgroundDecode + + /// The associated value of this member will be used as the target queue of dispatch callbacks when + /// retrieving images from cache. If not set, Kingfisher will use main queue for callbacks. + @available(*, deprecated, message: "Use `.callbackQueue(CallbackQueue)` instead.") + case callbackDispatchQueue(DispatchQueue?) + + /// The associated value will be used as the target queue of dispatch callbacks when retrieving images from + /// cache. If not set, Kingfisher will use `.mainCurrentOrAsync` for callbacks. + /// + /// - Note: + /// This option does not affect the callbacks for UI related extension methods. You will always get the + /// callbacks called from main queue. + case callbackQueue(CallbackQueue) + + /// The associated value will be used as the scale factor when converting retrieved data to an image. + /// Specify the image scale, instead of your screen scale. You may need to set the correct scale when you dealing + /// with 2x or 3x retina images. Otherwise, Kingfisher will convert the data to image object at `scale` 1.0. + case scaleFactor(CGFloat) + + /// Whether all the animated image data should be preloaded. Default is `false`, which means only following frames + /// will be loaded on need. If `true`, all the animated image data will be loaded and decoded into memory. + /// + /// This option is mainly used for back compatibility internally. You should not set it directly. Instead, + /// you should choose the image view class to control the GIF data loading. There are two classes in Kingfisher + /// support to display a GIF image. `AnimatedImageView` does not preload all data, it takes much less memory, but + /// uses more CPU when display. While a normal image view (`UIImageView` or `NSImageView`) loads all data at once, + /// which uses more memory but only decode image frames once. + case preloadAllAnimationData + + /// The `ImageDownloadRequestModifier` contained will be used to change the request before it being sent. + /// This is the last chance you can modify the image download request. You can modify the request for some + /// customizing purpose, such as adding auth token to the header, do basic HTTP auth or something like url mapping. + /// The original request will be sent without any modification by default. + case requestModifier(ImageDownloadRequestModifier) + + /// The `ImageDownloadRedirectHandler` contained will be used to change the request before redirection. + /// This is the possibility you can modify the image download request during redirect. You can modify the request for + /// some customizing purpose, such as adding auth token to the header, do basic HTTP auth or something like url + /// mapping. + /// The original redirection request will be sent without any modification by default. + case redirectHandler(ImageDownloadRedirectHandler) + + /// Processor for processing when the downloading finishes, a processor will convert the downloaded data to an image + /// and/or apply some filter on it. If a cache is connected to the downloader (it happens when you are using + /// KingfisherManager or any of the view extension methods), the converted image will also be sent to cache as well. + /// If not set, the `DefaultImageProcessor.default` will be used. + case processor(ImageProcessor) + + /// Supplies a `CacheSerializer` to convert some data to an image object for + /// retrieving from disk cache or vice versa for storing to disk cache. + /// If not set, the `DefaultCacheSerializer.default` will be used. + case cacheSerializer(CacheSerializer) + + /// An `ImageModifier` is for modifying an image as needed right before it is used. If the image was fetched + /// directly from the downloader, the modifier will run directly after the `ImageProcessor`. If the image is being + /// fetched from a cache, the modifier will run after the `CacheSerializer`. + /// + /// Use `ImageModifier` when you need to set properties that do not persist when caching the image on a concrete + /// type of `Image`, such as the `renderingMode` or the `alignmentInsets` of `UIImage`. + case imageModifier(ImageModifier) + + /// Keep the existing image of image view while setting another image to it. + /// By setting this option, the placeholder image parameter of image view extension method + /// will be ignored and the current image will be kept while loading or downloading the new image. + case keepCurrentImageWhileLoading + + /// If set, Kingfisher will only load the first frame from an animated image file as a single image. + /// Loading an animated images may take too much memory. It will be useful when you want to display a + /// static preview of the first frame from a animated image. + /// + /// This option will be ignored if the target image is not animated image data. + case onlyLoadFirstFrame + + /// If set and an `ImageProcessor` is used, Kingfisher will try to cache both the final result and original + /// image. Kingfisher will have a chance to use the original image when another processor is applied to the same + /// resource, instead of downloading it again. You can use `.originalCache` to specify a cache or the original + /// images if necessary. + /// + /// The original image will be only cached to disk storage. + case cacheOriginalImage + + /// If set and a downloading error occurred Kingfisher will set provided image (or empty) + /// in place of requested one. It's useful when you don't want to show placeholder + /// during loading time but wants to use some default image when requests will be failed. + case onFailureImage(KFCrossPlatformImage?) + + /// If set and used in `ImagePrefetcher`, the prefetching operation will load the images into memory storage + /// aggressively. By default this is not contained in the options, that means if the requested image is already + /// in disk cache, Kingfisher will not try to load it to memory. + case alsoPrefetchToMemory + + /// If set, the disk storage loading will happen in the same calling queue. By default, disk storage file loading + /// happens in its own queue with an asynchronous dispatch behavior. Although it provides better non-blocking disk + /// loading performance, it also causes a flickering when you reload an image from disk, if the image view already + /// has an image set. + /// + /// Set this options will stop that flickering by keeping all loading in the same queue (typically the UI queue + /// if you are using Kingfisher's extension methods to set an image), with a tradeoff of loading performance. + case loadDiskFileSynchronously + + /// The expiration setting for memory cache. By default, the underlying `MemoryStorage.Backend` uses the + /// expiration in its config for all items. If set, the `MemoryStorage.Backend` will use this associated + /// value to overwrite the config setting for this caching item. + case memoryCacheExpiration(StorageExpiration) + + /// The expiration extending setting for memory cache. The item expiration time will be incremented by this + /// value after access. + /// By default, the underlying `MemoryStorage.Backend` uses the initial cache expiration as extending + /// value: .cacheTime. + /// + /// To disable extending option at all add memoryCacheAccessExtendingExpiration(.none) to options. + case memoryCacheAccessExtendingExpiration(ExpirationExtending) + + /// The expiration setting for disk cache. By default, the underlying `DiskStorage.Backend` uses the + /// expiration in its config for all items. If set, the `DiskStorage.Backend` will use this associated + /// value to overwrite the config setting for this caching item. + case diskCacheExpiration(StorageExpiration) + + /// The expiration extending setting for disk cache. The item expiration time will be incremented by this value after access. + /// By default, the underlying `DiskStorage.Backend` uses the initial cache expiration as extending value: .cacheTime. + /// To disable extending option at all add diskCacheAccessExtendingExpiration(.none) to options. + case diskCacheAccessExtendingExpiration(ExpirationExtending) + + /// Decides on which queue the image processing should happen. By default, Kingfisher uses a pre-defined serial + /// queue to process images. Use this option to change this behavior. For example, specify a `.mainCurrentOrAsync` + /// to let the image be processed in main queue to prevent a possible flickering (but with a possibility of + /// blocking the UI, especially if the processor needs a lot of time to run). + case processingQueue(CallbackQueue) + + /// Enable progressive image loading, Kingfisher will use the `ImageProgressive` of + case progressiveJPEG(ImageProgressive) + + /// The alternative sources will be used when the original input `Source` fails. The `Source`s in the associated + /// array will be used to start a new image loading task if the previous task fails due to an error. The image + /// source loading process will stop as soon as a source is loaded successfully. If all `[Source]`s are used but + /// the loading is still failing, an `imageSettingError` with `alternativeSourcesExhausted` as its reason will be + /// thrown out. + /// + /// This option is useful if you want to implement a fallback solution for setting image. + /// + /// User cancellation will not trigger the alternative source loading. + case alternativeSources([Source]) +} + +// Improve performance by parsing the input `KingfisherOptionsInfo` (self) first. +// So we can prevent the iterating over the options array again and again. +/// The parsed options info used across Kingfisher methods. Each property in this type corresponds a case member +/// in `KingfisherOptionsInfoItem`. When a `KingfisherOptionsInfo` sent to Kingfisher related methods, it will be +/// parsed and converted to a `KingfisherParsedOptionsInfo` first, and pass through the internal methods. +public struct KingfisherParsedOptionsInfo { + + public var targetCache: ImageCache? = nil + public var originalCache: ImageCache? = nil + public var downloader: ImageDownloader? = nil + public var transition: ImageTransition = .none + public var downloadPriority: Float = URLSessionTask.defaultPriority + public var forceRefresh = false + public var fromMemoryCacheOrRefresh = false + public var forceTransition = false + public var cacheMemoryOnly = false + public var waitForCache = false + public var onlyFromCache = false + public var backgroundDecode = false + public var preloadAllAnimationData = false + public var callbackQueue: CallbackQueue = .mainCurrentOrAsync + public var scaleFactor: CGFloat = 1.0 + public var requestModifier: ImageDownloadRequestModifier? = nil + public var redirectHandler: ImageDownloadRedirectHandler? = nil + public var processor: ImageProcessor = DefaultImageProcessor.default + public var imageModifier: ImageModifier? = nil + public var cacheSerializer: CacheSerializer = DefaultCacheSerializer.default + public var keepCurrentImageWhileLoading = false + public var onlyLoadFirstFrame = false + public var cacheOriginalImage = false + public var onFailureImage: Optional = .none + public var alsoPrefetchToMemory = false + public var loadDiskFileSynchronously = false + public var memoryCacheExpiration: StorageExpiration? = nil + public var memoryCacheAccessExtendingExpiration: ExpirationExtending = .cacheTime + public var diskCacheExpiration: StorageExpiration? = nil + public var diskCacheAccessExtendingExpiration: ExpirationExtending = .cacheTime + public var processingQueue: CallbackQueue? = nil + public var progressiveJPEG: ImageProgressive? = nil + public var alternativeSources: [Source]? = nil + + var onDataReceived: [DataReceivingSideEffect]? = nil + + public init(_ info: KingfisherOptionsInfo?) { + guard let info = info else { return } + for option in info { + switch option { + case .targetCache(let value): targetCache = value + case .originalCache(let value): originalCache = value + case .downloader(let value): downloader = value + case .transition(let value): transition = value + case .downloadPriority(let value): downloadPriority = value + case .forceRefresh: forceRefresh = true + case .fromMemoryCacheOrRefresh: fromMemoryCacheOrRefresh = true + case .forceTransition: forceTransition = true + case .cacheMemoryOnly: cacheMemoryOnly = true + case .waitForCache: waitForCache = true + case .onlyFromCache: onlyFromCache = true + case .backgroundDecode: backgroundDecode = true + case .preloadAllAnimationData: preloadAllAnimationData = true + case .callbackQueue(let value): callbackQueue = value + case .scaleFactor(let value): scaleFactor = value + case .requestModifier(let value): requestModifier = value + case .redirectHandler(let value): redirectHandler = value + case .processor(let value): processor = value + case .imageModifier(let value): imageModifier = value + case .cacheSerializer(let value): cacheSerializer = value + case .keepCurrentImageWhileLoading: keepCurrentImageWhileLoading = true + case .onlyLoadFirstFrame: onlyLoadFirstFrame = true + case .cacheOriginalImage: cacheOriginalImage = true + case .onFailureImage(let value): onFailureImage = .some(value) + case .alsoPrefetchToMemory: alsoPrefetchToMemory = true + case .loadDiskFileSynchronously: loadDiskFileSynchronously = true + case .callbackDispatchQueue(let value): callbackQueue = value.map { .dispatch($0) } ?? .mainCurrentOrAsync + case .memoryCacheExpiration(let expiration): memoryCacheExpiration = expiration + case .memoryCacheAccessExtendingExpiration(let expirationExtending): memoryCacheAccessExtendingExpiration = expirationExtending + case .diskCacheExpiration(let expiration): diskCacheExpiration = expiration + case .diskCacheAccessExtendingExpiration(let expirationExtending): diskCacheAccessExtendingExpiration = expirationExtending + case .processingQueue(let queue): processingQueue = queue + case .progressiveJPEG(let value): progressiveJPEG = value + case .alternativeSources(let sources): alternativeSources = sources + } + } + + if originalCache == nil { + originalCache = targetCache + } + } +} + +extension KingfisherParsedOptionsInfo { + var imageCreatingOptions: ImageCreatingOptions { + return ImageCreatingOptions( + scale: scaleFactor, + duration: 0.0, + preloadAll: preloadAllAnimationData, + onlyFirstFrame: onlyLoadFirstFrame) + } +} + +protocol DataReceivingSideEffect: AnyObject { + var onShouldApply: () -> Bool { get set } + func onDataReceived(_ session: URLSession, task: SessionDataTask, data: Data) +} + +class ImageLoadingProgressSideEffect: DataReceivingSideEffect { + + var onShouldApply: () -> Bool = { return true } + + let block: DownloadProgressBlock + + init(_ block: @escaping DownloadProgressBlock) { + self.block = block + } + + func onDataReceived(_ session: URLSession, task: SessionDataTask, data: Data) { + DispatchQueue.main.async { + guard self.onShouldApply() else { return } + guard let expectedContentLength = task.task.response?.expectedContentLength, + expectedContentLength != -1 else + { + return + } + + let dataLength = Int64(task.mutableData.count) + self.block(dataLength, expectedContentLength) + } + } +} diff --git b/Pods/Kingfisher/Sources/Image/Filter.swift a/Pods/Kingfisher/Sources/Image/Filter.swift new file mode 100644 index 0000000..6e4b386 --- /dev/null +++ a/Pods/Kingfisher/Sources/Image/Filter.swift @@ -0,0 +1,146 @@ +// +// Filter.swift +// Kingfisher +// +// Created by Wei Wang on 2016/08/31. +// +// Copyright (c) 2019 Wei Wang +// +// 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. + +#if !os(watchOS) + +import CoreImage + +// Reuse the same CI Context for all CI drawing. +private let ciContext = CIContext(options: nil) + +/// Represents the type of transformer method, which will be used in to provide a `Filter`. +public typealias Transformer = (CIImage) -> CIImage? + +/// Represents a processor based on a `CIImage` `Filter`. +/// It requires a filter to create an `ImageProcessor`. +public protocol CIImageProcessor: ImageProcessor { + var filter: Filter { get } +} + +extension CIImageProcessor { + + /// Processes the input `ImageProcessItem` with this processor. + /// + /// - Parameters: + /// - item: Input item which will be processed by `self`. + /// - options: Options when processing the item. + /// - Returns: The processed image. + /// + /// - Note: See documentation of `ImageProcessor` protocol for more. + public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { + switch item { + case .image(let image): + return image.kf.apply(filter) + case .data: + return (DefaultImageProcessor.default |> self).process(item: item, options: options) + } + } +} + +/// A wrapper struct for a `Transformer` of CIImage filters. A `Filter` +/// value could be used to create a `CIImage` processor. +public struct Filter { + + let transform: Transformer + + public init(transform: @escaping Transformer) { + self.transform = transform + } + + /// Tint filter which will apply a tint color to images. + public static var tint: (KFCrossPlatformColor) -> Filter = { + color in + Filter { + input in + + let colorFilter = CIFilter(name: "CIConstantColorGenerator")! + colorFilter.setValue(CIColor(color: color), forKey: kCIInputColorKey) + + let filter = CIFilter(name: "CISourceOverCompositing")! + + let colorImage = colorFilter.outputImage + filter.setValue(colorImage, forKey: kCIInputImageKey) + filter.setValue(input, forKey: kCIInputBackgroundImageKey) + + return filter.outputImage?.cropped(to: input.extent) + } + } + + /// Represents color control elements. It is a tuple of + /// `(brightness, contrast, saturation, inputEV)` + public typealias ColorElement = (CGFloat, CGFloat, CGFloat, CGFloat) + + /// Color control filter which will apply color control change to images. + public static var colorControl: (ColorElement) -> Filter = { arg -> Filter in + let (brightness, contrast, saturation, inputEV) = arg + return Filter { input in + let paramsColor = [kCIInputBrightnessKey: brightness, + kCIInputContrastKey: contrast, + kCIInputSaturationKey: saturation] + let blackAndWhite = input.applyingFilter("CIColorControls", parameters: paramsColor) + let paramsExposure = [kCIInputEVKey: inputEV] + return blackAndWhite.applyingFilter("CIExposureAdjust", parameters: paramsExposure) + } + } +} + +extension KingfisherWrapper where Base: KFCrossPlatformImage { + + /// Applies a `Filter` containing `CIImage` transformer to `self`. + /// + /// - Parameter filter: The filter used to transform `self`. + /// - Returns: A transformed image by input `Filter`. + /// + /// - Note: + /// Only CG-based images are supported. If any error happens + /// during transforming, `self` will be returned. + public func apply(_ filter: Filter) -> KFCrossPlatformImage { + + guard let cgImage = cgImage else { + assertionFailure("[Kingfisher] Tint image only works for CG-based image.") + return base + } + + let inputImage = CIImage(cgImage: cgImage) + guard let outputImage = filter.transform(inputImage) else { + return base + } + + guard let result = ciContext.createCGImage(outputImage, from: outputImage.extent) else { + assertionFailure("[Kingfisher] Can not make an tint image within context.") + return base + } + + #if os(macOS) + return fixedForRetinaPixel(cgImage: result, to: size) + #else + return KFCrossPlatformImage(cgImage: result, scale: base.scale, orientation: base.imageOrientation) + #endif + } + +} + +#endif diff --git b/Pods/Kingfisher/Sources/Image/GIFAnimatedImage.swift a/Pods/Kingfisher/Sources/Image/GIFAnimatedImage.swift new file mode 100644 index 0000000..4685583 --- /dev/null +++ a/Pods/Kingfisher/Sources/Image/GIFAnimatedImage.swift @@ -0,0 +1,121 @@ +// +// AnimatedImage.swift +// Kingfisher +// +// Created by onevcat on 2018/09/26. +// +// Copyright (c) 2019 Wei Wang +// +// 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 +import ImageIO + +/// Represents a set of image creating options used in Kingfisher. +public struct ImageCreatingOptions { + + /// The target scale of image needs to be created. + public let scale: CGFloat + + /// The expected animation duration if an animated image being created. + public let duration: TimeInterval + + /// For an animated image, whether or not all frames should be loaded before displaying. + public let preloadAll: Bool + + /// For an animated image, whether or not only the first image should be + /// loaded as a static image. It is useful for preview purpose of an animated image. + public let onlyFirstFrame: Bool + + /// Creates an `ImageCreatingOptions` object. + /// + /// - Parameters: + /// - scale: The target scale of image needs to be created. Default is `1.0`. + /// - duration: The expected animation duration if an animated image being created. + /// A value less or equal to `0.0` means the animated image duration will + /// be determined by the frame data. Default is `0.0`. + /// - preloadAll: For an animated image, whether or not all frames should be loaded before displaying. + /// Default is `false`. + /// - onlyFirstFrame: For an animated image, whether or not only the first image should be + /// loaded as a static image. It is useful for preview purpose of an animated image. + /// Default is `false`. + public init( + scale: CGFloat = 1.0, + duration: TimeInterval = 0.0, + preloadAll: Bool = false, + onlyFirstFrame: Bool = false) + { + self.scale = scale + self.duration = duration + self.preloadAll = preloadAll + self.onlyFirstFrame = onlyFirstFrame + } +} + +// Represents the decoding for a GIF image. This class extracts frames from an `imageSource`, then +// hold the images for later use. +class GIFAnimatedImage { + let images: [KFCrossPlatformImage] + let duration: TimeInterval + + init?(from imageSource: CGImageSource, for info: [String: Any], options: ImageCreatingOptions) { + let frameCount = CGImageSourceGetCount(imageSource) + var images = [KFCrossPlatformImage]() + var gifDuration = 0.0 + + for i in 0 ..< frameCount { + guard let imageRef = CGImageSourceCreateImageAtIndex(imageSource, i, info as CFDictionary) else { + return nil + } + + if frameCount == 1 { + gifDuration = .infinity + } else { + // Get current animated GIF frame duration + gifDuration += GIFAnimatedImage.getFrameDuration(from: imageSource, at: i) + } + images.append(KingfisherWrapper.image(cgImage: imageRef, scale: options.scale, refImage: nil)) + if options.onlyFirstFrame { break } + } + self.images = images + self.duration = gifDuration + } + + // Calculates frame duration for a gif frame out of the kCGImagePropertyGIFDictionary dictionary. + static func getFrameDuration(from gifInfo: [String: Any]?) -> TimeInterval { + let defaultFrameDuration = 0.1 + guard let gifInfo = gifInfo else { return defaultFrameDuration } + + let unclampedDelayTime = gifInfo[kCGImagePropertyGIFUnclampedDelayTime as String] as? NSNumber + let delayTime = gifInfo[kCGImagePropertyGIFDelayTime as String] as? NSNumber + let duration = unclampedDelayTime ?? delayTime + + guard let frameDuration = duration else { return defaultFrameDuration } + return frameDuration.doubleValue > 0.011 ? frameDuration.doubleValue : defaultFrameDuration + } + + // Calculates frame duration at a specific index for a gif from an `imageSource`. + static func getFrameDuration(from imageSource: CGImageSource, at index: Int) -> TimeInterval { + guard let properties = CGImageSourceCopyPropertiesAtIndex(imageSource, index, nil) + as? [String: Any] else { return 0.0 } + + let gifInfo = properties[kCGImagePropertyGIFDictionary as String] as? [String: Any] + return getFrameDuration(from: gifInfo) + } +} diff --git b/Pods/Kingfisher/Sources/Image/Image.swift a/Pods/Kingfisher/Sources/Image/Image.swift new file mode 100644 index 0000000..da757ca --- /dev/null +++ a/Pods/Kingfisher/Sources/Image/Image.swift @@ -0,0 +1,377 @@ +// +// Image.swift +// Kingfisher +// +// Created by Wei Wang on 16/1/6. +// +// Copyright (c) 2019 Wei Wang +// +// 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. + + +#if os(macOS) +import AppKit +private var imagesKey: Void? +private var durationKey: Void? +#else +import UIKit +import MobileCoreServices +private var imageSourceKey: Void? +#endif + +#if !os(watchOS) +import CoreImage +#endif + +import CoreGraphics +import ImageIO + +private var animatedImageDataKey: Void? + +// MARK: - Image Properties +extension KingfisherWrapper where Base: KFCrossPlatformImage { + private(set) var animatedImageData: Data? { + get { return getAssociatedObject(base, &animatedImageDataKey) } + set { setRetainedAssociatedObject(base, &animatedImageDataKey, newValue) } + } + + #if os(macOS) + var cgImage: CGImage? { + return base.cgImage(forProposedRect: nil, context: nil, hints: nil) + } + + var scale: CGFloat { + return 1.0 + } + + private(set) var images: [KFCrossPlatformImage]? { + get { return getAssociatedObject(base, &imagesKey) } + set { setRetainedAssociatedObject(base, &imagesKey, newValue) } + } + + private(set) var duration: TimeInterval { + get { return getAssociatedObject(base, &durationKey) ?? 0.0 } + set { setRetainedAssociatedObject(base, &durationKey, newValue) } + } + + var size: CGSize { + return base.representations.reduce(.zero) { size, rep in + let width = max(size.width, CGFloat(rep.pixelsWide)) + let height = max(size.height, CGFloat(rep.pixelsHigh)) + return CGSize(width: width, height: height) + } + } + #else + var cgImage: CGImage? { return base.cgImage } + var scale: CGFloat { return base.scale } + var images: [KFCrossPlatformImage]? { return base.images } + var duration: TimeInterval { return base.duration } + var size: CGSize { return base.size } + + private(set) var imageSource: CGImageSource? { + get { return getAssociatedObject(base, &imageSourceKey) } + set { setRetainedAssociatedObject(base, &imageSourceKey, newValue) } + } + #endif + + // Bitmap memory cost with bytes. + var cost: Int { + let pixel = Int(size.width * size.height * scale * scale) + guard let cgImage = cgImage else { + return pixel * 4 + } + return pixel * cgImage.bitsPerPixel / 8 + } +} + +// MARK: - Image Conversion +extension KingfisherWrapper where Base: KFCrossPlatformImage { + #if os(macOS) + static func image(cgImage: CGImage, scale: CGFloat, refImage: KFCrossPlatformImage?) -> KFCrossPlatformImage { + return KFCrossPlatformImage(cgImage: cgImage, size: .zero) + } + + /// Normalize the image. This getter does nothing on macOS but return the image itself. + public var normalized: KFCrossPlatformImage { return base } + + #else + /// Creating an image from a give `CGImage` at scale and orientation for refImage. The method signature is for + /// compatibility of macOS version. + static func image(cgImage: CGImage, scale: CGFloat, refImage: KFCrossPlatformImage?) -> KFCrossPlatformImage { + return KFCrossPlatformImage(cgImage: cgImage, scale: scale, orientation: refImage?.imageOrientation ?? .up) + } + + /// Returns normalized image for current `base` image. + /// This method will try to redraw an image with orientation and scale considered. + public var normalized: KFCrossPlatformImage { + // prevent animated image (GIF) lose it's images + guard images == nil else { return base.copy() as! KFCrossPlatformImage } + // No need to do anything if already up + guard base.imageOrientation != .up else { return base.copy() as! KFCrossPlatformImage } + + return draw(to: size, inverting: true, refImage: KFCrossPlatformImage()) { + fixOrientation(in: $0) + return true + } + } + + func fixOrientation(in context: CGContext) { + + var transform = CGAffineTransform.identity + + let orientation = base.imageOrientation + + switch orientation { + case .down, .downMirrored: + transform = transform.translatedBy(x: size.width, y: size.height) + transform = transform.rotated(by: .pi) + case .left, .leftMirrored: + transform = transform.translatedBy(x: size.width, y: 0) + transform = transform.rotated(by: .pi / 2.0) + case .right, .rightMirrored: + transform = transform.translatedBy(x: 0, y: size.height) + transform = transform.rotated(by: .pi / -2.0) + case .up, .upMirrored: + break + #if compiler(>=5) + @unknown default: + break + #endif + } + + //Flip image one more time if needed to, this is to prevent flipped image + switch orientation { + case .upMirrored, .downMirrored: + transform = transform.translatedBy(x: size.width, y: 0) + transform = transform.scaledBy(x: -1, y: 1) + case .leftMirrored, .rightMirrored: + transform = transform.translatedBy(x: size.height, y: 0) + transform = transform.scaledBy(x: -1, y: 1) + case .up, .down, .left, .right: + break + #if compiler(>=5) + @unknown default: + break + #endif + } + + context.concatenate(transform) + switch orientation { + case .left, .leftMirrored, .right, .rightMirrored: + context.draw(cgImage!, in: CGRect(x: 0, y: 0, width: size.height, height: size.width)) + default: + context.draw(cgImage!, in: CGRect(x: 0, y: 0, width: size.width, height: size.height)) + } + } + #endif +} + +// MARK: - Image Representation +extension KingfisherWrapper where Base: KFCrossPlatformImage { + /// Returns PNG representation of `base` image. + /// + /// - Returns: PNG data of image. + public func pngRepresentation() -> Data? { + #if os(macOS) + guard let cgImage = cgImage else { + return nil + } + let rep = NSBitmapImageRep(cgImage: cgImage) + return rep.representation(using: .png, properties: [:]) + #else + #if swift(>=4.2) + return base.pngData() + #else + return UIImagePNGRepresentation(base) + #endif + #endif + } + + /// Returns JPEG representation of `base` image. + /// + /// - Parameter compressionQuality: The compression quality when converting image to JPEG data. + /// - Returns: JPEG data of image. + public func jpegRepresentation(compressionQuality: CGFloat) -> Data? { + #if os(macOS) + guard let cgImage = cgImage else { + return nil + } + let rep = NSBitmapImageRep(cgImage: cgImage) + return rep.representation(using:.jpeg, properties: [.compressionFactor: compressionQuality]) + #else + #if swift(>=4.2) + return base.jpegData(compressionQuality: compressionQuality) + #else + return UIImageJPEGRepresentation(base, compressionQuality) + #endif + #endif + } + + /// Returns GIF representation of `base` image. + /// + /// - Returns: Original GIF data of image. + public func gifRepresentation() -> Data? { + return animatedImageData + } + + /// Returns a data representation for `base` image, with the `format` as the format indicator. + /// + /// - Parameter format: The format in which the output data should be. If `unknown`, the `base` image will be + /// converted in the PNG representation. + /// + /// - Returns: The output data representing. + + /// Returns a data representation for `base` image, with the `format` as the format indicator. + /// - Parameters: + /// - format: The format in which the output data should be. If `unknown`, the `base` image will be + /// converted in the PNG representation. + /// - compressionQuality: The compression quality when converting image to a lossy format data. + public func data(format: ImageFormat, compressionQuality: CGFloat = 1.0) -> Data? { + return autoreleasepool { () -> Data? in + let data: Data? + switch format { + case .PNG: data = pngRepresentation() + case .JPEG: data = jpegRepresentation(compressionQuality: compressionQuality) + case .GIF: data = gifRepresentation() + case .unknown: data = normalized.kf.pngRepresentation() + } + + return data + } + } +} + +// MARK: - Creating Images +extension KingfisherWrapper where Base: KFCrossPlatformImage { + + /// Creates an animated image from a given data and options. Currently only GIF data is supported. + /// + /// - Parameters: + /// - data: The animated image data. + /// - options: Options to use when creating the animated image. + /// - Returns: An `Image` object represents the animated image. It is in form of an array of image frames with a + /// certain duration. `nil` if anything wrong when creating animated image. + public static func animatedImage(data: Data, options: ImageCreatingOptions) -> KFCrossPlatformImage? { + let info: [String: Any] = [ + kCGImageSourceShouldCache as String: true, + kCGImageSourceTypeIdentifierHint as String: kUTTypeGIF + ] + + guard let imageSource = CGImageSourceCreateWithData(data as CFData, info as CFDictionary) else { + return nil + } + + #if os(macOS) + guard let animatedImage = GIFAnimatedImage(from: imageSource, for: info, options: options) else { + return nil + } + var image: KFCrossPlatformImage? + if options.onlyFirstFrame { + image = animatedImage.images.first + } else { + image = KFCrossPlatformImage(data: data) + var kf = image?.kf + kf?.images = animatedImage.images + kf?.duration = animatedImage.duration + } + image?.kf.animatedImageData = data + return image + #else + + var image: KFCrossPlatformImage? + if options.preloadAll || options.onlyFirstFrame { + // Use `images` image if you want to preload all animated data + guard let animatedImage = GIFAnimatedImage(from: imageSource, for: info, options: options) else { + return nil + } + if options.onlyFirstFrame { + image = animatedImage.images.first + } else { + let duration = options.duration <= 0.0 ? animatedImage.duration : options.duration + image = .animatedImage(with: animatedImage.images, duration: duration) + } + image?.kf.animatedImageData = data + } else { + image = KFCrossPlatformImage(data: data, scale: options.scale) + var kf = image?.kf + kf?.imageSource = imageSource + kf?.animatedImageData = data + } + + return image + #endif + } + + /// Creates an image from a given data and options. `.JPEG`, `.PNG` or `.GIF` is supported. For other + /// image format, image initializer from system will be used. If no image object could be created from + /// the given `data`, `nil` will be returned. + /// + /// - Parameters: + /// - data: The image data representation. + /// - options: Options to use when creating the image. + /// - Returns: An `Image` object represents the image if created. If the `data` is invalid or not supported, `nil` + /// will be returned. + public static func image(data: Data, options: ImageCreatingOptions) -> KFCrossPlatformImage? { + var image: KFCrossPlatformImage? + switch data.kf.imageFormat { + case .JPEG: + image = KFCrossPlatformImage(data: data, scale: options.scale) + case .PNG: + image = KFCrossPlatformImage(data: data, scale: options.scale) + case .GIF: + image = KingfisherWrapper.animatedImage(data: data, options: options) + case .unknown: + image = KFCrossPlatformImage(data: data, scale: options.scale) + } + return image + } + + /// Creates a downsampled image from given data to a certain size and scale. + /// + /// - Parameters: + /// - data: The image data contains a JPEG or PNG image. + /// - pointSize: The target size in point to which the image should be downsampled. + /// - scale: The scale of result image. + /// - Returns: A downsampled `Image` object following the input conditions. + /// + /// - Note: + /// Different from image `resize` methods, downsampling will not render the original + /// input image in pixel format. It does downsampling from the image data, so it is much + /// more memory efficient and friendly. Choose to use downsampling as possible as you can. + /// + /// The input size should be smaller than the size of input image. If it is larger than the + /// original image size, the result image will be the same size of input without downsampling. + public static func downsampledImage(data: Data, to pointSize: CGSize, scale: CGFloat) -> KFCrossPlatformImage? { + let imageSourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary + guard let imageSource = CGImageSourceCreateWithData(data as CFData, imageSourceOptions) else { + return nil + } + + let maxDimensionInPixels = max(pointSize.width, pointSize.height) * scale + let downsampleOptions = [ + kCGImageSourceCreateThumbnailFromImageAlways: true, + kCGImageSourceShouldCacheImmediately: true, + kCGImageSourceCreateThumbnailWithTransform: true, + kCGImageSourceThumbnailMaxPixelSize: maxDimensionInPixels] as CFDictionary + guard let downsampledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, downsampleOptions) else { + return nil + } + return KingfisherWrapper.image(cgImage: downsampledImage, scale: scale, refImage: nil) + } +} diff --git b/Pods/Kingfisher/Sources/Image/ImageDrawing.swift a/Pods/Kingfisher/Sources/Image/ImageDrawing.swift new file mode 100644 index 0000000..6449431 --- /dev/null +++ a/Pods/Kingfisher/Sources/Image/ImageDrawing.swift @@ -0,0 +1,543 @@ +// +// ImageDrawing.swift +// Kingfisher +// +// Created by onevcat on 2018/09/28. +// +// Copyright (c) 2019 Wei Wang +// +// 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 Accelerate + +#if canImport(AppKit) && !targetEnvironment(macCatalyst) +import AppKit +#endif +#if canImport(UIKit) +import UIKit +#endif + +// MARK: - Image Transforming +extension KingfisherWrapper where Base: KFCrossPlatformImage { + // MARK: Blend Mode + /// Create image from `base` image and apply blend mode. + /// + /// - parameter blendMode: The blend mode of creating image. + /// - parameter alpha: The alpha should be used for image. + /// - parameter backgroundColor: The background color for the output image. + /// + /// - returns: An image with blend mode applied. + /// + /// - Note: This method only works for CG-based image. + #if !os(macOS) + public func image(withBlendMode blendMode: CGBlendMode, + alpha: CGFloat = 1.0, + backgroundColor: KFCrossPlatformColor? = nil) -> KFCrossPlatformImage + { + guard let _ = cgImage else { + assertionFailure("[Kingfisher] Blend mode image only works for CG-based image.") + return base + } + + let rect = CGRect(origin: .zero, size: size) + return draw(to: rect.size) { _ in + if let backgroundColor = backgroundColor { + backgroundColor.setFill() + UIRectFill(rect) + } + + base.draw(in: rect, blendMode: blendMode, alpha: alpha) + return false + } + } + #endif + + #if os(macOS) + // MARK: Compositing + /// Creates image from `base` image and apply compositing operation. + /// + /// - Parameters: + /// - compositingOperation: The compositing operation of creating image. + /// - alpha: The alpha should be used for image. + /// - backgroundColor: The background color for the output image. + /// - Returns: An image with compositing operation applied. + /// + /// - Note: This method only works for CG-based image. For any non-CG-based image, `base` itself is returned. + public func image(withCompositingOperation compositingOperation: NSCompositingOperation, + alpha: CGFloat = 1.0, + backgroundColor: KFCrossPlatformColor? = nil) -> KFCrossPlatformImage + { + guard let _ = cgImage else { + assertionFailure("[Kingfisher] Compositing Operation image only works for CG-based image.") + return base + } + + let rect = CGRect(origin: .zero, size: size) + return draw(to: rect.size) { _ in + if let backgroundColor = backgroundColor { + backgroundColor.setFill() + rect.fill() + } + base.draw(in: rect, from: .zero, operation: compositingOperation, fraction: alpha) + return false + } + } + #endif + + // MARK: Round Corner + /// Creates a round corner image from on `base` image. + /// + /// - Parameters: + /// - radius: The round corner radius of creating image. + /// - size: The target size of creating image. + /// - corners: The target corners which will be applied rounding. + /// - backgroundColor: The background color for the output image + /// - Returns: An image with round corner of `self`. + /// + /// - Note: This method only works for CG-based image. The current image scale is kept. + /// For any non-CG-based image, `base` itself is returned. + public func image(withRoundRadius radius: CGFloat, + fit size: CGSize, + roundingCorners corners: RectCorner = .all, + backgroundColor: KFCrossPlatformColor? = nil) -> KFCrossPlatformImage + { + guard let _ = cgImage else { + assertionFailure("[Kingfisher] Round corner image only works for CG-based image.") + return base + } + + let rect = CGRect(origin: CGPoint(x: 0, y: 0), size: size) + return draw(to: size) { _ in + #if os(macOS) + if let backgroundColor = backgroundColor { + let rectPath = NSBezierPath(rect: rect) + backgroundColor.setFill() + rectPath.fill() + } + + let path = NSBezierPath(roundedRect: rect, byRoundingCorners: corners, radius: radius) + #if swift(>=4.2) + path.windingRule = .evenOdd + #else + path.windingRule = .evenOddWindingRule + #endif + path.addClip() + base.draw(in: rect) + #else + guard let context = UIGraphicsGetCurrentContext() else { + assertionFailure("[Kingfisher] Failed to create CG context for image.") + return false + } + + if let backgroundColor = backgroundColor { + let rectPath = UIBezierPath(rect: rect) + backgroundColor.setFill() + rectPath.fill() + } + + let path = UIBezierPath( + roundedRect: rect, + byRoundingCorners: corners.uiRectCorner, + cornerRadii: CGSize(width: radius, height: radius) + ) + context.addPath(path.cgPath) + context.clip() + base.draw(in: rect) + #endif + return false + } + } + + #if os(iOS) || os(tvOS) + func resize(to size: CGSize, for contentMode: UIView.ContentMode) -> KFCrossPlatformImage { + switch contentMode { + case .scaleAspectFit: + return resize(to: size, for: .aspectFit) + case .scaleAspectFill: + return resize(to: size, for: .aspectFill) + default: + return resize(to: size) + } + } + #endif + + // MARK: Resizing + /// Resizes `base` image to an image with new size. + /// + /// - Parameter size: The target size in point. + /// - Returns: An image with new size. + /// - Note: This method only works for CG-based image. The current image scale is kept. + /// For any non-CG-based image, `base` itself is returned. + public func resize(to size: CGSize) -> KFCrossPlatformImage { + guard let _ = cgImage else { + assertionFailure("[Kingfisher] Resize only works for CG-based image.") + return base + } + + let rect = CGRect(origin: CGPoint(x: 0, y: 0), size: size) + return draw(to: size) { _ in + #if os(macOS) + base.draw(in: rect, from: .zero, operation: .copy, fraction: 1.0) + #else + base.draw(in: rect) + #endif + return false + } + } + + /// Resizes `base` image to an image of new size, respecting the given content mode. + /// + /// - Parameters: + /// - targetSize: The target size in point. + /// - contentMode: Content mode of output image should be. + /// - Returns: An image with new size. + /// + /// - Note: This method only works for CG-based image. The current image scale is kept. + /// For any non-CG-based image, `base` itself is returned. + public func resize(to targetSize: CGSize, for contentMode: ContentMode) -> KFCrossPlatformImage { + let newSize = size.kf.resize(to: targetSize, for: contentMode) + return resize(to: newSize) + } + + // MARK: Cropping + /// Crops `base` image to a new size with a given anchor. + /// + /// - Parameters: + /// - size: The target size. + /// - anchor: The anchor point from which the size should be calculated. + /// - Returns: An image with new size. + /// + /// - Note: This method only works for CG-based image. The current image scale is kept. + /// For any non-CG-based image, `base` itself is returned. + public func crop(to size: CGSize, anchorOn anchor: CGPoint) -> KFCrossPlatformImage { + guard let cgImage = cgImage else { + assertionFailure("[Kingfisher] Crop only works for CG-based image.") + return base + } + + let rect = self.size.kf.constrainedRect(for: size, anchor: anchor) + guard let image = cgImage.cropping(to: rect.scaled(scale)) else { + assertionFailure("[Kingfisher] Cropping image failed.") + return base + } + + return KingfisherWrapper.image(cgImage: image, scale: scale, refImage: base) + } + + // MARK: Blur + /// Creates an image with blur effect based on `base` image. + /// + /// - Parameter radius: The blur radius should be used when creating blur effect. + /// - Returns: An image with blur effect applied. + /// + /// - Note: This method only works for CG-based image. The current image scale is kept. + /// For any non-CG-based image, `base` itself is returned. + public func blurred(withRadius radius: CGFloat) -> KFCrossPlatformImage { + + guard let cgImage = cgImage else { + assertionFailure("[Kingfisher] Blur only works for CG-based image.") + return base + } + + // http://www.w3.org/TR/SVG/filters.html#feGaussianBlurElement + // let d = floor(s * 3*sqrt(2*pi)/4 + 0.5) + // if d is odd, use three box-blurs of size 'd', centered on the output pixel. + let s = max(radius, 2.0) + // We will do blur on a resized image (*0.5), so the blur radius could be half as well. + + // Fix the slow compiling time for Swift 3. + // See https://github.com/onevcat/Kingfisher/issues/611 + let pi2 = 2 * CGFloat.pi + let sqrtPi2 = sqrt(pi2) + var targetRadius = floor(s * 3.0 * sqrtPi2 / 4.0 + 0.5) + + if targetRadius.isEven { targetRadius += 1 } + + // Determine necessary iteration count by blur radius. + let iterations: Int + if radius < 0.5 { + iterations = 1 + } else if radius < 1.5 { + iterations = 2 + } else { + iterations = 3 + } + + let w = Int(size.width) + let h = Int(size.height) + + func createEffectBuffer(_ context: CGContext) -> vImage_Buffer { + let data = context.data + let width = vImagePixelCount(context.width) + let height = vImagePixelCount(context.height) + let rowBytes = context.bytesPerRow + + return vImage_Buffer(data: data, height: height, width: width, rowBytes: rowBytes) + } + + guard let context = beginContext(size: size, scale: scale, inverting: true) else { + assertionFailure("[Kingfisher] Failed to create CG context for blurring image.") + return base + } + context.draw(cgImage, in: CGRect(x: 0, y: 0, width: w, height: h)) + endContext() + + var inBuffer = createEffectBuffer(context) + + guard let outContext = beginContext(size: size, scale: scale, inverting: true) else { + assertionFailure("[Kingfisher] Failed to create CG context for blurring image.") + return base + } + defer { endContext() } + var outBuffer = createEffectBuffer(outContext) + + for _ in 0 ..< iterations { + let flag = vImage_Flags(kvImageEdgeExtend) + vImageBoxConvolve_ARGB8888( + &inBuffer, &outBuffer, nil, 0, 0, UInt32(targetRadius), UInt32(targetRadius), nil, flag) + // Next inBuffer should be the outButter of current iteration + (inBuffer, outBuffer) = (outBuffer, inBuffer) + } + + #if os(macOS) + let result = outContext.makeImage().flatMap { + fixedForRetinaPixel(cgImage: $0, to: size) + } + #else + let result = outContext.makeImage().flatMap { + KFCrossPlatformImage(cgImage: $0, scale: base.scale, orientation: base.imageOrientation) + } + #endif + guard let blurredImage = result else { + assertionFailure("[Kingfisher] Can not make an blurred image within this context.") + return base + } + + return blurredImage + } + + // MARK: Overlay + /// Creates an image from `base` image with a color overlay layer. + /// + /// - Parameters: + /// - color: The color should be use to overlay. + /// - fraction: Fraction of input color. From 0.0 to 1.0. 0.0 means solid color, + /// 1.0 means transparent overlay. + /// - Returns: An image with a color overlay applied. + /// + /// - Note: This method only works for CG-based image. The current image scale is kept. + /// For any non-CG-based image, `base` itself is returned. + public func overlaying(with color: KFCrossPlatformColor, fraction: CGFloat) -> KFCrossPlatformImage { + + guard let _ = cgImage else { + assertionFailure("[Kingfisher] Overlaying only works for CG-based image.") + return base + } + + let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height) + return draw(to: rect.size) { context in + #if os(macOS) + base.draw(in: rect) + if fraction > 0 { + color.withAlphaComponent(1 - fraction).set() + rect.fill(using: .sourceAtop) + } + #else + color.set() + UIRectFill(rect) + base.draw(in: rect, blendMode: .destinationIn, alpha: 1.0) + + if fraction > 0 { + base.draw(in: rect, blendMode: .sourceAtop, alpha: fraction) + } + #endif + return false + } + } + + // MARK: Tint + /// Creates an image from `base` image with a color tint. + /// + /// - Parameter color: The color should be used to tint `base` + /// - Returns: An image with a color tint applied. + public func tinted(with color: KFCrossPlatformColor) -> KFCrossPlatformImage { + #if os(watchOS) + return base + #else + return apply(.tint(color)) + #endif + } + + // MARK: Color Control + + /// Create an image from `self` with color control. + /// + /// - Parameters: + /// - brightness: Brightness changing to image. + /// - contrast: Contrast changing to image. + /// - saturation: Saturation changing to image. + /// - inputEV: InputEV changing to image. + /// - Returns: An image with color control applied. + public func adjusted(brightness: CGFloat, contrast: CGFloat, saturation: CGFloat, inputEV: CGFloat) -> KFCrossPlatformImage { + #if os(watchOS) + return base + #else + return apply(.colorControl((brightness, contrast, saturation, inputEV))) + #endif + } + + /// Return an image with given scale. + /// + /// - Parameter scale: Target scale factor the new image should have. + /// - Returns: The image with target scale. If the base image is already in the scale, `base` will be returned. + public func scaled(to scale: CGFloat) -> KFCrossPlatformImage { + guard scale != self.scale else { + return base + } + guard let cgImage = cgImage else { + assertionFailure("[Kingfisher] Scaling only works for CG-based image.") + return base + } + return KingfisherWrapper.image(cgImage: cgImage, scale: scale, refImage: base) + } +} + +// MARK: - Decoding Image +extension KingfisherWrapper where Base: KFCrossPlatformImage { + + /// Returns the decoded image of the `base` image. It will draw the image in a plain context and return the data + /// from it. This could improve the drawing performance when an image is just created from data but not yet + /// displayed for the first time. + /// + /// - Note: This method only works for CG-based image. The current image scale is kept. + /// For any non-CG-based image or animated image, `base` itself is returned. + public var decoded: KFCrossPlatformImage { return decoded(scale: scale) } + + /// Returns decoded image of the `base` image at a given scale. It will draw the image in a plain context and + /// return the data from it. This could improve the drawing performance when an image is just created from + /// data but not yet displayed for the first time. + /// + /// - Parameter scale: The given scale of target image should be. + /// - Returns: The decoded image ready to be displayed. + /// + /// - Note: This method only works for CG-based image. The current image scale is kept. + /// For any non-CG-based image or animated image, `base` itself is returned. + public func decoded(scale: CGFloat) -> KFCrossPlatformImage { + // Prevent animated image (GIF) losing it's images + #if os(iOS) + if imageSource != nil { return base } + #else + if images != nil { return base } + #endif + + guard let imageRef = cgImage else { + assertionFailure("[Kingfisher] Decoding only works for CG-based image.") + return base + } + + let size = CGSize(width: CGFloat(imageRef.width) / scale, height: CGFloat(imageRef.height) / scale) + return draw(to: size, inverting: true, scale: scale) { context in + context.draw(imageRef, in: CGRect(origin: .zero, size: size)) + return true + } + } +} + +extension KingfisherWrapper where Base: KFCrossPlatformImage { + + func beginContext(size: CGSize, scale: CGFloat, inverting: Bool = false) -> CGContext? { + #if os(macOS) + guard let rep = NSBitmapImageRep( + bitmapDataPlanes: nil, + pixelsWide: Int(size.width), + pixelsHigh: Int(size.height), + bitsPerSample: cgImage?.bitsPerComponent ?? 8, + samplesPerPixel: 4, + hasAlpha: true, + isPlanar: false, + colorSpaceName: .calibratedRGB, + bytesPerRow: 0, + bitsPerPixel: 0) else + { + assertionFailure("[Kingfisher] Image representation cannot be created.") + return nil + } + rep.size = size + NSGraphicsContext.saveGraphicsState() + guard let context = NSGraphicsContext(bitmapImageRep: rep) else { + assertionFailure("[Kingfisher] Image context cannot be created.") + return nil + } + + NSGraphicsContext.current = context + return context.cgContext + #else + UIGraphicsBeginImageContextWithOptions(size, false, scale) + guard let context = UIGraphicsGetCurrentContext() else { return nil } + if inverting { // If drawing a CGImage, we need to make context flipped. + context.scaleBy(x: 1.0, y: -1.0) + context.translateBy(x: 0, y: -size.height) + } + return context + #endif + } + + func endContext() { + #if os(macOS) + NSGraphicsContext.restoreGraphicsState() + #else + UIGraphicsEndImageContext() + #endif + } + + func draw( + to size: CGSize, + inverting: Bool = false, + scale: CGFloat? = nil, + refImage: KFCrossPlatformImage? = nil, + draw: (CGContext) -> Bool // Whether use the refImage (`true`) or ignore image orientation (`false`) + ) -> KFCrossPlatformImage + { + let targetScale = scale ?? self.scale + guard let context = beginContext(size: size, scale: targetScale, inverting: inverting) else { + assertionFailure("[Kingfisher] Failed to create CG context for blurring image.") + return base + } + defer { endContext() } + let useRefImage = draw(context) + guard let cgImage = context.makeImage() else { + return base + } + let ref = useRefImage ? (refImage ?? base) : nil + return KingfisherWrapper.image(cgImage: cgImage, scale: targetScale, refImage: ref) + } + + #if os(macOS) + func fixedForRetinaPixel(cgImage: CGImage, to size: CGSize) -> KFCrossPlatformImage { + + let image = KFCrossPlatformImage(cgImage: cgImage, size: base.size) + let rect = CGRect(origin: CGPoint(x: 0, y: 0), size: size) + + return draw(to: self.size) { context in + image.draw(in: rect, from: .zero, operation: .copy, fraction: 1.0) + return false + } + } + #endif +} diff --git b/Pods/Kingfisher/Sources/Image/ImageFormat.swift a/Pods/Kingfisher/Sources/Image/ImageFormat.swift new file mode 100644 index 0000000..14e3c7d --- /dev/null +++ a/Pods/Kingfisher/Sources/Image/ImageFormat.swift @@ -0,0 +1,131 @@ +// +// ImageFormat.swift +// Kingfisher +// +// Created by onevcat on 2018/09/28. +// +// Copyright (c) 2019 Wei Wang +// +// 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 + +/// Represents image format. +/// +/// - unknown: The format cannot be recognized or not supported yet. +/// - PNG: PNG image format. +/// - JPEG: JPEG image format. +/// - GIF: GIF image format. +public enum ImageFormat { + /// The format cannot be recognized or not supported yet. + case unknown + /// PNG image format. + case PNG + /// JPEG image format. + case JPEG + /// GIF image format. + case GIF + + struct HeaderData { + static var PNG: [UInt8] = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A] + static var JPEG_SOI: [UInt8] = [0xFF, 0xD8] + static var JPEG_IF: [UInt8] = [0xFF] + static var GIF: [UInt8] = [0x47, 0x49, 0x46] + } + + /// https://en.wikipedia.org/wiki/JPEG + public enum JPEGMarker { + case SOF0 //baseline + case SOF2 //progressive + case DHT //Huffman Table + case DQT //Quantization Table + case DRI //Restart Interval + case SOS //Start Of Scan + case RSTn(UInt8) //Restart + case APPn //Application-specific + case COM //Comment + case EOI //End Of Image + + var bytes: [UInt8] { + switch self { + case .SOF0: return [0xFF, 0xC0] + case .SOF2: return [0xFF, 0xC2] + case .DHT: return [0xFF, 0xC4] + case .DQT: return [0xFF, 0xDB] + case .DRI: return [0xFF, 0xDD] + case .SOS: return [0xFF, 0xDA] + case .RSTn(let n): return [0xFF, 0xD0 + n] + case .APPn: return [0xFF, 0xE0] + case .COM: return [0xFF, 0xFE] + case .EOI: return [0xFF, 0xD9] + } + } + } +} + + +extension Data: KingfisherCompatibleValue {} + +// MARK: - Misc Helpers +extension KingfisherWrapper where Base == Data { + /// Gets the image format corresponding to the data. + public var imageFormat: ImageFormat { + guard base.count > 8 else { return .unknown } + + var buffer = [UInt8](repeating: 0, count: 8) + base.copyBytes(to: &buffer, count: 8) + + if buffer == ImageFormat.HeaderData.PNG { + return .PNG + + } else if buffer[0] == ImageFormat.HeaderData.JPEG_SOI[0], + buffer[1] == ImageFormat.HeaderData.JPEG_SOI[1], + buffer[2] == ImageFormat.HeaderData.JPEG_IF[0] + { + return .JPEG + + } else if buffer[0] == ImageFormat.HeaderData.GIF[0], + buffer[1] == ImageFormat.HeaderData.GIF[1], + buffer[2] == ImageFormat.HeaderData.GIF[2] + { + return .GIF + } + + return .unknown + } + + public func contains(jpeg marker: ImageFormat.JPEGMarker) -> Bool { + guard imageFormat == .JPEG else { + return false + } + + var buffer = [UInt8](repeating: 0, count: base.count) + base.copyBytes(to: &buffer, count: base.count) + for (index, item) in buffer.enumerated() { + guard + item == marker.bytes.first, + buffer.count > index + 1, + buffer[index + 1] == marker.bytes[1] else { + continue + } + return true + } + return false + } +} diff --git b/Pods/Kingfisher/Sources/Image/ImageProcessor.swift a/Pods/Kingfisher/Sources/Image/ImageProcessor.swift new file mode 100644 index 0000000..887eddc --- /dev/null +++ a/Pods/Kingfisher/Sources/Image/ImageProcessor.swift @@ -0,0 +1,848 @@ +// +// ImageProcessor.swift +// Kingfisher +// +// Created by Wei Wang on 2016/08/26. +// +// Copyright (c) 2019 Wei Wang +// +// 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 +import CoreGraphics + +#if canImport(AppKit) && !targetEnvironment(macCatalyst) +import AppKit +#endif + +/// Represents an item which could be processed by an `ImageProcessor`. +/// +/// - image: Input image. The processor should provide a way to apply +/// processing on this `image` and return the result image. +/// - data: Input data. The processor should provide a way to apply +/// processing on this `image` and return the result image. +public enum ImageProcessItem { + + /// Input image. The processor should provide a way to apply + /// processing on this `image` and return the result image. + case image(KFCrossPlatformImage) + + /// Input data. The processor should provide a way to apply + /// processing on this `image` and return the result image. + case data(Data) +} + +/// An `ImageProcessor` would be used to convert some downloaded data to an image. +public protocol ImageProcessor { + /// Identifier of the processor. It will be used to identify the processor when + /// caching and retrieving an image. You might want to make sure that processors with + /// same properties/functionality have the same identifiers, so correct processed images + /// could be retrieved with proper key. + /// + /// - Note: Do not supply an empty string for a customized processor, which is already reserved by + /// the `DefaultImageProcessor`. It is recommended to use a reverse domain name notation string of + /// your own for the identifier. + var identifier: String { get } + + /// Processes the input `ImageProcessItem` with this processor. + /// + /// - Parameters: + /// - item: Input item which will be processed by `self`. + /// - options: Options when processing the item. + /// - Returns: The processed image. + /// + /// - Note: The return value should be `nil` if processing failed while converting an input item to image. + /// If `nil` received by the processing caller, an error will be reported and the process flow stops. + /// If the processing flow is not critical for your flow, then when the input item is already an image + /// (`.image` case) and there is any errors in the processing, you could return the input image itself + /// to keep the processing pipeline continuing. + /// - Note: Most processor only supports CG-based images. watchOS is not supported for processors containing + /// a filter, the input image will be returned directly on watchOS. + /// - Note: + /// This method is deprecated. Please implement the version with + /// `KingfisherParsedOptionsInfo` as parameter instead. + @available(*, deprecated, + message: "Deprecated. Implement the method with same name but with `KingfisherParsedOptionsInfo` instead.") + func process(item: ImageProcessItem, options: KingfisherOptionsInfo) -> KFCrossPlatformImage? + + /// Processes the input `ImageProcessItem` with this processor. + /// + /// - Parameters: + /// - item: Input item which will be processed by `self`. + /// - options: The parsed options when processing the item. + /// - Returns: The processed image. + /// + /// - Note: The return value should be `nil` if processing failed while converting an input item to image. + /// If `nil` received by the processing caller, an error will be reported and the process flow stops. + /// If the processing flow is not critical for your flow, then when the input item is already an image + /// (`.image` case) and there is any errors in the processing, you could return the input image itself + /// to keep the processing pipeline continuing. + /// - Note: Most processor only supports CG-based images. watchOS is not supported for processors containing + /// a filter, the input image will be returned directly on watchOS. + func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? +} + +extension ImageProcessor { + public func process(item: ImageProcessItem, options: KingfisherOptionsInfo) -> KFCrossPlatformImage? { + return process(item: item, options: KingfisherParsedOptionsInfo(options)) + } +} + +extension ImageProcessor { + + /// Appends an `ImageProcessor` to another. The identifier of the new `ImageProcessor` + /// will be "\(self.identifier)|>\(another.identifier)". + /// + /// - Parameter another: An `ImageProcessor` you want to append to `self`. + /// - Returns: The new `ImageProcessor` will process the image in the order + /// of the two processors concatenated. + public func append(another: ImageProcessor) -> ImageProcessor { + let newIdentifier = identifier.appending("|>\(another.identifier)") + return GeneralProcessor(identifier: newIdentifier) { + item, options in + if let image = self.process(item: item, options: options) { + return another.process(item: .image(image), options: options) + } else { + return nil + } + } + } +} + +func ==(left: ImageProcessor, right: ImageProcessor) -> Bool { + return left.identifier == right.identifier +} + +func !=(left: ImageProcessor, right: ImageProcessor) -> Bool { + return !(left == right) +} + +typealias ProcessorImp = ((ImageProcessItem, KingfisherParsedOptionsInfo) -> KFCrossPlatformImage?) +struct GeneralProcessor: ImageProcessor { + let identifier: String + let p: ProcessorImp + func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { + return p(item, options) + } +} + +/// The default processor. It converts the input data to a valid image. +/// Images of .PNG, .JPEG and .GIF format are supported. +/// If an image item is given as `.image` case, `DefaultImageProcessor` will +/// do nothing on it and return the associated image. +public struct DefaultImageProcessor: ImageProcessor { + + /// A default `DefaultImageProcessor` could be used across. + public static let `default` = DefaultImageProcessor() + + /// Identifier of the processor. + /// - Note: See documentation of `ImageProcessor` protocol for more. + public let identifier = "" + + /// Creates a `DefaultImageProcessor`. Use `DefaultImageProcessor.default` to get an instance, + /// if you do not have a good reason to create your own `DefaultImageProcessor`. + public init() {} + + /// Processes the input `ImageProcessItem` with this processor. + /// + /// - Parameters: + /// - item: Input item which will be processed by `self`. + /// - options: Options when processing the item. + /// - Returns: The processed image. + /// + /// - Note: See documentation of `ImageProcessor` protocol for more. + public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { + switch item { + case .image(let image): + return image.kf.scaled(to: options.scaleFactor) + case .data(let data): + return KingfisherWrapper.image(data: data, options: options.imageCreatingOptions) + } + } +} + +/// Represents the rect corner setting when processing a round corner image. +public struct RectCorner: OptionSet { + + /// Raw value of the rect corner. + public let rawValue: Int + + /// Represents the top left corner. + public static let topLeft = RectCorner(rawValue: 1 << 0) + + /// Represents the top right corner. + public static let topRight = RectCorner(rawValue: 1 << 1) + + /// Represents the bottom left corner. + public static let bottomLeft = RectCorner(rawValue: 1 << 2) + + /// Represents the bottom right corner. + public static let bottomRight = RectCorner(rawValue: 1 << 3) + + /// Represents all corners. + public static let all: RectCorner = [.topLeft, .topRight, .bottomLeft, .bottomRight] + + /// Creates a `RectCorner` option set with a given value. + /// + /// - Parameter rawValue: The value represents a certain corner option. + public init(rawValue: Int) { + self.rawValue = rawValue + } + + var cornerIdentifier: String { + if self == .all { + return "" + } + return "_corner(\(rawValue))" + } +} + +#if !os(macOS) +/// Processor for adding an blend mode to images. Only CG-based images are supported. +public struct BlendImageProcessor: ImageProcessor { + + /// Identifier of the processor. + /// - Note: See documentation of `ImageProcessor` protocol for more. + public let identifier: String + + /// Blend Mode will be used to blend the input image. + public let blendMode: CGBlendMode + + /// Alpha will be used when blend image. + public let alpha: CGFloat + + /// Background color of the output image. If `nil`, it will stay transparent. + public let backgroundColor: KFCrossPlatformColor? + + /// Creates a `BlendImageProcessor`. + /// + /// - Parameters: + /// - blendMode: Blend Mode will be used to blend the input image. + /// - alpha: Alpha will be used when blend image. From 0.0 to 1.0. 1.0 means solid image, + /// 0.0 means transparent image (not visible at all). Default is 1.0. + /// - backgroundColor: Background color to apply for the output image. Default is `nil`. + public init(blendMode: CGBlendMode, alpha: CGFloat = 1.0, backgroundColor: KFCrossPlatformColor? = nil) { + self.blendMode = blendMode + self.alpha = alpha + self.backgroundColor = backgroundColor + var identifier = "com.onevcat.Kingfisher.BlendImageProcessor(\(blendMode.rawValue),\(alpha))" + if let color = backgroundColor { + identifier.append("_\(color.hex)") + } + self.identifier = identifier + } + + /// Processes the input `ImageProcessItem` with this processor. + /// + /// - Parameters: + /// - item: Input item which will be processed by `self`. + /// - options: Options when processing the item. + /// - Returns: The processed image. + /// + /// - Note: See documentation of `ImageProcessor` protocol for more. + public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { + switch item { + case .image(let image): + return image.kf.scaled(to: options.scaleFactor) + .kf.image(withBlendMode: blendMode, alpha: alpha, backgroundColor: backgroundColor) + case .data: + return (DefaultImageProcessor.default |> self).process(item: item, options: options) + } + } +} +#endif + +#if os(macOS) +/// Processor for adding an compositing operation to images. Only CG-based images are supported in macOS. +public struct CompositingImageProcessor: ImageProcessor { + + /// Identifier of the processor. + /// - Note: See documentation of `ImageProcessor` protocol for more. + public let identifier: String + + /// Compositing operation will be used to the input image. + public let compositingOperation: NSCompositingOperation + + /// Alpha will be used when compositing image. + public let alpha: CGFloat + + /// Background color of the output image. If `nil`, it will stay transparent. + public let backgroundColor: KFCrossPlatformColor? + + /// Creates a `CompositingImageProcessor` + /// + /// - Parameters: + /// - compositingOperation: Compositing operation will be used to the input image. + /// - alpha: Alpha will be used when compositing image. + /// From 0.0 to 1.0. 1.0 means solid image, 0.0 means transparent image. + /// Default is 1.0. + /// - backgroundColor: Background color to apply for the output image. Default is `nil`. + public init(compositingOperation: NSCompositingOperation, + alpha: CGFloat = 1.0, + backgroundColor: KFCrossPlatformColor? = nil) + { + self.compositingOperation = compositingOperation + self.alpha = alpha + self.backgroundColor = backgroundColor + var identifier = "com.onevcat.Kingfisher.CompositingImageProcessor(\(compositingOperation.rawValue),\(alpha))" + if let color = backgroundColor { + identifier.append("_\(color.hex)") + } + self.identifier = identifier + } + + /// Processes the input `ImageProcessItem` with this processor. + /// + /// - Parameters: + /// - item: Input item which will be processed by `self`. + /// - options: Options when processing the item. + /// - Returns: The processed image. + /// + /// - Note: See documentation of `ImageProcessor` protocol for more. + public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { + switch item { + case .image(let image): + return image.kf.scaled(to: options.scaleFactor) + .kf.image( + withCompositingOperation: compositingOperation, + alpha: alpha, + backgroundColor: backgroundColor) + case .data: + return (DefaultImageProcessor.default |> self).process(item: item, options: options) + } + } +} +#endif + +/// Processor for making round corner images. Only CG-based images are supported in macOS, +/// if a non-CG image passed in, the processor will do nothing. +/// +/// - Note: The input image will be rendered with round corner pixels removed. If the image itself does not contain +/// alpha channel (for example, a JPEG image), the processed image will contain an alpha channel in memory in order +/// to show correctly. However, when cached to disk, Kingfisher respects the original image format by default. That +/// means the alpha channel will be removed for these images. When you load the processed image from cache again, you +/// will lose transparent corner. +/// +/// You could use `FormatIndicatedCacheSerializer.png` to force Kingfisher to serialize the image to PNG format in this +/// case. +/// +public struct RoundCornerImageProcessor: ImageProcessor { + + /// Identifier of the processor. + /// - Note: See documentation of `ImageProcessor` protocol for more. + public let identifier: String + + /// Corner radius will be applied in processing. + public let cornerRadius: CGFloat + + /// The target corners which will be applied rounding. + public let roundingCorners: RectCorner + + /// Target size of output image should be. If `nil`, the image will keep its original size after processing. + public let targetSize: CGSize? + + /// Background color of the output image. If `nil`, it will use a transparent background. + public let backgroundColor: KFCrossPlatformColor? + + /// Creates a `RoundCornerImageProcessor`. + /// + /// - Parameters: + /// - cornerRadius: Corner radius will be applied in processing. + /// - targetSize: Target size of output image should be. If `nil`, + /// the image will keep its original size after processing. + /// Default is `nil`. + /// - corners: The target corners which will be applied rounding. Default is `.all`. + /// - backgroundColor: Background color to apply for the output image. Default is `nil`. + public init( + cornerRadius: CGFloat, + targetSize: CGSize? = nil, + roundingCorners corners: RectCorner = .all, + backgroundColor: KFCrossPlatformColor? = nil) + { + self.cornerRadius = cornerRadius + self.targetSize = targetSize + self.roundingCorners = corners + self.backgroundColor = backgroundColor + + self.identifier = { + var identifier = "" + + if let size = targetSize { + identifier = "com.onevcat.Kingfisher.RoundCornerImageProcessor" + + "(\(cornerRadius)_\(size)\(corners.cornerIdentifier))" + } else { + identifier = "com.onevcat.Kingfisher.RoundCornerImageProcessor" + + "(\(cornerRadius)\(corners.cornerIdentifier))" + } + if let backgroundColor = backgroundColor { + identifier += "_\(backgroundColor)" + } + + return identifier + }() + } + + /// Processes the input `ImageProcessItem` with this processor. + /// + /// - Parameters: + /// - item: Input item which will be processed by `self`. + /// - options: Options when processing the item. + /// - Returns: The processed image. + /// + /// - Note: See documentation of `ImageProcessor` protocol for more. + public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { + switch item { + case .image(let image): + let size = targetSize ?? image.kf.size + return image.kf.scaled(to: options.scaleFactor) + .kf.image( + withRoundRadius: cornerRadius, + fit: size, + roundingCorners: roundingCorners, + backgroundColor: backgroundColor) + case .data: + return (DefaultImageProcessor.default |> self).process(item: item, options: options) + } + } +} + + +/// Represents how a size adjusts itself to fit a target size. +/// +/// - none: Not scale the content. +/// - aspectFit: Scales the content to fit the size of the view by maintaining the aspect ratio. +/// - aspectFill: Scales the content to fill the size of the view. +public enum ContentMode { + /// Not scale the content. + case none + /// Scales the content to fit the size of the view by maintaining the aspect ratio. + case aspectFit + /// Scales the content to fill the size of the view. + case aspectFill +} + +/// Processor for resizing images. +/// If you need to resize a data represented image to a smaller size, use `DownsamplingImageProcessor` +/// instead, which is more efficient and takes less memory. +public struct ResizingImageProcessor: ImageProcessor { + + /// Identifier of the processor. + /// - Note: See documentation of `ImageProcessor` protocol for more. + public let identifier: String + + /// The reference size for resizing operation in point. + public let referenceSize: CGSize + + /// Target content mode of output image should be. + /// Default is `.none`. + public let targetContentMode: ContentMode + + /// Creates a `ResizingImageProcessor`. + /// + /// - Parameters: + /// - referenceSize: The reference size for resizing operation in point. + /// - mode: Target content mode of output image should be. + /// + /// - Note: + /// The instance of `ResizingImageProcessor` will follow its `mode` property + /// and try to resizing the input images to fit or fill the `referenceSize`. + /// That means if you are using a `mode` besides of `.none`, you may get an + /// image with its size not be the same as the `referenceSize`. + /// + /// **Example**: With input image size: {100, 200}, + /// `referenceSize`: {100, 100}, `mode`: `.aspectFit`, + /// you will get an output image with size of {50, 100}, which "fit"s + /// the `referenceSize`. + /// + /// If you need an output image exactly to be a specified size, append or use + /// a `CroppingImageProcessor`. + public init(referenceSize: CGSize, mode: ContentMode = .none) { + self.referenceSize = referenceSize + self.targetContentMode = mode + + if mode == .none { + self.identifier = "com.onevcat.Kingfisher.ResizingImageProcessor(\(referenceSize))" + } else { + self.identifier = "com.onevcat.Kingfisher.ResizingImageProcessor(\(referenceSize), \(mode))" + } + } + + /// Processes the input `ImageProcessItem` with this processor. + /// + /// - Parameters: + /// - item: Input item which will be processed by `self`. + /// - options: Options when processing the item. + /// - Returns: The processed image. + /// + /// - Note: See documentation of `ImageProcessor` protocol for more. + public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { + switch item { + case .image(let image): + return image.kf.scaled(to: options.scaleFactor) + .kf.resize(to: referenceSize, for: targetContentMode) + case .data: + return (DefaultImageProcessor.default |> self).process(item: item, options: options) + } + } +} + +/// Processor for adding blur effect to images. `Accelerate.framework` is used underhood for +/// a better performance. A simulated Gaussian blur with specified blur radius will be applied. +public struct BlurImageProcessor: ImageProcessor { + + /// Identifier of the processor. + /// - Note: See documentation of `ImageProcessor` protocol for more. + public let identifier: String + + /// Blur radius for the simulated Gaussian blur. + public let blurRadius: CGFloat + + /// Creates a `BlurImageProcessor` + /// + /// - parameter blurRadius: Blur radius for the simulated Gaussian blur. + public init(blurRadius: CGFloat) { + self.blurRadius = blurRadius + self.identifier = "com.onevcat.Kingfisher.BlurImageProcessor(\(blurRadius))" + } + + /// Processes the input `ImageProcessItem` with this processor. + /// + /// - Parameters: + /// - item: Input item which will be processed by `self`. + /// - options: Options when processing the item. + /// - Returns: The processed image. + /// + /// - Note: See documentation of `ImageProcessor` protocol for more. + public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { + switch item { + case .image(let image): + let radius = blurRadius * options.scaleFactor + return image.kf.scaled(to: options.scaleFactor) + .kf.blurred(withRadius: radius) + case .data: + return (DefaultImageProcessor.default |> self).process(item: item, options: options) + } + } +} + +/// Processor for adding an overlay to images. Only CG-based images are supported in macOS. +public struct OverlayImageProcessor: ImageProcessor { + + /// Identifier of the processor. + /// - Note: See documentation of `ImageProcessor` protocol for more. + public let identifier: String + + /// Overlay color will be used to overlay the input image. + public let overlay: KFCrossPlatformColor + + /// Fraction will be used when overlay the color to image. + public let fraction: CGFloat + + /// Creates an `OverlayImageProcessor` + /// + /// - parameter overlay: Overlay color will be used to overlay the input image. + /// - parameter fraction: Fraction will be used when overlay the color to image. + /// From 0.0 to 1.0. 0.0 means solid color, 1.0 means transparent overlay. + public init(overlay: KFCrossPlatformColor, fraction: CGFloat = 0.5) { + self.overlay = overlay + self.fraction = fraction + self.identifier = "com.onevcat.Kingfisher.OverlayImageProcessor(\(overlay.hex)_\(fraction))" + } + + /// Processes the input `ImageProcessItem` with this processor. + /// + /// - Parameters: + /// - item: Input item which will be processed by `self`. + /// - options: Options when processing the item. + /// - Returns: The processed image. + /// + /// - Note: See documentation of `ImageProcessor` protocol for more. + public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { + switch item { + case .image(let image): + return image.kf.scaled(to: options.scaleFactor) + .kf.overlaying(with: overlay, fraction: fraction) + case .data: + return (DefaultImageProcessor.default |> self).process(item: item, options: options) + } + } +} + +/// Processor for tint images with color. Only CG-based images are supported. +public struct TintImageProcessor: ImageProcessor { + + /// Identifier of the processor. + /// - Note: See documentation of `ImageProcessor` protocol for more. + public let identifier: String + + /// Tint color will be used to tint the input image. + public let tint: KFCrossPlatformColor + + /// Creates a `TintImageProcessor` + /// + /// - parameter tint: Tint color will be used to tint the input image. + public init(tint: KFCrossPlatformColor) { + self.tint = tint + self.identifier = "com.onevcat.Kingfisher.TintImageProcessor(\(tint.hex))" + } + + /// Processes the input `ImageProcessItem` with this processor. + /// + /// - Parameters: + /// - item: Input item which will be processed by `self`. + /// - options: Options when processing the item. + /// - Returns: The processed image. + /// + /// - Note: See documentation of `ImageProcessor` protocol for more. + public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { + switch item { + case .image(let image): + return image.kf.scaled(to: options.scaleFactor) + .kf.tinted(with: tint) + case .data: + return (DefaultImageProcessor.default |> self).process(item: item, options: options) + } + } +} + +/// Processor for applying some color control to images. Only CG-based images are supported. +/// watchOS is not supported. +public struct ColorControlsProcessor: ImageProcessor { + + /// Identifier of the processor. + /// - Note: See documentation of `ImageProcessor` protocol for more. + public let identifier: String + + /// Brightness changing to image. + public let brightness: CGFloat + + /// Contrast changing to image. + public let contrast: CGFloat + + /// Saturation changing to image. + public let saturation: CGFloat + + /// InputEV changing to image. + public let inputEV: CGFloat + + /// Creates a `ColorControlsProcessor` + /// + /// - Parameters: + /// - brightness: Brightness changing to image. + /// - contrast: Contrast changing to image. + /// - saturation: Saturation changing to image. + /// - inputEV: InputEV changing to image. + public init(brightness: CGFloat, contrast: CGFloat, saturation: CGFloat, inputEV: CGFloat) { + self.brightness = brightness + self.contrast = contrast + self.saturation = saturation + self.inputEV = inputEV + self.identifier = "com.onevcat.Kingfisher.ColorControlsProcessor(\(brightness)_\(contrast)_\(saturation)_\(inputEV))" + } + + /// Processes the input `ImageProcessItem` with this processor. + /// + /// - Parameters: + /// - item: Input item which will be processed by `self`. + /// - options: Options when processing the item. + /// - Returns: The processed image. + /// + /// - Note: See documentation of `ImageProcessor` protocol for more. + public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { + switch item { + case .image(let image): + return image.kf.scaled(to: options.scaleFactor) + .kf.adjusted(brightness: brightness, contrast: contrast, saturation: saturation, inputEV: inputEV) + case .data: + return (DefaultImageProcessor.default |> self).process(item: item, options: options) + } + } +} + +/// Processor for applying black and white effect to images. Only CG-based images are supported. +/// watchOS is not supported. +public struct BlackWhiteProcessor: ImageProcessor { + + /// Identifier of the processor. + /// - Note: See documentation of `ImageProcessor` protocol for more. + public let identifier = "com.onevcat.Kingfisher.BlackWhiteProcessor" + + /// Creates a `BlackWhiteProcessor` + public init() {} + + /// Processes the input `ImageProcessItem` with this processor. + /// + /// - Parameters: + /// - item: Input item which will be processed by `self`. + /// - options: Options when processing the item. + /// - Returns: The processed image. + /// + /// - Note: See documentation of `ImageProcessor` protocol for more. + public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { + return ColorControlsProcessor(brightness: 0.0, contrast: 1.0, saturation: 0.0, inputEV: 0.7) + .process(item: item, options: options) + } +} + +/// Processor for cropping an image. Only CG-based images are supported. +/// watchOS is not supported. +public struct CroppingImageProcessor: ImageProcessor { + + /// Identifier of the processor. + /// - Note: See documentation of `ImageProcessor` protocol for more. + public let identifier: String + + /// Target size of output image should be. + public let size: CGSize + + /// Anchor point from which the output size should be calculate. + /// The anchor point is consisted by two values between 0.0 and 1.0. + /// It indicates a related point in current image. + /// See `CroppingImageProcessor.init(size:anchor:)` for more. + public let anchor: CGPoint + + /// Creates a `CroppingImageProcessor`. + /// + /// - Parameters: + /// - size: Target size of output image should be. + /// - anchor: The anchor point from which the size should be calculated. + /// Default is `CGPoint(x: 0.5, y: 0.5)`, which means the center of input image. + /// - Note: + /// The anchor point is consisted by two values between 0.0 and 1.0. + /// It indicates a related point in current image, eg: (0.0, 0.0) for top-left + /// corner, (0.5, 0.5) for center and (1.0, 1.0) for bottom-right corner. + /// The `size` property of `CroppingImageProcessor` will be used along with + /// `anchor` to calculate a target rectangle in the size of image. + /// + /// The target size will be automatically calculated with a reasonable behavior. + /// For example, when you have an image size of `CGSize(width: 100, height: 100)`, + /// and a target size of `CGSize(width: 20, height: 20)`: + /// - with a (0.0, 0.0) anchor (top-left), the crop rect will be `{0, 0, 20, 20}`; + /// - with a (0.5, 0.5) anchor (center), it will be `{40, 40, 20, 20}` + /// - while with a (1.0, 1.0) anchor (bottom-right), it will be `{80, 80, 20, 20}` + public init(size: CGSize, anchor: CGPoint = CGPoint(x: 0.5, y: 0.5)) { + self.size = size + self.anchor = anchor + self.identifier = "com.onevcat.Kingfisher.CroppingImageProcessor(\(size)_\(anchor))" + } + + /// Processes the input `ImageProcessItem` with this processor. + /// + /// - Parameters: + /// - item: Input item which will be processed by `self`. + /// - options: Options when processing the item. + /// - Returns: The processed image. + /// + /// - Note: See documentation of `ImageProcessor` protocol for more. + public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { + switch item { + case .image(let image): + return image.kf.scaled(to: options.scaleFactor) + .kf.crop(to: size, anchorOn: anchor) + case .data: return (DefaultImageProcessor.default |> self).process(item: item, options: options) + } + } +} + +/// Processor for downsampling an image. Compared to `ResizingImageProcessor`, this processor +/// does not render the images to resize. Instead, it downsample the input data directly to an +/// image. It is a more efficient than `ResizingImageProcessor`. +/// +/// Only CG-based images are supported. Animated images (like GIF) is not supported. +public struct DownsamplingImageProcessor: ImageProcessor { + + /// Target size of output image should be. It should be smaller than the size of + /// input image. If it is larger, the result image will be the same size of input + /// data without downsampling. + public let size: CGSize + + /// Identifier of the processor. + /// - Note: See documentation of `ImageProcessor` protocol for more. + public let identifier: String + + /// Creates a `DownsamplingImageProcessor`. + /// + /// - Parameter size: The target size of the downsample operation. + public init(size: CGSize) { + self.size = size + self.identifier = "com.onevcat.Kingfisher.DownsamplingImageProcessor(\(size))" + } + + /// Processes the input `ImageProcessItem` with this processor. + /// + /// - Parameters: + /// - item: Input item which will be processed by `self`. + /// - options: Options when processing the item. + /// - Returns: The processed image. + /// + /// - Note: See documentation of `ImageProcessor` protocol for more. + public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { + switch item { + case .image(let image): + guard let data = image.kf.data(format: .unknown) else { + return nil + } + return KingfisherWrapper.downsampledImage(data: data, to: size, scale: options.scaleFactor) + case .data(let data): + return KingfisherWrapper.downsampledImage(data: data, to: size, scale: options.scaleFactor) + } + } +} + +/// Concatenates two `ImageProcessor`s. `ImageProcessor.append(another:)` is used internally. +/// +/// - Parameters: +/// - left: The first processor. +/// - right: The second processor. +/// - Returns: The concatenated processor. +@available(*, deprecated, +message: "Will be removed soon. Use `|>` instead.", +renamed: "|>") +public func >>(left: ImageProcessor, right: ImageProcessor) -> ImageProcessor { + return left.append(another: right) +} + +infix operator |>: AdditionPrecedence +public func |>(left: ImageProcessor, right: ImageProcessor) -> ImageProcessor { + return left.append(another: right) +} + +extension KFCrossPlatformColor { + var hex: String { + var r: CGFloat = 0 + var g: CGFloat = 0 + var b: CGFloat = 0 + var a: CGFloat = 0 + + #if os(macOS) + (usingColorSpace(.sRGB) ?? self).getRed(&r, green: &g, blue: &b, alpha: &a) + #else + getRed(&r, green: &g, blue: &b, alpha: &a) + #endif + + let rInt = Int(r * 255) << 24 + let gInt = Int(g * 255) << 16 + let bInt = Int(b * 255) << 8 + let aInt = Int(a * 255) + + let rgba = rInt | gInt | bInt | aInt + + return String(format:"#%08x", rgba) + } +} diff --git b/Pods/Kingfisher/Sources/Image/ImageProgressive.swift a/Pods/Kingfisher/Sources/Image/ImageProgressive.swift new file mode 100644 index 0000000..2baa5e3 --- /dev/null +++ a/Pods/Kingfisher/Sources/Image/ImageProgressive.swift @@ -0,0 +1,321 @@ +// +// ImageProgressive.swift +// Kingfisher +// +// Created by lixiang on 2019/5/10. +// +// Copyright (c) 2019 Wei Wang +// +// 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 +import CoreGraphics + +private let sharedProcessingQueue: CallbackQueue = + .dispatch(DispatchQueue(label: "com.onevcat.Kingfisher.ImageDownloader.Process")) + +public struct ImageProgressive { + + /// A default `ImageProgressive` could be used across. + public static let `default` = ImageProgressive( + isBlur: true, + isFastestScan: true, + scanInterval: 0 + ) + + /// Whether to enable blur effect processing + let isBlur: Bool + /// Whether to enable the fastest scan + let isFastestScan: Bool + /// Minimum time interval for each scan + let scanInterval: TimeInterval + + public init(isBlur: Bool, + isFastestScan: Bool, + scanInterval: TimeInterval) { + self.isBlur = isBlur + self.isFastestScan = isFastestScan + self.scanInterval = scanInterval + } +} + +protocol ImageSettable: AnyObject { + var image: KFCrossPlatformImage? { get set } +} + +final class ImageProgressiveProvider: DataReceivingSideEffect { + + var onShouldApply: () -> Bool = { return true } + + func onDataReceived(_ session: URLSession, task: SessionDataTask, data: Data) { + + DispatchQueue.main.async { + guard self.onShouldApply() else { return } + self.update(data: task.mutableData, with: task.callbacks) + } + } + + private let option: ImageProgressive + private let refresh: (KFCrossPlatformImage) -> Void + + private let decoder: ImageProgressiveDecoder + private let queue = ImageProgressiveSerialQueue() + + init?(_ options: KingfisherParsedOptionsInfo, + refresh: @escaping (KFCrossPlatformImage) -> Void) { + guard let option = options.progressiveJPEG else { return nil } + + self.option = option + self.refresh = refresh + self.decoder = ImageProgressiveDecoder( + option, + processingQueue: options.processingQueue ?? sharedProcessingQueue, + creatingOptions: options.imageCreatingOptions + ) + } + + func update(data: Data, with callbacks: [SessionDataTask.TaskCallback]) { + guard !data.isEmpty else { return } + + queue.add(minimum: option.scanInterval) { completion in + + func decode(_ data: Data) { + self.decoder.decode(data, with: callbacks) { image in + defer { completion() } + guard self.onShouldApply() else { return } + guard let image = image else { return } + self.refresh(image) + } + } + + let semaphore = DispatchSemaphore(value: 0) + var onShouldApply: Bool = false + + CallbackQueue.mainAsync.execute { + onShouldApply = self.onShouldApply() + semaphore.signal() + } + semaphore.wait() + guard onShouldApply else { + self.queue.clean() + completion() + return + } + + if self.option.isFastestScan { + decode(self.decoder.scanning(data) ?? Data()) + } else { + self.decoder.scanning(data).forEach { decode($0) } + } + } + } +} + +private final class ImageProgressiveDecoder { + + private let option: ImageProgressive + private let processingQueue: CallbackQueue + private let creatingOptions: ImageCreatingOptions + private(set) var scannedCount = 0 + private(set) var scannedIndex = -1 + + init(_ option: ImageProgressive, + processingQueue: CallbackQueue, + creatingOptions: ImageCreatingOptions) { + self.option = option + self.processingQueue = processingQueue + self.creatingOptions = creatingOptions + } + + func scanning(_ data: Data) -> [Data] { + guard data.kf.contains(jpeg: .SOF2) else { + return [] + } + guard scannedIndex + 1 < data.count else { + return [] + } + + var datas: [Data] = [] + var index = scannedIndex + 1 + var count = scannedCount + + while index < data.count - 1 { + scannedIndex = index + // 0xFF, 0xDA - Start Of Scan + let SOS = ImageFormat.JPEGMarker.SOS.bytes + if data[index] == SOS[0], data[index + 1] == SOS[1] { + if count > 0 { + datas.append(data[0 ..< index]) + } + count += 1 + } + index += 1 + } + + // Found more scans this the previous time + guard count > scannedCount else { return [] } + scannedCount = count + + // `> 1` checks that we've received a first scan (SOS) and then received + // and also received a second scan (SOS). This way we know that we have + // at least one full scan available. + guard count > 1 else { return [] } + return datas + } + + func scanning(_ data: Data) -> Data? { + guard data.kf.contains(jpeg: .SOF2) else { + return nil + } + guard scannedIndex + 1 < data.count else { + return nil + } + + var index = scannedIndex + 1 + var count = scannedCount + var lastSOSIndex = 0 + + while index < data.count - 1 { + scannedIndex = index + // 0xFF, 0xDA - Start Of Scan + let SOS = ImageFormat.JPEGMarker.SOS.bytes + if data[index] == SOS[0], data[index + 1] == SOS[1] { + lastSOSIndex = index + count += 1 + } + index += 1 + } + + // Found more scans this the previous time + guard count > scannedCount else { return nil } + scannedCount = count + + // `> 1` checks that we've received a first scan (SOS) and then received + // and also received a second scan (SOS). This way we know that we have + // at least one full scan available. + guard count > 1 && lastSOSIndex > 0 else { return nil } + return data[0 ..< lastSOSIndex] + } + + func decode(_ data: Data, + with callbacks: [SessionDataTask.TaskCallback], + completion: @escaping (KFCrossPlatformImage?) -> Void) { + guard data.kf.contains(jpeg: .SOF2) else { + CallbackQueue.mainCurrentOrAsync.execute { completion(nil) } + return + } + + func processing(_ data: Data) { + let processor = ImageDataProcessor( + data: data, + callbacks: callbacks, + processingQueue: processingQueue + ) + processor.onImageProcessed.delegate(on: self) { (self, result) in + guard let image = try? result.0.get() else { + CallbackQueue.mainCurrentOrAsync.execute { completion(nil) } + return + } + + CallbackQueue.mainCurrentOrAsync.execute { completion(image) } + } + processor.process() + } + + // Blur partial images. + let count = scannedCount + + if option.isBlur, count < 6 { + processingQueue.execute { + // Progressively reduce blur as we load more scans. + let image = KingfisherWrapper.image( + data: data, + options: self.creatingOptions + ) + let radius = max(2, 14 - count * 4) + let temp = image?.kf.blurred(withRadius: CGFloat(radius)) + processing(temp?.kf.data(format: .JPEG) ?? data) + } + + } else { + processing(data) + } + } +} + +private final class ImageProgressiveSerialQueue { + typealias ClosureCallback = ((@escaping () -> Void)) -> Void + + private let queue: DispatchQueue = .init(label: "com.onevcat.Kingfisher.ImageProgressive.SerialQueue") + private var items: [DispatchWorkItem] = [] + private var notify: (() -> Void)? + private var lastTime: TimeInterval? + var count: Int { return items.count } + + func add(minimum interval: TimeInterval, closure: @escaping ClosureCallback) { + let completion = { [weak self] in + guard let self = self else { return } + + self.queue.async { [weak self] in + guard let self = self else { return } + guard !self.items.isEmpty else { return } + + self.items.removeFirst() + + if let next = self.items.first { + self.queue.asyncAfter( + deadline: .now() + interval, + execute: next + ) + + } else { + self.lastTime = Date().timeIntervalSince1970 + self.notify?() + self.notify = nil + } + } + } + + queue.async { [weak self] in + guard let self = self else { return } + + let item = DispatchWorkItem { + closure(completion) + } + if self.items.isEmpty { + let difference = Date().timeIntervalSince1970 - (self.lastTime ?? 0) + let delay = difference < interval ? interval - difference : 0 + self.queue.asyncAfter(deadline: .now() + delay, execute: item) + } + self.items.append(item) + } + } + + func notify(_ closure: @escaping () -> Void) { + self.notify = closure + } + + func clean() { + queue.async { [weak self] in + guard let self = self else { return } + self.items.forEach { $0.cancel() } + self.items.removeAll() + } + } +} diff --git b/Pods/Kingfisher/Sources/Image/ImageTransition.swift a/Pods/Kingfisher/Sources/Image/ImageTransition.swift new file mode 100644 index 0000000..c13a9d2 --- /dev/null +++ a/Pods/Kingfisher/Sources/Image/ImageTransition.swift @@ -0,0 +1,115 @@ +// +// ImageTransition.swift +// Kingfisher +// +// Created by Wei Wang on 15/9/18. +// +// Copyright (c) 2019 Wei Wang +// +// 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. + +#if os(iOS) || os(tvOS) +import UIKit + +/// Transition effect which will be used when an image downloaded and set by `UIImageView` +/// extension API in Kingfisher. You can assign an enum value with transition duration as +/// an item in `KingfisherOptionsInfo` to enable the animation transition. +/// +/// Apple's UIViewAnimationOptions is used under the hood. +/// For custom transition, you should specified your own transition options, animations and +/// completion handler as well. +/// +/// - none: No animation transition. +/// - fade: Fade in the loaded image in a given duration. +/// - flipFromLeft: Flip from left transition. +/// - flipFromRight: Flip from right transition. +/// - flipFromTop: Flip from top transition. +/// - flipFromBottom: Flip from bottom transition. +/// - custom: Custom transition. +public enum ImageTransition { + /// No animation transition. + case none + /// Fade in the loaded image in a given duration. + case fade(TimeInterval) + /// Flip from left transition. + case flipFromLeft(TimeInterval) + /// Flip from right transition. + case flipFromRight(TimeInterval) + /// Flip from top transition. + case flipFromTop(TimeInterval) + /// Flip from bottom transition. + case flipFromBottom(TimeInterval) + /// Custom transition defined by a general animation block. + /// - duration: The time duration of this custom transition. + /// - options: `UIView.AnimationOptions` should be used in the transition. + /// - animations: The animation block will be applied when setting image. + /// - completion: A block called when the transition animation finishes. + case custom(duration: TimeInterval, + options: UIView.AnimationOptions, + animations: ((UIImageView, UIImage) -> Void)?, + completion: ((Bool) -> Void)?) + + var duration: TimeInterval { + switch self { + case .none: return 0 + case .fade(let duration): return duration + + case .flipFromLeft(let duration): return duration + case .flipFromRight(let duration): return duration + case .flipFromTop(let duration): return duration + case .flipFromBottom(let duration): return duration + + case .custom(let duration, _, _, _): return duration + } + } + + var animationOptions: UIView.AnimationOptions { + switch self { + case .none: return [] + case .fade: return .transitionCrossDissolve + + case .flipFromLeft: return .transitionFlipFromLeft + case .flipFromRight: return .transitionFlipFromRight + case .flipFromTop: return .transitionFlipFromTop + case .flipFromBottom: return .transitionFlipFromBottom + + case .custom(_, let options, _, _): return options + } + } + + var animations: ((UIImageView, UIImage) -> Void)? { + switch self { + case .custom(_, _, let animations, _): return animations + default: return { $0.image = $1 } + } + } + + var completion: ((Bool) -> Void)? { + switch self { + case .custom(_, _, _, let completion): return completion + default: return nil + } + } +} +#else +// Just a placeholder for compiling on macOS. +public enum ImageTransition { + case none +} +#endif diff --git b/Pods/Kingfisher/Sources/Image/Placeholder.swift a/Pods/Kingfisher/Sources/Image/Placeholder.swift new file mode 100644 index 0000000..1179815 --- /dev/null +++ a/Pods/Kingfisher/Sources/Image/Placeholder.swift @@ -0,0 +1,80 @@ +// +// Placeholder.swift +// Kingfisher +// +// Created by Tieme van Veen on 28/08/2017. +// +// Copyright (c) 2019 Wei Wang +// +// 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. + +#if !os(watchOS) + +#if os(macOS) +import AppKit +#else +import UIKit +#endif + +/// Represents a placeholder type which could be set while loading as well as +/// loading finished without getting an image. +public protocol Placeholder { + + /// How the placeholder should be added to a given image view. + func add(to imageView: KFCrossPlatformImageView) + + /// How the placeholder should be removed from a given image view. + func remove(from imageView: KFCrossPlatformImageView) +} + +/// Default implementation of an image placeholder. The image will be set or +/// reset directly for `image` property of the image view. +extension KFCrossPlatformImage: Placeholder { + /// How the placeholder should be added to a given image view. + public func add(to imageView: KFCrossPlatformImageView) { imageView.image = self } + + /// How the placeholder should be removed from a given image view. + public func remove(from imageView: KFCrossPlatformImageView) { imageView.image = nil } +} + +/// Default implementation of an arbitrary view as placeholder. The view will be +/// added as a subview when adding and be removed from its super view when removing. +/// +/// To use your customize View type as placeholder, simply let it conforming to +/// `Placeholder` by `extension MyView: Placeholder {}`. +extension Placeholder where Self: KFCrossPlatformView { + + /// How the placeholder should be added to a given image view. + public func add(to imageView: KFCrossPlatformImageView) { + imageView.addSubview(self) + translatesAutoresizingMaskIntoConstraints = false + + centerXAnchor.constraint(equalTo: imageView.centerXAnchor).isActive = true + centerYAnchor.constraint(equalTo: imageView.centerYAnchor).isActive = true + heightAnchor.constraint(equalTo: imageView.heightAnchor).isActive = true + widthAnchor.constraint(equalTo: imageView.widthAnchor).isActive = true + } + + /// How the placeholder should be removed from a given image view. + public func remove(from imageView: KFCrossPlatformImageView) { + removeFromSuperview() + } +} + +#endif diff --git b/Pods/Kingfisher/Sources/Kingfisher.h a/Pods/Kingfisher/Sources/Kingfisher.h new file mode 100644 index 0000000..356adde --- /dev/null +++ a/Pods/Kingfisher/Sources/Kingfisher.h @@ -0,0 +1,37 @@ +// +// Kingfisher.h +// Kingfisher +// +// Created by Wei Wang on 15/4/6. +// +// Copyright (c) 2019 Wei Wang +// +// 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 + +//! Project version number for Kingfisher. +FOUNDATION_EXPORT double KingfisherVersionNumber; + +//! Project version string for Kingfisher. +FOUNDATION_EXPORT const unsigned char KingfisherVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git b/Pods/Kingfisher/Sources/Networking/AuthenticationChallengeResponsable.swift a/Pods/Kingfisher/Sources/Networking/AuthenticationChallengeResponsable.swift new file mode 100644 index 0000000..5f6fc57 --- /dev/null +++ a/Pods/Kingfisher/Sources/Networking/AuthenticationChallengeResponsable.swift @@ -0,0 +1,91 @@ +// +// AuthenticationChallengeResponsable.swift +// Kingfisher +// +// Created by Wei Wang on 2018/10/11. +// +// Copyright (c) 2019 Wei Wang +// +// 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 + +/// Protocol indicates that an authentication challenge could be handled. +public protocol AuthenticationChallengeResponsable: AnyObject { + + /// Called when a session level authentication challenge is received. + /// This method provide a chance to handle and response to the authentication + /// challenge before downloading could start. + /// + /// - Parameters: + /// - downloader: The downloader which receives this challenge. + /// - challenge: An object that contains the request for authentication. + /// - completionHandler: A handler that your delegate method must call. + /// + /// - Note: This method is a forward from `URLSessionDelegate.urlSession(:didReceiveChallenge:completionHandler:)`. + /// Please refer to the document of it in `URLSessionDelegate`. + func downloader( + _ downloader: ImageDownloader, + didReceive challenge: URLAuthenticationChallenge, + completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) + + /// Called when a task level authentication challenge is received. + /// This method provide a chance to handle and response to the authentication + /// challenge before downloading could start. + /// + /// - Parameters: + /// - downloader: The downloader which receives this challenge. + /// - task: The task whose request requires authentication. + /// - challenge: An object that contains the request for authentication. + /// - completionHandler: A handler that your delegate method must call. + func downloader( + _ downloader: ImageDownloader, + task: URLSessionTask, + didReceive challenge: URLAuthenticationChallenge, + completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) +} + +extension AuthenticationChallengeResponsable { + + public func downloader( + _ downloader: ImageDownloader, + didReceive challenge: URLAuthenticationChallenge, + completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) + { + if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust { + if let trustedHosts = downloader.trustedHosts, trustedHosts.contains(challenge.protectionSpace.host) { + let credential = URLCredential(trust: challenge.protectionSpace.serverTrust!) + completionHandler(.useCredential, credential) + return + } + } + + completionHandler(.performDefaultHandling, nil) + } + + public func downloader( + _ downloader: ImageDownloader, + task: URLSessionTask, + didReceive challenge: URLAuthenticationChallenge, + completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) + { + completionHandler(.performDefaultHandling, nil) + } + +} diff --git b/Pods/Kingfisher/Sources/Networking/ImageDataProcessor.swift a/Pods/Kingfisher/Sources/Networking/ImageDataProcessor.swift new file mode 100644 index 0000000..51e9705 --- /dev/null +++ a/Pods/Kingfisher/Sources/Networking/ImageDataProcessor.swift @@ -0,0 +1,80 @@ +// +// ImageDataProcessor.swift +// Kingfisher +// +// Created by Wei Wang on 2018/10/11. +// +// Copyright (c) 2019 Wei Wang +// +// 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 + +private let sharedProcessingQueue: CallbackQueue = + .dispatch(DispatchQueue(label: "com.onevcat.Kingfisher.ImageDownloader.Process")) + +// Handles image processing work on an own process queue. +class ImageDataProcessor { + let data: Data + let callbacks: [SessionDataTask.TaskCallback] + let queue: CallbackQueue + + // Note: We have an optimization choice there, to reduce queue dispatch by checking callback + // queue settings in each option... + let onImageProcessed = Delegate<(Result, SessionDataTask.TaskCallback), Void>() + + init(data: Data, callbacks: [SessionDataTask.TaskCallback], processingQueue: CallbackQueue?) { + self.data = data + self.callbacks = callbacks + self.queue = processingQueue ?? sharedProcessingQueue + } + + func process() { + queue.execute(doProcess) + } + + private func doProcess() { + var processedImages = [String: KFCrossPlatformImage]() + for callback in callbacks { + let processor = callback.options.processor + var image = processedImages[processor.identifier] + if image == nil { + image = processor.process(item: .data(data), options: callback.options) + processedImages[processor.identifier] = image + } + + let result: Result + if let image = image { + var finalImage = image + if let imageModifier = callback.options.imageModifier { + finalImage = imageModifier.modify(image) + } + if callback.options.backgroundDecode { + finalImage = finalImage.kf.decoded + } + result = .success(finalImage) + } else { + let error = KingfisherError.processorError( + reason: .processingFailed(processor: processor, item: .data(data))) + result = .failure(error) + } + onImageProcessed.call((result, callback)) + } + } +} diff --git b/Pods/Kingfisher/Sources/Networking/ImageDownloader.swift a/Pods/Kingfisher/Sources/Networking/ImageDownloader.swift new file mode 100644 index 0000000..74bdaca --- /dev/null +++ a/Pods/Kingfisher/Sources/Networking/ImageDownloader.swift @@ -0,0 +1,377 @@ +// +// ImageDownloader.swift +// Kingfisher +// +// Created by Wei Wang on 15/4/6. +// +// Copyright (c) 2019 Wei Wang +// +// 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. + +#if os(macOS) +import AppKit +#else +import UIKit +#endif + +/// Represents a success result of an image downloading progress. +public struct ImageLoadingResult { + + /// The downloaded image. + public let image: KFCrossPlatformImage + + /// Original URL of the image request. + public let url: URL? + + /// The raw data received from downloader. + public let originalData: Data +} + +/// Represents a task of an image downloading process. +public struct DownloadTask { + + /// The `SessionDataTask` object bounded to this download task. Multiple `DownloadTask`s could refer + /// to a same `sessionTask`. This is an optimization in Kingfisher to prevent multiple downloading task + /// for the same URL resource at the same time. + /// + /// When you `cancel` a `DownloadTask`, this `SessionDataTask` and its cancel token will be pass through. + /// You can use them to identify the cancelled task. + public let sessionTask: SessionDataTask + + /// The cancel token which is used to cancel the task. This is only for identify the task when it is cancelled. + /// To cancel a `DownloadTask`, use `cancel` instead. + public let cancelToken: SessionDataTask.CancelToken + + /// Cancel this task if it is running. It will do nothing if this task is not running. + /// + /// - Note: + /// In Kingfisher, there is an optimization to prevent starting another download task if the target URL is being + /// downloading. However, even when internally no new session task created, a `DownloadTask` will be still created + /// and returned when you call related methods, but it will share the session downloading task with a previous task. + /// In this case, if multiple `DownloadTask`s share a single session download task, cancelling a `DownloadTask` + /// does not affect other `DownloadTask`s. + /// + /// If you need to cancel all `DownloadTask`s of a url, use `ImageDownloader.cancel(url:)`. If you need to cancel + /// all downloading tasks of an `ImageDownloader`, use `ImageDownloader.cancelAll()`. + public func cancel() { + sessionTask.cancel(token: cancelToken) + } +} + +extension DownloadTask { + enum WrappedTask { + case download(DownloadTask) + case dataProviding + + func cancel() { + switch self { + case .download(let task): task.cancel() + case .dataProviding: break + } + } + + var value: DownloadTask? { + switch self { + case .download(let task): return task + case .dataProviding: return nil + } + } + } +} + +/// Represents a downloading manager for requesting the image with a URL from server. +open class ImageDownloader { + + // MARK: Singleton + /// The default downloader. + public static let `default` = ImageDownloader(name: "default") + + // MARK: Public Properties + /// The duration before the downloading is timeout. Default is 15 seconds. + open var downloadTimeout: TimeInterval = 15.0 + + /// A set of trusted hosts when receiving server trust challenges. A challenge with host name contained in this + /// set will be ignored. You can use this set to specify the self-signed site. It only will be used if you don't + /// specify the `authenticationChallengeResponder`. + /// + /// If `authenticationChallengeResponder` is set, this property will be ignored and the implementation of + /// `authenticationChallengeResponder` will be used instead. + open var trustedHosts: Set? + + /// Use this to set supply a configuration for the downloader. By default, + /// NSURLSessionConfiguration.ephemeralSessionConfiguration() will be used. + /// + /// You could change the configuration before a downloading task starts. + /// A configuration without persistent storage for caches is requested for downloader working correctly. + open var sessionConfiguration = URLSessionConfiguration.ephemeral { + didSet { + session.invalidateAndCancel() + session = URLSession(configuration: sessionConfiguration, delegate: sessionDelegate, delegateQueue: nil) + } + } + + /// Whether the download requests should use pipeline or not. Default is false. + open var requestsUsePipelining = false + + /// Delegate of this `ImageDownloader` object. See `ImageDownloaderDelegate` protocol for more. + open weak var delegate: ImageDownloaderDelegate? + + /// A responder for authentication challenge. + /// Downloader will forward the received authentication challenge for the downloading session to this responder. + open weak var authenticationChallengeResponder: AuthenticationChallengeResponsable? + + private let name: String + private let sessionDelegate: SessionDelegate + private var session: URLSession + + // MARK: Initializers + + /// Creates a downloader with name. + /// + /// - Parameter name: The name for the downloader. It should not be empty. + public init(name: String) { + if name.isEmpty { + fatalError("[Kingfisher] You should specify a name for the downloader. " + + "A downloader with empty name is not permitted.") + } + + self.name = name + + sessionDelegate = SessionDelegate() + session = URLSession( + configuration: sessionConfiguration, + delegate: sessionDelegate, + delegateQueue: nil) + + authenticationChallengeResponder = self + setupSessionHandler() + } + + deinit { session.invalidateAndCancel() } + + private func setupSessionHandler() { + sessionDelegate.onReceiveSessionChallenge.delegate(on: self) { (self, invoke) in + self.authenticationChallengeResponder?.downloader(self, didReceive: invoke.1, completionHandler: invoke.2) + } + sessionDelegate.onReceiveSessionTaskChallenge.delegate(on: self) { (self, invoke) in + self.authenticationChallengeResponder?.downloader( + self, task: invoke.1, didReceive: invoke.2, completionHandler: invoke.3) + } + sessionDelegate.onValidStatusCode.delegate(on: self) { (self, code) in + return (self.delegate ?? self).isValidStatusCode(code, for: self) + } + sessionDelegate.onDownloadingFinished.delegate(on: self) { (self, value) in + let (url, result) = value + do { + let value = try result.get() + self.delegate?.imageDownloader(self, didFinishDownloadingImageForURL: url, with: value, error: nil) + } catch { + self.delegate?.imageDownloader(self, didFinishDownloadingImageForURL: url, with: nil, error: error) + } + } + sessionDelegate.onDidDownloadData.delegate(on: self) { (self, task) in + guard let url = task.task.originalRequest?.url else { + return task.mutableData + } + return (self.delegate ?? self).imageDownloader(self, didDownload: task.mutableData, for: url) + } + } + + // MARK: Dowloading Task + /// Downloads an image with a URL and option. Invoked internally by Kingfisher. Subclasses must invoke super. + /// + /// - Parameters: + /// - url: Target URL. + /// - options: The options could control download behavior. See `KingfisherOptionsInfo`. + /// - completionHandler: Called when the download progress finishes. This block will be called in the queue + /// defined in `.callbackQueue` in `options` parameter. + /// - Returns: A downloading task. You could call `cancel` on it to stop the download task. + @discardableResult + open func downloadImage( + with url: URL, + options: KingfisherParsedOptionsInfo, + completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? + { + // Creates default request. + var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: downloadTimeout) + request.httpShouldUsePipelining = requestsUsePipelining + + if let requestModifier = options.requestModifier { + // Modifies request before sending. + guard let r = requestModifier.modified(for: request) else { + options.callbackQueue.execute { + completionHandler?(.failure(KingfisherError.requestError(reason: .emptyRequest))) + } + return nil + } + request = r + } + + // There is a possibility that request modifier changed the url to `nil` or empty. + // In this case, throw an error. + guard let url = request.url, !url.absoluteString.isEmpty else { + options.callbackQueue.execute { + completionHandler?(.failure(KingfisherError.requestError(reason: .invalidURL(request: request)))) + } + return nil + } + + // Wraps `completionHandler` to `onCompleted` respectively. + + let onCompleted = completionHandler.map { + block -> Delegate, Void> in + let delegate = Delegate, Void>() + delegate.delegate(on: self) { (_, callback) in + block(callback) + } + return delegate + } + + // SessionDataTask.TaskCallback is a wrapper for `onCompleted` and `options` (for processor info) + let callback = SessionDataTask.TaskCallback( + onCompleted: onCompleted, + options: options + ) + + // Ready to start download. Add it to session task manager (`sessionHandler`) + + let downloadTask: DownloadTask + if let existingTask = sessionDelegate.task(for: url) { + downloadTask = sessionDelegate.append(existingTask, url: url, callback: callback) + } else { + let sessionDataTask = session.dataTask(with: request) + sessionDataTask.priority = options.downloadPriority + downloadTask = sessionDelegate.add(sessionDataTask, url: url, callback: callback) + } + + let sessionTask = downloadTask.sessionTask + + // Start the session task if not started yet. + if !sessionTask.started { + sessionTask.onTaskDone.delegate(on: self) { (self, done) in + // Underlying downloading finishes. + // result: Result<(Data, URLResponse?)>, callbacks: [TaskCallback] + let (result, callbacks) = done + + // Before processing the downloaded data. + do { + let value = try result.get() + self.delegate?.imageDownloader( + self, + didFinishDownloadingImageForURL: url, + with: value.1, + error: nil + ) + } catch { + self.delegate?.imageDownloader( + self, + didFinishDownloadingImageForURL: url, + with: nil, + error: error + ) + } + + switch result { + // Download finished. Now process the data to an image. + case .success(let (data, response)): + let processor = ImageDataProcessor( + data: data, callbacks: callbacks, processingQueue: options.processingQueue) + processor.onImageProcessed.delegate(on: self) { (self, result) in + // `onImageProcessed` will be called for `callbacks.count` times, with each + // `SessionDataTask.TaskCallback` as the input parameter. + // result: Result, callback: SessionDataTask.TaskCallback + let (result, callback) = result + + if let image = try? result.get() { + self.delegate?.imageDownloader(self, didDownload: image, for: url, with: response) + } + + let imageResult = result.map { ImageLoadingResult(image: $0, url: url, originalData: data) } + let queue = callback.options.callbackQueue + queue.execute { callback.onCompleted?.call(imageResult) } + } + processor.process() + + case .failure(let error): + callbacks.forEach { callback in + let queue = callback.options.callbackQueue + queue.execute { callback.onCompleted?.call(.failure(error)) } + } + } + } + delegate?.imageDownloader(self, willDownloadImageForURL: url, with: request) + sessionTask.resume() + } + return downloadTask + } + + /// Downloads an image with a URL and option. + /// + /// - Parameters: + /// - url: Target URL. + /// - options: The options could control download behavior. See `KingfisherOptionsInfo`. + /// - progressBlock: Called when the download progress updated. This block will be always be called in main queue. + /// - completionHandler: Called when the download progress finishes. This block will be called in the queue + /// defined in `.callbackQueue` in `options` parameter. + /// - Returns: A downloading task. You could call `cancel` on it to stop the download task. + @discardableResult + open func downloadImage( + with url: URL, + options: KingfisherOptionsInfo? = nil, + progressBlock: DownloadProgressBlock? = nil, + completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? + { + var info = KingfisherParsedOptionsInfo(options) + if let block = progressBlock { + info.onDataReceived = (info.onDataReceived ?? []) + [ImageLoadingProgressSideEffect(block)] + } + return downloadImage( + with: url, + options: info, + completionHandler: completionHandler) + } +} + +// MARK: Cancelling Task +extension ImageDownloader { + + /// Cancel all downloading tasks for this `ImageDownloader`. It will trigger the completion handlers + /// for all not-yet-finished downloading tasks. + /// + /// If you need to only cancel a certain task, call `cancel()` on the `DownloadTask` + /// returned by the downloading methods. If you need to cancel all `DownloadTask`s of a certain url, + /// use `ImageDownloader.cancel(url:)`. + public func cancelAll() { + sessionDelegate.cancelAll() + } + + /// Cancel all downloading tasks for a given URL. It will trigger the completion handlers for + /// all not-yet-finished downloading tasks for the URL. + /// + /// - Parameter url: The URL which you want to cancel downloading. + public func cancel(url: URL) { + sessionDelegate.cancel(url: url) + } +} + +// Use the default implementation from extension of `AuthenticationChallengeResponsable`. +extension ImageDownloader: AuthenticationChallengeResponsable {} + +// Use the default implementation from extension of `ImageDownloaderDelegate`. +extension ImageDownloader: ImageDownloaderDelegate {} diff --git b/Pods/Kingfisher/Sources/Networking/ImageDownloaderDelegate.swift a/Pods/Kingfisher/Sources/Networking/ImageDownloaderDelegate.swift new file mode 100644 index 0000000..2cc9102 --- /dev/null +++ a/Pods/Kingfisher/Sources/Networking/ImageDownloaderDelegate.swift @@ -0,0 +1,127 @@ +// +// ImageDownloaderDelegate.swift +// Kingfisher +// +// Created by Wei Wang on 2018/10/11. +// +// Copyright (c) 2019 Wei Wang +// +// 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 + +/// Protocol of `ImageDownloader`. This protocol provides a set of methods which are related to image downloader +/// working stages and rules. +public protocol ImageDownloaderDelegate: AnyObject { + + /// Called when the `ImageDownloader` object will start downloading an image from a specified URL. + /// + /// - Parameters: + /// - downloader: The `ImageDownloader` object which is used for the downloading operation. + /// - url: URL of the starting request. + /// - request: The request object for the download process. + /// + func imageDownloader(_ downloader: ImageDownloader, willDownloadImageForURL url: URL, with request: URLRequest?) + + /// Called when the `ImageDownloader` completes a downloading request with success or failure. + /// + /// - Parameters: + /// - downloader: The `ImageDownloader` object which is used for the downloading operation. + /// - url: URL of the original request URL. + /// - response: The response object of the downloading process. + /// - error: The error in case of failure. + /// + func imageDownloader( + _ downloader: ImageDownloader, + didFinishDownloadingImageForURL url: URL, + with response: URLResponse?, + error: Error?) + + /// Called when the `ImageDownloader` object successfully downloaded image data from specified URL. This is + /// your last chance to verify or modify the downloaded data before Kingfisher tries to perform addition + /// processing on the image data. + /// + /// - Parameters: + /// - downloader: The `ImageDownloader` object which is used for the downloading operation. + /// - data: The original downloaded data. + /// - url: The URL of the original request URL. + /// - Returns: The data from which Kingfisher should use to create an image. You need to provide valid data + /// which content is one of the supported image file format. Kingfisher will perform process on this + /// data and try to convert it to an image object. + /// - Note: + /// This can be used to pre-process raw image data before creation of `Image` instance (i.e. + /// decrypting or verification). If `nil` returned, the processing is interrupted and a `KingfisherError` with + /// `ResponseErrorReason.dataModifyingFailed` will be raised. You could use this fact to stop the image + /// processing flow if you find the data is corrupted or malformed. + func imageDownloader(_ downloader: ImageDownloader, didDownload data: Data, for url: URL) -> Data? + + /// Called when the `ImageDownloader` object successfully downloads and processes an image from specified URL. + /// + /// - Parameters: + /// - downloader: The `ImageDownloader` object which is used for the downloading operation. + /// - image: The downloaded and processed image. + /// - url: URL of the original request URL. + /// - response: The original response object of the downloading process. + /// + func imageDownloader( + _ downloader: ImageDownloader, + didDownload image: KFCrossPlatformImage, + for url: URL, + with response: URLResponse?) + + /// Checks if a received HTTP status code is valid or not. + /// By default, a status code in range 200..<400 is considered as valid. + /// If an invalid code is received, the downloader will raise an `KingfisherError` with + /// `ResponseErrorReason.invalidHTTPStatusCode` as its reason. + /// + /// - Parameters: + /// - code: The received HTTP status code. + /// - downloader: The `ImageDownloader` object asks for validate status code. + /// - Returns: Returns a value to indicate whether this HTTP status code is valid or not. + /// - Note: If the default 200 to 400 valid code does not suit your need, + /// you can implement this method to change that behavior. + func isValidStatusCode(_ code: Int, for downloader: ImageDownloader) -> Bool +} + +// Default implementation for `ImageDownloaderDelegate`. +extension ImageDownloaderDelegate { + public func imageDownloader( + _ downloader: ImageDownloader, + willDownloadImageForURL url: URL, + with request: URLRequest?) {} + + public func imageDownloader( + _ downloader: ImageDownloader, + didFinishDownloadingImageForURL url: URL, + with response: URLResponse?, + error: Error?) {} + + public func imageDownloader( + _ downloader: ImageDownloader, + didDownload image: KFCrossPlatformImage, + for url: URL, + with response: URLResponse?) {} + + public func isValidStatusCode(_ code: Int, for downloader: ImageDownloader) -> Bool { + return (200..<400).contains(code) + } + public func imageDownloader(_ downloader: ImageDownloader, didDownload data: Data, for url: URL) -> Data? { + return data + } +} diff --git b/Pods/Kingfisher/Sources/Networking/ImageModifier.swift a/Pods/Kingfisher/Sources/Networking/ImageModifier.swift new file mode 100644 index 0000000..5ef0722 --- /dev/null +++ a/Pods/Kingfisher/Sources/Networking/ImageModifier.swift @@ -0,0 +1,116 @@ +// +// ImageModifier.swift +// Kingfisher +// +// Created by Ethan Gill on 2017/11/28. +// +// Copyright (c) 2019 Ethan Gill +// +// 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 + +/// An `ImageModifier` can be used to change properties on an image in between +/// cache serialization and use of the image. The modified returned image will be +/// only used for current rendering purpose, the serialization data will not contain +/// the changes applied by the `ImageModifier`. +public protocol ImageModifier { + /// Modify an input `Image`. + /// + /// - parameter image: Image which will be modified by `self` + /// + /// - returns: The modified image. + /// + /// - Note: The return value will be unmodified if modifying is not possible on + /// the current platform. + /// - Note: Most modifiers support UIImage or NSImage, but not CGImage. + func modify(_ image: KFCrossPlatformImage) -> KFCrossPlatformImage +} + +/// A wrapper for creating an `ImageModifier` easier. +/// This type conforms to `ImageModifier` and wraps an image modify block. +/// If the `block` throws an error, the original image will be used. +public struct AnyImageModifier: ImageModifier { + + /// A block which modifies images, or returns the original image + /// if modification cannot be performed with an error. + let block: (KFCrossPlatformImage) throws -> KFCrossPlatformImage + + /// Creates an `AnyImageModifier` with a given `modify` block. + public init(modify: @escaping (KFCrossPlatformImage) throws -> KFCrossPlatformImage) { + block = modify + } + + /// Modify an input `Image`. See `ImageModifier` protocol for more. + public func modify(_ image: KFCrossPlatformImage) -> KFCrossPlatformImage { + return (try? block(image)) ?? image + } +} + +#if os(iOS) || os(tvOS) || os(watchOS) +import UIKit + +/// Modifier for setting the rendering mode of images. +public struct RenderingModeImageModifier: ImageModifier { + + /// The rendering mode to apply to the image. + public let renderingMode: UIImage.RenderingMode + + /// Creates a `RenderingModeImageModifier`. + /// + /// - Parameter renderingMode: The rendering mode to apply to the image. Default is `.automatic`. + public init(renderingMode: UIImage.RenderingMode = .automatic) { + self.renderingMode = renderingMode + } + + /// Modify an input `Image`. See `ImageModifier` protocol for more. + public func modify(_ image: KFCrossPlatformImage) -> KFCrossPlatformImage { + return image.withRenderingMode(renderingMode) + } +} + +/// Modifier for setting the `flipsForRightToLeftLayoutDirection` property of images. +public struct FlipsForRightToLeftLayoutDirectionImageModifier: ImageModifier { + + /// Creates a `FlipsForRightToLeftLayoutDirectionImageModifier`. + public init() {} + + /// Modify an input `Image`. See `ImageModifier` protocol for more. + public func modify(_ image: KFCrossPlatformImage) -> KFCrossPlatformImage { + return image.imageFlippedForRightToLeftLayoutDirection() + } +} + +/// Modifier for setting the `alignmentRectInsets` property of images. +public struct AlignmentRectInsetsImageModifier: ImageModifier { + + /// The alignment insets to apply to the image + public let alignmentInsets: UIEdgeInsets + + /// Creates an `AlignmentRectInsetsImageModifier`. + public init(alignmentInsets: UIEdgeInsets) { + self.alignmentInsets = alignmentInsets + } + + /// Modify an input `Image`. See `ImageModifier` protocol for more. + public func modify(_ image: KFCrossPlatformImage) -> KFCrossPlatformImage { + return image.withAlignmentRectInsets(alignmentInsets) + } +} +#endif diff --git b/Pods/Kingfisher/Sources/Networking/ImagePrefetcher.swift a/Pods/Kingfisher/Sources/Networking/ImagePrefetcher.swift new file mode 100644 index 0000000..c2d5ff1 --- /dev/null +++ a/Pods/Kingfisher/Sources/Networking/ImagePrefetcher.swift @@ -0,0 +1,372 @@ +// +// ImagePrefetcher.swift +// Kingfisher +// +// Created by Claire Knight on 24/02/2016 +// +// Copyright (c) 2019 Wei Wang +// +// 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. + + +#if os(macOS) +import AppKit +#else +import UIKit +#endif + +/// Progress update block of prefetcher when initialized with a list of resources. +/// +/// - `skippedResources`: An array of resources that are already cached before the prefetching starting. +/// - `failedResources`: An array of resources that fail to be downloaded. It could because of being cancelled while +/// downloading, encountered an error when downloading or the download not being started at all. +/// - `completedResources`: An array of resources that are downloaded and cached successfully. +public typealias PrefetcherProgressBlock = + ((_ skippedResources: [Resource], _ failedResources: [Resource], _ completedResources: [Resource]) -> Void) + +/// Progress update block of prefetcher when initialized with a list of resources. +/// +/// - `skippedSources`: An array of sources that are already cached before the prefetching starting. +/// - `failedSources`: An array of sources that fail to be fetched. +/// - `completedResources`: An array of sources that are fetched and cached successfully. +public typealias PrefetcherSourceProgressBlock = + ((_ skippedSources: [Source], _ failedSources: [Source], _ completedSources: [Source]) -> Void) + +/// Completion block of prefetcher when initialized with a list of sources. +/// +/// - `skippedResources`: An array of resources that are already cached before the prefetching starting. +/// - `failedResources`: An array of resources that fail to be downloaded. It could because of being cancelled while +/// downloading, encountered an error when downloading or the download not being started at all. +/// - `completedResources`: An array of resources that are downloaded and cached successfully. +public typealias PrefetcherCompletionHandler = + ((_ skippedResources: [Resource], _ failedResources: [Resource], _ completedResources: [Resource]) -> Void) + +/// Completion block of prefetcher when initialized with a list of sources. +/// +/// - `skippedSources`: An array of sources that are already cached before the prefetching starting. +/// - `failedSources`: An array of sources that fail to be fetched. +/// - `completedSources`: An array of sources that are fetched and cached successfully. +public typealias PrefetcherSourceCompletionHandler = + ((_ skippedSources: [Source], _ failedSources: [Source], _ completedSources: [Source]) -> Void) + +/// `ImagePrefetcher` represents a downloading manager for requesting many images via URLs, then caching them. +/// This is useful when you know a list of image resources and want to download them before showing. It also works with +/// some Cocoa prefetching mechanism like table view or collection view `prefetchDataSource`, to start image downloading +/// and caching before they display on screen. +public class ImagePrefetcher: CustomStringConvertible { + + public var description: String { + return "\(Unmanaged.passUnretained(self).toOpaque())" + } + + /// The maximum concurrent downloads to use when prefetching images. Default is 5. + public var maxConcurrentDownloads = 5 + + private let prefetchSources: [Source] + private let optionsInfo: KingfisherParsedOptionsInfo + + private var progressBlock: PrefetcherProgressBlock? + private var completionHandler: PrefetcherCompletionHandler? + + private var progressSourceBlock: PrefetcherSourceProgressBlock? + private var completionSourceHandler: PrefetcherSourceCompletionHandler? + + private var tasks = [String: DownloadTask.WrappedTask]() + + private var pendingSources: ArraySlice + private var skippedSources = [Source]() + private var completedSources = [Source]() + private var failedSources = [Source]() + + private var stopped = false + + // A manager used for prefetching. We will use the helper methods in manager. + private let manager: KingfisherManager + + private let pretchQueue = DispatchQueue(label: "com.onevcat.Kingfisher.ImagePrefetcher.pretchQueue") + private static let requestingQueue = DispatchQueue(label: "com.onevcat.Kingfisher.ImagePrefetcher.requestingQueue") + + private var finished: Bool { + let totalFinished: Int = failedSources.count + skippedSources.count + completedSources.count + return totalFinished == prefetchSources.count && tasks.isEmpty + } + + /// Creates an image prefetcher with an array of URLs. + /// + /// The prefetcher should be initiated with a list of prefetching targets. The URLs list is immutable. + /// After you get a valid `ImagePrefetcher` object, you call `start()` on it to begin the prefetching process. + /// The images which are already cached will be skipped without downloading again. + /// + /// - Parameters: + /// - urls: The URLs which should be prefetched. + /// - options: Options could control some behaviors. See `KingfisherOptionsInfo` for more. + /// - progressBlock: Called every time an resource is downloaded, skipped or cancelled. + /// - completionHandler: Called when the whole prefetching process finished. + /// + /// - Note: + /// By default, the `ImageDownloader.defaultDownloader` and `ImageCache.defaultCache` will be used as + /// the downloader and cache target respectively. You can specify another downloader or cache by using + /// a customized `KingfisherOptionsInfo`. Both the progress and completion block will be invoked in + /// main thread. The `.callbackQueue` value in `optionsInfo` will be ignored in this method. + public convenience init( + urls: [URL], + options: KingfisherOptionsInfo? = nil, + progressBlock: PrefetcherProgressBlock? = nil, + completionHandler: PrefetcherCompletionHandler? = nil) + { + let resources: [Resource] = urls.map { $0 } + self.init( + resources: resources, + options: options, + progressBlock: progressBlock, + completionHandler: completionHandler) + } + + /// Creates an image prefetcher with an array of resources. + /// + /// - Parameters: + /// - resources: The resources which should be prefetched. See `Resource` type for more. + /// - options: Options could control some behaviors. See `KingfisherOptionsInfo` for more. + /// - progressBlock: Called every time an resource is downloaded, skipped or cancelled. + /// - completionHandler: Called when the whole prefetching process finished. + /// + /// - Note: + /// By default, the `ImageDownloader.defaultDownloader` and `ImageCache.defaultCache` will be used as + /// the downloader and cache target respectively. You can specify another downloader or cache by using + /// a customized `KingfisherOptionsInfo`. Both the progress and completion block will be invoked in + /// main thread. The `.callbackQueue` value in `optionsInfo` will be ignored in this method. + public convenience init( + resources: [Resource], + options: KingfisherOptionsInfo? = nil, + progressBlock: PrefetcherProgressBlock? = nil, + completionHandler: PrefetcherCompletionHandler? = nil) + { + self.init(sources: resources.map { $0.convertToSource() }, options: options) + self.progressBlock = progressBlock + self.completionHandler = completionHandler + } + + /// Creates an image prefetcher with an array of sources. + /// + /// - Parameters: + /// - sources: The sources which should be prefetched. See `Source` type for more. + /// - options: Options could control some behaviors. See `KingfisherOptionsInfo` for more. + /// - progressBlock: Called every time an source fetching successes, fails, is skipped. + /// - completionHandler: Called when the whole prefetching process finished. + /// + /// - Note: + /// By default, the `ImageDownloader.defaultDownloader` and `ImageCache.defaultCache` will be used as + /// the downloader and cache target respectively. You can specify another downloader or cache by using + /// a customized `KingfisherOptionsInfo`. Both the progress and completion block will be invoked in + /// main thread. The `.callbackQueue` value in `optionsInfo` will be ignored in this method. + public convenience init(sources: [Source], + options: KingfisherOptionsInfo? = nil, + progressBlock: PrefetcherSourceProgressBlock? = nil, + completionHandler: PrefetcherSourceCompletionHandler? = nil) + { + self.init(sources: sources, options: options) + self.progressSourceBlock = progressBlock + self.completionSourceHandler = completionHandler + } + + init(sources: [Source], options: KingfisherOptionsInfo?) { + var options = KingfisherParsedOptionsInfo(options) + prefetchSources = sources + pendingSources = ArraySlice(sources) + + // We want all callbacks from our prefetch queue, so we should ignore the callback queue in options. + // Add our own callback dispatch queue to make sure all internal callbacks are + // coming back in our expected queue. + options.callbackQueue = .dispatch(pretchQueue) + optionsInfo = options + + let cache = optionsInfo.targetCache ?? .default + let downloader = optionsInfo.downloader ?? .default + manager = KingfisherManager(downloader: downloader, cache: cache) + } + + /// Starts to download the resources and cache them. This can be useful for background downloading + /// of assets that are required for later use in an app. This code will not try and update any UI + /// with the results of the process. + public func start() { + pretchQueue.async { + guard !self.stopped else { + assertionFailure("You can not restart the same prefetcher. Try to create a new prefetcher.") + self.handleComplete() + return + } + + guard self.maxConcurrentDownloads > 0 else { + assertionFailure("There should be concurrent downloads value should be at least 1.") + self.handleComplete() + return + } + + // Empty case. + guard self.prefetchSources.count > 0 else { + self.handleComplete() + return + } + + let initialConcurrentDownloads = min(self.prefetchSources.count, self.maxConcurrentDownloads) + for _ in 0 ..< initialConcurrentDownloads { + if let resource = self.pendingSources.popFirst() { + self.startPrefetching(resource) + } + } + } + } + + /// Stops current downloading progress, and cancel any future prefetching activity that might be occuring. + public func stop() { + pretchQueue.async { + if self.finished { return } + self.stopped = true + self.tasks.values.forEach { $0.cancel() } + } + } + + private func downloadAndCache(_ source: Source) { + + let downloadTaskCompletionHandler: ((Result) -> Void) = { result in + self.tasks.removeValue(forKey: source.cacheKey) + do { + let _ = try result.get() + self.completedSources.append(source) + } catch { + self.failedSources.append(source) + } + + self.reportProgress() + if self.stopped { + if self.tasks.isEmpty { + self.failedSources.append(contentsOf: self.pendingSources) + self.handleComplete() + } + } else { + self.reportCompletionOrStartNext() + } + } + + var downloadTask: DownloadTask.WrappedTask? + ImagePrefetcher.requestingQueue.sync { + let context = RetrievingContext( + options: optionsInfo, originalSource: source + ) + downloadTask = manager.loadAndCacheImage( + source: source, + context: context, + completionHandler: downloadTaskCompletionHandler) + } + + if let downloadTask = downloadTask { + tasks[source.cacheKey] = downloadTask + } + } + + private func append(cached source: Source) { + skippedSources.append(source) + + reportProgress() + reportCompletionOrStartNext() + } + + private func startPrefetching(_ source: Source) + { + if optionsInfo.forceRefresh { + downloadAndCache(source) + return + } + + let cacheType = manager.cache.imageCachedType( + forKey: source.cacheKey, + processorIdentifier: optionsInfo.processor.identifier) + switch cacheType { + case .memory: + append(cached: source) + case .disk: + if optionsInfo.alsoPrefetchToMemory { + let context = RetrievingContext(options: optionsInfo, originalSource: source) + _ = manager.retrieveImageFromCache( + source: source, + context: context) + { + _ in + self.append(cached: source) + } + } else { + append(cached: source) + } + case .none: + downloadAndCache(source) + } + } + + private func reportProgress() { + + if progressBlock == nil && progressSourceBlock == nil { + return + } + + let skipped = self.skippedSources + let failed = self.failedSources + let completed = self.completedSources + CallbackQueue.mainCurrentOrAsync.execute { + self.progressSourceBlock?(skipped, failed, completed) + self.progressBlock?( + skipped.compactMap { $0.asResource }, + failed.compactMap { $0.asResource }, + completed.compactMap { $0.asResource } + ) + } + } + + private func reportCompletionOrStartNext() { + if let resource = self.pendingSources.popFirst() { + // Loose call stack for huge ammount of sources. + pretchQueue.async { self.startPrefetching(resource) } + } else { + guard allFinished else { return } + self.handleComplete() + } + } + + var allFinished: Bool { + return skippedSources.count + failedSources.count + completedSources.count == prefetchSources.count + } + + private func handleComplete() { + + if completionHandler == nil && completionSourceHandler == nil { + return + } + + // The completion handler should be called on the main thread + CallbackQueue.mainCurrentOrAsync.execute { + self.completionSourceHandler?(self.skippedSources, self.failedSources, self.completedSources) + self.completionHandler?( + self.skippedSources.compactMap { $0.asResource }, + self.failedSources.compactMap { $0.asResource }, + self.completedSources.compactMap { $0.asResource } + ) + self.completionHandler = nil + self.progressBlock = nil + } + } +} diff --git b/Pods/Kingfisher/Sources/Networking/RedirectHandler.swift a/Pods/Kingfisher/Sources/Networking/RedirectHandler.swift new file mode 100644 index 0000000..aaf0f7f --- /dev/null +++ a/Pods/Kingfisher/Sources/Networking/RedirectHandler.swift @@ -0,0 +1,76 @@ +// +// RedirectHandler.swift +// Kingfisher +// +// Created by Roman Maidanovych on 2018/12/10. +// +// Copyright (c) 2019 Wei Wang +// +// 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 + +/// Represents and wraps a method for modifying request during an image download request redirection. +public protocol ImageDownloadRedirectHandler { + + /// The `ImageDownloadRedirectHandler` contained will be used to change the request before redirection. + /// This is the posibility you can modify the image download request during redirection. You can modify the + /// request for some customizing purpose, such as adding auth token to the header, do basic HTTP auth or + /// something like url mapping. + /// + /// Usually, you pass an `ImageDownloadRedirectHandler` as the associated value of + /// `KingfisherOptionsInfoItem.redirectHandler` and use it as the `options` parameter in related methods. + /// + /// If you do nothing with the input `request` and return it as is, a downloading process will redirect with it. + /// + /// - Parameters: + /// - task: The current `SessionDataTask` which triggers this redirect. + /// - response: The response received during redirection. + /// - newRequest: The request for redirection which can be modified. + /// - completionHandler: A closure for being called with modified request. + func handleHTTPRedirection( + for task: SessionDataTask, + response: HTTPURLResponse, + newRequest: URLRequest, + completionHandler: @escaping (URLRequest?) -> Void) +} + +/// A wrapper for creating an `ImageDownloadRedirectHandler` easier. +/// This type conforms to `ImageDownloadRedirectHandler` and wraps a redirect request modify block. +public struct AnyRedirectHandler: ImageDownloadRedirectHandler { + + let block: (SessionDataTask, HTTPURLResponse, URLRequest, (URLRequest?) -> Void) -> Void + + public func handleHTTPRedirection( + for task: SessionDataTask, + response: HTTPURLResponse, + newRequest: URLRequest, + completionHandler: @escaping (URLRequest?) -> Void) + { + block(task, response, newRequest, completionHandler) + } + + /// Creates a value of `ImageDownloadRedirectHandler` which runs `modify` block. + /// + /// - Parameter modify: The request modifying block runs when a request modifying task comes. + /// + public init(handle: @escaping (SessionDataTask, HTTPURLResponse, URLRequest, (URLRequest?) -> Void) -> Void) { + block = handle + } +} diff --git b/Pods/Kingfisher/Sources/Networking/RequestModifier.swift a/Pods/Kingfisher/Sources/Networking/RequestModifier.swift new file mode 100644 index 0000000..06b062a --- /dev/null +++ a/Pods/Kingfisher/Sources/Networking/RequestModifier.swift @@ -0,0 +1,69 @@ +// +// RequestModifier.swift +// Kingfisher +// +// Created by Wei Wang on 2016/09/05. +// +// Copyright (c) 2019 Wei Wang +// +// 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 + +/// Represents and wraps a method for modifying request before an image download request starts. +public protocol ImageDownloadRequestModifier { + + /// A method will be called just before the `request` being sent. + /// This is the last chance you can modify the image download request. You can modify the request for some + /// customizing purpose, such as adding auth token to the header, do basic HTTP auth or something like url mapping. + /// + /// Usually, you pass an `ImageDownloadRequestModifier` as the associated value of + /// `KingfisherOptionsInfoItem.requestModifier` and use it as the `options` parameter in related methods. + /// + /// If you do nothing with the input `request` and return it as is, a downloading process will start with it. + /// + /// - Parameter request: The input request contains necessary information like `url`. This request is generated + /// according to your resource url as a GET request. + /// - Returns: A modified version of request, which you wish to use for downloading an image. If `nil` returned, + /// a `KingfisherError.requestError` with `.emptyRequest` as its reason will occur. + /// + func modified(for request: URLRequest) -> URLRequest? +} + +/// A wrapper for creating an `ImageDownloadRequestModifier` easier. +/// This type conforms to `ImageDownloadRequestModifier` and wraps an image modify block. +public struct AnyModifier: ImageDownloadRequestModifier { + + let block: (URLRequest) -> URLRequest? + + /// For `ImageDownloadRequestModifier` conformation. + public func modified(for request: URLRequest) -> URLRequest? { + return block(request) + } + + /// Creates a value of `ImageDownloadRequestModifier` which runs `modify` block. + /// + /// - Parameter modify: The request modifying block runs when a request modifying task comes. + /// The return `URLRequest?` value of this block will be used as the image download request. + /// If `nil` returned, a `KingfisherError.requestError` with `.emptyRequest` as its + /// reason will occur. + public init(modify: @escaping (URLRequest) -> URLRequest?) { + block = modify + } +} diff --git b/Pods/Kingfisher/Sources/Networking/SessionDataTask.swift a/Pods/Kingfisher/Sources/Networking/SessionDataTask.swift new file mode 100644 index 0000000..2fcfbf0 --- /dev/null +++ a/Pods/Kingfisher/Sources/Networking/SessionDataTask.swift @@ -0,0 +1,119 @@ +// +// SessionDataTask.swift +// Kingfisher +// +// Created by Wei Wang on 2018/11/1. +// +// Copyright (c) 2019 Wei Wang +// +// 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 + +/// Represents a session data task in `ImageDownloader`. It consists of an underlying `URLSessionDataTask` and +/// an array of `TaskCallback`. Multiple `TaskCallback`s could be added for a single downloading data task. +public class SessionDataTask { + + /// Represents the type of token which used for cancelling a task. + public typealias CancelToken = Int + + struct TaskCallback { + let onCompleted: Delegate, Void>? + let options: KingfisherParsedOptionsInfo + } + + /// Downloaded raw data of current task. + public private(set) var mutableData: Data + + /// The underlying download task. It is only for debugging purpose when you encountered an error. You should not + /// modify the content of this task or start it yourself. + public let task: URLSessionDataTask + private var callbacksStore = [CancelToken: TaskCallback]() + + var callbacks: [SessionDataTask.TaskCallback] { + lock.lock() + defer { lock.unlock() } + return Array(callbacksStore.values) + } + + private var currentToken = 0 + private let lock = NSLock() + + let onTaskDone = Delegate<(Result<(Data, URLResponse?), KingfisherError>, [TaskCallback]), Void>() + let onCallbackCancelled = Delegate<(CancelToken, TaskCallback), Void>() + + var started = false + var containsCallbacks: Bool { + // We should be able to use `task.state != .running` to check it. + // However, in some rare cases, cancelling the task does not change + // task state to `.cancelling` immediately, but still in `.running`. + // So we need to check callbacks count to for sure that it is safe to remove the + // task in delegate. + return !callbacks.isEmpty + } + + init(task: URLSessionDataTask) { + self.task = task + mutableData = Data() + } + + func addCallback(_ callback: TaskCallback) -> CancelToken { + lock.lock() + defer { lock.unlock() } + callbacksStore[currentToken] = callback + defer { currentToken += 1 } + return currentToken + } + + func removeCallback(_ token: CancelToken) -> TaskCallback? { + lock.lock() + defer { lock.unlock() } + if let callback = callbacksStore[token] { + callbacksStore[token] = nil + return callback + } + return nil + } + + func resume() { + guard !started else { return } + started = true + task.resume() + } + + func cancel(token: CancelToken) { + guard let callback = removeCallback(token) else { + return + } + if callbacksStore.count == 0 { + task.cancel() + } + onCallbackCancelled.call((token, callback)) + } + + func forceCancel() { + for token in callbacksStore.keys { + cancel(token: token) + } + } + + func didReceiveData(_ data: Data) { + mutableData.append(data) + } +} diff --git b/Pods/Kingfisher/Sources/Networking/SessionDelegate.swift a/Pods/Kingfisher/Sources/Networking/SessionDelegate.swift new file mode 100644 index 0000000..36c0609 --- /dev/null +++ a/Pods/Kingfisher/Sources/Networking/SessionDelegate.swift @@ -0,0 +1,253 @@ +// +// SessionDelegate.swift +// Kingfisher +// +// Created by Wei Wang on 2018/11/1. +// +// Copyright (c) 2019 Wei Wang +// +// 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 + +// Represents the delegate object of downloader session. It also behave like a task manager for downloading. +class SessionDelegate: NSObject { + + typealias SessionChallengeFunc = ( + URLSession, + URLAuthenticationChallenge, + (URLSession.AuthChallengeDisposition, URLCredential?) -> Void + ) + + typealias SessionTaskChallengeFunc = ( + URLSession, + URLSessionTask, + URLAuthenticationChallenge, + (URLSession.AuthChallengeDisposition, URLCredential?) -> Void + ) + + private var tasks: [URL: SessionDataTask] = [:] + private let lock = NSLock() + + let onValidStatusCode = Delegate() + let onDownloadingFinished = Delegate<(URL, Result), Void>() + let onDidDownloadData = Delegate() + + let onReceiveSessionChallenge = Delegate() + let onReceiveSessionTaskChallenge = Delegate() + + func add( + _ dataTask: URLSessionDataTask, + url: URL, + callback: SessionDataTask.TaskCallback) -> DownloadTask + { + lock.lock() + defer { lock.unlock() } + + // Create a new task if necessary. + let task = SessionDataTask(task: dataTask) + task.onCallbackCancelled.delegate(on: self) { [weak task] (self, value) in + guard let task = task else { return } + + let (token, callback) = value + + let error = KingfisherError.requestError(reason: .taskCancelled(task: task, token: token)) + task.onTaskDone.call((.failure(error), [callback])) + // No other callbacks waiting, we can clear the task now. + if !task.containsCallbacks { + let dataTask = task.task + self.remove(dataTask) + } + } + let token = task.addCallback(callback) + tasks[url] = task + return DownloadTask(sessionTask: task, cancelToken: token) + } + + func append( + _ task: SessionDataTask, + url: URL, + callback: SessionDataTask.TaskCallback) -> DownloadTask + { + let token = task.addCallback(callback) + return DownloadTask(sessionTask: task, cancelToken: token) + } + + private func remove(_ task: URLSessionTask) { + guard let url = task.originalRequest?.url else { + return + } + lock.lock() + defer {lock.unlock()} + tasks[url] = nil + } + + private func task(for task: URLSessionTask) -> SessionDataTask? { + + guard let url = task.originalRequest?.url else { + return nil + } + + lock.lock() + defer { lock.unlock() } + guard let sessionTask = tasks[url] else { + return nil + } + guard sessionTask.task.taskIdentifier == task.taskIdentifier else { + return nil + } + return sessionTask + } + + func task(for url: URL) -> SessionDataTask? { + lock.lock() + defer { lock.unlock() } + return tasks[url] + } + + func cancelAll() { + lock.lock() + let taskValues = tasks.values + lock.unlock() + for task in taskValues { + task.forceCancel() + } + } + + func cancel(url: URL) { + lock.lock() + let task = tasks[url] + lock.unlock() + task?.forceCancel() + } +} + +extension SessionDelegate: URLSessionDataDelegate { + + func urlSession( + _ session: URLSession, + dataTask: URLSessionDataTask, + didReceive response: URLResponse, + completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) + { + guard let httpResponse = response as? HTTPURLResponse else { + let error = KingfisherError.responseError(reason: .invalidURLResponse(response: response)) + onCompleted(task: dataTask, result: .failure(error)) + completionHandler(.cancel) + return + } + + let httpStatusCode = httpResponse.statusCode + guard onValidStatusCode.call(httpStatusCode) == true else { + let error = KingfisherError.responseError(reason: .invalidHTTPStatusCode(response: httpResponse)) + onCompleted(task: dataTask, result: .failure(error)) + completionHandler(.cancel) + return + } + completionHandler(.allow) + } + + func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { + guard let task = self.task(for: dataTask) else { + return + } + + task.didReceiveData(data) + + task.callbacks.forEach { callback in + callback.options.onDataReceived?.forEach { sideEffect in + sideEffect.onDataReceived(session, task: task, data: data) + } + } + } + + func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { + guard let sessionTask = self.task(for: task) else { return } + + if let url = task.originalRequest?.url { + let result: Result + if let error = error { + result = .failure(KingfisherError.responseError(reason: .URLSessionError(error: error))) + } else if let response = task.response { + result = .success(response) + } else { + result = .failure(KingfisherError.responseError(reason: .noURLResponse(task: sessionTask))) + } + onDownloadingFinished.call((url, result)) + } + + let result: Result<(Data, URLResponse?), KingfisherError> + if let error = error { + result = .failure(KingfisherError.responseError(reason: .URLSessionError(error: error))) + } else { + if let data = onDidDownloadData.call(sessionTask), let finalData = data { + result = .success((finalData, task.response)) + } else { + result = .failure(KingfisherError.responseError(reason: .dataModifyingFailed(task: sessionTask))) + } + } + onCompleted(task: task, result: result) + } + + func urlSession( + _ session: URLSession, + didReceive challenge: URLAuthenticationChallenge, + completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) + { + onReceiveSessionChallenge.call((session, challenge, completionHandler)) + } + + func urlSession( + _ session: URLSession, + task: URLSessionTask, + didReceive challenge: URLAuthenticationChallenge, + completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) + { + onReceiveSessionTaskChallenge.call((session, task, challenge, completionHandler)) + } + + func urlSession( + _ session: URLSession, + task: URLSessionTask, + willPerformHTTPRedirection response: HTTPURLResponse, + newRequest request: URLRequest, + completionHandler: @escaping (URLRequest?) -> Void) + { + guard let sessionDataTask = self.task(for: task), + let redirectHandler = Array(sessionDataTask.callbacks).last?.options.redirectHandler else + { + completionHandler(request) + return + } + + redirectHandler.handleHTTPRedirection( + for: sessionDataTask, + response: response, + newRequest: request, + completionHandler: completionHandler) + } + + private func onCompleted(task: URLSessionTask, result: Result<(Data, URLResponse?), KingfisherError>) { + guard let sessionTask = self.task(for: task) else { + return + } + remove(task) + sessionTask.onTaskDone.call((result, sessionTask.callbacks)) + } +} diff --git b/Pods/Kingfisher/Sources/Utility/Box.swift a/Pods/Kingfisher/Sources/Utility/Box.swift new file mode 100644 index 0000000..0303a6e --- /dev/null +++ a/Pods/Kingfisher/Sources/Utility/Box.swift @@ -0,0 +1,34 @@ +// +// Box.swift +// Kingfisher +// +// Created by Wei Wang on 2018/3/17. +// Copyright (c) 2019 Wei Wang +// +// 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 + +class Box { + var value: T + + init(_ value: T) { + self.value = value + } +} diff --git b/Pods/Kingfisher/Sources/Utility/CallbackQueue.swift a/Pods/Kingfisher/Sources/Utility/CallbackQueue.swift new file mode 100644 index 0000000..fa67f14 --- /dev/null +++ a/Pods/Kingfisher/Sources/Utility/CallbackQueue.swift @@ -0,0 +1,81 @@ +// +// CallbackQueue.swift +// Kingfisher +// +// Created by onevcat on 2018/10/15. +// +// Copyright (c) 2019 Wei Wang +// +// 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 + +/// Represents callback queue behaviors when an calling of closure be dispatched. +/// +/// - asyncMain: Dispatch the calling to `DispatchQueue.main` with an `async` behavior. +/// - currentMainOrAsync: Dispatch the calling to `DispatchQueue.main` with an `async` behavior if current queue is not +/// `.main`. Otherwise, call the closure immediately in current main queue. +/// - untouch: Do not change the calling queue for closure. +/// - dispatch: Dispatches to a specified `DispatchQueue`. +public enum CallbackQueue { + /// Dispatch the calling to `DispatchQueue.main` with an `async` behavior. + case mainAsync + /// Dispatch the calling to `DispatchQueue.main` with an `async` behavior if current queue is not + /// `.main`. Otherwise, call the closure immediately in current main queue. + case mainCurrentOrAsync + /// Do not change the calling queue for closure. + case untouch + /// Dispatches to a specified `DispatchQueue`. + case dispatch(DispatchQueue) + + public func execute(_ block: @escaping () -> Void) { + switch self { + case .mainAsync: + DispatchQueue.main.async { block() } + case .mainCurrentOrAsync: + DispatchQueue.main.safeAsync { block() } + case .untouch: + block() + case .dispatch(let queue): + queue.async { block() } + } + } + + var queue: DispatchQueue { + switch self { + case .mainAsync: return .main + case .mainCurrentOrAsync: return .main + case .untouch: return OperationQueue.current?.underlyingQueue ?? .main + case .dispatch(let queue): return queue + } + } +} + +extension DispatchQueue { + // This method will dispatch the `block` to self. + // If `self` is the main queue, and current thread is main thread, the block + // will be invoked immediately instead of being dispatched. + func safeAsync(_ block: @escaping ()->()) { + if self === DispatchQueue.main && Thread.isMainThread { + block() + } else { + async { block() } + } + } +} diff --git b/Pods/Kingfisher/Sources/Utility/Delegate.swift a/Pods/Kingfisher/Sources/Utility/Delegate.swift new file mode 100644 index 0000000..15915c9 --- /dev/null +++ a/Pods/Kingfisher/Sources/Utility/Delegate.swift @@ -0,0 +1,53 @@ +// +// Delegate.swift +// Kingfisher +// +// Created by onevcat on 2018/10/10. +// +// Copyright (c) 2019 Wei Wang +// +// 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 delegate helper type to "shadow" weak `self`, to prevent creating an unexpected retain cycle. +class Delegate { + init() {} + + private var block: ((Input) -> Output?)? + + func delegate(on target: T, block: ((T, Input) -> Output)?) { + // The `target` is weak inside block, so you do not need to worry about it in the caller side. + self.block = { [weak target] input in + guard let target = target else { return nil } + return block?(target, input) + } + } + + func call(_ input: Input) -> Output? { + return block?(input) + } +} + +extension Delegate where Input == Void { + // To make syntax better for `Void` input. + func call() -> Output? { + return call(()) + } +} diff --git b/Pods/Kingfisher/Sources/Utility/ExtensionHelpers.swift a/Pods/Kingfisher/Sources/Utility/ExtensionHelpers.swift new file mode 100644 index 0000000..f1e1e8b --- /dev/null +++ a/Pods/Kingfisher/Sources/Utility/ExtensionHelpers.swift @@ -0,0 +1,125 @@ +// +// ExtensionHelpers.swift +// Kingfisher +// +// Created by onevcat on 2018/09/28. +// +// Copyright (c) 2019 Wei Wang +// +// 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 CGFloat { + var isEven: Bool { + return truncatingRemainder(dividingBy: 2.0) == 0 + } +} + +#if canImport(AppKit) && !targetEnvironment(macCatalyst) +import AppKit +extension NSBezierPath { + convenience init(roundedRect rect: NSRect, topLeftRadius: CGFloat, topRightRadius: CGFloat, + bottomLeftRadius: CGFloat, bottomRightRadius: CGFloat) + { + self.init() + + let maxCorner = min(rect.width, rect.height) / 2 + + let radiusTopLeft = min(maxCorner, max(0, topLeftRadius)) + let radiusTopRight = min(maxCorner, max(0, topRightRadius)) + let radiusBottomLeft = min(maxCorner, max(0, bottomLeftRadius)) + let radiusBottomRight = min(maxCorner, max(0, bottomRightRadius)) + + guard !rect.isEmpty else { + return + } + + let topLeft = NSPoint(x: rect.minX, y: rect.maxY) + let topRight = NSPoint(x: rect.maxX, y: rect.maxY) + let bottomRight = NSPoint(x: rect.maxX, y: rect.minY) + + move(to: NSPoint(x: rect.midX, y: rect.maxY)) + appendArc(from: topLeft, to: rect.origin, radius: radiusTopLeft) + appendArc(from: rect.origin, to: bottomRight, radius: radiusBottomLeft) + appendArc(from: bottomRight, to: topRight, radius: radiusBottomRight) + appendArc(from: topRight, to: topLeft, radius: radiusTopRight) + close() + } + + convenience init(roundedRect rect: NSRect, byRoundingCorners corners: RectCorner, radius: CGFloat) { + let radiusTopLeft = corners.contains(.topLeft) ? radius : 0 + let radiusTopRight = corners.contains(.topRight) ? radius : 0 + let radiusBottomLeft = corners.contains(.bottomLeft) ? radius : 0 + let radiusBottomRight = corners.contains(.bottomRight) ? radius : 0 + + self.init(roundedRect: rect, topLeftRadius: radiusTopLeft, topRightRadius: radiusTopRight, + bottomLeftRadius: radiusBottomLeft, bottomRightRadius: radiusBottomRight) + } +} + +extension KFCrossPlatformImage { + // macOS does not support scale. This is just for code compatibility across platforms. + convenience init?(data: Data, scale: CGFloat) { + self.init(data: data) + } +} +#endif + +#if canImport(UIKit) +import UIKit +extension RectCorner { + var uiRectCorner: UIRectCorner { + + var result: UIRectCorner = [] + + if contains(.topLeft) { result.insert(.topLeft) } + if contains(.topRight) { result.insert(.topRight) } + if contains(.bottomLeft) { result.insert(.bottomLeft) } + if contains(.bottomRight) { result.insert(.bottomRight) } + + return result + } +} +#endif + +extension Date { + var isPast: Bool { + return isPast(referenceDate: Date()) + } + + var isFuture: Bool { + return !isPast + } + + func isPast(referenceDate: Date) -> Bool { + return timeIntervalSince(referenceDate) <= 0 + } + + func isFuture(referenceDate: Date) -> Bool { + return !isPast(referenceDate: referenceDate) + } + + // `Date` in memory is a wrap for `TimeInterval`. But in file attribute it can only accept `Int` number. + // By default the system will `round` it. But it is not friendly for testing purpose. + // So we always `ceil` the value when used for file attributes. + var fileAttributeDate: Date { + return Date(timeIntervalSince1970: ceil(timeIntervalSince1970)) + } +} diff --git b/Pods/Kingfisher/Sources/Utility/Result.swift a/Pods/Kingfisher/Sources/Utility/Result.swift new file mode 100644 index 0000000..8b9c5fd --- /dev/null +++ a/Pods/Kingfisher/Sources/Utility/Result.swift @@ -0,0 +1,239 @@ +// +// Result.swift +// Kingfisher +// +// Created by onevcat on 2018/09/22. +// +// Copyright (c) 2019 Wei Wang +// +// 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 + +#if swift(>=4.3) +/// Result type already built-in +#else +/// A value that represents either a success or failure, capturing associated +/// values in both cases. +public enum Result { + /// A success, storing a `Value`. + case success(Success) + + /// A failure, storing an `Error`. + case failure(Failure) + + /// Evaluates the given transform closure when this `Result` instance is + /// `.success`, passing the value as a parameter. + /// + /// Use the `map` method with a closure that returns a non-`Result` value. + /// + /// - Parameter transform: A closure that takes the successful value of the + /// instance. + /// - Returns: A new `Result` instance with the result of the transform, if + /// it was applied. + public func map( + _ transform: (Success) -> NewSuccess + ) -> Result { + switch self { + case let .success(success): + return .success(transform(success)) + case let .failure(failure): + return .failure(failure) + } + } + + /// Evaluates the given transform closure when this `Result` instance is + /// `.failure`, passing the error as a parameter. + /// + /// Use the `mapError` method with a closure that returns a non-`Result` + /// value. + /// + /// - Parameter transform: A closure that takes the failure value of the + /// instance. + /// - Returns: A new `Result` instance with the result of the transform, if + /// it was applied. + public func mapError( + _ transform: (Failure) -> NewFailure + ) -> Result { + switch self { + case let .success(success): + return .success(success) + case let .failure(failure): + return .failure(transform(failure)) + } + } + + /// Evaluates the given transform closure when this `Result` instance is + /// `.success`, passing the value as a parameter and flattening the result. + /// + /// - Parameter transform: A closure that takes the successful value of the + /// instance. + /// - Returns: A new `Result` instance, either from the transform or from + /// the previous error value. + public func flatMap( + _ transform: (Success) -> Result + ) -> Result { + switch self { + case let .success(success): + return transform(success) + case let .failure(failure): + return .failure(failure) + } + } + + /// Evaluates the given transform closure when this `Result` instance is + /// `.failure`, passing the error as a parameter and flattening the result. + /// + /// - Parameter transform: A closure that takes the error value of the + /// instance. + /// - Returns: A new `Result` instance, either from the transform or from + /// the previous success value. + public func flatMapError( + _ transform: (Failure) -> Result + ) -> Result { + switch self { + case let .success(success): + return .success(success) + case let .failure(failure): + return transform(failure) + } + } +} + +extension Result where Failure: Error { + /// Returns the success value as a throwing expression. + /// + /// Use this method to retrieve the value of this result if it represents a + /// success, or to catch the value if it represents a failure. + /// + /// let integerResult: Result = .success(5) + /// do { + /// let value = try integerResult.get() + /// print("The value is \(value).") + /// } catch error { + /// print("Error retrieving the value: \(error)") + /// } + /// // Prints "The value is 5." + /// + /// - Returns: The success value, if the instance represents a success. + /// - Throws: The failure value, if the instance represents a failure. + public func get() throws -> Success { + switch self { + case let .success(success): + return success + case let .failure(failure): + throw failure + } + } + + /// Unwraps the `Result` into a throwing expression. + /// + /// - Returns: The success value, if the instance is a success. + /// - Throws: The error value, if the instance is a failure. + @available(*, deprecated, message: "This method will be removed soon. Use `get() throws -> Success` instead.") + public func unwrapped() throws -> Success { + switch self { + case let .success(value): + return value + case let .failure(error): + throw error + } + } +} + +extension Result where Failure == Swift.Error { + /// Creates a new result by evaluating a throwing closure, capturing the + /// returned value as a success, or any thrown error as a failure. + /// + /// - Parameter body: A throwing closure to evaluate. + @_transparent + public init(catching body: () throws -> Success) { + do { + self = .success(try body()) + } catch { + self = .failure(error) + } + } +} + +extension Result : Equatable where Success : Equatable, Failure: Equatable { } + +extension Result : Hashable where Success : Hashable, Failure : Hashable { } + +extension Result : CustomDebugStringConvertible { + public var debugDescription: String { + var output = "Result." + switch self { + case let .success(value): + output += "success(" + debugPrint(value, terminator: "", to: &output) + case let .failure(error): + output += "failure(" + debugPrint(error, terminator: "", to: &output) + } + output += ")" + + return output + } +} +#endif + +// These helper methods are not public since we do not want them to be exposed or cause any conflicting. +// However, they are just wrapper of `ResultUtil` static methods. +extension Result where Failure: Error { + + /// Evaluates the given transform closures to create a single output value. + /// + /// - Parameters: + /// - onSuccess: A closure that transforms the success value. + /// - onFailure: A closure that transforms the error value. + /// - Returns: A single `Output` value. + func match( + onSuccess: (Success) -> Output, + onFailure: (Failure) -> Output) -> Output + { + switch self { + case let .success(value): + return onSuccess(value) + case let .failure(error): + return onFailure(error) + } + } + + func matchSuccess(with folder: (Success?) -> Output) -> Output { + return match( + onSuccess: { value in return folder(value) }, + onFailure: { _ in return folder(nil) } + ) + } + + func matchFailure(with folder: (Error?) -> Output) -> Output { + return match( + onSuccess: { _ in return folder(nil) }, + onFailure: { error in return folder(error) } + ) + } + + func match(with folder: (Success?, Error?) -> Output) -> Output { + return match( + onSuccess: { return folder($0, nil) }, + onFailure: { return folder(nil, $0) } + ) + } +} diff --git b/Pods/Kingfisher/Sources/Utility/Runtime.swift a/Pods/Kingfisher/Sources/Utility/Runtime.swift new file mode 100644 index 0000000..d5818e2 --- /dev/null +++ a/Pods/Kingfisher/Sources/Utility/Runtime.swift @@ -0,0 +1,35 @@ +// +// Runtime.swift +// Kingfisher +// +// Created by Wei Wang on 2018/10/12. +// +// Copyright (c) 2019 Wei Wang +// +// 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 + +func getAssociatedObject(_ object: Any, _ key: UnsafeRawPointer) -> T? { + return objc_getAssociatedObject(object, key) as? T +} + +func setRetainedAssociatedObject(_ object: Any, _ key: UnsafeRawPointer, _ value: T) { + objc_setAssociatedObject(object, key, value, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) +} diff --git b/Pods/Kingfisher/Sources/Utility/SizeExtensions.swift a/Pods/Kingfisher/Sources/Utility/SizeExtensions.swift new file mode 100644 index 0000000..19d05d6 --- /dev/null +++ a/Pods/Kingfisher/Sources/Utility/SizeExtensions.swift @@ -0,0 +1,110 @@ +// +// SizeExtensions.swift +// Kingfisher +// +// Created by onevcat on 2018/09/28. +// +// Copyright (c) 2019 Wei Wang +// +// 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 CoreGraphics + +extension CGSize: KingfisherCompatibleValue {} +extension KingfisherWrapper where Base == CGSize { + + /// Returns a size by resizing the `base` size to a target size under a given content mode. + /// + /// - Parameters: + /// - size: The target size to resize to. + /// - contentMode: Content mode of the target size should be when resizing. + /// - Returns: The resized size under the given `ContentMode`. + public func resize(to size: CGSize, for contentMode: ContentMode) -> CGSize { + switch contentMode { + case .aspectFit: + return constrained(size) + case .aspectFill: + return filling(size) + case .none: + return size + } + } + + /// Returns a size by resizing the `base` size by making it aspect fitting the given `size`. + /// + /// - Parameter size: The size in which the `base` should fit in. + /// - Returns: The size fitted in by the input `size`, while keeps `base` aspect. + public func constrained(_ size: CGSize) -> CGSize { + let aspectWidth = round(aspectRatio * size.height) + let aspectHeight = round(size.width / aspectRatio) + + return aspectWidth > size.width ? + CGSize(width: size.width, height: aspectHeight) : + CGSize(width: aspectWidth, height: size.height) + } + + /// Returns a size by resizing the `base` size by making it aspect filling the given `size`. + /// + /// - Parameter size: The size in which the `base` should fill. + /// - Returns: The size be filled by the input `size`, while keeps `base` aspect. + public func filling(_ size: CGSize) -> CGSize { + let aspectWidth = round(aspectRatio * size.height) + let aspectHeight = round(size.width / aspectRatio) + + return aspectWidth < size.width ? + CGSize(width: size.width, height: aspectHeight) : + CGSize(width: aspectWidth, height: size.height) + } + + /// Returns a `CGRect` for which the `base` size is constrained to an input `size` at a given `anchor` point. + /// + /// - Parameters: + /// - size: The size in which the `base` should be constrained to. + /// - anchor: An anchor point in which the size constraint should happen. + /// - Returns: The result `CGRect` for the constraint operation. + public func constrainedRect(for size: CGSize, anchor: CGPoint) -> CGRect { + + let unifiedAnchor = CGPoint(x: anchor.x.clamped(to: 0.0...1.0), + y: anchor.y.clamped(to: 0.0...1.0)) + + let x = unifiedAnchor.x * base.width - unifiedAnchor.x * size.width + let y = unifiedAnchor.y * base.height - unifiedAnchor.y * size.height + let r = CGRect(x: x, y: y, width: size.width, height: size.height) + + let ori = CGRect(origin: .zero, size: base) + return ori.intersection(r) + } + + private var aspectRatio: CGFloat { + return base.height == 0.0 ? 1.0 : base.width / base.height + } +} + +extension CGRect { + func scaled(_ scale: CGFloat) -> CGRect { + return CGRect(x: origin.x * scale, y: origin.y * scale, + width: size.width * scale, height: size.height * scale) + } +} + +extension Comparable { + func clamped(to limits: ClosedRange) -> Self { + return min(max(self, limits.lowerBound), limits.upperBound) + } +} diff --git b/Pods/Kingfisher/Sources/Utility/String+MD5.swift a/Pods/Kingfisher/Sources/Utility/String+MD5.swift new file mode 100644 index 0000000..07eaaf2 --- /dev/null +++ a/Pods/Kingfisher/Sources/Utility/String+MD5.swift @@ -0,0 +1,291 @@ +// +// String+MD5.swift +// Kingfisher +// +// Created by Wei Wang on 18/09/25. +// +// Copyright (c) 2019 Wei Wang +// +// 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 +import CommonCrypto + +extension String: KingfisherCompatibleValue { } +extension KingfisherWrapper where Base == String { + var md5: String { + guard let data = base.data(using: .utf8) else { + return base + } + + #if swift(>=5.0) + let message = data.withUnsafeBytes { (bytes: UnsafeRawBufferPointer) in + return [UInt8](bytes) + } + #else + let message = data.withUnsafeBytes { bytes in + return [UInt8](UnsafeBufferPointer(start: bytes, count: data.count)) + } + #endif + + let MD5Calculator = MD5(message) + let MD5Data = MD5Calculator.calculate() + + var MD5String = String() + for c in MD5Data { + MD5String += String(format: "%02x", c) + } + return MD5String + } +} + +// array of bytes, little-endian representation +func arrayOfBytes(_ value: T, length: Int? = nil) -> [UInt8] { + let totalBytes = length ?? (MemoryLayout.size * 8) + + let valuePointer = UnsafeMutablePointer.allocate(capacity: 1) + valuePointer.pointee = value + + let bytes = valuePointer.withMemoryRebound(to: UInt8.self, capacity: totalBytes) { (bytesPointer) -> [UInt8] in + var bytes = [UInt8](repeating: 0, count: totalBytes) + for j in 0...size, totalBytes) { + bytes[totalBytes - 1 - j] = (bytesPointer + j).pointee + } + return bytes + } + + #if swift(>=4.1) + valuePointer.deinitialize(count: 1) + valuePointer.deallocate() + #else + valuePointer.deinitialize() + valuePointer.deallocate(capacity: 1) + #endif + + return bytes +} + +extension Int { + // Array of bytes with optional padding (little-endian) + func bytes(_ totalBytes: Int = MemoryLayout.size) -> [UInt8] { + return arrayOfBytes(self, length: totalBytes) + } + +} + +extension NSMutableData { + + // Convenient way to append bytes + func appendBytes(_ arrayOfBytes: [UInt8]) { + append(arrayOfBytes, length: arrayOfBytes.count) + } + +} + +protocol HashProtocol { + var message: [UInt8] { get } + // Common part for hash calculation. Prepare header data. + func prepare(_ len: Int) -> [UInt8] +} + +extension HashProtocol { + + func prepare(_ len: Int) -> [UInt8] { + var tmpMessage = message + + // Step 1. Append Padding Bits + tmpMessage.append(0x80) // append one bit (UInt8 with one bit) to message + + // append "0" bit until message length in bits ≡ 448 (mod 512) + var msgLength = tmpMessage.count + var counter = 0 + + while msgLength % len != (len - 8) { + counter += 1 + msgLength += 1 + } + + tmpMessage += [UInt8](repeating: 0, count: counter) + return tmpMessage + } +} + +func toUInt32Array(_ slice: ArraySlice) -> [UInt32] { + var result = [UInt32]() + result.reserveCapacity(16) + + for idx in stride(from: slice.startIndex, to: slice.endIndex, by: MemoryLayout.size) { + let d0 = UInt32(slice[idx.advanced(by: 3)]) << 24 + let d1 = UInt32(slice[idx.advanced(by: 2)]) << 16 + let d2 = UInt32(slice[idx.advanced(by: 1)]) << 8 + let d3 = UInt32(slice[idx]) + let val: UInt32 = d0 | d1 | d2 | d3 + + result.append(val) + } + return result +} + +struct BytesIterator: IteratorProtocol { + + let chunkSize: Int + let data: [UInt8] + + init(chunkSize: Int, data: [UInt8]) { + self.chunkSize = chunkSize + self.data = data + } + + var offset = 0 + + mutating func next() -> ArraySlice? { + let end = min(chunkSize, data.count - offset) + let result = data[offset.. 0 ? result : nil + } +} + +struct BytesSequence: Sequence { + let chunkSize: Int + let data: [UInt8] + + func makeIterator() -> BytesIterator { + return BytesIterator(chunkSize: chunkSize, data: data) + } +} + +func rotateLeft(_ value: UInt32, bits: UInt32) -> UInt32 { + return ((value << bits) & 0xFFFFFFFF) | (value >> (32 - bits)) +} + +class MD5: HashProtocol { + + static let size = 16 // 128 / 8 + let message: [UInt8] + + init (_ message: [UInt8]) { + self.message = message + } + + // specifies the per-round shift amounts + private let shifts: [UInt32] = [7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, + 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, + 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, + 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21] + + // binary integer part of the sines of integers (Radians) + private let sines: [UInt32] = [0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, + 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, + 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, + 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, + 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, + 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, + 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, + 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, + 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, + 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x4881d05, + 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, + 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, + 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391] + + private let hashes: [UInt32] = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476] + + func calculate() -> [UInt8] { + var tmpMessage = prepare(64) + tmpMessage.reserveCapacity(tmpMessage.count + 4) + + // hash values + var hh = hashes + + // Step 2. Append Length a 64-bit representation of lengthInBits + let lengthInBits = (message.count * 8) + let lengthBytes = lengthInBits.bytes(64 / 8) + tmpMessage += lengthBytes.reversed() + + // Process the message in successive 512-bit chunks: + let chunkSizeBytes = 512 / 8 // 64 + + for chunk in BytesSequence(chunkSize: chunkSizeBytes, data: tmpMessage) { + // break chunk into sixteen 32-bit words M[j], 0 ≤ j ≤ 15 + let M = toUInt32Array(chunk) + assert(M.count == 16, "Invalid array") + + // Initialize hash value for this chunk: + var A: UInt32 = hh[0] + var B: UInt32 = hh[1] + var C: UInt32 = hh[2] + var D: UInt32 = hh[3] + + var dTemp: UInt32 = 0 + + // Main loop + for j in 0 ..< sines.count { + var g = 0 + var F: UInt32 = 0 + + switch j { + case 0...15: + F = (B & C) | ((~B) & D) + g = j + break + case 16...31: + F = (D & B) | (~D & C) + g = (5 * j + 1) % 16 + break + case 32...47: + F = B ^ C ^ D + g = (3 * j + 5) % 16 + break + case 48...63: + F = C ^ (B | (~D)) + g = (7 * j) % 16 + break + default: + break + } + dTemp = D + D = C + C = B + B = B &+ rotateLeft((A &+ F &+ sines[j] &+ M[g]), bits: shifts[j]) + A = dTemp + } + + hh[0] = hh[0] &+ A + hh[1] = hh[1] &+ B + hh[2] = hh[2] &+ C + hh[3] = hh[3] &+ D + } + var result = [UInt8]() + result.reserveCapacity(hh.count / 4) + + hh.forEach { + let itemLE = $0.littleEndian + let r1 = UInt8(itemLE & 0xff) + let r2 = UInt8((itemLE >> 8) & 0xff) + let r3 = UInt8((itemLE >> 16) & 0xff) + let r4 = UInt8((itemLE >> 24) & 0xff) + result += [r1, r2, r3, r4] + } + return result + } +} diff --git b/Pods/Kingfisher/Sources/Views/AnimatedImageView.swift a/Pods/Kingfisher/Sources/Views/AnimatedImageView.swift new file mode 100644 index 0000000..e32a1f5 --- /dev/null +++ a/Pods/Kingfisher/Sources/Views/AnimatedImageView.swift @@ -0,0 +1,615 @@ +// +// AnimatableImageView.swift +// Kingfisher +// +// Created by bl4ckra1sond3tre on 4/22/16. +// +// The AnimatableImageView, AnimatedFrame and Animator is a modified version of +// some classes from kaishin's Gifu project (https://github.com/kaishin/Gifu) +// +// The MIT License (MIT) +// +// Copyright (c) 2019 Reda Lemeden. +// +// 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. +// +// The name and characters used in the demo of this software are property of their +// respective owners. + +#if !os(watchOS) +#if canImport(UIKit) +import UIKit +import ImageIO + +/// Protocol of `AnimatedImageView`. +public protocol AnimatedImageViewDelegate: AnyObject { + + /// Called after the animatedImageView has finished each animation loop. + /// + /// - Parameters: + /// - imageView: The `AnimatedImageView` that is being animated. + /// - count: The looped count. + func animatedImageView(_ imageView: AnimatedImageView, didPlayAnimationLoops count: UInt) + + /// Called after the `AnimatedImageView` has reached the max repeat count. + /// + /// - Parameter imageView: The `AnimatedImageView` that is being animated. + func animatedImageViewDidFinishAnimating(_ imageView: AnimatedImageView) +} + +extension AnimatedImageViewDelegate { + public func animatedImageView(_ imageView: AnimatedImageView, didPlayAnimationLoops count: UInt) {} + public func animatedImageViewDidFinishAnimating(_ imageView: AnimatedImageView) {} +} + +#if swift(>=4.2) +let KFRunLoopModeCommon = RunLoop.Mode.common +#else +let KFRunLoopModeCommon = RunLoopMode.commonModes +#endif + +/// Represents a subclass of `UIImageView` for displaying animated image. +/// Different from showing animated image in a normal `UIImageView` (which load all frames at one time), +/// `AnimatedImageView` only tries to load several frames (defined by `framePreloadCount`) to reduce memory usage. +/// It provides a tradeoff between memory usage and CPU time. If you have a memory issue when using a normal image +/// view to load GIF data, you could give this class a try. +/// +/// Kingfisher supports setting GIF animated data to either `UIImageView` and `AnimatedImageView` out of box. So +/// it would be fairly easy to switch between them. +open class AnimatedImageView: UIImageView { + + /// Proxy object for preventing a reference cycle between the `CADDisplayLink` and `AnimatedImageView`. + class TargetProxy { + private weak var target: AnimatedImageView? + + init(target: AnimatedImageView) { + self.target = target + } + + @objc func onScreenUpdate() { + target?.updateFrameIfNeeded() + } + } + + /// Enumeration that specifies repeat count of GIF + public enum RepeatCount: Equatable { + case once + case finite(count: UInt) + case infinite + + public static func ==(lhs: RepeatCount, rhs: RepeatCount) -> Bool { + switch (lhs, rhs) { + case let (.finite(l), .finite(r)): + return l == r + case (.once, .once), + (.infinite, .infinite): + return true + case (.once, .finite(let count)), + (.finite(let count), .once): + return count == 1 + case (.once, _), + (.infinite, _), + (.finite, _): + return false + } + } + } + + // MARK: - Public property + /// Whether automatically play the animation when the view become visible. Default is `true`. + public var autoPlayAnimatedImage = true + + /// The count of the frames should be preloaded before shown. + public var framePreloadCount = 10 + + /// Specifies whether the GIF frames should be pre-scaled to the image view's size or not. + /// If the downloaded image is larger than the image view's size, it will help to reduce some memory use. + /// Default is `true`. + public var needsPrescaling = true + + /// Decode the GIF frames in background thread before using. It will decode frames data and do a off-screen + /// rendering to extract pixel information in background. This can reduce the main thread CPU usage. + public var backgroundDecode = true + + /// The animation timer's run loop mode. Default is `RunLoop.Mode.common`. + /// Set this property to `RunLoop.Mode.default` will make the animation pause during UIScrollView scrolling. + public var runLoopMode = KFRunLoopModeCommon { + willSet { + guard runLoopMode != newValue else { return } + stopAnimating() + displayLink.remove(from: .main, forMode: runLoopMode) + displayLink.add(to: .main, forMode: newValue) + startAnimating() + } + } + + /// The repeat count. The animated image will keep animate until it the loop count reaches this value. + /// Setting this value to another one will reset current animation. + /// + /// Default is `.infinite`, which means the animation will last forever. + public var repeatCount = RepeatCount.infinite { + didSet { + if oldValue != repeatCount { + reset() + setNeedsDisplay() + layer.setNeedsDisplay() + } + } + } + + /// Delegate of this `AnimatedImageView` object. See `AnimatedImageViewDelegate` protocol for more. + public weak var delegate: AnimatedImageViewDelegate? + + // MARK: - Private property + /// `Animator` instance that holds the frames of a specific image in memory. + private var animator: Animator? + + // Dispatch queue used for preloading images. + private lazy var preloadQueue: DispatchQueue = { + return DispatchQueue(label: "com.onevcat.Kingfisher.Animator.preloadQueue") + }() + + // A flag to avoid invalidating the displayLink on deinit if it was never created, because displayLink is so lazy. + private var isDisplayLinkInitialized: Bool = false + + // A display link that keeps calling the `updateFrame` method on every screen refresh. + private lazy var displayLink: CADisplayLink = { + isDisplayLinkInitialized = true + let displayLink = CADisplayLink( + target: TargetProxy(target: self), selector: #selector(TargetProxy.onScreenUpdate)) + displayLink.add(to: .main, forMode: runLoopMode) + displayLink.isPaused = true + return displayLink + }() + + // MARK: - Override + override open var image: KFCrossPlatformImage? { + didSet { + if image != oldValue { + reset() + } + setNeedsDisplay() + layer.setNeedsDisplay() + } + } + + deinit { + if isDisplayLinkInitialized { + displayLink.invalidate() + } + } + + override open var isAnimating: Bool { + if isDisplayLinkInitialized { + return !displayLink.isPaused + } else { + return super.isAnimating + } + } + + /// Starts the animation. + override open func startAnimating() { + guard !isAnimating else { return } + if animator?.isReachMaxRepeatCount ?? false { + return + } + + displayLink.isPaused = false + } + + /// Stops the animation. + override open func stopAnimating() { + super.stopAnimating() + if isDisplayLinkInitialized { + displayLink.isPaused = true + } + } + + override open func display(_ layer: CALayer) { + if let currentFrame = animator?.currentFrameImage { + layer.contents = currentFrame.cgImage + } else { + layer.contents = image?.cgImage + } + } + + override open func didMoveToWindow() { + super.didMoveToWindow() + didMove() + } + + override open func didMoveToSuperview() { + super.didMoveToSuperview() + didMove() + } + + // This is for back compatibility that using regular `UIImageView` to show animated image. + override func shouldPreloadAllAnimation() -> Bool { + return false + } + + // Reset the animator. + private func reset() { + animator = nil + if let imageSource = image?.kf.imageSource { + let targetSize = bounds.scaled(UIScreen.main.scale).size + let animator = Animator( + imageSource: imageSource, + contentMode: contentMode, + size: targetSize, + framePreloadCount: framePreloadCount, + repeatCount: repeatCount, + preloadQueue: preloadQueue) + animator.delegate = self + animator.needsPrescaling = needsPrescaling + animator.backgroundDecode = backgroundDecode + animator.prepareFramesAsynchronously() + self.animator = animator + } + didMove() + } + + private func didMove() { + if autoPlayAnimatedImage && animator != nil { + if let _ = superview, let _ = window { + startAnimating() + } else { + stopAnimating() + } + } + } + + /// Update the current frame with the displayLink duration. + private func updateFrameIfNeeded() { + guard let animator = animator else { + return + } + + guard !animator.isFinished else { + stopAnimating() + delegate?.animatedImageViewDidFinishAnimating(self) + return + } + + let duration: CFTimeInterval + + // CA based display link is opt-out from ProMotion by default. + // So the duration and its FPS might not match. + // See [#718](https://github.com/onevcat/Kingfisher/issues/718) + // By setting CADisableMinimumFrameDuration to YES in Info.plist may + // cause the preferredFramesPerSecond being 0 + let preferredFramesPerSecond = displayLink.preferredFramesPerSecond + if preferredFramesPerSecond == 0 { + duration = displayLink.duration + } else { + // Some devices (like iPad Pro 10.5) will have a different FPS. + duration = 1.0 / TimeInterval(preferredFramesPerSecond) + } + + animator.shouldChangeFrame(with: duration) { [weak self] hasNewFrame in + if hasNewFrame { + self?.layer.setNeedsDisplay() + } + } + } +} + +protocol AnimatorDelegate: AnyObject { + func animator(_ animator: AnimatedImageView.Animator, didPlayAnimationLoops count: UInt) +} + +extension AnimatedImageView: AnimatorDelegate { + func animator(_ animator: Animator, didPlayAnimationLoops count: UInt) { + delegate?.animatedImageView(self, didPlayAnimationLoops: count) + } +} + +extension AnimatedImageView { + + // Represents a single frame in a GIF. + struct AnimatedFrame { + + // The image to display for this frame. Its value is nil when the frame is removed from the buffer. + let image: UIImage? + + // The duration that this frame should remain active. + let duration: TimeInterval + + // A placeholder frame with no image assigned. + // Used to replace frames that are no longer needed in the animation. + var placeholderFrame: AnimatedFrame { + return AnimatedFrame(image: nil, duration: duration) + } + + // Whether this frame instance contains an image or not. + var isPlaceholder: Bool { + return image == nil + } + + // Returns a new instance from an optional image. + // + // - parameter image: An optional `UIImage` instance to be assigned to the new frame. + // - returns: An `AnimatedFrame` instance. + func makeAnimatedFrame(image: UIImage?) -> AnimatedFrame { + return AnimatedFrame(image: image, duration: duration) + } + } +} + +extension AnimatedImageView { + + // MARK: - Animator + class Animator { + private let size: CGSize + private let maxFrameCount: Int + private let imageSource: CGImageSource + private let maxRepeatCount: RepeatCount + + private let maxTimeStep: TimeInterval = 1.0 + private let animatedFrames = SafeArray() + private var frameCount = 0 + private var timeSinceLastFrameChange: TimeInterval = 0.0 + private var currentRepeatCount: UInt = 0 + + var isFinished: Bool = false + + var needsPrescaling = true + + var backgroundDecode = true + + weak var delegate: AnimatorDelegate? + + // Total duration of one animation loop + var loopDuration: TimeInterval = 0 + + // Current active frame image + var currentFrameImage: UIImage? { + return frame(at: currentFrameIndex) + } + + // Current active frame duration + var currentFrameDuration: TimeInterval { + return duration(at: currentFrameIndex) + } + + // The index of the current GIF frame. + var currentFrameIndex = 0 { + didSet { + previousFrameIndex = oldValue + } + } + + var previousFrameIndex = 0 { + didSet { + preloadQueue.async { + self.updatePreloadedFrames() + } + } + } + + var isReachMaxRepeatCount: Bool { + switch maxRepeatCount { + case .once: + return currentRepeatCount >= 1 + case .finite(let maxCount): + return currentRepeatCount >= maxCount + case .infinite: + return false + } + } + + var isLastFrame: Bool { + return currentFrameIndex == frameCount - 1 + } + + var preloadingIsNeeded: Bool { + return maxFrameCount < frameCount - 1 + } + + var contentMode = UIView.ContentMode.scaleToFill + + private lazy var preloadQueue: DispatchQueue = { + return DispatchQueue(label: "com.onevcat.Kingfisher.Animator.preloadQueue") + }() + + /// Creates an animator with image source reference. + /// + /// - Parameters: + /// - source: The reference of animated image. + /// - mode: Content mode of the `AnimatedImageView`. + /// - size: Size of the `AnimatedImageView`. + /// - count: Count of frames needed to be preloaded. + /// - repeatCount: The repeat count should this animator uses. + init(imageSource source: CGImageSource, + contentMode mode: UIView.ContentMode, + size: CGSize, + framePreloadCount count: Int, + repeatCount: RepeatCount, + preloadQueue: DispatchQueue) { + self.imageSource = source + self.contentMode = mode + self.size = size + self.maxFrameCount = count + self.maxRepeatCount = repeatCount + self.preloadQueue = preloadQueue + } + + func frame(at index: Int) -> KFCrossPlatformImage? { + return animatedFrames[index]?.image + } + + func duration(at index: Int) -> TimeInterval { + return animatedFrames[index]?.duration ?? .infinity + } + + func prepareFramesAsynchronously() { + frameCount = Int(CGImageSourceGetCount(imageSource)) + animatedFrames.reserveCapacity(frameCount) + preloadQueue.async { [weak self] in + self?.setupAnimatedFrames() + } + } + + func shouldChangeFrame(with duration: CFTimeInterval, handler: (Bool) -> Void) { + incrementTimeSinceLastFrameChange(with: duration) + + if currentFrameDuration > timeSinceLastFrameChange { + handler(false) + } else { + resetTimeSinceLastFrameChange() + incrementCurrentFrameIndex() + handler(true) + } + } + + private func setupAnimatedFrames() { + resetAnimatedFrames() + + var duration: TimeInterval = 0 + + (0.. maxFrameCount { return } + animatedFrames[index] = animatedFrames[index]?.makeAnimatedFrame(image: loadFrame(at: index)) + } + + self.loopDuration = duration + } + + private func resetAnimatedFrames() { + animatedFrames.removeAll() + } + + private func loadFrame(at index: Int) -> UIImage? { + let options: [CFString: Any] = [ + kCGImageSourceCreateThumbnailFromImageIfAbsent: true, + kCGImageSourceCreateThumbnailWithTransform: true, + kCGImageSourceShouldCacheImmediately: true, + kCGImageSourceThumbnailMaxPixelSize: max(size.width, size.height) + ] + + let resize = needsPrescaling && size != .zero + guard let cgImage = CGImageSourceCreateImageAtIndex(imageSource, + index, + resize ? options as CFDictionary : nil) else { + return nil + } + + let image = KFCrossPlatformImage(cgImage: cgImage) + return backgroundDecode ? image.kf.decoded : image + } + + private func updatePreloadedFrames() { + guard preloadingIsNeeded else { + return + } + + animatedFrames[previousFrameIndex] = animatedFrames[previousFrameIndex]?.placeholderFrame + + preloadIndexes(start: currentFrameIndex).forEach { index in + guard let currentAnimatedFrame = animatedFrames[index] else { return } + if !currentAnimatedFrame.isPlaceholder { return } + animatedFrames[index] = currentAnimatedFrame.makeAnimatedFrame(image: loadFrame(at: index)) + } + } + + private func incrementCurrentFrameIndex() { + currentFrameIndex = increment(frameIndex: currentFrameIndex) + if isLastFrame { + currentRepeatCount += 1 + if isReachMaxRepeatCount { + isFinished = true + } + delegate?.animator(self, didPlayAnimationLoops: currentRepeatCount) + } + } + + private func incrementTimeSinceLastFrameChange(with duration: TimeInterval) { + timeSinceLastFrameChange += min(maxTimeStep, duration) + } + + private func resetTimeSinceLastFrameChange() { + timeSinceLastFrameChange -= currentFrameDuration + } + + private func increment(frameIndex: Int, by value: Int = 1) -> Int { + return (frameIndex + value) % frameCount + } + + private func preloadIndexes(start index: Int) -> [Int] { + let nextIndex = increment(frameIndex: index) + let lastIndex = increment(frameIndex: index, by: maxFrameCount) + + if lastIndex >= nextIndex { + return [Int](nextIndex...lastIndex) + } else { + return [Int](nextIndex.. { + private var array: Array = [] + private let lock = NSLock() + + subscript(index: Int) -> Element? { + get { + lock.lock() + defer { lock.unlock() } + return array.indices ~= index ? array[index] : nil + } + + set { + lock.lock() + defer { lock.unlock() } + if let newValue = newValue, array.indices ~= index { + array[index] = newValue + } + } + } + + var count : Int { + lock.lock() + defer { lock.unlock() } + return array.count + } + + func reserveCapacity(_ count: Int) { + lock.lock() + defer { lock.unlock() } + array.reserveCapacity(count) + } + + func append(_ element: Element) { + lock.lock() + defer { lock.unlock() } + array += [element] + } + + func removeAll() { + lock.lock() + defer { lock.unlock() } + array = [] + } +} +#endif +#endif diff --git b/Pods/Kingfisher/Sources/Views/Indicator.swift a/Pods/Kingfisher/Sources/Views/Indicator.swift new file mode 100644 index 0000000..9ec9547 --- /dev/null +++ a/Pods/Kingfisher/Sources/Views/Indicator.swift @@ -0,0 +1,236 @@ +// +// Indicator.swift +// Kingfisher +// +// Created by João D. Moreira on 30/08/16. +// +// Copyright (c) 2019 Wei Wang +// +// 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. + +#if !os(watchOS) + +#if canImport(AppKit) && !targetEnvironment(macCatalyst) +import AppKit +public typealias IndicatorView = NSView +#else +import UIKit +public typealias IndicatorView = UIView +#endif + +/// Represents the activity indicator type which should be added to +/// an image view when an image is being downloaded. +/// +/// - none: No indicator. +/// - activity: Uses the system activity indicator. +/// - image: Uses an image as indicator. GIF is supported. +/// - custom: Uses a custom indicator. The type of associated value should conform to the `Indicator` protocol. +public enum IndicatorType { + /// No indicator. + case none + /// Uses the system activity indicator. + case activity + /// Uses an image as indicator. GIF is supported. + case image(imageData: Data) + /// Uses a custom indicator. The type of associated value should conform to the `Indicator` protocol. + case custom(indicator: Indicator) +} + +/// An indicator type which can be used to show the download task is in progress. +public protocol Indicator { + + /// Called when the indicator should start animating. + func startAnimatingView() + + /// Called when the indicator should stop animating. + func stopAnimatingView() + + /// Center offset of the indicator. Kingfisher will use this value to determine the position of + /// indicator in the super view. + var centerOffset: CGPoint { get } + + /// The indicator view which would be added to the super view. + var view: IndicatorView { get } + + /// The size strategy used when adding the indicator to image view. + /// - Parameter imageView: The super view of indicator. + func sizeStrategy(in imageView: KFCrossPlatformImageView) -> IndicatorSizeStrategy +} + +public enum IndicatorSizeStrategy { + case intrinsicSize + case full + case size(CGSize) +} + +extension Indicator { + + /// Default implementation of `centerOffset` of `Indicator`. The default value is `.zero`, means that there is + /// no offset for the indicator view. + public var centerOffset: CGPoint { return .zero } + + + /// Default implementation of `centerOffset` of `Indicator`. The default value is `.full`, means that the indicator + /// will pin to the same height and width as the image view. + public func sizeStrategy(in imageView: KFCrossPlatformImageView) -> IndicatorSizeStrategy { + return .full + } +} + +// Displays a NSProgressIndicator / UIActivityIndicatorView +final class ActivityIndicator: Indicator { + + #if os(macOS) + private let activityIndicatorView: NSProgressIndicator + #else + private let activityIndicatorView: UIActivityIndicatorView + #endif + private var animatingCount = 0 + + var view: IndicatorView { + return activityIndicatorView + } + + func startAnimatingView() { + if animatingCount == 0 { + #if os(macOS) + activityIndicatorView.startAnimation(nil) + #else + activityIndicatorView.startAnimating() + #endif + activityIndicatorView.isHidden = false + } + animatingCount += 1 + } + + func stopAnimatingView() { + animatingCount = max(animatingCount - 1, 0) + if animatingCount == 0 { + #if os(macOS) + activityIndicatorView.stopAnimation(nil) + #else + activityIndicatorView.stopAnimating() + #endif + activityIndicatorView.isHidden = true + } + } + + func sizeStrategy(in imageView: KFCrossPlatformImageView) -> IndicatorSizeStrategy { + return .intrinsicSize + } + + init() { + #if os(macOS) + activityIndicatorView = NSProgressIndicator(frame: CGRect(x: 0, y: 0, width: 16, height: 16)) + activityIndicatorView.controlSize = .small + activityIndicatorView.style = .spinning + #else + let indicatorStyle: UIActivityIndicatorView.Style + + #if os(tvOS) + if #available(tvOS 13.0, *) { + indicatorStyle = UIActivityIndicatorView.Style.large + } else { + indicatorStyle = UIActivityIndicatorView.Style.white + } + #else + if #available(iOS 13.0, * ) { + indicatorStyle = UIActivityIndicatorView.Style.medium + } else { + indicatorStyle = UIActivityIndicatorView.Style.gray + } + #endif + + #if swift(>=4.2) + activityIndicatorView = UIActivityIndicatorView(style: indicatorStyle) + #else + activityIndicatorView = UIActivityIndicatorView(activityIndicatorStyle: indicatorStyle) + #endif + #endif + } +} + +#if canImport(UIKit) +extension UIActivityIndicatorView.Style { + #if compiler(>=5.1) + #else + static let large = UIActivityIndicatorView.Style.white + #if !os(tvOS) + static let medium = UIActivityIndicatorView.Style.gray + #endif + #endif +} +#endif + +// MARK: - ImageIndicator +// Displays an ImageView. Supports gif +final class ImageIndicator: Indicator { + private let animatedImageIndicatorView: KFCrossPlatformImageView + + var view: IndicatorView { + return animatedImageIndicatorView + } + + init?( + imageData data: Data, + processor: ImageProcessor = DefaultImageProcessor.default, + options: KingfisherParsedOptionsInfo? = nil) + { + var options = options ?? KingfisherParsedOptionsInfo(nil) + // Use normal image view to show animations, so we need to preload all animation data. + if !options.preloadAllAnimationData { + options.preloadAllAnimationData = true + } + + guard let image = processor.process(item: .data(data), options: options) else { + return nil + } + + animatedImageIndicatorView = KFCrossPlatformImageView() + animatedImageIndicatorView.image = image + + #if os(macOS) + // Need for gif to animate on macOS + animatedImageIndicatorView.imageScaling = .scaleNone + animatedImageIndicatorView.canDrawSubviewsIntoLayer = true + #else + animatedImageIndicatorView.contentMode = .center + #endif + } + + func startAnimatingView() { + #if os(macOS) + animatedImageIndicatorView.animates = true + #else + animatedImageIndicatorView.startAnimating() + #endif + animatedImageIndicatorView.isHidden = false + } + + func stopAnimatingView() { + #if os(macOS) + animatedImageIndicatorView.animates = false + #else + animatedImageIndicatorView.stopAnimating() + #endif + animatedImageIndicatorView.isHidden = true + } +} + +#endif diff --git b/Pods/Manifest.lock a/Pods/Manifest.lock new file mode 100644 index 0000000..e326977 --- /dev/null +++ a/Pods/Manifest.lock @@ -0,0 +1,43 @@ +PODS: + - Alamofire (5.1.0) + - 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) + - Kingfisher (5.13.4): + - Kingfisher/Core (= 5.13.4) + - Kingfisher/Core (5.13.4) + - Sniffer (2.0.0) + - SwiftyJSON (5.0.0) + +DEPENDENCIES: + - Alamofire (~> 5.0) + - GoogleMaps + - GooglePlaces + - Kingfisher (~> 5.0) + - Sniffer (~> 2.0) + - SwiftyJSON + +SPEC REPOS: + https://github.com/CocoaPods/Specs.git: + - Alamofire + - GoogleMaps + - GooglePlaces + - Kingfisher + - Sniffer + - SwiftyJSON + +SPEC CHECKSUMS: + Alamofire: 9d5c5f602928e512395b30950c5984eca840093c + GoogleMaps: 7c8d66d70e4e8c300f43a7219d8fdaad7b325a9a + GooglePlaces: d5f70c3e9e427964fdeca1301a665d276ccd8754 + Kingfisher: d2279a7abece3c7f25a80cd2b7f363ca5cf3f44c + Sniffer: 8818aff78371938472e70121eff907f5b0928049 + SwiftyJSON: 36413e04c44ee145039d332b4f4e2d3e8d6c4db7 + +PODFILE CHECKSUM: fd91a353c468991f2507662fbc21f9faeaba13db + +COCOAPODS: 1.9.1 diff --git b/Pods/Pods.xcodeproj/project.pbxproj a/Pods/Pods.xcodeproj/project.pbxproj new file mode 100644 index 0000000..bead236 --- /dev/null +++ a/Pods/Pods.xcodeproj/project.pbxproj @@ -0,0 +1,1770 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 51; + objects = { + +/* Begin PBXAggregateTarget section */ + 0C1885900810601510E0C632060FAF26 /* GooglePlaces */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 2FC2AC47FDB0310285CE777F18CD1532 /* Build configuration list for PBXAggregateTarget "GooglePlaces" */; + buildPhases = ( + ); + dependencies = ( + D42315111F400C0605ACCD6B5CAF2178 /* PBXTargetDependency */, + ); + name = GooglePlaces; + }; + E5B4BBC6DD552AC8943C7E22772FC1D3 /* GoogleMaps */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 46921981F609C5BA3CB72EC4EA41299D /* Build configuration list for PBXAggregateTarget "GoogleMaps" */; + buildPhases = ( + ); + dependencies = ( + ); + name = GoogleMaps; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 003B9FC9E937C82635CF68E9E3576B52 /* URLRequest+Alamofire.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6D423412248122B2EC5D1BEF4BAE786 /* URLRequest+Alamofire.swift */; }; + 00BE32840808D358F6B11753A89B0460 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 217DE48750E9BED49CF941AEEED6AA1A /* Foundation.framework */; }; + 08281777D479CF46F0E0F24753E01A63 /* Request.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A1758AD300D0AC3B8BF823A145B9E31 /* Request.swift */; }; + 09433CD8C4A2D4227DF98AB78E246011 /* Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D27C59A4E37D2B1346892047FF972D7 /* Image.swift */; }; + 09759A3CD181CC07D8E69462D2E9424C /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B8516FA8FBA589C308B6F1182094AB00 /* CFNetwork.framework */; }; + 0FF6A3BBA9E9DAAE2E741513546FC4B6 /* Validation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E761716F68585D0656F71075AFD2D879 /* Validation.swift */; }; + 14672662061590430AF4801C432020A6 /* SessionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76DEC9980D0488EC683B0BF39F35D68C /* SessionDelegate.swift */; }; + 165F498AB8762893E957BCE0D70F4439 /* AFError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 810DCF6DC8913920A5639B6958DC2BA4 /* AFError.swift */; }; + 171CFEBFD0A93CFF6DC40E85EBF3BF8A /* CacheSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A86970FD0971BD8A3D4D109A3FD1C9C1 /* CacheSerializer.swift */; }; + 1942F7D3B7722A5637C3FC0BE44B1E6C /* SwiftyJSON-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = B40643F03209EA5FED6D46DC71095A34 /* SwiftyJSON-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 1A73F39CDDEEADB24723A3F6CBC359AE /* ImageView+Kingfisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 011F00241BDCB076E539D076F86F4BA3 /* ImageView+Kingfisher.swift */; }; + 1CE8BDA6C86345FD31F42F3AECB375EA /* NetworkReachabilityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EAA11F4D50B594FF8200EB11C83FCD7 /* NetworkReachabilityManager.swift */; }; + 2933B39BFCEF204A508F4FFF3188AD27 /* URLSessionConfiguration+Alamofire.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C202B3FA7EC2BC2DCE5DC0567C7FFD6 /* URLSessionConfiguration+Alamofire.swift */; }; + 2BC73DE30871EA6F7D9F600D50F11726 /* Alamofire-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 3EE4A4F53712962B4E0CC7F40A98D946 /* Alamofire-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2BE0DE689754A1603251EB331450718E /* ParameterEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37F4399209EB3507DA47E16F2F71375A /* ParameterEncoder.swift */; }; + 2BF6FBC0A64BF107C5D64C61F1397770 /* SwiftyJSON-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = F3AE2AEA39A52EA12DCE1A130832E83A /* SwiftyJSON-dummy.m */; }; + 2CE3B5EB3E415B5467CC027FC85D9F99 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C4BBB76B4F6F34FAE6EFC88E3170330 /* Result.swift */; }; + 37A2FCD24ED22C8D670188930CE4CA5D /* RequestTaskMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7FE9898595FF5D4BCF03568C020A03F /* RequestTaskMap.swift */; }; + 38ECB82C9A053349A8928D026F437792 /* ImageDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D27F4A813480A65A03CB48FB1FD9EA71 /* ImageDataProvider.swift */; }; + 3B6EE6F748F891E92E1AE63029F3D4DB /* Box.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AD26E03869B14F17E418F1906126DC0 /* Box.swift */; }; + 3C52CE0A60C1071B0A1586B57CC4E2FD /* KingfisherError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9871D5B3B73CACE26A8FA8EEE206EC59 /* KingfisherError.swift */; }; + 408258CCC9DF9A5BD371EBEC8286CD81 /* RetryPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CECB0D20252BA894F4C22807A84AD64 /* RetryPolicy.swift */; }; + 432848A52E6A7EC1F084299032907A29 /* ImageTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB7BEB3CFFFB0F8510F5797B17B1370 /* ImageTransition.swift */; }; + 433815428539A3F818A385B0EFEFB8DC /* DispatchQueue+Alamofire.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47DDF4AAAAA3E603AD143176E8BB5FE5 /* DispatchQueue+Alamofire.swift */; }; + 447FCEA8BB2DFACD60B6B8ECECC027B9 /* CallbackQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 767284A6730E28E58874C28DC3990DA9 /* CallbackQueue.swift */; }; + 452939C15B32AC5981C540FF69990CBE /* Sniffer-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 906EF19442CDF2F2AC75892AE0A5DA0E /* Sniffer-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 464450D037D17ED5EE2322E180A6A9F5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 217DE48750E9BED49CF941AEEED6AA1A /* Foundation.framework */; }; + 4781CFEA0881C7AFD1539C12F074632D /* NSButton+Kingfisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B9E0128BB4445FF693B9E5F807A7FED /* NSButton+Kingfisher.swift */; }; + 4AAB3F2054BD0939C95D961A3BAA43B4 /* ParameterEncoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA54E4C38F18CA402D8E29C0D9632476 /* ParameterEncoding.swift */; }; + 4B1C14991FCEB01E9E05EF8F1FCD5671 /* ImagePrefetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71F8E72286075B513BA8550B539BF40D /* ImagePrefetcher.swift */; }; + 4B288AC81506A29EC002A33979797B27 /* Deprecated.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD5B1F0B15197CA19FDD20E87B788399 /* Deprecated.swift */; }; + 4F767680534EDD5E80E526C3D76989B2 /* Alamofire-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = B0F9EAB1E6E9C621DB7A30F62CD595E8 /* Alamofire-dummy.m */; }; + 526D4A6E90525A42DEEE53B2EAD2C5E8 /* Kingfisher-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = B0BF94B2FD005CDC763DF04C375DFE84 /* Kingfisher-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 543FF1D8EE33F8C1136EB945CDF2A3E2 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B8516FA8FBA589C308B6F1182094AB00 /* CFNetwork.framework */; }; + 552979B096AE1C5676348FADD5CF76A6 /* Pods-ayudapy-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 0ECF9C5C778B0300FAE15C295E5C1EBA /* Pods-ayudapy-dummy.m */; }; + 57B60987700B37698F1C9996AF526AD5 /* ServerTrustEvaluation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26406F2E3BE042B26D0ADF94FF6E231E /* ServerTrustEvaluation.swift */; }; + 5AD5EE0D42721F7E45DAEB5D7D9FA3C4 /* ExtensionHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B5864DD581CB916524EBBF14809FA7C /* ExtensionHelpers.swift */; }; + 5C013EBC514C7B5DB4BB45AED1446B37 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 217DE48750E9BED49CF941AEEED6AA1A /* Foundation.framework */; }; + 5CA745D6F24598B881ED85918BBE6697 /* Indicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AAFEA1F480A677D0706B3A1763FA242 /* Indicator.swift */; }; + 60D4B28D45B4164E21715CF68D1E239B /* Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B30E2722306C75029578F3B3D2B7F02 /* Notifications.swift */; }; + 62AD7D42C96A04828EEA3CC151AA5E38 /* RedirectHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE1A9E37DD8CEAF9BA4D0C1B1D749C63 /* RedirectHandler.swift */; }; + 63C2BA763C69F183DDB32456D7201543 /* Filter.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCF5BAA261CF05C3F5A3BDF4F9D5E97A /* Filter.swift */; }; + 6804393EA55473086BC9AC4FE08F33F5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 217DE48750E9BED49CF941AEEED6AA1A /* Foundation.framework */; }; + 686FF933FB5A33BA7A11CF986A916A06 /* Placeholder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7834BDC3533BFFE7F295F1F6EB8C4536 /* Placeholder.swift */; }; + 6C8EC166C81EB4E72450A002224658DC /* HTTPMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54719D0DEB36D451058BBCEC0CD1E5B5 /* HTTPMethod.swift */; }; + 6E2AE42031E6C611C2AAAD2FE1B12BAD /* Delegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 810405AB483DBEC4BB4AFC65BEC41132 /* Delegate.swift */; }; + 713643837AE7E542FFD9595143E0AA39 /* ImageDrawing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A049E5FFFDD1FB4697F86546EFDF687 /* ImageDrawing.swift */; }; + 717C581962354033F5A82D15D6F676D5 /* WKInterfaceImage+Kingfisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C058C1552B61C32A11C19B437DAA829B /* WKInterfaceImage+Kingfisher.swift */; }; + 74F16EAC797A560DA6EE944A8CEBEC04 /* Kingfisher.h in Headers */ = {isa = PBXBuildFile; fileRef = FBC1B29F06B53A5DCB63D5000721803E /* Kingfisher.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7630B9EA3CC1C43885228418C6CE5790 /* Pods-ayudapy-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = C768BF5315ED7DAB1E0BF0F7FCDF3B16 /* Pods-ayudapy-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 767925B99CC34D22E127120952AC715D /* Runtime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B09F5EA2EB05B4A37A67DA12AF4B66D /* Runtime.swift */; }; + 7B4CE5C84161E2BFE477F42DFD634382 /* SwiftyJSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1130C57D15949D850209BA8C0DA3CD22 /* SwiftyJSON.swift */; }; + 80DBE8FF4DFE41CB54687CAB6A29F1B3 /* StringEncoding+Alamofire.swift in Sources */ = {isa = PBXBuildFile; fileRef = 563CE9994181B97042E18EF3B2CFD0EE /* StringEncoding+Alamofire.swift */; }; + 80F1CE5131DA37F9DF6516F927C78F16 /* KingfisherManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFBAF51E5F422D16770E45331F668EA4 /* KingfisherManager.swift */; }; + 84FB7295031BEDF0073A52A61AF7129A /* Kingfisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9644FB180C11360EF01E790DAD40918 /* Kingfisher.swift */; }; + 85E10F2E2F0044C1ACE78F76395F9077 /* HTTPHeaders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26FCEF29D7093C9B9F1E3D4636E49082 /* HTTPHeaders.swift */; }; + 86CE574CADD6EAE3E385F152C74EA78C /* FormatIndicatedCacheSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBDFCC1439B7773EC62562364C2208A5 /* FormatIndicatedCacheSerializer.swift */; }; + 89A9375A72E68E2D7A0FEE9F09B9DB10 /* AuthenticationChallengeResponsable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F714440C3789B34D24900B6AF99379E /* AuthenticationChallengeResponsable.swift */; }; + 8A50CF3AAB5BD9BF949142DEB8E5050A /* ImageModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A5E6232CD8D42166A7EBD968F793770 /* ImageModifier.swift */; }; + 8EB401DC59ED357AFE37BAF90CD90B3A /* GIFAnimatedImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D8A7C0407BFB5B4857D608C51570B10 /* GIFAnimatedImage.swift */; }; + 90435D953F67AB68BF5EA19B351F0085 /* Sniffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF1C9E983AC21F5582A2707B97C87970 /* Sniffer.swift */; }; + 918548CAAC87215B4960B1F4D2A71769 /* MultipartUpload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 340C215C90F489D89ADCCB43B94E77A5 /* MultipartUpload.swift */; }; + 964030107A87D7C1B25628B02C0E563F /* SessionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CE96C84B2FA277312B1E5F2A93B1AA7 /* SessionDelegate.swift */; }; + 977C462E588D9085193D50156CEF2D5A /* Resource.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB25E8C2ED9B8C198B4FFB08F7C490A7 /* Resource.swift */; }; + 9820B3A65D65CCE7A683E5126C5100A8 /* SizeExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBAEFA1B3E2253FD22055C1B419C2D1D /* SizeExtensions.swift */; }; + 9A074410CBBCE8C6BE663C80F235DC89 /* ImageFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75E60D1481168119290056C7E021B02D /* ImageFormat.swift */; }; + 9DC4A6FCE96D312A9EE0E5D51B4D4A80 /* URLEncodedFormEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 269223D6670216DCFD8C0D6183BA1F5E /* URLEncodedFormEncoder.swift */; }; + A19C9C58F87FAF5DB267AE064023DF30 /* Kingfisher-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = AA0CC5B2F570F020CD695D0305A50EB2 /* Kingfisher-dummy.m */; }; + AA8023C0CFE561DB8B8D92232C6125B1 /* AlamofireExtended.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326EF0715AC1400A86EE19C27FDAF115 /* AlamofireExtended.swift */; }; + AAE822224E085A6362FA7122C0605078 /* RedirectHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED13247345B4917E5150511E465C2356 /* RedirectHandler.swift */; }; + AB8B45111A4E958CF8D633D8C6231689 /* ImageProgressive.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABD08D50EA2E9A0FBBD08FDE891769F4 /* ImageProgressive.swift */; }; + B2047F988C61D368E76A806D5227329E /* ResponseSerialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 157D5E1C009B5F6B52D1DBD5A70E8999 /* ResponseSerialization.swift */; }; + B2D8F9D8B91DE8C26D5C2355F1FB941A /* KingfisherOptionsInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17421F96C95BE20FF3D90CDA121CA3DF /* KingfisherOptionsInfo.swift */; }; + B46D7720DA41A19FCE46F578979A2036 /* BodyDeserializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4675821A24A9DFC15098FEF0706B052 /* BodyDeserializer.swift */; }; + C0FFA57E39D99B6AC85FEF60CA966EE4 /* Source.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23CE0952A939BE0C9F684F904D12636D /* Source.swift */; }; + C2CDC860BA74B75C0504A55B02E072F6 /* AnimatedImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C9C9B80C95B7E73D6478E3078D25151 /* AnimatedImageView.swift */; }; + C409433F2328566B78FA477EB56CA377 /* DiskStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D0D4364CA2D471EF192E2744714BFE9 /* DiskStorage.swift */; }; + C58AC3B6AC4597752277700E774B0927 /* ImageDownloaderDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9743FB12DDCBD5E6496FBF020D12DD03 /* ImageDownloaderDelegate.swift */; }; + CB436452F5C36FC049E26A8C0475965C /* Result+Alamofire.swift in Sources */ = {isa = PBXBuildFile; fileRef = 150A4246534DC802B6B0513E2954C48E /* Result+Alamofire.swift */; }; + CD698D8D49A0DB3669D8D58B4571268E /* ImageProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AEB6980AA83A48067DE37DA0656B32D /* ImageProcessor.swift */; }; + CDCDB8A355FE70F12855CC8A3657E739 /* URLConvertible+URLRequestConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B58FF25491A5326639DE7F4CC37DAB /* URLConvertible+URLRequestConvertible.swift */; }; + CDD42E25D201469CAFEA9E6181525FA8 /* RequestModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEEBCB63AFE94FDC6D7FAA8181A2AEE7 /* RequestModifier.swift */; }; + D0508A5E7A4DF1129A99994EFC3AACEE /* SessionDataTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AA78468BEE0F0A00BE13A62876A076C /* SessionDataTask.swift */; }; + D0F6D13D0DC583351F5F6BCA1CB04ABD /* Protected.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEF84C89FE032DE3D06F20C336D0EFC1 /* Protected.swift */; }; + D1B4079BB427AA2EA1955C02EA34E7EC /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE0D57C287FA598F9CEEF14F6912716 /* ImageCache.swift */; }; + D3DB9F1DF95D7732960C4A55972C115E /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2ACE13A318E38C13B001C3F8B9C12297 /* Accelerate.framework */; }; + D95DC2E89078C034EBE2209EE67C355F /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D1FC207AB3C95A8C4122CF5AEEC8DEA5 /* UIKit.framework */; }; + DA07C0CDF1B0FBC959A4406DE142A081 /* EventMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C98A36BAD82E5EEAEEDD3483E752B02 /* EventMonitor.swift */; }; + DA6512AAAFD92EFA9860ED84C3A308B9 /* Alamofire.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D56382B4338EBA18A58567B5C70515F /* Alamofire.swift */; }; + DDA3EF3B4A1D99E1E4AB016CFC1B544D /* RequestInterceptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17543249A8340CB5CB919EE4FC6ACE96 /* RequestInterceptor.swift */; }; + DEBC2B060652397E0EF4A19A94EB636F /* Sniffer-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 3C1FF98EC80A2CD4E8F2D3FB0F18C453 /* Sniffer-dummy.m */; }; + DF22F9B2C3640931D82769C9E4707929 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 217DE48750E9BED49CF941AEEED6AA1A /* Foundation.framework */; }; + E4E727DD13DA225D574D9880D9CB7348 /* CachedResponseHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46AC5CDC2DD35FF05C9D4E8D8D90F9FB /* CachedResponseHandler.swift */; }; + E618A89E126F408A59FE3167A6CBB510 /* OperationQueue+Alamofire.swift in Sources */ = {isa = PBXBuildFile; fileRef = 188BAFE30769AF909D8213831CBB4C11 /* OperationQueue+Alamofire.swift */; }; + E7D7D875688EDFBCB8F4C2C1F4F5B5E5 /* String+MD5.swift in Sources */ = {isa = PBXBuildFile; fileRef = D99A47CF4126AB5BB335D8E441E8F29C /* String+MD5.swift */; }; + EE1F347816E69DB6AB8513488D9D08FF /* Session.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CDE5B39C4571176D1961DE019AA988C /* Session.swift */; }; + EE5FCC4133C3E712B85676510F2A3D7B /* Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07489DCF8EFA830A3D3F8B3BA59574B5 /* Response.swift */; }; + F0709DCF413ACE7F337DFAC0BA3B874B /* MemoryStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 199973D0DDE1935F552BB39AD2A78379 /* MemoryStorage.swift */; }; + F0E89F7017249BCB3B2C1320F9413B2C /* ImageDataProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FAAD7C2886A5CC33D11F6AEDB9F479C /* ImageDataProcessor.swift */; }; + F5E436F5E8C598430FABC8787E517F96 /* MultipartFormData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2008FE80DBC7AF7CA54C0446ACD4ECF2 /* MultipartFormData.swift */; }; + F66ED581D2EBD170ABAD4EC0206B6C5C /* UIButton+Kingfisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F133DA10DF6A93776C2FA276034B409 /* UIButton+Kingfisher.swift */; }; + F780F31334A5B069C756121AE82F008B /* Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8059F2695877BA3B709149C7310B22DF /* Storage.swift */; }; + FEAB0E502EA1A6C4F758823B3B4BDFFF /* ImageDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0C28C049878F9901D465282CA3FED8F /* ImageDownloader.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 2570F9CEEA81410AA3365BB1F17BF90B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = E5B4BBC6DD552AC8943C7E22772FC1D3; + remoteInfo = GoogleMaps; + }; + 47C7C484782DFCDC94CF15D32FEB70FD /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 42D1982FACF80BE1722E66C9D5705499; + remoteInfo = Sniffer; + }; + 5D1C1BE9278305DECBE5D81192904196 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = EAAA1AD3A8A1B59AB91319EE40752C6D; + remoteInfo = Alamofire; + }; + 6EB1C2DD30E6564FC3135C9E8002D8F7 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = E5B4BBC6DD552AC8943C7E22772FC1D3; + remoteInfo = GoogleMaps; + }; + 9C46CF39F576C9ECCC4C46B4C3317E60 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 0C1885900810601510E0C632060FAF26; + remoteInfo = GooglePlaces; + }; + 9DCEEA4B64E6AEBF1D7992106422B271 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = E8022D22FAA6690B5E1C379C1BCE1491; + remoteInfo = Kingfisher; + }; + C7439EE00C9A9E7219F03BF66125120F /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D118A6A04828FD3CDA8640CD2B6796D2; + remoteInfo = SwiftyJSON; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 011F00241BDCB076E539D076F86F4BA3 /* ImageView+Kingfisher.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "ImageView+Kingfisher.swift"; path = "Sources/Extensions/ImageView+Kingfisher.swift"; sourceTree = ""; }; + 02BAB62D95AD76D9D4EC0702BDF1843C /* GoogleMapsCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GoogleMapsCore.framework; path = Maps/Frameworks/GoogleMapsCore.framework; sourceTree = ""; }; + 07489DCF8EFA830A3D3F8B3BA59574B5 /* Response.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Response.swift; path = Source/Response.swift; sourceTree = ""; }; + 0B5864DD581CB916524EBBF14809FA7C /* ExtensionHelpers.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExtensionHelpers.swift; path = Sources/Utility/ExtensionHelpers.swift; sourceTree = ""; }; + 0B85DF0CEFC2DC3A7E694AB608890B2A /* Pods-ayudapy-resources.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-ayudapy-resources.sh"; sourceTree = ""; }; + 0CB7BEB3CFFFB0F8510F5797B17B1370 /* ImageTransition.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageTransition.swift; path = Sources/Image/ImageTransition.swift; sourceTree = ""; }; + 0ECF9C5C778B0300FAE15C295E5C1EBA /* Pods-ayudapy-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-ayudapy-dummy.m"; sourceTree = ""; }; + 0EE0D57C287FA598F9CEEF14F6912716 /* ImageCache.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageCache.swift; path = Sources/Cache/ImageCache.swift; sourceTree = ""; }; + 1130C57D15949D850209BA8C0DA3CD22 /* SwiftyJSON.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SwiftyJSON.swift; path = Source/SwiftyJSON/SwiftyJSON.swift; sourceTree = ""; }; + 134B105AA2D7AE3052699AC079A16A36 /* Kingfisher-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Kingfisher-prefix.pch"; sourceTree = ""; }; + 150A4246534DC802B6B0513E2954C48E /* Result+Alamofire.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Result+Alamofire.swift"; path = "Source/Result+Alamofire.swift"; sourceTree = ""; }; + 157D5E1C009B5F6B52D1DBD5A70E8999 /* ResponseSerialization.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ResponseSerialization.swift; path = Source/ResponseSerialization.swift; sourceTree = ""; }; + 17421F96C95BE20FF3D90CDA121CA3DF /* KingfisherOptionsInfo.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = KingfisherOptionsInfo.swift; path = Sources/General/KingfisherOptionsInfo.swift; sourceTree = ""; }; + 17543249A8340CB5CB919EE4FC6ACE96 /* RequestInterceptor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RequestInterceptor.swift; path = Source/RequestInterceptor.swift; sourceTree = ""; }; + 188BAFE30769AF909D8213831CBB4C11 /* OperationQueue+Alamofire.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "OperationQueue+Alamofire.swift"; path = "Source/OperationQueue+Alamofire.swift"; sourceTree = ""; }; + 199973D0DDE1935F552BB39AD2A78379 /* MemoryStorage.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = MemoryStorage.swift; path = Sources/Cache/MemoryStorage.swift; sourceTree = ""; }; + 19E26E87DC7D99B9937DF761149A7A7B /* Alamofire-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Alamofire-Info.plist"; sourceTree = ""; }; + 1A049E5FFFDD1FB4697F86546EFDF687 /* ImageDrawing.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageDrawing.swift; path = Sources/Image/ImageDrawing.swift; sourceTree = ""; }; + 1CDE5B39C4571176D1961DE019AA988C /* Session.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Session.swift; path = Source/Session.swift; sourceTree = ""; }; + 1EFA33EDF9AC66391746D4232E02F9CB /* GooglePlaces.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = GooglePlaces.release.xcconfig; sourceTree = ""; }; + 2008FE80DBC7AF7CA54C0446ACD4ECF2 /* MultipartFormData.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = MultipartFormData.swift; path = Source/MultipartFormData.swift; sourceTree = ""; }; + 212D95CBD8FE2141ED259910E0CE9141 /* SwiftyJSON.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = SwiftyJSON.release.xcconfig; sourceTree = ""; }; + 21433EDB6D492589F7F0A412256F5019 /* Alamofire.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Alamofire.release.xcconfig; sourceTree = ""; }; + 217DE48750E9BED49CF941AEEED6AA1A /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.2.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; + 23CE0952A939BE0C9F684F904D12636D /* Source.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Source.swift; path = Sources/General/ImageSource/Source.swift; sourceTree = ""; }; + 26406F2E3BE042B26D0ADF94FF6E231E /* ServerTrustEvaluation.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ServerTrustEvaluation.swift; path = Source/ServerTrustEvaluation.swift; sourceTree = ""; }; + 269223D6670216DCFD8C0D6183BA1F5E /* URLEncodedFormEncoder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = URLEncodedFormEncoder.swift; path = Source/URLEncodedFormEncoder.swift; sourceTree = ""; }; + 26FCEF29D7093C9B9F1E3D4636E49082 /* HTTPHeaders.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = HTTPHeaders.swift; path = Source/HTTPHeaders.swift; sourceTree = ""; }; + 294A11A76A9E4DF9B0E7540F98D439AB /* Alamofire-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Alamofire-prefix.pch"; sourceTree = ""; }; + 2AA23092FB4B8C5EE6B76EA2D272A9D3 /* Sniffer-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Sniffer-Info.plist"; sourceTree = ""; }; + 2AA78468BEE0F0A00BE13A62876A076C /* SessionDataTask.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SessionDataTask.swift; path = Sources/Networking/SessionDataTask.swift; sourceTree = ""; }; + 2ACE13A318E38C13B001C3F8B9C12297 /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.2.sdk/System/Library/Frameworks/Accelerate.framework; sourceTree = DEVELOPER_DIR; }; + 2B9E0128BB4445FF693B9E5F807A7FED /* NSButton+Kingfisher.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "NSButton+Kingfisher.swift"; path = "Sources/Extensions/NSButton+Kingfisher.swift"; sourceTree = ""; }; + 2D56382B4338EBA18A58567B5C70515F /* Alamofire.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Alamofire.swift; path = Source/Alamofire.swift; sourceTree = ""; }; + 302707EAC13CCD6B67F523285D9CF4DE /* SwiftyJSON-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "SwiftyJSON-Info.plist"; sourceTree = ""; }; + 32293F983FAE4B87B50267D3782CE3C6 /* GooglePlaces.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GooglePlaces.framework; path = Frameworks/GooglePlaces.framework; sourceTree = ""; }; + 326EF0715AC1400A86EE19C27FDAF115 /* AlamofireExtended.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AlamofireExtended.swift; path = Source/AlamofireExtended.swift; sourceTree = ""; }; + 340C215C90F489D89ADCCB43B94E77A5 /* MultipartUpload.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = MultipartUpload.swift; path = Source/MultipartUpload.swift; sourceTree = ""; }; + 37F4399209EB3507DA47E16F2F71375A /* ParameterEncoder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ParameterEncoder.swift; path = Source/ParameterEncoder.swift; sourceTree = ""; }; + 3B09F5EA2EB05B4A37A67DA12AF4B66D /* Runtime.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Runtime.swift; path = Sources/Utility/Runtime.swift; sourceTree = ""; }; + 3B30E2722306C75029578F3B3D2B7F02 /* Notifications.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Notifications.swift; path = Source/Notifications.swift; sourceTree = ""; }; + 3C1FF98EC80A2CD4E8F2D3FB0F18C453 /* Sniffer-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Sniffer-dummy.m"; sourceTree = ""; }; + 3C9C9B80C95B7E73D6478E3078D25151 /* AnimatedImageView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AnimatedImageView.swift; path = Sources/Views/AnimatedImageView.swift; sourceTree = ""; }; + 3EE4A4F53712962B4E0CC7F40A98D946 /* Alamofire-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Alamofire-umbrella.h"; sourceTree = ""; }; + 3F8A1F92B6938FC3DCDC32EE66382D88 /* Pods-ayudapy.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-ayudapy.debug.xcconfig"; sourceTree = ""; }; + 46AC5CDC2DD35FF05C9D4E8D8D90F9FB /* CachedResponseHandler.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CachedResponseHandler.swift; path = Source/CachedResponseHandler.swift; sourceTree = ""; }; + 47DDF4AAAAA3E603AD143176E8BB5FE5 /* DispatchQueue+Alamofire.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "DispatchQueue+Alamofire.swift"; path = "Source/DispatchQueue+Alamofire.swift"; sourceTree = ""; }; + 4AD26E03869B14F17E418F1906126DC0 /* Box.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Box.swift; path = Sources/Utility/Box.swift; sourceTree = ""; }; + 4EAA11F4D50B594FF8200EB11C83FCD7 /* NetworkReachabilityManager.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NetworkReachabilityManager.swift; path = Source/NetworkReachabilityManager.swift; sourceTree = ""; }; + 4FFA9FCC94E88E8646FD27E323E08F8C /* Sniffer-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Sniffer-prefix.pch"; sourceTree = ""; }; + 5068234F9EF54AAF9E56B52782C9E0EE /* Alamofire.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Alamofire.debug.xcconfig; sourceTree = ""; }; + 50B58FF25491A5326639DE7F4CC37DAB /* URLConvertible+URLRequestConvertible.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "URLConvertible+URLRequestConvertible.swift"; path = "Source/URLConvertible+URLRequestConvertible.swift"; sourceTree = ""; }; + 54719D0DEB36D451058BBCEC0CD1E5B5 /* HTTPMethod.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = HTTPMethod.swift; path = Source/HTTPMethod.swift; sourceTree = ""; }; + 563CE9994181B97042E18EF3B2CFD0EE /* StringEncoding+Alamofire.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "StringEncoding+Alamofire.swift"; path = "Source/StringEncoding+Alamofire.swift"; sourceTree = ""; }; + 59BC9381CB0680F53FD9B43FA215A4C7 /* Kingfisher.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = Kingfisher.modulemap; sourceTree = ""; }; + 5A1758AD300D0AC3B8BF823A145B9E31 /* Request.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Request.swift; path = Source/Request.swift; sourceTree = ""; }; + 5D0D4364CA2D471EF192E2744714BFE9 /* DiskStorage.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DiskStorage.swift; path = Sources/Cache/DiskStorage.swift; sourceTree = ""; }; + 5D797E9A5C5782CE845840781FA1CC81 /* Alamofire.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Alamofire.framework; path = Alamofire.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 6A5E6232CD8D42166A7EBD968F793770 /* ImageModifier.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageModifier.swift; path = Sources/Networking/ImageModifier.swift; sourceTree = ""; }; + 6AEB6980AA83A48067DE37DA0656B32D /* ImageProcessor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageProcessor.swift; path = Sources/Image/ImageProcessor.swift; sourceTree = ""; }; + 6D8A7C0407BFB5B4857D608C51570B10 /* GIFAnimatedImage.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = GIFAnimatedImage.swift; path = Sources/Image/GIFAnimatedImage.swift; sourceTree = ""; }; + 71F8E72286075B513BA8550B539BF40D /* ImagePrefetcher.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImagePrefetcher.swift; path = Sources/Networking/ImagePrefetcher.swift; sourceTree = ""; }; + 75E60D1481168119290056C7E021B02D /* ImageFormat.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageFormat.swift; path = Sources/Image/ImageFormat.swift; sourceTree = ""; }; + 767284A6730E28E58874C28DC3990DA9 /* CallbackQueue.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CallbackQueue.swift; path = Sources/Utility/CallbackQueue.swift; sourceTree = ""; }; + 76DEC9980D0488EC683B0BF39F35D68C /* SessionDelegate.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SessionDelegate.swift; path = Sources/Networking/SessionDelegate.swift; sourceTree = ""; }; + 7834BDC3533BFFE7F295F1F6EB8C4536 /* Placeholder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Placeholder.swift; path = Sources/Image/Placeholder.swift; sourceTree = ""; }; + 7A14E5C32E63DF3D82B838FA501D19DD /* GoogleMapsBase.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GoogleMapsBase.framework; path = Base/Frameworks/GoogleMapsBase.framework; sourceTree = ""; }; + 7AAFEA1F480A677D0706B3A1763FA242 /* Indicator.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Indicator.swift; path = Sources/Views/Indicator.swift; sourceTree = ""; }; + 7C98A36BAD82E5EEAEEDD3483E752B02 /* EventMonitor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = EventMonitor.swift; path = Source/EventMonitor.swift; sourceTree = ""; }; + 7CD6DCA105E025B6D00C2D32AB231269 /* Pods-ayudapy-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-ayudapy-acknowledgements.markdown"; sourceTree = ""; }; + 7CE96C84B2FA277312B1E5F2A93B1AA7 /* SessionDelegate.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SessionDelegate.swift; path = Source/SessionDelegate.swift; sourceTree = ""; }; + 7D27C59A4E37D2B1346892047FF972D7 /* Image.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Image.swift; path = Sources/Image/Image.swift; sourceTree = ""; }; + 8059F2695877BA3B709149C7310B22DF /* Storage.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Storage.swift; path = Sources/Cache/Storage.swift; sourceTree = ""; }; + 810405AB483DBEC4BB4AFC65BEC41132 /* Delegate.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Delegate.swift; path = Sources/Utility/Delegate.swift; sourceTree = ""; }; + 810DCF6DC8913920A5639B6958DC2BA4 /* AFError.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AFError.swift; path = Source/AFError.swift; sourceTree = ""; }; + 8C202B3FA7EC2BC2DCE5DC0567C7FFD6 /* URLSessionConfiguration+Alamofire.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "URLSessionConfiguration+Alamofire.swift"; path = "Source/URLSessionConfiguration+Alamofire.swift"; sourceTree = ""; }; + 8C4BBB76B4F6F34FAE6EFC88E3170330 /* Result.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Result.swift; path = Sources/Utility/Result.swift; sourceTree = ""; }; + 8D3CBDCF91193A4F1753EACDE07B951A /* Pods_ayudapy.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Pods_ayudapy.framework; path = "Pods-ayudapy.framework"; sourceTree = BUILT_PRODUCTS_DIR; }; + 8F133DA10DF6A93776C2FA276034B409 /* UIButton+Kingfisher.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "UIButton+Kingfisher.swift"; path = "Sources/Extensions/UIButton+Kingfisher.swift"; sourceTree = ""; }; + 906EF19442CDF2F2AC75892AE0A5DA0E /* Sniffer-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Sniffer-umbrella.h"; sourceTree = ""; }; + 9743FB12DDCBD5E6496FBF020D12DD03 /* ImageDownloaderDelegate.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageDownloaderDelegate.swift; path = Sources/Networking/ImageDownloaderDelegate.swift; sourceTree = ""; }; + 9871D5B3B73CACE26A8FA8EEE206EC59 /* KingfisherError.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = KingfisherError.swift; path = Sources/General/KingfisherError.swift; sourceTree = ""; }; + 9CECB0D20252BA894F4C22807A84AD64 /* RetryPolicy.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RetryPolicy.swift; path = Source/RetryPolicy.swift; sourceTree = ""; }; + 9D940727FF8FB9C785EB98E56350EF41 /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; lastKnownFileType = text; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; + 9E1215C916B0D94C4FAF563720F11FA2 /* SwiftyJSON.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = SwiftyJSON.modulemap; sourceTree = ""; }; + 9F714440C3789B34D24900B6AF99379E /* AuthenticationChallengeResponsable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AuthenticationChallengeResponsable.swift; path = Sources/Networking/AuthenticationChallengeResponsable.swift; sourceTree = ""; }; + 9FAAD7C2886A5CC33D11F6AEDB9F479C /* ImageDataProcessor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageDataProcessor.swift; path = Sources/Networking/ImageDataProcessor.swift; sourceTree = ""; }; + A1ADAD80982C662C9951F49E0F39002F /* Sniffer.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Sniffer.framework; path = Sniffer.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A37ED2407A545050DF667598E383CA85 /* SwiftyJSON-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "SwiftyJSON-prefix.pch"; sourceTree = ""; }; + A6D423412248122B2EC5D1BEF4BAE786 /* URLRequest+Alamofire.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "URLRequest+Alamofire.swift"; path = "Source/URLRequest+Alamofire.swift"; sourceTree = ""; }; + A86970FD0971BD8A3D4D109A3FD1C9C1 /* CacheSerializer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CacheSerializer.swift; path = Sources/Cache/CacheSerializer.swift; sourceTree = ""; }; + AA0CC5B2F570F020CD695D0305A50EB2 /* Kingfisher-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Kingfisher-dummy.m"; sourceTree = ""; }; + AB25E8C2ED9B8C198B4FFB08F7C490A7 /* Resource.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Resource.swift; path = Sources/General/ImageSource/Resource.swift; sourceTree = ""; }; + ABD08D50EA2E9A0FBBD08FDE891769F4 /* ImageProgressive.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageProgressive.swift; path = Sources/Image/ImageProgressive.swift; sourceTree = ""; }; + AEA94306EA9521A16FB414E5E3971C58 /* GoogleMaps.bundle */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = "wrapper.plug-in"; name = GoogleMaps.bundle; path = Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle; sourceTree = ""; }; + AEF84C89FE032DE3D06F20C336D0EFC1 /* Protected.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Protected.swift; path = Source/Protected.swift; sourceTree = ""; }; + B0BF94B2FD005CDC763DF04C375DFE84 /* Kingfisher-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Kingfisher-umbrella.h"; sourceTree = ""; }; + B0F9EAB1E6E9C621DB7A30F62CD595E8 /* Alamofire-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Alamofire-dummy.m"; sourceTree = ""; }; + B40643F03209EA5FED6D46DC71095A34 /* SwiftyJSON-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "SwiftyJSON-umbrella.h"; sourceTree = ""; }; + B4675821A24A9DFC15098FEF0706B052 /* BodyDeserializer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BodyDeserializer.swift; path = Sniffer/Classes/BodyDeserializer.swift; sourceTree = ""; }; + B8516FA8FBA589C308B6F1182094AB00 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.2.sdk/System/Library/Frameworks/CFNetwork.framework; sourceTree = DEVELOPER_DIR; }; + BB0EA96AF9B7CD694CEE44B915DD741C /* Pods-ayudapy-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-ayudapy-frameworks.sh"; sourceTree = ""; }; + BC85BC1F20B72982E08CFB847993ED6C /* Pods-ayudapy-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-ayudapy-acknowledgements.plist"; sourceTree = ""; }; + BD5B1F0B15197CA19FDD20E87B788399 /* Deprecated.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Deprecated.swift; path = Sources/General/Deprecated.swift; sourceTree = ""; }; + C01CF44ABFCA34429CFD6E16A7D9C230 /* Sniffer.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = Sniffer.modulemap; sourceTree = ""; }; + C058C1552B61C32A11C19B437DAA829B /* WKInterfaceImage+Kingfisher.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "WKInterfaceImage+Kingfisher.swift"; path = "Sources/Extensions/WKInterfaceImage+Kingfisher.swift"; sourceTree = ""; }; + C0C28C049878F9901D465282CA3FED8F /* ImageDownloader.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageDownloader.swift; path = Sources/Networking/ImageDownloader.swift; sourceTree = ""; }; + C193366879E7D9C7C728BFF6BFFAF16B /* Sniffer.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Sniffer.release.xcconfig; sourceTree = ""; }; + C3F44C782D64D7EB20B61CE3844EBFAD /* Kingfisher.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Kingfisher.framework; path = Kingfisher.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + C41DEC7FBFCE8EDE14BEFBAB8040DCD1 /* Sniffer.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Sniffer.debug.xcconfig; sourceTree = ""; }; + C768BF5315ED7DAB1E0BF0F7FCDF3B16 /* Pods-ayudapy-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-ayudapy-umbrella.h"; sourceTree = ""; }; + C8CD140BF4C274EB7AFFF56C9729C67F /* Kingfisher.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Kingfisher.release.xcconfig; sourceTree = ""; }; + CAF2C0EA15FE5EF3384B7BFF1C067360 /* Kingfisher.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Kingfisher.debug.xcconfig; sourceTree = ""; }; + CCF5BAA261CF05C3F5A3BDF4F9D5E97A /* Filter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Filter.swift; path = Sources/Image/Filter.swift; sourceTree = ""; }; + CF1C9E983AC21F5582A2707B97C87970 /* Sniffer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Sniffer.swift; path = Sniffer/Classes/Sniffer.swift; sourceTree = ""; }; + D0B136D0F6E8B67B2E7012C567296D64 /* SwiftyJSON.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = SwiftyJSON.debug.xcconfig; sourceTree = ""; }; + D15B7919D173FD6DBA2B0E5A05B3098A /* GooglePlaces.bundle */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = "wrapper.plug-in"; name = GooglePlaces.bundle; path = Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle; sourceTree = ""; }; + D1FC207AB3C95A8C4122CF5AEEC8DEA5 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.2.sdk/System/Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; }; + D27F4A813480A65A03CB48FB1FD9EA71 /* ImageDataProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageDataProvider.swift; path = Sources/General/ImageSource/ImageDataProvider.swift; sourceTree = ""; }; + D5740CA6B8C58D4346D20B199BEAB969 /* Pods-ayudapy.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = "Pods-ayudapy.modulemap"; sourceTree = ""; }; + D575BEB1109BF564FCF18F1D5BAF1853 /* GooglePlaces.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = GooglePlaces.debug.xcconfig; sourceTree = ""; }; + D9644FB180C11360EF01E790DAD40918 /* Kingfisher.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Kingfisher.swift; path = Sources/General/Kingfisher.swift; sourceTree = ""; }; + D99A47CF4126AB5BB335D8E441E8F29C /* String+MD5.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "String+MD5.swift"; path = "Sources/Utility/String+MD5.swift"; sourceTree = ""; }; + DA331CDC1AADC6340DCCC735DBE0CE92 /* GoogleMaps.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = GoogleMaps.release.xcconfig; sourceTree = ""; }; + E23C076BA70925415F490FEDB215DA92 /* SwiftyJSON.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = SwiftyJSON.framework; path = SwiftyJSON.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + E3D29C5E402326372843BDF4B50D2491 /* GoogleMaps.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = GoogleMaps.debug.xcconfig; sourceTree = ""; }; + E5FE74B2B928A397905AEEC537ABEE87 /* Kingfisher-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Kingfisher-Info.plist"; sourceTree = ""; }; + E761716F68585D0656F71075AFD2D879 /* Validation.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Validation.swift; path = Source/Validation.swift; sourceTree = ""; }; + EBAEFA1B3E2253FD22055C1B419C2D1D /* SizeExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SizeExtensions.swift; path = Sources/Utility/SizeExtensions.swift; sourceTree = ""; }; + EBDFCC1439B7773EC62562364C2208A5 /* FormatIndicatedCacheSerializer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = FormatIndicatedCacheSerializer.swift; path = Sources/Cache/FormatIndicatedCacheSerializer.swift; sourceTree = ""; }; + ED13247345B4917E5150511E465C2356 /* RedirectHandler.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RedirectHandler.swift; path = Source/RedirectHandler.swift; sourceTree = ""; }; + EE1A9E37DD8CEAF9BA4D0C1B1D749C63 /* RedirectHandler.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RedirectHandler.swift; path = Sources/Networking/RedirectHandler.swift; sourceTree = ""; }; + EEEBCB63AFE94FDC6D7FAA8181A2AEE7 /* RequestModifier.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RequestModifier.swift; path = Sources/Networking/RequestModifier.swift; sourceTree = ""; }; + EFBAF51E5F422D16770E45331F668EA4 /* KingfisherManager.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = KingfisherManager.swift; path = Sources/General/KingfisherManager.swift; sourceTree = ""; }; + F0EAEAF9C791D43605D0806300BA5BBB /* GoogleMaps.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GoogleMaps.framework; path = Maps/Frameworks/GoogleMaps.framework; sourceTree = ""; }; + F3AE2AEA39A52EA12DCE1A130832E83A /* SwiftyJSON-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "SwiftyJSON-dummy.m"; sourceTree = ""; }; + F442A3B41A88AE6EB8303270EA5E0959 /* Pods-ayudapy.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-ayudapy.release.xcconfig"; sourceTree = ""; }; + F60F4A61B2C1BECCA82B06329D51FE12 /* Pods-ayudapy-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-ayudapy-Info.plist"; sourceTree = ""; }; + F7FE9898595FF5D4BCF03568C020A03F /* RequestTaskMap.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RequestTaskMap.swift; path = Source/RequestTaskMap.swift; sourceTree = ""; }; + FA54E4C38F18CA402D8E29C0D9632476 /* ParameterEncoding.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ParameterEncoding.swift; path = Source/ParameterEncoding.swift; sourceTree = ""; }; + FBC1B29F06B53A5DCB63D5000721803E /* Kingfisher.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = Kingfisher.h; path = Sources/Kingfisher.h; sourceTree = ""; }; + FDE47C3620E672E8782B6C20D9B5F185 /* Alamofire.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = Alamofire.modulemap; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 29B52F9C761C5400AF4A6DD63A5FA6A2 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 5C013EBC514C7B5DB4BB45AED1446B37 /* Foundation.framework in Frameworks */, + D95DC2E89078C034EBE2209EE67C355F /* UIKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 61AF6B13CCC2E38FDA0B9BAB05581FD5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + D3DB9F1DF95D7732960C4A55972C115E /* Accelerate.framework in Frameworks */, + 09759A3CD181CC07D8E69462D2E9424C /* CFNetwork.framework in Frameworks */, + 6804393EA55473086BC9AC4FE08F33F5 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9C1C2D14882937DB0594D5E1A90588BE /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 543FF1D8EE33F8C1136EB945CDF2A3E2 /* CFNetwork.framework in Frameworks */, + DF22F9B2C3640931D82769C9E4707929 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9E124288194048CC21B2CB0EF8EB2020 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 00BE32840808D358F6B11753A89B0460 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + AD9C5A8DAF4334B670E91B2EC08DBE2C /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 464450D037D17ED5EE2322E180A6A9F5 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 034EF00D8111D0F384A86E95EF3AA8BC /* iOS */ = { + isa = PBXGroup; + children = ( + 2ACE13A318E38C13B001C3F8B9C12297 /* Accelerate.framework */, + B8516FA8FBA589C308B6F1182094AB00 /* CFNetwork.framework */, + 217DE48750E9BED49CF941AEEED6AA1A /* Foundation.framework */, + D1FC207AB3C95A8C4122CF5AEEC8DEA5 /* UIKit.framework */, + ); + name = iOS; + sourceTree = ""; + }; + 088EDEFB04425B49F1896C311BDC665E /* Alamofire */ = { + isa = PBXGroup; + children = ( + 810DCF6DC8913920A5639B6958DC2BA4 /* AFError.swift */, + 2D56382B4338EBA18A58567B5C70515F /* Alamofire.swift */, + 326EF0715AC1400A86EE19C27FDAF115 /* AlamofireExtended.swift */, + 46AC5CDC2DD35FF05C9D4E8D8D90F9FB /* CachedResponseHandler.swift */, + 47DDF4AAAAA3E603AD143176E8BB5FE5 /* DispatchQueue+Alamofire.swift */, + 7C98A36BAD82E5EEAEEDD3483E752B02 /* EventMonitor.swift */, + 26FCEF29D7093C9B9F1E3D4636E49082 /* HTTPHeaders.swift */, + 54719D0DEB36D451058BBCEC0CD1E5B5 /* HTTPMethod.swift */, + 2008FE80DBC7AF7CA54C0446ACD4ECF2 /* MultipartFormData.swift */, + 340C215C90F489D89ADCCB43B94E77A5 /* MultipartUpload.swift */, + 4EAA11F4D50B594FF8200EB11C83FCD7 /* NetworkReachabilityManager.swift */, + 3B30E2722306C75029578F3B3D2B7F02 /* Notifications.swift */, + 188BAFE30769AF909D8213831CBB4C11 /* OperationQueue+Alamofire.swift */, + 37F4399209EB3507DA47E16F2F71375A /* ParameterEncoder.swift */, + FA54E4C38F18CA402D8E29C0D9632476 /* ParameterEncoding.swift */, + AEF84C89FE032DE3D06F20C336D0EFC1 /* Protected.swift */, + ED13247345B4917E5150511E465C2356 /* RedirectHandler.swift */, + 5A1758AD300D0AC3B8BF823A145B9E31 /* Request.swift */, + 17543249A8340CB5CB919EE4FC6ACE96 /* RequestInterceptor.swift */, + F7FE9898595FF5D4BCF03568C020A03F /* RequestTaskMap.swift */, + 07489DCF8EFA830A3D3F8B3BA59574B5 /* Response.swift */, + 157D5E1C009B5F6B52D1DBD5A70E8999 /* ResponseSerialization.swift */, + 150A4246534DC802B6B0513E2954C48E /* Result+Alamofire.swift */, + 9CECB0D20252BA894F4C22807A84AD64 /* RetryPolicy.swift */, + 26406F2E3BE042B26D0ADF94FF6E231E /* ServerTrustEvaluation.swift */, + 1CDE5B39C4571176D1961DE019AA988C /* Session.swift */, + 7CE96C84B2FA277312B1E5F2A93B1AA7 /* SessionDelegate.swift */, + 563CE9994181B97042E18EF3B2CFD0EE /* StringEncoding+Alamofire.swift */, + 50B58FF25491A5326639DE7F4CC37DAB /* URLConvertible+URLRequestConvertible.swift */, + 269223D6670216DCFD8C0D6183BA1F5E /* URLEncodedFormEncoder.swift */, + A6D423412248122B2EC5D1BEF4BAE786 /* URLRequest+Alamofire.swift */, + 8C202B3FA7EC2BC2DCE5DC0567C7FFD6 /* URLSessionConfiguration+Alamofire.swift */, + E761716F68585D0656F71075AFD2D879 /* Validation.swift */, + FC218EF8C59B7461E88D3360CC88FD84 /* Support Files */, + ); + name = Alamofire; + path = Alamofire; + sourceTree = ""; + }; + 23A6A15B35A9B08845C67FB6EDF4B9FD /* Targets Support Files */ = { + isa = PBXGroup; + children = ( + 554CFC803AEF20CE459837F002AEA2F9 /* Pods-ayudapy */, + ); + name = "Targets Support Files"; + sourceTree = ""; + }; + 41B650619CC70481522DAF9007957B33 /* Products */ = { + isa = PBXGroup; + children = ( + 5D797E9A5C5782CE845840781FA1CC81 /* Alamofire.framework */, + C3F44C782D64D7EB20B61CE3844EBFAD /* Kingfisher.framework */, + 8D3CBDCF91193A4F1753EACDE07B951A /* Pods_ayudapy.framework */, + A1ADAD80982C662C9951F49E0F39002F /* Sniffer.framework */, + E23C076BA70925415F490FEDB215DA92 /* SwiftyJSON.framework */, + ); + name = Products; + sourceTree = ""; + }; + 448C18637C0BE040BC52A7A5CD665BC8 /* SwiftyJSON */ = { + isa = PBXGroup; + children = ( + 1130C57D15949D850209BA8C0DA3CD22 /* SwiftyJSON.swift */, + 65A5F6335235DAF57F21ECE854CC3808 /* Support Files */, + ); + name = SwiftyJSON; + path = SwiftyJSON; + sourceTree = ""; + }; + 4514F191901186B1A56AF9B8B31D71D9 /* GooglePlaces */ = { + isa = PBXGroup; + children = ( + 90030BA6AA3AEE8DF7DDEED127CB2CA2 /* Frameworks */, + 854CB17C021716C02BF000B37A73BAAC /* Resources */, + 55DF35DC8E230FA4A15E1695638D2897 /* Support Files */, + ); + name = GooglePlaces; + path = GooglePlaces; + sourceTree = ""; + }; + 45E3E023F4F35635DAF2A49D190712BB /* Resources */ = { + isa = PBXGroup; + children = ( + AEA94306EA9521A16FB414E5E3971C58 /* GoogleMaps.bundle */, + ); + name = Resources; + sourceTree = ""; + }; + 4E9F57ACC7B598E13A9C90C1F84CABF5 /* Support Files */ = { + isa = PBXGroup; + children = ( + C01CF44ABFCA34429CFD6E16A7D9C230 /* Sniffer.modulemap */, + 3C1FF98EC80A2CD4E8F2D3FB0F18C453 /* Sniffer-dummy.m */, + 2AA23092FB4B8C5EE6B76EA2D272A9D3 /* Sniffer-Info.plist */, + 4FFA9FCC94E88E8646FD27E323E08F8C /* Sniffer-prefix.pch */, + 906EF19442CDF2F2AC75892AE0A5DA0E /* Sniffer-umbrella.h */, + C41DEC7FBFCE8EDE14BEFBAB8040DCD1 /* Sniffer.debug.xcconfig */, + C193366879E7D9C7C728BFF6BFFAF16B /* Sniffer.release.xcconfig */, + ); + name = "Support Files"; + path = "../Target Support Files/Sniffer"; + sourceTree = ""; + }; + 554CFC803AEF20CE459837F002AEA2F9 /* Pods-ayudapy */ = { + isa = PBXGroup; + children = ( + D5740CA6B8C58D4346D20B199BEAB969 /* Pods-ayudapy.modulemap */, + 7CD6DCA105E025B6D00C2D32AB231269 /* Pods-ayudapy-acknowledgements.markdown */, + BC85BC1F20B72982E08CFB847993ED6C /* Pods-ayudapy-acknowledgements.plist */, + 0ECF9C5C778B0300FAE15C295E5C1EBA /* Pods-ayudapy-dummy.m */, + BB0EA96AF9B7CD694CEE44B915DD741C /* Pods-ayudapy-frameworks.sh */, + F60F4A61B2C1BECCA82B06329D51FE12 /* Pods-ayudapy-Info.plist */, + 0B85DF0CEFC2DC3A7E694AB608890B2A /* Pods-ayudapy-resources.sh */, + C768BF5315ED7DAB1E0BF0F7FCDF3B16 /* Pods-ayudapy-umbrella.h */, + 3F8A1F92B6938FC3DCDC32EE66382D88 /* Pods-ayudapy.debug.xcconfig */, + F442A3B41A88AE6EB8303270EA5E0959 /* Pods-ayudapy.release.xcconfig */, + ); + name = "Pods-ayudapy"; + path = "Target Support Files/Pods-ayudapy"; + sourceTree = ""; + }; + 55DF35DC8E230FA4A15E1695638D2897 /* Support Files */ = { + isa = PBXGroup; + children = ( + D575BEB1109BF564FCF18F1D5BAF1853 /* GooglePlaces.debug.xcconfig */, + 1EFA33EDF9AC66391746D4232E02F9CB /* GooglePlaces.release.xcconfig */, + ); + name = "Support Files"; + path = "../Target Support Files/GooglePlaces"; + sourceTree = ""; + }; + 592CC7BC26AB6428C5013E0C6BA0C46D /* Kingfisher */ = { + isa = PBXGroup; + children = ( + BFB0D45C841A8867E04DB07931654494 /* Core */, + F57D24BC24433AEBBDD44FA59CF0FA60 /* Support Files */, + ); + name = Kingfisher; + path = Kingfisher; + sourceTree = ""; + }; + 60ECB1B6AFD21BB558256366BC744808 /* Base */ = { + isa = PBXGroup; + children = ( + C17FA03EFB27D7231E02DEADC139AA90 /* Frameworks */, + ); + name = Base; + sourceTree = ""; + }; + 65A5F6335235DAF57F21ECE854CC3808 /* Support Files */ = { + isa = PBXGroup; + children = ( + 9E1215C916B0D94C4FAF563720F11FA2 /* SwiftyJSON.modulemap */, + F3AE2AEA39A52EA12DCE1A130832E83A /* SwiftyJSON-dummy.m */, + 302707EAC13CCD6B67F523285D9CF4DE /* SwiftyJSON-Info.plist */, + A37ED2407A545050DF667598E383CA85 /* SwiftyJSON-prefix.pch */, + B40643F03209EA5FED6D46DC71095A34 /* SwiftyJSON-umbrella.h */, + D0B136D0F6E8B67B2E7012C567296D64 /* SwiftyJSON.debug.xcconfig */, + 212D95CBD8FE2141ED259910E0CE9141 /* SwiftyJSON.release.xcconfig */, + ); + name = "Support Files"; + path = "../Target Support Files/SwiftyJSON"; + sourceTree = ""; + }; + 6FDBA8261B11F302A12737DC524C237C /* GoogleMaps */ = { + isa = PBXGroup; + children = ( + 60ECB1B6AFD21BB558256366BC744808 /* Base */, + A9A0115980CD3441AB6339838A121450 /* Maps */, + 7B99E2A6F50683B4B169838AFA160002 /* Support Files */, + ); + name = GoogleMaps; + path = GoogleMaps; + sourceTree = ""; + }; + 7B99E2A6F50683B4B169838AFA160002 /* Support Files */ = { + isa = PBXGroup; + children = ( + E3D29C5E402326372843BDF4B50D2491 /* GoogleMaps.debug.xcconfig */, + DA331CDC1AADC6340DCCC735DBE0CE92 /* GoogleMaps.release.xcconfig */, + ); + name = "Support Files"; + path = "../Target Support Files/GoogleMaps"; + sourceTree = ""; + }; + 7D85262D7A3092FBB302174F70C4D510 /* Pods */ = { + isa = PBXGroup; + children = ( + 088EDEFB04425B49F1896C311BDC665E /* Alamofire */, + 6FDBA8261B11F302A12737DC524C237C /* GoogleMaps */, + 4514F191901186B1A56AF9B8B31D71D9 /* GooglePlaces */, + 592CC7BC26AB6428C5013E0C6BA0C46D /* Kingfisher */, + F62E726014514148D39ED76AA0EC7E5A /* Sniffer */, + 448C18637C0BE040BC52A7A5CD665BC8 /* SwiftyJSON */, + ); + name = Pods; + sourceTree = ""; + }; + 80263857729FCA52E4D52802975D3C08 /* Frameworks */ = { + isa = PBXGroup; + children = ( + F0EAEAF9C791D43605D0806300BA5BBB /* GoogleMaps.framework */, + 02BAB62D95AD76D9D4EC0702BDF1843C /* GoogleMapsCore.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 854CB17C021716C02BF000B37A73BAAC /* Resources */ = { + isa = PBXGroup; + children = ( + D15B7919D173FD6DBA2B0E5A05B3098A /* GooglePlaces.bundle */, + ); + name = Resources; + sourceTree = ""; + }; + 90030BA6AA3AEE8DF7DDEED127CB2CA2 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 32293F983FAE4B87B50267D3782CE3C6 /* GooglePlaces.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + A9A0115980CD3441AB6339838A121450 /* Maps */ = { + isa = PBXGroup; + children = ( + 80263857729FCA52E4D52802975D3C08 /* Frameworks */, + 45E3E023F4F35635DAF2A49D190712BB /* Resources */, + ); + name = Maps; + sourceTree = ""; + }; + BA4F31F07263C99FC76E66D632A59F09 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 034EF00D8111D0F384A86E95EF3AA8BC /* iOS */, + ); + name = Frameworks; + sourceTree = ""; + }; + BFB0D45C841A8867E04DB07931654494 /* Core */ = { + isa = PBXGroup; + children = ( + 3C9C9B80C95B7E73D6478E3078D25151 /* AnimatedImageView.swift */, + 9F714440C3789B34D24900B6AF99379E /* AuthenticationChallengeResponsable.swift */, + 4AD26E03869B14F17E418F1906126DC0 /* Box.swift */, + A86970FD0971BD8A3D4D109A3FD1C9C1 /* CacheSerializer.swift */, + 767284A6730E28E58874C28DC3990DA9 /* CallbackQueue.swift */, + 810405AB483DBEC4BB4AFC65BEC41132 /* Delegate.swift */, + BD5B1F0B15197CA19FDD20E87B788399 /* Deprecated.swift */, + 5D0D4364CA2D471EF192E2744714BFE9 /* DiskStorage.swift */, + 0B5864DD581CB916524EBBF14809FA7C /* ExtensionHelpers.swift */, + CCF5BAA261CF05C3F5A3BDF4F9D5E97A /* Filter.swift */, + EBDFCC1439B7773EC62562364C2208A5 /* FormatIndicatedCacheSerializer.swift */, + 6D8A7C0407BFB5B4857D608C51570B10 /* GIFAnimatedImage.swift */, + 7D27C59A4E37D2B1346892047FF972D7 /* Image.swift */, + 0EE0D57C287FA598F9CEEF14F6912716 /* ImageCache.swift */, + 9FAAD7C2886A5CC33D11F6AEDB9F479C /* ImageDataProcessor.swift */, + D27F4A813480A65A03CB48FB1FD9EA71 /* ImageDataProvider.swift */, + C0C28C049878F9901D465282CA3FED8F /* ImageDownloader.swift */, + 9743FB12DDCBD5E6496FBF020D12DD03 /* ImageDownloaderDelegate.swift */, + 1A049E5FFFDD1FB4697F86546EFDF687 /* ImageDrawing.swift */, + 75E60D1481168119290056C7E021B02D /* ImageFormat.swift */, + 6A5E6232CD8D42166A7EBD968F793770 /* ImageModifier.swift */, + 71F8E72286075B513BA8550B539BF40D /* ImagePrefetcher.swift */, + 6AEB6980AA83A48067DE37DA0656B32D /* ImageProcessor.swift */, + ABD08D50EA2E9A0FBBD08FDE891769F4 /* ImageProgressive.swift */, + 0CB7BEB3CFFFB0F8510F5797B17B1370 /* ImageTransition.swift */, + 011F00241BDCB076E539D076F86F4BA3 /* ImageView+Kingfisher.swift */, + 7AAFEA1F480A677D0706B3A1763FA242 /* Indicator.swift */, + FBC1B29F06B53A5DCB63D5000721803E /* Kingfisher.h */, + D9644FB180C11360EF01E790DAD40918 /* Kingfisher.swift */, + 9871D5B3B73CACE26A8FA8EEE206EC59 /* KingfisherError.swift */, + EFBAF51E5F422D16770E45331F668EA4 /* KingfisherManager.swift */, + 17421F96C95BE20FF3D90CDA121CA3DF /* KingfisherOptionsInfo.swift */, + 199973D0DDE1935F552BB39AD2A78379 /* MemoryStorage.swift */, + 2B9E0128BB4445FF693B9E5F807A7FED /* NSButton+Kingfisher.swift */, + 7834BDC3533BFFE7F295F1F6EB8C4536 /* Placeholder.swift */, + EE1A9E37DD8CEAF9BA4D0C1B1D749C63 /* RedirectHandler.swift */, + EEEBCB63AFE94FDC6D7FAA8181A2AEE7 /* RequestModifier.swift */, + AB25E8C2ED9B8C198B4FFB08F7C490A7 /* Resource.swift */, + 8C4BBB76B4F6F34FAE6EFC88E3170330 /* Result.swift */, + 3B09F5EA2EB05B4A37A67DA12AF4B66D /* Runtime.swift */, + 2AA78468BEE0F0A00BE13A62876A076C /* SessionDataTask.swift */, + 76DEC9980D0488EC683B0BF39F35D68C /* SessionDelegate.swift */, + EBAEFA1B3E2253FD22055C1B419C2D1D /* SizeExtensions.swift */, + 23CE0952A939BE0C9F684F904D12636D /* Source.swift */, + 8059F2695877BA3B709149C7310B22DF /* Storage.swift */, + D99A47CF4126AB5BB335D8E441E8F29C /* String+MD5.swift */, + 8F133DA10DF6A93776C2FA276034B409 /* UIButton+Kingfisher.swift */, + C058C1552B61C32A11C19B437DAA829B /* WKInterfaceImage+Kingfisher.swift */, + ); + name = Core; + sourceTree = ""; + }; + C17FA03EFB27D7231E02DEADC139AA90 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 7A14E5C32E63DF3D82B838FA501D19DD /* GoogleMapsBase.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + CF1408CF629C7361332E53B88F7BD30C = { + isa = PBXGroup; + children = ( + 9D940727FF8FB9C785EB98E56350EF41 /* Podfile */, + BA4F31F07263C99FC76E66D632A59F09 /* Frameworks */, + 7D85262D7A3092FBB302174F70C4D510 /* Pods */, + 41B650619CC70481522DAF9007957B33 /* Products */, + 23A6A15B35A9B08845C67FB6EDF4B9FD /* Targets Support Files */, + ); + sourceTree = ""; + }; + F57D24BC24433AEBBDD44FA59CF0FA60 /* Support Files */ = { + isa = PBXGroup; + children = ( + 59BC9381CB0680F53FD9B43FA215A4C7 /* Kingfisher.modulemap */, + AA0CC5B2F570F020CD695D0305A50EB2 /* Kingfisher-dummy.m */, + E5FE74B2B928A397905AEEC537ABEE87 /* Kingfisher-Info.plist */, + 134B105AA2D7AE3052699AC079A16A36 /* Kingfisher-prefix.pch */, + B0BF94B2FD005CDC763DF04C375DFE84 /* Kingfisher-umbrella.h */, + CAF2C0EA15FE5EF3384B7BFF1C067360 /* Kingfisher.debug.xcconfig */, + C8CD140BF4C274EB7AFFF56C9729C67F /* Kingfisher.release.xcconfig */, + ); + name = "Support Files"; + path = "../Target Support Files/Kingfisher"; + sourceTree = ""; + }; + F62E726014514148D39ED76AA0EC7E5A /* Sniffer */ = { + isa = PBXGroup; + children = ( + B4675821A24A9DFC15098FEF0706B052 /* BodyDeserializer.swift */, + CF1C9E983AC21F5582A2707B97C87970 /* Sniffer.swift */, + 4E9F57ACC7B598E13A9C90C1F84CABF5 /* Support Files */, + ); + name = Sniffer; + path = Sniffer; + sourceTree = ""; + }; + FC218EF8C59B7461E88D3360CC88FD84 /* Support Files */ = { + isa = PBXGroup; + children = ( + FDE47C3620E672E8782B6C20D9B5F185 /* Alamofire.modulemap */, + B0F9EAB1E6E9C621DB7A30F62CD595E8 /* Alamofire-dummy.m */, + 19E26E87DC7D99B9937DF761149A7A7B /* Alamofire-Info.plist */, + 294A11A76A9E4DF9B0E7540F98D439AB /* Alamofire-prefix.pch */, + 3EE4A4F53712962B4E0CC7F40A98D946 /* Alamofire-umbrella.h */, + 5068234F9EF54AAF9E56B52782C9E0EE /* Alamofire.debug.xcconfig */, + 21433EDB6D492589F7F0A412256F5019 /* Alamofire.release.xcconfig */, + ); + name = "Support Files"; + path = "../Target Support Files/Alamofire"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 3D7DD627DB5C5AD636646E57B685AFB2 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 1942F7D3B7722A5637C3FC0BE44B1E6C /* SwiftyJSON-umbrella.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 6D49C9ADFC13AFFEBDADC9BE2F3C2D38 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 7630B9EA3CC1C43885228418C6CE5790 /* Pods-ayudapy-umbrella.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + BF8C72045F78FEDCDE4BA23EE6DD94BB /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 452939C15B32AC5981C540FF69990CBE /* Sniffer-umbrella.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DD5134CC0AD214337C10CC46995CC3CE /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 2BC73DE30871EA6F7D9F600D50F11726 /* Alamofire-umbrella.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + FCA903FBC6FCC19FBFF772C5B82479CE /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 526D4A6E90525A42DEEE53B2EAD2C5E8 /* Kingfisher-umbrella.h in Headers */, + 74F16EAC797A560DA6EE944A8CEBEC04 /* Kingfisher.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 42D1982FACF80BE1722E66C9D5705499 /* Sniffer */ = { + isa = PBXNativeTarget; + buildConfigurationList = 3792FD45764CC8C8E562B49EDD176F86 /* Build configuration list for PBXNativeTarget "Sniffer" */; + buildPhases = ( + BF8C72045F78FEDCDE4BA23EE6DD94BB /* Headers */, + 1D08EF7478C3DE35640EB18D1031AB62 /* Sources */, + 29B52F9C761C5400AF4A6DD63A5FA6A2 /* Frameworks */, + D47152091137084DA64DBACE07FF3AC1 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Sniffer; + productName = Sniffer; + productReference = A1ADAD80982C662C9951F49E0F39002F /* Sniffer.framework */; + productType = "com.apple.product-type.framework"; + }; + BC06EA6078D525B77DA58D4796B86F89 /* Pods-ayudapy */ = { + isa = PBXNativeTarget; + buildConfigurationList = 95494FDDB35329E11CEBA47EFEA978D8 /* Build configuration list for PBXNativeTarget "Pods-ayudapy" */; + buildPhases = ( + 6D49C9ADFC13AFFEBDADC9BE2F3C2D38 /* Headers */, + 7BC4FF0253835C4D36BA990E84373263 /* Sources */, + 9E124288194048CC21B2CB0EF8EB2020 /* Frameworks */, + B4681281CAD8CDB1483E76C31F10F292 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 40214741F676ACD950A18D8915A0EABD /* PBXTargetDependency */, + 5DF9AE45E5F41B17A5EC3DEAC480083F /* PBXTargetDependency */, + 0146B420C2CCE6799EDF14C88C22056D /* PBXTargetDependency */, + C9790F42FFB696F1FD2E33069B76ABFA /* PBXTargetDependency */, + 11DC6A22CF6821043B175E8248D27734 /* PBXTargetDependency */, + 56F7BB84D7A3C82A17B47CAB8BB9B6C0 /* PBXTargetDependency */, + ); + name = "Pods-ayudapy"; + productName = "Pods-ayudapy"; + productReference = 8D3CBDCF91193A4F1753EACDE07B951A /* Pods_ayudapy.framework */; + productType = "com.apple.product-type.framework"; + }; + D118A6A04828FD3CDA8640CD2B6796D2 /* SwiftyJSON */ = { + isa = PBXNativeTarget; + buildConfigurationList = 62468BF9C7EDA172E951A24083C3ECA7 /* Build configuration list for PBXNativeTarget "SwiftyJSON" */; + buildPhases = ( + 3D7DD627DB5C5AD636646E57B685AFB2 /* Headers */, + 1700C451C98D9DCECBEDFC5E351C5A9E /* Sources */, + AD9C5A8DAF4334B670E91B2EC08DBE2C /* Frameworks */, + D098F7F0080A87A1F472C81FC2F85528 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = SwiftyJSON; + productName = SwiftyJSON; + productReference = E23C076BA70925415F490FEDB215DA92 /* SwiftyJSON.framework */; + productType = "com.apple.product-type.framework"; + }; + E8022D22FAA6690B5E1C379C1BCE1491 /* Kingfisher */ = { + isa = PBXNativeTarget; + buildConfigurationList = F474713E7462A344D5557EB151CFA07E /* Build configuration list for PBXNativeTarget "Kingfisher" */; + buildPhases = ( + FCA903FBC6FCC19FBFF772C5B82479CE /* Headers */, + FDCA7CDF3C4EB63B09F959386061D67C /* Sources */, + 61AF6B13CCC2E38FDA0B9BAB05581FD5 /* Frameworks */, + 5291070C390A4C8318BCD0577D8D7E20 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Kingfisher; + productName = Kingfisher; + productReference = C3F44C782D64D7EB20B61CE3844EBFAD /* Kingfisher.framework */; + productType = "com.apple.product-type.framework"; + }; + EAAA1AD3A8A1B59AB91319EE40752C6D /* Alamofire */ = { + isa = PBXNativeTarget; + buildConfigurationList = 4B2E6A7DD9BEFE908126937ABFFCB84F /* Build configuration list for PBXNativeTarget "Alamofire" */; + buildPhases = ( + DD5134CC0AD214337C10CC46995CC3CE /* Headers */, + DA97C7E8121266408747CC86023CA8A2 /* Sources */, + 9C1C2D14882937DB0594D5E1A90588BE /* Frameworks */, + 69CDCEDFFCFB1CD5B62AE1ADB0858B89 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Alamofire; + productName = Alamofire; + productReference = 5D797E9A5C5782CE845840781FA1CC81 /* Alamofire.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + BFDFE7DC352907FC980B868725387E98 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1100; + LastUpgradeCheck = 1100; + }; + buildConfigurationList = 4821239608C13582E20E6DA73FD5F1F9 /* Build configuration list for PBXProject "Pods" */; + compatibilityVersion = "Xcode 10.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = CF1408CF629C7361332E53B88F7BD30C; + productRefGroup = 41B650619CC70481522DAF9007957B33 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + EAAA1AD3A8A1B59AB91319EE40752C6D /* Alamofire */, + E5B4BBC6DD552AC8943C7E22772FC1D3 /* GoogleMaps */, + 0C1885900810601510E0C632060FAF26 /* GooglePlaces */, + E8022D22FAA6690B5E1C379C1BCE1491 /* Kingfisher */, + BC06EA6078D525B77DA58D4796B86F89 /* Pods-ayudapy */, + 42D1982FACF80BE1722E66C9D5705499 /* Sniffer */, + D118A6A04828FD3CDA8640CD2B6796D2 /* SwiftyJSON */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 5291070C390A4C8318BCD0577D8D7E20 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 69CDCEDFFCFB1CD5B62AE1ADB0858B89 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + B4681281CAD8CDB1483E76C31F10F292 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D098F7F0080A87A1F472C81FC2F85528 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D47152091137084DA64DBACE07FF3AC1 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 1700C451C98D9DCECBEDFC5E351C5A9E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2BF6FBC0A64BF107C5D64C61F1397770 /* SwiftyJSON-dummy.m in Sources */, + 7B4CE5C84161E2BFE477F42DFD634382 /* SwiftyJSON.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 1D08EF7478C3DE35640EB18D1031AB62 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + B46D7720DA41A19FCE46F578979A2036 /* BodyDeserializer.swift in Sources */, + DEBC2B060652397E0EF4A19A94EB636F /* Sniffer-dummy.m in Sources */, + 90435D953F67AB68BF5EA19B351F0085 /* Sniffer.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 7BC4FF0253835C4D36BA990E84373263 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 552979B096AE1C5676348FADD5CF76A6 /* Pods-ayudapy-dummy.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DA97C7E8121266408747CC86023CA8A2 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 165F498AB8762893E957BCE0D70F4439 /* AFError.swift in Sources */, + 4F767680534EDD5E80E526C3D76989B2 /* Alamofire-dummy.m in Sources */, + DA6512AAAFD92EFA9860ED84C3A308B9 /* Alamofire.swift in Sources */, + AA8023C0CFE561DB8B8D92232C6125B1 /* AlamofireExtended.swift in Sources */, + E4E727DD13DA225D574D9880D9CB7348 /* CachedResponseHandler.swift in Sources */, + 433815428539A3F818A385B0EFEFB8DC /* DispatchQueue+Alamofire.swift in Sources */, + DA07C0CDF1B0FBC959A4406DE142A081 /* EventMonitor.swift in Sources */, + 85E10F2E2F0044C1ACE78F76395F9077 /* HTTPHeaders.swift in Sources */, + 6C8EC166C81EB4E72450A002224658DC /* HTTPMethod.swift in Sources */, + F5E436F5E8C598430FABC8787E517F96 /* MultipartFormData.swift in Sources */, + 918548CAAC87215B4960B1F4D2A71769 /* MultipartUpload.swift in Sources */, + 1CE8BDA6C86345FD31F42F3AECB375EA /* NetworkReachabilityManager.swift in Sources */, + 60D4B28D45B4164E21715CF68D1E239B /* Notifications.swift in Sources */, + E618A89E126F408A59FE3167A6CBB510 /* OperationQueue+Alamofire.swift in Sources */, + 2BE0DE689754A1603251EB331450718E /* ParameterEncoder.swift in Sources */, + 4AAB3F2054BD0939C95D961A3BAA43B4 /* ParameterEncoding.swift in Sources */, + D0F6D13D0DC583351F5F6BCA1CB04ABD /* Protected.swift in Sources */, + AAE822224E085A6362FA7122C0605078 /* RedirectHandler.swift in Sources */, + 08281777D479CF46F0E0F24753E01A63 /* Request.swift in Sources */, + DDA3EF3B4A1D99E1E4AB016CFC1B544D /* RequestInterceptor.swift in Sources */, + 37A2FCD24ED22C8D670188930CE4CA5D /* RequestTaskMap.swift in Sources */, + EE5FCC4133C3E712B85676510F2A3D7B /* Response.swift in Sources */, + B2047F988C61D368E76A806D5227329E /* ResponseSerialization.swift in Sources */, + CB436452F5C36FC049E26A8C0475965C /* Result+Alamofire.swift in Sources */, + 408258CCC9DF9A5BD371EBEC8286CD81 /* RetryPolicy.swift in Sources */, + 57B60987700B37698F1C9996AF526AD5 /* ServerTrustEvaluation.swift in Sources */, + EE1F347816E69DB6AB8513488D9D08FF /* Session.swift in Sources */, + 964030107A87D7C1B25628B02C0E563F /* SessionDelegate.swift in Sources */, + 80DBE8FF4DFE41CB54687CAB6A29F1B3 /* StringEncoding+Alamofire.swift in Sources */, + CDCDB8A355FE70F12855CC8A3657E739 /* URLConvertible+URLRequestConvertible.swift in Sources */, + 9DC4A6FCE96D312A9EE0E5D51B4D4A80 /* URLEncodedFormEncoder.swift in Sources */, + 003B9FC9E937C82635CF68E9E3576B52 /* URLRequest+Alamofire.swift in Sources */, + 2933B39BFCEF204A508F4FFF3188AD27 /* URLSessionConfiguration+Alamofire.swift in Sources */, + 0FF6A3BBA9E9DAAE2E741513546FC4B6 /* Validation.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + FDCA7CDF3C4EB63B09F959386061D67C /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C2CDC860BA74B75C0504A55B02E072F6 /* AnimatedImageView.swift in Sources */, + 89A9375A72E68E2D7A0FEE9F09B9DB10 /* AuthenticationChallengeResponsable.swift in Sources */, + 3B6EE6F748F891E92E1AE63029F3D4DB /* Box.swift in Sources */, + 171CFEBFD0A93CFF6DC40E85EBF3BF8A /* CacheSerializer.swift in Sources */, + 447FCEA8BB2DFACD60B6B8ECECC027B9 /* CallbackQueue.swift in Sources */, + 6E2AE42031E6C611C2AAAD2FE1B12BAD /* Delegate.swift in Sources */, + 4B288AC81506A29EC002A33979797B27 /* Deprecated.swift in Sources */, + C409433F2328566B78FA477EB56CA377 /* DiskStorage.swift in Sources */, + 5AD5EE0D42721F7E45DAEB5D7D9FA3C4 /* ExtensionHelpers.swift in Sources */, + 63C2BA763C69F183DDB32456D7201543 /* Filter.swift in Sources */, + 86CE574CADD6EAE3E385F152C74EA78C /* FormatIndicatedCacheSerializer.swift in Sources */, + 8EB401DC59ED357AFE37BAF90CD90B3A /* GIFAnimatedImage.swift in Sources */, + 09433CD8C4A2D4227DF98AB78E246011 /* Image.swift in Sources */, + D1B4079BB427AA2EA1955C02EA34E7EC /* ImageCache.swift in Sources */, + F0E89F7017249BCB3B2C1320F9413B2C /* ImageDataProcessor.swift in Sources */, + 38ECB82C9A053349A8928D026F437792 /* ImageDataProvider.swift in Sources */, + FEAB0E502EA1A6C4F758823B3B4BDFFF /* ImageDownloader.swift in Sources */, + C58AC3B6AC4597752277700E774B0927 /* ImageDownloaderDelegate.swift in Sources */, + 713643837AE7E542FFD9595143E0AA39 /* ImageDrawing.swift in Sources */, + 9A074410CBBCE8C6BE663C80F235DC89 /* ImageFormat.swift in Sources */, + 8A50CF3AAB5BD9BF949142DEB8E5050A /* ImageModifier.swift in Sources */, + 4B1C14991FCEB01E9E05EF8F1FCD5671 /* ImagePrefetcher.swift in Sources */, + CD698D8D49A0DB3669D8D58B4571268E /* ImageProcessor.swift in Sources */, + AB8B45111A4E958CF8D633D8C6231689 /* ImageProgressive.swift in Sources */, + 432848A52E6A7EC1F084299032907A29 /* ImageTransition.swift in Sources */, + 1A73F39CDDEEADB24723A3F6CBC359AE /* ImageView+Kingfisher.swift in Sources */, + 5CA745D6F24598B881ED85918BBE6697 /* Indicator.swift in Sources */, + A19C9C58F87FAF5DB267AE064023DF30 /* Kingfisher-dummy.m in Sources */, + 84FB7295031BEDF0073A52A61AF7129A /* Kingfisher.swift in Sources */, + 3C52CE0A60C1071B0A1586B57CC4E2FD /* KingfisherError.swift in Sources */, + 80F1CE5131DA37F9DF6516F927C78F16 /* KingfisherManager.swift in Sources */, + B2D8F9D8B91DE8C26D5C2355F1FB941A /* KingfisherOptionsInfo.swift in Sources */, + F0709DCF413ACE7F337DFAC0BA3B874B /* MemoryStorage.swift in Sources */, + 4781CFEA0881C7AFD1539C12F074632D /* NSButton+Kingfisher.swift in Sources */, + 686FF933FB5A33BA7A11CF986A916A06 /* Placeholder.swift in Sources */, + 62AD7D42C96A04828EEA3CC151AA5E38 /* RedirectHandler.swift in Sources */, + CDD42E25D201469CAFEA9E6181525FA8 /* RequestModifier.swift in Sources */, + 977C462E588D9085193D50156CEF2D5A /* Resource.swift in Sources */, + 2CE3B5EB3E415B5467CC027FC85D9F99 /* Result.swift in Sources */, + 767925B99CC34D22E127120952AC715D /* Runtime.swift in Sources */, + D0508A5E7A4DF1129A99994EFC3AACEE /* SessionDataTask.swift in Sources */, + 14672662061590430AF4801C432020A6 /* SessionDelegate.swift in Sources */, + 9820B3A65D65CCE7A683E5126C5100A8 /* SizeExtensions.swift in Sources */, + C0FFA57E39D99B6AC85FEF60CA966EE4 /* Source.swift in Sources */, + F780F31334A5B069C756121AE82F008B /* Storage.swift in Sources */, + E7D7D875688EDFBCB8F4C2C1F4F5B5E5 /* String+MD5.swift in Sources */, + F66ED581D2EBD170ABAD4EC0206B6C5C /* UIButton+Kingfisher.swift in Sources */, + 717C581962354033F5A82D15D6F676D5 /* WKInterfaceImage+Kingfisher.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 0146B420C2CCE6799EDF14C88C22056D /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = GooglePlaces; + target = 0C1885900810601510E0C632060FAF26 /* GooglePlaces */; + targetProxy = 9C46CF39F576C9ECCC4C46B4C3317E60 /* PBXContainerItemProxy */; + }; + 11DC6A22CF6821043B175E8248D27734 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = Sniffer; + target = 42D1982FACF80BE1722E66C9D5705499 /* Sniffer */; + targetProxy = 47C7C484782DFCDC94CF15D32FEB70FD /* PBXContainerItemProxy */; + }; + 40214741F676ACD950A18D8915A0EABD /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = Alamofire; + target = EAAA1AD3A8A1B59AB91319EE40752C6D /* Alamofire */; + targetProxy = 5D1C1BE9278305DECBE5D81192904196 /* PBXContainerItemProxy */; + }; + 56F7BB84D7A3C82A17B47CAB8BB9B6C0 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = SwiftyJSON; + target = D118A6A04828FD3CDA8640CD2B6796D2 /* SwiftyJSON */; + targetProxy = C7439EE00C9A9E7219F03BF66125120F /* PBXContainerItemProxy */; + }; + 5DF9AE45E5F41B17A5EC3DEAC480083F /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = GoogleMaps; + target = E5B4BBC6DD552AC8943C7E22772FC1D3 /* GoogleMaps */; + targetProxy = 2570F9CEEA81410AA3365BB1F17BF90B /* PBXContainerItemProxy */; + }; + C9790F42FFB696F1FD2E33069B76ABFA /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = Kingfisher; + target = E8022D22FAA6690B5E1C379C1BCE1491 /* Kingfisher */; + targetProxy = 9DCEEA4B64E6AEBF1D7992106422B271 /* PBXContainerItemProxy */; + }; + D42315111F400C0605ACCD6B5CAF2178 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = GoogleMaps; + target = E5B4BBC6DD552AC8943C7E22772FC1D3 /* GoogleMaps */; + targetProxy = 6EB1C2DD30E6564FC3135C9E8002D8F7 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 009DB1577F07B2FF797B78F2D1142134 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D0B136D0F6E8B67B2E7012C567296D64 /* SwiftyJSON.debug.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/SwiftyJSON/SwiftyJSON-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/SwiftyJSON/SwiftyJSON-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "Target Support Files/SwiftyJSON/SwiftyJSON.modulemap"; + PRODUCT_MODULE_NAME = SwiftyJSON; + PRODUCT_NAME = SwiftyJSON; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 0518F89D5C4730E5D1FB793988114CB0 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 21433EDB6D492589F7F0A412256F5019 /* Alamofire.release.xcconfig */; + buildSettings = { + CLANG_ENABLE_OBJC_WEAK = NO; + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/Alamofire/Alamofire-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/Alamofire/Alamofire-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "Target Support Files/Alamofire/Alamofire.modulemap"; + PRODUCT_MODULE_NAME = Alamofire; + PRODUCT_NAME = Alamofire; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.2; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 0C9896C7563CB84B32833F4506F3096C /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 212D95CBD8FE2141ED259910E0CE9141 /* SwiftyJSON.release.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/SwiftyJSON/SwiftyJSON-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/SwiftyJSON/SwiftyJSON-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "Target Support Files/SwiftyJSON/SwiftyJSON.modulemap"; + PRODUCT_MODULE_NAME = SwiftyJSON; + PRODUCT_NAME = SwiftyJSON; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 1422B121EAEAEA11307496903FA623C6 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_PREPROCESSOR_DEFINITIONS = ( + "POD_CONFIGURATION_RELEASE=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRIP_INSTALLED_PRODUCT = NO; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + SYMROOT = "${SRCROOT}/../build"; + }; + name = Release; + }; + 22AEB76955D631958E4064439A7195CE /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = C41DEC7FBFCE8EDE14BEFBAB8040DCD1 /* Sniffer.debug.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/Sniffer/Sniffer-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/Sniffer/Sniffer-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "Target Support Files/Sniffer/Sniffer.modulemap"; + PRODUCT_MODULE_NAME = Sniffer; + PRODUCT_NAME = Sniffer; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 4AE9FC8A852243DC9A2EFE9BA39BAC7B /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D575BEB1109BF564FCF18F1D5BAF1853 /* GooglePlaces.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 56FB8E57B5B5700A92247289129372FD /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F442A3B41A88AE6EB8303270EA5E0959 /* Pods-ayudapy.release.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; + CLANG_ENABLE_OBJC_WEAK = NO; + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Target Support Files/Pods-ayudapy/Pods-ayudapy-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/Pods-ayudapy/Pods-ayudapy.modulemap"; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 573B04AB9E058A1911353A22A780E993 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 1EFA33EDF9AC66391746D4232E02F9CB /* GooglePlaces.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 624AF9006C0213AC81A593D80AE47826 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = C8CD140BF4C274EB7AFFF56C9729C67F /* Kingfisher.release.xcconfig */; + buildSettings = { + CLANG_ENABLE_OBJC_WEAK = NO; + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/Kingfisher/Kingfisher-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/Kingfisher/Kingfisher-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "Target Support Files/Kingfisher/Kingfisher.modulemap"; + PRODUCT_MODULE_NAME = Kingfisher; + PRODUCT_NAME = Kingfisher; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 8CE5BA3EF4F6FABF028CE30772ECDA11 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = DA331CDC1AADC6340DCCC735DBE0CE92 /* GoogleMaps.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + A86537130E9D1C67331391B89A585CAB /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = CAF2C0EA15FE5EF3384B7BFF1C067360 /* Kingfisher.debug.xcconfig */; + buildSettings = { + CLANG_ENABLE_OBJC_WEAK = NO; + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/Kingfisher/Kingfisher-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/Kingfisher/Kingfisher-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "Target Support Files/Kingfisher/Kingfisher.modulemap"; + PRODUCT_MODULE_NAME = Kingfisher; + PRODUCT_NAME = Kingfisher; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + B79D0A7628A869816BAC153C607F300C /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 3F8A1F92B6938FC3DCDC32EE66382D88 /* Pods-ayudapy.debug.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; + CLANG_ENABLE_OBJC_WEAK = NO; + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Target Support Files/Pods-ayudapy/Pods-ayudapy-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/Pods-ayudapy/Pods-ayudapy.modulemap"; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + C71A2D4C95A058FB8A8D54CE8D8324DD /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5068234F9EF54AAF9E56B52782C9E0EE /* Alamofire.debug.xcconfig */; + buildSettings = { + CLANG_ENABLE_OBJC_WEAK = NO; + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/Alamofire/Alamofire-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/Alamofire/Alamofire-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "Target Support Files/Alamofire/Alamofire.modulemap"; + PRODUCT_MODULE_NAME = Alamofire; + PRODUCT_NAME = Alamofire; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.2; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + E1E452060D51DF935547186CE54B26CC /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E3D29C5E402326372843BDF4B50D2491 /* GoogleMaps.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + ED7888FA6713EABBF66D26A8003AD1CA /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "POD_CONFIGURATION_DEBUG=1", + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRIP_INSTALLED_PRODUCT = NO; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + SYMROOT = "${SRCROOT}/../build"; + }; + name = Debug; + }; + FBC2AF88DDEA6135EF246E82A6249EED /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = C193366879E7D9C7C728BFF6BFFAF16B /* Sniffer.release.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/Sniffer/Sniffer-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/Sniffer/Sniffer-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "Target Support Files/Sniffer/Sniffer.modulemap"; + PRODUCT_MODULE_NAME = Sniffer; + PRODUCT_NAME = Sniffer; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 2FC2AC47FDB0310285CE777F18CD1532 /* Build configuration list for PBXAggregateTarget "GooglePlaces" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4AE9FC8A852243DC9A2EFE9BA39BAC7B /* Debug */, + 573B04AB9E058A1911353A22A780E993 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 3792FD45764CC8C8E562B49EDD176F86 /* Build configuration list for PBXNativeTarget "Sniffer" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 22AEB76955D631958E4064439A7195CE /* Debug */, + FBC2AF88DDEA6135EF246E82A6249EED /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 46921981F609C5BA3CB72EC4EA41299D /* Build configuration list for PBXAggregateTarget "GoogleMaps" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + E1E452060D51DF935547186CE54B26CC /* Debug */, + 8CE5BA3EF4F6FABF028CE30772ECDA11 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 4821239608C13582E20E6DA73FD5F1F9 /* Build configuration list for PBXProject "Pods" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + ED7888FA6713EABBF66D26A8003AD1CA /* Debug */, + 1422B121EAEAEA11307496903FA623C6 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 4B2E6A7DD9BEFE908126937ABFFCB84F /* Build configuration list for PBXNativeTarget "Alamofire" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C71A2D4C95A058FB8A8D54CE8D8324DD /* Debug */, + 0518F89D5C4730E5D1FB793988114CB0 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 62468BF9C7EDA172E951A24083C3ECA7 /* Build configuration list for PBXNativeTarget "SwiftyJSON" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 009DB1577F07B2FF797B78F2D1142134 /* Debug */, + 0C9896C7563CB84B32833F4506F3096C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 95494FDDB35329E11CEBA47EFEA978D8 /* Build configuration list for PBXNativeTarget "Pods-ayudapy" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + B79D0A7628A869816BAC153C607F300C /* Debug */, + 56FB8E57B5B5700A92247289129372FD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + F474713E7462A344D5557EB151CFA07E /* Build configuration list for PBXNativeTarget "Kingfisher" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A86537130E9D1C67331391B89A585CAB /* Debug */, + 624AF9006C0213AC81A593D80AE47826 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = BFDFE7DC352907FC980B868725387E98 /* Project object */; +} diff --git b/Pods/Pods.xcodeproj/xcuserdata/roshka.xcuserdatad/xcschemes/Alamofire.xcscheme a/Pods/Pods.xcodeproj/xcuserdata/roshka.xcuserdatad/xcschemes/Alamofire.xcscheme new file mode 100644 index 0000000..bc06c13 --- /dev/null +++ a/Pods/Pods.xcodeproj/xcuserdata/roshka.xcuserdatad/xcschemes/Alamofire.xcscheme @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git b/Pods/Pods.xcodeproj/xcuserdata/roshka.xcuserdatad/xcschemes/GoogleMaps.xcscheme a/Pods/Pods.xcodeproj/xcuserdata/roshka.xcuserdatad/xcschemes/GoogleMaps.xcscheme new file mode 100644 index 0000000..114deb5 --- /dev/null +++ a/Pods/Pods.xcodeproj/xcuserdata/roshka.xcuserdatad/xcschemes/GoogleMaps.xcscheme @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git b/Pods/Pods.xcodeproj/xcuserdata/roshka.xcuserdatad/xcschemes/GooglePlaces.xcscheme a/Pods/Pods.xcodeproj/xcuserdata/roshka.xcuserdatad/xcschemes/GooglePlaces.xcscheme new file mode 100644 index 0000000..785ad22 --- /dev/null +++ a/Pods/Pods.xcodeproj/xcuserdata/roshka.xcuserdatad/xcschemes/GooglePlaces.xcscheme @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git b/Pods/Pods.xcodeproj/xcuserdata/roshka.xcuserdatad/xcschemes/Kingfisher.xcscheme a/Pods/Pods.xcodeproj/xcuserdata/roshka.xcuserdatad/xcschemes/Kingfisher.xcscheme new file mode 100644 index 0000000..32d098a --- /dev/null +++ a/Pods/Pods.xcodeproj/xcuserdata/roshka.xcuserdatad/xcschemes/Kingfisher.xcscheme @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git b/Pods/Pods.xcodeproj/xcuserdata/roshka.xcuserdatad/xcschemes/Pods-ayudapy.xcscheme a/Pods/Pods.xcodeproj/xcuserdata/roshka.xcuserdatad/xcschemes/Pods-ayudapy.xcscheme new file mode 100644 index 0000000..326d84c --- /dev/null +++ a/Pods/Pods.xcodeproj/xcuserdata/roshka.xcuserdatad/xcschemes/Pods-ayudapy.xcscheme @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git b/Pods/Pods.xcodeproj/xcuserdata/roshka.xcuserdatad/xcschemes/Sniffer.xcscheme a/Pods/Pods.xcodeproj/xcuserdata/roshka.xcuserdatad/xcschemes/Sniffer.xcscheme new file mode 100644 index 0000000..1e3b3b6 --- /dev/null +++ a/Pods/Pods.xcodeproj/xcuserdata/roshka.xcuserdatad/xcschemes/Sniffer.xcscheme @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git b/Pods/Pods.xcodeproj/xcuserdata/roshka.xcuserdatad/xcschemes/SwiftyJSON.xcscheme a/Pods/Pods.xcodeproj/xcuserdata/roshka.xcuserdatad/xcschemes/SwiftyJSON.xcscheme new file mode 100644 index 0000000..24941c0 --- /dev/null +++ a/Pods/Pods.xcodeproj/xcuserdata/roshka.xcuserdatad/xcschemes/SwiftyJSON.xcscheme @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git b/Pods/Pods.xcodeproj/xcuserdata/roshka.xcuserdatad/xcschemes/xcschememanagement.plist a/Pods/Pods.xcodeproj/xcuserdata/roshka.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..f37f47a --- /dev/null +++ a/Pods/Pods.xcodeproj/xcuserdata/roshka.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,60 @@ + + + + + SchemeUserState + + Alamofire.xcscheme + + isShown + + orderHint + 0 + + GoogleMaps.xcscheme + + isShown + + orderHint + 1 + + GooglePlaces.xcscheme + + isShown + + orderHint + 2 + + Kingfisher.xcscheme + + isShown + + orderHint + 3 + + Pods-ayudapy.xcscheme + + isShown + + orderHint + 4 + + Sniffer.xcscheme + + isShown + + orderHint + 5 + + SwiftyJSON.xcscheme + + isShown + + orderHint + 6 + + + SuppressBuildableAutocreation + + + diff --git b/Pods/Sniffer/LICENSE a/Pods/Sniffer/LICENSE new file mode 100644 index 0000000..eeecf9e --- /dev/null +++ a/Pods/Sniffer/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Taeun Kim + +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. diff --git b/Pods/Sniffer/README.md a/Pods/Sniffer/README.md new file mode 100644 index 0000000..7c33f5f --- /dev/null +++ a/Pods/Sniffer/README.md @@ -0,0 +1,100 @@ +# Sniffer + +[![Build Status](https://travis-ci.org/Kofktu/Sniffer.svg?branch=master)](https://travis-ci.org/Kofktu/Sniffer) +![Swift](https://img.shields.io/badge/Swift-5.0-orange.svg) +[![CocoaPods](http://img.shields.io/cocoapods/v/Sniffer.svg?style=flat)](http://cocoapods.org/?q=name%3ASniffer%20author%3AKofktu) +[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) + +- Automatic networking activity logger +- intercepting any outgoing requests and incoming responses for debugging purposes. + +![alt tag](Screenshot/Sample.png) + +## Requirements +- iOS 8.0+, macOS 10.9+, watchOS 2.0+, tvOS 9.0+ +- Swift 5.0 +- Swift 4.2 ([1.7.0](https://github.com/Kofktu/Sniffer/tree/1.7.0)) +- Swift 4.0 ([1.5.0](https://github.com/Kofktu/Sniffer/tree/1.5.0)) +- Swift 3.0 ([1.0.6](https://github.com/Kofktu/Sniffer/tree/1.0.6)) + +## Example +To run the example project, clone the repo, and run `pod install` from the Example directory first. + +## Installation + +#### CocoaPods +Sniffer is available through [CocoaPods](http://cocoapods.org). To install +it, simply add the following line to your Podfile: + +```ruby +pod "Sniffer", '~> 2.0' +``` + +#### Carthage +For iOS 8+ projects with [Carthage](https://github.com/Carthage/Carthage) + +``` +github "Kofktu/Sniffer" +``` + +## Usage + +#### for any requests you make via 'URLSession' + +```swift +func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { + Sniffer.register() // Register Sniffer to log all requests + return true +} +``` + +#### for URLSessionConfiguration + +```swift +let configuration = URLSessionConfiguration.default +Sniffer.enable(in: configuration) +``` + +#### for Custom Deserializer + +```swift +public protocol BodyDeserializer { + func deserialize(body: Data) -> String? +} + +public final class CustomTextBodyDeserializer: BodyDeserializer { + public func deserialize(body: Data) -> String? { + // customization + return String? + } +} + +Sniffer.register(deserializer: CustomTextBodyDeserializer(), for: ["text/plain"]) + +``` + +#### If you want to process the logs directly in your application + +```swift +// Register the handler if you want the log to be handled directly by the application +Sniffer.onLogger = { (url, log) in + print("\(url) : \(log)") +} +``` + +#### If you want to ignore domains +```swift +Sniffer.ignore(domains: ["github.com"]) +``` + +## References +- Timberjack (https://github.com/andysmart/Timberjack) +- ResponseDetective (https://github.com/netguru/ResponseDetective) + +## Authors + +Taeun Kim (kofktu), + +## License + +Sniffer is available under the ```MIT``` license. See the ```LICENSE``` file for more info. diff --git b/Pods/Sniffer/Sniffer/Classes/BodyDeserializer.swift a/Pods/Sniffer/Sniffer/Classes/BodyDeserializer.swift new file mode 100644 index 0000000..631ed65 --- /dev/null +++ a/Pods/Sniffer/Sniffer/Classes/BodyDeserializer.swift @@ -0,0 +1,65 @@ +// +// BodyDeserializer.swift +// Sniffer +// +// Created by kofktu on 2017. 2. 16.. +// Copyright © 2017년 Kofktu. All rights reserved. +// + +import Foundation + +#if os(iOS) || os(tvOS) || os(watchOS) + import UIKit +#elseif os(OSX) + import AppKit +#endif + +public protocol BodyDeserializer { + func deserialize(body: Data) -> String? +} + +public final class PlainTextBodyDeserializer: BodyDeserializer { + public func deserialize(body: Data) -> String? { + return String(data: body, encoding: .utf8) + } +} + +public final class JSONBodyDeserializer: BodyDeserializer { + public func deserialize(body: Data) -> String? { + do { + let obj = try JSONSerialization.jsonObject(with: body, options: []) + let data = try JSONSerialization.data(withJSONObject: obj, options: .prettyPrinted) + return String(data: data, encoding: .utf8) + } catch { + return nil + } + } +} + +public final class HTMLBodyDeserializer: BodyDeserializer { + public func deserialize(body: Data) -> String? { + do { + let attr = try NSAttributedString( + data: body, + options: [NSAttributedString.DocumentReadingOptionKey.documentType : NSAttributedString.DocumentType.html, + NSAttributedString.DocumentReadingOptionKey.characterEncoding: String.Encoding.utf8.rawValue + ], + documentAttributes: nil) + return attr.string + } catch { + return nil + } + } +} + +public final class UIImageBodyDeserializer: BodyDeserializer { + #if os(iOS) || os(tvOS) || os(watchOS) + private typealias Image = UIImage + #elseif os(OSX) + private typealias Image = NSImage + #endif + + public func deserialize(body: Data) -> String? { + return Image(data: body).map { "image = [ \(Int($0.size.width)) x \(Int($0.size.height)) ]" } + } +} diff --git b/Pods/Sniffer/Sniffer/Classes/Sniffer.swift a/Pods/Sniffer/Sniffer/Classes/Sniffer.swift new file mode 100644 index 0000000..4a79c6d --- /dev/null +++ a/Pods/Sniffer/Sniffer/Classes/Sniffer.swift @@ -0,0 +1,307 @@ +// +// Sniffer.swift +// Sniffer +// +// Created by kofktu on 2017. 2. 15.. +// Copyright © 2017년 Kofktu. All rights reserved. +// + +import Foundation + +public class Sniffer: URLProtocol { + + private enum Keys { + static let request = "Sniffer.request" + static let duration = "Sniffer.duration" + } + + static public var onLogger: ((URL, String) -> Void)? // If the handler is registered, the log inside the Sniffer will not be output. + static private var ignoreDomains: [String]? + + private lazy var session: URLSession = URLSession(configuration: URLSessionConfiguration.default, delegate: self, delegateQueue: nil) + private var urlTask: URLSessionDataTask? + private var urlRequest: NSMutableURLRequest? + private var urlResponse: HTTPURLResponse? + private var data: Data? + private let serialQueue = DispatchQueue(label: "com.kofktu.sniffer.serialQueue") + + private static var bodyDeserializers: [String: BodyDeserializer] = [ + "application/x-www-form-urlencoded": PlainTextBodyDeserializer(), + "*/json": JSONBodyDeserializer(), + "image/*": UIImageBodyDeserializer(), + "text/plain": PlainTextBodyDeserializer(), + "*/html": HTMLBodyDeserializer() + ] + + public class func register() { + URLProtocol.registerClass(self) + } + + public class func unregister() { + URLProtocol.unregisterClass(self) + } + + public class func enable(in configuration: URLSessionConfiguration) { + configuration.protocolClasses?.insert(Sniffer.self, at: 0) + } + + public class func register(deserializer: BodyDeserializer, `for` contentTypes: [String]) { + for contentType in contentTypes { + guard contentType.components(separatedBy: "/").count == 2 else { continue } + bodyDeserializers[contentType] = deserializer + } + } + + public class func ignore(domains: [String]) { + ignoreDomains = domains + } + + // MARK: - URLProtocol + open override class func canInit(with request: URLRequest) -> Bool { + guard let url = request.url, let scheme = url.scheme else { return false } + guard !isIgnore(with: url) else { return false } + return ["http", "https"].contains(scheme) && self.property(forKey: Keys.request, in: request) == nil + } + + open override class func canonicalRequest(for request: URLRequest) -> URLRequest { + return request + } + + private class func isIgnore(with url: URL) -> Bool { + guard let ignoreDomains = ignoreDomains, !ignoreDomains.isEmpty, + let host = url.host else { + return false + } + return ignoreDomains.first { $0.range(of: host) != nil } != nil + } + + open override func startLoading() { + if let _ = urlTask { return } + guard let urlRequest = (request as NSURLRequest).mutableCopy() as? NSMutableURLRequest , self.urlRequest == nil else { return } + + self.urlRequest = urlRequest + + Sniffer.setProperty(true, forKey: Keys.request, in: urlRequest) + Sniffer.setProperty(Date(), forKey: Keys.duration, in: urlRequest) + + log(request: urlRequest as URLRequest) + + urlTask = session.dataTask(with: request) + urlTask?.resume() + } + + open override func stopLoading() { + serialQueue.sync { [weak self] in + self?.urlTask?.cancel() + self?.urlTask = nil + self?.session.invalidateAndCancel() + } + } + + // MARK: - Private + + private func log(_ string: String) { + if let _ = Sniffer.onLogger { + urlRequest?.url.flatMap { + Sniffer.onLogger?($0, string) + } + } else { + print(string) + } + } + + fileprivate func clear() { + defer { + urlTask = nil + urlRequest = nil + urlResponse = nil + data = nil + } + + guard let urlRequest = urlRequest else { return } + + Sniffer.removeProperty(forKey: Keys.request, in: urlRequest) + Sniffer.removeProperty(forKey: Keys.duration, in: urlRequest) + } + + fileprivate var logDivider: String { + return "===========================================================" + } + + fileprivate var errorLogDivider: String { + return "===========================ERROR===========================" + } + + fileprivate func log(headers: [String: String]?) -> String { + guard let headers = headers, !headers.isEmpty else { return "" } + + var values: [String] = [] + values.append("Headers: [") + for (key, value) in headers { + values.append(" \(key) : \(value)") + } + values.append("]") + return values.joined(separator: "\n") + } + + private func body(from request: URLRequest) -> Data? { + return request.httpBody ?? request.httpBodyStream.flatMap { stream in + let data = NSMutableData() + stream.open() + while stream.hasBytesAvailable { + var buffer = [UInt8](repeating: 0, count: 1024) + let length = stream.read(&buffer, maxLength: buffer.count) + data.append(buffer, length: length) + } + stream.close() + return data as Data + } + } + + private func find(deserialize contentType: String) -> BodyDeserializer? { + for (pattern, deserializer) in Sniffer.bodyDeserializers { + do { + let regex = try NSRegularExpression(pattern: pattern.replacingOccurrences(of: "*", with: "[a-z]+")) + let results = regex.matches(in: contentType, range: NSRange(location: 0, length: contentType.count)) + + if !results.isEmpty { + return deserializer + } + } catch { + continue + } + } + + return nil + } + + private func deserialize(body: Data, `for` contentType: String) -> String? { + return find(deserialize: contentType)?.deserialize(body: body) + } + + fileprivate func log(body request: URLRequest) -> String { + guard let body = body(from: request) else { return "" } + + var result: [String] = [] + + if let deserialized = deserialize(body: body, for: request.value(forHTTPHeaderField: "Content-Type") ?? "application/octet-stream") { + result.append("Body: [") + result.append(deserialized) + result.append("]") + } + + return result.filter { !$0.isEmpty }.joined(separator: "\n") + } + + fileprivate func log(request: URLRequest) { + var result: [String] = [] + result.append(logDivider) + + if let method = request.httpMethod, let url = request.url?.absoluteString { + result.append("Request [\(method)] : \(url)") + } + + result.append(log(headers: request.allHTTPHeaderFields)) + result.append(log(body: request)) + result.append(logDivider) + + log(result.filter { !$0.isEmpty }.joined(separator: "\n")) + } + + fileprivate func log(response: URLResponse, data: Data?) { + var result: [String] = [] + result.append(logDivider) + + var contentType = "application/octet-stream" + + if let url = response.url?.absoluteString { + result.append("Response : \(url)") + } + + if let httpResponse = response as? HTTPURLResponse { + let localisedStatus = HTTPURLResponse.localizedString(forStatusCode: httpResponse.statusCode).capitalized + result.append("Status: \(httpResponse.statusCode) - \(localisedStatus)") + result.append(log(headers: httpResponse.allHeaderFields as? [String: String])) + + if let type = httpResponse.allHeaderFields["Content-Type"] as? String { + contentType = type + } + } + + if let urlRequest = urlRequest as URLRequest?, let startDate = Sniffer.property(forKey: Keys.duration, in: urlRequest) as? Date { + let difference = fabs(startDate.timeIntervalSinceNow) + result.append("Duration: \(difference)s") + } + + if let body = data, + let deserialize = self.deserialize(body: body, for: contentType) ?? PlainTextBodyDeserializer().deserialize(body: body) { + result.append("Body: [") + result.append(deserialize) + result.append("]") + } + + result.append(logDivider) + + log(result.filter { !$0.isEmpty }.joined(separator: "\n")) + } + + fileprivate func log(error: Error?) -> String { + guard let error = error else { return "" } + + var result: [String] = [] + result.append(errorLogDivider) + + let nsError = error as NSError + + result.append("Code : \(nsError.code)") + result.append("Description : \(nsError.localizedDescription)") + + if let reason = nsError.localizedFailureReason { + result.append("Reason : \(reason)") + } + if let suggestion = nsError.localizedRecoverySuggestion { + result.append("Suggestion : \(suggestion)") + } + + result.append(logDivider) + + return result.filter { !$0.isEmpty }.joined(separator: "\n") + } +} + +extension Sniffer: URLSessionTaskDelegate, URLSessionDataDelegate { + + // MARK: - NSURLSessionDataDelegate + public func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) { + client?.urlProtocol(self, wasRedirectedTo: request, redirectResponse: response) + } + + public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) { + urlResponse = response as? HTTPURLResponse + data = Data() + + completionHandler(.allow) + client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .allowed) + } + + public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { + self.data?.append(data) + client?.urlProtocol(self, didLoad: data) + } + + public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { + if let error = error { + log(log(error: error)) + client?.urlProtocol(self, didFailWithError: error) + } else if let urlResponse = urlResponse { + log(response: urlResponse, data: data) + client?.urlProtocolDidFinishLoading(self) + } + + serialQueue.sync { [weak self] in + self?.clear() + } + session.finishTasksAndInvalidate() + } + +} diff --git b/Pods/SwiftyJSON/LICENSE a/Pods/SwiftyJSON/LICENSE new file mode 100644 index 0000000..68e3fd7 --- /dev/null +++ a/Pods/SwiftyJSON/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2017 Ruoyu Fu + +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. diff --git b/Pods/SwiftyJSON/README.md a/Pods/SwiftyJSON/README.md new file mode 100644 index 0000000..7a10e29 --- /dev/null +++ a/Pods/SwiftyJSON/README.md @@ -0,0 +1,562 @@ +# SwiftyJSON + +[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) ![CocoaPods](https://img.shields.io/cocoapods/v/SwiftyJSON.svg) ![Platform](https://img.shields.io/badge/platforms-iOS%208.0%20%7C%20macOS%2010.10%20%7C%20tvOS%209.0%20%7C%20watchOS%203.0-F28D00.svg) [![Reviewed by Hound](https://img.shields.io/badge/Reviewed_by-Hound-8E64B0.svg)](https://houndci.com) + +SwiftyJSON makes it easy to deal with JSON data in Swift. + +Platform | Build Status +---------| --------------| +*OS | [![Travis CI](https://travis-ci.org/SwiftyJSON/SwiftyJSON.svg?branch=master)](https://travis-ci.org/SwiftyJSON/SwiftyJSON) | +[Linux](https://github.com/IBM-Swift/SwiftyJSON) | [![Build Status](https://travis-ci.org/IBM-Swift/SwiftyJSON.svg?branch=master)](https://travis-ci.org/IBM-Swift/SwiftyJSON) | + + +1. [Why is the typical JSON handling in Swift NOT good](#why-is-the-typical-json-handling-in-swift-not-good) +2. [Requirements](#requirements) +3. [Integration](#integration) +4. [Usage](#usage) + - [Initialization](#initialization) + - [Subscript](#subscript) + - [Loop](#loop) + - [Error](#error) + - [Optional getter](#optional-getter) + - [Non-optional getter](#non-optional-getter) + - [Setter](#setter) + - [Raw object](#raw-object) + - [Literal convertibles](#literal-convertibles) + - [Merging](#merging) +5. [Work with Alamofire](#work-with-alamofire) +6. [Work with Moya](#work-with-moya) +7. [SwiftyJSON Model Generator](#swiftyjson-model-generator) + +> [中文介绍](http://tangplin.github.io/swiftyjson/) + + +## Why is the typical JSON handling in Swift NOT good? + +Swift is very strict about types. But although explicit typing is good for saving us from mistakes, it becomes painful when dealing with JSON and other areas that are, by nature, implicit about types. + +Take the Twitter API for example. Say we want to retrieve a user's "name" value of some tweet in Swift (according to [Twitter's API](https://developer.twitter.com/en/docs/tweets/timelines/api-reference/get-statuses-home_timeline)). + +The code would look like this: + +```swift +if let statusesArray = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [[String: Any]], + let user = statusesArray[0]["user"] as? [String: Any], + let username = user["name"] as? String { + // Finally we got the username +} +``` + +It's not good. + +Even if we use optional chaining, it would be messy: + +```swift +if let JSONObject = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [[String: Any]], + let username = (JSONObject[0]["user"] as? [String: Any])?["name"] as? String { + // There's our username +} +``` + +An unreadable mess--for something that should really be simple! + +With SwiftyJSON all you have to do is: + +```swift +let json = JSON(data: dataFromNetworking) +if let userName = json[0]["user"]["name"].string { + //Now you got your value +} +``` + +And don't worry about the Optional Wrapping thing. It's done for you automatically. + +```swift +let json = JSON(data: dataFromNetworking) +let result = json[999999]["wrong_key"]["wrong_name"] +if let userName = result.string { + //Calm down, take it easy, the ".string" property still produces the correct Optional String type with safety +} else { + //Print the error + print(result.error) +} +``` + +## Requirements + +- iOS 8.0+ | macOS 10.10+ | tvOS 9.0+ | watchOS 2.0+ +- Xcode 8 + +## Integration + +#### CocoaPods (iOS 8+, OS X 10.9+) + +You can use [CocoaPods](http://cocoapods.org/) to install `SwiftyJSON` by adding it to your `Podfile`: + +```ruby +platform :ios, '8.0' +use_frameworks! + +target 'MyApp' do + pod 'SwiftyJSON', '~> 4.0' +end +``` + +#### Carthage (iOS 8+, OS X 10.9+) + +You can use [Carthage](https://github.com/Carthage/Carthage) to install `SwiftyJSON` by adding it to your `Cartfile`: + +``` +github "SwiftyJSON/SwiftyJSON" ~> 4.0 +``` + +If you use Carthage to build your dependencies, make sure you have added `SwiftyJSON.framework` to the "Linked Frameworks and Libraries" section of your target, and have included them in your Carthage framework copying build phase. + +#### Swift Package Manager + +You can use [The Swift Package Manager](https://swift.org/package-manager) to install `SwiftyJSON` by adding the proper description to your `Package.swift` file: + +```swift +// swift-tools-version:4.0 +import PackageDescription + +let package = Package( + name: "YOUR_PROJECT_NAME", + dependencies: [ + .package(url: "https://github.com/SwiftyJSON/SwiftyJSON.git", from: "4.0.0"), + ] +) +``` +Then run `swift build` whenever you get prepared. + +#### Manually (iOS 7+, OS X 10.9+) + +To use this library in your project manually you may: + +1. for Projects, just drag SwiftyJSON.swift to the project tree +2. for Workspaces, include the whole SwiftyJSON.xcodeproj + +## Usage + +#### Initialization + +```swift +import SwiftyJSON +``` + +```swift +let json = JSON(data: dataFromNetworking) +``` +Or + +```swift +let json = JSON(jsonObject) +``` +Or + +```swift +if let dataFromString = jsonString.data(using: .utf8, allowLossyConversion: false) { + let json = JSON(data: dataFromString) +} +``` + +#### Subscript + +```swift +// Getting a double from a JSON Array +let name = json[0].double +``` + +```swift +// Getting an array of string from a JSON Array +let arrayNames = json["users"].arrayValue.map {$0["name"].stringValue} +``` + +```swift +// Getting a string from a JSON Dictionary +let name = json["name"].stringValue +``` + +```swift +// Getting a string using a path to the element +let path: [JSONSubscriptType] = [1,"list",2,"name"] +let name = json[path].string +// Just the same +let name = json[1]["list"][2]["name"].string +// Alternatively +let name = json[1,"list",2,"name"].string +``` + +```swift +// With a hard way +let name = json[].string +``` + +```swift +// With a custom way +let keys:[JSONSubscriptType] = [1,"list",2,"name"] +let name = json[keys].string +``` + +#### Loop + +```swift +// If json is .Dictionary +for (key,subJson):(String, JSON) in json { + // Do something you want +} +``` + +*The first element is always a String, even if the JSON is an Array* + +```swift +// If json is .Array +// The `index` is 0.. = json["list"].arrayValue +``` + +```swift +// If not a Dictionary or nil, return [:] +let user: Dictionary = json["user"].dictionaryValue +``` + +#### Setter + +```swift +json["name"] = JSON("new-name") +json[0] = JSON(1) +``` + +```swift +json["id"].int = 1234567890 +json["coordinate"].double = 8766.766 +json["name"].string = "Jack" +json.arrayObject = [1,2,3,4] +json.dictionaryObject = ["name":"Jack", "age":25] +``` + +#### Raw object + +```swift +let rawObject: Any = json.object +``` + +```swift +let rawValue: Any = json.rawValue +``` + +```swift +//convert the JSON to raw NSData +do { + let rawData = try json.rawData() + //Do something you want +} catch { + print("Error \(error)") +} +``` + +```swift +//convert the JSON to a raw String +if let rawString = json.rawString() { + //Do something you want +} else { + print("json.rawString is nil") +} +``` + +#### Existence + +```swift +// shows you whether value specified in JSON or not +if json["name"].exists() +``` + +#### Literal convertibles + +For more info about literal convertibles: [Swift Literal Convertibles](http://nshipster.com/swift-literal-convertible/) + +```swift +// StringLiteralConvertible +let json: JSON = "I'm a json" +``` + +```swift +/ /IntegerLiteralConvertible +let json: JSON = 12345 +``` + +```swift +// BooleanLiteralConvertible +let json: JSON = true +``` + +```swift +// FloatLiteralConvertible +let json: JSON = 2.8765 +``` + +```swift +// DictionaryLiteralConvertible +let json: JSON = ["I":"am", "a":"json"] +``` + +```swift +// ArrayLiteralConvertible +let json: JSON = ["I", "am", "a", "json"] +``` + +```swift +// With subscript in array +var json: JSON = [1,2,3] +json[0] = 100 +json[1] = 200 +json[2] = 300 +json[999] = 300 // Don't worry, nothing will happen +``` + +```swift +// With subscript in dictionary +var json: JSON = ["name": "Jack", "age": 25] +json["name"] = "Mike" +json["age"] = "25" // It's OK to set String +json["address"] = "L.A." // Add the "address": "L.A." in json +``` + +```swift +// Array & Dictionary +var json: JSON = ["name": "Jack", "age": 25, "list": ["a", "b", "c", ["what": "this"]]] +json["list"][3]["what"] = "that" +json["list",3,"what"] = "that" +let path: [JSONSubscriptType] = ["list",3,"what"] +json[path] = "that" +``` + +```swift +// With other JSON objects +let user: JSON = ["username" : "Steve", "password": "supersecurepassword"] +let auth: JSON = [ + "user": user.object, // use user.object instead of just user + "apikey": "supersecretapitoken" +] +``` + +#### Merging + +It is possible to merge one JSON into another JSON. Merging a JSON into another JSON adds all non existing values to the original JSON which are only present in the `other` JSON. + +If both JSONs contain a value for the same key, _mostly_ this value gets overwritten in the original JSON, but there are two cases where it provides some special treatment: + +- In case of both values being a `JSON.Type.array` the values form the array found in the `other` JSON getting appended to the original JSON's array value. +- In case of both values being a `JSON.Type.dictionary` both JSON-values are getting merged the same way the encapsulating JSON is merged. + +In case, where two fields in a JSON have a different types, the value will get always overwritten. + +There are two different fashions for merging: `merge` modifies the original JSON, whereas `merged` works non-destructively on a copy. + +```swift +let original: JSON = [ + "first_name": "John", + "age": 20, + "skills": ["Coding", "Reading"], + "address": [ + "street": "Front St", + "zip": "12345", + ] +] + +let update: JSON = [ + "last_name": "Doe", + "age": 21, + "skills": ["Writing"], + "address": [ + "zip": "12342", + "city": "New York City" + ] +] + +let updated = original.merge(with: update) +// [ +// "first_name": "John", +// "last_name": "Doe", +// "age": 21, +// "skills": ["Coding", "Reading", "Writing"], +// "address": [ +// "street": "Front St", +// "zip": "12342", +// "city": "New York City" +// ] +// ] +``` + +## String representation +There are two options available: +- use the default Swift one +- use a custom one that will handle optionals well and represent `nil` as `"null"`: +```swift +let dict = ["1":2, "2":"two", "3": nil] as [String: Any?] +let json = JSON(dict) +let representation = json.rawString(options: [.castNilToNSNull: true]) +// representation is "{\"1\":2,\"2\":\"two\",\"3\":null}", which represents {"1":2,"2":"two","3":null} +``` + +## Work with [Alamofire](https://github.com/Alamofire/Alamofire) + +SwiftyJSON nicely wraps the result of the Alamofire JSON response handler: + +```swift +Alamofire.request(url, method: .get).validate().responseJSON { response in + switch response.result { + case .success(let value): + let json = JSON(value) + print("JSON: \(json)") + case .failure(let error): + print(error) + } +} +``` + +We also provide an extension of Alamofire for serializing NSData to SwiftyJSON's JSON. + +See: [Alamofire-SwiftyJSON](https://github.com/SwiftyJSON/Alamofire-SwiftyJSON) + + +## Work with [Moya](https://github.com/Moya/Moya) + +SwiftyJSON parse data to JSON: + +```swift +let provider = MoyaProvider() +provider.request(.showProducts) { result in + switch result { + case let .success(moyaResponse): + let data = moyaResponse.data + let json = JSON(data: data) // convert network data to json + print(json) + case let .failure(error): + print("error: \(error)") + } +} + +``` + +## SwiftyJSON Model Generator +Tools to generate SwiftyJSON Models +* [JSON Cafe](http://www.jsoncafe.com/) +* [JSON Export](https://github.com/Ahmed-Ali/JSONExport) diff --git b/Pods/SwiftyJSON/Source/SwiftyJSON/SwiftyJSON.swift a/Pods/SwiftyJSON/Source/SwiftyJSON/SwiftyJSON.swift new file mode 100644 index 0000000..f7a3f08 --- /dev/null +++ a/Pods/SwiftyJSON/Source/SwiftyJSON/SwiftyJSON.swift @@ -0,0 +1,1401 @@ +// SwiftyJSON.swift +// +// Copyright (c) 2014 - 2017 Ruoyu Fu, Pinglin Tang +// +// 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: - Error +// swiftlint:disable line_length +public enum SwiftyJSONError: Int, Swift.Error { + case unsupportedType = 999 + case indexOutOfBounds = 900 + case elementTooDeep = 902 + case wrongType = 901 + case notExist = 500 + case invalidJSON = 490 +} + +extension SwiftyJSONError: CustomNSError { + + /// return the error domain of SwiftyJSONError + public static var errorDomain: String { return "com.swiftyjson.SwiftyJSON" } + + /// return the error code of SwiftyJSONError + public var errorCode: Int { return self.rawValue } + + /// return the userInfo of SwiftyJSONError + public var errorUserInfo: [String: Any] { + switch self { + case .unsupportedType: + return [NSLocalizedDescriptionKey: "It is an unsupported type."] + case .indexOutOfBounds: + return [NSLocalizedDescriptionKey: "Array Index is out of bounds."] + case .wrongType: + return [NSLocalizedDescriptionKey: "Couldn't merge, because the JSONs differ in type on top level."] + case .notExist: + return [NSLocalizedDescriptionKey: "Dictionary key does not exist."] + case .invalidJSON: + return [NSLocalizedDescriptionKey: "JSON is invalid."] + case .elementTooDeep: + return [NSLocalizedDescriptionKey: "Element too deep. Increase maxObjectDepth and make sure there is no reference loop."] + } + } +} + +// MARK: - JSON Type + +/** +JSON's type definitions. + +See http://www.json.org +*/ +public enum Type: Int { + case number + case string + case bool + case array + case dictionary + case null + case unknown +} + +// MARK: - JSON Base + +public struct JSON { + + /** + Creates a JSON using the data. + + - parameter data: The NSData used to convert to json.Top level object in data is an NSArray or NSDictionary + - parameter opt: The JSON serialization reading options. `[]` by default. + + - returns: The created JSON + */ + public init(data: Data, options opt: JSONSerialization.ReadingOptions = []) throws { + let object: Any = try JSONSerialization.jsonObject(with: data, options: opt) + self.init(jsonObject: object) + } + + /** + Creates a JSON object + - note: this does not parse a `String` into JSON, instead use `init(parseJSON: String)` + + - parameter object: the object + + - returns: the created JSON object + */ + public init(_ object: Any) { + switch object { + case let object as Data: + do { + try self.init(data: object) + } catch { + self.init(jsonObject: NSNull()) + } + default: + self.init(jsonObject: object) + } + } + + /** + Parses the JSON string into a JSON object + + - parameter json: the JSON string + + - returns: the created JSON object + */ + public init(parseJSON jsonString: String) { + if let data = jsonString.data(using: .utf8) { + self.init(data) + } else { + self.init(NSNull()) + } + } + + /** + Creates a JSON using the object. + + - parameter jsonObject: The object must have the following properties: All objects are NSString/String, NSNumber/Int/Float/Double/Bool, NSArray/Array, NSDictionary/Dictionary, or NSNull; All dictionary keys are NSStrings/String; NSNumbers are not NaN or infinity. + + - returns: The created JSON + */ + fileprivate init(jsonObject: Any) { + object = jsonObject + } + + /** + Merges another JSON into this JSON, whereas primitive values which are not present in this JSON are getting added, + present values getting overwritten, array values getting appended and nested JSONs getting merged the same way. + + - parameter other: The JSON which gets merged into this JSON + + - throws `ErrorWrongType` if the other JSONs differs in type on the top level. + */ + public mutating func merge(with other: JSON) throws { + try self.merge(with: other, typecheck: true) + } + + /** + Merges another JSON into this JSON and returns a new JSON, whereas primitive values which are not present in this JSON are getting added, + present values getting overwritten, array values getting appended and nested JSONS getting merged the same way. + + - parameter other: The JSON which gets merged into this JSON + + - throws `ErrorWrongType` if the other JSONs differs in type on the top level. + + - returns: New merged JSON + */ + public func merged(with other: JSON) throws -> JSON { + var merged = self + try merged.merge(with: other, typecheck: true) + return merged + } + + /** + Private woker function which does the actual merging + Typecheck is set to true for the first recursion level to prevent total override of the source JSON + */ + fileprivate mutating func merge(with other: JSON, typecheck: Bool) throws { + if type == other.type { + switch type { + case .dictionary: + for (key, _) in other { + try self[key].merge(with: other[key], typecheck: false) + } + case .array: + self = JSON(arrayValue + other.arrayValue) + default: + self = other + } + } else { + if typecheck { + throw SwiftyJSONError.wrongType + } else { + self = other + } + } + } + + /// Private object + fileprivate var rawArray: [Any] = [] + fileprivate var rawDictionary: [String: Any] = [:] + fileprivate var rawString: String = "" + fileprivate var rawNumber: NSNumber = 0 + fileprivate var rawNull: NSNull = NSNull() + fileprivate var rawBool: Bool = false + + /// JSON type, fileprivate setter + public fileprivate(set) var type: Type = .null + + /// Error in JSON, fileprivate setter + public fileprivate(set) var error: SwiftyJSONError? + + /// Object in JSON + public var object: Any { + get { + switch type { + case .array: return rawArray + case .dictionary: return rawDictionary + case .string: return rawString + case .number: return rawNumber + case .bool: return rawBool + default: return rawNull + } + } + set { + error = nil + switch unwrap(newValue) { + case let number as NSNumber: + if number.isBool { + type = .bool + rawBool = number.boolValue + } else { + type = .number + rawNumber = number + } + case let string as String: + type = .string + rawString = string + case _ as NSNull: + type = .null + case nil: + type = .null + case let array as [Any]: + type = .array + rawArray = array + case let dictionary as [String: Any]: + type = .dictionary + rawDictionary = dictionary + default: + type = .unknown + error = SwiftyJSONError.unsupportedType + } + } + } + + /// The static null JSON + @available(*, unavailable, renamed:"null") + public static var nullJSON: JSON { return null } + public static var null: JSON { return JSON(NSNull()) } +} + +/// Private method to unwarp an object recursively +private func unwrap(_ object: Any) -> Any { + switch object { + case let json as JSON: + return unwrap(json.object) + case let array as [Any]: + return array.map(unwrap) + case let dictionary as [String: Any]: + var d = dictionary + dictionary.forEach { pair in + d[pair.key] = unwrap(pair.value) + } + return d + default: + return object + } +} + +public enum Index: Comparable { + case array(Int) + case dictionary(DictionaryIndex) + case null + + static public func == (lhs: Index, rhs: Index) -> Bool { + switch (lhs, rhs) { + case (.array(let left), .array(let right)): return left == right + case (.dictionary(let left), .dictionary(let right)): return left == right + case (.null, .null): return true + default: return false + } + } + + static public func < (lhs: Index, rhs: Index) -> Bool { + switch (lhs, rhs) { + case (.array(let left), .array(let right)): return left < right + case (.dictionary(let left), .dictionary(let right)): return left < right + default: return false + } + } +} + +public typealias JSONIndex = Index +public typealias JSONRawIndex = Index + +extension JSON: Swift.Collection { + + public typealias Index = JSONRawIndex + + public var startIndex: Index { + switch type { + case .array: return .array(rawArray.startIndex) + case .dictionary: return .dictionary(rawDictionary.startIndex) + default: return .null + } + } + + public var endIndex: Index { + switch type { + case .array: return .array(rawArray.endIndex) + case .dictionary: return .dictionary(rawDictionary.endIndex) + default: return .null + } + } + + public func index(after i: Index) -> Index { + switch i { + case .array(let idx): return .array(rawArray.index(after: idx)) + case .dictionary(let idx): return .dictionary(rawDictionary.index(after: idx)) + default: return .null + } + } + + public subscript (position: Index) -> (String, JSON) { + switch position { + case .array(let idx): return (String(idx), JSON(rawArray[idx])) + case .dictionary(let idx): return (rawDictionary[idx].key, JSON(rawDictionary[idx].value)) + default: return ("", JSON.null) + } + } +} + +// MARK: - Subscript + +/** + * To mark both String and Int can be used in subscript. + */ +public enum JSONKey { + case index(Int) + case key(String) +} + +public protocol JSONSubscriptType { + var jsonKey: JSONKey { get } +} + +extension Int: JSONSubscriptType { + public var jsonKey: JSONKey { + return JSONKey.index(self) + } +} + +extension String: JSONSubscriptType { + public var jsonKey: JSONKey { + return JSONKey.key(self) + } +} + +extension JSON { + + /// If `type` is `.array`, return json whose object is `array[index]`, otherwise return null json with error. + fileprivate subscript(index index: Int) -> JSON { + get { + if type != .array { + var r = JSON.null + r.error = self.error ?? SwiftyJSONError.wrongType + return r + } else if rawArray.indices.contains(index) { + return JSON(rawArray[index]) + } else { + var r = JSON.null + r.error = SwiftyJSONError.indexOutOfBounds + return r + } + } + set { + if type == .array && + rawArray.indices.contains(index) && + newValue.error == nil { + rawArray[index] = newValue.object + } + } + } + + /// If `type` is `.dictionary`, return json whose object is `dictionary[key]` , otherwise return null json with error. + fileprivate subscript(key key: String) -> JSON { + get { + var r = JSON.null + if type == .dictionary { + if let o = rawDictionary[key] { + r = JSON(o) + } else { + r.error = SwiftyJSONError.notExist + } + } else { + r.error = self.error ?? SwiftyJSONError.wrongType + } + return r + } + set { + if type == .dictionary && newValue.error == nil { + rawDictionary[key] = newValue.object + } + } + } + + /// If `sub` is `Int`, return `subscript(index:)`; If `sub` is `String`, return `subscript(key:)`. + fileprivate subscript(sub sub: JSONSubscriptType) -> JSON { + get { + switch sub.jsonKey { + case .index(let index): return self[index: index] + case .key(let key): return self[key: key] + } + } + set { + switch sub.jsonKey { + case .index(let index): self[index: index] = newValue + case .key(let key): self[key: key] = newValue + } + } + } + + /** + Find a json in the complex data structures by using array of Int and/or String as path. + + Example: + + ``` + let json = JSON[data] + let path = [9,"list","person","name"] + let name = json[path] + ``` + + The same as: let name = json[9]["list"]["person"]["name"] + + - parameter path: The target json's path. + + - returns: Return a json found by the path or a null json with error + */ + public subscript(path: [JSONSubscriptType]) -> JSON { + get { + return path.reduce(self) { $0[sub: $1] } + } + set { + switch path.count { + case 0: return + case 1: self[sub:path[0]].object = newValue.object + default: + var aPath = path + aPath.remove(at: 0) + var nextJSON = self[sub: path[0]] + nextJSON[aPath] = newValue + self[sub: path[0]] = nextJSON + } + } + } + + /** + Find a json in the complex data structures by using array of Int and/or String as path. + + - parameter path: The target json's path. Example: + + let name = json[9,"list","person","name"] + + The same as: let name = json[9]["list"]["person"]["name"] + + - returns: Return a json found by the path or a null json with error + */ + public subscript(path: JSONSubscriptType...) -> JSON { + get { + return self[path] + } + set { + self[path] = newValue + } + } +} + +// MARK: - LiteralConvertible + +extension JSON: Swift.ExpressibleByStringLiteral { + + public init(stringLiteral value: StringLiteralType) { + self.init(value) + } + + public init(extendedGraphemeClusterLiteral value: StringLiteralType) { + self.init(value) + } + + public init(unicodeScalarLiteral value: StringLiteralType) { + self.init(value) + } +} + +extension JSON: Swift.ExpressibleByIntegerLiteral { + + public init(integerLiteral value: IntegerLiteralType) { + self.init(value) + } +} + +extension JSON: Swift.ExpressibleByBooleanLiteral { + + public init(booleanLiteral value: BooleanLiteralType) { + self.init(value) + } +} + +extension JSON: Swift.ExpressibleByFloatLiteral { + + public init(floatLiteral value: FloatLiteralType) { + self.init(value) + } +} + +extension JSON: Swift.ExpressibleByDictionaryLiteral { + public init(dictionaryLiteral elements: (String, Any)...) { + let dictionary = elements.reduce(into: [String: Any](), { $0[$1.0] = $1.1}) + self.init(dictionary) + } +} + +extension JSON: Swift.ExpressibleByArrayLiteral { + + public init(arrayLiteral elements: Any...) { + self.init(elements) + } +} + +// MARK: - Raw + +extension JSON: Swift.RawRepresentable { + + public init?(rawValue: Any) { + if JSON(rawValue).type == .unknown { + return nil + } else { + self.init(rawValue) + } + } + + public var rawValue: Any { + return object + } + + public func rawData(options opt: JSONSerialization.WritingOptions = JSONSerialization.WritingOptions(rawValue: 0)) throws -> Data { + guard JSONSerialization.isValidJSONObject(object) else { + throw SwiftyJSONError.invalidJSON + } + + return try JSONSerialization.data(withJSONObject: object, options: opt) + } + + public func rawString(_ encoding: String.Encoding = .utf8, options opt: JSONSerialization.WritingOptions = .prettyPrinted) -> String? { + do { + return try _rawString(encoding, options: [.jsonSerialization: opt]) + } catch { + print("Could not serialize object to JSON because:", error.localizedDescription) + return nil + } + } + + public func rawString(_ options: [writingOptionsKeys: Any]) -> String? { + let encoding = options[.encoding] as? String.Encoding ?? String.Encoding.utf8 + let maxObjectDepth = options[.maxObjextDepth] as? Int ?? 10 + do { + return try _rawString(encoding, options: options, maxObjectDepth: maxObjectDepth) + } catch { + print("Could not serialize object to JSON because:", error.localizedDescription) + return nil + } + } + + fileprivate func _rawString(_ encoding: String.Encoding = .utf8, options: [writingOptionsKeys: Any], maxObjectDepth: Int = 10) throws -> String? { + guard maxObjectDepth > 0 else { throw SwiftyJSONError.invalidJSON } + switch type { + case .dictionary: + do { + if !(options[.castNilToNSNull] as? Bool ?? false) { + let jsonOption = options[.jsonSerialization] as? JSONSerialization.WritingOptions ?? JSONSerialization.WritingOptions.prettyPrinted + let data = try rawData(options: jsonOption) + return String(data: data, encoding: encoding) + } + + guard let dict = object as? [String: Any?] else { + return nil + } + let body = try dict.keys.map { key throws -> String in + guard let value = dict[key] else { + return "\"\(key)\": null" + } + guard let unwrappedValue = value else { + return "\"\(key)\": null" + } + + let nestedValue = JSON(unwrappedValue) + guard let nestedString = try nestedValue._rawString(encoding, options: options, maxObjectDepth: maxObjectDepth - 1) else { + throw SwiftyJSONError.elementTooDeep + } + if nestedValue.type == .string { + return "\"\(key)\": \"\(nestedString.replacingOccurrences(of: "\\", with: "\\\\").replacingOccurrences(of: "\"", with: "\\\""))\"" + } else { + return "\"\(key)\": \(nestedString)" + } + } + + return "{\(body.joined(separator: ","))}" + } catch _ { + return nil + } + case .array: + do { + if !(options[.castNilToNSNull] as? Bool ?? false) { + let jsonOption = options[.jsonSerialization] as? JSONSerialization.WritingOptions ?? JSONSerialization.WritingOptions.prettyPrinted + let data = try rawData(options: jsonOption) + return String(data: data, encoding: encoding) + } + + guard let array = object as? [Any?] else { + return nil + } + let body = try array.map { value throws -> String in + guard let unwrappedValue = value else { + return "null" + } + + let nestedValue = JSON(unwrappedValue) + guard let nestedString = try nestedValue._rawString(encoding, options: options, maxObjectDepth: maxObjectDepth - 1) else { + throw SwiftyJSONError.invalidJSON + } + if nestedValue.type == .string { + return "\"\(nestedString.replacingOccurrences(of: "\\", with: "\\\\").replacingOccurrences(of: "\"", with: "\\\""))\"" + } else { + return nestedString + } + } + + return "[\(body.joined(separator: ","))]" + } catch _ { + return nil + } + case .string: return rawString + case .number: return rawNumber.stringValue + case .bool: return rawBool.description + case .null: return "null" + default: return nil + } + } +} + +// MARK: - Printable, DebugPrintable + +extension JSON: Swift.CustomStringConvertible, Swift.CustomDebugStringConvertible { + + public var description: String { + return rawString(options: .prettyPrinted) ?? "unknown" + } + + public var debugDescription: String { + return description + } +} + +// MARK: - Array + +extension JSON { + + //Optional [JSON] + public var array: [JSON]? { + return type == .array ? rawArray.map { JSON($0) } : nil + } + + //Non-optional [JSON] + public var arrayValue: [JSON] { + return self.array ?? [] + } + + //Optional [Any] + public var arrayObject: [Any]? { + get { + switch type { + case .array: return rawArray + default: return nil + } + } + set { + self.object = newValue ?? NSNull() + } + } +} + +// MARK: - Dictionary + +extension JSON { + + //Optional [String : JSON] + public var dictionary: [String: JSON]? { + if type == .dictionary { + var d = [String: JSON](minimumCapacity: rawDictionary.count) + rawDictionary.forEach { pair in + d[pair.key] = JSON(pair.value) + } + return d + } else { + return nil + } + } + + //Non-optional [String : JSON] + public var dictionaryValue: [String: JSON] { + return dictionary ?? [:] + } + + //Optional [String : Any] + + public var dictionaryObject: [String: Any]? { + get { + switch type { + case .dictionary: return rawDictionary + default: return nil + } + } + set { + object = newValue ?? NSNull() + } + } +} + +// MARK: - Bool + +extension JSON { // : Swift.Bool + + //Optional bool + public var bool: Bool? { + get { + switch type { + case .bool: return rawBool + default: return nil + } + } + set { + object = newValue ?? NSNull() + } + } + + //Non-optional bool + public var boolValue: Bool { + get { + switch type { + case .bool: return rawBool + case .number: return rawNumber.boolValue + case .string: return ["true", "y", "t", "yes", "1"].contains { rawString.caseInsensitiveCompare($0) == .orderedSame } + default: return false + } + } + set { + object = newValue + } + } +} + +// MARK: - String + +extension JSON { + + //Optional string + public var string: String? { + get { + switch type { + case .string: return object as? String + default: return nil + } + } + set { + object = newValue ?? NSNull() + } + } + + //Non-optional string + public var stringValue: String { + get { + switch type { + case .string: return object as? String ?? "" + case .number: return rawNumber.stringValue + case .bool: return (object as? Bool).map { String($0) } ?? "" + default: return "" + } + } + set { + object = newValue + } + } +} + +// MARK: - Number + +extension JSON { + + //Optional number + public var number: NSNumber? { + get { + switch type { + case .number: return rawNumber + case .bool: return NSNumber(value: rawBool ? 1 : 0) + default: return nil + } + } + set { + object = newValue ?? NSNull() + } + } + + //Non-optional number + public var numberValue: NSNumber { + get { + switch type { + case .string: + let decimal = NSDecimalNumber(string: object as? String) + return decimal == .notANumber ? .zero : decimal + case .number: return object as? NSNumber ?? NSNumber(value: 0) + case .bool: return NSNumber(value: rawBool ? 1 : 0) + default: return NSNumber(value: 0.0) + } + } + set { + object = newValue + } + } +} + +// MARK: - Null + +extension JSON { + + public var null: NSNull? { + set { + object = NSNull() + } + get { + switch type { + case .null: return rawNull + default: return nil + } + } + } + public func exists() -> Bool { + if let errorValue = error, (400...1000).contains(errorValue.errorCode) { + return false + } + return true + } +} + +// MARK: - URL + +extension JSON { + + //Optional URL + public var url: URL? { + get { + switch type { + case .string: + // Check for existing percent escapes first to prevent double-escaping of % character + if rawString.range(of: "%[0-9A-Fa-f]{2}", options: .regularExpression, range: nil, locale: nil) != nil { + return Foundation.URL(string: rawString) + } else if let encodedString_ = rawString.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed) { + // We have to use `Foundation.URL` otherwise it conflicts with the variable name. + return Foundation.URL(string: encodedString_) + } else { + return nil + } + default: + return nil + } + } + set { + object = newValue?.absoluteString ?? NSNull() + } + } +} + +// MARK: - Int, Double, Float, Int8, Int16, Int32, Int64 + +extension JSON { + + public var double: Double? { + get { + return number?.doubleValue + } + set { + if let newValue = newValue { + object = NSNumber(value: newValue) + } else { + object = NSNull() + } + } + } + + public var doubleValue: Double { + get { + return numberValue.doubleValue + } + set { + object = NSNumber(value: newValue) + } + } + + public var float: Float? { + get { + return number?.floatValue + } + set { + if let newValue = newValue { + object = NSNumber(value: newValue) + } else { + object = NSNull() + } + } + } + + public var floatValue: Float { + get { + return numberValue.floatValue + } + set { + object = NSNumber(value: newValue) + } + } + + public var int: Int? { + get { + return number?.intValue + } + set { + if let newValue = newValue { + object = NSNumber(value: newValue) + } else { + object = NSNull() + } + } + } + + public var intValue: Int { + get { + return numberValue.intValue + } + set { + object = NSNumber(value: newValue) + } + } + + public var uInt: UInt? { + get { + return number?.uintValue + } + set { + if let newValue = newValue { + object = NSNumber(value: newValue) + } else { + object = NSNull() + } + } + } + + public var uIntValue: UInt { + get { + return numberValue.uintValue + } + set { + object = NSNumber(value: newValue) + } + } + + public var int8: Int8? { + get { + return number?.int8Value + } + set { + if let newValue = newValue { + object = NSNumber(value: Int(newValue)) + } else { + object = NSNull() + } + } + } + + public var int8Value: Int8 { + get { + return numberValue.int8Value + } + set { + object = NSNumber(value: Int(newValue)) + } + } + + public var uInt8: UInt8? { + get { + return number?.uint8Value + } + set { + if let newValue = newValue { + object = NSNumber(value: newValue) + } else { + object = NSNull() + } + } + } + + public var uInt8Value: UInt8 { + get { + return numberValue.uint8Value + } + set { + object = NSNumber(value: newValue) + } + } + + public var int16: Int16? { + get { + return number?.int16Value + } + set { + if let newValue = newValue { + object = NSNumber(value: newValue) + } else { + object = NSNull() + } + } + } + + public var int16Value: Int16 { + get { + return numberValue.int16Value + } + set { + object = NSNumber(value: newValue) + } + } + + public var uInt16: UInt16? { + get { + return number?.uint16Value + } + set { + if let newValue = newValue { + object = NSNumber(value: newValue) + } else { + object = NSNull() + } + } + } + + public var uInt16Value: UInt16 { + get { + return numberValue.uint16Value + } + set { + object = NSNumber(value: newValue) + } + } + + public var int32: Int32? { + get { + return number?.int32Value + } + set { + if let newValue = newValue { + object = NSNumber(value: newValue) + } else { + object = NSNull() + } + } + } + + public var int32Value: Int32 { + get { + return numberValue.int32Value + } + set { + object = NSNumber(value: newValue) + } + } + + public var uInt32: UInt32? { + get { + return number?.uint32Value + } + set { + if let newValue = newValue { + object = NSNumber(value: newValue) + } else { + object = NSNull() + } + } + } + + public var uInt32Value: UInt32 { + get { + return numberValue.uint32Value + } + set { + object = NSNumber(value: newValue) + } + } + + public var int64: Int64? { + get { + return number?.int64Value + } + set { + if let newValue = newValue { + object = NSNumber(value: newValue) + } else { + object = NSNull() + } + } + } + + public var int64Value: Int64 { + get { + return numberValue.int64Value + } + set { + object = NSNumber(value: newValue) + } + } + + public var uInt64: UInt64? { + get { + return number?.uint64Value + } + set { + if let newValue = newValue { + object = NSNumber(value: newValue) + } else { + object = NSNull() + } + } + } + + public var uInt64Value: UInt64 { + get { + return numberValue.uint64Value + } + set { + object = NSNumber(value: newValue) + } + } +} + +// MARK: - Comparable + +extension JSON: Swift.Comparable {} + +public func == (lhs: JSON, rhs: JSON) -> Bool { + + switch (lhs.type, rhs.type) { + case (.number, .number): return lhs.rawNumber == rhs.rawNumber + case (.string, .string): return lhs.rawString == rhs.rawString + case (.bool, .bool): return lhs.rawBool == rhs.rawBool + case (.array, .array): return lhs.rawArray as NSArray == rhs.rawArray as NSArray + case (.dictionary, .dictionary): return lhs.rawDictionary as NSDictionary == rhs.rawDictionary as NSDictionary + case (.null, .null): return true + default: return false + } +} + +public func <= (lhs: JSON, rhs: JSON) -> Bool { + + switch (lhs.type, rhs.type) { + case (.number, .number): return lhs.rawNumber <= rhs.rawNumber + case (.string, .string): return lhs.rawString <= rhs.rawString + case (.bool, .bool): return lhs.rawBool == rhs.rawBool + case (.array, .array): return lhs.rawArray as NSArray == rhs.rawArray as NSArray + case (.dictionary, .dictionary): return lhs.rawDictionary as NSDictionary == rhs.rawDictionary as NSDictionary + case (.null, .null): return true + default: return false + } +} + +public func >= (lhs: JSON, rhs: JSON) -> Bool { + + switch (lhs.type, rhs.type) { + case (.number, .number): return lhs.rawNumber >= rhs.rawNumber + case (.string, .string): return lhs.rawString >= rhs.rawString + case (.bool, .bool): return lhs.rawBool == rhs.rawBool + case (.array, .array): return lhs.rawArray as NSArray == rhs.rawArray as NSArray + case (.dictionary, .dictionary): return lhs.rawDictionary as NSDictionary == rhs.rawDictionary as NSDictionary + case (.null, .null): return true + default: return false + } +} + +public func > (lhs: JSON, rhs: JSON) -> Bool { + + switch (lhs.type, rhs.type) { + case (.number, .number): return lhs.rawNumber > rhs.rawNumber + case (.string, .string): return lhs.rawString > rhs.rawString + default: return false + } +} + +public func < (lhs: JSON, rhs: JSON) -> Bool { + + switch (lhs.type, rhs.type) { + case (.number, .number): return lhs.rawNumber < rhs.rawNumber + case (.string, .string): return lhs.rawString < rhs.rawString + default: return false + } +} + +private let trueNumber = NSNumber(value: true) +private let falseNumber = NSNumber(value: false) +private let trueObjCType = String(cString: trueNumber.objCType) +private let falseObjCType = String(cString: falseNumber.objCType) + +// MARK: - NSNumber: Comparable + +extension NSNumber { + fileprivate var isBool: Bool { + let objCType = String(cString: self.objCType) + if (self.compare(trueNumber) == .orderedSame && objCType == trueObjCType) || (self.compare(falseNumber) == .orderedSame && objCType == falseObjCType) { + return true + } else { + return false + } + } +} + +func == (lhs: NSNumber, rhs: NSNumber) -> Bool { + switch (lhs.isBool, rhs.isBool) { + case (false, true): return false + case (true, false): return false + default: return lhs.compare(rhs) == .orderedSame + } +} + +func != (lhs: NSNumber, rhs: NSNumber) -> Bool { + return !(lhs == rhs) +} + +func < (lhs: NSNumber, rhs: NSNumber) -> Bool { + + switch (lhs.isBool, rhs.isBool) { + case (false, true): return false + case (true, false): return false + default: return lhs.compare(rhs) == .orderedAscending + } +} + +func > (lhs: NSNumber, rhs: NSNumber) -> Bool { + + switch (lhs.isBool, rhs.isBool) { + case (false, true): return false + case (true, false): return false + default: return lhs.compare(rhs) == ComparisonResult.orderedDescending + } +} + +func <= (lhs: NSNumber, rhs: NSNumber) -> Bool { + + switch (lhs.isBool, rhs.isBool) { + case (false, true): return false + case (true, false): return false + default: return lhs.compare(rhs) != .orderedDescending + } +} + +func >= (lhs: NSNumber, rhs: NSNumber) -> Bool { + + switch (lhs.isBool, rhs.isBool) { + case (false, true): return false + case (true, false): return false + default: return lhs.compare(rhs) != .orderedAscending + } +} + +public enum writingOptionsKeys { + case jsonSerialization + case castNilToNSNull + case maxObjextDepth + case encoding +} + +// MARK: - JSON: Codable +extension JSON: Codable { + private static var codableTypes: [Codable.Type] { + return [ + Bool.self, + Int.self, + Int8.self, + Int16.self, + Int32.self, + Int64.self, + UInt.self, + UInt8.self, + UInt16.self, + UInt32.self, + UInt64.self, + Double.self, + String.self, + [JSON].self, + [String: JSON].self + ] + } + public init(from decoder: Decoder) throws { + var object: Any? + + if let container = try? decoder.singleValueContainer(), !container.decodeNil() { + for type in JSON.codableTypes { + if object != nil { + break + } + // try to decode value + switch type { + case let boolType as Bool.Type: + object = try? container.decode(boolType) + case let intType as Int.Type: + object = try? container.decode(intType) + case let int8Type as Int8.Type: + object = try? container.decode(int8Type) + case let int32Type as Int32.Type: + object = try? container.decode(int32Type) + case let int64Type as Int64.Type: + object = try? container.decode(int64Type) + case let uintType as UInt.Type: + object = try? container.decode(uintType) + case let uint8Type as UInt8.Type: + object = try? container.decode(uint8Type) + case let uint16Type as UInt16.Type: + object = try? container.decode(uint16Type) + case let uint32Type as UInt32.Type: + object = try? container.decode(uint32Type) + case let uint64Type as UInt64.Type: + object = try? container.decode(uint64Type) + case let doubleType as Double.Type: + object = try? container.decode(doubleType) + case let stringType as String.Type: + object = try? container.decode(stringType) + case let jsonValueArrayType as [JSON].Type: + object = try? container.decode(jsonValueArrayType) + case let jsonValueDictType as [String: JSON].Type: + object = try? container.decode(jsonValueDictType) + default: + break + } + } + } + self.init(object ?? NSNull()) + } + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + if object is NSNull { + try container.encodeNil() + return + } + switch object { + case let intValue as Int: + try container.encode(intValue) + case let int8Value as Int8: + try container.encode(int8Value) + case let int32Value as Int32: + try container.encode(int32Value) + case let int64Value as Int64: + try container.encode(int64Value) + case let uintValue as UInt: + try container.encode(uintValue) + case let uint8Value as UInt8: + try container.encode(uint8Value) + case let uint16Value as UInt16: + try container.encode(uint16Value) + case let uint32Value as UInt32: + try container.encode(uint32Value) + case let uint64Value as UInt64: + try container.encode(uint64Value) + case let doubleValue as Double: + try container.encode(doubleValue) + case let boolValue as Bool: + try container.encode(boolValue) + case let stringValue as String: + try container.encode(stringValue) + case is [Any]: + let jsonValueArray = array ?? [] + try container.encode(jsonValueArray) + case is [String: Any]: + let jsonValueDictValue = dictionary ?? [:] + try container.encode(jsonValueDictValue) + default: + break + } + } +} diff --git b/Pods/Target Support Files/Alamofire/Alamofire-Info.plist a/Pods/Target Support Files/Alamofire/Alamofire-Info.plist new file mode 100644 index 0000000..073e064 --- /dev/null +++ a/Pods/Target Support Files/Alamofire/Alamofire-Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + ${PRODUCT_BUNDLE_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + FMWK + CFBundleShortVersionString + 5.1.0 + CFBundleSignature + ???? + CFBundleVersion + ${CURRENT_PROJECT_VERSION} + NSPrincipalClass + + + diff --git b/Pods/Target Support Files/Alamofire/Alamofire-dummy.m a/Pods/Target Support Files/Alamofire/Alamofire-dummy.m new file mode 100644 index 0000000..a6c4594 --- /dev/null +++ a/Pods/Target Support Files/Alamofire/Alamofire-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_Alamofire : NSObject +@end +@implementation PodsDummy_Alamofire +@end diff --git b/Pods/Target Support Files/Alamofire/Alamofire-prefix.pch a/Pods/Target Support Files/Alamofire/Alamofire-prefix.pch new file mode 100644 index 0000000..beb2a24 --- /dev/null +++ a/Pods/Target Support Files/Alamofire/Alamofire-prefix.pch @@ -0,0 +1,12 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + diff --git b/Pods/Target Support Files/Alamofire/Alamofire-umbrella.h a/Pods/Target Support Files/Alamofire/Alamofire-umbrella.h new file mode 100644 index 0000000..00014e3 --- /dev/null +++ a/Pods/Target Support Files/Alamofire/Alamofire-umbrella.h @@ -0,0 +1,16 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + + +FOUNDATION_EXPORT double AlamofireVersionNumber; +FOUNDATION_EXPORT const unsigned char AlamofireVersionString[]; + diff --git b/Pods/Target Support Files/Alamofire/Alamofire.debug.xcconfig a/Pods/Target Support Files/Alamofire/Alamofire.debug.xcconfig new file mode 100644 index 0000000..385cdcf --- /dev/null +++ a/Pods/Target Support Files/Alamofire/Alamofire.debug.xcconfig @@ -0,0 +1,11 @@ +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Alamofire +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +OTHER_LDFLAGS = $(inherited) -framework "CFNetwork" +OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_ROOT = ${SRCROOT} +PODS_TARGET_SRCROOT = ${PODS_ROOT}/Alamofire +PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} +SKIP_INSTALL = YES +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git b/Pods/Target Support Files/Alamofire/Alamofire.modulemap a/Pods/Target Support Files/Alamofire/Alamofire.modulemap new file mode 100644 index 0000000..d1f125f --- /dev/null +++ a/Pods/Target Support Files/Alamofire/Alamofire.modulemap @@ -0,0 +1,6 @@ +framework module Alamofire { + umbrella header "Alamofire-umbrella.h" + + export * + module * { export * } +} diff --git b/Pods/Target Support Files/Alamofire/Alamofire.release.xcconfig a/Pods/Target Support Files/Alamofire/Alamofire.release.xcconfig new file mode 100644 index 0000000..385cdcf --- /dev/null +++ a/Pods/Target Support Files/Alamofire/Alamofire.release.xcconfig @@ -0,0 +1,11 @@ +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Alamofire +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +OTHER_LDFLAGS = $(inherited) -framework "CFNetwork" +OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_ROOT = ${SRCROOT} +PODS_TARGET_SRCROOT = ${PODS_ROOT}/Alamofire +PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} +SKIP_INSTALL = YES +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git b/Pods/Target Support Files/GoogleMaps/GoogleMaps.debug.xcconfig a/Pods/Target Support Files/GoogleMaps/GoogleMaps.debug.xcconfig new file mode 100644 index 0000000..4f0a2e6 --- /dev/null +++ a/Pods/Target Support Files/GoogleMaps/GoogleMaps.debug.xcconfig @@ -0,0 +1,11 @@ +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/GoogleMaps +FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/GoogleMaps/Base/Frameworks" "${PODS_ROOT}/GoogleMaps/Maps/Frameworks" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +OTHER_LDFLAGS = $(inherited) -l"c++" -l"z" -framework "Accelerate" -framework "CoreData" -framework "CoreGraphics" -framework "CoreImage" -framework "CoreLocation" -framework "CoreTelephony" -framework "CoreText" -framework "GLKit" -framework "ImageIO" -framework "Metal" -framework "OpenGLES" -framework "QuartzCore" -framework "SystemConfiguration" -framework "UIKit" +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_ROOT = ${SRCROOT} +PODS_TARGET_SRCROOT = ${PODS_ROOT}/GoogleMaps +PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} +SKIP_INSTALL = YES +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git b/Pods/Target Support Files/GoogleMaps/GoogleMaps.release.xcconfig a/Pods/Target Support Files/GoogleMaps/GoogleMaps.release.xcconfig new file mode 100644 index 0000000..4f0a2e6 --- /dev/null +++ a/Pods/Target Support Files/GoogleMaps/GoogleMaps.release.xcconfig @@ -0,0 +1,11 @@ +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/GoogleMaps +FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/GoogleMaps/Base/Frameworks" "${PODS_ROOT}/GoogleMaps/Maps/Frameworks" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +OTHER_LDFLAGS = $(inherited) -l"c++" -l"z" -framework "Accelerate" -framework "CoreData" -framework "CoreGraphics" -framework "CoreImage" -framework "CoreLocation" -framework "CoreTelephony" -framework "CoreText" -framework "GLKit" -framework "ImageIO" -framework "Metal" -framework "OpenGLES" -framework "QuartzCore" -framework "SystemConfiguration" -framework "UIKit" +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_ROOT = ${SRCROOT} +PODS_TARGET_SRCROOT = ${PODS_ROOT}/GoogleMaps +PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} +SKIP_INSTALL = YES +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git b/Pods/Target Support Files/GooglePlaces/GooglePlaces.debug.xcconfig a/Pods/Target Support Files/GooglePlaces/GooglePlaces.debug.xcconfig new file mode 100644 index 0000000..04411f9 --- /dev/null +++ a/Pods/Target Support Files/GooglePlaces/GooglePlaces.debug.xcconfig @@ -0,0 +1,11 @@ +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/GooglePlaces +FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/GoogleMaps/Base/Frameworks" "${PODS_ROOT}/GoogleMaps/Maps/Frameworks" "${PODS_ROOT}/GooglePlaces/Frameworks" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +OTHER_LDFLAGS = $(inherited) -framework "Accelerate" -framework "CoreData" -framework "CoreGraphics" -framework "CoreImage" -framework "CoreLocation" -framework "CoreTelephony" -framework "CoreText" -framework "GLKit" -framework "ImageIO" -framework "Metal" -framework "OpenGLES" -framework "QuartzCore" -framework "SystemConfiguration" -framework "UIKit" +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_ROOT = ${SRCROOT} +PODS_TARGET_SRCROOT = ${PODS_ROOT}/GooglePlaces +PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} +SKIP_INSTALL = YES +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git b/Pods/Target Support Files/GooglePlaces/GooglePlaces.release.xcconfig a/Pods/Target Support Files/GooglePlaces/GooglePlaces.release.xcconfig new file mode 100644 index 0000000..04411f9 --- /dev/null +++ a/Pods/Target Support Files/GooglePlaces/GooglePlaces.release.xcconfig @@ -0,0 +1,11 @@ +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/GooglePlaces +FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/GoogleMaps/Base/Frameworks" "${PODS_ROOT}/GoogleMaps/Maps/Frameworks" "${PODS_ROOT}/GooglePlaces/Frameworks" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +OTHER_LDFLAGS = $(inherited) -framework "Accelerate" -framework "CoreData" -framework "CoreGraphics" -framework "CoreImage" -framework "CoreLocation" -framework "CoreTelephony" -framework "CoreText" -framework "GLKit" -framework "ImageIO" -framework "Metal" -framework "OpenGLES" -framework "QuartzCore" -framework "SystemConfiguration" -framework "UIKit" +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_ROOT = ${SRCROOT} +PODS_TARGET_SRCROOT = ${PODS_ROOT}/GooglePlaces +PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} +SKIP_INSTALL = YES +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git b/Pods/Target Support Files/Kingfisher/Kingfisher-Info.plist a/Pods/Target Support Files/Kingfisher/Kingfisher-Info.plist new file mode 100644 index 0000000..df61036 --- /dev/null +++ a/Pods/Target Support Files/Kingfisher/Kingfisher-Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + ${PRODUCT_BUNDLE_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + FMWK + CFBundleShortVersionString + 5.13.4 + CFBundleSignature + ???? + CFBundleVersion + ${CURRENT_PROJECT_VERSION} + NSPrincipalClass + + + diff --git b/Pods/Target Support Files/Kingfisher/Kingfisher-dummy.m a/Pods/Target Support Files/Kingfisher/Kingfisher-dummy.m new file mode 100644 index 0000000..1b89d0e --- /dev/null +++ a/Pods/Target Support Files/Kingfisher/Kingfisher-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_Kingfisher : NSObject +@end +@implementation PodsDummy_Kingfisher +@end diff --git b/Pods/Target Support Files/Kingfisher/Kingfisher-prefix.pch a/Pods/Target Support Files/Kingfisher/Kingfisher-prefix.pch new file mode 100644 index 0000000..beb2a24 --- /dev/null +++ a/Pods/Target Support Files/Kingfisher/Kingfisher-prefix.pch @@ -0,0 +1,12 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + diff --git b/Pods/Target Support Files/Kingfisher/Kingfisher-umbrella.h a/Pods/Target Support Files/Kingfisher/Kingfisher-umbrella.h new file mode 100644 index 0000000..89b88ac --- /dev/null +++ a/Pods/Target Support Files/Kingfisher/Kingfisher-umbrella.h @@ -0,0 +1,17 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + +#import "Kingfisher.h" + +FOUNDATION_EXPORT double KingfisherVersionNumber; +FOUNDATION_EXPORT const unsigned char KingfisherVersionString[]; + diff --git b/Pods/Target Support Files/Kingfisher/Kingfisher.debug.xcconfig a/Pods/Target Support Files/Kingfisher/Kingfisher.debug.xcconfig new file mode 100644 index 0000000..71e88d4 --- /dev/null +++ a/Pods/Target Support Files/Kingfisher/Kingfisher.debug.xcconfig @@ -0,0 +1,11 @@ +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Kingfisher +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +OTHER_LDFLAGS = $(inherited) -framework "Accelerate" -framework "CFNetwork" +OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_ROOT = ${SRCROOT} +PODS_TARGET_SRCROOT = ${PODS_ROOT}/Kingfisher +PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} +SKIP_INSTALL = YES +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git b/Pods/Target Support Files/Kingfisher/Kingfisher.modulemap a/Pods/Target Support Files/Kingfisher/Kingfisher.modulemap new file mode 100644 index 0000000..2a20d91 --- /dev/null +++ a/Pods/Target Support Files/Kingfisher/Kingfisher.modulemap @@ -0,0 +1,6 @@ +framework module Kingfisher { + umbrella header "Kingfisher-umbrella.h" + + export * + module * { export * } +} diff --git b/Pods/Target Support Files/Kingfisher/Kingfisher.release.xcconfig a/Pods/Target Support Files/Kingfisher/Kingfisher.release.xcconfig new file mode 100644 index 0000000..71e88d4 --- /dev/null +++ a/Pods/Target Support Files/Kingfisher/Kingfisher.release.xcconfig @@ -0,0 +1,11 @@ +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Kingfisher +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +OTHER_LDFLAGS = $(inherited) -framework "Accelerate" -framework "CFNetwork" +OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_ROOT = ${SRCROOT} +PODS_TARGET_SRCROOT = ${PODS_ROOT}/Kingfisher +PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} +SKIP_INSTALL = YES +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git b/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy-Info.plist a/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy-Info.plist new file mode 100644 index 0000000..2243fe6 --- /dev/null +++ a/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy-Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + ${PRODUCT_BUNDLE_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0.0 + CFBundleSignature + ???? + CFBundleVersion + ${CURRENT_PROJECT_VERSION} + NSPrincipalClass + + + diff --git b/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy-acknowledgements.markdown a/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy-acknowledgements.markdown new file mode 100644 index 0000000..e35e929 --- /dev/null +++ a/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy-acknowledgements.markdown @@ -0,0 +1,110 @@ +# Acknowledgements +This application makes use of the following third party libraries: + +## Alamofire + +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. + + +## GoogleMaps + +Copyright 2020 Google + +## GooglePlaces + +Copyright 2020 Google + +## Kingfisher + +The MIT License (MIT) + +Copyright (c) 2019 Wei Wang + +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. + + + +## Sniffer + +MIT License + +Copyright (c) 2017 Taeun Kim + +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. + + +## SwiftyJSON + +The MIT License (MIT) + +Copyright (c) 2017 Ruoyu Fu + +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. + +Generated by CocoaPods - https://cocoapods.org diff --git b/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy-acknowledgements.plist a/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy-acknowledgements.plist new file mode 100644 index 0000000..f729a18 --- /dev/null +++ a/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy-acknowledgements.plist @@ -0,0 +1,172 @@ + + + + + PreferenceSpecifiers + + + FooterText + This application makes use of the following third party libraries: + Title + Acknowledgements + Type + PSGroupSpecifier + + + FooterText + 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. + + License + MIT + Title + Alamofire + Type + PSGroupSpecifier + + + FooterText + Copyright 2020 Google + License + Copyright + Title + GoogleMaps + Type + PSGroupSpecifier + + + FooterText + Copyright 2020 Google + License + Copyright + Title + GooglePlaces + Type + PSGroupSpecifier + + + FooterText + The MIT License (MIT) + +Copyright (c) 2019 Wei Wang + +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. + + + License + MIT + Title + Kingfisher + Type + PSGroupSpecifier + + + FooterText + MIT License + +Copyright (c) 2017 Taeun Kim + +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. + + License + MIT + Title + Sniffer + Type + PSGroupSpecifier + + + FooterText + The MIT License (MIT) + +Copyright (c) 2017 Ruoyu Fu + +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. + + License + MIT + Title + SwiftyJSON + Type + PSGroupSpecifier + + + FooterText + Generated by CocoaPods - https://cocoapods.org + Title + + Type + PSGroupSpecifier + + + StringsTable + Acknowledgements + Title + Acknowledgements + + diff --git b/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy-dummy.m a/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy-dummy.m new file mode 100644 index 0000000..a941e8a --- /dev/null +++ a/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_Pods_ayudapy : NSObject +@end +@implementation PodsDummy_Pods_ayudapy +@end diff --git b/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy-frameworks-Debug-input-files.xcfilelist a/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy-frameworks-Debug-input-files.xcfilelist new file mode 100644 index 0000000..151da17 --- /dev/null +++ a/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy-frameworks-Debug-input-files.xcfilelist @@ -0,0 +1,5 @@ +${PODS_ROOT}/Target Support Files/Pods-ayudapy/Pods-ayudapy-frameworks.sh +${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework +${BUILT_PRODUCTS_DIR}/Kingfisher/Kingfisher.framework +${BUILT_PRODUCTS_DIR}/Sniffer/Sniffer.framework +${BUILT_PRODUCTS_DIR}/SwiftyJSON/SwiftyJSON.framework \ No newline at end of file diff --git b/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy-frameworks-Debug-output-files.xcfilelist a/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy-frameworks-Debug-output-files.xcfilelist new file mode 100644 index 0000000..9110b33 --- /dev/null +++ a/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy-frameworks-Debug-output-files.xcfilelist @@ -0,0 +1,4 @@ +${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Alamofire.framework +${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Kingfisher.framework +${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Sniffer.framework +${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyJSON.framework \ No newline at end of file diff --git b/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy-frameworks-Release-input-files.xcfilelist a/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy-frameworks-Release-input-files.xcfilelist new file mode 100644 index 0000000..151da17 --- /dev/null +++ a/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy-frameworks-Release-input-files.xcfilelist @@ -0,0 +1,5 @@ +${PODS_ROOT}/Target Support Files/Pods-ayudapy/Pods-ayudapy-frameworks.sh +${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework +${BUILT_PRODUCTS_DIR}/Kingfisher/Kingfisher.framework +${BUILT_PRODUCTS_DIR}/Sniffer/Sniffer.framework +${BUILT_PRODUCTS_DIR}/SwiftyJSON/SwiftyJSON.framework \ No newline at end of file diff --git b/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy-frameworks-Release-output-files.xcfilelist a/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy-frameworks-Release-output-files.xcfilelist new file mode 100644 index 0000000..9110b33 --- /dev/null +++ a/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy-frameworks-Release-output-files.xcfilelist @@ -0,0 +1,4 @@ +${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Alamofire.framework +${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Kingfisher.framework +${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Sniffer.framework +${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyJSON.framework \ No newline at end of file diff --git b/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy-frameworks.sh a/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy-frameworks.sh new file mode 100755 index 0000000..e0f9dd3 --- /dev/null +++ a/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy-frameworks.sh @@ -0,0 +1,213 @@ +#!/bin/sh +set -e +set -u +set -o pipefail + +function on_error { + echo "$(realpath -mq "${0}"):$1: error: Unexpected failure" +} +trap 'on_error $LINENO' ERR + +if [ -z ${FRAMEWORKS_FOLDER_PATH+x} ]; then + # If FRAMEWORKS_FOLDER_PATH is not set, then there's nowhere for us to copy + # frameworks to, so exit 0 (signalling the script phase was successful). + exit 0 +fi + +echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" +mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" + +COCOAPODS_PARALLEL_CODE_SIGN="${COCOAPODS_PARALLEL_CODE_SIGN:-false}" +SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" + +# Used as a return value for each invocation of `strip_invalid_archs` function. +STRIP_BINARY_RETVAL=0 + +# This protects against multiple targets copying the same framework dependency at the same time. The solution +# was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html +RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") + +# Copies and strips a vendored framework +install_framework() +{ + if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then + local source="${BUILT_PRODUCTS_DIR}/$1" + elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then + local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" + elif [ -r "$1" ]; then + local source="$1" + fi + + local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" + + if [ -L "${source}" ]; then + echo "Symlinked..." + source="$(readlink "${source}")" + fi + + # Use filter instead of exclude so missing patterns don't throw errors. + echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" + rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" + + local basename + basename="$(basename -s .framework "$1")" + binary="${destination}/${basename}.framework/${basename}" + + if ! [ -r "$binary" ]; then + binary="${destination}/${basename}" + elif [ -L "${binary}" ]; then + echo "Destination binary is symlinked..." + dirname="$(dirname "${binary}")" + binary="${dirname}/$(readlink "${binary}")" + fi + + # Strip invalid architectures so "fat" simulator / device frameworks work on device + if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then + strip_invalid_archs "$binary" + fi + + # Resign the code if required by the build settings to avoid unstable apps + code_sign_if_enabled "${destination}/$(basename "$1")" + + # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. + if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then + local swift_runtime_libs + swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u) + for lib in $swift_runtime_libs; do + echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" + rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" + code_sign_if_enabled "${destination}/${lib}" + done + fi +} + +# Copies and strips a vendored dSYM +install_dsym() { + local source="$1" + warn_missing_arch=${2:-true} + if [ -r "$source" ]; then + # Copy the dSYM into the targets temp dir. + echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DERIVED_FILES_DIR}\"" + rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DERIVED_FILES_DIR}" + + local basename + basename="$(basename -s .dSYM "$source")" + binary_name="$(ls "$source/Contents/Resources/DWARF")" + binary="${DERIVED_FILES_DIR}/${basename}.dSYM/Contents/Resources/DWARF/${binary_name}" + + # Strip invalid architectures so "fat" simulator / device frameworks work on device + if [[ "$(file "$binary")" == *"Mach-O "*"dSYM companion"* ]]; then + strip_invalid_archs "$binary" "$warn_missing_arch" + fi + + if [[ $STRIP_BINARY_RETVAL == 1 ]]; then + # Move the stripped file into its final destination. + echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\" \"${DWARF_DSYM_FOLDER_PATH}\"" + rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.dSYM" "${DWARF_DSYM_FOLDER_PATH}" + else + # The dSYM was not stripped at all, in this case touch a fake folder so the input/output paths from Xcode do not reexecute this script because the file is missing. + touch "${DWARF_DSYM_FOLDER_PATH}/${basename}.dSYM" + fi + fi +} + +# Copies the bcsymbolmap files of a vendored framework +install_bcsymbolmap() { + local bcsymbolmap_path="$1" + local destination="${BUILT_PRODUCTS_DIR}" + echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}"" + rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}" +} + +# Signs a framework with the provided identity +code_sign_if_enabled() { + if [ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" -a "${CODE_SIGNING_REQUIRED:-}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then + # Use the current code_sign_identity + echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" + local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements '$1'" + + if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then + code_sign_cmd="$code_sign_cmd &" + fi + echo "$code_sign_cmd" + eval "$code_sign_cmd" + fi +} + +# Strip invalid architectures +strip_invalid_archs() { + binary="$1" + warn_missing_arch=${2:-true} + # Get architectures for current target binary + binary_archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | awk '{$1=$1;print}' | rev)" + # Intersect them with the architectures we are building for + intersected_archs="$(echo ${ARCHS[@]} ${binary_archs[@]} | tr ' ' '\n' | sort | uniq -d)" + # If there are no archs supported by this binary then warn the user + if [[ -z "$intersected_archs" ]]; then + if [[ "$warn_missing_arch" == "true" ]]; then + echo "warning: [CP] Vendored binary '$binary' contains architectures ($binary_archs) none of which match the current build architectures ($ARCHS)." + fi + STRIP_BINARY_RETVAL=0 + return + fi + stripped="" + for arch in $binary_archs; do + if ! [[ "${ARCHS}" == *"$arch"* ]]; then + # Strip non-valid architectures in-place + lipo -remove "$arch" -output "$binary" "$binary" + stripped="$stripped $arch" + fi + done + if [[ "$stripped" ]]; then + echo "Stripped $binary of architectures:$stripped" + fi + STRIP_BINARY_RETVAL=1 +} + +install_artifact() { + artifact="$1" + base="$(basename "$artifact")" + case $base in + *.framework) + install_framework "$artifact" + ;; + *.dSYM) + # Suppress arch warnings since XCFrameworks will include many dSYM files + install_dsym "$artifact" "false" + ;; + *.bcsymbolmap) + install_bcsymbolmap "$artifact" + ;; + *) + echo "error: Unrecognized artifact "$artifact"" + ;; + esac +} + +copy_artifacts() { + file_list="$1" + while read artifact; do + install_artifact "$artifact" + done <$file_list +} + +ARTIFACT_LIST_FILE="${BUILT_PRODUCTS_DIR}/cocoapods-artifacts-${CONFIGURATION}.txt" +if [ -r "${ARTIFACT_LIST_FILE}" ]; then + copy_artifacts "${ARTIFACT_LIST_FILE}" +fi + +if [[ "$CONFIGURATION" == "Debug" ]]; then + install_framework "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework" + install_framework "${BUILT_PRODUCTS_DIR}/Kingfisher/Kingfisher.framework" + install_framework "${BUILT_PRODUCTS_DIR}/Sniffer/Sniffer.framework" + install_framework "${BUILT_PRODUCTS_DIR}/SwiftyJSON/SwiftyJSON.framework" +fi +if [[ "$CONFIGURATION" == "Release" ]]; then + install_framework "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework" + install_framework "${BUILT_PRODUCTS_DIR}/Kingfisher/Kingfisher.framework" + install_framework "${BUILT_PRODUCTS_DIR}/Sniffer/Sniffer.framework" + install_framework "${BUILT_PRODUCTS_DIR}/SwiftyJSON/SwiftyJSON.framework" +fi +if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then + wait +fi diff --git b/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy-resources-Debug-input-files.xcfilelist a/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy-resources-Debug-input-files.xcfilelist new file mode 100644 index 0000000..0736434 --- /dev/null +++ a/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy-resources-Debug-input-files.xcfilelist @@ -0,0 +1,3 @@ +${PODS_ROOT}/Target Support Files/Pods-ayudapy/Pods-ayudapy-resources.sh +${PODS_ROOT}/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle +${PODS_ROOT}/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle \ No newline at end of file diff --git b/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy-resources-Debug-output-files.xcfilelist a/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy-resources-Debug-output-files.xcfilelist new file mode 100644 index 0000000..bd4c263 --- /dev/null +++ a/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy-resources-Debug-output-files.xcfilelist @@ -0,0 +1,2 @@ +${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleMaps.bundle +${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GooglePlaces.bundle \ No newline at end of file diff --git b/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy-resources-Release-input-files.xcfilelist a/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy-resources-Release-input-files.xcfilelist new file mode 100644 index 0000000..0736434 --- /dev/null +++ a/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy-resources-Release-input-files.xcfilelist @@ -0,0 +1,3 @@ +${PODS_ROOT}/Target Support Files/Pods-ayudapy/Pods-ayudapy-resources.sh +${PODS_ROOT}/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle +${PODS_ROOT}/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle \ No newline at end of file diff --git b/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy-resources-Release-output-files.xcfilelist a/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy-resources-Release-output-files.xcfilelist new file mode 100644 index 0000000..bd4c263 --- /dev/null +++ a/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy-resources-Release-output-files.xcfilelist @@ -0,0 +1,2 @@ +${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleMaps.bundle +${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GooglePlaces.bundle \ No newline at end of file diff --git b/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy-resources.sh a/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy-resources.sh new file mode 100755 index 0000000..fefdf0c --- /dev/null +++ a/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy-resources.sh @@ -0,0 +1,131 @@ +#!/bin/sh +set -e +set -u +set -o pipefail + +function on_error { + echo "$(realpath -mq "${0}"):$1: error: Unexpected failure" +} +trap 'on_error $LINENO' ERR + +if [ -z ${UNLOCALIZED_RESOURCES_FOLDER_PATH+x} ]; then + # If UNLOCALIZED_RESOURCES_FOLDER_PATH is not set, then there's nowhere for us to copy + # resources to, so exit 0 (signalling the script phase was successful). + exit 0 +fi + +mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" + +RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt +> "$RESOURCES_TO_COPY" + +XCASSET_FILES=() + +# This protects against multiple targets copying the same framework dependency at the same time. The solution +# was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html +RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") + +case "${TARGETED_DEVICE_FAMILY:-}" in + 1,2) + TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone" + ;; + 1) + TARGET_DEVICE_ARGS="--target-device iphone" + ;; + 2) + TARGET_DEVICE_ARGS="--target-device ipad" + ;; + 3) + TARGET_DEVICE_ARGS="--target-device tv" + ;; + 4) + TARGET_DEVICE_ARGS="--target-device watch" + ;; + *) + TARGET_DEVICE_ARGS="--target-device mac" + ;; +esac + +install_resource() +{ + if [[ "$1" = /* ]] ; then + RESOURCE_PATH="$1" + else + RESOURCE_PATH="${PODS_ROOT}/$1" + fi + if [[ ! -e "$RESOURCE_PATH" ]] ; then + cat << EOM +error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script. +EOM + exit 1 + fi + case $RESOURCE_PATH in + *.storyboard) + echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true + ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} + ;; + *.xib) + echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true + ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} + ;; + *.framework) + echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true + mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" + echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true + rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" + ;; + *.xcdatamodel) + echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\"" || true + xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom" + ;; + *.xcdatamodeld) + echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\"" || true + xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd" + ;; + *.xcmappingmodel) + echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\"" || true + xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm" + ;; + *.xcassets) + ABSOLUTE_XCASSET_FILE="$RESOURCE_PATH" + XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE") + ;; + *) + echo "$RESOURCE_PATH" || true + echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY" + ;; + esac +} +if [[ "$CONFIGURATION" == "Debug" ]]; then + install_resource "${PODS_ROOT}/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle" + install_resource "${PODS_ROOT}/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle" +fi +if [[ "$CONFIGURATION" == "Release" ]]; then + install_resource "${PODS_ROOT}/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle" + install_resource "${PODS_ROOT}/GooglePlaces/Frameworks/GooglePlaces.framework/Resources/GooglePlaces.bundle" +fi + +mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" +rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" +if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then + mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" + rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" +fi +rm -f "$RESOURCES_TO_COPY" + +if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "${XCASSET_FILES:-}" ] +then + # Find all other xcassets (this unfortunately includes those of path pods and other targets). + OTHER_XCASSETS=$(find -L "$PWD" -iname "*.xcassets" -type d) + while read line; do + if [[ $line != "${PODS_ROOT}*" ]]; then + XCASSET_FILES+=("$line") + fi + done <<<"$OTHER_XCASSETS" + + if [ -z ${ASSETCATALOG_COMPILER_APPICON_NAME+x} ]; then + printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" + else + printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" --app-icon "${ASSETCATALOG_COMPILER_APPICON_NAME}" --output-partial-info-plist "${TARGET_TEMP_DIR}/assetcatalog_generated_info_cocoapods.plist" + fi +fi diff --git b/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy-umbrella.h a/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy-umbrella.h new file mode 100644 index 0000000..8ef36ea --- /dev/null +++ a/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy-umbrella.h @@ -0,0 +1,16 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + + +FOUNDATION_EXPORT double Pods_ayudapyVersionNumber; +FOUNDATION_EXPORT const unsigned char Pods_ayudapyVersionString[]; + diff --git b/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy.debug.xcconfig a/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy.debug.xcconfig new file mode 100644 index 0000000..d331587 --- /dev/null +++ a/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy.debug.xcconfig @@ -0,0 +1,12 @@ +ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES +FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/Kingfisher" "${PODS_CONFIGURATION_BUILD_DIR}/Sniffer" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyJSON" "${PODS_ROOT}/GoogleMaps/Base/Frameworks" "${PODS_ROOT}/GoogleMaps/Maps/Frameworks" "${PODS_ROOT}/GooglePlaces/Frameworks" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Kingfisher/Kingfisher.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Sniffer/Sniffer.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyJSON/SwiftyJSON.framework/Headers" +LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' +OTHER_LDFLAGS = $(inherited) -ObjC -l"c++" -l"z" -framework "Accelerate" -framework "Alamofire" -framework "CFNetwork" -framework "CoreData" -framework "CoreGraphics" -framework "CoreImage" -framework "CoreLocation" -framework "CoreTelephony" -framework "CoreText" -framework "Foundation" -framework "GLKit" -framework "GoogleMaps" -framework "GoogleMapsBase" -framework "GoogleMapsCore" -framework "GooglePlaces" -framework "ImageIO" -framework "Kingfisher" -framework "Metal" -framework "OpenGLES" -framework "QuartzCore" -framework "Sniffer" -framework "SwiftyJSON" -framework "SystemConfiguration" -framework "UIKit" +OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_PODFILE_DIR_PATH = ${SRCROOT}/. +PODS_ROOT = ${SRCROOT}/Pods +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git b/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy.modulemap a/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy.modulemap new file mode 100644 index 0000000..0c398bf --- /dev/null +++ a/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy.modulemap @@ -0,0 +1,6 @@ +framework module Pods_ayudapy { + umbrella header "Pods-ayudapy-umbrella.h" + + export * + module * { export * } +} diff --git b/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy.release.xcconfig a/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy.release.xcconfig new file mode 100644 index 0000000..d331587 --- /dev/null +++ a/Pods/Target Support Files/Pods-ayudapy/Pods-ayudapy.release.xcconfig @@ -0,0 +1,12 @@ +ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES +FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/Kingfisher" "${PODS_CONFIGURATION_BUILD_DIR}/Sniffer" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyJSON" "${PODS_ROOT}/GoogleMaps/Base/Frameworks" "${PODS_ROOT}/GoogleMaps/Maps/Frameworks" "${PODS_ROOT}/GooglePlaces/Frameworks" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Kingfisher/Kingfisher.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Sniffer/Sniffer.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyJSON/SwiftyJSON.framework/Headers" +LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' +OTHER_LDFLAGS = $(inherited) -ObjC -l"c++" -l"z" -framework "Accelerate" -framework "Alamofire" -framework "CFNetwork" -framework "CoreData" -framework "CoreGraphics" -framework "CoreImage" -framework "CoreLocation" -framework "CoreTelephony" -framework "CoreText" -framework "Foundation" -framework "GLKit" -framework "GoogleMaps" -framework "GoogleMapsBase" -framework "GoogleMapsCore" -framework "GooglePlaces" -framework "ImageIO" -framework "Kingfisher" -framework "Metal" -framework "OpenGLES" -framework "QuartzCore" -framework "Sniffer" -framework "SwiftyJSON" -framework "SystemConfiguration" -framework "UIKit" +OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_PODFILE_DIR_PATH = ${SRCROOT}/. +PODS_ROOT = ${SRCROOT}/Pods +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git b/Pods/Target Support Files/Sniffer/Sniffer-Info.plist a/Pods/Target Support Files/Sniffer/Sniffer-Info.plist new file mode 100644 index 0000000..0a12077 --- /dev/null +++ a/Pods/Target Support Files/Sniffer/Sniffer-Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + ${PRODUCT_BUNDLE_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + FMWK + CFBundleShortVersionString + 2.0.0 + CFBundleSignature + ???? + CFBundleVersion + ${CURRENT_PROJECT_VERSION} + NSPrincipalClass + + + diff --git b/Pods/Target Support Files/Sniffer/Sniffer-dummy.m a/Pods/Target Support Files/Sniffer/Sniffer-dummy.m new file mode 100644 index 0000000..42322b1 --- /dev/null +++ a/Pods/Target Support Files/Sniffer/Sniffer-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_Sniffer : NSObject +@end +@implementation PodsDummy_Sniffer +@end diff --git b/Pods/Target Support Files/Sniffer/Sniffer-prefix.pch a/Pods/Target Support Files/Sniffer/Sniffer-prefix.pch new file mode 100644 index 0000000..beb2a24 --- /dev/null +++ a/Pods/Target Support Files/Sniffer/Sniffer-prefix.pch @@ -0,0 +1,12 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + diff --git b/Pods/Target Support Files/Sniffer/Sniffer-umbrella.h a/Pods/Target Support Files/Sniffer/Sniffer-umbrella.h new file mode 100644 index 0000000..621d661 --- /dev/null +++ a/Pods/Target Support Files/Sniffer/Sniffer-umbrella.h @@ -0,0 +1,16 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + + +FOUNDATION_EXPORT double SnifferVersionNumber; +FOUNDATION_EXPORT const unsigned char SnifferVersionString[]; + diff --git b/Pods/Target Support Files/Sniffer/Sniffer.debug.xcconfig a/Pods/Target Support Files/Sniffer/Sniffer.debug.xcconfig new file mode 100644 index 0000000..105760b --- /dev/null +++ a/Pods/Target Support Files/Sniffer/Sniffer.debug.xcconfig @@ -0,0 +1,11 @@ +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Sniffer +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +OTHER_LDFLAGS = $(inherited) -framework "Foundation" -framework "UIKit" +OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_ROOT = ${SRCROOT} +PODS_TARGET_SRCROOT = ${PODS_ROOT}/Sniffer +PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} +SKIP_INSTALL = YES +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git b/Pods/Target Support Files/Sniffer/Sniffer.modulemap a/Pods/Target Support Files/Sniffer/Sniffer.modulemap new file mode 100644 index 0000000..20886ef --- /dev/null +++ a/Pods/Target Support Files/Sniffer/Sniffer.modulemap @@ -0,0 +1,6 @@ +framework module Sniffer { + umbrella header "Sniffer-umbrella.h" + + export * + module * { export * } +} diff --git b/Pods/Target Support Files/Sniffer/Sniffer.release.xcconfig a/Pods/Target Support Files/Sniffer/Sniffer.release.xcconfig new file mode 100644 index 0000000..105760b --- /dev/null +++ a/Pods/Target Support Files/Sniffer/Sniffer.release.xcconfig @@ -0,0 +1,11 @@ +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Sniffer +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +OTHER_LDFLAGS = $(inherited) -framework "Foundation" -framework "UIKit" +OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_ROOT = ${SRCROOT} +PODS_TARGET_SRCROOT = ${PODS_ROOT}/Sniffer +PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} +SKIP_INSTALL = YES +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git b/Pods/Target Support Files/SwiftyJSON/SwiftyJSON-Info.plist a/Pods/Target Support Files/SwiftyJSON/SwiftyJSON-Info.plist new file mode 100644 index 0000000..e2771ff --- /dev/null +++ a/Pods/Target Support Files/SwiftyJSON/SwiftyJSON-Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + ${PRODUCT_BUNDLE_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + FMWK + CFBundleShortVersionString + 5.0.0 + CFBundleSignature + ???? + CFBundleVersion + ${CURRENT_PROJECT_VERSION} + NSPrincipalClass + + + diff --git b/Pods/Target Support Files/SwiftyJSON/SwiftyJSON-dummy.m a/Pods/Target Support Files/SwiftyJSON/SwiftyJSON-dummy.m new file mode 100644 index 0000000..3159bec --- /dev/null +++ a/Pods/Target Support Files/SwiftyJSON/SwiftyJSON-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_SwiftyJSON : NSObject +@end +@implementation PodsDummy_SwiftyJSON +@end diff --git b/Pods/Target Support Files/SwiftyJSON/SwiftyJSON-prefix.pch a/Pods/Target Support Files/SwiftyJSON/SwiftyJSON-prefix.pch new file mode 100644 index 0000000..beb2a24 --- /dev/null +++ a/Pods/Target Support Files/SwiftyJSON/SwiftyJSON-prefix.pch @@ -0,0 +1,12 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + diff --git b/Pods/Target Support Files/SwiftyJSON/SwiftyJSON-umbrella.h a/Pods/Target Support Files/SwiftyJSON/SwiftyJSON-umbrella.h new file mode 100644 index 0000000..b627dec --- /dev/null +++ a/Pods/Target Support Files/SwiftyJSON/SwiftyJSON-umbrella.h @@ -0,0 +1,16 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + + +FOUNDATION_EXPORT double SwiftyJSONVersionNumber; +FOUNDATION_EXPORT const unsigned char SwiftyJSONVersionString[]; + diff --git b/Pods/Target Support Files/SwiftyJSON/SwiftyJSON.debug.xcconfig a/Pods/Target Support Files/SwiftyJSON/SwiftyJSON.debug.xcconfig new file mode 100644 index 0000000..145ab25 --- /dev/null +++ a/Pods/Target Support Files/SwiftyJSON/SwiftyJSON.debug.xcconfig @@ -0,0 +1,10 @@ +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SwiftyJSON +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_ROOT = ${SRCROOT} +PODS_TARGET_SRCROOT = ${PODS_ROOT}/SwiftyJSON +PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} +SKIP_INSTALL = YES +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git b/Pods/Target Support Files/SwiftyJSON/SwiftyJSON.modulemap a/Pods/Target Support Files/SwiftyJSON/SwiftyJSON.modulemap new file mode 100644 index 0000000..6f41751 --- /dev/null +++ a/Pods/Target Support Files/SwiftyJSON/SwiftyJSON.modulemap @@ -0,0 +1,6 @@ +framework module SwiftyJSON { + umbrella header "SwiftyJSON-umbrella.h" + + export * + module * { export * } +} diff --git b/Pods/Target Support Files/SwiftyJSON/SwiftyJSON.release.xcconfig a/Pods/Target Support Files/SwiftyJSON/SwiftyJSON.release.xcconfig new file mode 100644 index 0000000..145ab25 --- /dev/null +++ a/Pods/Target Support Files/SwiftyJSON/SwiftyJSON.release.xcconfig @@ -0,0 +1,10 @@ +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SwiftyJSON +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_ROOT = ${SRCROOT} +PODS_TARGET_SRCROOT = ${PODS_ROOT}/SwiftyJSON +PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} +SKIP_INSTALL = YES +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git b/ayudapy.xcodeproj/project.pbxproj a/ayudapy.xcodeproj/project.pbxproj new file mode 100644 index 0000000..5dc61f2 --- /dev/null +++ a/ayudapy.xcodeproj/project.pbxproj @@ -0,0 +1,538 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 51; + objects = { + +/* Begin PBXBuildFile section */ + 00A219FF3E6635054413372B /* Pods_ayudapy.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C63B8C4DC2489AD78A899EE9 /* Pods_ayudapy.framework */; }; + BB056E90246480B0001A2929 /* HelpRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB056E8F246480B0001A2929 /* HelpRequest.swift */; }; + BBB1E3DD245A30AF004F92E1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB1E3DC245A30AF004F92E1 /* AppDelegate.swift */; }; + BBB1E3DF245A30AF004F92E1 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB1E3DE245A30AF004F92E1 /* SceneDelegate.swift */; }; + BBB1E3E1245A30AF004F92E1 /* MapViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB1E3E0245A30AF004F92E1 /* MapViewController.swift */; }; + BBB1E3E4245A30AF004F92E1 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BBB1E3E2245A30AF004F92E1 /* Main.storyboard */; }; + BBB1E3E6245A30B4004F92E1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BBB1E3E5245A30B4004F92E1 /* Assets.xcassets */; }; + BBB1E3E9245A30B4004F92E1 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BBB1E3E7245A30B4004F92E1 /* LaunchScreen.storyboard */; }; + BBB1E3F4245B4588004F92E1 /* Format.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB1E3F3245B4588004F92E1 /* Format.swift */; }; + BBB1E3F6245B6388004F92E1 /* InfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB1E3F5245B6388004F92E1 /* InfoViewController.swift */; }; + BBB1E3F8245BD6BD004F92E1 /* PendingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB1E3F7245BD6BD004F92E1 /* PendingsViewController.swift */; }; + BBB1E3FA245C9A25004F92E1 /* HTTPClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB1E3F9245C9A25004F92E1 /* HTTPClient.swift */; }; + BBB1E3FC245C9A9E004F92E1 /* APISessionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB1E3FB245C9A9E004F92E1 /* APISessionManager.swift */; }; + BBB1E3FE245C9AE7004F92E1 /* APIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB1E3FD245C9AE7004F92E1 /* APIError.swift */; }; + BBED5DFA2468BD4100E219D1 /* RequestViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBED5DF92468BD4100E219D1 /* RequestViewController.swift */; }; + BBED5DFD246B83CA00E219D1 /* Favorites.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = BBED5DFB246B83CA00E219D1 /* Favorites.xcdatamodeld */; }; + BBED5E06246BA23800E219D1 /* CoreDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBED5E05246BA23700E219D1 /* CoreDataManager.swift */; }; + BBED5E08246C031C00E219D1 /* MyPendingsTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBED5E07246C031C00E219D1 /* MyPendingsTableViewCell.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 90CB2B19505CF7DC493DEC03 /* Pods-ayudapy.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ayudapy.debug.xcconfig"; path = "Target Support Files/Pods-ayudapy/Pods-ayudapy.debug.xcconfig"; sourceTree = ""; }; + BB056E8F246480B0001A2929 /* HelpRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelpRequest.swift; sourceTree = ""; }; + BBB1E3D9245A30AF004F92E1 /* ayudapy.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ayudapy.app; sourceTree = BUILT_PRODUCTS_DIR; }; + BBB1E3DC245A30AF004F92E1 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + BBB1E3DE245A30AF004F92E1 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + BBB1E3E0245A30AF004F92E1 /* MapViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapViewController.swift; sourceTree = ""; }; + BBB1E3E3245A30AF004F92E1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + BBB1E3E5245A30B4004F92E1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + BBB1E3E8245A30B4004F92E1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + BBB1E3EA245A30B4004F92E1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + BBB1E3F3245B4588004F92E1 /* Format.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Format.swift; sourceTree = ""; }; + BBB1E3F5245B6388004F92E1 /* InfoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoViewController.swift; sourceTree = ""; }; + BBB1E3F7245BD6BD004F92E1 /* PendingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PendingsViewController.swift; sourceTree = ""; }; + BBB1E3F9245C9A25004F92E1 /* HTTPClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPClient.swift; sourceTree = ""; }; + BBB1E3FB245C9A9E004F92E1 /* APISessionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APISessionManager.swift; sourceTree = ""; }; + BBB1E3FD245C9AE7004F92E1 /* APIError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIError.swift; sourceTree = ""; }; + BBED5DF92468BD4100E219D1 /* RequestViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestViewController.swift; sourceTree = ""; }; + BBED5DFC246B83CA00E219D1 /* favoriteRequests.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = favoriteRequests.xcdatamodel; sourceTree = ""; }; + BBED5E03246B8DC900E219D1 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; + BBED5E05246BA23700E219D1 /* CoreDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataManager.swift; sourceTree = ""; }; + BBED5E07246C031C00E219D1 /* MyPendingsTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyPendingsTableViewCell.swift; sourceTree = ""; }; + C63B8C4DC2489AD78A899EE9 /* Pods_ayudapy.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ayudapy.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + F71E6D6A359CF368D8D4CD1F /* Pods-ayudapy.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ayudapy.release.xcconfig"; path = "Target Support Files/Pods-ayudapy/Pods-ayudapy.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + BBB1E3D6245A30AF004F92E1 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 00A219FF3E6635054413372B /* Pods_ayudapy.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 86AB6F03FDBA20D8085FBFC5 /* Frameworks */ = { + isa = PBXGroup; + children = ( + BBED5E03246B8DC900E219D1 /* CoreData.framework */, + C63B8C4DC2489AD78A899EE9 /* Pods_ayudapy.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + BBB1E3D0245A30AF004F92E1 = { + isa = PBXGroup; + children = ( + BBB1E3DB245A30AF004F92E1 /* ayudapy */, + BBB1E3DA245A30AF004F92E1 /* Products */, + EB03FCF923D536EEC47C48B8 /* Pods */, + 86AB6F03FDBA20D8085FBFC5 /* Frameworks */, + ); + sourceTree = ""; + }; + BBB1E3DA245A30AF004F92E1 /* Products */ = { + isa = PBXGroup; + children = ( + BBB1E3D9245A30AF004F92E1 /* ayudapy.app */, + ); + name = Products; + sourceTree = ""; + }; + BBB1E3DB245A30AF004F92E1 /* ayudapy */ = { + isa = PBXGroup; + children = ( + BBED5E0D246C806400E219D1 /* Static */, + BBED5E0C246C7F1400E219D1 /* CoreData */, + BBED5E0B246C7EC600E219D1 /* App */, + BBED5E09246C7DF400E219D1 /* Api */, + BBB1E3F2245B17B4004F92E1 /* Resources */, + BBB1E3E2245A30AF004F92E1 /* Main.storyboard */, + BBB1E3EA245A30B4004F92E1 /* Info.plist */, + BBED5E0A246C7E4B00E219D1 /* ViewControllers */, + ); + path = ayudapy; + sourceTree = ""; + }; + BBB1E3F2245B17B4004F92E1 /* Resources */ = { + isa = PBXGroup; + children = ( + BBB1E3E5245A30B4004F92E1 /* Assets.xcassets */, + ); + path = Resources; + sourceTree = ""; + }; + BBED5E09246C7DF400E219D1 /* Api */ = { + isa = PBXGroup; + children = ( + BBB1E3F9245C9A25004F92E1 /* HTTPClient.swift */, + BBB1E3FB245C9A9E004F92E1 /* APISessionManager.swift */, + BBB1E3FD245C9AE7004F92E1 /* APIError.swift */, + BB056E8F246480B0001A2929 /* HelpRequest.swift */, + ); + path = Api; + sourceTree = ""; + }; + BBED5E0A246C7E4B00E219D1 /* ViewControllers */ = { + isa = PBXGroup; + children = ( + BBED5E07246C031C00E219D1 /* MyPendingsTableViewCell.swift */, + BBB1E3E0245A30AF004F92E1 /* MapViewController.swift */, + BBB1E3F5245B6388004F92E1 /* InfoViewController.swift */, + BBB1E3F7245BD6BD004F92E1 /* PendingsViewController.swift */, + BBED5DF92468BD4100E219D1 /* RequestViewController.swift */, + ); + path = ViewControllers; + sourceTree = ""; + }; + BBED5E0B246C7EC600E219D1 /* App */ = { + isa = PBXGroup; + children = ( + BBB1E3DC245A30AF004F92E1 /* AppDelegate.swift */, + BBB1E3DE245A30AF004F92E1 /* SceneDelegate.swift */, + BBB1E3E7245A30B4004F92E1 /* LaunchScreen.storyboard */, + ); + path = App; + sourceTree = ""; + }; + BBED5E0C246C7F1400E219D1 /* CoreData */ = { + isa = PBXGroup; + children = ( + BBED5E05246BA23700E219D1 /* CoreDataManager.swift */, + BBED5DFB246B83CA00E219D1 /* Favorites.xcdatamodeld */, + ); + path = CoreData; + sourceTree = ""; + }; + BBED5E0D246C806400E219D1 /* Static */ = { + isa = PBXGroup; + children = ( + BBB1E3F3245B4588004F92E1 /* Format.swift */, + ); + path = Static; + sourceTree = ""; + }; + EB03FCF923D536EEC47C48B8 /* Pods */ = { + isa = PBXGroup; + children = ( + 90CB2B19505CF7DC493DEC03 /* Pods-ayudapy.debug.xcconfig */, + F71E6D6A359CF368D8D4CD1F /* Pods-ayudapy.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + BBB1E3D8245A30AF004F92E1 /* ayudapy */ = { + isa = PBXNativeTarget; + buildConfigurationList = BBB1E3ED245A30B4004F92E1 /* Build configuration list for PBXNativeTarget "ayudapy" */; + buildPhases = ( + 7D5466A8F7DFC73F8268B1E5 /* [CP] Check Pods Manifest.lock */, + BBB1E3D5245A30AF004F92E1 /* Sources */, + BBB1E3D6245A30AF004F92E1 /* Frameworks */, + BBB1E3D7245A30AF004F92E1 /* Resources */, + 655BA6D9F428FA482DA9C9F0 /* [CP] Copy Pods Resources */, + 86F29503F6EAF171FCB49AB0 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = ayudapy; + productName = ayudapy; + productReference = BBB1E3D9245A30AF004F92E1 /* ayudapy.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + BBB1E3D1245A30AF004F92E1 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1110; + LastUpgradeCheck = 1110; + ORGANIZATIONNAME = "Mobile Roshka"; + TargetAttributes = { + BBB1E3D8245A30AF004F92E1 = { + CreatedOnToolsVersion = 11.1; + }; + }; + }; + buildConfigurationList = BBB1E3D4245A30AF004F92E1 /* Build configuration list for PBXProject "ayudapy" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = BBB1E3D0245A30AF004F92E1; + productRefGroup = BBB1E3DA245A30AF004F92E1 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + BBB1E3D8245A30AF004F92E1 /* ayudapy */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + BBB1E3D7245A30AF004F92E1 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + BBB1E3E9245A30B4004F92E1 /* LaunchScreen.storyboard in Resources */, + BBB1E3E6245A30B4004F92E1 /* Assets.xcassets in Resources */, + BBB1E3E4245A30AF004F92E1 /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 655BA6D9F428FA482DA9C9F0 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-ayudapy/Pods-ayudapy-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-ayudapy/Pods-ayudapy-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ayudapy/Pods-ayudapy-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 7D5466A8F7DFC73F8268B1E5 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-ayudapy-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 86F29503F6EAF171FCB49AB0 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-ayudapy/Pods-ayudapy-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-ayudapy/Pods-ayudapy-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ayudapy/Pods-ayudapy-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + BBB1E3D5245A30AF004F92E1 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + BBB1E3F4245B4588004F92E1 /* Format.swift in Sources */, + BBB1E3F8245BD6BD004F92E1 /* PendingsViewController.swift in Sources */, + BBB1E3E1245A30AF004F92E1 /* MapViewController.swift in Sources */, + BBB1E3FC245C9A9E004F92E1 /* APISessionManager.swift in Sources */, + BB056E90246480B0001A2929 /* HelpRequest.swift in Sources */, + BBED5E08246C031C00E219D1 /* MyPendingsTableViewCell.swift in Sources */, + BBED5DFD246B83CA00E219D1 /* Favorites.xcdatamodeld in Sources */, + BBB1E3FA245C9A25004F92E1 /* HTTPClient.swift in Sources */, + BBED5E06246BA23800E219D1 /* CoreDataManager.swift in Sources */, + BBB1E3DD245A30AF004F92E1 /* AppDelegate.swift in Sources */, + BBB1E3DF245A30AF004F92E1 /* SceneDelegate.swift in Sources */, + BBB1E3F6245B6388004F92E1 /* InfoViewController.swift in Sources */, + BBED5DFA2468BD4100E219D1 /* RequestViewController.swift in Sources */, + BBB1E3FE245C9AE7004F92E1 /* APIError.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + BBB1E3E2245A30AF004F92E1 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + BBB1E3E3245A30AF004F92E1 /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + BBB1E3E7245A30B4004F92E1 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + BBB1E3E8245A30B4004F92E1 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + BBB1E3EB245A30B4004F92E1 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + BBB1E3EC245A30B4004F92E1 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + BBB1E3EE245A30B4004F92E1 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 90CB2B19505CF7DC493DEC03 /* Pods-ayudapy.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + INFOPLIST_FILE = ayudapy/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = roshka.ayudapy; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + BBB1E3EF245A30B4004F92E1 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F71E6D6A359CF368D8D4CD1F /* Pods-ayudapy.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + INFOPLIST_FILE = ayudapy/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = roshka.ayudapy; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + BBB1E3D4245A30AF004F92E1 /* Build configuration list for PBXProject "ayudapy" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + BBB1E3EB245A30B4004F92E1 /* Debug */, + BBB1E3EC245A30B4004F92E1 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + BBB1E3ED245A30B4004F92E1 /* Build configuration list for PBXNativeTarget "ayudapy" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + BBB1E3EE245A30B4004F92E1 /* Debug */, + BBB1E3EF245A30B4004F92E1 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCVersionGroup section */ + BBED5DFB246B83CA00E219D1 /* Favorites.xcdatamodeld */ = { + isa = XCVersionGroup; + children = ( + BBED5DFC246B83CA00E219D1 /* favoriteRequests.xcdatamodel */, + ); + currentVersion = BBED5DFC246B83CA00E219D1 /* favoriteRequests.xcdatamodel */; + path = Favorites.xcdatamodeld; + sourceTree = ""; + versionGroupType = wrapper.xcdatamodel; + }; +/* End XCVersionGroup section */ + }; + rootObject = BBB1E3D1245A30AF004F92E1 /* Project object */; +} diff --git b/ayudapy.xcodeproj/project.xcworkspace/contents.xcworkspacedata a/ayudapy.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..d9d4b0e --- /dev/null +++ a/ayudapy.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git b/ayudapy.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist a/ayudapy.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ a/ayudapy.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git b/ayudapy.xcodeproj/project.xcworkspace/xcuserdata/roshka.xcuserdatad/UserInterfaceState.xcuserstate a/ayudapy.xcodeproj/project.xcworkspace/xcuserdata/roshka.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..754cfd5 Binary files /dev/null and a/ayudapy.xcodeproj/project.xcworkspace/xcuserdata/roshka.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git b/ayudapy.xcodeproj/xcuserdata/roshka.xcuserdatad/xcschemes/xcschememanagement.plist a/ayudapy.xcodeproj/xcuserdata/roshka.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..ffdc3ef --- /dev/null +++ a/ayudapy.xcodeproj/xcuserdata/roshka.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,14 @@ + + + + + SchemeUserState + + ayudapy.xcscheme_^#shared#^_ + + orderHint + 7 + + + + diff --git b/ayudapy.xcworkspace/contents.xcworkspacedata a/ayudapy.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..7b53a9d --- /dev/null +++ a/ayudapy.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,16 @@ + + + + + + + + + + + diff --git b/ayudapy.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist a/ayudapy.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ a/ayudapy.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git b/ayudapy.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings a/ayudapy.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..6b30c74 --- /dev/null +++ a/ayudapy.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,10 @@ + + + + + BuildSystemType + Original + PreviewsEnabled + + + diff --git b/ayudapy.xcworkspace/xcuserdata/roshka.xcuserdatad/UserInterfaceState.xcuserstate a/ayudapy.xcworkspace/xcuserdata/roshka.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..cd6d5ca Binary files /dev/null and a/ayudapy.xcworkspace/xcuserdata/roshka.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git b/ayudapy.xcworkspace/xcuserdata/roshka.xcuserdatad/WorkspaceSettings.xcsettings a/ayudapy.xcworkspace/xcuserdata/roshka.xcuserdatad/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..dd7403b --- /dev/null +++ a/ayudapy.xcworkspace/xcuserdata/roshka.xcuserdatad/WorkspaceSettings.xcsettings @@ -0,0 +1,16 @@ + + + + + BuildLocationStyle + UseAppPreferences + CustomBuildLocationType + RelativeToDerivedData + DerivedDataLocationStyle + Default + IssueFilterStyle + ShowActiveSchemeOnly + LiveSourceIssuesEnabled + + + diff --git b/ayudapy.xcworkspace/xcuserdata/roshka.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist a/ayudapy.xcworkspace/xcuserdata/roshka.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 0000000..69ba14e --- /dev/null +++ a/ayudapy.xcworkspace/xcuserdata/roshka.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,6 @@ + + + diff --git b/ayudapy/Api/APIError.swift a/ayudapy/Api/APIError.swift new file mode 100644 index 0000000..bf85fce --- /dev/null +++ a/ayudapy/Api/APIError.swift @@ -0,0 +1,19 @@ +// +// APIError.swift +// ayudapy +// +// Created by Mobile Roshka on 5/1/20. +// Copyright © 2020 Mobile Roshka. All rights reserved. +// + +import Foundation + +class APIError { + var code: String? + var message: String? + + init(message: String) { + self.code = nil + self.message = message + } +} diff --git b/ayudapy/Api/APISessionManager.swift a/ayudapy/Api/APISessionManager.swift new file mode 100644 index 0000000..325a7d0 --- /dev/null +++ a/ayudapy/Api/APISessionManager.swift @@ -0,0 +1,29 @@ +// +// APISessionManager.swift +// ayudapy +// +// Created by Mobile Roshka on 5/1/20. +// Copyright © 2020 Mobile Roshka. All rights reserved. +// + +import Foundation +import Alamofire +import Sniffer + + +class APISessionManager: Session { + + static let shared: APISessionManager = { + let configuration = URLSessionConfiguration.default + configuration.timeoutIntervalForRequest = 40 + configuration.requestCachePolicy = .reloadIgnoringLocalAndRemoteCacheData + + Sniffer.enable(in: configuration) + + let manager = APISessionManager(configuration: configuration) + return manager + + + }() + +} diff --git b/ayudapy/Api/HTTPClient.swift a/ayudapy/Api/HTTPClient.swift new file mode 100644 index 0000000..33cf32b --- /dev/null +++ a/ayudapy/Api/HTTPClient.swift @@ -0,0 +1,91 @@ +// +// HTTPClient.swift +// ayudapy +// +// Created by Mobile Roshka on 5/1/20. +// Copyright © 2020 Mobile Roshka. All rights reserved. +// + +import Foundation +import Alamofire +import Sniffer + + + typealias APIHeaders = HTTPHeaders + typealias APIParameters = [String: Any] + +enum APIMethod { + case get + case post + case delete + case put + + fileprivate var value: HTTPMethod { + switch self { + case .get: + return HTTPMethod.get + case .post: + return HTTPMethod.post + case .delete: + return HTTPMethod.delete + case .put: + return HTTPMethod.put + } + } +} + +enum APIEncoding { + case json + case url + + fileprivate var value: ParameterEncoding { + switch self { + case .json: + return JSONEncoding.default + default: + return URLEncoding.default + } + } +} +class HTTPClient { + //se hace un metodo estatico o metodo de la clase + class func request(endpoint: String, + method: APIMethod = .get, + encoding: APIEncoding = .url, + parameters: APIParameters? = nil, + headers: APIHeaders? = nil, + onSuccess: @escaping (T) -> Void, + onFailure: ((APIError)-> Void)? = nil){ + + let request = APISessionManager + .shared + .request(endpoint, + method: method.value, + parameters: parameters, + encoding: encoding.value, + headers: headers) + + request.responseJSON(completionHandler: { response in + + guard response.error == nil else { + onFailure?(APIError(message: "Hay un error")) + return + } + + guard let data = response.data else { + onFailure?(APIError(message: "No vino ningun dato")) + return + } + + guard let object = try? JSONDecoder().decode(T.self, from: data) else { + onFailure?(APIError(message: "Lo sentimos")) + return + } + + onSuccess(object) + //print(response.result) + + }) + + } +} diff --git b/ayudapy/Api/HelpRequest.swift a/ayudapy/Api/HelpRequest.swift new file mode 100644 index 0000000..ddd7695 --- /dev/null +++ a/ayudapy/Api/HelpRequest.swift @@ -0,0 +1,53 @@ +// +// helpRequest.swift +// ayudapy +// +// Created by Mobile Roshka on 5/7/20. +// Copyright © 2020 Mobile Roshka. All rights reserved. +// + +import Foundation + +struct HelpRequests: Codable { + let type: String + let features: [Feature] +} + +struct Feature: Codable { + let type: FeatureType + let geometry: Geometry + let properties: Properties +} + +struct Geometry: Codable { + let type: GeometryType + let coordinates: [Double] +} + +enum GeometryType: String, Codable { + case point = "Point" +} + +struct Properties: Codable { + let pk: Int + let title, name, added: String +} + +enum FeatureType: String, Codable { + case feature = "Feature" +} + +struct Request: Codable { + let id: Int + let title, message, name, phone: String + let address, city: String + let location: Location + let picture: String + let active: Bool + let added: String +} + +struct Location: Codable { + let type: String + let coordinates: [Double] +} diff --git b/ayudapy/App/AppDelegate.swift a/ayudapy/App/AppDelegate.swift new file mode 100644 index 0000000..ae447e7 --- /dev/null +++ a/ayudapy/App/AppDelegate.swift @@ -0,0 +1,38 @@ +// +// AppDelegate.swift +// ayudapy +// +// Created by Mobile Roshka on 4/29/20. +// Copyright © 2020 Mobile Roshka. All rights reserved. +// + +import UIKit +import GoogleMaps +import CoreData + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + GMSServices.provideAPIKey("AIzaSyCDCQFseYL7K7okR_YL7lmh2SCRct9YiiM") + return true + } + + // MARK: UISceneSession Lifecycle + + func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { + // Called when a new scene session is being created. + // Use this method to select a configuration to create the new scene with. + return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) + } + + func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { + // Called when the user discards a scene session. + // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. + // Use this method to release any resources that were specific to the discarded scenes, as they will not return. + } + + +} + diff --git b/ayudapy/App/Base.lproj/LaunchScreen.storyboard a/ayudapy/App/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..865e932 --- /dev/null +++ a/ayudapy/App/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git b/ayudapy/App/SceneDelegate.swift a/ayudapy/App/SceneDelegate.swift new file mode 100644 index 0000000..b528aad --- /dev/null +++ a/ayudapy/App/SceneDelegate.swift @@ -0,0 +1,53 @@ +// +// SceneDelegate.swift +// ayudapy +// +// Created by Mobile Roshka on 4/29/20. +// Copyright © 2020 Mobile Roshka. All rights reserved. +// + +import UIKit + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + + var window: UIWindow? + + + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. + // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. + // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). + guard let _ = (scene as? UIWindowScene) else { return } + } + + func sceneDidDisconnect(_ scene: UIScene) { + // Called as the scene is being released by the system. + // This occurs shortly after the scene enters the background, or when its session is discarded. + // Release any resources associated with this scene that can be re-created the next time the scene connects. + // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). + } + + func sceneDidBecomeActive(_ scene: UIScene) { + // Called when the scene has moved from an inactive state to an active state. + // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. + } + + func sceneWillResignActive(_ scene: UIScene) { + // Called when the scene will move from an active state to an inactive state. + // This may occur due to temporary interruptions (ex. an incoming phone call). + } + + func sceneWillEnterForeground(_ scene: UIScene) { + // Called as the scene transitions from the background to the foreground. + // Use this method to undo the changes made on entering the background. + } + + func sceneDidEnterBackground(_ scene: UIScene) { + // Called as the scene transitions from the foreground to the background. + // Use this method to save data, release shared resources, and store enough scene-specific state information + // to restore the scene back to its current state. + } + + +} + diff --git b/ayudapy/Base.lproj/Main.storyboard a/ayudapy/Base.lproj/Main.storyboard new file mode 100644 index 0000000..c842af3 --- /dev/null +++ a/ayudapy/Base.lproj/Main.storyboard @@ -0,0 +1,206 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git b/ayudapy/CoreData/CoreDataManager.swift a/ayudapy/CoreData/CoreDataManager.swift new file mode 100644 index 0000000..3daeb24 --- /dev/null +++ a/ayudapy/CoreData/CoreDataManager.swift @@ -0,0 +1,56 @@ +// +// CoreDataManager.swift +// ayudapy +// +// Created by Mobile Roshka on 5/12/20. +// Copyright © 2020 Mobile Roshka. All rights reserved. +// + +import Foundation +import CoreData + +class CoreDataManager { + private let container: NSPersistentContainer! + init(){ + container = NSPersistentContainer(name: "Favorites") + setupDatabase() + } + private func setupDatabase(){ + container.loadPersistentStores{ (desc, error) in + if let error = error { + print("error loading store \(desc) - \(error)") + return + } + print("Database Ready!") + } + } + + func saveFavorites(id: Int, name: String, date: Date, address: String, message: String, completion: @escaping()-> Void){ + let context = container.viewContext + let favorite = Entity(context: context) + //let ifavorite = MyPendings(context: context) + favorite.id = Int64(id) + favorite.name = name + favorite.date = date + favorite.address = address + favorite.message = message + do{ + try context.save() + print("\(id) guardado") + completion() + }catch{ + print("error al guardar \(error)") + } + } + func fetchFavorites()->[Entity]{ + let fetchRequest: NSFetchRequest = Entity.fetchRequest() + do { + let result = try container.viewContext.fetch(fetchRequest) + return result + }catch{ + print("Error al obtener la información de favoritos - \(error)") + } + + return [] + } +} diff --git b/ayudapy/CoreData/Favorites.xcdatamodeld/favoriteRequests.xcdatamodel/contents a/ayudapy/CoreData/Favorites.xcdatamodeld/favoriteRequests.xcdatamodel/contents new file mode 100644 index 0000000..17f590e --- /dev/null +++ a/ayudapy/CoreData/Favorites.xcdatamodeld/favoriteRequests.xcdatamodel/contents @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git b/ayudapy/Data/MyPendings+CoreDataProperties.swift a/ayudapy/Data/MyPendings+CoreDataProperties.swift new file mode 100644 index 0000000..5239e14 --- /dev/null +++ a/ayudapy/Data/MyPendings+CoreDataProperties.swift @@ -0,0 +1,24 @@ +// +// MyPendings+CoreDataProperties.swift +// +// +// Created by Mobile Roshka on 5/13/20. +// +// + +import Foundation +import CoreData + + +extension MyPendings { + + @nonobjc public class func fetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "MyPendings") + } + + @NSManaged public var id: Int16 + @NSManaged public var title: String? + @NSManaged public var message: String? + @NSManaged public var date: String? + +} diff --git b/ayudapy/Entity+CoreDataClass.swift a/ayudapy/Entity+CoreDataClass.swift new file mode 100644 index 0000000..70a352a --- /dev/null +++ a/ayudapy/Entity+CoreDataClass.swift @@ -0,0 +1,15 @@ +// +// Entity+CoreDataClass.swift +// +// +// Created by Mobile Roshka on 5/13/20. +// +// + +import Foundation +import CoreData + +@objc(Entity) +public class Entity: NSManagedObject { + +} diff --git b/ayudapy/Entity+CoreDataProperties.swift a/ayudapy/Entity+CoreDataProperties.swift new file mode 100644 index 0000000..51f7d3a --- /dev/null +++ a/ayudapy/Entity+CoreDataProperties.swift @@ -0,0 +1,26 @@ +// +// Entity+CoreDataProperties.swift +// +// +// Created by Mobile Roshka on 5/13/20. +// +// + +import Foundation +import CoreData + + +extension Entity { + + @nonobjc public class func fetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "Entity") + } + + @NSManaged public var id: Int64 + @NSManaged public var name: String? + @NSManaged public var message: String? + @NSManaged public var date: Date? + @NSManaged public var address: String? + @NSManaged public var title: String? + +} diff --git b/ayudapy/Info.plist a/ayudapy/Info.plist new file mode 100644 index 0000000..15387e6 --- /dev/null +++ a/ayudapy/Info.plist @@ -0,0 +1,66 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + NSLocationWhenInUseUsageDescription + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git b/ayudapy/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json a/ayudapy/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d8db8d6 --- /dev/null +++ a/ayudapy/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "83.5x83.5", + "scale" : "2x" + }, + { + "idiom" : "ios-marketing", + "size" : "1024x1024", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git b/ayudapy/Resources/Assets.xcassets/AyudaPY/Contents.json a/ayudapy/Resources/Assets.xcassets/AyudaPY/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ a/ayudapy/Resources/Assets.xcassets/AyudaPY/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git b/ayudapy/Resources/Assets.xcassets/AyudaPY/addAyuda.imageset/Contents.json a/ayudapy/Resources/Assets.xcassets/AyudaPY/addAyuda.imageset/Contents.json new file mode 100644 index 0000000..b1d6c2b --- /dev/null +++ a/ayudapy/Resources/Assets.xcassets/AyudaPY/addAyuda.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "addAyuda@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git b/ayudapy/Resources/Assets.xcassets/AyudaPY/addAyuda.imageset/addAyuda@3x.png a/ayudapy/Resources/Assets.xcassets/AyudaPY/addAyuda.imageset/addAyuda@3x.png new file mode 100644 index 0000000..dcaeded Binary files /dev/null and a/ayudapy/Resources/Assets.xcassets/AyudaPY/addAyuda.imageset/addAyuda@3x.png differ diff --git b/ayudapy/Resources/Assets.xcassets/AyudaPY/downArrow.imageset/Contents.json a/ayudapy/Resources/Assets.xcassets/AyudaPY/downArrow.imageset/Contents.json new file mode 100644 index 0000000..e9249a1 --- /dev/null +++ a/ayudapy/Resources/Assets.xcassets/AyudaPY/downArrow.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "downArrow@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git b/ayudapy/Resources/Assets.xcassets/AyudaPY/downArrow.imageset/downArrow@3x.png a/ayudapy/Resources/Assets.xcassets/AyudaPY/downArrow.imageset/downArrow@3x.png new file mode 100644 index 0000000..b53e523 Binary files /dev/null and a/ayudapy/Resources/Assets.xcassets/AyudaPY/downArrow.imageset/downArrow@3x.png differ diff --git b/ayudapy/Resources/Assets.xcassets/AyudaPY/iconListoChecked.imageset/Contents.json a/ayudapy/Resources/Assets.xcassets/AyudaPY/iconListoChecked.imageset/Contents.json new file mode 100644 index 0000000..16f8436 --- /dev/null +++ a/ayudapy/Resources/Assets.xcassets/AyudaPY/iconListoChecked.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "iconListoChecked@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git b/ayudapy/Resources/Assets.xcassets/AyudaPY/iconListoChecked.imageset/iconListoChecked@3x.png a/ayudapy/Resources/Assets.xcassets/AyudaPY/iconListoChecked.imageset/iconListoChecked@3x.png new file mode 100644 index 0000000..a1815a3 Binary files /dev/null and a/ayudapy/Resources/Assets.xcassets/AyudaPY/iconListoChecked.imageset/iconListoChecked@3x.png differ diff --git b/ayudapy/Resources/Assets.xcassets/AyudaPY/iconListoUnchecked.imageset/Contents.json a/ayudapy/Resources/Assets.xcassets/AyudaPY/iconListoUnchecked.imageset/Contents.json new file mode 100644 index 0000000..5397145 --- /dev/null +++ a/ayudapy/Resources/Assets.xcassets/AyudaPY/iconListoUnchecked.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "iconListoUnchecked@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git b/ayudapy/Resources/Assets.xcassets/AyudaPY/iconListoUnchecked.imageset/iconListoUnchecked@3x.png a/ayudapy/Resources/Assets.xcassets/AyudaPY/iconListoUnchecked.imageset/iconListoUnchecked@3x.png new file mode 100644 index 0000000..09d389a Binary files /dev/null and a/ayudapy/Resources/Assets.xcassets/AyudaPY/iconListoUnchecked.imageset/iconListoUnchecked@3x.png differ diff --git b/ayudapy/Resources/Assets.xcassets/AyudaPY/iconPendientesChecked.imageset/Contents.json a/ayudapy/Resources/Assets.xcassets/AyudaPY/iconPendientesChecked.imageset/Contents.json new file mode 100644 index 0000000..85608a7 --- /dev/null +++ a/ayudapy/Resources/Assets.xcassets/AyudaPY/iconPendientesChecked.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "iconPendientesChecked@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git b/ayudapy/Resources/Assets.xcassets/AyudaPY/iconPendientesChecked.imageset/iconPendientesChecked@3x.png a/ayudapy/Resources/Assets.xcassets/AyudaPY/iconPendientesChecked.imageset/iconPendientesChecked@3x.png new file mode 100644 index 0000000..ae4a9f2 Binary files /dev/null and a/ayudapy/Resources/Assets.xcassets/AyudaPY/iconPendientesChecked.imageset/iconPendientesChecked@3x.png differ diff --git b/ayudapy/Resources/Assets.xcassets/AyudaPY/iconPendientesUnchecked.imageset/Contents.json a/ayudapy/Resources/Assets.xcassets/AyudaPY/iconPendientesUnchecked.imageset/Contents.json new file mode 100644 index 0000000..506c9d7 --- /dev/null +++ a/ayudapy/Resources/Assets.xcassets/AyudaPY/iconPendientesUnchecked.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "iconPendientesUnchecked@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git b/ayudapy/Resources/Assets.xcassets/AyudaPY/iconPendientesUnchecked.imageset/iconPendientesUnchecked@3x.png a/ayudapy/Resources/Assets.xcassets/AyudaPY/iconPendientesUnchecked.imageset/iconPendientesUnchecked@3x.png new file mode 100644 index 0000000..27c894f Binary files /dev/null and a/ayudapy/Resources/Assets.xcassets/AyudaPY/iconPendientesUnchecked.imageset/iconPendientesUnchecked@3x.png differ diff --git b/ayudapy/Resources/Assets.xcassets/AyudaPY/infoButton.imageset/Contents.json a/ayudapy/Resources/Assets.xcassets/AyudaPY/infoButton.imageset/Contents.json new file mode 100644 index 0000000..7ff6154 --- /dev/null +++ a/ayudapy/Resources/Assets.xcassets/AyudaPY/infoButton.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "infoButton@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git b/ayudapy/Resources/Assets.xcassets/AyudaPY/infoButton.imageset/infoButton@3x.png a/ayudapy/Resources/Assets.xcassets/AyudaPY/infoButton.imageset/infoButton@3x.png new file mode 100644 index 0000000..8372979 Binary files /dev/null and a/ayudapy/Resources/Assets.xcassets/AyudaPY/infoButton.imageset/infoButton@3x.png differ diff --git b/ayudapy/Resources/Assets.xcassets/AyudaPY/logoHeader.imageset/Contents.json a/ayudapy/Resources/Assets.xcassets/AyudaPY/logoHeader.imageset/Contents.json new file mode 100644 index 0000000..cf122f6 --- /dev/null +++ a/ayudapy/Resources/Assets.xcassets/AyudaPY/logoHeader.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "logoHeader@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git b/ayudapy/Resources/Assets.xcassets/AyudaPY/logoHeader.imageset/logoHeader@3x.png a/ayudapy/Resources/Assets.xcassets/AyudaPY/logoHeader.imageset/logoHeader@3x.png new file mode 100644 index 0000000..f038d9e Binary files /dev/null and a/ayudapy/Resources/Assets.xcassets/AyudaPY/logoHeader.imageset/logoHeader@3x.png differ diff --git b/ayudapy/Resources/Assets.xcassets/AyudaPY/pendientesButton.imageset/Contents.json a/ayudapy/Resources/Assets.xcassets/AyudaPY/pendientesButton.imageset/Contents.json new file mode 100644 index 0000000..93b4b8c --- /dev/null +++ a/ayudapy/Resources/Assets.xcassets/AyudaPY/pendientesButton.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "pendientesButton@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git b/ayudapy/Resources/Assets.xcassets/AyudaPY/pendientesButton.imageset/pendientesButton@3x.png a/ayudapy/Resources/Assets.xcassets/AyudaPY/pendientesButton.imageset/pendientesButton@3x.png new file mode 100644 index 0000000..c2e3274 Binary files /dev/null and a/ayudapy/Resources/Assets.xcassets/AyudaPY/pendientesButton.imageset/pendientesButton@3x.png differ diff --git b/ayudapy/Resources/Assets.xcassets/AyudaPY/pinCheckedGMAPS.imageset/Contents.json a/ayudapy/Resources/Assets.xcassets/AyudaPY/pinCheckedGMAPS.imageset/Contents.json new file mode 100644 index 0000000..75f3d22 --- /dev/null +++ a/ayudapy/Resources/Assets.xcassets/AyudaPY/pinCheckedGMAPS.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "pinCheckedGMAPS@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git b/ayudapy/Resources/Assets.xcassets/AyudaPY/pinCheckedGMAPS.imageset/pinCheckedGMAPS@3x.png a/ayudapy/Resources/Assets.xcassets/AyudaPY/pinCheckedGMAPS.imageset/pinCheckedGMAPS@3x.png new file mode 100644 index 0000000..b52aa69 Binary files /dev/null and a/ayudapy/Resources/Assets.xcassets/AyudaPY/pinCheckedGMAPS.imageset/pinCheckedGMAPS@3x.png differ diff --git b/ayudapy/Resources/Assets.xcassets/AyudaPY/pinFavGMAPS.imageset/Contents.json a/ayudapy/Resources/Assets.xcassets/AyudaPY/pinFavGMAPS.imageset/Contents.json new file mode 100644 index 0000000..d74bb4d --- /dev/null +++ a/ayudapy/Resources/Assets.xcassets/AyudaPY/pinFavGMAPS.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "pinFavGMAPS@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git b/ayudapy/Resources/Assets.xcassets/AyudaPY/pinFavGMAPS.imageset/pinFavGMAPS@3x.png a/ayudapy/Resources/Assets.xcassets/AyudaPY/pinFavGMAPS.imageset/pinFavGMAPS@3x.png new file mode 100644 index 0000000..be457c4 Binary files /dev/null and a/ayudapy/Resources/Assets.xcassets/AyudaPY/pinFavGMAPS.imageset/pinFavGMAPS@3x.png differ diff --git b/ayudapy/Resources/Assets.xcassets/AyudaPY/pinGMAPS.imageset/Contents.json a/ayudapy/Resources/Assets.xcassets/AyudaPY/pinGMAPS.imageset/Contents.json new file mode 100644 index 0000000..9e0db02 --- /dev/null +++ a/ayudapy/Resources/Assets.xcassets/AyudaPY/pinGMAPS.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "pinGMAPS@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git b/ayudapy/Resources/Assets.xcassets/AyudaPY/pinGMAPS.imageset/pinGMAPS@3x.png a/ayudapy/Resources/Assets.xcassets/AyudaPY/pinGMAPS.imageset/pinGMAPS@3x.png new file mode 100644 index 0000000..c9b2ba8 Binary files /dev/null and a/ayudapy/Resources/Assets.xcassets/AyudaPY/pinGMAPS.imageset/pinGMAPS@3x.png differ diff --git b/ayudapy/Resources/Assets.xcassets/AyudaPY/upArrow.imageset/Contents.json a/ayudapy/Resources/Assets.xcassets/AyudaPY/upArrow.imageset/Contents.json new file mode 100644 index 0000000..7ba3dd5 --- /dev/null +++ a/ayudapy/Resources/Assets.xcassets/AyudaPY/upArrow.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "upArrow@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git b/ayudapy/Resources/Assets.xcassets/AyudaPY/upArrow.imageset/upArrow@3x.png a/ayudapy/Resources/Assets.xcassets/AyudaPY/upArrow.imageset/upArrow@3x.png new file mode 100644 index 0000000..599d6d7 Binary files /dev/null and a/ayudapy/Resources/Assets.xcassets/AyudaPY/upArrow.imageset/upArrow@3x.png differ diff --git b/ayudapy/Resources/Assets.xcassets/Color.colorset/Contents.json a/ayudapy/Resources/Assets.xcassets/Color.colorset/Contents.json new file mode 100644 index 0000000..a7f0101 --- /dev/null +++ a/ayudapy/Resources/Assets.xcassets/Color.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + }, + "colors" : [ + { + "idiom" : "universal", + "color" : { + "color-space" : "srgb", + "components" : { + "red" : "0.334", + "alpha" : "1.000", + "blue" : "0.558", + "green" : "0.329" + } + } + } + ] +} \ No newline at end of file diff --git b/ayudapy/Resources/Assets.xcassets/Contents.json a/ayudapy/Resources/Assets.xcassets/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ a/ayudapy/Resources/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git b/ayudapy/Static/Format.swift a/ayudapy/Static/Format.swift new file mode 100644 index 0000000..ce94509 --- /dev/null +++ a/ayudapy/Static/Format.swift @@ -0,0 +1,133 @@ +// +// Format.swift +// ayudapy +// +// Created by Mobile Roshka on 4/30/20. +// Copyright © 2020 Mobile Roshka. All rights reserved. +// + +import Foundation +import SwiftUI + +class Format{ + let headerBackgroundColor = UIColor(displayP3Red: 200, green: 200, blue: 200, alpha: 0.6) + let footerBackgroundColor = UIColor(displayP3Red: 200, green: 200, blue: 200, alpha: 0.5) + let footerTextColor = UIColor(displayP3Red: 10/255, green: 10/255, blue: 10/255, alpha: 1) + let helpMeTextColor = UIColor(displayP3Red: 255/255, green: 30/255, blue: 100/255, alpha: 1) + let subTitleTextColor = UIColor(displayP3Red: 85/255, green: 85/255, blue: 140/255, alpha: 1) + let titleTextColor = UIColor(displayP3Red: 197/255, green: 52/255, blue: 75/255, alpha: 1) + let bodyTextColor = UIColor(displayP3Red: 10/255, green: 10/255, blue: 10/255, alpha: 1) + let dateTextColor = UIColor(displayP3Red: 50/255, green: 50/255, blue: 50/255, alpha: 1) + let dateFontStyle = UIFont.systemFont(ofSize: 10) + let titleFontStyle = UIFont.boldSystemFont(ofSize: 18) + let bodyFontStyle = UIFont.systemFont(ofSize: 15) + let subTitleFontStyle = UIFont.systemFont(ofSize: 16) + +} +extension String{ + + enum dateUnit{ + case year + case month + case day + case hour + } + + func getDatePart(dateUnit: dateUnit)->String{ + let date = self + var returnValue = String() + + switch dateUnit { + case .year: + returnValue = String(date.prefix(4)) + + case .month: + let start = date.index(date.startIndex, offsetBy: 5) + let end = date.index(date.startIndex, offsetBy: 7) + let range = start..String{ + var month = self + switch month { + case "01": + month = "JAN" + case "02": + month = "FEB" + case "03": + month = "MAR" + case "04": + month = "APR" + case "05": + month = "MAY" + case "06": + month = "JUN" + case "07": + month = "JUL" + case "08": + month = "AUG" + case "09": + month = "SET" + case "10": + month = "OCT" + case "11": + month = "NOV" + case "12": + month = "DEC" + default: + print("No corresponde a un mes") + } + return month + } + func getMonthInSpanish()->String{ + var month = self + switch month { + case "01": + month = "enero" + case "02": + month = "febrero" + case "03": + month = "marzo" + case "04": + month = "abril" + case "05": + month = "mayo" + case "06": + month = "junio" + case "07": + month = "julio" + case "08": + month = "agosto" + case "09": + month = "setiembre" + case "10": + month = "octubre" + case "11": + month = "noviembre" + case "12": + month = "diciembre" + default: + print("No corresponde a un mes") + } + return month + } +} + diff --git b/ayudapy/ViewControllers/InfoViewController.swift a/ayudapy/ViewControllers/InfoViewController.swift new file mode 100644 index 0000000..d9ee732 --- /dev/null +++ a/ayudapy/ViewControllers/InfoViewController.swift @@ -0,0 +1,36 @@ +// +// AyudaViewController.swift +// ayudapy +// +// Created by Mobile Roshka on 4/30/20. +// Copyright © 2020 Mobile Roshka. All rights reserved. +// + +import UIKit + +class InfoViewController: UIViewController { + + @IBAction func termsURL(_ sender: Any) { + if let url = NSURL(string: "https://ayudapy.org/legal"){ + UIApplication.shared.open(url as URL) + } + } + + @IBAction func frecuentQuestionsUrl(_ sender: Any) { + if let url = NSURL(string: "https://ayudapy.org/preguntas_frecuentes"){ + UIApplication.shared.open(url as URL) + } + } + + @IBAction func visitURL(_ sender: Any) { + if let url = NSURL(string: "https://ayudapy.org"){ + UIApplication.shared.open(url as URL) + } + } + + override func viewDidLoad() { + super.viewDidLoad() + + } + +} diff --git b/ayudapy/ViewControllers/MapViewController.swift a/ayudapy/ViewControllers/MapViewController.swift new file mode 100644 index 0000000..bd73f21 --- /dev/null +++ a/ayudapy/ViewControllers/MapViewController.swift @@ -0,0 +1,237 @@ +// +// MapViewController.swift +// ayudapy +// +// Created by Mobile Roshka on 4/29/20. +// Copyright © 2020 Mobile Roshka. All rights reserved. +// + +import UIKit +import GoogleMaps +import Alamofire +import Kingfisher + + +class MapViewController: UIViewController { + + private let manager = CoreDataManager() + var locationManager: CLLocationManager! + var currentLocation: CLLocation? + var mapView: GMSMapView! + var zoomLevel: Float = 16.0 + let format:Format = Format.init() + var bbox = String() + var id = Int() + + + let defaultLocation = CLLocation(latitude: -57.5879897, longitude: -43.2723609) + //-57.5879897,-43.2723609 -> Posición de Roshka + + func addPins(){ + + } + + + //logo cabecera ayudapy.org + func addHeader(){ + let headerView = UIView(frame: CGRect(x: 0, y: 40, width: view.frame.width, height: 50)) + headerView.backgroundColor = format.headerBackgroundColor + let headerIcon = UIImageView(frame: CGRect(x: 0, y: 0, width: 504/4, height: 99/4)) + headerView.addSubview(headerIcon) + headerIcon.image = UIImage(named: "logoHeader") + headerIcon.center.x = headerView.frame.width / 2 + headerIcon.center.y = headerView.frame.height / 2 + mapView.addSubview(headerView) + } + + //Barra inferior con botones + func addFooter(){ + let footerView = UIView(frame: CGRect(x: 0, y: view.frame.height*0.78, width: view.frame.width * 0.65, height: view.frame.width * 0.22)) + footerView.backgroundColor = format.footerBackgroundColor + footerView.center.x = view.frame.width / 2 + footerView.layer.cornerRadius = 25 + + //Botón Info + let infoButton = UIButton(frame: CGRect(x: footerView.frame.width * 0.1, y: footerView.frame.height * 0.05, width: footerView.frame.width * 0.25, height: footerView.frame.height * 0.7)) + let infoLabel = UILabel(frame: CGRect(x: footerView.frame.width * 0.1, y: footerView.frame.height * 0.8, width: footerView.frame.width * 0.25, height: footerView.frame.height * 0.1)) + infoLabel.text = "INFO" + infoLabel.textAlignment = .center + infoLabel.font = UIFont.systemFont(ofSize: 8) + infoLabel.textColor = format.footerTextColor + infoButton.setImage(UIImage(named: "infoButton"), for: .normal) + infoButton.addTarget(self, action: #selector(goToInfo), for: .touchUpInside) + + footerView.addSubview(infoLabel) + footerView.addSubview(infoButton) + + let helpMeButton = UIButton(frame: CGRect(x: footerView.frame.width / 3, y: -footerView.frame.height * 0.2 , width: footerView.frame.width * 0.25, height: footerView.frame.height * 0.9)) + helpMeButton.center.x = footerView.frame.width / 2 + footerView.addSubview(helpMeButton) + + let helpMeLabel = UILabel(frame: CGRect(x: footerView.frame.width * 0.1, y: footerView.frame.height * 0.8, width: footerView.frame.width * 0.25, height: footerView.frame.height * 0.1)) + helpMeLabel.center.x = footerView.frame.width / 2 + helpMeLabel.text = "AYUDENME" + helpMeLabel.textColor = format.helpMeTextColor + helpMeLabel.font = UIFont.systemFont(ofSize: 8) + helpMeLabel.textAlignment = .center + helpMeButton.setImage(UIImage(named: "addAyuda"), for: .normal) + helpMeButton.addTarget(self, action: #selector(openLink), for: .touchUpInside) + + footerView.addSubview(helpMeLabel) + + //Botón Pendientes + let pendingButton = UIButton(frame: CGRect(x: footerView.frame.width * 2/3, y: footerView.frame.height * 0.05 , width: footerView.frame.width * 0.25, height: footerView.frame.height * 0.7)) + footerView.addSubview(pendingButton) + let pendingLabel = UILabel(frame: CGRect(x: footerView.frame.width * 2/3, y: footerView.frame.height * 0.8, width: footerView.frame.width * 0.25, height: footerView.frame.height * 0.1)) + pendingLabel.text = "PENDIENTES" + pendingLabel.textColor = format.footerTextColor + pendingLabel.font = UIFont.systemFont(ofSize: 8) + pendingLabel.textAlignment = .center + pendingButton.setImage(UIImage(named: "pendientesButton"), for: .normal) + pendingButton.addTarget(self, action: #selector(goToPendientes), for: .touchUpInside) + footerView.addSubview(pendingLabel) + + + mapView.addSubview(footerView) + } + + //Selectors + @objc func goToInfo(){ + let infoVC = self.storyboard?.instantiateViewController(withIdentifier: "infoVC") as! InfoViewController + show(infoVC, sender: nil) + } + @objc func openLink(){ + if let url = NSURL(string: "https://ayudapy.org/recibir"){ + UIApplication.shared.open(url as URL) + } + } + @objc func goToPendientes(){ + let pendientesVC = self.storyboard?.instantiateViewController(withIdentifier: "pendientesVC") as! PendingsViewController + show(pendientesVC, sender: nil) + } + @objc func goToRequestViewController(pk: Int){ + let pedidosVC = self.storyboard?.instantiateViewController(withIdentifier: "pedidosVC")as! RequestViewController + pedidosVC.id = id + print(id) + pedidosVC.helpRequest() + show(pedidosVC, sender: nil) + } + + //Función para hallar el boundingbox actual + func getBoundingBox(mapView: GMSMapView)->String{ + + let northEast = mapView.projection.coordinate(for: CGPoint(x: mapView.layer.frame.width, y: 0)) + let southWest = mapView.projection.coordinate(for: CGPoint(x: 0, y: mapView.layer.frame.height)) + let boundingBox = "\(northEast.longitude),\(northEast.latitude),\(southWest.longitude),\(southWest.latitude)" + + return boundingBox + } + + //Solicitud api de Pedidos de ayuda + func helpRequest(){ + let url = "https://ayudapy.org/api/v1/helprequestsgeo/?in_bbox=" + + let boundingBox = getBoundingBox(mapView: mapView) + HTTPClient.request(endpoint: url + boundingBox, onSuccess: { (response: HelpRequests) in + let features = response.features + for i in 0.. Bool { + id = Int(marker.title!)! + goToRequestViewController(pk: id) + return true + } + func mapView(_ mapView: GMSMapView, willMove gesture: Bool) { + helpRequest() + } +} + + + diff --git b/ayudapy/ViewControllers/MyPendingsTableViewCell.swift a/ayudapy/ViewControllers/MyPendingsTableViewCell.swift new file mode 100644 index 0000000..fefc90c --- /dev/null +++ a/ayudapy/ViewControllers/MyPendingsTableViewCell.swift @@ -0,0 +1,31 @@ +// +// MyPendingsTableViewCell.swift +// ayudapy +// +// Created by Mobile Roshka on 5/13/20. +// Copyright © 2020 Mobile Roshka. All rights reserved. +// + +import UIKit + +class MyPendingsTableViewCell: UITableViewCell { + + @IBOutlet weak var dayLabel: UILabel! + @IBOutlet weak var monthLabel: UILabel! + @IBOutlet weak var titleLabel: UILabel! + @IBOutlet weak var detailLabel: UILabel! + let format = Format.init() + + override func awakeFromNib() { + super.awakeFromNib() + dayLabel.textColor = format.subTitleTextColor + monthLabel.textColor = format.titleTextColor + + } + + override func setSelected(_ selected: Bool, animated: Bool) { + super.setSelected(selected, animated: animated) + + } + +} diff --git b/ayudapy/ViewControllers/PendingsViewController.swift a/ayudapy/ViewControllers/PendingsViewController.swift new file mode 100644 index 0000000..a5167fa --- /dev/null +++ a/ayudapy/ViewControllers/PendingsViewController.swift @@ -0,0 +1,71 @@ +// +// PendientesViewController.swift +// ayudapy +// +// Created by Mobile Roshka on 5/1/20. +// Copyright © 2020 Mobile Roshka. All rights reserved. +// + +import UIKit +import CoreData + +class PendingsViewController: UIViewController { + + let manager = CoreDataManager() + var favorites = [(Int64, String, String)]() + let format = Format.init() + + @IBOutlet weak var tableView: UITableView! + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + let array = manager.fetchFavorites() + for i in 0.. Int { + return self.manager.fetchFavorites().count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! MyPendingsTableViewCell + let date = favorites[indexPath.row].2 + let month = date.getDatePart(dateUnit: .month) + let monthString = month.getMonthShortString() + let day = date.getDatePart(dateUnit: .day) + + cell.titleLabel.text = "\(favorites[indexPath.row].1)" + cell.detailLabel.text = "\(favorites[indexPath.row].2)" + cell.dayLabel.text = day + cell.monthLabel.text = monthString + return cell + } + func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + let headerLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 100, height: 50)) + headerLabel.text = "Mis Pendientes" + headerLabel.textColor = format.titleTextColor + headerLabel.textAlignment = .center + let centerX = view.frame.width / 2 + headerLabel.center.x = centerX + let heartImage = UIImageView(frame: CGRect(x: centerX + 60, y: 10, width: 22, height: 22)) + heartImage.center.y = headerLabel.frame.height/2 + heartImage.image = UIImage(named: "iconPendientesChecked") + headerLabel.addSubview(heartImage) + return headerLabel + } + + +} diff --git b/ayudapy/ViewControllers/RequestViewController.swift a/ayudapy/ViewControllers/RequestViewController.swift new file mode 100644 index 0000000..86f9bfe --- /dev/null +++ a/ayudapy/ViewControllers/RequestViewController.swift @@ -0,0 +1,180 @@ +// +// PedidosViewController.swift +// ayudapy +// +// Created by Mobile Roshka on 5/10/20. +// Copyright © 2020 Mobile Roshka. All rights reserved. +// + +import UIKit +import Kingfisher + +class RequestViewController: UIViewController { + var id = Int() + var name = String() + var date = String() + var message = String() + var hRequestTitle = String() + var city = String() + var address = String() + var phoneNumber = String() + var imageUrl: String? + var coordinates = [Double]() + var format = Format.init() + var isFavorite = Bool() + var checked = Bool() + var favorites = [Int]() +// private let appDelegate = UIApplication.shared.delegate as! AppDelegate +// private let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext + private let manager = CoreDataManager() + + + func helpRequest(){ + let url = "https://ayudapy.org/api/v1/helprequests/" + HTTPClient.request(endpoint: url + "\(id)", onSuccess: { (response: Request?) in + if let requestInformation = response{ + print("asdf\(requestInformation)") + self.name = response!.name + self.message = response!.message + self.hRequestTitle = response!.title + self.date = response!.added + self.city = response!.city + self.phoneNumber = response!.phone + self.address = response!.address + self.imageUrl = response!.picture + self.showRequestInformation() + } + else { + print("Falló la conexión con el servidor") + } + + }) + } + + func showRequestInformation(){ + let dateLabel = UILabel() + dateLabel.text = "Publicado el:\(date)" + dateLabel.font = format.dateFontStyle + dateLabel.textColor = format.dateTextColor + let titleLbl = UILabel() + titleLbl.text = "\(hRequestTitle)" + titleLbl.font = format.titleFontStyle + titleLbl.textColor = format.titleTextColor + let messageLabel = UILabel() + messageLabel.text = "\(message)" + messageLabel.font = format.bodyFontStyle + messageLabel.numberOfLines = .max + messageLabel.lineBreakMode = .byWordWrapping + messageLabel.sizeToFit() + let imageView = UIImageView() + if imageUrl != nil { + imageView.layer.frame = CGRect(x: 0, y: 0, width: 100, height: 100) + imageView.kf.indicatorType = .activity + imageView.kf.setImage(with: URL(string: imageUrl!)) + } + + let addressSubTittle = UILabel() + addressSubTittle.text = "Dirección" + addressSubTittle.textColor = format.subTitleTextColor + addressSubTittle.font = format.subTitleFontStyle + let addressLabel = UILabel() + addressLabel.text = "\(address)" + addressLabel.lineBreakMode = .byWordWrapping + addressLabel.numberOfLines = .max +// let cityLabel = UILabel() +// cityLabel.text = city + let howToGetButton = UIButton()//(frame: CGRect(x: 0, y: 0, width: view.frame.width * 0.9, height: 20)) + howToGetButton.backgroundColor = format.subTitleTextColor + howToGetButton.layer.cornerRadius = 10 +// let howToGetLabel = UILabel() +// howToGetLabel.text = "Como LLegar" +// howToGetLabel.font = format.subTitleFontStyle +// howToGetLabel.textColor = .white +// howToGetLabel.textAlignment = .center +// howToGetButton.addSubview(howToGetLabel) + howToGetButton.setTitle("Como llegar", for: .normal) + howToGetButton.titleLabel?.font = format.subTitleFontStyle + let contactLabel = UILabel() + contactLabel.text = "Contacto" + contactLabel.textColor = format.subTitleTextColor + contactLabel.font = format.subTitleFontStyle + let contactNameLabel = UILabel() + contactNameLabel.text = "\(name)" + let contactButton = UIButton() + contactButton.backgroundColor = format.subTitleTextColor + contactButton.titleLabel?.font = format.subTitleFontStyle + contactButton.layer.cornerRadius = 10 + contactButton.setTitle("\(phoneNumber)", for: .normal) + + let isFavoriteButton = UIButton() + let heartImage = UIImage(named: "iconPendientesUnchecked") + isFavoriteButton.setImage(heartImage, for: .normal) + isFavoriteButton.frame.size = CGSize(width: 80, height: 80) + isFavoriteButton.addTarget(self, action: #selector(addFavorite), for: .touchUpInside) + + let isFavoriteLabel = UILabel() + isFavoriteLabel.text = "Agregar a mis pendientes" + isFavoriteLabel.font = format.bodyFontStyle + isFavoriteLabel.numberOfLines = 2 + isFavoriteLabel.textAlignment = .center + let isFavoriteStackView = UIStackView(arrangedSubviews: [isFavoriteButton, isFavoriteLabel]) + isFavoriteStackView.axis = .vertical + isFavoriteStackView.distribution = .fillEqually + isFavoriteStackView.spacing = 0 + + let checkButton = UIButton() + let checkImage = UIImage(named: "iconListoUnchecked") + checkButton.setImage(checkImage, for: .normal) + checkButton.frame.size = CGSize(width: 80, height: 80) + + let checkLabel = UILabel() + checkLabel.text = "Marcar como listo" + checkLabel.font = format.bodyFontStyle + checkLabel.textAlignment = .center + + let checkStackView = UIStackView(arrangedSubviews: [checkButton, checkLabel]) + checkStackView.axis = .vertical + checkStackView.distribution = .fillEqually + checkStackView.spacing = 0 + + let footerStackView = UIStackView(arrangedSubviews: [isFavoriteStackView, checkStackView]) + footerStackView.axis = .horizontal + footerStackView.distribution = .fillEqually + footerStackView.spacing = 50 + //let scrollView = UIScrollView(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.height)) + let footerSpacing = UIView() + + let stackView = UIStackView(arrangedSubviews: [dateLabel, titleLbl, messageLabel, addressSubTittle, addressLabel,howToGetButton,contactLabel,contactNameLabel, contactButton, footerStackView, footerSpacing]) + stackView.axis = .vertical + stackView.distribution = .fillProportionally + stackView.spacing = 10 + stackView.layer.frame = CGRect(x: 20, y: 20, width: view.frame.width * 0.9, height: view.frame.height * 0.9) + //view.addSubview(scrollView) + //scrollView.addSubview(stackView) + view.addSubview(stackView) + //view.addSubview(titleLbl) + } + func isFavoriteCheck()->Bool{ + + return false + } + func isReadyCheck()->Bool{ + return false + } + func convertDate(stringDate: String)->Date{ + let dateFormatter = Date() + //dateFormatter.date(from: "stringDate") + return dateFormatter + } + + @objc func addFavorite(){ + manager.saveFavorites(id: id, name: name, date: convertDate(stringDate: date), address: address, message: hRequestTitle) { [weak self] in + print(self as Any) + } + } + + override func viewDidLoad() { + helpRequest() + } + +}