Multiple TableViews in a single screen - iphone

I have a UIViewController that I plan to have two TableViews and some other items in.
Both TableViews I am using on other screens, so I want to make them as independent and reusable as possible. One of those TableViews is called messageList (A UITableView) which shows my ChatHistory.
I am trying to understand if my approach is. [Edited 9/2 with correct code to make this approach work]
One approach would be to use a single table with 2 different sections, then in the delegate methods use a conditional statement to see which section is which and act accordingly.
The problem with this approach is usability. I want to easily reuse my TableViews in other views where one or the other TableView may or may not exist. Additionally, I want the DataSource to exist throughout the lifecycle of the app regardless of what Controller is instantiated or active.
My approach is to separate the view controller that manages the table view's from the table UITableViewDataSource and UITableViewDelegate implementations. But I am having a problem making this work.
Focusing on one of the TableViews, my ChatTableView.
In my AppDelegate has a property for chatHistory of type ChatHistory which implements UITableViewDelegate & UITableViewDataSource.
// AppDelegate.h
ChatHistory *chatHistory;
...
#property (nonatomic, retain) ChatHistory *chatHistory;
// ChatHistory.h
#import <Foundation/Foundation.h>
#interface ChatHistory : NSObject <UITableViewDelegate, UITableViewDataSource> {
UITableViewCell *nibLoadedCell;
NSMutableArray *messages;
}
#property (nonatomic, retain) UITableViewCell *nibLoadedCell;
#property (nonatomic, retain) NSMutableArray *messages;
#end
// ChatHistory.m - Note this code, including the custom cell was working correctly when it was a part of the controller so I believe it should be correct
#import "ChatHistory.h"
#include "ChatMessage.h"
#implementation ChatHistory
#synthesize nibLoadedCell; // custom cell design
#synthesize messages;
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section {
return [messages count];
}
- (NSString *)tableView:(UITableView *)tableView
titleForHeaderInSection:(NSInteger)section {
return [NSString stringWithFormat:#"Discussion"];
}
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
[[NSBundle mainBundle] loadNibNamed:#"ChatTableCell" owner:self options:nil];
cell = nibLoadedCell;
}
// custom tag order - username; message; future - Avatar; like; dislike
ChatMessage *obj = [messages objectAtIndex:indexPath.row];
UILabel *messageLabel = (UILabel *) [cell viewWithTag:1];
messageLabel.text = obj.message;
UILabel *usernameLabel = (UILabel *)[cell viewWithTag:2];
usernameLabel.text = obj.sender;
return cell;
}
- (void)dealloc {
if (messages) [messages release];
[super dealloc];
}
#end
// MyViewController.m
- (void)viewDidLoad { // MAKE SURE TO INITIALIZE viewDidLoad not InitWithNib
if (!appDelegate.chatHistory)
appDelegate.chatHistory = [[ChatHistory alloc] init];
messageList = [[UITableView alloc] initWithFrame:CGRectMake(0, 54, 320, 100) style:UITableViewStylePlain];
messageList.dataSource = appDelegate.chatHistory;
messageList.delegate = appDelegate.chatHistory;
[self.view addSubview:messageList];
...

You do not need to make a view controller your tableview DataSource or delegate; any object can be set. You can use a singleton as Felix suggests, or any other class structure you want. Since you mention that you want the chat history to be available from anywhere in the app, it makes sense to provide a UITableViewDataSource protocol to that chat history.
As for the UITableViewDelegate, you can simply create a new class as a subclass of NSObject and implement the delegate there. Make sure it's created and retained properly, and set in (upon load) as the delegate for your table views.

If you want one central data store, you could create a Singleton class with the data.
Then set it as the data source for the table view or fetch the array (or whatever you got) from the data store in your UIViewController / UITableViewController.
If you initialize the data store in your AppDelegate, you can access it from every class you want (note that all data you load, will remain in memory until your application gets terminated by iOS)
How to create a Singleton class in Objective-C

I would do it this way: make the two UITableViews subviews of one view controller.
In each of the datasource methods you simply distinguish between the two tables, similar to the code Apple provides in the UISearchDisplayController examples. For instance:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (tableView == myTableView1) {
// return appropriate number of rows
}
// return appropriate number of rows for the other table view
}
I know this does not separate the two table view classes very neatly. You would have to have a view controller controlling two other view controllers, one for each tableview.
Perhaps one has to consider the tradeoff between reusability and the complications of a more convoluted architecture (which typically also leads to classes that do not perform all that well in terms of reusability). That's why I would recommend the approach from the Apple sample projects.

Related

Separating UITableView's delegate/datasource from main ViewController

I'm quite new to iOS development. I have read about the issues with ViewControllers becoming huge and would like to follow the design I have seen in the answer to this previous question UITableView issue when using separate delegate/dataSource for my app which has 2 different tables and couple of buttons on one screen. But somehow I get confused in the storyboard connections to make between TestTableViewController and TestTableTestViewController.
Can anyone provide a sample working project or some screen shots on how to connect the UITableView delegate, data source and connecting outlet to the separate custom UIViewController subclass (TestTableTestViewController) in storyboard please?
Also, does this design work with xCode 5 / iOS 7 and above?
Note: For those having moved to Swift I strongly recommend using swift extensions for the delegate & data source and in fact any other implementation of an inherited class or protocol as per the 'Grouping' section of Natasha The Robot's blog post here
You have to create another class named as MyTableViewDataSource and implement TabbleViewDataSource methods in it. Create an a property of your data source class. Set data source in ViewController
Here is the examle:
#interface MyTableViewDataSource ()
#property (nonatomic, assign) int sectionOneRows;
#property (weak) id<YourDelegate> delegate;
#property (nonatomic, strong) NSArray *dataSourceArray;
#end
#implementation MyTableViewDataSource
#synthesize delegate=_delegate;
-(id) initDataSourceWithdArray:(NSArray *)array
delegate:(id)delegate
{
if (self = [super init]) {
self.dataSourceArray=array;
self.delegate = delegate;
}
return self;
}
#pragma mark - UITableViewDataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return numberOfRows;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cellToReturn = nil;
//Your code here
return cellToReturn
}
#pragma mark - UITableView Delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
//Your logic here
//implement the delegate method in you ViewController
[self.delegate performSelector:#selector(loadDetailsAtIndexPath:)withObject:indexPath];
}
}
In your ViewController:
self.myDataSource = [[MyTableViewDataSource alloc] initDataSourceWithdArray:self.dataArray delegate:self];
[self.myTableView setDataSource:self.myDataSource];
[self.myTableView setDelegate:self.myDataSource];
[self.myTableView reloadData];
Your delegate method:
-(void)loadDetailsAtIndexPath:(NSIndexPath *)indexPath
{
MyDetailViewController *myDetailController = [[MyDetailViewController alloc] initWithNibName:#"MyDetailViewController" bundle:nil];
//Your logic here
[self.navigationController pushViewController:myDetailController animated:YES];
}
Go to the Connections Inspectors and make changes like this:
Data source for the tableView will be set programatically here rather in IB.
I hope this will help you.
Press and hold Ctrl and click + hold + drag on item you want to make outlet to .h file. it will make connection by itself you just have to name them.
See This Video Tutorial
Also check these tutorials
Link 1
Link 2

UITableViewController in UIViewController as a partial view

I am new in ios dev, so my question might be so easy!
I want to use a UITableViewController in different places (reusable), so as a .net developer I think to create one UITableViewController and load it in different views (like Partial views in MVC.net)
I know the UIView that wants to contain this table should implement UITableViewDelegate and UITableViewDataSource methods, but I don't want to do that, I mean I want the Partial view to handle all of those logics because that is the view that has the access to CoreData.
I already searched for different solutions and in almost all of them I have to implement those methods in every single view that wants to use that partial view.
any suggestion?
Thanks
Yes, you can handle the logic in the partial view for example your exTableviewController.
Handle protocol UITableViewDataSource & UITableViewDelegate in your exTableViewController as below.
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 10;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Test"];
if (cell==nil) {
cell= [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"Test"];
// cell.textLabel.textAlignment = NSTextAlignmentCenter;
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
cell.textLabel.text = [NSString stringWithFormat:#"%i", indexPath.row];
return cell;
}
And associate the partial view and your view controller as below.
#interface YourViewController ()
#property (nonatomic, strong) exTableViewController *tableVC ;
#end
#implementation YourViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.tableVC = [[exTableViewController alloc] init];
[self.view addSubview:_tableVC.view];
// Do any additional setup after loading the view, typically from a nib.
}
#end
You can also customize the subview's size by using YourViewController.xib.
And binding self.tableVC with the subview in xib file.
Hope this can help you.

Are there Anyone that use TableViewController without subclassing?

I am just curious. In IB, we can put a tableviewcontroller. However, as far as I know, we always subclass that tableview controller right? That way we can implement delegate, etc.
However, it seems that for some "default" behavior, IPhone intended tableviewcontroller to be used as is. Otherwise, why would IB let us put tableViewController like that?
Are there any sample code where people use tableViewController without subclassing?
Where does they implement things like what cells to draw, etc. then?
I guess the right answer of the question is that it's simply ridiculous to use a UITableViewController without sub classing. No body is doing it. Please correct me if I am wrong. I am just curious.
Whether you use a subclass of UITableViewController or UIViewController you need to set the data your table is going to display, otherwise, what's the point of a blank table? To achieve that you have to subclass and implement some methods. It's also a good idea to keep the delegate and the datasource in the same controller, unless the complexity really asks for different classes.
That being said, I always create my own table controllers as a subclass of UIViewController and implement the table controllers methods myself, because it gives you more flexibility. Matt Gallagher has several posts on how and why. See UITableView construction, drawing and management (revisited).
If you want to give it a try, create a subclass of UIViewController with a XIB and add the following sample code:
// interface
#import <UIKit/UIKit.h>
#interface SettingsVC : UIViewController <UITableViewDelegate, UITableViewDataSource>
#property (nonatomic, retain) IBOutlet UITableView *tableView;
#property (nonatomic, retain) NSMutableArray *array;
#end
// implementation
#synthesize tableView = _tableView;
#synthesize array = _array;
# pragma mark - UITableViewDataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [self.array count];
}
- (UITableViewCell *)tableView:(UITableView *)aTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
int row = [indexPath row];
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [aTableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
cell.textLabel.text = [self.array objectAtIndex:row];
return cell;
}
Then add a UITableView object to the XIB, link the tableView of the controller to the UITableView object, and link the delegate and datasource of the UITableView to the controller.
No, this is not necessary to inherit your class with tableViewController. You can use table view by simply
putting TableViewController in xib.
and setting its delegate and datasourse to file's owner you can draw the table cells.
I don't think you can use a UITableViewController as is, it's like using a UIViewController without subclassing it : you can't set any inner mechanics.
But you can have a UITableView without using a UITableViewController.
Sure you can use UITableViewController without subclassing it.
Samplecode is very easy and straight forward.
For example like this:
- (IBAction)selectSomeOption:(id)sender {
UITableViewController *tableViewController = [[UITableViewController alloc] initWithStyle:UITableViewStyleGrouped];
tableViewController.tableView.dataSource = self;
tableViewController.tableView.delegate = self;
tableViewController.title = "Select some option";
[self.navigationController pushViewController:tableViewController animated:YES];
}
and the UITableViewDatasource and Delegate methods go into the same class.
Sure, if you like pain you could create a UIViewController in code and add a tableView on your own.
Or create a subclass for such an easy task.
The use of a non subclassed UITableViewController is sometimes convenient.

