I want to expand the cell when selected to show a row of buttons like the image above. I have a xib file which shows a cell that is 320 wide x 140 tall. I have subclassed that UITableViewCell. Additionally I have another xib file that has the row of buttons as shown in blue ink in the image above.
I am able to load the row of buttons using initWithCoder using this answer from a really smart guy!
However, it overwrites all my other views inside the MyCustomCell.
How can I load the xib just when the cell expands so that it is positioned in the lower half of the 140pt tall cell?
After doing a little more research, I found a different way to do something like what you want using just one cell. In this method, you have just the taller cell, but return a height that only lets you see the top half of the cell unless its selected. You have to do two things in the design of your cell to make this work. First select the box for "clip subviews" in IB, so the buttons won't show outside their view (the cell). Second, make the constraints such that the buttons are all lined up vertically with each other and give one of them a vertical spacing constraint to one of the UI elements in the top of the cell. Make sure none of the buttons has a constraint to the bottom of the cell. By doing this, the buttons will maintain a fixed distance with the top elements (which should have a fixed space to the top of the cell), which will cause them to be hidden when the cell is short. To animate the change in height, all you have to do is call beginUpdates followed by endUpdates on the table view.
#import "TableController.h"
#import "TallCell.h"
#interface TableController ()
#property (strong,nonatomic) NSArray *theData;
#property (strong,nonatomic) NSIndexPath *selectedPath;
#end
#implementation TableController
- (void)viewDidLoad {
[super viewDidLoad];
[self.tableView registerNib:[UINib nibWithNibName:#"TallCell" bundle:nil] forCellReuseIdentifier:#"TallCell"];
self.theData = #[#"One",#"Two",#"Three",#"Four",#"Five",#"Six",#"Seven",#"Eight",#"Nine"];
[self.tableView reloadData];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.theData.count;
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if ([indexPath isEqual:self.selectedPath]) {
return 88;
}else{
return 44;
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
TallCell *cell = [tableView dequeueReusableCellWithIdentifier:#"TallCell" forIndexPath:indexPath];
cell.label.text = self.theData[indexPath.row];
return cell;
}
#pragma mark - Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if ([self.selectedPath isEqual:indexPath]) {
self.selectedPath = nil;
}else{
self.selectedPath = indexPath;
}
[tableView beginUpdates];
[tableView endUpdates];
}
This, I think gives you the desired animation when you pick the first cell, but you get a somewhat different animation if you then pick a cell below the currently selected one. I don't know of any way around that.
I'm not sure what you have in your two xib files, but the way I would do it is to have one xib file for the shorter cell, and one for the taller cell (that has everything in it that you show in your drawing). I did it like this, with two xib files like I mention above:
#import "TableController.h"
#import "ShortCell.h"
#import "TallCell.h"
#interface TableController ()
#property (strong,nonatomic) NSArray *theData;
#property (strong,nonatomic) NSIndexPath *selectedPath;
#end
#implementation TableController
- (void)viewDidLoad {
[super viewDidLoad];
[self.tableView registerNib:[UINib nibWithNibName:#"ShortCell" bundle:nil] forCellReuseIdentifier:#"ShortCell"];
[self.tableView registerNib:[UINib nibWithNibName:#"TallCell" bundle:nil] forCellReuseIdentifier:#"TallCell"];
self.theData = #[#"One",#"Two",#"Three",#"Four",#"Five",#"Six",#"Seven",#"Eight",#"Nine"];
[self.tableView reloadData];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.theData.count;
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if ([indexPath isEqual:self.selectedPath]) {
return 88;
}else{
return 44;
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if (! [self.selectedPath isEqual:indexPath]) {
NSLog(#"returning short");
ShortCell *cell = [tableView dequeueReusableCellWithIdentifier:#"ShortCell" forIndexPath:indexPath];
cell.label.text = self.theData[indexPath.row];
return cell;
}else{
NSLog(#"returning long");
TallCell *cell = [tableView dequeueReusableCellWithIdentifier:#"TallCell" forIndexPath:indexPath];
cell.label.text = self.theData[indexPath.row];
return cell;
}
}
#pragma mark - Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
if ([self.selectedPath isEqual:indexPath]) {
self.selectedPath = nil;
[self.tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
return;
}
NSIndexPath *lastSelectedPath = self.selectedPath;
self.selectedPath = indexPath;
if (lastSelectedPath != nil) {
[self.tableView reloadRowsAtIndexPaths:#[indexPath,lastSelectedPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}else{
[self.tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
}
Related
I have a tableviewcell in the RegisterViewController, when i click on it, it push to the SelectCityViewController where i can pick a city from a uipickerview. and I have defined a delegate in the SelectCityViewController. But when i implement the delegate method, i got the city back from the pickerview and the selected indexpath from didSelectRowAtIndexPath in RegisterViewController. When i check the log, i get the city, and the selected indexpath. but when i call reloadRowsAtIndexPaths, the cell still got the old titlelabel text. the code:
SelectCityViewController.h
#protocol SelectCityDelegate <NSObject>
- (void)didGetCity:(City *)city;
#end
SelectCityViewController.m
- (void)finished:(UIBarButtonItem *)buttonItem
{
if ([self.delegate respondsToSelector:#selector(didGetCity:)]) {
[self.delegate didGetCity:self.city];
}
NSLog(#"%#", self.city.city);
[self.navigationController popViewControllerAnimated:YES];
}
RegisterViewController.m
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section == 2) {
self.selectedIndexPath = indexPath;
UIStoryboard *storyBoard = [UIStoryboard storyboardWithName:#"MainStoryboard_iPhone" bundle:nil];
SelectCityViewController *signUp = [storyBoard instantiateViewControllerWithIdentifier:#"SignVC"];
signUp.delegate = self;
[self.navigationController pushViewController:signUp animated:YES];
}
}
- (void)didGetCity:(City *)city
{
NSLog(#"%#", city.city);
NSLog(#"%#", selectedIndexPath);
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:selectedIndexPath];
cell.textLabel.text = city.city;
[self.tableView reloadRowsAtIndexPaths:#[selectedIndexPath] withRowAnimation:UITableViewRowAnimationFade];
}
I think the problem is in the below code you don't have to set the value here, because when you will reload, it will override the value with what is in the function where you are creating your cells.
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:selectedIndexPath];
cell.textLabel.text = city.city;
Just reload the cell, and put above code in your
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
//Cell intialization and other things
cell.textLabel.text = city.city;
}
didGetCity needs to update the model that backs the table, not the table cell itself. How do you answer numberOfRowsInSection:? With the count of some array?
That array at index selectedIndexPath.row needs to change. cellForRowAtIndexPath: should initialize the cell with that same data. Then your didGetCity method updates the array and reloads. It should not refer to cells.
I have 3 table views in one view and I was wondering why the tableView:cellForRowAtIndexPath: was not getting called.
#pragma mark -
#pragma mark <UITableViewDelegate>
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (tableView == self.mainVoucherTableViewController)
[self setSelectedIndex:indexPath.row];
}
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section
{
if (tableView == self.mainVoucherTableViewController){
return 10;
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (tableView == self.mainVoucherTableViewController){
static NSString *MyIdentifier = #"MyReuseIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyIdentifier];
}
cell.textLabel.text = #"THISTEXT";
return cell;
}
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
if (tableView == self.mainVoucherTableViewController)
return 1;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
// The header for the section is the region name -- get this from the region at the section index.
if (tableView == self.mainVoucherTableViewController){
NSString * myString = [NSString stringWithFormat:#"HELLLO WORLD"];
return myString;
}
}
WOULD anyone know why this is? Basically this doesn't create any cell or display cells. It just displays the table views. :(
Just to consolidate a few things from above:
From your naming, your tableView is called mainVoucherTableViewController - just want to confirm that this is indeed a UITableView and not a UITableViewController? If it's a UITableViewController then the rest of this won't work for obvious reasons (or not so obvious - let me know and can explain further)
Make sure you have set the current viewController as the tableView's delegate and dataSource, either with the code below or in Interface Builder if you're using a XIB
self.mainVoucherTableViewController.delegate = self;
self.mainVoucherTableViewController.dataSource = self;
Make sure your numberOfRowsInSection function is being called and that you're returning non-zero (put in NSLogs, etc) and do the same for numberOfSections as well (actually numberOfSections isn't required if you're only using 1 section) - see UITableViewDataSource Protocol Reference: http://developer.apple.com/library/ios/#documentation/uikit/reference/UITableViewDataSource_Protocol/Reference/Reference.html
As per previous post, log your cellForRow (if points #1-3 are checked and working) at the beginning to make sure it's triggered, and just before the return. Also do an NSLog of the cell you're returning just to make sure it isn't nil.
Start off by logging inside your tableView:cellForRowAtIndexPath: method to see if it gets called at all outside your if statement as well as inside to help narrow down the issue.
Also try instead of comparing your:
tableView == self.mainVoucherTableViewController
Set the tableViews to have tag values instead. Then you can do:
if(tableView.tag == 100){ // tag number we assigned self.mainVoucherTableViewController via IB
//do your stuff here
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (tableView == self.mainVoucherTableViewController)
{
return 10;
}
else
{retun 5;
}
}
It display row in first table 10, second table show 5 rows.
The order of instance declaration does matter. For example, if you have a ivar called tableView:
WRONG
self.tableView.delegate = self;
self.tableView = [UITableView alloc] init];
CORRECT
self.tableView = [UITableView alloc] init];
self.tableView.delegate = self;
check UITableView Object Frame Size. maybe Frame size is not enough to draw Cell.
I have UITableView...when user tap on row, another screen is opened. The problem is, that sometimes, I tap once, but didSelectRowAtIndexPath calls several times. How to prevent that ?
The one case how to reproduce that situation is (you even can try to reproduce that on native iPhone settings):
Tap one row but do not release finger
SLIDE few next rows from left to right or from right to left (not just tap, you should slide) next few rows in different order by other hand
Release finger
You will see that blue selection is on several rows, and what screen will be opened is random
UPDATE:
In didSelectRow I just started new controller, where in viewDidLoad synchronization begin.
And if to reproduce my scenario step by step, than synch can be started several times
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
SecondViewController *secondViewController =
[SecondViewController alloc] init];
[self.navigationController
pushViewController:secondViewController animated:YES];
[secondViewController release];
}
Yes, I find the same situation.
Tap one row but do not release finger.
Keep pressing and moving the finger slightly until the row deselected.
Keep the first finger pressing, and tap the screen some times by another finger.
Release all fingers.
Then you can see didSelectRowAtIndexPath method called several times.
I created a new project for test it, and just used the following code. It was reproduced in every times.
So I think it is a bug of iOS SDK !
#import "SPViewController.h"
#interface SPViewController ()
#property (nonatomic, strong) UITableView *tableView;
#end
#implementation SPViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
self.tableView.delegate = self;
self.tableView.dataSource = self;
[self.view addSubview:self.tableView];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - UITableViewDataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return 30;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *cellIdentifier = #"cellIdentifier";
UITableViewCell *cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if(cell == nil){
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
cell.textLabel.text = [NSString stringWithFormat:#"Test Cell %d", indexPath.row];
return cell;
}
#pragma mark - UITableViewDelegate
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
return 66;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
NSLog(#"%s %#", __FUNCTION__, indexPath);
}
#end
i am using below code for auto scrolling UITableview
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [DataArray count];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath
{
return 60;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *identifier = #"CustomTableCell";
CustomTableCell *cell = (CustomTableCell *)[tableView dequeueReusableCellWithIdentifier:identifier];
if (cell == nil)
{
cell = [[[CustomTableCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier] autorelease];
}
if(isAutoScrollEnabled)
[DataTable scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
cell.Time.text = [[DataArray objectAtIndex:indexPath.row]objectForKey:#"CurrentTime"];
cell.SerialNo.text = [NSString stringWithFormat:#"%d",indexPath.row+1];
return cell;
}
But it fluctates my tableview every time i reload data in tableview. Is there any solution ?
Can anyone help me ?
Thanks in advance......
What do you want?
smooth scrolling to down?
or
scrolling to new cell position?
first case(smooth scrolling) should use NSTimer.
second case(to new cell position) should override UITableView[updateData]
I'm not sure exactly what you are saying. But I will say that calling scrollToRowAtIndexPath in your cellForRowAtIndexPath looks like it could cause problems. What you are doing is telling it to scroll each time it generates a cell. That doesn't look right.
I also have to wonder if you really want to be animating that scrolling.
i have been trying to figure this out but i just can't....
for some reason, everytime my UITableView reaches a certain length due to the number of rows and sections, cells seem to randomly copy themselves into different cells at the end of the table without me wanting or programming it... Anyone else have this issue or knows how it's resolved? Any help is appreciated! Tanks.
Edit:
The "row" property is a counter which gets counted up to 13 and is then reset to 0 and counted up again and i always want the string "newUpdate" to be displayed in the corresponding row but at the same time i don't want the rest of the cells to be blank i want them to keep their old content until they're overwritten because the counter is starting at 0 again.
#import "ServerUpdateViewController.h"
#implementation ServerUpdateViewController
#synthesize newUpdate;
#synthesize row;
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
// Customize the number of rows in the table view.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 15;
}
- (NSString *)tableView: (UITableView *)table titleForHeaderInSection:(NSInteger) section {
return #"All Updates to Server";
}
// Customize the appearance of table view cells.
- (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] autorelease];
}
if (indexPath.row == self.row) {
cell.textLabel.text = self.newUpdate;
}
// Set up the cell...
return cell;
}
- (void)dealloc {
[super dealloc];
}
#end
You're re-using cells. In your cellForRowAtIndexPath: method, you need to fully configure the cell every time it's called.
There are a few things that can cause this and I can't be specific without seeing code, but it comes down to the fact that cells are reused. The entire content of the cell will have to be re-set/redrawn every time cellForRowAtIndexPath is called.