ios开发(十六)custom control

http://www.raywenderlich.com/36288/how-to-make-a-custom-control

http://hubpages.com/hub/iOS-Create-Custom-Buttons-and-Controls

本文主要是对上门的链接的摘要。

下面是apple control的一个hierarchy(如果需要查看更详细的hierachy参照UIKit Framework Reference):

从上图中UIControl可以相应一些event,是做contol很好的开始。

下面就是一个做带两个按钮的slider(效果如下图)的很好的例子:

Learn how to make a custom control like a slider that has two knobs


1. 首先从UIControl继承一个class叫CERangeSlider,当然我们希望能够一边改进这个control一边看到改进的结果,所以我们在能显示的view中添加如下代码:

- (void)viewDidLoad
{
    [super viewDidLoad];
 
    // Do any additional setup after loading the view, typically from a nib.
 
    NSUInteger margin = 20;
    CGRect sliderFrame = CGRectMake(margin, margin, self.view.frame.size.width - margin * 2, 30);
    _rangeSlider = [[CERangeSlider alloc] initWithFrame:sliderFrame];
    _rangeSlider.backgroundColor = [UIColor redColor];
 
    [self.view addSubview:_rangeSlider];
}
然后就看到如下图片


2. 给control添加一些表示当前control状态的内容

@property (nonatomic) float maximumValue;
@property (nonatomic) float minimumValue;
@property (nonatomic) float upperValue;
@property (nonatomic) float lowerValue;
并给一些初始数值

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
        _maximumValue = 10.0;
        _minimumValue = 0.0;
        _upperValue = 8.0;
        _lowerValue = 2.0;
    }
    return self;
}

3. Images vs. CoreGraphics

image 和 CoreGraphics是两种不同的描绘control的显示的方式。

两种各有优缺点,image要求美工好,CoreGraphics要求代码多。

文章介绍了用CoreGraphics的方式来做,首先要添加一个库QuartzCore.framework, 具体添加办法如下图:


然后修改CERangeSlider的m文件注意不是h文件

#import <QuartzCore/QuartzCore.h>
@implementation CERangeSlider
{
    CALayer* _trackLayer;
    CALayer* _upperKnobLayer;
    CALayer* _lowerKnobLayer;
 
    float _knobWidth;
    float _useableTrackLength;
}
然后在initial Frame中添加如下代码来初始化这些layer:

_trackLayer = [CALayer layer];
_trackLayer.backgroundColor = [UIColor blueColor].CGColor;
[self.layer addSublayer:_trackLayer];
 
_upperKnobLayer = [CALayer layer];
_upperKnobLayer.backgroundColor = [UIColor greenColor].CGColor;
[self.layer addSublayer:_upperKnobLayer];
 
_lowerKnobLayer = [CALayer layer];
_lowerKnobLayer.backgroundColor = [UIColor greenColor].CGColor;
[self.layer addSublayer:_lowerKnobLayer];
 
[self setLayerFrames];
然后继续添加一些初始化的功能

  
  
- (void) setLayerFrames
{
    _trackLayer.frame = CGRectInset(self.bounds, 0, self.bounds.size.height / 3.5);
    [_trackLayer setNeedsDisplay];
 
    _knobWidth = self.bounds.size.height;
    _useableTrackLength = self.bounds.size.width - _knobWidth;
 
    float upperKnobCentre = [self positionForValue:_upperValue];
    _upperKnobLayer.frame = CGRectMake(upperKnobCentre - _knobWidth / 2, 0, _knobWidth, _knobWidth);
 
    float lowerKnobCentre = [self positionForValue:_lowerValue];
    _lowerKnobLayer.frame = CGRectMake(lowerKnobCentre - _knobWidth / 2, 0, _knobWidth, _knobWidth);
 
    [_upperKnobLayer setNeedsDisplay];
    [_lowerKnobLayer setNeedsDisplay];
}
 
- (float) positionForValue:(float)value // 坐标映射
{
    return _useableTrackLength * (value - _minimumValue) /
        (_maximumValue - _minimumValue) + (_knobWidth / 2);
}
然后运行会有下面的结果

