I have a rather complex form being laid out in a UITableView. This form has some UICollectionView inside a table view cell and also some pickers that show up the same way it does on Calendar app:
Now when I do this on my app, one of the UICollectionView I have gets taller than how it started - and it keeps going (I added a red border to make sure it was the UICollectionView that was being resized):
I tried debugging the UICollectionView view property but it never changes (and it's getter/setter aren't called more than what I expected) - even though when I print it on cellForRowAtIndexPath it does shows up resized. Is there any way I can debug this better, or have someone been at the same situation?
Thanks!
Code:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//self.campos is a dictionary with sections and rows stored
NSMutableArray *infoCampos = [[self.campos objectAtIndex:indexPath.section] objectForKey:#"campos"];
NSDictionary *infoCampo = [infoCampos objectAtIndex:indexPath.row];
if ([[infoCampo objectForKey:#"nome"] isEqualToString:#"dosePorComprimido"]) {
//Remove picker cell if finds it
for (infoCampo in infoCampos) {
if ([infoCampo objectForKey:#"view"] == self.dosagemMedicamento.view) {
[infoCampos removeObject:infoCampo];
[tableView reloadSections:[NSIndexSet indexSetWithIndex:indexPath.section] withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView deselectRowAtIndexPath:indexPath animated:YES];
return;
}
}
//Insert new cell
infoCampo = [infoCampos objectAtIndex:indexPath.row];
[infoCampos addObject:#{#"tipo":#5, #"view":self.dosagemMedicamento.view, #"nome":[infoCampo objectForKey:#"nome"]}];
[tableView reloadSections:[NSIndexSet indexSetWithIndex:indexPath.section] withRowAnimation:UITableViewRowAnimationAutomatic];
[self.dosagemMedicamento.view.superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[pickerDosagem]|" options:0 metrics:nil views:#{#"pickerDosagem":self.dosagemMedicamento.view}]];
}
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSDictionary *infoCampo = [[[self.campos objectAtIndex:indexPath.section] objectForKey:#"campos"] objectAtIndex:indexPath.row];
UIView *view;
if ([infoCampo objectForKey:#"view"]) {
view = (UIView *) [infoCampo objectForKey:#"view"];
return view.frame.size.height;
}
return 44.0f;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:
(NSIndexPath *)indexPath
{
UITableViewCell *cell;
NSMutableDictionary *infoCampo = [[[self.campos objectAtIndex:indexPath.section] objectForKey:#"campos"] objectAtIndex:indexPath.row];
UIView *view;
cell = [tableView dequeueReusableCellWithIdentifier:[[infoCampo objectForKey:#"nome"] stringByAppendingString:#"ViewLinhaCellView"] forIndexPath:indexPath];
view = [cell viewWithTag:1];
[view addSubview:[infoCampo objectForKey:#"view"]];
return cell;
}
EDIT: forgot to mention that I'm not updating the table view with reloadData but only the needed sections/rows with reloadRowsAtIndexPaths and reloadSections. So that makes it even weirder because I'm not reloading that particular section.
EDIT 2: added data source e delegate code
I believe your problem is here, in the implementation of tableView:heightForRowAtIndexPath:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSDictionary *infoCampo = [[[self.campos objectAtIndex:indexPath.section] objectForKey:#"campos"] objectAtIndex:indexPath.row];
UIView *view;
if ([infoCampo objectForKey:#"view"]) {
view = (UIView *) [infoCampo objectForKey:#"view"];
return view.frame.size.height;
}
return 44.0f;
}
The view is probably getting resized by auto layout or its autoresizing mask. There isn't enough info here to say exactly why, but table views and auto layout can have some funny behavior. I recommend just returning a height that you set, rather than getting the height from the view:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSDictionary *infoCampo = [[[self.campos objectAtIndex:indexPath.section] objectForKey:#"campos"] objectAtIndex:indexPath.row];
UIView *view;
if ([infoCampo objectForKey:#"view"]) {
if ([infoCampo objectForKey:#"viewHeight"]) {
return [[infoCampo objectForKey:#"viewHeight"] floatValue];
} else {
return 216.0f;
}
}
return 44.0f;
}
The viewHeight key allows you to specify a custom height for each cell's view, but this implementation also returns a default height (216) that happens to be the standard height for picker views.
Related
I have a UIWebView inside my custom UITableViewCells. The problem is that I don't quite know how to dynamically size my UITableViewCell cells since the webview inside the cell needs to render before I can know the webview's height.
My proposed solution is as follows:
CustomCell.m conforms to UIWebViewDelegate and it returns its own height with delegate method after the webview inside it has finished loading.
- (void)webViewDidFinishLoad:(UIWebView *)webView {
CGFloat height = [[webView stringByEvaluatingJavaScriptFromString:#"document.body.scrollHeight"] floatValue];
[self.contentWebview setFrameHeight:height];
[self.delegate cellDidFinishLoadingWithCell:self withHeight:height];
}
Then TableViewController.m conforms to the cell's delegate, and redraws by doing something like:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(nonnull NSIndexPath *)indexPath {
return [self.heights[[NSString stringWithFormat:#"%i%i",indexPath.section,indexPath.row]] floatValue];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UIWebViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"webview-cell" forIndexPath:indexPath];
MyData *d = self.data[indexPath.section];
[cell setUpCellForData:d];
[cell setDelegate:self];
return cell;
}
- (void)cellDidFinishLoadingWithCell:(LessonTableViewCell *)cell withHeight:(CGFloat)height {
NSIndexPath *indexPath = [self.tableview indexPathForCell:cell];
NSString *key = [NSString stringWithFormat:#"%i%i",indexPath.section,indexPath.row];
if (!self.heights[key]) {
[self.heights setObject:[NSNumber numberWithFloat:height] forKey:key];
}
[self.tableview beginUpdates];
[self.tableview endUpdates];
}
Some cells seem to be the correct height, but many of them end up having a height of 0...
remove :
[self.tableView beginUpdates];
[self.tableView endUpdates];
add :
[self.tableView reloadData];
This is Apple Documents:
beginUpdates
reloadData
https://stackoverflow.com/a/8174094/2530660
I'm trying to create a dynamic UITableView where a cell can expand/collapse as the user selects the cell.
- (void)setUpCell:(DynamicTableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
cell.label.text = [self.dataSource objectAtIndex:indexPath.row];
cell.secondLabel.text = [self.dataSource objectAtIndex:self.dataSource.count - indexPath.row - 1];
if ([self.isVisible[indexPath.row] isEqual:#NO]) {
cell.secondLabel.hidden = YES;
} else {
cell.secondLabel.hidden = NO;
}
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.dataSource.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
DynamicTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
[self setUpCell:cell atIndexPath:indexPath];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
DynamicTableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
if ([self.isVisible[indexPath.row] isEqual: #YES]) {
self.isVisible[indexPath.row] = #NO;
cell.secondLabel.hidden = YES;
} else {
self.isVisible[indexPath.row] = #YES;
cell.secondLabel.hidden = NO;
}
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
static DynamicTableViewCell *cell = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
});
[self setUpCell:cell atIndexPath:indexPath];
return [self calculateHeightForConfiguredSizingCell:cell];
}
- (CGFloat)calculateHeightForConfiguredSizingCell:(DynamicTableViewCell *)sizingCell {
[sizingCell layoutIfNeeded];
CGSize size = [sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
return size.height;
}
I forked this project and have the test code here.
Once the cell has been sized it does not change when selecting the cell, only hides/shows the content of the cell. I've tried replacing the explicit size calculation with UITableViewAutomaticDimension. Also tried reloading the cell. Seems once the cell size has been calculated, it does not change.
Any suggestions as to what to try would be greatly appreciated!
In iOS development a view never collapses if you set the hidden property to true.
Instead you should use autolayout. Assuming your view has two labels vertically stacked on top of each other, pin the first label to the cells contentView's top, give it a height constraint, pin the second label's top to the first labels bottom, pin the second labels bottom to the cell's contentView bottom. Set the height of the second label, and save this constraint in a variable, lets called it secondLabelHeightConstraint, now you can collapse and expand the cell by setting the value of secondLabelHeightConstraint to 0 or what ever value you would like.
I could not figure out why the scroll view is not being scrolled whereas the current_set variable is getting increased.What is the exact position to write the code to scroll the UIScrollView when it is a subview of the UITableView?
Here is my code:
The definition for tableView:cellForRowAtIndexPath: method:
- (UITableViewCell *)tableView:(UITableView *)tableView1 cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *cellIdentifier=[NSString stringWithFormat:#"regularExerciseCell%li",indexPath.row];
RegularExerciseCell *cell=[tableView1 dequeueReusableCellWithIdentifier:cellIdentifier];
if(!cell)
{
[tableView1 registerNib:[UINib nibWithNibName:#"RegularExerciseCell" bundle:nil] forCellReuseIdentifier:#"regularExerciseCell"];
cell=[tableView1 dequeueReusableCellWithIdentifier:#"regularExerciseCell"];
}
NSLog(#"cellForRow At indexPath %li",indexPath.row);
return cell;
}
Here is the definition for tableView: willDisplayCell:forRowAtIndexPath: method
-(void)tableView:(UITableView *)tableView willDisplayCell:(RegularExerciseCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
if(selectedIndex==indexPath.row)
{
cell.routineViewCard.hidden=NO;
//SCROLL VIEW
float scrollView_width=[UIScreen mainScreen].bounds.size.width;
cell.setsScrollView.tag=indexPath.row;
totalSetsInActiveExercise=[array count];
for (int k=0; k<=totalSetsInActiveExercise; k++)
{
[cell.setsScrollView addSubview:[self subviewOfScrollView:scrollView_width]];
}
cell.setsScrollView.contentSize = CGSizeMake(scrollView_width*([workoutViewSetData count]+1),cell.setsScrollView.frame.size.height);
if(condition) //this condition may be true or false depending upon the scenario
{
[self moveToNextSet:indexPath.row and:#"left"];
}
}
else
{
cell.routineViewCard.hidden=YES;
}
}
The method which is actually scrolling the scroll view
-(void)moveToNextSet:(long)sender_tag and:(NSString*)direction
{
NSIndexPath *indexPath=[NSIndexPath indexPathForItem:sender_tag inSection:1];
RegularExerciseCell *cell=(RegularExerciseCell*) [workoutTableView cellForRowAtIndexPath:indexPath];
if ([direction isEqualToString:#"right"])
{
if(current_set!=0)
current_set--;
}
else if([direction isEqualToString:#"left"])
{
current_set++;
}
CGRect frame = CGRectMake((cell.setsScrollView.bounds.size.width*(current_set)),0,cell.setsScrollView.bounds.size.width,cell.setsScrollView.bounds.size.height);
[cell.setsScrollView scrollRectToVisible:frame animated:YES];
}
Instead of using the 'scrollRectToVisible' method, you may try to set 'contentOffset' of the ScrollView.
[cell.setsScrollView setContentOffset:CGPointMake(x, 0) animated:true];
x is the point you want the scrollview scroll to. If you set to (0,0) which is the front.
I have a custom UITableViewCell, and when it's selected, it expands and adds a UILabel to the selected cells UIView that I added in the storyBoard.
When I run the app and select a cell, the label gets added to myView as expected. The problem is, when I scroll down, the label is also shown at another cell.
Apparently the reason its behaving like so, is because I'm reusing the cell and I don't clean them as Emilie stated. I'm trying to call the method of prepareForReuse and 'cleaning' the cell, but I'm having trouble doing that. Here is my code:
- (void)prepareForReuse {
NSArray *viewsToRemove = [self.view subviews];
for (UILablel *v in viewsToRemove) {
[v removeFromSuperview];
}
Doing that, cleans even the selected cells label.
- (void)viewDidLoad {
self.sortedDictionary = [[NSArray alloc] initWithObjects:#"Californa", #"Alabama", #"Chicago", #"Texas", #"Colorado", #"New York", #"Philly", #"Utah", #"Nevadah", #"Oregon", #"Pensilvainia", #"South Dekoda", #"North Dekoda", #"Iowa", #"Misouri", #"New Mexico", #"Arizona", #"etc", nil];
self.rowSelection = -1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
CategorieCell *customCell = [tableView dequeueReusableCellWithIdentifier:#"cellID" forIndexPath:indexPath];
customCell.title.text = [self.sortedDictionary objectAtIndex:indexPath.row];
return customCell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
CategorieCell *customCell = (CategorieCell *)[tableView cellForRowAtIndexPath:indexPath];
if (self.info) {
[self.info removeFromSuperview];
}
self.info = [[UILabel alloc] init];
[self.info setText:#"Hello"];
[self.info setBackgroundColor:[UIColor brownColor]];
CGRect labelFrame = CGRectMake(0, 0, 50, 100);
[self.info setFrame:labelFrame];
[customCell.infoView addSubview:self.info];
NSLog(#"%ld", (long)indexPath.row);
self.rowSelection = [indexPath row];
[tableView beginUpdates];
[tableView endUpdates];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([indexPath row] == self.rowSelection) {
return 159;
}
return 59;
}
The answer is quite simple : you reuse your cell like you should, but never clean them
Reusing your UITableViewCell means that the cell you clicked on previously will be reused when it will go off-screen.
When clicked, you add a view to your UITableViewCell. When reused, the view is still there because you never remove it.
You have two choices : One, you could set a tag of the self.info view (or check with the indexpath you're keeping in memory), then check when you dequeue the cell if the info view is there, and remove it. The cleaner solution would be to implement the view removal by overriding the prepareForReuse method of your custom UITableViewCell
Precision
The first thing you need to do is set a tag for your self.info view after initializing it:
[self.info setTag:2222];
If you want to keep it as simple as possible, you could check and remove the self.info view directly in your cellForRowAtIndexPath method :
CategorieCell *customCell = [tableView dequeueReusableCellWithIdentifier:#"cellID" forIndexPath:indexPath];
customCell.title.text = [self.sortedDictionary objectAtIndex:indexPath.row];
if [customCell.infoView viewWithTag: 2222] != nil {
[self.info removeFromSuperview]
}
return customCell;
I am not a percent sure this code compiles, I cannot test it on my side for now. Hope it works !
i have a custom cell with a button and a blue blank view in it, when the button is tapped, the cell should change the blue view's cell height constraint. But now when the blue view's height is changed, the cell height doesn't change, i want the cell height to change the same amount as the blue view, but now it doesn't.
Here is the constraint.
the result is
what is want is update the cell height to accommodate the blue view.
here is the code.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 10;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"TestZanTableViewCell";
TestZanTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
[self configureCell:cell forIndexPath:indexPath];
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
static TestZanTableViewCell *cell;
if (cell == nil) {
cell = [tableView dequeueReusableCellWithIdentifier:#"TestZanTableViewCell"];
}
[self configureCell:cell forIndexPath:indexPath];
NSLog(#"%f", [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height + 1.0f);
return [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height + 1.0f;
}
- (void)configureCell:(TestZanTableViewCell *)cell forIndexPath:(NSIndexPath *)indexPath {
if (cell.tapped) {
cell.heightOfLikeView.constant = 50;
}
cell.testLabel.text = #"Helloworld";
[cell.zanButton addTarget:self action:#selector(didTapButton:) forControlEvents:UIControlEventTouchUpInside];
}
- (void)didTapButton:(id)sender {
// Cast Sender to UIButton
UIButton *button = (UIButton *)sender;
// Find Point in Superview
CGPoint pointInSuperview = [button.superview convertPoint:button.center toView:self.tableView];
// Infer Index Path
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:pointInSuperview];
TestZanTableViewCell *cell = (TestZanTableViewCell *)[self.tableView cellForRowAtIndexPath:indexPath];
cell.tapped = YES;
[self.tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
[cell.contentView setNeedsUpdateConstraints];
[cell.contentView layoutIfNeeded];
}
You need to refresh the table view and explicitly tell the tableview that, that particular cell has a different height.
You can refresh the table view as follows:
[tableview reloadData]
Or if you want to only refresh the one cell/ specific cells
[tableview reloadRowsAtIndexPaths:#[indexPathOfYourCell] withRowAnimation:UITableViewRowAnimationNone];
You will also need to add the height function to your TableView's delegate so that the table view can load with custom heights for each cell.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if (hasTheTallBlueView) {
return largerValue;
}
return regularHeight;
}