Ask Sawal

Discussion Forum
Notification Icon1
Write Answer Icon
Add Question Icon

What is nibname in swift?

5 Answer(s) Available
Answer # 1 #

The name of the view controller's nib file, if one was specified.

[5]
Edit
Query
Report
Ruedi Wasserman
Aeronautical Engineer
Answer # 2 #

If you're not using storyboards and segues, then you are in charge of instantiating the view controllers of your project. While this requires a bit of additional work from your part, it has a few benefits. Because you are responsible for the initialization of each view controller, you can use initializer injection to inject the view controller's dependencies.

The example we explore in this episode is similar to the example of the previous episode. Download the starter project of this episode if you'd like to follow along. Notice that the main storyboard is missing. Each view controller of the project has a XIB file that defines its user interface.

The approach I recommend involves two steps. First, we define a designated initializer that accepts the dependencies of the view controller. Second, we set the view controller's dependencies in the initializer and invoke the designated initializer of its superclass.

Let's apply this technique to the RootViewController class. Before we implement the initializer, we declare the notes property as a constant and don't assign it an initial value. This implies that the value of the notes property needs to be set during initialization and that it cannot be modified once it's set. That is one of the advantages of using initializer injection.

The compiler throws an error. We can fix the error by defining a designated initializer for the RootViewController class that accepts an array of Note instances.

We assign the value of the notes parameter to the notes property. Once every stored property has a valid value, we can invoke the designated initializer of the superclass, init(nibName:bundle:).

The compiler doesn't agree with the changes we made. It throws another error. It notifies us that we need to implement a required initializer of the UIViewController class, init(coder:).

If we click the error, Xcode offers a solution. Click the "Fix" button to see what solution Xcode has in store for us.

This is what the implementation of the init(coder:) initializer looks like.

Because we defined a designated initializer, we are required to override the required initializers of the superclass, that is, the UIViewController class.

You may be wondering why Xcode adds an implementation of the init(coder:) method in which a fatal error is thrown. That sounds like asking for trouble.

To understand why Xcode offers this solution, we need to modify the implementation. Let's remove the fatal error and invoke the implementation of the superclass. This introduces another problem, though.

While this may appear to be a viable solution, it isn't. The compiler notifies us that the notes property doesn't have a valid value before we invoke the init(coder:) method of the superclass.

How do we set the value of the notes property? We can't pass a value for the notes property to the initializer because we need to implement the designated initializer as it's defined by the superclass. The answer is "We don't."

We won't be using the init(coder:) method and, if we want to use initializer injection, the only option we have is throwing a fatal error in the initializer. You may have seen this implementation before and wondered why a fatal error is thrown. This is the reason.

But it's the message of the fatalError(_:file:line:) function that confuses many developers. A better message would be "You should not instantiate this view controller by invoking init(coder:)." That makes more sense and explains why a fatal error is thrown.

Because the fatalError(_:file:line:) function doesn't return control to the initializer, the compiler is happy with this implementation. But remember that, if the init(coder:) method is invoked, the application is terminated. That's why it should never be invoked.

It's time to put the designated initializer to use. Open AppDelegate.swift and create an instance of the RootViewController class by invoking init(with:), passing in the array of note instances.

Because we pass the array of Note instances as an argument of the initializer, we don't need to configure the root view controller. We set the root view controller as the root view controller of the application's window.

Notice that we explicitly initialize the window of the application in application(_:didFinishLaunchingWithOptions:). Because we're not using a storyboard, we are responsible for initializing and configuring the window of the application.

Even though I like the conveniences storyboards offer, I prefer this implementation in the context of dependency injection. Why is that? As I mentioned earlier, we are responsible for instantiating the view controller. That means we can implement a custom initializer and pass the dependencies of the view controller during initialization. The result is that the dependencies can be declared as constants.

But there's another subtle benefit. The dependencies are defined by the initializer. The interface of the RootViewController class defines the dependencies of the view controller. Any developer inspecting the custom initializer of the RootViewController class understands that the class needs an array of Note instances to do its work.

The changes we need to make to the DetailViewController class are similar. We start by declaring the note property as a constant property. It no longer needs to be an optional, which means we don't need to rely on optional chaining in the viewDidLoad() method.

To instantiate the detail view controller, we need to invoke init(nibName:bundle:), passing in the name of the XIB file and the bundle in which the XIB file lives. We need to set the value of the note property before we do this. Remember that every stored property needs to have a valid value before the end of the initialization and before we invoke the initializer of the superclass.

Because we implemented a custom initializer, we are required to override the required initializer of the UIViewController class. We need to implement the init(coder:) method. Its implementation is identical to that of the RootViewController class.

Because we're not using segues to navigate the application, we don't need to implement the prepare(for:sender:) method in the RootViewController class. We need to present the detail view controller manually when the user taps a row in the table view. We do this in the tableView(_:didSelectRowAt:) method of the UITableViewDelegate protocol.

We safely unwrap the value that's returned by indexPathForSelectedRow and use the index path to fetch the Note instance that corresponds with the row the user tapped. We initialize an instance of the DetailViewController class and pass in the Note instance as an argument. We present the detail view controller modally by invoking present(_:animated:completion:).

Build and run the application to see if everything works as expected.

There's another benefit of using initializer injection over property injection that I'd like to point out. Because we pass the dependencies of the RootViewController and DetailViewController classes during initialization, we can declare the dependencies of these classes private.

Why is this important? A key benefit Swift has over Objective-C is powerful access control. It pays to embrace access control in Swift. Let me show you what I mean.

