- Swift 3
- Xcode Version 8.0 beta (8S128d)
Click here if you prefer to read this article on Github. I'm not a huge fan of how SquareSpace renders the code snippets in MarkDown, so feel free to read this on Github.
Lets make a re-usable custom view using XIB's.
Instructions
XIB File
- First, lets create the XIB file. File --> New will present us with this window:
- Select User Interface under the iOS heading on the left and his Next. I named the file
WeatherView
. After doing so, you should see theWeatherView.xib
file in the navigator pane on the left. - Select the
WeatherView.xib
file, you should be presented with the following:
- In the Utilities Menu, which you open up by selecting the right-most button in the top right of Xcode, select the Attributes inspector which will bring up various options.
- Underneath the Simulated Metrics, we want to change the size to freeform:
- Now going to the Size Inspector (icon that looks like a ruler next to the Attributes Inspector), I want to set the Height & Width of this View on screen. I'm setting the Width to equal 600 pts, and the Height to equal 300 pts like so:
I want to stick to these proportions even though the
UIView
object that will have its Custom Class set to this particularWeatherView
object might not be 600w x 300h. When I later create thisUIView
object in theMain.storyboard
file, I will want to stick to these proportions (or make the constraints equal to 600w x 300h to match this). This might not make too much sense now, come back to it when this is all complete.Next, you design your view. I've already done the work here (I won't step through how I did it as it's not the point of this blog). You can check out the project here.
View Class
- Lets create a new Cocoa Touch Class like so:
- Make sure it's a subclass of
UIView
. I'm naming this viewWeatherView
.
- The contents of your
WeatherView.swift
file should look like this:
- Next, I added the following methods:
import UIKit
final class WeatherView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
//TODO: Do some stuff
}
}
- To take advantage of Static Dispatch (in that this WeatherView class will NOT be sublcalssed, I marked it final).
init(frame:)
is inherited fromUIView
, WeatherView is subclassed fromUIView
. Here, we're overriding its implementation first calling onsuper
's implementation passing in theframe
we receive in as an argument.init?(coder:)
is inherited fromUIView
, but it was marked as required inUIView
's implemntation, because of that.. we are required to mark it as required as well. Similar toinit(frame:)
we are first calling onsuper
's implementation passing in theNSCoder
object we receive.- You'll notice that both methods just described both call on
commonInit()
which is marked as a private function. Marking it private only allows for people within this particular .swift file (WeatherView.swift
) to call on this method. This protects us from having this function get called from any other file.
Hopefully, I have your full attention. If not (don't just keep reading). Go back and re-read any sections you glossed over. In making sure you understand this material, it's important that you understand every step of the way.
Lets go back to the XIB file for a moment.
Back to the XIB file
- In the WeatherView.xib file, I'm selecting the File's Owner in the left pane like so:
- When File's Owner is selected, take a look in the top right underneath the selected Identity Inspector. Under Custom Class we can open the drop down menu under the option Class or type something in that box. Lets set it to our
WeatherView
class file.
- Bring up the assistant editor now (I like mine split where I have the code portion at the bottom and the view elements on top):
- Option drag from the
View
seen here in the following screenshot into your code as an Outlet and name itcontentView
.
- After doing that, setup all of the other
UILabel
's andUIImageView
like so:
- Now we can access these view elements in code! Lets go back to the
WeatherView.swift
file now.
Back to the View Class
- Lets go back to the
WeatherView.swift
and implement thecommonInit()
method that is marked with a TODO.
private func commonInit() {
// 1. Load the nib
Bundle.main().loadNibNamed("WeatherView", owner: self, options: [:])
// 2. Add the 'contentView' to self
addSubview(contentView)
// 3. Constraints to be done in code contentView.translatesAutoresizingMaskIntoConstraints = false
// 4. Setup constraints
contentView.topAnchor.constraint(equalTo: topAnchor).isActive = true
contentView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
contentView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
contentView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
}
- I'm going to touch on 2. for a minute. The .xib file itself contains a
UIView
object (which we've been adding otherUIView
objects to.. the multiple labels and the imageView). The constraints on those various view elements were made to what is now namedcontentView
which is theUIView
object in the .xib file. But.. here we are inside ofWeatherView
class, which is a sublcass ofUIView
which means it IS aUIView
. Ok.. so we have two separateUIView
's here, but we know that theWeatherView
acts as the owner of theUIView
now namedcontentView
in theWeatherView.xib
file.
Stick with me.
Whenever an instance of our
WeatherView
view object is created (whether that be in code or Interface Builder),commonInit()
will be called on that instance. When that occurs, we load into memory the WeatherView.xib file which hooks up all our outlets as theWeatherView.swift
file is its owner. Then, we addcontentView
as a subview toself
,self
being the newly greated instance ofWeatherView
(again.. that will occur either in code somewhere or in Interface Builder which we will do later). The subview we're adding toself
here is thecontentView
which contains ALL of those view elements we created. But.. how will thatcontentView
constrain itself to the view it's now be placed inside of. It doesn't know how to do that so we set thetranslatesAutoresizingMaskIntoConstraints
property on thecontentView
tofalse
which allows us to programtically create constraints on it. So we do that. We then set the constraints of thecontentView
to equalself'
s top, bottom, left, and right anchors. That constrains thecontentView
to fit perfectly intoself
(self
again being the instance ofWeatherView
).Lets put this in action.
Storyboad
- To demonstrate how this will work, go to the Main.Storyboard file and drag out a View onto the canvas, like so:
- Here, I'm setting up the constraints of the view we just dragged in like so (making sure to adhere to that 2:1 proportion of constraints we created in the .xib file (600w, 300h) which looking back was a little aggressive. I should have kept the ratio but made it slightly smaller.
- Now select that View object that we just added constraints to and open the Identity Inspector like so:
- Set the Class there underneath the Custom Class heading to
WeatherView
That's it.
Build & Run: