addTarget:action:forControlEvents - UISwitch in TableView - sender ok, event always 0x0 - objective-c

Using the fantastic posts in this forum, I created a switch as a accessoryView in a tableView. When the switch is touched my action (switchChanged) is called. Only the sender has a valid value, the event is 0x0.
Adding target to the switchView:
[switchView addTarget:self action:#selector(switchChanged:forEvent:) forControlEvents:(UIControlEventValueChanged | UIControlEventTouchDragInside)];
The Action:
- (void) switchChanged:(id)sender forEvent:(UIEvent *)event {
if(event) {
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:[[[event touchesForView:sender] anyObject] locationInView:self.tableView]];
IFDFlightlogFormQuestions *question = [self.resultsController objectAtIndexPath:indexPath];
NSLog(#"IFDNewFlightlogViewController_Pad:switchChanged Switch is %# xmlAttrib %#",[sender isOn],question.XmlAttrib);
[self.xmlResults setValue:([sender isOn])?#"true":#"false" forKey:question.XmlAttrib];
}
}
Using action:#selector(switchChanged: forEvent:) - Added space - no change.
Using action:#selector(switchChanged::) - removed forEvent - unrecognized selector.
My goal is to get the indexPath to the tableView so I can change the value in my dictionary.
My current workaround is to use just the sender information, but I would like the event information:
- (void) switchChanged:(id)sender forEvent:(UIEvent *)event {
UISwitch *switchView = (UISwitch *)sender;
UITableViewCell *cell = (UITableViewCell *)switchView.superview;
UITableView *tableView = (UITableView *)cell.superview;
NSIndexPath *indexPath = [tableView indexPathForCell:cell];
IFDFlightlogFormQuestions *question = [self.resultsController objectAtIndexPath:indexPath];
NSLog(#"IFDNewFlightlogViewController_Pad:switchChanged Switch is %# xmlAttrib %#",([sender isOn])?#"On":#"Off",question.XmlAttrib);
[self.xmlResults setValue:([sender isOn])?#"true":#"false" forKey:question.XmlAttrib];
}
Any pointers on getting the event information on this?

It is possible to trigger #selector by adding a target to the UISwitch.
[switchCtl addTarget:self action:#selector(action:) forControlEvents:UIControlEventValueChanged];

Another possibility is to set the tag property on the UISwitch to match the row of your tableview. If you use multiple sections as well, then you could come up with some solution to encode these in a single integer value. Keep in mind that with this approach, you need to update the tag every time you update the cell (tableView:cellForRowAtIndexPath:) since the row can change as cells are reused.

Unfortunately no forEvent: is sent from a UIControl action... only the first part which is why you are not seeing any value.
The way your workaround works is a suitable method.
Another way would be to subclass the UISwitch so it held a reference to the NSIndexPath, or even overrides
- (void)sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event
to interrupt and send a custom event...

Related

UITableView cell retaining out of view data

I am trying to solve this issue regarding a UITableView cell being off screen, or outside the visible area.
Within my tableview cells I have a UITextField which I am able to parse easily using the code below. However I find that that for the cells that are not visible it returns a NULL value.
I am guessing this is a feature to improve memory usage but is there anyway to turn it off? Or if not is there a work around?
InputCell *inputCell = (InputCell *)[self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]];
UITextField *cellContent = (UITextField *)[inputCell.textInput viewWithTag:0];
NSLog(#"Cell Content: %#" , cellContent.text);
Thanks and thanks again!
Views need models, especially table views. A model is some object (often a group of objects in collection classes) that represents the state of your app. A table view requires an array. The datasource protocol asks you to describe that array. Since tableview cells are part of the view, they shouldn't be relied upon to keep the state of your app. That's up to you as follows:
In your vc's private interface:
#property(strong, nonatomic) NSMutableArray *myDatasource;
Early, like in view did load:
myDatasource = [NSMutableArray array];
// fill it with strings
In numberOfRowsInSection...:
return self.myDatasource.count;
In cellForRowAtIndexPath:
cellContent.text = self.myDatasource[indexPath.row];
Make the vc your textField's delegate and implement:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
NSString *candidateString = [textField.text stringByReplacingCharactersInRange:range withString:string];
NSIndexPath *indexPath = [self indexPathWithSubview:textField];
self.myDatasource replaceObjectAtIndex:[indexPath.row] withObject: candidateString];
return YES;
}
This helper finds the indexPath of any textField (any subview) of any cell:
- (NSIndexPath *)indexPathWithSubview:(UIView *)subview {
while (![subview isKindOfClass:[UITableViewCell self]] && subview) {
subview = subview.superview;
}
return [self.tableView indexPathForCell:(UITableViewCell *)subview];
}
It looks like a lot, but not so bad when you get used to it. The pattern is always - think of the objects that describe the state of your app (model). Think of views that best describe and manipulate that state (view). Make your view controllers (controllers) (a) notice model changes and change the views accordingly, and (b) hear about user actions from the views and update the model.

call UITableView methods from UITouch

I have a table with customs cells, which has subclass UITableViewCells and for control touches I use UITouch (code based on this tutorial http://gregprice.co.uk/blog/?p=280) I need inside touchesEnded method get index of cell which was touch.
I try:
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
//some code
UITableView *parentTable = (UITableView *)self.superview;
NSIndexPath *index = [NSIndexPath alloc];
index = [parentTable indexPathForSelectedRow];
NSLog(#"Call, x= %f, index=%d", xPos, index.row);
}
But always I've got index=0.
Whats I do wrong?
it seems like you are taking a very round about way to select a row in a UITableView. set your UITableView's delegate and data source to the class you are working in, then implement its protocol methods which you can find in the UITableViewDelegate and UITableViewDataSource reference. Once you have done that you can use this method to determine which row was selected in the table
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
}
maybe you already knew this but it sounds like you are new to iOS development and i dont see why you would want to manually do it that way.
and also as Dave Leverton's answer said, dont do an alloc like that
Not sure this is causing the problems but it's bad practice to call 'alloc' like you are here. I'd remove the line with alloc (As this is creating memory for an object you're not initialising or using) and just have the NSIndexPath object defined in-line:
UITableView *parentTable = (UITableView *)self.superview;
NSIndexPath *index = [parentTable indexPathForSelectedRow];
NSLog(#"Call, x= %f, index=%d", xPos, index.row);

update a UILabel when the cell in UITableView is selected

A really simple question here. I have a label on one view and a UITableView on the previous view. I have got a segue triggered when the user selects the row and I want the label to be updated with the text from that row. Here's one example, the code is pretty obvious.
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *countrySelection;
switch (indexPath.section) {
case kFirstSection:
countrySelection = [[NSString alloc]
initWithFormat:#"The country you have chosen is %#",
[self.MyCountries objectAtIndex: indexPath.row]];
[self performSegueWithIdentifier:#"doneResults" sender:self];
self.countryResult.text = countrySelection;
break;
The label isn't updated and I just don't know what should be done.
Thanks in advance!
These kind of things really need to be set on the View Controller that owns them. Use a public property to pass the value of the selected country to that view controller as outlined below:
First, create a property called something like:
#property(non atomic,strong) NSString *countryChosen;
in the destination View Controller, and make sure to #synthesize it
No reason to create another property for the IndexPath. Just use
// Pass along the indexPath to the segue prepareForSegue method, since sender can be any object
[self performSegueWithIdentifier:#"doneResults" sender:indexPath];
in the didSelectRowAtIndexPath method.
Then in the prepareForSegueMethod:
MyDestinationViewController *mdvc = segue.destinationViewController;
NSIndexPath *indexPath = (NSIndexPath *)sender;
mdvc.countryChosen = [self.MyCountries objectAtIndex: indexPath.row]];
On the viewDidLoad event of the Destination VC, just use:
self.countryResult.text = countryChosen;
* EDIT *
To deal with a datasource that has multiple sections, just use the same logic that you have in the cellForRowAtIndexPath.
NSDictionary *selRow = [[self.countriesIndexArray valueForKey:[[[self.countriesIndexArray allKeys] sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)] objectAtIndex:indexPath.section]] objectAtIndex:sindexPath.row];
Change this to suit your needs, but basically you are implementing the same logic that you would to display a cell, except you are specifying the indexPath (both section and row) that you want.
Then something like the following to set that property on the destination VC:
self.countryResult.text = [selRow valueForKey#"Country"];
In your current view controller create a new property for the indexPath of the cell the user selected, like this:
#property(strong,nonatomic) NSIndexPath *path;
#synthesize it and then when a user selects a row, set it by using
self.path = indexPath;
When you perform a segue, it will always call
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
So what you can do now when prepareForSegue: gets called, is the following:
/* if this is not the only segue you are performing you want to check on the identifier first to make sure this is the correct segue */
NSString *countrySelection = [[NSString alloc]
initWithFormat:#"The country you have chosen is %#",
[self.MyCountries objectAtIndex: self.path.row]];
segue.destinationViewController.countryResult.text = countrySelection;
/* after creating the text, set the indexPath to nil again because you don't have to keep it around anymore */
self.path = nil;
For this to work the view controller you want to show after selecting the cell must have a property for the UILabel, on which you are trying to set the text.

NSIndexPath of UITableViewCell subview?

Is there any was to get the NSIndexPath of a UITableViewCell's contentView subview?
In my case, I have a table view in which each row is split into 3 buttons. I can use the button tag to keep track of which column it is but I also need to know what row of the table view it is a subview of (when the user taps the button).
Because the button blocks the entire actual table view row, didSelectRowAtIndexPath is never called (which is expected), so I can't get it that way.
I have tried just [thisButton superview] but that doesn't seem to work. Any thoughts?
I assume the button is sending its UIControlEventTouchUpInside event message to your view controller? Then you could do something like:
- (void)buttonPressed:(UIButton *)button
{
CGPoint location = [button.superview convertPoint:button.center toView:self.tableView];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:location];
[self doSomethingWithRowAtIndexPath:indexPath];
}
One possible option is when making the cell for index path you can assign the tag to be 3 * row_number + <the tag number>. then just divide the tag by 3 to get the row and %3 to get the button.
You don't need to call button.superview. You can go straight from any subview in the tree right to the table view
Objective-C
- (NSIndexPath *)indexPathForCellContainingView:(UIView *)view inTableView:(UITableView *)tableView {
CGPoint viewCenterRelativeToTableview = [tableView convertPoint:CGPointMake(CGRectGetMidX(view.bounds), CGRectGetMidY(view.bounds)) fromView:view];
NSIndexPath *cellIndexPath = [tableView indexPathForRowAtPoint:viewCenterRelativeToTableview];
return cellIndexPath
}
Swift
func indexPathForCellContainingView(view: UIView, inTableView tableView:UITableView) -> NSIndexPath? {
let viewCenterRelativeToTableview = tableView.convertPoint(CGPointMake(CGRectGetMidX(view.bounds), CGRectGetMidY(view.bounds)), fromView:view)
return tableView.indexPathForRowAtPoint(viewCenterRelativeToTableview)
}

How to use a UITableView to return a value

How would I use a tableView as a value selector?
So I have a series of input fields and what I want is when you select a cetian field it opens a tableview of options that you can pick from as a value for that field.
Upon selecting an option it returns to the previous View with the selected value filling that field.
This is what I do, similar to the Settings > General > International > Language table view in the iPhone/iPod.
The user can tap a row and a check mark will appear. The view is dismissed when "Done" or "Cancel" is tapped.
First, create a UITableViewController that will display your options. Have a toolbar on the top with a Cancel and Done button. Also have these properties:
SEL selector; // will hold the selector to be invoked when the user taps the Done button
id target; // target for the selector
NSUInteger selectedRow; // hold the last selected row
This view will be presented with the presentModalViewController:animated: method so it appears from the bottom of the screen. You could present it in any other way, but it seems kind of standard across iPhone applications.
Before presenting the view, set target and selector so a method will be called when the user taps the "Done" button.
Now, in your newly created UITableViewController you can implement the thetableView:didSelectRowAtIndexPath:` method as:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell * cell = [self.tableView cellForRowAtIndexPath:indexPath];
cell.accessoryType = UITableViewCellAccessoryCheckmark; // show checkmark
[cell setSelected:NO animated:YES]; // deselect row so it doesn't remain selected
cell = [self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:selectedRow inSection:0]];
cell.accessoryType = UITableViewCellAccessoryNone; // remove check from previously selected row
selectedRow = indexPath.row; // remember the newly selected row
}
Also implement cancel and done methods for the toolbar buttons:
- (IBAction)done:(UIBarButtonItem *)item
{
[target performSelector:selector withObject:[stringArray objectAtIndex:selectedRow]];
[self dismissModalViewControllerAnimated:YES];
}
- (IBAction)cancel:(UIBarButtonItem *)item
{
[self dismissModalViewControllerAnimated:YES];
}
You should use UITableViewDelegate's tableView:didSelectRowAtIndexPath:, remember the value somewhere in another object (a share instance/singleton maybe? - depending on your architecture) and then dismiss this table view.
I implemented a ViewController for Date pick.
I create a protocol to return the date picked to the previous view.
#protocol DataViewDelegate
#optional
- (void)dataViewControllerDidFinish:(NSDate*)dateSelected;
#end
...
- (void) viewDidDisappear:(BOOL)animated
{
if ([ (id)(self.delegate) respondsToSelector:#selector(dataViewControllerDidFinish:)])
{
[self.delegate dataViewControllerDidFinish:self.data];
}
[super viewDidDisappear:animated];
}
In the picker view you can use the
tableView:didSelectRowAtIndexPath:
to select the row you want. Here i set the data property.
The previous view is the delegate for the protocol.