Author Topic: mmBiz, Relationships & other musings.  (Read 7887 times)

Carlos_Lema

  • Newbie
  • *
  • Posts: 3
    • View Profile
mmBiz, Relationships & other musings.
« on: January 18, 2016, 09:01:44 AM »
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
    }
   

}
« Last Edit: January 19, 2016, 10:28:46 AM by Carlos_Lema »

kjmcneish

  • Administrator
  • *****
  • Posts: 719
    • View Profile
Re: mmBiz, Relationships & other musings.
« Reply #1 on: January 19, 2016, 01:07:25 PM »
Carlos,

Can you send compress and send me a copy of your project (you can attach it your reply) so I can better answer your questions?

All the best!
Kevin

Carlos_Lema

  • Newbie
  • *
  • Posts: 3
    • View Profile
Re: mmBiz, Relationships & other musings.
« Reply #2 on: January 19, 2016, 05:09:49 PM »
Thanks Kevin!

Hope all is well in the sunny side of the world.

Here we go, I am sure it is something super obvious, but I just can't see it. best, \\ carlos

kjmcneish

  • Administrator
  • *****
  • Posts: 719
    • View Profile
Re: mmBiz, Relationships & other musings.
« Reply #3 on: January 25, 2016, 10:18:12 AM »
Carlos,

I made a few changes to your Task class so it works properly.

1. First of all, for the sake of clarity, I renamed the taskEntity property to taskEntityList, so it's more obvious that the property contains a list of entities rather than a single entity.

2.  I changed the code in the getTasksForList() method so it stores the results in the taskEntityList property before returning the results. This matches the pattern in the getAllTaskEntities() method:

Code: [Select]
    // Get all items for this List
    func getTasksforList(listEntity: ListEntity) -> Array<T> {
       
        let predicate = NSPredicate(format: "list = %@", listEntity)
       
        // KJM - Store result in array before returning it
        self.taskEntityList = self.getEntitiesMatchingPredicate(predicate)
        return self.taskEntityList
    }

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?

It's similar in concept to a SQL join. You're asking the Task object to return a list of tasks for a specific list.

Q2. All CRUD functions only take place in the Entity Business Controller, yes? this separation of responsibility is it considered MVVM?

It's a more straightforward MVC (Model View Controller) design pattern.

Q3. Can the mmBiZObj be in the backgroundContext? -- I understand that CRUD must happen in the same Context to avoid confusing CoreData.

Yes, it can

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.

I haven't worked with ReactiveCocoa, though I've read up on it. That said, mmBizObj can peacefully coexist with ReactiveCocoa.

Q5. Ohh yeah, can I use mmBiZObj or a variation of thereof in a production app?

Absolutely.

All the best!
Kevin

Carlos_Lema

  • Newbie
  • *
  • Posts: 3
    • View Profile
Re: mmBiz, Relationships & other musings.
« Reply #4 on: January 25, 2016, 01:06:23 PM »
Thanks Kevin.  Got it, I see the mistake.

Plus, I got relationships to work.