Custom Scrollbar for iPhone's UIView (Making Long Scrolls Not Suck) - iphone

In a post, Making Long Scrolls on the iPhone Not Suck, Aza Raskin describes an alternative scrollbar control that's better at getting around on very long pages:
It's not important that the scrollbar "remains for some amount of time" to activate it; I'm fine with simply swiping along the right edge of the iPhone's screen to grab hold of the scrollbar handle. The idea is that if I drag the handle 3/4 of the way down on the physical screen, I'd be 3/4 of the way down on the page.
Tthe Dropbox iPhone app (it's great, btw!) has exactly this kind of scrollbar for long PDF documents. Regular scrolling is done by dragging anywhere but on the handle; dragging the handle moves the view to that location. This seems to have been implemented "from scratch", as I don't think the SDK is flexible enough to customize the behavior of the existing scrollbar.
However, Dropbox uses the native document viewers to show documents on the iPhone, so somehow they add the scrollbar functionality to it. See the scrollbar handle? You can drag that to quickly get somewhere else in the document.
This concept is very similar to how index bars work in UITableView (ie. Contacts.app); the index appears as a bar on the right hand side of the table (for example, "a" through "z"), and you can touch a particular label to jump to the target section. In this case, however, a very long page doesn't have sections, and it should work for general-purpose scrolling, not jumping to sections.
So how can I go about implementing this method of scrolling? I'm looking for general ideas and specific implementation details. I'm also interested if an open-source implementation exists (this seems like a general-purpose problem/solution).

A general idea:
I grabbed the dropbox app (it is awesome) and played around with a bit. It looks like pdf viewing takes a bit from the photo app in that it conditionally displays a translucent navbar and toolbar on touches, in addition to supporting the scrollbar. I'm pretty sure what's going on is that they have a custom view controller intercepting touches and reacting accordingly.
On a touch:
If it's a tap, show/hide the
navbar and toolbar.
If it's on
the scrubber, begin tracking the
touch and scrolling the
scrollview/webview (whatever they're
displaying with). I'm sure the
scrolling is something simple like
scrollView.contentOffset =
CGPointMake(0, (scrubber.y / [UIScreen mainScreen].bounds.size.height) *
scrollView.contentSize.height). 3)
Else, pass the touch on to the
enclosed view.
There may be other hidden magic with PDF displaying (I've never done it in cocoa touch) but something tells me this is their basic process.

I don't know of any iPhone specific solutions, but this is an old and well travelled topic in the world of Flash development... and you could probably extract a ton of pseudo code from that realm.
If you know the height of your window, and the height of your content, and the current offset of the content (which you do), then you have all the tools you need to create a custom UIView which can serve as a touch-responding slider. And then just paint it over the default scroller.

There's probably an open source implementation for this. I don't know any. Maybe shoot an email to Dropbox developers?
Anyway, the way I'd do this is:
#interface UICustomisedScrollView : UISCrollView
{
BOOL showingScroller;
UIView scroller; //Customise this, either in IB or in viewDidLoad
}
#implementation UICustomisedScrollView
- (BOOL)touchesShouldBegin:(NSSet *)touches withEvent:(UIEvent *)event inContentView:(UIView *)view {
showingScroller = !showingScroller;
if(showingScroller)
scroller.hidden = NO;
else
scroller.hidden = YES;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if(showingScroller) {
if(/*the touch is on the scroller*/) {
/* scrollview.setContentOffset(...) we want to scroll according to how much the user scrolls here */
}
//move scroller.frame.origin to where the touch is.
}
}
I'm guessing it won't be too difficult... But I haven't tested the above code yet. That's the general idea anyway =)

Try using a UIPanGestureRecognizer. In your action, can use the locationInView to determine the point the user is touching. Do this when the state is UIGestureRecognizerStateBegan and if it's close enough to the side of the view implement fast scrolling. Otherwise, implement slow scrolling.

Related

i*-sdk: Placing a bubble on top of all controls

