0

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?

tylerSF
  • 1,479
  • 2
  • 16
  • 25
  • There is no such method as `devices(withMediaType:)`. Did you mean this? https://developer.apple.com/documentation/avfoundation/avcapturedevice/1390520-devices – matt Aug 10 '17 at 03:28
  • Related methods have changed much recently. What's your app's Deployment Target? Do you choose using _deprecated_ methods? Or want to use the latest API suitable for your purpose even if you need complex `if #available...` things? – OOPer Aug 10 '17 at 03:40
  • @matt Actually there is (was) a `AVCaptureDevice devices(withMediaType:)` method but it is deprecated as of iOS 10.0. – rmaddy Aug 10 '17 at 03:42
  • The code contains Swift 2 and 3 syntax (probably it will not compile anyway) and since you are ignoring the error handling completely (`error` will never be `true`) the `print` lines will never be reached. The error occurs because the API returns `[Any]` and you have to cast the type to something more specific. – vadian Aug 10 '17 at 04:21

1 Answers1

1

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)

OOPer
  • 47,149
  • 6
  • 107
  • 142
  • Thanks @OOPer. I'm using a deployment target of 10, so I don't need to worry about the older methods that have been deprecated. I updated my code with Case 2 above and that has resolved the issues. Thanks again! – tylerSF Aug 10 '17 at 13:19