Tag Archives: model

This Old App: Models in the Playgrounds

Back a few months ago, an  app of mine  was removed from the App Store for being too old and not updated enough. In this series of articles I’ll document what I did on rebuilding and improving the app. In our first installment I analyzed the changes necessary. In this second installment,  I’ll start making the model.

I think in Model-View-Controller or MVC. If you are not familiar with MVC, I suggest you take a look at my video in LinkedInLearning on MVC and delegates. In short, a common programming practice is to break an application into three parts: the model, the View and the controller. The model is the code that processes data. The view is the user interface.  We don’t let the view and model interact with each other. Instead we use a controller to coordinate between the two.  This way we can change one while affecting the other two minimally.

While I might get some ideas for the view in my planning stages, I tend to start  coding with the model. In one sense the controller and view are window dressing for the model, where your data is. Views and controllers are the presentation and interaction with that model.

I’ll make a confession here: I hate working on the model. It the most tedious type of programming in my mind. It has none of the glamor or action of views or controllers.  It is just data, and I’m not much of a data person. As such I’ll give you a warning that this is a bit dry, but there’s a lot there to look at, and in the case of  RunCalc, some interesting problems that drove me up the wall the first time I wrote this code.

What’s in the Old Model

Experience in mobile applications has changed me, and the model in the original code was a reminder of this.  Written in Objective-C, The model for version 1.0 was a case of putting all the eggs in one basket.  I did everything in one model.  The original model had four properties to calculate running statistics:

  • Distance
  •  Time
  •  Pace
  • Speed

I added speed to handle treadmill settings, though it is measuring the same thing as pace: How fast the runner is going.

There are four core  methods which derive pace, speed, time and distance.  Basically you can derive each like this:

distance = pace * time

time = distance / pace

pace = time / distance

speed = distance / time

My measurements were seconds for time measurements, which I would format as strings in HH:MM:SS format for display. Distance was in miles. It was easy for me to calculate test cases in miles. I had conversion methods to metric measurements for miles and kilometers.  This meant that I had pace seconds per mile and speed as mile per second, which would have some more conversion to compute it to minutes per mile and miles per hour and kilometers per hour. Metric pace was in kilometers per seconds for some unknown reason that isn’t in my original notes. I think it was laziness.

This was the basics of the model, but there were a few more features that got added to it, The first and most important was the “locking” mechanism. There is a subtle problem with using the formulas above. Suppose you change pace. What do you update, distance or time? If you update both, then your numbers are wrong because things get updated on top of each other, and you can’t change all your settings. For changes in the calculator, what you need is to lock either distance or time in place and only compute one of them when you change pace. I did this with an enumeration in the early version to keep track of what I locked.

The last part of the model was running totals for the splits. It meant I had another set of four properties with totals for distance and time. There were two more for average pace and speed.

Building the new model : Properties and Units

The original had a lot of rounding errors and strange calculations due to switching between kilometers and Imperial measurements. Unlike other measurements systems, there will be a lot of switching between units, particularly in distance, and I really didn’t think this out well the first time. Long distance running uses different distance measurements at the drop of a hat. There are 5K, 8K, 10K, and 15k races. But there are half marathons at 13.1 Miles and full marathons at 26.2 miles. This and other measurements gave me the following measurements:

  • Distance
    • Meters
    • Kilometers
    • Miles
  • Time
    • Seconds
    • Minutes:Seconds
    • Hours:Minutes:Seconds
  • Pace/Speed
    • Meters per second
    • Minutes per mile
    • Minutes per Kilometer
    • Kilometers per hour
    • Miles per hour

Instead of computing in the units I have displayed, I kept with two standard units, meters and seconds, and converted everything else from them. These units match units found in other API’s such as CoreMotion, if I ever decide to interface this with other apps or API’s.

Pace and speed are different ways of describing the same thing, so I made them the same with one measure: Meters per Second.  Everything else  is a conversion.

Derivation Functions and Locking

The derivation function problem the first time took me a month to come up with the enumeration solution. For some reason I could nt wrap my head around the initial problem of handling that many variables.  Version 2.0 will use a different solution the model. There are six methods that change the model, two for each of the three properties. Each one changed one of the two other properties For example for distance:

func distance(changedBy pace:Double) {
    self.distance = distance(pace: pace, time:self.time)
}
func distance(changedBy time:Double) {
    let newDistance = distance(pace: self.pace, time:time )
    self.distance = newDistance
}

The Model and Conversions

I built the model as described into two models. One, RunStats for the model described above and one RunStatsIntervals for a collection of RunsStats objects and calculations for finding summations of the collection data. It was here I ran into my problem: The summations would need their own conversions, which were a repeat of the conversions above.

Here’s where object-oriented programming gets its biggest strength: I made up three classes PaceStat, TimeStat, and DistanceStat, that had the conversions. IN stead of using Doubles in my RunStats class I used these three classes. I went one step more and made constructors that would work in any unit I wanted, making the result the correct unit.  For example here’s distance:

public class DistanceStat{
// Basic unit is meters
// you can use meters, kilometers or miles for this stat
private var distanceStat:Double = 0.0 //private variable so you must use the setters and getters.
//Constructors
public init (){
    self.distanceStat = 0.0
}

public init(meters:Double){
   self.distanceStat = meters
}

public init(kilometers:Double){
    distance(kilometers:Double)
}

public init(miles:Double){
    distance(miles:miles)
}

// getters distance
public func miles()->Double{
    return distanceStat / 1609.34 
}

public func meters()->Double{
    return distanceStat
}

public func kilometers()->Double{
    return distanceStat / 1000.0
}

//Setters
public func distance(meters:Double){
    distanceStat = meters
}

public func distance(miles:Double){
    distanceStat = miles * 1609.34
}

public func distance(kilometers:Double){
    distanceStat = kilometers * 1000.0
}

}

Now to specify a 8K distance, I can say

let newDistance = DistanceStat(kilometers:8.0)

and return that as miles.

print( newDistance.miles())

I do the same with pace and time. With the pace, time, and distance stat in their own classes, I can use them anywhere and get the conversions I need anywhere.  With that I can make summation functions.  I’ll total the distance and the time, and use them to get an average pace for my summation data. That summation data can be in any of the units I have for that type.

I built the model not in the app, but in a separate playground. Playgrounds, especially for models offer an excellent way of looking at all your code and making the debugging process easier.  I can type them in on either my Mac or my iPad and check how every part works, finding errors as I go.  I did for example find an error in my  conversion formulas, and was able to find and fix it from the playground.

In the next installment we’ll add the model to the old Xcode project, get more than a few errors and start hooking up some user interfaces for testing.

Why do we need Delegates in iOS and WatchOS?

About two years ago someone asked me a very good question: Why do we need delegates for UIViewControllers?  He thought Swift  made things easier, but this delegate stuff seems very complicated. Shouldn’t we be able to send a message or initializer between classes?

When I first learned iOS, I’ll admit it took me months to understand what happened with delegation. I found lots of confusing code and little explanation. AS I was researching some more preferences to send people to, I found the results lacking. Most often, tutorials refer to how to use an Apple factory delegate, not making your own callback. Its these callbacks which require full knowledge of delegates.

I decided it was time to update this, and to include two examples developers might run into: the iOS and watchOS versions. With the maturing of watchOS in watchOS 3 I think more developers might begin to look at developing watch apps, and there’s some twists there that might cause some confusion.

Let’s start at the beginning, so everyone understands the problem.

What is a Class?

Let’s start at the beginning, so everyone understands the problem. While we use classes in object-oriented programming, it’s good to review what they actually are. A class is a collection of data, which we call properties, and actions we can do to those properties, which we call methods.

Properties and methods are either public or private. A public method is one that classes other than the defining class can see and can use. Private means that the property or method is only usable and visible within the defining class. Other classes cannot see or use it. In Swift the private keyword makes properties and methods private. Swift’s calculated properties feature is another way to make properties private. In Swift, there is also a default state which makes a method or class public to the current target, but not other targets.

We tend not to like other people messing with our insides, and that is a good programming practice too. In general, it is best to leave what is public by other classes to a necessary minimum. Keeping properties and methods private and not exposing all of our class is known as encapsulation.

Encapsulation allows us to make code as modular as building blocks. Just as a few stubs come out of an otherwise standard sized brick, only a few usable methods come out of a class. Then they can attach to a lot of other bricks.

What is  Model –  View – Controller  or MVC?

MVC schematic blankA term heard often  when working with Xcode, is MVC. MVC stands for Model-View-Controller. It is not an exclusive term to Xcode projects. It is a pattern of programming, a good organization of any program or application in a graphics-rich environment, and arguably any environment that interacts with the user. MVC separates the major parts of an application. First it separates the data and the user interaction then adds an intermediary between them. Why is this important? You might write and publish an application for an iPhone, then decide an iPad version would be a good idea, then decide to make a watch version. With MVC, you only change one part completely, the view and possibly some of the controller. The code handling your data never changes between the versions saving a lot of time and effort.

What is a Model?

MVC schematic modelThere are parts of our program that deal with information we want to process. A pizza ordering system has a list of data giving us information about each person’s order. There may be other links to that data with more data about each customer, and about each pizza. In a pizza ordering system this is our model: the collection of all the data we will use in our ordering system. It does not in any way interact with the user. It does not display anything or does it ask for input. It is just data. Here is an example of very simple model:

