I have a tableview each cell contains a text and a button. For each button in each cell in cellForRowAtIndexPath I define an action using a selector as such:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
ProductTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"ProductCell" forIndexPath:indexPath];
cell.stateButton.tag = indexPath.row;
[cell.stateButton addTarget:self action:#selector(stateChange:) forControlEvents:UIControlEventTouchUpInside];
/// Rest of code
return cell;
}
At some point in the flow I want to generate an action for all the buttons in my tableview. I do it like that:
#pragma mark - Delegate
- (void)onAllSwitchesStateChange{
for(int i = 0; i < [[SsrDeviceManager instance] ssrDevices].count; i ++){
ProductTableViewCell* cell = (ProductTableViewCell *)[self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]];
UIButton* stateButton = [cell stateButton];
[stateButton performSelector:#selector(stateChange:) withObject:nil];
}
}
This is my selector method:
-(void)stateChange:(UIButton*)sender{
[self.activityIndicator sizeToFit];
[sender addSubview:self.activityIndicator];
[self.activityIndicator startAnimating];
//// Rest of code
}
However, why I try to run this code, I get an error and the app crashes with this message:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UIButton stateChange:]: unrecognized selector sent to instance 0x1023686d0'
*** First throw call stack:
(0x233133180 0x23230b9f8 0x23304f9bc 0x25f94c220 0x2331389c8 0x23313a65c 0x101073bdc 0x100fdd844 0x25f922300 0x25f3cb424 0x25f3cb744 0x25f3ca7b0 0x25f9595c4 0x25f95a7ec 0x25f93a85c 0x25fa009d4 0x25fa03100 0x25f9fc330 0x2330c4f1c 0x2330c4e9c 0x2330c4784 0x2330bf6c0 0x2330befb4 0x2352c079c 0x25f920c38 0x10102b794 0x232b828e0)
How can I generate a click action for all buttons at once
"How can I generate a click action for all buttons at once"
The very notion makes no sense. A table view is just view. It lets the user communicate with you (the app) because it is touchable interface, but it is not, in itself, a source of activity; it merely represents the data.
If you want something to happen, it is insane to turn to all your buttons and ask them all to initiate some sort of action. Simply do to your data, yourself, whatever it is that you want done.
UIButton doesn't have a method stateChange:. Your class has a method stateChange:. You could just call your own class's method like [self stateChange:stateButton]. If you want the button to send the message it would send if a particular type of even happened, you could do something like [stateButton sendActionsForControlEvents:UIControlEventTouchUpInside].
Related
I am trying to build a multilayer xmlparser which shows data in a tableview and switches to the next view based on the selected row (most likely to the same viewcontroller).
I started with storyboard segues but as i am using dynamic cells i dont know how to create more than one push segue (because it needs to push to various viewcontrollers). So i kept the storyboard views, deleted all the segues and used the code below instead.
however it throws this exception when the new view tries to pupulate the row:
Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason: 'UITableView dataSource
must return a cell from tableView:cellForRowAtIndexPath:'
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"index: %i", indexPath.row);
switch (indexPath.row) {
case 0:
{
DetailViewController *detailViewController = [DetailViewController alloc];
[self.navigationController pushViewController:detailViewController animated:YES];
break;
}
//...
default:
{
LayerViewController *layerViewController= [LayerViewController alloc];
[layerViewController setStartUpWithIndex:indexPath.row andLayer:layercount];
[self.navigationController pushViewController:layerViewController animated:YES];
break;
}
}
}
setStartUpWithIndex:andLayer: is my init method...
the problem does not occur when im pushing via storyboard segue with the following segue code:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
[[segue destinationViewController] setStartUpWithIndex:indexPath.row andLayer:layercount];
}
i think i am missing something the "segue" method does which i need to include in my didSelectRowAtIndexPath as cellForRowAtIndexPath: works fine in the first layer of the view.
requested edit:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"LayerCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
cell.textLabel.text = [[articles objectAtIndex:indexPath.row]objectForKey:#"HerstellerName"];
// cell.textLabel.text = #"ololol";
return cell;
}
the articles array is working btw
You aren't creating any cells. I don't know why Apple didn't include this in the default implementation, but after:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
You need to add:
if (cell==nil){
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
Otherwise you're just relying on dequeued cells which won't exist when the tableView loads.
Edit: And if you're not using ARC, you need to wrap that alloc method with an autorelease.
You are pushing another viewController to be viewed and this next viewController has no dataSource methods implemented. This what happened.
As far as I understood, you are trying to present an xml node with a table in the way that each table row consist child nodes of presented node. selecting row with chld node gets you to next table view which presents table with child nodes of selected node.
IMO you may need only one view controller to present current xml node, which changes only data to be presented in a table and reloads the table again. So no pushing view controllers is needed. You can use then method
– reloadRowsAtIndexPaths:withRowAnimation:
for all the nodes in the table, setting animation similar to changing view controller (aka sliding old table left)
UIControl - changing assigned selectors: addTarget & removeTarget
States that you should remove the target before changing to another. However what if I am setting the target in cellForRowAtIndexPath? Should I remove the target before adding it again even if it is not changing? Will it call the method twice if I don't remove it or will it just overwrite it?
[cell.cellSwitch removeTarget:self action:#selector(notifySwitchChanged:) forControlEvents:UIControlEventValueChanged];
[cell.cellSwitch addTarget:self action:#selector(notifySwitchChanged:) forControlEvents:UIControlEventValueChanged];
Instead of adding/removing targets, I've found that if I'm already subclassing the UITableViewCell, I'll add a new delegate and set the delegate to the view controller. That way, any methods called on the delegate can pass in the entire cell and therefore I can get the index path of the cell by calling UITableView's indexPathForCell method.
Following my experience, it will be called only once.
But IMO, it is better to use removeTarget always because code can be changed in the future. And someday, you may need to add multiple targets and selectors.
Be a code in safe, scalable, and maintainable.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = // code with reuse identifier ...
if(cell == nil)
{
// making view for cell ....
}
// myAction will be called ONLY ONCE after many times of scrolling
[cell.myButton addTarget:self action:#selector(myAction:) forControlEvents:UIControlEventTouchUpInside];
return cell;
}
i m parsing json data and populating the tableview and making some validation with the incoming json data..everthing works fine.i made the code such that when the last table view row is clicked it got to open a modal view controller.when clicked .i m getting this error [tableiew1] Unrecognised selector send at the instance...could u guys help me out below is the code.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Navigation logic may go here. Create and push another view controller.
if (indexPath.row == 5) {
if (self.dvController6 == nil)
{
Vad_tycker *temp = [[Vad_tycker alloc] initWithNibName:#"Vad_tycker" bundle:[NSBundle mainBundle]];
self.dvController6 = temp;
[temp release];
}
[self presentModalViewController:self.dvController6 animated:YES];
}
}
Seems like you have forgotten to provide the access for the tableView1 in Vad_tycker.
Or You should do a crosscheck whether you have assigned the correct instance in tableView delegate's and also make sure to provide the implementation for the method of delegate's in their respect target classes.
I think you forgot to connect the tableView Datasource and Delegate methods in the vad_tycker controller.
Also check that the instance of UITableView i.e. in your case tableView1 is also connected with the TableView. on the view.
Thanks
So I have a UITableView which should loop through a single NSMutableArray and use each of them as row labels. Currently the only way I can get this to run is with 0 or 1 rows, 2 or higher throws out an error saying the array index is off. I tried NSLog to output my array and can confirm it's reading all the Strings.
// table methods
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 2;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
}
- (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];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
// Set up the cell...
NSString *cellValue = [harvestRecipeList objectAtIndex:indexPath.row];
cell.textLabel.text = cellValue;
return cell;
}
The array code is stored in the exact same file (MasterViewController.m) which I added below.
- (void)viewDidLoad
{
[super viewDidLoad];
harvestRecipeList = [[NSMutableArray alloc] init];
[harvestRecipeList addObject:#"Ice Cream"];
[harvestRecipeList addObject:#"Walnut Cake"];
[harvestRecipeList addObject:#"Cookies"];
[harvestRecipeList addObject:#"Salad"];
[harvestRecipeList addObject:#"Grilled Fish"];
//Set the title
self.navigationItem.title = #"BTN Recipes";
}
I would love any help on this it's been bugging me. I was using [harvestRecipeList count] but this throws the same array index error. And as I mentioned I can get the app to run perfectly fine with 0 or 1 rows - thanks in advance for any help!
EDIT: here is the error I'm getting in the output window after building:
Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndex:]: index 1 beyond bounds [0 .. 0]'
EDIT2: included below my property setup for harvestRecipeList
//MasterViewController.h
#interface MasterViewController : UITableViewController {
NSMutableArray *harvestRecipeList;
}
#property (nonatomic, retain) NSMutableArray *harvestRecipeList;
// and also my MasterViewController.m
#synthesize harvestRecipeList;
EDIT3
here's my source code zipped for this project. It's called treehouse, just a testing name for now but you can dl from my cloudapp here.
Updated Solution:
So I have checked your code and found the problem. Do the following:
Go into your storyboard and select the Master Table View (click where it says Static Content)
Click on the Attributes Inspector (looks like a downward arrow sort of) and change the content from Static Cells to Dynamic Prototypes
Click on the prototype cell and type Cell into Identifier field (this is what you are using as a cell ID
Also change the Accessory from None to Disclosure Indicator
In your tableView:numberOfRowsInSection return self.harvestRecipeList.count
In tableView:cellForRowAtIndexPath: you can remove the following two lines (as they are provided by the Storyboard):
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
Recreate your Push segue from your Master Cell to your Detail View Controller
It should all now work fine - and I've tested that it works. The basic problem was you had specified Static Cells rather than Dynamic Prototypes and the rest of the instructions are just mopping up. The NSRangeException was caused by only having a single cell so that was all that was displayed.
Hope this helps.
Previous Solution:
So, a few comments but, first, if you've updated your code can you post an update?
Your harvestRecipeList that you add objects to in ViewDidLoad does not appear to be the same harvestRecipeList that you synthesised - it will be local to the method so your instance variable will always be nil. You should always use self.harvestRecipeList - do this everywhere. This could easily explain your NSRangeException. Also see #4 below.
In your tableView:numberOfRowsInSection: you should return self.harvestRecipeList.count
If you are using the iOS5 SDK, you do not need to check if cell == nil as dequeueReusableCellWithIdentifier: is guaranteed to return non-nil cell. You do need to check if you are on the iOS4 SDK.
Change your #synthesize harvestRecipeList; to #synthesize harvestRecipeList = _harvestRecipeList; as this will assist with #1 and checking you are accessing the ivar.
Try #1 & #2 as a minimum and then post an update on the problems you are having. Hope this helps.
I examined the code you put in the ZIP file. I immediately noticed your using the iOS 5 Storyboard feature. I haven't got Xcode 4 nor the iOS 5 SDK, so I could not test that part of your application.
However, I went on and coded your Storyboard part by hand. I have tested your MasterViewController solely and found no errors. I added in the AppDelegate this method to replace the Storyboard automatical features and just show the view controller where you think the error is coming from.
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
MasterViewController *myVC;
_window = [[UIWindow alloc] initWithFrame:
[[UIScreen mainScreen] applicationFrame]];
myVC = [[MasterViewController alloc] initWithStyle:UITableViewStylePlain];
[_window setAutoresizesSubviews:YES];
[_window addSubview:myVC.view];
[_window makeKeyAndVisible];
return YES;
}
To prove your MasterViewController.m contains no error, I add this screenshot:
Conclusion: Your error is to be found somewhere else, probably in your Storyboard file. However I have never used that new functionality so I cannot help you with that. I suggest you review your Storyboard and put all attention to that file.
Okay, if your log messages display an array with five objects right before you try to query the second, and you application gives you an NSRangeException the bug is definitely not to be found in the code you show us.
Try to find it by placing various logs before and after any -[NSArray objectAtIndex:] and see which log doesn't come through after the call. There's your error.
Remember you can use
NSLog(#"%s", __PRETTY_FUNCTION__);
to show where you log message is coming from. Their also exists a line and file macro, but normally the function macro should help you enough.
Example:
NSLog(#"%s Before", __PRETTY_FUNCTION__);
[myArray objectAtIndex:anIndex];
NSLog(#"%s After", __PRETTY_FUNCTION__);
If your second log message doesn't come through, then you'll have found your error.
It is always best to use the setter and getter methods for your instance variables. It takes care of a lot of problems. My guess is that is your problem. So anywhere you want to use harvestRecipeList use self.harvestRecipeList
It would be useful to know what your property declaration is for harvestRecipeList
UITabBarViewController & UINavigationController & UITableView
My app crashes when I try to push a detail view controller when selecting a cell.
If I empty the detail view controller I am trying to push, it works, but when I add UITextFields and action buttons to the view, the app crashes.
Here is my code:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSUInteger row = [indexPath row];
if(self.addSimpleTrainingVC == nil)
{
AddDetailVC *tempVC = [[AddDetailVC alloc] initWithNibName:#"addDetailVC" bundle:nil];
self.addDetailVC = tempVC;
[tempVC release];
}
addDetailVC.title = [NSString stringWithFormat:#"%#",[myArray objectAtIndex:row]];
[self.navigationController pushViewController:self.addDetailVC animated:YES];
addDetailVC.title = [NSString stringWithFormat:#"%#",[myArray objectAtIndex:row]];
// myAppDelegate *delegate = [[UIApplication sharedApplication] delegate];
// [delegate.myNavBarController pushViewController:addDetailVC animated:YES];
// self.addDetailVC.view.hidden =NO;
// [delegate.myNavBarController pushViewController:addDetailVC animated:YES];
}
If the view is being loaded OK when there are no UI elements, then most probably there must be something wrong at the interface builder side of things. Probably, an outlet/IBAction was defined and it can no longer be found.
The best thing to do is to open the 'Console' application (comes default in a Mac). Then, run your simulator and let the application crash. The Console will let you know what went wrong and will probably provide the source of the error. You might be seeing something like this:
11-08-04 1:02:06 AM StackOverFlow[10840] ** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-[UIViewController _loadViewFromNibNamed:bundle:] loaded the "Detail" nib but the view outlet was not set.'*