Xcode: Difference between revisions
(9 intermediate revisions by the same user not shown) | |||
Line 13: | Line 13: | ||
Really enjoyed the demonstration from Youtube. [[https://www.youtube.com/watch?v=hFLdbWEE3_Y]]<br> | Really enjoyed the demonstration from Youtube. [[https://www.youtube.com/watch?v=hFLdbWEE3_Y]]<br> | ||
=VIPER Source Example= | =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 | |||
[[File:Remove Main Xcode01.png|700px]]<br> | |||
*Now go to info.plist and delete Storyboard Name | |||
[[File:Delete main xcode.png|700px]] | |||
In the SceneDelegate.swift | |||
<syntaxhighlight lang="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() | |||
} | |||
</syntaxhighlight> | |||
==View== | ==View== | ||
<syntaxhighlight lang="swift"> | <syntaxhighlight lang="swift"> | ||
Line 89: | Line 123: | ||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { | ||
let cell = tableview.dequeueReusableCell(withIdentifier: "cell", for: indexPath) | let cell = tableview.dequeueReusableCell(withIdentifier: "cell", for: indexPath) | ||
cell.textLabel?.text = users[indexPath.row]. | cell.textLabel?.text = users[indexPath.row].name | ||
return cell | return cell | ||
} | } | ||
Line 96: | Line 130: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
==Interactor== | ==Interactor== | ||
<syntaxhighlight lang="swift"> | <syntaxhighlight lang="swift"> | ||
import Foundation | import Foundation | ||
protocol | protocol AnyInteractor2 { | ||
var presenter: AnyPresenter? { get set } | var presenter: AnyPresenter? { get set } | ||
func getUsers() | func getUsers() | ||
Line 131: | Line 166: | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
==Presenter== | ==Presenter== | ||
<syntaxhighlight lang="swift"> | <syntaxhighlight lang="swift"> | ||
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") | |||
} | |||
} | |||
} | |||
</syntaxhighlight> | </syntaxhighlight> | ||
==Entity== | ==Entity== | ||
<syntaxhighlight lang="swift"> | <syntaxhighlight lang="swift"> | ||
import Foundation | |||
struct User:Codable { | |||
let name:String | |||
} | |||
</syntaxhighlight> | </syntaxhighlight> | ||
==Router== | ==Router== | ||
<syntaxhighlight lang="swift"> | <syntaxhighlight lang="swift"> | ||
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 | |||
} | |||
} | |||
</syntaxhighlight> | </syntaxhighlight> |
Latest revision as of 05:00, 9 July 2022
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
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
- Now go to info.plist and delete Storyboard Name
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
}
}