I am currently making an app which displays the user's profile. For that purpose, I used an UITableViewCell with custom cells for the different types of data (phone numbers, mail addresses, etc...). There is a maximum of 8 cells per profile.
The user is allowed to edit its profile in the simplest way. When the tableview's editing mode is triggered, all editable labels are replaced by textfields. And then turned back to labels when the modifications are finished.
Homever, there seems to be a problem with the cells that are not visible. Everytime they reappear in the view, they are reloaded, the setEditing:YES method is triggered again, etc... Because of this, every change made in the textfield is lost.
Is there a way to prevent the tableview to remove the non-visible cells and to add them back ? There are only eight cells, so it wouldn't be very resources consuming, and I wouldn't have to save their state everytime a change is made.
PS : I have tried several things with the dequeueReusableCellWithIdentifier method and the identifiers of each cell, but I have not managed to achieve what I want. Everytime I hide a cell, its content is refreshed.
You should use static cell not dynamic. Select table view and change config like image.
And add cell in interface builder!
In this case, you are not helped with UITableView`s Reusability(Reusability is ofcourse a great thing in most cases) but will have too much difficulty in preserving edits. So you can avoid reusability and prepare your cells before hand.
Add an NSMutableArray iVar or property in your ViewController
#property (nonatomic, strong) NSMutableArray *cells;
In your viewDidLoad: prepare your cells without any reuseIdentifier
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
//Creates tableView cells.
[self createCells];
}
- (void)createCells
{
self.cells = [NSMutableArray array];
TCTimeCell *cellCallTime = [[TCTimeCell alloc] initWithTitle:#"CALL" forTimecard:_timecard andTimeEntryType:TCTimeEntryTypeCall];
[_cells addObject:cellCallTime];
TCTimeCell *cellLunchOut = [[TCTimeCell alloc] initWithTitle:#"LUNCH START" forTimecard:_timecard andTimeEntryType:TCTimeEntryTypeLunchOut];
[_cells addObject:cellLunchOut];
TCTimeCell *cellLunchIn = [[TCTimeCell alloc] initWithTitle:#"LUNCH END" forTimecard:_timecard andTimeEntryType:TCTimeEntryTypeLunchIn];
[_cells addObject:cellLunchIn];
TCTimeCell *cellSecondMealOut = [[TCTimeCell alloc] initWithTitle:#"2ND MEAL START" forTimecard:_timecard andTimeEntryType:TCTimeEntryTypeSecondMealOut];
[_cells addObject:cellSecondMealOut];
TCTimeCell *cellSecondMealIn = [[TCTimeCell alloc] initWithTitle:#"2ND MEAL END" forTimecard:_timecard andTimeEntryType:TCTimeEntryTypeSecondMealIn];
[_cells addObject:cellSecondMealIn];
TCTimeCell *cellWrapTime = [[TCTimeCell alloc] initWithTitle:#"WRAP" forTimecard:_timecard andTimeEntryType:TCTimeEntryTypeWrap];
[_cells addObject:cellWrapTime];
}
You can populate your tableView from this array.
- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return self.cells.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
return self.cells[indexPath.row];
}
If you have a sectioned tableView, you can prepare your cells as array of arrays. In that case, your Data Source methods should look like below
- (NSInteger) numberOfSectionsInTableView:(UITableView *)tableView{
return [self.cells count];
}
- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return [self.cells[section] count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
return self.cells[indexPath.section][indexPath.row];
}
Related
I have created expanded tableview using plist, I have populated all the values and images into my tableview. Here I want to make animation for my particular cell when user click that cell.
For example, first time images will be default, next time if I click that cell, the particular cell get expanded. Here I need to make my image has hide; again if user press it will show the image. How to do this?
Every time the user taps a cell, a UITableViewDelegate method gets called, and in this method you can reload the cell:
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
When a cell is reloaded, the method below is called (between the others), and the only thing you have to do is to return a different height according with the current state of the cell:
- (CGFloat)tableView:(UITableView *)tableView
heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return (/* cell state */) ? 100.0 : 50.0;
}
You have multiple ways to store your cell's state, you can do it in its model for example.
There are other ways also, this is only a solution. But the key point is that you have to reload the cell, and return a different height according with the conditions you want to consider.
You can check my full solution in the following github
Here is some of my implementation.
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
movies = [[NSArray alloc] initWithObjects:
(NSArray*)[[Section alloc ] init:#"Istanbul" movieNames:#[#"Uskudar", #"Sariyer"] isExpanded:false],
(NSArray*)[[Section alloc ] init:#"Bursa" movieNames:#[#"Osmangazi", #"Mudanya", #"Nilufer"]
isExpanded:false],
(NSArray*)[[Section alloc ] init:#"Antalya" movieNames:#[#"Alanya", #"Kas"] isExpanded:false], nil
];
}
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return movies.count;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return ((Section*)movies[section]).movies.count ;
}
-(void)toggleSection:(ExpandableHeaderFoorterView *)headerView withSection:(int)section
{
((Section*)movies[section]).expanded = !((Section*)movies[section]).expanded;
[expandableTableView beginUpdates];
for (int i= 0; i< ((Section*)movies[section]).movies.count; i++)
{
NSArray* rowsToReload = [NSArray arrayWithObjects:[NSIndexPath indexPathForRow:i inSection:section], nil];
[expandableTableView reloadRowsAtIndexPaths:rowsToReload
withRowAnimation:UITableViewRowAnimationFade];
}
[expandableTableView endUpdates];
}
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
ExpandableHeaderFoorterView* headerView = [[ExpandableHeaderFoorterView alloc] initWithCustom:((Section*)movies[section]).genre withSection:(int)section withDelegate:self];
return (ExpandableHeaderFoorterView*)headerView;
}
http://www.iostute.com/2015/04/expandable-and-collapsable-tableview.html
You can see this tutorial. It's a very good tutorial based on expandable tableview cell.
I have my main view controller that shows a UITableView.
Each cell of this are custom (I've created a UIView for custom presentation).
For showing these items in my tableView, I populate an array with the content of the "allFilesFolderPath" folder with this code:
- (void)configureView {
_itemArray = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:allFilesFolderPath error:nil];
}
and
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [self.itemArray count];
}
and I create my custom cells for showing them with :
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSUInteger row = [indexPath row];
myItem = [self.itemArray objectAtIndex:row];
NSLog(#"My Item : %#", _itemArray.description);
static NSString *CellIdentifer = #"cardCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifer];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifer];
}
return cell;
}
When I print the array with the NSLog, I get the correct list of item and in the alphabetical order (like how they are stored in the Documents location on my iPhone):
My Item : (
Music,
Music10,
Music2,
Music3,
Music4,
Music5,
Music6,
Music7,
Music8,
Music9,
Photos,
Videos
)
But when I run the app in my iPhone (or in the simulator), the cells are correctly displayed (in the order) until the eighth item. After this number, in my case, instead of having "Music8", "Music9", "Photos", "Video" I come back to the beginning of th array so "Music", "Music10", "Music2" and "Music3"
To better understand what I get, here is the screenshots :
I'm really lost! I've searched (and search again) what I'm doing wrong but I don't find anything, everything is correct for me.
Please help me to find my issue so that I can sleep normally.
EDIT: here is the method I've set to retrieve the myItem string from my other class :
+ (NSString *)getItemName {
return myItem;
}
And here is how I retrieve it from my other class :
NSString *test = [ViewController getItemName];
_itemName.text = test;
EDIT2 : Here is the code used for setting my custom TableViewCell
(sorry for missing these informations
#import "TableViewCell.h"
#implementation TableViewCell
- (void)awakeFromNib {
// Initialization code
[self cardSetup];
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
- (void)cardSetup {
_cardView.layer.masksToBounds = NO;
_cardView.layer.shadowOffset = CGSizeMake(0, 1);
_cardView.layer.shadowRadius = 1;
_cardView.layer.shadowOpacity = 0.3;
NSString *test = [ViewController getItemName];
_itemName.text = test;
}
#end
There is this call named "dequeueReusableCell...". Table view cells are reused. If 8 cells fit on the screen, and you scroll the view up, your ninth row will reuse the cell that was used for the first row. That's why you have to set up your cell in cellForRowAtIndexPath, which apparently you refuse to do.
Cells are used just for display. They are not used for storing data. You should have a data model, accessed by everyone. cellForRowAtIndexPath reads from that data model. And then if something happens (for example by tapping on a button in a cell) that changes the data model, then you change the data model, and the data model should tell all the interested parties that the model has changed.
Your cell in one view and a UILabel elsewhere should definitely not be connected at all. Any changes should propagate through your data model.
You're not using myItem anywhere in cellForRowAtIndexPath: Your cells seem to be getting their text from some other method, when they should be getting it from celForRowAtIndexPath:
I'm a newbie to iOS development. Currently learning by following tutorials from iOS Apprentice by Matthijs Hollemans. I'm using Xcode 6 and Objective-C to make the tutorial apps.
In the book's second tutorial, he teaches his readers how to build a to-do-list app with table views, navigation controllers, etc.
In the beginning:
A custom Label with the text "Label" is placed in the prototype cell of the table. On running the app at this point, "Label" shows up in the app, as expected.
To display custom text in the table view, he asks the readers to set the 'Tag' identifier for the Label and then display custom text using the 'Tag' as shown in the code below. This leads to abnormal behaviour.
What this does is that it displays "Label" on launching the app. However, on scrolling the text off the screen and then back on the screen, the custom text shows up.
To resolve this, instead of setting the 'Tag' identifier on the Label, I set it on the Table View Cell, in spite of the Author of the book specifically warning against doing this. I also had to remove the default text "Label" from the Label in the table view so that the custom text and "Label" don't overlap.
This fixed the problem, but now I'm confused as to what is the correct protocol to follow for setting the 'Tag' identifier when it comes to using table views. Should the 'Tag' be set on Table View Cell or the Label? If it should be set on the Label then what could be the reason for me to run into this problem?
Here's the code for the main ViewController.m
#import "ViewController.h"
#import "ChecklistItem.h"
#interface ViewController ()
#end
#implementation ViewController
{
NSMutableArray * _items;
}
- (void)viewDidLoad {
[super viewDidLoad];
_items = [[NSMutableArray alloc] initWithCapacity:20];
ChecklistItem * item;
item = [[ChecklistItem alloc]init];
item.text = #"Walk the dog";
item.checked = NO;
[_items addObject:item];
item = [[ChecklistItem alloc]init];
item.text = #"Brush teeth";
item.checked = NO;
[_items addObject:item];
item = [[ChecklistItem alloc]init];
item.text = #"Prepare breakfast";
item.checked = NO;
[_items addObject:item];
item = [[ChecklistItem alloc]init];
item.text = #"Soccer practice";
item.checked = NO;
[_items addObject:item];
item = [[ChecklistItem alloc]init];
item.text = #"Eat ice cream";
item.checked = NO;
[_items addObject:item];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
//data source method no.1 to get the number of rows in section for the table view
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [_items count];
}
- (void)configureCheckmarkForCell:(UITableViewCell *)cell withChecklistItem:(ChecklistItem *) item {
//if the item is checked, display checkmark
if (item.checked) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
} else {
cell.accessoryType = UITableViewCellAccessoryNone;
}
}
- (void)configureTextForCell:(UITableViewCell *)cell withChecklistItem:(ChecklistItem *) item {
UILabel * label = (UILabel *)[cell viewWithTag:1000];
label.text = item.text;
}
//data source mathod no.2 to get the cell to display the row in the given index path
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:#"ChecklistItem"];
ChecklistItem * item = _items[indexPath.row];
[self configureTextForCell:cell withChecklistItem:item];
[self configureCheckmarkForCell:cell withChecklistItem:item];
return cell;
}
//delegate method to handle taps on rows
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell * cell = [tableView cellForRowAtIndexPath:indexPath];
ChecklistItem * item = _items[indexPath.row];
[item toggleChecked];
[self configureCheckmarkForCell:cell withChecklistItem:item];
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
#end
I'm not familiar with the tutorial, but using tags to identify labels within cells this way is not a good idea.
The cell should know about it's own label. It's far better to just have a method on the cell that you can pass the text to, and then let the cell take care of displaying the text in the cell.
By using the tag in this way, you are expecting to know too much about the internal implementation of the cell, and this is brittle and is likely to break.
So my answer is to set the tag on neither of them, and to use a proper configuration method of the cell itself.
Edited to add
You can download a simple version of a project for configuring a cell, without using tags here https://bitbucket.org/abizern/so-27713743/get/543739690dc4.zip
An alternative way to solve this is to not deal with a view tag at all, but create a subclass of UITableViewCell and design your layout with this subclass. When you dequeue the cell in tableView: cellForRowAtIndexPath: or get the cell in tableView:didSelectRowAtIndexPath, use your subclass for the cell.
For example, create a subclass of UITableViewCell and give it a label as a property. If you're using xib's or storyboard, attach this label as an outlet. Then you can access that label directly as you would any other property.
I'm working on an app with stocks, arranged in portfolios. So this is a natural fit for the table view, and I'm working on the editing interaction; it's straightforward enough to allow the user to add or delete stocks, drag them around within one portfolio or to another portfolio, but one thing that I haven't been able to do gracefully is let the user drag one portfolio above or below another.
I've got a hacky solution right now, where row 0 of each section is the portfolio name, and if they drag that row above another portfolio, the whole table is reloaded with the portfolios switched. This works, but doesn't feel very natural.
I'm sure I'm not the first to encounter this problem; anyone have a more refined solution?
A related question - how do I let users create a new portfolio/section?
Easy peasy:
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
{
NSMutableArray *_data;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
_data = [NSMutableArray arrayWithObjects:#"One", #"Two", #"Three", nil];
self.tableView.editing = YES;
}
- (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 *identifier = #"reuseIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (!cell)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:identifier];
}
cell.textLabel.text = _data[indexPath.row];
cell.showsReorderControl = YES;
return cell;
}
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
{
return UITableViewCellEditingStyleNone;
}
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
{
return YES;
}
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath
{
[_data exchangeObjectAtIndex:sourceIndexPath.row withObjectAtIndex:destinationIndexPath.row];
}
#end
EDIT:
What you've asked for now is a little more complicated. I created an example that puts tables into cells, which gives you nested cells. The example is highly unattractive, but it works, and there's no reason you can't make it look pretty, so check it out:
https://github.com/MichaelSnowden/TableViewInCell
If that doesn't work for you, try making UITableView moveSection:(NSInteger) toSection:(NSInteger) look pretty.
Documentation for that method is here.
My experience with the above method was that it's very easy to use, and it looks nice when it's called. A smart way to use it would be to create headers with tap gesture recognizers. On the first tap, highlight that section and record that indexPath, and on the second tap, call the method on the two index paths. It should work nicely, but you won't get drag-and-drop from it.
I have created a viewbased application, having modal linked viewcontrollers for an Iphone storyboard without a Navigation Controller. For One of my View Controllers i have designed a tableview with dynamic cells:
- (void)viewDidLoad {
[super viewDidLoad];
tabledata =[[NSArray alloc] initWithObjects:#"Data Mining",#"Software Quality",#"Project Management",#"Data Base", nil];
tablesubtitles =[[NSArray alloc] initWithObjects:#"Test parsing and KDD",#"Management of Software Creation Cycle",#"Roles in a Project",#"Database Lifecycle process", nil];
- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [tabledata count];
}
- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell=nil;
cell= [ tableView dequeueReusableCellWithIdentifier:#"Cell1"];
if (cell == nil) {
cell= [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"Cell1"];
}
cell.textLabel.text= [tabledata objectAtIndex:indexPath.row];
cell.detailTextLabel.text =[tablesubtitles objectAtIndex:indexPath.row];
return cell;
}
Next thing i would like to implement is to link tableview rows to a ViewController which has a couple of textboxes available on it. The big idea is that on selecting a TableItem i want to load a ViewController and populate automatically textboxes based on selected items, also it would be good to have an edit/save possibility.
For Example: I have a Tableview with items
Dodge
Chrysler
Chevrolete
Ford
On pressing "Dodge" a new ViewController appears having multiple TextBoxes and data is filled in with some details like "Since 1900".
On editing the "Since 1900" textbox and pressing Save button, the control returns to viewcontroller with TableView in it.
After pressing "Dodge" the second time previously saved items are displayed.
I've tried a lot of variants, combinations how to achive it, I am new to Xcode and any help would be much appriciated.
Thank you in advance.
It's not hard. Basically you just need to implement didSelectRowAtIndexPath in your UITableView delegate. To see how to do this, create a new project in XCode and make it a MasterDetail application. That gives you the boilerplate code for a table view with selectable rows. The Master view is your table, the Detail view is how you handle the selected row.