REUSE a UITableView with its UI/delegate methods - ios

I have 2 UIViewControllers, the 2 ones are containing EXACTLY the SAME UITableView(with its custom cells and delegate methods).
My question is their any way to "centralize" the UITableView UI and code(datasource and delegates), so that I just have to modify in one file instead of 2 .

following up on my comment, the table view in the xib in your father vc and the delegate methods in your father vc are just in the same place because you chose it to be like that, the table view and the delegate methods are actually quite detached.
so create a new object, say FatherTableController which implements UITableViewDatasource and UITabelViewDelegate and copy those methods out of your FatherViewController into this FatherTableController
now in your FatherViewController, go like
FatherTableController tableController = [FatherTableController new]; //should be a property or a singleton
self.tableview.delegate = tableController;
self.tableview.datasource = tableController;
now you can do that in both your separate vc's that use the same table, and even use the exact same table contoller between the two views if you share it in some way (possibly via a singleton pattern, which can be useful for sharing state between the two view controllers)

Solution:
#interface FatherViewController : UIViewController <UITableViewDataSource,UITableViewDelegate>
#property (strong, nonatomic) IBOutlet UITableView *parentTableView;
#implementation FatherViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.parentTableView.delegate=self;
self.parentTableView.dataSource=self;
}
//declare the delegate / datasource methods
--------------------- CHILD VIEW CONTROLLER ---------------------
#interface ViewController : FatherViewController
#property (strong, nonatomic) IBOutlet UITableView *tableView;
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.delegate=self;
self.tableView.dataSource=self;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
return [super tableView:tableView cellForRowAtIndexPath:indexPath];
}
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return [super numberOfSectionsInTableView:tableView];
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return [super tableView:tableView numberOfRowsInSection:section];
}

Related

Custom UITableViewCell without dequeueReusableCellWithIdentifier

So I have a custom UITableViewCell:
TestTableViewCell.h
#import <UIKit/UIKit.h>
#interface TestTableViewCell : UITableViewCell
#property (strong, nonatomic) IBOutlet UILabel *testCellLabel;
#end
TestTabelViewCell.m
#import "TestTableViewCell.h"
#implementation TestTableViewCell
-(id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
_testCellLabel = [[UILabel alloc] init];
}
return self;
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
#end
And then I have view controller with a table view that uses the custom table view cell. However this issue is that I don't want to use dequeueReusableCellWithIdentifier within the cellForRowAtIndexPath. I instead want to have an array of cells.
ViewController.h
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>
#end
ViewController.m
#import "ViewController.h"
#import "TestTableViewCell.h"
#interface ViewController ()
#property (weak, nonatomic) IBOutlet UITableView *tableView;
#property (strong, nonatomic) NSArray *myTableViewCells;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (NSArray *)myTableViewCells {
TestTableViewCell *cell1 = [[TestTableViewCell alloc] init];
cell1.testCellLabel.text = #"one";
cell1.backgroundColor = [UIColor blueColor];
TestTableViewCell *cell2 = [[TestTableViewCell alloc] init];
cell2.testCellLabel.text = #"two";
cell1.backgroundColor = [UIColor greenColor];
if (!_myTableViewCells) {
_myTableViewCells = #[cell1, cell2];
}
return _myTableViewCells;
}
#pragma mark - UITableView delegate functions
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.myTableViewCells.count;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
TestTableViewCell *cell = self.myTableViewCells[indexPath.row];
return cell;
}
#end
The problem is that there is no testCellLabel appearing in the table view cell. I know the cells are there, because I set their background colour.
After talking to a few people, apparently I need to do some sort of loading from the XIB or the NIB for the UI to load properly? Even though the label is defined in the cell in the storyboard.
I know this is going against the norm and that Apple really wants you to use dequeueReusableCellWithIdentifier, but I know it won't work in the situation I need it in. I have done the reading on that much so please don't just tell me to use it. This code example is just very basic for example sake and ease of use.
Any help would be greatly appreciated.
TestTableViewCell *cell1 = [[TestTableViewCell alloc] init];
Creates a new TestTableViewCell object and does not instantiate it from the storyboard like you're thinking it does. Therefor all outlets created will be nil and simply not show up. The fact that you can set the background colour is not evidence that your implementation works.
You need to use dequeueReusableCellWithIdentifier. You say that it doesn't work for your problem.. show me how it doesn't work and I will tell you why you're wrong.
Edit
I see in your comments you say your cell needs a custom setter. Well, when you use dequeueReusableCellWithIdentifier you can do all setup work in awakeFromNib (If using a xib file) OR initWithCoder if you are using the storyboard.
You can create cell without dequeueResableCellWithIdentifer.
[[UITableViewCell alloc]initWithStyle:<#UITableCellStyle#> resueIdentifier:<#(nullable *NSString)#>]

Making Main View Controller Delegate of UITableViewDataSource

I'm new to iOS development. My Main View Controller doesn't display any cells from its table view. I was trying to set it up to display just one cell for now. The main view controller is a subclass of the UIViewController, and has a table view with the prototype cell as well. So my MainViewController.h file looks like below:
#import <UIKit/UIKit.h>
#interface MainViewController : UIViewController <UITableViewDataSource>
#property (weak, nonatomic) IBOutlet UIBarButtonItem *sidebarButton;
#end
I made the MainVewController a delegate of the UITableViewDataSource, is that the right idea here? My MainViewController.m looks like below:
#import "MainViewController.h"
#import "SWRevealViewController.h"
#interface MainViewController ()
#end
#implementation MainViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.title = #"Home";
SWRevealViewController *revealViewController = self.revealViewController;
if(revealViewController) {
[self.sidebarButton setTarget: self.revealViewController];
[self.sidebarButton setAction: #selector(revealToggle:)];
[self.view addGestureRecognizer:self.revealViewController.panGestureRecognizer];
}
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 1; //change to number of post objects in array (array.count)
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"basicCell" forIndexPath:indexPath];
return cell;
}
#end
I don't understand what I'm doing wrong here. Shouldn't my MainViewController's Table View be properly displaying the cell? Thoughts?
You should use in viewDidLoad:
[self.tableView setDelegate:self];
[self.tableView setDataSource:self];
I don't see the Table View outlet. Did you forget to connect the Table View from interface builder to your view controller header file? After doing that you should also assign the delegate and data source properties of the table view to "self".
Your class just conforms to <UITableViewDataSource>
you should also conform UITableViewDelegate do it this way.
#interface MainViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>
You missed setting the delegate and dataSource
It can be done in 2 ways:
using code:
[self.tableView setDelegate:self];
[self.tableView setDataSource:self];
put this code in viewDidLoad:
using storyboard: ctrl drag from tableView to your ViewController and set it as delegate and dataSource. see the Image below.
EDIT:
Why don't we need to connect the table's cell as well?
Ans: Table cell is returned from dataSource method tableView:cellForRowAtIndexPath:. This cell is displayed in the tableView. So we don't connect it in the storyboard. However we can configure it in the storyboard.
What's the difference between data source and delegate?
Ans: Delegate: The delegate is an object that is delegated control of the user interface for that event.
Datasource: A data source is like a delegate except that, instead of being delegated control of the user interface, it is delegated control of data.
For more information see Delegates and Data Sources and this answer.