4. 下面要添加一些互动的功能

想从CALayer继承一个class CERangeSliderKnobLayer。

        
        
#import <QuartzCore/QuartzCore.h>
 
@class CERangeSlider;
 
@interface CERangeSliderKnobLayer : CALayer
 
@property BOOL highlighted;
@property (weak) CERangeSlider* slider;
 
@end
下面把原来的两个layer替换掉

         
         
CERangeSliderKnobLayer* _upperKnobLayer;
CERangeSliderKnobLayer* _lowerKnobLayer;

init frame里也做如下修改

          
          
_upperKnobLayer = [CERangeSliderKnobLayer layer];
_upperKnobLayer.slider = self;
_upperKnobLayer.backgroundColor = [UIColor greenColor].CGColor;
[self.layer addSublayer:_upperKnobLayer];
 
_lowerKnobLayer = [CERangeSliderKnobLayer layer];
_lowerKnobLayer.slider = self;
_lowerKnobLayer.backgroundColor = [UIColor greenColor].CGColor;
[self.layer addSublayer:_lowerKnobLayer];

再在CERangeSlide添加触摸相应的内容,分为begin continue end三部分:

          
          
- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event // 首先要筛选出相应的区域
{
    _previousTouchPoint = [touch locationInView:self];
 
    // hit test the knob layers
    if(CGRectContainsPoint(_lowerKnobLayer.frame, _previousTouchPoint))
    {
        _lowerKnobLayer.highlighted = YES;
        [_lowerKnobLayer setNeedsDisplay];
    }
    else if(CGRectContainsPoint(_upperKnobLayer.frame, _previousTouchPoint))
    {
        _upperKnobLayer.highlighted = YES;
        [_upperKnobLayer setNeedsDisplay];
    }
    return _upperKnobLayer.highlighted || _lowerKnobLayer.highlighted;
}

#define BOUND(VALUE, UPPER, LOWER)	MIN(MAX(VALUE, LOWER), UPPER)
 
- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event // 修改数值
{
    CGPoint touchPoint = [touch locationInView:self];
 
    // 1. determine by how much the user has dragged
    float delta = touchPoint.x - _previousTouchPoint.x;
    float valueDelta = (_maximumValue - _minimumValue) * delta / _useableTrackLength;
 
    _previousTouchPoint = touchPoint;
 
    // 2. update the values
    if (_lowerKnobLayer.highlighted)
    {
        _lowerValue += valueDelta;
        _lowerValue = BOUND(_lowerValue, _upperValue, _minimumValue);
    }
    if (_upperKnobLayer.highlighted)
    {
        _upperValue += valueDelta;
        _upperValue = BOUND(_upperValue, _maximumValue, _lowerValue);
    }
 
    // 3. Update the UI state
    [CATransaction begin];
    [CATransaction setDisableActions:YES] ;
 
    [self setLayerFrames];
 
    [CATransaction commit];
 
    return YES;
}

