UIView Fundamentals

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 :)

Starter Project

*screenshot*

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, Cfrom the image above?

A

A = (400, 200)

[collapse]
B

B = (200, 400)

[collapse]
C

C = (400, 400)

[collapse]

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 UIColorto 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 the File 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 :)

Solution

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)
}

[collapse]

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).

Hint

Try adding view in a line and change their background color. If you make them thin enough it will look like a gradient.

[collapse]
Solution

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
}

[collapse]

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.

Solution

@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
        }
    }
}

[collapse]

4. Robot

Use UberView to design a robot.

here’s mine:

and Silviu’s:

  4 comments for “UIView Fundamentals

  1. Almarma
    November 2, 2017 at 11:19 am

    Awesome! These tutorials are really clear, concise and very well structured! Thank you!!!

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Subscribe
We send about one email per week with our latest tutorials and updates
Never display this again :)