How to put UISlider’s in UIScrollView’s
It’s a fairly simple request you’d imagine. Simply put a slider inside some content which scrolls.
As this is on iOS that content is obviously inside a UIScrollView.
The Problem
The main problem with the above is that they don’t like each other. If you place a standard UISlider inside a normal UIScrollView the scroll view delays the touches for a fraction of a second to see if you are scrolling. Here is the big problem, you swipe sideways to scroll and you, want to, swipe sideways on the sliders thumb to quickly change it’s value.
As a result you have to tap, pause and then drag the slider in order to use it. Manageable but hardly smooth or ideal to use.
Of course there is a very simple way to avoid this.
scrollView.delaysContentTouches = NO;
Simple? Well, at least for me on iOS 5, a little too simple. This caused almost all objects to prevent my scrollView from scrolling, buttons, sliders, even an image view seemed to block it. So we filter for the slider?
If we look at the UIScrollView documentation we see a few little things that allow us to control the delays and cancelling touches. Creating a custom subclass gives us a little more power, but still not enough.
What do we do with our new subclass then? Well, we need to control when to allow touches to behave normally and when we should scroll.
@interface KHCustomScrollView : UIScrollView
@property (nonatomic, assign) BOOL ignoreSliders;
@end
@implementation KHCustomScrollView
@synthesize ignoreSliders = _ignoreSliders;
-(BOOL)touchesShouldCancelInContentView:(UIView *)view{
if (self.ignoreSliders) {
if ([view isKindOfClass:[UISlider class]]) {
return NO;
}
else {
return YES;
}
}
return [super touchesShouldCancelInContentView:view];
}
@end
// Somewhere else...
scrollView.canCancelContentTouches = YES;
scrollView.delaysContentTouches = NO;
scrollView.ignoreSliders = YES;
What we do here is tell it we want the scrollView to let all touches to start normally, but we want it to be able to cancel them if it wants to scroll. Instead of the normal behavour of delaying them until its decided whether or not to scroll it now cancels touches when it wants to. Then we have our scrollView say it can cancel touches and start scrolling for everything, other than sliders.
Now can you guess the problem here?
If we touch anywhere on the slider it will not scroll, however the slider only responds over the thumb image that indicates the value selected. But we can fix this too, however we have to work with the sliders themselves :(, although luckily we don’t need to subclass them.
// Somewhere...
-(void)monitorSlider:(UISlider*)slider{
[slider addTarget:self action:@selector(willSlide:) forControlEvents:UIControlEventTouchDown];
[slider addTarget:self action:@selector(didSlide:) forControlEvents:UIControlEventTouchUpInside];
[slider addTarget:self action:@selector(didSlide:) forControlEvents:UIControlEventTouchUpOutside];
[slider addTarget:self action:@selector(didSlide:) forControlEvents:UIControlEventTouchCancel];
}
-(void)willSlide:(id)sender{
scrollView.scrollEnabled = NO;
}
-(void)didSlide:(id)sender{
scrollView.scrollEnabled = YES;
}
// Someone else...
[self monitorSlider:slider];
// In our scrollView subclass we made earilier
if ([view isKindOfClass:[UISlider class]] && !self.scrollEnabled) {
return NO;
}
By the way that `scrollView.scrollEnabled` is only state storage. It could have been written scrollView.tag = 42, if (scrollView.tag == 42) …
So now we allow touches to reach things inside the scrollView, we know when the slider was touched on its thumb image, and we can control which touches are canceled and which are allowed to prevent us from scrolling.
And finially, here’s an example project on Github to show you an example for each stage of this article. Hope someone finds this useful as I couldn’t find anything on the subject.
foolremote likes this
iky1e posted this