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

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.

Related

Prototype Cell has blank content on ios 7+

This one is driving me crazy - I don't know what am I missing.
here is my ViewController.
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[self.tableView registerClass:[CurrentMatchCell class] forCellReuseIdentifier:#"CurrentMatchCell"];
self.tableView.delegate = self;
self.tableView.dataSource = self;
}
#pragma mark -
#pragma mark Table View Data Source Methods
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
NSLog(#"1");
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSLog(#"2");
return 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"3");
CurrentMatchCell *cell = (CurrentMatchCell *)[tableView dequeueReusableCellWithIdentifier:#"CurrentMatchCell"];
if (cell == nil) {
NSLog(#"XXX");
}
[cell.matchDescription setText: #"Home Team vs Away Team"];
return cell;
}
Here is screenshots from the app.
delegate and datasource are set programmatically.
cell attributes :
And the .h file :
#interface CurrentMatchesViewController : UIViewController <NSFetchedResultsControllerDelegate,UITableViewDelegate, UITableViewDataSource>
#property (weak, nonatomic) IBOutlet UITableView *tableView;
So, I can see logs 1,2,3 being printed out, cell is not nill but I do not see my content. Why is that?
I only see a number of empty white cells (even if I return 0 or whatever it does show the same every time).
Thanks
If you create your table view and your cell prototypes in a storyboard, the storyboard loader takes care of registering the cell prototypes that you defined in the storyboard. So:
You don't need to call registerClass:forCellReuseIdentifier: again in the code. This will actually mess up your storyboard settings.
You can also use dequeueReusableCellWithIdentifier:forIndexPath: instead of dequeueReusableCellWithIdentifier:. That method always returns a cell, so you don't have to have a nil check.
Edit: If that doesn't do the trick, try calling [self.tableView reloadData] after setting the delegate / data source, or set the delegate and data source in the storyboard.
It is because you're not loading your custom nib. Try this. (Make sure CurrentMatchCell is the name of your xib file).
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
UINib *nib = [UINib nibWithNibName:#"CurrentMatchCell" bundle:nil];
[self.tableView registerNib:nib forCellReuseIdentifier:#"CurrentMatchCell"];
self.tableView.delegate = self;
self.tableView.dataSource = self;
}
Edit: based on your comment: Don't register the custom cell class when using a storyboard.... it does that for you and it also sets the delegate. So try removing those lines from the viewdidload.... and second I would try actually making CurrentMatchesViewController a subclass of UITableViewController

How do I add storyboard-based Header and CustomTableCell to a “Search Bar and Search Display Controller”

PRESENTATION
Mine is a simple project: It consists of a NavigationController, ViewController, and a “Search Bar and Search Display Controller”
My .h file is
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController <UISearchDisplayDelegate, UITableViewDataSource, UITableViewDelegate>
#end
and my .m file is
#import "ViewController.h"
#interface ViewController ()
#property(nonatomic,strong)NSMutableArray *data;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.searchDisplayController.displaysSearchBarInNavigationBar = YES;
self.data=[[NSMutableArray alloc]init];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)aTableView {
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)aTableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
return [_data count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"CellIdentifier";
// Dequeue or create a cell of the appropriate type.
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] ;
cell.accessoryType = UITableViewCellAccessoryNone;
}
// Configure the cell.
cell.textLabel.text = [NSString stringWithFormat:#"Row %d", indexPath.row];
cell.textLabel.text = [NSString stringWithFormat:#"Row %d: %#", indexPath.row, [_data objectAtIndex:indexPath.row]];
return cell;
}
#pragma mark - delegate
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
{
[NSTimer scheduledTimerWithTimeInterval:2 target:self selector:#selector(mockSearch:) userInfo:searchString repeats:NO];
return NO;
}
- (void)mockSearch:(NSTimer*)timer
{
[_data removeAllObjects];
int count = 1 + random() % 20;
for (int i = 0; i < count; i++) {
[_data addObject:timer.userInfo];
}
[self.searchDisplayController.searchResultsTableView reloadData];
}
#end
And that’s the entire program. What does it do? User makes a search and the data is displayed in a TableView (similar to a google search).
PROBLEM
I need to use a CustomTableViewCell for my table. And I need to build the TableViewCell from the storyboard (easy to visualize). I am stuck with the storyboard part. How do I place a TableViewCell on the storyboard without a TableView to place it in? I had an idea, I tried it, but it didn’t work. Here is what I did. I placed a “never-to-be-used” TableViewController in the storyboard whose sole purpose is to hold my CustomTableViewCell. Then in code I subclass TableViewCell and use IBOutlet to link the sub-views of the storyboard TableViewCell to my CustomTableViewCell . And then I used my cell as CustomTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];. This didn’t work. The tableView remained blank. And my guess for the failure is that CustomTableViewCell does not belong to the tableView being dequeued from.
I need the UISearchBar to always stay inside the NavigationBar. But so far self.searchDisplayController.displaysSearchBarInNavigationBar = YES; is not doing it. When I first start the app the searchBar is inside the NavigationBar. But as soon as I click on it, it smacks itself right in the middle of my scene/screen and never leaves.
I need to add a Header to my TableView. Which I thought of doing the same way as the CustomTableViewCell, but with a UIView parent class. But again, the CustomTableViewCell portion failed.
PLEA
Thank you for any help you can provide me.
UPDATE
All I am trying to do is allow my users to launch a server side search and view the results in a tableView. This is such a basic thing, I image many people here have done this a number of times. Being new to iOS, I am stuck. But at this point, I have posted my entire project (anyone can reconstruct it), and I have explained in details all the ways I tried to solve the problem. So if someone has a sample project they don’t mind sharing, it would help very much. Or if you know of a git project please put a link to it. Thanks.
If you want to make a stand-alone view (not in a view controller) in IB, then you should do it in a xib file, not a storyboard. You can have as many storyboard and xib files in an app as you want; they can be mixed freely. To make a new cell in a xib file, just go to New File --> User Interface --> Empty then drag in a UITableViewCell. Add any subviews you want, and in your table view controller (or whatever class is your table view data source), register the xib file with, registerNib:forIdentifier: (usually, you do this in viewDidLoad).

Passing data from a dynamic table to a static table

I'm a newbie in IOS,
I've been strugling with passing data from child to parent tableview. I have defined the parent table view as static, four cells are connected to other table views. These tableviews has data i would like when selected to be passed to my static cell accordingly. I read a lot of solutions about passing data, using delegates and segue but none of them seem to be working for me. i.e. a repeat cell in static table view has two labels and the UILabel Repeat, I don't want that to change and repeatDetail this is the one that when a disclosure indicator is triggered and a new tableview is presented with the data to choose to be able when i click back button to have the seleted data in my repeatDetail Label. My static table is embeded in Navigation controller using storyboard. I would like when data is selected in FirstChildViewController to modify selected data i.e. Monday to Mon in RootViewController. However in my code after selecing data in child checkmark is there but as soon
as I move back to RootVC nothing is showing,and when i go back to Child no selction is howing either.
1. Save the selected Data in Child, only change when there is new selection
2. Use short week names when sending to RootVC
3. repeatDetail to have the selected data
Without writting too much let me show what i have done.
in RootViewController.h // RootViewController is static
#import "FirstChildViewController"
#interface RootViewController: UITableViewController <repeatProtocol> //RootViewController COnfirms to the delegate
#property repeat, repeatDetail;
#end
next on my RootViewController.m
#implementaion RootViewController
#sysnthesis repeat,repeatDetail;
- (void) viewDidload
{
repeat.text = #"Repeat"
repeatDetail= //not show how call this label from 1stViewController
}
-(void) selectedValue:(NSString *)string //protocol method
{
FirstChildViewController *RVC =[[FirstChildViewController alloc] init];
RVC.delegate =self;
[self selectedValue:string]; //This part confuses me, i know i have to implement the delegate method but not sure if i implement it correctly.
}
-(void) didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
}
in FirstChildViewController.h
#class FirstChildViewController
#protocol repeatProtocol <NSObject>
#required
-(void) selectedValue:(NSString *)string;
#end
#interface FirstChildViewController: UITableViewController
{
NSArray *tableData;
id <repeatProtocol > repeatDelegate;
NSString *selectedDay;
}
#property (retain) id <repeatProtocol> repeatDelegate;
in FirstChildViewController.m
#synthesize tableData;
#synthesize repeatDelegate;
- (void) viewDidLoad
{
[super viewDidLoad]
tableData= [NSArray alloc] initWithArrays:#"Sunday",#"Monday",#"Tuesday",#"Wednesday",#"Thursday",#"Friday",#"Saturday";
}
- (int)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (int)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 7;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"RepeatCell"];
if(cell == nil){
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"RepeatCell"];
}
cell.textLabel.text = [tableData objectAtIndex:indexPath.row]//strings from an array here;
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = {tableView cellForRowAtIndexPath:indexPath];
cell.accessaryType = UITableViewCellAccessaryCheckMark;
[tableView deselectRowAtIndexPath:indexPath animated:YES];
if([self.delegate respondsToSelector:#selector(selectedValue:)])
{
[self.myDelegate selectedValue:selectedDay];
NSLog(#"string passed");
}
[self dismissViewControllerAnimated:YES completion:nil];
NSLog(#"FirstChildViewController dismissed");
}
#end
It's a little hard to sort out what's happening from your description; so I'll restate what I think the issue is.
You have a UITableView that displays something like settings that you which to modify in a series of distal view controllers. But you're unsure of what mechanism to use when returning that data to the static table view. Basically, you want to capture that data when the distal controller finishes. I won't deal with how you're displaying it in the root view controller, because it's unclear from your code sample.
Nonetheless, I'd favor not using a formal delegate protocol at all. It's just one datum going back - so a protocol seems like a wasted formality. I'd use a completion block.
So your FirstViewController interface could look like
typedef void(^WeekdayCompletionBlock)(NSString *dayName);
#interface FirstViewController : UIViewController
#property (nonatomic, strong) WeekdayCompletionBlock completionBlock;
#end
When you instantiate your FirstViewController, just provide it with a completion block. Since I think you are using Storyboards, you'd do this in prepareForSegue: method of your RootViewController.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
UIViewController *destinationController = segue.destinationViewController;
if( [destinationController isKindOfClass:[FirstViewController class]] ) {
[(FirstViewController *)destinationController setCompletionBlock:^(NSString *returnString){
// do something here with your string
// maybe you must reload your table
// it depends on where your returning data needs to display
}];
}
}
Finally, you need to execute that block when the user passes control back to the RootViewController. For example, is there a Save button or the like? There you would just execute the completion block, e.g. self.completionBlock(myNewDayOfWeekString)
Alternatively, you can create a global NSString in rootVC.h:
NSString *returnString;
Include rootVC.h in firstVC.h if you haven't already done that. This allows returnString to be accessible from firstVC.m:
#import "rootVC.h"
You can assign returnString in firstVC.m:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
...
returnString = <selected value>;
}
And show the returnString in rootVC.m below as NSBum pointed out:
-(void) viewDidload
{
repeat.text = #"Repeat";
repeatDetail.text = returnString;
}

UITextField inside UITableViewCell - method for preventing text reset

I have a UITableView tall enough that it necessitates scrolling. The top-most cell in the table contains a UITextField for the user to enter some text.
The standard way to build this might be to create and add the text field and add it to a cell created or recycled in cellFOrRowAtIndexPath: However, this constant re-creation means that the text entered in the field is erased when the cell is scrolled out and back into view.
The solutions I've found so far suggest using UITextField delegation to track the text as it changes and store it in an iVar or property. I would like to know why this is recommended instead of the simpler approach I am using:
I am creating the UITextField in the init method of the UITableViewController and immediately storing it in a property. In cellFOrROwAtIndexPath I am simply adding the pre-existing field instead of initializing a new one. The cell itself can be recycled without issue, but because I am always using the one and only UITextField, the content is maintained.
Is this a reasonable approach? What might go wrong? Any improvements (perhaps I could still create the field in cellForRowAtIndexPath but first check if the property is nil?)
When you are creating cells in cellForRowAtIndexPath you have to use one reusable identifier for that first cell (ie. cellId1) and another for the rest (ie. cellId2).
If you do this, when you get the cell for the first element by calling [tableView dequeueReusableCellWithIdentifier:#"cellId1"] you will always get the same Object and will not be reused by other cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MyCell *cell = nil;
// Only for first row
if (indexPath.row == 0) {
static NSString *cellId1 = #"cellId1";
cell = [tableView dequeueReusableCellWithIdentifier:cellId1];
if (cell == nil) {
cell = [[MyCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellId1];
}
}
else {
static NSString *cellId2 = #"cellId2";
cell = [tableView cellId2];
if (cell == nil) {
cell = [[MyCell alloc] initWithStyle:UITableViewCellStyleDefault cellId2];
}
}
// do whatever
return cell;
}
If there is only one UITextField, then I agree that your approach would be better/same as compared to using UITextField delegation (I think).
However, let us assume that you want to "expand" your view so that there are about 7-8 or more TextFields now. Then if you go about using your approach, then the problem will be that you will be storing 7-8 or more TextFields in memory and maintaining them.
In such a situation, a better approach would be that you create only that number of textfields as visible on screen. Then you create a dictionary which would maintain the content present in the textfield (which you can get by UITextFieldDelegate methods). This way, the same textfield can be used when the cell is reused. Only the values will change and will be dictated by the values in the dictionary.
On a sidenote, do minimal creation in cellForRowAtIndexPath as that is called during every table scroll and so creating a textField in cellForRowAtIndexPath can be expensive.
#import "ViewController.h"
#import "TxtFieldCell.h"
#define NUMBER_OF_ROWS 26
#interface ViewController ()<UITableViewDataSource, UITableViewDelegate, UITextFieldDelegate>
#property (weak, nonatomic) IBOutlet UITableView *tablView;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.tablView.datasource = self; //set textfield delegate in storyboard
textFieldValuesArray = [[NSMutableArray alloc] init];
for(int i=0; i<NUMBER_OF_ROWS; i++){
[textFieldValuesArray addObject:#""];
}
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
#pragma mark - TableView Datasource
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
TxtFieldCell *cell = [tableView dequeueReusableCellWithIdentifier:#"TxtFieldCellId" forIndexPath:indexPath];
cell.txtField.tag = indexPath.row;
if (textFieldValuesArray.count > 0) {
NSString *strText = [textFieldValuesArray objectAtIndex:indexPath.row];
cell.txtField.text = strText;
}
return cell;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return NUMBER_OF_ROWS;
}
#pragma mark - TextField Delegate
- (void)textFieldDidEndEditing:(UITextField *)textField {
[textFieldValuesArray replaceObjectAtIndex:textField.tag withObject:textField.text];
}

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

Resources