Swift, creating a Generic UIViewController - ios

I am trying to create a Class like this :
class MyClass<T:UIView>: UIViewController{
override init()
{
super.init(nibName: nil, bundle: nil);
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func loadView() {
self.view = T();
println("loadView")
}
override func viewDidLoad() {
super.viewDidLoad();
println("viewDidLoad")
}
}
When I want to use my class like that :
self.navigationController?.pushViewController(MyClass<UIView>(), animated: true)
The methods viewDidLoad and loadView are never called !!!
Do you know why and if there is some way of doing what I want.
Thanks in advance.

As mentioned in OP comments, Generic class cannot be properly represented in Objective-C.
The workaround would be using the class as a property. something like this:
class MyClass: UIViewController{
let viewCls:UIView.Type
init(viewCls:UIView.Type = UIView.self) {
self.viewCls = viewCls
super.init(nibName: nil, bundle: nil);
}
required init(coder aDecoder: NSCoder) {
self.viewCls = UIView.self
super.init(coder: aDecoder)
}
override func loadView() {
self.view = viewCls();
println("loadView")
}
override func viewDidLoad() {
super.viewDidLoad();
println("viewDidLoad")
}
}
// and then
self.navigationController?.pushViewController(MyClass(viewCls: UIView.self), animated: true)

Related

Has no accessible initializers (UITableView) [duplicate]

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)
}

Reusable code for classes

Several classes in my app do the same thing and have the same instance variables:
// one of the many classes I have
// they all load nibs and update the frames of their views
class HelpView: UIView {
#IBOutlet var view: UIView!
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
NSBundle.mainBundle().loadNibNamed("HelpView", owner: self, options: nil)
self.addSubview(self.view)
self.view.frame = self.bounds
}
}
I want to avoid duplicated code, so I thought about using a superclass so that all the classes inherit from it.
// my new superclass all classes will inherit from
class ReusableView: UIView {
#IBOutlet var view: UIView! // all subclasses have different views
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
// every class has a different nib name
NSBundle.mainBundle().loadNibNamed("Nib name goes here", owner: self, options: nil)
self.addSubview(self.view)
self.view.frame = self.bounds
}
}
The problem is that view is nil until the nib is loaded, so it's apparently not possible to call that superclass' method because you're passing a nil object. How can I handle this?
This will work:
class ReusableView: UIView {
func getHelperView() -> UIView! {
preconditionFailure("This method must be overridden")
}
func getNibName() -> String {
preconditionFailure("This method must be overridden")
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
// every class has a different nib name
NSBundle.mainBundle().loadNibNamed(self.getNibName(), owner: self, options: nil)
self.addSubview(self.getHelperView())
self.getHelperView().frame = self.bounds
}
}
class HelpView: ReusableView {
#IBOutlet var view: UIView!
override func getHelperView() -> UIView! {
return view;
}
override func getNibName() -> String {
return "NibName";
}
}

Push ViewController with coder aDecoder: NSCoder

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()

Trouble creating an instance of ViewController

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
}

IBOutlet properties nil after custom view loaded from xib

Something strange going on with IBOutlets.
In code I've try to access to this properties, but they are nil. Code:
class CustomKeyboard: UIView {
#IBOutlet var aButt: UIButton!
#IBOutlet var oButt: UIButton!
class func keyboard() -> UIView {
let nib = UINib(nibName: "CustomKeyboard", bundle: nil)
return nib.instantiateWithOwner(self, options: nil).first as UIView
}
override init() {
super.init()
commonInit()
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
// MARK: - Private
private func commonInit() {
println(aButt)
// aButt is nil
aButt = self.viewWithTag(1) as UIButton
println(aButt)
// aButt is not nil
}
}
That's expected, because the IBOutlet(s) are not assigned by the time the initializer is called.
Instead of calling commonInit() in init(coder:), do that in an override of awakeFromNib as follows:
// ...
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func awakeFromNib() {
super.awakeFromNib()
commonInit()
}
// ...
Assuming you tried the standard troubleshooting steps for connecting IBOutlets, try this:
Apparently, you need to disable awake from nib in certain runtime cases.
override func awakeAfter(using aDecoder: NSCoder) -> Any? {
guard subviews.isEmpty else { return self }
return Bundle.main.loadNibNamed("MainNavbar", owner: nil, options: nil)?.first
}
Your nib may not be connected. My solution is quite simple. Somewhere in your project (I create a class called UIViewExtension.swift), add an extension of UIView with this handy connectNibUI method.
extension UIView {
func connectNibUI() {
let nib = UINib(nibName: String(describing: type(of: self)), bundle: nil).instantiate(withOwner: self, options: nil)
let nibView = nib.first as! UIView
nibView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(nibView)
//I am using SnapKit cocoapod for this method, to update constraints. You can use NSLayoutConstraints if you prefer.
nibView.snp.makeConstraints { (make) -> Void in
make.edges.equalTo(self)
}
}
}
Now you can call this method on any view, in your init method, do this:
override init(frame: CGRect) {
super.init(frame: frame)
connectNibUI()
}
Building on #ScottyBlades, I made this subclass:
class UIViewXib: UIView {
// I'm finding this necessary when I name a Xib-based UIView in IB. Otherwise, the IBOutlets are not loaded in awakeFromNib.
override func awakeAfter(using aDecoder: NSCoder) -> Any? {
guard subviews.isEmpty else { return self }
return Bundle.main.loadNibNamed(typeName(self), owner: nil, options: nil)?.first
}
}
func typeName(_ some: Any) -> String {
return (some is Any.Type) ? "\(some)" : "\(type(of: some))"
}
There is possibility that you not mentioned the FileOwner for xib.
Mention its class in File owner not in views Identity Inspector .
And how did you initiate your view from the controlller? Like this:
var view = CustomKeyboard.keyboard()
self.view.addSubview(view)

Resources