《macOS开发》自定义控件之NSButton

控件功能简介:
1. 可设置(默认/选中)背景(图片/颜色)
2. 可设置(默认/选中)文字(内容/颜色/字体样式)
4. 可设置文字下划线样式
5. 可设置文字对齐格式
6. 可设置任意圆角
7. 当鼠标移动到控件位置时,鼠标变为"小手"
8. 当鼠标移动出控件,鼠标变为"箭头"
9. 基本的点击功能

#import <Cocoa/Cocoa.h>

typedef enum {
    
    LLRectCornerTopLeft     = 1 << 0,
    LLRectCornerTopRight    = 1 << 1,
    LLRectCornerBottomLeft  = 1 << 2,
    LLRectCornerBottomRight = 1 << 3,
    LLRectCornerAllCorners  = ~0UL
} LLRectCorner;

typedef enum {
    
    LLTextAlignmentLeft  = 0, //左对齐
    LLTextAlignmentCenter,    //居中
    LLTextAlignmentRight      //右对齐
    
}LLTextAlignment;

typedef enum {
    
    LLTextUnderLineStyleNone  = 0,     //无下划线
    LLTextUnderLineStyleSingle,        //单下划线
    LLTextUnderLineStyleDouble,        //双下划线
    LLTextUnderLineStyleDeleteSingle,  //单删除线
    LLTextUnderLineStyleDeleteDouble   //双删除线
    
}LLTextUnderLineStyle;

@interface LLCustomBtn : NSView

@property (nullable, weak) id target;
@property (nullable) SEL action;

///当鼠标移动到控件时,是否显示"小手"
@property (nonatomic, assign) BOOL isHandCursor;

///圆角
@property (nonatomic, assign) CGFloat radius;
@property (nonatomic, assign) LLRectCorner rectCorners;

///按钮文字
@property (nonatomic, nullable, strong) NSString *defaultTitle;
@property (nonatomic, nullable, strong) NSString *selectedTitle;

///按钮文字对齐方式
@property (nonatomic, assign) LLTextAlignment textAlignment;

///按钮文字下划线样式
@property (nonatomic, assign) LLTextUnderLineStyle textUnderLineStyle;

///按钮文字颜色
@property (nonatomic, nullable, strong) NSColor  *defaultTitleColor;
@property (nonatomic, nullable, strong) NSColor  *selectedTitleColor;

///按钮字体
@property (nonatomic, nullable, strong) NSFont   *defaultFont;
@property (nonatomic, nullable, strong) NSFont   *selectedFont;

///当背景图片存在时,背景色无效
@property (nonatomic, nullable, strong) NSImage  *defaultBackgroundImage;
@property (nonatomic, nullable, strong) NSImage  *selectedBackgroundImage;

///当背景图片不存在时,显示背景色
@property (nonatomic, nullable, strong) NSColor  *defaultBackgroundColor;
@property (nonatomic, nullable, strong) NSColor  *selectedBackgroundColor;

@end

#import "LLCustomBtn.h"
#import <objc/message.h>

#define LLMsgSend(...)       ((void (*)(void *, SEL, id))objc_msgSend)(__VA_ARGS__)
#define LLMsgTarget(target)  (__bridge void *)(target)
@interface LLCustomBtn () {
    NSTrackingArea *_trackingArea;
}

@property (nonatomic,assign) BOOL mouseDown;

@end

@implementation LLCustomBtn

- (void)setMouseDown:(BOOL)mouseDown {
    if (_mouseDown == mouseDown) return;
    
    _mouseDown = mouseDown;
    [self setNeedsDisplay];
}

///圆角
- (void)setRectCorners:(LLRectCorner)rectCorners {
    if (_rectCorners == rectCorners) return;
    
    _rectCorners = rectCorners;
    [self setNeedsDisplay];
}

///半径
- (void)setRadius:(CGFloat)radius {
    if (_radius == radius) return;
    
    _radius = radius;
    [self setNeedsDisplay];
}

///按钮文字
- (void)setDefaultTitle:(NSString *)defaultTitle {
    if ([_defaultTitle isEqualToString:defaultTitle]) return;
    _defaultTitle = defaultTitle;
    [self setNeedsDisplay];
}

- (void)setSelectedTitle:(NSString *)selectedTitle {
    if ([_selectedTitle isEqualToString:selectedTitle]) return;
    _selectedTitle = selectedTitle;
    [self setNeedsDisplay];
}

