欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

iOS自定義控件開發(fā)梳理總結(jié)

 更新時(shí)間:2016年11月08日 10:08:00   作者:瀟瀟瀟瀟瀟瀟瀟  
這篇文章主要介紹了iOS自定義控件開發(fā)梳理總結(jié),自定義控件能讓我們完全控制視圖的展示內(nèi)容以及交互操作。具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。

在日常iOS開發(fā)中,系統(tǒng)提供的控件常常無法滿足業(yè)務(wù)功能,這個(gè)時(shí)候需要我們實(shí)現(xiàn)一些自定義控件。自定義控件能讓我們完全控制視圖的展示內(nèi)容以及交互操作。本篇將介紹一些自定義控件的相關(guān)概念,探討自定義控件開發(fā)的基本過程及技巧。

UIView

在開始之前我們先介紹一個(gè)類UIVew,它在iOS APP中占有絕對(duì)重要的地位,因?yàn)閹缀跛械目丶际抢^承自UIView類。
UIView表示屏幕上的一個(gè)矩形區(qū)域,負(fù)責(zé)渲染區(qū)域內(nèi)的內(nèi)容,并且響應(yīng)區(qū)域內(nèi)發(fā)生的觸摸事件。

在UIView的內(nèi)部有一個(gè)CALayer,提供內(nèi)容的繪制和顯示,包括UIView的尺寸樣式。UIView的frame實(shí)際上返回的CALayer的frame。

UIView繼承自UIResponder類,它能接收并處理從系統(tǒng)傳來的事件,CALayer繼承自NSObject,它無法響應(yīng)事件。所以UIView與CALayer的最大區(qū)別在于:UIView能響應(yīng)事件,而CALayer不能。
更詳細(xì)的資料:https://developer.apple.com/reference/uikit/uiview

兩種實(shí)現(xiàn)方式
在創(chuàng)建自定義控件時(shí),主要有兩種實(shí)現(xiàn)方式,分別是純代碼以及xib。接下來我們用這兩種方式分別演示一下創(chuàng)建自定義控件的步驟。
我們實(shí)現(xiàn)一個(gè)簡單的demo ,效果如下,封裝一個(gè)圓形的imageView。

使用代碼創(chuàng)建自定義控件
使用代碼創(chuàng)建自定義控件,首先創(chuàng)建一個(gè)繼承自UIView的類

實(shí)現(xiàn)initWithFrame:方法。在該方法中,設(shè)置自定義控件的屬性,并創(chuàng)建、添加子視圖:

-(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;
}

如果需要對(duì)子視圖重新布局,需要調(diào)用layoutSubViews方法:

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

}

layoutSubviews是調(diào)整子視圖布局的方法,官方文檔如下:

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

意思是當(dāng)你需要調(diào)整subview的大小的時(shí)候,重寫layoutSubviews方法。

layoutSubviews在以下情況下會(huì)被調(diào)用:

  • init初始化不會(huì)觸發(fā)layoutSubviews
  • addSubview會(huì)觸發(fā)layoutSubviews
  • 設(shè)置view的Frame會(huì)觸發(fā)layoutSubviews,當(dāng)然前提是frame的值設(shè)置前后發(fā)生了變化
  • 滾動(dòng)一個(gè)UIScrollView會(huì)觸發(fā)layoutSubviews
  • 旋轉(zhuǎn)Screen會(huì)觸發(fā)父UIView上的layoutSubviews事件
  • 改變一個(gè)UIView大小的時(shí)候也會(huì)觸發(fā)父UIView上的layoutSubviews事件

這個(gè)自定義控件提供對(duì)外接口方法,為自定義的控件賦值

- (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創(chuàng)建自定義控件
首先創(chuàng)建一個(gè)自定義控件XibCircleImageView,繼承自UIView
創(chuàng)建xib文件,與XibCircleImageView類同名
配置xib中imageView的屬性,并將XibCircleImageView 類與對(duì)應(yīng)的xib文件進(jìn)行綁定
代碼如下

- (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;

}

在頁面中調(diào)用方式有點(diǎn)不同,通過loadNibNamed方法創(chuàng)建xib對(duì)象

 

 //使用xib創(chuàng)建自定義控件
 _xibCircleImageView = [[[NSBundle mainBundle] loadNibNamed:@"XibCircleImageView" owner:nil options:nil] lastObject];
 _xibCircleImageView.frame = CGRectMake(0, 500, 100, 100);
 [_xibCircleImageView configeWithImage:image];
 [self.view addSubview:_xibCircleImageView];

當(dāng)使用xib創(chuàng)建自定義控件時(shí),初始化不會(huì)調(diào)用initWithFrame:方法,只會(huì)調(diào)用initWithCoder:方法,初始化完畢后才調(diào)用awakeFromNib方法,注意要在awakeFromNib中初始化子控件。因?yàn)閕nitWithCoder:方法表示對(duì)象是從文件解析來的,就會(huì)調(diào)用,而awakeFromNib方法是從xib或者storyboard加載完畢后才會(huì)調(diào)用。

