Sample CodeiOS, iPadOS, Mac CatalystReviewed 2026-07-21View on Apple Developer

Customizing an image picker controller

At a glance

Item Summary
Purpose Replaces the camera picker’s built-in controls with a custom overlay that supports immediate, delayed, and interval capture.
App architecture One feature controller owns the picker, overlay controls, capture timer, and captured-image session; UIKit and AVFoundation provide delegate and authorization boundaries.
Main patterns Framework-controller customization, delegate callbacks, overlay composition, permission gate, and timer-driven commands.
Project style Legacy storyboard sample with most feature logic intentionally colocated in one APLViewController file.

Project structure

PhotoPicker/
├── AppDelegate.swift
├── ViewController.swift
├── Base.lproj/
│   ├── Main.storyboard
│   └── LaunchScreen.storyboard
├── Assets.xcassets/
└── Info.plist

Structure observations

  • ViewController.swift owns the entire camera/photo-picker feature; there is no separate camera service or session model.
  • The storyboard supplies the main image view, camera overlay, and toolbar button outlets.
  • AVFoundation is used only for camera authorization; capture itself goes through UIImagePickerController.

Overall architecture

Reference code

PhotoPicker/ViewController.swift:167 — source type selects presentation style; camera mode disables standard controls and installs the storyboard overlay before presentation.

imagePickerController.sourceType = sourceType
imagePickerController.modalPresentationStyle =
    sourceType == .camera ? .fullScreen : .popover

let presentationController =
    imagePickerController.popoverPresentationController
presentationController?.barButtonItem = button

if sourceType == .camera {
    imagePickerController.showsCameraControls = false
    overlayView?.frame =
        (imagePickerController.cameraOverlayView?.frame)!
    imagePickerController.cameraOverlayView = overlayView
}

present(imagePickerController, animated: true)

Interpretation

The app customizes a system-owned capture workflow rather than building an AVCaptureSession. APLViewController is both composition root and session coordinator: it chooses the picker mode, injects the overlay, translates overlay actions into picker commands, and consumes delegate results.

Ownership and state

Ownership evidence

PhotoPicker/ViewController.swift:13 — the controller stores all visual connections plus the picker, timer, and captured-image buffer.

@IBOutlet var imageView: UIImageView?
@IBOutlet var cameraButton: UIBarButtonItem?
@IBOutlet var overlayView: UIView?
@IBOutlet var takePictureButton: UIBarButtonItem?
@IBOutlet var startStopButton: UIBarButtonItem?
@IBOutlet var delayedPhotoButton: UIBarButtonItem?
@IBOutlet var doneButton: UIBarButtonItem?

var imagePickerController = UIImagePickerController()
var cameraTimer = Timer()
var capturedImages = [UIImage]()
Owner Object or state Relationship Mutation authority
APLViewController Image picker Creates and strongly retains one reusable picker Setup and source-selection methods
APLViewController Overlay and toolbar outlets Strong storyboard connections Overlay action methods
Image picker Active camera overlay and delegate collaboration Retains overlay while configured; calls delegate Framework presentation lifecycle
APLViewController cameraTimer Replaces/invalidates capture schedule Delay/start/stop/done actions
APLViewController capturedImages Owns current capture batch Picker delegate and finishAndUpdate
Timer/RunLoop Scheduled callback Callback captures controller until completion/invalidation Run loop plus stop/done paths

The timer and its closure make lifecycle explicit: repeating work must be invalidated before finishing the capture session.

Class and protocol design

Type or protocol Responsibility Depends on or conforms to
APLViewController Authorize, configure/present picker, handle overlay actions, collect/display results UIImagePickerControllerDelegate, UINavigationControllerDelegate
UIImagePickerController Provide photo library or camera UI and still-image capture UIKit delegate contract
AppDelegate Hold application window and startup lifecycle UIApplicationDelegate

