Core Data in Swift Tutorial (Part 2)

 

This post compatible with Xcode 6 GM

This is part two of a tutorial series covering the usage of Core Data in Swift to write iOS apps with persistence. If you haven’t read part one yet, read that first.

If you really want to get your feet wet, my Swift book which is now available for pre-order with early access.

Creating more records

First off, if we’re going to do anything more interesting than just showing a single record pop up in an alert, we’ll need to generate some more records to play with. Let’s open up our LogItem.swift file and add a conveinence function for adding new records.

This will be a class method so instead of typing this:

let newItem = NSEntityDescription.insertNewObjectForEntityForName("LogItem", inManagedObjectContext: self.managedObjectContext!) as LogItem
newItem.title = "Wrote Core Data Tutorial"
newItem.itemText = "Wrote and posted a tutorial on the basics of Core Data to blog."

We can type something more like this:

let newItem = LogItem.createInManagedObjectContext(managedObjectContext, "Item Title", "Item text")

The LogItem class is generic for any managedObjectContext, so we want to make sure we aren’t storing that anywhere in the model, it needs to be passed in when we want to create an object like this.

Okay so let’s implement the method…

class func createInManagedObjectContext(moc: NSManagedObjectContext, title: String, text: String) -> LogItem {
    let newItem = NSEntityDescription.insertNewObjectForEntityForName("LogItem", inManagedObjectContext: moc) as LogItem
    newItem.title = title
    newItem.itemText = text
    
    return newItem
}

The first line is the function definition. It’s a class function called createInManagedObjectContext, which takes an object of type NSManagedObjectContext as the argument “moc”, a String called title, and a String called text. The function returns a LogItem object that’s been inserted in to the specified managed object context.

Then it executes nearly identical code as before to create a new LogItem object, except now it’s using the arguments passed in to the function to set up the new LogItem object.

We can replace our original code in ViewController.swift now to just use the new method. Let’s add a bunch of new items…

override func viewDidLoad() {
    super.viewDidLoad()
    
    let newItem = NSEntityDescription.insertNewObjectForEntityForName("LogItem", inManagedObjectContext: self.managedObjectContext!) as LogItem
    
    LogItem.createInManagedObjectContext(self.managedObjectContext!, title: "1st Item", text: "This is my first log item")
    LogItem.createInManagedObjectContext(self.managedObjectContext!, title: "2nd Item", text: "This is my second log item")
    LogItem.createInManagedObjectContext(self.managedObjectContext!, title: "3rd Item", text: "This is my third log item")
    LogItem.createInManagedObjectContext(self.managedObjectContext!, title: "4th Item", text: "This is my fourth log item")
    LogItem.createInManagedObjectContext(self.managedObjectContext!, title: "5th Item", text: "This is my fifth log item")
    LogItem.createInManagedObjectContext(self.managedObjectContext!, title: "6th Item", text: "This is my sixth log item")
    
    presentItemInfo()
}

Now that we have a couple of records, let’s remove presentItemInfo and instead opt for a table view here. We’ll add this all right under viewDidLoad and programmatically create the UITableView. In my iTunes tutorial we do this using storyboards. If you are more interested in working with storyboards I recommend taking a pit stop there to read about how to get that set up.

We’ll set up the tableView by adding a logTableView to the ViewController class, and set it all up in viewDidLoad()

// Create the table view as soon as this class loads
var logTableView = UITableView(frame: CGRectZero, style: .Plain)