///按钮文字对齐方式
- (void)setTextAlignment:(LLTextAlignment)textAlignment {
    if (_textAlignment == textAlignment) return;
    _textAlignment = textAlignment;
    [self setNeedsDisplay];
}

///按钮文字下划线样式
- (void)setTextUnderLineStyle:(LLTextUnderLineStyle)textUnderLineStyle {
    if (_textUnderLineStyle == textUnderLineStyle) return;
    _textUnderLineStyle = textUnderLineStyle;
    [self setNeedsDisplay];
}

///按钮文字颜色
- (void)setDefaultTitleColor:(NSColor *)defaultTitleColor {
    if (_defaultTitleColor == defaultTitleColor) return;
    _defaultTitleColor = defaultTitleColor;
    [self setNeedsDisplay];
}

- (void)setSelectedTitleColor:(NSColor *)selectedTitleColor {
    if (_selectedTitleColor == selectedTitleColor) return;
    _selectedTitleColor = selectedTitleColor;
    [self setNeedsDisplay];
}

///按钮字体
- (void)setDefaultFont:(NSFont *)defaultFont {
    if (_defaultFont == defaultFont) return;
    _defaultFont = defaultFont;
    [self setNeedsDisplay];
}

- (void)setSelectedFont:(NSFont *)selectedFont {
    if (_selectedFont == selectedFont) return;
    _selectedFont = selectedFont;
    [self setNeedsDisplay];
}

///当背景图片存在时,背景色无效
- (void)setDefaultBackgroundImage:(NSImage *)defaultBackgroundImage {
    if (_defaultBackgroundImage == defaultBackgroundImage) return;
    _defaultBackgroundImage = defaultBackgroundImage;
    [self setNeedsDisplay];
}

- (void)setSelectedBackgroundImage:(NSImage *)selectedBackgroundImage {
    if (_selectedBackgroundImage == selectedBackgroundImage) return;
    _selectedBackgroundImage = selectedBackgroundImage;
    [self setNeedsDisplay];
}

///当背景图片不存在时,显示背景色
- (void)setDefaultBackgroundColor:(NSColor *)defaultBackgroundColor {
    if (_defaultBackgroundColor == defaultBackgroundColor) return;
    _defaultBackgroundColor = defaultBackgroundColor;
    [self setNeedsDisplay];
}

- (void)setSelectedBackgroundColor:(NSColor *)selectedBackgroundColor {
    if (_selectedBackgroundColor == selectedBackgroundColor) return;
    _selectedBackgroundColor = selectedBackgroundColor;
    [self setNeedsDisplay];
}

- (void)setNeedsDisplay {
    if (self.superview) {
        [self setNeedsDisplay:YES];
    }
}

-(void)updateTrackingAreas {
    if (_trackingArea == nil) {
        _trackingArea = [[NSTrackingArea alloc] initWithRect:self.bounds
                                                     options:NSTrackingMouseEnteredAndExited|NSTrackingActiveInKeyWindow
                                                       owner:self
                                                    userInfo:nil];
        [self addTrackingArea:_trackingArea];
    }
}

-(void)mouseEntered:(NSEvent *)theEvent{
    if (_isHandCursor == NO) return;
    [[NSCursor pointingHandCursor] set];
}

-(void)mouseExited:(NSEvent *)theEvent{
    if (_isHandCursor == NO) return;
    [[NSCursor arrowCursor] set];
}

- (void)mouseDown:(NSEvent *)event {
    NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
    if (CGRectContainsPoint(self.bounds, point)) {
        self.mouseDown = YES;
    }
}

- (void)mouseUp:(NSEvent *)event {
    if (self.mouseDown) {
        self.mouseDown = NO;
        [self setNeedsDisplay:YES];
        
        NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
        if (CGRectContainsPoint(self.bounds, point)) {
            
            if (self.target && self.action && [self.target respondsToSelector:self.action]) {
                LLMsgSend(LLMsgTarget(self.target), self.action, self);
            }
        }
    }
}

