Working on a small iOS app, and I made a custom tableview cell (and its own class) for my UITableView. Two of the things on the cell are "Add" and "Remove" button. When these are clicked, I need to update an array object that is in the UITableView, but because the table cell is a separate class, how can I send those changes over to the UITableView class?
For example, the code below is for my "remove" button on the cell:
- (IBAction)decrementItem:(id)sender {
int count = (int)[self.specificItemCount.text integerValue];
//Cannot have negative amount of food
if(count == 0) {
return;
} else {
count--;
self.specificItemCount.text = [NSString stringWithFormat:#"%i",count];
}
But I need to update my array object in my table view when that button is clicked, how can I do that?
Define a protocol on your cell class that your tableview class will implement. Here's an example: http://www.tutorialspoint.com/ios/ios_delegates.htm. Then set your tableview as a delegate on the cell (make sure to use a weak reference).
Use delegate or block.
If you only have one cell to operate, I recommend you use a block.
Define block, call when needed
// CustomCell.h
#interface CustomCell : UITableViewCell
#property (nonatomic, copy) void(^addBlock)(NSString *text);
#property (nonatomic, copy) void(^removeBlock)(NSString *text);
#end
// CustomCell.m
- (IBAction)decrementItem:(id)sender {
int count = (int)[self.specificItemCount.text integerValue];
//Cannot have negative amount of food
if(count == 0) {
return;
} else {
count--;
self.specificItemCount.text = [NSString stringWithFormat:#"%i",count];
}
if (removeBlock) {
removeBlock(self.specificItemCount.text)
}
}
Write the operation you need inside
// VC.m
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath == customIndexPath) {
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:customIdentifier forIndexPath:indexPath];
__weak typeof(self)weakSelf = self;
cell.removeBlock = ^(NSString *text) {
__strong typeof(weakSelf)strongSelf = weakSelf;
// operation
};
cell.addBlock = ^(NSString *text) {
__strong typeof(weakSelf)strongSelf = weakSelf;
// operation
};
return customCell;
}
// ······
}
Related
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;
}
I am trying to use use CollectionView custom cell, where I need to update the cell from the collection view cell custom class itself.
Here is the custom cell-class
Cell_Obj.h
#import <UIKit/UIKit.h>
#interface Cell_Obj : UICollectionViewCell
#property (weak, nonatomic) IBOutlet UILabel *label;
- (void)changeImage;
- (void)updateTextLabelName:(NSString*)str;
#end
Cell_Obj.m
#import "Cell_Obj.h"
static NSString *labelTxt ;
#implementation Cell_Obj{
}
+ (void)initialize {
if (self == [Cell_Obj class]) {
labelTxt = [[NSString alloc] init];
}
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
}
return self;
}
- (void)awakeFromNib {
_label.text = labelTxt;
[NSTimer scheduledTimerWithTimeInterval:2.0f
target:self
selector:#selector(updateLabel)
userInfo:nil
repeats:YES];
}
- (void)updateLabel
{
NSString * txt = [NSString stringWithFormat:#"%#",labelTxt];
_label.text = txt;
}
- (void)updateTextLabelName :(NSString*)str
{
labelTxt = str;
}
#end
Where in viewCotroller I am adding the cell like,
- (void)addCell
{
Cell_Obj *cell = [[Cell_Obj alloc] init];
NSString * txt = [NSString stringWithFormat:#"%d",[GridArray count]];
[cell updateTextLabelName: txt];
[GridArray addObject:cell];
[_collection insertItemsAtIndexPaths:#[[NSIndexPath indexPathForItem:[GridArray count]-1 inSection:0]]];
}
The problem with above code is when I add the first cell, the label of first cell is 0 and that's fine, but when I add the second cell and the timer call happens both cell1 and cell2 has label value 1. And it supposed to have 0,1 respectively.
And it seems like the cell object is sharing static variable upon on any update happens on already created cell, like a timer event.
Why this happening, is there any mistake in my approach?
Please let me know your valuable suggestion.
Edit
Based on below answer I have moved the static variable as instance variable,
#implementation Cell_Obj{
NSString *labelTxt ;
}
but inside updateLabel labelTxt is nil. Where when I debug updateTextLabelName called before updateLabel and the labelTxt has correct value.
This is because collectionview resues the cell to make it memory efficient. So evrery time it will call awakeFromNib when it deque the cell. So you should use collection view datasource methods to update or set content of collection view controls. you should implement cellForItemAtIndexPath to set data in your label!!
As it is an static variable, it's shared by all cell instances.
The way to make it work will be to remove static from labelTxt definition.
Also, what's the meaning of it being static? If it's due to the timer, just check in the timer method that label is not null before making the update and that will solve all your problems.
I am using a Storyboard's prototype cell with a custom table cell class and the UILabels are nil in cellForRowAtIndexPath. Xcode's Identifier is correct, the cell is initialized, and the default UILabels (i.e. textLabel and detailTextLabel) are initialized but not the custom UILabels I added and setup IBOutlets for. A couple things I have tried:
I tried removing the registerClass calls in ViewDidLoad but that crashes since the cells need it to be initialized as the custom class GenericDetailCell.
viewWithTag returns nil
I tried iterating through all the subviews of UITableView to see if I was getting the wrong cell. The correct cell is getting returned by dequeueReusableCellWithIdentifier
Has anyone run into this?
#implementation PeopleGroupPickerViewController
{
NSArray *people;
NSArray *searchResults;
}
- (void)viewDidLoad
{
[super viewDidLoad];
people = [[DAL sharedInstance] getPeople:false];
[self.tableView registerClass:[GenericDetailCell class] forCellReuseIdentifier:#"PersonCell"];
[self.searchDisplayController.searchResultsTableView registerClass:[GenericDetailCell class] forCellReuseIdentifier:#"PersonCell"];
// stackoverflow.com/questions/5474529
[self.searchDisplayController setActive:YES];
[self.searchDisplayController.searchBar becomeFirstResponder];
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
if(tableView == self.searchDisplayController.searchResultsTableView)
{
return 1;
}
else
{
return 1;
}
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if(tableView == self.searchDisplayController.searchResultsTableView)
{
return searchResults.count;
}
else
{
return people.count;
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
GenericDetailCell *cell = [tableView dequeueReusableCellWithIdentifier:#"PersonCell" forIndexPath:indexPath];
Person_ *thisPerson;
if(tableView == self.searchDisplayController.searchResultsTableView)
{
thisPerson = (Person_ *) searchResults[indexPath.row];
NSLog(#"searchResultsTableView, %#",thisPerson.sName);
}
else
{
thisPerson = (Person_ *) people[indexPath.row];
NSLog(#"UITableView, %#",thisPerson.sName);
}
Person_ *thisSpouse = [[DAL sharedInstance] getSpouse:thisPerson People:people];
// cell.fieldName and cell.fieldValue are nil, cell is not nil
cell.fieldName.text = thisPerson.sName;
cell.fieldValue.text = thisSpouse.sName;
return cell;
}
GenericDetailCell.h:
#interface GenericDetailCell : UITableViewCell
#property (nonatomic,weak) IBOutlet UILabel *fieldName;
#property (nonatomic,weak) IBOutlet UILabel *fieldValue;
#end
Seems to me you might be attempting to combine a UITableViewCell predefined style (e.g. UITableViewCellStyleSubtitle) with a custom cell.
In your storyboard, if you have selected a predefined style for your Prototype cell, other controls will not be recognised.
To remedy this problem, in the Attribute Inspector for your Prototype UITableViewCell, select "Custom" type. Then add into the Prototype cell all the controls you require, including those needed to replace the default controls previously added automatically into the predefined cell type.
UITableView data disappears on touch, as per screen shots below
The data loads correctly when the view is first loaded, like so.
On touching the screen and then releasing, the data disappears. (If I touch and hold, the data is still present.)
I set a breakpoint in the UITableView custom class and noticed that the methods within (such as cellForRowAtIndexPath:) are called when the view is loaded, but not after touch. The didSelectRowAtIndexPath: method is never called.
The code is very similar to the DateCell example. I'm trying to load a DatePicker (configured to show time only) when a cell is touched.
The relevant code is below, along with a screenshot of the IB delegate and datasource connections. Please let me know if you need any more info. I am new to iOS, so I would greatly appreciate as much detail of possible causes and solutions as possible.
#interface ScheduleTableViewController ()
#property (nonatomic, strong) NSArray *dataArray;
#property (nonatomic, strong) NSDateFormatter *timeFormatter;
#property (nonatomic, strong) NSIndexPath *timePickerIndexPath;
#property (assign) NSInteger pickerCellRowHeight;
#property (nonatomic, strong) IBOutlet UIDatePicker *pickerView;
#property (nonatomic, strong) IBOutlet UIBarButtonItem *doneButton; //to be used later for ios 6 compatability
#end
#implementation ScheduleTableViewController
- (void)viewDidLoad
{
[super viewDidLoad];
NSMutableDictionary *itemOne = [[#{ kPeriodKey : #" Tap a cell to change the survey time: " } mutableCopy ] autorelease];
NSMutableDictionary *itemTwo = [[#{ kPeriodKey : #"Morning Survey",
kTimeKey : [NSDate date] } mutableCopy] autorelease];
NSMutableDictionary *itemThree = [[#{ kPeriodKey : #"Evening Survey",
kTimeKey : [NSDate date] } mutableCopy] autorelease];
self.dataArray = #[itemOne, itemTwo, itemThree];
self.timeFormatter = [[[NSDateFormatter alloc] init] autorelease];
[self.timeFormatter setDateFormat:#"h:mm a"];
[self.timeFormatter setDateStyle:NSDateFormatterNoStyle];
[self.timeFormatter setTimeStyle:NSDateFormatterShortStyle];
UITableViewCell *pickerViewCellToCheck = [self.tableView dequeueReusableCellWithIdentifier:kTimePickerID];
self.pickerCellRowHeight = pickerViewCellToCheck.frame.size.height;
[self.tableView setDelegate:self];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
if (cell.reuseIdentifier == kDayPeriodAndTimeCellID) {
// todo check for ios < 7.0
[self displayInlineTimePickerForRowAtIndexPath:indexPath];
} else {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = nil;
NSString *cellID = kDayPeriodAndTimeCellID;
if ([self indexPathHasPicker:indexPath]) {
cellID = kTimePickerID;
}
cell = [tableView dequeueReusableCellWithIdentifier:cellID forIndexPath:indexPath];
if (indexPath.row == 0) {
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
NSInteger modelRow = indexPath.row;
if (self.timePickerIndexPath != nil && self.timePickerIndexPath.row < indexPath.row) {
modelRow--;
}
NSDictionary *itemData = self.dataArray[modelRow];
if ([cellID isEqualToString:kDayPeriodAndTimeCellID]) {
cell.textLabel.text = [itemData valueForKey:kPeriodKey];
cell.detailTextLabel.text = [self.timeFormatter stringFromDate:[itemData valueForKey:kPeriodKey]];
}
return cell;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if ([self hasInlineTimePicker]) {
NSInteger numRows = self.dataArray.count;
return ++numRows;
}
return self.dataArray.count;
}
I am adding to an existing application, so all of the existing UI is implemented in XIB files, however this table is implemented in a storyboard. Here is a screen shot of the delegate and datasource outlet connections.
I think you need to provide a little more context here, but I have a guess. A UITableView needs a UITableViewDataSource to tell it what data should be in the cells. Do you hook that up in the XIB? If so, I don't see any of the methods implemented in your ScheduleTableViewController.
Make sure you implement that protocol and specifically the method "tableView:cellForRowAtIndexPath:". This is where you actually configure the cells that appear in the table view.
It sounds like you created the prototype table cells in InterfaceBuilder. For each prototype cell you created, make sure you set it's cell identifier in the properties inspector. Then use this cell identifier to identify which cell you are configuring in the call to "tableView:cellForRowAtIndexPath:".
Have you tried setting your table data with the functions
numberOfSectionsInTableView:tableView
tableView:tableView numberOfRowsInSection:section
I had the similar issue (in swift). It was solved by putting an instance variable to my tableViewManager responsible for presenting tableView. Initially an instance of it was created and called from button action method. So it was deallocated by ARC and consequently tableView had disappeared when I was trying to interact with it.
Inspired by: iOS TableViewController in PopOver disappears after touching tableview
In my case I was using SwipeMenuViewController (https://github.com/yysskk/SwipeMenuViewController)
and in each page there was a table view.
When I click the tabs it's switch the tables with no problem. but when I touched the table the data disappeared although
the table exists (I used background colours to check)
My solution was to hold a reference to the UIViewControllers return from the SwipeMenuViewController method:
func swipeMenuView(_ swipeMenuView: SwipeMenuView, viewControllerForPageAt index: Int) -> UIViewController {
viewControllers[index.description] = baseContactsTableViewController
return baseContactsTableViewController
}
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