When using MVVM, how do I extend the ViewModel in child ViewControllers? - ios

I have a hierarchy of this sort:
class OneViewModel {
}
class OneViewController {
var viewModel = OneViewModel()
}
class TwoViewModel : OneViewModel {
}
class TwoViewController : OneViewController {
var viewModel = TwoViewModel() // ???
}
I know that overriding property types is not allowed in Swift, but it doesn't make sense for the child ViewController to carry two ViewModels, how would one go about solving this? Thanks!

this should work
class OneViewModel {
}
class OneViewController {
var viewModel = OneViewModel()
}
class TwoViewModel : OneViewModel {
}
class TwoViewController : OneViewController {
override init() {
super.init()
self.viewModel = TwoViewModel()
}
}
you can set your viewModel instance to be TwoViewModel class anywhere.

I use this:
class OneViewController {
var viewModel: OneViewModel {
return storedViewModel
}
lazy var storedViewModel: OneViewModel = {
type(of: self).viewModelProvider()
}()
class func viewModelProvider() -> OneViewModel {
return OneViewModel()
}
}
class TwoViewController {
override var viewModel: TwoViewModel {
return storedViewModel as! TwoViewModel
}
override class func viewModelProvider() -> OneViewModel {
return TwoViewModel()
}
}
It's somewhat verbose to setup, but is easy and clear to use afterwards.

Related

Access function protocol in generic class

I want to access function in protocol, but XCode complaint
Instance member 'createColumns' cannot be used on type 'T'; did you
mean to use a value of this type instead?
What I have done:
Create protocol:
protocol StorageModelDelegate {
func createColumns(for tableBuilder: TableBuilder)
}
Create class generic that receive StorageModelDelegate:
class SQLiteStorage<T: StorageModelDelegate> {
func createTable(tableName: TableKey) -> Bool {
let table = Table(tableName.rawValue)
let query = table.create(ifNotExists: true) { (builder: TableBuilder) in
T.createColumns(for: builder) // -> this is the error comes up.
}
}
}
Create class that implement SQLiteStorage:
final class InfoStorageModel {
private let sqlite: SQLiteStorage = SQLiteStorage<Info>()
}
so, how to fix the error in SQLiteStorage class?
The error indicates that you need an instance of T, not the type itself.
So you need something like:
class SQLiteStorage<T: StorageModelDelegate> {
var delegate:T
init (delegate:T) {
self.delegate = delegate
}
func createTable(tableName: TableKey) -> Bool {
let table = Table(tableName.rawValue)
let query = table.create(ifNotExists: true) { (builder: TableBuilder) in
self.delegate.createColumns(for: builder) // -> this is the error comes up.
}
}
}
You want to call static method instead of instance method.
In order to fix, you should add instance parameter:
First of all, use weak var delegate in order to prevent retain cycles.
protocol StorageModelDelegate: class {
func createColumns(for tableBuilder: TableBuilder)
}
final class SQLiteStorage<T: StorageModelDelegate> {
weak var delegate: T?
func createTable(tableName: TableKey) -> Bool {
let table = Table(tableName.rawValue)
let query = table.create(ifNotExists: true) { (builder: TableBuilder) in
delegate?.createColumns(for: builder)
}
}
}
Or use static protocol methods:
protocol StorageModelDelegate {
static func createColumns(for tableBuilder: TableBuilder)
}
final class SQLiteStorage<T: StorageModelDelegate> {
weak var delegate: T?
func createTable(tableName: TableKey) -> Bool {
let table = Table(tableName.rawValue)
let query = table.create(ifNotExists: true) { (builder: TableBuilder) in
T.createColumns(for: builder)
}
}
}

Add class property to protocol in Swift

I want to have a protocol that have a variable. And class that conform to that protocol should use it like "normal" variable. What i want is something like:
protocol MyProtocol {
var foo: Int
}
class A {}
extension A: MyProtocol {
var foo: Int!
}
Code above not compile, i only want to show point i want to achieve.
I ended up with this, but i suppose there must be better way:
enum NextController {
case AuthSelection
case Main
}
protocol SmsEntryPresenterProtocol {
var nextController: NextController { get set }
}
class SmsEntryPresenter {
var _nextController: NextController!
weak var view: SmsEntryViewProtocol?
}
extension SmsEntryPresenter: SmsEntryPresenterProtocol {
var nextController: NextController {
get {
return _nextController
}
set {
_nextController = newValue
}
}
}
You can fix this as below,
class SmsEntryPresenter {
var nextController: NextController = .Main
weak var view: SmsEntryViewProtocol?
}
extension SmsEntryPresenter: SmsEntryPresenterProtocol {}

How to make different classes conform to the same protocol with different functionality?