override func viewDidLoad() {
    super.viewDidLoad()
    
    LogItem.createInManagedObjectContext(self.managedObjectContext!, title: "1st Item", text: "This is my first log item")
    LogItem.createInManagedObjectContext(self.managedObjectContext!, title: "2nd Item", text: "This is my second log item")
    LogItem.createInManagedObjectContext(self.managedObjectContext!, title: "3rd Item", text: "This is my third log item")
    LogItem.createInManagedObjectContext(self.managedObjectContext!, title: "4th Item", text: "This is my fourth log item")
    LogItem.createInManagedObjectContext(self.managedObjectContext!, title: "5th Item", text: "This is my fifth log item")
    LogItem.createInManagedObjectContext(self.managedObjectContext!, title: "6th Item", text: "This is my sixth log item")
    
    
    // Now that the view loaded, we have a frame for the view, which will be (0,0,screen width, screen height)
    // This is a good size for the table view as well, so let's use that
    // The only adjust we'll make is to move it down by 20 pixels, and reduce the size by 20 pixels
    // in order to account for the status bar
    
    // Store the full frame in a temporary variable
    var viewFrame = self.view.frame
    
    // Adjust it down by 20 points
    viewFrame.origin.y += 20
    
    // Reduce the total height by 20 points
    viewFrame.size.height -= 20
    
    // Set the logTableview's frame to equal our temporary variable with the full size of the view
    // adjusted to account for the status bar height
    logTableView.frame = viewFrame
    
    // Add the table view to this view controller's view
    self.view.addSubview(logTableView)
    
    // Here, we tell the table view that we intend to use a cell we're going to call "LogCell"
    // This will be associated with the standard UITableViewCell class for now
    logTableView.registerClass(UITableViewCell.classForCoder(), forCellReuseIdentifier: "LogCell")
    
    // This tells the table view that it should get it's data from this class, ViewController
    logTableView.dataSource = self
    
}

Since we set the dataSource to be our ViewController class, we also need to adhere to the UITableViewDataSource protocol, so add that to the ViewController’s class definition:

class ViewController: UIViewController, UITableViewDataSource {

…and add the actual dataSource methods…

// MARK: UITableViewDataSource
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return 5
}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("LogCell") as UITableViewCell
    cell.textLabel?.text = "\(indexPath.row)"
    return cell
}

Still with me? Hope so… if not here’s the full ViewController.swift code up until this point:

import UIKit
import CoreData

class ViewController: UIViewController, UITableViewDataSource {
    
    lazy var managedObjectContext : NSManagedObjectContext? = {
        let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
        if let managedObjectContext = appDelegate.managedObjectContext {
            return managedObjectContext
        }
        else {
            return nil
        }
    }()

    
    // Create the table view as soon as this class loads
    var logTableView = UITableView(frame: CGRectZero, style: .Plain)

    override func viewDidLoad() {
        super.viewDidLoad()
        
        LogItem.createInManagedObjectContext(self.managedObjectContext!, title: "1st Item", text: "This is my first log item")
        LogItem.createInManagedObjectContext(self.managedObjectContext!, title: "2nd Item", text: "This is my second log item")
        LogItem.createInManagedObjectContext(self.managedObjectContext!, title: "3rd Item", text: "This is my third log item")
        LogItem.createInManagedObjectContext(self.managedObjectContext!, title: "4th Item", text: "This is my fourth log item")
        LogItem.createInManagedObjectContext(self.managedObjectContext!, title: "5th Item", text: "This is my fifth log item")
        LogItem.createInManagedObjectContext(self.managedObjectContext!, title: "6th Item", text: "This is my sixth log item")
        
        
        // Now that the view loaded, we have a frame for the view, which will be (0,0,screen width, screen height)
        // This is a good size for the table view as well, so let's use that
        // The only adjust we'll make is to move it down by 20 pixels, and reduce the size by 20 pixels
        // in order to account for the status bar
        
        // Store the full frame in a temporary variable
        var viewFrame = self.view.frame
        
        // Adjust it down by 20 points
        viewFrame.origin.y += 20
        
        // Reduce the total height by 20 points
        viewFrame.size.height -= 20
        
        // Set the logTableview's frame to equal our temporary variable with the full size of the view
        // adjusted to account for the status bar height
        logTableView.frame = viewFrame
        
        // Add the table view to this view controller's view
        self.view.addSubview(logTableView)
        
        // Here, we tell the table view that we intend to use a cell we're going to call "LogCell"
        // This will be associated with the standard UITableViewCell class for now
        logTableView.registerClass(UITableViewCell.classForCoder(), forCellReuseIdentifier: "LogCell")
        
        // This tells the table view that it should get it's data from this class, ViewController
        logTableView.dataSource = self
    }
    
