Collapse a table row when a button is tapped - ios

I have a table view that has 3 rows in it. When one of the rows is tapped, the row height expands to show a UICollectionView with 3 cells in it.
I want the row to reduce height again when one of the collection view cells is tapped. This is what I've attempted:
//customCell.m
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
ProductViewController *productView = [[ProductViewController alloc]init];
[productView collapseRow];
}
//ProductViewController.m
#property NSIndexPath *selectedRow;
- (void)collapseRow {
menuCell *cell = (menuCell *)[self.tableView cellForRowAtIndexPath:self.selectedRow];
[self.tableView beginUpdates];
self.selectedRow = nil;
[self.tableView endUpdates];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView beginUpdates];
if ([indexPath compare:self.selectedRow] == NSOrderedSame) {
self.selectedRow = nil;
} else {
self.selectedRow = indexPath;
}
[tableView endUpdates];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath; {
if ([indexPath compare:self.selectedRow] == NSOrderedSame)
return 105;
else
return 50;
}
Am i doing anything wrong here? I know that the table view is updating when I tap a collection view cell, but it's not checking against the currently open row and setting the height using that.

The things we have a ViewController(ProductViewController), containing an instance of UITableView, the cells of table view are created in CustomCell class.
1) Since ProductViewController has the instance of UITableView, so using that instance ProductViewController can call methods on UITableView and it's cells.
2) Also, while creating table view we set the delegate and dataSource of tableview as
tableView.delegate = self;
tableView.dataSource = self;
self is nothing but the reference of the class in which table view is being created(in your case it's ProductViewController)
you implement the tableView's delegate and dataSource method in the ProductViewController class
and what the table view does, if it needs to call a method on your class ProductViewController' instance it uses the reference contained in it's delegate and dataSource properties, which is nothing but the reference of ProductViewController. So calling the method using delegate/dataSource by table view will execute the methods in ProductViewController(if the method exists there).
3) Now as you have created a class called CustomCell, using which you create the custom cells and add on the table view, the table can call methods on the cell's instance but if you need the other way round(cell calling the method of table view or instance of ProductViewController, you can't do so since the cell does not know the reference/address of the ProductViewController), so to call a method on the instance of ProductViewController which contains the table view and cell, the cell should know the address of the ProductViewController. And to provide the cell with the reference/ address of ProductViewController we use the concept of delegation as used by UITableView in 2 point point above.
You can use the below delegation pattern implementation to meet the requirement of point 3-
In CustomCell.h class declare a delegate as
#protocol CustomCellDelegate;
#interface CustomCell : UITableViewCell {
//declare member variables
...
id<CustomCellDelegate> cellDelegate;
}
...
#property (nonatomic, assign) id<CustomCellDelegate> cellDelegate;
...
// method declarations
#end
#protocol CustomCellDelegate <NSObject>
- (void)collapseRow;
#end
in CustomCell.m
#import "CustomCell.h"
#implementation CustomCell
#synthesize cellDelegate = _ cellDelegate;
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
}
return self;
}
// methods to create cells
// methods to create collection view and it's items
// collection view item selection handler
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
// using delegate will call the method collapseRow in ProductViewController
[self. cellDelegate collapseRow];
}
#end
Also, adopt the delegate in ProductViewController.h as
#import "CustomCell.h"
#interface ProductViewController:UIViewController < CustomCellDelegate >
and in in ProductViewController.m
when you are creating the cell as
CustomCell *cell = [[[CustomCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier] autorelease];
pass the reference of ProductViewController instance as
cell.cellDelegate = self;

You can also use
- (void)collapseRow
{
NSArray* indexArray = [NSArray arrayWithObjects:self.selectedRow, nil];
self.selectedRow = nil;
[self.tableView reloadRowsAtIndexPaths:indexArray withRowAnimation:UITableViewRowAnimationNone];
}

I think the actual problem for you lies in this method
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
ProductViewController *productView = [[ProductViewController alloc]init];
[productView collapseRow];
}
In the above method you are creating a new instance of ProductViewController using ProductViewController *productView = [[ProductViewController alloc]init]; and this will call the collapseRow, but as it's not on the instance of the previous ProductViewController class so the row of the table contained in the previous instance will not get collapsed.
In above method you should use delegation pattern, when you are creating the cells for the collection view than during cell creation just pass the reference of ProductViewController instance(using delegation) and when any collection view cell is selected, just use the same delegate(which contains the reference of ProductViewController instance) to call the collapseRow method on the correct class rather than allocating the new one.

