Using the AVFoundation API, we are going to set up a capture session and make an app that allows us to use all the new fine-grained controls added to iOS 8. This includes manually controlling focus, exposure, and ISO. First off, we just need to set up a basic camera preview. In Part 1, we created a touch-based way to manually control focus. If you haven’t read that yet you can read it here.
In this part of the tutorial, we’re going to dive in to the API a little deeper and create a second axis for controlling our image using the vertical touch position.
First off, let’s just clean up this code that determines how far in to the screen is being touched. Let’s create a function that takes in a UITouch object, and returns a CGPoint specifying how far in to the screen we’ve tapped, as a percentage of the screens total width. For example tapping on the very top-right of the screen will return a CGPoint with values 1,1. While tapping on the bottom-left will return 0,0… the center of the screen 0.5, 0.5, and all values in between.
func touchPercent(touch : UITouch) -> CGPoint { // Get the dimensions of the screen in points let screenSize = UIScreen.mainScreen().bounds.size // Create an empty CGPoint object set to 0, 0 var touchPer = CGPointZero // Set the x and y values to be the value of the tapped position, divided by the width/height of the screen touchPer.x = touch.locationInView(self.view).x / screenSize.width touchPer.y = touch.locationInView(self.view).y / screenSize.height // Return the populated CGPoint return touchPer }
This declares a method named touchPercent, which takes a UITouch object named touch as an argument, and returns a CGPoint. The actual math is simple… take the touched point and divide by the total number of points for either the width or height of the screen. We are already using the x value for focus, but we need to adjust the touchesBegan and touchesMoved method to use our new function.
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) { let touchPer = touchPercent( touches.anyObject() as UITouch ) focusTo(Float(touchPer.x)) } override func touchesMoved(touches: NSSet, withEvent event: UIEvent) { let touchPer = touchPercent( touches.anyObject() as UITouch ) focusTo(Float(touchPer.x)) }
Note: you can remove the old screenWidth variable we were declaring before.
Next, we want to specify the ISO. If you’re unfamiliar with that is, it’s simple enough to say that the ISO is a setting for digital cameras that specifies what amount of light to allow in to the lens. A lower ISO will give brighter and higher quality pictures, but with a slower shutter speed, resulting in blurrier photos. A very low ISO results in a camera mode where a tripod is useful. With a very high ISO, you can take quick action shots with the very low shutter speed, but expect to see lower quality images with more noise. I’m by no means a professional photographer, so I’m probably not explaining it all that well, but that’s about my level of understanding.
Okay, moving on… let’s just add it to the app and see what it does for ourselves 🙂
First, let’s update the focusTo() method to take in an isoValue in addition to a focusValue
func updateDeviceSettings(focusValue : Float, isoValue : Float) { if let device = captureDevice { if(device.lockForConfiguration(nil)) { device.setFocusModeLockedWithLensPosition(focusValue, completionHandler: { (time) -> Void in // }) // Adjust the iso to clamp between minIso and maxIso based on the active format let minISO = device.activeFormat.minISO let maxISO = device.activeFormat.maxISO let clampedISO = isoValue * (maxISO - minISO) + minISO device.setExposureModeCustomWithDuration(AVCaptureExposureDurationCurrent, ISO: clampedISO, completionHandler: { (time) -> Void in // }) device.unlockForConfiguration() } } }
The first new thing in this function is the name is now updateDeviceSettings. This is just because we’re adding more than focus. We’ve also specified focusValue and isoValue as distinct values.
Next, we have the normal locking and setting of the focus, followed by an additional bit of code that stored the minISO and maxISO, then adjusts our isoValue to fit within those two values, proportionally.
For example, if minISO is 50, and maxISO is 100. A value of 0 passed on to isoValue will result in a clampedISO of 50. But a value of 0.5 will result in a clampedISO of 75.
After adjusting this value, we can call the method that actually sets the ISO, setExposureModeCustomWithDuration().
The first parameter to this method is the shutter speed, in this case we aren’t trying to specify shutter speed so we simply use the constant AVCaptureExposureDurationCurrent. This basically just says we don’t want to specify a shutter time, and are just modifying the ISO. Second, is our clampedISO value, then finally we have a completionHandler just as we do with the setFocusModeLockedWithLensPosition method.
Try running the app and sliding your finger from the top to the bottom of the screen. Do you see the difference in the general pixel brightness? This is the ISO being modified in real time.
In the next part, we’ll dig a little deeper and naturally, implement some more UI to control these settings.
Here is the final code from this post:
Part 2 on Github
Any Ideas on when the third part of this tutorial will be out? I really like it, its easy to follow, and very informative!
This actually ended up being the main tutorial used in my book, so it’s not published here. The process is changed a bit in the book as well.