source/UberRides/RideRequestView.swift (134 lines of code) (raw):

// // RideRequestView.swift // UberRides // // Copyright © 2016 Uber Technologies, Inc. All rights reserved. // // 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 WebKit import CoreLocation import UberCore /** Delegates are informed of events that occur in the RideRequestView such as errors. - Warning: The Ride Request Widget is deprecated, and will no longer work for new apps. Existing apps have until 05/31/2018 to migrate. See the Uber API Changelog for more details. */ @objc(UBSDKRideRequestViewDelegate) public protocol RideRequestViewDelegate { /** An error has occurred in the Ride Request Control. - parameter rideRequestView: the RideRequestView - parameter error: the NSError that occured, with a code of RideRequestViewErrorType */ func rideRequestView(_ rideRequestView: RideRequestView, didReceiveError error: NSError) } /** A view that shows the embedded Uber experience. - Warning: The Ride Request Widget is deprecated, and will no longer work for new apps. Existing apps have until 05/31/2018 to migrate. See the Uber API Changelog for more details. */ @objc(UBSDKRideRequestView) public class RideRequestView: UIView { /// The RideRequestViewDelegate of this view. @objc public var delegate: RideRequestViewDelegate? /// The access token used to authorize the web view @objc public var accessToken: AccessToken? /// Ther RideParameters to use for prefilling the RideRequestView @objc public var rideParameters: RideParameters var webView: WKWebView let redirectURL = "uberconnect://oauth" static let sourceString = "ride_request_view" /** Initializes to show the embedded Uber ride request view. - parameter rideParameters: The RideParameters to use for presetting values; defaults to using the current location for pickup - parameter accessToken: specific access token to use with web view; defaults to using TokenManager's default token - parameter frame: frame of the view. Defaults to CGRectZero - returns: An initialized RideRequestView */ @objc public required init(rideParameters: RideParameters, accessToken: AccessToken?, frame: CGRect) { self.rideParameters = rideParameters self.accessToken = accessToken let configuration = WKWebViewConfiguration() configuration.processPool = Configuration.shared.processPool webView = WKWebView(frame: CGRect.zero, configuration: configuration) super.init(frame: frame) initialSetup() } /** Initializes to show the embedded Uber ride request view. Uses the TokenManager's default accessToken - parameter rideParameters: The RideParameters to use for presetting values - parameter frame: frame of the view - returns: An initialized RideRequestView */ @objc public convenience init(rideParameters: RideParameters, frame: CGRect) { self.init(rideParameters: rideParameters, accessToken: TokenManager.fetchToken(), frame: frame) } /** Initializes to show the embedded Uber ride request view. Frame defaults to CGRectZero Uses the TokenManager's default accessToken - parameter rideParameters: The RideParameters to use for presetting values - returns: An initialized RideRequestView */ @objc public convenience init(rideParameters: RideParameters) { self.init(rideParameters: rideParameters, accessToken: TokenManager.fetchToken(), frame: CGRect.zero) } /** Initializes to show the embedded Uber ride request view. Uses the current location for pickup Uses the TokenManager's default accessToken - parameter frame: frame of the view - returns: An initialized RideRequestView */ @objc public convenience override init(frame: CGRect) { self.init(rideParameters: RideParametersBuilder().build(), accessToken: TokenManager.fetchToken(), frame: frame) } /** Initializes to show the embedded Uber ride request view. Uses the current location for pickup Uses the TokenManager's default accessToken Frame defaults to CGRectZero - returns: An initialized RideRequestView */ @objc public convenience init() { self.init(rideParameters: RideParametersBuilder().build(), accessToken: TokenManager.fetchToken(), frame: CGRect.zero) } required public init?(coder aDecoder: NSCoder) { rideParameters = RideParametersBuilder().build() let configuration = WKWebViewConfiguration() configuration.processPool = Configuration.shared.processPool webView = WKWebView(frame: CGRect.zero, configuration: configuration) super.init(coder: aDecoder) initialSetup() } deinit { webView.scrollView.delegate = nil NotificationCenter.default.removeObserver(self) } // MARK: Public /** Load the Uber Ride Request Widget view. Requires that the access token has been retrieved. */ @objc public func load() { guard !webView.isLoading else { return } guard let accessToken = accessToken else { self.delegate?.rideRequestView(self, didReceiveError: RideRequestViewErrorFactory.errorForType(.accessTokenMissing)) return } let tokenString = accessToken.tokenString rideParameters.source = rideParameters.source ?? RideRequestView.sourceString let endpoint = Components.rideRequestWidget(rideParameters: rideParameters) guard let request = Request(session: nil, endpoint: endpoint, bearerToken: tokenString) else { delegate?.rideRequestView(self, didReceiveError: RideRequestViewErrorFactory.errorForType(.invalidRequest)) return } request.prepare() var urlRequest = request.urlRequest urlRequest.cachePolicy = .returnCacheDataElseLoad webView.load(urlRequest) } /** Stop loading the Ride Request Widget View and clears the view. If the view has already loaded, calling this still clears the view. */ @objc public func cancelLoad() { webView.stopLoading() if let url = URL(string: "about:blank") { webView.load(URLRequest(url: url)) } } // MARK: Private private func initialSetup() { webView.navigationDelegate = self webView.scrollView.delegate = self NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillAppear(_:)), name: UIResponder.keyboardWillShowNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(keyboardDidAppear(_:)), name: UIResponder.keyboardDidShowNotification, object: nil) setupWebView() } private func setupWebView() { addSubview(webView) webView.translatesAutoresizingMaskIntoConstraints = false webView.scrollView.bounces = false let views = ["webView": webView] let horizontalConstraints = NSLayoutConstraint.constraints(withVisualFormat: "H:|[webView]|", options: NSLayoutConstraint.FormatOptions(rawValue: 0), metrics: nil, views: views) let verticalConstraints = NSLayoutConstraint.constraints(withVisualFormat: "V:|[webView]|", options: NSLayoutConstraint.FormatOptions(rawValue: 0), metrics: nil, views: views) addConstraints(horizontalConstraints) addConstraints(verticalConstraints) } // MARK: Keyboard Notifications @objc func keyboardWillAppear(_ notification: Notification) { webView.scrollView.isScrollEnabled = false } @objc func keyboardDidAppear(_ notification: Notification) { webView.scrollView.isScrollEnabled = true } } // MARK: WKNavigationDelegate extension RideRequestView: WKNavigationDelegate { public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { if let url = navigationAction.request.url { if url.absoluteString.lowercased().hasPrefix(redirectURL.lowercased()) { let error = OAuthUtil.parseRideWidgetErrorFromURL(url) delegate?.rideRequestView(self, didReceiveError: error) decisionHandler(.cancel) return } else if url.scheme == "tel" || url.scheme == "sms" { UIApplication.shared.open(url) { [weak self] succeeded in guard let self = self, !succeeded else { decisionHandler(.cancel) return } self.delegate?.rideRequestView(self, didReceiveError: RideRequestViewErrorFactory.errorForType(.notSupported)) decisionHandler(.cancel) } return } } decisionHandler(.allow) } public func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { delegate?.rideRequestView(self, didReceiveError: RideRequestViewErrorFactory.errorForType(.networkError)) } public func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) { guard (error as NSError).code != 102 else { return } delegate?.rideRequestView(self, didReceiveError: RideRequestViewErrorFactory.errorForType(.networkError)) } } // MARK: UIScrollViewDelegate extension RideRequestView : UIScrollViewDelegate { public func scrollViewDidScroll(_ scrollView: UIScrollView) { if !scrollView.isScrollEnabled { scrollView.bounds = self.webView.bounds } } }