I've got a problem thats been perplexing me for a while. I have a custom control for the iPhone sdk. When the user touches the control I want to draw a small bubble above the users touch position with some information in it. A bit like a thought bubble in a cartoon.
Initially I've done it by adding a UIView subview to the control. However if I use the control where I don't have control of the z-order, for example in a table view, then the bubble will be drawn under other controls.
I've looked around but I'm not sure how to approach this problem. Everything I've read seems to indicate that you need to know the tree structure of the controls. Ideally I'd like to apply it to some layer that sits over the window as a whole, but I'm not sure how. I've also look at core graphics but cannot see any obvious answers.
Does anyone have any ideas of perhaps something they can point me at which will help.
Thanks
If you want to add a UIView to the 'top window', you can use the application UIWindow for that.
UIWindow is a subclass of UIView, so you can just use - (void)addSubview:(UIView *)view to add the new view to the window.
[[UIApplication sharedApplication] keyWindow] addSubview:yourView];
You could try adding it as a subview of the window, though, I don't think that's the most appropriate solution.
Personally, I would add my control as a subview to whichever view (maybe a table cell) and then tell that view to bring your control to the front.
[tablecell bringSubviewToFront:myControl];
That way, when you display your bubble, it'll be on top.

iphone switch views by swiping fingers to left and right, similar as the stock app

I am interested in how the lower portion of the iphone stock app is implemented. The lower part of the app, where you can switch the views by swiping your finger to left/right and the "..." below the view to indicate which view the user is looking at.
It looks to me a tabview component. I am trying to look for the UI component as well as google it, but i have not yet find anything like it. Most of the examples I found are using scroll view. When you swipe the finger to left/right the scroll bar appears, and more importantly, there is no "..." under the view to indicate which view the user is looking at.
Could anybody point me to a similar example?
Thanks,
Thomas
It's a UIScrollView with pagingEnabled set to YES taking up most of the screen with a UIPageView underneath it. See Apple's PageControl sample code for details.
You can turn off showing the scroll indicators by setting the showsHorizontalScrollIndicator and showsVerticalScrollIndicator properties of the scroll view to NO.
It might also be worth checking out the PhotoScroller sample code for an in-depth example of how to do pagination using a UIScrollView. There's also a great video called "Designing Apps with Scroll Views" that walks you through paginating views using a UIScrollView in the WWDC 2010 videos. I highly recommend watching it and the other WWDC videos. You can get them by signing into the iOS developer center and scrolling to the bottom.
Also, like they tell you in the video, you don't need to use touch events or override touchesBegan:withEvent: or similar functions to make this happen. If you do it that way, you have to do lots of work to pass events that you don't care about through to the views you're showing and stuff like that. UIScrollView with pagingEnabled set to YES does all of the hard work for you.
If you don't want to use a scroll view for some reason, using a UIGestureRecognizer is the way to go. But really, just use a scroll view.
This is called touch events. more so Swipe touch on the iphone.
And here are two examples.
Heres a code snippet:
You first need to tell the application that touchesBegan.
You can find some details on the API for this. Then the delegate methods will be called automatically everytime a user touches the iphone screen.
- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
UITouch *touch = [touches anyObject];
if (touch.info & UITouchSwipedRight) {
[self changeViewRight];
} else if (touch.info & UITouchSwipeLeft) {
[self changeViewLeft];
}
return;
}
check this example:
http://devblog.wm-innovations.com/tag/touchesbegan/
Its a really good example.
Hope this helps.
Let me know if it did a little. Thanks
PK
Take a look at following link, it might be nice approach to navigate through views on swipe.
Views Navigation Using Swipe Gesture

UIScrollView with embedded UIWebView not scrolling after holding

I have a UIWebView which is embedded in a UIScrollView. The webView is resized so that the scroll view manages all the scrolling (I need control over the scrolling).
In the webView I have disabled userSelection via '-webkit-user-select: none;'
Everything is working fine except one annoying detail. When I hold down my finger on the content before starting to scroll for about a second the scrollView won't scroll. My best guess is, that it has something to do with userSelection. The time is about the same it usually takes for the copy/paste/magnifying-thing to appear which usually disables scrolling as well.
I am running out of ideas on how to solve this. Every help would be greatly appreciated!
Thanks!
EDIT: Another aspect of the problem is, that the non-scrolling actually triggers JS-Eventhandler (click, mousedown, mouseup) inside my webView which leads to surprising app behavior. The user puts her finger down, waits, scrolls, nothing happens, removes her finger and this is perceived as a click, which feels wrong from a users perspective.
I would guess what is happening is that after that short duration, the scrollview is no longer interpreting the touch as being on it's view and instead passes the touch down to it's content views.
Have you tried delaying the content touches for the scrollview? This will essentially tell the scrollview to delay taking action on the touch event and instead to briefly monitor the touch and if the touch moves then it recognizes it as a swipe gesture for scrolling. If it doesn't move, it will eventually pass the touch along to it's subviews.
scrollView.delaysContentTouches = YES;
I think even then, there is a standard delay time before the scrollview will pass the touch events along the responder chain. If you hold for too long, it's going to naturally perceive it as being a press down event rather than a scroll event.
This question is not relevant anymore. As of iOS 5.0 the UIWebView is based on a real UIScrollView and also exposes that UIScrollView via a property. Use that instead.
And don't mess with UIWebViews embedded in UIScrollViews anymore. The documentation explicitly advises against that.
Relevant Documentation

