iOS自訂控制項開發梳理總結_IOS

來源:互聯網
上載者:User

在日常iOS開發中,系統提供的控制項常常無法滿足業務功能,這個時候需要我們實現一些自訂控制項。自訂控制項能讓我們完全控制視圖的展示內容以及互動操作。本篇將介紹一些自訂控制項的相關概念,探討自訂控制項開發的基本過程及技巧。

UIView

在開始之前我們先介紹一個類UIVew,它在iOS APP中佔有絕對重要的地位,因為幾乎所有的控制項都是繼承自UIView類。
UIView表示螢幕上的一個矩形地區,負責渲染地區內的內容,並且響應地區內發生的觸摸事件。

在UIView的內部有一個CALayer,提供內容的繪製和顯示,包括UIView的尺寸樣式。UIView的frame實際上返回的CALayer的frame。

UIView繼承自UIResponder類,它能接收並處理從系統傳來的事件,CALayer繼承自NSObject,它無法響應事件。所以UIView與CALayer的最大區別在於:UIView能響應事件,而CALayer不能。
更詳細的資料:https://developer.apple.com/reference/uikit/uiview

兩種實現方式
在建立自訂控制項時,主要有兩種實現方式,分別是純程式碼以及xib。接下來我們用這兩種方式分別示範一下建立自訂控制項的步驟。
我們實現一個簡單的demo ,效果如下,封裝一個圓形的imageView。

使用代碼建立自訂控制項
使用代碼建立自訂控制項,首先建立一個繼承自UIView的類

實現initWithFrame:方法。在該方法中,設定自訂控制項的屬性,並建立、添加子視圖:

-(instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) {  _imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, frame.size.width, frame.size.height)];  _imageView.contentMode = UIViewContentModeScaleAspectFill;  _imageView.layer.masksToBounds = YES;  _imageView.layer.cornerRadius = frame.size.width/2;  [self addSubview:_imageView]; } return self;}

如果需要對子視圖重新布局,需要調用layoutSubViews方法:

-(void)layoutSubviews { [super layoutSubviews]; _imageView.frame = self.frame; _imageView.layer.cornerRadius = self.frame.size.width/2;}

layoutSubviews是調整子視圖布局的方法,官方文檔如下:

You should override this method only if the autoresizing and constraint-based behaviors of the subviews do not offer the behavior you want.

意思是當你需要調整subview的大小的時候,重寫layoutSubviews方法。

layoutSubviews在以下情況下會被調用:

  • init初始化不會觸發layoutSubviews
  • addSubview會觸發layoutSubviews
  • 設定view的Frame會觸發layoutSubviews,當然前提是frame的值設定前後發生了變化
  • 滾動一個UIScrollView會觸發layoutSubviews
  • 旋轉Screen會觸發父UIView上的layoutSubviews事件
  • 改變一個UIView大小的時候也會觸發父UIView上的layoutSubviews事件

這個自訂控制項提供對外介面方法,為自訂的控制項賦值

- (void)configeWithImage:(UIImage *)image { _imageView.image = image;}

最後,添加自訂控制項到頁面上

 _circleImageView = [[CircleImageView alloc] initWithFrame:CGRectMake(0, 80, 150, 150)]; [_circleImageView configeWithImage:[UIImage imageNamed:@"tree"]]; [self.view addSubview:_circleImageView];

通過xib建立自訂控制項
首先建立一個自訂控制項XibCircleImageView,繼承自UIView
建立xib檔案,與XibCircleImageView類同名
配置xib中imageView的屬性,並將XibCircleImageView 類與對應的xib檔案進行綁定
代碼如下

