NSButton并沒(méi)有UIButton可以設(shè)置state的接口辜羊,雖然系統(tǒng)提供了很多button的樣式,但是自定義程度不夠高词顾,比如hover或highlighted的效果八秃。沒(méi)關(guān)系,既然沒(méi)有肉盹,那就自定義好了昔驱。
其實(shí),自定義NSButton非常簡(jiǎn)單上忍,基本思路就是要在響應(yīng)不同的鼠標(biāo)事件時(shí)讓button呈現(xiàn)不同的狀態(tài)(通過(guò)字體或背景顏色區(qū)分骤肛,也可以是邊框顏色或?qū)挾龋?/p>
先上效果:左邊的button是通過(guò)各種顏色的變化來(lái)表示hover、highlighted和selected狀態(tài)的窍蓝,右邊的button是通過(guò)圖片切換來(lái)表示腋颠,原理相同。
為了響應(yīng)不同鼠標(biāo)事件吓笙,可以定義四種狀態(tài)淑玫,分別對(duì)應(yīng)正常、鼠標(biāo)移入、鼠標(biāo)按下絮蒿、鼠標(biāo)釋放尊搬。
typedef NS_ENUM(NSUInteger, SWSTAnswerButtonState) {
SWSTAnswerButtonNormalState = 0,
SWSTAnswerButtonHoverState = 1,
SWSTAnswerButtonHighlightState = 2,
SWSTAnswerButtonSelectedState = 3
};
NSButton繼承了NSResponder的鍵鼠方法,我們要實(shí)現(xiàn)這四種狀態(tài)需要重寫(xiě)mouseEntered:土涝、mouseExited:毁嗦、mouseDown:以及mouseUp:四個(gè)方法。
- (void)mouseEntered:(NSEvent *)theEvent {
self.hover = YES;
if (!self.selected) {
self.buttonState = SWSTAnswerButtonHoverState;
}
}
- (void)mouseExited:(NSEvent *)theEvent {
self.hover = NO;
if (!self.selected) {
[self setButtonState:SWSTAnswerButtonNormalState];
}
}
- (void)mouseDown:(NSEvent *)event {
self.mouseUp = NO;
if (self.enabled && !self.selected) {
self.buttonState = SWSTAnswerButtonHighlightState;
}
}
- (void)mouseUp:(NSEvent *)event {
self.mouseUp = YES;
if (self.enabled) {
if (self.canSelected && self.hover) {
self.selected = !self.selected;
self.buttonState = self.selected ? SWSTAnswerButtonSelectedState : SWSTAnswerButtonNormalState;
} else {
if (!self.selected) {
self.buttonState = SWSTAnswerButtonNormalState;
}
}
}
}
通過(guò)mouseEntered:和mouseExited:來(lái)標(biāo)記hover狀態(tài)回铛,而mouseDown:來(lái)標(biāo)記highlighted狀態(tài),最后mouseUp:標(biāo)記selected狀態(tài)克锣。
在buttonState改變的時(shí)候更新UI:
- (void)updateButtonApperaceWithState:(SWSTAnswerButtonState)state {
CGFloat cornerRadius = 0.f;
CGFloat borderWidth = 0.f;
NSColor *borderColor = nil;
NSColor *themeColor = nil;
NSColor *backgroundColor = nil;
switch (state) {
case SWSTAnswerButtonNormalState: {
cornerRadius = self.cornerNormalRadius;
borderWidth = self.borderNormalWidth;
borderColor = self.borderNormalColor;
themeColor = self.normalColor;
backgroundColor = self.backgroundNormalColor;
if (self.normalImage != nil) {
self.defaultImage = self.normalImage;
}
break;
}
case SWSTAnswerButtonHoverState: {
cornerRadius = self.cornerHoverRadius;
borderWidth = self.borderHoverWidth;
borderColor = self.borderHoverColor;
themeColor = self.hoverColor;
backgroundColor = self.backgroundHoverColor;
if (self.hoverImage != nil) {
self.defaultImage = self.hoverImage;
}
}
break;
case SWSTAnswerButtonHighlightState: {
cornerRadius = self.cornerHighlightRadius;
borderWidth = self.borderHighlightWidth;
borderColor = self.borderHighlightColor;
themeColor = self.highlightColor;
backgroundColor = self.backgroundHighlightColor;
if (self.highlightImage != nil) {
self.defaultImage = self.highlightImage;
}
}
break;
case SWSTAnswerButtonSelectedState: {
cornerRadius = self.cornerSelectedRadius;
borderWidth = self.borderSelectedWidth;
borderColor = self.borderSelectedColor;
themeColor = self.selectedColor;
backgroundColor = self.backgroundSelectedColor;
if (self.selectedImage != nil) {
self.defaultImage = self.selectedImage;
}
}
break;
}
if (self.defaultImage != nil) {
self.image = self.defaultImage;
}
[self setFontColor:themeColor];
if (self.hasBorder) {
self.layer.cornerRadius = cornerRadius;
self.layer.borderWidth = borderWidth;
self.layer.borderColor = borderColor.CGColor;
} else {
self.layer.cornerRadius = 0.f;
self.layer.borderWidth = 0.f;
self.layer.borderColor = [NSColor clearColor].CGColor;
}
self.layer.backgroundColor = backgroundColor.CGColor;
}
這樣就可以簡(jiǎn)單的實(shí)現(xiàn)你想要的幾乎任何NSButton支持的樣式茵肃,只要擴(kuò)展對(duì)應(yīng)的屬性就好了。
不過(guò)袭祟,在實(shí)現(xiàn)樣式的時(shí)候验残,發(fā)現(xiàn)了一個(gè)嚴(yán)重的問(wèn)題。在重寫(xiě)mouseDown:和mouseUp:方法后巾乳,NSButton的action不執(zhí)行了您没。這里調(diào)用super方法也不管用,看來(lái)系統(tǒng)會(huì)在這種情況下忽略action方法胆绊。
既然系統(tǒng)不執(zhí)行氨鹏,我在mouseUp:方法里手動(dòng)執(zhí)行一下:
- (void)mouseUp:(NSEvent *)event {
self.mouseUp = YES;
if (self.enabled) {
if (self.canSelected && self.hover) {
self.selected = !self.selected;
self.buttonState = self.selected ? SWSTAnswerButtonSelectedState : SWSTAnswerButtonNormalState;
} else {
if (!self.selected) {
self.buttonState = SWSTAnswerButtonNormalState;
}
}
if (self.hover && self.enabled) {
NSString *selString = NSStringFromSelector(self.action);
if ([selString hasSuffix:@":"]) {
[self.target performSelector:self.action withObject:self afterDelay:0.f];
} else {
[self.target performSelector:self.action withObject:nil afterDelay:0.f];
}
}
}
}
修改后的mouseUp:方法在hover(確保鼠標(biāo)在button的frame內(nèi))及enabled的狀態(tài)下會(huì)通過(guò)performSelector:withObject:afterDelay:方法讓button的target執(zhí)行button的action⊙棺矗看起來(lái)有點(diǎn)奇怪仆抵,但確實(shí)可以這么干。
NSButton的初始化可以在代碼里給所有屬性賦值种冬,但我相信你會(huì)覺(jué)得這些代碼看著很惡心镣丑。其實(shí)完全不用放在代碼里,這些初始化可以全丟給storyboard娱两。
在storyboard里選中NSButton莺匠,選擇右邊第三個(gè)標(biāo)簽(Identity),看到有一行是User Defined Runtime Attributes十兢。這里可以在運(yùn)行時(shí)初始化一些自定義屬性趣竣,支持基本類(lèi)型外,居然還支持NSPoint纪挎、NSSize期贫、NSRect、NScolor异袄,甚至是NSImage(傳圖片名就好了)通砍。
最后,在updateTrackingAreas方法中,添加了自己的NSTrackingArea封孙,以保證窗口不在最前時(shí)移動(dòng)鼠標(biāo)到button也可以實(shí)現(xiàn)hover效果迹冤。
- (void)updateTrackingAreas {
[super updateTrackingAreas];
if (self.trackingArea != nil) {
[self removeTrackingArea:self.trackingArea];
self.trackingArea = nil;
}
NSTrackingAreaOptions options = NSTrackingInVisibleRect|NSTrackingMouseEnteredAndExited|NSTrackingActiveAlways;
self.trackingArea = [[NSTrackingArea alloc] initWithRect:CGRectZero options:options owner:self userInfo:nil];
[self addTrackingArea:self.trackingArea];
}
完整代碼見(jiàn)gitlab:https://gitlab.com/Maulyn/CustomButtonDemo
歡迎隨時(shí)交流~