    // MARK: UITableViewDataSource
    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 5
    }

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("LogCell") as UITableViewCell
        cell.textLabel?.text = "\(indexPath.row)"
        return cell
    }

}

This will give us a numbered list of rows if we run the app. This just confirms the table view is set up correctly.

Now that we have this all set up and ready to go, we can start working on getting our log data showing up in the table view.

Please note that at this point in the tutorial, I’m intentionally avoiding the NSFetchedResultsController class. I’m doing this because I believe it will make more sense from the Core Data perspective the first time around if you see it done the old fashioned way. After this tutorial is over, I encourage you to look over the class and see how you might use it to implement some of these things instead. I think it is important to first learn how to implement a core data backed table view without using the helper class. The helper class is not applicable in all situations, and learning it first would be doing yourself a disservice. You’ll find it doesn’t work for all use cases (not even close), and in it’s magic it hides some of what’s going on.

First off, let’s fetch all the results from Core Data in viewDidLoad(), and store them in an array of LogItem’s, logItems.

First we’ll add the variable to the ViewController class:

// Create an empty array of LogItem's
var logItems = [LogItem]()

Next, we’ll populate it from viewDidLoad(), inside of a function called fetchLog().

override func viewDidLoad() {
    ...
    fetchLog()
}

func fetchLog() {
    let fetchRequest = NSFetchRequest(entityName: "LogItem")
    if let fetchResults = managedObjectContext!.executeFetchRequest(fetchRequest, error: nil) as? [LogItem] {
        logItems = fetchResults
    }
}

Now we can modify the tableView dataSource methods to refer to this array instead of hard-coding values.

// MARK: UITableViewDataSource
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    // How many rows are there in this section?
    // There's only 1 section, and it has a number of rows
    // equal to the number of logItems, so return the count
    return logItems.count
}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("LogCell") as UITableViewCell
    
    // Get the LogItem for this index
    let logItem = logItems[indexPath.row]
    
    // Set the title of the cell to be the title of the logItem
    cell.textLabel?.text = logItem.title
    return cell
}

This should set us up to see the items listed in the table view, but we want to show the text of an item when it’s clicked. So we need to set the table view to use this view controller as it’s delegate, so we can receive the callback method, didSelectRowAtIndexPath.

Similar to before, add the UITableViewDelegate protocol to the class…

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

And set the delegate to be self, you can do this right under where you set the dataSource in viewDidLoad…

// This tells the table view that it should get it's data from this class, ViewController
logTableView.dataSource = self
logTableView.delegate = self

Finally we can implement the method, knowing the Table View will call it when a cell is clicked…

// MARK: UITableViewDelegate
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    let logItem = logItems[indexPath.row]
    let alert = UIAlertView(title: logItem.title, message: logItem.itemText, delegate: nil, cancelButtonTitle: nil, otherButtonTitles: "OK")
    alert.show()
}

This will set us up so that when the button is clicked, a message will pop up showing the logItem.title message. The purpose of this tutorial is not really to explain how to set up a table view manually, but it’s sort of necessary in order to get a good look at the data. For this reason, I’m providing the source code up until this point in the tutorial here. Feel free to simply check this out and work from this code base, from here we’ll be talking more about the Core Data side of things.
https://github.com/jquave/Core-Data-In-Swift-Tutorial/tree/Part2.5

You may see something kind of like this now:

Your ordering may be different, this is because it’s pretty much random. In some data stores you might get this data back in the same order as it was inserted, but with Core Data it ends up being fairly random. Let’s change this by adding a sort to the fetch request.

