Initialization of a UIViewController with default values consisted of singletons - swift

Trying to refactor my code in order to avoid having to reference shared instances all over the place, rather I'd like to inject them via a custom initializer. My limited understanding of initilazation in Swift is preventing me to do so.
Here's my ViewController, minus the methods and boilerplate:
class LoginVC: UIViewController {
let dataManager: DataManager
let restManager: RestManager
let defaults: UserDefaults
init(defaults: UserDefaults = .standard, dataManager: DataManager = .sharedInstance, restManager: RestManager = .sharedInstance) {
self.defaults = defaults
self.dataManager = dataManager
self.restManager = restManager
super.init(nibName: nil, bundle: nil)
}
}
I've provided default values which are shared instances declared as a static constant in their respective singleton classes of DataManager and RestManager. Was planning to do this in each and every ViewController that has these singletons referenced in them. Anyways, my problem arises as the compiler complains that I must provide a required initializer init(coder: aDecoder).
No problem, but after implementing...
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
... the compiler once again complains, this time that
Blockquote Property 'self.defaults' not initialized at super.init call
Which I guess makes sense since the required init? is, as far as I know, a delegating init (could be that I'm totally mistaken here). I do not know how to pass the default params of my custom init to the super init of this initializer, or if that's even possible.
I have tried a workaround though by changing the constants to mutable variables and by making them implicitly unwrapped like so:
class LoginVC: UIViewController {
var dataManager: DataManager!
var restManager: RestManager!
var defaults: UserDefaults!
init(defaults: UserDefaults = .standard, dataManager: DataManager = .sharedInstance, restManager: RestManager = .sharedInstance) {
self.defaults = defaults
self.dataManager = dataManager
self.restManager = restManager
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
This workaround actually manages to compile and build, but the app crashes as soon as userDefaults which are in viewDidLoad are unwrapped because they have a nil value. All of them have nil values even though they are defaulted in the params of the custom initializer which leads me to believe that I've royally screwed up.
Any help/clarification would be greatly appreciated, thanks in advance.

Not sure that this is what you would like to do, but I would suggest you this solution
extension UIViewController {
var defaults: UserDefaults {
get {
return .standard
}
}
}
and so in each UIViewController you can do self.defaults
If you want some of your UIViewControllers to have another references, you can do this
//create protocol with all vars
protocol References {
var defaults: UserDefaults {get}
}
//create extension with default implementation
extension References {
var defaults: UserDefaults {
get {
return .standard
}
}
}
//add this protocol to all UIViewControllers
extension UIViewController: References {
}
//or create new class with references
class ReferenceViewController: UIViewController, References {
}
//override reference in some view controller if needed
class AnotherReferencesViewController: UIViewController { //or ReferenceViewController
var defaults: UserDefaults {
get {
//something new
return .standard
}
}
}

Related

Why is this subclass failing to be archived by NSKeyedArchived?

I have this class that have to be the subclass of OIDAuthState (this class is an NSObject and conforms to NSSecureCoding),
The code to encode the class OIDAuthState was working fine before
self.data = try NSKeyedArchiver.archivedData(withRootObject: authState, requiringSecureCoding: true)
Once I created this subclass that includes adds a simple Boolean to the class isExpiredTokenEnforced, I now get the error when using NSKeyedArchiver,
The error says:
NSDebugDescription=Caught exception during archival: *** -decodeBoolForKey: only defined for abstract class. Define -[NSKeyedArchiver decodeBoolForKey:]!
Here is the subclass I am trying to archive and down below the override method to encode and decode this class, I am simply encoding and decoding this 1 extra property and then passing down the coder to the parent class,
class AuthenticationStateManager: OIDAuthState, AuthenticationState {
var isExpiredTokenEnforced = false
var lastTokenResponseInterface: TokenResponse? {
if isExpiredTokenEnforced {
return EnforcedExpiredTokenResponse(idToken: super.lastTokenResponse?.idToken,
accessToken: super.lastTokenResponse?.accessToken)
} else {
return super.lastTokenResponse
}
}
override static var supportsSecureCoding: Bool { true }
required override init(authorizationResponse: OIDAuthorizationResponse?, tokenResponse: OIDTokenResponse?, registrationResponse: OIDRegistrationResponse?) {
super.init(authorizationResponse: authorizationResponse, tokenResponse: tokenResponse, registrationResponse: registrationResponse)
}
required init?(coder: NSCoder) {
coder.encode(isExpiredTokenEnforced, forKey: "isExpiredTokenEnforced")
super.init(coder: coder)
}
override func encode(with coder: NSCoder) {
self.isExpiredTokenEnforced = coder.decodeBool(forKey: "isExpiredTokenEnforced")
super.encode(with: coder)
}
}
I can't figure out why this is failing I couldn't find relevant information to fix this,
does anyone have a clue what could be wrong here?
Thank you in advance for your time.
You are mixing up your encoder and decoder functions. Your func encode() is trying to call decodeBool. It should be calling encode().
Similarly your init?(coder:) function should be calling decode (or rather decodeBool(forKey:).)

Using protocol's associated type in generic functions

I'm trying to write a simple MVP pattern to follow in my app, so I've written two porotocols to define View Controller and Presenters:
protocol PresenterType: class {
associatedtype ViewController: ViewControllerType
var viewController: ViewController? { get set }
func bind(viewController: ViewController?)
}
protocol ViewControllerType: class {
associatedtype Presenter: PresenterType
var presenter: Presenter { get }
init(presenter: Presenter)
}
After having those defined I started writing some RootViewController and RootViewPresenter. The latter looks like:
protocol RootViewControllerType: ViewControllerType {
}
final class RootPresenter<VC: RootViewControllerType>: PresenterType {
weak var viewController: VC?
func bind(viewController: VC?) {
self.viewController = viewController
}
}
Up to this point everything complies and is fine, but when I start implementing View Controller like this:
protocol RootPresenterType: PresenterType {
}
final class RootViewController<P: RootPresenterType>: UIViewController, ViewControllerType {
let presenter: P
init(presenter: Presenter) {
self.presenter = presenter
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
presenter.bind(viewController: self)
}
}
Immediately I get the following error message:
Cannot convert value of type 'RootViewController' to expected argument type '_?'
I know that protocols with associated types can introduce some limitations, but this example is pretty straightforward and I can't make it work. Is it possible to achieve something that I want, or do I have to look for some other, less Swifty pattern?
I don't think what you're trying to achieve is possible due to the circular dependency between the respective associated types of the PresenterType and ViewControllerType protocols.
Consider for a moment if the suspect code above did compile ... how would you go about instantiating either the RootPresenter or RootViewController classes? Because both depend on one another, you'll end up with errors like the following:
As you can see, the compiler can't fully resolve the generic parameters due to the associated types.
I think your best bet is to remove the associated type from one or both of the protocols. For example, removing the associated type from the PresenterType protocol and updating the RootPresenter class breaks the circular dependency and allows your code to compile normally.
protocol PresenterType: class {
var viewController: UIViewController? { get set }
func bind(viewController: UIViewController?)
}
final class RootPresenter: PresenterType {
weak var viewController: UIViewController?
func bind(viewController: UIViewController?) {
self.viewController = viewController
}
}

Swift pass and store a class implementing a protocol to another class

Anyone may have a clean pre-swift-4 solution in mind for this?
I've seen similar questions, but this is more specific and the other solutions don't work here
NOTE: I can't create the second viewCotnroller in init, it needs data to be passed to init, and that data comes from server
protocol Cool { }
class Class1 {
let viewController: UIViewController
let cool: Cool
// Swift 4 : let viewController: UIViewController & Cool
public init<T: UIViewController>(content: T) where T: Cool {
self.viewController = content
self.cool = content
}
func atSomePointLater () {
// Ho to get this to compile?
Class2(content: viewController as! Cool, someText: textfield.text)
}
}
class Class2 {
public init<T: UIViewController>(content: T, someText: String) where T: Cool {
}
}
Since both Class1 and Class2 initializers require a view controller conforming to the Cool protocol for their content parameter, we can make Class1 a generic class of a type we'll call T (where T is of type UIViewController and conforms to the Cool protocol).
Since viewController is a always of type T, we can change its type in its declaration. The Class2 initializer will now accept viewController as parameter because it's now not just any UIViewController, but one that also conforms to the Cool protocol.
protocol Cool { }
class Class1<T: UIViewController> where T: Cool {
let viewController: T
let cool: Cool
public init(content: T) {
self.viewController = content
self.cool = content
}
func atSomePointLater () {
let cls2 = Class2(content: viewController, someText: "Hello")
print("Class 2", cls2)
}
}
class Class2 {
public init<T: UIViewController>(content: T, someText: String) where T: Cool {
}
}
class MyVC: UIViewController { }
extension MyVC: Cool {} // Mark MyVC as conforming to 'Cool' protocol
let vc1 = MyVC()
let cls1 = Class1(content: vc1)
cls1.atSomePointLater()
// Both 'viewController' and 'cool' reference 'vc1'
print(cls1.viewController == vc1) // true
print(cls1.cool) // the same instance of MyVC, i.e. vc1
Disclaimer: I have yet to familiarize myself with Swift 4.. so hopefully this helps.
First, you dont have a subclass of UIViewController that adheres to Cool. So you need to do that. UIViewController, and Cool like you've shown here doesnt provide enough info to suggest that downcasting can be perfomed between the two. Afterwards, you need to update viewcontroller in Class1 as that subclass. You wont be able to cast UIViewController as Cool otherwise with what you're providing here.
Maybe some more context would be helpful, to help with the purpose of Cool. By this I mean that i do not know what you need Cool for/what Cool is for generally in your use-case, but the following gets it all to compile.
public protocol Cool {
init<T: UIViewController>(content: T) where T: Cool
}
class CoolController : UIViewController, Cool {
required init<T>(content: T) where T : UIViewController, T : Cool {
super.init(nibName: nil, bundle: nil)
//Do whatever you need here..?
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class Class1: NSObject, Cool {
var viewController: CoolController?
var cool: Cool?
required init<T>(content: T) where T : UIViewController, T : Cool {
super.init()
if let cont = content as? Cool {
cool = cont
} else if let controller = content as? UIViewController{
viewController = CoolController(content: content)
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
/*
// Swift 4 : let viewController: UIViewController & Cool
*/
func atSomePointLater() {
// Ho to get this to compile? ///Optionally.. use a GUARD statement here..
if let c = viewController {
Class2(content: c, someText: nil)
}
}
}
class Class2 {
public init<T: UIViewController>(content: T, someText: String?) where T: Cool {
}
}
Sidenote - Update: I would definitely provide context for Cool, and as to why you're instantiating a UIViewController inside of an object-only class. Not mandatory, but definitely intriguing. Also, I could not use the cool = Cool? variable inside of your atSomePointLater() method because there's no way of knowing that Cool and UIViewController can be downcasted (as I said above).
Stay Cool .. sorry, i just had to...

How do I make a custom initializer for a UIViewController subclass in Swift?

Apologies if this has been asked before, I've searched around a lot and many answers are from earlier Swift betas when things were different. I can't seem to find a definitive answer.
I want to subclass UIViewController and have a custom initializer to allow me to set it up in code easily. I'm having trouble doing this in Swift.
I want an init() function that I can use to pass a specific NSURL I'll then use with the view controller. In my mind it looks something like init(withImageURL: NSURL). If I add that function it then asks me to add the init(coder: NSCoder) function.
I believe this is because it's marked in the superclass with the required keyword? So I have to do it in the subclass? I add it:
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
Now what? Is my special initializer considered a convenience one? A designated one? Do I call a super initializer? An initializer from the same class?
How do I add my special initializer onto a UIViewController subclass?
class ViewController: UIViewController {
var imageURL: NSURL?
// this is a convenient way to create this view controller without a imageURL
convenience init() {
self.init(imageURL: nil)
}
init(imageURL: NSURL?) {
self.imageURL = imageURL
super.init(nibName: nil, bundle: nil)
}
// if this view controller is loaded from a storyboard, imageURL will be nil
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
For those who write UI in code
class Your_ViewController : UIViewController {
let your_property : String
init(your_property: String) {
self.your_property = your_property
super.init(nibName: nil, bundle: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) is not supported")
}
}
This is very similar to the other answers, but with some explanation. The accepted answer is misleading because its property is optional and doesn't expose the fact that your init?(coder: NSCoder) MUST initialize each and every property and the only solution to that is having a fatalError(). Ultimately you could get away by making your properties optionals, but that doesn't truly answer the OP’s question.
// Think more of a OnlyNibOrProgrammatic_NOTStoryboardViewController
class ViewController: UIViewController {
let name: String
override func viewDidLoad() {
super.viewDidLoad()
}
// I don't have a nib. It's all through my code.
init(name: String) {
self.name = name
super.init(nibName: nil, bundle: nil)
}
// I have a nib. I'd like to use my nib and also initialze the `name` property
init(name: String, nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle? ) {
self.name = name
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
// when you do storyboard.instantiateViewController(withIdentifier: "ViewController")
// The SYSTEM will never call this!
// it wants to call the required initializer!
init?(name: String, coder aDecoder: NSCoder) {
self.name = "name"
super.init(coder: aDecoder)
}
// when you do storyboard.instantiateViewController(withIdentifier: "ViewController")
// The SYSTEM WILL call this!
// because this is its required initializer!
// but what are you going to do for your `name` property?!
// are you just going to do `self.name = "default Name" just to make it compile?!
// Since you can't do anything then it's just best to leave it as `fatalError()`
required init?(coder aDecoder: NSCoder) {
fatalError("I WILL NEVER instantiate through storyboard! It's impossible to initialize super.init?(coder aDecoder: NSCoder) with any other parameter")
}
}
You basically have to ABANDON loading it from storyboard. Why?
Because when you call a viewController storyboard.instantiateViewController(withIdentifier: "viewController") then UIKit will do its thing and call
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
You can never redirect that call to another init method.
Docs on instantiateViewController(withIdentifier:):
Use this method to create a view controller object to present
programmatically. Each time you call this method, it creates a new
instance of the view controller using the init(coder:) method.
Yet for programmatically created viewController or nib created viewControllers you can redirect that call as shown above.
Convenience initializers are secondary, supporting initializers for a
class. You can define a convenience initializer to call a designated
initializer from the same class as the convenience initializer with
some of the designated initializer’s parameters set to default values.
You can also define a convenience initializer to create an instance of
that class for a specific use case or input value type.
They are documented here.
If you need a custom init for a popover for example you can use the following approach:
Create a custom init that uses the super init with nibName and bundle and after that access the view property to force the load of the view hierarchy.
Then in the viewDidLoad function you can configure the views with the parameters passed in the initialization.
import UIKit
struct Player {
let name: String
let age: Int
}
class VC: UIViewController {
#IBOutlet weak var playerName: UILabel!
let player: Player
init(player: Player) {
self.player = player
super.init(nibName: "VC", bundle: Bundle.main)
if let view = view, view.isHidden {}
}
override func viewDidLoad() {
super.viewDidLoad()
configure()
}
func configure() {
playerName.text = player.name + "\(player.age)"
}
}
func showPlayerVC() {
let foo = Player(name: "bar", age: 666)
let vc = VC(player: foo)
present(vc, animated: true, completion: nil)
}

Explicitly set true Bool is somehow set to false

Do the following:
Create a class called A, subclass of UITableViewCell:
class A: UITableViewCell {
var isChosen: Bool = true
}
Create a xib file and drag a UITableViewCell object as the top level object, and make sure to set its class to A:
Create an instance of A:
var a = NSBundle.mainBundle().loadNibNamed("A", owner: nil, options: nil)[0] as A
Print isChosen:
println(a.isChosen)
Output:
false
Why is this happening? It only happens when you initialize the instance from a nib.
Even if you declare the variable as an optional and set it to nil:
var isChosen: Bool! = nil
it'll still be set to false somehow.
Since your class A does not have any init methods defined swift automatically generated default initializer for you. With default init() method code var isChosen: Bool = true is a shortcut to:
class A: UITableViewCell {
var isChosen: Bool
init() {
isChosen = true
}
}
When you create your custom cell of type A from Nib then auto generated init() method does not get called because initWithCoder called hence isChosen value is false.
UPDATE:
As already mentioned by #MattGibson in comments to the question, with xCode 6 Beta 5 update we can address the problem. It can be solved by adding init with coder initializer and marking it as required, so A should contain code bellow:
required init(coder aDecoder: NSCoder!) {
super.init(coder: aDecoder)
}
How it works? From Beta 5 Release Notes:
The required modifier is written before every subclass implementation
of a required initializer. Required initializers can be satisfied by
automatically inherited initializers.
UPDATE:
required init(coder aDecoder: NSCoder!) { ... } should be added only if you override at lest one init method in your class.