Every time you look at your iPhone you see UIViews. UIViews everywhere!
In this article we are going to see what a UIView can do and how you can use it to make your apps.
Views 101
The UIView class defines a rectangular area on the screen on which it shows content. It handles the rendering of any content in its area and also any interactions with that content – touches and gestures.
The most basic thing a view can do is fill its area with a background color.
Download this starter project and open it on the Main.storyboard
to open Interface Builder
. You should see a red square
Play around a bit with it. change it’s size, position and the background color.
Awesome!
Now let see what actually happened when we changed the view. The first thing we did was to make the view taller and then move it around. Views draw content in a rectangular area. The size and position of that area are stored in theframe
property. The frame has an origin
– the position of the top left corner – and a size
– the width and height of the view.
You can find the frame by opening the Size Inspector
. Make sure your Utilities Pane
is visible.
After that we changed the color to orange. This actually sets the backgroundColor
property of the view toUIColor.orangeColor()
.
Coordinate system
To understand how a frame represents a rectangle on the screen we have to take a look at the coordinate system used by views.
The red square has the origin in (x: 200, y: 200)
and the size (width: 200, height: 200)
.
The top left corner of the screen is the point is (0, 0)
or CGPointZero
. In iOS we represent points using theCGPoint
struct. The horizontal axis is the X axis
and the vertical one is the Y axis
. The more to the right a point is the greater the x coordinate will be. The lower a point is on the screen the greater the y coordinate will be.
The red square has a width of 200 points
. Can you guess the coordinates of points A
, B
, C
from the image above?
A = (400, 200)
B = (200, 400)
C = (400, 400)
Adding a new view
In the bottom of the Utilities Pane
you can find the Object Library
.
The object library contains all the types of objects that you can use to make the interface of your app. Here are a few:
What do all the components from above have in common?
They all subclass UIView
. So all you learn about UIView
applies to all of the components you will use
Ok ok… Let’s add that view!
In the bottom of the Object Library
you will see a search field. Look for UIView
.
Drag and drop the view object on the screen.
You will notice that the view is invisible. The default background color for a view is transparent – or clear color. In order to see the view you will need to change the background color to something else.
Play around – see what other components you can add on the screen.
Creating a view from code
The iOS SDK uses a design pattern named Model View Controller – MVC – to display data on the screen. The pattern labels the code needed to make up a screen in three categories: models, views and controllers. The model or models refer to different data you app might use – like a user profile or the GPS position of the phone. Views are viewable objects – things you ca see on the screen. And finally controllers are the glue in between.
If you want to read more about object oriented programming and design patters in swift read our OOP guide.
In iOS the MVC pattern is implemented with a small twist. All controllers are actually a ViewController – a controller that has a view. All view controllers are a subclass of UIViewController
and their view can be accessed by calling theview
property. Because of this a lot of functionality is built in UIViewController
and UIView
that helps us developers deliver a consistent user experience.
To add a view to the screen you need to add it as a subview to the current view controller’s view or one of it’s subviews.
First open ViewController.swift
. The file should look like this:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
}
File and project templates usually contain redundant comments and boilerplate code. Take a few seconds and remove them after creating new files. Your code will look much better
The viewDidLoad
method is called when the screen has loaded it’s interface. In this case whatever you have in your storyboard. User interfaces can also be loaded from NIB files or directly from code. viewDidLoad
is most common place you will add you setup logic for your screen.
Let’s add another view to the screen. First we create a frame for it. Then we instantiate a new UIView
object with that frame. And to make it visible we are going to make the background color blue. After that we add it as a subview toview
.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let frame = CGRect(x: 50, y: 50, width: 50, height: 50)
let blueSquare = UIView(frame: frame)
blueSquare.backgroundColor = UIColor.blueColor()
view.addSubview(blueSquare)
}
...
}
If you run the app now you are going to see something like this.
Notice that the red square is not completely visible. Thats because it’s frame is outside the view. When we made the interface we worked on a 600x600
resolution and the resolution of the iPhone is smaller than that.
Nesting views
Before we did not explain a step. Adding blueSquare
as a subview to view
(the view of the view controller).
Views can have other views that are drawn inside them. They are called subviews. The coordinate system of subviews has the origin (the point (0, 0)
) in the top left corner of the parent view – also called superview. The list of subviews a view has can be accessed by calling it’s subviews
property. You can also access the superview of a view by calling it’s superview
property.
When we called view.addSubview(blueSquare)
we added the blue square inside the view of the screen, which is inside a window. The UIWindow
class inherits from UIView
– its purpose is to draw a screen. Unless the iOS device running the app has access to another screen the app will have only one window.
You can remove a view from it’s superview by calling the removeFromSuperview()
method.
Let’s add another view inside blueSquare
.
class ViewController: UIViewController {
override func viewDidLoad() {
...
let smallFrame = CGRect(x: 10, y: 10, width: 30, height: 30)
let blackSquare = UIView(frame: smallFrame)
blackSquare.backgroundColor = UIColor.blackColor()
blueSquare.addSubview(blackSquare)
}
...
}
If you run the app your screen should look like this:
If you want to see the view hierarchy you can pause your app and take a look.
First open the Debug Area
. In the top right corner of Xcode you have a segmented control that looks like this:
Make sure the middle on is blue.
Run the app. In the lower mid section of Xcode find and click Debug View Hierarchy
button:
If you followed all the steps you should see an interactive 3D representation of the view hierarchy.
the black rectangle in the back is the window
Interface Outlets
After designing your interface you might need to access a few components in order to populate them with content or add extra customisation.
You can get a reference to an interface object by creating an IBOutlet
for it. To do this add a property in your view controller with the type of the object you want to connect and mark is as an outlet using the IBOutlet
declaration attribute.
Let’s connect that red square
First we make a property and mark it as an IBOutlet
:
class ViewController: UIViewController {
@IBOutlet var redSquare: UIView!
...
}
The property is of type
UIView!
which is an implicitly unwrapped optional. The reason for this is that when you create the ViewController you don’t have the interface loaded. But after that is done you can assume that you have these objects and don’t want to test that they exist every time you use them.
Open Main.storyboard
. Make sure you have the Document Outline
pane open – here you can see all of the objects form the interface.
Right click View Controller
– it has a yellow icon. A popup should appear. In it there is a list of outlets you can connect to the view controller. If you look closely you will sport the newly added redSquare
outlet.
Connect it to the red view in the interface.
Great! Now that we have a reference to it we can change at runtime.
class ViewController: UIViewController {
@IBOutlet var redSquare: UIView!
override func viewDidLoad() {
super.viewDidLoad()
redSquare.backgroundColor = UIColor.orangeColor()
...
}
}
If you followed with all the steps you should see a orange view instead of the red one. You change any view attribute at runtime and the changes should immediately be visible.
There is a shorter version for adding an outlet.
Open the Assistant Editor
.
Right Click on the view you want to add an outlet to. Then drag an drop the outlet directly in the code of the view controller.
To finish off give it a name and set the type if necessary.
User interaction
Views can respond to user interaction. There are two main ways to get touch events.
1) Gesture Recognizers
The standard library includes gestures recognisers for the following gestures:
- Tap – can detect taps – you can customise the number of taps or fingers needed to activate the gesture
- Pan – pan simple means moving across the view
- Swipe – similar to the pan gesture. but a swipe is a swift gesture.
- Pinch – this is the gesture used to zoom in/out
- Long Press – detects a long press – you can customize the minimum duration and number of fingers
- Rotation – detects a rotation – this is a gesture with two fingers – you can extract the angle and speed of the gesture
If you want to learn about handling touch gestures read this tutorial
2) Getting touch event from the first responder – in the usual case that is the current ViewController. This is done by overriding the following methods:
touchesBegan(_:withEvent:)
touchesMoved(_:withEvent:)
touchesEnded(_:withEvent:)
touchesCancelled(_:withEvent:)
More about UIResponder in Apple’s Documentation
Useful view properties
So far we learned how to change the size, position and background color of a view. We can customise the appearance and behaviour of views by changing other properties.
alpha
If the value is smaller than 1 the view and all its subviews will become transparent.
transform
You can apply affine transformations to a view.
I’m not going to go into detail on this topic. I will only mention two transformations that you will likely use.
Scale
The scale transform has two factors – one for x axis and one for the y axis.
let scale = CGAffineTransformMakeScale(0.5, 0.5)
The above transformation makes the view half it’s size
Rotate
The rotate transform rotates the view by a number of degrees. The value given to CGAffineTransformMakeRotation
is expressed in radians. A positive value means a counterclockwise rotation and a negative value means a clockwise rotation.
let rotate = CGAffineTransformMakeRotation(CGFloat(M_PI_2 / 2))
The above transformation rotates the view by 45 degrees counterclockwise
Combining two transformations
You can combine two transformations into one using CGAffineTransformConcat
. The order does not matter.
let transformation = CGAffineTransformConcat(scale, rotate)
tag
The tag
property is exactly what it says. You can label a view with an Int
value. The default value is 0. This is useful when you have multiple view with the same functionality(ex. the number buttons from a calculator app – you can use the tag to extract the digit and all the logic is the same).
hidden
Sometimes you don’t want to show a view but you want it to remain in the view hierarchy. Set the value of hidden
totrue
to hide a view. All touch events will be ignored while the view is hidden.
bounds
This property gives the frame of the view in ints own coordinate system.
When is this useful?
Good question!
I keep on seeing this pattern. You decide to add a background image in a screen.
let backgroundFrame = CGRect(x: 0,
y:0,
width: view.frame.size.width,
height: view.frame.size.height)
let backgroundImage = BackgroundImage(frame: backgroundFrame)
Why not?
let backgroundImage = BackgroundImage(frame: view.bounds)
center
It gives you the center coordinate of the view in the coordinate system of its superview.
Maybe I like math too much. If you like designing custom animations or controls you will definitely like this property
The Layer
The view uses a CALayer
object to render its components. It can be accessed by calling the layer
property.
While the layer is used to draw the view it also has a few visual attributes that you can use to customise the view. The most common ones are:
borderWidth
You can add a border to the drawing area by setting the borderWidth
property to a value greater than 0.
With border 1.0
:
view.layer.borderWidth = 1.0
With border 10.0
:
view.layer.borderWidth = 10.0
cornerRadius
Ever wondered how those rounded corners are made? Search no more! cornerRadius
is what you are looking for!
view.layer.cornerRadius = 40.0
If you have a square view and you set cornerRadius
to half the width you will get a circle.
view.layer.cornerRadius = view.frame.width / 2.0
borderColor
You can set the color of the border by setting the borderColor
property to a GCColor
. You can convert any UIColor
to a CGColor
by calling it’s CGColor
property. (ex: UIColor.blackColor().CGColor
)
view.layer.borderColor = UIColor.blueColor().CGColor
Shadows
shadowOpacity
To enable shadows on a view you will need to set the shadowOpacity
property to a value greater the 0
. The default value is 0
.
Doing so on a view will give you this effect.
This is because shadows can be customised from other properties that have default value.
shadowOffset
The shadow can be offset to give different effects based on the supposed light might be. The reason the default shadow was so high it that the default value of shadowOffset
is (0, -3)
.
Here you can see the shadow with offset (0, 0)
:
shadowRadius
The shadow shows in a band round the layer. The width of that band is called shadowRadus
. The default value is 3.0
.
Here is how the shadow looks at radius 10.0
:
shadowColor
You can make the shadow have any color you want. I personally like purple shadows – they are the best. No really – stick to black or gray.
More on CALayer in Apple’s Documentation.
Animable Properties
While changing view properties at runtime is useful, it may not look pretty. Most changes look better if they are animated from one state to another.
Implementing animation could not be any simpler. Most UIView
properties are animable. That means that a change on that property can be smoothly transitioned from one state to another in an animation. This is the complete list of animable properties:
- frame
- alpha
- transform
- bounds
- backgroundColor
- center
- transform
- contentStretchChange the view as you would normally do and then wrap the code inside an animation block.
Quik examples:
- animating a frame change
UIView.animateWithDuration(2, animations: {
self.redSquare.frame =
CGRect(x: 100, y: 100, width: 100, height: 50)
})
- adding a color change to the animation
let blue = UIColor(red: 41.0/255.0,
green: 0.5,
blue: 185/255.0,
alpha: 1.0)
UIView.animateWithDuration(2, animations: {
self.redSquare.frame =
CGRect(x: 100, y: 100, width: 100, height: 50)
self.redSquare.backgroundColor = blue
})
- and adding a skew transformation
let blue = UIColor(red: 41.0/255.0,
green: 0.5,
blue: 185/255.0,
alpha: 1.0)
let transform =
CGAffineTransformMake(1, 0, 0.5, 1, 0, 0)
redSquare.layer.borderWidth = 2.0
UIView.animateWithDuration(2, animations: {
self.redSquare.frame =
CGRect(x: 100, y: 100, width: 100, height: 50)
self.redSquare.backgroundColor = blue
self.redSquare.transform = transform
})
Changing frames in code wont work if Autolayout is enabled. To disable Autolayout go in
Interface Builder
in theFile Inspector
and disable it from the checkbox.
I case you are wondering how the animations from the videos where made. I set some options for the animations:.Autoreverse
so the view return to the initial state and .Repeat
so it go on forever.
UIView.animateWithDuration(2,
delay: 0,
options: [UIViewAnimationOptions.Autoreverse, UIViewAnimationOptions.Repeat],
animations: {
self.redSquare.frame =
CGRect(x: 100, y: 100, width: 100, height: 50)
self.redSquare.backgroundColor = blue
let transform =
CGAffineTransformMake(1, 0, 0.5, 1, 0, 0)
self.redSquare.transform = transform
}, completion: nil)
IBDesignable
You can expose more customisation points in Interface Builder by making certain properties IBInspectable
and the class IBDesignable
.
Marking a class IBDesignable
tells interface builder to run the code from that view and render it instead of showing a simulated version.
Marking a property as IBInspectable
tell interface builder to show a field for that property.
Here is a simple example of a view that exposes the borderWidth
layer property.
@IBDesignable
class BorderedView: UIView {
@IBInspectable var borderWidth: CGFloat = 0 {
didSet {
layer.borderWidth = borderWidth
}
}
}
To use it. Add a UIView
in your screen. Select it. Then go to the Indentity Inspector
.
Change it’s class to BorderedView
.
Then go to the Attributes Inspector
. You should see the Border Width
field:
Learn more about IBDesignable from our tutorial: How to make awesome UI components
Conclusions
Although UIView
might seem small it is actually the blood of iOS. Knowing how to use it is critical if you wish not to reinvent the wheel. There are a lot of customisation points for views, by combining them you can avoid writing code and make better apps.
Apple encourages developers to subclass UIView
when they need to make a visual component that require user interaction. But only when the standard systems views do not solve your problem – know them well!
Exercises
1. Target
Write the code to make a target
let width = 20
let padding = 20
let red = UIColor(red: 192.0/255.0, green: 57.0/255.0, blue: 43.0/255.0, alpha: 1.0)
let blue = UIColor(red: 41.0/255.0, green: 0.5, blue: 185/255.0, alpha: 1.0)
let colors = [red, blue, red]
var l = (colors.count * (width + padding) - padding) * 2
var parent = view
for color in colors {
let frame = CGRect(x: padding + width, y: padding + width, width: l, height: l)
let circle = UIView(frame: frame)
circle.backgroundColor = UIColor.whiteColor()
circle.layer.borderColor = color.CGColor
circle.layer.borderWidth = CGFloat(width)
circle.layer.cornerRadius = CGFloat(l / 2)
parent.addSubview(circle)
parent = circle
l -= 2 * (width + padding)
}
2. Gradient
Make a gradient that goes from black to white using only UIViews
.
You can use UIColor(white:alpha:)
to create the different shades of gray. The white
parameter should have a value between 0.0
(black) and 1.0
(white).
let height: CGFloat = 50
let lineWidth: CGFloat = 1
let width = view.frame.width
var x: CGFloat = 0.0
while x < width {
let frame = CGRect(x: x, y: 50, width: lineWidth, height: height)
let line = UIView(frame: frame)
line.backgroundColor = UIColor(white: x / width, alpha: 1)
view.addSubview(line)
x += stepSize
}
Warning: this is not a practical approach! In real life you would use an image background that can stretch orCAGradientLayer.
3. UberView
Make a view that exposes the borderWidth
, borderColor
and cornerRadius
layer properties.
@IBDesignable
class UberView: UIView {
@IBInspectable var cornerRadius: CGFloat = 0 {
didSet {
layer.cornerRadius = cornerRadius
}
}
@IBInspectable var borderWidth: CGFloat = 0 {
didSet {
layer.borderWidth = borderWidth
}
}
@IBInspectable var borderColor: UIColor = UIColor.blackColor() {
didSet {
layer.borderColor = borderColor.CGColor
}
}
}
4. Robot
Use UberView
to design a robot.
here’s mine:
and Silviu’s:
Awesome! These tutorials are really clear, concise and very well structured! Thank you!!!