/OlaApp-Task

This is basic example for the car booking app with custom map pin points.

Primary LanguageSwift

OlaApp-Task

Swift 5.0

This repository contains a OlaApp-Task based on Web APIs. User can see pre fetch car list and able to see the car inside the mapView with details.

Application having basic functionality for the Car grouping inside the Map View with custom annotation image.

Car list displaying in the View forms not directly horizontally presented in a short from once user tap on it. App will start to display details about the car. πŸš—

This is the one pager πŸ“Ÿ application and truly handy in a single page.

  • Concept of the application - App will fetch the users current location based on location it will display the Available rental cars. Application having functionalists for the display Car type like Mini, Luxury, Premium. Based on selection use can view the cars details and able to do booking.



Features

  • Map View
  • Car Details
  • Car Types
  • Book car

Requirements

  • iOS 13.1+
  • Xcode 11.1

Project Structure

β”œβ”€ Common 
  β”œβ”€ StoryBoarded
  β”œβ”€ LoadingViewController
  β”œβ”€ ImageProvider
  β”œβ”€ Extensions
  β”œβ”€ ANCustomView
β”œβ”€ Networking
β”œβ”€ Home
  β”œβ”€ Remote 
  β”œβ”€ Model
  β”œβ”€ ViewModel
  β”œβ”€ View (ViewController, Cell, Annotation View)

Swift Package Manager

- RxSwift
    dependencies: [
    .package(url: "https://github.com/ReactiveX/RxSwift.git", from: "5.0.0")
  ],
  targets: [
    .target(name: "OlaApp-Task", dependencies: ["RxSwift", "RxCocoa"])
  ]

Architecture

  • MVVM [Model View ViewModel]

    This project completely developed in MVVM design patten.

Image Downloading & Cache

  • This class will be responsible for the downloading image asynchronously & cache.
import UIKit


struct ImageProvider {
    
    fileprivate let downloadQueue = DispatchQueue(label: "Images cache", qos: DispatchQoS.background)
    internal var cache = NSCache<NSURL, UIImage>()

    
    //MARK: - Fetch image from URL and Images cache
    func loadImages(from url: NSURL, completion: @escaping (_ image: UIImage) -> Void) {
        downloadQueue.async(execute: { () -> Void in
            if let image = self.cache.object(forKey: url) {
                DispatchQueue.main.async {
                    completion(image)
                }
                return
            }
            
            do{
                let data = try Data(contentsOf: url as URL)
                if let image = UIImage(data: data) {
                    DispatchQueue.main.async {
                        self.cache.setObject(image, forKey: url)
                        completion(image)
                    }
                } else {
                    print("Could not decode image")
                }
            }catch {
                print("Could not load URL: \(url): \(error)")
            }
        })
    }
}

Networking πŸš€

Inside this application networking handling by Endpoint. It is responsible for the creation of URL path.

struct Endpoint {
    let path: String
    
    enum Errors: Error {
        case invalidURL
    }
}


extension Endpoint {

    var url: URL? {
        var components = URLComponents()
        components.scheme = "http"
        components.host = "www.mocky.io"
        components.path = path
        return components.url
    }
}

extension Endpoint{
    static func fetchVehicleList() -> Endpoint{
        return Endpoint(
            path: "/v2/5dc3f2c13000003c003477a0"
        )
    }
}
  • Networking wrapper completely written Generic it's capable for handle any kind of response data.
typealias Networking = (Endpoint) -> Future<Data>

extension URLSession {
   func request(_ endpoint: Endpoint) -> Future<Data> {
       // Start by constructing a Promise, that will later be
       // returned as a Future
       let promise = Promise<Data>()
       
       // Immediately reject the promise in case the passed
       // endpoint can't be converted into a valid URL
       guard let url = endpoint.url else {
           promise.reject(with: Endpoint.Errors.invalidURL)
           return promise
       }
       
       let task = dataTask(with: url) { data, _, error in
           // Reject or resolve the promise, depending on the result
           if let error = error {
               promise.reject(with: error)
           } else {
               promise.resolve(with: data ?? Data())
           }
       }
       task.resume()
       
       return promise
   }
}

Remote class

Inside this class created `loadProduct()` function for the specific path it’s could be based on URL request.
Return type required to define here `Future<Vehicles>`.
struct VehicleLoader {
   
   private let networking: Networking
   
   init(networking: @escaping Networking = URLSession.shared.request) {
       self.networking = networking
   }
   
   func loadProduct() -> Future<Vehicles> {
       return networking(.fetchVehicleList()).decoded()
   }
}
  • Use of this wrapper inside the project
let vehicleLoader = VehicleLoader()
vehicleLoader.loadProduct().observe { result in
                switch result{
                case .success(let values):
                    debugPrint(values)
                case .failure(let error):
                    debugPrint(error.localizedDescription)
                }
            }

JSONDecoding

Model inside this application are using the Codable protocol. Response from Network request in a from of Data decoding by extension Future.

fileprivate func newJSONDecoder() -> JSONDecoder {
    let decoder = JSONDecoder()
    if #available(iOS 10.0, OSX 10.12, tvOS 10.0, watchOS 3.0, *) {
        decoder.dateDecodingStrategy = .iso8601
    }
    return decoder
}

extension Future where Value == Data {
    func decoded<T: Decodable>() -> Future<T> {
        return transformed{ try newJSONDecoder().decode(T.self, from: $0) }
    }
}

Storyboarded

This Protocol basically using for the UIViewController initialization.
protocol Storyboarded {
    static func instantiate() -> Self
}

extension Storyboarded where Self: UIViewController {
    static func instantiate() -> Self {
        let className = String(describing: self)
        let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
        return storyboard.instantiateViewController(withIdentifier: className) as! Self
    }
}
  • Use of it.
 let mapViewController = MapViewController.instantiate()

UI Components & Apple APIs

  • Storyboard
  • Coadable Protocol
  • UICollectionView
  • UIAlertController
  • MKMapView
  • UIStackView

UIViewController Extension

This is using for display the Loadingview which works with the RxBinder for add and remove form UIViewController.

extension UIViewController{
    
    func add(_ child: UIViewController) {
        addChild(child)
        view.addSubview(child.view)
        child.didMove(toParent: self)
    }
    
    func remove() {
        guard parent != nil else {
            return
        }
        
        willMove(toParent: nil)
        view.removeFromSuperview()
        removeFromParent()
    }
    
    public var isAnimating: Binder<Bool> {
        let loadingViewController = LoadingViewController()
        
        return Binder(self, binding: {[weak self] (vc, active) in
            if active {
                self?.add(loadingViewController)
            } else {
                loadingViewController.remove()
            }
        })
    }
}

πŸ‘€ Author

Anscoder (Anand Nimje)