Reference on ImageCaptureCore DeviceBrowser Mask
The One-Line Fix That Took Me Days: How a Random Reddit Post Solved My ImageCaptureCore Scanner Mystery
I’ve been building a macOS scanning app: a native, AI-powered tool that uses SwiftUI, Swift 6, CoreML, and all the new Apple Foundation models to auto-tag my documents. The dream: plug in my Epson scanner, open the app, click scan, magic. Reality: two days of pressing “Search” while staring at an empty device list.
My app couldn’t see the scanner. At all.
Every run, ICDeviceBrowser dutifully reported 0 devices via pristine OSLogs, while Apple’s own Image Capture app found the scanner instantly. The Epson ICA drivers were installed, USB was solid; the hardware worked, but my code pretended the scanner didn’t exist.
So I did what any modern developer does: threw an AI coding agent at the problem, complete with Xcode MCP server integration and Apple documentation plugins. I said something like “Write me a scanner app using ImageCaptureCore and SwiftUI.” It spat out a functional app with sandbox entitlements, put together a nice SwiftUI view. Still nothing. I added com.apple.security.device.usb, com.apple.security.device.scanner,and com.apple.security.device.camera thanks to this Stack Overflow page. Zero devices. I even paid Apple $99 for the Developer Program, convinced I needed code signing to make a USB device appear. (Spoiler: I didn’t.)
Then I had an almost embarrassing thought: What if I just search Reddit?
I typed ImageCaptureCore into Google with the site:reddit.com modifier and landed in r/macosprogramming. A thread from a few years ago, “More problems with scanning,” had a reply from developer David Phillip Oster. He linked his GitHub repo: imageCaptureScanner. An Objective-C project, but it’ll still work in Xcode. I cloned it, built it, and of course, the scanner appeared instantly.
My eyes locked onto a single line in ScannerBoss.m, line 26:
self.browser.browsedDeviceTypeMask = ICDeviceTypeMaskScanner | ICDeviceLocationTypeMaskLocal;
That was it. My entire two-day nightmare, solved by one pipe character. And no, it wasn’t Mario.
In my Swift code, I’d been setting the mask like any reasonable person or AI coding LLM:
browser.browsedDeviceTypeMask = .scanner
ICDeviceTypeMask holds .camera and .scanner; I wanted scanners, so .scanner seemed correct. Even the AI agents used that line. But the documentation’s Discussion section, which I’d skimmed like a Yu-Gi-Oh card text, actually says:
“Construct this property by performing bitwise OR on values of ICDeviceTypeMask with values of ICDeviceLocationTypeMask.”
The mask isn’t just a device type; it’s a bitmask that demands both a type and a location. Without OR-ing in a location like .local (USB), .shared, .bonjour, or .bluetooth, the browser hunts for scanners at “no location” – like asking for a coffee shop but forgetting to specify a planet.
David’s Objective-C code naturally OR’d the integer constants with |. Swift, however, wraps those constants in a struct, so you can’t just | them. You have to extract raw values, OR them, and wrap them back. That’s why .scanner compiled but meant nothing.
The Swift spell that finally summoned my scanner:
browser.browsedDeviceTypeMask = ICDeviceTypeMask(
rawValue: ICDeviceTypeMask.scanner.rawValue | ICDeviceLocationTypeMask.local.rawValue
)!
To also catch network scanners:
browser.browsedDeviceTypeMask = ICDeviceTypeMask(
rawValue: ICDeviceTypeMask.scanner.rawValue
| ICDeviceLocationTypeMask.local.rawValue
| ICDeviceLocationTypeMask.shared.rawValue
| ICDeviceLocationTypeMask.bonjour.rawValue
| ICDeviceLocationTypeMask.bluetooth.rawValue
)!
The force-unwrap is safe because any combination of valid raw values is a valid mask.
After that one-line change, the scanner appeared like it had been waiting for me to just ask properly. I laughed out loud. Hours of head-scratching, AI rabbit holes, and a $99 developer membership, all fixed by a mask I’d glossed over a dozen times.
Takeaways:
- Read the Discussion sections. Apple hides critical behavior in there. The property summary said “mask of device types,” but the Discussion revealed the location OR requirement. Without that, you’re lost.
- AI agents mirror your blind spots. Mine never suggested combining location masks because I didn’t know I needed to. They echoed my missing piece and offered entitlements as a fix instead.
- Old-school repos are gold. David’s 2024 Objective-C project, buried in a Reddit comment, contained the exact working pattern. No blog post, no Medium tutorial, just code that cut through the noise.
- Swift’s type safety can obscure bitwise intentions. In C, you
|numbers and it’s obvious. In Swift, the struct wrapper hides the fact that you need to combine two different enums. Not a bug, but a design trap.
Thank you David! And go star David’s imageCaptureScanner repo – that little Objective-C gem saved my sanity.
References
- David Phillip Oster’s imageCaptureScanner repository - The minimal Objective-C project that showed correct mask usage.
- Reddit thread on r/macosprogramming - Where I found the repo link.
- Apple’s ICDeviceBrowser documentation - Official docs with the crucial Discussion note about mask construction.
- Apple’s ImageCaptureCore framework overview - Full capabilities of the framework.
