在iOS學習和程序開發過程中,我們經常會遇到一些自定義UI控件或控制器在初始化時出現問題,尤其在大家剛開始接觸時,幾種初始化方法的作用以及調用的時機往往容易混淆,這也跟我們對iOS程序設計中,類的創建和實例化的過程了解不透徹有關系。本文用一些小例子來簡單梳理一下幾者的關系,后面再陸續討論一些復雜情況的深入對比。
問題: 一、什么時候用initWithFrame,什么時候用aweakFromNib、initWithCoder
二、在初始化時控件自身的frame何時能獲得?layoutSubViews何時調用
首先,我們實例化一個(控件類型)對象可以有多種方式:
(1)純代碼創建。創建自定義的UI控件類,然后實例化該類型的對象。
(2)通過IB(Interface Builder)創建,就是俗稱的“拖線”。當我們創建好xib文件的時候,就相當於創建好了控件類,但是如果不實例化,也是沒有用的,所以需要加載,這里用loadNibName來加載(實例化)UI控件。
1、搭建實驗環境A,代碼創建控件(TestCodeingView繼承自UIView)
-(void)loadFromCoding { TestCodeingView * viewCoding = [[TestCodeingView alloc]init]; viewCoding.frame=CGRectMake(100, 100, 200, 200); viewCoding.backgroundColor=[UIColor greenColor]; [self.view addSubview:viewCoding]; } 在TestCodeingView類中對以下方法進行重寫 -(instancetype)init { self=[super init]; NSLog(@" init =====> 執行了"); NSLog(@"此時view的frame====》 %@",NSStringFromCGRect(self.frame)); return self; } -(instancetype)initWithFrame:(CGRect)frame { self=[super initWithFrame:frame]; NSLog(@" initWithFrame =====> 執行了"); NSLog(@"此時view的frame====》 %@",NSStringFromCGRect(self.frame)); return self; } -(instancetype)initWithCoder:(NSCoder *)aDecoder { self=[super initWithCoder:aDecoder]; NSLog(@" initWithCoder =====> 執行了"); return self; } -(void)awakeFromNib { NSLog(@" awakeFromNib =====> 執行了"); } -(void)layoutSubviews { NSLog(@" layoutSubviews =====> 執行了"); NSLog(@"此時view的frame====》 %@",NSStringFromCGRect(self.frame)); }
運行結果:
然后更改部分代碼:
-(instancetype)initWithFrame:(CGRect)frame { self=[super initWithFrame:frame]; NSLog(@" initWithFrame =====> 執行了"); NSLog(@"此時view的frame====》 %@",NSStringFromCGRect(self.frame)); UILabel * label = [[UILabel alloc]init]; label.text=@"我是新建的label"; label.backgroundColor=[UIColor orangeColor]; self.label=label; [self addSubview:label]; return self; } -(void)layoutSubviews { NSLog(@" layoutSubviews =====> 執行了"); NSLog(@"此時view的frame====》 %@",NSStringFromCGRect(self.frame)); self.label.frame = CGRectMake((self.frame.size.width-150)/2,self.frame.size.height/2, 150, 30); }
運行結果:
小結一下:(1)純代碼創建的UI控件不執行aweakFromNib方法和 initWithCoder方法。
(2)layoutSubciews方法在控件初始化完成后(自身和子控件的實例化結束)調用,方法中能獲得到當前控件的frame,以便於給子控件布局。如有子控件,調用兩次。
(3)系統在調用以上方法時,有着特定的先后順序。
2、搭建實驗環境B,Xib創建控件
通過xib加載自定義UI控件,如下圖,TestXibView類為手動創建的UI控件類,繼承自UIView
-(void)loadFromXib { TestXibView * viewXib = [[[NSBundle mainBundle]loadNibNamed:@"testXibView" owner:nil options:nil] lastObject]; viewXib.center=self.view.center; [self.view addSubview:viewXib]; }
在TestCodeingView類中對以下方法進行重寫
-(instancetype)init { self=[super init]; NSLog(@" init =====> 執行了"); return self; } -(instancetype)initWithFrame:(CGRect)frame { self=[super initWithFrame:frame]; NSLog(@" initWithFrame =====> 執行了"); return self; } -(instancetype)initWithCoder:(NSCoder *)aDecoder { self=[super initWithCoder:aDecoder]; NSLog(@" initWithCoder =====> 執行了"); return self; } -(void)awakeFromNib { NSLog(@" awakeFromNib =====> 執行了"); } -(void)layoutSubviews { NSLog(@" layoutSubviews =====> 執行了"); }
運行結果:
更改部分代碼,對Xib加載的控件使用代碼進行修改 (添加了一個子控件和更改背景顏色):
-(instancetype)initWithCoder:(NSCoder *)aDecoder { self=[super initWithCoder:aDecoder]; NSLog(@" initWithCoder =====> 執行了"); UILabel * label = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, 150, 30)]; label.text=@"我是新建的label"; label.backgroundColor=[UIColor orangeColor]; label.center=CGPointMake(self.center.x, self.frame.size.height-30); [self addSubview:label]; return self; } -(void)awakeFromNib { NSLog(@" awakeFromNib =====> 執行了"); self.backgroundColor=[UIColor yellowColor]; }
運行結果:
小結一下:(1)通過Xib創建UI控件,不會調用init和initwith方法。
(2)創建一個控件類,和xib關聯,是可以修改Xib中的屬性的。
(3)一樣會調用layoutSubViews方法
(4)因為通過拖線和配置,已經固定了控件的大小和布局,所以frame可以獲得
(5)initWithCoder和 aweakFromNib 在這里作用相同,都被系統調用
總結及延伸:
當我們弄清楚控制器加載的各種情況后,相對於用代碼,使用IB和xib文件來組織UI,可以省下大量代碼和時間,從而得到更快的開發速度;同時,Xib最大的問題在於其設置往往並非最終設置,在代碼中你將有機會覆蓋你在xib文件中進行的UI設計,造成錯誤和混亂。
說了好多,總結一下也無非幾句話:
1、用Xib創建控件,對於控件的后續操作都寫在initWithCoder或aweakFromNib方法中;
2、純代碼寫創建的控件,對於控件的后續操作都寫在initWithFrame方法中;
3、添加子控件時,注意布局(frame的獲得),合理靈活的使用xib加載控件;
4、至於initWithCoder和aweakFromNib的區別在后面再做討論(關於通過xib加載控制器)。