UITableView refreshing problem

I am trying to refresh an UITableView every time I navigate the the view that contains this Table.
I Have a ViewController and a custom UITableViewController that manages to set the Table Correctly when the application starts, using an NSMutableArray contained inside the controller.
When I navigate to the page containing the table, the ViewController calls a function that gets the data from a server with an HTTP request and parse it in an NSMutableArray.
Now here is my problem. I manage to send this array to my UITableViewController, but when I want to refresh my tableView, nothing happens.
I tried to use [myTable reloadData], but it doesn't calls the numberOfRowsInSection, or cellForRowAtIndexPath functions. I saw that people with the same problem solved it using [self.myTable ReloadData], but I get an error :
accessing unknown getter/setter method
I am pretty new to objective-C, and this error is still a bit mysterious to me as I get it a bit randomly.
Anyway, there is a high probability that I made a mess with the declaration of the UITableViewController (where am I supposed to declare it?) and with the Interface Builder links, so this can be a clue to find the solution.
Any one have an idea?
Thank you very much!
EDIT : Here is my tableview controller class:
#import "MyCell.h"
#class Mycell;
#interface MyTableController : UITableViewController {
IBOutlet MyCell * myCell;
IBOutlet UITableView * myTable;
NSMutableArray *data;
}
#property (nonatomic, retain) IBOutlet UITableView * myTable;
- (void) EditTable : (NSMutableArray*) param;
#end
And now the .m:
#implementation MyTableController
#synthesize myTable;
- (void) viewDidLoad {
[super viewDidLoad];
myTable = [[UITableView alloc] init];
data = [[NSMutableArray alloc] init];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [data count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"MyCell";
MyCell *cell = (MyCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier]; >
if (cell == nil) {
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:#"MyCell" owner:self options:nil];
for (id currentObject in topLevelObjects){
if ([currentObject isKindOfClass:[UITableViewCell class]]){
cell = (MyCell *) currentObject;
}
}
}
NSString *datastring = [listenom objectAtIndex:indexPath.row];
[cell setCell: datastring ];
return cell;
}
- (void) EditTable : (NSMutableArray*) param{
//This function is called by the ViewController when the user goes to the page containing the view
data = param; //The param array contains the data from the HTTP request
[self.tableView reloadData];
[self.myTable reloadData]; //I tried both, but only the first one actually calls the previous functions
}
You have a number of problems in this code sample. I'll point out a few of them here but I highly recommend reading the relevant Apple documentation at:
http://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/Introduction/Introduction.html
and
http://developer.apple.com/library/ios/#documentation/userexperience/conceptual/TableView_iPhone/AboutTableViewsiPhone/AboutTableViewsiPhone.html
Some issues in your code:
Since the class MyTableController is a subclass of UITableViewController you don't need the attribute and property for myTableView. The tableView property is defined and initialized as part of UITableViewController's implementation with its dataSource and delegate set to the UITableViewController instance. This is why [self.tableView reloadData] is calling your delegate and dataSource protocol methods.
You are also using interface builder so if you did want to create your own subviews you should either do that within IB and set the outlet there or do it in your code which means creating the subview(s) in viewDidLoad and then adding them to your view with [view addSubview:mySubView].
A better way to set the data for your table would be to create a property for your data attribute and call setData from the view controller that has initialized the MyTableController instance. You would use the setData: method to do this. You can call [self.tableView reloadData] in setData. You don't need to explicitly reload the table when the view is loaded as this is done automatically. A more minor point, if you stay with EditTable I would rename it to be more descriptive and to use camel case (e.g. setDataForTable`) to be consistent with iOS conventions.
You don't show any init/alloc for the listenom attribute referenced in tableView:cellForRowAtIndexPath:. Did you mean to use data instead?
Is your MyTableController.m file the complete version? If so, you are missing viewDidUnload and dealloc methods. Both of which are required. viewDidUnload should release any objects allocated in viewDidLoad and dealloc should release anything retained by the controller (including objects released in viewDidUnload.
As you are using tableViewController you should be able to use self.tableView instead to reload the data like this
[self.tableView reloadData];
you need to synthesize first then you can use self.myTable
do on the top
#synthesize myTable
and then
[self.myTable reloadData];

Simple way to separate UITableview datasource and delegate from main UIViewController class?

The typical UITableView usage pattern is to have the main UIViewController become a target datasource and delegate for the UITableView it is holding on to.
Are there any simple and easy to follow tutorials that would help me figure out how to move the code that pertains to the UITableViewDelegate and UITableViewDataSource methods into a separate class and hook that to my UIViewController instead? I would ideally like to have both the delegate and datasource living in the same class.
Right now, I am creating the UITableView via Interface Builder and connecting its outlet to my controller class.
Typical code:
#interface MyController : UIViewController <UITableViewDelegate, UITableViewDataSource>
{
IBOutlet UITableview *myTableview;
}
I want to do something more like this:
#interface MyController : UIViewController
{
IBOutlet UITableview *myTableview;
}
#end
#interface MyTableSourceDelegate : NSObject<UITableViewDelegate, UITableViewDataSource>
{
}
#implementation MyTableSourceDelegate
// implement all of the UITableViewDelegate and methods in this class
#end
I spend 2 hours to solve this problem:
It's working for me
// GenreDataSource.h
#import Foundation/Foundation.h
#interface GenreDataSource : NSObject <UITableViewDataSource> {
NSArray *dataSource;
CGSize cellSize;
}
#property(nonatomic, assign) CGSize cellSize;
#end
// GenreDataSource.m
#import "GenreDataSource.h"
#implementation GenreDataSource
#synthesize cellSize;
-(id)init{
self = [super init];
if ( self != nil ) {
dataSource = [[NSArray alloc] initWithObjects:#"All",#"Folk",#"Disco",#"Blues",#"Rock",#"Dance",#"Hip-Hop",#"R&B",#"Soul",#"Lounge",#"Techno",#"Bubstep", nil];
}
return self;
}
#pragma mark - UITableViewDataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return [dataSource count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *CellIdentifier = #"CellPicker";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero] autorelease];
[cell setSelectionStyle:UITableViewCellSelectionStyleGray];
//сконфигурируем структуру
FontLabel *fLabel= [[FontLabel alloc] initWithFrame:CGRectMake(30,
5,
cellSize.width-30,
cellSize.height-5)
fontName:#"HelveticaNeueCondensedBlack"
pointSize:18.0f];
[fLabel setTextColor:[UIColor darkTextColor]];
[fLabel setTag:101];
[fLabel setBackgroundColor:[UIColor clearColor]];
[cell.contentView addSubview:fLabel];
[fLabel release];
}
FontLabel *fLabel = (FontLabel*)[cell viewWithTag:101];
[fLabel setText:[dataSource objectAtIndex:indexPath.row]];
return cell;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return 1;
}
#end
First thing is if you're using a UITableViewController subclass with interface builder you will want to disconnect the delegate and datasource outlets that are already hooked up by default. (Hint, look in the connections inspector). Check even if you have a tableView inside a viewController.
Second create your classes and make sure they conform to <UITableViewDelegate> and <UITableViewDataSource>. You're probably going to have to declare this contract in the .h file if you're using objc.
Third, In your view controller instantiate this class or two separate classes somewhere like viewDidLoad, and then assign self.tableView.delegate = myCustomDelegateInstance and self.tableView.dataSource = myCustomDataSourceInstance.
Now, any calls that come through the controller will be dispatched to your custom handlers. Pretty basic.
The only reason to really do this is if you 1) have a very bloated controller, or 2) you need to reuse the dataSource and delegate methods somewhere else and you want to avoid code repetition. Otherwise, it's probably better practice to leave it put.
You can create separe classes (with UITableViewDelegate , UITableViewDataSource) and add them in IB as external files and link the IBActions
In IB, you can drag a 'External Object' from Library->Cocoa Touch->Controllers into your xib window. You can then select that object, view the inspector, and set the class. It is now available to serve as a delegate, etc.