Learn UIKit (Part 1 of 3) — Introduction for SwiftUI Devs

Fernando Putra
10 min readDec 28, 2023

--

Welcome to the first part of my Learn UIKit series. In this opening chapter, we’ll explore UIKit, compare it with SwiftUI, delve into programming styles, understand foundational components, and set up a new UIKit project. Hopefully, this understanding will be the cornerstone for our journey to create a real UIKit application.

What Is UIKit?

UIKit is a graphical framework for building apps on Apple platforms. Introduced in 2007 alongside the first iPhone, it quickly became the standard framework that provides a comprehensive set of components and tools to create visually appealing and interactive user interfaces.

Despite the introduction of SwiftUI in 2019, UIKit remains an essential part of the iOS development ecosystem. While SwiftUI offers a modern and intuitive way to design interfaces, UIKit provides a robust and mature set of APIs that SwiftUI can leverage. This allows developers to combine the power of both frameworks and create more advanced and feature-rich applications.

Here are some of the key features of UIKit:

  • Extensive API coverage: Access an extensive library of items in the UIKit framework, including the core objects, views, and controls you’ll need to build iOS apps with few or no modifications.
  • Compositional layouts: Create flexible visual layouts by constructing a collection view from small, reusable components and applying state-driven updates to cells.
  • Precise UI display: Use Auto Layout to create layouts that work on all Apple devices in any orientation, whether you’re building your view programmatically or using Interface Builder.
  • Legacy app support: Before iOS 13, developers built all apps on UIKit. If you’re maintaining or extending legacy apps in iOS, watchOS, or tvOS, you need a good foundation in UIKit.
  • Strong community adoption: Benefit from extensive content and support from the UIKit community and Apple documentation.
  • Interoperability with SwiftUI: Easily integrate UIKit and SwiftUI to use the best of both frameworks.

The Differences Between UIKit & SwiftUI

UIKit and SwiftUI are both frameworks used for building user interfaces in iOS applications, but they differ in several key aspects:

  1. Nature of the frameworks: UIKit is a traditional framework for iOS app development, introduced in 2007, while SwiftUI is a newer framework introduced in iOS 13 in 2019.
  2. Programming style: UIKit is an imperative framework, requiring developers to write code that creates and manipulates views directly, giving them more control over the UI. In contrast, SwiftUI is a declarative framework, allowing developers to define the desired user interface using a more streamlined and intuitive syntax.
  3. Customization and flexibility: UIKit provides more control over the user interface, allowing for more customized and advanced layouts and more UI elements and functionality. SwiftUI, on the other hand, has a more limited range of customization options due to its declarative nature.
  4. Cross-platform compatibility: UIKit primarily focuses on iOS development, while SwiftUI is known for its ability to design interfaces across multiple platforms.
  5. Ease of use and debugging: UIKit might be more suitable for complex apps with advanced functionality, while SwiftUI is generally considered easier to use and more intuitive, especially for creating simple to medium-complexity interfaces.

Imperative vs. Declarative Style

Imperative and declarative styles are two different programming paradigms used in UIKit and SwiftUI. While frameworks from Win32 to web to Android and iOS conventionally follow an imperative style of UI programming, the declarative style also gained traction in more recent frameworks like Flutter and SwiftUI. This paradigm shift, however, requires a slight adjustment in thinking about how to manipulate UI.

Here’s a simple analogy to illustrate the differences between the two: Imagine planning a vacation.

  • In an imperative approach, you would list the steps to take, such as booking a flight, reserving a hotel, and packing your bags. Meaning that it provides explicit instructions for every action you need to take.
  • In a declarative approach, you would simply state your desired outcome, such as “I want to enjoy a relaxing vacation in a nice hotel.” Meaning that the focus is on what you want to achieve rather than how to achieve it.

In the contexts of UIKit and SwiftUI, UIKit is where developers manually construct full-functioning UI entities and mutate them using methods and setters when the UI changes, while SwiftUI is where developers describe the current UI state and let the framework handle the implementation, making the code more concise and easier to read.

