Xcode

From bibbleWiki
Jump to navigation Jump to search

Introduction

This page is for Xcode. We started on Xcode 13

Layouts

This is a bit like Android Studio and very much like Java where you build views in views and constraint where it makes sense. In this example we build three views, place them in a vertical stack view and constraint that. For Dice we put them in a horizontal stack view. For the button text we add a constraint to the width. Not sure where margin comes in currently.

XCodeAutoLayout.png

When happy select the view layouts with the command key ⌘ and select default background

XCodeAutoLayoutTransparent.png

Adding the constraint for the number was hard. Put it in a view and add a constraint to indent on the right.

XCodeContraintView.png

VIPER

VIPER is a design pattern which seems to be associated with Apple
VIPER01.png
Really enjoyed the demonstration from Youtube. [[1]]

VIPER Source Example

Non Source Changes

Remove Main

By default main.storyboard is used to remove this

  • First, delete the Main.storyboard file
  • Next, go to PROJECT_NAME -> GENERAL
  • In main interface drop-down, delete the text Main

Remove Main Xcode01.png

  • Now go to info.plist and delete Storyboard Name

Delete main xcode.png In the SceneDelegate.swift

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
        // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
        // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
                
        // Removed for VIPER Example
        // guard let _ = (scene as? UIWindowScene) else { return }
        
        
        
        // Added for VIPER Example
        guard let windowScene = (scene as? UIWindowScene) else { return }
        //
        let userRouter = UserRouter.start()
        let initialViewController = userRouter.entry
        
        let window =  UIWindow(windowScene: windowScene)
        window.rootViewController = initialViewController
        self.window = window
        window.makeKeyAndVisible()
    }

View

import Foundation
import UIKit

protocol AnyView {
    var presenter: AnyPresenter? {get set}
    
    func update(with users: [User])
    func update(with error: String)
}

class UserViewController: UIViewController, AnyView, UITableViewDelegate, UITableViewDataSource   {
    var presenter: AnyPresenter?
    
    var users: [User] = []
    
    private let tableview: UITableView = {
        let table = UITableView()
        table.register(UITableViewCell.self,
                       forCellReuseIdentifier: "cell")
        table.isHidden = true
        return table
    }()
    
    private let label: UILabel = {
        let label = UILabel()
        label.textAlignment = .center
        label.isHidden = true
        return label
    }()
    
    override func viewDidLoad() {
    
        super.viewDidLoad()
        view.addSubview(label)
        label.center = view.center
        view.backgroundColor = .systemBlue
        
        view.addSubview(tableview)
        tableview.delegate = self
        tableview.dataSource = self
    }
    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        tableview.frame = view.bounds
        label.frame = CGRect(x: 0, y: 0, width: 200, height: 50)
        label.center = view.center
        
    }
    
    func update(with users: [User]) {
        DispatchQueue.main.async {
            self.users = users
            self.tableview.reloadData()
            self.tableview.isHidden = false
        }
    }
    
    func update(with error: String) {
        print("Iain was ere", error)
        DispatchQueue.main.async {
            self.users = []
            self.label.text = error
            self.tableview.isHidden = true
            self.label.isHidden = false
        }
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return users.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableview.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        cell.textLabel?.text = users[indexPath.row].name
        return cell
    }

}

Interactor

import Foundation

protocol AnyInteractor2 {
    var presenter: AnyPresenter? { get set }
    func getUsers()
}

class UserInteractor: AnyInteractor2 {
    
    var presenter: AnyPresenter?
    
    func getUsers() {
        guard let url  = URL(string: "https://jsonplaceholder.typicode.com/users") else  { return }
        let task = URLSession.shared.dataTask(with: url) { [weak self] data, _, error in
            guard let data  = data, error == nil else {
                self?.presenter?.interactorDidFetchUsers(with: .failure(FetchError.failed))
                return
            }
            
            do {
                let entities = try JSONDecoder().decode([User].self, from: data)
                
                self?.presenter?.interactorDidFetchUsers(with: .success(entities))
            }
            catch {
                self?.presenter?.interactorDidFetchUsers(with: .failure(error))
            }
        }
                
        task.resume()
    }
}

Presenter

import Foundation

protocol AnyPresenter {
    var router: AnyRouter? { get set }
    var interactor: AnyInteractor2? { get set }
    var view: AnyView? { get set }
    
    func interactorDidFetchUsers(with result: Result<[User],Error>)
}
enum FetchError: Error {
    case failed
}
 
class UserPresenter: AnyPresenter {
    var router: AnyRouter?

    var interactor: AnyInteractor2? {
        didSet {
            interactor?.getUsers()
        }
    }
    
    var view: AnyView?
    
    init() {
        interactor?.getUsers()
    }

    func interactorDidFetchUsers(with result: Result<[User],Error>) {
        switch result {
        case .success(let users):
            view?.update(with: users)
        case .failure:
            view?.update(with: "Something went wrong")
        }
    }
}

Entity

import Foundation
 
struct User:Codable {
    let name:String
}

Router

import Foundation
import UIKit

typealias EntryPoint = AnyView & UIViewController

protocol AnyRouter {
    
    // var view: AnyView & UIViewController { get }
    var entry: EntryPoint? { get }
    
    static func start() -> AnyRouter
}

class UserRouter: AnyRouter {
    
    var entry: EntryPoint?
    
    static func start() -> AnyRouter {
        let router = UserRouter()
        
        var view: AnyView = UserViewController()
        var presenter: AnyPresenter = UserPresenter()
        var interactor: AnyInteractor2 = UserInteractor()
        
        view.presenter = presenter
        
        interactor.presenter = presenter
        
        presenter.router =  router
        presenter.view = view
        presenter.interactor = interactor
        
        router.entry = view as? EntryPoint
        
        return router
    }
}