I have an application that is to help troubleshooting a device. To do this troubleshooting I make suggestions and then ask for user input with a question that they must answer to continue. Depending on the answer I load a new view with a new set of suggestions and question. I am not sure if I understand the Model part of the MVC setup. I have a model that contains suggestions, suggestion_images, questions, answers, ... I also then link the questions to new model objects. I would just like to know if this is considered best practice? Or am I misunderstanding the MVC design scheme?
EDIT: Here is my model overview:
#import <Foundation/Foundation.h>
#interface troubleshootingInfo : NSObject {
NSString *stepTitle;
NSString *stepCount;
NSString *description;
NSString *imageTitle;
NSString *descriptionImageLink;
NSString *questionTitle;
NSMutableArray *actionsToPerform;
NSMutableArray *actionsStatus;
NSMutableArray *actionsImage;
NSMutableArray *actionImageTitle;
NSMutableArray *logActions;
troubleshootingInfo *nextNoObject;
troubleshootingInfo *nextYesObject;
// This is to be used with selections
NSMutableArray *userInputForAction;
}
//perform function on the set values
- (int) setActionRowHieght:(int)actionID; // get row hieght for table cell
- (int) setDescriptionRowHeight; // get row hieght for table cell
- (int) setQuestionTitleHeight; // get row hieght for table header
- (int) actionCount; // the number of actions in the actionsToPerform array
- (BOOL) isAction; // is action set
- (BOOL) isDescription; // is description set
- (BOOL) isQuestion; // is question set
- (BOOL) isActionPerformed:(int)action; // check if action is performed
- (BOOL) isActionImage:(int)action; // check if action has an image
- (NSString *) updateActionPerformed:(int)action;
Any help would be great.
Thanks
EDIT: fixed the code per suggestions (I think).
My header file now.
#interface TroubleshootingInfo : NSObject
// values of the class
#property NSString *stepTitle;
#property NSString *stepCount;
#property NSString *description;
#property NSString *imageTitle;
#property NSString *descriptionImageLink;
#property NSString *questionTitle;
#property NSMutableArray *actionsToPerform;
#property NSMutableArray *actionsStatus;
#property NSMutableArray *actionsImage;
#property NSMutableArray *actionImageTitle;
#property NSMutableArray *logActions;
#property TroubleshootingInfo *nextNoObject;
#property TroubleshootingInfo *nextYesObject;
// This is to be used with selections
#property NSMutableArray *userInputForAction;
//perform function on the set values
- (int) actionCount; // the number of actions in the actionsToPerform array
- (BOOL) hasAction; // is action set
- (BOOL) hasDescription; // is description set
- (BOOL) hasQuestion; // is question set
- (BOOL) isActionPerformed:(int)action; // check if action is performed
- (BOOL) ihasActionImage:(int)action; // check if action has an image
- (NSString *) updateActionPerformed:(int)action;
Ok sorry for the confusion. My question is related to hierarchical data models and table views. After reading through Apples documentation I feel I have a better idea of how a complex table should be handled. I believe the class I have featured here actually needs to be apart of a much more complex data model. I will rethink my design. Thank you.
That looks much more like a Controller-style object than a Model object. The Model in a quiz domain should have absolutely nothing to do with things like row height! The Model is the domain: questions and answers. Maybe "next question" (it depends on whether the choice of next question is determined by the previous answer, as in an adaptive test).
The classic MVC triad is pretty straightforward:
The View reflects the state of the Model
The Controller structures and presents the View and other Controllers
The Controller also tracks the state of the Model and can modify the UX and...
The Controller makes requests of the Model to modify its state ("Next question")
In iOS, the View will typically be subclasses of UIView, often which have other UIView elements and widgets as members. The Controller will be some form of UIViewController. The Model are in a form appropriate to the domain.
Since you're talking about row heights, I suggest that you read some tutorials about UITableViewController.
I'd make a couple adjustments:
Naming: Classnames should be capitalized.
Properties: The ivars listed between braces can be replaced by properties. Compilation will generate getters and setters automatically.
Design: The methods that mention UI layout attributes, like rowHeight, do not belong to the model. The view controller usually concerns itself with this sort of thing (for example, as the datasource for a table view). Most likely, the code you have in those methods belongs in your view controller.
A few observations
Use a capital letter for class names (TroubleshootingInfo).
Don't use ivars directly, use properties. Properties that should be private (i.e. not used by other classes directly) can go in a () category (#interface TroubleshootingInfo () in the .m file.
The model should not know anything about row heights. That is for the view (and occasionally view controller).
Your isAction, etc. bools sound like they would better be hasAction or isActionSet, etc. They are asking if the object has a property, not if the object is that property.
Related
I have 2 viewControllers that each contains one UITableView among other subviews. The 2 UITableViews have the same section structure and cell types, but the data source is different.
So I decided to extract this UITableView into a custom UITableView. The custom UITableView implements methods like tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath, and is its own data source and delegate. The viewController only provides data.
The question is I can't figure out which is the best way for viewControllers to provide data. I can think of 2 ways:
first way
in MyCustomTableView.h:
#protocol MyCustomTableViewDataProvider <NSObject>
- (NSArray*)dataArray;
#end
#interface MyCustomTableView : UITableView
#property (nonatomic, weak) id<MyCustomTableViewDataProvider> dataProvider;
#end
The viewController should implement this protocol:
- (NSArray*)dataArray {
return self.dataArray;
}
And in MyCustomTableView.m I use it like:
NSArray* dataArray = [self.dataProvider dataArray];
second way
in MyCustomTableView.h:
#interface MyCustomTableView : UITableView
#property (nonatomic, weak) NSArray* dataArray;
#end
Every time when there's a change in data, the viewController should inform the custom TableView to update its data:
self.customTableView.dataArray = self.dataArray;
And in MyCustomTableView.m I use it like:
self.dataArray
It seems that the second way could save some code, but I heard that you shouldn't store data in views because it violates the MVC principle. Could you please give me some advice? Many thanks!
IMHO, I wouldn't subclass any UIView unless it's absolutely necessary. Both TableViews and Collections provide protocols to control their behavior correctly.
If I were you, in order to keep the ViewController clean and short, I would create a custom NSObject implementing both UITableViewDelegate and DataSource protocols. This class would be in charge of providing the style of cells and sections (that is shared in those 2 table views) and also the data source. Of course, this object must have a property referencing its TableView.
The ViewController would only be in charge of triggering data retrieval whenever is needed. That data should be passed to that custom NSObject, which would be in charge of updating the table view.
BTW, this object could even be added through InterfaceBuilder.
It's a bit old, I know, but you could check :Lighter VC It's just a step forward towards MVVM.
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 8 years ago.
Improve this question
My View Controllers are holding my object properties and iVars etc. They shouldn't hold any at all.
** The rest of this post has been heavily edited for clarity as requested. **
My Class Object using the example given by NSBum that seeks to show data handled by one method, is able to continue being used in another.
#import <Foundation/Foundation.h>
#interface MYEmployee : NSObject
#property (nonatomic, strong) NSString *firstName;
#end
now for my VC interface file
#import <UIKit/UIKit.h>
#import "MYEmployee.h"
#interface MYEmployeeDetailViewController : UIViewController
#property ( nonatomic,strong) MYEmployee *employee; // as suggested
// UI elements
// a button that picks up from a textfield
- (IBAction)getName:(UIButton *)sender;
#property (strong, nonatomic) IBOutlet UITextField *inputField;
// a button that puts the names in a label
- (IBAction)showName:(UIButton *)sender;
#property (strong, nonatomic) IBOutlet UILabel *employeeNameLabel;
// a second label shows how i've been doing it so far
#property (strong, nonatomic) IBOutlet UILabel *contractorNameLabel;
#end
and the relevant essentials in my VC implementation file where i've used a private contractor property of the VC.
#import "MYEmployeeDetailViewController.h"
#interface MYEmployeeDetailViewController ()
#property (nonatomic, strong) NSString *contractor;
#end
#implementation MYEmployeeDetailViewController
// MYEmployee *employee; // use of an iVar has no effect
- (IBAction)getName:(UIButton *)sender {
// MYEmployee *employee = [[MYEmployee alloc] init]; // no effect except only within the method
// employee.firstName = self.inputField.text; //
self.employee.firstName = self.inputField.text; // class object picks up text from a textField
self.contractor = self.inputField.text; // self does exactly the same
}
- (IBAction)showName:(UIButton *)sender {
// MYEmployee *employee = [[MYEmployee alloc] init]; // resets contents of the iVar to nil
// self.employeeNameLabel.text = employee.firstName; //
self.employeeNameLabel.text = self.employee.firstName; // no joy using the property in .h file as suggested
self.contractorNameLabel.text = self.contractor; // this actually displays the text
}
#end
the employee is not to be seen while the contractor shows up for work (typical, we call them 'Blisters' they only show up when the work is done ;)
Back to serious stuff, the [alloc[init]] and iVar lines are commented out as they had no effect, but left in anyway for the sake of completeness. I didn't think I should edit the title, but my quandry is more or less the same in that "..why can't I use the model simply as a substitution of self?" After all, I outsourced the function
I might try to narrow the focus of the question somewhat; but I'll take a shot at what I think the issues are:
My View Controllers are holding my object properties and iVars etc.
They shouldn't hold any at all.
If you're following good MVC principles, then your view controllers should not have properties that would more correctly belong to the model layer. But given that view controllers are the conduit between the model and the view, certain exposed properties on the controller are essential.
Let's say you have a view controller that displays details about an employee - first name, last name, salary, department, etc. You'll probably want a model object that represents the employee. Let's call it MYEmployee. Next think about what other classes need to know about instances of MYEmployee. Those are the properties exposed in the class interface.
Now, how will our controller work with an instance of MYEmployee? Here, the view controller - let's call it MYEmployeeDetailViewController - needs to know what employee to display, so we need to expose the represented employee in the view controller's interface, e.g.
#property (nonatomic, strong) MYEmployee *employee;
Now MYEmployeeDetailViewController class users can pass an employee to display.
then how does one use Class Objects with the expectation i outlined?
I think your uncertainties revolve around questions like "who owns what data?" etc. My recommendation is to take a step back and really read the documentation on Apple's take on MVC and tap into some resources on object-oriented design. It always helps me to think first about interfaces (class interfaces, not UI) and less about implementation. Mapping out the relationships between classes before coding also helps.
You should be calling init on your new myClass object item instead of just alloc for starters. I doubt you really need to only allocate memory but not actually initialize the object.
Beyond that, your question is a bit hard to follow. You use objects to encapsulate data, and then you expose that data to other objects that might need it. Your view controllers will have objects that handle views, and objects that handle data. The controller will get what it needs from the data objects and pass it as needed to view objects. In this way, the controller will hold the data model itself (if you are doing a proper MVC design).
You say you want your class object to hold data between methods: well, that's what instance variables are for.
I notice that Apple has what seems to be duplicate variable names:
2 properties and two ivars. Why does Apple do this?
//.h file
#interface TypeSelectionViewController : UITableViewController {
#private
Recipe *recipe;
NSArray *recipeTypes;
}
#property (nonatomic, retain) Recipe *recipe;
#property (nonatomic, retain, readonly) NSArray *recipeTypes;
And they then update the recipe instance below. Why have two variable with the same name?
Will one affect the recipe variable of the parentViewController since that recipe variable was set when presenting this view controller the code was in from the parentViewController?
//.m file
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// If there was a previous selection, unset the accessory view for its cell.
NSManagedObject *currentType = recipe.type;
if (currentType != nil) {
NSInteger index = [recipeTypes indexOfObject:currentType];
NSIndexPath *selectionIndexPath = [NSIndexPath indexPathForRow:index inSection:0];
UITableViewCell *checkedCell = [tableView cellForRowAtIndexPath:selectionIndexPath];
checkedCell.accessoryType = UITableViewCellAccessoryNone;
}
// Set the checkmark accessory for the selected row.
[[tableView cellForRowAtIndexPath:indexPath] setAccessoryType:UITableViewCellAccessoryCheckmark];
// Update the type of the recipe instance
recipe.type = [recipeTypes objectAtIndex:indexPath.row];
// Deselect the row.
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
UPDATE 1
This code is from Apple's iPhoneCoreDataRecipes core data example:
First have a look at the RecipeViewController's didSelect delegate method, which will present the TypeSelectionViewController (child) view controller. Then have a look at that viewcontroller's didSelect delegate method where you will find the code implementation.
The reason I started looking at this is because I was interested how the parent's tableView cell got updated based on the selection in the ChildViewController in editing mode.
To see this for yourself, do the following:
Run the application
Select the Recipes tab
Click on a recipe - Chocolate Cake.
Click the edit button on the top right
Make note of the current category - should be on desert - then click on it.
Then you will be taken to the child view controller
Click on a different category, then click back and you will notice that the category button for that recipe has magically been updated. And I don't know how that's happening.
Does it have something to do with the private ivars and properties? which affects the parentViewController's cell?
My question i Guess is, how does selecting a category type in the child view controller's table affect the cell.text in the Parent View Controller's table? I can't see where the managedObjectcontext is saved in the child view controller for it to automatically update the parent View controller's cell text.
What you're seeing here is relatively old code, and there's not much need to do this anymore, thanks to Objective-C auto-synthesis.
Nowadays, when you issue a #property (nonatomic) NSArray *foo;, you implicitly get a #synthesize foo = _foo; in your implementation file and an instance variable declaration in your header. You don't see this, the compiler "inserts" it automatically. foo is the property and _foo is the instance variable. (In your above example, the #property and backing instance variable are both the same name, which could get confusing very quickly. With the foo property, you couldn't accidentally say self._foo, that doesn't exist. There's self.foo and _foo. With your example recipe is the ivar and self.recipe is the property. Very easy for one to quickly confuse the two when reading code.
Before the auto-synthesis, there was an intermediate step where you still needed a #synthesize, but you the backing instance variable was generated for you. These new features help you remove boilerplate code.
Answering Update 1
The code doing what you're wondering is in tableView:cellForRowAtIndexPath. There's nothing magical here. When you selected a new Category via the TypeSelectionViewController, the NSManagedObject is updated. Back in the RecipeDetailViewController, cellForRowAtIndexPath pulls the lasted information from CoreData. text = [recipe.type valueForKey:#"name"];
You might be getting confused about what an #property really is. It's just syntactic sugar. A #property these days automatically creates accessor and mutator methods and a backing ivar. Properties themselves aren't areas to store data, it's just a quick way of generating some methods and backing ivars.
Example
#interface MyClass
{
NSUInteger _foo;
}
#end
#implementation MyClass
- (NSUInteger)foo
{
return (_foo)
}
- (void)setFoo:(NSUInteger)newFoo
{
_foo = newFoo;
}
#end
is equivalent to:
#interface MyClass
#property (nonatomic, assign) NSUInteger foo;
#end
You save a lot of typing. When you get into things like NSString properties and different property modifiers like strong or copy, the amount of code you save (and memory management mistakes you avoid) in the mutators becomes much greater.
Your .h file should be your public api. You can re-declare your properties in your .m, implementation file, which are also considered private. For example
.h
#interface MyViewController : UITableViewController
#property (readonly) NSString *name;
#end
.m
#implementation MyViewController
#property (readwrite) NSString *name
#end
Here we are declaring a public name property that is readonly and in your implementation you 're re-declaring the property so that you can use the setter accessor.
I have a storyboard in which I have specified a parent view controller and two container views (made up of two UITableViewControllers). In my parent view controller I have buttons that are used to filter the content of the two tables.
My problem is figuring out how to send messages to the container views to perform these filters. I imagine we use delegates but is there a best practice way of implementing these delegates?
Subject to some caveats, you could define properties for each of the two contained tables, connect the outlets in your .xib, and message them directly in your button handlers.
For example:
#interface ParentViewController : UIViewController
#property (nonatomic) IBOutlet Table1Class *table1;
#property (nonatomic) IBOutlet Table2Class *table2;
#end
#implementation ParentViewController
...
- (IBAction)table1FilterButton:(UIButton *)sender
{
[self.table1 filterBy:...];
}
- (IBAction)table2FilterButton:(UIButton *)sender
{
[self.table2 filterBySomethingElse:...];
}
#end
Now, the caveats - you probably won't want to do this if you anticipate that the number of contained view controllers is likely to grow significantly, as it will be unwieldy to have table1, table2, table3, ..., tableN. You'll probably also want to find a way to extract a common interface (in the form of a protocol) from the two contained view controllers, in order to write less divergent code for handling the filtering of each table.
Maybe something like this, instead:
#protocol ContainedTableProtocol
#property (nonatomic) NSPredicate *contentFilterPredicate;
#property (nonatomic) NSComparator sortComparator;
#end
#interface ParentViewController : UIViewController
#property (nonatomic) IBOutlet UITableViewController<ContainedTableProtocol> *table1;
#property (nonatomic) IBOutlet UITableViewController<ContainedTableProtocol> *table2;
#end
#implementation ParentViewController
- (IBAction)filterTable1ButtonAction:(UIButton *)sender
{
[self filterTable:self.table1];
}
- (IBAction)filterTable2ButtonAction:(UIButton *)sender
{
[self filterTable:self.table2];
}
- (void)filterTable:(UITableViewController<ContainedTableProtocol> *)table
{
// Create predicate and comparator as needed...
NSPredicate *predicate = ... ;
NSComparator comparator = ... ;
table.contentFilterPredicate = predicate;
table.sortComparator = comparator;
}
#end
This uses a common interface to apply the filtering operations to each table view controller, and then codes to that interface rather than an API specific to a particular Table1Class or Table2Class.
You can see the answer at How do I create delegates in Objective-C?.
The simpler way is declare the delegate in the Childs and implement in the parent (ie: The childs send data to the parent).
I am a new in iOS Development and I canĀ“t move forwards... Is here somebody with experience, who can help me to solve my problem?
I use a Storyboard. First you see a ViewController with buttons. When you click on one of the button, informations about this button appear in a TableViewCell. - until now is it clear.
Than you click on a button "Continue" (than you come back to ViewController with buttons) and choose another button from ViewController.
HOW to do it, that informations from this button comes to a TableView as a second Cell? (so that when you click on for example three of the buttons, they appear as 3 cells under each other in this table?)
My Sample Code is here to download. - (on this website click on the smallest icon "Download").
Thank you very very much for your help!!!
Iva
You need a Model to store your selections/data.
For example: On MainViewController, when you click on button1, record that in the Model using array or just variable. Then, when you load SecondViewController, check the values in Model and load cells accordingly.
=> Example code for Model
// Header
#interface ModelData : NSObject
#property (nonatomic) NSMutableArray selectedProducts;
#property (nonatomic) NSInteger value1;
#property (nonatomic) NSInteger value2;
#property (nonatomic) NSInteger value3;
#end
// Implementation
#implementation ModelData
#synthesize selectedProducts = _selectedProducts;
#synthesize value1 = _value1;
#synthesize value2 = _value2;
#synthesize value3 = _value3;
#end
Various options:
Use NSUserDefaults to store information like settings
Use a Model class as described above - it could be a singleton class
Use CoreData to store and retrieve your data - complicated approach and suitable for dynamic things
Here is a reference to a good Data Model tutorial (reported by Iva)