Size Classes 具體使用
UIView和CALayer是什麼關系?
- UIView顯示在螢幕上歸功于CALayer,通過調用drawRect方法來渲染自身的内容,調節CALayer屬性可以調整UIView的外觀,
- UIView繼承自UIResponder,比起CALayer可以響應使用者事件,Xcode6之後可以友善的通過視圖調試功能檢視圖層之間的關系
- UIView是iOS系統中界面元素的基礎,所有的界面元素都繼承自它。它内部是由Core Animation來實作的,它真正的繪圖部分,是由一個叫CALayer(Core Animation Layer)的類來管理。UIView本身,更像是一個CALayer的管理器,通路它的跟繪圖和坐标有關的屬性,如frame,bounds等,實際上内部都是通路它所在CALayer的相關屬性
- UIView有個layer屬性,可以傳回它的主CALayer執行個體,UIView有一個layerClass方法,傳回主layer所使用的類,UIView的子類,可以通過重載這個方法,來讓UIView使用不同的CALayer來顯示,如:
- (class) layerClass {
// 使某個UIView的子類使用GL來進行繪制
return ([CAEAGLLayer class]);
}
- UIView的CALayer類似UIView的子View樹形結構,也可以向它的layer上添加子layer,來完成某些特殊的顯示。例如下面的代碼會在目标View上敷上一層黑色的透明薄膜。
grayCover = [[CALayer alloc]init];
grayCover.backgroudColor = [[UIColor blackColor]colorWithAlphaComponent:0.2].CGColor;
[self.layer addSubLayer:grayCover];
- 補充部分,這部分有深度了,大緻了解一下吧,UIView的layer樹形在系統内部被系統維護着三份copy
- 邏輯樹,就是代碼裡可以操縱的,例如更改layer的屬性等等就在這一份
- 動畫樹,這是一個中間層,系統正是在這一層上更改屬性,進行各種渲染操作
- 顯示樹,這棵樹的内容是目前正被顯示在螢幕上的内容
- 這三棵樹的邏輯結構都是一樣的,差別隻有各自的屬性
作為一個開發者,有一個學習的氛圍跟一個交流圈子特别重要,這是一個我的iOS交流群:413038000,不管你是大牛還是小白都歡迎入駐 ,分享BAT,阿裡面試題、面試經驗,讨論技術, 大家一起交流學習成長!
推薦閱讀
iOS開發——最新 BAT面試題合集(持續更新中)
loadView的作用?
- loadView用來自定義view,隻要實作了這個方法,其他通過xib或storyboard建立的view都不會被加載
- 看懂控制器view建立的這個圖就行
IBOutlet連出來的視圖屬性為什麼可以被設定成weak?
- 因為父控件的subViews數組已經對它有一個強引用
IB中User Defined Runtime Attributes如何使用?
- User Defined Runtime Attributes是一個不被看重但功能非常強大的的特性,它能夠通過KVC的方式配置一些你在interface builder中不能配置的屬性
- 當你希望在IB中作盡可能多得事情,這個特性能夠幫助你編寫更加輕量級的viewcontroller
沙盒目錄結構是怎樣的?各自用于那些場景?
- Application:存放程式源檔案,上架前經過數字簽名,上架後不可修改
- Documents:常用目錄,iCloud備份目錄,存放資料
- Library
- Caches:存放體積大又不需要備份的資料
- Preference:設定目錄,iCloud會備份設定資訊
- tmp:存放臨時檔案,不會被備份,而且這個檔案下的資料有可能随時被清除的可能
pushViewController和presentViewController有什麼差別
- 兩者都是在多個試圖控制器間跳轉的函數
- presentViewController提供的是一個模态視圖控制器(modal)
- pushViewController提供一個棧控制器數組,push/pop
請簡述UITableView的複用機制
- 每次建立cell的時候通過dequeueReusableCellWithIdentifier:方法建立cell,它先到緩存池中找指定辨別的cell,如果沒有就直接傳回nil
- 如果沒有找到指定辨別的cell,那麼會通過initWithStyle:reuseIdentifier:建立一個cell
- 當cell離開界面就會被放到緩存池中,以供下次複用
如何高性能的給 UIImageView 加個圓角?
- (UIImage *)circleImage
{
// NO代表透明
UIGraphicsBeginImageContextWithOptions(self.size, NO, 0.0);
// 獲得上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();
// 添加一個圓
CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
CGContextAddEllipseInRect(ctx, rect);
// 裁剪
CGContextClip(ctx);
// 将圖檔畫上去
[self drawInRect:rect];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
// 關閉上下文
UIGraphicsEndImageContext();
return image;
}
- 還有一種方案:使用了貝塞爾曲線"切割"個這個圖檔, 給UIImageView 添加了的圓角,其實也是通過繪圖技術來實作的
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
imageView.center = CGPointMake(200, 300);
UIImage *anotherImage = [UIImage imageNamed:@"image"];
UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, NO, 1.0);
[[UIBezierPath bezierPathWithRoundedRect:imageView.bounds
cornerRadius:50] addClip];
[anotherImage drawInRect:imageView.bounds];
imageView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[self.view addSubview:imageView];
使用drawRect有什麼影響?
- drawRect方法依賴Core Graphics架構來進行自定義的繪制
- 缺點:它處理touch事件時每次按鈕被點選後,都會用setNeddsDisplay進行強制重繪;而且不止一次,每次單點事件觸發兩次執行。這樣的話從性能的角度來說,對CPU和記憶體來說都是欠佳的。特别是如果在我們的界面上有多個這樣的UIButton執行個體,那就會很糟糕了
- 這個方法的調用機制也是非常特别. 當你調用 setNeedsDisplay 方法時, UIKit 将會把目前圖層标記為dirty,但還是會顯示原來的内容,直到下一次的視圖渲染周期,才會将标記為 dirty 的圖層重建立立Core Graphics上下文,然後将記憶體中的資料恢複出來, 再使用 CGContextRef 進行繪制
描述下SDWebImage裡面給UIImageView加載圖檔的邏輯
- SDWebImage 中為 UIImageView 提供了一個分類UIImageView+WebCache.h, 這個分類中有一個最常用的接口sd_setImageWithURL:placeholderImage:,會在真實圖檔出現前會先顯示占位圖檔,當真實圖檔被加載出來後在替換占位圖檔
- 加載圖檔的過程大緻如下:
- 首先會在 SDWebImageCache 中尋找圖檔是否有對應的緩存, 它會以url 作為資料的索引先在記憶體中尋找是否有對應的緩存
- 如果緩存未找到就會利用通過MD5處理過的key來繼續在磁盤中查詢對應的資料, 如果找到了, 就會把磁盤中的資料加載到記憶體中,并将圖檔顯示出來
- 如果在記憶體和磁盤緩存中都沒有找到,就會向遠端伺服器發送請求,開始下載下傳圖檔
- 下載下傳後的圖檔會加入緩存中,并寫入磁盤中
- 整個擷取圖檔的過程都是在子線程中執行,擷取到圖檔後回到主線程将圖檔顯示出來
設計個簡單的圖檔記憶體緩存器
- 類似上面SDWebImage實作原理即可
- 一定要有移除政策:釋放資料模型對象
控制器的生命周期
- 就是問的view的生命周期,下面已經按方法執行順序進行了排序
// 自定義控制器view,這個方法隻有實作了才會執行
- (void)loadView
{
self.view = [[UIView alloc] init];
self.view.backgroundColor = [UIColor orangeColor];
}
// view是懶加載,隻要view加載完畢就調用這個方法
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(@"%s",__func__);
}
// view即将顯示
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSLog(@"%s",__func__);
}
// view即将開始布局子控件
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
NSLog(@"%s",__func__);
}
// view已經完成子控件的布局
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
NSLog(@"%s",__func__);
}
// view已經出現
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSLog(@"%s",__func__);
}
// view即将消失
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
NSLog(@"%s",__func__);
}
// view已經消失
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
NSLog(@"%s",__func__);
}
// 收到記憶體警告
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
NSLog(@"%s",__func__);
}
// 方法已過期,即将銷毀view
- (void)viewWillUnload
{
}
// 方法已過期,已經銷毀view
- (void)viewDidUnload
{
}
你是怎麼封裝一個view的
- 可以通過純代碼或者xib的方式來封裝子控件
- 建立一個跟view相關的模型,然後将模型資料傳給view,通過模型上的資料給view的子控件指派
/**
* 純代碼初始化控件時一定會走這個方法
*/
- (instancetype)initWithFrame:(CGRect)frame
{
if(self = [super initWithFrame:frame])
{
[self setup];
}
return self;
}
/**
* 通過xib初始化控件時一定會走這個方法
*/
- (id)initWithCoder:(NSCoder *)aDecoder
{
if(self = [super initWithCoder:aDecoder])
{
[self setup];
}
return self;
}
- (void)setup
{
// 初始化代碼
}
如何進行iOS6、7的适配
- 通過判斷版本來控制,來執行響應的代碼
- 功能适配:保證同一個功能在6、7上都能用
- UI适配:保證各自的顯示風格
// iOS版本為7.0以上(包含7.0)
#define iOS7 ([[UIDevice currentDevice].systemVersion doubleValue]>=7.0)
如何渲染UILabel的文字?
- 通過NSAttributedString/NSMutableAttributedString(富文本)
UIScrollView的contentSize能否在viewDidLoad中設定?
- 能
- 因為UIScrollView的内容尺寸是根據其内部的内容來決定的,是以是可以在viewDidLoad中設定的
- 補充:(這僅僅是一種特殊情況)
- 前提,控制器B是控制器A的一個子控制器,且控制器B的内容隻在控制器A的view的部分區域中顯示
- 假設控制器B的view中有一個UIScrollView這樣一個子控件
- 如果此時在控制器B的viewDidLoad中設定UIScrollView的contentSize的話會導緻不準确的問題
- 因為任何控制器的view在viewDidLoad的時候的尺寸都是不準确的,如果有子控件的尺寸依賴父控件的尺寸,在這個方法中設定會導緻子控件的frame不準确,是以這時應該在下面的方法中設定子控件的尺寸
-(void)viewDidLayoutSubviews;
觸摸事件的傳遞
- 觸摸事件的傳遞是從父控件傳遞到子控件
- 如果父控件不能接收觸摸事件,那麼子控件就不可能接收到觸摸事件
- 不能接受觸摸事件的四種情況
- 不接收使用者互動,即:userInteractionEnabled = NO
- 隐藏,即:hidden = YES
- 透明,即:alpha <= 0.01
- 未啟用,即:enabled = NO
- 提示:UIImageView的userInteractionEnabled預設就是NO,是以UIImageView以及它的子控件預設是不能接收觸摸事件的
- 如何找到最合适處理事件的控件:
- 首先,判斷自己能否接收觸摸事件
- 可以通過重寫hitTest:withEvent:方法驗證
- 其次,判斷觸摸點是否在自己身上
- 對應方法pointInside:withEvent:
- 從後往前(先周遊最後添加的子控件)周遊子控件,重複前面的兩個步驟
- 如果沒有符合條件的子控件,那麼就自己處理
事件響應者鍊
- 如果目前view是控制器的view,那麼就傳遞給控制器
- 如果控制器不存在,則将其傳遞給它的父控件
- 在視圖層次結構的最頂層視圖也不能處理接收到的事件或消息,則将事件或消息傳遞給UIWindow對象進行處理
- 如果UIWindow對象也不處理,則将事件或消息傳遞給UIApplication對象
- 如果UIApplication也不能處理該事件或消息,則将其丢棄
- 補充:如何判斷上一個響應者
- 如果目前這個view是控制器的view,那麼控制器就是上一個響應者
- 如果目前這個view不是控制器的view,那麼父控件就是上一個響應者
作為一個開發者,有一個學習的氛圍跟一個交流圈子特别重要,這是一個我的iOS交流群:413038000,不管你是大牛還是小白都歡迎入駐 ,分享BAT,阿裡面試題、面試經驗,讨論技術, 大家一起交流學習成長!
推薦閱讀
iOS開發——最新 BAT面試題合集(持續更新中)