Scroll programmatically an UITableView with acceleration and deceleration - iphone

I'm trying to scroll a UITableView programmatically by tapping a button. It should be the same effect at doing it by hand or even a little bit longer in scrolling time.
First I tried to use scrollToRowAtIndexPath:atScrollPosition:animated: however it too fast and fixed in time. Also it does not have any acceleration and deceleration.
Any ideas how to scroll a UITableView programmatically as it has been scrolled by hand? I was thinking to send some custom touches to it. Do you think it could work?
Any ideas and help are greatly appreciated! Thanks!

UITableview is a subclass of UIScrollview. Like a UIScrollview, you can programmatically set UITableview's scroll position by setting the contentOffset property.
You change this property value inside an animation block so you have more control in the timing. You'll need to calculate the Y point position. Say you want to scroll to the 50th row, it might be 50 * rowHeight (assuming it's uniform height).
For example:
CGFloat calculatedPosY = 50 * rowHeight // do you own calculation of where you'd like it.
[UIView animateWithDuration:0.2
animations:^{theTableview.contentOffset = CGPointMake(0.0, calculatedPosY);}
completion:^(BOOL finished){ }];
There's another method you could use that provides more options as well
Take a look at this method:
+ (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion

Set your button in cell subview and use this code to scroll table programmatically on touch event of button. And if you are not setting button as a subview of table cell then modify this code as needed.
CGPoint point = [button.superview convertPoint:button.frame.origin toView:self.tableView];
CGPoint contentOffset = self.tableView.contentOffset;
contentOffset.y += (point.y - contentOffset.y - self.navigationController.navigationBar.frame.size.height); // Adjust this value as you need
[self.tableView setContentOffset:contentOffset animated:YES];

Well, these are just a couple of "off the top of my head answers," but you could try using this method (in UIScrollView, of which UITableView is a subclass):
-(void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated
Once -(CGRect) rectForRowAtIndexPath: gives you the coordinates of the cell, you can then animate to the location of cell. But rather than calling it once, call it a couple of times with a smaller offset, then with a larger and larger offsets and then a smaller ones again. I am worried that it will be difficult to make it look smooth, though.
Still, if that doesn't work, perhaps changing contentOffset value inside of a UIView animateWithDuration:delay:options:animations:completion block would work. Maybe if you use the UIViewAnimationOptionCurveEaseInOut, you can get the effect you want just calling it once. If not, run several animations each part of way and covering great and greater distances before slowing down.

Related

UITableView frame change animation issue

I've googled about this problem a lot but there seems to be no answer. So I'm hoping some of you may know how to deal with this. I have a view controller that has a tableview, when I change the view frame with animation, everything goes well, except one particular case, when tableview has more items than it can fit to screen, and only when the tableview is scrolled to bottom. Then if I shrink the view height, my view animates correctly, but the tableview somehow jumps up a bit and only then animates to the bottom.
If I shrink the view, but tableview isn't scrolled to bottom (even if I can see the last cell, lets say a bit more than half of it) it does animate correctly.
I've tried couple of things, like setting autoresizing masks on and off and also animate from current state or something like that, but that didn't help :/
So any suggestions what could be the problem?
EDIT:
Code that i use to change frame
[UIView animateWithDuration:0.5
delay:0.0
options: UIViewAnimationCurveEaseOut
animations:^{
[_contView setFrame:CGRectMake(0, 0, 320, 420)];
}
completion:^(BOOL finished){
}];
I fought this same bug for awhile before realizing I couldn't figure out for the life of me why it was happening...until I found a very detailed explanation and solution.
Full write-up can be found here:
http://lists.apple.com/archives/cocoa-dev/2012/Apr/msg00341.html
Quick solution is to change contentInset instead of the table view frame. Just add the height of whatever you are showing (ie a keyboard or ad banner view) to the contentInset.bottom property. The scrolling area will be increased to account for the required resize.
In this example, I was showing an ad banner view on top of my tableview:
UIEdgeInsets insets = myTable.contentInset;
insets.bottom += 50; // 50px is the height of the ad banner view
myTable.contentInset = insets;
I used to have a similar problem and I implemented this workaround (Disclaimer: this is a hack to a currently-unresolved iOS bug):
// check if the table view is scrolled to the bottom
if (tableView.contentOffset.y + tableView.frame.size.height == tableView.contentSize.height) {
// if it is, shift the table view contents up one pixel
[tableView setContentOffset:CGPointMake(tableView.contentOffset.x, tableView.contentOffset.y - 1) animated:NO];
}
// call the animation block afterwards here
It's a hack, though not noticeable to the user since it's just a 1 pixel motion. UITableView has a few bugs (reported to Apple already, but not yet resolved) when it comes to animations from a scrolled-to-bottom position.
You should use autoresizing from xib which is right corner of xcode ... I think this may help you.
Try to add footer view to your table with non-zero height.

Is there a way to change content offset via Core Animation on UIScrollView with reusing views?

EDIT: I got some progress, so I have simplified and narrowed the problem: I have created new question, which is more clearer and more understandable I hope
I have very large horizontally scrolling UIScrollView, which is reusing its subviews. When user scrolls left, the rightmost view is moved to the leftmost position and its content is updated. Similarly when user scrolls right, the leftmost view is moved to the rightmost position and its content is updated. Similarly like in UITableView. This works nicely using scrollviewDidScroll: delegate method.
Now I need to change offset programatically, but with custom animation behaviour (inertia and bounce back to the final position).
To prototype this, I use
[UIView animateWithDuration:contentOffsetCADuration
delay:0
options:UIViewAnimationOptionCurveLinear
animations:^{
[self setContentOffsetNoDelegateCalls:contentOffset animated:NO];
}
completion:^(BOOL finish){
dispatch_async(dispatch_get_main_queue(), ^{
[timer invalidate];
});
}];
where I animate content offset with disabled delegate callback, so that views are not messed during animation. But I need something to simultaneously reuse and reposition them.
My solution is to create timer and call scrollViewDidScroll: manually so that reusing works as usual, parallel to animation. But it simply does not work - nothing is reused and scrollview's content after animation is in the same state as before animation -> formerly visible pages are moved out of screen.
So the question narrows down how to move subviews and update their content during animation?
Or maybe there might be completely different way doing this ...
Check my answer over here.
Edit: lol, just noticed it's the same author
you may try to change scroll offset in a methode then you can call it from
[self performSelector:<#(SEL)#> withObject:<#(id)#> afterDelay:<#(NSTimeInterval)#>];
then make it stop when it reaches a specific value

What is the best way to display images/slides/containers on the iPad?

For an app that I am writing I need to display several views (in my case images, yet I guess that should not matter all that much) in a style similar to the way images are displayed in the Fotos app for the iPad. Consider this graphic:
Obviously on portrait view fewer slides can fit in per row than on Landscape view. What I have/want to do now:
Animate the addition of each slide by using a sliding animation
I already have a UIView subclass that represents a slide and that responds to touches the way I need it.
Where I am unsure is as to what is the best way of actually putting the slides onto the screen. I was originally just going to loop through them and place them onto the viewControllers view using the Frame of each slide (then I can do a count and increase the y-value after every 5 slides or so). Yet if I do it like that, I fear that my View will basically only support one screen orientation as I would need to relayout all the slides if the orientation changes, which seems rather a waste of resources to me (?).
How would you solve this problem? Is there maybe any "linebreak on overflow" ability such as with CSS (where a container automatically drops onto the next line if the float property is set and the parent container does not provide enough space)?
I am surely not the first person to think about this :)
Thanks for your answers!
There's no "linebreak on overflow" setting in Cocoa. If you want to make something that does that, you'll need to write code that lays out the slides. Here's a sample of the code I use to do it:
CGRect frame = CGRectMake(margins.left, margins.top, cellSize.width, cellSize.height);
for (UIButton *button in self.buttons) {
button.frame = frame;
// Move frame to next position
frame.origin.x += cellSize.width + cellSpacing;
if (frame.origin.x + frame.size.width > margins.left + contentWidth) {
frame.origin.x = leftMargin;
frame.origin.y += cellSize.height + cellSpacing;
}
}
I'm using UIButtons for each image, hence the self.buttons ivar.
Yes, you will need to relayout when the screen orientation changes. To do that, have a method like this:
- (void)layoutSubviews {
[super layoutSubviews];
[self layoutButtons];
}
Which calls your layout code. In my case, it's layoutButtons. This is in a subclass of UIScrollView, so when the view is autoresized after an autorotation, the layoutSubviews method gets called.
If you're using a UIScrollView, make sure you update the self.contentSize ivar too.

Can a UIScrollView be adapted to zoom horizontally but not vertically? (how)

I'm trying to make a timeline view that shows events as UIButtons that can be pressed for more info.
Ideally I want to lay out the buttons on a UIScrollView, and allow them to stretch/shrink horizontally, but not vertically.
What I'm trying to accomplish would basically be equivalent to using pinch gestures to resize the content view, and using a scroll view to move side to side - could you point me in the right direction, or suggest how you might accomplish something like this?
My first thought is a UIScrollView to scale a UIView subview, and have the UIView subview contain another subview that does not resize vertically, but only does horizontally when bounds change, and disable vertical scrolling. Maybe I could skip one of these UIView subviews and have only one do everything. It just feels like I'm trying to hack this together like an HTML page or something with all these containers.
Not sure if I've explained any of this well enough to hope for an answer, any help is appreciated though.
I've implemented horizontal-only scaling by creating a subclass of UIView that simply overrides setTransform and sets the Y scale to 1 whenever the UIScrollView changes the scale:
#interface DiagramStripView : UIView { }
#end
#implementation DiagramStripView
- (void)setTransform:(CGAffineTransform)newValue;
{
newValue.d = 1.0;
[super setTransform:newValue];
}
#end
As the class name suggests, my view holds a series of diagrams that are each one screen wide. When the user lets go, the view controller resets the view's scale to 1 and redraws everything to the new scale:
- (void) scrollViewDidEndZooming:(UIScrollView *)scrollView
withView:(UIView *)view atScale:(float)scale
{
diagramStripView.transform = CGAffineTransformIdentity;
[self redrawDiagrams:scale];
}
Haven't tried this, but you could try placing everything in a UIScrollView, and whenever you detect a zoom level change, adjust all of the child view transforms to CGAffineTransformMakeScale(1.0, 1.0/zoomLevel). This way, the scrollview is zooming everything up, but each subview is scaling themselves vertically downward, canceling out the zoom.
Assuming you don't actually want to stretch/shrink the button text, the easiest way is to resize all the buttons. I know, it's a pain.

Refreshing UIScrollView / Animation, timing (Objective-C)

I have a UIScrollView that is displaying a list of data.
Right now, when the user adds one more item to the list, I can extend the content size of the UIScrollView and scroll down smoothly using setContentOffset and YES to animated.
When the user removes an item from the list, I want to resize the content size of the UIScrollView, and scroll back up one step also in an animated fashion.
How can I get the ordering right? Right now, if I resize the content size before scrolling back up, the scroll isn't animated.
I tried scrolling back up before resizing the content size, but that still didn't give a smooth transition.
Is there a way to finish the scrolling animation BEFORE resizing the content size?
Thanks
Yes, you need to call the method to resize your content after the scroll view's animation is complete. You can do that in the scroll view delegate's scrollViewDidEndScrollingAnimation: method. Set your controller as the delegate, and implement that method.
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {
[scrollView setContentSize: newSize];
}
You don't need a timer or a table view for this.
I think you should use UITablView not UIScrollView.
in UITableView you can implement refresh functionality.
using the one line code:
[tblView reloadData];
One thing you can do is to use a timer. As I understand, you want to do this:
Scroll to new viewport
Remove item from list
Resize content size
You could do it like this:
[scrollView setContentOffset:... animated:YES];
[NSTimer scheduledTimerWithTimeInterval:SECONDS_AS_FLOAT target:self selector:#selector(timerCallback:) userinfo:nil repeats:NO];
- (void)timerCallback:(NSTimer *)timer {
// remove item from list at this point
...
// set new content size
[scrollView setContentSize];
}
You have to experiment to see what is the right SECONDS_AS_FLOAT time to use, it should be slightly more than the scroll duration. It will be on the order of a few hundred milliseconds, so you can experiment with a value between say 0.2 and 0.5.