I have an app, in which I have 7 different UITableViewControllers. All 7 are linked through a tabBarController. I am looking for a way to have a single custom class to be used throughout all 7 UITableViewControllers. I have 7 different arrays that all hold a specific number of objects. I need to know how to:
Change the number of rows in the tableView, depending on the array that I'm using as my data source.
Change the array that is being used as the data source based on which ViewController the user is currently looking at (Can this even be done?)
Change the contents of a cell, based on the array being used as the data source.
I'm familiar with using UITableView with a single data source, but I really don't know how to approach it with multiple data sources.
Thanks!
You can have one class be the dataSource for all of the UITableViewControllers
You might implement this by creating a custom subclass of UITabBarController which keeps an array of UITableViewControllers and a corresponding dictionary that maps a UITableVC to the array used by it's data source.
Set that as the data source for all the UITableViews and then handle each dataSource method like my example below.
Take a look at the UITableViewDataSource docs.
All of the methods pass in which tableView they're trying to get information about.
For example:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
//Here you could compare the tableView var to your array of tableViews to figure out which table view called this
//Based on that you could query your dictionary to find the array that houses the data for that tableView.
//Use the indexPath to find the data that you need to create and return the right cell
}
• Change the number of rows in the tableView, depending on the array
that I'm using as my data source.
You can accomplish this by conditions in tableView delegates
- (NSInteger)tableView:tableView numberOfRowsInSection:section
Inside this delegate you need to identify which dataSource for the particular tableView.
Check the table if its the one being refreshed like so:
- (NSInteger)tableView:tableView numberOfRowsInSection:section
{
if (tableView == self.firstTableView)
return self.firstTableDataSource.count;
if (tableView == self.secondTableView)
return self.secondTableDataSource.count;
//and so on..
}
• Change the array that is being used as the data source based on
which ViewController the user is currently looking at (Can this even
be done?)
Figuring which array you will be using for that particular table is up to you. You can use segement control, buttons, another table, it's up to you.
But the very important part is [tableView reloadData]; at your target table (table that is currently active) and again table delegates will be triggered and you will be doing all the filtering inside those delegates..
while you can check if the viewController is visible by:
if ([self/*viewController*/ isViewLoaded] && self/*viewController*/.view.window)
{
//visible
}
which was already discussed here
• Change the contents of a cell, based on the array being used as the
data source.
This one is not clear.
Is it just the content/values of the subviews of the cell like: cell.textLabel, cell.detailTextLabel and cell.imageView?
or the cell.contentView which is basically, you want to change the look of your cell?
If content/values again you just have to determine which is which, like this (using customCell):
assuming you have a dataSource that looks like:
{
data_source = (
{
text_label = test0;
detail_label = "this is just a text";
image_name = "your_image0.png";
},
{
text_label = test1;
detail_label = "this is just a another text";
image_name = "your_image1.png";
}
)
}
then in the delegate cellForRowAtIndexPath it'll be something like:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellID = #"tableID";
self.customCell = [tableView dequeueReusableCellWithIdentifier:cellID];
if (!self.customCell)
self.customCell = [[YourCustomCell alloc] initWithStyle:(UITableViewCellStyle) reuseIdentifier:cellID];
static NSString *dataSource = #"data_source";
static NSString *textLabel = #"text_label";
static NSString *detailLabel = #"detail_label";
static NSString *imageName = #"image_name";
if (tableView == self.firstTableView)
{
self.customCell.textLabel.text = [self.firstDataSource valueForKey:dataSource][indexPath.row][textLabel];
self.customCell.detailTextLabel.text = [self.firstDataSource valueForKey:dataSource][indexPath.row][detailLabel];
self.customCell.imageView.image = [UIImage imageNamed:[self.firstDataSource valueForKey:dataSource][indexPath.row][imageName]];
}
if (tableView == self.secondTableView)
{
self.customCell.textLabel.text = [self.secondDataSource valueForKey:dataSource][indexPath.row][textLabel];
self.customCell.detailTextLabel.text = [self.secondDataSource valueForKey:dataSource][indexPath.row][detailLabel];
self.customCell.imageView.image = [UIImage imageNamed:[self.secondDataSource valueForKey:dataSource][indexPath.row][imageName]];
}
// and so on...
}
To check all other methods, check apples documentation ,i hope this is useful for you and for others as well.. Happy coding.. :)
Related
So I'm developing an app where I have to display a table view as the content of a Static table view cell inside a UITableViewController, but I can't seem to populate it with my array.
Because I have two table views inside a UITableViewController, every time I try to add the Controller as it's data source the two tables contents are conflicted.
Does Someone know hot to bypass this?
Actually it is quite possible to have multiple table views using the same view controller as data source. There are many complex layout where this could be useful. To prevent your data from getting mixed up, just check which table view is asking for data in all of the data source methods, for example like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
YourObject *object;
if (tableView == _tableViewNumberOne) {
object = [self.yourFirstArray objectAtIndex:indexPath.row]
} else if(tableView == _tableViewNumberTwo) {
object = [self.yourSecondArray objectAtIndex:indexPath.row]
}
Setup cell ......
}
You need references to the two tableviews from where they were created of course
This will be really useful for me since I have been struggling with this issue for some time. Every time I am trying to create a custom UITableViewCell and use it in a UITableView, things get mixed up. I believe it is because of the function I use to fill the cell with, in this example it's name is fillPlayerCellWithPlayer:.
I have a NSArray of PlayerContacts as my DataSource of the UITableView. My idea is to fill the cell's contents like UILabels and UIImageViews and etc with each PlayerContact properties. The usual practice is to fill these properties right in cellForRowAtIndexPath like: tablecell.playerNameLabel.text = player.playerName and so on. But I implement it like as follows for convenience and reasons I will tell later down below:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = #"PlayerCellID";
AddPlayerObjectCell *tablecell = (AddPlayerObjectCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
NSArray * chosenPlayerArray = [self generatePlayersArrayForCategoryRow:selectedCategoryIndexRow];
NSMutableDictionary * dict = (NSMutableDictionary *)[chosenPlayerArray objectAtIndex:indexPath.row];
PlayerContact * player = (PlayerContact *)[dict objectForKey:#"sgfPlayer"];
[tablecell fillPlayerCellWithPlayer:player];
return tablecell;}
To explain why I don't use the common practice method, first let me share the fillPlayerCellWithPlayer function:
- (void) fillPlayerCellWithPlayer : (PlayerContact *) player {
[self createGestureRegocnizer];
myPlayer = player; //this sets the player as the private variable of AddPlayerObjectCell class
cellItemLabel.text = myPlayer.playerName;
cellItemSubLabel.text = myPlayer.playerTeam;
UIImage * img = [UIImage imageNamed:#"messi.JPG"];
cellItemImageView.image = img; }
- (void) createGestureRegocnizer{
UITapGestureRecognizer *gesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTapFrom)];
gesture.delegate = self;
[self addGestureRecognizer:gesture];}
- (void) handleTapFrom{
NSString * imageName = #"unselected_user_icon.png";
NSString * imageNameSelected = #"selected_user_icon.png";
if(playerSelected){
playerSelected = NO;
myPlayer.playerSelected = playerSelected;
[cellItemSelectionImageView setImage:[UIImage imageNamed:imageName]];}
else{
playerSelected = YES;
myPlayer.playerSelected = playerSelected;
[cellItemSelectionImageView setImage:[UIImage imageNamed:imageNameSelected]];}}
As You can see, when the user touches the cell, handleTapFrom fires and i can change the playerSelected property of myPlayer so i can later on use this info to filter my main array for selected players.
One of my problems is that whenever i slide my finger in the tableview of players, their selected checkmarks gets changed like crazy. I guess this is caused by my using reusable cells logic in a wrong way. If you guys can suggest me a way to this properly, i would be really happy.
And my second question is more important for me since, if you guys say its true, I want to use it as a general logic throughout my app design. Lets say i passed a NSArray named playersArray to the tableview as datasource. And the cell class changed the playersArray object that corresponds to its indexPath. In my controller class, and the classes that can access playersArray, this change is observed. This is good news for me because i don't need to make a copy of array and change its contents accordingly. Do you friends think this is a good practice for a UITableViewCell to change the contents of its UITableView's DataSource? or is UITableViewCell's only job to display a cell as needed? Should I implement this some other way? like delegation or something if I want to update the datasource?
Thank you very much in advance. I can be more clear in anyway if you guys need me to.
Aytunç İşseven
In general you should avoid that the Cell is holding the data model itself and manipulating them. You also should avoid creating & adding this gesture recogniser multiple times (as that will happen if you call fillPlayerCellWithPlayer: and the cell is reused). That will increase the count of the tap recognisers and each of the will trigger the a tap. Instead you could override
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
on the UITableViewCell and handle your selection there. This will be triggered each time you change the selection state of the UITableViewCell. You should only change the visual appearance of the cell here as the cell should not hold the player object.
To handle the selection of the player just implement the UITableViewDelegate method (on your controller)
- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath
and handle the manipulation of your data model there.
The reason why is:
The UITableViewCell is a view and following the Model-View-Controller Pattern (which Appkit is based on) the view should just represent the model without managing it. The controller (which implements the UITableViewDelegate - e.g. a UIViewController) is the connector between those two and should be the one managing the model information instead of the view as you suggested.
I am trying to get a URL from a cell. To do this, I am using NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow]; and then would like to do something like NSURL *url = self.finalURL[indexPath.row] but because indexPath.row is only for Arrays, this doesn't work. Is there a way to achieve the same thing as indexPath.row but for objects not in an array.
Here is how I am saving the url:
cell.finalURL = self.finalURL;
A cell doesn't have a URL, unless you create a subclass of the cell and add that property to is. Conventionally, you will have an array of objects, strings, dictionaries, etc., and that is your tableView's data source.
If I had an array with three NSURLs in it called myArray that contained google, amazon, and bing, and I wanted to display three cells with the respective labels matching the items in the array, I would implement the following code:
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// we only want a single section for this example
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// this tells the tableView that you will have as many cells as you have items in the myArray array
return myArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// first we try to reuse a cell (if you don't understand this google it, there's a million resources)
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cell"];
// if we were unable to reuse a cell
if (cell == nil) {
// we want to create one
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"cell"];
// here is where we do ANY code that is generic to every cell, such as setting the font,
// text color, etc...
cell.textLabel.textColor = [UIColor redColor];
}
// here is where we do ANY code that is unique to each cell - traits that are based on your data source
// that you want to be different for each cell
// first get the URL object associated with this row
NSURL *URL = myArray[indexPath.row];
// then set the text label's text to the string value of the URL
cell.textLabel.text = [URL absoluteString];
// now return this freshly customized cell
return cell;
}
That, along with the rest of the default tableview code and setting up the array itself, results in the following:
When a user taps on a cell you can access the URL in the array and do something with it like so:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// first deselect the row so it doesn't stay highlighted
[tableView deselectRowAtIndexPath:indexPath animated:YES];
// get the URL associated with this cell by using indexPath.row as an index on your
// data source array, if you tapped the first cell, it will give you back the first URL in your array...
NSURL *selectedURL = myArray[indexPath.row];
// do something with that URL here...
}
Think of your table view's data source as a bunch of little cubbies. You can create the data source in a million different ways, but once you have it you basically take the items and place them in numbered cubbies. Your table view create's itself based on what's in those cubbies, so to make the first cell it looks in the first cubbie, and so on, and later on when a user selects a cell from that tableview, all the table view does is tell you the cubbie number that was selected, and it's your job to use that information to retrieve the data from that specific cubbie and do what you need to with it. Hope that helps!
I'm really new to Objective-C here so what I'm asking may be trivial to most of you but any guidance will help.
Here's a picture of my storyboad.
My current objective is to allow for the user to enter in the number of sets (NSInteger *numReps) and then press the "Log Reps" button and have the table initialize with numReps cells that look like the prototype cell.
Now where I'm at a loss for the implementation. I've never done this kind of thing before so I'm not exactly sure what the best way to go about it is. I have thought of making a custom class for the UITableView table that would take info from the view after the Log Reps button is pushed. I'm not entirely sure how this would need to be implemented. Or can I simply add the table to the properties of the view controller and setup the table within the view controller? That was my initial idea and seems ideal so that I would have everything in one place.
Pleas advise. I am new to all of this and come from a C++ background so I'm still learning a lot of the notation.
Try this:
-(IBAction)btnLogClicked {
int iSet = 4 //Number of row in table
UITableView *tblView= [[UITab;eView alloc]initWithFrame:CGRectMake(0,50,320,100)];
tblView.delegate = self;
tblView.dataSource = self;
[self.view addSubView:tblView];
}
Table View Data Source and Delegate Methods
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return iSet;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
// Display what you want to display in each cell
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
It's not clear what you want to present in your prototype cells, but you need an array (or probably an array of dictionaries) to populate the cells. The number of rows is determined by the number of entires (the count) of that array. So, if you take the number entered by the user, and add that many object to your array, then call reloadData on the table, you will get the number of rows that you want. What those object are that you add to the array, depends on what you're trying to show there.
you could start reading: Table View Programming Guide for iOS
But I can answer you:
You can add the UITableView to the UIViewController, but you need set your UIViewController like the TableView's delegate and dataSource. Your ViewController need to implement the protocol: UITableViewDataSource
The method that you are looking for is: tableView:numberOfRowsInSection:
But I really recommend you that read the Apple Reference.
I have an app that lists some data in a tableview, in cells. I want the user to be able to select a table view cell, any one, and have the app cycle through the 5 lower cells. Here is what I have so far:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
// Navigation logic may go here. Create and push another view controller.
[self fetchCellsToProcess:indexPath];
}
Here is the fetchCellsToProcess: method:
-(void)fetchCellsToProcess:(NSIndexPath*)indexPath{
for (int cellsToProcess = 0; cellsToProcess < 5; cellsToProcess++) {
//process each cell
//...
}
}
I need use the indexPath to get its indexPath.row. Then add 5 to that indexPath.row and only process the tweets between indexPath.row passed in and indexPath.row+5. What programming logic should I use to cycle through cells x -> x+5?
You should not be using cellForRowAtIndexPath: here: that's part of the presentation logic, while you are working on the model-level logic here.
Look at your cellForRowAtIndexPath: method, and see from where does the text of the cell's labels come. Usually it is an NSArray or some other collection. Your didSelectRowAtIndexPath: method should go directly to that same collection, and grab the info from there.
There is no direct approach to achieve this as dasblinkenlight mentioned
but there is a work around which works for me.
Please follow below approach:
Create your custom button within your custom cell
Create your custom cell class and put button(change type to custom ) do necessary connection custom cell class
create and connect action method named "actionSelectedCell" as below from that button in class you are implementing this logic.
And cellForRowAtIndexPath cell.customButton.text = data from your array or dictionary
- (IBAction)actionSelectedCell:(UIButton *)sender {
// below code for getting selected cell row and its section
UIView *view = sender.superview;
while (view && ![view isKindOfClass:[UITableViewCell self]]) view = view.superview;
UITableViewCell *cell = (UITableViewCell *)view;
indexPathForSelectedCell = [self.tableViewListViewVC indexPathForCell:cell];
NSLog(#"cell is in section %d, row %d", indexPathForSelectedCell.section, indexPathForSelectedCell.row);// so now you are getting indexpath for selected cell
// now you can create simple loop logic to get required data and do whatever you like post it to something or store it for any other use
NSString *strFifthCellContent = [NSString stringWithFormat:#"%#",[dataArray ObjectAtIndex:indexPathForSelectedCell.row+5]]; // in this way you can get data from any cell
}