Gold, Silver, Bronze, and Chapter 9 in Swift


#1

Please let me know of any improvements I could make!

Note that I created some Objective C NSMutableArray-eske extensions for the Swift Array. If you don’t recognize then function I call, that’s why.

Bronze override func tableView(tableView: UITableView!, titleForDeleteConfirmationButtonForRowAtIndexPath indexPath: NSIndexPath!) -> String!"
Silver (also changed the cellForRowAtIndexPath function, as in the previous chapters challenge override func tableView(tableView: UITableView!, canMoveRowAtIndexPath indexPath: NSIndexPath!) -> Bool"
Gold override func tableView(tableView: UITableView!, targetIndexPathForMoveFromRowAtIndexPath sourceIndexPath: NSIndexPath!, toProposedIndexPath proposedDestinationIndexPath: NSIndexPath!) -> NSIndexPath!

ItemsViewController.swift

[code]//
// ItemsViewController.swift
// Homepwner
//
// Created by adam on 6/18/14.
// Copyright © 2014 Adam Schoonmaker. All rights reserved.
//

import UIKit

class ItemsViewController: UITableViewController {

var _headerView : UIView? = nil


// All instances of TableViewController should use UITableViewStyle.Plain
// and this initializer
convenience init() {
    self.init(style: UITableViewStyle.Plain)
}
/*
// should be using this, but use of UITableViewController.init(style:) has a
// bug where it accidentally calls "unimplemented initializer 'init(nibName:bundle:)"
init() {
super.init(style: UITableViewStyle.Plain)
}*/

// Register the UITableViewCell class for reuse within the table
override func viewDidLoad() {
    super.viewDidLoad()
    //loadHeaderView()
    tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "UITableViewCell")
    tableView.tableHeaderView = headerView
}

}

extension ItemsViewController {
// MARK: HeaderView

@IBOutlet var headerView : UIView {
get {
    if !_headerView {
        println("HeaderView nib about to be loaded from bundle (placed right before line)")
        // Setting the owner to 'self' ensures that when the NSBundle is parsing
        // the resultant NIB file at runtime, any connections to the File's Owner
        // placeholder will be made to that ItemsViewController instance
        NSBundle.mainBundle().loadNibNamed("HeaderView", owner: self, options: nil)
        println("HeaderView nib loaded from bundle (placed right after line)")
    }
    println("got headerView")
    return _headerView!
}
set {
    println("headerView has been 'set' to newValue")
    _headerView = newValue
}
}

/*
func loadHeaderView() {
    // If the HeaderView has not been loaded yet...
    if !headerView {
        // Load HeaderView.xib
        println("loaded HeaderView")
        NSBundle.mainBundle().loadNibNamed("HeaderView", owner: self, options: nil)
    }
}
*/

@IBAction func addNewItem(sender: UIButton) {
    // Create a new Item and add it to the store
    let newItem = ItemStore.sharedStore.createItem()
    
    // Figure out where that item is in the array
    let lastRow = ItemStore.sharedStore.allItems.indexOf(object: newItem)
    println("newItem index is \(lastRow)")
    
    let indexPath = NSIndexPath(forRow: lastRow, inSection: 0)
    
    // Insert this new row into the table
    tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Top)
}

@IBAction func toggleEditingMode(sender: UIButton) {
    // If you are currently in editing mode...
    if editing {
        // Change the text of the button to inform user of state
        sender.setTitle("Edit", forState: UIControlState.Normal)
        // Turn off editing mode
        setEditing(false, animated: true)
    } else {
        // Change the text of the button to inform user of state
        sender.setTitle("Done", forState: UIControlState.Normal)
        // Enter editing mode
        setEditing(true, animated: true)
    }
}

}

extension ItemsViewController : UITableViewDataSource {
// MARK: UITableViewDataSource protocol functions

// Set the number of rows in each section
override func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int {
    return ItemStore.sharedStore.allItems.count + 1
}

// Create/set the cell for each row in each section
override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
    // Create an instance of UITableViewCell, with default appearance
    // NOTE: THIS DOES NOT REUSE CELLS (line below)
    //let cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "UITableViewCell")
    
    // Get a new or recycled cell
    // By convention, the reuse identifier is typically the name of the cell class
    // For this to work, the table view must know which kind of cell to instantiate
    // if there are no cells in the pool, done in viewDidLoad...
    let cell : UITableViewCell = tableView.dequeueReusableCellWithIdentifier("UITableViewCell", forIndexPath: indexPath) as UITableViewCell
    
