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.
Related
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;
}
// ······
}
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
}
1) I started a new project in xcode using the single view application.
2) I deleted the default view controller and added a new UITableViewController
3) In storyboard, I dragged out a UITableViewController and set it to the one I just created
4) Set the reuse identifier
In my code I tried to override the init method to do some setup. Why is my custom init method not being called? When you are using storyboard, and you drag out a UITableViewController and set it to a custom class, can you not override the initWithStyle: method? When I put the setup in viewDidLoad then it worked.
Here is the code for the view controller:
#import "ItemsViewController.h"
#import "BNRItem.h"
#import "BNRItemStore.h"
#implementation ItemsViewController
- (id)init
{
// Call the superclass's designated initializer
self = [super initWithStyle:UITableViewStyleGrouped];
if (self) {
for (int i = 0; i < 10; i++) {
[[BNRItemStore defaultStore] createItem];
NSLog(#"Test init");
}
}
return self;
}
- (id)initWithStyle:(UITableViewStyle)style
{
NSLog(#"test init style");
return [self init];
}
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section
{
NSLog(#"test tableview rowsinsection");
return [[[BNRItemStore defaultStore] allItems] count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"test tableview cellforrow");
// Create an instance of UITableViewCell, with default appearance
// Check for a reusable cell first, use that if it exists
UITableViewCell *cell =
[tableView dequeueReusableCellWithIdentifier:#"itemsCell"];
// If there is no reusable cell of this type, create a new one
if (!cell) {
cell = [[UITableViewCell alloc]
initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:#"itemsCell"];
}
// Set the text on the cell with the description of the item
// that is at the nth index of items, where n = row this cell
// will appear in on the tableview
[[cell textLabel] setText:#"Hello"];
return cell;
}
#end
init is only called when you using [[class alloc] init]
you can override
- (void)awakeFromNib
awakeFromNib
Prepares the receiver for service after it has been loaded from an Interface Builder archive, or nib file.
- (void)awakeFromNib
Discussion
An awakeFromNib message is sent to each object loaded from the archive, but only if it can respond to the message, and only after all the objects in the archive have been loaded and initialized. When an object receives an awakeFromNib message, it is guaranteed to have all its outlet instance variables set.
In my tableview, I have several different custom cells. In one of them, it has a button. This button brings up another view controller. However, It is not needed until the tableview has fully loaded. In cellForRowAtIndexPath I set up all my different custom cells. I can uncomment [buttonCell.myButton setHidden:YES]; and it will hide my button. See below.
else if (indexPath.section == 3)
{
ButtonCell *buttonCell = [tableView dequeueReusableCellWithIdentifier:#"ButtonCell"];
//[buttonCell.myButton setHidden:YES];
cell = buttonCell;
}
return cell;
However, I want to then unhide the button after the tableview loads. I finish loading all my arrays in another method where I call reloadData. In that method, I tried to unhide the button by doing this..
[ButtonCell.myButton setHidden:NO];
But the compiler gives me a warning that property myButton is not found in ButtonCell. Does anyone have any ideas how to go about unhiding my button. What am I doing wrong, and what do I not get! Thanks for all your help.
EDIT 1
My button cell class is...
.h
#import
#interface ButtonCell : UITableViewCell
#property (strong, nonatomic) IBOutlet UIButton *myButton;
- (IBAction)YDI:(id)sender;
#end
.m
#import "ButtonCell.h"
#import "AnotherWebViewController.h"
#implementation ButtonCell
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
}
return self;
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
- (IBAction)YDI:(id)sender
{
}
#end
EDIT 2
With everyone's help that answered (thank you all) I have gotten a bit further, but the button is not showing itself. So I still hide the button in cellForRowAtIndexPath, that works as should. Then in my method that I reload the data in I put the following code.
NSIndexPath *index = [NSIndexPath indexPathForRow:0 inSection:3];
ButtonCell *buttonCell = (ButtonCell *) [self.tableView cellForRowAtIndexPath:index];
[buttonCell.myButton setHidden:NO];
The ButtonCell with the button is always the fourth section (counting the first as 0) and it only has one row. Any other help would be appreciated. Almost there!
EDIT 3
Got it! However, it was due to a comment that I was able to figure it out. Thanks to #A-Live. Although I do know how to get the cell in a method outside of cellForRowAtIndexPath thanks to ElJay. So I am giving him the check since I learned something new which is why we post questions anyway. So inside my method cellForRowAtIndexPath is where I hide/show the button. I have a BOOL in my App called finished, it is originally set to true. When the table view ends loading it is set to false. So I just used this bool to show/hide the button.
else if (indexPath.section == 3)
{
ButtonCell *buttonCell = [tableView dequeueReusableCellWithIdentifier:#"ButtonCell"];
if (!_finished)
{
[buttonCell.myButton setHidden:YES];
}else{
[buttonCell.myButton setHidden:NO];
}
cell = buttonCell;
}
return cell;
Once again this is only part of my cellForRowAtIndexPath method. Thanks once again for all the help. I was surprised to see so many answers! Thanks.
Make the property publicaly accessible.
#property (nonatomic, retain) UIButton *myButton;
Then in cellForRowAtIndexpath
ButtonCell *buttonCell =(ButtonCell *) [tableView dequeueReusableCellWithIdentifier:#"ButtonCell"];
myButton belongs to a cell. You will need to get an instance of that UITableViewCell and then you can unhide it, this assumes you want to modify the cell's objects outside of cellForRowAtIndexPsth or willDisplayCell.
In your code
[ButtonCell.myButton setHidden:NO];
You are trying to use the object class name instead of the object name. You need to get the cell that contains your button
buttonCell = [tableView cellForRowAtIndexPath:indexPath];
buttonCell.myButton.hidden = NO;
Mistake in uppercase maybe ?
[buttonCell.myButton setHidden:NO]; // Trying to access instance variable
Instead of :
[ButtonCell.myButton setHidden:NO]; // Trying to access class variable
Do you have a public accessor for that property in the header file of ButtonCell? Something like #property (nonatomic, retain) UIButton *myButton;
This is how I usually see such a compiler warning.
I have an nslog that is successfully telling me that the information I will load into dynamic cells is there. However, I've discovered that my tableView is not loading the array "dogs" with object "dogs" at all because the nslog for name11Label is returning null despite the fact that there is a value for dogs in viewDidLoad. What would I need to do to initiate the tableView? (.h does have an iboutlet and a property(just to make sure) for my "tableView" and also has as well as and import for cell11.h)
.m file
-(void)viewDidLoad{
...
self.tableview.delegate = self;
...
}
- (NSInteger)tableView:(UITableView *)table numberOfRowsInSection:(NSInteger)section {
if ( dogs != NULL ) {
return [dogs count];
}
return 0;
}
- (UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath {
Cell11 *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell11"];
if (cell == nil)
{
cell = [[[Cell11 alloc] initWithFrame:CGRectZero reuseIdentifier:#"Cell11"] autorelease]; //I know that this method is depreciated, but it is not the source of this problem
}
NSDictionary *itemAtIndex =(NSDictionary *)[dogs objectAtIndex:indexPath.row];
cell.name11Label.text = [itemAtIndex objectForKey:#"dogs"];
NSLog(#"dogs array = %#", dogs); //returns correct information of the object dogs
NSLog(#"%#", cell.name11Label.text); //returning null even though "dogs" in viewDidLoad is showing a result;
return cell;
}
Cell11.h
#import <UIKit/UIKit.h>
#interface Cell11 : UITableViewCell
#property (nonatomic, strong) IBOutlet UILabel *name11Label;
#end
Cell11.m
#import "Cell11.h"
#implementation Cell11
#synthesize name11Label;
#end
Change:
Cell11 *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell11"];
to
Cell11 *cell = [tv dequeueReusableCellWithIdentifier:#"Cell11"];
tableView is probably not hooked up right.
The nslog statement will never get called. Move it above:
NSLog(#"%#", cell.name11Label);
return cell;
Also you are initializing the cell with cgrect zero which won't display anything. Make sure it has a height and width that are expecting (e.g. CGRectMake(0.0f, 0.0f, 320.0f, 50.0f))
If the cell still doesn't display, are you calling reloadData on the tableview after the array dogs is set up?
Have you assigned your UITableView's "dataSource" property to the object with the UITableViewDataSource protocol (the above methods)?
First of all: The NSLog after the return will never be called.
NSLog(#"%#", cell.name11Label);
return cell;
}
Second: Control drag from you tableview of the interface builder to your class. And make sure the connection dataSource is properly connected.