func fetchLog() {
    let fetchRequest = NSFetchRequest(entityName: "LogItem")
    
    // Create a sort descriptor object that sorts on the "title"
    // property of the Core Data object
    let sortDescriptor = NSSortDescriptor(key: "title", ascending: true)
    
    // Set the list of sort descriptors in the fetch request,
    // so it includes the sort descriptor
    fetchRequest.sortDescriptors = [sortDescriptor]
    
    if let fetchResults = managedObjectContext!.executeFetchRequest(fetchRequest, error: nil) as? [LogItem] {
        logItems = fetchResults
    }
}

Now the fetch request has it’s sortDescriptors property set. Note that this is an array, which is why we need the brackets around the single sortDescriptor we created using the title key. Running the app you should now see the sorted (alphabetically) list of items, much better.

Let’s also look at filtering out certain elements. First, let’s just try and only get the “1st Item”. We’ll create an NSPredicate that uses a string to represent the requirement that any object must fulfill in order to pass through the query.

// Create a new predicate that filters out any object that
// doesn't have a title of "1st Item" exactly.
let predicate = NSPredicate(format: "title == %@", "1st Item")

// Set the predicate on the fetch request
fetchRequest.predicate = predicate

If you haven’t seen the format syntax yet, or haven’t seen it in a while, it’s simple enough to say that any time you see format as a named parameter, it comes from the Objective-C methods “predicateWithFormat”, or “stringWithFormat”, and so on. This replaces any instances of the %@ symbol with an object’s description (the value of a string, or an otherwise useful representation of other types of objects). For primitive types such as an Int, you’ll want to instead opt for %i, %f for Float, and so on.

So when you see
(format: “title == %@”, “1st Item”)

What the compiler sees is something like this:
(“title == ’1st Item’”)

So we’re just specifying we want title to equal that exact string.

Running the app now we should see just our first item.

We could also do a string comparison using the contains keyword, if we look for the substring “th” we’ll only get the 4th, 5th, and 6th items.

// Search for only items using the substring "th"
let predicate = NSPredicate(format: "title contains %@", "th")

// Set the predicate on the fetch request
fetchRequest.predicate = predicate

What if we wanted to combine the two though? We want both items containing the string “th” and any one with a title “1st Item”?

First, let’s create the two separate predicates:

// Create a new predicate that filters out any object that
// doesn't have a title of "1st Item" exactly.
let firstPredicate = NSPredicate(format: "title == %@", "1st Item")

// Search for only items using the substring "th"
let thPredicate = NSPredicate(format: "title contains %@", "th")

Then combine them using the NSCompoundPredicate constructor:

// Combine the two predicates above in to one compound predicate
let predicate = NSCompoundPredicate(type: .OrPredicateType, subpredicates: [firstPredicate, thPredicate])

// Set the predicate on the fetch request
fetchRequest.predicate = predicate

Since we want both cases of the “1st Item” and the 4-6 items, we use a compound predicate type of .OrPredicateType

All this is just a confusing way of saying, “give me any items where the firstPredicate or the thPredicate is true.”

This concludes Part 2 for now, in the next section we’ll move in to a more applicable scenario by adding a way for user’s to create log items, edit, save, and delete them.

The complete source code to this section »

If you’re reading this scratching your head, or just want a deeper dive in to Core Data, be sure to look at my Swift book which is now available for pre-order. And don’t forget to subscribe to get updates on this tutorial series via e-mail.

Got a question or issue?

Join us on our new forums.

Sharing is caring :)

 
Developing iOS 8 Apps in Swift
An upcoming ebook detailing everything you need to know to produce marketable apps for iOS 8 using swift.
Learn to produce real world applications through tutorials. Available for pre-order today at a 50% discount.

Early Access Available Now Via PayPal


Sign up now and get a set of FREE video tutorials on writing iOS apps coming soon.



Subscribe via RSS

Core Data in Swift Tutorial (Part 1)

 

This post compatible with Xcode 6 GM

Core Data is the de facto standard way to persist and manage data in both iPhone and Mac applications. So it’s only natural that we should take some time to learn about it when building apps.

The first thing to know about Core Data before diving in is that it is not a relational database, and although it uses SQLite as a backing engine, is not an ORM to a relational database either. The SQLite backend is more of an implementation detail, and in fact binary files or plists can be used instead.

