UI Development Concepts: How Objective-C Drives Apple's Interfaces

Get oriented in how Objective-C code interacts with Apple's platform UI — UIView, UIViewController, UIApplication, UIWindow, controls, delegates, target-action, Auto Layout, the view lifecycle, navigation, table and collection views, gestures, notifications, and accessibility — without attempting to teach full application development, and understand Objective-C's real interoperability and legacy/production relevance today.

This lesson is an orientation, not a complete UI-development course — building a full iOS or macOS application is a large enough subject to fill a course of its own, and that is intentionally out of scope here. What this lesson does instead is give you a map: the core UIKit concepts you will run into constantly if you read, maintain, or extend an existing Objective-C application, with pointers to where this course covers several of these concepts in the depth they deserve, either earlier (delegation, target-action, notifications) or later (the view controller lifecycle in the very next lesson, Auto Layout, table and collection views, and accessibility in later modules). By the end of this lesson you should be able to read a typical UIKit-based Objective-C source file and recognize what each major moving part is doing, even if you have not yet built a complete app around it.

UIView: The Base Visual Building Block

UIView is the fundamental class for anything that appears on screen in a UIKit application. Every visible element — a button, a label, a text field, a custom-drawn shape, an entire screen's background — either is a UIView directly or is a subclass of UIView. A UIView has a frame (its position and size in its superview's coordinate system) and a bounds (its position and size in its own coordinate system, which matters once you start dealing with rotation or scaling), and views nest inside one another to form a view hierarchy: a tree where each view has zero or more subviews and at most one superview.

Adding a view to that hierarchy is done with addSubview:, and removing one is done with removeFromSuperview. Because the hierarchy is a tree, operations like layout, rendering, and hit-testing (figuring out which view a touch landed on) all naturally recurse down through it — a superview's frame constrains where its subviews can meaningfully be positioned, and touches are routed starting from the top of the hierarchy down to the most specific view under the touch point. As mentioned in the previous lesson, every UIView is backed by a CALayer from Core Animation, which is what actually handles the GPU-composited rendering you see on screen.

UIViewController: Managing a Screen

A UIViewController manages one screen's worth of content: it owns a single root view (accessible as its .view property, itself a UIView with whatever subviews make up that screen), and it is responsible for that view's lifecycle — creating it, responding as it appears and disappears, and cleaning up as needed. Where UIView is purely about visual presentation and layout, UIViewController is about the logic and lifecycle that sits behind a screen: loading data to display, responding to user interaction, and coordinating with other view controllers as the user navigates between screens.

This lesson only previews UIViewController's role; the very next lesson in this module, 'View Controller Lifecycle,' walks through its lifecycle methods — loadView, viewDidLoad, viewWillAppear, viewDidAppear, and their disappearance counterparts — in full depth, including what work belongs at each stage and the common mistakes developers make around them. For now, the important thing to internalize is simply that a view controller is the object responsible for a screen, distinct from the view objects that make up that screen's actual visual content.

UIApplication and UIWindow: The App's Root Objects

UIApplication is a singleton object — there is exactly one instance per running app, accessed via [UIApplication sharedApplication] — representing the application itself at the system level. It is the object the system talks to for app-wide events: entering the background, receiving a remote notification, handling a URL the app was opened with, and so on. Most application code in a modern, well-structured app delegates this responsibility further to an app delegate object and, on modern iOS versions, a scene delegate, but UIApplication remains the root object representing 'this running app' from the operating system's point of view.

UIWindow is the root container for a view hierarchy — the top of the tree that a UIViewController's view, and everything beneath it, ultimately hangs from. In a typical single-window iOS app, there is one UIWindow, which holds a rootViewController, whose .view becomes the top of the visible view hierarchy. Swapping out a UIWindow's rootViewController is one of the standard ways to switch between major sections of an app — for example, switching from a login screen to the app's main interface once authentication succeeds.

Controls, Delegates, and Target-Action

Controls are the interactive UIView subclasses users directly manipulate: UIButton, UISwitch, UISlider, UITextField, UISegmentedControl, and others, all inheriting from the common UIControl base class. Controls are how user intent — a tap, a toggle, a drag — becomes something your code can respond to, and UIKit gives you two complementary mechanisms for that: delegation and target-action, both of which this course already covered as dedicated topics in earlier modules, so this lesson does not re-teach either from scratch.

As a brief reminder of how each applies here: target-action is the mechanism controls themselves use — you call addTarget:action:forControlEvents: on a UIButton, say, to say 'when this button is tapped, send this action message to this target object,' and UIKit handles routing the actual touch event into that specific method call. Delegation, by contrast, is how more complex components like UITextField, UITableView, and UICollectionView (covered later in this module) hand control back to your code at well-defined moments — 'the user is about to begin editing this text field, do you want to allow it?' — via a delegate object implementing an appropriate protocol.

Auto Layout, View Lifecycle, and Navigation

Auto Layout is UIKit's system for describing a view's position and size in terms of constraints relative to other views and to the screen, rather than fixed pixel coordinates, so that the same interface adapts correctly across the wide range of screen sizes, orientations, and dynamic type sizes Apple devices support. This lesson only previews Auto Layout's existence; a later module in this course covers constraints, layout anchors, and content-hugging/compression-resistance priorities in the depth the subject deserves.

The view lifecycle — the sequence a view controller's view goes through from creation to being displayed to eventually being torn down — is likewise only previewed here, since the very next lesson in this module is dedicated entirely to walking through it. Navigation refers to moving between screens: the most common pattern in UIKit is UINavigationController, a container view controller that manages a stack of other view controllers, letting you 'push' a new screen onto the stack (typically sliding in from the right, with an automatic back button) and 'pop' back to the previous one. This lesson mentions navigation only briefly, as one of the standard structural patterns you'll recognize in real UIKit code, without building out a full multi-screen navigation flow here.

Table Views, Collection Views, and Reusable Cells

UITableView displays a scrollable, single-column list of rows, and UICollectionView generalizes that idea to arbitrary, often multi-column or grid-based layouts — both are extremely common in real apps for displaying any list or grid of data, from a settings screen to a photo grid to a social media feed. Both are built around a crucial performance idea: reusable cells. Rather than creating a new view object for every single row or item — which would be wasteful for a list of, say, ten thousand items when only a dozen or so are ever visible on screen at once — table and collection views maintain a small pool of reusable cell objects, recycling a cell that has scrolled off screen to display new content scrolling on screen, via a dequeue-and-configure pattern.

This lesson only previews table views, collection views, and cell reuse; a later module dedicated to 'Tables and Collections' covers the full data-source and delegate protocols, cell registration, and the dequeuing pattern itself in real depth, with working examples. For now, recognize table views and collection views as UIKit's standard tools for displaying any list-like or grid-like data efficiently, no matter how large that data set is.

Gestures, Notifications, and Accessibility

Gestures — taps, swipes, pinches, long presses, pans, and rotations — are recognized through the UIGestureRecognizer family of classes (UITapGestureRecognizer, UISwipeGestureRecognizer, UIPinchGestureRecognizer, and others), which you attach to a view and configure with a target-action pair, much like a control's target-action, so your code is called only when the specific gesture pattern is actually recognized rather than having to interpret raw touch events yourself. This lesson mentions gestures only briefly, as another example of UIKit's general pattern of turning low-level input events into higher-level, named occurrences your code responds to.

Notifications, in the UIKit context, refers to the same NSNotificationCenter mechanism this course already covered as a dedicated topic in an earlier module — UIKit itself posts many system notifications your code can observe, such as UIKeyboardWillShowNotification when the on-screen keyboard is about to appear, using exactly the observer-registration pattern you already learned. This lesson does not re-teach Notification Center here; refer back to that earlier lesson for the full mechanism.

Accessibility is UIKit's support for making an interface usable by people with visual, motor, auditory, or cognitive disabilities — most centrally through VoiceOver, Apple's screen reader, which relies on views exposing accessibility labels, traits, and hints describing what they are and how to interact with them. This lesson only previews accessibility's existence as a first-class UIKit concern woven throughout every control and view; a later module in this course covers accessibility APIs and practices in the depth the subject deserves.

Framing: Objective-C Is Not Required for Modern Apple UI Development

It is important to be explicit about something this lesson has been careful not to imply: Objective-C is not required for modern Apple UI development, and this course does not claim otherwise. Swift, and increasingly SwiftUI (a declarative UI framework quite different in style from UIKit), are the current default choice for new app development on Apple platforms, and have been Apple's primary recommendation for new projects for years now. Nothing in this lesson's coverage of UIView, UIViewController, controls, or any other UIKit concept should be read as an argument that Objective-C is somehow necessary to build apps today.

Where Objective-C remains essential is interoperability and legacy/production relevance. Countless existing applications — including plenty still under active development and maintenance at real companies — are substantially written in Objective-C, sometimes many hundreds of thousands of lines of it, and rewriting a mature, working app from scratch in Swift purely for its own sake is rarely a good use of engineering time; instead, Objective-C and Swift code coexist in the same project via Apple's bidirectional interoperability, with new features often added in Swift while existing Objective-C code continues to run unchanged. Beyond that, a substantial number of third-party libraries and SDKs — some quite widely used — still expose Objective-C APIs (or C APIs consumed the way this course's SQLite lessons showed), whether because they predate Swift, prioritize broad compatibility, or simply haven't been rewritten.