- (void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
{
    _lowerKnobLayer.highlighted = _upperKnobLayer.highlighted = NO;
    [_lowerKnobLayer setNeedsDisplay];
    [_upperKnobLayer setNeedsDisplay];
}


5. control可以拖动了,那怎样告诉使用control的view当前的状态呢?

有两种pattern可以采用delegate和target-action,一般认为前一种拓展更好,后一种更加系统,等于是内置的,这里我们主要用后面一种。
在 continueTrackingWithTouch:withEvent:中添加如下代码:
[self sendActionsForControlEvents:UIControlEventValueChanged];
然后再在我们显示的主程序中添加如下代码:

       
       
[_rangeSlider addTarget:self
                     action:@selector(slideValueChanged:)
           forControlEvents:UIControlEventValueChanged];
- (void)slideValueChanged:(id)control
{
    NSLog(@"Slider value changed: (%.2f,%.2f)",
          _rangeSlider.lowerValue, _rangeSlider.upperValue);
}
至此主要的功能都有了。

6. 利用CoreGraphic修改外观

再添加一个 CERangeSliderTrackLayer

        
        
#import <QuartzCore/QuartzCore.h>
 
@class CERangeSlider;
 
@interface CERangeSliderTrackLayer : CALayer
 
@property (weak) CERangeSlider* slider;
 
@end
同样对之前设置的
_trackLayer做和之前其他的layer一样的修改。
然后CERangeSlider.h 添加
@property (nonatomic) UIColor* trackColour;
@property (nonatomic) UIColor* trackHighlightColour;
@property (nonatomic) UIColor* knobColour;
@property (nonatomic) float curvaceousness;
 
- (float) positionForValue:(float)value;
CERangeSlider.m   initWithFrame中添加

         
         
_trackHighlightColour = [UIColor colorWithRed:0.0 green:0.45 blue:0.94 alpha:1.0];
_trackColour = [UIColor colorWithWhite:0.9 alpha:1.0];
_knobColour = [UIColor whiteColor];
_curvaceousness = 1.0;
_maximumValue = 10.0;
_minimumValue = 0.0;
然后在CERangeSliderTrackLayer.m中添加

          
          
- (void)drawInContext:(CGContextRef)ctx
{
    // clip
    float cornerRadius = self.bounds.size.height * self.slider.curvaceousness / 2.0;
    UIBezierPath *switchOutline = [UIBezierPath bezierPathWithRoundedRect:self.bounds
                                                             cornerRadius:cornerRadius];
	CGContextAddPath(ctx, switchOutline.CGPath);
    CGContextClip(ctx);
 
    // 1) fill the track
    CGContextSetFillColorWithColor(ctx, self.slider.trackColour.CGColor);
    CGContextAddPath(ctx, switchOutline.CGPath);
    CGContextFillPath(ctx);
 
    // 2) fill the highlighed range
    CGContextSetFillColorWithColor(ctx, self.slider.trackHighlightColour.CGColor);
    float lower = [self.slider positionForValue:self.slider.lowerValue];
    float upper = [self.slider positionForValue:self.slider.upperValue];
    CGContextFillRect(ctx, CGRectMake(lower, 0, upper - lower, self.bounds.size.height));
 
    // 3) add a highlight over the track
    CGRect highlight = CGRectMake(cornerRadius/2, self.bounds.size.height/2,
                                  self.bounds.size.width - cornerRadius, self.bounds.size.height/2);
    UIBezierPath *highlightPath = [UIBezierPath bezierPathWithRoundedRect:highlight
                                                             cornerRadius:highlight.size.height * self.slider.curvaceousness / 2.0];
    CGContextAddPath(ctx, highlightPath.CGPath);
    CGContextSetFillColorWithColor(ctx, [UIColor colorWithWhite:1.0 alpha:0.4].CGColor);
    CGContextFillPath(ctx);
 
    // 4) inner shadow
    CGContextSetShadowWithColor(ctx, CGSizeMake(0, 2.0), 3.0, [UIColor grayColor].CGColor);
    CGContextAddPath(ctx, switchOutline.CGPath);
    CGContextSetStrokeColorWithColor(ctx, [UIColor grayColor].CGColor);
    CGContextStrokePath(ctx);
 
    // 5) outline the track
    CGContextAddPath(ctx, switchOutline.CGPath);
    CGContextSetStrokeColorWithColor(ctx, [UIColor blackColor].CGColor);
    CGContextSetLineWidth(ctx, 0.5);
    CGContextStrokePath(ctx); 
}
显示如下
Screenshot #4

然后再修改其他的layer CERangeSliderKnobLayer.m,

- (void)drawInContext:(CGContextRef)ctx
{
    CGRect knobFrame = CGRectInset(self.bounds, 2.0, 2.0);
 
    UIBezierPath *knobPath = [UIBezierPath bezierPathWithRoundedRect:knobFrame
                                                        cornerRadius:knobFrame.size.height * self.slider.curvaceousness / 2.0];
 
    // 1) fill - with a subtle shadow
    CGContextSetShadowWithColor(ctx, CGSizeMake(0, 1), 1.0, [UIColor grayColor].CGColor);
    CGContextSetFillColorWithColor(ctx, self.slider.knobColour.CGColor);
    CGContextAddPath(ctx, knobPath.CGPath);
    CGContextFillPath(ctx);
 
    // 2) outline
    CGContextSetStrokeColorWithColor(ctx, [UIColor grayColor].CGColor);
    CGContextSetLineWidth(ctx, 0.5);
    CGContextAddPath(ctx, knobPath.CGPath);
    CGContextStrokePath(ctx);
 
 
    // 3) inner gradient
    CGRect rect = CGRectInset(knobFrame, 2.0, 2.0);
    UIBezierPath *clipPath = [UIBezierPath bezierPathWithRoundedRect:rect
                                                        cornerRadius:rect.size.height * self.slider.curvaceousness / 2.0];
 
    CGGradientRef myGradient;
    CGColorSpaceRef myColorspace;
    size_t num_locations = 2;
    CGFloat locations[2] = { 0.0, 1.0 };
    CGFloat components[8] = { 0.0, 0.0, 0.0 , 0.15,  // Start color
        0.0, 0.0, 0.0, 0.05 }; // End color
 
    myColorspace = CGColorSpaceCreateDeviceRGB();
    myGradient = CGGradientCreateWithColorComponents (myColorspace, components,
                                                      locations, num_locations);
 
    CGPoint startPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMinY(rect));
    CGPoint endPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMaxY(rect));
 
    CGContextSaveGState(ctx);
    CGContextAddPath(ctx, clipPath	.CGPath);
    CGContextClip(ctx);
    CGContextDrawLinearGradient(ctx, myGradient, startPoint, endPoint, 0);
 
    CGGradientRelease(myGradient);
    CGColorSpaceRelease(myColorspace);
    CGContextRestoreGState(ctx);
 
    // 4) highlight
    if (self.highlighted)
    {
        // fill
        CGContextSetFillColorWithColor(ctx, [UIColor colorWithWhite:0.0 alpha:0.1].CGColor);
        CGContextAddPath(ctx, knobPath.CGPath);
        CGContextFillPath(ctx);
    }
}

