您的位置:首页 > 移动开发 > IOS开发

ios开发(十六)custom control

2013-12-11 21:18 176 查看
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(效果如下图)的很好的例子:

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); }
显示如下
然后再修改其他的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);    }}

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;}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