The practical upshot: understanding how Objective-C drives UIKit — the concepts this lesson has surveyed — is not a historical curiosity. It's what lets you read, debug, extend, and interoperate with the very large body of real, running, production Objective-C UI code that exists today and will continue to exist for a long time to come, even in an industry where Swift and SwiftUI are, correctly, the default choice for greenfield app development.

Code Examples

This view controller builds a small view hierarchy inside viewDidLoad: a UILabel and a UIButton, both added as subviews of self.view. The button is wired using target-action — addTarget:action:forControlEvents: tells UIKit to call continueTapped: on self whenever the button registers a touch-up-inside event. This is the standard pattern for handling a control tap, and it relies on the target-action mechanism covered in an earlier module.

[continueButton addTarget:self action:@selector(continueTapped:) forControlEvents:UIControlEventTouchUpInside];

This example shows delegation in action — LoginViewController adopts UITextFieldDelegate and sets itself as usernameField's delegate, so UIKit calls textFieldShouldReturn: when the user taps the keyboard's return key. Inside that callback, self.navigationController pushViewController:animated: demonstrates UINavigationController-style navigation, pushing a new HomeViewController screen onto the navigation stack. self.navigationController is nil unless this view controller is currently embedded in a UINavigationController's stack — a common real-world bug is calling pushViewController: on a view controller that was presented modally instead, where navigationController is nil and the push silently does nothing.

[self.navigationController pushViewController:home animated:YES];

UIImageView does not respond to touches by default, so userInteractionEnabled is turned on and a UITapGestureRecognizer is attached with a target-action pair, following the same pattern as a control. The accessibility properties — isAccessibilityElement, accessibilityLabel, and accessibilityHint — are what let VoiceOver describe this otherwise-silent image view meaningfully to a user relying on it, previewing the accessibility concepts a later module covers fully.

[avatarView addGestureRecognizer:tap];

Common mistake: Forgetting userInteractionEnabled = YES on a UIImageView (it defaults to NO) is a common reason a gesture recognizer silently never fires on an image view, unlike UIButton and other UIControl subclasses which are interactive by default.

Confusing a UIView with a UIViewController, treating them as interchangeable.

A UIView is a visual element; a UIViewController manages one screen's root view and its lifecycle, logic, and navigation — distinct responsibilities that are easy to conflate when first learning UIKit.

This distinction matters constantly in real code: you add subviews to a view, but you present or push view controllers, and lifecycle methods like viewDidLoad belong to the controller, not the view itself.

Calling self.navigationController pushViewController:animated: on a view controller that isn't inside a navigation stack.

