Please excuse me if its repeated topic.
I usually write my apps without storyboards, and put views creation into "viewDidLoad", like:
class LoginVC: UIViewController {
var view1: UIView!
var label1: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
loadStaticViews()
}
func loadStaticViews() {
view1 = UIView()
label1 = UILabel()
view.addSubview(view1)
view1.addSubview(label1)
// constraints...
}
}
And now I want to try MVVM pattern in my next app, and just not sure where to put views creation.
Now I think about something like that:
class LoginVCViews {
static func loadViews<T, T1, T2>(superview: UnsafeMutablePointer<T>, view: UnsafeMutablePointer<T1>, label: UnsafeMutablePointer<T2>) {
guard let superview = superview.pointee as? UIView else { return }
let v = UIView()
let l = UILabel()
superview.addSubview(v)
v.addSubview(l)
// constraints...
view.pointee = v as! T1
label.pointee = l as! T2
}
}
class LoginVC: UIViewController {
private var view1: UIView!
private var label1: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
LoginVCViews.loadViews(superview: &view, view: &view1, label: &label1)
}
}
What do you think ? I'm not familiar with UnsafeMutablePointer very well and don't sure there wont be a some problems.
And how much it's ugly ?
Maybe you should try the fully object oriented path. A view composition looks something like that:
// reusable protocol set
protocol OOString: class {
var value: String { get }
}
protocol Executable: class {
func execute()
}
protocol Screen: class {
var ui: UIViewController { get }
}
protocol ViewRepresentation: class {
var ui: UIView { get }
}
// reusable functionality (no uikit dependency)
final class ConstString: OOString {
init(_ value: String) {
self.value = value
}
let value: String
}
final class ExDoNothing: Executable {
func execute() { /* do nothing */ }
}
final class ExObjCCompatibility: NSObject, Executable {
init(decorated: Executable) {
self.decorated = decorated
}
func execute() {
decorated.execute()
}
private let decorated: Executable
}
// reusable UI (uikit dependency)
final class VrLabel: ViewRepresentation {
init(text: OOString) {
self.text = text
}
var ui: UIView {
get {
let label = UILabel()
label.text = text.value
label.textColor = UIColor.blue
return label
}
}
private let text: OOString
}
final class VrButton: ViewRepresentation {
init(text: OOString, action: Executable) {
self.text = text
self.action = ExObjCCompatibility(decorated: action)
}
var ui: UIView {
get {
let button = UIButton()
button.setTitle(text.value, for: .normal)
button.addTarget(action, action: #selector(ExObjCCompatibility.execute), for: .touchUpInside)
return button
}
}
private let text: OOString
private let action: ExObjCCompatibility
}
final class VrComposedView: ViewRepresentation {
init(first: ViewRepresentation, second: ViewRepresentation) {
self.first = first
self.second = second
}
var ui: UIView {
get {
let view = UIView()
view.backgroundColor = UIColor.lightGray
let firstUI = first.ui
view.addSubview(firstUI)
firstUI.translatesAutoresizingMaskIntoConstraints = false
firstUI.topAnchor.constraint(equalTo: view.topAnchor, constant: 100).isActive = true
firstUI.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20).isActive = true
firstUI.widthAnchor.constraint(equalToConstant: 100).isActive = true
firstUI.heightAnchor.constraint(equalToConstant: 40).isActive = true
let secondUI = second.ui
view.addSubview(secondUI)
secondUI.translatesAutoresizingMaskIntoConstraints = false
secondUI.topAnchor.constraint(equalTo: firstUI.topAnchor).isActive = true
secondUI.leadingAnchor.constraint(equalTo: firstUI.trailingAnchor, constant: 20).isActive = true
secondUI.widthAnchor.constraint(equalToConstant: 80).isActive = true
secondUI.heightAnchor.constraint(equalToConstant: 40).isActive = true
return view
}
}
private let first: ViewRepresentation
private let second: ViewRepresentation
}
// a viewcontroller
final class ContentViewController: UIViewController {
convenience override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
self.init()
}
convenience required init(coder aDecoder: NSCoder) {
self.init()
}
convenience init() {
fatalError("Not supported!")
}
init(content: ViewRepresentation) {
self.content = content
super.init(nibName: nil, bundle: nil)
}
override func loadView() {
view = content.ui
}
private let content: ViewRepresentation
}
// and now the business logic of a screen (not reusable)
final class ScStartScreen: Screen {
var ui: UIViewController {
get {
return ContentViewController(
content: VrComposedView(
first: VrLabel(
text: ConstString("Please tap:")
),
second: VrButton(
text: ConstString("OK"),
action: ExDoNothing()
)
)
)
}
}
}
Usage in AppDelegate:
window?.rootViewController = ScStartScreen().ui
Note:
- it follows the rules of object oriented coding (clean coding, elegant objects, decorator pattern, ...)
- every class is very simple constructed
- classes communicate by protocols with each other
- all dependencies are given by dependency injection as far as possible
- everything (except the business screen at end) is reusable -> in fact: the portfolio of reusable code grows with every day you code
- the business logic of your app is concentrated in implementations of Screen objects
- unittesting is very simple when using fake implementations for the protocols (even mocking is not needed in most cases)
- lesser problems with retain cycles
- avoiding Null, nil and Optionals (they pollute your code)
- ...
In my opinion it's the best way to code, but most people don't do it like that.