Open RootViewController.swift and choose Jump to Generated Interface from Xcode's Navigate menu. Xcode shows us the interface of the RootViewController class. This is similar to a header file in Objective-C.

Notice that there's no trace of the notes property since we declared it private. A developer new to the project sees that the initializer accepts an array of Note instances, but they don't know anything about the internals of the RootViewController class.

And that's how it should be. Other objects don't need to know how the RootViewController class manages the array of Note instances it's given during initialization. Ignorance is bliss and this very often applies to software development.

[2]
Edit
Query
Report
Yedoye LaZar
Website Content Writer
Answer # 3 #

For example, let’s say that an app that we’re working on contains a ContactListViewController that requires a ContactList model object to function. Ideally, we wouldn’t want to be able to create an instance of that view controller without its required model, and to make that happen, we might implement a custom initializer that looks like this:

However, if we now try to compile our app after making the above change, we’ll get a build error — saying that subclasses of UIViewController always need to provide an implementation of the init(coder:) initializer.

That’s because UIKit uses NSCoder to decode objects like views and view controllers when storyboards are used, and although Xcode will at this point suggest that we simply add that required initializer with a fatalError as its body — there are often other approaches that we can take, especially if we still want those objects to remain storyboard-compatible.

One way that can be used if the dependencies that we’re looking to inject can be retrieved statically (such as when using the singleton pattern) is to turn init(coder:) into a convenience initializer, and then have it call our custom one using those static instances as arguments — like this:

Another option that also lets our ContactListViewController remain compatible with storyboards is to use another flavor of dependency injection, rather than a custom initializer. For example, here we’re using property-based dependency injection to have our view controller use ContactList.shared by default, while also enabling that dependency to be overridden by assigning a new value to its property (optionally only within debug builds):

If storyboard compatibility is not an issue, we could of course go with what Xcode originally suggested, and implement the required NSCoder-based initializer with a call to fatalError — but if we end up doing that, let’s also mark that initializer as unavailable to make it impossible for us to accidentally call it within our own code. Here we’re doing just that within a view controller that requires a Product model to be passed into its initializer:

In iOS 13 and above, there are also a few other options that we could explore that both enable us to keep using storyboards, while also making it possible for our view controllers to use custom initializers.

One such option is the instantiateViewController method that’s available on UIStoryboard (the runtime API for Xcode’s storyboards). Using that API, we can programmatically create any view controller that our storyboard contains, which in turn enables us to take complete control over its initialization.

To make it possible to use that API, let’s first tweak our ProductDetailsViewController from before 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:

Note how we’ve also added a renamed: parameter to our above @available declaration, to again make our intentions crystal clear, both to any other developers that we might be working with, and to our future selves.

Then, here’s how we could use the above new implementation to create an instance of ProductDetailsViewController, for example within another view controller that displays a list of products:

Note that we’re retrieving the storyboard reference for our ProductDetailsViewController using its identifier, which can be assigned using the Storyboard ID text field within the Identity Inspector when a view controller was selected in Xcode’s storyboard editor.

Another option that enables us to use storyboard segues, instead of creating our view controllers manually, is to use the @IBSegueAction function attribute. Marking any function that accepts a single NSCoder argument with that attribute lets us delegate the creation of a given segue’s destination view controller to that function — for example like this:

To connect a custom @IBSegueAction function to a storyboard segue, select the segue within Xcode’s storyboard editor, open the Connections Inspector, and drag the instantiation action to the method that you’d like to connect it to.

[1]
Edit
Query
Report
Slavko Fatu
Bridge Inspector
Answer # 4 #

A common task when developing iOS apps is to register custom cell subclasses for both UITableView and UICollectionView. Well, that is if you don’t use Storyboards, of course.

Both UITableView and UICollectionView offer a similar API to register custom cell classes:

A widely accepted solution to handle cell registration and dequeuing is to declare a constant for the reuse identifier:

Let’s try to generalize this code and make it simpler and safe.

First of all, it would be nice to get away with declaring a constant for every reuse identifier in our app. We can just use the name of the custom cell class as a default reuse identifier. We can create a protocol for Reusable Views and provide a default implementation constrained to UIView subclasses.

By making UICollectionViewCell conform to the ReusableView protocol, we get a unique reuse identifier per cell subclass.

Next, we can get rid of the hard-coded string we are using to load the Nib.

Let’s create a protocol for Nib Loadable Views and provide a default implementation using protocol extensions.

By making our BookCell class conform to the NibLoadableView protocol we now have a safer way to get the Nib name.

If you use a different name for the XIB file than the one provided by Xcode, you can always override the default implementation of the nibName property.

With these two protocols in place, we can use Swift Generics and extend UICollectionView to simplify cell registration and dequeuing.

Notice that we created two versions of the register method, one for cell subclasses implementing just ReusableView and another one for cell subclasses implementing both ReusableView and NibLoadableView. This nicely decouples the view controller from the specific cell registration method.

Another nice detail is that the dequeueReusableCell method doesn’t need to take any reuse identifier and uses the cell subclass type for the return value.

Now the cell registration and dequeuing code looks much better :).

[1]
Edit
Query
Report
Monet Nicholas
Experimental Psychologist
Answer # 5 #
  • Create a XIB File.
  • Design Your View in Interface Builder.
  • Create a Custom UIView File.
  • Override Both UIView Initializers.
  • Add UIView As XIB File's Owner.
  • Add Entire View as an IB Outlet in UIView File.
  • Load XIB in Common Initializer.
  • Use Your View.
[0]
Edit
Query
Report
Shimo oqpwcqnm
TUBE BENDER HAND II