- (void)drawRect:(NSRect)dirtyRect {
    
    NSString *title      = nil;
    NSFont   *font       = nil;
    NSColor  *titleColor = nil;
    NSColor  *backgroundColor = nil;
    NSImage  *backgroundImage = nil;
    
    if (self.mouseDown) {
        title = self.selectedTitle;
        font  = self.selectedFont;
        titleColor = self.selectedTitleColor;
        backgroundColor = self.selectedBackgroundColor;
        backgroundImage = self.selectedBackgroundImage;
        
        if (title == nil) {
            title = self.defaultTitle;
        }
        if (font == nil) {
            font  = self.defaultFont;
        }
        if (titleColor == nil) {
            titleColor = self.defaultTitleColor;
        }
        if (backgroundColor == nil) {
            backgroundColor = self.defaultBackgroundColor;
        }
        if (backgroundImage == nil) {
            backgroundImage = self.defaultBackgroundImage;
        }
    }
    else {
        title = self.defaultTitle;
        font  = self.defaultFont;
        titleColor = self.defaultTitleColor;
        backgroundColor = self.defaultBackgroundColor;
        backgroundImage = self.defaultBackgroundImage;
    }
    
    if (title == nil) {
        title = @"按钮";
    }
    if (font == nil) {
        font = [NSFont systemFontOfSize:17];
    }
    if (titleColor == nil) {
        titleColor = [NSColor blackColor];
    }
    if (backgroundImage) {
        backgroundColor = [NSColor colorWithPatternImage:backgroundImage];
    }
    else {
        if (backgroundColor == nil) {
            backgroundColor = [NSColor clearColor];
        }
    }
    
    if (_rectCorners) {
        NSBezierPath *bezierPath;
        if (_rectCorners == LLRectCornerAllCorners) {
            bezierPath = [NSBezierPath bezierPathWithRoundedRect:dirtyRect xRadius:_radius yRadius:_radius];
        }
        else {
            bezierPath = [NSBezierPath bezierPath];
            
            CGFloat topRightRadius = 0.0, topLeftRadius = 0.0, bottomLeftRadius = 0.0, bottomRightRadius = 0.0;
            
            if (_rectCorners & LLRectCornerTopRight) {
                topRightRadius = _radius;
            }
            if (_rectCorners & LLRectCornerTopLeft) {
                topLeftRadius = _radius;
            }
            if (_rectCorners & LLRectCornerBottomLeft) {
                bottomLeftRadius = _radius;
            }
            if (_rectCorners & LLRectCornerBottomRight) {
                bottomRightRadius = _radius;
            }
            
            //右上
            CGPoint topRightPoint = CGPointMake(dirtyRect.origin.x+dirtyRect.size.width, dirtyRect.origin.y+dirtyRect.size.height);
            topRightPoint.x -= topRightRadius;
            topRightPoint.y -= topRightRadius;
            [bezierPath appendBezierPathWithArcWithCenter:topRightPoint radius:topRightRadius startAngle:0 endAngle:90];
            
            //左上
            CGPoint topLeftPoint = CGPointMake(dirtyRect.origin.x, dirtyRect.origin.y+dirtyRect.size.height);
            topLeftPoint.x += topLeftRadius;
            topLeftPoint.y -= topLeftRadius;
            [bezierPath appendBezierPathWithArcWithCenter:topLeftPoint radius:topLeftRadius startAngle:90 endAngle:180];
            
            //左下
            CGPoint bottomLeftPoint = dirtyRect.origin;
            bottomLeftPoint.x += bottomLeftRadius;
            bottomLeftPoint.y += bottomLeftRadius;
            [bezierPath appendBezierPathWithArcWithCenter:bottomLeftPoint radius:bottomLeftRadius startAngle:180 endAngle:270];
            
            //右下
            CGPoint bottomRightPoint = CGPointMake(dirtyRect.origin.x+dirtyRect.size.width, dirtyRect.origin.y);
            bottomRightPoint.x -= bottomRightRadius;
            bottomRightPoint.y += bottomRightRadius;
            [bezierPath appendBezierPathWithArcWithCenter:bottomRightPoint radius:bottomRightRadius startAngle:270 endAngle:360];
        }
        [backgroundColor setFill];
        [bezierPath fill];
    }
    else {
        [backgroundColor setFill];
        NSRectFill(dirtyRect);
    }
    
    if (title) {
        
        //绘制文字
        NSMutableAttributedString *attTitle = [[NSMutableAttributedString alloc] initWithString:title];
        
        NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc]init];
        paragraphStyle.lineSpacing = 1;
        paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping;
        NSDictionary *attributes = @{NSFontAttributeName:font,
                                     NSParagraphStyleAttributeName:paragraphStyle,
                                     NSForegroundColorAttributeName:titleColor};
        
        [attTitle addAttributes:attributes range:NSMakeRange(0, attTitle.length)];
        
        if (self.textUnderLineStyle == LLTextUnderLineStyleSingle) {
            NSUnderlineStyle style = NSUnderlineStyleSingle;
            [attTitle addAttributes:@{NSUnderlineStyleAttributeName:@(style)} range:NSMakeRange(0, attTitle.length)];
            [attTitle addAttributes:@{NSUnderlineColorAttributeName:titleColor} range:NSMakeRange(0, attTitle.length)];
        }
        else if (self.textUnderLineStyle == LLTextUnderLineStyleDouble) {
            NSUnderlineStyle style = NSUnderlineStyleDouble;
            [attTitle addAttributes:@{NSUnderlineStyleAttributeName:@(style)} range:NSMakeRange(0, attTitle.length)];
            [attTitle addAttributes:@{NSUnderlineColorAttributeName:titleColor} range:NSMakeRange(0, attTitle.length)];
        }
        else if (self.textUnderLineStyle == LLTextUnderLineStyleDeleteSingle) {
            [attTitle addAttributes:@{NSStrikethroughStyleAttributeName:@(NSUnderlinePatternSolid|NSUnderlineStyleSingle),
                                      NSStrikethroughColorAttributeName:titleColor}
                          range:NSMakeRange(0, attTitle.length)];
        }
        else if (self.textUnderLineStyle == LLTextUnderLineStyleDeleteDouble) {
            [attTitle addAttributes:@{NSStrikethroughStyleAttributeName:@(NSUnderlinePatternSolid|NSUnderlineStyleDouble),
                                      NSStrikethroughColorAttributeName:titleColor}
                              range:NSMakeRange(0, attTitle.length)];
        }
        
        CGSize titleSize = [attTitle.string boundingRectWithSize:CGSizeMake(self.bounds.size.width, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil].size;
        
        CGRect titleRect;
        if (self.textAlignment == LLTextAlignmentLeft) {
            titleRect = CGRectMake(0,
                                   (self.bounds.size.height-titleSize.height)/2.0,
                                   titleSize.width,
                                   titleSize.height);
        }
        else if (self.textAlignment == LLTextAlignmentCenter) {
            titleRect = CGRectMake((self.bounds.size.width-titleSize.width)/2.0,
                                   (self.bounds.size.height-titleSize.height)/2.0,
                                   titleSize.width,
                                   titleSize.height);
        }
        else {
            titleRect = CGRectMake((self.bounds.size.width-titleSize.width),
                                   (self.bounds.size.height-titleSize.height)/2.0,
                                   titleSize.width,
                                   titleSize.height);
        }
        [attTitle drawInRect:titleRect];
    }
}

