Created
April 15, 2020 00:01
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) { | |
} | |
} |
Filed as FB7663947 (http://www.openradar.me/FB7663947).
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
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:
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.)requestReadData(atOffset:length:completion:)
completion block is subsequently called with a Data object that is 0 bytes in length.Example console output:
Instead, the expected output is:
For Open Radar: The minimum code sample to reproduce this is at https://gist.github.com/mjrusso/b223bf8db93e64d858e80b6fb8ab6ef8