class switchState{
    var state:Bool
    func textState()->String{
        if state {
            return "On"
        } 
        return "Off"
    }
    func overLoadSwitch(users:Int,load:Int){
      let totalLoad = users * load 
      if totalLoad > 100{
          switch = false
      }
}

This model is the state of a switch.  That’s the data. the model has two methods. textState(), describes the state of the switch as a string,  overLoadSwitch() turns off the switch if users multiplied by load is greater than 100 . There is a lot more methods I should add to describe the switch, but any method is changing or describing data only. There is no user input or output here. Models might make calculations but again there is no user interaction here.

What is a View?

MVC schematic viewWhere all the user interaction happens is in the view. In Xcode, most people use Interface Builder either as a scene in a storyboard or a .xib file to build their views.  A developer can programmatically create a view class to hold the different controls.

2016-08-01_07-11-48As the model never interacts with the user,  the view never interacts directly with the data. The view doesn’t do much but sit there. It might respond to a user touch with feedback such as a notifying a method somewhere, a color change when a button gets tapped or a scrolling motion at times, but that is all it does. The view does contain a lot of properties and methods and that tell us the state of the view. We can change the appearance and behavior of the view through methods and properties. The view can tell the controller that there was a change in the view, such as a button getting pressed, or a character typed. it can’t do anything about it, but it can broadcast something.

What is a Controller?

MVC schematicThe heart of MVC connects these two. Called the controller or view controller, it coordinates what happens in the model and what happens in the view. If a user presses a button on the view, the controller responds to that event. If that response means sending messages to the model, the view controller does that. If the response requires getting information from the model, the controller does that too. in Xcode, @IBOutlet and @IBAction connect Interface Builder files containing views to the view controller.

The key to MVC is communication. To be more accurate, the lack of communication. MVC takes encapsulation very seriously.  The view and the model never directly talk to each other. The controller can send messages to the view and the controller. The view and controller may do an internal action to the message sent as a method call or it may return a value to the controller. The controller never directly changes anything in either the view or the model.

So to summarize, a view, a model and a controller cannot directly change a property in each other. A view and a model may not talk to each other at all. A view can tell the controller there is a change in the model. A controller can send messages in the form of method calls to the view and the model and get the responses back through that method.

MVC schematic full annotated

How Does MVC Connect to Other MVC’s

What we’ve discussed so far is for only one scene in a much larger application. Suppose I have the following  watchOS storyboard:

2016-09-21_05-01-58

I have an a button Switch which loads a second face that has a switch.  When I decide which way I want the switch, I press done.

The Easy Direction

In Xcode we have what are known as segues. Segues are a convenience to point from one view controller to another.  When segues keep track of some things that would become cumbersome to control ourselves in a simple way. When we move from one controller to the next, the segue tells the system to open this particular view controller, which then opens up a view and model. The model and view in the new MVC setup is different then the calling one. Apple includes a method prepare(for segue:) for  iOS which give us a chance to set values in the new view controller,  and subsequently the new view controller’s view and model.

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "switch"{
         let vc = segue.destination as! SwitchViewController
         vc.switchState = false
        }

With the introduction of WatchOS came a slightly different approach.  instead of having the full model available,  watchOS sends to new controllers a single value called context. As this is type Any?,  you can put whatever you want in this value. Most often developers send dictionaries of values to other controllers through the contextForSegue method. When the destination application wakes up. the awake method  converts the context to the proper type and assigns it correctly.

override func contextForSegue(withIdentifier segueIdentifier: String) -> Any? {
        return self
    }

For both a identical  phone or watch app I could press the Switch button.  It launches  the switch face and  pass a value to the switch to be false.

The Problem Direction

We can turn the switch off and on easily enough.  But when we press Done to send it back to the original controller  is when problems show up. By the rules of MVC, we need a method to return a value. Where in a called instance can we go back to the class calling it? With an encapsulated class we can’t. There is no way to send that revised model back to the original controller without breaking encapsulation or MVC. The new view controller does not know anything about the class that called it. We look stuck. If we try to make a reference directly to the calling controller, we may cause a reference loop that will kill our memory. Simply put, we can’t send things backwards.

This is the problem that delegates and protocols solve by being a little sneaky. Imagine another class, one that is really a skeleton of a class. This class contains only methods. It declares that certain methods are in this class, but never implements them. In Swift they are protocols. We make a protocol class that has one method. That method is what you do when you are done  with the switch, and want to go back to the calling controller. It has a few parameters, things you want to pass back to the calling view controller.  So it might look like this:

protocol SwitchDelegate {
    func didFinishSwitch(switchState:Bool)
}

I passed back the state of the switch in this case.

In the controller with the switch, we make an instance of this protocol, calling it delegate.

delegate:SwitchDelegate! = nil

Since we have a property of type SwitchDelegate, we can use the methods of the SwitchDelegate type, In our example, that is our method didFinishSwitch. We can stick that method call in a action for a Done button:

@IBAction func doneButtonPressed(sender:UIButton!){
    delegate.didFinishSwitch(switchState: switchState)
    dismiss(animated: true, completion: nil)
}

or for WatchOS

@IBAction func submitSwitchStatus() {
    delegate.didFinishSwitch(switchState: switchState)
    pop()      
}

Since protocols are skeletons, it means any other class can adopt them. A class makes the protocol methods part of its own class with a stipulation. As soon as a protocol gets adopted, you need to flesh out the skeleton. The developer has to code the required methods in the adopted class’ code. We adopt a protocol by placing it after the name of the class and superclass. For iOS you might have

class OrderPizzaViewController:UIViewController,PizzaEditDelegate

and for watchOS, you might have

class InterfaceController: WKInterfaceController,SwitchDelegate {

As soon as you do that, you will get a compiler error since the protocol’s method does not exist in the class. In the code for the adopting class, in our example OrderPizzaViewController, we would implement the method

func didFinishSwitch(switchState: Bool) {
        if switchState {
            textState = "Switch is On"
        } else {
            textState = "Switch is Off"
        }        
    }

We get the data back, and what we need with it, in this case a string that we’ll print to the label.
One more step. While back in the destination controller, I said the delegate was an instance of the protocol, I didn’t say where the delegate was. In prepare(for Segue) I add one more line vc.delegate = self saying the protocol is your controller

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "switch"{
         let vc = segue.destination as! SwitchViewController
         vc.switchState = false
         vc.delegate = self
        }

In WatchOS this gets a bit trickier. I have only one context to pass both the switchState and the delegate. For multiple values generally developers use a dictionary like this:

override func contextForSegue(withIdentifier segueIdentifier: String) -> Any? {
        let context:[String:Any] = ["switchState":false,"delegate":self]
        return context
    }

the awake method would have code to unwrap the dictionary and assign the values.

When we tap Done in a watch or phone application, the method runs, it knows it is located in the calling controller, and calls it there where we added to the original class.The data is a parameter so the program can easily transfer into the controller and to the model. Delegates and protocols are bit sneaky but it works, and is one of the most important techniques when working with view controllers.

mvc-schematic

The original question asked why didn’t Swift make this simpler. As I hope I’ve shown here with a Swift context, it doesn’t matter what object oriented language you use. Delegates are part of the MVC pattern, which is a good programming practice.

How to Make Drill-Down Tables in Swift

drilldown
Table views are a lot like potato chips: You can’t have just one. Often, table views relate to each other in what is often referred to as a drill-down. Drill downs typically take the selected data and provide another list in a table about the selection. It creates a hierarchical selection. You might have a list of car manufacturers for example. From that list you select one manufacturer, which gives you a list of models that manufacturers makes. Click on a model and you might get details about the car. I’ve gotten more questions about drill-down than any other topic, mostly along the lines of “how do I program my table view to…” I’ve given short answers, but I want to give the long one. It really has nothing to do with the table view controllers. It has to do with the models.

For this lesson, we’ll explore ways of getting to that result. This lesson will require skills on building classes, dictionaries, and table views. If you do not understand something you might want to refer to these lessons:

A Look at Some Data

Table views are actually quite simple-minded by design. They can handle data from a simple array and not more than that. Our sophistication with table views happens not in the controller, but in the model. How we handle our data and present it to the controller is key to good design of table views. For most cases, these models are generated from some form of persistent data source, such as a data file, Core Data or SQLite. You may have more then one source of data you want to associate with each other as well. Lets take as an example two data tables. The first table lists foods and the meal it is eaten.

Food Meal
Pizza Lunch
Pasta Lunch
Chocolate Mousse Dessert
Oatmeal Breakfast
Pancakes Breakfast
Peanut Butter and Jelly Lunch
Mole con Pollo Dinner
Hamburger Lunch
Pad Thai Dinner
Paneer Dinner
Chocolate Chip Cookie Snack
Tuna Salad Sandwich Lunch
Bagel Breakfast

The second table lists foods, prices and calorie counts

Food Price Calories
Pizza 4.99 317
Pasta 7.99 325
Chocolate Mousse 6.45 470
Oatmeal 2.95 160
Pancakes 3.95 470
Peanut Butter and Jelly 1.99 319
Pollo con Mole 8.50 860
Hamburger 4.35 250
Pad Thai 7.35 430
Paneer 6.99 100
Sirloin Steak 24.49 165
Chocolate Chip Cookie 2.75 210
Tuna Salad Sandwich 3.25 420
Bagel 1.50 275

What we would like to have is a table view that lists meals

2015-10-01_09-11-50

When we select a meal, the app lists those foods.

2015-10-01_09-19-22

When we select a food, it displays price and calorie information about that food.

2015-10-01_09-19-55

How do we code this?

Set Up the Application

Let’s go through a few different options for doing drill-downs. We’ll need two table views and a view controller. Make a new Xcode single view project DrillDownTables. Make Swift the language and keep the application Universal. Save the project.

The Launchscreen

In the navigator, click the Launchscreen.storyboard file. We’ll need to set up our own launch screen. For most demo and practice apps, I just center a line of text on a label. Drag a label out on the storyboard. Change the text to Drill Down Tables. With the label selected, click the align menu on the bottom right of the storyboard. Click on Horizontally in Container and Vertically in Container. Make sure the values for both are 0. Select Items of New constraints for update frames

center alignment

I usually change the background color as well, this time to #EEFFDD but that is to taste.

Make the Storyboard

We have a bit of work setting up the storyboard, but we only have to do it once for several iterations of the application. Drag a table view controller to the storyboard. In the attributes inspector for the table view controller, under View Controller check on Is Initial View Controller. In the drop down menu, select Editor>Embed in>Navigation Controller . Double-click the navigation bar on the top of the table view controller and title the controller Meals. Drag the original view controller out of the way for now.
Select the tableview and make sure it says Dynamic Prototypes. If you wish you can set a background color, scroll down to view and setting the color. I set a pale yellow (#FFFFCC) for the background.

Select the table view cell. Set the identifier to cell. Set the accessory indicator to Disclosure.

Press Command-N and make a new Cocoa Touch Class subclassing NSObject named MealsTableViewController. I prefer to build table view controllers from scratch to keep a lot of the extra methods in the templates out of the way. NSObject gives a nice blank slate. Save the file and make sure it is in the DrillDownTables folder group. When it opens, change NSObject to UITableViewController so you get this:

class MealsTableViewController: UITableViewController {

}

Go back to the storyboard. Select the Meals View Controller icon. in the identity inspector, set the class to MealsTableViewController.

The Food Table

Drag another table view controller on to the storyboard. Control-drag from the cell in the Meals view controller to the new view controller. Select a show Segue. Set the segue Identifier to Foods.
Select the tableview and make sure it says dynamic prototypes. I’m in a bit of a pastel mood, so I set a peachy color (#FFDDCC) for the background.

Set the table view cell’s identifier cell. Set the Accessory to Disclosure.

Press Command-N and make a new Cocoa Touch Class subclassing NSObject named FoodsTableViewController. Save the file and make sure it is in the DrillDownTables folder group. When it opens, change NSObject to UITableViewController so you get this:

class FoodsTableViewController: UITableViewController {

}

Go back to the storyboard. Select the View Controller icon for the second table. In the identity inspector, set the class to FoodsTableViewController.

The Info View Controller

Control-Drag from the table cell in the Foods view controller to the orphaned view controller from earlier. Select a show Segue. Set the segue Identifier to Info. I set something bluish (#DDEEFF) for the background.
Drag two labels onto the story board, labeling them Calories and Price. Drag Price above Calories. Using autolayout, pin the Price label 20 points up, 0 points left and 0 points right. For Update Frames select Items of New Constraints

Autolayout pin 20-0-0-X

Pin the Calories label the same way: 20 points up, 0 points left and 0 points right. For Update Frames select Items of New Constraints. You should have something like this:

2015-10-02_08-05-01

Press Command-N and make a new Cocoa Touch Class subclassing UIViewController named InfoViewController. Save the file making sure it is in the DrillDownTables folder group. You will only need viewDidload so you can delete everything else in the class.

Go back to the storyboard. Select the view controller icon for this last scene. In the identity inspector, set the class to InfoViewController.

Open the assistant editor. Control-drag from the Price label and make an outlet named priceLabel. Control drag from the Calories label and make an outlet caloriesLabel. Close the Assistant editor.

Make the First Model

Our first goal is to get a working table view. We’ll just list what we need in each of the models first, to make sure the table reads it right.

A List of Meals

Press Command-N and make a new Swift Cocoa Touch file Subclassing NSObject named MealModel. Code the class like this:

class MealModel{
    var meal:[String] = [
        "Breakfast",
        "Lunch",
        "Dinner"]
}

A List of Foods

Our second table is our foods table. For right now we’ll list all the foods in the table to make sure everything in the view controller is working. We’ll use a dictionary to make this table. Press Command-N and make a new Swift Cocoa Touch file Subclassing NSObject named FoodsModel. Code the class like this:

class FoodsModel {
    var foodList:[String:String] = [
        "Pizza":"Lunch",
        "Pasta":"Lunch",
        "Chocolate Mousse":"Dessert",
        "Oatmeal":"Breakfast",
        "Pancakes":"Breakfast",
        "Peanut Butter and Jelly":"Lunch",
        "Mole con Pollo":"Dinner",
        "Hamburger":"Lunch",
        "Pad Thai":"Dinner",
        "Paneer":"Dinner",
        "Sirloin Steak":"Dinner",
        "Chocolate Chip Cookie":"Snack",
        "Tuna Salad Sandwich":"Lunch",
        "Bagel":"Breakfast"
    ]
   func foods(meal:String) -> [String]{
            return Array(foodList.keys)
    }
}    

This model uses a dictionary with the food as a unique key, and the meal as a value. For the table view, we still need an array. We made the function foods to get the array. We don’t use the parameter — yet. I did that so we don’t have to change it later.

We are not going to use the Info view controller just yet. We want to get our tables working first.

Coding our Tables

We have some models to work with. Before we work with the models in more sophisticated ways, it helps to set up the tables and make sure they are working correctly.

Coding the Meals Table

Go to MealTableviewController. Add the model as a property to our blank class:

let meals = MealModel()

We need three data source methods in our table. First add this:

    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 1
    }
    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return meals.meal.count
    }

We have one section and we will take the number of elements of the array meals.meal for the number of rows. Add the cellForRowAtIndexPath data source

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("cell",
        forIndexPath: indexPath)
    let row = indexPath.row
    cell.textLabel?.text = meals.meal[row]
    return cell
    }

The code gets the cell, then the row in the index, since we only have one section. We take the property meal and use the row to determine which element of that array we use.

Coding the Foods Table

Go to FoodsTableviewController. Add the model as a property to our blank class and an array to store the foods from the model:

let foodsModel = FoodsModel()
var meal = "Lunch"
var foods = [String]()

initialize foods by adding a viewDidLoad to the class:

override func viewDidLoad() {
    super.viewDidLoad()
    foods = foodsModel.foods(meal)
}

We need those three data source methods in our table. First add the table size ones:

override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return foods.count
}

Add the cellForRowAtIndexPath data source

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
    let row = indexPath.row
    cell.textLabel?.text = foods[row]
        
    if (row % 2) == 0  { //alternate rowcolors
        cell.backgroundColor = UIColor.clearColor()
        cell.textLabel?.textColor = UIColor.blackColor()
    }
    cell.alpha = 0.75
    return cell
    }

This time I added a little spice, as the rows will alternate colors. I decide that by getting the modulo of the row divided by two. It can be either 1 or 0, If 0, I change the color.

Coding the Food Info View

In the Info view controller, we are not yet going to use a model, but set up the controller a bit. Go to the InfoController, add the following property:

var food = "Food Info" // Key 

Change the viewDidLoad to this:

 override func viewDidLoad() {
    super.viewDidLoad()
    navigationItem.title = food
}        

We’ll set the food name as our navigation title.
Build and run. On the first screen you should get the three meals.

2015-10-05_06-04-09

Select a meal. You should see all the foods.

2015-10-05_06-04-27

You can select a food, and not much will happen besides a title. We havent programmed our segues yet.

2015-10-05_06-04-44

Adding Segues

Go to MealsTableViewController. Add a prepareForSegue method

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    if segue.identifier == "Foods"{
        let vc = segue.destinationViewController as! FoodTableViewController
        let selectedMeal = meals.meal[(tableView.indexPathForSelectedRow?.row)!]
        vc.meal = selectedMeal
        vc.navigationItem.title = selectedMeal
     }
}

We send the selected meal to the next controller, setting its title and the meal property. Go to the FoodsTableViewController. Add this prepareForSegue method:

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    let selectedRow = (tableView.indexPathForSelectedRow?.row)!
    if segue.identifier == "Info"{
        let vc = segue.destinationViewController
             as! InfoViewController
        vc.food = foods[selectedRow]   
    }
}  

In both of these controllers, we use the indexPathForSelectedRow property to get the index path of the row selected, and use the data from that row to send to the destination controller.
Build and run. Our selection reflect on the next view’s navigation title, but not the food selection.

2015-10-05_06-14-49

Select a food to go to the Info scene and the food shows on the Navigation bar title

2015-10-05_06-15-35

Iteration 1: Coding with a Dictionary

Much of what we need is already set up. The MealsTableViewController is already sending the selected meal to the FoodsTableViewController. We have to do something with it. The long list of foods needs to be a short list of foods for only that meal. Comment out the current foods and replace with this:

//linear search for all food with a given meal
func foods(meal:String) -> [String]{
    var shortList:[String] = []
    for food in Array(foodList.keys){
    	if foodList[food] == meal{
            shortList += [food]
        }
    }
    return shortList
} 

This performs a linear search for all foods for that meal. It returns the list for that have meal for a value. Build and run. Select Breakfast. You get breakfast foods.

2015-10-05_06-20-17

A One to Many Table

Notice something about our table though: We have a one to one relationship between the meal and the food. Using this code I can only have a Hamburger for lunch or dinner, but not both.

The key of the dictionary is the food, not the meal. We need a unique key for the meal, and then a list of foods. In the MealsModel.swift file, comment out the current model and add this for the class:

class MealModel {
    var mealList = [String:[String]]()
}

This makes a dictionary where we have a meal for a key and a array of foods associated with the food.

To get the meals, we can add the following computed property

var meal:[String]{
    get{
        return Array(mealList.keys)
	 }
}

To make it easier to add items to the model, add an addItem method:

func addItem(meal:String,foods:[String]){
    mealList[meal] = foods
}

We’ll initialize the class with slightly different data. Add the following:

init(){
        //what we do here would usually be a database or file access
    addItem("Breakfast",
        foods: [
            "Oatmeal",
            "Pancakes",
            "Bagel"
        ])

    addItem("Lunch",
        foods: [
             "Pizza",
             "Pasta",
             "Peanut Butter and Jelly",
             "Tuna Salad Sandwich", 
             "Hamburger"])
    addItem("Dinner",
       foods: [
             "Mole con Pollo",
             "Pad Thai",
             "Paneer",
             "Sirloin Steak",
             "Pizza",
             "Pasta",
             "Hamburger"
       ])
    addItem("Snack",
       foods: [
            "Chocolate Chip Cookie",
            "Pizza"
        ])
 }