This should work:
- (void)collapseRow {
NSIndexPath *selectedRow = self.selectedRow;
[self.tableView beginUpdates];
self.selectedRow = nil;
[self.tableView reloadRowsAtIndexPaths:#[selectedRow] withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates];
}
Or you could simply:
- (void)collapseRow {
self.selectedRow = nil;
[self.tableview reloadData];
}
but that would cause all the cells to be reloaded, so it's not as efficient.

Related

How do I get the tableView by the custom cell?

How do I get tableView by custom cell in the CustomCell?
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:#"CustomCell" forIndexPath:indexPath];
return cell;
}
#implementation CustomCell
- (void)awakeFromNib {
[super awakeFromNib];
// How do I get tableView by custom cell in the CustomCell?
}
#end
To answer the question, Apple does not provide a public API for that and you would have to do it using what is known about view hierarchies.
A tableViewCell would always be part of a tableView. Technically, a tableViewCell would always be in a tableView's view hierarchy or a tableViewCell would always have some superview out there that is a tableView. Here is a method similar to this one:
- (UITableView *)getParentTableView:(UITableViewCell *)cell {
UIView *parent = cell.superview;
while ( ![parent isKindOfClass:[UITableView class]] && parent.superview){
parent = parent.superview;
}
if ([parent isKindOfClass:[UITableView class]]){
UITableView *tableView = (UITableView *) parent;
return tableView;
} else {
// This should not be reached unless really you do bad practice (like creating the cell with [[UITableView alloc] init])
// This means that the cell is not part of a tableView's view hierarchy
// #throw NSInternalInconsistencyException
return nil;
}
}
More generally, Apple did not provide such public API for a reason. It is indeed best practice for the cell to use other mechanisms to avoid querying the tableView, like using properties that can be configured at runtime by the user of the class in tableView:cellForRowAtIndexPath:.

Need assistance understanding custom uitableviewcell initialization

