版本记录
版本号 | 时间 |
---|---|
V1.0 | 2018.12.01 星期六 |
前言
源码
1. Swift
首先看一下工程结构。
下面看一下sb中的内容
下面看一下代码
1. API.swift
import Foundation
// MARK: - return types
public enum APIResult {
case success
case failure(_ error: String?)
}
public enum MotivationalLotteryResult {
case success(_ motivation: String)
case failure
}
// MARK: - API access class
public class API {
// Update this URL definition
static let baseURL = URL(string:
static let defaultsKey = "TOKEN-KEY"
static let defaults = UserDefaults.standard
public static var token: String? {
get {
return defaults.string(forKey: API.defaultsKey)
}
set {
defaults.set(newValue, forKey: API.defaultsKey)
}
}
// MARK: - Access functions
/// Register a new user and save the returned access token.
///
/// - Parameters:
/// - username: desired username
/// - password: desired password
/// - completion: closure that receives an `APIResult` after the call completes. This is **NOT** called on the main thread.
public static func register(_ username: String, password: String, completion: @escaping (APIResult) -> Void) {
struct RegisterData: Encodable {
let username: String
let password: String
}
guard let baseURL = baseURL else {
completion(.failure(nil))
return
}
let body = RegisterData(username: username, password: password)
let url = baseURL.appendingPathComponent("/api/user")
var registerRequest = URLRequest(url: url)
registerRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
registerRequest.httpMethod = "POST"
do {
registerRequest.httpBody = try JSONEncoder().encode(body)
} catch {
completion(.failure(nil))
return
}
URLSession.shared.dataTask(with: registerRequest) { data, response, _ in
DispatchQueue.main.async {
guard let jsonData = data else {
completion(.failure(nil))
return
}
guard
let httpResponse = response as? HTTPURLResponse,
200..<300 ~= httpResponse.statusCode else {
do {
let json = try JSONSerialization.jsonObject(with: jsonData, options: JSONSerialization.ReadingOptions.allowFragments) as? [String: Any]
if let error = json?["reason"] as? String {
completion(.failure(error))
return
}
} catch {}
completion(.failure(nil))
return
}
do {
let token = try JSONDecoder().decode(Token.self, from: jsonData)
self.token = token.token
completion(.success)
} catch {
completion(.failure(nil))
}
}
}
.resume()
}
/// Login an existing user and save the returned access token.
///
/// - Parameters:
/// - username: user's username
/// - password: user's password
/// - completion: closure that receives an `APIResult` after the call completes. This is **NOT** called on the main thread.
public static func login(_ username: String, password: String, completion: @escaping (APIResult) -> Void) {
guard let loginString = "\(username):\(password)"
.data(using: .utf8)?
.base64EncodedString()
else {
fatalError()
}
guard let baseURL = baseURL else {
completion(.failure(nil))
return
}
let url = baseURL.appendingPathComponent("/api/login")
var loginRequest = URLRequest(url: url)
loginRequest.addValue("Basic \(loginString)", forHTTPHeaderField: "Authorization")
loginRequest.httpMethod = "POST"
URLSession.shared.dataTask(with: loginRequest) { data, response, _ in
DispatchQueue.main.async {
guard let jsonData = data else {
completion(.failure(nil))
return
}
guard
let httpResponse = response as? HTTPURLResponse,
200..<300 ~= httpResponse.statusCode else {
do {
let json = try JSONSerialization.jsonObject(with: jsonData, options: JSONSerialization.ReadingOptions.allowFragments) as? [String: Any]
if let error = json?["reason"] as? String {
completion(.failure(error))
return
}
} catch {}
completion(.failure(nil))
return
}
do {
let token = try JSONDecoder().decode(Token.self, from: jsonData)
self.token = token.token
completion(.success)
} catch {
completion(.failure(nil))
}
}
}
.resume()
}
/// Log the user out and delete the saved access token.
public static func logout() {
guard let baseURL = baseURL, let token = token else {
return
}
let url = baseURL.appendingPathComponent("/api/logout")
var logoutRequest = URLRequest(url: url)
logoutRequest.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
logoutRequest.httpMethod = "POST"
URLSession.shared.dataTask(with: logoutRequest) { _, response, _ in
DispatchQueue.main.async {
guard
let httpResponse = response as? HTTPURLResponse,
200..<300 ~= httpResponse.statusCode
else {
return
}
self.token = nil
}
}
.resume()
}
/// Change the user's password
///
/// - Parameters:
/// - newPassword: the desired new password
/// - completion: closure that receives an `APIResult` after the call completes. This is **NOT** called on the main thread.
public static func changePassword(_ newPassword: String, completion: @escaping (APIResult) -> Void) {
struct NewPasswordData: Encodable {
let newPassword: String
}
guard let baseURL = baseURL, let token = token else {
completion(.failure(nil))
return
}
let body = NewPasswordData(newPassword: newPassword)
let url = baseURL.appendingPathComponent("/api/user")
var changeRequest = URLRequest(url: url)
changeRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
changeRequest.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
changeRequest.httpMethod = "PUT"
do {
changeRequest.httpBody = try JSONEncoder().encode(body)
} catch {
completion(.failure(nil))
return
}
URLSession.shared.dataTask(with: changeRequest) { _, response, _ in
DispatchQueue.main.async {
guard
let httpResponse = response as? HTTPURLResponse,
200..<300 ~= httpResponse.statusCode
else {
completion(.failure(nil))
return
}
completion(.success)
}
}
.resume()
}
/// Retreive a new motivational quote for the logged in user
///
/// - Parameter completion: closure that receives a `MotivationalLotteryResult` after the call completes. This is **NOT** called on the main thread.
public static func motivationalLottery(_ completion: @escaping (MotivationalLotteryResult) -> Void) {
struct MotivationalLotteryData: Decodable {
let motivation: String
}
guard let baseURL = baseURL, let token = token else {
completion(.failure)
return
}
let url = baseURL.appendingPathComponent("/api/motivation")
var motivationalLotteryRequest = URLRequest(url: url)
motivationalLotteryRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
motivationalLotteryRequest.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
URLSession.shared.dataTask(with: motivationalLotteryRequest) {
data, response, _ in
DispatchQueue.main.async {
guard
let httpResponse = response as? HTTPURLResponse,
200..<300 ~= httpResponse.statusCode,
let jsonData = data
else {
completion(.failure)
return
}
do {
let motivationalLottery = try JSONDecoder().decode(MotivationalLotteryData.self, from: jsonData)
completion(.success(motivationalLottery.motivation))
} catch {
completion(.failure)
}
}
}
.resume()
}
}
// MARK: - private structure
final class Token: Codable {
var token: String
var userID: UUID
init(token: String, userID: UUID) {
self.token = token
self.userID = userID
}
}
2. UltraMotivatorViewController.swift
import UIKit
class UltraMotivatorViewController: UIViewController {
var keyboardDismisser : UITapGestureRecognizer!
override func viewDidLoad() {
super.viewDidLoad()
keyboardDismisser = UITapGestureRecognizer(target: self, action:#selector(handleTap(_:)))
view.addGestureRecognizer(keyboardDismisser)
}
@objc func handleTap(_ recognizer: UITapGestureRecognizer) {
view.endEditing(true)
}
func fillInFieldsReminder() {
showAlert("Error", message: "Fill in all the fields, please.")
}
func showAlert(_ title: String, message: String? = nil, completion: (() -> Void)? = nil) {
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
let OKAction = UIAlertAction(title: "OK", style: .default) { _ in completion?() }
alertController.addAction(OKAction)
present(alertController, animated: true)
}
}
3. LoginViewController.swift
import UIKit
class LoginViewController: UltraMotivatorViewController {
@IBOutlet var usernameField : UITextField!
@IBOutlet var passwordField : UITextField!
@IBOutlet var submitButton : UIButton!
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(true)
view.endEditing(true)
usernameField.text = nil
passwordField.text = nil
}
@IBAction private func signIn(_ sender: Any) {
view.endEditing(true)
guard
let username = usernameField.text,
let password = passwordField.text,
!username.isEmpty, !password.isEmpty
else {
fillInFieldsReminder()
return
}
submitButton.isEnabled = false
API.login(username, password: password) { result in
self.submitButton.isEnabled = true
switch result {
case .success:
self.performSegue(withIdentifier: "Logged In", sender: nil)
case .failure(let error):
self.showAlert("Error", message: error ?? "Login Failed")
}
}
}
}
4. SignupViewController.swift
import UIKit
class SignupViewController: UltraMotivatorViewController {
@IBOutlet var usernameField : UITextField!
@IBOutlet var passwordField : UITextField!
@IBOutlet var submitButton : UIButton!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
usernameField.becomeFirstResponder()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(true)
view.endEditing(true)
if API.token == nil {
usernameField.text = nil
passwordField.text = nil
} else {
API.logout()
}
}
@IBAction func signUp(_ sender: Any) {
view.endEditing(true)
guard
let username = usernameField.text,
let password = passwordField.text,
!username.isEmpty, !password.isEmpty
else {
fillInFieldsReminder()
return
}
submitButton.isEnabled = false
API.register(username, password: password) { result in
self.submitButton.isEnabled = true
switch result {
case .success:
self.showAlert("Signed Up!", message: "Log in to get motivated") {
self.navigationController?.popToRootViewController(animated: true)
}
case .failure(let error):
self.showAlert("Error", message: error ?? "Sign Up Failed")
}
}
}
}
5. OneTimeCodeViewController.swift
import UIKit
class OneTimeCodeViewController: UltraMotivatorViewController {
@IBOutlet weak var oneTimeCodeField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.setHidesBackButton(true, animated: false)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
oneTimeCodeField.textContentType = .oneTimeCode
oneTimeCodeField.becomeFirstResponder()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(true)
view.endEditing(true)
oneTimeCodeField.text = nil
}
@IBAction func submitTapped(_ sender: Any) {
view.endEditing(true)
performSegue(withIdentifier: "Verified", sender: nil)
}
}
6. MotivationalViewController.swift
import UIKit
class MotivationalViewController: UIViewController {
@IBOutlet weak var motivationalLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.setHidesBackButton(true, animated: false)
}
override func viewWillAppear(_ animated: Bool) {
motivationalLabel.isHidden = true
API.motivationalLottery { result in
if case let .success(motivation) = result {
self.motivationalLabel.text = motivation
}
self.motivationalLabel.isHidden = false
}
}
@IBAction func logoutTapped(_ sender: Any) {
API.logout()
navigationController?.popToRootViewController(animated: true)
}
}
后记
本篇讲述了提高用户安全性和体验,感兴趣的给个赞或者关注~~~