小結(jié)

這兩種創(chuàng)建自定義控件的方式各有優(yōu)劣,純代碼方式比較靈活,維護(hù)和擴(kuò)展都比較方便,但寫起來比較麻煩。xib方式開發(fā)效率高,但不易擴(kuò)展和維護(hù),適合功能樣式比較穩(wěn)定的自定義控件。

事件傳遞機(jī)制

在自定義控件中,可能需要?jiǎng)討B(tài)響應(yīng)事件,如按鈕太小,不易點(diǎn)擊,需要擴(kuò)大按鈕的點(diǎn)擊范圍,接下來我們談?wù)刬OS的事件傳遞機(jī)制。

事件響應(yīng)鏈

UIResponder類能夠響應(yīng)觸摸、手勢以及遠(yuǎn)程控制等事件。它是所有可響應(yīng)事件的基類,其中包括很常見的UIView、UIViewController以及UIApplication。

UIResponder的屬性和方法如下圖,其中nextResponder表示指向一個(gè)UIResponder對(duì)象。

那么事件響應(yīng)鏈與UIResponder有什么關(guān)系呢?應(yīng)用內(nèi)的視圖按一定的結(jié)構(gòu)組織起來,即樹狀層次結(jié)構(gòu),一個(gè)視圖可以有多個(gè)子視圖,而子視圖只能有一個(gè)父視圖。當(dāng)一個(gè)視圖被添加到父視圖上時(shí)。每一個(gè)視圖的nextResponder屬性就指向它的父視圖,這樣,整個(gè)應(yīng)用就通過nextResponder串成了一條鏈,即響應(yīng)鏈。響應(yīng)鏈?zhǔn)且粋€(gè)虛擬鏈,并不是真實(shí)存在的,它借助UIResponder的nextResponder串連起來。如下圖

Hit-Test View

有了事件響應(yīng)鏈,接下來就是尋找具體響應(yīng)對(duì)象了,我們稱之為:Hit-Testing View,尋找這個(gè)View的過程稱為Hit-Test。
什么是Hit-Test?我們可以把它理解為一個(gè)探測器,通過這個(gè)探測器,我們可以找到并判斷手指是否觸摸在某個(gè)視圖上。
Hit-Test是如何工作的?Hit-Test采用遞歸方式從視圖的根節(jié)點(diǎn)開始遍歷,直到找到某個(gè)點(diǎn)擊的視圖。

首先從UIWindow發(fā)送hitTest:withEvent:消息開始,判斷該視圖是否能響應(yīng)觸摸事件,如果不能響應(yīng)返回nil,表示該視圖不能響應(yīng)觸摸事件。然后再調(diào)用pointInside:withEvent:方法,該方法用于判斷觸摸事件點(diǎn)擊的位置是否處理該視圖范圍內(nèi),如果pointInside:withEvent:返回no,那么hitTest:withEvent:也直接返回nil。

如果pointInside:withEvent: 方法返回yes,那么該視圖向所有子視圖發(fā)送hitTest:withEvent:消息,所有子視圖的調(diào)用順序是從最頂層視圖一直到最底層視圖,即從subViews的數(shù)組的末尾向前遍歷。直到有子視圖返回非空對(duì)象或全部遍歷完畢。若有子視圖返回非空對(duì)象,則hitTest:withEvent:方法返回該對(duì)象,處理結(jié)束;若所有子視圖都返回nil,則hitTest:withEvent:方法返回該視圖自身。

事件傳遞機(jī)制的應(yīng)用

舉幾個(gè)例子,說明一下事件傳遞機(jī)制在自定義控件中的應(yīng)用。

一、擴(kuò)大view的點(diǎn)擊區(qū)域。假設(shè)一個(gè)button的大小為20px 20px,太小難以點(diǎn)擊。我們通過重寫這個(gè)button子類的hitTest:withEvent:方法,判斷點(diǎn)擊處point是否在button周圍20px以內(nèi),如果是則返回自身,實(shí)現(xiàn)擴(kuò)大點(diǎn)擊范圍的功能,代碼如下:

-(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;
}

二、穿透傳遞事件。

