Debugging, Printing, and Logging

FinalBugViewController.swift

import UIKit

// MARK: - FinalBugViewController: UIViewController

class FinalBugViewController: UIViewController {

    // MARK: Properties
    
    let bugFactory = BugFactory.sharedInstance()
    let maxBugs = 100
    let moveDuration = 3.0
    let disperseDuration = 1.0    
    var bugs = [UIImageView]()

    // MARK: Life Cycle
    
    override func viewDidLoad() {
        super.viewDidLoad()        
        let singleTapRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleSingleTap))
        view.addGestureRecognizer(singleTapRecognizer)                
    }

    // MARK: Bug Functions
    
    func addBugToView() {
        if bugs.count < maxBugs {
            let newBug = bugFactory.createBug()
            bugs.append(newBug)
            view.addSubview(newBug)
            moveBugsAnimation()
        }
    }

    func emptyBugsFromView() {
        for bug in self.bugs {
            bug.removeFromSuperview()
        }
        self.bugs.removeAll(keepCapacity: true)
    }
    
    // MARK: View Animations
    
    func moveBugsAnimation() {
        UIView.animateWithDuration(moveDuration) {
            for bug in self.bugs {
                let randomPosition = CGPoint(x: CGFloat(arc4random_uniform(UInt32(UInt(self.view.bounds.maxX - bug.frame.size.width))) + UInt32(bug.frame.size.width/2)), y: CGFloat(arc4random_uniform(UInt32(UInt(self.view.bounds.maxY - bug.frame.size.height))) + UInt32(bug.frame.size.height/2)))
                bug.frame = CGRect(x: randomPosition.x - bug.frame.size.width/1.5, y: randomPosition.y - bug.frame.size.height/1.5, width: BugFactory.bugSize.width, height: BugFactory.bugSize.height)
            }
        }
    }
    
    func disperseBugsAnimation() {
        UIView.animateWithDuration(disperseDuration, animations: { () -> Void in
            for bug in self.bugs {
                let offScreenPosition = CGPoint(x: (bug.center.x - self.view.center.x) * 20, y: (bug.center.y - self.view.center.y) * 20)
                bug.frame = CGRect(x: offScreenPosition.x, y: offScreenPosition.y, width: BugFactory.bugSize.width, height: BugFactory.bugSize.height)
            }
        }, completion: { (finished) -> Void in
            if finished { self.emptyBugsFromView() }
        })
    }
    
    // MARK: Actions
    
    @IBAction func popToMasterView() {
        self.navigationController!.popToRootViewControllerAnimated(true)
    }
}

// MARK: - FinalBugViewController (UIResponder)

extension FinalBugViewController {
    override func canBecomeFirstResponder() -> Bool { return true }
    override func motionEnded(motion: UIEventSubtype, withEvent event: UIEvent?) {
        if motion == .MotionShake { disperseBugsAnimation() }
    }
    func handleSingleTap(recognizer: UITapGestureRecognizer) { addBugToView() }
}

// MARK: - FinalBugViewController (CustomStringConvertible)

// NOTE: You don't have to conform to CustomStringConvertible since this is already done by FinalBugViewController's superclasses (via NSObject).

extension FinalBugViewController {

    override var description: String {
        return "FinalBugViewController contains \(bugs.count) bugs\n"
    }
    
}

// MARK: - FinalBugViewController (CustomDebugStringConvertible)

// NOTE: You don't have to conform to CustomDebugStringConvertible since this is already done by FinalBugViewController's superclasses (via NSObject).

extension FinalBugViewController {
    
    override var debugDescription: String {
        var index = 0
        var debugString = "FinalBugViewController contains \(bugs.count) bugs...\n"
        for bug in bugs {
            debugString = debugString + "Bug\(index): \(bug.frame)\n"
            index += 1
        }
        return debugString
    }
}

// MARK: - FinalBugViewController (debugQuickLookObject)

extension FinalBugViewController {
    
    func debugQuickLookObject() -> AnyObject? {
        
        let singleSquareLength: CGFloat = 10.0
        let squaresInRow = 10
        let imageSize = CGSizeMake(singleSquareLength * CGFloat(squaresInRow), singleSquareLength * CGFloat(bugs.count / squaresInRow + 1))
        
        UIGraphicsBeginImageContextWithOptions(imageSize, true, 0)
        var x: CGFloat = 0.0
        var y: CGFloat = 0.0
        for bug in bugs {
            bug.tintColor.set()
            UIRectFill(CGRectMake(x, y, singleSquareLength, singleSquareLength))
            x += singleSquareLength
            if x > CGFloat(squaresInRow) * singleSquareLength {
                y += singleSquareLength
                x = 0.0
            }
        }
        UIColor.yellowColor().set()
        UIRectFill(CGRectMake(x, y, singleSquareLength, singleSquareLength))
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        
        return image
    }
}
import UIKit

// MARK: - FinalSettingsViewController: UIViewController

class FinalSettingsViewController: UIViewController {
    
    // MARK: Properties
    
    let bugFactory = BugFactory.sharedInstance()
    
    // MARK: Outlets
    
    @IBOutlet weak var currentBugTypeImageView: UIImageView!
    
    // MARK: Life Cycle
    
    override func viewDidLoad() {
        super.viewDidLoad()
        currentBugTypeImageView.tintColor = BugFactory.bugTints[bugFactory.currentBugType.rawValue]
    }
    
    // MARK: Actions
    
    @IBAction func dismissSettingsTouched(sender: AnyObject) { self.dismissViewControllerAnimated(true, completion: nil) }
    
    @IBAction func bugTypeSelected(sender: UIButton) {
        bugFactory.currentBugType = BugFactory.BugType(rawValue: Int(sender.currentTitle!)!)!
        self.dismissViewControllerAnimated(true, completion: nil)
    }
}

How information flows

Context (notebook)
-> notification
FetchedResultsController
-> delegate
CoreDataTableViewController
-> update
TableView

saving images(BLOBs) and migrating data model

Importing large sets of objects into the database without blocking the user interface
Downloading new objects from a REST service and inserting them into the db.
Saving datasets in the background since saving can take a user-perceivable amount of time.

Filtering By Section

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)-> UITableViewCell{
	
	let note = fetchedResultsController?.object(at: indexPath) as! Note

	let cell = tableView.dequeueReusableCell(withIdentifier: "Note", for: indexPath)

	cell.textLabel?.text = note.text

	return cell
}
override func prepare(for segue: UIStoryboardSeque, sender: Any?){
	
	if segue.identifier! == "displayNote"{
	 	if let notesVC = segue.destination as? NotesViewController {

	 	let fr = NSFetchRequest<NSFetchRequestResult>(entityName: "Note")

	 	fr.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending:false),NSSortDescriptor(key: "text", ascending: true)]

	 	let indexPath = tableView.indexPathForSelectedRow!
	 	let notebook = fetchedResultsController?.object(at: indexPath)

	 	let pred = NSPredicate(format: "notebook = %@", argumentArray: [notebook!])

	 	fr.predicate = pred

	 	let fc = NSFetchedResultsController(fetchRequest: fr, managedObjectContext:fetchedResultsController!.managedObjectContext, sectionNameKeyPath: nil, cacheName: nil)

	 	notesVC.fetchedResultsController = fc
	 	}
	}
}

coredata

-How the table displays changes
-How a user can add a new note
-Saving and persisting changes
-Memory management

class CoreDataTableViewController: UITableViewController {
	
	var fetchedResultsController : NSFetchedResultsController<NSFetchRequestsResult>?{
		didSet {
			fetchedResultsController?.delegate = self
			executeSearch()
			tableView.reloadData()
		}
	}

	init(fetchedResultsController fc : NSFetchedResultsController<NSFetchRequestResult>, style: UITableViewStyle = .plain){
		fetchedResultsController = fc
		super.init(style: style)
	}

