Learn UIKit (Part 1 of 3) — Introduction for SwiftUI Devs
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.
Table of Contents
· What Is UIKit?
· The Differences Between UIKit & SwiftUI
· Imperative vs. Declarative Style
· Understanding UIView and UIViewController
∟ UIView
∟ UIView Lifecycle Phases
∟ UIViewController
∟ UIViewController Lifecycle Phases
· Project Setup
∟ 1. Creating a New UIKit Project
∟ 2. Exploring the Template
∟ 3. Removing the Storyboard
∟ 4. Setting Up the App Delegate
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:
- 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.
- 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.
- 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.
- Cross-platform compatibility: UIKit primarily focuses on iOS development, while SwiftUI is known for its ability to design interfaces across multiple platforms.
- 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:
- 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. - 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 callsetNeedsLayout()
. Developers often override thelayoutSubviews()
method to update the layout of subviews and perform complex layout calculations. - Drawing Phase: Render the view and its subviews onto the screen using
UIKit
orCore Graphics
. During this phase, developers often override thedraw(_:)
method to implement custom drawing and animation tasks usingCore Graphics
. - Event-Handling Phase: Manage the view’s user interactions. As a subclass of
UIResponder
, theUIView
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 astouchesBegan(_:with:)
,touchesMoved(_:with:)
,touchesEnded(_:with:)
, andtouchesCancelled(_:with:)
. - 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:
- 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. - 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. - 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. - 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. - 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. - Deallocation Phase
-deinit()
: Used for custom cleanup tasks before theUIViewController
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
- Open Xcode and select
Create a new Xcode project
. - Choose the
App
template underiOS
and clickNext
. - 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:
AppDelegate.swift
: This file contains theAppDelegate
class, which manages the overall lifecycle of your app. You’ll handle tasks such as app initialization and responding to app state changes here.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.ViewController.swift
: This file houses the defaultViewController
class. The view controller is a fundamental component for managing the visual elements and logic of a screen in your app.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:
- In the Project Navigator, locate the
Main.storyboard
file and delete. - Confirm the deletion by choosing “Move to Trash.”
Deleting the Storyboard Name
key in Info.plist
file.
- in the Project Navigator, find and open the
Info.plist
file. - Look for a key named
Storyboard Name
and delete.
Removing the UIKit Main Storyboard File Base Name
setting from the Info.plist
values:
- Navigate to the Project Settings by selecting your project in the Project Navigator.
- Under the target settings, go to
Build Settings
. - In the search bar, type
Main
to filter the settings. - 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:
- Inside
scene(_:willConnectTo:options:)
, retrieve theUIWindowScene
. - Initialize a new
UIWindow
instance using the providedwindowScene
. - Set the root view controller of the window to an instance of
ViewController
. - 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 🍕🍕🍕.