- (void)removeFromSuperview {
    if (_trackingArea) {
        [self removeTrackingArea:_trackingArea];
    }
    [super removeFromSuperview];
}

@end

//使用方法
LLCustomBtn *btn = [[LLCustomBtn alloc] initWithFrame:CGRectMake(200, 200, 100, 20)];
btn.isHandCursor = YES;
btn.defaultTitle = @"未选中";
btn.selectedTitle = @"已选中";
btn.defaultTitleColor = [NSColor whiteColor];
btn.selectedTitleColor = [NSColor blackColor];
btn.defaultFont = [NSFont systemFontOfSize:10];
btn.selectedFont = [NSFont systemFontOfSize:10];
btn.defaultBackgroundColor = [NSColor greenColor];
btn.selectedBackgroundColor = [NSColor blueColor];
btn.defaultBackgroundImage = [NSImage imageNamed:@""];
btn.selectedBackgroundImage = [NSImage imageNamed:@""];
btn.rectCorners = LLRectCornerTopLeft|LLRectCornerBottomLeft;
btn.radius = 15;
btn.textAlignment = LLTextAlignmentLeft;
btn.textUnderLineStyle = LLTextUnderLineStyleDeleteDouble;
[btn setTarget:self];
[btn setAction:@selector(btnCilck:)];
[self.view addSubview:btn];

我们是伟大的程序员,我们天生爱分享!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351

推荐阅读更多精彩内容