基本控制元件(一)

大神蝦發表於2018-09-29

iOS 的基本控制元件

作為iOS的基本控制元件,我們首要能想到的就是UIButton,UILabel,UITextField等等。這些控制元件基本都屬於UIKit庫裡面的。官方對於UIKit是這麼解釋的

UIKit框架為您的iOS或tvOS應用程式提供所需的基礎結構。 它提供了用於實現介面的視窗和檢視架構,用於嚮應用程式提供Multitouch和其他型別輸入的事件處理基礎架構,以及管理使用者,系統和應用程式之間互動所需的主要執行迴圈。 該框架提供的其他功能包括動畫支援,文件支援,繪圖和列印支援,有關當前裝置的資訊,文字管理和顯示,搜尋支援,可訪問性支援,應用程式擴充套件支援和資源管理。除非另有說明,否則僅從應用程式的主執行緒或主排程佇列中使用UIKit類。 此限制特別適用於從UIResponder派生的類或涉及以任何方式操縱應用程式的使用者介面的類。

那麼UIButton作為其中的一個類,我們當時做的最基礎的初始化就是用

 buttonWithType: 這個方法
複製程式碼

為什麼不用init呢,是因為我們建立按鈕的時候往往需要用到按鈕不同的型別,但是UIButton的規則就是建立後不能改變其型別,只能建立時更改。所以我們就常用上面的方法了。按鈕建立後,我們知道他有幾種狀態,預設,選擇,高亮什麼的,這裡就不一一敘述,有一些小點很有意思,但平時很少用到

@property(nonatomic) BOOL adjustsImageWhenHighlighted;
複製程式碼

這個方法就是控制高亮狀態下,按鈕上面的圖片是否變亮的,但是我們由於經常自定義控制元件,所以就不會經常出現按鈕上面的圖片需要高亮的情況,同時我們用titleEdgeInset和ImageEdgeInset來調整文字和圖片距離邊界的距離.

那麼工作中有兩種需求比較常見,一種按鈕不能重複點選,一種是獲取觸控事件,還有一種是放大按鈕的點選作用域。第一種是因為很多點選按鈕發動網路請求,如果重複點選可能導致多次傳送請求,同時如果有蒙版的話也會彈多次。第二種是我們有時候這個按鈕有點選事件,但是我們不希望獲取到這個按鈕的點選事件,可能希望獲取到按鈕的父View的點選事件,那麼這種就要用hitTest,還有放大按鈕的點選作用域,這種確切的說其實是iOS的響應鏈問題,我們下面分別討論。

第二種響應鏈問題,單單從UI的角度來說,響應鏈就是告訴你你觸控的螢幕上面的UI是如何響應你的。比方說A上面有個B·,你摸了B,那麼系統就會先問A有人摸你嗎,A說摸了,系統再問,你上面還有人嗎,A說有B。於是系統再問B,有人摸你嗎,直到問到最上層為止,下面是我從網上找的一張圖和程式碼,我們可以看清楚原理

基本控制元件(一)

//in every view .m overide those methods
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    NSLog(@"進入A_View---hitTest withEvent ---");
    UIView * view = [super hitTest:point withEvent:event];
    NSLog(@"離開A_View--- hitTest withEvent ---hitTestView:%@",view);
    return view;
}

- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event {
    NSLog(@"A_view--- pointInside withEvent ---");
    BOOL isInside = [super pointInside:point withEvent:event];
    NSLog(@"A_view--- pointInside withEvent --- isInside:%d",isInside);
    return isInside;
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    NSLog(@"A_touchesBegan");
}

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event {
    NSLog(@"A_touchesMoved");
}

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event {
    NSLog(@"A_touchesEnded");
}
複製程式碼

基本控制元件(一)
從這兩張圖中我們可以看到,touchBegan是在hitTest找到響應後才響應。 那麼我在工作中用到的hitTest有那麼幾次,最經常的就是放大按鈕作用域,過載UIButton的- (BOOL)pointInside:withEvent:方法,讓Point即使落在Button的Frame外圍也返回YES。還有就是子檢視超過父檢視無法獲取點選事件一樣用這種方式來解決

第一種不能連續點選我目前的做法通常都是將按鈕的可點選置為NO,然後等到網路請求完畢後才將按鈕置為YES,不過這麼做的缺點也是顯而易見的,就是一旦網路速度慢,返回慢的情況,這個按鈕就無法點選了,要等很久很久,使用者體驗不好。網上對於這種解決方式通常給這麼以下幾點:

1.按鈕點選後取消之前的操作,說實話我感覺這種方法就是扯淡,不知道為什麼那麼多人推薦 官方文件上面cancelPreviousPerformRequestsWithTarget的意思是取消執行先前使用performSelector註冊的請求:withObject:afterDelay:。

- (void)buttonClicked:(id)sender{//點選按鈕後先取消之前的操作,再進行需要進行的操作
[[selfclass]cancelPreviousPerformRequestsWithTarget:selfselector:@selector(buttonClicked:)object:sender];
[selfperformSelector:@selector(buttonClicked: )withObject:senderafterDelay:0.2f];
}
複製程式碼

我的感覺它根本就不取消啥

2.使用Runtime讓所有按鈕在0.幾秒後都不能繼續點選。嗯,這種方法,怎麼說呢,我也用過,一方面在非常軸的測試面前,這種方式仍然會有機率造成重複點選,另一種方面對於這個需求來說使用runtime這種方式顯得有點大才小用了,同時這會造成程式碼汙染,以後出現的bug問題你都不好判斷尋找。 所以這種被我pass掉了,不過大神除外哈。

3.這個也就是我目前使用的看起來最土的一種方法,不過有效簡單穩定,有更好的方法也請教教我。

同時這段時間工作也接觸了ReactNative的東西,RN裡button還是叫button,有一些props對於屬性而已,上面那段我們提到了runtime,那麼下一節我們就說一下runtime。

相關文章