	required init?(coder aDecoder: NSCoder){
		super.init(coder: aDecoder)
	}
}
class NotebooksViewController: CoreDataTableViewController {
	
	// MARK: Life Cycle

	overeride func viewDidLoad(){
		super.viewDidLoad()

		title = "CoolNotes"

		let delegate = UIApplication.shared.delegate as! AppDelegate
		let stack = delegate.stack

		let fr = NSFetchRequest<NSFetchRequestResult>(entityName: "Notebook")
		fr.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true), NSSortDescriptor(key: "creationDate", ascending: false)]

		fetchedResultsController = NSFetchedResultsController(fetchRequest:  fr, managedObjectContext: stack.context, sectionNameKeyPath: nil, cacheName: nil)

	}

	override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
		let nb = fetchedResultsController!.object(at: indexPath) as! Notebook

		let cell = tableView.dequeueReusableCell(withIdentifier: "NotebookCell", for: indexPath)

		cell.textLabel?.text = nb.name
		cell.detailTextLabel?.text = "\(nb.notes!.count) notes"

		return cell
	}
}

Creating managed objects

import Foundation
import CoreData

extesion Note {
	@NSManaged var creationDate: NSDate?
	@NSManaged var text: String?
	@NSManaged var notebook: Notebook?
}
import Foundation
import CoreData

extesion NSManagedObject {
	convention init(text:String = "New Note", context: NSManagedObjectContext){
		if let ent = NSEntityDescription.entityForName("Note",
			inManagedObjectContext; context){
			self.init(entity: ent, insertIntoManagedObjectContext: context)
			self.text = text
			self.creationDate = NSDate()
		} else {
			fatalError("Unable to find Entity name!")
		}
	}
}

relationship

// Don't worry about the @NSManaged thing.
// it's just a cue to the compiler that the property
// will be managed by Core Date
class Note: NSManagedObject {
	@NSManaged var notebook : Notebook
}

class Notebook: NSManagedObject {
	@NSManaged var note : ????
}
import CoreData
struct CoreDataStack {
	// MARK: - Properties
	private let model : NSManagedObjectModel
	private let coordinator : MSPersistentStoreCoordinator
	private let modelURL : NSURL
	private let dbURL : NSURL
	let context : NSManagedObjectContext

	// MARK: - Initializers
	init?(modelName: String){

		guard let modelURL = NSBundle.mainBundle().URLForResource(modelName,
			withExtension: "momd") else {
				print("Unable to find \(modelName)in the main bundle")
				return nil}
		self.modelURL = model URL

		// Try to create the model from the URL
		guard let model = NSManagedObjectModel(contentsOfURL: modelURL) else{
			print("unable to create a model from \(modelURL)")
		}
		self.model = model

		coordinator = NSPersistentStoreCoordinator(managedObjectModel: model)

		context = NSManagedObjectContext(concurrencyType: .
			MainQueueConcurrencyType)
		context.persistentStoreCoordinator = coordinator

		// Add a SQLite store located in the documents folder
		let fm = NSFileManager.defaultManager()

		guard let docUrl = fm.URLsForDirectory(.DocumentDirectory, inDomains: UserDomainMask).first else {
			print("manage to people the document folder")
			return nil
		}
	}
}

Core Data intro

-core data intro
-core data architecture
-create a model, managed objects, and begin our own app

core data
Model(managed objects), View, Controller

Object Model
(aka “managed Object Model”)
-specifies app classes and relationships

Managed Object
-class:NSManagedObject
-example:a character of a game app
-saves the contents of its properties to a DB file

Context
-class:NSManagedObject+Context
-imagine as a place
-where objects live and where operations take place

Fetch request
-searches context for certain managed objects

Fetched Results Controller
-part of controller layer
-controls how data from a fetch request is displayed in a view

Stores
-where managed objects are stored

Store coordinator
-allow you to have multiple stores

