I'm subclassing UITableView and its protocol UITableViewDelegate and its "datasource" UITableDataSource, everything works fine but I haven't find the clean way to forward the methods from "delegate" and "datasource" from UITableView.
I have tried to "play" with respondsToSelector:, forwardingTargetForSelector: and forwardInvocation: methods of NSObject but I didn't get anything.
I'll show you some code about I'm trying:
My CustomTableView.h:
#protocol TableViewCustomDelegate <UITableViewDelegate>
- (void) anyCustomMethodDelegate;
#end
#protocol TableViewCustomDataSource <UITableViewDataSource>
- (NSInteger) anyCustomMethod;
#end
#interface TableViewCustom : UITableView <UITableViewDelegate, UITableViewDataSource>
#property (nonatomic, weak) id<TableViewCustomDelegate> myDelegate;
#property (nonatomic, weak) id<TableViewCustomDataSource> myDataSource;
#end
And this is my TableViewCustom.m:
#implementation TableViewCustom
-(BOOL)respondsToSelector:(SEL)aSelector {
if ([self.myDelegate respondsToSelector:aSelector]) {
return YES;
}
return [super respondsToSelector:aSelector];
}
-(id)forwardingTargetForSelector:(SEL)aSelector {
if ([self.myDelegate respondsToSelector:aSelector]) {
return self.myDelegate;
}
return [super forwardingTargetForSelector:aSelector];
}
...
...
- (void) setMyDelegate:(id<TableViewCustomDelegate>)delegate
{
[super setDelegate:self];
_myDelegate = delegate;
}
- (void) setMyDataSource:(id<TableViewCustomDataSource>)dataSource
{
[super setDataSource:self];
_myDataSource = dataSource;
}
..
..
// A method from UITableViewDelegate that I would like to avoid
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
if ([self.delegateDelegate
respondsToSelector:#selector(tableView:viewForHeaderInSection:)]) {
return [self.myDelegate tableView:tableView
viewForHeaderInSection:section];
}
return nil;
}
..
..
#end
I would like to avoid conforms methods just to forward it to "myDelegate".
I think it should be work with responsToSelector: and forwardingTargetForSelector: like I do in my code but it doesn't work. I don't know if I'm doing something wrong or if it is not possible.
Thanks in advance.
Regards.
I might be misunderstanding your intent, but if you just want your subclass to utilize the standard delegate and datasource, just leave those methods alone and hand the subclass whatever delegate/datasource you want. e.g.
MyTableViewSubclass *tableView = [[MyTableViewSubclass alloc] initWithFrame:....];
tableView.datasource = // anything that implements UITableViewDatasource
tableView.delegate = // anything that implements UITableViewDelegate
Then don't do anything special with those properties or protocols in your subclass.
In order to make messaging work, you need to implement following methods:
-(void)forwardInvocation:(NSInvocation *)invocation {
SEL aSelector = [invocation selector];
if ([self.myDelegate respondsToSelector:aSelector]) {
[invocation invokeWithTarget:self.myDelegate];
}
}
-(NSMethodSignature*)methodSignatureForSelector:(SEL)selector {
NSMethodSignature *signature = [super methodSignatureForSelector:selector];
if (nil == signature) {
signature = [self.myDelegate methodSignatureForSelector:selector];
}
return signature;
}
Please see the comments lines for explanation
- (void) setMyDelegate:(id<TableViewCustomDelegate>)delegate
{
//you told you will handle delegate methods to SUPER in our case UITableView
[super setDelegate:self];
_myDelegate = delegate; //You have internally stored the delegate
}
In View For Header
//You are overriding this method
//Table View will not handle this as you have overridden.
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
//You check whether your delegate can do that.
if ([self.delegateDelegate respondsToSelector:#selector(tableView:viewForHeaderInSection:)])
{
return [self.myDelegate tableView:tableView
viewForHeaderInSection:section];
}
//If you delegate is not doing, you need to handle that
// However returning nil. So Instead of UIView the framework gets nil
//You need to handle this if your delegate is not handling this
return nil;
}
Related
There are a huge number of questions relating to this topic but I have not yet come across my use case so here goes.
This is my first couple weeks in OBJ-C so I have no clue what I am doing with some of this stuff...
What I Want
I do not particularly enjoy seeing so many classes in OBJ-C that overload the view controller classes with every and any function on this earth. It looks dirty and feels gross as far as OOP goes. In my use case I don't have a full screen table just a little one to hold 10 things. Therefore it's quite inappropriate to use a full UITableViewController. Instead, I want to have all my table delegate specific methods to be in a UITableView sub-class. NOT in a UITableViewController or a ViewController with a UITableView property. This should be mega simple yet...
The Problem
No matter what I do I cannot seem to get the method cellForRowAtIndexPath to fire. I know enough to know that this stuff relies heavily on the delegate and datasource assignment... however since I have a separate UITableView class that uses the <UITableViewDelegate, UITableViewDataSource> delegations I don't think I should have to do any sort of assignment at all!
What am I gonna write?? self.delegate = self ? or worse, in the ViewController that calls this UITableView class, self.tasksTable.delgate = self.tasksTable ? Eww... gross
Here is what I am doing in code.
The Code
TasksTableView.h
#import <UIKit/UIKit.h>
#interface TasksTableView : UITableView <UITableViewDelegate, UITableViewDataSource> {
NSArray *tasksData;
}
- (NSMutableArray *)getAllTasks;
#end
TasksTableView.m
#import "TasksTableView.h"
#import "NSObject+RemoteFetch.h" //<--I use this to fetch, obvs
#interface TasksTableView ()
#property (nonatomic, strong) NSString *cellId;
#end
#implementation TasksTableView
- (instancetype)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if(self) {
_cellId = #"AllTasksTableCell";
tasksData = [self getAllTasks];
}
return self;
}
#pragma mark - Custom Table Functionality
- (NSMutableArray *)getAllTasks {
#try {
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSString *TASKS_URL = [userDefaults objectForKey:#"tasksUrl"];
NSObject *fetcher = [[NSObject alloc] init];
NSDictionary *response = [fetcher fetchAPICall:TASKS_URL httpRequestType:#"GET" requestBodyData:nil];
return [response objectForKey:#"data"];
} #catch (NSException *exception) {
NSLog(#"could not get tasks, error: %#", exception);
return nil;
}
}
#pragma mark - UITableView DataSource Methods
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [tasksData count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
//<-- NEVER GETS HERE
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:_cellId];
if(cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:_cellId];
}
cell.textLabel.text = [tasksData objectAtIndex:indexPath.row];
return cell;
}
#end
I am also having a hard time figuring out what to set as the datasource. In other languages you would typically set the DataSource object with self.DataSource = [self getAllTasks]... however all the tuturials I have done thus far all tend to use some weird ad-hoc NSArray or NSDictionary to then correlate the index of the table functions with the index of the array or dictionary keys... This confuses me greatly as to why I can't just set the DataSource object and have the table know to iterate over it's data.
My conclusion is that this isn't firing because it thinks the DataSource object is empty and there are no rows? (which it is, but like I said people seem to get Tables to work fine on YouTube doing this)
Thanks.
TasksTableView class is derived from UITableView class & You are implementing the UITableview delegates in the same class. This will not work.
Instead of creating a UITableView subclass. Create TasksTableView class as NSObject sub class. And pass the tableview object from where you added a tableview.
#interface TasksTableView : NSObject <UITableViewDelegate, UITableViewDataSource> {
NSArray *tasksData;
__weak UITableView *tableView;
}
And set that table view delegate to self(TasksTableView object) while init the TasksTableView Class
- (instancetype)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if(self) {
_cellId = #"AllTasksTableCell";
tasksData = [self getAllTasks];
self.tableView.delgate = self;
self.tableView.datasource = self;
}
return self;
}
Now your delegate methods will trigger for that specific tableview
I have a UIView subclass with a delegate property. In the init method, I set
self.delegate = nil.
The view also has a button, so in the init method, I also set the target of the button to be self.delegate, which is nil:
[myButton addTarget:self.delegate action:#selector(buttonAction) forControlEvents:UIControlEventTouchUpInside]
In the UIViewController that sets up my UIView subclass, I call a method in the UIView that sets the UIView's self.delegate to the UIViewController. When I click the button, the change in target seems to be reflected.
I am wondering how this ends up working, as my understanding is that addTarget:action:forControlEvents takes an id as the target, and pointers should be pass by value in Obj-C. Thus, I am pretty confused about why the originally nil-valued pointer was updated after the addTarget method was already called.
The right way to do that is declaring a protocol for your view, which will delegate for button's tap action, i.e.
YourView.h
#class YourView;
#protocol YourViewDelegate
#optional
- (void)customView:(YourView *)view didSelectButton:(id)button;
#end
#interface YourView : UIView
//...
#property (weak, nonatomic) id <YourViewDelegate> delegate;
#end
YourView.m
#interface YourView()
#end
#implementation
- (id)init
{
if (self = [super init]) {
//...
[self setup];
}
return self;
}
- (void)awakeFromNib
{
//...
// setup logic when this view created from storyboard
[self setup];
}
- (void)setup
{
[myButton addTarget:self
action:#selector(buttonTapped:)
forControlEvents:UIControlEventTouchUpInside];
}
- (void)buttonTapped:(id)sender
{
if (self.delegate && [self.delegate respondsToSelector:#selector(customVIew:didSelectButton)] {
[self.delegate customView:self didSelectButton:sender];
}
}
#end
Then, in your view controller implement YourViewDelegate category:
#interface YourViewController()
//...
#end
#implementation
- (void)viewDidLoad
{
[super viewDidLoad];
//...
self.yourView.delegate = self;
}
//...
- (void)customView:(YourView *)view didSelectButton:(id)button
{
//do your stuff
}
#end
Objective-C uses Dynamic binding. Method to invoke is determined at runtime instead of at compile time. Which is why it is also referred to as late binding.
Reference link -
https://developer.apple.com/library/ios/documentation/general/conceptual/DevPedia-CocoaCore/DynamicBinding.html
So what will be the delegate and which method is being called is defined at runtime.
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.
I am trying to implement the concept shown in this example project. My goal is to separate my view controller class and the datasource protocol. Instead of implementing the table view datasource methods in my table view controller class, I try to put it in its own class, and in my view controller, I only call this method to set up my table view:
- (void)setupTableView
{
void (^configureCell)(JVRTodoItemCell *, JVRTodoItem *) = ^(JVRTodoItemCell *cell, JVRTodoItem *todoItem)
{
[cell configureForTodoItem:todoItem];
};
NSArray *todoItems = currentUser.todoItems;
self.todoArrayDataSource = [[JVRArrayDataSource alloc] initWithItems:todoItems withCellIdentifier:TodoCellIdentifier withConfigureCellBlock:configureCell];
self.tableView.dataSource = self.todoArrayDataSource;
[self.tableView registerClass:[JVRTodoItemCell class] forCellReuseIdentifier:TodoCellIdentifier];
}
The data source is separated into its own class:
#interface JVRArrayDataSource ()
#property (copy,nonatomic) NSArray *items;
#property (copy,nonatomic) NSString *cellIdentifier;
#property (copy,nonatomic) void (^configureCellBlock)(id item, id cell);
#end
#implementation JVRArrayDataSource
...
#pragma mark - UITableViewDataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.items.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:self.cellIdentifier forIndexPath:indexPath];
id item = [self itemAtIndexPath:indexPath];
self.configureCellBlock(cell,item);
return cell;
}
The interesting part is that creating the cell based on the identifier (using dequeueReusableCellWithIdentifier:forIndexPath:) seems to be successful, because the correct cell gets allocated, but its labels remain nil. I try setting up my cell using the following method, but the values remain nil (aTodoItem has valid properties):
- (void)configureForTodoItem:(JVRTodoItem *)aTodoItem
{
self.todoItemTitle.text = aTodoItem.title;
self.todoItemPriority.text = [NSString stringWithFormat:#"%d", aTodoItem.priority];
}
I am trying to figure out what could possibly be missing here, but so far, I haven't managed to fix the issue yet, and I'm starting to lose hope. Any help would be appreciated.
UPDATE:
To make it clear, the issue is shown on this picture.
It seems that the cells get created, but its labels don't.
If all you want to do is separate your tableview datasource delegate from the view controller you can create a separate class called TableViewDataSource. Within that class you can manage the datasources and their table view cells; configuring the them in your view controller, but letting the TableViewDataSource manage them.
TDSTableViewDataSource.h
#import <Foundation/Foundation.h>
#protocol TDSTableViewDataSourceDelegate <NSObject>
- (NSString *)fetchCellIdentifierForObject:(id)object;
- (UITableViewCell *)configureCell:(UITableViewCell *)cell usingObject:(id)item;
#end
#interface TDSTableViewDataSource : NSObject <UITableViewDataSource>
#property (strong, nonatomic) NSArray *items;
#property (strong, nonatomic) id<TDSTableViewDataSourceDelegate> delegate;
#end
TableViewDataSource.m
#import "TDSTableViewDataSource.h"
#implementation TDSTableViewDataSource
- (NSArray *)items {
if (!_items) _items = [[NSArray alloc] init];
return _items;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
if ([self.items count]) {
return [self.items count];
} else {
NSLog(#"numberOfSectionsInTableView could not be determined. self.items is nil or empty.");
return 0;
}
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if ([self.items count]) {
return [self.items count];
} else {
NSLog(#"numberOfRowsInSection could not be determined. self.items contains fewer section requested does not contain any items.");
return 0;
}
}
/*
Single dimension Array of items belonging to a UITableView section
The method checks if the cell implements the HZConfigureTableViewCellDelegate, which is required.
The delegate should be the View Controller.
*/
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
id obj = [self.items objectAtIndex:indexPath.row];
UITableViewCell *cell = nil;
if ([self.delegate conformsToProtocol:#protocol(TDSTableViewDataSourceDelegate)]) {
NSString *cellIdentifier = [self.delegate fetchCellIdentifierForObject:obj];
cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (obj)
cell = [self.delegate configureCell:cell usingObject:obj];
}
return cell;
}
#end
This class and the protocol will essentially allow you to fetch and configure UITableViewCell's and not have to implement the protocols into your View Controller.
Inside your view controller, you create a datasource property using the protocol above.
#import "TDSViewController.h"
#import "TDSTableViewDataSource.h"
#interface TDSViewController () <UITableViewDelegate, TDSTableViewDataSourceDelegate>
#property (strong, nonatomic) TDSTableViewDataSource *dataSource; // UITableView data source.
#property (weak, nonatomic) IBOutlet UITableView *tableView;
#end
#implementation TDSViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.tableView.dataSource = self.dataSource;
self.dataSource.delegate = self;
}
#pragma mark - UITableView methods
-(NSString *)fetchCellIdentifierForObject:(id)object {
// Check if this is an event or a Reminder class.
if ([object isKindOfClass:[UITableViewCell class]]) {
// Return the cell identifier for this particular cell.
return #"com.myapp.defaultcell";
}
return #"blankcell";
}
- (UITableViewCell *)configureCell:(UITableViewCell *)cell usingObject:(id)item {
UITableViewCell *configuredCell = cell;
// Check if this is an event or a reminder.
if ([item isKindOfClass:[UITableViewCell class]]) {
// Configure the cell to present what data we want here...
}
return configuredCell;
}
#end
This is a complete example project. You can use this to configure any kind of cell you want, without having to add the datasource methods to your view controllers.
The view controller is used by the ConfigureTableViewCellDelegate protocol to configure the UITableViewCell's and use them in the Table View. Since the code is segregated now, the TableViewDataSource class now handles presenting the data to the table view. The View Controller is simply used to configure the cell's. This allows you to use custom UITableViewCells' on each ViewController if you want, and not have to deal with implementing the data sources each time.
UPDATED
Provided a better example, a complete project template.
In the ViewDidLoad register the nib, it fix the problem :)
-(void)viewDidLoad
{
[self.leftTableView registerNib:[UINib nibWithNibName:NIB_FILE bundle:nil] forCellReuseIdentifier:CELL_IDENTIFIER];
}
After hours of digging, I've managed to solve the issue (for now), by changing my custom cell's outlets to strong properties, and initializing them in the cell's init method:
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
self.todoItemTitle = [[UILabel alloc] init];
self.todoItemPriority = [[UILabel alloc] init];
}
return self;
}
This is very strange, since I thought that creating my views in storyboard, this should be automatically taken care of, and I've never had to do this manually before.
I know how to call a method of one class to another class. However This time its not working for me and its just driving me nuts. Below is my code
MenuPageCell.h
#import <UIKit/UIKit.h>
#class MenuPageViewController;
#interface MenuPageCell : UITableViewCell{
NSInteger m_cellIndex;
MenuPageViewController *m_parentViewController;
}
#property(nonatomic, assign) NSInteger m_cellIndex;
#property(nonatomic, strong) MenuPageViewController *m_parentViewController;
-(IBAction) addToCart;
#end
MenuPAgeCell.m
#import "MenuPageCell.h"
#import "MenuPageViewController.h"
#implementation MenuPageCell
#synthesize m_cellIndex;
#synthesize m_parentViewController;
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
}
return self;
}
-(IBAction) addToCart
{
NSLog(#"Add To cart = %d",self.m_cellIndex);
[m_parentViewController addItemToCart:self.m_cellIndex];
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
MenuPageViewController.m
-(void) addItemToCart:(NSInteger)aIndexItem
{
NSLog(#"In Add to Cart method");
}
Now, This code works fine for non ARC Used project but its not working for me. I know it should be silly mistake but I'm unable to figure it out.
Thanks & regards
Mayur
Referencing ViewController from a cell is a design flaw, consider using delegate instead. But if you really need the ViewController property, make it weak instead of strong because currently you end up with retain cycle.
#protocol MenuPageCellDelegate<NSObject>
- (void)addItemToCart:(NSInteger)aIndexItem;
#end
#interface MenuPageCell : UITableViewCell {
NSInteger m_cellIndex;
}
#property(nonatomic, assign) NSInteger m_cellIndex;
#property(nonatomic, weak) id<MenuPageCellDelegate> delegate;
-(IBAction) addToCart;
#end
#implementation MenuPageCell
...
-(IBAction) addToCart
{
NSLog(#"Add To cart = %d",self.m_cellIndex);
if ([self.delegate responsToSelector:#selector(addItemToCart:)]) {
[self.delegate addItemToCart:self.m_cellIndex];
}
}
...
#end
Add MenuPageCellDelegate to the list of implemented protocols of MenuPageViewController and (if it's implementing UITableViewDataSource protocol) in the tableView:cellForRowAtIndexPath: method write cell.delegate = self; instead of cell.m_parentViewController = self;
initialize your m_parentViewController in viewDidLoad method.
such like
m_parentViewController = [[yourViewControllerName alloc] init];
and then call
[m_parentViewController addItemToCart:self.m_cellIndex];
in the cellforrow method of your tableview add a selector to the cell button and also set the tag equal to the indexpath. now in the selector just distinguish between the different cells with the help of sender.tag.