We have a list of foods. We added Snack, and removed the orphan dessert chocolate mousse. A hamburger is now a lunch and dinner food. Pizza is a lunch, dinner, and nack food. We have a one to many relationship.

We need only one more thing to complete this new class. Add the following code:

func foods(meal:String) -> [String]{ //for use with the model
	if let list = self.mealList[meal]{
		return list
	} else {
		return []
	}
}

Notice the code is a lot simpler in this version of foods, since we use a dictionary property. We just check if the dictionary exists using optional chaining then return our food list. If no entry, return an empty array

Changing the FoodModel

The class FoodModel has a lot less to do. I could remove it completely, but in a real application there may be things I do to a selected list of foods that I won’t do to the entire list. Comment out the current FoodModel class and add this class after it

/* Second Iteration of the model */
class FoodsModel{
    var foodList:[String] = []
    func foods(meal:String) ->[String]{
        return foodList
    }
}

We have in this model version a string array for the list of foods. If we assume MealsModel sends the list of selected foods to this model, we only have to return the value.

Coding the Tables

The beautiful thing about MVC is that if we use the same properties and methods in two classes we can swap them out easily. Our new versions of FoodsModel and MealsModel need very few changes. FoodsModel works exactly the same from the table view controllers view, though the meal parameter is once again redundant. We have to change only one thing in the meals table for these models. We added the foods function to the MealsModel. We will use that to load the FoodsModel in our prepareForSegue like this:

vc.foodsModel.foodList = meals.foods(selectedMeal)

In the MealsTableViewController, change prepareForSegue to this:

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    if segue.identifier == "Foods"{
    let vc = segue.destinationViewController as! FoodsTableViewController   
    let selectedMeal = meals.meal[(tableView.indexPathForSelectedRow?.row)!]
    vc.meal = selectedMeal
    vc.navigationItem.title = selectedMeal
    vc.foodsModel.foodList = meals.foods(selectedMeal)
}

Build and run. Select Lunch,

2015-10-05_07-13-20

Go back to the menu and Select Dinner

2015-10-05_12-50-28

Hamburger and pizza show up in both.

Coding the Info Model

We’ve been ignoring the last view controller. While we’ve been drilling down, we have not gotten to any detail about our foods. At some point we will need to work with data. In our example, it is calorie and price information. In Xcode, press Command-N and make one more class subclassing NSObject called FoodInfoModel. Above the FoodInfoModel class, add this class:

class FoodItemInfo{ // the record or row of the database
    var name = ""
    var price = 0.00
    var calories = 0
    init(name:String,price:Double,calories:Int){
        self.name = name  //primary key
        self.price = price
        self.calories = calories
    }
}

If you think in database terms, this is our record or row. We’ll use this in the dictionary we’ll create in FoodInfoModel to hold our data. Add this to the FoodInfoModel class:

var list = [String:FoodItemInfo]()
    
func addItem(key:String,price:Double,calories:Int){
    let foodItem = FoodItemInfo(
        name: key, 
        price: price, 
        calories: calories)
    list[key] = foodItem  //this modifies or adds to the file
}    

He’s our dictionary of info with a key of a food item’s name. We have one seeming redundancy: name is the key, and is a property in the FoodItemInfo class. I may pass the FoodItemInfo somewere without the key, and so I’d like to keep the name handy. The method addItem adds the record for a food to our dictionary. I could do use FoodItemInfo’s initializer for each food item to load my dictionary:

list["Pizza"] = FoodItemInfo("Pizza", price: 4.99, calories: 317)

However, addItem is better documentation and is less keystrokes. Add the following to the end of the FoodInfoModel:

init(){
    // this would normally be a loaded file
    // or Database from SQL or Core Data
    addItem("Pizza",
    	price: 4.99,
    	calories: 317)
    addItem("Pasta", 
    	price: 7.99, 
    	calories: 325)
    addItem("Chocolate Mousse", 
    	price: 6.45, 
    	calories: 470)
    addItem("Oatmeal", 
    	price: 2.95, 
    	calories: 160)
    addItem("Pancakes", 
    	price: 3.95, 
    	calories: 470)
    addItem("Peanut Butter and Jelly", 
    	price: 1.99, 
    	calories: 319)
    addItem("Pollo con Mole", 
    	price: 8.50, 
    	calories: 860)
    addItem("Hamburger", 
    	price: 4.35, 
    	calories: 250)
    addItem("Pad Thai", 
    	price: 7.35, 
    	calories: 430)
    addItem("Paneer", 
    	price: 6.99, 
    	calories: 100)
    addItem("Sirloin Steak", 
    	price: 24.49, 
    	calories: 165)
    addItem("Chocolate Chip Cookie", 
    	price: 2.75, 
    	calories: 210)
    addItem("Tuna Salad Sandwich", 
    	price: 3.25, 
    	calories: 420)
    addItem("Bagel", 
        price: 1.50,
    	calories: 275)
}

Our data will now load when we initialize the class.

Coding The Info View Controller

We’ll use the key:value coding of dictionareis again to find and access information about a certain food. Dictionary access returns an optional value, so we’ll check for nil using optional chaining. If there is a value, we’ll display it. If not, we’ll display that it wasn’t found in our database. Add the following to InfoViewController:

var foodInfoModel = FoodInfoModel() //our model
func displayFoodInfo(){
    if let foodInfo = foodInfoModel.list[food]{
       caloriesLabel.text = String(format:"%i Calories",foodInfo.calories)
       priceLabel.text = String(format:"Price: %1.2f",foodInfo.price)
   } else {
      caloriesLabel.text = "Not Found"
      priceLabel.text = "Not Found"
   }
 }

We’ll display the info when we load the view controller. Change viewDidLoad to this:

override func viewDidLoad() {
    super.viewDidLoad()
    navigationItem.title = food
    displayFoodInfo()
 }
    

Build and run. In Lunch, select Peanut butter and jelly

2015-10-05_07-49-08

This demo app worked with literal values we added in the init() of a class. In a real app, it’s likely that these would be SQLite databases, files or Core Data. Actually implementing them in an app is more than few lessons in themselves, so I went with this easier method. Once you understand this concept, you might want to learn about persistent storage of some kind.

The Whole Code

Note in the code below I did not format for web readability. I assume this will be code cut and pasted to Xcode, so left it in the standard format, with the exception of adding data to the arrays and dictionaries.

MealModel.swift

//
//  MealModel.swift
//  DrillDownTableDemo
//
//  Created by Steven Lipton on 10/5/15.
//  Copyright © 2015 MakeAppPie.Com. All rights reserved.
//

import UIKit
/* first iteration of model
class MealModel{
 
    //first iteration for testing
    var meal:[String] = [
        "Breakfast",
        "Lunch",
        "Dinner"]
} */

/* Second iteration of model */
class MealModel {
    var mealList = [String:[String]]()
    var meal:[String]{
        get{
            return Array(mealList.keys)
        }
    }
    func addItem(meal:String,foods:[String]){
        //adds an item to the dictionary if not there
        //side effect: changes current elements in dictionary
        mealList[meal] = foods
    }
    
    func foods(meal:String) -> [String]{ //for use with the model
        if let list = self.mealList[meal]{
            return list
        } else {
            return []
        }
    }

    /* Load data into the model
       Note that this would normally be some external load
       from a file or database
       For this lesson we are loading it literally
    */
    init(){
        addItem("Breakfast",
            foods: [
                "Oatmeal",
                "Pancakes",
                "Bagel"
            ])
        addItem("Lunch",
            foods: [
                "Pizza",
                "Pasta",
                "Peanut Butter and Jelly",
                "Tuna Salad Sandwich",
                "Hamburger"
            ])
        addItem("Dinner",
            foods: [
                "Mole con Pollo",
                "Pad Thai",
                "Paneer",
                "Sirloin Steak",
                "Pizza",
                "Pasta",
                "Hamburger"
            ])
        addItem("Snack",
            foods: [
                "Chocolate Chip Cookie",
                "Pizza"
            ])
    }
    
}

FoodsModel.swift

//
//  FoodsModel.swift
//  DrillDownTableDemo
//
//  Created by Steven Lipton on 10/5/15.
//  Copyright © 2015 MakeAppPie.Com. All rights reserved.
//

import UIKit
/* First Iteration of the model
class FoodsModel {
    var foodList:[String:String] = [
        "Pizza":"Lunch",
        "Pasta":"Lunch",
        "Chocolate Mousse":"Dessert",
        "Oatmeal":"Breakfast",
        "Pancakes":"Breakfast",
        "Peanut Butter and Jelly":"Lunch",
        "Mole con Pollo":"Dinner",
        "Hamburger":"Lunch",
        "Pad Thai":"Dinner",
        "Paneer":"Dinner",
        "Sirloin Steak":"Dinner",
        "Chocolate Chip Cookie":"Snack",
        "Tuna Salad Sandwich":"Lunch",
        "Bagel":"Breakfast"
    ]
    /*
    func foods(meal:String) -> [String]{
        return Array(foodList.keys)
    }
*/
    //linear search for all food with a given meal
    func foods(meal:String) -> [String]{
        var shortList:[String] = []
        for food in Array(foodList.keys){
            if foodList[food] == meal{
                shortList += [food]
            }
        }
        return shortList
    }
}*/


/* Second Iteration of the model */
class FoodsModel{
    var foodList:[String] = []
    func foods(meal:String) ->[String]{
        return foodList
    }
}

FoodInfoModel.swift