Here’s a simple example to illustrate the difference in code:

  • Imperative (UIKit): Explicitly lists the steps to create and add a button to the view hierarchy.
  • Declarative (SwiftUI): Defines the desired outcome — a button with the text ‘Click me’ — and lets the framework handle the implementation.

Understanding UIView and UIViewController

When developing iOS user interfaces with UIKit, it’s important to have a good understanding of UIView and UIViewController. These two classes play key roles in managing your app’s visual elements and logic. Let’s delve into these classes and explore their lifecycle phases.

UIView

A UIView is an object that manages the content for a rectangular area on the screen, capable of displaying content and responding to user interactions. UIViews can be created programmatically or using Interface Builder, and they can be nested to construct complex user interfaces. It is the main way your application interacts with the user. Some common UIView subclasses include UILabel, UIButton, UITextField, UITableView, and UICollectionView.

UIView Lifecycle Phases

The UIView lifecycle comprises a series of phases, each crucial in ensuring UI elements are created, managed, and disposed of properly. These phases include:

  1. Initialization Phase: Set up the view’s initial state and structure. During this phase, developers often perform tasks like initializing properties and adding subviews, typically through one of two methods:
    - init(frame:): Used when creating a view entirely programmatically.
    - init(coder:) : Used when creating a view from storyboards or nib files, and the view requires custom initialization.
  2. Layout Phase: Arrange and adjust the view’s subviews to accommodate different screen sizes and orientations. During this phase, the layoutSubviews() method is called whenever the view’s bounds or constraints change or when you manually call setNeedsLayout(). Developers often override the layoutSubviews() method to update the layout of subviews and perform complex layout calculations.
  3. Drawing Phase: Render the view and its subviews onto the screen using UIKit or Core Graphics. During this phase, developers often override the draw(_:) method to implement custom drawing and animation tasks using Core Graphics.
  4. Event-Handling Phase: Manage the view’s user interactions. As a subclass of UIResponder, the UIView can respond to various events, including touches and other interactive gestures. During this phase, developers often override gesture recognizer methods to adeptly handle common user gestures, such as touchesBegan(_:with:), touchesMoved(_:with:), touchesEnded(_:with:), and touchesCancelled(_:with:).
  5. Deallocation Phase: Remove the UI element from the view hierarchy, and its associated resources are released. During this phase, Swift automatically handles memory management, so manual cleanup is typically not required. However, developers often use the deinit() method to perform any custom cleanup tasks that may be necessary for a class instance before it is deallocated.

UIViewController

A UIViewController is an object that manages a single-view hierarchy, which can contain one or more UIViews. It is responsible for updating the contents of the views, responding to user interactions, resizing views, managing the layout of the overall interface, and coordinating with other view controllers. It is often subclassed to create custom view controllers tailored to specific app requirements. Some common UIViewController subclasses include UINavigationController, UITabBarController, and UIAlertController.

UIViewController Lifecycle Phases

The UIViewController lifecycle comprises a series of phases, each crucial to performing setup and cleanup tasks, responding to changes in the view hierarchy, and managing resources. These phases include:

  1. Initialization Phase
    - init(coder:): Initializes the view controller, particularly when loaded from a storyboard or nib file.
    - init(nibName:bundle:): Used for custom initialization when creating the view controller.
  2. Loading Phase
    - loadView(): Creates or loads the view when the view controller is created programmatically.
    - viewDidLoad(): Triggered after the view is loaded into memory. This is often used for additional setup tasks, such as initializing properties, adding subviews, and fetching initial data.
  3. Appearing Phase
    - viewWillAppear(_:): Triggered just before the view is about to be displayed. This is often used for tasks that should occur every time the view is about to appear, like refreshing data or starting animations.
    - viewWillLayoutSubviews(): Triggered just before the layout of subviews occurs.
    - layoutSubviews(): Where the layout of the view’s subviews takes place.
    - viewDidAppear(_:): Triggered after the view fully transitions onto the screen. This is often used to perform tasks like tracking analytics, requesting a user’s location, etc.
  4. Disappearing Phase
    - viewWillDisappear(_:): Called just before the view was removed from the window. This is often used to perform tasks like saving data or stopping ongoing animations before the view disappears.
    - viewDidDisappear(_:): Invoked after the view has been fully transitioned off the screen.
  5. Move Phase
    -
    willMove(toParent:): Called just before the view controller is added or removed from a parent view controller. This is often used for setup before the transition.
    - didMove(toParent:): Called after the view controller has been added or removed from a parent view controller. This is often used for tasks after the transition.
  6. Deallocation Phase
    - deinit(): Used for custom cleanup tasks before the UIViewController instance is deallocated.

