UITableView w/ paging & momentum - objective-c

OBSERVATIONS
I've discovered some "weird" things about how UITableViews / UIScrollViews scroll in Objective-C for iOS after you've lifted your finger:
A scroll view's "velocity" (as in scrollViewWillEndDragging:withVelocity:targetContentOffset:) is not based in time but rather based in "render" or "frame refresh" or the like; i.e., a "velocity" of 3.0 means that the scroll view will scroll 3.0 points before the next refresh, at which point it will scroll yet another 3.0 points if the "velocity" is still 3.0. (in contrast to an absolute time-based velocity)
"Refreshes" occur approx. 950 times per second, but this varies and is not dependable. (again, in contrast to an absolute time-based refresh rate)
Because UIScrollViews decelerate exponentially rather than linearly, decelerationRate is a geometric constant. This means that on every "refresh" of the scroll view, its "velocity" is set to its previous velocity times the "deceleration rate".
At some point, the scroll view's velocity is close enough to zero that it stops scrolling. I am not sure what this threshold is yet.
DESIRED BEHAVIOR / PROBLEM
I've derived an equation that can theoretically be solved to adjust the deceleration rate so that the scroll view can scroll naturally with momentum and settle close enough to the top of a table view cell to mimic "paging." (I also have an animation added to scrollViewDidEndDragging:willDecelerate: to adjust the content offset minimally. Adjusting content offset w/o adjust decelerationRate looks weird; the scroll view either jumps ahead or lurches backwards suddenly.)
This equation is: Ad^n + Bd + C = 0 where d = decelerationRate. Ideally I'd like something in the form of d(n, A, B, C), but my math PhD friend tells me that's not deterministically possible. He pointed me towards the following page: http://en.wikipedia.org/wiki/Root-finding_algorithm
I've also found that I can't animate a scroll view to a predetermined content offset using animation style UIViewAnimationCurveEaseOut because setContentOffset:animated: doesn't take an options: parameter. Otherwise I would "fake" scrolling to my precalculated content offset.
(I found this on SO but it didn't work for me: UIScrollview setContentOffset with non linear animation ?)
I'd like to use a table view because all of my elements in my scroll view will be cells essentially, and table views are best for displaying a list of cells. However, based on my difficulty getting my desired behavior, I might just make a paging scrollview, which will probably be easier. I can then just limit the number of "cells" visible in my custom "table view" or else load them post-hoc as necessary.
What are your ideas / thoughts / suggestions?
So far I have not found existing examples of what I would like.
ILLUSTRATION
OR

So this answer was obtained via a snarky but brilliant user on the #iphonedev IRC channel!
From within scrollViewWillEndDragging:withVelocity:targetContentOffset: you can just re-set the targetContentOffset to whatever you desire via *targetContentOffset = newTargetOffset;! Incredibly simple and elegant.
My mistake was trying to call [scrollview setContentOffset:newTargetOffset animated:YES] from within scrollViewWillEndDragging:withVelocity:targetContentOffset:, which didn't work at all, and therefore I set out to "solve" my problem in a more convoluted way that also didn't work at all.

Related

How could I create a horizontal UIPickerView style control?

I want to create a custom UIView that would be equivalent to a horizontal UIPickerView. The data elements, such as "Mountain View, Sunnyvale, Cupertino, Santa Clara, San Jose" would move left to right. The view would be long and thin. 50px in height, 350x in width sort of thing.
I will need to use protocols to obtain my datasource elements likely, but what I'm more interested in figuring out is the visual aspect of this.
How would I animate things left and right? Should I just make one incredibly long view (off screen), and change the frame left and right. This seems like a bad idea, as likely only 3-5 options need to be visible in the control at one time. If the datasource is 100 elements, there is no point in loading the other 95 off screen in a long view.
So perhaps I should load ~9 or something. The 3-5 on screen, plus an additional ~3 left and right. Each time the control is triggered to move left and right, it will load up another element on the view(?).
Is this a good way to achieve this? A long thin view with ~9 UILabel's. As the control moves to the right, I would shift the further left UILabel to move hidden to the far right, and change the UILabel to be the next in the data source.
I also likely want to change the text size based on its position. If it's currently selected, I either want to bold it and possibly increase the font size. How can I gradually achieve this as the view is moved? It would be weird if the text only changed once it was moved perfectly center inside the view. It should likely gradually grow as it gets closer to the middle.
How can I achieve this?
A collection view would do the trick, obviously there's some coding involved.
I'd suggest you to take a look at the code of, or use, the following library.
I've used it once,does almost exactly what you need and works very well:
AKPickerView

How can I set the end dragging velocity of a UICollectionView with custom flow layout?

I'm working on a UICollectionView with a custom flow layout subclass which, among other things, does some custom "paging". Everything's fine but for the fact that depending on how I drag, when I release and after - (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity gets called, the collection view (or some part of the UICollectionViewFlowLayout which I did not yet know I need to override) is controlling the velocity with which the animation of an item snapping happens.
That is, if I slightly offset an item from the center of the
collection view qnd release, it snaps back to its position pretty
quickly (desired).
But If I drag the item, say, half way past the collection view's
frame and/or change swiping directions while still dragging and then
release, the "snap" animation takes too long (not desired: I'd like the velocity to adjust so that the end drag animation takes the same amount of time always, regardless of distance).
I tried modifying the decelerationRate of the collection view but it doesn't seem to do anything. And I'm thinking of writing my own animation block in one of the collection view delegate methods, but I'm wondering if there is a different way (perhaps from within the flow layout subclass?).
Well, actually setting self.collectionView.decelerationRate = 0.; seems to work for now. It at least does not decelerate the scrolling and so it looks like constant velocity which is not exactly what I wanted but feels almost right.

How does the Reeder Mac app animate lists when switching folders?

Initially I was under the impression that it uses the table row slideup/down animations while inserting/deleting new rows but I doubt if it's doing that as it does it so fluidly even with thousands of items in the list (otherwise it would take a lot of time for the deletions/insertions to work).
Am I right in my assumption that it's simply attaching a new instance of the News list at the bottom of the screen, shrinking the above one while the one at the bottom expands to fill up space?
UPDATE:
Please see this video of what I mean: http://dl.dropbox.com/u/4960327/ReederAnim.mov
I can not tell you exactly how Silvio Rizzi made this, but as you see in the playback, a list view is added behind the shown list view, and the front list view fades out (.alpha = 0.0;) while the list view behind it expands its height per row.
When you desicate it frame by frame it becomes quite clear what he does, and it is really not that advanced. But I have to admit, with the white "milky" polished interface, it looks quite neat.
In addition, you can see that while animating, the background list view only renders the top 7 entries (hopefully calculated by dividing the view height with the average height of the cells shown) making the list view quick to load. Then afterwards, he can load an extended array of cells once you start scrolling, or in a background thread starting once the animation is complete.

When using UIScrollView, how do I make the viewed ends to be complete, not partial

How do I make the UIScroll view show complete views, not partial views?
(Note) I don't want it jumping to a complete view. It needs to move naturally or at least not immediate... needs to be smooth.
thanks
If your views are all of a constant size and you just want left/right or up/down scrolling, set pagingEnabled on the scroll view to YES. Supposing you wanted your scroll view to be 320x480 but to show the sides of the next and previous pages (so, e.g., each thing inside the view was 280 points wide), you'd size the scroll view to be 280x480 but set masksToBounds to NO.
If you have a more complicated scheme, install a scroll view delegate and act on scrollViewDidScroll:, paying attention to contentOffset. Probably you want to implement logic like:
add an observer on tracking; when it transitions to NO from 'YES' enable your logic inside scrollViewDidScroll: In there:
if a forced scroll is pending, cancel it
calculate where you'd force the scrolling to from the current position
schedule a scroll to there (which you'll effect via setContentOffset:animated:) for half a second from now
You can use a non-repeating NSTimer for the scheduling aspect. The logic you've essentially implemented is that if the user stops adjusting the view, wait for the natural inertia to end (which you'll detect by the 0.5 second gap since last movement), then transition smoothly to the nearest aligned position.
Check out Apple's documentation here first:
https://developer.apple.com/library/ios/#documentation/WindowsViews/Conceptual/UIScrollView_pg/Introduction/Introduction.html
Then the ScrollViewSuite sample:
https://developer.apple.com/library/ios/#samplecode/ScrollViewSuite/Introduction/Intro.html#//apple_ref/doc/uid/DTS40008904
I think you are referring to Paging techniques.

How can I scroll a UIView of indefinite size within a UIScrollView

I'm trying to draw a graph that is indefinitely large horizontally, and the same height as the screen. I've added a UIScrollView, and a subclass of a UIView within it, which implements the -drawRect: method. In the simulator, everything works fine, but on the device, it can't seem to draw the graph after it reaches a certain size.
I'm already caching pretty much everything I can, and basically only calling CGContextAddLineToPoint in the -drawRect: section. I'm only drawing what's visible on the screen. I have a delegate to the UIScrollView which listens for -scrollViewDidScroll: which then tells the graph to redraw itself ([graphView setNeedsDisplay]).
I found one method that tells me to override the +layerClass method and return [CATiledLayer class]. This does allow the graph to actually draw on the device, but it functions very poorly. It's incredibly slow to actually draw, and the fade in that occurs is undesirable.
Any suggestions?
Well, here's my answer: I basically did something similar to how the UITableView works with cells: I have an NSMutableSet of GraphView objects which store unused graphs. When a section of the scroll view becomes visible, I take a graph view from that set (or make a new one if the set is empty). It already had a scrollX property to determine which part of it was supposed to draw. I set the scrollX property to the correct value and, instead of using the screen width, I gave it an arbitrary width to draw. When it goes out of the scroll view, it is removed from the UIScrollView and added to the set.
I wonder though if I really even need to remove them when they go outof the view? It may be prudent to try leaving them in and remove the ones not on screen only if I get a low memory warning? This might get rid of the pause whenever it needs to redraw a section of graph that hasn't changed.
My saving grace here was that my GraphView already was set up to draw only a portion of the graph. All I needed to do then was just make more than one of them.
I think this is a limitation of the iPhone graphics hardware. Through experimentation, I have seen that the iPhone will refuse to draw a frame that is bigger than 2000 pixels in either height or width. It probably has something to do with limited size for frame buffers in hardware.
Watch the 2011 WWDC session video entitled "Session 104 - Advanced Scroll View Techniques".
Thanks, that's helpful. One question -- what did you use for the contentSize of the UIScrollView? Does UIScrollView tolerate large content sizes (over 2000 px) as long as you're not creating buffers to fill the entire space in your content view? Or are you keeping the UIScrollView a constant size (say, 2 screen widths) and resting the UIScrollView contentOffset property each time you draw (using scrollX instead of the contentOffset to store your position)?
I think I answered my own question (the latter seems like a better alternative), heh, but I'll go ahead and post this in case other people need clarification.