//
//  FoodInfoModel.swift
//  drillDownTable
//
//  Created by Steven Lipton on 10/1/15.
//  Copyright © 2015 MakeAppPie.Com. All rights reserved.
//

import UIKit

class FoodItemInfo{ // the record or row of the database
    var name = ""
    var price = 0.00
    var calories = 0
    init(name:String,price:Double,calories:Int){
        self.name = name  //primary key
        self.price = price
        self.calories = calories
    }
    

}

class FoodInfoModel{ // the file of the database
    var list = [String:FoodItemInfo]()
    
    func addItem(key:String,price:Double,calories:Int){
        let foodItem = FoodItemInfo(name: key, price: price, calories: calories)
        list[key] = foodItem  //this modifies or adds to the file
    }
    
    init(){
        // this would normally be a loaded file
        // or Database from SQL or Core Data
        addItem("Pizza",
           price: 4.99, 
           calories: 317)
        addItem("Pasta", 
           price: 7.99, 
           calories: 325)
        addItem("Chocolate Mousse", 
           price: 6.45, 
           calories: 470)
        addItem("Oatmeal", 
           price: 2.95, 
           calories: 160)
        addItem("Pancakes", 
           price: 3.95, 
           calories: 470)
        addItem("Peanut Butter and Jelly", 
           price: 1.99, 
           calories: 319)
        addItem("Pollo con Mole", 
           price: 8.50, 
           calories: 860)
        addItem("Hamburger", 
           price: 4.35, 
           calories: 250)
        addItem("Pad Thai", 
           price: 7.35, calories: 430)
        addItem("Paneer", 
           price: 6.99, 
           calories: 100)
        addItem("Sirloin Steak", 
           price: 24.49, 
           calories: 165)
        addItem("Chocolate Chip Cookie", 
           price: 2.75, 
           calories: 210)
        addItem("Tuna Salad Sandwich", 
           price: 3.25, 
           calories: 420)
        addItem("Bagel", 
           price: 1.50, 
           calories: 275)
    }

}

MealsTableViewController.swift

//
//  MealsTableViewController.swift
//  DrillDownTableDemo
//
//  Created by Steven Lipton on 10/4/15.
//  Copyright © 2015 MakeAppPie.Com. All rights reserved.
//

import UIKit

class MealsTableViewController: UITableViewController {
    let meals = MealModel()
    
    //MARK:  Table View Datasources
    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 1
    }
    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return meals.meal.count
    }
    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("cell",
            forIndexPath: indexPath)
        let row = indexPath.row
        cell.textLabel?.text = meals.meal[row]
        return cell
    }
    
    //MARK: Life Cycle 
    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        if segue.identifier == "Foods"{
            let vc = segue.destinationViewController as! FoodsTableViewController   
            let selectedMeal = meals.meal[(tableView.indexPathForSelectedRow?.row)!]
            vc.meal = selectedMeal
            vc.navigationItem.title = selectedMeal
            vc.foodsModel.foodList = meals.foods(selectedMeal)
        }
    }

}

FoodsTableViewController.swift

//
//  FoodsTableViewController.swift
//  DrillDownTableDemo
//
//  Created by Steven Lipton on 10/4/15.
//  Copyright © 2015 MakeAppPie.Com. All rights reserved.
//

import UIKit

class FoodsTableViewController: UITableViewController {
    let foodsModel = FoodsModel()
    var meal = "Lunch"
    var foods = [String]()
    
    //MARK: Table View Data Sources
    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 1
    }
    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return foods.count
    }
    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
        let row = indexPath.row
        cell.textLabel?.text = foods[row]
        
        if (row % 2) == 0  { //alternate rowcolors
            cell.backgroundColor = UIColor.clearColor()
            cell.textLabel?.textColor = UIColor.blackColor()
        }
        cell.alpha = 0.75
        return cell
    }
    
    
    //MARK: Life Cycle
    override func viewDidLoad() {
        super.viewDidLoad()
        foods = foodsModel.foods(meal)
    }
    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        let selectedRow = (tableView.indexPathForSelectedRow?.row)!
        if segue.identifier == "Info"{
            let vc = segue.destinationViewController
                as! InfoViewController
            vc.food = foods[selectedRow]
        }
    
    }
}

InfoViewController.swift

//
//  InfoViewController.swift
//  DrillDownTableDemo
//
//  Created by Steven Lipton on 10/5/15.
//  Copyright © 2015 MakeAppPie.Com. All rights reserved.
//

import UIKit

class InfoViewController: UIViewController {

    @IBOutlet weak var priceLabel: UILabel!
    
    @IBOutlet weak var caloriesLabel: UILabel!
    
    
    var food = "Food Info"
    var foodInfoModel = FoodInfoModel() //our model
    func displayFoodInfo(){
        if let foodInfo = foodInfoModel.list[food]{
            caloriesLabel.text = String(format:"%i Calories",foodInfo.calories)
            priceLabel.text = String(format:"Price: %1.2f",foodInfo.price)
        } else {
            caloriesLabel.text = "Not Found"
            priceLabel.text = "Not Found"
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        navigationItem.title = food
        displayFoodInfo()
    }

}

From Apple To Raspberry Pi: An Example of Implementing a Python MVC (Part 2)

appleberryIn the last part we implemented the view and controller for the Popper’s Penguins application. Now we add the model.  Along the way, we will make a delegate method for the model and change our target actions to add our data to a list.

Add the Model Class

We want to keep a list of our penguin observations. As a beginning data structure, the list data structure in Python will do this. Lists are mutable collections of sequential data. Lists can take any data type, including more lists. This will come in handy for us. We have two pieces of data we want to keep together: the penguin type and the penguin action.  We can then send that list to the list in our model.

Add this code:

#(1)adding the model 2014-May-28 with convenience method
class MyModel():
    def __init__(self,vc):
        self.vc = vc
        self.myList = []
        self.count = 0
#Setters and getters for the model
    def getList(self):
        return self.myList
    def addToList(self,item):
        myItem = item
        myList = self.myList
        myList.append(myItem)
        self.myList=myList
    def getItemAtIndex(self,index):
        return myList[index]
#other methods
    def addRecordToList(self,penguin,action):
        self.addToList([penguin,action])

We included a method addRecordToList() to take the penguin data and make it a list that then ends up in the model’s list. We are trying not to handle data manipulations in the controller, so making methods like this keeps all of those manipulations in the model.  The rest of the code is pretty simple, and is very similar to the example code from the MVC template post. We make some getters and setters for handling lists. There are many more we could create, but we will make what we need as we build the application.

Add the Delegate

In the View, we had the command attribute to signal that a change occurred the controller needs to handle. In the model, there is no such notification, we use a delegate instead. Add this code above the setters and getters in the model:

#(2)Delegate goes here. Model would call this on internal change
    def modelDidChange(self):
        self.vc.modelDidChangeDelegate()

This refers  to a method in the view controller that will handle the change. Now add this as the last line to the addList() method:

self.modelDidChange()

We now will jump to the delegate when there is an entry added. Note the one problem with delegates: We need to remember to put them in the model’s methods where there is change to the model.

Add a Model Instance to the Controller

We have set up our model. Now to use it.  Add this to the view controller, just under the instantiation for the view:

#added instantiation of view 2014 May 26
        self.view = MyView(self)
#(3)added instantiation of model 2014 May 28
        self.model = MyModel(self)

There is now a connection between the controller and the model.

Add the Delegate in the Controller

We have a delegate that needs attention. The model will call a delegate in the controller and we need to do something in that delegate to make sure it is working. Our next post will cover what to do with changing data in a GUI, but for now we’ll just print to the console.  Add this to the code for the controller, under the getters and setters.

#(4) added a delegate for the model 2014 May 28
    def modelDidChangeDelegate(self):
        print("Gork!!")
        print(self.model.getList())

Use the Model

We now have a model, so add the data collected to the list. In the controller, change the  addPressed() method to the following:

    def addPressed(self):
#Change getters and setters for the view
        self.view.setLabelText(self.view.getPenguinType()+ ' Penguin '+ self.view.getPenguinAction() + ' Added')
#(5)added true functionality -- this adds the entry to the model. 2014 May 28
        self.model.addRecordToList(self.view.getPenguinType(),self.view.getPenguinAction())

See If It works

Build and Run. Again there is no change when using the user interface.

Screenshot 2014-05-19 08.53.39
Press the Add button. The console does make a change.

>> ================================ RESTART ================================
>>>
Gork!!
[['Flippy Slippy', 'Sad']]

Add a few more, and the list increases:

>> ================================ RESTART ================================
>>>
Gork!!
[['Flippy Slippy', 'Sad']]
Gork!!
[['Flippy Slippy', 'Sad'], ['Adele', 'Singing']]
Gork!!
[['Flippy Slippy', 'Sad'], ['Adele', 'Singing'], ['Oswald Cobblepot', 'Plotting Evil']]
Gork!!
[['Flippy Slippy', 'Sad'], ['Adele', 'Singing'], ['Oswald Cobblepot', 'Plotting Evil'], ['Tux', 'Happy']]

This is something we should see in the window with a scrolling list. Next time we’ll add a scrolling list to the application.

The Whole Code

Here’s what our code looks like after this lesson. I have also posted code on github for the Popper’s Penguins and MVC  if you want to download it here.

# poppers_penguins_MVC_02
#MVC Version 2014 May 28
#Change: Implements the Model according to the MVC template

from tkinter import *
from tkinter import ttk
from tkinter import messagebox

class MyViewController():
    def __init__(self,parent):
        self.parent = parent;      

#added instantiation of view 2014 May 26
        self.view = MyView(self)
#(3)added instantiation of model 2014 May 28
        self.model = MyModel(self)