I apologize beforehand if title is unclear, but I like to know how this is possible with some code snippets first:
ClassOne:
#objc protocol FetchProtocol
{
var someVar: Bool
{
get set
}
var someUIView: FetchProtocol
{
get set
}
func someFuncOne()
}
class ClassOne: UIViewController, FetchProtocol
{
...
#IBOutlet var someUIView: FetchProtocol!
....
}
ClassTwo:
#objc protocol FetchProtocol
{
var someVar: Bool
{
get set
}
var someTableView: FetchProtocol
{
get set
}
var someUIView: FetchProtocol
{
get set
}
func someFuncOne()
func someFuncTwo()
}
class ClassTwo: UIViewController, FetchProtocol
{
...
#IBOutlet var someTableView: FetchProtocol!
#IBOutlet var someUIView: FetchProtocol!
....
}
Both ClassOne and ClassTwo conform to the same FetchProtocol and both classes use the same someVar as well as someFuncOne, but ClassTwo also uses someTableView and someFuncTwo unique to only ClassTwo.
How can I use the same protocol between both classes, but the other class has "additional" different skeletal implementation?
For example, something like the following:
if let vc = currentVC as? FetchProtocol
{
if vc.someVar == true
{
// Check if vc is of class type ClassOne, call someFuncOne
// Otherwise if vc is of class type ClassTwo, call someFuncOne and someFuncTwo
}
}
Is something like the above possible using protocols and if so, how to properly implement it, or is there another alternative?
Your code doesn't compile and I think you're overcomplicating things and use protocols for the sake of using them.. There is no need to use any protocols at all if all you want to do is this:
if let vc = currentVC as? FetchProtocol
{
if vc.someVar == true
{
// Check if vc is of class type ClassOne, call someFuncOne
// Otherwise if vc is of class type ClassTwo, call someFuncOne and someFuncTwo
}
}
Why not delete all protocols and just do:
if currentVC.someVar {
if let class1 = currentVC as? ClassOne {
class1.someFuncOne()
} else if let class2 = currentVC as? ClassTwo {
class2.someFuncOne()
class2.someFuncTwo()
}
}
You don't really need protocols here because whether protocols exist or not, you still have to check whether currentVC is ClassOne or ClassTwo.
Protocols act like "black boxes". Consider this method:
func foo(fetchObj: FetchProtocol) {
fetchObj.someFuncOne()
}
foo doesn't care about what fetchObj really is. It just says "I don't care what you are, just do someFuncOne!"
What you're trying to do here is completely the opposite: "I do care what you are. If you're ClassOne, do this. If you're ClassTwo, do that."
Your problem is quite abstract, and pretty hard to follow, but this does what you're asking for. That being said, I suspect there's no need for you to be using protocols here at all.
protocol P1 {
var someVar1: String { get }
func func1();
}
protocol P2: P1 {
var someVar2: String { get }
func func2();
}
class C1: P1 {
var someVar1 = "String 1 in C1"
func func1() {
print(someVar1)
}
}
class C2: P2 {
var someVar1 = "String 1 in C2"
var someVar2 = "String 2 in C2"
func func1() {
print(someVar1)
}
func func2() {
print(someVar2)
}
}
func foo(with object: P1) {
object.func1()
if let object = object as? P2 {
object.func2()
}
}
print("Test case 1:")
foo(with: C1())
print("Test case 1:\n")
foo(with: C2())

Protocol can only be used as a generic constraint because it has Self or associated type requirements

I have this protocol:
protocol ViewType {
associatedtype T: ViewData.View
var data:T! {get set}
}
ViewData.View is a class
I have a class called TemplateLabel that inherits UILabel and conforms to ViewType
class TemplateLabel:UILabel, ViewType {
var data: ViewData.View.Label!
}
I get this TemplateLabel from storyboard as a UIView and try to cast the UIView into ViewType to assign the data property to it
let view = SB.instantiateViewControllerWithIdentifier("view_label").view
if var v = view as? ViewType { // Error
v.data = data // Error
}
But I get errors:
Protocol 'ViewType' can only be used as a generic constraint because it has Self or associated type requirements
Member 'data' cannot be used on value of protocol type 'ViewType'; use a generic constraint instead
I have an answer for you, but that's pretty much bare code. I think it can really be useful in the defined context.
import UIKit
// Framework
/**
* Intended usage:
*
* enum AppStoryboard: BundledStoryboard {
*
* case Login
* case Main
*
* var storyboard: UIStoryboard {
* return UIStoryboard(name: "\(self)", bundle: nil)
* }
*
* }
*/
protocol BundledStoryboard {
var storyboard: UIStoryboard { get }
}
protocol StoryboardViewController {
static var bundledStoryboard: BundledStoryboard { get }
static var storyboardId: String { get }
static func instantiateFromStoryboard() -> Self
}
extension StoryboardViewController {
static var storyboardId: String { return "\(self)" }
static func instantiateFromStoryboard() -> Self {
return bundledStoryboard.storyboard.instantiateViewControllerWithIdentifier(storyboardId) as! Self
}
}
// Application specific
enum AppStoryboard: BundledStoryboard {
//case Login
case Main
var storyboard: UIStoryboard {
return UIStoryboard(name: "\(self)", bundle: nil)
}
}
extension StoryboardViewController {
static var bundledStoryboard: BundledStoryboard { return AppStoryboard.Main }
}
// View-Model relation
protocol ViewType {
associatedtype Model
func loadModel(m: Model)
}
// ViewController
final class ViewController: UIViewController, StoryboardViewController, ViewType {
override func viewDidLoad() {
super.viewDidLoad()
}
func loadModel(m: UIColor?) {
view.backgroundColor = m // Strange example, yeah.
}
}
// Then somewhere...
let vc = ViewController.instantiateFromStoryboard()
vc.loadModel(.redColor())
I don't think you really need any dynamic solution here. You must know what you're instantiating and what data it can receive.

