I'm wondering what is the best approach/design when using custom UITableViewCells.
One way I'm doing it is having a init that takes all parameters necessary to setup the UITableViewCell. Like -initWithLabel:groups:error
Then I would init it like CustomUITableViewCell *cell = [CustomUITableViewCell alloc] initWithLabel:#"Hello" groups:#[#"1", #"2", #"3"] error:NO];
Then perhaps later if I would need to update the error of the CustomUITableViewCell to YES I would just reload the cell for CustomUITableViewCell and supply YES in the init.
Now I'm wondering if it would be "better" to have a setter for the cell, like - (void)setError:(BOOL)error that I can set whenever I need to update the error of the cell instead of reloading the whole cell.
What would be pros/cons comparing the two options?
Your first choice doesn't seems to be a good approach. If you are creating cell each time it just fine. But actually happening is table view will create only 10-15 cells(depends) and later they will be reused. So initialization is no going to perform unless you call it externally(that is not good).
So the good approach in custom cells are use the properties and expose all the things that is going to change in cellForRow of your datasource object. Make your labels, error... as properties if it is accessible to the outer class. So you can easily change any values.
When you have a member variable that can change, then for sure it should use a setter (and be a property), so you don't have to replace the class each time. The source-code will be clearer, and the program will be more efficient. You might also include it in your init, if you think it's a good idea to encourage clients to explicitly set that variable when they make a new object.
Related
I'm trying to set up a view-based table in Swift using bindings. All of the examples I've seen use a datasource/delegate setup.
I have an array of Flag objects which has two properties - flagName: String and flagImage: NSImage. I have an NSArrayController managing this array.
If I set up a cell-based table, and bind one column to arrangedObjects.flagImage and the other to arrangedObjects.flagName, I get a table displaying images and names, and I can use the array controller's add and remove methods, so there are no problems with my datasource or my array controller.
I have been following the instructions in Apple's TableView Programming Guide to bind my view-based table to my array controller:
tableView Content binding: FlagController.arrangedObjects
textField Value binding: TableCellView.objectValue.flagName
imageView Value binding: TableCellView.objectValue.flagImage
(IBs autocomplete is not happy with the paths for objectValue.flagName respectively flagImage; it doesn't feel that there should be any completion whatsoever and says it can't resolve the path, so it looks as if the problem is with the tableView's content.)
If I do this, my table has a number of rows that corresponds to the number of elements that my array controller is managing at that moment (I have two simple setups, one object vs. 50 objects, so it's clear that something is bound). What I don't get is a display; and selecting a table row does not seem to send back a message to my flagController.
What am I missing? Has anyone been able to make this work? I've had no problems with other bindings in Swift so far, but I'm starting to think that the sudden reappearance of datasource examples is not unrelated to this.
It sounds like you've failed to implement the delegate method -tableView:viewForTableColumn:row:. That's a common cause of blank tables.
You don't actually have to implement that if you make sure the identifier of the table column and the identifier of the table cell view are the same in IB.
Otherwise, the method can just do this:
- (NSView*) tableView:(NSTableView*)tableView viewForTableColumn:(NSTableColumn*)tableColumn row:(NSInteger)row
{
return [tableView makeViewWithIdentifier:#"TheIdentifierOfYourTableCellView" owner:self];
}
(It could also do more, if desired.)
Edit 2: What I previously planned was probably a bad idea and I now changed my design: My UITableViewController has an array with all the values of my UITextFields and I am using delegation to update the values in the array. (If a value in one UITableViewCell changes, I send a message with the new value and the index of the cell).
Original Question
I would like to create a UITableViewCell subclass. To access my cells, I would like to have an NSMutableArray in my UITableViewController with all the cells. Whenever I create a new cell in - tableView:cellForRowAtIndexPath: I would add it to the array. The cells should however know about this array. I would declare a property like this for the UITableViewCell:
#property (nonatomic, assign) NSMutableArray *cellsArray;
Whenever I create a new cell, I would set its cellsArray to my array.
My (probably simple) question is: Is it correct that cellsArray will hold a pointer to the array in the UITableViewController and when I add stuff to the array of the UITableViewController, the cells will know this too, i.e. can access it?
Edit: The UITableViewCells contain UITextFields. I used to rely on the -cellForRowAtIndexPath: method and the visibleCells array, however when the cells moved out of view, the content of their UITextFields would also be lost. I then decided to store the cells in an array. When the user taps save, I iterate through the array and store the values. Also, I would like to automatically update the enabled property of the save button, depending on whether all cells contain something - for this I need all cells, too.
The cells should know about the other cells so that they can select the next cell when the return/next key on the keyboard is pressed.
If there are better approaches to this, I am glad to hear about them!
Not a direct answer of your question, but this sounds like a very bad design. Why should one cell need to know about its siblings? Any event/change that occurs in one cell and has an effect on the other cells should be handled by the table view controller. The single cells should be separate entities that should have no need to know about the state of each other.
Secondly, there is no need to introduce another array to manage the table cells. The table view already has a property visibleCells that you can access from the table view controller. And should never have to interact with invisible cells anyway because those are managed by the table view and its reuse facility.
I believe the answer is Yes.
My understanding of assign is that you can assign a value to such a variable and the retain count for the original object is not incremented. Similarly you need not release the variable in the object's dealloc method. You may run the risk, however, that the original array goes away and then cellsArray is pointing at something that is no longer there.
I use assign when I want to pass a reference to an object to another object (e.g. a view controller that is going to display or otherwise manipulate the object). And in the latter object, I do not release it's pointer to the object.
You also see assign used with properties that are id's, like
#property (nonatomic, assign) id<SomeProtocol> _myDelegate;
All that being said, with the exception of the id case, often I feel "safer" using retain for the property and being sure to release in dealloc. :-)
Anyway, I think that's the crux of the difference.
Question - Is it necessary to have subview (e.g. UILabel) instance variables in a UITableViewCell subclass?
The alternative I am thinking of being to construct say the UILabels you want for your custom UITableViewCell subclass when you create it, assign them to the content view (e.g. [self.contentView addSubview:label_1]), and then release the UILabel (e.g. [label_1 release]).
So is it the case the only reason you need to keep the labels as instance variables (declared in the header) of the UITableViewCell subclass, so that you can grab them more easily to configure/make changes to them later. That is as opposed to having to find through by looking them up directly in the contentView via their tag values?
thanks
it is a convenience, but one worth sticking with. if you call viewWithTag everytime something needs to be changed/redrawn it can be less efficient than just using the pointer stored in the stack, as this would have to be recalculated each time.
If I am using a custom UITableViewCell I can use the following methods to change the cell's appearance when transitioning state:
- (void)willTransitionToState:(UITableViewCellStateMask)state
- (void)didTransitionToState:(UITableViewCellStateMask)state
Is there a way to achieve this if I'm not using a custom tableview cell?
Edit: Please see Daniel Hanly's comment. Categories may be selectively applied using #import. My apologies to anyone that may have been misled by this answer. Relevant sections will be redacted for future reference.
Okay, attempt number two. As far as I am aware, there is no other documented way to implement the functionality you require without subclassing UITableViewCell. It's worth noting that Apple's docs on UITableViewCell specifically mention that the state transition methods are meant to be implemented by subclasses. That having been said, If you absolutely need to implement them without a subclass, there are a couple of less conventional solutions. Each comes with its own issues, and it may end up being unfeasible for you to implement them, but it's an interesting question nonetheless.
Disclaimer
If you only want a sane and relatively simple explanation, then consider the answer to your question to be "no, there is no way to do what you want." I only present the options below with the assertion that they will work. In no way do I endorse actually using them. Consider this my penance for providing my first answer with such an obvious flaw.
Option One - Categories
It is possible to get the functionality you're looking for by overriding the methods you listed in a custom UITableViewCell category.
The problem is that this approach would be a pretty bad idea 99% of the time. Once you define the category on UITableViewCell, those methods would be defined for all UITableViewCell objects throughout the app. Unless you want the exact same state transition functionality for every single table cell in the app, this approach isn't very helpful.
Option Two - Runtime magic
You can use the low-level Objective-C runtime functions to change the implementation of any method on the fly. Unlike the categories option, this approach is flexible enough to redefine the intended behavior whenever you need to, instead of being a one-shot deal.
For example, if you're trying to manage state transitions from a UITableViewController, you could do this:
CustomTableViewController.m
#import <objc/runtime.h>
- (void) customStateWillChange:(UITableViewCellStateMask)state
{
//custom UITableViewCell code
}
- (void) viewDidAppear:(BOOL)animated
{
//Store the original implementation
Method originalStateWillChangeMethod = class_getInstanceMethod([UITableViewCell class], #selector(willTransitionToState:));
originalStateWillChangeImplementation = method_getImplementation(originalStateWillChangeMethod); //variable declared in header file as type IMP
//Get the new implementation
Method newStateWillChangeMethod = class_getInstanceMethod([self class], #selector(customStateWillChange:));
IMP newStateWillChangeImplementation = method_getImplementation(newStateWillChangeMethod);
//Replace implementation
method_setImplementation(originalStateWillChangeMethod, newStateWillChangeImplementation);
//the rest of your viewDidAppear code
[super viewDidAppear:animated];
}
- (void) viewDidDisappear:(BOOL)animated
{
//restore the original implementation
Method originalStateWillChangeMethod = class_getInstanceMethod([UITableViewCell class], #selector(willTransitionToState:));
method_setImplementation(originalStateWillChangeMethod, originalStateWillChangeImplementation);
//rest of viewDidDisappear code
[super viewDidDisappear:animated];
}
This code may not suit your exact purposes, but I think it provides a useful example.
It's incredibly ugly though because the customStateWillChange: method defined here is only intended to be run as a part of the UITableViewCell class, but in this example it will be compiled as though it were part of the CustomTableController class. Among other annoyances, you would have to eschew the property dot notation, ignore compiler warnings and give up most if not all compile-time checks for that method's body.
Option 3 - Category with runtime magic
Exactly what it sounds like. Define any custom state change methods you like within a category (or several categories) on UITableViewCell. Be sure that each one has a separate name - adding two categories that each have a method of the same name will result in undefined behavior. Also, each one needs to have the same return type and argument types as the method it is intended to replace.
Then the references to [self class] in the above code would be replaced with [UITableViewCell class], and the customStateWillChange: method would be moved to the custom category. While still ugly, you can at least rely on the compiler to interpret the method bodies properly.
Of course, messing with the runtime adds a whole lot of complexity to keep track of. It could work fine, but it's not good design, it would take serious effort to ensure it worked safely and correctly, and it would be likely to bring anguish and despair to anyone maintaining it.
References
The Objective-C Programming Language - Categories and Extensions
Objective-C Runtime Reference
Absolutely. The UITableViewDelegate protocol specifies a number of methods to manage state transitions for the table view's cells. Take a look at the UITableViewDelegate Class Reference, specifically the methods listed under the heading "Editing Table Rows".
Edit
Sorry, you're right. The UITableViewDelegate methods don't respond to direct changes to the cell's properties. I've found a way that does work, but I'm going to put it in a different answer to avoid confusion.
From the official documentation:
The reuse identifier is associated with a UITableViewCell object that the table-view’s delegate creates with the intent to reuse it as the basis (for performance reasons) for multiple rows of a table view. It is assigned to the cell object in initWithFrame:reuseIdentifier: and cannot be changed thereafter. A UITableView object maintains a queue (or list) of the currently reusable cells, each with its own reuse identifier, and makes them available to the delegate in the dequeueReusableCellWithIdentifier: method.
http://developer.apple.com/iphone/library/documentation/UIKit/Reference/UITableViewCell_Class/Reference/Reference.html#//apple_ref/occ/instp/UITableViewCell/reuseIdentifier
I don't understand this. Well, I understand the basic idea, I think, that you create UITableViewCells, and try to reuse as many as you can instead of making new ones (or something like that). But what exactly decides whether or not a cell is reusable? If I've got two identical (visually) cells, but with different texts (well I suppose they aren't entirely identical), can they both have the same identifier? Or should they have different ones? Or in what situation are you supposed to use different identifiers?
Can anyone clarify or link to a place where it is?
Ok, this is how I believe it works:
Using dequeueReusableCellWithIdentifier for the tableView, you can greatly speed things up. Instead of instantiating a lot of cells, you just instantiate as many as needed, i.e. as many that are visible (this is handled automatically). If scrolling to an area in the list where there are "cells" that haven't got their visual representation yet, instead of instantiating new ones, you reuse already existing ones.
You can try this yourself by doing this:
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
NSLog(#"new one");
}
else
{
NSLog(#"old one");
}
Remember, you only want dequeueReusableCellWithIdentifier to return a cell if it is applicable. So if a cell is going to be reused, make sure it is correct for the situation. That's what reuseIdentifiers are for. Usually, you will only need one. But there might be a list that uses several different kinds of cells, and in that case, you'd have to keep them separate by providing different reuseIdentifiers. Otherwise you might end up getting a cell that you treat as some other kind of cell (for example, UITableView cell instead of the custom one you wanted).
So basically, as I understand it, use different reuseIdentifiers for different kinds of cells, where kind means class. If you only use standard cells, you probably only need one reuseIdentifier.
This design pattern is known as object pooling.
Just to add some things to quano's otherwise very good answer: (I tried to add this as a comment, but it was too long!)
Even reuse identifiers can be omitted in developing, although this must be done in very specific circumstances. If you have a table view of 6-7 cells, and each one is different, you may find that creating a new cell with nil as the identifier may be preferable.
Having a reusable cell means that in each time the cellForRowAtIndexPath is called, you must check the cell, initialize it if there is no reusable cell, and outside of the init scope you must explicitly iterate through all possible indexpaths and set the values for each label explicitly depending on what kind of cell you have! So, in a table view with 10 dinstinct cells, you will have to take care of creating the cell if nil, and filling it up depending on what you created.
Therefore, in this case, it's preferable in terms of code maintenance to initialize each cell with nil identifier (since it's not going to be reused anyway) and fill each cell's info appropriately without worrying about reusing it.
UITableView is like having a cell pool for each reuseIdentifier, so that it recycle the cell
I like this video from http://oleb.net/blog/2014/05/scrollviews-inside-scrollviews/
http://im.ezgif.com/tmp/ezgif-3302899694.gif