- (void)awakeFromNib { [super awakeFromNib]; _imageView.layer.masksToBounds = YES; _imageView.layer.cornerRadius = self.frame.size.width/2; // [self addSubview:_imageView];}- (void)configeWithImage:(UIImage *)image { _imageView.image = image;}-(void)layoutSubviews { [super layoutSubviews]; _imageView.layer.cornerRadius = self.frame.size.width/2;}

在頁面中調用方式有點不同,通過loadNibNamed方法建立xib對象

 

 //使用xib建立自訂控制項 _xibCircleImageView = [[[NSBundle mainBundle] loadNibNamed:@"XibCircleImageView" owner:nil options:nil] lastObject]; _xibCircleImageView.frame = CGRectMake(0, 500, 100, 100); [_xibCircleImageView configeWithImage:image]; [self.view addSubview:_xibCircleImageView];

當使用xib建立自訂控制項時,初始化不會調用initWithFrame:方法,只會調用initWithCoder:方法,初始化完畢後才調用awakeFromNib方法,注意要在awakeFromNib中初始化子控制項。因為initWithCoder:方法表示對象是從檔案解析來的,就會調用,而awakeFromNib方法是從xib或者storyboard載入完畢後才會調用。

小結

這兩種建立自訂控制項的方式各有優劣,純程式碼方式比較靈活,維護和擴充都比較方便,但寫起來比較麻煩。xib方式開發效率高,但不易擴充和維護,適合功能樣式比較穩定的自訂控制項。

事件傳遞機制

在自訂控制項中,可能需要動態響應事件,如按鈕太小,不易點擊,需要擴大按鈕的點擊範圍,接下來我們談談iOS的事件傳遞機制。

事件響應鏈

UIResponder類能夠響應觸摸、手勢以及遠端控制等事件。它是所有可響應事件的基類,其中包括很常見的UIView、UIViewController以及UIApplication。

UIResponder的屬性和方法如下圖,其中nextResponder表示指向一個UIResponder對象。

那麼事件響應鏈與UIResponder有什麼關係呢?應用內的視圖按一定的結構組織起來,即樹狀階層,一個視圖可以有多個子視圖,而子視圖只能有一個父視圖。當一個視圖被添加到父視圖上時。每一個視圖的nextResponder屬性就指向它的父視圖,這樣,整個應用就通過nextResponder串成了一條鏈,即響應鏈。響應鏈是一個虛擬鏈,並不是真實存在的,它藉助UIResponder的nextResponder串聯起來。如下圖

Hit-Test View

有了事件響應鏈,接下來就是尋找具體響應對象了,我們稱之為:Hit-Testing View,尋找這個View的過程稱為Hit-Test。
什麼是Hit-Test?我們可以把它理解為一個探測器,通過這個探測器,我們可以找到並判斷手指是否觸摸在某個視圖上。
Hit-Test是如何工作的?Hit-Test採用遞迴方式從視圖的根節點開始遍曆,直到找到某個點擊的視圖。

首先從UIWindow發送hitTest:withEvent:訊息開始,判斷該視圖是否能響應觸摸事件,如果不能響應返回nil,表示該視圖不能響應觸摸事件。然後再調用pointInside:withEvent:方法,該方法用於判斷觸摸事件點擊的位置是否處理該視圖範圍內,如果pointInside:withEvent:返回no,那麼hitTest:withEvent:也直接返回nil。

如果pointInside:withEvent: 方法返回yes,那麼該視圖向所有子視圖發送hitTest:withEvent:訊息,所有子視圖的調用順序是從最頂層視圖一直到最底層視圖,即從subViews的數組的末尾向前遍曆。直到有子視圖返回非Null 物件或全部遍曆完畢。若有子視圖返回非Null 物件,則hitTest:withEvent:方法返回該對象,處理結束;若所有子視圖都返回nil,則hitTest:withEvent:方法返回該視圖自身。

事件傳遞機制的應用

舉幾個例子,說明一下事件傳遞機制在自訂控制項中的應用。

一、擴大view的點擊地區。假設一個button的大小為20px 20px,太小難以點擊。我們通過重寫這個button子類的hitTest:withEvent:方法,判斷點擊處point是否在button周圍20px以內,如果是則返回自身,實現擴大點擊範圍的功能,代碼如下:

-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { if (!self.isUserInteractionEnabled || self.hidden || self.alpha<=0.01) {  return nil; } CGRect touchRect = CGRectInset(self.bounds, -20, -20); if (CGRectContainsPoint(touchRect, point)) {  for (UIView *subView in [self.subviews reverseObjectEnumerator]) {   CGPoint convertedPoint = [subView convertPoint:point toView:self];   UIView *hitTestView = [subView hitTest:convertedPoint withEvent:event];   if (hitTestView) {    return hitTestView;   }  }  return self; } return nil;}

二、穿透傳遞事件。

假設有兩個view,viewA和viewB,viewB完全覆蓋viewA,我們希望點擊viewB時能響應viewA的事件。我們重寫這個viewA的hitTest:withEvent:方法,不繼續遍曆它的子視圖,直接返回自身。代碼如下:

-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { if (!self.isUserInteractionEnabled || self.hidden || self.alpha<=0.01) {  return nil; } if ([self pointInside:point withEvent:event]) {  NSLog(@"in view A");  return self; } return nil;}

回調機制

在自訂控制項開發中,需要向它的父類回傳傳回值。比如一個存放按鈕的自訂控制項,需要在上層接收按鈕點擊事件。我們可以使用多種方式回調訊息,比如target action模式、代理、block、通知等。

Target-Action

Target-Action是一種設計模式,當事件觸發時,它讓一個對象向另一個對象發送訊息。這個模式我們接觸的比較多,如為按鈕綁定點擊事件,為view添加手勢事件等。UIControl及其子類都支援這個機制。Target-Action 在訊息的寄件者和接收者之間建立了一個鬆散的關係。訊息的接收者不知道寄件者,甚至訊息的寄件者也不知道訊息的接收者會是什麼。

基於 target-action 傳遞機制的一個局限是,發送的訊息不能攜帶自訂的資訊。iOS 中,可以選擇性的把寄件者和觸發 action 的事件作為參數。除此之外就沒有別的控制 action 訊息內容的方法了。

舉個例子,我們使用Target-Action為控制項添加一個單擊手勢。

  UITapGestureRecognizer *tapGR = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(refresh)];  [_imageView addGestureRecognizer:tapGR];- (void)refresh{ NSLog(@"Touch imageView");}

