Kenneth Au

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:

  1. 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.
  2. 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.
  3. 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.
  4. 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