Hi Kevin,
Thanks for Mastering Xcode 7 & Swift and your written clarity. I am new to Swift & iOS but not new to programming or app Dev -- AngularJS, Cordova, jQuery, HTML, PHP, SQL, noSQL, JSON, ActionScript.
After going through most of the Book's examples, I am trying to implement a basic "ToDo List" based on iDeliverMobileCD & ToDoListDemo. I got it to work, but I don't think the code below is correct. I am using the Master List template. mmBizObj, ABizObj, DataModel and EntitiesBizControllers are somewhat "set."
Conceptually, the app loads and displays a List from an array that comes from the DB via mmBizObj CoreData's wrapper. When I select a MasterView row it loads the DetailView with only the Task Array associated with this entity only -- is this correct? Feels very RDBMS.
Relationships with mmBizObj & CoreData is not getting through plus I have several questions and a bunch of concepts that I would welcome some clarification.
Q1. When do the ListEntity's "tasks" NSSet gets populated? or does ListEntity remain clueless and it simply trickles up the chain? isn't this like a SQL join?
// Concepts:
Q2. All CRUD functions only take place in the Entity Business Controller, yes? this separation of responsibility is it considered MVVM?
Q3. Can the mmBiZObj be in the backgroundContext? -- I understand that CRUD must happen in the same Context to avoid confusing CoreData.
Q4. I know am getting ahead of myself, ReactiveCocoa/RxReactive sounds seductive, could mmBiZObj peacefully coexist? I couldn't find your POV article on ReactiveCocoa.
Q5. Ohh yeah, can I use mmBiZObj or a variation of thereof in a production app?
// SET UP
ListEntity
@NSManaged var listName: String
@NSManaged var tasks: NSSet?
TaskEntity
@NSManaged var taskName: String
@NSManaged var taskStatus: NSNumber
@NSManaged var list: ListEntity
// ZE BIZCONTROLLERS:
class List<T: ListEntity>: ABusinessObject<T> {
// the List
var listEntity: Array<T>!
override init() {
super.init()
}
func getAllListEntities() -> Array<T> {
self.listEntity = self.getAllEntities()
return self.listEntity
}
// MARK: - CD
// TODO: - CRUD
func addItemToList(desc: String) -> ListEntity {
// Create New Entity & Populate
let newListEntity = self.createEntity()
newListEntity.listName = desc
// Save to array
self.listEntity.append(newListEntity)
// Seve to Database
self.saveEntities()
return newListEntity
}
// Remove the object at index
func removeObjectAtIndexPath(indexPath: NSIndexPath) {
self.listEntity.removeAtIndex(indexPath.row)
self.saveEntities() // why is not
}
// Remove Entity
override func deleteEntity(entity: T) {
self.deleteEntity(entity)
// super.deleteEntity(entity)
self.saveEntities()
}
}
class Task<T: TaskEntity>: ABusinessObject<T> {
// Tasks
var taskEntity: Array<T>!
// MARK: - CD
// TODO: - CRUD
func addItemToTask(taskName: String, status: Bool, list: ListEntity?) -> TaskEntity {
// Create entity & Populate
let newTaskEntity = self.createEntity()
newTaskEntity.taskName = taskName
newTaskEntity.taskStatus = status
newTaskEntity.list = list
// Insert into array... Crash, unwrapping an Optional value??
self.taskEntity.append(newTaskEntity)
// Seve to dB
self.saveEntities()
// return Array
return newTaskEntity
}
// Get all items for this List
func getTasksforList(listEntity: ListEntity) -> Array<T> {
let predicate = NSPredicate(format: "list = %@", listEntity)
return self.getEntitiesMatchingPredicate(predicate)
}
func getAllTaskEntities() -> Array<T> {
self.taskEntity = self.getAllEntities()
return self.taskEntity
}
}
// ZE VIEWS
// Master View:
class MasterViewController: UITableViewController {
var detailViewController: DetailViewController? = nil
// The List's Business Controller
var list = List()
// Managed Object
var listEntity: ListEntity!
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.leftBarButtonItem = self.editButtonItem()
self.list.getAllListEntities()
// Add btn.
let addButton = UIBarButtonItem(barButtonSystemItem: .Add, target: self, action: "insertNewObject:")
self.navigationItem.rightBarButtonItem = addButton
// 6+/iPad UI
if let split = self.splitViewController {
let controllers = split.viewControllers
self.detailViewController = (controllers[controllers.count-1] as! UINavigationController).topViewController as? DetailViewController
}
}
override func viewWillAppear(animated: Bool) {
self.clearsSelectionOnViewWillAppear = self.splitViewController!.collapsed
super.viewWillAppear(animated)
}
// MARK: - Insert func
func insertNewObject(sender: AnyObject) {
self.list.addItemToList("Absolutely") // Manual insert
// find where to insert the new row
let indexPath = NSIndexPath(forRow: (self.list.listEntity.count-1), inSection: 0)
// show UI insertion
self.tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
}
// MARK: - Segues
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showDetail" {
// which row did you select?
if let indexPath = self.tableView.indexPathForSelectedRow {
let controller = (segue.destinationViewController as! UINavigationController).topViewController as! DetailViewController
controller.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem()
controller.navigationItem.leftItemsSupplementBackButton = true
// Pass the Selected Entity
controller.selectedListEntity = self.list.listEntity[indexPath.row]
}
}
}
// MARK: - Table View
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.list.listEntity.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
cell.textLabel?.text = self.list.listEntity[indexPath.row].listName
return cell
}
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return true
}
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
// why remove again?
self.list.removeObjectAtIndexPath(indexPath)
// delete Entity, why again??? -- Crash. invalid #rows
// self.list.deleteEntity(self.list.listEntity[indexPath.row])
// UI show removal -- Crash. invalid #rows
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
} else if editingStyle == .Insert {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view.
}
}
}
// Detail View
class DetailViewController: UIViewController {
@IBOutlet weak var detailDescriptionLabel: UILabel!
@IBOutlet weak var tableView: UITableView!
// Ze List
var selectedListEntity: ListEntity!
// Business controller
var task = Task()
override func viewDidLoad() {
super.viewDidLoad()
// self.configureView()
// Share the List & Task Context
self.task.managedObjectContext = self.selectedListEntity.managedObjectContext!
// Get only this List Task Entities
self.task.getTasksforList(selectedListEntity)
}
@IBAction func addTask(sender: AnyObject) {
// Add to the DB
self.task.addItemToTask("Another Manual Insert", status: false, list: selectedListEntity)
// Where to Insert Row
let indexPath = NSIndexPath(forRow: (self.task.taskEntity.count-1), inSection: 0)
// insert UI
self.tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
// Array into an NSSet...

// self.selectedListEntity.tasks = self.task.taskEntity
}
// TODO: - Clean up
var detailItem: AnyObject? {
didSet {
// Update the view.
self.configureView()
}
}
func configureView() {
// Update the user interface for the detail item.
if let detail = self.detailItem {
if let label = self.detailDescriptionLabel {
label.text = detail.description
}
}
}
// MARK: - Table View
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Might be empty
if let tasks = self.task.taskEntity {
return tasks.count
}
return 0
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("taskCell", forIndexPath: indexPath)
cell.textLabel?.text = self.task.taskEntity[indexPath.row].taskName
return cell
}
}