iOS: Table Views, Image Views, App Bundles & More…

John Kim
12 min readFeb 27, 2022

In this project, I will cover the following topics:

  1. Table Views
  2. Image Views
  3. App Bundles
  4. FileManager
  5. Typecasting
  6. View Controllers
  7. Storyboards
  8. Outlets
  9. Auto Layout
  10. UIImage

Building your Project

Options to configure your project

The organization identifier is a unique identifier usually made up of your personal website domain name in reverse (i.e. com.example).

After you create a project, you can choose the product or device to deploy your project using the following Xcode main menu → Product > Destination.

To have line numbers shown in your Xcode code editor, you can go to the Xcode main menu → Preferences > Text Editing > Line Numbers (check the box).

To review some basic Xcode shortcuts:

  1. Cmd + R → run a project or rerun a project
  2. Cmd + . → stop a running project

Listing Images with FileManager

Behind the scenes, an iOS app is a directory containing lots of files such as the following:

  1. The binary (the compiled version of your code, ready to run)
  2. All the media assets your app uses
  3. Any visual layout files you have
  4. Metadata & security entitlements

App directories are called bundles, and they have the .app file extension.

An example of a UIViewController

A view controller is best thought of as being 1 screen of information.

import UIKit

This imports UIKit, which references the iOS user interface toolkit. When you see “UI”, it means it derives from UIKit.

class ViewController: UIViewController {

UIViewController is Apple’s default screen type, which is empty.

override func viewDidLoad() {

viewDidLoad() is called by UIKit when the screen has loaded and is ready for you to customize.

super.viewDidLoad()

This tells Apple’s UIViewController to run its own code before running yours.

FileManager

let fm = FileManager.default

FileManager.default is a data type that lets us work with the filesystem, and in our case, we’ll be using it to look for files.

let path = Bundle.main.resourcePath!

A bundle is a directory containing our compiled program and all our assets. This line provides you with the path where you can find all the files in the directory or iOS app.

let items = try! fm.contentsOfDirectory(atPath: path)

The constant items is set to the contents of the directory at a path. The items constant will be an array of strings containing filenames.

if item.hasPrefix(“nssl”) {

The hasPrefix() method takes 1 parameter (the prefix to search for) and returns a Boolean value.

It’s perfectly fine to use Bundle.main.resourcePath! and try! because if this code fails, it means our app can’t read its own data so something must be seriously wrong.

The pictures array will be created when the ViewController screen is created and exist for as long as the screen exists

pictures.append(item)

Swift’s arrays have a built-in method called append that we can use to add any items we want.

print(pictures)

print(pictures) tells Swift to print the contents of pictures to the Xcode debug console.

Designing our Interface

An example of a UITableViewController

class ViewController: UITableViewController {

UITableViewController is based on UIViewController but adds the ability to show rows of data that can be scrolled and selected. Examples of an UITableViewController can be seen in the Settings, Mail, Notes, and Health app. Behind the scenes, UITableViewController builds on top of UIViewController, which is an example of “class hierarchy” — a common way to build up functionality quickly.

User interfaces can be written entirely in code if you want, but more commonly, they are created using a graphical editor called Interface Builder. In the project navigator (the pane on the left), storyboards (Main.storyboard) contain the user interface for your app and let you visualize some or all of it on a single screen.

Main.storyboard

The best way to view, select, edit, and delete items in Interface Builder is to use the document outline, which can be accessed in the Xcode menu → Editor > Document Outline.

You can access the object library through the Xcode menu → View > Show Library. Alternatively, the shortcut to open the object library is Cmd + Shift + L and Alt + Cmd + Shift + L will make the object library a movable, resizable window when it appears.

The current tab displaying at the right-hand pane is the identity inspector

Setting ViewController as the class of the table view controller tells Xcode that this table view controller in the storyboard is the same one we have in code inside ViewController.swift.

The current tab displaying at the right-hand pane is the attributes inspector

Checking the box “Is Initial View Controller” makes sure that this new table view controller is what should be shown first when the app runs.

Document outline

A table view cell is responsible for displaying one row of data in a table.

Assigning an identifier to the table view cell

Select Table View Cell in the document outline and enter the text “Picture” into the text field marked Identifier in the attributes inspector. Also, change the style option from Custom to Basic.

To place our table view controller into a navigation controller, we can go to the Xcode menu → Editor > Embed In > Navigation Controller.

UITableViewController Data Source Methods

numberOfRowsInSection method

override

The override keyword means the method has been defined already and we want to override this existing behavior with the new behavior. On the other hand, if you didn’t override it, then the previously defined method would execute, which in this instance it would say there are no rows.

func

The func keyword starts a new function or a new method. A method is a function that appears inside a class.

tableView

tableView is the method’s name

_ tableView: UITableView

_ means the first parameter isn’t passed in using a name when called externally. tableView is the name that we can use to reference the table view inside the method. UITableView is the data type.

numberOfRowsInSection section: Int

numberOfRowsInSection will be triggered when iOS wants to know how many rows are in the table view. section is there because table views can be split into sections, like the way the Contacts app separates names by first letter. We only have 1 section, so we can ignore this number. Int means “this will be an integer” which means a whole number.

→ Int

“This method must return an integer”, which ought to be the number of rows to show in the table.

return pictures.count

Return the number of pictures in our array.

cellForRowAt method

override func tableView(_ tableView: UITableView

The method name is tableView and it will pass in a table view as its first parameter. Because the method name is the same as the parameter being passed in internally, we can set the external parameter as _.

cellForRowAt indexPath: IndexPath)

The important part of the method is cellForRowAt, and will be called when you need to provide a row. The row to show is specified in the parameter: indexPath.

IndexPath is a data type that contains both a section and row number — we only have 1 section so we can ignore this and use the row number.

→ UITableViewCell

This method must return a table view cell. We created one inside Interface Builder and gave it the identifier “Picture”.

let cell = tableView.dequeueReusableCell(withIdentifier: “Picture”, for: indexPath)

This creates a new constant called cell by dequeuing a recycled cell from the table.

To save CPU time and RAM, iOS only creates as many rows as it needs to work. When 1 row moves off the top of the screen, iOS will take it away and put it into a reuse queue ready to be recycled into a new row that comes in from the bottom — essentially, as you scroll, iOS just recycles the existing rows over and over again without creating any new table view cells.

cell.textLabel?.text = pictures[indexPath.row]

This gives the text label of the cell the same text as a picture in our array. The cell property textLabel is optional, and indexPath.row will contain the row number we’re being asked to load, so we’re going to use that to read the corresponding picture from pictures and place it into the cell’s text label.

return cell

Because this method expects a table view cell to be returned, we need to send back the one we created.

Building a Detail Screen

Creating a new file

Create a new View Controller by going to the Xcode menu → File > New > File… > Cocoa Touch Class, and make sure that the Subclass of the new View Controller is UIViewController.

Setting a view controller’s Storyboard ID

Drag a new view controller from the object library onto your Interface Builder. Click on that view controller, and go to the identity inspector and enter Detail where it says Storyboard ID. This allows us to refer to this view controller as Detail in code.

Same as we did before with the table view controller, set the Class of the new view controller as DetailViewController.

Auto Layout

There are 2 rules when it comes to Auto Layout (rules or constraints for how your views should be laid out):

  1. Your layout rules must be complete: you must specify both an X & Y position — X position is from the left of the screen and Y position is from the top of the screen.
  2. Your layout rules must not conflict — you can’t specify a view to be 10 points away from the left and right edge and 1000 points wide on an iPhone due to their fixed width.

Nevertheless, Auto Layout will try to recover from these problems by breaking rules until it finds a solution.

Go to the Xcode menu → Editor > Resolve Auto Layout Issues > Reset to Suggested Constraints.

Once you added constraints successfully, you can see those constraints each individually in the document outline pane.

The Assistant Editor

The Assistant Editor splits your Xcode editor into two: the view you had before on top (or left) and a related view at the bottom (or right). You can access the AE by going to the Xcode menu → Editor > Assistant. You also can modify the AE’s layout by going to the Xcode menu → Editor > Layout.

To exit out of the AE and return to the FE (Full-screen Editor), you can go to the Xcode menu → Editor > Show Editor Only.

An example of an IBOutlet

The gray circle with a line around it is Xcode’s way of telling you the line of code is connected to the image view in your storyboard.

@IBOutlet

This attribute is used to tell Xcode that there’s a connection between this line of code and Interface Builder.

var imageView

This declares a new variable property which has a name imageView assigned to the UIImageView

UIImageView!

This property imageView is of type UIImageView with an implicitly unwrapped optional symbol.

The process behind implicitly unwrapped optionals

Implicitly unwrapped optional means that the UIImageView may or may not be there but we’re certain it definitely will be there by the time we want to use it.

Loading Images with UIImage

didSelectRowAt is an optional UITableViewController method

storyboard?.instantiateViewController(withIdentifier: “Detail”) as? DetailViewController

Every view controller has a property called storyboard that is either the storyboard it was loaded from or nil. instantiateViewController(withIdentifier:) allows us to load a view controller from the storyboard using its storyboard ID.

Even though instantiateViewController() will send us back a DetailViewController if everything worked correctly, Swift thinks it will return back a UIViewController because it can’t see inside the storyboard to know what’s what.

navigationController?.pushViewController(detailViewController, animated: true)

Every view controller has a property called navigationController that contains the navigation controller they are inside if it exists, or nil otherwise.

Navigation controllers are responsible for maintaining a big stack of screens that users navigate through. By default, they contain the first view controller you created for them in the storyboard, but when new screens are created you can push them onto the navigation controller’s stack to have them slide in smoothly just like you see in Settings. As more screens are pushed on, they just keep sliding in. When users go back a screen — i.e. by tapping Back or by swiping from left to right edge of the screen — the navigation controller will automatically destroy the old view controller and free up its memory.

DetailViewController

imageView.image = UIImage(named: imageToLoad)

UIImage doesn’t have “View” in its name like UIImageView so it’s not something you can view — it’s not something that’s visible to users.

UIImage is the data type you’ll use to load image data, such as PNGs or JPEGs. It takes a parameter called named that lets you specify the name of the image to load. Then, UIImage looks for this filename in your app’s bundle and loads it.

The Content Mode of the UIImageView can be found in the Attributes Inspector

The default setting for all images shown by an UIImageView are Aspect Fit (stretched to fit the screen). We can change the Content Mode under View from Aspect Fit to Aspect Fill.

Aspect Fit sizes the image so that it’s all visible, and Aspect Fill hangs the image effectively outside its view area (enabling Clip to Bounds will have the image avoid overspilling).

UINavigationController has a property called hidesBarsOnTap, and when this is set to true, the user can tap anywhere on the current view controller to hide the navigation bar, then tap again to show it.

Disclosure indicator

Gray arrows at the right of the table view are called disclosure indicators, and you can turn them on by going to the Attributes Inspector → Accessory.

View Controller Lifecycles

viewDidLoad()

This is called when the view controller’s layout has been loaded.

viewWillAppear()

This is called when the view is about to be shown.

viewDidAppear()

This is called when the view has been shown.

viewWillDisappear()

This is called when the view is about to go away.

viewDidDisappear()

This is called when the view has gone away.

An example of viewWillAppear() and viewWillDisappear()

override

We’re using override for each of these methods because they already have defaults defined in UIViewController and we’re asking the DetailViewController to use ours instead.

(_ animated: Bool)

Both methods have a single parameter of whether the action is animated or not.

super.viewWillAppear(animated) & super.viewWillDisappear(animated)

Both methods uses the super prefix which tells the parent data type that these methods were called. In this case, the DetailViewController passes the method on to UIViewController (the parent class), which may perform its own processing.

navigationController?.hidesBarsOnTap = Boolean Value

? indicates if somehow we weren’t inside a navigation controller, the hidesBarsOnTap lines does nothing.

An example of the title property being set

title

A title property automatically gets prepared by the navigation controller — if you provide this title, it will be displayed in the gray navigation bar at the top.

View controllers have a title property that we get from UIViewController, and this title is automatically used for the Back button so that users know what they are going back to.

An example of not needing to unwrap an optional string

We don’t need to unwrap selectedImage because both selectedImage and title are optional strings — we are assigning one optional string to another.

An example of large titles being configured

Apple’s design guidelines recommend to use large titles.

navigationController?.navigationBar.prefersLargeTitles = true

This makes the title of the view controller a large title. Each new view controller that gets pushed onto the navigation controller stack inherits the style of its predecessor.

navigationItem.largeTitleDisplayMode = .never

This makes the title of the view controller appear as normal, or not large.

If you made it here, I hope you found this insightful and definitely be proud of what you were able to learn and achieve. If you have any lingering questions or comments, please don’t feel afraid to ask or connect with me on social media!

LinkedIn

AngelList

GitHub

Portfolio

--

--

John Kim

iOS Developer | Full Stack Developer | Software Engineer | LinkedIn: john-kim-developer | GitHub: cloudiosx | Portfolio: cloudiosx.com