Phone Call within Table Cell - ios

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

Related

Add new cell with expanded UITablviewcell

In my application, I have done Expanded table view cell inside that again I need to expand the cell.I Am facing the problem with again inserting expanded cell.Is it possible to do?
//cellForRowAtIndexPath:
[cell.expandButton addTarget:self action:#selector(expandAction:) forControlEvents:UIControlEventTouchUpInside];
if (indexPath.item < [array count] + 1) {
NSLog(#"Show one cell”);
}else{
NSLog(#"Show another cell")
}
//Button action
-(void) expandAction:(UIButton*)sender{
if (indexPath.item < [array count] + 1) {
NSLog(#"Show one cell”);
}else{
NSLog(#"Show another cell")
}
}
How do you generate your tableView data? If you use array of objects, then you could make it kind of like that:
CellItem class saves cell state
#interface CellItem : NSObject
#property BOOL isExpanded;
#end
Your custom cell class. Setup method customize your cell depending on CellItem object state
#interface ExpandableCell : UITableViewCell
- (void)setupWith:(CellItem *)cellItem;
#end
#implementation ExpandableCell
- (void)setupWith:(CellItem *)cellItem {
if(cellItem.isExpanded) {
//Expand your cell
} else {
//Don't exp and
}
}
#end
Array of objects where you get data for your tableView
#property NSArray<CellItem *> *items;
Protocol, which defines methods for cell state delegate. Implement this methods in class, which is also delegate of yourtableView. Call this method from cell everytime user expand / close it. In implementation save cellItem in items arrays.
#protocol ExpandableCellDelegate
- (void)cellDidChangeStateWithCellItem: (CellItem *)cellItem;
#end
UITableView cells are reusable, so everytime cell appears on a screen you should customize it again.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
CellItem *cellItem = self.items[indexPath.row];
ExpandableCell *cell = (ExpandableCell *)[tableView dequeueReusableCellWithIdentifier:#"InsertYourCEllID"];
[cell setupWith:cellItem];
return cell;
}

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;
}

I am using a singleton class for web services and I want to call cellforrowindexpath in UITableView after JSON data arrived

I am using singletone class for fetch json data and i want to show in tableview but when i am click view first time then data not showing, on second time it's shows and cell repeating data on every click.
- (void)viewDidLoad
{
[super viewDidLoad];
singCls = [SingletoneClass sharedInstanceMethod]; // Declared Class for instance method of singletone class
webserviceclas = [[WebserviceUtility alloc] init];
[webserviceclas getorchardslist];
}
#pragma mark - TableView Delegates
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return singCls.orcharsList.count; // Get json data of orchards list in array
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *orchardscell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(orchardscell == nil)
{
orchardscell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
orchardscell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
NSString *strcell = [singCls.orcharsList objectAtIndex:indexPath.row];
orchardscell.textLabel.text = strcell;
// Display Orchards list pass from json
return orchardscell;
}
Your tableview controller was not notified when actual data was fetched by WebserviceUtility class. I'm assuming that your WebserviceUtility class calls webservices and once the data is received in this class, the singleton class' orcharsList array will be updated. So if I'm correct till this point, then you need to notify the tableviewcontroller after updating orcharsList. And on receiving notification here, the tableView need to be reloaded and then your tableview delegates will be called.
What you need to do is:
Add protocol method in your WebserviceUtility class.
#protocol WebServiceDelegate
-(void)fetchedData:(WebserviceUtility*) self;
#end
Add a delegate property to which notifications need to be send
#interface WebserviceUtility {
id<WebServiceDelegate>delegate;
}
#property (nonatomic, assign) id <WebServiceDelegate> delegate;
Then notify the delegate once data is available
if ([delegate respondsToSelector:#selector(fetchedData:)]) {
[delegate fetchedData:self];
}
Implement those protocols in your tableviewcontroller class and add this class as delegate to WebserviceUtility
In .h file
#interface YourTableViewController: UITableViewController<WebServiceDelegate>
In .m file, assign your tableviewcontroller as delegate for WebserviceUtility
webserviceclas = [[WebserviceUtility alloc] init];
webserviceclas.delegate = self;
Implement the protocol method in your controller
-(void)fetchedData:(WebserviceUtility*) self{
//Reload your tableview here
}
Hope this helps.
I assume that you did not embed a callback that notifies your UITableViewController to reload data when your singleton finished loading data. Right now there is no data available when first presenting the tableView and it does not refresh automatically.

ReloadData for UITableView not working; tableView returns NULL when logged

I am calling a method in my TableViewController class from another class.
To call the method of displaying the tableview, I do this:
TableViewController *tableVC = [[TableViewController alloc]init];
[tableVC setTableViewContent];
then in TableViewController.h
#interface TableViewController : UITableViewController <UITableViewDelegate, UITableViewDataSource>
{
NSMutableArray *nameArray;
}
-(void)setTableViewContent;
#property (nonatomic, strong) IBOutlet UITableView *tableView;
#end
TableViewController.m
#implementation TableViewController
#synthesize tableView;
- (void)viewDidLoad
{
nameArray = [[NSMutableArray alloc]init];
[super viewDidLoad];
}
-(void)setTableViewContent{
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
for(int i=0;i< [appDelegate.businessArray count];i++)
{
NSDictionary *businessDict = [[appDelegate.businessArray objectAtIndex:i] valueForKey:#"location"];
nameArray = [appDelegate.businessArray valueForKey:#"name"];
}
NSLog(#"%#", nameArray);
NSLog(#"tableview: %#", tableView);
// here tableview returns null
[tableView reloadData];
}
- (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 [nameArray count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"updating tableview...");
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell =[tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
// Configure the cell...
cell.textLabel.text = [nameArray objectAtIndex:indexPath.row];
return cell;
}
For some reason when I try to log the tableview, it returns null, so the ReloadData doesn't work. The delegate and datasource is connected properly in IB, and there is a referencing outlet for tableView.
Any idea what is going on here? Thanks in advance
If you added the table view controller to a container view, then you can get a reference to that controller in prepareForSegue. For a controller in a container view, prepareForSegue will be called right before the parent controller's viewDidLoad, so you don't need to do anything to invoke it. In my example below, I've called the segue "TableEmbed" -- you need to give the segue that identifier in IB.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if([segue.identifier isEqualToString:#"TableEmbed"]) {
TableViewController *tableVC = (TableViewController *)segue.destinationViewController;
[tableVC setTableViewContent];
}
}
Be aware that prepareForSegue:sender: is called before either controller's viewDidLoad is called, so you should move the initialization of your array to setTableViewContent, and your reloadTable should go into viewDidLoad.
BTW, it's not clear to me why you want to call setTableContent from your other class anyway. Why not move all the code in that method to the viewDidLoad method of the table view controller?
This is happening because you are calling a method on tableView before it actually exists. Simply initializing that class doesn't draw the table itself, so using reloadData before the table has actually been created doesn't really make any sense.
What you want to do in this situation is create your nameArray in whatever class is calling setTableViewContent, and then pass it in either via a custom init method, or by setting tableVC.nameArray before loading that table view controller.
What I would do is make custom init method like - (id)initWithArray:(NSMutableArray *)nameArr
Which should look something like this:
if (self = [super init]) {
nameArray = [nameArr copy];
}
return self;
Then where you have TableViewController *tableVC = [[TableViewController alloc]init]; put TableViewController *tableVC = [[TableViewController alloc]initWithArray:theNameArray]; where theNameArray is the content in setTableViewContent (which you are now generating in the same class that calls the table view instead of in the table view itself).
Make sense?
I solved a similar situation by creating a "safe" reload method on the UITableViewController:
- (void)reloadTableViewData
{
if ([self isViewLoaded])
[self.tableView reloadData];
}
According to the docs for isViewLoaded:
Calling this method reports whether the view is loaded. Unlike the view property, it does not attempt to load the view if it is not already in memory.
Therefore it is safe to call reloadTableViewData on the table view controller at any time.

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