假設(shè)有兩個(gè)view,viewA和viewB,viewB完全覆蓋viewA,我們希望點(diǎn)擊viewB時(shí)能響應(yīng)viewA的事件。我們重寫這個(gè)viewA的hitTest:withEvent:方法,不繼續(xù)遍歷它的子視圖,直接返回自身。代碼如下:

-(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;
}

回調(diào)機(jī)制

在自定義控件開發(fā)中,需要向它的父類回傳返回值。比如一個(gè)存放按鈕的自定義控件,需要在上層接收按鈕點(diǎn)擊事件。我們可以使用多種方式回調(diào)消息,比如target action模式、代理、block、通知等。

Target-Action

Target-Action是一種設(shè)計(jì)模式,當(dāng)事件觸發(fā)時(shí),它讓一個(gè)對(duì)象向另一個(gè)對(duì)象發(fā)送消息。這個(gè)模式我們接觸的比較多,如為按鈕綁定點(diǎn)擊事件,為view添加手勢事件等。UIControl及其子類都支持這個(gè)機(jī)制。Target-Action 在消息的發(fā)送者和接收者之間建立了一個(gè)松散的關(guān)系。消息的接收者不知道發(fā)送者,甚至消息的發(fā)送者也不知道消息的接收者會(huì)是什么。

基于 target-action 傳遞機(jī)制的一個(gè)局限是,發(fā)送的消息不能攜帶自定義的信息。iOS 中,可以選擇性的把發(fā)送者和觸發(fā) action 的事件作為參數(shù)。除此之外就沒有別的控制 action 消息內(nèi)容的方法了。

舉個(gè)例子,我們使用Target-Action為控件添加一個(gè)單擊手勢。

  UITapGestureRecognizer *tapGR = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(refresh)];
  [_imageView addGestureRecognizer:tapGR];

- (void)refresh{
 NSLog(@"Touch imageView");
}

代理

代理是一種我們常用的回調(diào)方式,也是蘋果推薦的方式,在系統(tǒng)框架UIKit中大量使用,如UITableView、UITextField。

優(yōu)點(diǎn):1,代理語法清晰,可讀性高,易于維護(hù) ;2,它減少了代碼耦合性,使事件監(jiān)聽與事件處理分離;3,一個(gè)控制器可以實(shí)現(xiàn)多個(gè)代理,滿足自定義開發(fā)需求,靈活性較高;

缺點(diǎn):1,實(shí)現(xiàn)代理的過程較繁瑣;2,跨層傳值時(shí)加大代碼的耦合性,并且程序的層次結(jié)構(gòu)也變得混亂;3,當(dāng)多個(gè)對(duì)象同時(shí)傳值時(shí)不易區(qū)分,導(dǎo)致代理易用性大大降低;

Block

Block封裝一段代碼,并當(dāng)做變量進(jìn)行傳遞,它十分方便地將不同地方的代碼組織在一起,可讀性很高。

優(yōu)點(diǎn):1,語法簡潔,代碼可讀性和可維護(hù)性較高。2,配合GCD優(yōu)秀的解決多線程問題。

缺點(diǎn):1,Block中得代碼將自動(dòng)進(jìn)行一次retain操作,容易造成內(nèi)存泄露。 2.Block內(nèi)默認(rèn)引用為強(qiáng)引用,容易造成循環(huán)引用。

通知

代理是一對(duì)一的關(guān)系,通知是一對(duì)多的關(guān)系,通知相比代理可以實(shí)現(xiàn)更大跨度的通信機(jī)制。但接收對(duì)象多了,就難以控制,有時(shí)不希望的對(duì)象也接收處理了消息。

優(yōu)點(diǎn):1,使用簡單,代碼精簡。2,支持一對(duì)多,解決了同時(shí)向多個(gè)對(duì)象監(jiān)聽的問題。3,傳值方便快捷,Context自身攜帶相應(yīng)的內(nèi)容。

缺點(diǎn):1,通知使用完畢后需要注銷,否則會(huì)造成意外崩潰。2,key不夠安全,編譯器不會(huì)檢測到是否被通知中心正確處理。3,調(diào)試時(shí)難以跟蹤。 4,當(dāng)使用者向通知中心發(fā)送通知的時(shí)候,并不能獲得任何反饋信息。 5.需要一個(gè)第三方的對(duì)象來做監(jiān)聽者與被監(jiān)聽者的中介。

總結(jié)

至此,開發(fā)自定義控件的相關(guān)知識(shí)梳理了一遍,希望能幫助大家更好地理解自定義控件開發(fā)。

相關(guān)文章

最新評(píng)論