Reload tableview inside a UIview

My table view is inside a UI View, this has been done writing codes, now i am trying to reload the data in table view but the table view is not refreshing.
The UIview and the table view declaration are as follows:
#interface
{
IBOutlet UITableView *tabView;
}
#property (nonatomic, strong) IBOutlet * tabView
#property (nonatomic, strong) UIView * myView; // in .m file
Ok there could be various mistakes which lead to your problem. First of all do you implement the datasource and delegate methods ?
To do so you should declare your header like this:
#interface MyClass : UIViewController<UITableViewDelegate, UITableViewDataSource> {}
Second you should hook up your tableView with those delegate/datasource methods. To do so drag&drop it in the InterfaceBuilder or in you viewDidLoad method write this:
tableView.delegate = self;
tableView.dataSource = self;
Now make sure to implement all the necessary methods:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {}
If you done all this you should update your tableView like so:
[tableView reloadData];
Also check if you connected your IBOutlet UITableView *tableView; in the Interface Builder.
Provided you have linked up the delegate and the data source methods of the table view correctly, you could just do :
[self.tableView reloadData];
Make sure you have done :
self.tableView.dataSource = self;

How to set up UITableView within a UIViewController created on a .xib file

I have a class like this:
#interface ExerciseLogDetails : UIViewController<UIActionSheetDelegate, UITableViewDelegate, UITableViewDataSource> {
where I am trying to display some elements followed by a UITextView. The UITextView element is created on Interface Builder. When executing this code:
- (void)viewDidLoad {
self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
tableView.dataSource = self;
tableView.delegate = self;
[self.view addSubview:self.tableView];
}
a table shows, but not the one I configured in Interface Builder. It is completely blank and unformatted. How can I access my table and populate it progrmmatically with data?
Thank you!
Several of the tips on this thread helped me create this. I am going to offer some more complete code files in order to help others as well:
Step 1. Drag your UITableView onto your View Controller either in Storyboards or XIBs. In my example I am using a story board.
Step 2: Open your ViewController (in my case its just DefaultViewController) and add the two delegates for the UITableView: UITableViewDelegate and UITableViewDataSource. Also add a simple data source for population and the UITableView IBOutlet.
DefaultViewController.h
#import <UIKit/UIKit.h>
#interface DetailViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
#property (strong, nonatomic) IBOutlet UITableView *tableView;
#property (strong, nonatomic) NSMutableArray *newsArray;
#end
Step 3: Open your implementation file (DefaultViewController.m) and add the following:
#import "DetailViewController.h"
#interface DetailViewController ()
- (void)configureView;
#end
#implementation DetailViewController
#synthesize newsArray;
#synthesize tableView;
#pragma mark - Managing the detail item
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self configureView];
}
- (void)configureView
{
// Update the user interface for the detail item.
self.newsArray = [[NSMutableArray alloc] initWithObjects:#"Hello World",#"Goodbye World", nil];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark UITableViewDelegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// typically you need know which item the user has selected.
// this method allows you to keep track of the selection
}
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView
editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
{
return UITableViewCellEditingStyleDelete;
}
// This will tell your UITableView how many rows you wish to have in each section.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.newsArray count];
}
// This will tell your UITableView what data to put in which cells in your table.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifer = #"CellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifer];
// Using a cell identifier will allow your app to reuse cells as they come and go from the screen.
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifer];
}
// Deciding which data to put into this particular cell.
// If it the first row, the data input will be "Data1" from the array.
NSUInteger row = [indexPath row];
cell.textLabel.text = [self.newsArray objectAtIndex:row];
return cell;
}
#end
Step 4: Goto your Storyboards or XIB and select your UITableView and drag the datasource and delegate outlets onto your DefaultViewController to wire them up. Also you will need to wire up the Referencing Outlet for the UITableView to your IBOutlet tableView object you created in your header file.
Once this is finished you should be able to run it and the sample data will be in place.
I hope this along with the other tips on this thread will help others setup a UITableView from scratch on a ViewController.
If you configured a tableView in IB you shouldn't also create one programmatically, you should create #property (nonatomic, retain) IBOutlet UITableView *tableView; and connect it to the tableView you configured in IB.
Try to set a breakpoint in the tableView's
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
delegate method to see if this method get called.
From Apple UITableView docs:
A UITableView object must have an object that acts as a data source
and an object that acts as a delegate; typically these objects are
either the application delegate or, more frequently, a custom
UITableViewController object. The data source must adopt the
UITableViewDataSource protocol and the delegate must adopt the
UITableViewDelegate protocol. The data source provides information
that UITableView needs to construct tables and manages the data model
when rows of a table are inserted, deleted, or reordered. The delegate
provides the cells used by tables and performs other tasks, such as
managing accessory views and selections.
As u can see if u don't set a dataSource to your tableView, the tableView will not know how and what to display, so nothing will happen.
You can set one by calling tableView.dataSource = self; or in IB drag from your tableView to the file's owner (that is your viewController that must implement the UITableViewDataSource Protocol)
There are two methods in the UITableViewDataSource protocol that your dataSource must implement:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
and
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath
If u won't implement those methods u will get a compiler warnings.
You can have more control on how the tableView will look if you implement the UITableViewDelegate protocol - like row/header/footer height, selections and more...
From Apple UITableView docs:
UITableView overrides the layoutSubviews method of UIView so that it
calls reloadData only when you create a new instance of UITableView or
when you assign a new data source. Reloading the table view clears
current state, including the current selection. However, if you
explicitly call reloadData, it clears this state and any subsequent
direct or indirect call to layoutSubviews does not trigger a reload.
ReloadData get called when the tableView is created or when you assign a new dataSource (or when you explicitly call it of course..).
This is when the tableView needs to know what to display (how many sections?, how many rows?, and which cell to display?) - So this is when numberOfRowsInSextion method called.
Like Eyal said, you shouldn't create a UITableView programmatically and in the Interface Builder. Instead, it is much easier to just create one in Interface Builder and assigns it's delegate and datasource properties to File's Owner in IB.
Once you've done this, you don't need to create one programmatically and there's no need for a #property for the tableview.
Instead, you could have your UIViewController's class files look like this:
// YourViewController.h
#import <UIKit/UIKit.h>
#interface YourViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
#property (strong, nonatomic) NSArray *yourData;
#end
Where the NSArray will contain your data that you will enter into the table programmatically. You may use other data classes too like an NSDictionary depending on what data you have and how you want it to sit in the table.
// YourViewController.m
#import "YourViewController.h"
#implementation YourViewController
#synthesize yourData;
- (void)viewDidLoad
{
[super viewDidLoad];
// Here you are creating some data to go in your table by inputting it as an array.
// I just used some basic strings as an example.
NSArray *array = [[NSArray alloc] initWithObjects:#"Data1", #"Data2", #"Data3", nil];
// Copying the array you just created to your data array for use in your table.
self.yourData = array;
}
- (void)viewDidUnload
{
[super viewDidUnload];
self.yourData = nil;
}
#pragma mark Table View Data Source Methods
// This will tell your UITableView how many rows you wish to have in each section.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [self.yourData count];
}
// This will tell your UITableView what data to put in which cells in your table.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifer = #"CellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifer];
// Using a cell identifier will allow your app to reuse cells as they come and go from the screen.
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifer];
}
// Deciding which data to put into this particular cell.
// If it the first row, the data input will be "Data1" from the array.
NSUInteger row = [indexPath row];
cell.textLabel.text = [yourData objectAtIndex:row];
return cell;
}
#end
This should just create a simple UITableView with three entries of data that you have entered programmatically.
If you have any problems or questions just post a comment. :)
Hope this helps.