Screenshot #5

7. 之前都是参数相应control的变化,现在让control相应参数的变化 

下面的代码比较tricky但是也是一种很好的模式。

#define GENERATE_SETTER(PROPERTY, TYPE, SETTER, UPDATER) \
- (void)SETTER:(TYPE)PROPERTY { \
    if (_##PROPERTY != PROPERTY) { \
        _##PROPERTY = PROPERTY; \
        [self UPDATER]; \
    } \
}

GENERATE_SETTER(trackHighlightColour, UIColor*, setTrackHighlightColour, redrawLayers)
 
GENERATE_SETTER(trackColour, UIColor*, setTrackColour, redrawLayers)
 
GENERATE_SETTER(curvaceousness, float, setCurvaceousness, redrawLayers)
 
GENERATE_SETTER(knobColour, UIColor*, setKnobColour, redrawLayers)
 
GENERATE_SETTER(maximumValue, float, setMaximumValue, setLayerFrames)
 
GENERATE_SETTER(minimumValue, float, setMinimumValue, setLayerFrames)
 
GENERATE_SETTER(lowerValue, float, setLowerValue, setLayerFrames)
 
GENERATE_SETTER(upperValue, float, setUpperValue, setLayerFrames)
 
- (void) redrawLayers
{
    [_upperKnobLayer setNeedsDisplay];
    [_lowerKnobLayer setNeedsDisplay];
    [_trackLayer setNeedsDisplay];
}

之后再设置一下主调用control的view

[self performSelector:@selector(updateState) withObject:nil afterDelay:1.0f];
- (void)updateState
{
    _rangeSlider.trackHighlightColour = [UIColor redColor];
    _rangeSlider.curvaceousness = 0.0;
}





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值