Could not load Nib in bundle - Wrong nibname in error - ios

I'm trying to push a ViewController's view initialized with a xib file. To do so, I'm calling the initializer of my controller which calls himself the initwithnibname:bundle: to load the correct xib file.The problem is that I'm getting the following error
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Could not load NIB in bundle: [...] with name 'BYZ-38-t0r-view-x4c-fw-L1g'
The nib name in the error does not match the provided nib name in the initializer call.
Here is my code :
ViewController declaration/initialization
let connexionViewController = ConnexionViewController()
self.view.addSubview(connexionViewController.view) // Exception thrown on this line
ViewController code
import Foundation
import UIKit
class ConnexionViewController: UIViewController {
#IBOutlet var validateButton: UIButton!
#IBOutlet var usernameTextField: UITextField!
required init(coder: NSCoder) {
super.init(coder: coder)
}
override init() {
super.init(nibName: "ConnexionViewController", bundle: nil)
}
override init(nibName nibNameOrNil: String!, bundle nibBundleOrNil: NSBundle!) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
The xib file's name match the string provided to the initializer and the xib is added to the build phases for the actual target.

ViewController * pvc = [[ViewController alloc]initWithNibName:NSStringFromClass([ViewController class]) bundle:nil];
I was loading nib this way to avoid strings.
This in turn started returning nib name as myproject.ViewController on adding swift compatibility to existing objC project for Mix and Match.
To sort this, either use the below kind of code or do string processig to crop the string till .
ViewController * pvc = [[ViewController alloc]initWithNibName:#"ViewController" bundle:nil];
Hope this helps someone in future.

It seems that the navigation controller wasn't fully initialized when the new controller was pushed. I changed the location of the call and it now works fine.

Related

iOS Swift: nil IBOutlets with custom ViewController

Evening I have a problem with outlets.
viewController1 make several instances of ViewController2, presenting them into a page container controlled with a pageControl.
The problem is that the view controller outlets in the ViewController2 are always nil.
Probably because the ViewController2 is instantiate via code.
How can I fix this?
here I create the different ViewController2
let page = OnboardPageViewController(onboard: onboard)
pages.append(page)
Here is the init code for ViewController2
//--------------------
//MARK: - Outlets
//--------------------
#IBOutlet var backgroundVideoView: BackgroundVideo!
#IBOutlet var backgroundUIImage: UIImageView!
#IBOutlet var titleLabel: UILabel!
#IBOutlet var descriptionLabel: UILabel!
//--------------------
//MARK: - Properties
//--------------------
let onboard: Onboard
//--------------------
//MARK: - View's Methods
//--------------------
override func viewDidLoad() {
super.viewDidLoad()
print("loaded: \(onboard.title)")
//FIXME: - need to find a way to link the outlets even if the controller is called via code
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
print("presenting: \(onboard.title)")
}
init(onboard: Onboard) {
self.onboard = onboard
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
You have to instantiate through UIStoryboard object, something like this:
if let viewController = UIStoryboard.init(name: "YourStoryBoardName", bundle: nil).instantiateViewController(withIdentifier: "YourViewController") {
// do something with it
}
You can cast it to your custom class in at the same time of the unwrap (with as? CustomClassViewCotroller)
Edit: static func to instantiate your view controller like init:
class YourViewController: UIViewController {
static func instantiate(withViewModel vm: ViewModel) -> YourViewController? {
if let viewController = UIStoryboard.init(name: "YourStoryboard", bundle: nil).instantiateViewController(withIdentifier: "YourViewController") as? YourViewController {
viewController.viewModel = vm
return viewController
}
return nil
}
var viewModel: ViewModel?
// ...
}
There will be more optional unwrapping in your code when using viewModel var but I think this is the correct way to create view controllers programmatically (in segues you have to set variables too, but that is another history).
Good luck mate.
Your outlets will be nil until the Storyboard file is loaded. So, right after init, they will be nil. You have to wait until viewDidLoad is called before accessing them.
If you need to init and set up things in the VC, you have to add other (non outlet) properties to hold that information. You can't just init and then access an outlet.
EDIT: In your code (added later), you aren't using a XIB or Storyboard. But, since you have outlets, I am assuming that you actually have one.
Don't use a custom init. Instead add properties and set them after you initialize using a Storyboard instantiate.

Weak reference getting zeroed out, but the object hasn't been deallocated

I have a weak instance variable holding onto a view controller in the UINavigationController.viewControllers stack.
My variable is automatically getting turned to nil, but the view controller hasn't been deallocated (since UINavigationController owns it).
Why is my weak reference getting zeroed?
class NavController: SuperNavigationController
{
weak var weakViewController: UIViewController?
required override init() {
let rootViewController: UIViewController
if (/* whatever */) {
rootViewController = ViewController1(/*whatever*/)
weakViewController = rootViewController
} else {
/* whatever */
}
/*** `weakViewController` is not `nil` at this point ***/
/***
*** This superclass function just does:
*** super.init(navBarClass:toolbarClass:)
*** viewControllers = [rootViewController]
***/
super.init(rootViewController: rootViewController)
}
// Without this, I get an "unimplemented initializer" exception
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
...
}
But as soon as I get to viewDidLoad, weakViewController is nil, even though self.viewControllers.first is still the exact same object I had when initializing.
Is there something weird about the way UINavigationController owns its viewControllers?
EDIT:
I managed to identify and fix the cause at a shallow level (see my answer below), but I'd still like to know why this happens. I'll happily accept and upvote an answer which can explain what's going on!
A weak reference says that if nothing else is pointing at this, I don't need it. So if the owner is the only one who has a ref and that is weak, arc is free to deallocate it.
https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html
Calling super.init() was causing the weak subclass instance variables I had set to be zeroed out.
I fixed this by waiting to set weakViewController until after the call to super.init()

Custom init for UIViewController in Swift with interface setup in storyboard

I'm having issue for writing custom init for subclass of UIViewController, basically I want to pass the dependency through the init method for viewController rather than setting property directly like viewControllerB.property = value
So I made a custom init for my viewController and call super designated init
init(meme: Meme?) {
self.meme = meme
super.init(nibName: nil, bundle: nil)
}
The view controller interface resides in storyboard, I've also make the interface for custom class to be my view controller. And Swift requires to call this init method even if you are not doing anything within this method. Otherwise the compiler will complain...
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
The problem is when I try to call my custom init with MyViewController(meme: meme) it doesn't init properties in my viewController at all...
I was trying to debug, I found in my viewController, init(coder aDecoder: NSCoder) get called first, then my custom init get called later. However these two init method return different self memory addresses.
I'm suspecting something wrong with the init for my viewController, and it will always return self with the init?(coder aDecoder: NSCoder), which, has no implementation.
Does anyone know how to make custom init for your viewController correctly ?
Note: my viewController's interface is set up in storyboard
here is my viewController code:
class MemeDetailVC : UIViewController {
var meme : Meme!
#IBOutlet weak var editedImage: UIImageView!
// TODO: incorrect init
init(meme: Meme?) {
self.meme = meme
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func viewDidLoad() {
/// setup nav title
title = "Detail Meme"
super.viewDidLoad()
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
editedImage = UIImageView(image: meme.editedImage)
}
}
As it was specified in one of the answers above you can not use both and custom init method and storyboard.
But you still can use a static method to instantiate ViewController from a storyboard and perform additional setup on it.
It will look like this:
class MemeDetailVC : UIViewController {
var meme : Meme!
static func makeMemeDetailVC(meme: Meme) -> MemeDetailVC {
let newViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "IdentifierOfYouViewController") as! MemeDetailVC
newViewController.meme = meme
return newViewController
}
}
Don't forget to specify IdentifierOfYouViewController as view controller identifier in your storyboard. You may also need to change the name of the storyboard in the code above.
You can't use a custom initializer when you initialize from a Storyboard, using init?(coder aDecoder: NSCoder) is how Apple designed the storyboard to initialize a controller. However, there are ways to send data to a UIViewController.
Your view controller's name has detail in it, so I suppose that you get there from a different controller. In this case you can use the prepareForSegue method to send data to the detail (This is Swift 3):
override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "identifier" {
if let controller = segue.destinationViewController as? MemeDetailVC {
controller.meme = "Meme"
}
}
}
I just used a property of type String instead of Meme for testing purposes. Also, make sure that you pass in the correct segue identifier ("identifier" was just a placeholder).
As #Caleb Kleveter has pointed out, we can't use a custom initializer while initialising from a Storyboard.
But, we can solve the problem by using factory/class method which instantiate view controller object from Storyboard and return view controller object.
I think this is a pretty cool way.
Note: This is not an exact answer to question rather a workaround to solve the problem.
Make class method, in MemeDetailVC class, as follows:
// Considering your view controller resides in Main.storyboard and it's identifier is set to "MemeDetailVC"
class func `init`(meme: Meme) -> MemeDetailVC? {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "MemeDetailVC") as? MemeDetailVC
vc?.meme = meme
return vc
}
Usage:
let memeDetailVC = MemeDetailVC.init(meme: Meme())
One way that I've done this is with a convenience initializer.
class MemeDetailVC : UIViewController {
convenience init(meme: Meme) {
self.init()
self.meme = meme
}
}
Then you initialize your MemeDetailVC with let memeDetailVC = MemeDetailVC(theMeme)
Apple's documentation on initializers is pretty good, but my personal favorite is the Ray Wenderlich: Initialization in Depth tutorial series which should give you plenty of explanation/examples on your various init options and the "proper" way to do things.
EDIT: While you can use a convenience initializer on custom view controllers, everyone is correct in stating that you cannot use custom initializers when initializing from the storyboard or through a storyboard segue.
If your interface is set up in the storyboard and you're creating the controller completely programmatically, then a convenience initializer is probably the easiest way to do what you're trying to do since you don't have to deal with the required init with the NSCoder (which I still don't really understand).
If you're getting your view controller via the storyboard though, then you will need to follow #Caleb Kleveter's answer and cast the view controller into your desired subclass then set the property manually.
There were originally a couple of answers, which were cow voted and deleted even though they were basically correct. The answer is, you can't.
When working from a storyboard definition your view controller instances are all archived. So, to init them it's required that init?(coder... be used. The coder is where all the settings / view information comes from.
So, in this case, it's not possible to also call some other init function with a custom parameter. It should either be set as a property when preparing the segue, or you could ditch segues and load the instances directly from the storyboard and configure them (basically a factory pattern using a storyboard).
In all cases you use the SDK required init function and pass additional parameters afterwards.
Swift 5
You can write custom initializer like this ->
class MyFooClass: UIViewController {
var foo: Foo?
init(with foo: Foo) {
self.foo = foo
super.init(nibName: nil, bundle: nil)
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.foo = nil
}
}
UIViewController class conform to NSCoding protocol which is defined as:
public protocol NSCoding {
public func encode(with aCoder: NSCoder)
public init?(coder aDecoder: NSCoder) // NS_DESIGNATED_INITIALIZER
}
So UIViewController has two designated initializer init?(coder aDecoder: NSCoder) and init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?).
Storyborad calls init?(coder aDecoder: NSCoder) directly to init UIViewController and UIView,There is no room for you to pass parameters.
One cumbersome workaround is to use an temporary cache:
class TempCache{
static let sharedInstance = TempCache()
var meme: Meme?
}
TempCache.sharedInstance.meme = meme // call this before init your ViewController
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder);
self.meme = TempCache.sharedInstance.meme
}
As of iOS 13 you can initialize the view controller that resides in a storyboard using:
instantiateViewController(identifier:creator:) method on the UIStoryboard instance.
tutorial:
https://sarunw.com/posts/better-dependency-injection-for-storyboards-in-ios13/
Although we can now do custom init for the default controllers in the storyboard using instantiateInitialViewController(creator:) and for segues including relationship and show.
This capability was added in Xcode 11 and the following is an excerpt from the Xcode 11 Release Notes:
A view controller method annotated with the new #IBSegueAction attribute can be used to create a segue’s destination view controller in code, using a custom initializer with any required values. This makes it possible to use view controllers with non-optional initialization requirements in storyboards. Create a connection from a segue to an #IBSegueAction method on its source view controller. On new OS versions that support Segue Actions, that method will be called and the value it returns will be the destinationViewController of the segue object passed to prepareForSegue:sender:. Multiple #IBSegueAction methods may be defined on a single source view controller, which can alleviate the need to check segue identifier strings in prepareForSegue:sender:. (47091566)
An IBSegueAction method takes up to three parameters: a coder, the sender, and the segue’s identifier. The first parameter is required, and the other parameters can be omitted from your method’s signature if desired. The NSCoder must be passed through to the destination view controller’s initializer, to ensure it’s customized with values configured in storyboard. The method returns a view controller that matches the destination controller type defined in the storyboard, or nil to cause a destination controller to be initialized with the standard init(coder:) method. If you know you don’t need to return nil, the return type can be non-optional.
In Swift, add the #IBSegueAction attribute:
#IBSegueAction
func makeDogController(coder: NSCoder, sender: Any?, segueIdentifier: String?) -> ViewController? {
PetController(
coder: coder,
petName: self.selectedPetName, type: .dog
)
}
In Objective-C, add IBSegueAction in front of the return type:
- (IBSegueAction ViewController *)makeDogController:(NSCoder *)coder
sender:(id)sender
segueIdentifier:(NSString *)segueIdentifier
{
return [PetController initWithCoder:coder
petName:self.selectedPetName
type:#"dog"];
}
In XCode 11/iOS13, you can use
instantiateViewController(identifier:creator:)
also without segues:
let vc = UIStoryboard(name: "StoryBoardName", bundle: nil).instantiateViewController(identifier: "YourViewControllerIdentifier", creator: {
(coder) -> YourViewController? in
return YourViewController(coder: coder, customParameter: "whatever")
})
present(vc, animated: true, completion: nil)
Disclaimer: I do not advocate for this and have not thoroughly tested its resilience, but it is a potential solution I discovered while playing around.
Technically, custom initialization can be achieved while preserving the storyboard-configured interface by initializing the view controller twice: the first time via your custom init, and the second time inside loadView() where you take the view from storyboard.
final class CustomViewController: UIViewController {
#IBOutlet private weak var label: UILabel!
#IBOutlet private weak var textField: UITextField!
private let foo: Foo!
init(someParameter: Foo) {
self.foo = someParameter
super.init(nibName: nil, bundle: nil)
}
override func loadView() {
//Only proceed if we are not the storyboard instance
guard self.nibName == nil else { return super.loadView() }
//Initialize from storyboard
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let storyboardInstance = storyboard.instantiateViewController(withIdentifier: "CustomVC") as! CustomViewController
//Remove view from storyboard instance before assigning to us
let storyboardView = storyboardInstance.view
storyboardInstance.view.removeFromSuperview()
storyboardInstance.view = nil
self.view = storyboardView
//Receive outlet references from storyboard instance
self.label = storyboardInstance.label
self.textField = storyboardInstance.textField
}
required init?(coder: NSCoder) {
//Must set all properties intended for custom init to nil here (or make them `var`s)
self.foo = nil
//Storyboard initialization requires the super implementation
super.init(coder: coder)
}
}
Now elsewhere in your app you can call your custom initializer like CustomViewController(someParameter: foo) and still receive the view configuration from storyboard.
I don't consider this a great solution for several reasons:
Object initialization is duplicated, including any pre-init properties
Parameters passed to the custom init must be stored as optional properties
Adds boilerplate which must be maintained as outlets/properties are changed
Perhaps you can accept these tradeoffs, but use at your own risk.
Correct flow is, call the designated initializer which in this case is the init with nibName,
init(tap: UITapGestureRecognizer)
{
// Initialise the variables here
// Call the designated init of ViewController
super.init(nibName: nil, bundle: nil)
// Call your Viewcontroller custom methods here
}
This solution shows a way to have custom initializers but still be able to use Storyboard WITHOUT using the self.init(nib: nil, bundle: nil) function.
To make it possible to use that, let’s first tweak our MemeDetailsVC to also accept an NSCoder instance as part of its custom initializer, and to then delegate that initializer to super.init(coder:), rather than its nibName equivalent:
class MemeDetailVC : UIViewController {
var meme : Meme!
#IBOutlet weak var editedImage: UIImageView!
init?(meme: Meme, coder: NSCoder) {
self.meme = meme
super.init(coder: aDecoder)
}
#available(*, unavailable, renamed: "init(product:coder:)")
required init?(coder: NSCoder) {
fatalError("Invalid way of decoding this class")
}
override func viewDidLoad() {
title = "Detail Meme"
super.viewDidLoad()
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
editedImage = UIImageView(image: meme.editedImage)
}
}
And then, you instantiate & show the View Controller this way:
guard let viewController = storyboard?.instantiateViewController(
identifier: "MemeDetailVC",
creator: { coder in
MemeDetailVC(meme: meme, coder: coder)
}
) else {
fatalError("Failed to create Product Details VC")
}
//Then you do what you want with the view controller.
present(viewController, sender: self)
// View controller is in Main.storyboard and it has identifier set
Class B
class func customInit(carType:String) -> BViewController
{
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let objClassB = storyboard.instantiateViewController(withIdentifier: "BViewController") as? BViewController
print(carType)
return objClassB!
}
Class A
let objB = customInit(carType:"Any String")
navigationController?.pushViewController(objB,animated: true)

How to connect to custom UIViewContoller with xib on Storybord

I created MyViewController.swift and MyViewController.xib files.
Then I put label on xib file.
I droped to UIViewController on the Storyboad.
Then I changed it's 'Custom Class' UIViewController to MyViewController.
But it does not appear the label which on xib.
How can I show the MyViewController with xib?
Should I write some code in MyViewController to relate with xib?
I just want to reuse components with xib in my app.
Your MyViewController.swift file should at least contain the declaration for the class as this :
import UIKit
class MyViewController: UIViewController {
}
inside your class declaration you will also need the following if you want to load your view from the xib :
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
// Custom initialization
}
Finally in order to access your label you can create an IBOutlet in your class declaration like this:
#IBOutlet weak var myLabel: UILabel?
Then you will be able to connect it using Interface Builder and access it in your code

Double Initialization Of UIViewController's Subclass Member in Swift

I wanted to make a custom container view controller and added some members to the subclass of UIViewController. When I try to init it from the app delegate by using following code:
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
self.window?.rootViewController = CustomContainerViewController()
self.window?.makeKeyAndVisible()
all the members in CustomContainerViewController were initialized twice.
Here is CustomContainerViewController's code:
class CustomContainerViewController: UIViewController {
let tabBar = CustomTabBar()
override init() {
super.init()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
super.init(nibName: nibNameOrNil?, bundle: nibBundleOrNil?)
}
}
Here is CustomTabBar's code:
class CustomTabBar: UIView {
override init(){
println("init")
super.init()
}
override init(frame: CGRect) {
println("initWithFrame:")
super.init(frame: frame)
}
required init(coder aDecoder: NSCoder) {
println("initWithCoder:")
super.init(coder: aDecoder)
}
}
Whenever you init the CustomContainerViewController from the app delegate by using the code previously mentioned, is always prints "init", "initWithFrame" twice.
Incorrect designated initializer used.
UIViewController has only one designated initializer init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?).
As its comment says
The designated initializer. If you subclass UIViewController, you must call the super implementation of this method, even if you aren't using a NIB. (As a convenience, the default init method will do this for you, and specify nil for both of this methods arguments.) In the specified NIB, the File's Owner proxy should have its class set to your view controller subclass, with the view outlet connected to the main view. If you invoke this method with a nil nib name, then this class' -loadView method will attempt to load a NIB whose name is the same as your view controller's class. If no such NIB in fact exists then you must either call -setView: before -view is invoked, or override the -loadView method to set up your views programatically.
So whenever you override the init() method of UIViewController, once you call super, UIViewController's implementation would call init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) on behalf of you. So all the members in your UIViewController's subclass were initialized twice.
To solve this problem, use following code in the app delegate
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
self.window?.rootViewController = CustomContainerViewController(nibName: nil, bundle: nil)
self.window?.makeKeyAndVisible()
And never call the init() method of UIViewController or override this method in subclasses.

Resources