UIKit Fundamentals
Irbisa · cheatsheetSeptember 13, 2026

UIKit Fundamentals

UIView, UIViewController lifecycle, view hierarchy, touch handling

Junior Developer20 itemscompressed for a skim
  1. 01

    What is UIKit? How does it relate to SwiftUI in modern iOS apps?

    Easy

    UIKit is Apple's imperative UI framework for iOS, iPadOS, tvOS and Catalyst — it has powered every iOS app since 2008.

    import UIKit
    
    class HomeViewController: UIViewController {
      override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .systemBackground
    …
  2. 02

    Walk through the UIViewController lifecycle: which callbacks fire once and which fire on every appearance?

    Easy

    Only viewDidLoad fires once per controller instance; the appear and disappear callbacks fire again every single time the screen comes back.

    class FeedViewController: UIViewController {
      override func viewDidLoad() {
        super.viewDidLoad()
        // One-time: build the view tree, register cells, set delegates
      }
    …
  3. 03

    What is the difference between frame and bounds in UIView?

    Easy

    A view's frame is measured in its superview's coordinate system, its bounds is the view's own coordinate system, and that difference is exactly what makes scrolling work.

    let v = UIView()
    v.frame = CGRect(x: 20, y: 100, width: 120, height: 60)
    // frame.origin = (20,100), bounds.origin = (0,0), bounds.size = (120,60)
    
    // Subview is positioned in v's coordinate system
    let inner = UIView(frame: CGRect(x: 10, y: 10, width: 20, height: 20))
    …
  4. 04

    When the user taps the screen, how does UIKit decide which view receives the touch?

    Medium

    Every touch is resolved in two passes: UIKit first hit-tests to find the view under the finger, then delivers the event up the responder chain starting from that view.

    class TouchView: UIView {
      override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        print("Touch began on \(self)")
        // Omit super to consume the touch; call super to forward it up the chain
        super.touchesBegan(touches, with: event)
      }
    …
  5. 05

    How do UITableView and UICollectionView work? What is reuse?

    Medium

    Both are scroll-based containers backed by a data source and a delegate.

    // Modern diffable data source for UICollectionView
    class FeedVC: UIViewController {
      enum Section { case main }
      struct Item: Hashable { let id: UUID; let title: String }
    
      var collectionView: UICollectionView!
    …
  6. 06

    What is the difference between Storyboards, XIBs, and programmatic UIs?

    Medium

    Storyboards, XIBs and code are three ways to build the same UIKit screen, and they differ mainly in how well they survive a team, a merge and a refactor.

    // Storyboard — load by identifier
    let sb = UIStoryboard(name: "Main", bundle: nil)
    let vc = sb.instantiateViewController(identifier: "DetailVC") as! DetailVC
    navigationController?.pushViewController(vc, animated: true)
    
    // XIB — instantiate a UIView subclass from its nib
    …
  7. 07

    Since scenes arrived, which lifecycle events belong to AppDelegate and which to SceneDelegate?

    Easy

    AppDelegate owns process-level events, while each UI instance is a UIWindowScene whose SceneDelegate owns the window and the foreground and background transitions.

    @main
    final class AppDelegate: UIResponder, UIApplicationDelegate {
      // Process level, once per launch: DI graph, analytics, push registration
      func application(_ app: UIApplication,
                       didFinishLaunchingWithOptions options: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        Services.bootstrap()
    …
  8. 08

    Every UIView owns a CALayer — what does the layer actually do, and when do you drop down to it?

    Medium

    The view handles touches, layout and the responder chain, while the layer it owns holds the bitmap that is actually drawn and animated.

    final class GradientButton: UIButton {
      // Back the view with the layer type you need instead of syncing frames in layoutSubviews
      override class var layerClass: AnyClass { CAGradientLayer.self }
      private var gradient: CAGradientLayer { layer as! CAGradientLayer }
    
      override init(frame: CGRect) {
    …
  9. 09

    What is the difference between pushing a view controller, presenting one, and embedding one as a child?

    Medium

    Pushing adds a controller to an existing navigation stack, presenting puts one on top modally regardless of any stack, and containment makes one controller a child that lives inside your own view.

    // Push: onto an existing navigation stack, with a back button and interactive pop
    navigationController?.pushViewController(DetailViewController(id: id), animated: true)
    navigationController?.popToRootViewController(animated: true)
    
    // Present: modally, above everything, independent of any stack
    let editor = UINavigationController(rootViewController: EditorViewController())
    …
  10. 10

    Why is a delegate property declared weak, and when would you choose a closure or a notification instead?

    Easy

    A delegate is a one-to-one callback contract, and the property is weak because the delegate almost always owns the object it delegates for — a strong reference back would close a retain cycle and leak both.

    protocol EditorDelegate: AnyObject {   // class-bound, so the property can be weak
      func editor(_ editor: EditorViewController, didSave draft: Draft)
      func editorDidCancel(_ editor: EditorViewController)
    }
    
    final class EditorViewController: UIViewController {
    …
  11. 11

    Which UIKit animation API do you reach for, and how do you animate an Auto Layout change?

    Medium

    Animating a constraint means changing its constant and then calling layoutIfNeeded() inside the animation block, because it is the layout pass — not the assignment — that gets animated.

    // Animating a constraint: change the constant, then lay out inside the block
    heightConstraint.constant = isExpanded ? 240 : 88
    UIView.animate(withDuration: 0.3) {
      self.view.layoutIfNeeded()   // the layout pass is what is actually being animated
    }
    …
  12. 12

    How do you put a UIKit view inside SwiftUI, and a SwiftUI view inside UIKit?

    Medium

    UIViewRepresentable wraps a UIKit view so SwiftUI can use it, and UIHostingController wraps a SwiftUI view so UIKit can use it.

    struct SearchBar: UIViewRepresentable {
      @Binding var text: String
    
      func makeUIView(context: Context) -> UISearchBar {
        let bar = UISearchBar()
        bar.delegate = context.coordinator     // the delegate lives on the coordinator
    …
  13. 13

    Your button's contentEdgeInsets do nothing and the font you set on titleLabel resets after a tap — what replaced that API?

    Easy

    Since iOS 15 a UIButton.Configuration describes the whole button as one value, and the button rebuilds itself from that value on every state change — which is why anything you poke directly onto titleLabel is overwritten and the edge-inset properties are deprecated.

    // iOS 15+: one value describes layout and appearance
    var config = UIButton.Configuration.filled()
    config.title = "Save"
    config.subtitle = "to your library"
    config.image = UIImage(systemName: "square.and.arrow.down")
    config.imagePlacement = .leading
    …
  14. 14

    An avatar comes out squashed and the photo spills past the rounded corners you set — which two view properties are wrong?

    Easy

    contentMode defaults to .scaleToFill, which stretches the image to the view's aspect ratio, and cornerRadius on its own rounds only the layer's background and border — the contents keep their square edges until clipping is on.

    // WRONG: the default .scaleToFill distorts, and cornerRadius alone does not cut the photo
    let avatar = UIImageView(image: photo)
    avatar.layer.cornerRadius = 24
    avatar.layer.shadowOpacity = 0.3
    avatar.clipsToBounds = true        // ...and now the shadow is clipped away as well
    …
  15. 15

    A label is set straight from a URLSession completion handler and the app later crashes somewhere unrelated — what is going on?

    Easy

    UIKit is not thread-safe and must be driven from the main thread, but a URLSession completion handler runs on the session's own delegate queue — so that assignment mutates the view tree from a background thread and the damage surfaces later, in code that is not guilty.

    // WRONG: URLSession calls back on its own queue, not the main one
    URLSession.shared.dataTask(with: url) { data, _, _ in
      self.titleLabel.text = String(decoding: data!, as: UTF8.self)   // UIKit off-main
    }.resume()
    
    // RIGHT, callback style: hop back for the UI work only
    …
  16. 16

    A pan on your draggable card fights the scroll view under it, and the single tap fires before the double tap — how do you arbitrate between recognizers?

    Medium

    Gesture recognizers are little state machines competing for the same touches, and by default only one of them may win: you order them with require(toFail:) and let them coexist with the delegate's shouldRecognizeSimultaneouslyWith.

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

    You need a table scrolled to today's row before the screen appears, but bounds are wrong in viewWillAppear and viewDidLayoutSubviews runs repeatedly — where does that code go?

    Medium

    In viewIsAppearing(_:) — the callback added in iOS 17 and back-deployed to iOS 13 for anything built with Xcode 15 or newer, which runs once per appearance, after the view is in the hierarchy with its real traits and geometry, and before the screen is shown.

    final class AgendaViewController: UIViewController {
      private let tableView = UITableView()
      private let header = UIView()
      private var didScrollToToday = false
      private var todayIndexPath = IndexPath(row: 0, section: 0)
    …
  18. 18

    Your view's border stays the light-mode colour after the user switches to dark, and traitCollectionDidChange is deprecated — what is the fix now?

    Medium

    A UIColor is a recipe that UIKit re-resolves every time it draws, but .cgColor is a photograph of one appearance taken at the moment you asked — so every layer-level colour has to be resolved again when the trait changes, and since iOS 17 you observe that with registerForTraitChanges(_:handler:).

    final class BadgeView: UIView {
      override init(frame: CGRect) {
        super.init(frame: frame)
    
        // WRONG: resolved once, against whichever style happened to be current
        layer.borderColor = UIColor.separator.cgColor
    …
  19. 19

    Users swipe your editor sheet down in the middle of typing and lose the draft — how do you take control of an interactive dismissal?

    Medium

    Set isModalInPresentation = true, or answer presentationControllerShouldDismiss(_:) dynamically, and then handle the refused swipe in presentationControllerDidAttemptToDismiss(_:) — that is where the "Discard draft?" choice belongs.

    final class InboxViewController: UIViewController, UIAdaptivePresentationControllerDelegate {
      private weak var editor: EditorViewController?
    
      func showEditor(_ draft: Draft) {
        let editor = EditorViewController(draft: draft)
        let nav = UINavigationController(rootViewController: editor)
    …
  20. 20

    VoiceOver reads your custom rating control as "star.fill, button" and never says the score — what do you set on the view?

    Medium

    The accessibility tree is derived from your view hierarchy, so a control you drew yourself has to describe itself: isAccessibilityElement, a human accessibilityLabel, an accessibilityValue for the part that changes, and the traits that say what kind of control it is.

    final class RatingControl: UIControl {
      private let stars = UIStackView()
      var rating: Int = 0 {
        didSet {
          updateStars()
          accessibilityValue = "\(rating) out of 5"   // update where the state changes
    …