I am using XCode's Navigation-based Application template to create an app that centers around an UITableView. I am having some problems deleting some rows in the tableView.
In all of the tableViews cells I have added a button by subclassing UITableViewCell like this:
TableViewCell.h:
#implementation TableViewCell
#synthesize button;
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString*)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
RoutinesAppDelegate *appDelegate = (RoutinesAppDelegate *) [[UIApplication sharedApplication]delegate];
// Initialization code
button = [[UIButton buttonWithType:UIButtonTypeRoundedRect] retain];
[button setFrame:CGRectMake(320.0 - 90.0, 6.0, 80.0, 30.0)];
[button setTitle:#"Done" forState:UIControlStateNormal];
[button addTarget:appDelegate action:#selector(routineDone) forControlEvents:UIControlEventTouchUpInside];
button.hidden = YES;
[self.contentView addSubview:button];
}
return self;
}
when the button is pressed the method "routineDone" in RoutinesAppDelegate.m is called.
The method looks like this:
RoutinesAppDelegate.m
-(void) routineDone
{
if ([[NSFileManager defaultManager] fileExistsAtPath:[self todayListPath]])
{
int indexToRemove = buttonIndex + 1;
todayListArray = [NSMutableArray arrayWithContentsOfFile:[self todayListPath]];
[todayListArray removeObjectAtIndex:indexToRemove];
[todayListArray writeToFile:[self todayListPath] atomically:YES];
}
}
This method removes an item from the array that the tableView loads. This works fine.
MY PROBLEM:
The cell is not removed from the screen, when the button is clicked.
In RoutinesAppDelegate.m I tried to use
[tableView reloadData]
but it does not seem to work from outside the RootViewController class.
It would be nice if I somehow could use the deleteRowsAtIndexPaths method, but I dont know how to call that method from outside commitEditingStyle in the RootViewController class.
So my question is really: How can make the cell go away? :)
Without seeing all the code it's hard to tell, but you can try adding after
[todayListArray writeToFile:[self todayListPath] atomically:YES];
[self.tableView reloadData];
Also, you don't have to retain that button, and probably don't have to have it as a property
Related
I have a UITableView on my MainViewController which has custom UITableCells defined in a CustomCell class. I need an UIButton, which is in my CustomCell class, to call a method in MainViewController. I cannot create a new instance of MainViewController because the method uses some variables which would all be in the default state if I created a new instance. What do I do??
This is my code:
MainViewController.m (this is the method I want called):
-(void)updateLabels{
double totalValue=0, personValue=0;
[self returnTickArray];
for(NSInteger i = 0; i < n; i++) {
totalValue += ([[[self returnPricesArray] objectAtIndex:i] doubleValue] * [[[self returnQtyArray] objectAtIndex:i]doubleValue]);
if([[[self returnPeopleArray] objectAtIndex:i]doubleValue]>0) personValue += ([[[self returnPricesArray] objectAtIndex:i] doubleValue] * [[[self returnQtyArray] objectAtIndex:i]doubleValue]/ [[[self returnPeopleArray] objectAtIndex:i]doubleValue] * [[[self returnTickArray] objectAtIndex:i]doubleValue]);
}
_totalValue.text = [NSString stringWithFormat:#"$ %.02lf", totalValue];
_tip.text= [NSString stringWithFormat:#"$ %.02lf", totalValue*(([_tipPercentage.text doubleValue]/100))];
_addedValue.text= [NSString stringWithFormat:#"$ %.02lf",([[_tip.text substringFromIndex:2] doubleValue]+totalValue) ];
_perPerson.text=[NSString stringWithFormat:#"$ %.02lf", personValue];
}
This is the method in CustomCell.m which gets called when I press the button:
- (IBAction)tick:(UIButton *)sender {
if ([sender isSelected]) {
[sender setImage:[UIImage imageNamed:#"off"] forState:UIControlStateNormal];
[sender setSelected:NO];
_isTicked = [NSNumber numberWithInt:0];
}
else {
[sender setImage:[UIImage imageNamed:#"on"] forState:UIControlStateSelected];
[sender setSelected:YES];
_isTicked = [NSNumber numberWithInt:1];
}
}
you can assign a method from your controller to a button from UITableView in your - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath method
[cell.yourButton addTarget:self action:#selector(updateLabels) forControlEvents:UIControlEventTouchUpInside];
(just don't forget to add IBOutlet of your button to your custom cell class)
Handling changes, actions, or user interaction in other views is the essential use case of delegates. The best practice is to have the MainViewController be the delegate of the CustomCell, and then as events happen in the cell (i.e., tick is called), it calls certain methods on its delegate to notify it and the delegate then calls updateLabels.
Other options for keeping values/state in sync between views are:
Core Data - best used for data
Key-Value Observation - best used for data
NSNotifications sent through NSNotificationCenter - best used for actions
interesting :)
You need to use [UIApplication sharedApplication] singleton to process this.
Here are few steps:
Create object of MainViewController In you AppDelegate class:
in AppDelegate.h import MainViewController.h
#interface AppDelegate
{
MainViewController *mvc;
}
#property (strong) MainViewController *mvc;
in MainViewController.m
#implementation AppDelegate
#synthesize mvc =_mvc
Now come to your CustomCell.m
- (IBAction)tick:(UIButton *)sender {
[[(AppDelegate *)[[UIApplication sharedApplication] delegate] hvc] tick:sender];
}
Here you are seeing a method is called- tick_fromCell:. Now you have to define this method in your MainViewController class' .h ans .m files.
in MainViewController.h
-(void)tick:(id)sender;
and in MainViewController.m
-(void)tick:(id)sender {
NSLog("Clicking from my CustomCell :)");// Here you will put updateLabels method
[self updateLabels];
}
I am trying to create a UITableView with a custom UIButton in each table cell. I implemented like this..
#implementation CouponDetailsCustomTableViewCell
...............
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
[self setBackgroundColor:[UIColor whiteColor]];
CGRect frame = self.contentView.frame;
self.radioButton = [UIButton buttonWithType:UIButtonTypeCustom];
[self.radioButton setImage:[UIImage imageNamed:#"radio_blank.png"] forState:UIControlStateNormal];
[self.radioButton setImage:[UIImage imageNamed:#"radio_selected"] forState:UIControlStateSelected];
[self.radioButton setFrame:CGRectMake(16, 10, 29, 29)];
[self.radioButton addTarget:nil action:#selector(radioButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:radioButton];
}
#end
and UITableView Delegate as......
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *COUPON_CELL_ID = #"CouponCell" ;
CouponDetailsCustomTableViewCell * couponCell = (CouponDetailsCustomTableViewCell *) [tableView dequeueReusableCellWithIdentifier:COUPON_CELL_ID];
if (couponCell == nil) {
couponCell = [[[CouponDetailsCustomTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:COUPON_CELL_ID] autorelease];
couponCell.selectionStyle = UITableViewCellSelectionStyleNone;
}
[couponCell.radioButton setSelected:NO];
return couponCell;
}
and the radioButtonPressed method is
-(void)radioButtonPressed:(id) sender
{
[sender setSelected:YES];
}
Now i run the program and a custom UIButton is shown in every table row . If i click on a button it gets selected (shows the radio_selected.png).
The problem arises when i scroll down the table (i am showing only 4 rows of the table in the window) . When i scroll up again..what i see is the clicked button is showing the radio_blank.png.
I am new in iPhone development. i dont know why is this happening. The most i can guess is the button property is changing .. setSelected:NO.
Someone please give me suggestions to fix this problem.
Thank you.
When you scroll your UITableView, hidden cells are not rendered anymore and might be reused for cells that are becoming visible. If a new cell becomes visible, tableView:cellForRowAtIndexPath: gets called.
The problem is that you're setting the selected state there:
[couponCell.radioButton setSelected:NO];
Therefore, whenever you scroll your cell out of the visible area and back again, it gets reset to selected = NO.
I suggest you create a NSMutableDictionary where you store the selection state of each row/NSIndexPath, which you then re-apply in tableView:cellForRowAtIndexPath:.
replace [couponCell.radioButton setSelected:NO]; in tableView:cellForRowAtIndexPath: with code that sets the selected property depending on a state from your dataSource.
something along those lines:
/* get the button state from your data source */
FancyCouponObject *coupon = [self.coupons objectAtIndex:indexPath.row];
BOOL buttonState = coupon.buttonState;
[couponCell.radioButton setSelected:buttonState];
The cells of a tableView are reused when they are moved off screen. You can't save state in them.
problem is when you scroll the table at that time your cellForRowAtIndexPath: delegate method called for every row... so here when its called at time your setSelected Method call with NO argument like bellow...
[couponCell.radioButton setSelected:NO];
so when you scroll table at time your setSelected method call and your button turn with radio_blank.png
...
:)
-(void)radioButtonPressed:(id) sender
{
[sender setSelected:YES];
}
in this method you are setting button as selected and for selected button you have set the radio_blank.png image
I am currently trying to set up my tableview, when its first loaded I call my connection class which in turn calls my parser class then inside my parser class I call a method in my ViewController, which is the original view that is being set up. This method is passed an array which will be used latter.
The method passes the array to an array variable in this ViewController, in this method I then call
[self.tableView reloadData];
and what I am wanting that to do is reload cellForRowAtIndexPath so that it will go through my logic (if statements) and check if ([returnedArray count] != 0){ then do its thing.. but the thread never makes it back to this delegate method, which in turn never makes it back to the if statment.
MORE INFO :)
So first of all, when the the ViewController loads
tableView:cellForRowAtIndexPath: is called and sets up my UITableView that all looks perfect, it then calls my NSURLConnection method which connects to my server downloads all of the data and then passes that over to my parser class. From there my parser dose its thing, and everything is fine.
This is what the code looks like in my tableView:cellForRowAtIndexPath:, method
//..
if (indexPath.row == 0){
if ([FilterArray count] == 0){
[cellActivityIndicator startAnimating];
//-- Start NSURLConnection
EngineRequests *engineRequests = [[EngineRequests alloc] init];
[engineRequests initalizePacketVariables];
}
if ([FilterArray count] != 0){
[cellActivityIndicator stopAnimating];
cell.accessoryView = nil; //hides activity indicator
cell.userInteractionEnabled = YES;
cell.backgroundColor = [UIColor whiteColor];
UILabel *label1;
label1 = (UILabel *)[cell viewWithTag:1];
label1.textColor = [UIColor darkGrayColor];
UILabel *label2;
label2 = (UILabel *)[cell viewWithTag:2];
label2.textColor = [UIColor darkGrayColor];
//...etc
Inside my parser class's parserDidEndDocument method I am passing the NSArray back to the MainView.
- (void)parserDidEndDocument:(NSXMLParser *)parser
{
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"%K like %#",#"ISCHECKED",#"T"];
NSArray *filteredArray = [parsedDataArrayOfDictionaries filteredArrayUsingPredicate:predicate];
//call method in VieController1 to pass array over
SearchViewController *searchViewController = [[SearchViewController alloc] init];
[SearchViewController initFilterArray:filteredArray];
}
So then I head back over to my VC1 and the method that I have declared in the .h and then obviously in the .m file
and this is all of the code that I have in it.
#pragma - Reciver methods
-(void)initFilterArray:(NSArray*)array
{
//initalise array variable for use in latter views
FilterArray = array;
//reload to make cell white
[self.tableView reloadData];
// NSLog(#"%#", FilterArray);
}
While debugging the code the thread makes it to this method and runs everything.. if I uncomment that NSLog, it displays my filitered array and everything. However for some reason the reloadData dose not seem to call tableView:cellForRowAtIndexPath:.. I know this because I have debugged it with breakpoints etc.
So... hopefully this added information will help you help me :)
[self.tableView reloadData];
it will call the delegate methods.
if it isn't, you can check if self.tableView is linked to the tableView you want to reload.
What I want to do is basically send some form of id with my button so I can calculate which one has been clicked. My buttons get dynamically created and all of them execute the same function that loads a new view, I want to know which button was clicked so I can display the correct data on the new view.
I have an NSMutableArray of annotations that I add a button to the details of the pin. The button works and it loads the next view, but I want to figure out which button was pressed.
I use a singleton with an NSMutableArray called array. The singleton name is Helper
What I did was:
Helper* array = [Helper sharedManager];
int clickedNum = [array.array indexOfObject:annotation];
[myDetailButton setTag:clickedNum];
[myDetailButton addTarget:self action:#selector(moreInfo:event:) forControlEvents:UIControlEventTouchUpInside];
That is where i create the annotations that go unto the map. The next line is my function ID
- (IBAction)moreInfo:(UIButton*)sender
And this is where I want to retrieve the tag
Helper *array = [Helper sharedManager];
array.clicked = sender.tag;
When I run this and click on the button in one of my annotations I get an exception saying NSInvalidArgumentException, reason: -(MapViewController moreInfo:event:]
Any help would be appriciated
Edit:
As requested the interface for helper:
#interface Helper : NSObject {
NSMutableArray *array;
int clicked;
}
#property (nonatomic, retain) NSMutableArray *array;
#property (nonatomic) int clicked;
+(id)sharedManager;
#end
Also I thought it wise to add:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
static NSString *identifier = #"Events";
MKPinAnnotationView *retval = nil;
if ([annotation isKindOfClass:[Events class]])
{
(MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if (retval == nil) {
retval = [[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier] autorelease];
Helper *array = [Helper sharedManager];
int clickedNum = [array.array indexOfObject:annotation];
// Set up the Left callout
UIButton *myDetailButton = [UIButton buttonWithType:UIButtonTypeCustom];
myDetailButton.frame = CGRectMake(0, 0, 23, 23);
myDetailButton.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
myDetailButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;
[myDetailButton setTag:clickedNum];
NSLog([NSString stringWithFormat:#"Testing tag: %#", clickedNum]);
[myDetailButton addTarget:self action:#selector(moreInfo:) forControlEvents:UIControlEventTouchUpInside];
// Set the image for the button
[myDetailButton setImage:[UIImage imageNamed:#"icon72.png"] forState:UIControlStateNormal];
// Set the button as the callout view
retval.leftCalloutAccessoryView = myDetailButton;
}
if (retval) {
[retval setPinColor:MKPinAnnotationColorGreen];
retval.animatesDrop = YES;
retval.canShowCallout = YES;
}
}
return retval;
}
Your method moreInfo: doesn't match what you have set as the selector in
[myDetailButton addTarget:self action:#selector(moreInfo:event:) forControlEvents:UIControlEventTouchUpInside];
So change that to
[myDetailButton addTarget:self action:#selector(moreInfo:) forControlEvents:UIControlEventTouchUpInside];
Or change the method definition.
Although Deepak's answer is correct and will work, since your buttons are annotation callout accessory views, there is an easier way to tell which annotation's button was selected.
Instead of adding your own target+action, use the map view's mapView:annotationView:calloutAccessoryControlTapped: delegate method which will get called when a callout view is tapped. There, the annotation is available as view.annotation. You don't need to set the button tag or use the annotation's index.
For an example, see this other answer.
A separate issue is that in your viewForAnnotation method, you are calling dequeueReusableAnnotationViewWithIdentifier but not doing anything with the result (so not getting any re-use). Instead, it should be assigned to retval and inside the last if, set retval.annotation.
I am using DTGridView from the DTKit by Daniel Tull. I implemented it in a very simple ViewController and the test I am doing is to place a button in the last row of the grid, which should add another row to the grid (and therefor moving the button to a row beneath it).
The problem is, when I click the button a couple of times and then start scrolling, the grid seems to lose its content. As I am not completly sure this is a bug in the grid, but more in my code, I hope you guys can help me out and track down the bug.
First I have my header file, which is quite simple, because this is a test:
#import <UIKit/UIKit.h>
#import "DTGridView.h"
#interface TestController : UIViewController <DTGridViewDelegate, DTGridViewDataSource>
{
DTGridView* thumbGrid;
}
#end
I declare a DTGridView, which will be my grid, where I want to put content in.
Now, my code file:
#import "TestController.h"
#implementation TestController
int rows = 1;
- (NSInteger)numberOfRowsInGridView:(DTGridView *)gridView
{
return rows;
}
- (NSInteger)numberOfColumnsInGridView:(DTGridView *)gridView forRowWithIndex:(NSInteger)index
{
if (index == rows - 1)
return 1;
else
return 3;
}
- (CGFloat)gridView:(DTGridView *)gridView heightForRow:(NSInteger)rowIndex
{
return 57.0f;
}
- (CGFloat)gridView:(DTGridView *)gridView widthForCellAtRow:(NSInteger)rowIndex column:(NSInteger)columnIndex
{
if (rowIndex == rows - 1)
return 320.0f;
else
return 106.6f;
}
- (DTGridViewCell *)gridView:(DTGridView *)gridView viewForRow:(NSInteger)rowIndex column:(NSInteger)columnIndex
{
DTGridViewCell *view = [[gridView dequeueReusableCellWithIdentifier:#"thumbcell"] retain];
if (!view)
view = [[DTGridViewCell alloc] initWithReuseIdentifier:#"thumbcell"];
if (rowIndex == rows - 1)
{
UIButton* btnLoadMoreItem = [[UIButton alloc] initWithFrame:CGRectMake(10, 0, 301, 57)];
[btnLoadMoreItem setTitle:[NSString stringWithFormat:#"Button %d", rowIndex] forState:UIControlStateNormal];
[btnLoadMoreItem.titleLabel setFont:[UIFont boldSystemFontOfSize:20]];
[btnLoadMoreItem setBackgroundImage:[[UIImage imageNamed:#"big-green-button.png"] stretchableImageWithLeftCapWidth:10.0 topCapHeight:0.0] forState:UIControlStateNormal];
[btnLoadMoreItem addTarget:self action:#selector(selectLoadMoreItems:) forControlEvents:UIControlEventTouchUpInside];
[view addSubview:btnLoadMoreItem];
[btnLoadMoreItem release];
}
else {
UILabel* label = [[UILabel alloc] initWithFrame:CGRectMake(10,0,100,57)];
label.text = [NSString stringWithFormat:#"%d x %d", rowIndex, columnIndex];
[view addSubview:label];
[label release];
}
return [view autorelease];
}
- (void) selectLoadMoreItems:(id) sender
{
rows++;
[thumbGrid setNeedsDisplay];
}
- (void)viewDidLoad
{
[super viewDidLoad];
thumbGrid = [[DTGridView alloc] initWithFrame:CGRectMake(0,0, 320, 320)];
thumbGrid.dataSource = self;
thumbGrid.gridDelegate = self;
[self.view addSubview:thumbGrid];
}
- (void)viewDidUnload
{
[super viewDidUnload];
}
- (void)dealloc {
[super dealloc];
}
#end
I implement all the methods for the DataSource, which seem to work. The grid is filled with as many rows as my int 'rows' ( +1 ) has. The last row does NOT contain 3 columns, but just one. That cell contains a button which (when pressed) adds 1 to the 'rows' integer.
The problem starts, when it starts reusing cells (I am guessing) and content start disappearing. When I scroll back up, the UILabels I am putting in the cells are gone.
Is there some bug, code error, mistake, dumb-ass-move I am missing here? Hope anyone can help.
I've had a quick look at the code and you should definitely be calling reloadData instead of setNeedsDisplay in selectLoadMoreItems like so:
- (void) selectLoadMoreItems:(id) sender {
rows++;
[thumbGrid reloadData];
}
Let me know if this fixes it, if not I'll take a proper look through your code later today.
There's a pull request that seems to solve this issue. The bottom line is calling initialiseViews everytime checkViews is called.