I've got a nice table populated with data, with custom-designed cell layouts. Currently, tapping a cell will push in a new view (for more information, say), which works fine.
What I'd like to do, though, is have the new view push into the cell's bounds, not fill up the whole screen. Can't seem to wrap my brain around how to accomplish this -- any pointers you could offer?
Thanks! :)
Update: Here's some of the code, in case that helps the helpers:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
FlippedViewController *flippedViewController = [[FlippedViewController alloc] initWithNibName:#"FlippedView" bundle:nil];
// What goes in here? Where do contentView and originalView come from?
[originalView removeFromSuperview];
[contentView addSubview:flippedViewController.view];
[flippedViewController release];
}
You can add a hook in tableView:didDeselectRowAtIndexPath: (a method of UITableViewDelegate) which uses a pointer to the particular cell's contentView, and performs the changes on that UIView just as it would any other.
For example, if you wanted to simply replace originalView (assuming that's what's in the cell now) with newView, you could do something like this:
// Use your own data structure and indexPath to retrieve contentView.
[originalView removeFromSuperview];
[contentView addSubview:newView];
That would update the view without an animation.
About the animation -- here are two related previous questions:
UIView based animation
Animating the display of a view
Related
I'm trying to mimic the iMessage bubble text behaviour with an UITableView. In order to always scroll to the bottom I'm using scrollToRowAtIndexPath when viewDidLoad and viewDidAppear. This is because when the viewDidLoad method is called, the table has not been completely loaded, so I need that extra scroll in viewDidAppear. This code makes the trick. However, what I want is not an animated scroll (setting animated to NO does not solve this), I want the table to be displayed always from the bottom, not load the table and then go to the last row.
Is this possible? I can't find any solution that fits completely with the desired behaviour.
This is the best solution!
Just reverse everything!
tableView.transform = CGAffineTransformMakeRotation(-M_PI);
cell.transform = CGAffineTransformMakeRotation(M_PI);
Swift 4.0:
tableView.transform = CGAffineTransform(rotationAngle: -CGFloat.pi)
cell.transform = CGAffineTransform(rotationAngle: CGFloat.pi)
Be careful though, because now the headerView and footerView positions are reversed as well.
You can avoid the call from viewDidLoad because scrolling from within viewDidAppear makes that first call redundant. viewDidAppear is called every time you navigate back to the view but viewDidLoad is only called once when the view is initialized.
I would agree with earlier suggestions of hiding the scroll from the user instead of changing the way a UITableView is loading data. My suggestion would be to use the scrollToRowAtIndexPath method in the viewWillAppear method with animation set to NO. After that if you have to add a new row while the table is visible to the user, use insertRowsAtIndexPaths:withRowAnimation: to add a row at the bottom of the table view. Be sure to take care of adding the data at the end of your data model so that when the user navigates away and comes back, s/he comes back to the same layout.
Hope this helps.
edit:
Just saw your reason for not accepting the previous answers and thought I'd elaborate a little more. The solution I propose would require minimum effort, avoid calling reloadData time and again and thus avoid calling the scrollToRowAtIndexPath method again and again. You only need to make one call to scrollToRowAtIndexPath in viewWillAppear to scroll to the bottom of the table view (hiding the transition from the user when doing so) and you wouldn't need to do that again.
I do something similar in an RPN calculator I've built. I have a table view with all the numbers in it and when a number is added to the stack, everything pops up one cell. When I load the view I call:
[self.myTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:NumOfStackItems - 1 inSection:0]
atScrollPosition:UITableViewScrollPositionTop animated:NO];
In my viewWillAppear. This way my table view starts shown at the bottom of the stack and no animation is seen. By putting this in the viewWillAppear, every time I navigate to the view, it shows up at the bottom of the table.
When I add numbers to the stack, I just add it in an array that holds all the numbers and then put the text in the proper row like this:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Cell initialization here...
NSUInteger row_num = [indexPath row];
cell.rowNumber.text = [NSString stringWithFormat:#"%g", [DataArray objectAtIndex:NumberOfStackItems-row_num-1];// subtract the row number off to get the correct array index
return cell
}
I also make sure that whenever I update the tableview with a new value i first call the reloadData function, and then call the scrollToRowAtIndexPath function I cited above, this way I stay at the bottom of the table.
You can have your UITableView hidden on viewDidLoad, and then change it to visible on viewDidAppear right after you scroll the table to the bottom. This way the user won't see the scrolling animation.
The solution is to override viewWillAppear and let it scroll (non-animated) to the bottom:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self goToBottom];
}
-(void)goToBottom
{
NSIndexPath *lastIndexPath = [self lastIndexPath];
[self.tableView scrollToRowAtIndexPath:lastIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:NO];
}
-(NSIndexPath *)lastIndexPath
{
NSInteger lastSectionIndex = MAX(0, [self.tableView numberOfSections] - 1);
NSInteger lastRowIndex = MAX(0, [self.tableView numberOfRowsInSection:lastSectionIndex] - 1);
return [NSIndexPath indexPathForRow:lastRowIndex inSection:lastSectionIndex];
}
By performing this at viewWillAppear it will be done before the user sees the table.
You can fix it by making an invisible footer and do the calculations in there. When the footer is loaded the contentSize is updated. To make it scroll I check set the contentOffset of the tableview.
I have commented out the animation part, since you wanted it without, but it also works.
-(CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
{
return 1;
}
-(UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section
{
if( tableView.contentOffset.y != tableView.contentSize.height - tableView.frame.size.height && automaticScroll ){
//[UIView animateWithDuration:0.0 animations:^{
self.contentTableView.contentOffset = CGPointMake(0, tableView.contentSize.height - self.contentTableView.frame.size.height);
//} completion:^(BOOL finished) {
[tableView reloadData];
//}];
automaticScroll = NO;
}
UIView *emptyFooter = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 1)];
emptyFooter.backgroundColor = [UIColor clearColor];
return emptyFooter;
}
I created a BOOL automaticScroll to trigger the scroll to the bottom. This should be set in the viewWillAppear method, or whenever you load the data and reload the tableView.
If you want to add rows, you also need to set the BOOL, like:
-(void)addItemButtonClicked:(id)sender
{
automaticScroll = YES;
//Add object to data
[self.contentTableView reloadData];
}
If you need more help, please let me know.
scrollToRowAtIndexPath
use to scroll the row in tableview to particular position
just change content inset after load data to move content view of table view if height is less than parent view.
[self.tableView reloadData];
[self.tableView setContentInset:UIEdgeInsetsMake(self.view.frame.size.height - self.tableView.contentSize.height < 0 ? 0 : self.view.frame.size.height - self.tableView.contentSize.height, 0, 0, 0)];
Swift 3.1
tableView.transform = CGAffineTransform(rotationAngle: CGFloat.pi)
cell.transform = CGAffineTransform(rotationAngle: CGFloat.pi)
Credits: #Christos Hadjikyriacou
I'm using a UISegmentedControl as the headerview of a tableview. Now I want to add loading view (a view defined by myself) only covering the table cells but not my headerview. How do I achieve this?
You can add this view to the cell you want:
UITableViewCell *cell = [self tableView:self.tableView cellForRowAtIndexPath:indexPathOfCell];
[cell addSubview:view];
The simplest way to add the loading view would be like this
// get the frame of your table header view
CGRect headerFrame = headerView.frame;
// construct a frame that is the screen minus the space for your header
CGRect loadingFrame = [[UIScreen mainScreen] applicationFrame];
loadingFrame.origin.y += headerFrame.size.height;
loadingFrame.size.height -= headerFrame.size.height;
// use that frame to create your loading view
MyLoadingView *loadingView = [[MyLoadingView alloc] initWithFrame:loadingFrame];
// add your view to the window *
[[headerView window] addSubview:loadingView];
* Adding the view to the window may not be the best thing for your design, but since I don't know any of the details of your view hierarchy, this is the way that will always work.
Caution: If you do not cover up the segmented control, and it is enabled, the user may click on it and change the state of the app when you aren't expecting it - like when you're trying to load something for them. Be sure that you can cancel this loading view if the user changes the state of the app.
I'm looking to use a UISegmentedControl to flip between two tables on the same view. One table (the default when the view is loaded) is grouped. The second table should be plain (non-grouped). The tableView is set to Grouped in the xib.
Right now I'm doing a reloadData when flipping between the two sets of data. So of course both "views" are in the grouped style. I know I can't change a tableView's style once set, but I'm looking to mimic that intent.
The best idea I've had is to create a second (plain) tableView in code, then flip between the two as needed. I could either show/hide (keeping them both in memory) or add and remove from the superView.
Both views may result in the user clicking through to additional pushed viewControllers. I want to retain consistency with the chain, including the ability to popToRootViewController from viewControllers further down the chain (the view in question is two or three steps down from the root).
Before I tear up my code with this idea I figure I'd check around to see what others thought.
Thanks!
UPDATE: Getting there. When I want to display the plain table view I have this:
PlainViewController *plainViewController = [[PlainViewController alloc] init];
UIView *plainTableView = plainViewController.view;
plainTableView.frame = CGRectMake(320, 0, 320, 480);
[self.view addSubview:plainViewController.view];
which loads and displays fine. But when PlainViewController's didSelectRowAtIndexPath fires, it fails to load the next view. didSelectRowAtIndexPath is firing (it's logged), but pushViewController does nothing; the next view's viewDidLoad never fires.
I suspect there's a discontinuity in the NavigationController but not quite sure where to fix that.
You can create a separate UIViewController and add its table to the 'main' view.
PlainTableViewController *ctr = [[PlainTableViewController alloc] init];
[self.view addSubview:ctr.view];
[ctr release];
You can also easily animate this process:
PlainTableViewController *ctr = [[PlainTableViewController alloc] init];
UIView *plainTableView = ctr.view;
plainTableView.frame = CGRectMake(320, 0, 320, 480);
[self.view addSubview:plainTableView];
[UIView animateWithDuration:0.3
animations:^{
groupedTableView.frame = CGRectMake(-320, 0, 320, 480);
plainTableView.frame = CGRectMake(0, 0, 320, 480);}
completion:^(BOOL finished){[groupedTableView removeFromSuperView];}
];
[ctr release];
Notice: You don't add it as view controller in your navigation controller so all calls of popToRootViewController should lead to your 'main' viewController. And that's what you want.
Assign your tableViewStyle in something like - (void) refreshTable; then when you tap your segmentedControl, also call [myTable reloadData];
You can use two different views that have table with both the style but with the same data and you can flip that two views on the segment selected index change event
OR
You have to remove the table from its superview and then realloc table with the selected style and add it again to that view.
I think that adding/removing the table views from the view containing the segmented control should work seamlessly.
In order to keep consistency, I think the approach is storing somewhere the information as to which view is selected through the segmented control, so that when you are displaying that view you know which is the current style. This info will be also stored in NSUserDefaults, so that it is persistent across runs.
Simple, every time that the segmented control changes, re-create the UITableView.
Upside: saves you from writing too much code, memory.
Downside: uses a bit more CPU.
But you'd be redrawing the view with reloadData anyway, so it is a very minimal CPU drain to simply recreate the view.
-(void)segmentedControlChanged{
CGRect originalFrame = tableView.frame;
UITableViewStyle tableStyle;
if(selectedSegmentIndex == 0){
tableStyle = UITableViewStylePlain;
} else {
tableStyle = UITableViewStyleGrouped;
}
[tableView removeFromSubview];
tableView = [[UITableView alloc] initWithFrame:originalFrame style:tableStyle];
tableView.delegate = self;
tableView.dataSource = self;
[self.view addSubview:tableView];
}
Note: code written by memory without checking in Xcode.
I have a UITableView that is blank be default until the user edits and adds data to it. I want to show an image with instructions that is shown until the user edits it.
The size of the picture is so it fits under perfectly between the nav bar and the tab bar.
Is there a way to do this programmatically?
you can use removeFromSuperview to remove UITableView from super view then add your UIImageView.
Use the below code :
-(void) NeedView:(BOOL) aIsImageView
{
if(aIsImageView)
{
[m_TableView removeFromSuperview];
[self.view addSubviews:m_ImageView];
}
else
{
[m_ImageView removeFromSuperview];
[self.view addSubviews:m_TableView];
}
}
Create a UIView add your UITableView as a subview. Create a UIImageView with your instructions image and add it as a subView as well. Position the instructions imageView behind the the tableView. In ViewWillAppear add a test for empty table and hide the table based on that check. If you allow users to delete rows you will also have to check for empty table in:
- (void)tableView:(UITableView *)tableView
commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete)
// do delete
// get count of rows in UITableView and hide table if it's empty
}
...
It's not that difficult to achieve, you just can show the image you want to show with the specified frame (may be frame(0, 44, 320, 392)). and check for the table rows(actually the count of array that u are using to show in table) if it is nonzero just show the image and show the table otherwise.
try this concept and come back with any problem or query..
In my app, I have a table view that has about eight cells. There is a navigation bar at the top. When a user touches a cell, nothing happens for about 1/2 second. Then the touched cell highlights blue and immediately the new view slides into position.
The problem is that there is no feedback to the user about which cell he touched until just before the new view slides into position.
For example, when I explore the tables in the iPhone's Settings application, when you touch a cell, the cell immediately turns blue, and then there is a 1/2 second delay, and then you see the new view.
How do I get my table's feedback of highlighting the cells to happen immediately? I am using tableView didSelectRowAtIndexPath:, and each cell has an accessory button.
Thanks for any insight.
are you using a custom-drawn cell (with a drawRect override) or something like that?
if you have a custom drawRect method, you'll need to do something like this (based off the code for tweetie, found here):
//default colors for cell
UIColor *backgroundColor = [UIColor whiteColor];
UIColor *textColor = [UIColor blackColor];
//on highlight, swap colors
if(self.highlighted){
backgroundColor = [UIColor clearColor];
textColor = [UIColor whiteColor];
}
that should give you the default behavior.
It sounds like the screen is refreshing after the new slide has been processed. You need to refresh the screen before rendering the new slide view.
a few things:
it's probably best to have a property in beforeViewController so it can set its own title on load (instead of setting it from the parent class).
second, why are you setting the back button for the current class? youre also leaking that (you alloc the UIBarButtonItem but dont release it).
NewViewController *newViewController = [[[NewViewController alloc]
initWithNibName:#"New" bundle:nil] autorelease];
newViewController.name = [self.listData objectAtIndex:indexPath.row];
[self.navigationController pushViewController:beforeAfterViewController animated:YES];
then in NewViewController, you have
- (void) viewDidLoad{
self.title = self.name;
}
re: your secondary question: if you pop the child controller using [self.navigationController popViewControllerAnimated:YES], the parent view should auto-deselect the row that was previously selected. it shoulnt stay selected unless you are forcing it to stay that way.
you don't need to do anything like [self.tableView deselectRowAtIndexPath:] unless you are not pushing child views (and doing something like checkmarking a cell that the user tapped).
You may put these 2 lines of code at the beginning of the didSelectRowAtIndexPath method:
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
[cell setSelected:YES animated:YES];
it should first highlight the cell before processing other program logic.