Object tracking gets the demos, but image tracking pays the bills. A poster, a product box, a museum label, a machine's rating plate, a trade-show banner — any flat printed thing your customer already ships is a free AR trigger. It works on every ARKit device back to the iPhone 6s, needs no LiDAR, no scanning rig, and no Create ML training run. This tutorial builds an image-triggered AR experience in RealityKit: static reference images, images downloaded at runtime, video pinned to a poster, and the tracking-quality details that decide whether the effect looks glued to the print or floats half an inch off it.
Requirements: Xcode 16 or later, iOS 18 or later for the RealityKit APIs used here, and a physical device — image tracking does not work in the simulator. Everything below is RealityKit; if you are still on ARSCNView, read Migrating an ARSCNView App to RealityKit first.
1. Add reference images to an AR Resource Group
In Xcode, open your asset catalog, right-click, and choose New AR Resource Group. Drag in your images and give each one a name — that name is the identifier you will match on in code.
For every image, set Units and physical width in the attributes inspector. This is the single most common source of bad image tracking. ARKit uses the declared physical size to estimate distance; if you tell it a poster is 20 cm wide and it is really 60 cm, your content will appear at a third of the intended distance and drift as you move. Measure the printed artwork, not the paper, and measure it in the finished piece.
Xcode will warn you about images it does not like. Take the warnings seriously:
- Low contrast / not enough features. Flat brand fields, gradients, and large white areas give the tracker nothing. Detail spread across the whole image is what works.
- Repeated structures. Grids, checkerboards, and repeating patterns cause the tracker to lock onto the wrong instance and jitter.
- Histogram narrow. Wash-out images track poorly under real lighting.
A quick rule from client work: if you can crop a 100×100 px square from anywhere in the image and still tell which image it came from, it will track well.
2. The minimum viable image anchor
RealityKit's AnchorEntity(.image:) handles detection and tracking for you. No delegate, no session configuration.
import SwiftUI
import RealityKit
struct PosterARView: View {
var body: some View {
RealityView { content in
let anchor = AnchorEntity(.image(group: "ARPosters", name: "spring-catalog"))
let card = ModelEntity(
mesh: .generatePlane(width: 0.3, height: 0.18, cornerRadius: 0.01),
materials: [SimpleMaterial(color: .init(white: 0.1, alpha: 0.9), isMetallic: false)]
)
// Image anchors are +Y up out of the printed surface, so lay the plane flat
card.transform.rotation = simd_quatf(angle: -.pi / 2, axis: [1, 0, 0])
card.position = [0, 0.001, -0.14] // 1 mm above the print, above the poster
anchor.addChild(card)
content.add(anchor)
}
.edgesIgnoringSafeArea(.all)
}
}
Two things worth internalising. First, an image anchor's coordinate space has the image lying in its XZ plane with +Y pointing out of the paper — content authored for a vertical wall poster still uses the same local space, so build against that and let the anchor handle orientation. Second, offset anything coplanar with the print by a millimetre or two. Z-fighting against the tracked plane is the number one "why does it flicker" support ticket.
3. Choose your configuration: detection vs. tracking
Under the hood you have two options, and the difference matters for battery and for feel.
ARWorldTrackingConfiguration with detectionImages set gives you full world tracking: content stays put in the room after the image leaves frame, and you can mix image anchors with plane detection and people occlusion. Set maximumNumberOfTrackedImages to the number of images you expect on screen at once (default 1 — raise it deliberately, it costs CPU).
ARImageTrackingConfiguration tracks images only, with no world map. It is noticeably lighter, recovers faster when the camera whips between posters, and works when the user is moving through a space or when the image itself moves (a handheld card, a box on a conveyor). Content exists only while the image is visible.
Pick image-only tracking for magazine, packaging, and trade-show experiences. Pick world tracking when the AR content needs to persist in the room or interact with floors and walls.
To use the explicit configuration with RealityKit, reach for the underlying session:
guard let refs = ARReferenceImage.referenceImages(
inGroupNamed: "ARPosters", bundle: nil
) else { fatalError("Missing AR Resource Group") }
let config = ARImageTrackingConfiguration()
config.trackingImages = refs
config.maximumNumberOfTrackedImages = 4
arView.session.run(config, options: [.resetTracking, .removeExistingAnchors])
4. Reference images that ship after the app
Baking images into the asset catalog means a new build for every new poster. Most real campaigns need images that arrive from a CMS. Build them at runtime from a downloaded CGImage:
func referenceImage(from cgImage: CGImage,
physicalWidthMeters: CGFloat,
name: String) -> ARReferenceImage {
let ref = ARReferenceImage(cgImage,
orientation: .up,
physicalWidth: physicalWidthMeters)
ref.name = name
ref.validate { error in
if let error { print("Reference image \(name) rejected: \(error)") }
}
return ref
}
validate(completionHandler:) runs the same quality checks Xcode does. Run it server-side or at ingest time, not on the user's device mid-campaign, and reject bad artwork before a marketing team prints ten thousand of it.
Downloaded images also mean you cannot know the entity content at compile time. Watch anchor state directly instead of pre-declaring anchors:
func session(_ session: ARSession, didUpdate anchors: [ARAnchor]) {
for case let imageAnchor as ARImageAnchor in anchors {
guard let name = imageAnchor.referenceImage.name else { continue }
if imageAnchor.isTracked {
show(experienceFor: name, at: imageAnchor)
} else {
hide(experienceFor: name) // don't delete — the image may come back
}
}
}
isTracked flipping to false means the image left the frame or the tracker lost confidence. Hide the content; do not tear it down and rebuild it, or you will get a visible pop every time the user glances away.
5. Video pinned to a poster
The single most requested image-tracking effect: the print comes alive. In RealityKit this is a VideoMaterial.
let url = Bundle.main.url(forResource: "campaign", withExtension: "mp4")!
let player = AVPlayer(url: url)
let screen = ModelEntity(
mesh: .generatePlane(width: 0.4, height: 0.225), // match the video aspect ratio
materials: [VideoMaterial(avPlayer: player)]
)
screen.transform.rotation = simd_quatf(angle: -.pi / 2, axis: [1, 0, 0])
let anchor = AnchorEntity(.image(group: "ARPosters", name: "spring-catalog"))
anchor.addChild(screen)
content.add(anchor)
player.play()
Production notes we have learned the hard way: match the plane aspect ratio to the video or it will stretch; pause the player when isTracked goes false, or you will burn battery decoding video nobody can see; use H.264 or HEVC at the resolution you actually display — a 4K asset on a 40 cm plane is wasted decode; and if you need transparency, HEVC with an alpha channel is supported and looks far better than a chroma-key shader.
6. Tracking quality in the real world
The demo works on your desk and falls apart in the store. Things that actually move the needle:
- Lighting. Image tracking is a camera feature. Under 100 lux, detection latency climbs and jitter appears. Retail and museum installs need to be tested at the venue's real light levels, at the venue's real time of day.
- Glossy stock and glass. Specular highlights destroy features. Matte lamination, or an acrylic-free mount, is a print-spec decision your AR team should be in the room for.
- Curved surfaces. ARKit reference images assume flat. A label wrapped around a bottle tracks poorly past about 15 degrees of curvature — that is an object-tracking job, not an image-tracking one. See object tracking on iOS 27 and visionOS 27.
- Distance. Practical range is roughly 5× to 10× the image's physical width. A 5 cm product label is a "hold it up close" experience; a 1 m banner works across a room.
- Smoothing. Small anchor jitter is normal. Rather than snapping the entity to every anchor update, interpolate the transform over two or three frames. The eye forgives a few milliseconds of lag far more readily than it forgives shimmer.
7. Image tracking on visionOS
The same idea exists on Vision Pro, with a different API. In an ImmersiveSpace you run an ImageTrackingProvider against reference images from a resource group, and consume an async stream of ImageAnchor updates rather than attaching an AnchorEntity. The reference images and physical-size discipline carry over unchanged, which makes image tracking one of the cheapest features to bring to both platforms — the WWDC 2026 convergence story applies here too. Note that image tracking on visionOS requires the user to grant world-sensing permission in an immersive space; plan the onboarding copy for that ask.
8. When image tracking is the wrong tool
Be honest with stakeholders about the boundaries:
- The trigger has to be flat, printed, and visually distinctive. A plain black machine panel is not a tracking target — add a label, or use object tracking.
- The user has to point a camera at it. If the value only appears after five seconds of aiming, the experience needs a strong reason to exist on the packaging itself.
- Anything at a known real-world location is often better served by geo anchors or a scanned room; image tracking that only works when someone stands in the right spot with the right poster is fragile.
- If all you need is "show a 3D model of this product," AR Quick Look with a USDZ is a one-line web link and no app at all. Our native vs. Quick Look vs. WebXR comparison covers the trade-off, and Object Capture covers making the USDZ.
A short checklist before you print
- Reference artwork is high contrast, feature-rich, non-repeating, and validated by
ARReferenceImage.validate. - Physical width in the resource group matches the finished printed piece, measured, in metres.
- Print stock is matte; no glass or acrylic in front of the trigger.
maximumNumberOfTrackedImagesmatches the number of triggers a user can see at once.- Content is offset off the print plane, hidden (not destroyed) on
isTracked == false, and video pauses with it. - Tested on the oldest device you support, at the venue's real light level.
Image tracking is the least glamorous and most reliably shippable part of ARKit. If you have a campaign, a catalogue, or an equipment fleet with printed plates on it, it is usually the fastest route from idea to something a customer can hold. Talk to a RealityRogue ARKit consultant if you want a second pair of eyes on the artwork or the build.