Search code examples
swiftavcapturesessiongoogle-vision

'Any' is not convertible to 'AVCaptureDevice' error translating Objective C to Swift


I'm rebuilding the Google Mobile Vision "Googly Eyes" demo in Swift 3. I figured almost all of it out, but I'm stuck on translating a function from Objective C to Swift.

The Objective C function in the demo view controller is:

  - (AVCaptureDeviceInput *)cameraForPosition:(AVCaptureDevicePosition)desiredPosition {
    BOOL hadError = NO;
    for (AVCaptureDevice *device in [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]) {
      if ([device position] == desiredPosition) {
        NSError *error = nil;
        AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device
                                                                            error:&error];
        if (error) {
          hadError = YES;
          NSLog(@"Could not initialize for AVMediaTypeVideo for device %@", device);
        } else if ([self.session canAddInput:input]) {
          return input;
        }
      }
    }
    if (!hadError) {
      NSLog(@"No camera found for requested orientation");
    }
    return nil;
  }

I've translated that into the following:

func camera(for desiredPosition: AVCaptureDevicePosition) -> AVCaptureDeviceInput {
    var hadError: Bool = false
    for device: AVCaptureDevice in AVCaptureDevice.devices(withMediaType: AVMediaTypeVideo) { // ERROR ON THIS LINE
        if device.position() == desiredPosition {
            var error: Error? = nil
            let input = try? AVCaptureDeviceInput(device: device)
            if error != nil {
                hadError = true
                print("Could not initialize for AVMediaTypeVideo for device \(device)")
            }
            else if session.canAdd(input!) {
                return input!
            }
        }
    }
    if !hadError {
        print("No camera found for requested orientation")
    }
  }

The error I'm getting is on the 3rd line (for device: AVCaptureDevice in AVCaptureDevice.devices...). The error is: 'Any' is not convertible to 'AVCaptureDevice'. I'm not very familiar with Objective C and have never used AVCaptureSession before so I've been struggling to figure it out. Any suggestions on how I need to rewrite this "for device" statement?


Solution

  • Assuming you use the latest release version of Xcode. It's Xcode 8.3.3 at the time of this post.

    I show you codes for some simplified cases.


    Case 1

    Continue using deprecated method (not recommended):

    func camera(for desiredPosition: AVCaptureDevicePosition) -> AVCaptureDeviceInput? {
        var hadError: Bool = false
        for device in AVCaptureDevice.devices(withMediaType: AVMediaTypeVideo) as! [AVCaptureDevice] { //### You need to cast
            if device.position == desiredPosition {
                do {
                    let input = try AVCaptureDeviceInput(device: device)
                    if session.canAddInput(input) { //###<-
                        return input
                    }
                } catch {
                    hadError = true
                    print("Could not initialize for AVMediaTypeVideo for device \(device) with \(error)")
                }
            }
        }
        if !hadError {
            print("No camera found for requested orientation")
        }
        return nil
    }
    

    The return type of devices(withMediaType:) is [Any]!, so if you want to use each element as AVCaptureDevice, you need to cast it.

    Similar to your code, but I fixed some parts, canAdd to canAddInput and the error handling.


    Case 2

    Use AVCaptureDeviceDiscoverySession ignoring older versions of iOS than 10.0.

    func camera(for desiredPosition: AVCaptureDevicePosition) -> AVCaptureDeviceInput? {
        var hadError: Bool = false
        for device in AVCaptureDeviceDiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: AVMediaTypeVideo, position: desiredPosition).devices {
            do {
                let input = try AVCaptureDeviceInput(device: device)
                if session.canAddInput(input) {
                    return input
                }
            } catch {
                hadError = true
                print("Could not initialize for AVMediaTypeVideo for device \(device) with \(error)")
            }
        }
        if !hadError {
            print("No camera found for requested orientation")
        }
        return nil
    }
    

    A simple example of using AVCaptureDeviceDiscoverySession, .builtInWideAngleCamera matches for front camera, normal back camera and wide angle camera on dual camera device.


    NOTE

    Many methods in above codes have changed their signatures in Swift 4/Xcode 9, including the return type. And in iOS SDK 11, there's a nice method:

    class func default(AVCaptureDevice.DeviceType, for: AVMediaType?, position: AVCaptureDevice.Position)