代理

代理是一種我們常用的回調方式,也是蘋果推薦的方式,在系統架構UIKit中大量使用,如UITableView、UITextField。

優點:1,代理文法清晰,可讀性高,易於維護 ;2,它減少了代碼耦合性,使事件監聽與事件處理分離;3,一個控制器可以實現多個代理,滿足自訂開發需求,靈活性較高;

缺點:1,實現代理的過程較繁瑣;2,跨層傳值時加大代碼的耦合性,並且程式的階層也變得混亂;3,當多個對象同時傳值時不易區分,導致代理易用性大大降低;

Block

Block封裝一段代碼,併當做變數進行傳遞,它十分方便地將不同地方的程式碼群組織在一起,可讀性很高。

優點:1,文法簡潔,代碼可讀性和可維護性較高。2,配合GCD優秀的解決多線程問題。

缺點:1,Block中得代碼將自動進行一次retain操作,容易造成記憶體泄露。 2.Block內預設引用為強引用,容易造成循環參考。

通知

代理是一對一的關係,通知是一對多的關係,通知相比代理可以實現更大跨度的通訊機制。但接收對象多了,就難以控制,有時不希望的對象也接收處理了訊息。

優點:1,使用簡單,代碼精簡。2,支援一對多,解決了同時向多個對象監聽的問題。3,傳值方便快捷,Context自身攜帶相應的內容。

缺點:1,通知使用完畢後需要登出,否則會造成意外崩潰。2,key不夠安全,編譯器不會檢測到是否被通知中樞正確處理。3,調試時難以跟蹤。 4,當使用者向通知中樞發送通知的時候,並不能獲得任何反饋資訊。 5.需要一個第三方的對象來做監聽者與被監聽者的中介。

總結

至此,開發自訂控制項的相關知識梳理了一遍,希望能協助大家更好地理解自訂控制項開發。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.