Access UIViewController methods from within subview - ios

I subclassed UITableViewCell and initialized it in a UIView. From the TableView, I need to call a method in the parent ViewController (to perform segues, etc.).
Now, since the ViewController cannot be directly passed to its subviews (in respect of MVC principles), I created a delegation pattern, adding a protocol to the UITableView, and implementing the protocol as delegate in the UIVIewController.
The problem is that since the TableView is instantiated from the UIView, which is a subview of the Viewcontroller, I cannot assign the ViewController as delegate in the cellForRowAtIndexPath method since "self" would point to the View not the ViewController.
If I move all the table methods to the ViewController and instantiate the table from there, everything work as I wish. But that would not allow me to reuse the code in other ViewControllers.
CustomViewController.h
#import "CustomTableViewCell.h"
#interface CustomViewController : UIViewController<CustomTableViewCellDelegate>
CustomViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
// Create a sub-view where the custom table will appear
_customUIView = [[CustomUIView alloc] initWithFrame:CGRectMake(0, 60, self.view.frame.size.width, self.view.frame.size.height-60)];
[self.view addSubview:_customUIView];
[_customUIView populateTheView];
}
CustomUIView.m
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *cellIdentifier = [NSString stringWithFormat:#"cell%ld",(long)indexPath.row];
CustomTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
cell = [[CustomTableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
cell.delegate = self; // << THIS WILL NOT WORK AS IT WOULD POINT TO THE View, RATHER THAN THE ViewController
}
}
CustomTableViewCell.h
#protocol CustomTableViewCellDelegate <NSObject>
-(void)performSegue;
#end
#interface CustomTableViewCell : UITableViewCell
#property(weak, nonatomic) id<CustomTableViewCellDelegate>delegate;
#end
CustomTableViewCell.m
if ([self.delegate respondsToSelector:#selector(performSegue)]) {
[self.delegate performSegue];
}

The best solution I could find is using notifications.
In your viewController set up the observer in the viewWillAppear method. You will also need to remove it in the viewWillDisappear method or the app will crash:
// In the parent view controller
- (void)viewWillAppear:(BOOL)animated {
// Add observers to perform an action
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(imageTappedMessageReceived:) name:#"imageTapped" object:nil];
}
-(void) imageTappedMessageReceived:(NSNotification *)notification {
NSLog(#"The image was tapped");
}
- (void)viewWillDisappear:(BOOL)animated {
NSLog(#"View controller will disappear");
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"imageTapped" object:nil];
}
In your subview issue the notification when you need to (button pushed or other logic your have):
// In the subview
[[NSNotificationCenter defaultCenter] postNotificationName:#"imageTapped" object:self userInfo:tempDictionary];
The "imageTappedMessageReceived" method in the viewController will be fired every time the "imageTapped" notification is called in the subview.

Related

How to call parent table view method from a cell?

I have a custom cell with a label that should trigger a method of parent/table view controller.
Even though XCode provides me that method in autocomplete suggestions, it throws an error when I tap on that particular label:
UITableViewWrapperView showUserProfile unrecognized selector sent to instance
This is my code:
#implementation ItemTableViewCell
#synthesize item;
- (void)awakeFromNib
{
self.authorLabel.userInteractionEnabled = YES;
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(showUserProfile)];
[self.authorLabel addGestureRecognizer:tap];
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected:selected animated:animated];
}
- (void)showUserProfile
{
id parentView = [self superview];
[parentView showUserProfile];
}
#end
Thanks!
it is not the good way to passing some actions to the tableview. You must use a delegate between the custom class UITableViewCell and your controller.
In your custom UITableViewCell.h
#protocol myUITableViewCellDelegate
#interface myUITableViewCell : UITableViewCell
#property (nonatomic, assign) id<myUITableViewCellDelegate> delegate;
#end
#protocol myUITableViewCellDelegate <NSObject>
-(void) cellDidTap:(myUITableViewCell*) sender
#end
In your custom UITableViewCell.m
...
- (void)showUserProfile
{
[self.delegate cellDidTap:self];
}
...
In your controller
-(UITableViewCell*) tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexpath
{
......
cell.delegate = self
......
}
-(void) cellDidTap:(myUITableViewCell*) sender
{
[self showUserProfile];
}
There is a difference between a view and a view controller. A view controller's class is UIViewController (or a subclass) and it has a property view of class UIView which it controls and which is the "visible part" of the view controller.
In the method
- (void)showUserProfile
{
id parentView = [self superview];
[parentView showUserProfile];
}
you call showUserProfile on the table view cell's superview but not on the view controller. As you don't know the internal implementation of a UITableView you cannot even be sure that the cell's superview is the same as your table view controller's view. In fact, it is not. Because as you can see from the error log the table view itself has a subview of class UITableViewWrapperView which contains all the cells. But this view doesn't know anything about the method you declared in your table view controller. That is why the app crashes.
For calling a method in your table view controller you can either declare a delegate as suggested by tdelepine or you can add an action to your button in your table view controller's tableView:cellForRowAtIndexPath: method right after dequeuing the cell:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
ItemTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"yourID"];
[cell.yourButton addTarget:self action:#selector(showUserProfile) forControlEvents:UIControlEventTouchUpInside];
// customize other properties of your cell
return cell;
}
This would be the easiest way to go in my opinion.
You can add the controller (your ViewController that hosts UITableView for example) to your customized cell. Then, you use something like:
if ([self.controller respondsToSelector:#selector("afunctioname")]) {
[self.controller performSelector:#selector("afunctionname")];
}
Your customizedCell: MyTableViewCell header
#property (nonatomic, assign) id controller;
In the method cellforRow;
MyTableViewCell *aCell = ...
aCell.controller = self
..
return aCell;
You can add parameters to this selector if the 'afunctioname' takes parameters. If you have multi parameters, use NSDictionary as parameter.

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];
}

UITableViewCells not displaying on screen after intercepting calls to UITableViewDelegate methods

While playing around with the iOS 7.1 SDK (on XCode 5.1.1), I am trying to intercept the calls to the delegate methods of UITableViewController by creating a class that implements the UITableViewDelegate and UITableViewDataSource protocol. I am expecting the tableView to delegate the calls to MyTableViewDelegate which does some customization and delegate back to HomeViewController.
Through debugging, I found tableView:cellForRowAtIndexPath: method and tableView:willDisplayCell:forRowAtIndexPath: of the HomeViewController, the drawRect: method of my custom UITableViewCell class was called, and the cells have right content. **So it seems to me that the cells are drawn to somewhere. But just not displaying on the screen (The table row divider lines were displayed on the screen though).**I wonder if anyone knows why it doesn't work. Below is the code snippet. Thanks in advance for your insights.
HomeViewController.h
// HomeViewController.h
#interface HomeViewController : UITableViewController <UITableViewDelegate, UITableViewDataSource> {
UITableViewController *tableViewController;
UITableView *tableView;
}
#property(nonatomic, strong) MyTableViewDelegate *myDelegate;
#end
HomeViewController.m
// HomeViewController.m
- (void)loadView {
[super loadView];
tableViewController = [[UITableViewController alloc] initWithStyle:UITableViewStylePlain];
[self addChildViewController:tableViewController];
[tableViewController didMoveToParentViewController:self];
myDelegate = [[MyTableViewDelegate alloc] init];
[myDelegate setDelegate:self];
tableView = [[tableViewController tableView] retain];
[tableView setFrame:[[self view] bounds]];
[tableView setAutoresizingMask:UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth];
[tableView setDelegate:myDelegate];
[tableView setDataSource:myDelegate];
[tableView setClipsToBounds:NO];
[[self view] addSubview:tableView];
}
- (UITableViewCell *)tableView:(UITableView *)table cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MyEntry *entry = [self entryAtIndexPath:indexPath];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cellClass"];
if (cell == nil) cell = [[[cellClass alloc] initWithReuseIdentifier:#"cellClass"] autorelease];
[self configureCell:cell forEntry:entry];
return cell;
}
MyTableViewDelegate.h
// MyTableViewDelegate.h
#interface MyTableViewDelegate : NSObject <UITableViewDelegate, UITableViewDataSource> {
}
#property (nonatomic, assign) id delegate;
#end
MyTableViewDelegate.m
// MyTableViewDelegate.m
#implementation MyTableViewDelegate
#synthesize delegate;
// Display customization
#pragma mark - Delegate
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([delegate respondsToSelector:#selector(tableView:willDisplayCell:forRowAtIndexPath:)]) {
<...some customization ....>
[delegate tableView:tableView willDisplayCell:cell forRowAtIndexPath:indexPath];
}
}
...
// and all other required and optional methods declared by the UITableViewDelegate and UITableViewDataSource protocol.
EDITED:
if I set the delegate and dataSource of tableView to the HomeViewController instance rather than MyTableViewDelegate instance, it works just fine.
// HomeViewController.m
- (void)loadView {
[super loadView];
...
[tableView setDelegate:self];
[tableView setDataSource:self];
...
EDITED:
Normally we can just do all the work in HomeViewController. However, in my case, I am trying to see if it's possible to insert a layer between HomeViewController and tableView. I have a special use case where I would expect HomeViewController to not be able to override the customization implemented in MyTableViewDelegate (intended to be a library). Hence, it's not a good idea to implement MyTableViewDelegate as a base class and make HomeViewController derive from it.
As far as I can see, the only connection between the HomeViewController and tableView is that tableView is managed as a subView by HomeViewController and tableViewController is also a childViewController of HomeViewController. Would the additional layer (ie. MyTableViewDelegate) break this connection since MyTableViewDelegate delegates every method call back to HomeViewController? If so, how does it break? Again, without the MyTableViewDelegate layer, the above code works just fine.

How to create a Common Custom TableView Class?

I want to create a custom tableView class, Which can be used in any view Controller. I have to just create tableview object and set an array and frame of tableview . Then this tableview will be add as subview on my view. and also give me a click event.
I just want to avoid writing tableview datasource and delegate method in every viewController class.
Take a viewController or tableviewController class and code all the delegates and data source methods there. now in you view controller where you want to make it as a subview call the tableview class and add it as a subview.
EX:
TableviewContrller *libaray =[TableviewContrller new];
[libaray willMoveToParentViewController:self];
[self.view addSubview:libaray.view];
[self addChildViewController:libaray];
To hide write this code in your tableview controller class
[self.view removeFromSuperView];
As you are using a reusable class you need to send the array information to that class. along with it it will be better to send either class name or setting tag value to tableview
So in your tableview class write this
-(id)initWithInformationArray :(NSMutableArray *)dataArray andTagValueforTableview :(int) tagValue
{
self = [super init];
if (self != nil)
{
NSLog(#"%#", dataArray);
}
return self;
}
Now sub viewing will be like this
TableviewContrller *libaray =[[TableviewContrller alloc]initWithInformationArray:YOURARRAY andTagValueforTableview:TAGVALUE];
[libaray willMoveToParentViewController:self];
[self.view addSubview:libaray.view];
[self addChildViewController:libaray];
Hope this will help.
May be you can use UITableViewController.
UITableViewController is a subclass of UIViewController, when you create a subclass of UITableViewController, the template has the usual methods of tableview datasource and delegate methods.
You'll need to create a custom class and create your own delegate in that class for UITableView. Now whenever you create a UITableView assign that custom class as the class for UITableView.
If you don't know how to create custom delegates then check below links:
http://www.alexefish.com/post/522641eb31fa2a0015000002
http://ios-blog.co.uk/tutorials/quick-tip-create-your-own-objective-c-delegate-protocol/
Hope this will help you :)
You can create BaseTableView class.
#interface BaseTableView : UITableView <UITableViewDelegate, UITableViewDataSource>
{
NSArray* listObject;
}
#property (nonatomic, retain) NSArray *listObject;
-(id) initWithFrame:(CGRect)frame style:(UITableViewStyle)style;
#end
#implementation BaseTable
#synthesize listObject;
-(id) initWithFrame:(CGRect)frame style:(UITableViewStyle)style
{
if(self = [super initWithFrame:frame style:style])
{
self.dataSource = self;
self.delegate = self;
}
return self;
}
-(void)setListObject:(NSArray *)listObjectRef
{
[listObject release];
listObject = [listObjectRef retain];
[self reloadData];
}
-(void) dealloc
{
[listObject release];
[super dealloc];
}
#end
Inherit this class for specific use and override following methods according to needs
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
In your ViewController class use following code
SpecificTableView *table = [[SpecificTableView alloc] init];
[table setListObject:((FRFTReportList*)obj)];
Hopefully this will help.

IOS drop down with UITextField and UITableView

I have created a drop down using UITextField and UITableView. When the user selects a textfield then a tableview is displayed as a drop down. I had set the tableview delegate and datasource in another class.
Now my issue is i wanted to get the text of the selected row in tableview on to the textfield i.e I want to send the tableview row text back to view controller(which consists of textfield) when user selects a row in tableview.
Thanks in advance.
add below code in In view did load of class in which you have added UITextField
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(addValueToTextField:) name:#"addValueToTextFiel" object:nil];
-(void)addValueToTextField:(NSNotification *) notification{
NSString* text = [notification text];
yourTextField.text = text;
}
And in Didselect delegate of UITable view of other class you have to add following code
UITableViewCell *selectedCell =[tableView cellForRowAtIndexPath:indexPath];
[[NSNotificationCenter defaultCenter] postNotificationName:#"addValueToTextField" object:nil userInfo:selectedCell.Text];
Or alternate of its is you can use custom delegates
Create custom delegate for controller with UITableView
ItemsList .h file
#protocol ItemsListDelegate : NSObject
#optional
- (void)itemSelected:(int)num withTitle:(NSString *)title;
#end
#interface ItemsList : UITableViewController{
id <ItemsListDelegate> delegate;
...
}
ItemsList .m file
#import "ItemsList.h"
#implementation ItemsList<UITableViewDataSource, UITableViewDelegate>
#synthesize delegate;
.....
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
[delegate itemsSelected:[indexPath row] withTitle:[items objectAtIndex:[indexPath row]]];
}
.....
And in ViewController with your field set in .h
#import "ItemsList.h"
#interface ViewWithField<ItemsListDelegate>{
ItemsList *itemsList;
}
....
And in .m file
.....
- (void)viewDidLoad
{
itemList.delegate = self;
}
- (void)itemSelected:(int)num withTitle:(NSString *)title{
self.textField.text = title;
}
.....
Something like this. I don't check errors in this code. But look at this way.
Or use NotificationCenter, but this way is more correct.
Sorry for my ugly English.

Resources