    #Handlers -- target action
    def addPressed(self):
#Change getters and setters for the view
        self.view.setLabelText(self.view.getPenguinType()+ ' Penguin '+ self.view.getPenguinAction() + ' Added')
#(5)added true functionality -- this adds the entry to the model. 2014 May 28
        self.model.addRecordToList(self.view.getPenguinType(),self.view.getPenguinAction())
    def quitPressed(self):
#Change getters and setters for the view
        self.view.setLabelText('Quitting')
        answer = messagebox.askokcancel('Ok to Quit','This will quit the program. \n Ok to quit?')
        if answer==True:
            self.parent.destroy()
#(4) added a delegate for the model 2014 May 28
    def modelDidChangeDelegate(self):
        print("Gork!!")
        print(self.model.getList())

class MyView(Frame):
#Change parent to vc and add a frame 2014 May 26 a
    def __init__(self,vc):
        self.frame=Frame()
        self.frame.grid(row=0,column=0)
        self.vc = vc

        #properties of the view
        self.labelText = StringVar()
        self.labelText.set("Popper's Penguins Ready")
        self.penguinType = StringVar()
        self.penguinType.set('Penguin Type')
        self.penguinAction = StringVar()
        self.penguinAction.set('Penguin Action')
#remove self.vc here as vc calls this, not the othere way around.   2014 May 26
#       self.vc = MyViewController(self)
        self.loadView()
        self.makeStyle()

#Handlers -- our pseudo-controller
#(3)removed from application 2014-May-26

#added setters and getters for the properties 2014-May 26
    def setLabelText(self,newText):
        self.labelText.set(newText)
    def getLabelText(self):
        return self.labelText.get()
    def setPenguinType(self,newType):
        self.penguinType.set(newType)
    def getPenguinType(self):
        return self.penguinType.get()
    def setPenguinAction(self,newAction):
        self.penguinAction.set(newAction)
    def getPenguinAction(self):
        return self.penguinAction.get()

#Style Sheet
    def makeStyle(self):
        self.s = ttk.Style()
        self.s.configure('TFrame',background = '#5555ff')
        self.s.configure('TButton',background = 'blue', foreground = '#eeeeff', font = ('Sans','14','bold'), sticky = EW)
        self.s.configure('TLabel',font=('Sans','16','bold'),background = '#5555ff', foreground = '#eeeeff')
        self.s.map('TButton', foreground = [('hover','#5555ff'), ('focus', 'yellow')])
        self.s.map('TButton', background = [('hover', '#eeeeff'),('focus','orange')])
        self.s.configure('TCombobox',background = '#5555ff',foreground ='#3333ff',font = ('Sans',18))

#loading the view
#changed the command= to refer to the view controller 2014-May-26
    # self.addPressed now is self.vc.addPressed
    # self.quitPressed now is self.vc.quitPressed
    def loadView(self):
        #label
        status_label = ttk.Label(self.frame, textvariable = self.labelText)
        #status_label.configure(font=('Sans','16','bold'),background = 'blue', foreground = '#eeeeff')
        status_label.grid(row=0,column=0,columnspan=4,sticky=EW)

        add_button = ttk.Button(self.frame,command= self.vc.addPressed,text = 'Add')
        add_button.grid(row = 2, column = 0)
        quit_button = ttk.Button(self.frame, command = self.vc.quitPressed, text = 'Quit')
        quit_button.grid(row = 2, column = 3)

        penguinType_values = ['Adele','Emperor','King','Blackfoot','Humboldt','Galapagos','Macaroni','Tux','Oswald Cobblepot','Flippy Slippy']
        penguinType_combobox = ttk.Combobox(values = penguinType_values, textvariable = self.penguinType)
        penguinType_combobox.grid(row =1, column = 0)

        penguinAction_values = ['Happy','Sad','Angry','Standing','Swimming','Eating','Sleeping','On Belly','Plotting Evil','Singing','Dancing','Being Cute']
        penguinAction_combobox = ttk.Combobox(values = penguinAction_values, textvariable = self.penguinAction)
        penguinAction_combobox.grid(row=1, column = 3)

#(1)adding the model 2014-May-28 with convenience method
class MyModel():
    def __init__(self,vc):
        self.vc = vc
        self.myList = []
        self.count = 0
#(2)Delegate goes here. Model would call this on internal change
    def modelDidChange(self):
        self.vc.modelDidChangeDelegate()
#Setters and getters for the model
    def getList(self):
        return self.myList
    def addToList(self,item):
        myItem = item
        myList = self.myList
        myList.append(myItem)
        self.myList=myList
        self.modelDidChange()
    def getItemAtIndex(self,index):
        return myList[index]
#other methods
    def addRecordToList(self,penguin,action):
        self.addToList([penguin,action])

def main():
    root = Tk()
#(8) Set up the main loop 2014 May 26
    frame = Frame(root, background="#5555ff", takefocus = 0)
    root.title("Popper's Penguins")
    app = MyViewController(root)
    root.mainloop() 

if __name__ == '__main__':
    main()  

From Apple To Raspberry Pi: An Example of Implementing a Python MVC (Part 1)

appleberryI gave a template for MVC in the last post, This time I’m going to use it in the Popper’s Penguins application. We’ll re-organize what we already did with views and controllers to match the template.  We’ll add the model in the next installment of this series.

Changing the View

Let’s start by modifying the view. The code in the view controller is dependent on this code.

Change the View’s Constructor

Change the __init__ for the view to this:

#(1) Change parent to vc and add a frame 2014 May 26 a  
    def __init__(self,vc):
        self.frame=Frame()
        self.frame.grid(row=0,column=0)
        self.vc = vc
        
        #properties of the view 
        self.labelText = StringVar()
        self.labelText.set("Popper's Penguins Ready")
        self.penguinType = StringVar()
        self.penguinType.set('Penguin Type')
        self.penguinAction = StringVar()
        self.penguinAction.set('Penguin Action')
#(2)remove self.vc here as vc calls this, not the other way around.   2014 May 26
#       self.vc = MyViewController(self)
        self.loadView()
        self.makeStyle()

Lines 3 and 4 creates the frame for the view. We set the pointer to the view controller to self.vc in line 5 which the view controller passes to the view’s constructor when it instantiates the view object. We left lines 8-13 alone. There is a valid point to set them in the view controller, but there is an equally valid point they are initial values, and thus part of the view. In Interface Builder terms, this is the same as setting the default values in the properties window.

Since the view controller instead of the view is the first class called by main() we remove self.vc in line 15. I commented it out here but it perfectly okay to remove it completely.

Remove the Pseudo-Controller

We have in our code the redundant pseudo-controller for target-actions within the view. We want target actions to happen in our controller, not our view. Remove the code or comment it out like this:

Handlers -- our pseudo-controller
#(3)removed from application 2014-May-26
#    def addPressed(self):
#        self.labelText.set(self.penguinType.get()+ ' Penguin '+ self.penguinAction.get() + ' Added')
#    def quitPressed(self):
#        self.labelText.set('Quitting')
#        answer = messagebox.askokcancel('Ok to Quit','This will quit the program. \n Ok to quit?')
#        if answer==True:
#            self.parent.destroy()

Add Setters and Getters for Our Properties

Next we need setters and getters for the controller to communicate with the view. Add the following methods after the __init__() method:

#(4)added setters and getters for the properties 2014-May 26
    def setLabelText(self,newText):
        self.labelText.set(newText)
    def getLabelText(self):
        return self.labelText.get()
    def setPenguinType(self,newType):
        self.penguinType.set(newType)
    def getPenguinType(self):
        return self.penguinType.get()
    def setPenguinAction(self,newAction):
        self.penguinAction.set(newAction)
    def getPenguinAction(self):
        return self.penguinAction.get()

None of this special. At the moment they are simple setters and getters. We may make some form of type checking for safety at some point, but for simplicity, we’ll skip that for now.

Change the Target-Action Calls

The command attributes are not calling the correct methods. They are calling the target action we just removed. We need to change the calls so they call the controller’s versions.  Change the loadView() for the buttons as follows:

#(5)changed the command= to refer to the view controller 2014-May-26
    # self.addPressed now is self.vc.addPressed
    # self.quitPressed now is self.vc.quitPressed
    #buttons
        add_button = ttk.Button(self.frame,command= self.vc.addPressed,text = 'Add')
        add_button.grid(row = 2, column = 0)
        quit_button = ttk.Button(self.frame, command = self.vc.quitPressed, text = 'Quit')
        quit_button.grid(row = 2, column = 3)

The View Controller

We’ve made the change to the view. Now to make the changes to the view controller. We’ll instantiate the view, make target actions and refer to view properties by setters and getters.

Instantiate the View in the View Controller

Change the constructor first so we have a view to use. Change the __init__ in MyController to this:

    def __init__(self,parent):
        self.parent = parent;               
#(6)added instantiation of view 2014 May 26
        self.view = MyView(self)

Any time we need to use a property of a view, we can now access it from self.view.

Change the Target Actions

We moved the target actions into the view controllers.  We’ll need to set up those in the View Controller.   There are a few place-maker versions already there. Replace them with this:

#Handlers -- target action
    def addPressed(self):
#(7a) Change getters and setters for the view
        self.view.setLabelText(self.view.getPenguinType()+ ' Penguin '+ self.view.getPenguinAction() + ' Added')
        
    def quitPressed(self):
#(7b) Change getters and setters for the view
        self.view.setLabelText('Quitting')
        answer = messagebox.askokcancel('Ok to Quit','This will quit the program. \n Ok to quit?')
        if answer==True:
            self.parent.destroy() 

In lines 4 and 8, we are now using the setters and getters we made in the view, and not directly using Tkinter set() and get(). Line 11 calls the root node to destroy the window when we indicate in the dialog box to quit.

Change the Main Method

We deleted references to the window in our view constructor. We also have to change what class we instantiate in main() from MyView to MyViewController. It makes a window, sets a title, starts the view controller and starts mainloop(). Change it to this:

def main():
    root = Tk()
#(8) Set up the main loop 2014 May 26
    root.title("Popper's Penguins")         
    app = MyViewController(root)
    root.mainloop() 
 

Test the Application

Build and run:

Screenshot 2014-05-19 08.54.16

If all works right, nothing will change.  In our next lesson, we’ll discuss a little about lists and will add our first iteration of the  model.

The Whole Code.

# poppers_penguins_MVC_01
#MVC Version 2014 May 26
#implements the View and View Controller according to the MVC template

from tkinter import *
from tkinter import ttk
from tkinter import messagebox
 
 
class MyViewController():
    def __init__(self,parent):
        self.parent = parent;      
         
#(6)added instantiation of view 2014 May 26
        self.view = MyView(self)

#Handlers -- target action
    def addPressed(self):
#(7a) Change getters and setters for the view
        self.view.setLabelText(self.view.getPenguinType()+ ' Penguin '+ self.view.getPenguinAction() + ' Added')
        
    def quitPressed(self):
#(7b) Change getters and setters for the view
        self.view.setLabelText('Quitting')
        answer = messagebox.askokcancel('Ok to Quit','This will quit the program. \n Ok to quit?')
        if answer==True:
            self.parent.destroy()
 
class MyView(Frame):
#(1) Change parent to vc and add a frame 2014 May 26 a  
    def __init__(self,vc):
        self.frame=Frame()
        self.frame.grid(row=0,column=0)
        self.vc = vc
        
        #properties of the view 
        self.labelText = StringVar()
        self.labelText.set("Popper's Penguins Ready")
        self.penguinType = StringVar()
        self.penguinType.set('Penguin Type')
        self.penguinAction = StringVar()
        self.penguinAction.set('Penguin Action')
#(2)remove self.vc here as vc calls this, not the othere way around.   2014 May 26
#        self.vc = MyViewController(self)
        self.loadView()
#       self.makeStyle()
         
#Handlers -- our pseudo-controller
#(3)removed from application 2014-May-26
#    def addPressed(self):
#        self.labelText.set(self.penguinType.get()+ ' Penguin '+ self.penguinAction.get() + ' Added')
#    def quitPressed(self):
#        self.labelText.set('Quitting')
#        answer = messagebox.askokcancel('Ok to Quit','This will quit the program. \n Ok to quit?')
#        if answer==True:
#            self.parent.destroy()

#(4)added setters and getters for the properties 2014-May 26
    def setLabelText(self,newText):
        self.labelText.set(newText)
    def getLabelText(self):
        return self.labelText.get()
    def setPenguinType(self,newType):
        self.penguinType.set(newType)
    def getPenguinType(self):
        return self.penguinType.get()
    def setPenguinAction(self,newAction):
        self.penguinAction.set(newAction)
    def getPenguinAction(self):
        return self.penguinAction.get()
    

#Style Sheet
    def makeStyle(self):
        self.s = ttk.Style()
        self.s.configure('TFrame',background = '#5555ff')
        self.s.configure('TButton',background = 'blue', foreground = '#eeeeff', font = ('Sans','14','bold'), sticky = EW)
        self.s.configure('TLabel',font=('Sans','16','bold'),background = '#5555ff', foreground = '#eeeeff')
        self.s.map('TButton', foreground = [('hover','#5555ff'), ('focus', 'yellow')])
        self.s.map('TButton', background = [('hover', '#eeeeff'),('focus','orange')])
        self.s.configure('TCombobox',background = '#5555ff',foreground ='#3333ff',font = ('Sans',18))
   
#loading the view
    def loadView(self):
        #label
        status_label = ttk.Label(self.frame, textvariable = self.labelText)
        status_label.grid(row=0,column=0,columnspan=4,sticky=EW)
#(5)changed the command= to refer to the view controller 2014-May-26
    # self.addPressed now is self.vc.addPressed
    # self.quitPressed now is self.vc.quitPressed

    #buttons
        add_button = ttk.Button(self.frame,command= self.vc.addPressed,text = 'Add')
        add_button.grid(row = 2, column = 0)
        quit_button = ttk.Button(self.frame, command = self.vc.quitPressed, text = 'Quit')
        quit_button.grid(row = 2, column = 3)
    #combobox
        penguinType_values = ['Adele','Emperor','King','Blackfoot','Humboldt','Galapagos','Macaroni','Tux','Oswald Cobblepot','Flippy Slippy']
        penguinType_combobox = ttk.Combobox(values = penguinType_values, textvariable = self.penguinType)
        penguinType_combobox.grid(row =1, column = 0)
        penguinAction_values = ['Happy','Sad','Angry','Standing','Swimming','Eating','Sleeping','On Belly','Plotting Evil','Singing','Dancing','Being Cute']
        penguinAction_combobox = ttk.Combobox(values = penguinAction_values, textvariable = self.penguinAction)
        penguinAction_combobox.grid(row=1, column = 3)


         
def main():
    root = Tk()
#(8) Set up the main loop 2014 May 26
    root.title("Popper's Penguins")         
    app = MyViewController(root)
    root.mainloop() 
 
 
if __name__ == '__main__':
    main()  

From Apple to Raspberry Pi: A MVC Template for Tkinter

appleberryIn this installment I’m going to take a side path from the building of the Popper’s penguins app to clarify something I gave in my last post on Python, Tkinter and Raspberry Pi. In the world of Xcode, both for OS X development and for iOS there is this critical idea called the MVC pattern, or Model View Controller.  Apple describes it this way:

The Model-View-Controller design pattern (MVC) is quite old. Variations of it have been around at least since the early days of Smalltalk. It is a high-level pattern in that it concerns itself with the global architecture of an application and classifies objects according to the general roles they play in an application…Object-oriented programs benefit in several ways by adapting the MVC design pattern for their designs. Many objects in these programs tend to be more reusable and their interfaces tend to be better defined. The programs overall are more adaptable to changing requirements—in other words, they are more easily extensible than programs that are not based on MVC. Moreover, many technologies and architectures in Cocoa—such as bindings, the document architecture, and scriptability—are based on MVC and require that your custom objects play one of the roles defined by MVC.

Apple considers MVC a core competency. However most developers do not need to get too deep into what MVC is, since Xcode does a lot of the work for you. There are three parts of MVC which can be basically described as:

  1. Model: The data you are processing
  2. View: The user interface
  3. Controller: Connects the Model to the View

The big advantage of this pattern is developers do not need to rewrite all of their code when one of the three changes. I had a conversation recently about software validation for medical devices adding user interfaces. For a lot of reasons I won’t go into here, Tkinter would never make it through a software validation process. I might have to buy a validated user interface module or write my own to get a medical device approved for use. If everything that is Tkinter is contained in the view, I only have to change the view, not the entire program.

Moving from Xcode to Python MVC: A Bare Bones Template

IN Xcode, models would be classes which handle data. Views are storyboards or interface builder .xib files. The developer connects up the view and the model in a subclass of UIViewController and handles events happening with the model and the view. Much of this is near automatic in Xcode. In Python, this is not the case. We need to spell out everything. I tried finding one nice template or example on the internet that made sense to me, but had little luck. So, I took what I could find and made up my own:

#MVC_Template_01
#2014 May 23  by Steven Lipton http://makeAppPie.com
#Controller initializing MVC -- simplest version possible.
from tkinter import *

#
# A A Model-View-Controller framework for TKinter.
# Model: Data Structure. Controller can send messages to it, and model can respond to message.
# View : User interface elements. Controller can send messages to it. View can call methods from Controller when an event happens.
# Controller: Ties View and Model together. turns UI responses into changes in data and vice versa.

#
#Controller: Ties View and Model together.
#       --Performs actions based on View events.
#       --Sends messages to Model and View and gets responses
#       --Has Delegates
#       --Controllers may talk to other controllers through delegates

class MyController():
    def __init__(self,parent):
        self.parent = parent
        self.model = MyModel(self)    # initializes the model
        self.view = MyView(self)  #initializes the view

        #initialize properties in view, if any
        pass

        #initalize properties in model, if any
        pass

#event handlers -- add functions called by command attribute in view
    def someHandelerMethod(self):
        pass
#delegates -- add functions called by delegtes in model or view
    def modelDidChangeDelegate(self):
        pass

#View : User interface elements.
#       --Controller can send messages to it.
#       --View can call methods from Controller vc when an event happens.
#       --NEVER communicates with Model.
#       --Has setters and getters to communicate with controller

class MyView(Frame):
    def loadView(self):
        pass
    def __init__(self,vc):
        #make the view
        self.frame=Frame()
        self.frame.grid(row = 0,column=0)

        #set the delegate/callback pointer
        self.vc = vc

        #control variables go here. Make getters and setters for them below
        someControlVariable= StringVar()
        someControlVariable = ('nil')

