Selector of an object inside custom cell - ios

I have a switch inside a custom cell. The switch is allocated and set to the accessoryView of the cell inside the .m file of the custom cell.
However, I need the selector method for the switch to be handled in the ViewController of the tableView the custom cell resides in.
Currently when clicking the switch I get a crash that it can't find the selector, most likely because its looking in the cell's .m.
How can I declare my switch to have its selector look in the correct location?
edit as per request...
//cell .m
- (void)setType:(enum CellType)type
{
if (_type == SwitchType)
{
UISwitch *switchView = [[UISwitch alloc] init];
[switchView addTarget:self action:#selector(flip:) forControlEvents:UIControlEventValueChanged];
self.accessoryView = switchView;
}
}

Sounds like the job for a delegate. Create a protocol in your cell interface like:
#protocol MyCellDelegate <NSObject>
- (void)myCell:(MyCell *)sender switchToggled:(BOOL)value;
#end
and specify a delegate
id <MyCellDelegate> delegate;
Then in your MyCell.m, when the switch is toggled check if the delegate is defined, if so call it:
if (self.delegate != nil && [self.delegate respondsToSelector:#selector(myCell:switchToggled:)]) {
[self.delegate myCell:self switchToggled:switch.value]
}
And in your ViewController be sure to set the ViewController to be the delegate for the cell and implement the protocol method.

You could create your switch as a public property then set its target in cellForRowAtIndex:
#interface CustomCell : UITableViewCell
#property (nonatomic, strong) UISwitch *switch;
Or you could create a custom NSNotification that is fired off. And have your viewController listen for the notification then deal with it.
Blocktastic :)
You could also get fancy with blocks.
typedef void(^CustomCellSwitchBlock)(BOOL on);
#interface CustomCell : UITableViewCell
#property (nonatomic, readwrite) CustomCellSwitchBlock switchAction;
Then in your CustomCell.m:
- (void)handleSwitch:(UISwitch *)switch
{
switchAction(switch.on);
}
Then in your cellForRowAtIndex::
cell.action = ^(BOOL on){
if (on) {
// Perform On Action
} else {
// Perform Off Action
}
};

Related

Run Delegate Method in viewcontroller directly from Custom Cell

I have setup a delegate method on Custom cell. let's say,
#protocol CheckDelegate <NSObject>
#required
- (void)checkForUpdate;
#end
#property (nonatomic, weak) id<CheckDelegate> delegateForChecker;
-- I will call above function somewhere in my cell ---
and I want to listen for any change in Custom Cell at Viewcontroller. How can I implement this using the delegate in Viewcontroller without using tableviewcontroller.
In ViewController I though about doing,
-(void) viewDidLoad{
CustomCell * cll = [[Customcell alloc]init]
cll.delegateForChecker = self;
}
-(void) checkDelegate{
NSLog(#"Something changed");
}
P.S. I do not want use NSNotification
I came up with different approach to pass the data from cell to another viewcontroller (not the tableviewcontroller where the cell is instantiated) using block. This may help someone who may find themselves stuck with this issue.
In custom cell, I created a block property called that would pass the property to the tableviewcontroller where the cell is instantied.
#property (copy, nonatomic) void (^didTouchButtonOnCell) (BOOL selectedButton);
In button touched in customCell I set the boolean property with Boolean Value.
self.didTouchButtOnCell(YES/NO);
In the tableViewcontroller I created another block that would pass data to the viewcontroller where I intended to pass the Boolean value.
In the TableViewController,
#property (copy, nonatomic) void (^passBooleanToViewController) (BOOL selectionFromButton)
In cellForRowAtIndexPath in TableViewController, I get the boolean property and pass it to ^passBooleanToViewController.
cell.didTouchButtonCell = ^(BOOL selection)
{
passBooleanToViewController(selection)
}
Finally, in ViewController I again get the Block property of TableViewcontroller that contained the Boolean value that was passed over to TableViewController from CustomCell
Viewcontroller.passBooleanToViewController = ^(Bool sel){
NSLog(#"%I here is the boolean value",sel);
}
** This is one of the ways I was able to fix the issue of passing value from custom cell to viewcontroller. It would be great if some other approaches can be shared too.

Go to another view by clicking a button in Custom Cell

I created the custom cell (XIB) as the subclass of UICollectionViewCell and the cell has a button in it. When I click a button, I want to go to another view with some data on that, and could go back to the original view by clicking a button as well. I've search for that and found something like "segue" or "modal" but I can't do it initially from my custom cell.
Is there any way to do this? Any help would be very thankful.
So what you want to do, since it seems like UICollectionView works the same as UITableView, is make a subclass of UICollectionViewCell that contains a protocol to send actions, like pressing a button, to a view controller from a different view. In this case, a different view being the UICollectionViewCell.
Adding a Protocol to a UICollectionViewCell
Add a new Cocoa Touch Class called UICustomCollectionViewCell with subclass of UICollectionViewCell. And include the interface builder file
header file UICustomCollectionViewCell.h
#protocol UICustomCollectionViewCellDelegate;
#interface UICustomCollectionViewCell : UICollectionViewCell
#property ( nonatomic, retain) IBOutlet UIButton *button;
- (IBAction)pressButton:(id)sender;
#property ( assign) id< UICustomCollectionViewCellDelegate> delegate;
#end
#protocol UICustomCollectionViewCellDelegate <NSObject>
#optional
- (void)customCollectionViewCell:(UICustomCollectionViewCell *)cell pressedButton:(UIButton *)button;
#end
implementation file UICustomCollectionViewCell.m
#implementation UICustomCollectionViewCell
#synthesize delegate;
- (IBAction)pressButton:(id)sender {
if ([delegate respondsToSelector: #selector( customCollectionViewCell:pressedButton:)])
[delegate customCollectionViewCell: self pressedButton: sender];
}
#end
xib file UICustomCollectionViewCell.xib
make sure the connections from the UICustomCollectionViewCell are connected to the button from the Connections Inspector:
button
-pressButton:
Finally, using this class in your project
Import the class as well as the delegate:
#import "UICustomCollectionViewCell.h"
#interface ViewController () < UICustomCollectionViewCellDelegate>
#end
In this following code, you will use the UICustomCollectionViewCell class instead of UICollectionViewCell:
UICustomCollectionViewCell *cell;
...
[cell setDelegate: self];
...
return cell;
And now the action, or method, that is called when the button is pressed:
- (void)customCollectionViewCell:(UICustomCollectionViewCell *)cell pressedButton:(UIButton *)button {
//action will be here when the button is pressed
}
If you want to find out what indexPath this cell was from:
[collectionView indexPathForCell: cell];
You can't/shouldn't perform navigation jobs in the cell, navigation is not in the cells domain.
What you can try is
1) Use a delegate, setup a delegate and wire it up to the button action, the controller hosting the tableview/collection view can set itself up as the delegate and listen to any events. This controller should be in charge of pushing a new view to the stack using any method you desire.
2) If you hate delegates but love blocks, you can setup a callback block on the cell, its actions could be setup in the cellForRowAtIndex: method in the controller.
Noticed a pattern here? both the above methods are delegating the task from the cell to the controller.
If all fails, just implement didSelectItemAtIndexPath: and stick with it.
Did you try with didSelect method?
- (void)collectionView:(UICollectionView *)collectionView
didSelectItemAtIndexPath:(NSIndexPath *)indexPath{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle: nil];
YourNewViewControllerClass *someViewController = [storyboard instantiateViewControllerWithIdentifier:#"YourNewVCID"];
[self presentViewController:someViewController
animated:YES
completion:nil];
}
Easiest way would be to implement cellForRow.. method, set a tag for your cell/button and react basing on that tag (eg. indexPath.row).
1.custom your button
NouMapButton.h
#import <Foundation/Foundation.h>
#interface NouMapButton : UIButton
#property (nonatomic, readwrite, retain) NSObject *dataObj;
#end
NouMapButton.m
#import "NouMapButton.h"
#implementation NouMapButton
#end
set your button data and target in
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
btn01.dataObj = YOUR_DATA;
[btn01 addTarget:self action:#selector(map:) forControlEvents:UIControlEventTouchUpInside];
then you can get button custom dataObj in sender.dataObj
-(void)map:(NouMapButton *)sender{
MapViewController *nextView = [[MapViewController alloc] init];
nextView.dataObj = sender.dataObj;
//TODO....
}

Passing an object to a class through another class

I have a tableview controller class, a tableviewcell subclass and a uibutton subclass.
I am creating an instance of the unbutton subclass in the tableviewcell subclass and initialize a button in a specific cell position.
Then I am using this cell in the tableview controller class. Also I am trying to add an IBAction to the button. But it can't recognize the object of the uibutton subclass while everything else works fine. What am I doing wrong in the declaration?
tableviewcell.m
#import "tableviewcell.h"
#import "CustomCheckButton.h"
CustomCheckButton *starbtn = [[CustomCheckButton alloc] init];;
starbtn = [[CustomCheckButton alloc]initWithFrame:CGRectMake(243,0, 30, 30)];
tableviewcontroller.m
In the cellForRowAtIndexPath object startbtn won't be recognized:
#import "ScannedProductControllerViewController.h"
#import "imageCellCell.h"
tableviewcell*firstRowCell = (tableviewcell *)cell;
[firstRowCell.prodimage setImage: [UIImage imageNamed:#"test1.jpg"]];
[firstRowCell.label1 setText:#"17.5"];
[firstRowCell.label2 setText:#"Score"];
[firstRowCell.basket setImage: [UIImage imageNamed:#"Basket.jpg"]];
// reference of the home button to the buttonclick method
[firstRowCell.homebtn addTarget:self action:#selector(clickButton:) forControlEvents:UIControlEventTouchUpInside];
// reference of the favorites button to the buttonclick method
[firstRowCell.starbtn addTarget:self action:#selector(clickFavButton:) forControlEvents:UIControlEventTouchUpInside];
CustomCheckButton.h
#import <UIKit/UIKit.h>
#interface CustomCheckButton : UIButton {
BOOL _checked;
}
#property (nonatomic, setter = setChecked:) BOOL checked;
-(void) setChecked:(BOOL) check;
#end
#interface TableViewCell : UITableViewCell
#property (nonatomic, strong) CustomCheckButton *startButton;
#end
#implementation TableViewCell
- (instancetype)init {
self = [super init];
self.startButton = [[CustomCheckButton alloc]initWithFrame:CGRectMake(243,0, 30, 30)];
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self.contentView addSubview:self.startButton];
}
#end
First, why don't you create your cells in the storyboard as a prototype cell or inside of a XIB file.
Then, create an IBAction to the button and respond to it in the cell. Finally, make a protocol for your cell that will inform the delegate when the button is tapped. You can then setup your table view controller as the delegate of the cells and respond to the button tapped. If you need to determine which cell you're working with, you can even pass the cell as a parameter of one of the protocol's methods you use.

Pass data from custom UITableViewCell and UIViewController

I am (fairly) familiar with segues and delegates to pass data between UIViewControllers, but my current situation is slightly different and I cannot get it working. Context: XCode 5 and iOS7 with Objective C.
I have a tableview (dynamic prototypes) that loads a custom cell (from a separate nib) that contains a UILabel and a UISwitch. CustomCell.xib loads its data from CustomCell.h/m. The main content is in ViewController.h/m and in that file I need to know whether the switch value changed (or actually the new value of the UISwitch). Obviously I know this within the CustomCell.h/m files but need to pass them to ViewController.h/m.
I tried using a delegate, but I cannot set a delegate for the UINib instance (in contrast to setting a delegate on a viewcontroller's instance). Also, the custom cell is implemented in the viewcontroller, so it is not pushed like another viewcontroller would be in a navigation stack.
CustomCell.h
#import <UIKit/UIKit.h>
#protocol CustomCellDelegate <NSObject>
- (void)switchControlValueChanged:(UISwitch*)switchControl toNewValue:(BOOL)value;
#end
#interface CustomCell : UITableViewCell
#property (nonatomic, weak) IBOutlet UILabel *titleLabel;
#property (nonatomic, weak) IBOutlet UISwitch *switchControl;
#property (nonatomic, assign) id <CustomCellDelegate> delegate;
- (void)setValueForSwitchControlTo:(BOOL)value;
- (IBAction)changeColorForSwitchControl;
#end
CustomCell.m
- (void)changeColorForSwitchControl // value changed method
{
...
[self.delegate switchControlValueChanged:self.switchControl toNewValue:self.switchControl.on];
}
ViewController.h
#import <UIKit/UIKit.h>
#import "CustomCell.h"
#interface ViewController : UITableViewController <CustomCellDelegate>
...
#end
ViewController.m
- (void)viewDidLoad
{
...
// cannot set a delegate on the cellNib
UINib *cellNib = [UINib nibWithNibName:kCustomCell bundle:nil];
[self.tableView registerNib:cellNib forCellReuseIdentifier:kCustomCell];
}
- (void)switchControlValueChanged:(UISwitch *)switchControl toNewValue:(BOOL)value
{
NSLog(#"Switch changed!"); // this is not getting displayed
}
The right time to set your view controller to be the delegate of your cell would be when you set up other attributes of your cell. You do that in tableView:cellForRowAtIndexPath:.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:kCustomCell forIndexPath:indexPath];
...
cell.delegate = self;
return cell;
}
Side note: registerNib:forCellReuseIdentifier: does exactly what it says, it just registers your nib for reuse. The contents of the nib aren't loaded until the table view decides to do that. It creates copies of the cell contained in the nib as and when required.
One option would be to use NSNotification, not as elegant but would work for your purpose. Every time the switch value changes, you can send a notification in CustomCell.m, something like:
NSDictionary *cellInfo = #{}; // add useful information to identify the cell to this dictionary
[[NSNotificationCenter defaultCenter] postNotificationName:#"SwitchValueChanged" object:nil userInfo:cellInfo];
Then you catch the notification in your ViewController by registering it as an observer with:
-(void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(yourMethod) name:#"SwitchValueChanged" object:nil];
}

iOS: Passing object to custom UITableViewCell to UIViewController via a button action on cell

I have a custom UITableViewCell, which have a button on it, IB linked to a function called:
- (IBAction)clickUse:(id)sender;
In this function, I planned to pass an object from UITableView's data source (an object in a NSMutableArray) to next UIViewController, when the user clicks the button on the UITableViewCell.
I set a property in the custom UITableViewCell, like this:
#property (nonatomic, retain) SomeObject *some_object;
In UITableView's cellForRowAtIndexPath function, I pass the object to the cell:
MyCustomCell *cell = (MyCustomCell *)[tableView dequeueReusableCellWithIdentifier:identifier];
cell.some_object = [self.cellData objectAtIndex:indexPath.row];
At this moment I track the object, it's still here. But in the MyCustomCell cell, the object is gone and assigned to nil. Therefore, the object cannot be passed to next UIViewController.
What did I miss?
Perhaps it's better to use a different approach. You can give each cell button a tag. The tag value could be the row index path.
Your -tableView:cellForRowAtIndexPath: method could include the following:
MyCustomCell *cell = (MyCustomCell *)[tableView dequeueReusableCellWithIdentifier:identifier];
cell.button.tag = indexPath.row
And your -clickUse: method could look like this:
- (IBAction)clickUse:(id)sender
{
UIButton *button = (UIButton *)sender;
SomeObject *object = [self.cellData objectAtIndex:button.tag];
// do stuff with your object on click
}
I recommend creating a delegate protocol to handle this. Define a delegate on the cell. In your cellForRowAtIndexPath: method, set the cell.delegate to the viewController that implements that method. Make sure to nil your delegate in your cell's dealloc and prepareForReuse methods. In my opinion, this is the solution that sets up the cleanest relationships between the objects involved. See example below.
Assigning a button target to an object that is some object other than the superview of the button always seems counterintuitive to me. Or, whenever I work on a codebase where there's a setup like that I find that it eventually gets in the way / confuses things.
Inside CommentCell.h:
#class Comment;
#class SMKCommentCell;
#protocol SMKCommentCellDelegate <NSObject>
#required
- (void)commentCellDidTapShowReplies:(SMKCommentCell *)cell;
- (void)commentCellDidTapUsername:(SMKCommentCell *)cell;
#end
#interface SMKCommentCell : UITableViewCell
#property (nonatomic, strong) Comment *comment;
#property (nonatomic, weak) id<SMKCommentCellDelegate> delegate;
#end
Inside CommentCell.m:
#pragma mark - Actions
- (IBAction)didTapShowReplies:(id)sender
{
if ([self.delegate respondsToSelector:#selector(commentCellDidTapShowReplies:)])
{
[self.delegate commentCellDidTapShowReplies:self];
}
}
- (IBAction)didTapUsername:(id)sender
{
if ([self.delegate respondsToSelector:#selector(commentCellDidTapUsername:)])
{
[self.delegate commentCellDidTapUsername:self];
}
}
Inside your viewController.m:
#pragma mark - SMKCommentCell Delegate
- (void)commentCellDidTapShowReplies:(SMKCommentCell *)cell
{
// Do something
}
- (void)commentCellDidTapUsername:(SMKCommentCell *)cell
{
// Do something
}
Inside cellForRowAtIndexPath:
commentCell.comment = comment;
commentCell.delegate = self;

Resources