Traverse view controller hierarchy in Swift

I would like to traverse the view controller hierarchy in Swift and find a particular class. Here is the code:
extension UIViewController{
func traverseAndFindClass<T : UIViewController>() -> UIViewController?{
var parentController = self.parentViewController as? T?
^
|
|
// Error: Could not find a user-defined conversion from type 'UIViewController?' to type 'UIViewController'
while(parentController != nil){
parentController = parentController!.parentViewController
}
return parentController
}
}
Now, I know that the parentViewController property returns an optional UIViewController, but I do not know how in the name of God I can make the Generic an optional type. Maybe use a where clause of some kind ?
Your method should return T? instead of UIViewController?, so that the generic type
can be inferred from the context. Checking for the wanted class has also to
be done inside the loop, not only once before the loop.
This should work:
extension UIViewController {
func traverseAndFindClass<T : UIViewController>() -> T? {
var currentVC = self
while let parentVC = currentVC.parentViewController {
if let result = parentVC as? T {
return result
}
currentVC = parentVC
}
return nil
}
}
Example usage:
if let vc = self.traverseAndFindClass() as SpecialViewController? {
// ....
}
Update: The above method does not work as expected (at least not in the Debug
configuration) and I have posted the problem
as a separate question: Optional binding succeeds if it shouldn't. One possible workaround (from an answer to that question)
seems to be to replace
if let result = parentVC as? T { ...
with
if let result = parentVC as Any as? T { ...
or to remove the type constraint in the method definition:
func traverseAndFindClass<T>() -> T? {
Update 2: The problem has been fixed with Xcode 7, the
traverseAndFindClass() method now works correctly.
Swift 4 update:
extension UIViewController {
func traverseAndFindClass<T : UIViewController>() -> T? {
var currentVC = self
while let parentVC = currentVC.parent {
if let result = parentVC as? T {
return result
}
currentVC = parentVC
}
return nil
}
}
One liner solution (using recursion), Swift 4.1+:
extension UIViewController {
func findParentController<T: UIViewController>() -> T? {
return self is T ? self as? T : self.parent?.findParentController() as T?
}
}
Example usage:
if let vc = self.findParentController() as SpecialViewController? {
// ....
}
Instead of while loops, we could use recursion (please note that none of the following code is thoroughly tested):
// Swift 2.3
public extension UIViewController {
public var topViewController: UIViewController {
let o = topPresentedViewController
return o.childViewControllers.last?.topViewController ?? o
}
public var topPresentedViewController: UIViewController {
return presentedViewController?.topPresentedViewController ?? self
}
}
On the more general issue of traversing the view controller hierarchy, a possible approach is to have two dedicated sequences, so that we can:
for ancestor in vc.ancestors {
//...
}
or:
for descendant in vc.descendants {
//...
}
where:
public extension UIViewController {
public var ancestors: UIViewControllerAncestors {
return UIViewControllerAncestors(of: self)
}
public var descendants: UIViewControllerDescendants {
return UIViewControllerDescendants(of: self)
}
}
Implementing ancestor sequence:
public struct UIViewControllerAncestors: GeneratorType, SequenceType {
private weak var vc: UIViewController?
public mutating func next() -> UIViewController? {
guard let vc = vc?.parentViewController ?? vc?.presentingViewController else {
return nil
}
self.vc = vc
return vc
}
public init(of vc: UIViewController) {
self.vc = vc
}
}
Implementing descendant sequence:
public struct UIViewControllerDescendants: GeneratorType, SequenceType {
private weak var root: UIViewController?
private var index = -1
private var nextDescendant: (() -> UIViewController?)? // TODO: `Descendants?` when Swift allows recursive type definitions
public mutating func next() -> UIViewController? {
if let vc = nextDescendant?() {
return vc
}
guard let root = root else {
return nil
}
while index < root.childViewControllers.endIndex - 1 {
index += 1
let vc = root.childViewControllers[index]
var descendants = vc.descendants
nextDescendant = { return descendants.next() }
return vc
}
guard let vc = root.presentedViewController where root === vc.presentingViewController else {
return nil
}
self.root = nil
var descendants = vc.descendants
nextDescendant = { return descendants.next() }
return vc
}
public init(of vc: UIViewController) {
root = vc
}
}

Resources