Introduction
This is part of a series of articles describing the creation of a simple iOS app. A complete list of the articles is included in the first part A Simple Swift iOS App from Start to Finish - Introduction.
I have set up a web site for the finished app at http://DaysWithoutThings.com and you can download it free directly from the app store:
In this article I am going to create a class to encapsulate a collection of my Thing objects. My app will use this to hold all the things the user defines. The class will contain the high level interface to the data which will be stored in a local core data store. I will leave the low level core data functions in the AppDelegate class.
My class will be called ThingCollection and contain method to do the following:I have set up a web site for the finished app at http://DaysWithoutThings.com and you can download it free directly from the app store:
In this article I am going to create a class to encapsulate a collection of my Thing objects. My app will use this to hold all the things the user defines. The class will contain the high level interface to the data which will be stored in a local core data store. I will leave the low level core data functions in the AppDelegate class.
- Add a new Thing.
- Check if a Thing already exists in the data store.
- Find a Thing if it already exists in the data store.
- Delete a Thing from the data store.
- Update the record number of days for a Thing.
- Reset the current number of days for a thing.
- Reload all the Things from the data store.
My class will also include a property called ‘things’ that will return an array of all the things in the data store sorted into alphabetical order.
I will call the new file ThingCollection.swift.
Once I have clicked 'Create' new swift file is added to my project. The swift file is empty apart from the copyright banner and the import for the foundation library. I will start by adding a declaration for my new class.
import Foundation
class ThingCollection {
}
The things property
The first thing I’m going to add is the things property. This will hold a sorted list of Thing objects.
class ThingCollection {
var things:Array<Thing> = []
}
The property will be initialized in the init method of the class by calling the reloadData method.
class ThingCollection {
var things:Array<Thing> = []
init() {
self.reloadData()
}
func reloadData() {
}
}
The reloadData method
The reloadData method will just call a private method to create a Core Data fetch request result which it will then assign to the things property. Before I can do this I need to give my class a reference to the AppDelegate class so that it can access its core data methods.To do thisI am going to add a constant property to hold a reference to the AppDelegate object and assign it using the global reference in the init method. The global reference is a property of the UIApplication class and to use this I need to import the UIKit library.
import Foundation
import UIKit
class ThingCollection {
let appDelegate:AppDelegate
var things:Array<Thing> = []
init() {
appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
reloadData()
}
I can now implement my reloadData method.
func reloadData() {
things = fetchThings()
}
The fetchThings method
func fetchThings() -> Array<Thing> {
var error: NSError? = nil
var request: NSFetchRequest = NSFetchRequest(entityName: "Thing")
var sort: NSSortDescriptor = NSSortDescriptor(key: "name", ascending: true)
var resultItems = Array<Thing>()
request.sortDescriptors = [sort]
if let myError = error
{
var saveFlag = false
if let resultArray = results {
for result in resultArray {
if var thing = result as? Thing {
if updateRecord(&thing) {
saveFlag = true
}
resultItems.append(thing)
}
}
if saveFlag {
appDelegate.saveContext()
}
}
}
return resultItems
}
var error: NSError? = nil
var request: NSFetchRequest = NSFetchRequest(entityName: "Thing")
var sort: NSSortDescriptor = NSSortDescriptor(key: "name", ascending: true)
var resultItems = Array<Thing>()
var results = appDelegate.managedObjectContext?.executeFetchRequest(
request, error: &error){
// Handle error
} else {var saveFlag = false
if let resultArray = results {
if var thing = result as? Thing {
if updateRecord(&thing) {
saveFlag = true
}
resultItems.append(thing)
}
}
if saveFlag {
appDelegate.saveContext()
}
}
}
return resultItems
}
I use the Swift 'if let' construct to ensure I have a valid set of results before attempting to process them. I have to use 'if var' instead to check for a valid Thing object. This is because my updateRecord method may make changes to the object.
The updateRecord method
The updateRecord method takes a reference to a Thing object as a parameter and checks if its currentDayCount is larger than its recordDayCount. If it is it sets the recordDayCount to the currentDayCount and then calls the AppDelegate’s saveContext method to commit the change to the data store.
func updateRecord(inout thing:Thing) -> Bool {
if Int(thing.recordDayCount) < Int(thing.currentDayCount) {
thing.recordDayCount = thing.currentDayCount
appDelegate.saveContext()
return true
}
return false
}The addThing method
func addThing(name: String!) -> Bool
{
if !thingExists(name)
{
createThing(name)
return true
}
return false
}
The thingExists and findThing methods
The thingExists and findThing methods are both concerned with finding a record for a particular Thing. They both use the Thing’s name to identify it so they can both use the same fetch request. Because of this I’m going to construct that fetch request in a separate method called findRequest.All three methods take a single string parameter which is the name of the thing. findRequest creates an NSFetchRequest object for my Thing entity and attaches a predicate to match the name to the name parameter.
func findRequest(name: String) -> NSFetchRequest {
var request: NSFetchRequest = NSFetchRequest(entityName: "Thing")
request.predicate = NSPredicate(format:"name == '\(name)' ")
request.includesSubentities = false
return request
} If the count is zero the method returns false and if the count is greater than zero it returns true. If the request fails with an error it will return false.
func thingExists(name: String) -> Bool {
var error: NSError? = nil
var request: NSFetchRequest = findRequest(name)
var count = appDelegate.managedObjectContext?.countForFetchRequest(
request, error:&error)
if let myError = error
{
return false
} else {
return count > 0
}
}
func findThing(name: String) -> Thing? {
var error: NSError? = nil
var request: NSFetchRequest = findRequest(name)
if results.count < 1 {
return nil
} else {
return results[0] as? Thing
}
} else {
return nil
}
}
var error: NSError? = nil
var request: NSFetchRequest = findRequest(name)
if let results = appDelegate.managedObjectContext?.executeFetchRequest(
request, error: &error) {return nil
} else {
return results[0] as? Thing
}
} else {
return nil
}
}
The createThing method
If the new Thing object is created successfully I assign the name that was given as a parameter and set the startDate to the current Date and Time. The ‘dateType’ attribute is set to its default value of 'when'.
Once the attributes are set I use the appDelegate's saveContext method to save the new record to the store.
Finally I call the ThingCollection's reloadData method to update the things property to include the new record.
func createThing(name:String)
{
if let newThing =
NSEntityDescription.insertNewObjectForEntityForName("Thing",inManagedObjectContext: appDelegate.managedObjectContext!)
as? Thing {
newThing.name = name
newThing.startDate = NSDate().beginningOfDay()
newThing.dateType = .When
appDelegate.saveContext()
reloadData()
}
}
{
if let newThing =
NSEntityDescription.insertNewObjectForEntityForName("Thing",inManagedObjectContext: appDelegate.managedObjectContext!)
as? Thing {
newThing.startDate = NSDate().beginningOfDay()
newThing.dateType = .When
appDelegate.saveContext()
reloadData()
}
}
The deleteThing method
func deleteThing(thing:Thing)
{
appDelegate.managedObjectContext?.deleteObject(thing)
appDelegate.saveContext()
reloadData()
}
The resetDaysForThing method
The resetDaysForThing method will call updateRecord in case the currentDayCount exceeds the record day count. It then sets the start date to the start of the current day which has the effect of reseting the currentDayCount to zero.
func resetDaysForThing(inout thing:Thing) {
updateRecord(&thing)
thing.startDate = NSDate().beginningOfDay()
appDelegate.saveContext()
}
updateRecord(&thing)
appDelegate.saveContext()
}
Adding a global ThingCollection instance
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var thingCollection: ThingCollection?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
//sleep(10)
thingCollection = ThingCollection()
return true
}
Wrapping up
My ThingCollection class is now complete. Before moving on I am going to run my app in the Simulator to make sure it still compiles and then commit it to source control.
In my next article I will start to create the UI in Interface Builder.
Next: A Simple Swift iOS App from Start to Finish - Creating the Main Display View
Next: A Simple Swift iOS App from Start to Finish - Creating the Main Display View
No comments:
Post a Comment