How to imitate rotation from the photos app on thumbnail view - iphone

I have an app that mimics the photos app in that it displays a table of thumbnails which represent an album that the user has clicked on. I'm having trouble imitating the animation of this window when rotation is occurred. For your reference the window I'm talking about is below:
I'm able to get the end result to turn out fine (ie after the animation is complete, everything works), however my rotation doesn't look as good as the photos app. The photos app actually looks like it's rotating the window and you can barely notice the photos resetting themselves. On mine, you can see the thumbnails sort of moving around and it doesn't look as good.
Does anyone have a clue as to what the photos app is doing? Is it possible that it's speeding up the animation, or maybe blurring it in the middle so you can't see what's going on? My code is listed below:
- (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];
}
else
{
for (UIButton *photoButton in [cell subviews])
{
[photoButton removeFromSuperview];
}
}
NSInteger photosPerRow;
if (self.interfaceOrientation == UIInterfaceOrientationPortrait)
{
photosPerRow = 4;
}
else
{
photosPerRow = 6;
}
CGRect frame = CGRectMake(4, 2, 75, 75);
for (int i = [indexPath row] * photosPerRow; i < (([indexPath row] * photosPerRow) + photosPerRow) && i < [[self photos] count]; i++)
{
[[photos objectAtIndex:i] setFrame:frame];
[cell addSubview: [photos objectAtIndex:i]];
frame.origin.x = frame.origin.x + frame.size.width + 4;
}
return cell;
}
Then here is how I'm animating it in my tableview controller:
- (BOOL) shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
{
[self.tableView reloadData];
return (toInterfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
UPDATE: Well, I've tried a lot. So far switched all my code to a scroll view, then tried to have the thumbnails animated as follows when a rotation occurs: fade out the thumbnail, move it to it's new location, then fade it in. Of course I only did that with UIThumbViews that needed to be moved. I coming to the conclusion that apple must be using two views in order to pull them off. One view with the old set of thumbnail views, then rotating over the new set of thumbnail views. Any ideas?

UPDATED: This takes two lines of code, and you have to implement the thumbnails view in a tableview, not a scrollView:
[self.tableView reloadRowsAtIndexPaths: [self.tableView indexPathsForVisibleRows] withRowAnimation:UITableViewRowAnimationFade];
[self.tableView reloadData];

Related

Scroll list of thumbnails

I need to allow my users to scroll through a list of thumbnails in the documents directory on an iPad application. When they tap on one of the thumbnails, I need to know which one was tapped (or have it call a method, which is basically the same thing.)
I know how to put the thumbnails in the documents directory, and how to read them from there. But I’m not sure which widget is best for displaying the content. Is it UITableView? CCScrollLayer? CCMenuAdvanced? Something else?
CCScrollLayer doesn't work as well as UIScrollView.
I use the view to CCdirector for add UIkit.
UIScrollView *scrollView = [UIScrollView alloc] init];
[[[CCDirector sharedDirector] view] addSubview:scrollView];
or add UITableView.
Take all the paths for thumbnails in one array, lets call pathsArray
NSArray *pathsArray = [seld getPathsForAllImages];
Now implement UITableViewDataSource and UITableViewDelegate:
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [pathsArray count];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
}
NSString *pathForImage = [pathsArray objectAtIndex:indexPath.row];
cell.imageView.image = [UIImage imageWithContentsOfFile:pathForImage];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *pathForImage = [pathsArray objectAtIndex:indexPath.row];
}
I've had all kinds of problems with overlaying a UIScrollView on a cocos2d project. Even with all the suggested hacks, if a single frame takes too long in cocos2d, the UIScrollView just stops working right. I would suspect that would happen with a UITableView as well.
To solve my own problems, I created a custom ScrollNode class. It's really easy to use:
// create the scroll node
ScrollNode *scrollNode = [ScrollNode nodeWithSize:CGSizeMake(size.width, size.height)];
[self addChild:scrollNode];
// Generate some content for the scroll view
// add it directly to scrollView, or to the built in menu (scrollView.menu)
[scrollNode.menu addChild:yourMenuItem];
// Set the content rect to represent the scroll area
CGRect contentRect = [scrollNode calculateScrollViewContentRect];
[scrollNode setScrollViewContentRect:contentRect];

Custom UITableViewCell backgrounds, when to reload, etc

