最近遇到一個(gè)關(guān)于導(dǎo)航欄返回按鈕的問題,因?yàn)橹绊?xiàng)目里面都是用的系統(tǒng)默認(rèn)的返回按鈕樣式所以沒有想過要去更改,后來有需要將返回按鈕箭頭旁邊的文字去掉,同時(shí)將該返回按鈕的點(diǎn)擊事件重新定義。一開始嘗試自定義按鈕然后設(shè)置為leftbarbuttonitem,但是這樣圖片可能跟系統(tǒng)自帶的不一樣,還有就是返回按鈕的位置跟系統(tǒng)自帶的不一樣。后來找了一些資料,發(fā)現(xiàn)將文字去掉比較簡(jiǎn)單,一般做法是控制器中添加如下代碼,然后他的下一級(jí)控制就有一個(gè)只有箭頭沒有文字返回按鈕:
self.navigationitem.backbarbuttonitem = backbtn;
也可以創(chuàng)建一個(gè)根控制器在其中使用上述代碼然后讓其他控制器繼承這個(gè)根控制器實(shí)現(xiàn)批量操作。但是如果遇到需要自定義該返回的點(diǎn)擊事件的時(shí)候,在上面方法中添加target與action是不可行的,同時(shí)這種做法也會(huì)產(chǎn)生一個(gè)問題,就是實(shí)際的返回按鈕點(diǎn)擊區(qū)域總是比按鈕看到的范圍大,一般是距離箭頭右邊30距離都可點(diǎn)擊。接下來我就帶大家一起了解這些問題產(chǎn)生的原因以及如何更好的解決這些問題。
首先我們看一下按照上面代碼去掉返回按鈕文字之后的導(dǎo)航欄視圖的結(jié)構(gòu)層次,因?yàn)閷?dǎo)航欄的視圖加載以及初始化跟viewcontroller的view不一樣,不能再veiwdidload中去觀察(viewwillappear中也不行)要在viewdidload中才可以看到完整的導(dǎo)航欄視圖結(jié)構(gòu)層次。我們可以在一個(gè)有去掉文字的返回按鈕控制器的viewdidload中打上斷點(diǎn)然后在控制臺(tái)執(zhí)行:
po [[uiwindow keywindow] recursivedescription]
會(huì)得到如下輸出:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<uiwindow: 0x8d6f970; frame = (0 0; 320 480); autoresize = w+h; gesturerecognizers = <nsarray: 0x8d5dbf0>; layer = <uiwindowlayer: 0x8d717d0>> | <uilayoutcontainerview: 0x8d7bbf0; frame = (0 0; 320 480); autoresize = w+h; gesturerecognizers = <nsarray: 0x8d78a70>; layer = <calayer: 0x8d7bcd0>> | | <uinavigationtransitionview: 0x8d813f0; frame = (0 0; 320 480); clipstobounds = yes; autoresize = w+h; layer = <calayer: 0x8d814d0>> | | | <uiviewcontrollerwrapperview: 0x8d61050; frame = (0 0; 320 480); autoresize = w+h; userinteractionenabled = no; layer = <calayer: 0x8d88f40>> | | | | <uiview: 0x8ab0dc0; frame = (0 0; 320 480); autoresize = rm+bm; layer = <calayer: 0x8ab0610>> | | | | | <_uilayoutguide: 0x8ab0e20; frame = (0 0; 0 64); hidden = yes; layer = <calayer: 0x8ab0e90>> | | | | | <_uilayoutguide: 0x8ab1080; frame = (0 480; 0 0); hidden = yes; layer = <calayer: 0x8ab10f0>> | | <uinavigationbar: 0x8d75c40; frame = (0 20; 320 44); opaque = no; autoresize = w; userinteractionenabled = no; gesturerecognizers = <nsarray: 0x8d5e750>; layer = <calayer: 0x8d70f00>> | | | <_uinavigationbarbackground: 0x8d59af0; frame = (0 -20; 320 64); opaque = no; autoresize = w; userinteractionenabled = no; layer = <calayer: 0x8d549f0>> - (null) | | | | <_uibackdropview: 0x8d7c440; frame = (0 0; 320 64); opaque = no; autoresize = w+h; userinteractionenabled = no; layer = <_uibackdropviewlayer: 0x8d7e7b0>> | | | | | <_uibackdropeffectview: 0x8d7f1c0; frame = (0 0; 320 64); clipstobounds = yes; opaque = no; autoresize = w+h; userinteractionenabled = no; animations = { filters.colormatrix.inputcolormatrix=<cabasicanimation: 0x8ba4490>; }; layer = <cabackdroplayer: 0x8d7f480>> | | | | | <uiview: 0x8d7fc80; frame = (0 0; 320 64); hidden = yes; opaque = no; autoresize = w+h; userinteractionenabled = no; layer = <calayer: 0x8d7fce0>> | | | | <uiimageview: 0x8d67cc0; frame = (0 64; 320 0.5); userinteractionenabled = no; layer = <calayer: 0x8d67d50>> - (null) | | | <uinavigationitemview: 0x8ab6400; frame = (124 8; 163 27); opaque = no; userinteractionenabled = no; layer = <calayer: 0x8ab6480>> | | | | <uilabel: 0x8ab64b0; frame = (0 3; 163 22); text = 'a story about a fish' ; clipstobounds = yes; opaque = no; userinteractionenabled = no; layer = <calayer: 0x8ab6550>> | | | <uinavigationitembuttonview: 0x8ab6c80; frame = (8 6; 41 30); opaque = no; userinteractionenabled = no; layer = <calayer: 0x8ab6d60>> | | | | <uilabel: 0x8ab6f10; frame = (-981 -995; 91 22); text = '' ; clipstobounds = yes; opaque = no; userinteractionenabled = no; layer = <calayer: 0x8ab6fb0>> | | | <_uinavigationbarbackindicatorview: 0x8d87560; frame = (8 12; 12.5 20.5); opaque = no; userinteractionenabled = no; layer = <calayer: 0x8d87650>> - back |
直接看或許不容易懂,還需要結(jié)合xcode的“debug view hierarchy”或者是reveal工具看實(shí)際視圖結(jié)構(gòu):
我們可以看到在uinavigationbar中包含有4個(gè)類,它們大致的作用是:
_uinavigationbarbackground :(uiimageview)導(dǎo)航欄背景圖(不可以直接設(shè)置圖片)
uinavigationitemview :(uiview)包含顯示導(dǎo)航欄標(biāo)題
uinavigationitembuttonview :(uiview)包含顯示導(dǎo)航欄左視圖(不可移除,更改大小,顏色,可以隱藏,決定了點(diǎn)擊區(qū)域的大小)
_uinavigationbarbackindicatorview :(uiimageview)導(dǎo)航欄返回按鈕箭頭圖片(不可以修改圖片)
_uinavigationbarbackindicatorview就是返回按鈕的箭頭也就是我們需要保留的,uinavigationitembuttonview就是我們需要研究的也是我們前面提到問題的主要原因所在。再次看看這個(gè)對(duì)象在控制臺(tái)的輸出:
1
2
|
| | | <uinavigationitembuttonview: 0x8ab6c80; frame = (8 6; 41 30); opaque = no; userinteractionenabled = no; layer = <calayer: 0x8ab6d60>> | | | | <uilabel: 0x8ab6f10; frame = (-981 -995; 91 22); text = '' ; clipstobounds = yes; opaque = no; userinteractionenabled = no; layer = <calayer: 0x8ab6fb0>> |
這個(gè)uinavigationitembuttonview應(yīng)該是系統(tǒng)在這個(gè)view的draw方法里就決定frame,修改frame就觸發(fā)needdisplay重新改變它的frame,因此這個(gè)view只會(huì)根據(jù)其上的label(也就是返回按鈕顯示的文字)的內(nèi)容變化而改變寬度其余基本不可變,我們雖然將label的內(nèi)容設(shè)置為空但是這個(gè)uinavigationitembuttonview的大小卻并沒有改變同時(shí)點(diǎn)擊區(qū)域也沒有改變。從控制臺(tái)里的還可看到這個(gè)veiw的userinteractionenabled屬性為no,如果說點(diǎn)擊的是這個(gè)view,但現(xiàn)在這個(gè)view是不可交互的,只能說明是真正響應(yīng)點(diǎn)擊事件的是這個(gè)view背后的某個(gè)控件(視圖結(jié)構(gòu)和代碼里都沒有發(fā)現(xiàn)這個(gè)控件)。因此要想解決我之前提到的問題就得利用這個(gè)uinavigationitembuttonview,我們可以在viewdidappear中取得到uinavigationitembuttonview(如果用遍歷的方式獲取會(huì)有一個(gè)延遲,因?yàn)檫@個(gè)view的位置固定為第三個(gè)所以我們就直接獲取就可以了),將其userinteractionenabled設(shè)置為yes并且在這個(gè)view上添加手勢(shì)tap事件即可:
1
2
3
4
5
6
7
8
9
10
|
uiview *nav_back = [self.navigationcontroller.navigationbar.subviews objectatindex:2]; if ([nav_back iskindofclass:nsclassfromstring(@ "uinavigationitembuttonview" )]) { nav_back.userinteractionenabled = yes; // uitapgesturerecognizer *tap = [[uitapgesturerecognizer alloc] initwithtarget:self action:@selector(backaction:)]; // [nav_back addgesturerecognizer:tap]; uibutton *backbutton = [uibutton buttonwithtype:uibuttontypecustom]; backbutton.frame = cgrectmake(0, 0, 20, 21); [backbutton addtarget:self action:@selector(custommethod) forcontrolevents:uicontroleventtouchupinside]; [nav_back addsubview:backbutton]; } |
這樣可以改變返回按鈕的點(diǎn)擊事件,此時(shí)想要解決點(diǎn)擊范圍只能在這個(gè)view上添加一個(gè)指定大小的按鈕了。最后總結(jié)出來的解決辦法就是創(chuàng)建一個(gè)viewcontroller的分類:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
|
uiviewcontroller+custombackbutton.h文件 #import <uikit/uikit.h> @interface uiviewcontroller (custombackbutton) - ( void )customnavbackbuttonmethod; @end uiviewcontroller+custombackbutton.m文件 #import "uiviewcontroller+custombackbutton.h" #import <objc/runtime.h> @implementation uiviewcontroller (custombackbutton) + ( void )load { static dispatch_once_t oncetoken; dispatch_once(&oncetoken, ^{ class class = [self class ]; sel originalselector = @selector(viewdidload); sel swizzledselector = @selector(mm_viewdidload); sel originalselector1 = @selector(viewdidappear:); sel swizzledselector1 = @selector(mm_viewdidappear); method originalmethod = class_getinstancemethod( class , originalselector); method swizzledmethod = class_getinstancemethod( class , swizzledselector); method originalmethod1 = class_getinstancemethod( class , originalselector1); method swizzledmethod1 = class_getinstancemethod( class , swizzledselector1); bool didaddmethod = class_addmethod( class , originalselector, method_getimplementation(swizzledmethod), method_gettypeencoding(swizzledmethod)); bool didaddmethod1 = class_addmethod( class , originalselector1, method_getimplementation(swizzledmethod1), method_gettypeencoding(swizzledmethod1)); if (didaddmethod) { class_replacemethod( class , swizzledselector, method_getimplementation(originalmethod), method_gettypeencoding(originalmethod)); } else { method_exchangeimplementations(originalmethod, swizzledmethod); } if (didaddmethod1) { class_replacemethod( class , swizzledselector1, method_getimplementation(originalmethod1), method_gettypeencoding(originalmethod1)); } else { method_exchangeimplementations(originalmethod1, swizzledmethod1); } }); } #pragma mark - method swizzling - ( void )mm_viewdidload { [self mm_viewdidload]; uibarbuttonitem *backbuttonitem = [[uibarbuttonitem alloc] initwithtitle:@ "" style:uibarbuttonitemstyleplain target:nil action:nil]; [self.navigationitem setbackbarbuttonitem:backbuttonitem]; } - ( void )mm_viewdidappear { [self mm_viewdidappear]; uiview *nav_back = [self.navigationcontroller.navigationbar.subviews objectatindex:2]; if ([nav_back iskindofclass:nsclassfromstring(@ "uinavigationitembuttonview" )]) { nav_back.userinteractionenabled = yes; // uitapgesturerecognizer *tap = [[uitapgesturerecognizer alloc] initwithtarget:self action:@selector(backaction:)]; // [nav_back addgesturerecognizer:tap]; uibutton *backbutton = [uibutton buttonwithtype:uibuttontypecustom]; backbutton.frame = cgrectmake(0, 0, 20, 21); [backbutton addtarget:self action:@selector(customnavbackbuttonmethod) forcontrolevents:uicontroleventtouchupinside]; [nav_back addsubview:backbutton]; } } - ( void )customnavbackbuttonmethod { [self.navigationcontroller popviewcontrolleranimated:yes]; } @end |
在項(xiàng)目里面創(chuàng)建完以后就會(huì)生效。這里所做的就是在所有的viewcontroller執(zhí)行viewdidload的時(shí)候?qū)⒎祷匕粹o的文字置空,在執(zhí)行viewdidappear的時(shí)候添加一個(gè)自定義的按鈕去響應(yīng)pop事件。如果遇到需要自定義返回事件我在.h將返回事件開放出來,只要在對(duì)應(yīng)的viewdidload中復(fù)寫customnavbackbuttonmethod方法就可以在點(diǎn)擊返回按鈕時(shí)執(zhí)行到你復(fù)寫的方法中了,好了,是不是感覺很簡(jiǎn)單呢。