Check that the view controller is actually embedded in a UINavigationController (for example, by confirming the app's window or a container sets one up) before relying on self.navigationController being non-nil.

self.navigationController is nil for a view controller presented modally or otherwise not embedded in a navigation stack, and calling a method through a nil pointer in Objective-C silently does nothing rather than crashing, which can make this bug quietly invisible during testing.

Attaching a gesture recognizer to a UIImageView (or a similarly non-interactive view) without enabling userInteractionEnabled.

Set userInteractionEnabled = YES on any plain UIView or UIImageView before expecting a gesture recognizer attached to it to fire.

UIControl subclasses like UIButton are interactive by default, but plain UIView and UIImageView are not, and a gesture recognizer attached to a non-interactive view never receives touches.

Assuming Objective-C is a prerequisite for building a modern iOS app.

Swift and SwiftUI are the current default for new Apple app development; Objective-C's ongoing relevance is chiefly interoperability with existing large codebases and libraries that still expose Objective-C APIs.

This lesson's closing section makes this framing explicit precisely because it's a common misconception — treating everything covered here as evidence that Objective-C is 'still required' rather than as the interoperability and legacy-maintenance skill set it actually is.

What is the core architectural difference between UIView and UIViewController? A. UIView manages navigation between screens, while UIViewController only draws pixels B. UIView is a visual element in the view hierarchy, while UIViewController manages a screen's root view, its lifecycle, and related logic C. They are two names for the same class D. UIViewController is only used on macOS, and UIView is only used on iOS

B. UIView is a visual element in the view hierarchy, while UIViewController manages a screen's root view, its lifecycle, and related logic — UIView objects make up the visible hierarchy (labels, buttons, containers), while a UIViewController owns and manages one screen's root view along with its lifecycle and the logic behind that screen.

Which mechanism does a UIButton typically use to notify your code when it's tapped? A. Notification Center B. Target-action, via addTarget:action:forControlEvents: C. Key-value observing D. Delegation exclusively

B. Target-action, via addTarget:action:forControlEvents: — UIControl subclasses like UIButton use target-action: you register a target object and an action selector for a specific control event (such as touch-up-inside), and UIKit invokes that method when the event occurs.

What problem does cell reuse in UITableView and UICollectionView primarily solve? A. It makes Auto Layout constraints optional B. It avoids creating a new cell view for every row/item, recycling off-screen cells for efficiency with large data sets C. It replaces the need for a data source object D. It automatically handles network requests for remote images

B. It avoids creating a new cell view for every row/item, recycling off-screen cells for efficiency with large data sets — Rather than instantiating a view for every row or item — wasteful for large lists — table and collection views maintain a small pool of reusable cells, dequeuing and reconfiguring an off-screen cell to show new content as the user scrolls.

Which class is the root container that a screen's view hierarchy (via a rootViewController) ultimately hangs from? A. UIApplication B. UIWindow C. UIViewController D. UIScreen

B. UIWindow — UIWindow is the root container holding a rootViewController, whose .view becomes the top of the visible view hierarchy — UIApplication represents the app as a whole at the system level, a different responsibility.

According to this lesson's closing framing, why does Objective-C still matter for UI development today? A. Because SwiftUI cannot be used to build production apps B. Because Apple requires all App Store apps to include Objective-C code C. Because many existing production apps and third-party libraries/SDKs are still substantially written in or expose Objective-C, and it interoperates directly with Swift D. Because UIKit itself no longer works with Swift code

C. Because many existing production apps and third-party libraries/SDKs are still substantially written in or expose Objective-C, and it interoperates directly with Swift — The lesson is explicit that Swift and SwiftUI are the current default for new development, and that Objective-C's continued importance is about legacy/production relevance and interoperability with existing large codebases and libraries, not a requirement for new apps.

Which topic did this lesson explicitly defer to a later, dedicated module rather than teaching in depth here? A. Target-action B. Delegation C. Auto Layout D. NSNotificationCenter usage

C. Auto Layout — Auto Layout is only previewed in this lesson, with full coverage of constraints and layout anchors reserved for a later dedicated module — unlike target-action and delegation, which this lesson explicitly points back to earlier modules rather than re-teaching, since they were already covered there.

@interface WelcomeViewController : UIViewController
@end

@implementation WelcomeViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(20, 80, 280, 30)];
    titleLabel.text = @"Welcome";
    [self.view addSubview:titleLabel];

    UIButton *continueButton = [UIButton buttonWithType:UIButtonTypeSystem];
    continueButton.frame = CGRectMake(20, 130, 280, 44);
    [continueButton setTitle:@"Continue" forState:UIControlStateNormal];
    [continueButton addTarget:self
                        action:@selector(continueTapped:)
              forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:continueButton];
}

- (void)continueTapped:(UIButton *)sender {
    NSLog(@"Continue tapped — proceeding to the next screen.");
}

@end
@interface LoginViewController : UIViewController <UITextFieldDelegate>
@property (nonatomic, strong) UITextField *usernameField;
@end

@implementation LoginViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.usernameField = [[UITextField alloc] initWithFrame:CGRectMake(20, 100, 280, 36)];
    self.usernameField.delegate = self;
    self.usernameField.borderStyle = UITextBorderStyleRoundedRect;
    [self.view addSubview:self.usernameField];
}

- (BOOL)textFieldShouldReturn:(UITextField *)textField {
    [textField resignFirstResponder];

    HomeViewController *home = [[HomeViewController alloc] init];
    [self.navigationController pushViewController:home animated:YES];
    return YES;
}

@end
- (void)viewDidLoad {
    [super viewDidLoad];

    UIImageView *avatarView = [[UIImageView alloc] initWithFrame:CGRectMake(20, 200, 60, 60)];
    avatarView.image = [UIImage imageNamed:@"avatar"];
    avatarView.userInteractionEnabled = YES;

    // Give VoiceOver something meaningful to announce for this image.
    avatarView.isAccessibilityElement = YES;
    avatarView.accessibilityLabel = @"Profile photo";
    avatarView.accessibilityHint = @"Double tap to change your profile photo";

    UITapGestureRecognizer *tap =
        [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(avatarTapped:)];
    [avatarView addGestureRecognizer:tap];

    [self.view addSubview:avatarView];
}

- (void)avatarTapped:(UITapGestureRecognizer *)recognizer {
    NSLog(@"Avatar tapped — presenting photo picker.");
}