Post

UIKit Deep Dive

UIKit is Apple's traditional UI framework for building iOS apps. It is older than SwiftUI but still very important because many production iOS apps are built with UIKit.

UIKit Deep Dive

UIKit is mainly based on:

  • UIViewController
  • UIView
  • UILabel
  • UIButton
  • UIImageView
  • UITextField
  • UITableView
  • UICollectionView
  • UINavigationController
  • UITabBarController
  • Auto Layout
  • Delegates
  • Target-action pattern

1. UIViewController

UIViewController controls one screen in a UIKit app. It manages the screen’s view, lifecycle, UI setup, user interaction, and navigation.

1
2
3
4
5
6
7
8
9
10
import UIKit

class HomeViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = .systemBackground
    }
}

2. View Controller Lifecycle

UIKit screens have lifecycle methods. These methods are called when the screen loads, appears, disappears, or is removed.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import UIKit

class HomeViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        print("View loaded once")
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        print("View will appear")
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        print("View appeared")
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        print("View will disappear")
    }

    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        print("View disappeared")
    }
}

Simple Explanation

  • viewDidLoad() → called once when the view is loaded.
  • viewWillAppear() → called before the screen appears.
  • viewDidAppear() → called after the screen appears.
  • viewWillDisappear() → called before the screen disappears.
  • viewDidDisappear() → called after the screen disappears.

3. Programmatic UI

Programmatic UI means creating the interface using Swift code instead of Storyboard.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import UIKit

class HomeViewController: UIViewController {

    private let titleLabel = UILabel()

    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = .systemBackground
        setupTitleLabel()
    }

    private func setupTitleLabel() {
        titleLabel.text = "Hello UIKit"
        titleLabel.font = .systemFont(ofSize: 28, weight: .bold)
        titleLabel.textAlignment = .center
        titleLabel.translatesAutoresizingMaskIntoConstraints = false

        view.addSubview(titleLabel)

        NSLayoutConstraint.activate([
            titleLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            titleLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])
    }
}

4. Layout with Auto Layout

UIKit uses Auto Layout to position UI elements on different screen sizes. Auto Layout works with constraints.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import UIKit

class LayoutViewController: UIViewController {

    private let boxView = UIView()

    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = .systemBackground
        setupBoxView()
    }

    private func setupBoxView() {
        boxView.backgroundColor = .systemBlue
        boxView.layer.cornerRadius = 16
        boxView.translatesAutoresizingMaskIntoConstraints = false

        view.addSubview(boxView)

        NSLayoutConstraint.activate([
            boxView.widthAnchor.constraint(equalToConstant: 200),
            boxView.heightAnchor.constraint(equalToConstant: 120),
            boxView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            boxView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])
    }
}

Important

When using Auto Layout programmatically, always set:

1
view.translatesAutoresizingMaskIntoConstraints = false

5. Safe Area Layout

Safe area helps avoid the notch, status bar, home indicator, and navigation bar.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import UIKit

class SafeAreaViewController: UIViewController {

    private let titleLabel = UILabel()

    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = .systemBackground
        setupUI()
    }

    private func setupUI() {
        titleLabel.text = "Safe Area Example"
        titleLabel.font = .systemFont(ofSize: 24, weight: .bold)
        titleLabel.translatesAutoresizingMaskIntoConstraints = false

        view.addSubview(titleLabel)

        NSLayoutConstraint.activate([
            titleLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 24),
            titleLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            titleLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20)
        ])
    }
}

6. UIStackView

UIStackView arranges views vertically or horizontally. It makes layout easier.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import UIKit

class StackViewController: UIViewController {

    private let stackView = UIStackView()
    private let titleLabel = UILabel()
    private let subtitleLabel = UILabel()
    private let button = UIButton(type: .system)

    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = .systemBackground
        setupStackView()
    }

    private func setupStackView() {
        titleLabel.text = "Welcome"
        titleLabel.font = .systemFont(ofSize: 28, weight: .bold)

        subtitleLabel.text = "Learn UIKit step by step"
        subtitleLabel.font = .systemFont(ofSize: 16)
        subtitleLabel.textColor = .secondaryLabel

        button.setTitle("Continue", for: .normal)

        stackView.axis = .vertical
        stackView.spacing = 12
        stackView.alignment = .center
        stackView.translatesAutoresizingMaskIntoConstraints = false

        stackView.addArrangedSubview(titleLabel)
        stackView.addArrangedSubview(subtitleLabel)
        stackView.addArrangedSubview(button)

        view.addSubview(stackView)

        NSLayoutConstraint.activate([
            stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])
    }
}

7. UILabel

UILabel is used to show text.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import UIKit

class LabelViewController: UIViewController {

    private let label = UILabel()

    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = .systemBackground
        setupLabel()
    }

    private func setupLabel() {
        label.text = "This is a UILabel"
        label.font = .systemFont(ofSize: 24, weight: .semibold)
        label.textColor = .label
        label.textAlignment = .center
        label.numberOfLines = 0
        label.translatesAutoresizingMaskIntoConstraints = false

        view.addSubview(label)

        NSLayoutConstraint.activate([
            label.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            label.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            label.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])
    }
}

8. UIButton

UIButton is used for clickable actions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import UIKit

class ButtonViewController: UIViewController {

    private let button = UIButton(type: .system)

    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = .systemBackground
        setupButton()
    }

    private func setupButton() {
        button.setTitle("Tap Me", for: .normal)
        button.titleLabel?.font = .systemFont(ofSize: 18, weight: .bold)
        button.backgroundColor = .systemBlue
        button.tintColor = .white
        button.layer.cornerRadius = 12
        button.translatesAutoresizingMaskIntoConstraints = false

        button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)

        view.addSubview(button)

        NSLayoutConstraint.activate([
            button.widthAnchor.constraint(equalToConstant: 180),
            button.heightAnchor.constraint(equalToConstant: 50),
            button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            button.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])
    }

    @objc private func buttonTapped() {
        print("Button tapped")
    }
}

9. Target-Action Pattern

UIKit uses target-action to handle button taps and control events.

1
2
3
4
5
button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)

@objc private func buttonTapped() {
    print("Button clicked")
}

Simple Explanation

  • target means who will handle the action.
  • action means which method will run.
  • .touchUpInside means button tap event.

10. UIImageView

UIImageView is used to display images.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import UIKit

class ImageViewController: UIViewController {

    private let imageView = UIImageView()

    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = .systemBackground
        setupImageView()
    }

    private func setupImageView() {
        imageView.image = UIImage(systemName: "photo")
        imageView.tintColor = .systemBlue
        imageView.contentMode = .scaleAspectFit
        imageView.translatesAutoresizingMaskIntoConstraints = false

        view.addSubview(imageView)

        NSLayoutConstraint.activate([
            imageView.widthAnchor.constraint(equalToConstant: 120),
            imageView.heightAnchor.constraint(equalToConstant: 120),
            imageView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            imageView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])
    }
}

11. UITextField

UITextField is used for single-line text input.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import UIKit

class TextFieldViewController: UIViewController {

    private let emailTextField = UITextField()

    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = .systemBackground
        setupTextField()
    }

    private func setupTextField() {
        emailTextField.placeholder = "Enter email"
        emailTextField.borderStyle = .roundedRect
        emailTextField.keyboardType = .emailAddress
        emailTextField.autocapitalizationType = .none
        emailTextField.translatesAutoresizingMaskIntoConstraints = false

        view.addSubview(emailTextField)

        NSLayoutConstraint.activate([
            emailTextField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            emailTextField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            emailTextField.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            emailTextField.heightAnchor.constraint(equalToConstant: 48)
        ])
    }
}

12. UITextView

UITextView is used for multi-line text input.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import UIKit

class TextViewController: UIViewController {

    private let textView = UITextView()

    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = .systemBackground
        setupTextView()
    }

    private func setupTextView() {
        textView.font = .systemFont(ofSize: 16)
        textView.text = "Write something..."
        textView.layer.borderWidth = 1
        textView.layer.borderColor = UIColor.systemGray4.cgColor
        textView.layer.cornerRadius = 12
        textView.translatesAutoresizingMaskIntoConstraints = false

        view.addSubview(textView)

        NSLayoutConstraint.activate([
            textView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            textView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            textView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 40),
            textView.heightAnchor.constraint(equalToConstant: 180)
        ])
    }
}

13. UISwitch

UISwitch is used for on/off settings.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import UIKit

class SwitchViewController: UIViewController {

    private let premiumSwitch = UISwitch()

    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = .systemBackground
        setupSwitch()
    }

    private func setupSwitch() {
        premiumSwitch.isOn = false
        premiumSwitch.translatesAutoresizingMaskIntoConstraints = false
        premiumSwitch.addTarget(self, action: #selector(switchChanged), for: .valueChanged)

        view.addSubview(premiumSwitch)

        NSLayoutConstraint.activate([
            premiumSwitch.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            premiumSwitch.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])
    }

    @objc private func switchChanged() {
        print("Switch value: \(premiumSwitch.isOn)")
    }
}

14. UISlider

UISlider is used to select a value from a range.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import UIKit

class SliderViewController: UIViewController {

    private let slider = UISlider()
    private let valueLabel = UILabel()

    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = .systemBackground
        setupUI()
    }

    private func setupUI() {
        valueLabel.text = "Value: 50"
        valueLabel.textAlignment = .center

        slider.minimumValue = 0
        slider.maximumValue = 100
        slider.value = 50
        slider.addTarget(self, action: #selector(sliderChanged), for: .valueChanged)

        let stackView = UIStackView(arrangedSubviews: [valueLabel, slider])
        stackView.axis = .vertical
        stackView.spacing = 16
        stackView.translatesAutoresizingMaskIntoConstraints = false

        view.addSubview(stackView)

        NSLayoutConstraint.activate([
            stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])
    }

    @objc private func sliderChanged() {
        valueLabel.text = "Value: \(Int(slider.value))"
    }
}

15. UISegmentedControl

UISegmentedControl is used to switch between multiple options.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import UIKit

class SegmentViewController: UIViewController {

    private let segmentedControl = UISegmentedControl(items: ["Free", "Pro", "Ultra"])

    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = .systemBackground
        setupSegment()
    }

    private func setupSegment() {
        segmentedControl.selectedSegmentIndex = 0
        segmentedControl.addTarget(self, action: #selector(segmentChanged), for: .valueChanged)
        segmentedControl.translatesAutoresizingMaskIntoConstraints = false

        view.addSubview(segmentedControl)

        NSLayoutConstraint.activate([
            segmentedControl.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            segmentedControl.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            segmentedControl.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])
    }

    @objc private func segmentChanged() {
        print("Selected index: \(segmentedControl.selectedSegmentIndex)")
    }
}

16. UIScrollView

UIScrollView is used when content is larger than the screen.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import UIKit

class ScrollViewController: UIViewController {

    private let scrollView = UIScrollView()
    private let contentView = UIView()

    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = .systemBackground
        setupScrollView()
        setupContent()
    }

    private func setupScrollView() {
        scrollView.translatesAutoresizingMaskIntoConstraints = false
        contentView.translatesAutoresizingMaskIntoConstraints = false

        view.addSubview(scrollView)
        scrollView.addSubview(contentView)

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

            contentView.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor),
            contentView.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor),
            contentView.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor),
            contentView.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor),

            contentView.widthAnchor.constraint(equalTo: scrollView.frameLayoutGuide.widthAnchor)
        ])
    }

    private func setupContent() {
        let label = UILabel()
        label.text = String(repeating: "UIKit ScrollView Example\n", count: 50)
        label.numberOfLines = 0
        label.translatesAutoresizingMaskIntoConstraints = false

        contentView.addSubview(label)

        NSLayoutConstraint.activate([
            label.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 20),
            label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
            label.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20),
            label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -20)
        ])
    }
}

17. UITableView

UITableView is used to show vertical lists.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import UIKit

class UserListViewController: UIViewController {

    private let tableView = UITableView()
    private let users = ["Tawheed", "John", "Sarah", "Alex"]

    override func viewDidLoad() {
        super.viewDidLoad()

        title = "Users"
        view.backgroundColor = .systemBackground
        setupTableView()
    }

    private func setupTableView() {
        tableView.dataSource = self
        tableView.delegate = self
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "UserCell")
        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)
        ])
    }
}

extension UserListViewController: UITableViewDataSource, UITableViewDelegate {

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

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

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

18. UITableView Custom Cell

Custom cells are used when each row needs custom design.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import UIKit

class UserTableViewCell: UITableViewCell {

    static let identifier = "UserTableViewCell"

    private let nameLabel = UILabel()
    private let subtitleLabel = UILabel()

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)

        setupUI()
    }

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

    private func setupUI() {
        nameLabel.font = .systemFont(ofSize: 18, weight: .bold)
        subtitleLabel.font = .systemFont(ofSize: 14)
        subtitleLabel.textColor = .secondaryLabel

        let stackView = UIStackView(arrangedSubviews: [nameLabel, subtitleLabel])
        stackView.axis = .vertical
        stackView.spacing = 4
        stackView.translatesAutoresizingMaskIntoConstraints = false

        contentView.addSubview(stackView)

        NSLayoutConstraint.activate([
            stackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 12),
            stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
            stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
            stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -12)
        ])
    }

    func configure(name: String, subtitle: String) {
        nameLabel.text = name
        subtitleLabel.text = subtitle
    }
}

Usage:

1
tableView.register(UserTableViewCell.self, forCellReuseIdentifier: UserTableViewCell.identifier)
1
2
3
4
5
6
7
8
let cell = tableView.dequeueReusableCell(
    withIdentifier: UserTableViewCell.identifier,
    for: indexPath
) as! UserTableViewCell

cell.configure(name: "Tawheed", subtitle: "iOS Developer")

return cell

19. UICollectionView

UICollectionView is used for grid layouts and advanced list designs.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
import UIKit

class GridViewController: UIViewController {

    private var collectionView: UICollectionView!
    private let items = Array(1...20)

    override func viewDidLoad() {
        super.viewDidLoad()

        title = "Grid"
        view.backgroundColor = .systemBackground
        setupCollectionView()
    }

    private func setupCollectionView() {
        let layout = UICollectionViewFlowLayout()
        layout.itemSize = CGSize(width: 100, height: 100)
        layout.minimumLineSpacing = 12
        layout.minimumInteritemSpacing = 12

        collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
        collectionView.backgroundColor = .systemBackground
        collectionView.dataSource = self
        collectionView.delegate = self
        collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "GridCell")
        collectionView.translatesAutoresizingMaskIntoConstraints = false

        view.addSubview(collectionView)

        NSLayoutConstraint.activate([
            collectionView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
            collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
            collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])
    }
}

extension GridViewController: UICollectionViewDataSource, UICollectionViewDelegate {

    func collectionView(
        _ collectionView: UICollectionView,
        numberOfItemsInSection section: Int
    ) -> Int {
        items.count
    }

    func collectionView(
        _ collectionView: UICollectionView,
        cellForItemAt indexPath: IndexPath
    ) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(
            withReuseIdentifier: "GridCell",
            for: indexPath
        )

        cell.backgroundColor = .systemBlue
        cell.layer.cornerRadius = 12

        return cell
    }
}

20. UINavigationController

UINavigationController manages screen-to-screen navigation.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import UIKit

class HomeViewController: UIViewController {

    private let button = UIButton(type: .system)

    override func viewDidLoad() {
        super.viewDidLoad()

        title = "Home"
        view.backgroundColor = .systemBackground
        setupButton()
    }

    private func setupButton() {
        button.setTitle("Go to Details", for: .normal)
        button.addTarget(self, action: #selector(openDetails), for: .touchUpInside)
        button.translatesAutoresizingMaskIntoConstraints = false

        view.addSubview(button)

        NSLayoutConstraint.activate([
            button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            button.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])
    }

    @objc private func openDetails() {
        let detailsVC = DetailsViewController()
        navigationController?.pushViewController(detailsVC, animated: true)
    }
}

class DetailsViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        title = "Details"
        view.backgroundColor = .systemBackground
    }
}

Scene setup example:

1
2
3
4
5
let homeVC = HomeViewController()
let navigationController = UINavigationController(rootViewController: homeVC)

window?.rootViewController = navigationController
window?.makeKeyAndVisible()

21. UITabBarController

UITabBarController is used for bottom tab navigation.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import UIKit

class MainTabBarController: UITabBarController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let homeVC = UINavigationController(rootViewController: HomeViewController())
        homeVC.tabBarItem = UITabBarItem(
            title: "Home",
            image: UIImage(systemName: "house"),
            tag: 0
        )

        let settingsVC = UINavigationController(rootViewController: SettingsViewController())
        settingsVC.tabBarItem = UITabBarItem(
            title: "Settings",
            image: UIImage(systemName: "gear"),
            tag: 1
        )

        viewControllers = [homeVC, settingsVC]
    }
}

class SettingsViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        title = "Settings"
        view.backgroundColor = .systemBackground
    }
}

22. UIAlertController

UIAlertController is used to show alert dialogs.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import UIKit

class AlertViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = .systemBackground
    }

    private func showAlert() {
        let alert = UIAlertController(
            title: "Delete File?",
            message: "Are you sure you want to delete this file?",
            preferredStyle: .alert
        )

        alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
        alert.addAction(UIAlertAction(title: "Delete", style: .destructive) { _ in
            print("File deleted")
        })

        present(alert, animated: true)
    }
}

23. Action Sheet

Action sheet shows options from the bottom.

1
2
3
4
5
6
7
8
9
10
11
let actionSheet = UIAlertController(
    title: "Choose Option",
    message: nil,
    preferredStyle: .actionSheet
)

actionSheet.addAction(UIAlertAction(title: "Camera", style: .default))
actionSheet.addAction(UIAlertAction(title: "Gallery", style: .default))
actionSheet.addAction(UIAlertAction(title: "Cancel", style: .cancel))

present(actionSheet, animated: true)

24. Modal Presentation

Modal presentation opens a screen over the current screen.

1
2
3
4
let detailsVC = DetailsViewController()
detailsVC.modalPresentationStyle = .pageSheet

present(detailsVC, animated: true)

Dismiss modal:

1
dismiss(animated: true)

25. Passing Data Between Screens

Data can be passed by setting a property before navigation.

1
2
3
4
5
6
7
8
9
10
11
class DetailsViewController: UIViewController {

    var username: String?

    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = .systemBackground
        print(username ?? "No username")
    }
}

Pass data:

1
2
3
4
let detailsVC = DetailsViewController()
detailsVC.username = "Tawheed"

navigationController?.pushViewController(detailsVC, animated: true)

26. Closure Callback

Closure callback is useful for sending data back.

1
2
3
4
5
6
7
8
9
class EditProfileViewController: UIViewController {

    var onNameUpdated: ((String) -> Void)?

    private func saveName() {
        onNameUpdated?("Tawheed")
        navigationController?.popViewController(animated: true)
    }
}

Use callback:

1
2
3
4
5
6
7
let editVC = EditProfileViewController()

editVC.onNameUpdated = { updatedName in
    print("Updated name: \(updatedName)")
}

navigationController?.pushViewController(editVC, animated: true)

27. Delegate Pattern

Delegate pattern allows one object to send events to another object.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
protocol ProfileUpdateDelegate: AnyObject {
    func didUpdateName(_ name: String)
}

class EditProfileViewController: UIViewController {

    weak var delegate: ProfileUpdateDelegate?

    private func saveName() {
        delegate?.didUpdateName("Tawheed")
        navigationController?.popViewController(animated: true)
    }
}

class ProfileViewController: UIViewController, ProfileUpdateDelegate {

    func didUpdateName(_ name: String) {
        print("Name updated: \(name)")
    }
}

Important

Use weak for delegate to avoid memory leaks.


28. Gesture Recognizer

Gesture recognizers detect taps, swipes, pinches, and long press actions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import UIKit

class GestureViewController: UIViewController {

    private let boxView = UIView()

    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = .systemBackground
        setupBoxView()
    }

    private func setupBoxView() {
        boxView.backgroundColor = .systemBlue
        boxView.layer.cornerRadius = 16
        boxView.translatesAutoresizingMaskIntoConstraints = false

        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(boxTapped))
        boxView.addGestureRecognizer(tapGesture)
        boxView.isUserInteractionEnabled = true

        view.addSubview(boxView)

        NSLayoutConstraint.activate([
            boxView.widthAnchor.constraint(equalToConstant: 160),
            boxView.heightAnchor.constraint(equalToConstant: 160),
            boxView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            boxView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])
    }

    @objc private func boxTapped() {
        print("Box tapped")
    }
}

29. Animation

UIKit supports animations using UIView.animate.

1
2
3
UIView.animate(withDuration: 0.3) {
    self.view.backgroundColor = .systemBlue
}

Example with transform:

1
2
3
UIView.animate(withDuration: 0.3) {
    self.button.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
}

Reset:

1
2
3
UIView.animate(withDuration: 0.3) {
    self.button.transform = .identity
}

30. UIActivityIndicatorView

UIActivityIndicatorView shows loading state.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import UIKit

class LoadingViewController: UIViewController {

    private let activityIndicator = UIActivityIndicatorView(style: .large)

    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = .systemBackground
        setupLoader()
    }

    private func setupLoader() {
        activityIndicator.translatesAutoresizingMaskIntoConstraints = false
        activityIndicator.startAnimating()

        view.addSubview(activityIndicator)

        NSLayoutConstraint.activate([
            activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])
    }
}

31. Pull to Refresh

Pull to refresh is commonly used with table views.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private let refreshControl = UIRefreshControl()

private func setupRefreshControl() {
    refreshControl.addTarget(self, action: #selector(refreshData), for: .valueChanged)
    tableView.refreshControl = refreshControl
}

@objc private func refreshData() {
    print("Refreshing data")

    DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
        self.refreshControl.endRefreshing()
    }
}

32. UserDefaults

UserDefaults stores small local data like settings.

1
2
3
4
5
UserDefaults.standard.set(true, forKey: "isPremiumUser")

let isPremium = UserDefaults.standard.bool(forKey: "isPremiumUser")

print(isPremium)

Use UserDefaults for:

  • Theme preference
  • Login flag
  • Simple app settings
  • Small values

Do not store sensitive tokens or passwords in UserDefaults.


33. URLSession API Call

URLSession is used for network requests.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import Foundation

struct Post: Codable {
    let id: Int
    let title: String
}

func fetchPosts() async throws -> [Post] {
    let url = URL(string: "https://jsonplaceholder.typicode.com/posts")!

    let (data, response) = try await URLSession.shared.data(from: url)

    guard let httpResponse = response as? HTTPURLResponse,
          httpResponse.statusCode == 200 else {
        throw URLError(.badServerResponse)
    }

    return try JSONDecoder().decode([Post].self, from: data)
}

Use in UIKit:

1
2
3
4
5
6
7
8
Task {
    do {
        let posts = try await fetchPosts()
        print(posts)
    } catch {
        print("Failed: \(error)")
    }
}

34. Keyboard Dismiss

Dismiss keyboard when tapping outside.

1
2
3
4
5
6
7
8
9
10
override func viewDidLoad() {
    super.viewDidLoad()

    let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
    view.addGestureRecognizer(tapGesture)
}

@objc private func dismissKeyboard() {
    view.endEditing(true)
}

35. Keyboard Handling

Move UI when keyboard appears.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
override func viewDidLoad() {
    super.viewDidLoad()

    NotificationCenter.default.addObserver(
        self,
        selector: #selector(keyboardWillShow),
        name: UIResponder.keyboardWillShowNotification,
        object: nil
    )

    NotificationCenter.default.addObserver(
        self,
        selector: #selector(keyboardWillHide),
        name: UIResponder.keyboardWillHideNotification,
        object: nil
    )
}

@objc private func keyboardWillShow(notification: Notification) {
    view.frame.origin.y = -100
}

@objc private func keyboardWillHide(notification: Notification) {
    view.frame.origin.y = 0
}

36. Dark Mode Support

Use system colors to support light and dark mode automatically.

1
2
3
4
view.backgroundColor = .systemBackground
label.textColor = .label
subtitleLabel.textColor = .secondaryLabel
button.backgroundColor = .systemBlue

Common adaptive colors:

1
2
3
4
5
6
.systemBackground
.secondarySystemBackground
.label
.secondaryLabel
.systemBlue
.systemGray

37. Accessibility

Accessibility helps all users use the app properly.

1
2
3
button.accessibilityLabel = "Continue"
button.accessibilityHint = "Double tap to continue to the next screen"
button.accessibilityTraits = .button

For image:

1
2
imageView.accessibilityLabel = "Profile picture"
imageView.isAccessibilityElement = true

38. Reusable Custom Button

Reusable components keep code clean.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import UIKit

class PrimaryButton: UIButton {

    init(title: String) {
        super.init(frame: .zero)

        setTitle(title, for: .normal)
        titleLabel?.font = .systemFont(ofSize: 18, weight: .bold)
        backgroundColor = .systemBlue
        tintColor = .white
        layer.cornerRadius = 12
        translatesAutoresizingMaskIntoConstraints = false

        heightAnchor.constraint(equalToConstant: 52).isActive = true
    }

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

Use:

1
2
let button = PrimaryButton(title: "Continue")
view.addSubview(button)

39. MVC in UIKit

UIKit traditionally uses MVC.

1
2
3
Model      = Data
View       = UI
Controller = Handles UI logic and user interaction

Example model:

1
2
3
4
struct User {
    let id: Int
    let name: String
}

Controller:

1
2
3
4
5
6
7
8
9
class UserViewController: UIViewController {
    private let user = User(id: 1, name: "Tawheed")

    override func viewDidLoad() {
        super.viewDidLoad()

        print(user.name)
    }
}

Problem:

1
ViewController can become too large if all logic is inside it.

40. MVVM in UIKit

MVVM helps keep UIKit code clean.

1
2
3
Model      = Data
View       = UIKit UI
ViewModel  = State and business logic

Model:

1
2
3
4
struct Product {
    let id: Int
    let name: String
}

ViewModel:

1
2
3
4
5
6
7
8
9
10
11
class ProductViewModel {

    private(set) var products: [Product] = []

    func loadProducts() {
        products = [
            Product(id: 1, name: "Monthly Plan"),
            Product(id: 2, name: "Yearly Plan")
        ]
    }
}

ViewController:

1
2
3
4
5
6
7
8
9
10
11
class ProductViewController: UIViewController {

    private let viewModel = ProductViewModel()

    override func viewDidLoad() {
        super.viewDidLoad()

        viewModel.loadProducts()
        print(viewModel.products)
    }
}

41. UIKit with SwiftUI

You can use SwiftUI inside UIKit using UIHostingController.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import UIKit
import SwiftUI

class SwiftUIHostViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let swiftUIView = Text("SwiftUI inside UIKit")
            .font(.title)

        let hostingController = UIHostingController(rootView: swiftUIView)

        addChild(hostingController)
        view.addSubview(hostingController.view)
        hostingController.didMove(toParent: self)

        hostingController.view.translatesAutoresizingMaskIntoConstraints = false

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

42. Storyboard

Storyboard is a visual way to design UIKit screens.

Storyboard can be useful for:

  • Simple apps
  • Quick UI design
  • Visual layout
  • Beginner learning

But many professional teams prefer programmatic UI because:

  • Easier Git collaboration
  • Easier code review
  • Better control
  • Easier reusable components
  • Better for large projects

43. XIB

XIB is a separate UI file for reusable views or cells.

Use XIB for:

  • Custom table cells
  • Reusable UI sections
  • Small reusable components

Programmatic UI is still often preferred in large modern projects.


44. Memory Management

UIKit uses classes heavily, so memory management is important.

Use [weak self] inside closures to avoid retain cycles.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class DownloadViewController: UIViewController {

    private var onComplete: (() -> Void)?

    override func viewDidLoad() {
        super.viewDidLoad()

        onComplete = { [weak self] in
            self?.showSuccess()
        }
    }

    private func showSuccess() {
        print("Download completed")
    }
}

45. Performance Tips

UIKit performance tips:

1
2
3
4
5
6
7
8
9
10
Do not block main thread.
Reuse table view and collection view cells.
Use lazy loading for images.
Use correct image sizes.
Avoid unnecessary layout updates.
Avoid too many nested views.
Use Instruments to detect memory leaks.
Use weak delegate references.
Cache expensive data.
Use background thread for heavy work.

46. UIKit Learning Order

Follow this order:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
1. UIViewController
2. View lifecycle
3. Programmatic UI
4. Auto Layout
5. Safe area
6. UILabel
7. UIButton
8. UIImageView
9. UITextField
10. UITextView
11. UIStackView
12. UIScrollView
13. UITableView
14. Custom table cell
15. UICollectionView
16. UINavigationController
17. UITabBarController
18. UIAlertController
19. Modal presentation
20. Passing data
21. Closure callback
22. Delegate pattern
23. Gesture recognizer
24. Animation
25. URLSession
26. UserDefaults
27. Keyboard handling
28. Dark mode
29. Accessibility
30. Reusable components
31. MVC
32. MVVM
33. UIKit + SwiftUI
34. Performance optimization
35. Memory management

Key Takeaways

1
2
3
4
5
6
7
8
9
10
11
12
13
UIKit is still very important for iOS development.
UIViewController controls one screen.
Auto Layout makes UI responsive.
UIStackView simplifies layout.
UITableView is used for vertical lists.
UICollectionView is used for grids and custom layouts.
UINavigationController handles screen navigation.
UITabBarController handles bottom tabs.
Delegate pattern is very common in UIKit.
Target-action is used for button and control events.
Use weak delegates to avoid memory leaks.
Use system colors for dark mode support.
Use MVVM to avoid massive ViewControllers.

Final Note

UIKit is powerful because it gives deep control over iOS UI.

1
2
3
4
5
6
7
8
9
10
11
Programmatic UI
Auto Layout
ViewController lifecycle
Navigation
TableView
CollectionView
Delegates
Reusable components
Memory management
Performance
Clean architecture
This post is licensed under CC BY 4.0 by the author.