Drag & sweep with Cocoa on iPhone

I'm about to start a new iPhone app that requires a certain functionality but I'm not sure if it's doable. I'm willing to research but first I just wanted to know if I should at least consider it or not.
I haven't seen this in an app before (which is my main concern, even though I haven't seen too many apps since I don't own an iPhone), but an example would be the iPhone shortcuts panels: you can hold on an app, and then drag it to another panel, sweeping while still dragging it. But this is the core app, is it possible to reproduce something similar within a normal app?
I only need to be sure it can be done before I start digging, I don't need code examples or anything, but if you have some exact resources that you consider helpful, that would be appreciated.
Thanks!
Yes. If you have your custom UIView subclass instance inside a UIScrollView, your view controller just needs to set the UIScrollView to delay content touches and not allow it to cancel touch events.
[scrollView setCanCancelContentTouches:NO];
[scrollView setDelaysContentTouches:YES];
When the user taps and holds in the custom view, the event goes to that custom view, which can process the touch events to drag an item around, but if the user quickly swipes, it scrolls the view.
The "panel" view that you're referring to appears to be a UIPageControl view — although, perhaps, the specific incarnation of this view that Apple uses for the iPhone's home page may be customized.
Instances of generic UIView views that you might touch-and-drag will receive touch events. By overriding methods in the view, these events can be processed and passed to the page control, in order to tell it to "sweep" between pages.
If I wanted to do what you're asking about, that's how I might approach it. It seems doable to me, in any case.
Start with this: Swip from one view to the next view
Try using a UIButton that tracks the time since the state of the button changed to "highlighted". You may need to do this in order to track the dragging and move the button around:
Observing pinch multi-touch gestures in a UITableView
Check to see if the button starts overlapping one side of the screen while being dragged. If s certain amount of time elapses since the button first started overlapping the edge and then manipulate the UIScrollView so that it switches to the next page on the corresponding side of the screen
You may need to use NSTimer to keep track of how long the button is held down, etc.
In any case there's no reason why this couldn't work.
If UIButton doesn't work then perhaps try a custom subclass of UIControl (which tracks the same touch down actions etc.). If that doesn't work then use the window event intercept thing to track everything.

UITabBar / UIToolBar overlap problem (iphone sdk)

I have a custom button in another view directly above my UITabBar. It seems like there's a hidden "hit area" above the UITTabBar that is preventing me from hitting the bottom half of my custom button in another view. The button subview is on top of all other views including the custom UITabBar.
It's really easy to notice this effect in the simulator using the UICatalog sample code. Head to the toolbar section. Position your mouse cursor about 5-10 pixels above the tool bar items on the bottom and click to see that you can trigger the touch event way above the button.
I need to figure out how to restrict this hit area to the bounds of the uitoolbar or uitabbar itself and not let the iPhone do any sort of hit accessibility magic.
I think I've exhausted all options :\ I thought clipsToBounds (on the UITabBar) would do the trick, but apparently not.
Also I'm doing this completely in code, so no Interface Builder...
For anyone who stumbles across this question, I have a solution for this, although it falls well into "dirty hack" territory, and may break with newer (than 3.0) iPhone OS releases... use at your peril!
I created a UITabBar category with a hitTest:withEvent: implementation, and noticed when I set a breakpoint in the body of this method that the x,y values in the supplied CGPoint struct were rather odd - when touching in the ~15px or so ABOVE the tab bar the values look something like (x=23.482749, y=12.938475), whereas if you touch within the tab bar the values were nicely rounded to the nearest integer, e.g. (x=23, y=12).
Now, for some reason I was expecting the touches in the ~15px "hit zone" above the tab bar to be negative for the y axis (which is why I started down this path in the first place), but alas, no. You can touch in two different places and get similar coordinates, such as in the example above.
So, my "solution": I'm relying on a touch in the tab bar itself always having an integer-rounded value for the y axis, otherwise I'm assuming it's in the overlap zone above the tab bar. I tested this for quite a while, and it seems to consistently behave this way.
The code:
#implementation UITabBar (Me)
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
if ([[NSString stringWithFormat:#"%.6f", point.y] hasSuffix:#".000000"])
{
return [super hitTest:point withEvent:event];
}
else
{
return nil;
}
}
#end