Irbisa · Cheatsheet

UIKit Fundamentals

UIView, UIViewController lifecycle, view hierarchy, touch handling

Junior Developer · 25 questions

  1. 01

    What is UIWindow, how does the view hierarchy start, and how is a window different from an ordinary UIView?

    UIWindow is the root of a scene's view hierarchy: a UIView subclass with no superview that holds the rootViewController and receives events from UIApplication to pass to the views inside it.

  2. 02

    Walk through the UIViewController lifecycle: which callbacks run once, which run on every appearance, and where do you start loading data?

    loadView and viewDidLoad run once per controller instance; viewWillAppear, viewIsAppearing, viewDidAppear and the two disappear callbacks run every time the screen is shown or hidden.

  3. 03

    What is loadView, when do you override it, and how is it different from viewDidLoad?

    loadView creates the controller's root view and assigns it to view; viewDidLoad runs right after it and configures a view that already exists.

    final class ProfileView: UIView {  let nameLabel = UILabel()  // builds its own subviews and constraints in init} final class ProfileViewController: UIViewController {…
  4. 04

    What is the difference between frame and bounds, and when is bounds.origin not zero?

    frame is the view's position and size in its superview's coordinate system; bounds is the same rectangle in the view's own coordinates.

    let card = UIView(frame: CGRect(x: 20, y: 100, width: 120, height: 60))// card.frame  = (20, 100, 120, 60)  in the superview's coordinates// card.bounds = (0, 0, 120, 60)     in its own coordinates let dot = UIView(frame: CGRect(x: 10, y: 10, width: 20, height: 20))card.addSubview(dot)                 // (10, 10) inside card…
  5. 05

    What are the trade-offs between storyboards, XIBs and building the UI in code?

    All three produce the same UIKit views: storyboards and XIBs give you a visual editor, code gives readable diffs and far fewer merge conflicts.

  6. 06

    What is the difference between @IBOutlet and @IBAction, and how does target-action connect a control to your code?

    @IBOutlet is a property Interface Builder fills with a reference to a view, so code can read and change that view; @IBAction is a method Interface Builder connects to a control event, so the control can call your code.

    final class LoginViewController: UIViewController {  @IBOutlet private weak var emailField: UITextField!  @IBOutlet private weak var loginButton: UIButton!  private let retryButton = UIButton(type: .system)   @IBAction private func loginTapped(_ sender: UIButton) {…
  7. 07

    How does UINavigationController work, and what is the difference between pushing and presenting a view controller?

    UINavigationController manages a stack of view controllers: push adds one on top with a back button and swipe-back, pop removes it.

    // Push: onto the current navigation stacknavigationController?.pushViewController(DetailViewController(id: id), animated: true)navigationController?.popViewController(animated: true) // Present: modally, with its own navigation barlet editor = UINavigationController(rootViewController: EditorViewController())…
  8. 08

    How does UITableView work: what do the data source and the delegate do, and what is cell reuse?

    A table view asks its data source how many rows there are and for a cell for each visible row, and tells its delegate about selection, sizing and scrolling.

    final class ContactsViewController: UITableViewController {  private var names = ["Alice", "Bob", "Carol"]   override func viewDidLoad() {    super.viewDidLoad()    tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")…
  9. 09

    How does UIScrollView work: what are contentSize, contentOffset and contentInset?

    A scroll view is a view whose bounds.origin moves as the user drags: contentSize is how big the scrollable content is, contentOffset is which point of it sits at the top-left corner, and contentInset adds extra scrollable space around it.

    final class FeedViewController: UIViewController, UIScrollViewDelegate {  private let scrollView = UIScrollView()  private let header = UIView()   override func viewDidLoad() {    super.viewDidLoad()…
  10. 10

    What is the difference between UIImage and UIImageView, and between .scaleAspectFit and .scaleAspectFill?

    UIImage is image data (pixels and scale) that is not on screen; UIImageView is a view that shows it using contentMode.

  11. 11

    How do you round a view's corners and add a shadow, and why do the two conflict?

    Round with layer.cornerRadius plus clipsToBounds and cast the shadow with layer.shadowOpacity/shadowRadius/shadowOffset; clipping cuts the shadow off, so use two views: an outer one with the shadow and an inner one that clips.

    final class CardView: UIView {  let content = UIView()                 // inner view: clips to the rounded shape   override init(frame: CGRect) {    super.init(frame: frame)    layer.shadowColor = UIColor.black.cgColor…
  12. 12

    This label is updated from a URLSession callback and the app later crashes at random. What is wrong, and how do you fix it?

    func loadProfile() {  URLSession.shared.dataTask(with: profileURL) { data, _, _ in    guard let data else { return }    let profile = try? JSONDecoder().decode(Profile.self, from: data)    self.nameLabel.text = profile?.name    self.view.setNeedsLayout()  }.resume()}

    The dataTask completion handler runs on a background queue, and UIKit may only be used on the main thread.

  13. 13

    How do you support Dark Mode in UIKit, and why does a layer's border colour not switch along with it?

    Use dynamic colours (system semantic colours such as .label and .systemBackground, or asset catalog colours with a dark variant), and UIKit re-resolves them on every appearance change.

    final class BadgeView: UIView {  override init(frame: CGRect) {    super.init(frame: frame)    backgroundColor = .secondarySystemBackground   // dynamic: updates itself    layer.borderWidth = 1    updateBorder()…
  14. 14

    How do you animate a view in UIKit, and how do you animate an Auto Layout constraint change?

    Put the property changes inside UIView.animate(withDuration:animations:).

    // Expand a panel by changing its height constraintview.layoutIfNeeded()                        // settle pending layout firstheightConstraint.constant = isExpanded ? 240 : 88UIView.animate(withDuration: 0.3) {  self.view.layoutIfNeeded()                 // the layout pass is animated}…
  15. 15

    What is viewIsAppearing(_:), and why is it a better place for geometry-dependent setup than viewWillAppear or viewDidLayoutSubviews?

    viewIsAppearing runs once per appearance, after the view is in the hierarchy with its final size and traits but before it is on screen.

    final class AgendaViewController: UIViewController {  private let tableView = UITableView()  private var todayIndexPath = IndexPath(row: 0, section: 0)   // wrong: scrolling in viewWillAppear, bounds and traits may be stale  override func viewWillAppear(_ animated: Bool) {…
  16. 16

    Find the bug: after a few visits to the cart screen, a single cart change is logged to analytics several times.

    final class CartViewController: UIViewController {  override func viewWillAppear(_ animated: Bool) {    super.viewWillAppear(animated)    NotificationCenter.default.addObserver(      self, selector: #selector(cartDidChange),      name: .cartDidChange, object: nil)    reloadBadge()  }   @objc private func cartDidChange() {    reloadBadge()    analytics.log("cart_changed")  }}

    The observer is added in viewWillAppear, which runs on every appearance, and is never removed.

  17. 17

    How do you pass data forward to the next view controller, and back to the previous one?

    Forward, give the next controller what it needs before showing it, ideally through its init.

    struct Item { var title: String } final class EditViewController: UIViewController {  private var item: Item  var onSave: ((Item) -> Void)?              // back: a callback the parent sets…
  18. 18

    What is view controller containment, and which calls do you make to add and remove a child controller?

    Containment lets one view controller embed another's view inside its own while the child stays a real controller with its own lifecycle.

    extension UIViewController {  func embed(_ child: UIViewController, in container: UIView) {    addChild(child)                                   // 1. relationship    child.view.translatesAutoresizingMaskIntoConstraints = false    container.addSubview(child.view)                  // 2. view    NSLayoutConstraint.activate([…
  19. 19

    What is the responder chain, and how does hitTest(_:with:) decide which view receives a touch?

    UIKit first hit-tests from the window down to find the deepest view under the finger and delivers the touch there; whatever that view does not handle travels up the responder chain: superview, view controller, window, application.

    // Enlarge a small button's tap area without changing its sizefinal class BigTapButton: UIButton {  override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {    bounds.insetBy(dx: -12, dy: -12).contains(point)  }}…
  20. 20

    How do gesture recognizers work, and how do you make a single tap wait for a double tap or a pan work alongside a scroll view?

    A gesture recognizer is a state machine attached to a view: it watches the touches and calls its action when it recognizes its gesture.

    final class CardViewController: UIViewController, UIGestureRecognizerDelegate {  private let scrollView = UIScrollView()  private let card = UIView()  private var dragStart: CGPoint = .zero  override func viewDidLoad() {    super.viewDidLoad()…
  21. 21

    What is the difference between UIView and CALayer, and when do you work with a layer directly?

    Every UIView is backed by a CALayer: the view handles touches, layout, the responder chain and accessibility, while the layer holds the content that Core Animation draws and animates.

    final class GradientView: UIView {  // The backing layer itself is a gradient: no frame syncing needed  override class var layerClass: AnyClass { CAGradientLayer.self }  private var gradient: CAGradientLayer { layer as! CAGradientLayer }   override init(frame: CGRect) {…
  22. 22

    What is the difference between setNeedsLayout and setNeedsDisplay, and when does draw(_:) run?

    setNeedsLayout marks a view for a new layout pass, so layoutSubviews runs; setNeedsDisplay marks its content as stale, so draw(_:) runs to redraw it.

    final class ProgressRingView: UIView {  var progress: CGFloat = 0 {    didSet { setNeedsDisplay() }          // content changed: redraw once  }   override init(frame: CGRect) {…
  23. 23

    How does the main run loop work, and why does a Timer stop firing while the user scrolls?

    The main run loop is the main thread's event loop: it handles touches, timers and queued blocks, then commits layout and drawing.

    // wrong: scheduledTimer registers for .default only, so it pauses while scrolling// Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in ... } // right: register for .common, which includes .tracking// tick() is a method of the owning controllerlet countdown = Timer(timeInterval: 1, repeats: true) { [weak self] _ in…
  24. 24

    This controller is dismissed 3 seconds after it opens. What prints, when, and is this a memory leak?

    final class DetailViewController: UIViewController {  private let statusLabel = UILabel()   override func viewDidLoad() {    super.viewDidLoad()    DispatchQueue.main.asyncAfter(deadline: .now() + 5) {      self.statusLabel.text = "Loaded"      print("closure ran")    }  }   deinit { print("deinit") }}

    Nothing prints at 3 seconds.

  25. 25

    Find the bug: while the user scrolls fast, rows briefly show another user's avatar, and sometimes the wrong one stays.

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {  let cell = tableView.dequeueReusableCell(withIdentifier: "Avatar", for: indexPath) as! AvatarCell  let user = users[indexPath.row]  cell.nameLabel.text = user.name  Task {    let image = await imageLoader.image(for: user.avatarURL)    cell.avatarView.image = image  }  return cell}

    The Task captures the cell, not the row.