The official Apple documentation describes Core Data like this:

“The Core Data framework provides generalized and automated solutions to common tasks associated with object life-cycle and object graph management, including persistence.”
[developer.apple.com]

Before we get too technical about what Core Data is, I think it’s useful to dive in and start playing with the API a bit.

Create a new Xcode 6 project using a single-view template, Swift as the language, and with Core Data enabled. I’ll call the project MyLog.

Looking at the AppDelegate.swift file you’ll notice using this option has added quite a few functions. Most of these are setting up the Core Data stack and the defaults are fine for now. The primary object that needs to be used to work with Core Data is the managedObjectContext defined here.

lazy var managedObjectContext: NSManagedObjectContext? = {
    // Returns the managed object context for the application (which is already bound to the persistent store
    // coordinator for the application.) This property is optional since there are legitimate error
    // conditions that could cause the creation of the context to fail.
    let coordinator = self.persistentStoreCoordinator
    if coordinator == nil {
        return nil
    }
    var managedObjectContext = NSManagedObjectContext()
    managedObjectContext.persistentStoreCoordinator = coordinator
    return managedObjectContext
}()

For the most part, all you really need to know about this though, is that managedObjectContext is a lazily computed variable on AppDelegate. Knowing this we can find it from our ViewController.swift file. For example in viewDidLoad() of ViewController.swift, we can use this code to print the managedObjectContext’s description to the console.

First we need to set up a lazily loaded variable in the ViewController.swift file that can help us get the managedObjectContext from the app delegate using the UIApplication.sharedApplication().delegate property. Note that you’ll need to import CoreData in order to use the NSManagedObjectContext class name here.

lazy var managedObjectContext : NSManagedObjectContext? = {
    let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
    if let managedObjectContext = appDelegate.managedObjectContext {
        return managedObjectContext
    }
    else {
        return nil
    }
}()

override func viewDidLoad() {
    super.viewDidLoad()
    println(managedObjectContext!)
}

The managedObjectContext variable is computed using the existing managedObjectContext in the application’s delegate. In viewdidLoad() we cause this variable to be computed by printing it to the console. If your application is set up right you should see something like this:

<NSManagedObjectContext: 0x7fff39c3b790>

You’ll notice the Xcode template produced an additional file, MyLog.xcdatamodeld.

Opening up this file you can see the Core Data model editor.

Let’s add a new Core Data entity called LogItem. Our log app will show a list of LogItems, which have a bit of text in them.

Click the “Add Entity” button and then in the right-hand panel select the “Data Model Inspector”. From here we can rename the default name, Entity, to by LogItem.

Next, at the bottom we can add our first attribute by pressing the “+ Atrribute” button at the bottom.

Name this attribute title, and give it a type of String. We’ll also add a second attribute of type String called itemText.

Now that we have our first Entity created, we want to also be able to directly access this entity as a class in our code. Xcode provides an automated tool to do this. In the menubar select Editor->Create NSManagedObject Subclass…

In the first prompt, check the MyLog model and press next. Then, check the LogItem entity, and press next again.
A file save window should appear with an option to specify the language as Swift, select this. Finally hit Create, and you should now see a LogItem.swift file added.

import Foundation
import CoreData

class LogItem: NSManagedObject {
    @NSManaged var title: String
    @NSManaged var itemText: String
}

This class is generated from the xcdatamodeld file. The entity we created is represented by the similarly named class LogItem, and the attributes are turned in to variables using the @NSManaged identifier, which gives the variables special treatment allowing them to operate with Core Data. For most intents and purposes though, you can just think of these as your typical properties.

Because of the way Swift modules work, we need to make one modification to the core data model. In the field “Class” under the data model inspector for our entity, LogItem, we need to specify the project name as a prefix to the class name. So instead of just specifying “LogItem” as the class, it needs to say “MyLog.LogItem”.

In our ViewController.swift file in the viewDidLoad method, let’s instantiate some instances of LogItem. There are many ways to do this, but the least verbose is to use the insertNewObjectForEntityForName method of NSEntityDescription.