Project Setup

With the theoretical groundwork set, it’s time to dive into action. Throughout this series, we’ll create a UIKit Project while exploring various topics, including customizing UI elements, implementing auto layout, managing data, adopting MVVM architecture, working with navigation controllers, making network requests, and integrating with SwiftUI.

Keep in mind that we won’t be using storyboards; instead, we’ll take a programmatic approach. To facilitate this, we’ll do a quick project setup to remove the storyboard, allowing us to focus on code-centric development.

1. Creating a New UIKit Project

  1. Open Xcode and select Create a new Xcode project.
  2. Choose the App template under iOS and click Next.
  3. Set the interface to StoryBoard and done.

2. Exploring the Template

After creating the project, take a moment to explore the generated files in the Project Navigator. There are several key files that play pivotal roles in your UIKit project:

  1. AppDelegate.swift: This file contains the AppDelegate class, which manages the overall lifecycle of your app. You’ll handle tasks such as app initialization and responding to app state changes here.
  2. SceneDelegate.swift: Introduced in iOS 13, this file manages the scenes in your app. Each scene represents a window and its corresponding interface. You'll find relevant configuration here if your app supports multiple windows or scenes.
  3. ViewController.swift: This file houses the default ViewController class. The view controller is a fundamental component for managing the visual elements and logic of a screen in your app.
  4. Main.storyboard: This is the default storyboard file, providing a visual interface for designing and organizing your app’s user interface. However, as we’ve chosen a programmatic approach, we’ll remove it shortly.

3. Removing the Storyboard

Deleting the Main.storyboard file:

  1. In the Project Navigator, locate the Main.storyboard file and delete.
  2. Confirm the deletion by choosing “Move to Trash.”

Deleting the Storyboard Name key in Info.plist file.

  1. in the Project Navigator, find and open the Info.plist file.
  2. Look for a key named Storyboard Name and delete.

Removing the UIKit Main Storyboard File Base Name setting from the Info.plist values:

  1. Navigate to the Project Settings by selecting your project in the Project Navigator.
  2. Under the target settings, go to Build Settings.
  3. In the search bar, type Main to filter the settings.
  4. Locate the UIKit Main Storyboard File Base Name setting and delete.

4. Setting Up the Scene Delegate

Open the SceneDelegate.swift file and update it with the following code:

With this code, you:

  1. Inside scene(_:willConnectTo:options:), retrieve the UIWindowScene.
  2. Initialize a new UIWindow instance using the provided windowScene.
  3. Set the root view controller of the window to an instance of ViewController.
  4. call makeKeyAndVisible() method, which makes the app’s main window active and visible on the device screen.

Now, press the Run button (or Command + R) to build and run your app. Your app should launch successfully with no errors.

Congrats! You’ve learned an introduction to UIKit and set up a new UIKit Project. In the next chapter, you’ll be hands-on in customizing UI elements, implementing auto layout, managing data, adopting MVVM architecture, working with navigation controllers, and making network requests.

If you found this article helpful, kindly share it with your friends and follow my profile for future updates. Your support is much appreciated!

You can access the repository for this series below:

PS: I’m always welcome for pizza 🍕🍕🍕.

--

--