The iOS file system

The iOS file system
The sandbox
Subfolders of the sandbox
Writing and reading to the file system

“sandbox”: where the app keeps all of its “stuff”.

Sandbox
Bundle Container – MyApp.app
-> executable code, resources
Data Container – Documents, Library, Temp
-> user data(Documents/Inbox), non-user data(Library/Applications Support, Library/Caches)
iCloud Container – …

Library/Preferences/info.myapp.mobile.plist

*Documents: important stuff
*Caches:
*Library:

1. Find where the sandbox is.
2. Write to a file

-NSFileManager to get the path to the sandbox
-String to write or read text files
-NSData to write or read binary files

func sandboxPlayground(){
	let fm = FileManager.default
	let urls = fm.urls(for:.documentDirectory, in: .userDomainMask)
	let url = urls.last?.appendingPathComponent("file.txt")

	do {
		try "Hi There!".write(to: url!, atomically: true, encoding: String.Encoding.utf8)
	} catch { 
		print("Error while writing")
	}

	do {
		let content = try String(contentsOf: url!, encoding: String.utf8)

		if content == "Hi There!"{
			print("yay")
		} else {
			print("oops")
		} 
	} catch {
		print("Something went wrong")
	}

}

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
	
	sandboxPlayground()
	return true
})

ViewController.swift

func checkIfFirstLaunch(){
	if UserDefaults.standard.bool(forKey: "HasLaunchedBefore"){
		print("App has launched before")
	} else {
		print("This is the first launch ever!")
		UserDefaults.standard.set(true, forKey: "HasLaunchedBefore")
		UserDefaults.standard.set(0.0, forKey: "Slider Value Key")
		UserDefaults.standard.synchronize()
	}

	func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool{
		print("App Delegate: will finish launching")
	}

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

	do {
		audioPlayer = try AVAudioPlayer(contentsof: receivedAudio.filePathUrl as URL)
	} catch _ {
		audioPlayer = nil
	}
	audioPalyer.enableRate = true

	audioEngine = AVAudioEngine()
	do {
		audioFile = try AVAudioFile(forReading: receivedAudio.filePathUrl as URL)
	} catch _ {
		audioFile = nil
	}

	sliderView.value = UserDefaults.standard.float(forKey: SliderValueKey)

	setUserInterfaceToPlayMode(false)
}

	@IBAction func playAudio(_ sender: UIButton){
		let pitch = sliderView.value

		playAudioWithVariablePitch(pitch)
		setUserInterfaceToPlayMode(true)

		UserDefaults.standard.set(sliderView.value, forKey: SliderValueKey)
	}

Before we diveinto

default database <- system-wide feault, language defaults, app-specific defaults NSUserDefaults.standardUserDefaults() -> shared defaults object

key, value
“address” -> “P. Sherman, 42 Wallaby Way, Sydney”

NSUserDefaults.standardUserDefaults().valueForKey(key:String)

stringForKey(key:String), boolForKey(key:String), floatForKey(key:String)

NSUserDefaults.standardUserDefaults().setValue(AnyObject?, key:”keyString”)

import UIKit
import Foundation

class ViewController: UIViewController {
	@IBOutlet var mainView: UIView!
	@IBOutlet weak var midnightThemeLabel: UILabel!
	@IBOutlet weak var themeSwitch: UISwitch!
	@IBOutlet weak var titleLabel: UILabel!
	@IBOutlet weak var imageView: UIImageView!

	override func viewDidLoad(){
		super.viewDidLoad()
		if let weWantMidnight = NSUserDefaults.standardUserDefaults().valueForKey("midnightThemeOn") {
			if weWantMidnight as Bool {
				switchToMidnight()
				print("we lik midnight")
			} else {
				print("we like daylight")
			}
		} else {
		NSUserDefaults.standardUserDefaults().setValue(false, forKey:"midnightThemeOn")
		print("This is the first launch ever!")
		}
	}
}