    // Set the text on the cell with the description of the item that is at the
    // nth index of items, when n = "row this cell will appear in on the tableview"
    // If it's the last row, make it have the text "No more items!"
    if indexPath.row < ItemStore.sharedStore.allItems.count {
        let item = ItemStore.sharedStore.allItems[indexPath.row]
        cell.textLabel.text = item.description
    } else {
        // The last row
        cell.textLabel.text = "No more items!"
    }
    
    return cell
}

// For removing objects from the tableView
override func tableView(tableView: UITableView!, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath!) {
    
    // If the cell isn't the last cell...
    if indexPath.row < ItemStore.sharedStore.allItems.count {
        // If the tableView is asking to commit a delete command...
        if editingStyle == UITableViewCellEditingStyle.Delete {
            let items = ItemStore.sharedStore.allItems
            let item = items[indexPath.row]
            ItemStore.sharedStore.removeItem(item)
            
            // Also remove that row from the table view with an animation
            tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Fade)
        }
    }
}

// For moving rows in the tableView
override func tableView(tableView: UITableView!, moveRowAtIndexPath sourceIndexPath: NSIndexPath!, toIndexPath destinationIndexPath: NSIndexPath!) {
    
    // If the cell isn't the last cell, then move it
    if sourceIndexPath.row < ItemStore.sharedStore.allItems.count {
        ItemStore.sharedStore.moveItemAtIndex(sourceIndexPath.row, toIndex: destinationIndexPath.row)
    }
}

// Set which rows can be edited
override func tableView(tableView: UITableView!, canEditRowAtIndexPath indexPath: NSIndexPath!) -> Bool {
    // All but the last row can be edited
    if indexPath.row < ItemStore.sharedStore.allItems.count {
        return true
    } else {
        return false
    }
}

// Set which rows can be moved
override func tableView(tableView: UITableView!, canMoveRowAtIndexPath indexPath: NSIndexPath!) -> Bool {
    // All but the last row can be moved
    if indexPath.row < ItemStore.sharedStore.allItems.count {
        return true
    } else {
        return false
    }
}

}

extension ItemsViewController: UITableViewDelegate {
// MARK: UITableViewDelegate protocol methods

// Renaming the Delete Button when editing rows
override func tableView(tableView: UITableView!, titleForDeleteConfirmationButtonForRowAtIndexPath indexPath: NSIndexPath!) -> String! {
    
    return "Remove"
}

// Allows customization of the target row for a particular row as it is being moved/reordered
override func tableView(tableView: UITableView!, targetIndexPathForMoveFromRowAtIndexPath sourceIndexPath: NSIndexPath!, toProposedIndexPath proposedDestinationIndexPath: NSIndexPath!) -> NSIndexPath! {
    
    // If target row is last row, then make it the row just before the last
    if proposedDestinationIndexPath.row < ItemStore.sharedStore.allItems.count {
        return proposedDestinationIndexPath
    }
    else {
        return NSIndexPath(forRow: proposedDestinationIndexPath.row - 1, inSection: 0)
    }
}

}
[/code]

ItemStore.Swift

[code]//
// ItemStore.swift
// Homepwner
//
// Created by adam on 6/18/14.
// Copyright © 2014 Adam Schoonmaker. All rights reserved.
//

import Foundation

// lazily initiated, thread-safe from "let"
let _GlobalItemStoreSharedInstance = ItemStore()

class ItemStore : NSObject {

class var sharedStore : ItemStore {
    return _GlobalItemStoreSharedInstance
}

// Common design pattern for a class that wants strict control over its
// internal data:
// Internally, ItemStore needs to be able to mutate the array and add new 
// Items (and remove and reorder them)
var _privateItems = Item[]()
// The allItems property can't be changed by other objects
var allItems: Item[] {
    return _privateItems
}

func createItem() -> Item {
    let item = Item.randomItem()
    _privateItems.append(item)
    return item
}

func removeItem(item: Item) {
    for (index, element) in enumerate(_privateItems) {
        if element === item {
            _privateItems.removeAtIndex(index)
        }
    }
}

func moveItemAtIndex(fromIndex: Int, toIndex: Int) {
    _privateItems.moveObjectAtIndex(fromIndex, toIndex: toIndex)
}

}[/code]

ArrayExtensions.swift

[code]//
// ArrayExtensions.swift
//
//
// Created by adam on 6/19/14.
// Copyright © 2014 Adam Schoonmaker. All rights reserved.
//

import Foundation

extension Array {
func contains(#object:AnyObject) -> Bool {
return self.bridgeToObjectiveC().containsObject(object)
}

func indexOf(#object:AnyObject) -> Int {
    return self.bridgeToObjectiveC().indexOfObject(object)
}

mutating func moveObjectAtIndex(fromIndex: Int, toIndex: Int) {
    if ((fromIndex == toIndex) || (fromIndex > self.count) ||
        (toIndex > self.count)) {
            return
    }
    // Get object being moved so it can be re-inserted
    let object = self[fromIndex]
    
    // Remove object from array
    self.removeAtIndex(fromIndex)
    
    // Insert object in array at new location
    self.insert(object, atIndex: toIndex)
}

}

// Causes a compile-time error
// Supposedly because of a bug, according to a Stack Overflow post…?
// http://stackoverflow.com/questions/24154163/xcode-swift-failed-with-exit-code-254
/*
protocol Identifiable {
@infix func ===(lhs: Self, rhs: Self) -> Bool
}

func indexOf<T: Identifiable>(inout object: T, inArray array: T[]) -> Int? {
var objectIndex: Int?
for (index, element) in enumerate(array) {
if element === object {
objectIndex = index
}
}
return objectIndex
}
*/
[/code]

Item.swift

[code]//
// Item.swift
// Homepwner
//
// Created by adam on 6/18/14.
// Copyright © 2014 Adam Schoonmaker. All rights reserved.
//

import Foundation

class Item : NSObject {

var itemName: String
var serialNumber: String
var valueInDollars: Int
var dateCreated: NSDate

override var description: String {
    return "\(itemName) (\(serialNumber)): Worth \(valueInDollars), recorded on \(dateCreated)"
}

init(itemName name: String, valueInDollars value: Int, serialNumber sNumber: String) {
    // Give the instance variables initial values
    itemName = name
    serialNumber = sNumber
    valueInDollars = value
    dateCreated = NSDate()
    
    super.init()
}

convenience init(itemName name: String) {
    self.init(itemName: name, valueInDollars: 0, serialNumber: "")
}

convenience init() {
    self.init(itemName: "", valueInDollars: 0, serialNumber: "")
}

// A class method for the type Item itself, not Item instances
class func randomItem() -> Item {
    let randomAdjectiveList = ["Fluffy", "Rusty", "Shiny"]
    let randomNounList = ["Bear", "Spork", "Mac"]
    
    // Get the index of a random adjective/noun from the lists
    let adjectiveIndex = Int(arc4random() % UInt32(randomAdjectiveList.count))
    let nounIndex = Int(arc4random() % UInt32(randomNounList.count))
    
    let randomName = "\(randomAdjectiveList[adjectiveIndex]) \(randomNounList[nounIndex])"
    
    let randomValue = Int(arc4random() % 100)
    
    let randomSerialNumber = "0\(arc4random() % 10)A\(arc4random() % 26)0\(arc4random() % 10)A\(arc4random() % 26)0\(arc4random() % 10)"
    
    return Item(itemName: randomName, valueInDollars: randomValue, serialNumber: randomSerialNumber)
}

}
[/code]

AppDelegate.swift

[code]//
// AppDelegate.swift
// Homepwner
//
// Created by adam on 6/18/14.
// Copyright © 2014 Adam Schoonmaker. All rights reserved.
//

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

var window: UIWindow?


func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
    self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
    // Override point for customization after application launch.
    
    // Create a ItemsViewController
    let itemsViewController = ItemsViewController()
    
    // Place ItemsViewController's table view in the windoe hierarchy
    window!.rootViewController = itemsViewController
    
    self.window!.backgroundColor = UIColor.whiteColor()
    self.window!.makeKeyAndVisible()
    return true
}

func applicationWillResignActive(application: UIApplication) {
    // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
    // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}

func applicationDidEnterBackground(application: UIApplication) {
    // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
    // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}

func applicationWillEnterForeground(application: UIApplication) {
    // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}

func applicationDidBecomeActive(application: UIApplication) {
    // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}

func applicationWillTerminate(application: UIApplication) {
    // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}

}

[/code]