Skip to main content

Swift UIKit Basics

Introduction

UIKit is Apple's framework for building graphical, event-driven applications for iOS, iPadOS, and tvOS. It provides the infrastructure for constructing and managing your app's user interface, handling user interactions, and managing the flow of information through your application.

In this tutorial, we'll explore the fundamental components of UIKit and how to use Swift to build iOS applications with intuitive and responsive user interfaces. UIKit has been the backbone of iOS development since the platform's inception, and even with the introduction of SwiftUI, understanding UIKit remains crucial for iOS developers.

UIKit vs. SwiftUI

Before diving in, let's clarify the relationship between UIKit and SwiftUI:

  • UIKit is the mature, established framework used in most iOS apps in production today
  • SwiftUI is Apple's newer, declarative UI framework introduced in 2019
  • Many applications use both frameworks together
  • UIKit knowledge remains essential for iOS developers

Setting Up Your First UIKit Project

Let's start by creating a basic UIKit project:

  1. Open Xcode and select "Create a new Xcode project"
  2. Choose the "App" template under iOS
  3. Enter your project details
  4. Make sure the interface is set to "Storyboard" (for UIKit)
  5. Select Swift as the language

Once your project is created, you'll notice several important files:

  • AppDelegate.swift: Manages application lifecycle
  • SceneDelegate.swift: Manages scene lifecycle (iOS 13+)
  • ViewController.swift: Your main view controller
  • Main.storyboard: Visual interface builder
  • Info.plist: App configuration

UIKit Core Components

UIView

UIView is the fundamental building block for all visual elements in UIKit. Views represent rectangular areas that can display content, handle user interactions, and participate in the view hierarchy.

swift
// Creating a simple UIView programmatically
let myView = UIView(frame: CGRect(x: 50, y: 100, width: 200, height: 200))
myView.backgroundColor = .blue
view.addSubview(myView)

This code creates a 200×200 blue square positioned at (50, 100) from the top-left corner of the parent view.

UIViewController

UIViewController manages a view hierarchy, handles transitions between user interface screens, and coordinates the flow of information between your app's data model and the views that display that data.

Here's a basic example of a view controller:

swift
import UIKit

class MyViewController: UIViewController {

override func viewDidLoad() {
super.viewDidLoad()
// Setup your view
view.backgroundColor = .white

let welcomeLabel = UILabel(frame: CGRect(x: 20, y: 100, width: view.bounds.width - 40, height: 50))
welcomeLabel.text = "Welcome to UIKit!"
welcomeLabel.textAlignment = .center
welcomeLabel.font = UIFont.systemFont(ofSize: 24, weight: .bold)
view.addSubview(welcomeLabel)
}

override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Called when view is about to appear
}

override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// Called after view has appeared
}
}

Common UIKit Controls

Let's explore some of the most frequently used UI controls in UIKit:

UILabel

UILabel displays read-only text:

swift
let label = UILabel(frame: CGRect(x: 20, y: 50, width: 200, height: 30))
label.text = "Hello UIKit!"
label.textColor = .darkGray
label.font = UIFont.systemFont(ofSize: 18)
label.textAlignment = .center
view.addSubview(label)

UIButton

UIButton responds to user taps and triggers actions:

swift
let button = UIButton(type: .system)
button.frame = CGRect(x: 100, y: 200, width: 200, height: 50)
button.setTitle("Tap Me", for: .normal)
button.backgroundColor = .systemBlue
button.setTitleColor(.white, for: .normal)
button.layer.cornerRadius = 10
button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
view.addSubview(button)

// Action method that will be called when button is tapped
@objc func buttonTapped() {
print("Button was tapped!")
// Do something in response to button tap
}

UITextField

UITextField allows users to input text:

swift
let textField = UITextField(frame: CGRect(x: 20, y: 300, width: 300, height: 40))
textField.placeholder = "Enter your name"
textField.borderStyle = .roundedRect
textField.delegate = self // You need to conform to UITextFieldDelegate
view.addSubview(textField)

To handle text field events, you need to conform to UITextFieldDelegate:

swift
extension ViewController: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder() // Hide the keyboard
return true
}

func textFieldDidEndEditing(_ textField: UITextField) {
if let text = textField.text, !text.isEmpty {
print("User entered: \(text)")
}
}
}

