带有渐变、圆角、边框和阴影的UIButton

17

网站上有几个类似的问题,但我正在寻找一些特定且略微不同的东西。

我遵循了这里给出的指示:http://www.cimgf.com/2010/01/28/fun-with-uibuttons-and-core-animation-layers/,通过对UIButton进行子类化来创建一个通用类,可以在其中指定渐变颜色,而不是尝试使用静态图像。

我遇到了一个问题,即按钮层上的setMasksToBounds要么允许 A)显示投影,但同时也允许渐变层在圆角之外显示 要么 B)让渐变层剪切到圆角,但不允许显示投影

我解决问题的方法似乎很笨拙(尽管它有效),我想知道是否有人知道更好和/或更清晰的方法来完成相同的事情。以下是我的代码:

CSGradientButton.h

#import <UIKit/UIKit.h>
@interface CSGradientButton : UIButton {
    UIColor *_highColor;
    UIColor *_lowColor;
    CAGradientLayer *gradientLayer;
    CALayer *wrapperLayer;
    CGColorRef _borderColor;
}

@property (nonatomic, retain) UIColor *_highColor;
@property (nonatomic, retain) UIColor *_lowColor;
@property (nonatomic) CGColorRef _borderColor;
@property (nonatomic, retain) CALayer *wrapperLayer;
@property (nonatomic, retain) CAGradientLayer *gradientLayer;

- (void)setHighColor:(UIColor*)color;
- (void)setLowColor:(UIColor*)color;
- (void)setBorderColor:(CGColorRef)color;
- (void)setCornerRadius:(float)radius;

@end

CSGradient.m(仅涉及有趣的部分)

#import "CSGradientButton.h" 

@implementation CSGradientButton

...

- (void)awakeFromNib
{
    // Initialize the gradient wrapper layer
    wrapperLayer = [[CALayer alloc] init];
    // Set its bounds to be the same of its parent
    [wrapperLayer setBounds:[self bounds]];
    // Center the layer inside the parent layer
    [wrapperLayer setPosition:
     CGPointMake([self bounds].size.width/2,
                 [self bounds].size.height/2)];

    // Initialize the gradient layer
    gradientLayer = [[CAGradientLayer alloc] init];
    // Set its bounds to be the same of its parent
    [gradientLayer setBounds:[self bounds]];
    // Center the layer inside the parent layer
    [gradientLayer setPosition: CGPointMake([self bounds].size.width/2,
             [self bounds].size.height/2)];

    // Insert the layer at position zero to make sure the 
    // text of the button is not obscured
    [wrapperLayer insertSublayer:gradientLayer atIndex:0];
    [[self layer] insertSublayer:wrapperLayer atIndex:0];

    // Set the layer's corner radius
    [[self layer] setCornerRadius:0.0f];
    [wrapperLayer setCornerRadius:0.0f];
    // Turn on masking
    [wrapperLayer setMasksToBounds:YES];
    // Display a border around the button 
    // with a 1.0 pixel width
    [[self layer] setBorderWidth:1.0f];

}

- (void)drawRect:(CGRect)rect
{
    if (_highColor && _lowColor)
    {
        // Set the colors for the gradient to the 
        // two colors specified for high and low
        [gradientLayer setColors:
         [NSArray arrayWithObjects:
          (id)[_highColor CGColor], 
          (id)[_lowColor CGColor], nil]];
    }

    [super drawRect:rect];
}

- (void)setCornerRadius:(float)radius
{
    [[self layer] setCornerRadius:radius];
    // and get the wrapper for the gradient layer too
    [wrapperLayer setCornerRadius:radius];
}

- (void)setHighColor:(UIColor*)color
{
    // Set the high color and repaint
    [self set_highColor:color];
    [[self layer] setNeedsDisplay];
}

- (void)setLowColor:(UIColor*)color
{
    // Set the low color and repaint
    [self set_lowColor:color];
    [[self layer] setNeedsDisplay];
}

- (void)setBorderColor:(CGColorRef)color
{
    [[self layer] setBorderColor:color];
    [[self layer] setNeedsDisplay];
}


@end

可以看出,我添加了一个“包装”层,梯度图层可以安全地进行遮盖,而按钮视图的顶层CALayer在添加阴影时可以安全地设置masksToBounds = NO。 我添加了一个setCornerRadius:方法,允许顶层和“包装”都进行调整。

所以,与其像这样做:[[myCustomButton layer] setCornerRadius:3.0f];,我只需这样说:[myCustomButton setCornerRadius:3.0f]; 可以看出,这可能不是我希望的那么简洁。

有更好的方法吗?


具有讽刺意味的是,随着iOS 7的推出,这个问题可能会变得不那么相关了 :) - jinglesthula
3个回答