There is no app-defined protocol. The framework delegate is the inversion point: the picker owns the interaction flow, then calls the controller with an image or cancellation.

Access control

Symbol Access Verified effect Why it fits this design
APLViewController and feature state implicit internal Other app-module code could read/replace picker, timer, arrays, and outlets. Reflects an older teaching sample; production code could make most state private.
Overlay action and delegate methods implicit internal Reachable by storyboard/Objective-C action dispatch and UIKit callbacks. They form the framework/UI integration surface.
Info-key conversion helpers file-scope private Usable only inside ViewController.swift. Keeps compatibility conversion out of the app-module API.
AppDelegate.window implicit internal Stores storyboard application window. Standard legacy app lifecycle surface.
public / fileprivate / private(set) Not used No reusable public API or split read/write boundary. The sample emphasizes behavior rather than library encapsulation.

Reference code

PhotoPicker/ViewController.swift:337 — the only explicit privacy boundary contains compatibility utilities used solely by picker-result handling.

private func convertFromUIImagePickerControllerInfoKeyDictionary(
    _ input: [UIImagePickerController.InfoKey: Any]
) -> [String: Any] {
    Dictionary(uniqueKeysWithValues:
        input.map { key, value in (key.rawValue, value) })
}

private func convertFromUIImagePickerControllerInfoKey(
    _ input: UIImagePickerController.InfoKey
) -> String {
    input.rawValue
}

Logic ownership and placement

Logic Owning type or file Placement rationale
Camera authorization and Settings alert APLViewController Requires presentation context and the initiating bar button.
Picker mode, popover/full-screen choice, overlay injection APLViewController It owns both picker and storyboard overlay.
Immediate/delayed/repeating capture commands APLViewController Toolbar actions map directly to takePicture and timer lifecycle.
Image acceptance/cancellation Picker delegate methods UIKit defines completion callbacks.
Batch display and cleanup finishAndUpdate One method closes the modal session and commits captured images to the main view.
Capture implementation UIImagePickerController System framework owns camera/media-library mechanics.

Design patterns

Pattern Source evidence Purpose or tradeoff
Framework-controller customization PhotoPicker/ViewController.swift:177 Reuses system capture while replacing its controls.
Overlay composition PhotoPicker/ViewController.swift:189 Injects a storyboard view into cameraOverlayView.
Permission gate PhotoPicker/ViewController.swift:90 Branches denied/not-determined/authorized before camera presentation.
Delegate callback PhotoPicker/ViewController.swift:298 Receives each captured image and decides when to finish.
Timer-driven command PhotoPicker/ViewController.swift:225 Adds delayed and repeating capture without owning camera hardware.
Session buffer PhotoPicker/ViewController.swift:28 Collects multiple delegate results before committing display.

Naming conventions

  • APLViewController uses Apple’s older “APL” sample prefix rather than a feature-specific type name.
  • Actions use explicit verbs: showImagePickerForCamera, takePhoto, delayedTakePhoto, and stopTakingPicturesAtIntervals.
  • State names reveal lifecycle: cameraTimer and capturedImages.
  • finishAndUpdate combines two responsibilities in its name because it both dismisses and updates the result view.

Architecture takeaways

  • Customize a system controller through its supported overlay and delegate seams before building a lower-level capture stack.
  • Keep one clear owner for picker, timer, capture buffer, and presentation.
  • Treat repeating timers as owned resources that must be invalidated on every finish path.
  • Framework delegate methods are sufficient here; an app-defined abstraction would only be justified when capture logic must be reused or tested independently.
  • The sample’s module-wide state is pedagogical, not a model of minimal access control.

Source map

Source file Relevant symbols
PhotoPicker/ViewController.swift Authorization, picker/overlay composition, timers, delegate results
PhotoPicker/AppDelegate.swift Application/window lifecycle
PhotoPicker/Base.lproj/Main.storyboard Main UI and camera overlay outlets/actions
PhotoPicker/Info.plist Camera usage description