Skip to content

Instantly share code, notes, and snippets.

@NeilsUltimateLab
Last active September 10, 2020 07:05
Show Gist options
  • Save NeilsUltimateLab/91531aa15d05971c4792386706383faa to your computer and use it in GitHub Desktop.
Save NeilsUltimateLab/91531aa15d05971c4792386706383faa to your computer and use it in GitHub Desktop.
WebRequest Classes and Structures
import UIKit
public class AppHUD {
public init() {}
public enum `Type` {
case definite(String?, String?)
case indefinite
case message(UIImage?, String?, String?, TimeInterval)
}
public enum Style {
case light
case dark
var blurEffect: UIBlurEffect.Style {
switch self {
case .light:
return .extraLight
case .dark:
return .dark
}
}
var textColor: UIColor {
switch self {
case .light:
return .black
case .dark:
return .white
}
}
}
var isAlreayRenderedOnWindow: Bool = false
public var animationDuration: TimeInterval = 0.2
public var style: Style = .light
var backgroundView: UIView = {
return ViewFactory.view(forBackgroundColor: UIColor.black.withAlphaComponent(0.3), clipsToBounds: true)
}()
var containerView: UIView = {
let view = ViewFactory.view(forBackgroundColor: .clear, clipsToBounds: true)
view.layer.cornerRadius = 9.0
view.layer.masksToBounds = true
return view
}()
lazy var visualEffectView: UIVisualEffectView = {
return ViewFactory.visualEffectView(blurEffectStyle: self.style.blurEffect)
}()
public var activityIndicator: UIActivityIndicatorView = {
return ViewFactory.activityIndicatorView(style: .whiteLarge,
hidesWhenStopped: true,
color: UIColor.gray)
}()
public lazy var titleLabel: UILabel = {
let label = ViewFactory.label(title: "",
textAlignment: .center,
numberOfLines: 1,
lineBreakMode: .byTruncatingTail)
label.font = UIFont.systemFont(ofSize: 19, weight: UIFont.Weight.medium)
label.textColor = self.style.textColor
return label
}()
public lazy var messageLabel: UILabel = {
let label = ViewFactory.label(title: "",
textAlignment: .center,
numberOfLines: 0,
lineBreakMode: .byWordWrapping)
label.font = UIFont.systemFont(ofSize: 17, weight: UIFont.Weight.regular)
label.textColor = self.style.textColor
return label
}()
public lazy var imageView: UIImageView = {
let imageView = UIImageView(frame: .zero)
imageView.contentMode = .scaleAspectFit
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
public var progressView: UIProgressView = {
return ViewFactory.progressView()
}()
lazy var stackView: UIStackView = {
let stackView = ViewFactory.stackView(forAxis: .vertical,
alignment: .center,
distribution: .fill,
spacing: 5.0)
return stackView
}()
private func constrainAllEdges(of childView: UIView,
to parentView: UIView,
top: CGFloat = 0,
left: CGFloat = 0,
right: CGFloat = 0,
bottom: CGFloat = 0) {
parentView.addSubview(childView)
NSLayoutConstraint.activate([
childView.leftAnchor.constraint(equalTo: parentView.leftAnchor, constant: left),
childView.topAnchor.constraint(equalTo: parentView.topAnchor, constant: top),
childView.rightAnchor.constraint(equalTo: parentView.rightAnchor, constant: -right),
childView.bottomAnchor.constraint(equalTo: parentView.bottomAnchor, constant: -bottom)
])
}
private func constrainCenter(of childView: UIView, to parentView: UIView) {
parentView.addSubview(childView)
NSLayoutConstraint.activate([
childView.centerXAnchor.constraint(equalTo: parentView.centerXAnchor),
childView.centerYAnchor.constraint(equalTo: parentView.centerYAnchor)
])
}
private func setupHUD(for type: Type, completion: (()->Void)? = nil) {
guard let window = UIApplication.shared.keyWindow else { return }
constrainAllEdges(of: backgroundView, to: window)
backgroundView.alpha = 0
let view = UIView(frame: .zero)
view.backgroundColor = .white
view.translatesAutoresizingMaskIntoConstraints = false
self.containerView = view
constrainAllEdges(of: visualEffectView, to: containerView)
constrainCenter(of: containerView, to: window)
self.containerView.layer.cornerRadius = 9.0
self.containerView.clipsToBounds = true
let heightAnchorConstraint = containerView.heightAnchor.constraint(equalToConstant: 100)
let widthAnchorConstraint = containerView.widthAnchor.constraint(equalToConstant: 100)
let leftAnchorConstraint = containerView.leftAnchor.constraint(equalTo: window.leftAnchor, constant: 50)
switch type {
case .indefinite:
widthAnchorConstraint.isActive = true
heightAnchorConstraint.isActive = true
self.setIndifiniteHUDMode()
case let .definite(title, message):
widthAnchorConstraint.isActive = false
leftAnchorConstraint.isActive = true
self.setDefiniteHUDMode(title: title, message: message)
case .message(let image, let title, let message, let delay):
heightAnchorConstraint.isActive = false
widthAnchorConstraint.isActive = false
leftAnchorConstraint.isActive = true
self.setMessageHUDMode(image: image, title: title, message: message, delay: delay, completion: completion)
}
containerView.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
containerView.alpha = 0
window.endEditing(true)
}
private func setIndifiniteHUDMode() {
self.activityIndicator.removeFromSuperview()
self.titleLabel.removeFromSuperview()
self.messageLabel.removeFromSuperview()
self.progressView.removeFromSuperview()
constrainCenter(of: activityIndicator, to: containerView)
activityIndicator.startAnimating()
}
private func setDefiniteHUDMode(title: String? = nil, message: String? = nil) {
self.activityIndicator.removeFromSuperview()
self.titleLabel.removeFromSuperview()
self.messageLabel.removeFromSuperview()
self.progressView.removeFromSuperview()
self.progressView.setProgress(0, animated: false)
constrainAllEdges(of: stackView, to: containerView, top: 16, left: 16, right: 16, bottom: 16)
if let titleText = title {
stackView.addArrangedSubview(titleLabel)
titleLabel.text = titleText
}
if let messageText = message {
stackView.addArrangedSubview(messageLabel)
messageLabel.text = messageText
}
stackView.addArrangedSubview(progressView)
}
private func setMessageHUDMode(image: UIImage?, title: String?, message: String?, delay: TimeInterval, completion: (()->Void)?) {
self.activityIndicator.removeFromSuperview()
self.progressView.removeFromSuperview()
self.titleLabel.removeFromSuperview()
self.imageView.removeFromSuperview()
constrainAllEdges(of: stackView, to: containerView, top: 16, left: 16, right: 16, bottom: 16)
if let image = image {
stackView.addArrangedSubview(imageView)
imageView.image = image
}
if let titleText = title {
stackView.addArrangedSubview(titleLabel)
titleLabel.text = titleText
}
if let titleText = message {
stackView.addArrangedSubview(messageLabel)
messageLabel.text = titleText
}
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
self.hideHUD(completion: completion)
}
}
private func animateHUD(completion: ((Bool)->Void)? = nil) {
UIView.animate(withDuration: animationDuration, delay: 0, options: UIView.AnimationOptions.curveEaseOut, animations: {
self.backgroundView.alpha = 1
self.containerView.transform = .identity
self.containerView.alpha = 1
}, completion: {(success)in
if success {
self.isAlreayRenderedOnWindow = true
completion?(success)
}
})
}
public func hideHUD(completion: (()->Void)? = nil) {
UIView.animate(withDuration: animationDuration, delay: 0, options: .curveEaseIn, animations: {
self.backgroundView.alpha = 0
self.containerView.alpha = 0
}) { (succeed) in
if succeed {
self.backgroundView.removeFromSuperview()
self.containerView.removeFromSuperview()
self.activityIndicator.stopAnimating()
self.progressView.removeFromSuperview()
self.titleLabel.removeFromSuperview()
self.messageLabel.removeFromSuperview()
self.isAlreayRenderedOnWindow = false
completion?()
}
}
}
public func updateProgress(percetage: Float, shouldHideAfterCompletion: Bool, completion: (()->Void)? = nil) {
self.progressView.setProgress(percetage, animated: true)
if shouldHideAfterCompletion {
if percetage >= 1.0 {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0, execute: { [weak self] in
self?.hideHUD(completion: completion)
})
}
}else {
completion?()
}
}
public func showHUD() {
guard !isAlreayRenderedOnWindow else { return }
self.setupHUD(for: .indefinite)
self.animateHUD()
}
public func showHud(image: UIImage?, title: String?, message: String?, delay: TimeInterval, completion: (()->Void)? = nil) {
guard !isAlreayRenderedOnWindow else { return }
self.setupHUD(for: .message(image, title, message, delay), completion: completion)
self.animateHUD()
}
public func showDefiniteHUD(title: String?, message: String?) {
guard !isAlreayRenderedOnWindow else { return }
self.setupHUD(for: .definite(title, message))
self.animateHUD()
}
}
extension AppHUD {
static let shared = AppHUD()
}
import UIKit
extension FetchingView {
enum State {
case fetching
case fetched
case error(AppError)
}
}
class FetchingView {
var listView: UIView
var parentView: UIView
var centerYOffset: CGFloat = 0 {
didSet {
centerYConstraint?.constant = centerYOffset
}
}
private var centerYConstraint: NSLayoutConstraint!
// MARK: - Initialiser -
/// FetchingView Mechanism
///
/// - Parameters:
/// - listView: This view could be your `tableView`, `collectionView`, `scrollView` or some other `UIView`. `listView` will hide when fetching state views were rendered.
/// - parentView: ParentView may be the `superview` of `listView`. ParentView will be the `containerView` for the fetching state views.
public init(listView: UIView, parentView: UIView, centerYOffset: CGFloat = -20) {
self.listView = listView
self.parentView = parentView
self.centerYOffset = centerYOffset
prepareViews()
}
// MARK: - State Machine -
/// Tracking states of web-request
public var fetchingState: State = .fetching {
didSet {
validate(state: fetchingState)
}
}
/// When `fetchingState` changes this method will be called.
///
/// - Parameter state: `fetchingState`
private func validate(state: State) {
self.listView.isHidden = true
self.containerView.isHidden = false
switch state {
case .fetching:
self.imageView.removeFromSuperview()
self.buttonStackView.removeFromSuperview()
self.labelStackView.removeFromSuperview()
parentStackView.addArrangedSubview(loadingStackView)
indicatorView.startAnimating()
case .error(let error):
loadingStackView.removeFromSuperview()
buttonStackView.removeFromSuperview()
imageView.removeFromSuperview()
if let image = error.image {
imageView.image = image
parentStackView.addArrangedSubview(imageView)
}
parentStackView.addArrangedSubview(labelStackView)
titleLabel.text = error.title
descriptionLabel.text = error.subtitle
case .fetched:
self.listView.isHidden = false
self.containerView.isHidden = true
}
}
// MARK: - UIElements -
/// Parent `containerView` for the all stackViews.
lazy var containerView: UIView = {
let view = ViewFactory.view(forBackgroundColor: .clear, clipsToBounds: true)
view.addSubview(parentStackView)
parentStackView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
parentStackView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
parentStackView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 0).isActive = true
parentStackView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true
return view
}()
// MARK: UIStackViews
/// Parent StackView that contains `imageStackView` ,`titleStackView`, `textStackView`, `buttonStackView`.
var parentStackView: UIStackView = {
return ViewFactory.stackView(forAxis: .vertical,
alignment: .fill,
distribution: .fill,
spacing: 20)
}()
/// Loading StackView contains `UIActivityIndicatorView`, `UILabel`.
lazy var loadingStackView: UIStackView = {
let stackView = ViewFactory.stackView(forAxis: .vertical,
alignment: .center,
distribution: .fill,
spacing: 16)
stackView.addArrangedSubview(self.indicatorView)
stackView.addArrangedSubview(self.loadingLabel)
return stackView
}()
/// Label StackView contains `UILabel`s for `title` and `description`
lazy var labelStackView: UIStackView = {
let stackView = ViewFactory.stackView(forAxis: .vertical,
alignment: .center,
distribution: .fill,
spacing: 4)
stackView.addArrangedSubview(self.titleLabel)
stackView.addArrangedSubview(self.descriptionLabel)
return stackView
}()
/// Button StackView *will contain the response buttons provided by the* **API client** .
lazy var buttonStackView: UIStackView = {
return ViewFactory.stackView(forAxis: .vertical,
alignment: .center,
distribution: .fill,
spacing: 4)
}()
/// UIActivityIndicatorView will be rendered when `fetchingState` is `.fetching`
public var indicatorView: UIActivityIndicatorView = {
return ViewFactory.activityIndicatorView(style: .gray,
hidesWhenStopped: true)
}()
public var loadMoreIndicatorView: UIActivityIndicatorView = {
return ViewFactory.activityIndicatorView(style: .gray, hidesWhenStopped: true)
}()
// MARK: UILabels
/// `loadingLabel` will be rendered below `indicatorView` when `fetchingState` is `fetching`.
///
/// The default text is "`LOADING`".
///
/// `loadingLabel`'s text can be changed by API User.
public var loadingLabel: UILabel = {
let label = ViewFactory.label(title: "Loading".uppercased(),
textAlignment: .center,
textColor: .gray,
numberOfLines: 1)
label.font = UIFont.systemFont(ofSize: 13)
return label
}()
/// `titleLabel` will be rendered when `fetchingState` is `fetchedError(AppErrorProvider)`.
///
/// The default text is empty text.
///
/// `titleLabel`'s text will be changed `AppErrorProvider`'s `title` property.
public var titleLabel: UILabel = {
let label = ViewFactory.label(title: "",
textAlignment: .center,
textColor: .gray,
numberOfLines: 0,
lineBreakMode: .byWordWrapping)
label.font = UIFont.systemFont(ofSize: 21, weight: UIFont.Weight.medium)
return label
}()
/// `descriptionLabel` will be rendered when `fetchingState` is `fetchedError(AppErrorProvider)`.
///
/// The default text is empty text.
///
/// `descriptionLabel`'s text will be changed `AppErrorProvider`'s `subtitle` property.
public var descriptionLabel: UILabel = {
let label = ViewFactory.label(title: "",
textAlignment: .center,
textColor: .gray,
numberOfLines: 0,
lineBreakMode: .byWordWrapping)
label.font = UIFont.systemFont(ofSize: 17, weight: UIFont.Weight.regular)
return label
}()
// MARK: UIImageView
/// `imageView` will be rendered when `fetchingState` is `fetchedError(AppErrorProvider)`.
///
/// The default image is `nil`.
///
/// `imageView`'s image will be changed `AppErrorProvider`'s `image` property.
public var imageView: UIImageView = {
let imageView = ViewFactory.imageView(image: nil,
contentMode: .scaleAspectFit)
imageView.tintColor = UIColor.gray
return imageView
}()
// MARK: - Utilities
func prepareViews() {
self.parentView.addSubview(containerView)
containerView.centerXAnchor.constraint(equalTo: parentView.centerXAnchor, constant: 0).isActive = true
containerView.leftAnchor.constraint(equalTo: parentView.leftAnchor, constant: 32).isActive = true
centerYConstraint = containerView.centerYAnchor.constraint(equalTo: parentView.centerYAnchor, constant: self.centerYOffset)
centerYConstraint?.isActive = true
}
/// To add `UIButton`s to `buttonStackView`. `FetchingView` will not handle any `UIButton` touch `events`
///
/// - Parameter buttons: `UIButton`'s `targetAction` must be set by API User.
public func add(_ buttons: [UIButton]) {
buttonStackView.arrangedSubviews.forEach({$0.removeFromSuperview()})
for button in buttons {
buttonStackView.addArrangedSubview(button)
}
buttonStackView.removeFromSuperview()
parentStackView.addArrangedSubview(buttonStackView)
}
func stopWithAlert(error: AppError) {
if let rootVC = UIApplication.shared.keyWindow?.rootViewController {
let alert = UIAlertController(title: error.title, message: error.subtitle, preferredStyle: UIAlertController.Style.alert)
switch error {
case .sessionExpired:
let loginAction = UIAlertAction(title: "Login again", style: UIAlertAction.Style.default, handler: { (_) in
print("Redirect to login screen")
})
alert.addAction(loginAction)
default:
break
}
let okAction = UIAlertAction(title: "Ok", style: .cancel, handler: { (_) in
print("Nothing will happen here")
})
alert.addAction(okAction)
rootVC.present(alert, animated: true, completion: nil)
}
}
func stopWithAlert(with title: String? = nil, message: String) {
if let rootVC = UIApplication.shared.keyWindow?.rootViewController {
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertController.Style.alert)
let okAction = UIAlertAction(title: "Ok", style: .cancel, handler: nil)
alert.addAction(okAction)
rootVC.present(alert, animated: true, completion: nil)
}
}
// MARK: - LoadMoreView -
func loadMoreView() -> UIView {
let view = UIView(frame: CGRect(x: 0, y: 0, width: self.listView.frame.width, height: 56))
view.addSubview(loadMoreIndicatorView)
loadMoreIndicatorView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
loadMoreIndicatorView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
loadMoreIndicatorView.startAnimating()
return view
}
func stopLoadMore() {
loadMoreIndicatorView.stopAnimating()
loadMoreIndicatorView.removeFromSuperview()
}
}
//
// ViewFactory.swift
// FetchingView
//
// Created by Ratnesh Jain on 10/12/17.
//
import UIKit
public class ViewFactory {
/// A simple autolayout ready UIView with some default properties.
///
/// - Parameters:
/// - backgroundColor: *default* is `.white`
/// - clipsToBounds: *default* is `false`
/// - Returns: returns UIView with above properties.
public static func view(forBackgroundColor backgroundColor: UIColor = .white,
clipsToBounds: Bool = false) -> UIView {
let view = UIView(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = backgroundColor
view.clipsToBounds = clipsToBounds
return view
}
/// A simple autolayout ready `UIStackView` with some default properties.
///
/// - Parameters:
/// - axis: *default* is `.vertical`
/// - alignment: *default* is `.fill`
/// - distribution: *default* is `.fill`
/// - spacing: *default* is `0`
/// - Returns: returns `UIStackView` with above properties.
public static func stackView(forAxis axis: NSLayoutConstraint.Axis = .vertical,
alignment: UIStackView.Alignment = .fill,
distribution: UIStackView.Distribution = .fill,
spacing: CGFloat = 0) -> UIStackView {
let stackView = UIStackView(frame: .zero)
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = axis
stackView.alignment = alignment
stackView.distribution = distribution
stackView.spacing = spacing
return stackView
}
/// A simple autolayout ready `UILabel` with some default properties.
///
/// - Parameters:
/// - textAlignment: *default* is `.center`
/// - textColor: *default* is `.black`
/// - numberOfLines: *default* is `0`
/// - lineBreakMode: *default* is `.byTruncatingTail`
/// - Returns: returns `UILabel` with above properties.
public static func label(title: String,
textAlignment: NSTextAlignment = .center,
textColor: UIColor = .black,
numberOfLines: Int = 0,
lineBreakMode: NSLineBreakMode = .byTruncatingTail) -> UILabel {
let label = UILabel(frame: .zero)
label.translatesAutoresizingMaskIntoConstraints = false
label.text = title
label.textAlignment = textAlignment
label.textColor = textColor
label.numberOfLines = numberOfLines
label.lineBreakMode = lineBreakMode
return label
}
/// A simple autolayout ready `UIImageView` with some default properties.
///
/// - Parameters:
/// - image: *default* is `nil`
/// - contentMode: *default* is `.center`
/// - Returns: returns `UILabel` with above properties.
public static func imageView(image: UIImage? = nil,
contentMode: UIView.ContentMode = .center) -> UIImageView {
let imageView = UIImageView(frame: .zero)
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.image = image
imageView.contentMode = contentMode
return imageView
}
/// A simple autolayout ready `UIActivityIndicatorView` with some default properties.
///
/// - Parameters:
/// - style: *default* is `.gray`
/// - hidesWhenStopped: *default* is `true`
/// - color: *default* is `nil`
/// - Returns: returns `UIActivityIndicatorView` with above properties.
public static func activityIndicatorView(style: UIActivityIndicatorView.Style = .gray,
hidesWhenStopped: Bool = true,
color: UIColor? = nil) -> UIActivityIndicatorView {
let indicatorView = UIActivityIndicatorView(style: style)
indicatorView.translatesAutoresizingMaskIntoConstraints = false
indicatorView.hidesWhenStopped = hidesWhenStopped
indicatorView.color = color
return indicatorView
}
/// A simple autolayout ready `UIButton` with some default properties.
///
/// - Parameters:
/// - type: *default* is `.system`
/// - title: *default* is `"Button"`
/// - image: *default* is `nil`
/// - tintColor: *default* is `.white`
/// - Returns: returns `UIButton` with above properties.
public static func button(type: UIButton.ButtonType = .system,
title: String? = "Button",
image: UIImage? = nil,
tintColor: UIColor = .white) -> UIButton {
let button = UIButton(type: type)
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle(title, for: .normal)
button.setImage(image, for: .normal)
button.tintColor = tintColor
return button
}
/// A simple autolayout ready `UISegmentedControl` with some default properties.
///
/// - Parameter titles: is a `variadic` parameter.
/// - Returns: returns `UISegmentedControl` with above properties.
public static func segmentedControl(titles: String...) -> UISegmentedControl {
let segmentedControl = UISegmentedControl(frame: .zero)
segmentedControl.translatesAutoresizingMaskIntoConstraints = false
for (index, title) in titles.enumerated() {
segmentedControl.insertSegment(withTitle: title, at: index, animated: false)
}
segmentedControl.selectedSegmentIndex = 0
return segmentedControl
}
public static func visualEffectView(blurEffectStyle style: UIBlurEffect.Style) -> UIVisualEffectView {
let blurEffect = UIBlurEffect(style: style)
let visualEffectView = UIVisualEffectView(effect: blurEffect)
visualEffectView.translatesAutoresizingMaskIntoConstraints = false
return visualEffectView
}
public static func progressView(progressViewStyle style: UIProgressView.Style = .default,
isUserInterationEnabled interactionEnabled: Bool = false,
trackImage: UIImage = UIImage(),
trackTintColor: UIColor = .lightGray,
progressTintColor: UIColor = UIColor.blue) -> UIProgressView {
let pv = UIProgressView(progressViewStyle: style)
pv.isUserInteractionEnabled = interactionEnabled
pv.trackImage = trackImage
pv.trackTintColor = trackTintColor
pv.progressTintColor = progressTintColor
return pv
}
public static func shadowButton() -> UIButton {
let button = UIButton(frame: .zero)
button.backgroundColor = #colorLiteral(red: 0.007843137255, green: 0.6235294118, blue: 0.8588235294, alpha: 1)
button.layer.cornerRadius = button.frame.height/2
button.layer.shadowColor = #colorLiteral(red: 0.5529411765, green: 0.7725490196, blue: 0.8666666667, alpha: 1)
button.layer.shadowOpacity = 1.0
button.layer.shadowRadius = 4
button.layer.shadowOffset = CGSize(width: 0, height: 0)
return button
}
}
import Foundation
enum AppConfig {
static let scheme: URLScheme = .https
static let host: URLHost = .live
}
enum URLPath {
case login
}
extension URLPath {
var path: String {
switch self {
case .login:
return "/login"
}
}
var queryItems: [URLQueryItem]? {
return nil
}
var url: URL? {
var urlComponent = URLComponents()
urlComponent.scheme = AppConfig.scheme.rawValue
urlComponent.host = AppConfig.host.rawValue
urlComponent.path = AppConfig.host.fixedPath + self.path
urlComponent.queryItems = self.queryItems
return urlComponent.url
}
}
extension URLPath: CustomStringConvertible {
var description: String {
return "\(self.url?.absoluteString ?? "Can not get the url!")"
}
}
extension URLPath {
func url(for host: URLHost) -> URL? {
var urlComponent = URLComponents()
urlComponent.scheme = AppConfig.scheme.rawValue
urlComponent.host = host.rawValue
urlComponent.path = host.fixedPath + self.path
urlComponent.queryItems = self.queryItems
return urlComponent.url
}
}
enum URLScheme: String {
case https
case http
}
enum URLHost: String {
case live = "www.myshopdata.com"
var host: String {
return self.rawValue
}
var fixedPath: String {
switch self {
case .live:
return "/api/gpn"
}
}
}
typealias JSONType = [String: Any]
enum HTTPMethod {
case get
case post(parameter: JSONType)
}
import Alamofire
extension HTTPMethod {
var parameter: JSONType? {
switch self {
case .post(let parameter):
return parameter
default:
return nil
}
}
var alamofireMethod: Alamofire.HTTPMethod {
switch self {
case .get:
return Alamofire.HTTPMethod.get
case .post:
return Alamofire.HTTPMethod.post
}
}
}
class RequestToken {
weak var urlTask: URLSessionTask?
init(task: URLSessionTask?) {
self.urlTask = task
}
func cancel() {
self.urlTask?.cancel()
}
}
enum AppError: Error, Equatable {
case noRecords(image: UIImage?, title: String, message: String?)
case canNotParse
case notReachable
case requestTimedOut
case sessionExpired
case serverError(String)
}
extension AppError {
var title: String? {
switch self {
case .noRecords(_, let title, _):
return title
case .canNotParse:
return "Oops"
case .notReachable:
return "Oops"
case .requestTimedOut:
return "Request Timed Out"
case .sessionExpired:
return "Session Expired!"
case .serverError:
return "Error"
}
}
var subtitle: String? {
switch self {
case .noRecords(_, _, let message):
return message
case .canNotParse:
return "Something went wrong"
case .notReachable:
return "Internet connection not reachable."
case .requestTimedOut:
return nil
case .sessionExpired:
return "Please login again to continue."
case .serverError(let message):
return message
}
}
var image: UIImage? {
return nil
}
}
enum Result<A> {
case value(A)
case error(AppError)
}
extension Result {
var value: A? {
switch self {
case .value(let val):
return val
default:
return nil
}
}
var error: AppError? {
switch self {
case .error(let err):
return err
default:
return nil
}
}
}
extension Decodable {
static func decode<A>(from data: Data) -> Result<A> where A: Decodable {
do {
let jsonDecoded = try JSONDecoder().decode(A.self, from: data)
return .value(jsonDecoded)
} catch {
print("Can not parse: \(A.self), because of error: ", error)
return .error(AppError.canNotParse)
}
}
}
struct WebResource<A: Decodable> {
var path: URLPath
var header: [String: String]?
var method: HTTPMethod
func request(completion: @escaping (Result<A>)->Void) -> RequestToken? {
return WebResourceManager.shared.fetchResource(self, completion: completion)
}
}
extension WebResource: CustomStringConvertible {
var description: String {
let dic = NSMutableDictionary()
dic["ParseDataType"] = A.self
dic["URL"] = self.path
dic["HTTPMethod"] = self.method
dic["Header"] = self.header ?? [:]
dic["Parameters"] = self.method.parameter ?? [:]
return "\(String.consoleSeperator) WebResource = \(dic) \(String.consoleSeperator)"
}
}
import Alamofire
class WebResourceManager {
static let shared = WebResourceManager()
static let timeout: TimeInterval = 30.0
static var sessionManager: SessionManager = {
let config = URLSessionConfiguration.default
config.timeoutIntervalForRequest = timeout
let manager = SessionManager(configuration: config)
return manager
}()
var isReachable: Bool {
return NetworkReachabilityManager()?.isReachable ?? false
}
func fetchResource<A>(_ resource: WebResource<A>, completion: @escaping (Result<A>)-> Void) -> RequestToken? where A: Decodable {
guard let url = resource.path.url else {
return nil
}
let header = resource.header
let paremeter = resource.method.parameter
let paremeterEncoding = URLEncoding.default
print("\(String.consoleSeperator)Webservice call for resource: \(resource.path)...")
guard isReachable else {
completion(.error(.notReachable))
UIApplication.shared.isNetworkActivityIndicatorVisible = false
return nil
}
UIApplication.shared.isNetworkActivityIndicatorVisible = true
let dataRequest = WebResourceManager.sessionManager.request(url, method: resource.method.alamofireMethod, parameters: paremeter, encoding: paremeterEncoding, headers: header).responseData { (response) in
UIApplication.shared.isNetworkActivityIndicatorVisible = false
guard response.response?.url == url else {
if let error = response.result.error {
if error._code == NSURLErrorTimedOut {
completion(.error(AppError.requestTimedOut))
} else {
completion(.error(AppError.serverError(error.localizedDescription)))
}
}
return
}
if let code = response.response?.statusCode {
if code == 401 {
completion(.error(.sessionExpired))
return
}
print("Response Code = \(code)")
}
if let respondedError = response.result.error {
completion(.error(.serverError(respondedError.localizedDescription)))
return
}
if let data = response.result.value {
completion(A.decode(from: data))
return
} else {
completion(.error(.canNotParse))
}
}.responseString { (response) in
print("\(String.consoleSeperator)Webservice responded for Web Resource: \(resource) \nwith Response: \(response.debugDescription)")
}
let task = dataRequest.task
return RequestToken(task: task)
}
func uploadResource<A>(_ resource: WebResource<A>, completion: @escaping (Result<A>)->Void, progressCompletion: ((Double)->Void)?) where A: Decodable {
guard let url = resource.path.url else {
return
}
let parameter = resource.method.parameter
let header = resource.header
guard isReachable else {
UIApplication.shared.isNetworkActivityIndicatorVisible = false
completion(.error(.notReachable))
return
}
print("Calling Webservice call for resource: \(resource.path)....")
UIApplication.shared.isNetworkActivityIndicatorVisible = true
Alamofire.upload(multipartFormData: { (multipartFormData) in
if let parameters = parameter {
for (key, value) in parameters {
let (url, mimeType) = MediaType.generateMimeType(key: key, value: value)
if let url = url {
multipartFormData.append(url, withName: key, fileName: url.fileName.appending(".").appending(url.pathExtension), mimeType: mimeType)
}else {
if let stringValue = value as? String, let data = stringValue.data(using: String.Encoding.utf8) {
multipartFormData.append(data, withName: key, mimeType: "text/plain")
}
}
}
}
}, to: url, headers: header) { (encodingResult) in
switch encodingResult {
case .success(let upload, _, _):
UIApplication.shared.isNetworkActivityIndicatorVisible = false
upload.uploadProgress(closure: {(progress: Progress) in
progressCompletion?(progress.fractionCompleted)
})
upload.responseData(completionHandler: { (response) in
UIApplication.shared.isNetworkActivityIndicatorVisible = false
// print("\(String.consoleSeperator)Webservice responded for : \(resource) \nwith: \(response)")
print("\(String.consoleSeperator)Webservice responded for Web Resource: \(resource) \nwith Response: \(response.debugDescription)")
if let code = response.response?.statusCode {
if code == 401 {
completion(.error(.sessionExpired))
return
}
}
if let data = response.result.value {
completion(A.decode(from: data))
} else {
guard response.response?.url == url else {
return
}
if let error = response.result.error {
completion(.error(.serverError(error.localizedDescription)))
}
}
})
case .failure(let error):
print("Error : \(error)")
}
}
}
}
enum MediaType: String {
case doc, dot, docx,
dotx, dotm, docm
case txt, rtf
case ppt, pot, pps, ppa,
pptx, potx, ppsx, ppam, pptm,
potm, ppsm
case xls, xlt, xla, xlsx, xltx,
xlsm, xltm, xlam, xlsb
case pdf
case jpg, png, jpeg
}
extension MediaType {
var mimeType: String {
switch self {
case .doc, .dot, .docx,
.dotx, .dotm, .docm:
return "application/msword"
case .txt, .rtf:
return "text/plain"
case .ppt, .pot, .pps, .ppa,
.pptx, .potx, .ppsx, .ppam, .pptm,
.potm, .ppsm:
return "application/vnd.openxmlformats-officedocument.presentationml.presentation"
case .pdf:
return "application/pdf"
case .xls, .xlt, .xla, .xlsx, .xltx,
.xlsm, .xltm, .xlam, .xlsb:
return "application/vnd.ms-excel"
case .png:
return "image/png"
case .jpg, .jpeg:
return "image/jpeg"
}
}
}
extension MediaType {
static func generateMimeType(key: String, value: Any) -> (url: URL?, mimeType: String) {
if let url = value as? URL {
guard let mediaType = MediaType(rawValue: url.pathExtension.lowercased()) else {
return (nil, "")
}
return (value as? URL, mediaType.mimeType)
}
return (nil, "")
}
}
extension String {
static var consoleSeperator: String {
return "-------------------------"
}
}
extension URL {
var fileName: String {
return self.deletingPathExtension().lastPathComponent
}
var fileExtension: String {
return self.pathExtension
}
var fileNameWithExtension: String {
return fileName.appending(".").appending(fileExtension)
}
var image: UIImage? {
if FileManager.default.fileExists(atPath: self.path) {
return UIImage(contentsOfFile: path)
}
return nil
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment