Manan Patel

Tinkering and Sharing

📱 UIKit Fundamentals

Essential UIKit concepts and patterns for iOS development

schedule 7 min read calendar_today December 25, 2023

UIKit Basics

Understanding UIKit fundamentals is essential for iOS development, even in the SwiftUI era.

1. View Controllers

class UserViewController: UIViewController {
    @IBOutlet weak var tableView: UITableView!
    @IBOutlet weak var nameLabel: UILabel!
    
    private var users: [User] = []
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
        loadUsers()
    }
    
    private func setupUI() {
        title = "Users"
        navigationItem.rightBarButtonItem = UIBarButtonItem(
            barButtonSystemItem: .add,
            target: self,
            action: #selector(addUserTapped)
        )
        
        tableView.delegate = self
        tableView.dataSource = self
        tableView.register(UserCell.self, forCellReuseIdentifier: "UserCell")
    }
    
    @objc private func addUserTapped() {
        // Handle add user action
    }
    
    private func loadUsers() {
        // Load users from API
    }
}

2. Auto Layout

class CustomView: UIView {
    private let titleLabel = UILabel()
    private let subtitleLabel = UILabel()
    private let actionButton = UIButton(type: .system)
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupViews()
        setupConstraints()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func setupViews() {
        addSubview(titleLabel)
        addSubview(subtitleLabel)
        addSubview(actionButton)
        
        titleLabel.text = "Title"
        titleLabel.font = UIFont.boldSystemFont(ofSize: 18)
        
        subtitleLabel.text = "Subtitle"
        subtitleLabel.font = UIFont.systemFont(ofSize: 14)
        subtitleLabel.textColor = .gray
        
        actionButton.setTitle("Action", for: .normal)
        actionButton.backgroundColor = .systemBlue
        actionButton.setTitleColor(.white, for: .normal)
        actionButton.layer.cornerRadius = 8
    }
    
    private func setupConstraints() {
        titleLabel.translatesAutoresizingMaskIntoConstraints = false
        subtitleLabel.translatesAutoresizingMaskIntoConstraints = false
        actionButton.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            // Title label constraints
            titleLabel.topAnchor.constraint(equalTo: topAnchor, constant: 16),
            titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16),
            titleLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
            
            // Subtitle label constraints
            subtitleLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 8),
            subtitleLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16),
            subtitleLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
            
            // Action button constraints
            actionButton.topAnchor.constraint(equalTo: subtitleLabel.bottomAnchor, constant: 16),
            actionButton.centerXAnchor.constraint(equalTo: centerXAnchor),
            actionButton.widthAnchor.constraint(equalToConstant: 120),
            actionButton.heightAnchor.constraint(equalToConstant: 44),
            actionButton.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -16)
        ])
    }
}

3. Table Views

extension UserViewController: UITableViewDataSource, UITableViewDelegate {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return users.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "UserCell", for: indexPath) as! UserCell
        let user = users[indexPath.row]
        cell.configure(with: user)
        return cell
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
        let user = users[indexPath.row]
        // Navigate to user detail
    }
    
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 60
    }
}

class UserCell: UITableViewCell {
    private let nameLabel = UILabel()
    private let emailLabel = UILabel()
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        setupViews()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func setupViews() {
        contentView.addSubview(nameLabel)
        contentView.addSubview(emailLabel)
        
        nameLabel.font = UIFont.boldSystemFont(ofSize: 16)
        emailLabel.font = UIFont.systemFont(ofSize: 14)
        emailLabel.textColor = .gray
        
        // Setup constraints...
    }
    
    func configure(with user: User) {
        nameLabel.text = user.name
        emailLabel.text = user.email
    }
}

4. Navigation

class MainViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        setupNavigation()
    }
    
    private func setupNavigation() {
        // Programmatic navigation
        let userVC = UserViewController()
        let navController = UINavigationController(rootViewController: userVC)
        present(navController, animated: true)
        
        // Push navigation
        navigationController?.pushViewController(userVC, animated: true)
        
        // Modal presentation
        let modalVC = ModalViewController()
        modalVC.modalPresentationStyle = .pageSheet
        present(modalVC, animated: true)
    }
}

// Navigation delegate
extension MainViewController: UINavigationControllerDelegate {
    func navigationController(_ navigationController: UINavigationController, 
                            willShow viewController: UIViewController, 
                            animated: Bool) {
        // Handle navigation events
    }
}

5. Delegates and Protocols

protocol UserSelectionDelegate: AnyObject {
    func didSelectUser(_ user: User)
    func didDeselectUser(_ user: User)
}

class UserListViewController: UIViewController {
    weak var delegate: UserSelectionDelegate?
    private var selectedUsers: Set = []
    
    private func handleUserSelection(_ user: User) {
        if selectedUsers.contains(user) {
            selectedUsers.remove(user)
            delegate?.didDeselectUser(user)
        } else {
            selectedUsers.insert(user)
            delegate?.didSelectUser(user)
        }
    }
}

class ParentViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let userListVC = UserListViewController()
        userListVC.delegate = self
        addChild(userListVC)
        view.addSubview(userListVC.view)
        userListVC.didMove(toParent: self)
    }
}

extension ParentViewController: UserSelectionDelegate {
    func didSelectUser(_ user: User) {
        print("Selected user: \(user.name)")
    }
    
    func didDeselectUser(_ user: User) {
        print("Deselected user: \(user.name)")
    }
}

6. Best Practices

✅ Do's

  • Use weak references for delegates
  • Implement proper memory management
  • Use Auto Layout for responsive design
  • Follow MVC or MVVM patterns
  • Test your view controllers

❌ Don'ts

  • Don't retain delegates strongly
  • Don't perform heavy operations on main thread
  • Don't ignore memory warnings
  • Don't mix UI and business logic