source/UberCore/Authentication/LoginButton.swift (142 lines of code) (raw):

// // UberLoginButton.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 UIKit @objc public enum LoginButtonState : Int { case signedIn case signedOut } /** * Protocol to listen to login button events, such as logging in / out */ @objc(UBSDKLoginButtonDelegate) public protocol LoginButtonDelegate { /** The Login Button attempted to log out - parameter button: The LoginButton involved - parameter success: True if log out succeeded, false otherwise */ @objc func loginButton(_ button: LoginButton, didLogoutWithSuccess success: Bool) /** The Login Button completed a login - parameter button: The LoginButton involved - parameter accessToken: The access token that - parameter error: The error that occured */ @objc func loginButton(_ button: LoginButton, didCompleteLoginWithToken accessToken: AccessToken?, error: NSError?) } /** * Protocol to provide content for login */ @objc(UBSDKLoginButtonDataSource) public protocol LoginButtonDataSource { @objc func prefillValues(_ button: LoginButton) -> Prefill? } /// Button to handle logging in to Uber @objc(UBSDKLoginButton) public class LoginButton: UberButton { let horizontalCenterPadding: CGFloat = 50 let loginVerticalPadding: CGFloat = 15 let loginHorizontalEdgePadding: CGFloat = 15 /// The LoginButtonDelegate for this button @objc public weak var delegate: LoginButtonDelegate? @objc public weak var dataSource: LoginButtonDataSource? /// The LoginManager to use for log in @objc public var loginManager: LoginManager { didSet { refreshContent() } } /// The UberScopes to request @objc public var scopes: [UberScope] /// The view controller to present login over. Used @objc public var presentingViewController: UIViewController? /// The current LoginButtonState of this button (signed in / signed out) @objc public var buttonState: LoginButtonState { if let _ = TokenManager.fetchToken(identifier: accessTokenIdentifier, accessGroup: keychainAccessGroup) { return .signedIn } else { return .signedOut } } private var accessTokenIdentifier: String { return loginManager.accessTokenIdentifier } private var keychainAccessGroup: String { return loginManager.keychainAccessGroup } private var loginCompletion: ((_ accessToken: AccessToken?, _ error: NSError?) -> Void)? @objc public init(frame: CGRect, scopes: [UberScope], loginManager: LoginManager) { self.loginManager = loginManager self.scopes = scopes super.init(frame: frame) setup() } public required init?(coder aDecoder: NSCoder) { loginManager = LoginManager(loginType: .native) scopes = [] super.init(coder: aDecoder) setup() } deinit { NotificationCenter.default.removeObserver(self) } //Mark: UberButton /** Setup the LoginButton by adding a target to the button and setting the login completion block */ override public func setup() { super.setup() NotificationCenter.default.addObserver(self, selector: #selector(refreshContent), name: Notification.Name(rawValue: TokenManager.tokenManagerDidSaveTokenNotification), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(refreshContent), name: Notification.Name(rawValue: TokenManager.tokenManagerDidDeleteTokenNotification), object: nil) addTarget(self, action: #selector(uberButtonTapped), for: .touchUpInside) loginCompletion = { token, error in self.delegate?.loginButton(self, didCompleteLoginWithToken: token, error: error) self.refreshContent() } sizeToFit() } /** Updates the content of the button. Sets the image icon and font, as well as the text */ override public func setContent() { super.setContent() let buttonFont = UIFont.systemFont(ofSize: 13) let titleText = titleForButtonState(buttonState) let logo = getImage("ic_logo_white") uberTitleLabel.font = buttonFont uberTitleLabel.text = titleText uberImageView.image = logo uberImageView.contentMode = .center } /** Adds the layout constraints for the Login button. */ override public func setConstraints() { uberTitleLabel.translatesAutoresizingMaskIntoConstraints = false uberImageView.translatesAutoresizingMaskIntoConstraints = false uberImageView.setContentHuggingPriority(UILayoutPriority.defaultHigh, for: .horizontal) uberTitleLabel.setContentHuggingPriority(UILayoutPriority.defaultHigh, for: .horizontal) uberTitleLabel.setContentHuggingPriority(UILayoutPriority.defaultHigh, for: .vertical) let imageLeftConstraint = NSLayoutConstraint(item: uberImageView, attribute: .left, relatedBy: .equal, toItem: self, attribute: .left, multiplier: 1.0, constant: loginHorizontalEdgePadding) let imageTopConstraint = NSLayoutConstraint(item: uberImageView, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1.0, constant: loginVerticalPadding) let imageBottomConstraint = NSLayoutConstraint(item: uberImageView, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1.0, constant: -loginVerticalPadding) let titleLabelRightConstraint = NSLayoutConstraint(item: uberTitleLabel, attribute: .right, relatedBy: .equal, toItem: self, attribute: .right, multiplier: 1.0, constant: -loginHorizontalEdgePadding) let titleLabelCenterYConstraint = NSLayoutConstraint(item: uberTitleLabel, attribute: .centerY, relatedBy: .equal, toItem: uberImageView, attribute: .centerY, multiplier: 1.0, constant: 0.0) let imagePaddingRightConstraint = NSLayoutConstraint(item: uberTitleLabel, attribute: .left, relatedBy: .greaterThanOrEqual , toItem: uberImageView, attribute: .right, multiplier: 1.0, constant: imageLabelPadding) let horizontalCenterPaddingConstraint = NSLayoutConstraint(item: uberTitleLabel, attribute: .left, relatedBy: .greaterThanOrEqual , toItem: uberImageView, attribute: .right, multiplier: 1.0, constant: horizontalCenterPadding) horizontalCenterPaddingConstraint.priority = UILayoutPriority.defaultLow addConstraints([imageLeftConstraint, imageTopConstraint, imageBottomConstraint]) addConstraints([titleLabelRightConstraint, titleLabelCenterYConstraint]) addConstraints([imagePaddingRightConstraint, horizontalCenterPaddingConstraint]) } //Mark: UIView override public func sizeThatFits(_ size: CGSize) -> CGSize { let sizeThatFits = super.sizeThatFits(size) let iconSizeThatFits = uberImageView.image?.size ?? CGSize.zero let labelSizeThatFits = uberTitleLabel.intrinsicContentSize let labelMinHeight = labelSizeThatFits.height + 2 * loginVerticalPadding let iconMinHeight = iconSizeThatFits.height + 2 * loginVerticalPadding let height = max(iconMinHeight, labelMinHeight) return CGSize(width: sizeThatFits.width + horizontalCenterPadding, height: height) } override public func updateConstraints() { refreshContent() super.updateConstraints() } //Mark: Internal Interface @objc func uberButtonTapped(_ button: UIButton) { switch buttonState { case .signedIn: let success = TokenManager.deleteToken(identifier: accessTokenIdentifier, accessGroup: keychainAccessGroup) delegate?.loginButton(self, didLogoutWithSuccess: success) refreshContent() case .signedOut: loginManager.login( requestedScopes: scopes, presentingViewController: presentingViewController, prefillValues: dataSource?.prefillValues(self), completion: loginCompletion ) } } //Mark: Private Interface @objc private func refreshContent() { DispatchQueue.main.async { [weak self] in guard let strongSelf = self else { return } strongSelf.uberTitleLabel.text = strongSelf.titleForButtonState(strongSelf.buttonState) } } private func titleForButtonState(_ buttonState: LoginButtonState) -> String { var titleText: String! switch buttonState { case .signedIn: titleText = NSLocalizedString("Sign Out", bundle: Bundle(for: type(of: self)), comment: "Login Button Sign Out Description").uppercased() case .signedOut: titleText = NSLocalizedString("Sign In", bundle: Bundle(for: type(of: self)), comment: "Login Button Sign In Description").uppercased() } return titleText } private func getImage(_ name: String) -> UIImage? { let bundle = Bundle(for: LoginButton.self) return UIImage(named: name, in: bundle, compatibleWith: nil)?.withRenderingMode(.alwaysTemplate) } }