Subclassing UIView in Swift
Provide the functionality that a standard UIView just can’t
Here are Apple’s Subclassing notes from their documentation:
UIViewclass is a key subclassing point for visual content that also requires user interactions. Although there are many good reasons to subclass
UIView, it is recommended that you do so only when the basic
UIViewclass or the standard system views do not provide the capabilities that you need. Subclassing requires more work on your part to implement the view and to tune its performance.
Which is where many stackoverflow posts will point you to. This doesn’t necessarily help that many users, in the way that this guide will with a practical guide as to actually subclass a UIView with examples.
This guide to the rescue!
Difficulty: Beginner | Easy | Normal | Challenging
- Some knowledge of OO would be useful (although I’ll take you through the basics here), and I will use Playgrounds (and include this in full at the end) but if you want to see the result of your work press one of the following symbols (the eye or the rectangle) to see it!
Class: An object that defines properties and methods in common
Subclass: A class that inherits from a superclass
Superclass: The class that is being inherited from
There are a host of reasons why you might choose to subclass rather than extend, but one that is as good as any is if you are designing using a Storyboard you might love using @IBDesignable to make the storyboard work as part of the design process (It’s awesome, do it!).
In any case, Apple recommend you only subclass when the functionality of a standard UIView doesn’t give you the capabilities that you require.
Responsibilities of a UIView
UIViews have a large number of responsibilities. An (incomplete) list of these is:
- Drawing (using UIKit or Core Graphics) and animation
- The animation of a UIView
- The management of layout and subviews (which can number zero or more), adjusting the size and position of subviews
- Use Auto Layout to define the rules for resizing and repositioning the views in the view hierarchy
- Event handling, and responding to touches and other events as a subclass of UIResponder or the installation of gesture recognizers to handle common gestures
It should be noted that the constraints for any specific view instance should be declared in the parent, that is they should be declared in the parent rather than the subview.
Creating a UIView
A red UIView
A red UIView can easily be created using
UIView(frame:), and then later changing the background color by using the background instance property. In the playground this looks like the following:
This is fine, but how would we create a
UIView subclass that is always red?
A red UIView subclass
It seems like a reasonable amount of code for something so relatively trivial. However, this particular subclass can be initialised from the storyboard or code.
“Setting this value to
true causes subviews to be clipped to the bounds of the receiver. If set to
false, subviews whose frames extend beyond the visible bounds of the receiver are not clipped. The default value is
Frames and bounds
Each view is defined by both its frame and bounds properties. The frame of any iOS view is the expression of the origin and dimensions of the view in the coordinate system of the superview, while the bounds expresses the dimensions of the view in the coordinate system of that view. This means that you can never guarantee that the frame and bounds of a UIView are the same (and there are examples where they are not).
This leads to potential issues, as the initilizers
init(frame: CGRect) and
init?(coder: NSCoder) are called, and then the frame may later be changed through a resize. Therefore an init method is simply not he best place to perform layout changes on your UIView.
Setting the layout with constraints (Auto Layout)
If you use Auto Layout you can set constraints either in Interface builder or programatically. If setting them programatically you can add constraints in the initializer (here in a function called from the initializer, so in practical terms inside the initializer).
Since we are using auto layout these constraints persist even through a resize or orientation change. Nice.
Setting the layout by overriding layoutSubviews
If you have complex setup that means constraint-based layout is not suitable for you, you can override
layoutSubviews() and set the frame there.
The view drawing cycle
The system works through the Run Loop, then Constraints before the Deferred Layout Pass, where views are updated if they are marked as dirty. The
func draw(_:) method is at the heart of this, and is called by the system when it is first displayed and when a change is made to the visible part of the view.
Drawing a simple shape
We can override
func draw(:) and we are given a context to draw into. Thanks Swift!
why didn't you use
bP.close()? In this case it’s not required, but I can see why some would want it there.
Equally we could use
override func draw(_ rect: CGRect, for formatter: UIViewPrintFormatter) however I don’t want to draw differently when printing. Ok?
setNeedsDisplay() and setNeedsDisplay(_:)
setNeedsDisplay() forces a redraw of a particular view. Because you should never call
draw(_ rect: CGRect) directly you can think of
setNeedsDisplay() as a method of asking UIKit for a redraw. Another way of thinking of this is that calling
setNeedsDisplay() marks a view as dirty, that is on the next update cycle the view will be redrawn through a deferred layout pass.
Since the pass is deferred, it will occur during the next update cycle during which
func draw(_:) will be called on all such views.
This seems a little bewildering to the beginner, since most UI components take care of this for us. However, there can be a property that is not directly tied to a UI component and we need to inform Swift of what’s what with our call.
and the following can be called with the following (setting the shapePoints property for the number of points for the polygon)
let shapeView = ShapeView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))shapeView.shapePoints = 1
Triggering layout refreshes
When a view changes the layout changes, and this requires a recalculation by autolayout.
Usually the layout is automatically updated, that is when a view is resized, added to the view hierarchy, constraints are updated, the device is rotated or the user scrolls.
There are situations where we need to force the layout of a particular view instance to be recalculated. and these are covered by the next couple of methods.
Asks for a layout update on a particular view. This will take place on the next update cycle, which due to the quick refresh of iOS device screens should be fast enough so the user does not experience any lag.
This is similar to
layoutIfNeeded() that forces an immediate update of the layout. One particular instance where this is used is for the animation of constraints, which due to animation would need to be immediately.
layoutSubview method is called when the view resizes — including the very first time it is set.
This meant that it would be a suitable place to set corner radius of a UIView, or similar.
Rather than being called directly, this is triggered by the system when the view is first laid out (that is, on first draw) as well as on rotation. It can be requested with
setNeedsLayout() for the next drawing update or forced immediately through
If you are working in a view controller viewDidLayoutSubviews() is your similar method here.
Intrinsic content size
It might be nice for any subclass to declare an
intrinsicContentSize which would be a natural size for the subclass. For reference, the intrinsic size of a UILabel would be the size of the label with the text, giving auto layout something to work from.
UIView is a common task for Swift developers, and you might as well get used to the methods that are relevant to subclassing a
The examples above should help the reader understand the way this can be done, and to extend your knowledge why not Read Apple’s documentation for UIView?
Any questions please do get in touch with me on Twitter.