I've set up a custom grouped tableview style using the following code in cellForRowAtIndexPath. Each cell is given a new background view and it's corners are rounded according to its position in the section. I use this style throughout my app and in various views I use animations to add and delete rows. (Using [tableview insertRowsAtIndexPaths:], etc).
If the last row of a section is inserted or removed I need to be able to reload the cells background view corners. How can I do this without calling [tableview reloadData] or even reloadCellsAtIndexPaths? Both of those functions mess up the animations of inserting and deleting cells. Is there somewhere I can put this background corner definition that would get called at the appropriate time? How does the default grouped tableview do this?
Sorry if this is unclear. Ask for clarification and I will edit. Thanks! :)
UITableViewCell *cell = [aTableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:[self styleForCellAtIndexPath:indexPath]
reuseIdentifier:cellIdentifier] autorelease];
// Theme the cell
TableCellBackgroundView *backgroundView = [[TableCellBackgroundView alloc] initWithFrame:cell.frame];
TableCellBackgroundView *selectedBackgroundView = [[TableCellBackgroundView alloc] initWithFrame:cell.frame];
selectedBackgroundView.selectedStyle = YES;
// top row, without header
BOOL header = [self tableView:aTableView heightForHeaderInSection:indexPath.section]!=0;
if (indexPath.row == 0 && (!header || tableTheme==kTableThemeSimple)) {
backgroundView.topRadius = 6;
selectedBackgroundView.topRadius = 6;
}
// bottom row
if (indexPath.row == [self tableView:aTableView numberOfRowsInSection:indexPath.section]-1) {
backgroundView.bottomRadius = 6;
selectedBackgroundView.bottomRadius = 6;
}
cell.backgroundView = backgroundView;
cell.selectedBackgroundView = selectedBackgroundView;
cell.contentView.backgroundColor = [UIColor clearColor];
cell.backgroundColor = [UIColor clearColor];
}
}
If you have a method that you call for the custom animation, when the animation is completed, or rather prior to the display of the new cell, using a subclass of UITableViewCell make a call to a method named something like - (void) fixCornersWithIndexPath: (NSIndexPath *) indexPath.
- (void) customAnimationForCellAtIndexPath: (NSIndexPath *) path {
//your animation things
CustomCell *cell = [self.tableView cellForRowAtIndexPath: indexPath];
[cell fixCornersWithIndexPath: path andRowCount: [self tableView:aTableView numberOfRowsInSection:indexPath.section]];
}
and then fix corners should be something like this:
- (void) fixCornersWithIndexPath: (NSIndexPath *) indexPath andRowCount: (NSInteger *) rowCount {
if (indexPath.row == 0 && (!header || tableTheme==kTableThemeSimple)) {
self.backgroundView.topRadius = 6;
self.selectedBackgroundView.topRadius = 6;
}
// bottom row
if (indexPath.row == rowCount-1) {
self.backgroundView.bottomRadius = 6;
self.selectedBackgroundView.bottomRadius = 6;
}
}
Hope this helps!
I think this is what you're looking for. After you've altered your model, call:
- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
This will cause your cellAtIndexPath method to be invoked. Send an array with a single index path of the row that's out of date (I guess for the rounded corners problem, that's either the last row after a row was removed, or the row before a newly added last row).
You can call it along with your other update methods in between begin/end updates.
The solution that worked for me is this:
Set the background in tableView:willDisplayCell:forRowAtIndexPath:
Create a convenience method called reloadBackgroundForRowAtIndexPath that calls willDisplayCell manually
When I add a row, call the convenience method after a delay to allow the table animations to finish
- (void)reloadBackgroundForRowAtIndexPath:(NSIndexPath*)indexPathToReload {
[self tableView:self.tableView willDisplayCell:[self.tableView cellForRowAtIndexPath:indexPathToReload] forRowAtIndexPath:indexPathToReload];
}
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
// Logic to determine the background image
}
// elsewhere, update the table, then
NSIndexPath *indexPathToFix = [NSIndexPath indexPathForRow:count-1 inSection:0];
// If the new row is coming out, we want to update the background right away. If the new row is going away, we want to wait to update until the animation is done.
// I don't like hard-coding the animation length here, but I don't think I have a choice.
[self performSelector:#selector(reloadBackgroundForRowAtIndexPath:) withObject:indexPathToFix afterDelay:(self.tableView.isEditing ? 0.0 : 0.2)];

Not refreshing screen in programmatically filled TableView

When I scroll down and up again my text in tableView will disappear.
And my code is:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [screenDefBuild.elementsToTableView count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *MyIdentifier = #"MyIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyIdentifier];
}
ScreenListElements *currentScreenElement = [screenDefBuild.elementsToTableView objectAtIndex:indexPath.row];
cell.textLabel.text = currentScreenElement.objectName;
currentRow++;
return cell;
}
- (void)viewDidLoad
{
[super viewDidLoad];
tableView = [[UITableView alloc] initWithFrame:self.view.bounds];
[tableView setDataSource:self];
[self.view addSubview:tableView];
}
I also want to fill my table view to entire screen. (grey strap on the top).
I don't know what you're doing with this variable
currentRow++;
But whatever you use that for, i'd wager its breaking your code.
the UITableView will call cellForRowAtIndexPath every time a cell is about to appear on screen regardless of whether it has been on screen before or not. When you scroll down and then scroll back up this variable will have increased beyond the bounds of your data, hence you get empty cells.
You need to design this method in such a way that it can create any cell in the table view at any time. You can't rely on the order that the cells will be made and with scrolling you will have to make the same cell over and over again. Only use indexPath to figure out which cell you are currently supposed to be making.
http://developer.apple.com/library/ios/#documentation/UIKit/Reference/UITableView_Class/Reference/Reference.html
Answer for the second part of the question- grey strap. You are adding the table view to current view, so you should use the size property of self.view.frame but not the origin. You want this to be set to 0,0.
Change
tableView = [[UITableView alloc] initWithFrame:self.view.bounds];
to
CGRect viewFrame=self.view.frame;
viewFrame.origin=CGPointZero;
tableView = [[UITableView alloc] initWithFrame:viewFrame];
As for the first part of your question- it's strange, as you seem to do everything properly. One thing i may suggest is to add [tableView reloadData]; in the end of viewDidLoad.

