Skip to content

Instantly share code, notes, and snippets.

@mjrusso
Created April 15, 2020 00:01
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mjrusso/b223bf8db93e64d858e80b6fb8ab6ef8 to your computer and use it in GitHub Desktop.
Save mjrusso/b223bf8db93e64d858e80b6fb8ab6ef8 to your computer and use it in GitHub Desktop.
iPadOS 13.4: ImageCaptureCore's ICCameraFile `requestReadData(atOffset:length:completion:)` always passes empty Data object to completion block
import UIKit
import ImageCaptureCore
import MobileCoreServices
class ViewController: UIViewController {
var deviceFinder = DeviceFinder()
override func viewDidLoad() {
super.viewDidLoad()
}
}
// MARK: - DeviceFinder
class DeviceFinder: NSObject, ICDeviceBrowserDelegate {
private var deviceBrowser: ICDeviceBrowser
var devices: [ICCameraDevice: CameraDevice] = [:]
override init() {
self.deviceBrowser = ICDeviceBrowser()
super.init()
deviceBrowser.delegate = self
deviceBrowser.start()
print("[DeviceFinder] started ICDeviceBrowser; connect camera device now")
}
deinit {
deviceBrowser.stop()
}
// MARK: ICDeviceBrowserDelegate
func deviceBrowser(_ browser: ICDeviceBrowser, didAdd device: ICDevice, moreComing: Bool) {
guard let device = device as? ICCameraDevice else {
return
}
devices[device] = CameraDevice(device)
}
func deviceBrowser(_ browser: ICDeviceBrowser, didRemove device: ICDevice, moreGoing: Bool) {
guard let device = device as? ICCameraDevice else {
return
}
devices.removeValue(forKey: device)
}
}
// MARK: - CameraDevice
class CameraDevice: NSObject, ICCameraDeviceDelegate {
let device: ICCameraDevice
init(_ device: ICCameraDevice) {
self.device = device
super.init()
self.device.delegate = self
self.device.requestOpenSession()
}
// MARK: ICDeviceDelegate
func device(_ device: ICDevice, didOpenSessionWithError error: Error?) {
if let error = error {
print("[CameraDevice] error opening session:", error)
} else {
print("[CameraDevice] session opened")
}
}
func device(_ device: ICDevice, didCloseSessionWithError error: Error?) {
}
func didRemove(_ device: ICDevice) {
}
// MARK: ICCameraDeviceDelegate
func deviceDidBecomeReady(withCompleteContentCatalog device: ICCameraDevice) {
print("[CameraDevice] device ready, with complete catalog of \(device.mediaFiles?.count ?? 0) items")
let cameraFiles = device.mediaFiles?
.filter { ($0.uti ?? "") == (kUTTypeImage as String) }
.compactMap { $0 as? ICCameraFile } ?? []
guard let file = cameraFiles.first else {
print("[CameraDevice] there aren't any image files")
return
}
print("[CameraDevice] about to requestReadData for file '\(file)' with size \(file.fileSize)")
file.requestReadData(atOffset: 0, length: file.fileSize) { maybeData, maybeError in
print("[CameraFile] in requestReadData completion block")
if let error = maybeError {
print("*** [CameraFile] error in requestReadData: \(error.localizedDescription)")
}
if let data = maybeData {
if data.isEmpty {
print("*** [CameraFile] data read via requestReadData is empty!")
} else {
print("*** [CameraFile] data read via requestReadData has \(data.count) bytes!")
}
} else {
print("*** [CameraFile] data read via requestReadData is nil!")
}
}
}
func cameraDevice(_ camera: ICCameraDevice, didAdd items: [ICCameraItem]) {
}
func cameraDevice(_ camera: ICCameraDevice, didRemove items: [ICCameraItem]) {
}
func cameraDevice(_ camera: ICCameraDevice, didCompleteDeleteFilesWithError error: Error?) {
}
func cameraDevice(_ camera: ICCameraDevice, didReceiveMetadata metadata: [AnyHashable : Any]?, for item: ICCameraItem, error: Error?) {
}
func cameraDevice(_ camera: ICCameraDevice, didReceiveThumbnail thumbnail: CGImage?, for item: ICCameraItem, error: Error?) {
}
func cameraDevice(_ camera: ICCameraDevice, didRenameItems items: [ICCameraItem]) {
}
func cameraDeviceDidChangeCapability(_ camera: ICCameraDevice) {
}
func cameraDevice(_ camera: ICCameraDevice, didReceivePTPEvent eventData: Data) {
}
func cameraDeviceDidEnableAccessRestriction(_ device: ICDevice) {
}
func cameraDeviceDidRemoveAccessRestriction(_ device: ICDevice) {
}
}
@mjrusso
Copy link
Author

mjrusso commented Apr 15, 2020

ImageCaptureCore: ICCameraFile requestReadData(atOffset:length:completion:) always passes empty Data object to completion block

Important: this is a regression in iPadOS 13.4. This has worked with previous versions of iPadOS, since beta versions of 13.2.

On iPadOS, ICCameraFile's requestReadData(atOffset:length:completion:) method does not read data from the camera file.

When requestReadData is called with an offset of 0 and a length corresponding to the size of the file, the completion block is executed with a non-nil, 0 byte Data object. (The error is nil.) The expected result is a Data object with the actual contents of the camera file (and a nil error), not a 0 byte Data object.

The attached project reproduces this sample issue. (There is no app UI; see the messages logged to the console.) This project:

  • Instantiates an ICDeviceBrowser, sets its delegate, and starts its browser.
  • (User must tap OK at the permission prompt to access files on connected cameras and storage.)
  • When an ICCameraDevice (such as an SD card) is connected, the code automatically sets its delgate, and opens a session.
  • When the ICCameraDeviceDelegate's deviceDidBecomeReady(withCompleteContentCatalog:) delegate method is called, requestReadData(atOffset:length:completion:) is called with on the first ICCameraFile object. (Using the first ICCameraFile is arbitrary; the results are the same regardless of which file is chosen.)
  • The requestReadData(atOffset:length:completion:) completion block is subsequently called with a Data object that is 0 bytes in length.

Example console output:

[DeviceFinder] started ICDeviceBrowser; connect camera device now
[CameraDevice] session opened
[CameraDevice] device ready, with complete catalog of 178 items
[CameraDevice] about to requestReadData for file '    🅁 | DSCF2030.RAF | 0 | ' with size 50544640
[CameraFile] in requestReadData completion block
*** [CameraFile] data read via requestReadData is empty!

Instead, the expected output is:

[DeviceFinder] started ICDeviceBrowser; connect camera device now
[CameraDevice] session opened
[CameraDevice] device ready, with complete catalog of 178 items
[CameraDevice] about to requestReadData for file '    🅁 | DSCF2030.RAF | 0 | ' with size 50544640
[CameraFile] in requestReadData completion block
*** [CameraFile] data read via requestReadData has 50544640 bytes!

For Open Radar: The minimum code sample to reproduce this is at https://gist.github.com/mjrusso/b223bf8db93e64d858e80b6fb8ab6ef8

@mjrusso
Copy link
Author

mjrusso commented Apr 15, 2020

Filed as FB7663947 (http://www.openradar.me/FB7663947).

@mjrusso
Copy link
Author

mjrusso commented Apr 15, 2020

Mac Catalyst issue filed as FB7663990 (http://www.openradar.me/FB7663990).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment