Increasing a thumbs "click area" in UISlider - iphone

I am creating a custom UISlider and wonder, how do you increase a thumb image's "click area" ? The code below works great, except the thumb's "click area" remains unchanged. If I am not wrong, I believe the standard thumb area is 19 px ? Lets say I would like to increase it to 35px with the following code below, what steps do I need to take? Keep in mind I am not an expert within this area at all.
EDIT The code below is corrected and ready for copy pasta! Hope it helps you!
main.h
#import "MyUISlider.h"
#interface main : MLUIViewController <UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate, UIActionSheetDelegate, UIAlertViewDelegate, MFMailComposeViewControllerDelegate, UIGestureRecognizerDelegate, MLSearchTaskDelegate, MLDeactivateDelegate, MLReceiptDelegate>
{
..........
}
- (void)create_Custom_UISlider;
main.m
- (void)viewDidLoad
{
[self create_Custom_UISlider];
[self.view addSubview: customSlider];
}
- (void)create_Custom_UISlider
{
CGRect frame = CGRectMake(20.0, 320.0, 280, 0.0);
customSlider = [[MyUISlider alloc] initWithFrame:frame];
[customSlider addTarget:self action:#selector(sliderAction:) forControlEvents:UIControlEventValueChanged];
// in case the parent view draws with a custom color or gradient, use a transparent color
customSlider.backgroundColor = [UIColor clearColor];
UIImage *stetchLeftTrack = [[UIImage imageNamed:#"blue_slider08.png"]
stretchableImageWithLeftCapWidth: 10.0 topCapHeight:0.0];
UIImage *stetchRightTrack = [[UIImage imageNamed:#"blue_slider08.png"]
stretchableImageWithLeftCapWidth: 260.0 topCapHeight:0.0];
[customSlider setThumbImage: [UIImage imageNamed:#"slider_button00"] forState:UIControlStateNormal];
[customSlider setMinimumTrackImage:stetchLeftTrack forState:UIControlStateNormal];
[customSlider setMaximumTrackImage:stetchRightTrack forState:UIControlStateNormal];
customSlider.minimumValue = 0.0;
customSlider.maximumValue = 1.0;
customSlider.continuous = NO;
customSlider.value = 0.00;
}
-(void) sliderAction: (UISlider *) sender{
if(sender.value !=1.0){
[sender setValue: 0.00 animated: YES];
}
else{
[sender setValue 0.00 animated: YES];
// add some code here depending on what you want to do!
}
}
EDIT (trying to subclass with the code below)
MyUISlider.h
#import <UIKit/UIKit.h>
#interface MyUISlider : UISlider {
}
#end
MyUISlider.m
#import "MyUISlider.h"
#implementation MyUISlider
- (CGRect)thumbRectForBounds:(CGRect)bounds trackRect:(CGRect)rect value:(float)value
{
return CGRectInset ([super thumbRectForBounds:bounds trackRect:rect value:value], 10 , 10);
}
#end

You have to override thumbRectForBounds:trackRect:value: in a custom subclass (instead of calling the method yourself like you do in your code, which does nothing except returning a value that you don't even bother to retrieve).
thumbRectForBounds:trackRect:value: is not a method to set the tumb rect, but to get it. The slider will internally call this thumbRectForBounds:trackRect:value: method to know where to draw the thumb image, so you need to override it — as explained in the documentation — to return the CGRect you want (so that it will be 35x35px as you wish).

Updated for Swift 3.0 and iOS 10
private func generateHandleImage(with color: UIColor) -> UIImage {
let rect = CGRect(x: 0, y: 0, width: self.bounds.size.height + 20, height: self.bounds.size.height + 20)
return UIGraphicsImageRenderer(size: rect.size).image { (imageContext) in
imageContext.cgContext.setFillColor(color.cgColor)
imageContext.cgContext.fill(rect.insetBy(dx: 10, dy: 10))
}
}
Calling:
self.sliderView.setThumbImage(self.generateHandleImage(with: .white), for: .normal)
Original answer:
By far the easiest method is just to increase the size of the thumb image with invisible border around it:
- (UIImage *)generateHandleImageWithColor:(UIColor *)color
{
CGRect rect = CGRectMake(0.0f, 0.0f, self.bounds.size.height + 20.f, self.bounds.size.height + 20.f);
UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0.f);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, color.CGColor);
CGContextFillRect(context, CGRectInset(rect, 10.f, 10.f));
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
And than adding this image to slider:
[self.sliderView setThumbImage:[self generateHandleImageWithColor:[UIColor redColor]] forState:UIControlStateNormal];

I also had this problem and solved it by overriding
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
method.
You can return YES for those points, that you want to be touched.

You can check my double slider project
In summary, I implemented the hit test method so the beginTrackingWithTouch is invoked.
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if (CGRectContainsPoint(self.leftHandler.frame, point) || CGRectContainsPoint(self.rightHandler.frame, point)) {
return self;
}
return [super hitTest:point withEvent:event];
}
Check the class for further details

why you wanna go for custom slider while you can change the area of a default slider ;)
take a look at this code it worked for me to increase the touch area of my UISlider hope it helps you too
CGRect sliderFrame = yourSlider.frame;
sliderFrame.size.height = 70.0;
[yourSlider setFrame:sliderFrame];

Related

Make a custom UISlider's thumb-click area larger? [duplicate]

I am creating a custom UISlider and wonder, how do you increase a thumb image's "click area" ? The code below works great, except the thumb's "click area" remains unchanged. If I am not wrong, I believe the standard thumb area is 19 px ? Lets say I would like to increase it to 35px with the following code below, what steps do I need to take? Keep in mind I am not an expert within this area at all.
EDIT The code below is corrected and ready for copy pasta! Hope it helps you!
main.h
#import "MyUISlider.h"
#interface main : MLUIViewController <UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate, UIActionSheetDelegate, UIAlertViewDelegate, MFMailComposeViewControllerDelegate, UIGestureRecognizerDelegate, MLSearchTaskDelegate, MLDeactivateDelegate, MLReceiptDelegate>
{
..........
}
- (void)create_Custom_UISlider;
main.m
- (void)viewDidLoad
{
[self create_Custom_UISlider];
[self.view addSubview: customSlider];
}
- (void)create_Custom_UISlider
{
CGRect frame = CGRectMake(20.0, 320.0, 280, 0.0);
customSlider = [[MyUISlider alloc] initWithFrame:frame];
[customSlider addTarget:self action:#selector(sliderAction:) forControlEvents:UIControlEventValueChanged];
// in case the parent view draws with a custom color or gradient, use a transparent color
customSlider.backgroundColor = [UIColor clearColor];
UIImage *stetchLeftTrack = [[UIImage imageNamed:#"blue_slider08.png"]
stretchableImageWithLeftCapWidth: 10.0 topCapHeight:0.0];
UIImage *stetchRightTrack = [[UIImage imageNamed:#"blue_slider08.png"]
stretchableImageWithLeftCapWidth: 260.0 topCapHeight:0.0];
[customSlider setThumbImage: [UIImage imageNamed:#"slider_button00"] forState:UIControlStateNormal];
[customSlider setMinimumTrackImage:stetchLeftTrack forState:UIControlStateNormal];
[customSlider setMaximumTrackImage:stetchRightTrack forState:UIControlStateNormal];
customSlider.minimumValue = 0.0;
customSlider.maximumValue = 1.0;
customSlider.continuous = NO;
customSlider.value = 0.00;
}
-(void) sliderAction: (UISlider *) sender{
if(sender.value !=1.0){
[sender setValue: 0.00 animated: YES];
}
else{
[sender setValue 0.00 animated: YES];
// add some code here depending on what you want to do!
}
}
EDIT (trying to subclass with the code below)
MyUISlider.h
#import <UIKit/UIKit.h>
#interface MyUISlider : UISlider {
}
#end
MyUISlider.m
#import "MyUISlider.h"
#implementation MyUISlider
- (CGRect)thumbRectForBounds:(CGRect)bounds trackRect:(CGRect)rect value:(float)value
{
return CGRectInset ([super thumbRectForBounds:bounds trackRect:rect value:value], 10 , 10);
}
#end
You have to override thumbRectForBounds:trackRect:value: in a custom subclass (instead of calling the method yourself like you do in your code, which does nothing except returning a value that you don't even bother to retrieve).
thumbRectForBounds:trackRect:value: is not a method to set the tumb rect, but to get it. The slider will internally call this thumbRectForBounds:trackRect:value: method to know where to draw the thumb image, so you need to override it — as explained in the documentation — to return the CGRect you want (so that it will be 35x35px as you wish).
Updated for Swift 3.0 and iOS 10
private func generateHandleImage(with color: UIColor) -> UIImage {
let rect = CGRect(x: 0, y: 0, width: self.bounds.size.height + 20, height: self.bounds.size.height + 20)
return UIGraphicsImageRenderer(size: rect.size).image { (imageContext) in
imageContext.cgContext.setFillColor(color.cgColor)
imageContext.cgContext.fill(rect.insetBy(dx: 10, dy: 10))
}
}
Calling:
self.sliderView.setThumbImage(self.generateHandleImage(with: .white), for: .normal)
Original answer:
By far the easiest method is just to increase the size of the thumb image with invisible border around it:
- (UIImage *)generateHandleImageWithColor:(UIColor *)color
{
CGRect rect = CGRectMake(0.0f, 0.0f, self.bounds.size.height + 20.f, self.bounds.size.height + 20.f);
UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0.f);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, color.CGColor);
CGContextFillRect(context, CGRectInset(rect, 10.f, 10.f));
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
And than adding this image to slider:
[self.sliderView setThumbImage:[self generateHandleImageWithColor:[UIColor redColor]] forState:UIControlStateNormal];
I also had this problem and solved it by overriding
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
method.
You can return YES for those points, that you want to be touched.
You can check my double slider project
In summary, I implemented the hit test method so the beginTrackingWithTouch is invoked.
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if (CGRectContainsPoint(self.leftHandler.frame, point) || CGRectContainsPoint(self.rightHandler.frame, point)) {
return self;
}
return [super hitTest:point withEvent:event];
}
Check the class for further details
why you wanna go for custom slider while you can change the area of a default slider ;)
take a look at this code it worked for me to increase the touch area of my UISlider hope it helps you too
CGRect sliderFrame = yourSlider.frame;
sliderFrame.size.height = 70.0;
[yourSlider setFrame:sliderFrame];

Subclassing UIView in a map to draw lines

I have an app that displays custom maps. I use a CATiledView to display the maps.
I would like to be able to draw a route over the top of the maps. To do this, I am creating a UIView then adding it to the scrollView after I add the tiling layer like this:
- (void)displayTiledImageNamed:(NSString *)imageName size:(CGSize)imageSize
{
// clear the previous imageView
[imageView removeFromSuperview];
[imageView release];
imageView = nil;
[linesView removeFromSuperview];
[linesView release];
linesView = nil;
// reset our zoomScale to 1.0 before doing any further calculations
self.zoomScale = 1.0;
// make a new TilingView for the new image
imageView = [[TilingView alloc] initWithImageName:imageName size:imageSize];
linesView = [[LinesView alloc]initWithImageName:imageName size:imageSize];
linesView.backgroundColor = [UIColor clearColor];
[self addSubview:imageView];
[self addSubview:linesView];
[self configureForImageSize:imageSize];
}
The problem, is the line that I create in linesView is not scaling correctly.
It's hard to describe, but the line that is being created is scaled as if it were drawn on the device itself, rather than drawn on the map. See the following code:
#import "LinesView.h"
#implementation LinesView
#synthesize imageName;
- (id)initWithImageName:(NSString *)name size:(CGSize)size
{
if ((self = [super initWithFrame:CGRectMake(0, 0, size.width, size.height)])) {
self.imageName = name;
}
return self;
}
- (void)dealloc
{
[super dealloc];
}
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetRGBStrokeColor(context, 1, 0, 0, 1);
CGContextSetLineWidth(context, 20.0);
CGContextMoveToPoint(context, 1.0f, 220.0f);
CGContextAddLineToPoint(context, 340.0f, 80);
CGContextStrokePath(context);
}
#end
I have tried putting the code to draw the line in the drawRect method of the tilingView and it works perfectly. The line width is 20px relative to the map. In the linesView the line appears to be 20px wide relative to the device and positioned relative to the scrollview.
Sorry I'm trying my best to describe the problem...
Figured this out - I added the linesView to the tilingView instead of the scrollView like this:
- (void)displayTiledImageNamed:(NSString *)imageName size:(CGSize)imageSize
{
// clear the previous imageView
[imageView removeFromSuperview];
[imageView release];
imageView = nil;
[linesView removeFromSuperview];
[linesView release];
linesView = nil;
// reset our zoomScale to 1.0 before doing any further calculations
self.zoomScale = 1.0;
// make a new TilingView for the new image
imageView = [[TilingView alloc] initWithImageName:imageName size:imageSize];
linesView = [[LinesView alloc]initWithImageName:imageName size:imageSize];
linesView.backgroundColor = [UIColor clearColor];
[self addSubview:imageView];
[imageView addSubview:linesView];
[self configureForImageSize:imageSize];
}

Change background color of UIButton when Highlighted

Hi I want to change the UIButton background color which user Tap on the button.
I am showing background color using gradient, when user tap I need to change the gradient color.
[btn setTitle: #"Next" forState:UIControlStateNormal];
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = btn.bounds;
gradient.cornerRadius = 10.0f;
locations = [[NSArray alloc] initWithObjects: 0.0f, 0.0f, 0.0f,0.0f, nil];
[gradient setLocations:locations];
colorNext = [[NSArray alloc] initWithObjects:…., nil];
gradient.colors = colorNext;
[locations release];
[btn.layer insertSublayer:gradient atIndex:0];
btn.titleLabel.textColor = [UIColor blackColor];
Changing the internal layer hierarchy of a UIButton is not recommended since it may break in a future update of the iOS. Also, it may cause Apple to reject your application. A better solution would be to create a button subclass. Here is an example of how to do it:
#interface MyButton : UIButton
#end
#implementation MyButton
- (id)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame])
{
[self addObserver:self forKeyPath:#"highlighted" options:NSKeyValueObservingOptionNew context:NULL];
}
return self;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
if (self.highlighted == YES)
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
const CGFloat *topGradientColorComponents = CGColorGetComponents([UIColor whiteColor].CGColor);
const CGFloat *bottomGradientColorComponents = CGColorGetComponents([UIColor blackColor].CGColor);
CGFloat colors[] =
{
topGradientColorComponents[0], topGradientColorComponents[1], topGradientColorComponents[2], topGradientColorComponents[3],
bottomGradientColorComponents[0], bottomGradientColorComponents[1], bottomGradientColorComponents[2], bottomGradientColorComponents[3]
};
CGGradientRef gradient = CGGradientCreateWithColorComponents(rgb, colors, NULL, sizeof(colors) / (sizeof(colors[0]) * 4));
CGColorSpaceRelease(rgb);
CGContextDrawLinearGradient(ctx, gradient, CGPointMake(0, 0), CGPointMake(0, self.bounds.size.height), 0);
CGGradientRelease(gradient);
}
else
{
// Do custom drawing for normal state
}
}
- (void)dealloc
{
[self removeObserver:self forKeyPath:#"highlighted"];
[super dealloc];
}
#end
You may need to modify it a bit to get it to do what you want but I think you get the basic idea.
Try this:
[Button addTarget:self action:#selector(doSomething:) forControlEvents:UIControlEventTouchUpInside];
[Button addTarget:self action:#selector(setBgColorForButton:) forControlEvents:UIControlEventTouchDown];
[Button addTarget:self action:#selector(clearBgColorForButton:) forControlEvents:UIControlEventTouchDragExit];
-(void)setBgColorForButton:(UIButton*)sender
{
[sender setBackgroundColor:[UIColor blackColor]];
}
-(void)clearBgColorForButton:(UIButton*)sender
{
[sender setBackgroundColor:[UIColor redColor]];
}
-(void)doSomething:(UIButton*)sender
{
double delayInSeconds = 0.3;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[sender setBackgroundColor:[UIColor redColor]];
});
//do something
}
You can use this method to create and solid UIImage of specified color and then apply it to your button. I've made a category for UIImage to do this.
+ (UIImage *)imageWithColor:(UIColor *)color size:(CGSize)size
{
UIGraphicsBeginImageContext(size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, color.CGColor);
CGContextFillRect(context, (CGRect){.size = size});
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
+ (UIImage *)imageWithColor:(UIColor *)color
{
return [UIImage imageWithColor:color size:CGSizeMake(1, 1)];
}
Usage:
[button setBackgroundImage:[UIImage imageWithColor:[UIColor redColor]]
forState:UIControlStateNormal];
As Matthias pointed out, you can safely use 1x1 image for your button background – it will be stretched to fill the button.
I found this AYUIButton on GitHub and it works perfectly :)
Here is sample code:
[btn setBackgroundColor:[UIColor redColor] forState:UIControlStateNormal];
[btn setBackgroundColor:[UIColor blueColor] forState:UIControlStateHighlighted];
When you set the TintColor of the Button it works when highlighted
[YourButton setTintColor:[UIColor color]];
Swift
Extension for UIButton that let you specify background color for each state:
https://github.com/acani/UIButtonBackgroundColor
Please note that for iphone 6 plus you should change the following, inside the extension in the .swift class, that will fix it for all the versions:
//let rect = CGRect(x: 0, y: 0, width: 0.5, height: 0.5) comment this line
let rect = CGRect(x: 0, y: 0, width: 1, height: 1)

UITextView inside UIAlertView subclass

I'm trying to put a UITextView inside a custom subclass of UIAlertView that I create using a background image of mine. The UITextView is only for displaying large quantities of text (so the scrolling).
Here is the code of my subclass
- (id)initNarrationViewWithImage:(UIImage *)image text:(NSString *)text{
if (self = [super init]) {
self.backgroundImage = image;
UITextView * textView = [[UITextView alloc] initWithFrame:CGRectZero];
self.textualNarrationView = textView;
[textView release];
self.textualNarrationView.text = text;
self.textualNarrationView.backgroundColor = [UIColor colorWithRed:0.0 green:1.0 blue:0.0 alpha:1.0];
self.textualNarrationView.opaque = YES;
[self addSubview:textualNarrationView];
}
return self;
}
- (void)drawRect:(CGRect)rect {
NSLog(#"DRAU");
CGSize imageSize = self.backgroundImage.size;
[self.backgroundImage drawInRect:CGRectMake(0, 0, imageSize.width, imageSize.height)];
}
- (void)layoutSubviews {
CGSize imageSize = self.backgroundImage.size;
self.textualNarrationView.bounds = CGRectMake(0, 0, imageSize.width - 20, imageSize.height - 20);
self.textualNarrationView.center = CGPointMake(320/2, 480/2);
}
- (void)show{
[super show];
NSLog(#"SCIO");
CGSize imageSize = self.backgroundImage.size;
self.bounds = CGRectMake(0, 0, imageSize.width, imageSize.height);
self.center = CGPointMake(320/2, 480/2);
}
This is my first time subclassing a UIView, I'm surely missing something since the background image appears correctly, the UITextview also but after a fraction of a second it jumps all up (seems like a coordinate problem).
I've finally created my own customized version of UIAlertView and provided a UITextView inside of it. Here is what brought me to do so.
By doing it I experienced a weird behaviour when I was trying to attach the UITextView to a background UIImageView as a subview, it wasn't responsive to touches anymore, the way to make it work as expected was to attach it to the base view and implement the following method
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
by making it return YES when encessary.

How to make a subview tint a portion of the screen on the iPhone?

I would like to write a UIView subclass that, among other things, colors whatever's underneath it a certain color. This is what I've come up with, but it unfortunately doesn't seem to work correctly:
#import <UIKit/UIKit.h>
#class MyOtherView;
#interface MyView : UIView
{
MyOtherView *subview;
}
#end
#implementation MyView
- (id)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
frame.origin.x += 100.0;
frame.origin.y += 100.0;
frame.size.width = frame.size.height = 200.0;
subview = [[MyOtherView alloc] initWithFrame:frame];
[self addSubview:subview];
[subview release];
}
return self;
}
- (void)drawRect:(CGRect)rect
{
// Draw a background to test out on
UIImage *image = [UIImage imageNamed:#"somepic.png"];
[image drawAtPoint:rect.origin];
const CGContextRef ctx = UIGraphicsGetCurrentContext();
[[UIColor blueColor] setFill];
rect.size.width = rect.size.height = 200.0;
CGContextFillRect(ctx, rect);
}
#end
#interface MyOtherView : UIView
#end
#implementation MyOtherView
- (void)drawRect:(CGRect)rect
{
// This should tint "MyView" but it doesn't.
const CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSaveGState(ctx);
CGContextSetBlendMode(ctx, kCGBlendModeScreen);
[[UIColor redColor] setFill];
CGContextFillRect(ctx, rect);
CGContextRestoreGState(ctx);
}
#end
I want "MyOtherView" to color "MyView" red where it overlaps, but instead it just draws an opaque red block onto it. However, this seems work fine if I copy the -drawRect: function from "MyOtherView" and append it to the one in "MyView" (this took me quite a headache to finally realize). Anyone know what I'm doing wrong? Is it even possible to do this, or should I be approaching it differently?
I think you're over-thinking this. Overlay one view over the other, and set the alpha of the top view to 0.5. You will also need to set opaque to NO.
If you set the background color of the view appropriately, you won't even need to override drawRect.
Might want to check out this SO question.