        #load the widgets
        self.loadView()
    #Getters and setters for the control variables.
    def getSomeControlVariable(self):
    #returns a string of the entry text
        return self.entry_text.get()
    def setSomeControlVariable(self,text):
    #sets the entry text given a string
        self.entry_text.set(text)

#Model: Data Structure.
#   --Controller can send messages to it, and model can respond to message.
#   --Uses delegates from vc to send messages to the Controller of internal change
#   --NEVER communicates with View
#   --Has setters and getters to communicate with Controller

class MyModel():
    def __init__(self,vc):
        #set delegate/callback pointer
        self.vc = vc
        #initialize model
        self.myModel = 0

#Delegate goes here. Model would call this on internal change
    def modelDidChange(self):
        self.vc.listChangedDelegate()
#Setters and getters for the model
    def getModel(self):
        return self.myModel
    def setList(self,newData)
        self.MyModel = newData
        self.modelDidChange #delegate called on change
#Any internal processing for the model        

def main():
    root = Tk()
    frame = Frame(root )
    root.title('MVC Skeleton')
    app = MyController(root)
    root.mainloop()  

if __name__ == '__main__':
    main()

This is just a bare bones example, though heavily commented. Run it and it will do nothing. Those who have never used MVC or those not comfortable with it will still not understand what is going on. Therefore let’s make a very simple example application piece by piece to explain what MVC does and how to use the template.

The View: What the User Gets

The view is the user interface. The user pushes buttons, move sliders, enters text and all the other types of things we can do with a user interface. The user also gets feedback on their actions from the user interface. For those following my series, we’ve been exploring Tkinter for a while and should be quite comfortable with it. If you are not you might want to check out my introduction to Tkinter The view is all Tkinter code. If I decide to change to a different UI package or make some of my own in pyGame, I’d do all that in the view.

Let’s take an example of a small app that adds a text entry to a list. Here is what our view would look like:

class MyView(Frame):
    def loadView(self):
        quitButton = Button(self.frame,text = 'Quit', command= self.vc.quitButtonPressed).grid(row = 0,column = 0)
        addButton = Button(self.frame,text = "Add", command = self.vc.addButtonPressed).grid(row = 0, column = 1)
        entry = Entry(self.frame,textvariable = self.entry_text).grid(row = 1, column = 0, columnspan = 3, sticky = EW)
        label = Label(self.frame,textvariable = self.vc.label_text).grid(row = 2, column = 0, columnspan = 3, sticky = EW)
    def __init__(self,vc):
        self.frame=Frame()
        self.frame.grid(row = 0,column=0)
        self.vc = vc
        self.entry_text = StringVar()
        self.entry_text.set('nil')
        self.label_text = StringVar()
        self.label_text.set('nil')
        self.loadView()
    def getEntry_text(self):
    #returns a string of the entry text
        return self.entry_text.get()
    def setEntry_text(self,text):
    #sets the entry text given a string
        self.entry_text.set(text)
    def getLabel_text(self):
    #returns a string of the Label text
        return self.label_text.get()
    def setLabel_text(self,text):
    #sets the label text given a string
        self.label_text.set(text)

The __init__ function in lines 7 though 13 executes first. It initializes several control variables and sets up the frame. The method then calls loadView() which loads out widgets into the frame in lines 2 through 6. All of that is basic Tkinter code.
What isn’t basic Tkinter is line 10. The constructor method gets a parameter vc, and line 10 assigns it to self.vc. This is a link back to the view controller. A view keeps everything to itself — it encapsulates its data. Neither the model nor the controller can see or change it. A view is not allowed to see or change anything in the model or controller as well. The controller will contain the methods found in our command attributes for our buttons. If we cannot talk to one another, how do we tell the controller to do anything? That is what self.vc is for. It is a pointer used only for callbacks to the controller. That is why we have command = self.vc.addButtonPressed in the add button code. It executes a method in the controller.

Lines 16 through 27 create setters and getters. There are values we want to change or use in the view. This will be a message the view controller will send to the view, and the view can responds by doing something, or sending something back. Remember a setter and a getter is a function or message. It is an indirect change of a property. MVC only forbids direct changes. It does not break the MVC model.

The Model:Where the Data Lives

The model and view are similar in may ways. Both have setters and getters to let the controller use information in each class. The difference between the two is how models will react to change compared to views:

class MyModel():
    def __init__(self,vc):
        self.vc = vc
        self.myList = ['duck','duck','goose']
        self.count = 0
#Delegates-- Model would call this on internal change
    def listChanged(self):
        self.vc.listChangedDelegate()
#setters and getters
    def getList(self):
        return self.myList
    def initListWithList(self, aList):
        self.myList = aList
    def addToList(self,item):
        print("returned")
        myList = self.myList
        myList.append(item)
        self.myList=myList
        self.listChanged()

The code for the model here is quite simple. In the real world we might hookup a database or data structure to the model but for our purposes a simple list will do. In structure, we do almost the same as the view — it is a set of setters and getters. There are different types of processing as well, depending on the model. We might add a method for deletion of a list element . The key to the model is that it needs to notify the controller of a change to the model. In our case, it needs to tell the controller we have added an element to the list. While there are ways to do that more automatically, for our simpler model we will use a delegate. Delegates are another indirect method for getting the controller to react when something changes in the Model(or the View in some cases). Line 7-8 is the delegate method on this side. It calls a method in the controller which does the real work.

The Controller: The Hardworking Middle Class

The third part of MVC is the controller, which sits between the view and the model and coordinates between both of them. A lot of the work of the user interface after the user changing something the controller does.

class MyController():
    def __init__(self,parent):
        self.parent = parent
        self.model = MyModel(self)    # initializes the model
        self.view = MyView(self)  #initializes the view
        #initialize objects in view
        self.view.setEntry_text('Add to Label')
        self.view.setLabel_text('Ready')
 #event handlers
    def quitButtonPressed(self):
        self.parent.destroy()
    def addButtonPressed(self):
        self.view.setLabel_text(self.view.entry_text.get())
        self.model.addToList(self.view.entry_text.get())
    def listChangedDelegate(self):
        #model internally chages and needs to signal a change
        print(self.model.getList())

The controller will take information from both the model and the view and process it accordingly. To start, in lines 4 and 5 we set up the model and the view, and give the pointer back to the view controller as a parameter. We then send messages to the view to set our text in the Entry widget and Label widgets. Lines 10-14 are methods for handling view events. the first for clicking the quit button, and the second for clicking the add button. In the case of the add button, we handle this by sending messages to both the model and the view. In lines 15 – 17, we have the delegate from the model. When there is a change to the model, we use this method to do something about it. In this case, we print our contents out on the console.

This is the basic MVC pattern we will follow. The way I set it up this template would be easy to break, by directly assigning something in the model to the view. This is particularly true in Python where variables are bit more fluid than Objective-C. Programming patterns are not a restriction built into the design, though they can be. Programming patterns like MVC are disciplines of the developers to keep to the pattern. When using MVC with this template, the developer needs discipline to follow the rules set out for MVC.

We had a simple example in this code. In our next installment we will apply MVC to Popper’s Penguin app.

The Whole Code

above I gave the template. Here is the whole example code with comments to see it all in context.

#MVC_Template_01
#2014 May 23  by Steven Lipton http://makeAppPie.com
#Controller initializing MVC -- simplest version possible.
from tkinter import *

#
# A A Model-View-Controller framework for TKinter.
# Model: Data Structure. Controller can send messages to it, and model can respond to message.
# View : User interface elements. Controller can send messages to it. View can call methods from Controller when an event happens.
# Controller: Ties View and Model together. turns UI responses into chages in data.

#
#Controller: Ties View and Model together.
#       --Performs actions based on View events.
#       --Sends messages to Model and View and gets responses
#       --Has Delegates 

class MyController():
    def __init__(self,parent):
        self.parent = parent
        self.model = MyModel(self)    # initializes the model
        self.view = MyView(self)  #initializes the view
        #initialize objects in view
        self.view.setEntry_text('Add to Label') #a non cheat way to do MVC wiht tkinter control variables
        self.view.setLabel_text('Ready')
 #event handlers
    def quitButtonPressed(self):
        self.parent.destroy()
    def addButtonPressed(self):
        self.view.setLabel_text(self.view.entry_text.get())
        self.model.addToList(self.view.entry_text.get())
    def listChangedDelegate(self):
        #model internally chages and needs to signal a change
        print(self.model.getList())

#View : User interface elements.
#       --Controller can send messages to it.
#       --View can call methods from Controller vc when an event happens.
#       --NEVER communicates with Model.
#       --Has setters and getters to communicate with controller

class MyView(Frame):
    def loadView(self):
        quitButton = Button(self.frame,text = 'Quit', command= self.vc.quitButtonPressed).grid(row = 0,column = 0)
        addButton = Button(self.frame,text = "Add", command = self.vc.addButtonPressed).grid(row = 0, column = 1)
        entry = Entry(self.frame,textvariable = self.entry_text).grid(row = 1, column = 0, columnspan = 3, sticky = EW)
        label = Label(self.frame,textvariable = self.label_text).grid(row = 2, column = 0, columnspan = 3, sticky = EW)
    def __init__(self,vc):
        self.frame=Frame()
        self.frame.grid(row = 0,column=0)
        self.vc = vc
        self.entry_text = StringVar()
        self.entry_text.set('nil')
        self.label_text = StringVar()
        self.label_text.set('nil')
        self.loadView()
    def getEntry_text(self):
    #returns a string of the entry text
        return self.entry_text.get()
    def setEntry_text(self,text):
    #sets the entry text given a string
        self.entry_text.set(text)
    def getLabel_text(self):
    #returns a string of the Label text
        return self.label_text.get()
    def setLabel_text(self,text):
    #sets the label text given a string
        self.label_text.set(text)

#Model: Data Structure.
#   --Controller can send messages to it, and model can respond to message.
#   --Uses delegates from vc to send messages to the Controll of internal change
#   --NEVER communicates with View
#   --Has setters and getters to communicate with Controller

class MyModel():
    def __init__(self,vc):
        self.vc = vc
        self.myList = ['duck','duck','goose']
        self.count = 0
#Delegates-- Model would call this on internal change
    def listChanged(self):
        self.vc.listChangedDelegate()
#setters and getters
    def getList(self):
        return self.myList
    def initListWithList(self, aList):
        self.myList = aList
    def addToList(self,item):
        print("returned")
        myList = self.myList
        myList.append(item)
        self.myList=myList
        self.listChanged()

def main():
    root = Tk()
    frame = Frame(root,bg='#0555ff' )
    root.title('Hello Penguins')
    app = MyController(root)
    root.mainloop()  

if __name__ == '__main__':
    main()