Xcode
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.
When happy select the view layouts with the command key ⌘ and select default background
Adding the constraint for the number was hard. Put it in a view and add a constraint to indent on the right.
VIPER
VIPER is a design pattern which seems to be associated with Apple
Really enjoyed the demonstration from Youtube. [[1]]
VIPER Source Example
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].namess
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
}
}