DaPhil
DaPhil

Reputation: 1599

MVC: Where to store data?

I like to apply the Model-View-Controller design pattern in a Cocoa application (written in Swift). Let us assume the famous person example: the App should show a text field and an add button. A persons name can be put into the text field and then be stored in an array by clicking the add button (and the arrays content may be shown in a table view). Now, the storyboard contains all my View-related stuff while a class Person (with a variable name) constitutes the Model-class. The view controller would be responsible for receiving an showing the data (table view delegate and data source for example) and has an IBAction which extracts the name and puts it somewhere. But, I am not sure where to actually put the array that holds all the names. Would I have it in the view controller as well (which would be the most easiest solution I think)? Or somewhere else? What if the App becomes more and more elaborate with multiple data holding structures in different views (with different view controllers)?

Upvotes: 2

Views: 1905

Answers (2)

user7014451
user7014451

Reputation:

Model:

You will want a database, or Core Data.

I'm separating out CD because in some corners of IT it's considered more of a "model" with either a mySQL database or an XML file sitting behind it. Like anything open for discussion, CD has it's strengths and weaknesses. Many consider one of the largest weaknesses to be expanding things after implementation.

What if the App becomes more and more elaborate with multiple data holding structures in different views (with different view controllers)?

To me this is the key thing for the Model, and this means a simple SQL (or relational) database. If you don't know what I mean by relational, look it up, spend a few days learning it. (Hint: you already are there when you talk a "person class". Model (see what I did there?) your classes to match - by names, types, and properties - your database tables. The closer to 1:1 you can do, the better.

But keep in mind scalability and tiers (Model, View, Controller). Swift has computed properties. If you are querying something like "how many people live in Europe" you are best using SQL - it keeps the data going over the pipe cleaner, and performs better. But if you want to display something like "how many days until John Doe's birthday" you probably are best making that either a computed property or a callable function in the controller side of things.

You also want to decide on local or cloud. Facebook obviously has a database behind it in the cloud. But my third party podcast app (not Overcast BTW) has a local database of some sort.

View:

You seem to have a good handle on this. Xcode does a good job of sticking to MVC for the most part. If you are able to stick entirely in Interface Builder, then you already have this down.

But some coders (like me) aren't able to do that. For all Apple does a good job with auto layout constraints, they make some decisions I'm not privy to the reasoning behind - like all orientations on an iPad (not including when split screen or slide out is in use) are currently normal size. My app needs to move the UISlider controls from the bottom (portrait) to the right (landscape) to maximize screen space for my GLKView. This means code at least some things (orientation changes and superview bounds). And since IB cannot process code during design time, I've decided to move all subviews into code and eliminate IB. I was hopeful with Xcode 8 that I could use IB and after a month of playing with both the new IB stuff I realized it wasn't worth it yet - I need constraint arrays to activate/deactive based on superview bounds.

Long story short: IB works well, use it as much as you can. But remember it's limitations, and don't be afraid to drop into code for your view layer when necessary.

Controller:

First off, keep in mind that once you remove the "model" and "view" from things, everything that's left is "controller".

Of course. Of course! Your view controller needs to present data in it's view (and subviews). It also needs to process actions from the user like button presses slider changes, etc. But what about Retrieving data from a database? What about retrieving price or tax info from a third party web service? Or for you, what about bringing up a list of people (contacts?) in a UITableView?

If you put all that into your view controller congratulations! You've just learned about UIViewController "cruft". We've all done this - it's all a part of learning the Xcode MVC way. It's something of a science, and something of an art to know what works. (It's mostly experience though.)

As your app grows you'll definitely have more UIViewControllers in it. You'll likely have other classes like Person, Country, etc. Eventually you'll start creating extensions to various classes, and maybe create protocols for your classes. Now, quick question: How do you keep these things organized? Separate files, groups, what? You may reach of point that you find that a framework target works best. (And if you plan to use many of Apple's app extensions, you will.)

Maybe more important than nailing your "model" before coding is nailing your "controller" organization.

Upvotes: 1

bubuxu
bubuxu

Reputation: 2197

1) ViewController (from UITableViewController or UIViewController):

class ViewController: UITableViewController, UITextFieldDelegate {

    var model = ViewModel()

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "personCell")
        let nib = UINib(nibName: "InputTableViewCell", bundle: nil)
        tableView.register(nib, forCellReuseIdentifier: "inputCell")

        // Request or load the data into model
        // requestData() & tableView.reloadData() here

    }

    // MARK: - UITableViewDataSource

    override func numberOfSections(in tableView: UITableView) -> Int {
        return 2
    }


    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return section == 0 ? 1 : model.persons?.count ?? 0
    }


    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        if indexPath.section == 0 {
            let cell = tableView.dequeueReusableCell(withIdentifier: "inputCell", for: indexPath)
            if let inputCell = cell as? InputTableViewCell {
                inputCell.textField.text = model.input
                inputCell.textField.delegate = self
                inputCell.textField.addTarget(self, action: #selector(textFieldEditing(_:)), for: .editingChanged)
                inputCell.buttonAdd.addTarget(self, action: #selector(buttonAddTapped(_:)), for: .touchUpInside)
            }
            return cell
        } else {
            let cell = tableView.dequeueReusableCell(withIdentifier: "personCell", for: indexPath)
            let person = model.persons?[indexPath.row]
            cell.textLabel?.text = person?.name
            return cell
        }
    }

    // MARK: - Event Handler

    func textFieldEditing(_ sender: UITextField) {
        model.input = sender.text
    }

    func buttonAddTapped(_ sender: UIButton) {
        if let name = model.input, name.characters.count > 0 {
            let person = Person()
            person.name = name
            model.addPerson(person)
            model.input = nil
            // Also save data to CoreData if you want to retrieve them again later
            // saveData()
        }
        tableView.reloadData()
    }


}

2) Define cells: including Input cell or Person cell if need. When create the new class, check the "also create XIB file" too, and connect and UIs.

class InputTableViewCell: UITableViewCell {

    @IBOutlet weak var textField: UITextField!
    @IBOutlet weak var buttonAdd: UIButton!

}

3) Define the view model:

class ViewModel {

    var persons: [Person]?
    var input: String?

    func addPerson(_ person: Person) {
        if persons == nil { persons = [] }
        persons?.append(person)
    }

}

4) Define models:

class Person {
    var name: String?
}

Upvotes: 0

Related Questions