I'm attempting to implement an inline UIPicker inside a table-view cell, similar to both this and this SO question. I believe I'm close in my implementation, but at the moment, no picker is displayed when I select the appropriate cell. Can anyone point me in the right direction in regards to what I'm doing wrong? Thank you!
Below is where I determine what rows occur in each section:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
switch (indexPath.section) {
case NotificationsSection:
return [self tableView:tableView cellForAreaOneRowAtIndexPath:indexPath];
break;
case RedZoneSection:
return [self tableView:tableView cellForAreaTwoRowAtIndexPath:indexPath];
break;
case TimeOfDaySection:
return [self tableView:tableView cellForAreaThreeRowAtIndexPath:indexPath];
break;
default:
return nil;
break;
}
}
Below is where I check the number of rows in each section. I suspect my problem may lie here, but I am not completely sure.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
switch (section) {
case AreaOneSection:
return AreaOneRows;
break;
case AreaTwoSection:
return TotalAreaTwoRows;
break;
case AreaThreeSection:
return TotalAreaThreeRows;
break;
default:
return 0;
break;
}
}
Below is where I return the height for each row:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
CGFloat rowHeight = self.tableView.rowHeight;
// if (indexPath.section == TimeOfDaySection && indexPath.row == HourTimeZoneRow && self.timePickerIsShowing == NO){
return rowHeight;
}
Finally, below is where I check if the user selected the index path that I want to insert the UIPicker cell below. If they did, then I call a method to show the the picker.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
if (indexPath.section == SectionThree && indexPath.row == RowOne && self.timePickerIsShowing == NO){
[tableView beginUpdates];
[self showTimePicker];
[tableView endUpdates];
} else{
[self hideTimePicker];
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
}
}
Finally, below is where I show and hide the UIPicker.
- (void)showTimePicker
{
self.timePickerIsShowing = YES;
self.timePicker.hidden = NO;
//build the index path to where the picker should be inserted here
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:HourTimeZoneRow + 1 inSection:TimeOfDaySection];
static NSString *CellIdentifier = #"TimePickerCell";
UITableViewCell *cell = (UITableViewCell*)[self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
_timePicker = [[UIPickerView alloc] initWithFrame:CGRectMake(0, 0, self.tableView.bounds.size.width, 160)];
[cell.contentView addSubview:self.timePicker];
[self.tableView insertRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
[self.tableView reloadData];
self.timePicker.alpha = 0.0f;
[UIView animateWithDuration:0.25 animations:^{
self.timePicker.alpha = 1.0f;
}];
}
- (void)hideTimePicker {
self.timePickerIsShowing = NO;
self.timePicker.hidden = YES;
[self.tableView reloadData];
[UIView animateWithDuration:0.25
animations:^{
self.timePicker.alpha = 0.0f;
}
completion:^(BOOL finished){
self.timePicker.hidden = YES;
}];
}
In your showPicker function, you don't seem to do anything? You create a cell, do things with it then it dies when that function ends. The cell is not added anywhere from what I can see?
You need to add the picker inside cellForRowAtIndexPath, for the index path you know needs a picker.
What I do requires very little coding. I create a prototype cell in interface builder which contains a picker view. In my case I also add a toolbar above the picker which I can put buttons in to allow Cancel and Done. Add suitable properties to pass in the current values for the picker to show initially. Add a delegate to use to inform the creator (your tableView) of changes in picker values. You can wait for it to finish picking, or use it to update live the cell its editing values for by reloading the cell being edited every time the value changes. I prefer that the picker can pick a value and you can commit it or cancel it.
When a cell needs edited, I update my data model to insert an editing entry then call [tableView reload]. In my case selecting a cell starts editing, clicking cancel/done ends editing.
The table view at this point will start asking for cells. This time one of those will be your new picker cell, which will be for editing the cell it is below. When you create it you pass it the data model reference for the data it is to edit.
So you can achieve all this by simply adding a new prototype cell type and creating it inside cellForRowAtIndexPath when required.
You have a choice as to how to remove the picker. In my case I have Cancel/Done buttons which just removes the entry from the data model and reloads the table again, resulting in it never being created. You could also make the mode as being click on a cell to add and click again to remove. Again you just update the data model and reload. See how the time picker works on the Calander app for a new appointment.
You may be thinking this is a lot of reloads. However it only affects what is on screen and I found it to be easier than trying to figure out the affected cells all the time. Its also very smooth. When it is working you can always optimize the code.
Use constraints in the cell to make sure it has the layout you want.
Related
I have UITableViewCell with detailed arrow that points to top or to down.
Than I tap on this cell I want to expand it. So I will reload UITableView and I want to animate rotation ofUIImageView`.
Question - is it possible to animate rotation of UIImageView simultaneously with reloading data in UITableView?
Yes, you can create a table with expandable/contactable cell's, also while expanding you can animate the containers of the cell's.
But, one thing until and unless it is a requirement to reload table, I'll suggest you to reload row with the animations performed simultaneously.
Also as you asked in comment what's the best way to track whether the cell needs to be shown simple or detailed, I think it depends on your requirements, like if you need to show only one cell expanded at a time, then you just keep the index of current expanded cell in the class else if you need to show more than one cell expanded at a time than you can use the data source to keep the information whether the cell needs to be show as simple or detailed. If you do not have the data source then you can do the same by creating a list of cell's index path keeping track of detailed cells.
Programmatically you need to do following things to show simple/ detailed cells-
Note: I have assumed to show only one cell expanded at a time.
1) Create a member variable to keep track of expanded cell.
NSIndexPath *expandedCellIndexPath;
2) Return height for expanded cell(if expanded) otherwise for simple cell
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if([expandedCellIndexPath compare:indexPath] == NSOrderedSame)
return 100.0f;
return 50.0f;
}
3) Create cell as-
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"cellId";
CustomCell *customCell = nil;
customCell = (CustomCell *) [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if(customCell == nil)
{
NSString *nibName = #"CustomCell_iPhone";
customCell = (CustomCell *)[[[NSBundle mainBundle] loadNibNamed:nibName owner:self options:nil] lastObject];
customCell.selectionStyle = UITableViewCellSelectionStyleNone;
}
if([expandedCellIndexPath compare:indexPath] == NSOrderedSame)
{
[customCell.m_arrowImageView setImage:[UIImage imageNamed:DOWN_ARROW_KEY]];
[self showSelectedCellAnimations:customCell];
}
else
{
[customCell.m_arrowImageView setImage:[UIImage imageNamed:UP_ARROW_KEY]];
[self showUnSelectedCellAnimations:customCell];
}
// you can also set default up arrow image and rotate it with down arrow image and vice versa
return customCell;
}
4) Rotate Up/Down arrow image using-
- (void)rotateImage:(UIImageView *)image duration:(NSTimeInterval)duration
curve:(int)curve degrees:(CGFloat)degrees
{
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:duration];
[UIView setAnimationCurve:curve];
[UIView setAnimationBeginsFromCurrentState:YES];
CGAffineTransform transform =
CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(degrees));
image.transform = transform;
[UIView commitAnimations];
}
5) Call above method as-
[self rotateImage:customCell.m_helpImage duration:0.25
curve:UIViewAnimationCurveEaseIn degrees:0.0];
6) Inside didSelectRowAtIndexPath: set the expandedCellIndexPath to the current index path which needs to be expanded and reload previous expanded row to contract and reload current expandedCellIndexPath row to expand, like-
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:NO];
CustomCell *customCell = nil;
customCell = (CustomCell *)[tableView cellForRowAtIndexPath:indexPath];
NSIndexPath *previousIndexPath = expandedCellIndexPath;
if([expandedCellIndexPath compare:indexPath] != NSOrderedSame)
expandedCellIndexPath = indexPath;
[self.m_tableView reloadRowsAtIndexPaths:#[previousIndexPath]
withRowAnimation:UITableViewRowAnimationFade];
[self.m_tableView reloadRowsAtIndexPaths:#[expandedCellIndexPath]
withRowAnimation:UITableViewRowAnimationFade];
[self.m_tableView scrollToRowAtIndexPath:indexPath
atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}
I am new to iOS development. I have doubt related to UITableViewCell height.
I am using one tableview, I have taken prototype cell in storyboard. The cell height I have given is 75. But what I need is when I select cell height will be increase and show 1 button (which is in black color in the image). The button is declared in UITableViewCell's custom class. Below of that image I have one label. If button is showing I need to place the label at the bottom of button or else in the place of button I need to show that label.
Thanks in advance.......
You can use [tableView beginUpdates] and [tableView endUpdates];
for Ex:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self hideControlMethodsTable];//Declare method to hide when button tableview loads initially
[tableList beginUpdates];
[tableList deselectRowAtIndexPath:indexPath animated:YES];
CellRes = (ReservationTableViewCell*)
[tableList cellForRowAtIndexPath:indexPath];
if (cellSelectedFirstTime == true) {
self.selectedRow = indexPath.row;
[self showControlsMethodsTable];
cellSelectedFirstTime = false;
}
else{
if(self.selectedRow == indexPath.row){
self.selectedRow = -1;
[self hideControlMethodsTable];
}
else{
self.selectedRow = indexPath.row;
[self showControlsMethodsTable];
}
}
[tableList endUpdates];
}
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if(openForFirstCell == true){
if (cellSelectedFirstTime==true) {
CellRes = [UIColor lightGrayColor];
return 180.;
}
return 60;
}
}
Hope this helps you.
I would like to make something like expandable cells. Result I'm trying to achieve is:
Actually I have:
Here is my code:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
DoctorCell *cell = self.prototypeCell;
DoctorModel *doctor = [self.fetchController objectAtIndexPath:frcIndexPath];
// depending on isActive property I add (or do not add) some content.
[cell cellForDoctor:doctor isActiveCell:[self.activeCellIndexPath isEqual:indexPath]];
[cell setNeedsUpdateConstraints];
[cell setNeedsLayout];
[cell layoutIfNeeded];
CGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
return height + 1;
}
Here is my cell selection method
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:NO];
NSIndexPath *oldIndexPath = self.activeCellIndexPath;
if ([indexPath isEqual:self.activeCellIndexPath]) {
self.activeCellIndexPath = nil;
oldIndexPath = nil;
} else {
self.activeCellIndexPath = indexPath;
}
[self.doctorTableView beginUpdates];
[self.doctorTableView reloadRowsAtIndexPaths:[NSArray arrayWithObjects:indexPath, oldIndexPath, nil] withRowAnimation:UITableViewRowAnimationNone];
[self.doctorTableView endUpdates];
[self.doctorTableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
As you can see, my animation looks weird (kind of moving to wrong position, jumps, etc). What I'm doing wrong? Or do I need to pick another approach for expanding cells?
You can use UITableViews animation automation like this:
Call [tableView beginUpdates];
Change the data source.
Insert or delete rows and sections, or reload them
This should cause new heights to be calculated, etc.
Make sure you use something appropriate for the withRowAnimation parameter of these methods
Call [tableView endUpdates];
Your animation should now perform as expected.
Maybe your problem is that you selected UITableViewRowAnimationNone for the row animation.
I have a UITableView with cards. Every time I want to add a new card after pushing the draw button I want it to be moved from the center of the view to the location in the table it should be placed with some basic animation. I have managed to get the destination of the new drawn card with this code:
cellRectInTableDrawnCard = [[self playerCardsTable] rectForRowAtIndexPath:drawnCardIndexPath];
cellInSuperviewDrawnCard = [[self playerCardsTable] convertRect:cellRectInTableDrawnCard toView:[[self playerCardsTable] superview]];
However, to determine the cellRectInTableDrawnCard I need to reload the playerCardsTable with reloadData but this shows the drawn card already. It is just a fraction of a second because I place the new card in the table with an animation which fires just after the reloadData. Doing a reload after the animation is not an option, because I don't have the drawnCardIndexPath then.
Is there a way I can get the rect without reloading the tableview? Or else, is there a way I can hide the new cell after reloadData and show it after the animation is done?
Thanks!
You probably want to insert the row and populate it individually and not do a complete table reload.
The code snippet shows a button that uses insertRowsAtIndexPaths:indexPathArray to add a new row which gives you the cell rect to do you animation stuff.
When you're done with animation just use reloadRowsAtIndexPaths to populate the cell value (show you're card I'd guess).
Use a bool to decide in cellForRowAtIndexPath when you should display the new card (basically after you call reloadRowsAtIndexPaths).
- (IBAction)butAddCardToHandAction:(id)sender {
// Add a blank record to the array
NSString *strCard = #"New Card";
_showCard = NO;
[_arrayHandCards addObject:strCard];
// create the index path where you want to add the card
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:(_arrayHandCards.count - 1) inSection:0];
NSArray *indexPathArray = [NSArray arrayWithObjects:indexPath,nil];
// Update the table
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:indexPathArray withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates];
// Ok - you got a blank record in the table, get the cell rect.
CGRect cellRectInTableDrawnCard = [[self tableView] rectForRowAtIndexPath:indexPath];
NSLog(#"My new rect has y position : %f",cellRectInTableDrawnCard.origin.y);
//Do the animation you need to do and when finished populate the selected cell
_showCard = YES;
[self.tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationNone];
}
Code to control whats displayed in cell:
- (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];
}
// Set up the cell and use boolean to decide what to show
NSString *strToDisplayInCell;
if (!_showCard)
{
strToDisplayInCell = #"";
}
else
{
NSString *strToDisplayInCell = [_arrayHandCards objectAtIndex:indexPath.row];
cell.textLabel.font = [UIFont fontWithName:#"Helvetica" size:15];
cell.textLabel.text = strToDisplayInCell;
}
return cell;
}
I have a table view of custom cells and some buttons in each cell.Clicking on any of the button inside the cell will reveal another custom view below that cell.Next click on the same button will collapse the view and need this same for all cells.I tried with insertrow method on the button click but in vain.How can i do this with using only the table view delegates.
This is what i tried:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"CustomCell_For_Dashboard";
CustomCellFor_Dashboard *customCell = (CustomCellFor_Dashboard *)[tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (customCell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"CustomCellFor_Dashboard" owner:self options:nil];
customCell = [nib objectAtIndex:0];
}
[customCell.howyoulfeelBtn addTarget:self action:#selector(buttonclicked:) forControlEvents:UIControlEventTouchUpInside];
customCell.nameLabel.text = #"test";
customCell.imgView.image = [UIImage imageNamed:#"Default.png"];
// customCell.prepTimeLabel.text = [prepTime objectAtIndex:indexPath.row];
return customCell;
}
-(void)buttonclicked:(id)sender{
NSIndexPath *indexPath = [myTable indexPathForCell:sender];
[myTable beginUpdates];
NSIndexPath *insertPath = [NSIndexPath indexPathForRow:indexPath.row inSection:indexPath.section];
[myTable insertRowsAtIndexPaths:[NSArray arrayWithObject:insertPath] withRowAnimation:UITableViewRowAnimationTop];
}
can anyone help me?
I got the same task on one project with just one thing different: There were no buttons, just tapping on cell will expand or collapse it.
There are several things you should edit in your code. First, the button method code will look something like this:
- (void) collapseExpandButtonTap:(id) sender
{
UIButton* aButton = (UIButton*)sender; //It's actually a button
NSIndexPath* aPath = [self getIndexPathForCellWithButtonByMagic:aButton];
//expandedCells is a mutable set declared in your interface section or private class extensiont
if ([expandedCells containsObject:aPath])
{
[expandedCells removeObject:aPath];
}
else
{
[expandedCells addObject:aPath];
}
[myTableView beginEditing];
[myTableView endEditing]; //Yeah, that old trick to animate cell expand/collapse
}
Now the second thing is UITableViewDelegate method:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([expandedCells containsObject:indexPath])
{
return kExpandedCellHeight; //It's not necessary a constant, though
}
else
{
return kNormalCellHeigh; //Again not necessary a constant
}
}
Key thing here is to determine if your cell should be expanded/collapsed and return right height in delegate method.
Going off of what #eagle.dan.1349 said, this is how to do it on the clicking of the cell. In storyboard, you also need to set the table cell to clip subviews, otherwise the content that would be hidden will show.
.h
#property (strong, nonatomic) NSMutableArray *expandedCells;
.m
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([self.expandedCells containsObject:indexPath])
{
[self.expandedCells removeObject:indexPath];
}
else
{
[self.expandedCells addObject:indexPath];
}
[tableView beginUpdates];
[tableView endUpdates];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
CGFloat kExpandedCellHeight = 150;
CGFloat kNormalCellHeigh = 50;
if ([self.expandedCells containsObject:indexPath])
{
return kExpandedCellHeight; //It's not necessary a constant, though
}
else
{
return kNormalCellHeigh; //Again not necessary a constant
}
}
Saw this post and just wanted to give my 2 cents as my solution to this is very similar to the chosen answer (the tapping of a whole area).
Many people architect this by using just cells alone, but I believe there is a way to build this that might align better with what people are trying to achieve:
There are headers and there are cells. Headers should be tappable, and then cells underneath the headers would show or hide. This can be achieved by adding a gesture recognizer to the header, and when tapped, you just remove all of the cells underneath that header (the section), and viceversa (add cells). Of course, you have to maintain state of which headers are "open" and which headers are "closed."
This is nice for a couple of reasons:
The job of headers and cells are separated which makes code cleaner.
This method flows nicely with how table views are built (headers and cells) and, therefore, there isn't much magic - the code is simply removing or adding cells, and should be compatible with later versions of iOS.
I made a very simple library to achieve this. As long as your table view is set up with UITableView section headers and cells, all you have to do is subclass the tableview and the header.
Link: https://github.com/fuzz-productions/FZAccordionTableView
I also had a same situation and my solution was to put a button on top of the Section Title with viewForHeaderInSection method.
noOfRows defines how many rows are there in each section and button.tag keeps which button of section is pressed.
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
UIButton *btnSection = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, tableView.frame.size.width, tableView.frame.size.height)];
btnSection.tag = section;
[btnSection setTitle:[sectionArray objectAtIndex:section] forState:UIControlStateNormal];
[btnSection addTarget:self action:#selector(sectionButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
return btnSection;
}
- (void)sectionButtonTapped:(UIButton *)button {
sectionIndex = button.tag;
if (button.tag == 0) {
noOfRows = 3;
} else if (button.tag == 1) {
noOfRows = 1;
} else if (button.tag == 2) {
noOfRows = 2;
}
[self.tableView reloadData];
}
Hope this will help you..