I have the following data model class to represent a MyDataModel:
public class MyDataModel <T> : Codable, Comparable where T:Codable {
...
}
Next I implement a view that holds an instance of MyDataModel. I can not obviously hold a generic parameter in the subclass of UIView so I try to workaround it as follows:
protocol MyDataProtocol {
associatedtype T:Codable
var dataParam:T { get set }
}
public class MyDataView: UIView {
public var myData:some MyDataProtocol?
}
But I get an error
An 'opaque' type must specify only 'Any', 'AnyObject', protocols, and/or a base class
So I can use Any? as type for MyDataProtocol but that will still not tell me the param type of MyDataModel. I am wondering what is the solution here and right way to handle this issue.
You should set an object for myData property and the error wouldn't show.
// Protocol example
public protocol MyDataProtocol {
var dataParam: Codable? { get set }
}
class MyData: MyDataProtocol {
var dataParam: Codable?
init() {}
}
public class MyDataView: UIView {
public var myData: MyDataProtocol?
public override init(frame: CGRect) {
print("test")
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
MyDataView(frame: .zero)
// Opaque example
public protocol MyDataProtocol {
associatedtype T
var dataParam: T? { get set }
}
class MyData: MyDataProtocol {
var dataParam: Codable?
init() {}
}
public class MyDataView: UIView {
public var myData: some MyDataProtocol = MyData()
public override init(frame: CGRect) {
print("test")
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
MyDataView(frame: .zero)
Related
I´ve got a base ViewController and a base ViewModel. The base ViewModel is used by the base ViewController. Also, I´ve got 2 subclasses of ViewControllers and 2 subclasses of ViewModels that need to be used together.
Example:
class BaseViewModel {
func somethingBasic() {}
}
class ConcreteViewModel1: BaseViewModel {
func somethingConcrete1() {}
}
class ConcreteViewModel2: BaseViewModel {
func somethingConcrete2() {}
}
class BaseViewController {
let viewModel: BaseViewModel
init(with viewModel: BaseViewModel) {
self.viewModel = viewModel
}
}
class ConcreteViewController1: BaseViewController {
init(with viewModel: ConcreteViewModel1) {
super.init(with: viewModel)
}
func useViewModel() {
viewModel.somethingBasic()
viewModel.somethingConcrete1() //this does not work
}
}
class ConcreteViewController2: BaseViewController {
init(with viewModel: ConcreteViewModel2) {
super.init(with: viewModel)
}
func useViewModel() {
viewModel.somethingBasic()
viewModel.somethingConcrete2() //this does not work
}
}
The question is: what is the preferred solution to make viewmodel.somethingConcrete1() and viewmodel.somethingConcrete2() work?
Try using Generics for this.
Create init in BaseViewController accepting a generic parameter T constrained to type BaseViewModel, i.e.
class BaseViewController<T: BaseViewModel> {
let viewModel: T
init(with viewModel: T) {
self.viewModel = viewModel
}
}
Now inherit ConcreteViewController1 and ConcreteViewController2 from BaseViewController giving the specific type for generic parameter T, i.e.
class ConcreteViewController1: BaseViewController<ConcreteViewModel1> {
func useViewModel() {
viewModel.somethingBasic()
viewModel.somethingConcrete1()
}
}
class ConcreteViewController2: BaseViewController<ConcreteViewModel2> {
func useViewModel() {
viewModel.somethingBasic()
viewModel.somethingConcrete2()
}
}
I discussed this with a few other colleagues, and we came around with this solution, based on Composition instead of inheritance:
class BaseViewModel {
func somethingBasic() {}
}
class ConcreteViewModel1 {
private let baseViewModel = BaseViewModel()
func somethingConcrete1() {}
func somethingBasic() {
baseViewModel.somethingBasic()
}
}
class ConcreteViewModel2 {
private let baseViewModel = BaseViewModel()
func somethingConcrete2() {}
func somethingBasic() {
baseViewModel.somethingBasic()
}
}
class BaseViewController {}
class ConcreteViewController1 {
private let base = BaseViewController()
private let viewModel: ConcreteViewModel1
init(with viewModel: ConcreteViewModel1) {
self.viewModel = viewModel
}
func useViewModel() {
viewModel.somethingBasic()
viewModel.somethingConcrete1()
}
}
class ConcreteViewController2: BaseViewController {
private let base = BaseViewController()
private let viewModel: ConcreteViewModel2
init(with viewModel: ConcreteViewModel2) {
self.viewModel = viewModel
}
func useViewModel() {
viewModel.somethingBasic()
viewModel.somethingConcrete2()
}
}
With that solution, you get the type safety, you avoid Generics and you don´t need to cast anywhere.
In AppDelegate.swift I have:
func application(_ application: UIApplication, shouldRestoreApplicationState coder: NSCoder) -> Bool {
return true
}
And iOS will call my encodeRestorableState() & decodeRestorableState() class methods during state restoration.
How does Codable work with respect to state restoration? What does iOS call and how do I tie in my Codable structs and classes?
encodeRestorableState(with:) passes you an instance of NSCoder. Any variables you require to restore your state must be encoded here using encode(_:forKey:) with this coder and must therefore conform to Codable.
decodeRestorableState(with:) passes you this same Coder into the function body. You can access the properties in the decoder with the key you used when they were encoded and then set them to instance variables or otherwise use them to configure your controller.
e.g.
import UIKit
struct RestorationModel: Codable {
static let codingKey = "restorationModel"
var someStringINeed: String?
var someFlagINeed: Bool?
var someCustomThingINeed: CustomThing?
}
struct CustomThing: Codable {
let someOtherStringINeed = "another string"
}
class ViewController: UIViewController {
var someStringIDoNotNeed: String?
var someStringINeed: String?
var someFlagINeed: Bool?
var someCustomThingINeed: CustomThing?
override func encodeRestorableState(with coder: NSCoder) {
super.encodeRestorableState(with: coder)
let restorationModel = RestorationModel(someStringINeed: someStringINeed,
someFlagINeed: someFlagINeed,
someCustomThingINeed: someCustomThingINeed)
coder.encode(restorationModel, forKey: RestorationModel.codingKey)
}
override func decodeRestorableState(with coder: NSCoder) {
super.decodeRestorableState(with: coder)
guard let restorationModel = coder.decodeObject(forKey: RestorationModel.codingKey) as? RestorationModel else {
return
}
someStringINeed = restorationModel.someStringINeed
someFlagINeed = restorationModel.someFlagINeed
someCustomThingINeed = restorationModel.someCustomThingINeed
}
}
I would like to know what is a proper way of setting delegates in the ViewModel in MVVM pattern in Swift.
I'm instantiating the ViewController from another class:
let viewModel = DashboardViewModel()
let viewController = DashboardViewController(viewModel: viewModel)
My ViewModel:
protocol DashboardViewModelType {
var items: [Item] { get }
var reloadDelegate: DashboardDataReloadDelegate? { get set }
}
protocol DashboardDataReloadDelegate: class {
func reloadData()
}
class DashboardViewModel: DashboardViewModelType {
var items: [Item] = []
weak var reloadDelegate: DashboardDataReloadDelegate?
init() {
loadItems()
}
func loadItems() {
let databaseFetcher = DatabaseDaysFetcher()
databaseFetcher.getDays(onData: { (items) in
self.items = items
reloadDelegate?.reloadData() //delegate is nil here
}) { (error) in
print(error)
}
}
}
and ViewController:
class DashboardViewController: UIViewController {
var viewModel: DashboardViewModelType?
init(viewModel: DashboardViewModelType) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
self.viewModel!.reloadDelegate = self // it is executed after
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
extension DashboardViewController: DashboardDataReloadDelegate {
func reloadData() {
print("data reloaded")
}
}
So the main problem is that if I want to inject the viewModel in another class I'm instantiating the viewModel when delegate is not yet set. Would it be better to declare loadItems inside the DashboardViewModelType protocol and then call this function from the init or viewDidLoad inside the ViewController?
Yes, you could inject DatabaseDaysFetcher in the init for the DashboardViewModel and then as you say, move loadItems to the DashboardViewModelType protocol.
Then when you call loadItems, it should callback in to the caller.
Then use [weak self] in the loadItems callback.
This would negate the need for the delegate.
protocol DashboardViewModelType {
init(databaseFetcher: DatabaseDaysFetcher)
func loadItems(completion: ([Item]) -> Void, error: (Error) -> Void)
}
final class DashboardViewModel: DashboardViewModelType {
private var databaseFetcher: DatabaseDaysFetcher
init(databaseFetcher: DatabaseDaysFetcher) {
self.databaseFetcher = databaseFetcher
}
func loadItems(completion: ([Item]) -> Void, onError: (Error) -> Void) {
self.databaseFetcher.getDays(onData: { (items) in
completion(items)
}) { (error) in
onError(error)
}
}
}
I made a framework that wraps Alamofire.
In my Framework when testing (in Test target) i have this code that works grate.
import Foundation
#testable import NetworkManager
class MockRouter: Router {
enum APICalls {
case func1
case func2
}
var calls: APICalls!
init(calls: APICalls) {
self.calls = calls
}
}
When i add it as a framework to a different project
import Foundation
import NetworkManager
class JokesRouter: Router {
enum APICalls {
case func1
case func2
}
var calls: APICalls!
init(calls: APICalls) {
self.calls = calls
}
}
I get an error:
super init isn't called on all paths before returning from initializer
So i added Super.init()
init(calls: APICalls) {
super.init()
self.calls = calls
}
And now i get this error:
super.init cannot be called outside of an initializer
Any idea what is the problem?
You'll need to declare an empty public init() {} in Router.
Though it's a cryptic error, the problem is that the subclass cannot call super.init() because that initializer is not accessible outside of the NetworkManager module unless you declare it as public. The automatically-generated init() in Router is not public by default.
It worked in your test class because the #testable import sidestepped the default internal access restriction.
I was trying to create an Inheritance feature of the Swift Programming.
I created the ViewController as super class.
Then I created another class as subclass name as ViewControllerA of super class ViewController
ViewController Class :
import UIKit
class ViewController: UIViewController {
//: public variables
var area: CGFloat?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
public func printArea(){
print("Print area in Super class")
}
public func calculateArea(){
}
}
ViewControllerA as subclass :
import UIKit
class ViewControllerA: ViewController {
var length: CGFloat?
init( len: CGFloat) {
self.length = len
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func printArea() {
print("The are of A \(area!)")
}
override func calculateArea() {
area = length! * length!
}
}
I have got the error.
Must call a designated initializer of the superclass 'ViewController' in subclass init method.
So I declare the empty init method in super class i.e ViewController
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
It worked for me.
I am trying to subclass WKWebView. When I implement my own initializer, I got this error:
'required' initializer 'init(coder:)' must be provided by subclass of 'WKWebView'
Ok, that is well known that we have to implement it for subclasses of UIView. For a direct subclass of UIView it works just implementing it, but with WKWebView it does not seem so simple. I followed the Fix-it hint, and this snippet is added to the code:
required #availability(*, unavailable) convenience init!(coder: NSCoder!) {
fatalError("init(coder:) has not been implemented")
}
So I get a class like the following:
import WebKit
class TSWebView : WKWebView {
let s: String
let i: Int
init(s: String, i: Int) {
self.s = s
self.i = i
super.init(frame: CGRectZero, configuration: WKWebViewConfiguration())
}
required #availability(*, unavailable) convenience init!(coder: NSCoder!) {
fatalError("init(coder:) has not been implemented")
}
}
However, when I do this I get these four other errors:
expected declaration
required #availability(*, unavailable) convenience init!(coder: NSCoder!) {
consecutive declarations on a line must be separated by ';'
required #availability(*, unavailable) convenience init!(coder: NSCoder!) {
cannot override 'init' which has been marked unavailable
required #availability(*, unavailable) convenience init!(coder: NSCoder!) {
'required' modifier must be present on all overrides of a required initializer
required #availability(*, unavailable) convenience init!(coder: NSCoder!) {
Any ideas? My Xcode Version is 6.1.1 (6A2008a). Thanks a lot.
Just override the regular initialization like this. This worked for me, Swift 5.
override init(frame: CGRect, configuration: WKWebViewConfiguration) {
super.init(frame: frame, configuration: configuration)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
This is totes possible. You must only use convenience initializers and properties with default values set:
import WebKit
class MPWebView : WKWebView {
var transparent: Bool = false
convenience init(config: WKWebViewConfiguration = WKWebViewConfiguration()) {
let prefs = WKPreferences()
prefs.plugInsEnabled = true // NPAPI for Flash, Java, Hangouts
prefs.minimumFontSize = 14
prefs.javaScriptCanOpenWindowsAutomatically = true;
config.preferences = prefs
config.suppressesIncrementalRendering = false
self.init(frame: CGRectZero, configuration: config)
}
convenience required init(url: NSURL) {
self.init(config: nil)
loadRequest(NSURLRequest(URL: url))
}
}
Try taking out the extra decorations:
import WebKit
class TSWebView : WKWebView {
let s: String
let i: Int
init(s: String, i: Int) {
self.s = s
self.i = i
super.init(frame: CGRectZero, configuration: WKWebViewConfiguration())
}
convenience init!(coder: NSCoder!) {
super.init(coder:coder)
}
}
Although I'm guessing the whole point of the "availablity(*, unavailable)" is to make it so that you can't invoke the initializer (and hence can't effectively subclass WKWebView.