I currently have a UITableView that is populated by a .plist full of exercises. What I want to be able to do is access individual exercises within the table by storing each exercise that is clicked on into an array, that will later be used to populate a seperate UITableView.
How exactly do I get access to these individual cells so that I can store them into this array. Here is what I have so far:
-(IBAction) saveWorkout {
NSMutableArray *workout = [NSMutableArray arrayWithCapacity:10];
[workout addObject: ] // I'm assuming this is where I add a cell to an array (or atleast the cell's string).
}
Any help?
Without delving too much into the actual code part of your question, calling -cellForRowAtIndexPath to retrieve a title is (can be) extremely expensive, especially if it is called multiple times. Use -didSelectRowAtIndexPath: to get the index of the title within your datasource array, then add that object to your list. Call -saveWorkout when you are finished/reach a certain limit.
A same might look like:
-(void)didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
//other code and such...
//get the index of the object in our original array and add the corresponding object to the new array.
[customWorkoutArray addObject:[workoutArray objectAtIndex:indexPath.row]];
}
To restate #CodaFi in code:
#property (strong, nonatomic) NSMutableArray *selectedElements;
#synthesize selectedElements=_selectedElements;
- (NSMutableArray *)selectedElements {
if (!_selectedElements) {
_selectedElements = [NSMutableArray array];
}
return _selectedElements;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
id element = [self.myModel objectAtIndex:indexPath.row];
// this is the key: this array will now be the basis for your subsequent table of selected items
[self.selectedElements addObject:element];
// do something to your model here that indicates it's been selected
// have your cellForRow... method interrogate this key and draw something special
[element setValue:[NSNumber numberWithInt:1] forKey:#"selected"];
// reload that row so it will look different
[tableView beginUpdates];
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath withRowAnimation:UITableViewRowAnimationFade];
[tableView endUpdates];
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
-(void)didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
You can get the index here by indexPath.row
}
Related
It's my first time using Storyboard to build an app. I'm trying to create a UITableView with a custom cell. I've created the cell in IB and created a custom tableViewCell class for it (added some labels, created appropriate outlets, connected them in IB and assigned the custom class to the custom cell in IB).
In the ViewController responsible for the TableView I create the data source (a dict with some arrays) and fill out all the obligatory methods (I've tried both with UITableViewControler and with a UIViewController that is set as a delegate and data source for the tableview)
And when I run the app - the tableview is empty. And after doing some NSLogging I've noticed that the data source methods are never executed. And I have NO IDEA why.
I'm going crazy about this for a few hours now. Please help me out :)
Let me know if you need to see the code or Storyboard, or whatever else.
UPDATE: Alright, after some digging, I've decided to test out the same code but using an NSMutableArray as data source instead of a NSMutableDictionary. And it works!
Now, could someone explain to me why it didn't work with a dict?
Here's what did. I had a dict with 5 arrays, and each array had 5 strings.
In the numberOfRowsForSection method, I returned [dict count]
And in the cellForRowAtIndexPath method, I've used this code
NSArray * routeArray = [dealsDict objectForKey:#"route"];
cell.routeName.text = [routeArray objectAtIndex:indexPath.row];
NSArray * companyArray = [dealsDict objectForKey:#"company"];
cell.companyName.text = [companyArray objectAtIndex:indexPath.row];
NSArray * priceArray = [dealsDict objectForKey:#"price"];
cell.priceLabel.text = [priceArray objectAtIndex:indexPath.row];
NSArray * dateArray = [dealsDict objectForKey:#"date"];
cell.dateLabel.text = [dateArray objectAtIndex:indexPath.row];
NSArray * monthArray = [dealsDict objectForKey:#"month"];
cell.monthLabel.text = [monthArray objectAtIndex:indexPath.row];
NSLog(#"I'm in here");
return cell;
why didn't it want to show anything?
In the IB, select your table view, and make sure that the delegate and dataSource outlets are set for your controller
UITableView need some collection dataset as data source. Its delegate method "cellForRowAtIndexPath" get called equals to number of elements of collection (ARRAY). like numberOfRowsInSection delegate tells the table view that it need to call cellForRowAtIndexPath for every element in collection. and it also need some iterator to read each time the next element, in case of dictionary it always stick to same data element at each call of cellForRowAtIndexPath. I hope it wil give you the idea of its working, Please let me know if you need anything else to know.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.data count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
MessagesCell *cell = [tableView dequeueReusableCellWithIdentifier:#"messageCellIdentifier"];
Message *msg = [self.data objectAtIndex:indexPath.row];
cell.Name.text = msg.Name;
cell.Message.text = msg.MessageText;
cell.Time.text = msg.Time;
return cell;
}
I have a uitableview that displays the values of an array. I would like to know if there is a way to update the subtitle of its table cells, based on how many times each cell was tapped.
Thank you!
First of all, you'll want to use a NSMutableArray so you can change its contents after instantiation. Here's a basic over view of what I just tried to achieve your intended results:
In your interface
#property (strong, nonatomic) NSMutableArray *tapCountArray;
In your implementation
- (void)viewDidLoad
{
[super viewDidLoad];
self.tapCountArray = [NSMutableArray new];
int numberOfRows = 20;
for (int i = 0; i < numberOfRows; i ++) {
[self.tapCountArray addObject:#(0)];
}
}
Then the important part!
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.tapCountArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
NSString *text = [self.tapCountArray[indexPath.row] stringValue];
[cell.textLabel setText:text];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self.tapCountArray replaceObjectAtIndex:indexPath.row withObject:#([self.tapCountArray[indexPath.row] intValue] + 1)];
[self.tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
When each cell is tapped the number in its detailTextLabel will be incremented up by one.
You shouldn't create a new array or set, that could lead to problems if the two arrays get out of sync with each other. The way to do it, as you suggested in your comment, is to use dictionaries. The way you said you were doing that is probably not the way, however. You want an array of dictionaries where the values for one key would be whatever your main data is and the value for the other key would be the number of taps. For example, lets call the two keys main and sub, and your main data is a set of names. The array of dictionaries would look like this: ({main:#"Tom",sub:1}, {main:#"Dick", sub:0}, {main:#"Harry",sub:2}, .....). In the tableView:cellForRowAtIndexPath:indexPath method you would provide the data to the cells like this:
cell.textLabel.text = [[array objectAtIndex:indexPath.row] valueForKey:#"main"];
cell.detailTextLabel.text = [[array objectAtIndex:indexPath.row] valueForKey:#"sub"];
I think you can just set up another array of the same length as the one you have now. Then when your didSelectRowAtIndexPath is triggered, increment your indexPath.row entry of the new array and refresh that cell. If you don't expect to shuffle the table, you don't need a dictionary.
You can insert the object into an NSCountedSet, and on your cellForRowAtIndexPath method, you would take the model object for the cell and verify the number of times it has been inserted into the NSCountedSet instance.
Take a look at the NSCountedSet documentation: https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSCountedSet_Class/Reference/Reference.html
Basically I'm making a list view that you can add things to the top of. The best way I can think of doing this is to store the UITableViewCells themselves in a NSMutableArray — Because I can simply pull them from the array them with all their data inside the object, and this list view will never be over 10 cells long.
Also note that I'm using Storyboards, hence the initWithCoder use.
The following code is what I'm trying, and it doesn't work:
// This is where my NSMutableArray is initialized:
- (id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super initWithCoder:aDecoder]) {
if (!_CellsArray) {
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"TestCell"];
_CellsArray = [NSMutableArray arrayWithObject:cell];
}
}
return self;
}
//UITableView Delegate & DataSource Methods
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"TestCell"];
[_CellsArray insertObject:cell atIndex:0];
return [_CellsArray objectAtIndex:indexPath.row];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 10;
}
I realize I may be approaching this in the wrong way, that's why I'm here though :)
Thank you.
edit: fixed a type in the code (TimerCell -> UITableViewCell)
Let's look at the order things get called in and what happens.
Your view controller is unarchived, so your initWithCoder: method is called. This method creates a mutable array and puts one instance of TimerCell into it. Said instance is not further configured (unless you've overridden initWithStyle:reuseIdentifier: to do some configuration).
Your data source method tableView:numberOfRowsInSection: is called, and it tells the table view there are ten rows.
Thus, your tableView:cellForRowAtIndexPath: is called ten times. Each time, it creates a new instance of UITableViewCell and inserts it into your mutable array. (After ten calls, your mutable array contains one TimerCell at index 10 and ten UITableViewCells at indices 0-9.) It does nothing to configure the cell's contents or appearance, then it returns the cell at the specified row index. On the first call, you're asked for row 0, so the cell you just created and inserted at index 0 is returned. On the second call, you're asked for row 1, so the cell at index 1 in your array is returned -- since you just inserted a new cell at index 0, the cell you created on the last call has shifted to index 1, and you return it again. This continues with each call: you return the same unconfigured UITableViewCell ten times.
It looks like you're trying to out-think UIKit. This is almost never a good thing. (It's been said that premature optimization is the root of all evil.)
UITableView already has a mechanism for cell reuse; it's best to just keep track of your own cell content and let that mechanism do its thing. I took so long to type this that other answers have been written describing how to do that. Look to them, or to Apple's documentation or any third-party UITableView tutorial.
Why don't you just store the cell information in an array. Then in the -cellForRowAtIndexPath: method, just extract the data needed to change each cell.
Here is a simple example:
//Lets say you have an init like this that inits some cell information
- (id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super initWithCoder:aDecoder]) {
cellArray = [NSArray alloc] initWithObjects:#"firstCell",#"secondCell",#"thirdCell",nil];
}
return self;
}
//then for each cell, just extract the information using the indexPath and change the cell that way
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// Configure the cell...
cell.textLabel.text = [cellArray objectAtIndex:indexPath.row];
return cell;
}
Table views don't store things. Rather, they just ask for the data they want to display, and you typically get that data from elsewhere (like an NSArray, or an NSFetchedResultsController). Just store the things you want into some data container, and let the table display them for you.
// Probably your data model is actually a member of your class, but for purposes of demonstration...
static NSArray* _myArray = [[NSArray alloc] initWithObjects:#"Bob", #"Sally", #"Joe", nil];
- (NSInteger) tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section
{
return [_myArray count];
}
- (UITableViewCell*) tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
static NSString* CellIdentifier = #"TestCell";
// Make a cell.
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if( cell == nil ) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
// Setup the cell with the right content.
NSString* aString = [_myArray objectAtIndex:[indexPath row]];
cell.textLabel = aString;
return cell;
}
Now if you want more stuff in the list, add it to your array, and you're done.
Edit: On another note, initWithCoder: isn't generally the best place to do initialization for a view controller. Reason being, at the point that it's called, there's a good chance that stuff isn't loaded yet (IBOutlets, for example). I tend to prefer viewDidLoad (don't forget to cleanup in viewDidUnload in that case), or awakeFromNib.
I have an array which loads in table view, and if users taps a certain cell it changes to UITableViewCellAccessoryCheckmark.
How can I check what object in array is checked and add all objects that are checked to another array?
If you want a function that actually gets the checked objects at a whim, use the following:
- (NSMutableArray*)checkedObjectsInTable:(UITableView*)tableView
{
NSMutableArray *checkedObjects = [[[NSMutableArray alloc] init] autorelease];
for (int i=0; i<tableDataSource.count; i++)
{
if ([tableView cellForRowAtIndexPath:
[NSIndexPath indexPathForRow:i inSection:0]].accessoryType == UITableViewCellAccessoryCheckmark)
{
[checkedObjects addObject:[tableDataSource objectAtIndex:i]];
}
}
return checkedObjects;
}
That would allow you to get the data on demand. Note that it would be much less efficient than simply using Jasarien's method, yet there are some situations where it is a better solution.
Something like this in your tableView:didSelectRowAtIndexPath: method:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//set checkmark accessory on table cell ...
// get object and add to checkedObjects array
NSInteger index = [indexPath row];
MyObject *object = [myArray objectAtIndex:index];
[checkedObjects addObject:object];
}
I went through all the examples online and could not figure out how to properly add a cell to a tableview with animation. Let's say I have one section with one cell and I want to add another cell once the user clicks on the first cell's accessory.
My "add" method does this:
- (IBAction) toggleEnabledTextForSwitch1onSomeLabel: (id) sender {
if (switch1.on) {
NSArray *appleComputers = [NSArray arrayWithObjects:#"WWWWW" ,#"XXXX", #"YYYY", #"ZZZZ", nil];
NSDictionary *appleComputersDict = [NSDictionary dictionaryWithObject:appleComputers forKey:#"Computers"];
[listOfItems replaceObjectAtIndex:0 withObject:appleComputersDict];
[tblSimpleTable reloadData];
}
Which is working but there is no animation. I understand that in order to add animation, I need to use insertRowsAtIndexPaths:withRowAnimation, so I tried tons of options but it always crashes when executing the insertRowsAtIndexPaths:withRowAnimation method.
My recent try was by doing this:
- (IBAction) toggleEnabledTextForSwitch1onSomeLabel: (id) sender {
if (switch1.on) {
NSIndexPath *path1 = [NSIndexPath indexPathForRow:1 inSection:0]; //ALSO TRIED WITH indexPathRow:0
NSArray *indexArray = [NSArray arrayWithObjects:path1,nil];
[tblSimpleTable insertRowsAtIndexPaths:indexArray withRowAnimation:UITableViewRowAnimationRight];
}
}
What am I doing wrong? How can I make this happen easily? I dont understand this whole indexPathForRow thing...I also dont understand how with this method I can add a label name to the new cell. Please help...thanks!!
It's a two step process:
First update your data source so numberOfRowsInSection and cellForRowAtIndexPath will return the correct values for your post-insert data. You must do this before you insert or delete rows or you will see the "invalid number of rows" error that you're getting.
Then insert your row:
[tblSimpleTable beginUpdates];
[tblSimpleTable insertRowsAtIndexPaths:indexArray withRowAnimation:UITableViewRowAnimationRight];
[tblSimpleTable endUpdates];
Simply inserting or deleting a row doesn't change your data source; you have to do that yourself.
The important thing to keep in mind when using insertRowsAtIndexPaths is that your UITableViewDataSource needs to match what the insert is telling it to do. If you add a row to the tableview, make sure the backing data is already updated to match.
First of all, you should update your data model just before update table itself.
Also you can use:
[tableView beginUpdates];
// do all row insertion/delete here
[tableView endUpdates];
And table will produce all changed at once with animation (if you specify it)
The insertRowsAtIndexPaths:withRowAnimation: AND the changes to your data model both need to occur in-between beginUpdates and endUpates
I've created a simple example that should work on its own. I spent a week fiddling around trying to figure this out since I couldn't find any simple examples, so I hope this saves someone time and headache!
#interface MyTableViewController ()
#property (nonatomic, strong) NSMutableArray *expandableArray;
#property (nonatomic, strong) NSMutableArray *indexPaths;
#property (nonatomic, strong) UITableView *myTableView;
#end
#implementation MyTableViewController
- (void)viewDidLoad
{
[self setupArray];
}
- (void)setupArray
{
self.expandableArray = #[#"One", #"Two", #"Three", #"Four", #"Five"].mutableCopy;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.expandableArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//here you should create a cell that displays information from self.expandableArray, and return it
}
//call this method if your button/cell/whatever is tapped
- (void)didTapTriggerToChangeTableView
{
if (/*some condition occurs that makes you want to expand the tableView*/) {
[self expandArray]
}else if (/*some other condition occurs that makes you want to retract the tableView*/){
[self retractArray]
}
}
//this example adds 1 item
- (void)expandArray
{
//create an array of indexPaths
self.indexPaths = [[NSMutableArray alloc] init];
for (int i = theFirstIndexWhereYouWantToInsertYourAdditionalCells; i < theTotalNumberOfAdditionalCellsToInsert + theFirstIndexWhereYouWantToInsertYourAdditionalCells; i++) {
[self.indexPaths addObject:[NSIndexPath indexPathForRow:i inSection:0]];
}
//modify your array AND call insertRowsAtIndexPaths:withRowAnimation: INBETWEEN beginUpdates and endUpdates
[self.myTableView beginUpdates];
//HERE IS WHERE YOU NEED TO ALTER self.expandableArray to have the additional/new data values, eg:
[self.expandableArray addObject:#"Six"];
[self.myTableView insertRowsAtIndexPaths:self.indexPaths withRowAnimation:(UITableViewRowAnimationFade)]; //or a rowAnimation of your choice
[self.myTableView endUpdates];
}
//this example removes all but the first 3 items
- (void)retractArray
{
NSRange range;
range.location = 3;
range.length = self.expandableArray.count - 3;
//modify your array AND call insertRowsAtIndexPaths:withRowAnimation: INBETWEEN beginUpdates and endUpdates
[self.myTableView beginUpdates];
[self.expandableArray removeObjectsInRange:range];
[self.myTableView deleteRowsAtIndexPaths:self.indexPaths withRowAnimation:UITableViewRowAnimationFade]; //or a rowAnimation of your choice
[self.myTableView endUpdates];
}
#end
For the swift users
// have inserted new item into data source
// update
self.tableView.beginUpdates()
var ip = NSIndexPath(forRow:find(self.yourDataSource, theNewObject)!, inSection: 0)
self.tableView.insertRowsAtIndexPaths([ip], withRowAnimation: UITableViewRowAnimation.Fade)
self.tableView.endUpdates()