UIImageView

UIImageView displays images:

swift
let imageView = UIImageView(frame: CGRect(x: 50, y: 350, width: 300, height: 200))
imageView.image = UIImage(named: "sample_image")
imageView.contentMode = .scaleAspectFit
view.addSubview(imageView)

Auto Layout Basics

Auto Layout dynamically calculates the size and position of views based on constraints. This allows interfaces to adapt to different screen sizes and orientations.

Adding Constraints Programmatically

swift
let redBox = UIView()
redBox.translatesAutoresizingMaskIntoConstraints = false
redBox.backgroundColor = .red
view.addSubview(redBox)

// Add constraints
NSLayoutConstraint.activate([
redBox.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
redBox.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
redBox.widthAnchor.constraint(equalToConstant: 100),
redBox.heightAnchor.constraint(equalToConstant: 100)
])

Handling User Interactions

UIKit uses a target-action pattern for handling control events (like button taps) and delegation for more complex interactions.

Target-Action Example

swift
let actionButton = UIButton(type: .system)
actionButton.setTitle("Show Alert", for: .normal)
actionButton.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(actionButton)

// Position the button
NSLayoutConstraint.activate([
actionButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
actionButton.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])

// Add action
actionButton.addTarget(self, action: #selector(showAlert), for: .touchUpInside)

// Alert action method
@objc func showAlert() {
let alert = UIAlertController(
title: "Hello!",
message: "This is a UIKit alert",
preferredStyle: .alert
)

alert.addAction(UIAlertAction(title: "OK", style: .default))
present(alert, animated: true)
}

UITableView - A Fundamental UIKit Component

UITableView is one of the most used components in iOS apps, displaying scrollable lists of data.

swift
class FruitsTableViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

let tableView = UITableView()
let fruits = ["Apple", "Banana", "Cherry", "Date", "Elderberry", "Fig", "Grape"]

override func viewDidLoad() {
super.viewDidLoad()

// Setup tableView
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
tableView.dataSource = self
tableView.delegate = self

tableView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(tableView)

NSLayoutConstraint.activate([
tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
tableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
])
}

// MARK: - UITableViewDataSource

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return fruits.count
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel?.text = fruits[indexPath.row]
return cell
}

// MARK: - UITableViewDelegate

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
print("Selected \(fruits[indexPath.row])")
}
}

Navigation between view controllers typically uses UINavigationController:

swift
// In SceneDelegate.swift or AppDelegate.swift
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }

let window = UIWindow(windowScene: windowScene)

// Create main view controller
let mainViewController = ViewController()

// Wrap it in a navigation controller
let navigationController = UINavigationController(rootViewController: mainViewController)

// Set the navigation controller as the root
window.rootViewController = navigationController
self.window = window
window.makeKeyAndVisible()
}

To navigate to another view controller:

swift
@objc func navigateToDetail() {
let detailViewController = DetailViewController()
navigationController?.pushViewController(detailViewController, animated: true)
}

Practical Example: Contact List App

Let's put everything together by creating a simple contact list app:

swift
// Contact model
struct Contact {
let name: String
let phone: String
}

// ContactListViewController
class ContactListViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

let tableView = UITableView()
var contacts = [
Contact(name: "John Smith", phone: "(555) 123-4567"),
Contact(name: "Jane Doe", phone: "(555) 987-6543"),
Contact(name: "Bob Johnson", phone: "(555) 456-7890")
]

override func viewDidLoad() {
super.viewDidLoad()
title = "Contacts"

// Setup add button
navigationItem.rightBarButtonItem = UIBarButtonItem(
barButtonSystemItem: .add,
target: self,
action: #selector(addContactTapped)
)

// Configure tableView
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "ContactCell")
tableView.dataSource = self
tableView.delegate = self

tableView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(tableView)

