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.
Related
I have a custom UITableViewCell which contains two UIButtons (An upVote button and a downVote button), and a label that is meant to count the number of votes. I am using the Parse framework for my backend.
I cannot figure out how to associate the value of a particular label with the custom UITableViewCell that contains the label so that I can then save it to the Parse backend. How can I reference the indexPath of the cell that contains the button using Swift and then associate it with the label so that I can then save it to Parse?
As always, if you feel there is a better way, please share.
.
Everything can happen in your custom UITableViewCell. The key is to store the parse object as a property of the UITableViewCell in cellForRowAtIndexPath: so you never need to worry about looking up the indexPath. Hook your two UIButtons up and when a button is tapped: update the vote count on the parse object, update the label, save the parse object. Something like this should give you the idea:
#interface CustomTableViewCell
#property (nonatomic, strong) MyParseData *parseObject;
#property (nonatomic, strong) UILabel *voteCountLabel;
#end
#implementation CustomTableViewCell
- (IBAction)upVoteButton:(id)sender {
self.parseObject.voteCount++;
[self updateVote];
}
- (IBAction)downVoteButton:(id)sender {
self.parseObject.voteCount--;
[self updateVote];
}
- (void)updateVote {
self.voteCountLabel.text = [self.parseObject.voteCount description];
[self.parseObject saveInBackground];
}
#end
I think you created a custom cell to do this. So you have to create two methods in the custom cell class that will handle the touch up inside to its delegate (the tableview controller, for example). When delegate is called, it has a reference to the cell touched and you can go on.
I've got a ViewController that has a UITableView within it. When I'm watching tutorials people are using things like this:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return _Title.count;
}
How am I able to generate the stubs without firstly creating the class with them in. When I made the class I selected it as a UIViewController. I've been playing around trying to auto generate the stubs but all to no avail.
Simply add the UITableViewDataSource (and most likely the UITableViewDelegate) to your UIViewController declaration. Example:
// MyViewController.h
#interface MyViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>
// ...
#end
After that your implementation file MyViewcontroller.m should help you with the code completion.
One note: don't forget to set yourself as dataSource:
_tableview.dataSource = self;
If you added the tableview by code, you need to create a property (weak) in order to have a reference to your table view after adding it to your view controller's subview. If you add it by using interface builder, you need to create a iboutlet property that will allow you to "bind" your table view property with the xib/storyboard file representing your view controller. Alternatively, you can use UITableViewController as the parent class of your view controller. This class already has a property to access the table view in your view controller.
Tell your controller that you need to conform to the table view protocols and they will start to auto-complete when you try to type them in. You can check the docs of a protocol to find the available methods. Checking the UITableView docs would tell you about the relevant data source and delegate:
The data source must adopt the UITableViewDataSource protocol and the delegate must adopt the UITableViewDelegate protocol.
In your header file:
#interface MyViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>
You have a couple of options.
You could make your class inherit from UITableViewController instead of UIViewController. This will give you a tableView so you don't need to make one.
Or...
Your UIViewController could implement the protocols UITableViewDataSource and UITableViewDelegate. Then set the dataSource and delegate properties of your table view to self (your view controller containing the table).
-First of all you may need to add datasource and delegate of UITableViewController in your UIViewController header file
#interface MyViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>
and then implement the required and optional methods to populate the data in your _tableView.
Sample Code for TableView demonstration by Apple:
https://developer.apple.com/library/ios/samplecode/TableViewSuite/Introduction/Intro.html#//apple_ref/doc/uid/DTS40007318
For a while now I've had this dilemma on my mind. A cell in UITableView is essentially a view, thus the class for UITableViewCell should take care of view related things (i.e. presentation methods, layout and so on.) and have no business logic inside of it (usually taken care of the controller). But since we don't have a controller for each cell and only a controller for the whole table, I have trouble figuring out where to put my cell-wise logic. Putting it in the cell itself breaks MVC, but putting it in the table controller makes it hard to determine what cell the method is being called from (I prefer writing subclasses for my senders if the view is action based so I can add properties to help me determine what view this is).
For instance I have a cell, that cell has a UIButton inside of it, when the button is pushed a UIPopover appears. Now where do I put the popover presentation code (The presentation appears from one specific cell, therefore I must know which cell it's being called from.)
I'd like to know what other people do in this case and what are their best practices.
If you put the presentation of the popover inside the cell, then it's the best option. Why ?, because this is not logic, this is view related things and because the button who makes this action is inside your cell, then the code should be inside your cell (or you can send message(delegate) to your viewController to show that).
Then what is the logic ? The logic is for example: calculating, date operations, sending things to server. All these should be inside another object that we can call it module or manager.
The controller can exchange messages between all these objects (view - model), but the view and the module should be separated from each other.
Update:
You may want to take a look at Single Responsibility principle
Normally, it's to your View Controller to handle the "filling" logic for your cells. Cells are recipient that you fill each time.
It is even said in prepareForReuse: of UITableViewCell :
The table view's delegate in tableView:cellForRowAtIndexPath: should always reset all content when reusing a cell.
So indeed, your cells shouldn't hold any logic other than displaying.
If you need logic like button in your cell, you should set a delegate (you create one protocol) to your subclass of UITableViewCell and then hold in your UIViewController the cell logic.
If you cell is unique, I recommend you to define your cell as a static cell (no reuse identifier). And make a strong link to it.
You could subclass UITableView and UITableViewCell. Then, add delegate methods for the button. e.g. tableView:buttonWasPressedForCell: & buttonWasPressedForCell:. The tableView would conform to the cell's delegate and receive the message buttonWasPressedForCell:. Then, the tableView would send the message tableView:buttonWasPressedForCell: to it's delegate, in this case, your controller. This way you know which UITableView and which UITableViewCell the message was sent from.
Example:
ABCTableView.h
#protocol ABCTableViewDelegate <NSObject, UITableViewDelegate>
// You may not need this delegate method in a different UIViewController.
// So, lets set it to optional.
#optional
// Instead of passing the cell you could pass the index path.
- (void)tableView:(ABCTableView *)tableView buttonWasPressedForCell:(ABCTableViewCell *)cell;
#end
#interface ABCTableView : UITableView
// Declare the delegate as an IBOutlet to enable use with IB.
#property (weak, nonatomic) IBOutlet id<ABCTableViewDelegate> delegate;
#end
ABCTableView.m
#implementation ABCTableView
#dynamic delegate;
- (void)buttonWasPressedForCell:(ABCTableViewCell *)cell
{
// Check if the delegate responds to the selector since
// the method is optional.
if ([self.delegate respondsToSelector:#selector(tableView:buttonWasPressedForCell:)])
{
[self.delegate tableView:self buttonWasPressedForCell:cell];
}
}
#end
ABCTableViewCell.h
#protocol ABCTableViewCellDelegate;
#interface ABCTableViewCell : UITableViewCell
// Declare the delegate as an IBOutlet to enable use with IB.
#property (weak, nonatomic) IBOutlet id<ABCTableViewCellDelegate> delegate;
#end
#protocol ABCTableViewCellDelegate <NSObject>
// You may not need this delegate method in a different custom UITableView.
// So, lets set it to optional.
#optional
- (void)buttonWasPressedForCell:(ABCTableViewCell *)cell;
#end
ABCTableViewCell.m
#implementation ABCTableViewCell
- (IBAction)action:(id)sender
{
// Check if the delegate responds to the selector since
// the method is optional.
if ([self.delegate respondsToSelector:#selector(buttonWasPressedForCell:)])
{
[self.delegate buttonWasPressedForCell:self];
}
}
#end
Note:
When you dequeue the cell in tableView:cellForRowAtIndexPath: or add the cell using Interface Builder be sure to set the cell's delegate to the tableView.
E.g.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
ABCTableViewCell *cell = (ABCTableViewCell *)[tableView dequeueReusableCellWithIdentifier:#"Cell"];
cell.delegate = tableView;
return cell;
}
Usually for tasks like this I assign to cell my viewController as delegate (and define some protocol for it). Also, i keep weak reference to object from which I populate my cell, so on button's action I will forward to delegate (viewController) method like this:
- (void)actionOnCell:(UITableViewCell *)cell fromView:(UIView *)sender withItem:(id)sourceItem;
so in this way, I know where from show my popover, and what information (appropriate to sourceItem) show in it.
EDIT Also, if there multiple controls on cell to avoid duplication of pretty similar methods you can just add one parameter to function mentioned above, and define enum of all possible actions
Create an action handler and a data source for the cell. Have your data source conform to the data source protocol (View Model). Then there is no need for the cell to even know about the data model.
In the interface: TableViewCell
#property (nonatomic, weak) id <SomeTableViewCellActionHandler> actionHandler;
#protocol SomeTableViewCellActionHandler <NSObject>
- (void)cell:(SomeTableViewCell *)cell didReceiveStartButtonAction:(UIButton *)button;
- (void)cell:(SomeTableViewCell *)cell didReceivePauseButtonAction:(UIButton *)button;
- (void)cell:(SomeTableViewCell *)cell didReceiveClearButtonAction:(UIButton *)button;
#end
Implementation
- (void)prepareActionsForControls
{
[self.startButton addTarget:self action:#selector(handleStartButtonAction:) forControlEvents:UIControlEventTouchUpInside];
[self.pauseButton addTarget:self action:#selector(handlePauseButtonAction:) forControlEvents:UIControlEventTouchUpInside];
[self.clearButton addTarget:self action:#selector(handleClearButtonAction:) forControlEvents:UIControlEventTouchUpInside];
}
- (void)handleStartButtonAction:(id)sender
{
[self.actionHandler cell:self didReceiveStartButtonAction:sender];
}
- (void)handlePauseButtonAction:(id)sender
{
[self.actionHandler cell:self didReceivePauseButtonAction:sender];
}
- (void)handleClearButtonAction:(id)sender
{
[self.actionHandler cell:self didReceiveClearButtonAction:sender];
}
When you create your cell in the View Controller
create an action handler that conforms to the MyTableViewCellActionHandler protocol, pass the action handler the View Controller if it needs to do presentation.
cell.actionHandler = self.tableViewCellActionHandler;
You may also provide a datasource for your cell and pass in a View Model. (MVVM) This will allow you to keep only presentation code in the cell and keep all of your business logic where it belongs. Separation of concerns.
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've been searching all throughout the internet for assistance, however there has been little to no solutions to my issue at hand. My project that im trying to get a gasp on is somewhat unique (UI is not exactly following the typical norms).
Current Development Enviroment:
xcode 4
storyboards instead of nibs
Below is a diagram of what i am trying to accomplish -- all within a UIView Controller:
UIView is the light grey background
UITableView 1 - this is a static (or it can be dynamic, thats another challenge) UITableview which will hold different numeric
values for calculation
UITableView 2 - this is a UITableview which will hold calculated results every time it is run.
UIImageView 1 - this is a calculated image example (I have that figured out)
Im sure experienced developers are fully aware of my issue, and or what im about to ask. I understand that a static UITableView is required to be in a tableview controller, but I need to display both the UItableView's at the same time which means it has to be within a UIView.
I can make the interface look the way I need it to through the IB however when trying to compile and build I receive the error that requires the UITableView's to be within a UITableViewController and not a UIView Controller. I have seen many examples using a master-detail layout, but the only stipulation is that this UITableview NEEDS to be displayed 100% of the time when in this view.
So basically, I am asking for direction... but a code example never hurt either! Thank you 100x's over!
-Jonathan
UITableViewController is just a specialized UIViewController specially designed to display full screen UITableViews. It is (quite) equivalent to use an UITableViewController subclass or an UIViewController <UITableViewDataSource, UITableViewDelegate> subclass to manage a tableview.
So even if UITableViewController has some more spiecialized behaviors (automatically creates the UITableView if it does not exists, scrolls it automatically to display the keyboard, sets itself as the delegate and dataSource of the unique UITableView it manages, etc), you can use a standard UIViewController to manage a UITableView and be its dataSource to fill it.
That's even a way to manage a tableview that is not taking the full screen (as UITableViewController expects its view property to directly be the UITableView it manages, not a subview of its main view or whatever, and thus expects the UITableView to take the whole screen, contrary to using an UIViewController that has an UITableView as a custom-sized subclass of its view)
So in your case, you can have an UIViewController that has two IBOutlets, one for each tableView, and that unique UIViewController can be the dataSource (and delegate) of both the UITableViews. That's not a problem. Just be careful then in your datasource methods to distinguish if you are returning data for the first or the second UITableView to feed the correct tables each time.
#interface MyViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>
#property (nonatomic, retain) IBOutlet UITableView* masterTableView;
#property (nonatomic, retain) IBOutlet UITableView* detailsTableView;
#end
#implementation MyViewController
#synthesize masterTableView = _masterTableView;
#synthesize detailsTableView = _detailsTableView;
// Proper memory mgmt not shown here:
// - don't forget to set self.masterTableView and self.detailsTableView to nil in viewDidUnload
// - and to release _masterTableView and _detailsTableView in your dealloc method
-(UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
UITableViewCell* cell;
if (tableView == self.masterTableView)
{
static NSString* kMasterCellIdentifier = #"MasterCell";
cell = [tableView dequeueReusableCellWithIdentifier:kMasterCellIdentifier];
if (!cell)
{
cell = [[[UITableViewCell alloc] initWithReuseIdentiier:kMasterCellidentifier] autorelease];
// do some configuration common to all your master cells
}
// configure the rest of your cell for each property that is different from one cell to another
}
else if (tableView == self.detailsTableView)
{
// Do exactly the same principle, but for the cells of your "details" TableView
}
return cell;
}