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)
}
Related
I am using MVVM and want to assign my viewModel to the viewController on the Controllers init. I thought I would achieve this like so:
class LoginViewController: UIViewController, UITextFieldDelegate {
init(loginViewModel: LoginViewModel) {
self.loginViewModel = loginViewModel
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
fatalError("init(coder:) has not been implemented")
}
However I get the error:
Super.init isn't called on all paths before returning from initializer
Is this not the correct route to take? also how would I init the viewModel correctly when it requires an object to do so, but it has to perform a network request first? Init with a blank object instance?
Thanks
edit: this is what im trying now
initWithViewAndViewModel:(loginView: LoginView, loginViewModel: LoginViewModel) {
super.init()
self.loginView = loginView
self.loginViewModel = loginViewModel
}
As a best practice,
Step 1:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
Step 2:
init(loginViewModel : LoginViewModel) {
super.init(nibName: nil, bundle: nil)
initWithModel:(loginViewModel: LoginViewModel)
}
Step 3: implement initWithModel function
Try adding this if it works:
convenience init() {
self.init(loginViewModel: nil)
}
init(loginViewModel: LoginViewModel?) {
self.loginViewModel = loginViewModel
super.init(nibName: nil, bundle: nil)
}
Please go through this link it might help: https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Initialization.html#//apple_ref/doc/uid/TP40014097-CH18-XID_324
I am trying to setup a master details navigation.
I use storyboard, master is a dynamic table and details is a static table.
I have a nameLabel setup as an outlet in the controller but when i try to access it in viewDidLoad, its still set to nil.
Instead of using prepareForSegue, I have used didSelectRowAtIndexPath which pushes the detail view like this: (because i'm using the TableViewBindingHelper, see https://github.com/ColinEberhardt/ReactiveTwitterSearch/tree/master/ReactiveTwitterSearch/Util)
func showLessonView(lessonVM: LessonViewModel) {
let lessonViewController = LessonViewController(WithViewModel: lessonVM)
self.navigationController?.pushViewController(lessonViewController, animated: true)
}
LessonViewController:
import Foundation
import ReactiveCocoa
class LessonViewController: UITableViewController {
#IBOutlet var lessonNameLabel: UILabel!
private var viewModel: LessonViewModel
init(WithViewModel viewModel: LessonViewModel){
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
bindData()
}
func bindData() {
// null here!
if (lessonNameLabel != nil) {
lessonNameLabel.rac_text <~ viewModel.name
}
}
}
How can I fix this?
Other sample code i have seen performs the navigation in segue which ends up calling the init(coder aDecoder: NSCoder) constructor and all the outlets are already initialized.
Because you initialise the view controller with the WithViewModel initialiser, it knows nothing about the storyboard and so the outlets are not hooked up. To get the outlets hooked up as specified in the storyboard, you need either to use a segue, or to use the storyboard's instantiateViewControllerWithIdentifier(identifier:) method to create the view controller. Either way, you can't (easily) pass the ViewModel as an argument for the initialisation, so you will need to expose the viewModel var (remove private) and set it separately in your showLessonView method. To use instantiateViewControllerWithIdentifier(identifier:), give your Lesson View Controller an identifier (say "LessonViewController") in the storyboard. Then amend your showLessonView as follows:
func showLessonView(lessonVM: LessonViewModel) {
let lessonViewController = self.storyboard!.instantiateViewControllerWithIdentifier(identifier:"LessonViewController") as! LessonViewController
lessonViewController.viewModel = lessonVM
self.navigationController?.pushViewController(lessonViewController, animated: true)
}
When a view controller is instantiated from a storyboard, the init(coder:) initialiser is used, so either remove the override of that method, or amend it to call the super implementation:
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
I'm trying to push a view controller using:
var vc2 = ViewController2()
self.navigationController?.pushViewController(vc2, animated: false)
but in my second view controller, I'm have:
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
But I get the error Missing argument for parameter 'coder' in call in the first ViewController. What goes in the parenthesis in the first view controller?
There are two ways you can fix this issue:
The easy way, just call the function passing nil to the parameters:
var vc2 = ViewController2(nibName: nil, bundle: nil)
The best way, create convenience initializers in your class:
class ViewController2: UIViewController {
required init(coder aDecoder: NSCoder) {
fatalError("This class does not support NSCoding")
}
override init (frame : CGRect) {
super.init(frame : frame)
}
convenience override init () {
self.init(frame:CGRectZero)
}
}
and now you can call:
var vc2 = ViewController2()
class ViewController2: UIViewController {
convenience init () {
self.init(nibName: nil, bundle: nil)
}
}
Now you can call ViewController2()
All,
I have a bar button item on my ViewController. I have set a computed property to will turn the BarButton off. I want to be able to set this from another class.
Here is my code in the view controller :
class ViewController: UIViewController {
var PayButton : Int {
didSet {
navigationItem.leftBarButtonItem = nil
}
}
required init(coder aDecoder: NSCoder)
{
self.PayButton = 0
super.init(coder: aDecoder)
}
When it try and create an instance on the view controller (so I can set the PayButton integer)
let test = ViewController()
I get an error saying
Missing Argument for parameter 'coder' in call
Any ideas ?
It is asking for the parameter 'coder', because you have it in the required init.
To use your code as it stands, you would need to initialise with:
let test = ViewController(coder: NSCoder)
There are several ways to get around this. The easiest would be to remove the required initialiser.
import UIKit
class ViewController: UIViewController {
var PayButton : Int = 0 {
didSet {
navigationItem.leftBarButtonItem = nil
}
}
}
and then implement with
let test = ViewController()
test.PayButton = 0
Because you have implemented the required initializer in ViewController class.
There are two solutions
Add a default initializer
init() {
super.init(nibName: nil, bundle:nil)
}
Remove the required initializer.
class ViewController: UIViewController {
var PayButton : Int {
didSet {
navigationItem.leftBarButtonItem = nil
}
}
init() {
self.PayButton = 0
super.init(nibName: nil, bundle:nil)
}
required init(coder aDecoder: NSCoder)
{
self.PayButton = 0
super.init(coder: aDecoder)
}
Try this:
This is the required initializer:
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
This is the super initializer:
override init(frame: CGRect) {
super.init(frame: frame)
}
This is your convenience initializer where you can pass the size the view you want to create
convenience init(view: UIView){
self.init(frame: view.frame)
}
This is your convenience initializer where the view is initialized with a value pre defined:
convenience init(){
self.init(frame: CGRectZero) //Put you predefined value here
}
class SecondViewController:UIViewController {
required init(coder aDecoder: NSCoder){
super.init(coder: aDecoder)
//Custom logic here
}
}
Quite a newbie question:
a view controller(SecondViewController), inherent from UIViewController needs a designated init function like above.
In this case, how should I call it, given I am not sure what the value for "coder" should be? I used to call the controller as: SecondViewController(), but it gives:
Missing argument for parameter 'coder' in call
I understand coder parameter has to be provided, but want to ask what its value comes from.
Thanks for the answers from #Chackle. Finally the solution I figured out is below.
What I want:
Inherit my SecondViewController from UIViewController
Simply to initialize any new SecondViewController as SecondViewController()
Solution:
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
init() {
super.init(nibName: nil, bundle: nil)
//Do whatever you want here
}
"required init(coder aDecoder: NSCoder)" is a must if you create a subclass of UIViewController. And so is "super.init(nibName: nil, bundle: nil)", because it is the way how UIViewController does initialization.