22

这是我发现的一个制作圆角、渐变和阴影按钮的方法。这个示例使用了一个特定的渐变,但显然可以替换为其他渐变。

@implementation CustomButton

- (id)initWithFrame:(CGRect)frame
{
    if((self = [super initWithFrame:frame])){
        [self setupView];
    }

    return self;
}

- (void)awakeFromNib {
    [self setupView];
}

# pragma mark - main

- (void)setupView
{
    self.layer.cornerRadius = 10;
    self.layer.borderWidth = 1.0;
    self.layer.borderColor = [UIColor colorWithRed:167.0/255.0 green:140.0/255.0 blue:98.0/255.0 alpha:0.25].CGColor;
    self.layer.shadowColor = [UIColor blackColor].CGColor;
    self.layer.shadowRadius = 1;
    [self clearHighlightView];

    CAGradientLayer *gradient = [CAGradientLayer layer];
    gradient.frame = self.layer.bounds;
    gradient.cornerRadius = 10;
    gradient.colors = [NSArray arrayWithObjects:
                         (id)[UIColor colorWithWhite:1.0f alpha:1.0f].CGColor,
                         (id)[UIColor colorWithWhite:1.0f alpha:0.0f].CGColor,
                         (id)[UIColor colorWithWhite:0.0f alpha:0.0f].CGColor,
                         (id)[UIColor colorWithWhite:0.0f alpha:0.4f].CGColor,
                         nil];
    float height = gradient.frame.size.height;
    gradient.locations = [NSArray arrayWithObjects:
                            [NSNumber numberWithFloat:0.0f],
                            [NSNumber numberWithFloat:0.2*30/height],
                            [NSNumber numberWithFloat:1.0-0.1*30/height],
                            [NSNumber numberWithFloat:1.0f],
                            nil];
    [self.layer addSublayer:gradient];}

- (void)highlightView 
{
    self.layer.shadowOffset = CGSizeMake(1.0f, 1.0f);
    self.layer.shadowOpacity = 0.25;
}

- (void)clearHighlightView {
    self.layer.shadowOffset = CGSizeMake(2.0f, 2.0f);
    self.layer.shadowOpacity = 0.5;
}

- (void)setHighlighted:(BOOL)highlighted
{
    if (highlighted) {
        [self highlightView];
    } else {
        [self clearHighlightView];
    }
    [super setHighlighted:highlighted];
}


@end

你好Ischult2 - 你能否包含头文件或者指定这个实现的超类是什么? - Rayfleck
这是一个UIButton的子类(从代码和主要问题来看)。我使用了这段代码,它完美地工作了。不过,我进行了一些修改,使按钮看起来更好。这应该被标记为答案。 - pnizzle
你的梯度位置很有帮助。你这样做数学运算的原因是什么?难道你不能直接做类似于10.9或10.95甚至只是明确地写出0.9或0.95吗?也许我漏掉了什么,如果你能解释一下为什么梯度位置的数学运算是这样的,那就太好了。谢谢。 - Pavan
这个方法很好用,但是渐变会出现在按钮文本上方。将图层插入到索引0处即可解决问题:[self.layer insertSublayer:gradient atIndex:0]; - KevinS
@Pavan 乍一看,我会说这可能是为了在调整大小时使渐变看起来不像在背景中移动。 - Samie Bencherif
1
我之前不知道的是:--- gradient.cornerRadius = 10; - Amber K

9

你可以重写+layerClass方法来返回CAGradientLayer类,而不是插入渐变层。这样按钮的图层就是该类的图层,你可以轻松设置它的颜色等属性。

+ (Class)layerClass {
    return [CAGradientLayer class];
}

4

我遇到了类似的问题,但是我的背景不是渐变,而是一张图片。最终我通过以下方式解决了这个问题:

+ (void) styleButton:(UIButton*)button
{
CALayer *shadowLayer = [CALayer new];
shadowLayer.frame = button.frame;

shadowLayer.cornerRadius = 5;

shadowLayer.backgroundColor = [UIColor whiteColor].CGColor;
shadowLayer.opacity = 0.5;
shadowLayer.shadowColor = [UIColor blackColor].CGColor;
shadowLayer.shadowOpacity = 0.6;
shadowLayer.shadowOffset = CGSizeMake(1,1);
shadowLayer.shadowRadius = 3;

button.layer.cornerRadius = 5;
button.layer.masksToBounds = YES;

UIView* parent = button.superview;
[parent.layer insertSublayer:shadowLayer below:button.layer];
}

真正有趣的是,如果您将clearColor作为shadowLayer.backgroundColor,它就不会被绘制。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接