NSLayoutConstraint.activate([
tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
}

@objc func addContactTapped() {
let alertController = UIAlertController(
title: "Add Contact",
message: "Enter contact details",
preferredStyle: .alert
)

alertController.addTextField { textField in
textField.placeholder = "Name"
}

alertController.addTextField { textField in
textField.placeholder = "Phone Number"
textField.keyboardType = .phonePad
}

let addAction = UIAlertAction(title: "Add", style: .default) { [weak self] _ in
guard let name = alertController.textFields?[0].text, !name.isEmpty,
let phone = alertController.textFields?[1].text, !phone.isEmpty else {
return
}

let newContact = Contact(name: name, phone: phone)
self?.contacts.append(newContact)
self?.tableView.reloadData()
}

let cancelAction = UIAlertAction(title: "Cancel", style: .cancel)

alertController.addAction(addAction)
alertController.addAction(cancelAction)

present(alertController, animated: true)
}

// MARK: - UITableViewDataSource

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return contacts.count
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ContactCell", for: indexPath)
let contact = contacts[indexPath.row]

cell.textLabel?.text = contact.name
cell.detailTextLabel?.text = contact.phone

return cell
}

// MARK: - UITableViewDelegate

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)

let contact = contacts[indexPath.row]
let detailVC = ContactDetailViewController(contact: contact)
navigationController?.pushViewController(detailVC, animated: true)
}
}

// Contact Detail View Controller
class ContactDetailViewController: UIViewController {

let contact: Contact

init(contact: Contact) {
self.contact = contact
super.init(nibName: nil, bundle: nil)
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

override func viewDidLoad() {
super.viewDidLoad()
title = contact.name
view.backgroundColor = .white

// Create UI
let nameLabel = UILabel()
nameLabel.translatesAutoresizingMaskIntoConstraints = false
nameLabel.font = UIFont.boldSystemFont(ofSize: 24)
nameLabel.text = contact.name

let phoneLabel = UILabel()
phoneLabel.translatesAutoresizingMaskIntoConstraints = false
phoneLabel.font = UIFont.systemFont(ofSize: 18)
phoneLabel.text = contact.phone

let callButton = UIButton(type: .system)
callButton.translatesAutoresizingMaskIntoConstraints = false
callButton.setTitle("Call", for: .normal)
callButton.backgroundColor = .systemGreen
callButton.setTitleColor(.white, for: .normal)
callButton.layer.cornerRadius = 10
callButton.addTarget(self, action: #selector(callTapped), for: .touchUpInside)

view.addSubview(nameLabel)
view.addSubview(phoneLabel)
view.addSubview(callButton)

NSLayoutConstraint.activate([
nameLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
nameLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),

phoneLabel.topAnchor.constraint(equalTo: nameLabel.bottomAnchor, constant: 10),
phoneLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),

callButton.topAnchor.constraint(equalTo: phoneLabel.bottomAnchor, constant: 30),
callButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
callButton.widthAnchor.constraint(equalToConstant: 200),
callButton.heightAnchor.constraint(equalToConstant: 50)
])
}

@objc func callTapped() {
let phoneNumber = contact.phone.replacingOccurrences(of: " ", with: "")
.replacingOccurrences(of: "-", with: "")
.replacingOccurrences(of: "(", with: "")
.replacingOccurrences(of: ")", with: "")

if let url = URL(string: "tel://\(phoneNumber)"), UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url)
} else {
let alert = UIAlertController(
title: "Cannot Make Call",
message: "This device cannot make phone calls.",
preferredStyle: .alert
)
alert.addAction(UIAlertAction(title: "OK", style: .default))
present(alert, animated: true)
}
}
}

Summary

In this tutorial, we've covered:

  • The basics of UIKit and its role in iOS development
  • Core components like UIView and UIViewController
  • Common UI controls (UILabel, UIButton, UITextField, UIImageView)
  • Auto Layout for responsive interfaces
  • Handling user interactions with target-action and delegation
  • Working with UITableView for displaying lists
  • Navigation between view controllers
  • A practical example building a contacts app

UIKit is an extensive framework with many more components and capabilities than we could cover here. With these fundamentals, you're now ready to explore more advanced topics like custom view controllers, collection views, animations, and more.

Additional Resources

Practice Exercises

  1. Modify the contact list app to use custom table view cells with a more polished UI
  2. Add the ability to edit existing contacts
  3. Create a settings screen that lets users customize the app's appearance
  4. Add data persistence to the contacts app using UserDefaults or Core Data
  5. Create a simple calculator app using UIButtons and Auto Layout

By mastering UIKit fundamentals, you'll be well on your way to building professional iOS applications!



If you spot any mistakes on this website, please let me know at feedback@compilenrun.com. I’d greatly appreciate your feedback! :)