While implementing custom UITableViewCell in tableView:cellForRowAtIndexPath: whats the difference between these two ways
loadNibNamed:owner:options:
and
SimpleTableCell *cell = [[SimpleTableCell alloc]init];
does loadNibNamed:owner:options: also alloc init? if not how SimpleTableCell will work without alloc init?
SimpleTableCell *cell = (SimpleTableCell *)[tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"SimpleTableCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
there is no explicit call of SimpleTableCell *cell = [[SimpleTableCell alloc]init];
OK, first off. I'm not actually going to answer the question at all. Instead I'll tell you how to create and use a custom UITableViewCell subclass. What you're doing at the moment isn't right.
Let's stick to the name SimpleTableCell that you have used.
Create the sub class
Create a subclass of UITableViewCell.
SimpleTableCell.h
#interface SimpleTableCell : UITableViewCell
// if coding only
#property (nonatomic, strong) UILabel *simpleLabel
// if from nib
#property (nonatomic, weak) IBOutlet UILabel *simpleLabel;
#end
SimpleTableCell.m
#import "SimpleTableCell.h"
#implementation SimpleTableCell
// if coding only
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
//create the simpleLabel and add to self.contentView
}
return self;
}
// if from nib no need to do anything at all
// other stuff...
- (void)prepareForReuse
{
// empty the cell here.
// means you don't have to empty everything out in the controller
self.simpleLabel.text = #"";
}
#end
OK, so now we have a cell class.
Create the NIB if that's what you want
It looks like you're doing this already.
Create the nib with the same name (or not, doesn't really matter).
Make the top level item a UITableViewCell and set the subclass to SimpleTableCell. Now connect the outlets. In this example simpleLabel is all there is to connect.
Register the subclass with the table view
In the view controller that owns the table view. This means the table view can deal with creating and dequeueing the cells and you don't have to manually create them at all.
- (void)viewDidLoad
{
[super viewDidLoad];
// set up the other stuff...
// if coding only
[self.tableView registerClass:[SimpleTableCell class] forCellReuseIdentifier:#"SimpleCell"];
// if from a nib
UINib *cellNib = [UINib nibWithNibName:#"SimpleTableCell" bundle:[NSBundle mainBundle]];
[self.tableView registerNib:cellNib forCellReuseIdentifier:#"SimpleCell"];
}
Now you just let the table deal with creating the cells. It keeps track of the reuse identifiers and cell queues so it can handle everything as normal. You just need to ask it to dequeue a cell for you with the identifier you registered you subclass for.
- (UITableViewCell*)tableView:(UITableView *)tableView cellFroRowAtIndexPath:(NSIndexPath *)indexPath
{
// This API was introduced in iOS6 and will ALWAYS return a valid cell.
// However, you need to register the class or nib with the table first.
// This is what we did in viewDidLoad.
// If you use a storyboard or nib to create a tableview and cell then this works too.
SimpleTableCell *mySimpleCell = [tableView dequeueReusableCellWithIdentifier:#"SimpleCell" forIndexPath:indexPath];
mySimpleCell.simpleLabel.text = #"Hello, World";
return mySimpleCell;
}
EDIT
You can create a cell (indeed any class) using...
SimpleTableCell *cell = [[SimpleTableCell alloc] init];
But doing it this way means it isn't associated to a table view or part of a queue.
One step down (if you like) from the method in my answer is to use the old dequeuReusableCell... method and then to check if it's nil and create it like this...
- (UITableViewCell*)tableView:(UITableView *)tableView cellFroRowAtIndexPath:(NSIndexPath *)indexPath
{
// Old way, don't do this if you're targeting iOS6.0+
SimpleTableCell *mySimpleCell = [tableView dequeueReusableCellWithIdentifier:#"SimpleCell"];
if (!mySimpleCell) {
// have to use initWithStyle:reuseIdentifier: for the tableView to be able to dequeue
mySimpleCell = [[SimpleTableCell alloc] initWithStyle:UITableViewCellStyleCustom reuseIdentifier:#"SimpleCell"];
}
mySimpleCell.simpleLabel.text = #"Hello, World";
return mySimpleCell;
}
I'm not even sure you can load from a nib in here as you wouldn't be able to set the reuse identifier on the cell.
Multiple cell subclasses
OK, last edit :D
For multiple UITableViewCell subclasses you can use this too. I have done exactly this in the past. You might, for instance, have a cell for a Post item a cell for an Image item a cell for a Comment item etc... and they are all different.
So...
- (void)viewDidLoad
{
// the rest
// register each subclass with a different identifier
[self.tableView registerClass:[PostCell class] forCellReuseIdentifier:#"PostCell"];
[self.tableView registerClass:[ImageCell class] forCellReuseIdentifier:#"ImageCell"];
[self.tableView registerClass:[CommentCell class] forCellReuseIdentifier:#"CommentCell"];
}
To help keep this small (and it comes in handy for NSFetchedResultsControllers too, I move the configuration of the cell out to another method.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell;
if (the required cell is a post cell) {
cell = [tableView dequeueReusableCellWithIdentifier:#"PostCell" forIndexPath:indexPath];
[self configurePostCell:(PostCell *)cell atIndexPath:indexPath];
} else if (the required cell is a image cell) {
cell = [tableView dequeueReusableCellWithIdentifier:#"ImageCell" forIndexPath:indexPath];
[self configureImageCell:(ImageCell *)cell atIndexPath:indexPath];
} else if (the required cell is a comment cell) {
cell = [tableView dequeueReusableCellWithIdentifier:#"CommentCell" forIndexPath:indexPath];
[self configureCommentCell:(CommentCell *)cell atIndexPath:indexPath];
}
return cell;
}
- (void)configurePostCell:(PostCell *)postCell atIndexPath:(NSIndexPath *)indexPath
{
// get the object to be displayed...
postCell.postLabel = #"This is the post text";
postCell.dateLabel = #"5 minutes ago";
}
- (void)configureImageCell:(ImageCell *)imageCell atIndexPath:(NSIndexPath *)indexPath
{
// get the object to be displayed...
imageCell.theImageView.image = //the image
imageCell.dateLabel = #"5 minutes ago";
}
- (void)configureCommentCell:(CommentCell *)commentCell atIndexPath:(NSIndexPath *)indexPath
{
// you get the picture...
}
loadNibNmed:... is asking the system to recreate an object (usually, but not limited to a UIView) from a canned instance in a nib file. The nib file is created using the interface builder portion of Xcode.
When an object is loaded from a nib file (loadNibNamed...) init doesn't get called, instead initWithCoder: gets called. If you're looking to do post initialization setup on a view loaded from a nib file, the usual method is to awakeFromNib and call [super awakeFromNib]

UITableViewController over-ride init function not being called when using storyboard

1) I started a new project in xcode using the single view application.
2) I deleted the default view controller and added a new UITableViewController
3) In storyboard, I dragged out a UITableViewController and set it to the one I just created
4) Set the reuse identifier
In my code I tried to override the init method to do some setup. Why is my custom init method not being called? When you are using storyboard, and you drag out a UITableViewController and set it to a custom class, can you not override the initWithStyle: method? When I put the setup in viewDidLoad then it worked.
Here is the code for the view controller:
#import "ItemsViewController.h"
#import "BNRItem.h"
#import "BNRItemStore.h"
#implementation ItemsViewController
- (id)init
{
// Call the superclass's designated initializer
self = [super initWithStyle:UITableViewStyleGrouped];
if (self) {
for (int i = 0; i < 10; i++) {
[[BNRItemStore defaultStore] createItem];
NSLog(#"Test init");
}
}
return self;
}
- (id)initWithStyle:(UITableViewStyle)style
{
NSLog(#"test init style");
return [self init];
}
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section
{
NSLog(#"test tableview rowsinsection");
return [[[BNRItemStore defaultStore] allItems] count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"test tableview cellforrow");
// Create an instance of UITableViewCell, with default appearance
// Check for a reusable cell first, use that if it exists
UITableViewCell *cell =
[tableView dequeueReusableCellWithIdentifier:#"itemsCell"];
// If there is no reusable cell of this type, create a new one
if (!cell) {
cell = [[UITableViewCell alloc]
initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:#"itemsCell"];
}
// Set the text on the cell with the description of the item
// that is at the nth index of items, where n = row this cell
// will appear in on the tableview
[[cell textLabel] setText:#"Hello"];
return cell;
}
#end
init is only called when you using [[class alloc] init]
you can override
- (void)awakeFromNib
awakeFromNib
Prepares the receiver for service after it has been loaded from an Interface Builder archive, or nib file.
- (void)awakeFromNib
Discussion
An awakeFromNib message is sent to each object loaded from the archive, but only if it can respond to the message, and only after all the objects in the archive have been loaded and initialized. When an object receives an awakeFromNib message, it is guaranteed to have all its outlet instance variables set.

Phone Call within Table Cell

Im writing an app where someone adds a contact to the app, giving their name, number and photo. Then this information is displayed in a table, with each individual contact on a different cell and when the user presses on the cell it will call the number that was typed in for the contact. I have put in a large button on each of the cells for the user to press. This is the code
PictureListMainTable.m
#import "PictureListMainTable.h"
#import "PictureListDetail.h"
#import "CoreDataHelper.h"
#import "Pictures.h"
#implementation PictureListMainTable
#synthesize managedObjectContext, pictureListData, callButton;
// When the view reappears, read new data for table
- (void)viewWillAppear:(BOOL)animated
{
// Repopulate the array with new table data
[self readDataForTable];
}
// Grab data for table - this will be used whenever the list appears or reappears after an add/edit
- (void)readDataForTable
{
// Grab the data
pictureListData = [CoreDataHelper getObjectsForEntity:#"Pictures" withSortKey:#"title" andSortAscending:YES andContext:managedObjectContext];
// Force table refresh
[self.tableView reloadData];
}
#pragma mark - Actions
// Button to log out of app (dismiss the modal view!)
- (IBAction)logoutButtonPressed:(id)sender
{
[self dismissModalViewControllerAnimated:YES];
}
#pragma mark - Segue methods
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// Get a reference to our detail view
PictureListDetail *pld = (PictureListDetail *)[segue destinationViewController];
// Pass the managed object context to the destination view controller
pld.managedObjectContext = managedObjectContext;
// If we are editing a picture we need to pass some stuff, so check the segue title first
if ([[segue identifier] isEqualToString:#"EditPicture"])
{
// Get the row we selected to view
NSInteger selectedIndex = [[self.tableView indexPathForSelectedRow] row];
// Pass the picture object from the table that we want to view
pld.currentPicture = [pictureListData objectAtIndex:selectedIndex];
}
}
#pragma mark - Table view data source
// Return the number of sections in the table
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
// Return the number of rows in the section (the amount of items in our array)
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [pictureListData count];
}
// Create / reuse a table cell and configure it for display
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
// Get the core data object we need to use to populate this table cell
Pictures *currentCell = [pictureListData objectAtIndex:indexPath.row];
// Fill in the cell contents
cell.textLabel.text = [currentCell title];
cell.detailTextLabel.text = [currentCell desc];
int number;
number = [currentCell desc];
-(IBAction)MakePhoneCall:(id)sender {
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:#"tel:",number]];
}
// If a picture exists then use it
if ([currentCell smallPicture])
{
cell.imageView.contentMode = UIViewContentModeScaleAspectFit;
cell.imageView.image = [UIImage imageWithData:[currentCell smallPicture]];
}
else{
}
return cell;
}
// Swipe to delete has been used. Remove the table item
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete)
{
// Get a reference to the table item in our data array
Pictures *itemToDelete = [self.pictureListData objectAtIndex:indexPath.row];
// Delete the item in Core Data
[self.managedObjectContext deleteObject:itemToDelete];
// Remove the item from our array
[pictureListData removeObjectAtIndex:indexPath.row];
// Commit the deletion in core data
NSError *error;
if (![self.managedObjectContext save:&error])
NSLog(#"Failed to delete picture item with error: %#", [error domain]);
// Delete the row from the data source
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
}
-(IBAction)MakePhoneCall:(id)sender {
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:#"tel:",number]];
}
#end
PictureListMainTable.h
#import <UIKit/UIKit.h>
#interface PictureListMainTable : UITableViewController
#property (strong, nonatomic) NSManagedObjectContext *managedObjectContext;
#property (strong, nonatomic) NSMutableArray *pictureListData;
#property (nonatomic, retain) IBOutlet UIButton *callButton;
-(IBAction)MakePhoneCall:(id)sender;
- (void)readDataForTable;
#end
Where should I place the IBaction and why isint it working at the moment where it is and how can I make it work?
There are a couple of approaches you could take to achieve this. But firstly, I don't understand what you are doing at the bottom of -tableview:cellForRowAtIndexPath:. It's as if you are trying to define your IBAction method inside this method. You also have it defined at the bottom of the implementation, but in that method the number variable is not in scope.
Anyway, you should subclass the UITableViewCell. In the implementation for the subclass, you should define the IBAction method and hook it up in interface builder, or otherwise.
When the button is tapped, you should hand the number for the selected cell back to the PictureListMainTable view controller, in order for that view controller to process it (i.e. call the number). You can do this in two ways:
1) the delegate method
Create a protocol, defined in the header file for your subclass of UITableViewCell. And make the main view controller conform to this protocol. Set the cell's delegate to the main view controller. In the implementation of the cell subclass, call this delegate method. For example:
the header file for the UITableViewCell subclass "PictureListMainTableCell.h"
#protocol PictureListMainTableCellDelegate;
#interface PictureListMainTableCell : UITableViewCell
#property (nonatomic, copy) NSString *telephoneNumber;
#property (nonatomic, weak) id<PictureListMainTableCellDelegate> delegate;
#end
#protocol PictureListMainTableCellDelegate
-(void)pictureListMainTableCell:(PictureListMainTableCell *)cell wantsToCallNumber:(NSString *)number;
#end
the implementation file "PictureListMainTableCell.m"
#import "PictureListMainTableCell.h"
#implementation PictureListMainTableCell
-(IBAction)MakePhoneCall:(id)sender
{
//send the delegate the number to call.
[self.delegate pictureListMainTableCell:self wantsToCallNumber:self.telephoneNumber];
}
#end
Above, in the MakePhoneCall method, we call -pictureListMainTableCell:wantsToCallNumber: on the delegate. In this case, the delegate is your main view controller. We will set this below.
Setting the cell's delegate: In your main view controller file (PictureListMainTable.m), in the -tableView:cellForRowAtIndexPath: method, set the delegate on the cell to self. e.g.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// get the cell...
PictureListMainTableCell *cell = // dequeue the cell
// do some other setting up...
// set the delegate on the cell
cell.delegate = self;
// set the telephoneNumber variable on the cell, for example...
cell.telephoneNumber = [currentCell desc];
return cell;
}
Now you need to make sure self implements the delegate method. So still in PictureListMainTable.m, you need to define the method as follows:
#pragma mark - PictureListMainTableCellDelegate methods
-(void)pictureListMainTableCell:(PictureListMainTableCell *)cell wantsToCallNumber:(NSString *)number
{
NSString *urlString = [NSString stringWithFormat:#"tel://%#", number];
NSLog(#"calling telephone number [%#]", number);
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:urlString]];
}
You should also specify that the PictureListMainTable class conforms to your new protocol, as well as the UITableViewDataSource protocol. Add a private category on PictureListMainTable as follows (at the top of the implementation file, after the imports, before #implementation):
#interface PictureListMainTable () <UITableViewDataSource, PictureListMainTableCellDelegate>
#end
(this extends the PictureListMainTable interface. It only extends it to specify privately that it conforms to these protocols.)
2) the NSNotification method
While I was typing out the above explanation, I decided it's my preferred way of doing things, so I would recommend doing it like that. There is the option of posting an NSNotification form your cell subclass, and observing for this notification from your main view controller. Just look into NSNotificationCenter, the following methods:
–postNotificationName:object:userInfo: (send the number in userInfo dictionary). Listen for it using –addObserver:selector:name:object:.
But like I said, option 1 is better, in my opinion.
Let me know if anything is unclear, good luck :)
EDIT: I really recommend reading this blog post to understand delegation: http://alexefish.com/post/15966868557/understanding-and-creating-delegates-in-objective-c

Why can't I initialize a UITextField from a subclass of UITableViewCell?

I've created a subclass of UITableViewCell for an iPad app. I need to dynamically generate text fields, take input from the user, and then store that information in an array. I thought of asking the UITableViewCell for the UITextField.text object, which would hold whatever the user wrote before my View Controller's segue (I'm saving the NSString objects upon the segue being called). So I've got an array of UITableViewCells which I ask for the UITextField.text object. But for some reason while my UITableViewCell subclass is being created, my UITextField is not. I can call UITableViewSubclass and it's initialized, but UITableViewSubclass.UITextField is nil.
Here's my UITableViewCell Subclass header (Yes, the UITextField is connected in the storyboard):
#import <UIKit/UIKit.h>
#interface ConditionCell : UITableViewCell
#property (strong, nonatomic) IBOutlet UITextField *condition;
#end
Here's my implementation file:
#import "ConditionCell.h"
#implementation ConditionCell
#synthesize condition;
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
self.condition = (UITextField *)[self viewWithTag:10];
}
return self;
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
#end
This here is the Table View Controller handling the table that contains the cells:
.h file:
#import <UIKit/UIKit.h>
#import "ConditionCell.h"
#interface ConditionsTableViewController : UITableViewController
#property (strong, nonatomic) NSMutableArray *conditionCellArray;
- (void)addNewConditionCell;
#end
.m file:
#import "ConditionsTableViewController.h"
#interface ConditionsTableViewController ()
#end
#implementation ConditionsTableViewController
#synthesize conditionCellArray = _conditionCellArray;
- (NSMutableArray *)conditionCellArray
{
if (_conditionCellArray == nil) {
// Create the array object
_conditionCellArray = [[NSMutableArray alloc] init];
}
return _conditionCellArray;
}
- (void)addNewConditionCell
{
ConditionCell *condCell = [[ConditionCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"conditionCell"];
[self.conditionCellArray addObject:condCell];
[self.tableView beginUpdates];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:self.conditionCellArray.count-1 inSection:0];
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationTop];
[self.tableView endUpdates];
[self.tableView reloadData];
}
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Uncomment the following line to preserve selection between presentations.
// self.clearsSelectionOnViewWillAppear = NO;
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return self.conditionCellArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"conditionCell";
ConditionCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[ConditionCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
// Configure the cell...
//cell.condition = (UITextField *)[cell viewWithTag:1];
return cell;
}
// Override to support conditional editing of the table view.
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
// Return NO if you do not want the specified item to be editable.
return YES;
}
// Override to support editing the table view.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the row from the data source
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
else if (editingStyle == UITableViewCellEditingStyleInsert) {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
/*
// Override to support rearranging the table view.
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath
{
}
*/
/*
// Override to support conditional rearranging of the table view.
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
{
// Return NO if you do not want the item to be re-orderable.
return YES;
}
*/
#pragma mark - Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Navigation logic may go here. Create and push another view controller.
/*
<#DetailViewController#> *detailViewController = [[<#DetailViewController#> alloc] initWithNibName:#"<#Nib name#>" bundle:nil];
// ...
// Pass the selected object to the new view controller.
[self.navigationController pushViewController:detailViewController animated:YES];
*/
}
#end
This Table View Controller lives inside a UIView Controller as the table view does not take up the whole screen. When the user presses an 'ok' button there is a segue that is triggered and it is here that I ask this Table View Controller for the array containing the UITableViewCells, which I then run through a foreach to get their .text properties. Unfortunately I can't seem to get anything I input into the text fields, hence the .text's are always nil. If anyone could help me with this issue it would be greatly appreciated!
You might find this much easier to do using the free Sensible TableView framework. The framework has these text field cells out of the box, and can even create them automatically from your array.
I figured out a better way to do what I wanted to do here that works. Turns out that the way iOS's UITableView works is totally different from what I wanted to do. UITableView works by looking at your storyboard and given the identifiers for the cells, it creates them and allows you to set their properties within the cellForRowAtIndexPath method. However, when the cell goes offscreen, it is not retained as it's own separate object; it is reused. So, you can think of it as if when you scroll a table view, the cells that disappear to one end reappear on the other end with new information. This is key - UITableView want YOU to provide the cell's information. It was not made for input of information directly on a UITableViewCell, which is what I wanted to do.
So what I ended up doing was copy-pasting my cells into their own .xib file, and in the subclass initWithStyle:reuseIdentifier method, do:
NSArray *nibArray = [[NSBundle mainBundle] loadNibNamed:#"ConditionCell" owner:self options:nil];
self = [nibArray objectAtIndex:0];
And that creates the cell with whatever style - setup - UI elements you want.
Next, I want to hold on to a reference to the cell, because that cell has a textbox, and I need to save what's on the textbox when the user presses a "done" button. However, testing revealed the reuse problem I explained above. So how to do this? In my Table's view controller, whenever the user wants to add a new textbox (and presses the button to do so) I have a method which does
[self.conditionCellArray insertObject:[[ConditionCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"conditionCell"] atIndex:0];
This adds a new cell to an array - this is important because I need to have a reference to ALL cells at all times. (It is adding the cell at index 0 because I want to insert it at the top). Then, in the cellForRowAtIndexPath method, I did
return [self.conditionCellArray objectAtIndex:indexPath.row];
Which will return the corresponding cell. Bear in mind, from what I have read this whole thing about keeping a reference to each and every cell in the table is contrary to Apple's stated best practices when using UITableView. However, as I said before, UITableView is meant to display information, not to gather it from user input. So this is why I had to break the rules, if you will, to achieve the desired effect (that I wanted). I hope this helps others who are looking to do the same thing; and if there is a better way don't be shy about telling me.
EDIT: Oh by the way, when you copy paste the cells created in storyboard to their own .xib file make sure to disconnect any IBOutlets and change their class back to UITableViewCell. That way there won't be any problems or conflicts when you connect your .xib file cell.

Resources