UIButton с градиентом, закругленные углы, граница и тень
На сайте есть несколько похожих вопросов, но я ищу что-то конкретное и немного другое.
Я следил за направлением, приведенным здесь: 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 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 при добавлении dropshadow. Я добавляю метод setCornerRadius: чтобы разрешить как верхний слой, так и "обертку".
Итак, вместо того, чтобы делать что-то вроде [[myCustomButton layer] setCornerRadius:3.0f];
, я просто говорю [myCustomButton setCornerRadius:3.0f];
Как вы можете видеть, это не так просто, как я надеюсь.
Есть ли лучший способ?
Ответы
Ответ 1
Так я нашел кнопку с закругленным углом, градиентом и тень. Этот пример имеет один конкретный градиент, но, очевидно, может быть заменен другими градиентами.
@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
Ответ 2
Вместо того, чтобы вставлять слой градиента, вы также можете переопределить метод +layerClass
, чтобы вернуть класс CAGradientLayer
. Уровень кнопки меньше, чем у этого класса, и вы можете легко установить его цвета и т.д.
+ (Class)layerClass {
return [CAGradientLayer class];
}
Ответ 3
У меня была аналогичная проблема, однако вместо градиента у меня было изображение для фона. Я решил это в конце, используя:
+ (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 просто не рисовал.
Ответ 4
Благодаря @lschult2. Вот быстрая версия.
import UIKit
class GradientButton: UIButton {
var gradientLayer : CAGradientLayer!
override func layoutSubviews() {
super.layoutSubviews()
setup()
}
override init(frame: CGRect) {
super.init(frame: frame)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
}
func setup() {
layer.shadowOffset = CGSize(width: 2, height: 2)
layer.shadowOpacity = 0.3
layer.shadowRadius = 3.0
layer.cornerRadius = 5
setGradient(colorTop: UIColor(red: 96/255.0, green: 120/255.0, blue: 234/255.0, alpha: 1.0), colorBottom: UIColor(red: 23/255.0, green: 234/255.0, blue: 217/255.0, alpha: 1.0))
}
func setGradient(colorTop:UIColor, colorBottom:UIColor) {
if gradientLayer == nil {
gradientLayer = CAGradientLayer()
gradientLayer.colors = [colorTop.cgColor, colorBottom.cgColor]
gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.5)
gradientLayer.frame = bounds
gradientLayer.name = "gradientLayer"
gradientLayer.cornerRadius = 5
layer.insertSublayer(gradientLayer, at: 0)
}
}
}