How can I subclass a UITableView?

I want to subclass a UITableView as I want to create a reusable table view component in my application.
The idea is instead of using a delegate for say cellForRowAtIndexPath I want the table view itself to get that call.
I don't think I want a UITableViewController as this UITableView that I want to build has to live in various UIViewControllers (and these UIViewController might have UITableViews of their own).
I subclassed my UITableView as:
#interface ShareUITableView : UITableView
but none of its methods get called.
My ShareUITableView is created via the NIB by setting the custom class to ShareUITableView. I have verified in code that a ShareUITableView is instantiated.
My UITableView does not delegate to its view controller, so that's not the problem.
Any ideas?
If I understood you, you need this class declaration:
#interface ShareUITableView : UITableView <UITableViewDataSource>
And then, in your class constructor, you should assign the instance itself as its own datasource:
- (id)init
{
//...
self.dataSource = self;
//...
}
Of course, the class will have to adopt the protocol.
Good luck!
MyTableView.h
// MyTableView.h
// This overrides the UITableViewDataSource with your own so you can add any methods you would like.
#protocol MyTableViewDataSource <UITableViewDataSource>
#required
// This is where you put methods that are required for your custom table to work (optional)
- (int)myRequiredMethod;
#optional
// This is where you put methods that are optional, like settings (optional)
#end
// This overrides the UITableViewDelegate with your own so you can add any methods you would like.
#protocol MyTableViewDelegate <UITableViewDelegate>
#required
// This is where you put methods that are required for your custom table to work (optional)
#optional
// This is where you put methods that are optional, like settings (optional)
#end
// Make sure you add UITableViewDelegate and UITableViewDataSource implementations.
#interface MyTableView : UITableView <UITableViewDelegate, UITableViewDataSource> {
// Your customer datasource and delegate.
id <MyTableViewDataSource> myDataSource;
id <MyTableViewDelegate> myDelegate;
}
#end
MyTableView.m
// MyTableView.m
#import "MyTableView.h"
#implementation MyTableView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
// This is how you can use your custom method.
int i = [myDataSource myRequiredMethod];
}
return self;
}
- (void)awakeFromNib {
// This assigns the delegate and datasource you assigned to File's Owner in your xib to your custom methods
myDataSource = (id<MyTableViewDataSource>)self.dataSource;
myDelegate = (id<MyTableViewDelegate>)self.delegate;
self.delegate = self;
self.dataSource = self;
}
// This is an example of how to override an existing UITableView method.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// This calls the method implemented in your ViewController. See Below.
NSInteger rows = [myDataSource tableView:tableView numberOfRowsInSection:section];
return rows;
}
#end
MyViewController.h
// MyViewController.h
#import "MyTableView.h"
// Use MyTableViewDataSource and MyTableViewDelegate instead of UITableViewDataSource and UITableViewDelegate
#interface MyViewController : UIViewController <MyTableViewDataSource, MyTableViewDelegate> {
#end
MyViewController.m
// MyViewController.m
#import "MyViewController.h"
#interface MyViewController ()
#end
#implementation MyViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
}
// This method will be overridden by myTableViewDataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 1;
}
- (int)myRequiredMethod {
return 2;
}
Subclassing is a great way to make reusable custom UI elements.
I think, you should still go with a Controller class. I expect subclassing UITableView to be tedious work — if possible with reasonable amount at all.
There is no problem to have UIViewController/NoViewController implemented the delegate and datasource and yet assign another controller to a specific tableView. note, that the datasource and delegate don't need to be subclasses of UITableViewController.
have a look at this answer: Implement Delegate at Run Time?
My UITableView does not delegate to its view controller, so that's not the problem.
You have to have to use delegate and datasource, that is how TableViews are filled and configured. otherwise you will have to overwrite every method of UITableView — including private ones, a no-go if you want into AppStore. Recreating UITableView without subclassing it would be even easier.

Resources