Cost of iterating over cells in a UITableView

Inside each UITableViewCell of my UITableView, I have a UIScrollView. The scroll view is setup so that when the user swipes right a menu will appear. This is similar to the behavior of the cells in the iPhone Twitter app. When a user swipes upon another cell I iterate over all visible cells to tell the UIScrollView to scroll back to the cell content (i.e. its initial position). The iteration is done in the scrollViewWillBeginDragging method with the following code:
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
if( [scrollView tag] == 90 ) {
NSLog(#"Dragging a scroll view inside a cell!");
for (UITableViewCell *cell in self.tableView.visibleCells) {
[(UICellContentScrollView *)[cell viewWithTag:90] scrollRectToVisible:CGRectMake(0.0f, 0.0f, 320.0f, [cell frame].size.height) animated:YES];
}
}
}
In the method viewDidDisappear I iterate again over all cells to reset various things like so:
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
for( NSUInteger section = 0; section < [[self tableView] numberOfSections]; section++ ) {
for( NSUInteger row = 0; row < [[self tableView] numberOfRowsInSection:section]; row++ ) {
UITableViewCell *cell = [[self tableView] cellForRowAtIndexPath:[NSIndexPath indexPathForRow:row inSection:section]];
// resetting cell here
}
}
}
My question is if I am (a) going about this the right way and (b) does anyone have any recommendations on a better solution considering the table view may be storing up 50 (no more than 100) items.
Check out the NSNotification documentation. You could register all of your UITableViewCell objects to receive a notification you could call something like "cellWasSwiped" or "needToResetCells" or whatever. Then whenever you want to reset the cells you just post the notification. All of your UITableViewCell objects that are registered to receive it will get the notification and can then call whatever method you need.

Why doesn't UITableViewCell selection not draw if I tap very quickly?

I have a a UITableView with cells that are partially custom using addSubview. I am using a different cell ID for the last cell, whose purpose is to load more data from a server which will make new cells appear. (Think the "Load more messages from server" cell in Mail.app)
E.g.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// <... code for normal cells omitted...>
static NSString *LoadMoreCellIdentifier = #"LoadMoreCellIdentifier";
UILabel *loadMoreLabel;
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:LoadMoreCellIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:LoadMoreCellIdentifier] autorelease];
cell.selectionStyle = UITableViewCellSelectionStyleGray;
loadMoreLabel = [[[UILabel alloc] initWithFrame:CGRectMake(0.0, 0.0, cell.frame.size.width, self.tableView.rowHeight - 1)] autorelease];
loadMoreLabel.tag = LOAD_MORE_TAG;
loadMoreLabel.font = [UIFont boldSystemFontOfSize:16.0];
loadMoreLabel.textColor = [UIColor colorWithRed:0.153 green:0.337 blue:0.714 alpha:1.0]; // Apple's "Load More Messages" font color in Mail.app
loadMoreLabel.textAlignment = UITextAlignmentCenter;
[cell.contentView addSubview:loadMoreLabel];
}
else
{
loadMoreLabel = (UILabel *)[cell.contentView viewWithTag:LOAD_MORE_TAG];
}
loadMoreLabel.text = [NSString stringWithFormat:#"Load Next %d Hours...", _defaultHoursQuery];
return cell;
}
As you can see above, I set cell.selectionStyle = UITableViewCellSelectionStyleGray;
When you tap on a cell, I clear the selection like so:
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (_showLoadMoreEntriesButton && indexPath.row == [_sourceArray count])
{
// <... omitted...>
// Do a potentially blocking 1 second operation here to obtain data for more
// cells. This will potentially change the size of the _sourceArray
// NSArray that acts as the source for my UITableCell
[tableView deselectRowAtIndexPath:indexPath animated:NO];
[self.tableView reloadData];
return;
}
[tableView deselectRowAtIndexPath:indexPath animated:NO];
[self _loadDataSetAtIndex:indexPath.row];
}
The problem I'm seeing is that I have to tap and hold my finger down to have the gray highlight appear. If I tap very quickly, it doesn't display draw a highlight at all. The problem being that sometimes I perform an blocking operation that will take a second or so. I want some simple UI feedback that something is happening, instead of the UI just locking up. I think this may be related to me conditionally checking for if the indexPath is the last row of the table or not.
Any idea how to get it to draw the highlight every time?
If possible, move your blocking operation to another thread. NSOperation is probably a good system to use.
Apple's documentation
A random tutorial I found
There's no clean way to tell the UI thread to process anything while you're in the middle of a blocking operation.