override func viewDidLoad() {
    super.viewDidLoad()

    let newItem = NSEntityDescription.insertNewObjectForEntityForName("LogItem", inManagedObjectContext: self.managedObjectContext!) as LogItem
}

Here we insert a new object in to the core data stack through the managedObjectContext that the template function added to AppDelegate for us. This method returns an NSManagedObject which is a generic type of Core Data object that responds to methods like valueForKey. If you don’t quite understand what that means, don’t worry too much about it, let’s keep moving.

With an NSManagedObject version of newItem, we could say newItem.valueForKey(“title”) to get the title. But this is not the best approach because it leaves too much opportunity to mis-type an attribute name, or get the wrong object type unexpectedly and have hard crashes trying to access these attributes.

So, in our case, we instead cast the NSManagedObject that insertNewObjectForEntityForName returns, to our generated class LogItem.

What this means, simply put, is that we can set the title and itemText like this:

override func viewDidLoad() {
    super.viewDidLoad()
    
    let newItem = NSEntityDescription.insertNewObjectForEntityForName("LogItem", inManagedObjectContext: self.managedObjectContext!) as LogItem
    
    newItem.title = "Wrote Core Data Tutorial"
    newItem.itemText = "Wrote and post a tutorial on the basics of Core Data to blog."
    presentFirstItem()
}

If we had not generated our LogItem.swift file earlier, the type LogItem would not be defined and we would be limited to working only with NSManagedObject types. This is a really nasty way to work with the Core Data API as it relies heavily on determining object classes, entities, state, and more at runtime based on string comparisons, yuck!

Now that we’ve created a new item, and set both it’s title and text, we can query it elsewhere in our app and get the object back. At the end of viewDidLoad() call the method, presentItemInfo, we’ll implement it now:

func presentItemInfo() {
    let fetchRequest = NSFetchRequest(entityName: "LogItem")
    if let fetchResults = managedObjectContext!.executeFetchRequest(fetchRequest, error: nil) as? [LogItem] {
        
        let alert = UIAlertView()
        alert.title = fetchResults[0].title
        alert.message = fetchResults[0].itemText
        alert.show()
    }
}

First, we create a new NSFetchRequest instance using the entity LogItem.
Next, we create a fetchResults variable by using the executeFetchRequest method of managedObjectContext. The only thing we have specified in our fetchRequest is the entity, so this particular fetch just returns every record.
Next we create an empty UIAlertView object to present a message to the screen, and set it’s title and message properties to the title and itemText properties of the first LogItem in our fetch results (which is an array of LogItem’s).

Run the app and you should see the item presented to the screen. You’ve now stored and retrieved data from Core Data. This is the first step in to building apps with persistent data.

In part 2, we’ll talk about working with multiple records and using NSPredicate to perform filtered requests.

Go To Part 2 Now »

The full source code to this part is available here on Github.

If you found this post useful, check out my other tutorials, and take a look at my Swift eBook, which is now available for early access to anyone who pre-orders via PayPal.

Got a question or issue?

Join us on our new forums.

Sharing is caring :)

 
Developing iOS 8 Apps in Swift
An upcoming ebook detailing everything you need to know to produce marketable apps for iOS 8 using swift.
Learn to produce real world applications through tutorials. Available for pre-order today at a 50% discount.

Early Access Available Now Via PayPal


Sign up now and get a set of FREE video tutorials on writing iOS apps coming soon.



Subscribe via RSS

Taking control of the iPhone camera in iOS 8 with Swift (Part 2)

 

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

Got a question or issue?

Join us on our new forums.

Sharing is caring :)

 
Developing iOS 8 Apps in Swift
An upcoming ebook detailing everything you need to know to produce marketable apps for iOS 8 using swift.
Learn to produce real world applications through tutorials. Available for pre-order today at a 50% discount.

Early Access Available Now Via PayPal


Sign up now and get a set of FREE video tutorials on writing iOS apps coming soon.



Subscribe via RSS