加速計(jì)是整個(gè)ios屏幕旋轉(zhuǎn)的基礎(chǔ),依賴加速計(jì),設(shè)備才可以判斷出當(dāng)前的設(shè)備方向,ios系統(tǒng)共定義了以下七種設(shè)備方向:
typedef ns_enum(nsinteger, uideviceorientation) {
uideviceorientationunknown,
uideviceorientationportrait, // device oriented vertically, home button on the bottom
uideviceorientationportraitupsidedown, // device oriented vertically, home button on the top
uideviceorientationlandscapeleft, // device oriented horizontally, home button on the right
uideviceorientationlandscaperight, // device oriented horizontally, home button on the left
uideviceorientationfaceup, // device oriented flat, face up
uideviceorientationfacedown // device oriented flat, face down
};
以及如下四種界面方向:
typedef ns_enum(nsinteger, uiinterfaceorientation) {
uiinterfaceorientationportrait = uideviceorientationportrait,
uiinterfaceorientationportraitupsidedown = uideviceorientationportraitupsidedown,
uiinterfaceorientationlandscapeleft = uideviceorientationlandscaperight,
uiinterfaceorientationlandscaperight = uideviceorientationlandscapeleft
};
一、uikit處理屏幕旋轉(zhuǎn)的流程
當(dāng)加速計(jì)檢測(cè)到方向變化的時(shí)候,會(huì)發(fā)出 uideviceorientationdidchangenotification 通知,這樣任何關(guān)心方向變化的view都可以通過注冊(cè)該通知,在設(shè)備方向變化的時(shí)候做出相應(yīng)的響應(yīng)。上一篇博客中,我們已經(jīng)提到了在屏幕旋轉(zhuǎn)的時(shí)候,uikit幫助我們做了很多事情,方便我們完成屏幕旋轉(zhuǎn)。
uikit的相應(yīng)屏幕旋轉(zhuǎn)的流程如下:
1、設(shè)備旋轉(zhuǎn)的時(shí)候,uikit接收到旋轉(zhuǎn)事件。
2、uikit通過appdelegate通知當(dāng)前程序的window。
3、window會(huì)知會(huì)它的rootviewcontroller,判斷該view controller所支持的旋轉(zhuǎn)方向,完成旋轉(zhuǎn)。
4、如果存在彈出的view controller的話,系統(tǒng)則會(huì)根據(jù)彈出的view controller,來判斷是否要進(jìn)行旋轉(zhuǎn)。
二、uiviewcontroller實(shí)現(xiàn)屏幕旋轉(zhuǎn)
在響應(yīng)設(shè)備旋轉(zhuǎn)時(shí),我們可以通過uiviewcontroller的方法實(shí)現(xiàn)更細(xì)粒度的控制,當(dāng)view controller接收到window傳來的方向變化的時(shí)候,流程如下:
1、首先判斷當(dāng)前viewcontroller是否支持旋轉(zhuǎn)到目標(biāo)方向,如果支持的話進(jìn)入流程2,否則此次旋轉(zhuǎn)流程直接結(jié)束。
2、調(diào)用 willrotatetointerfaceorientation:duration: 方法,通知view controller將要旋轉(zhuǎn)到目標(biāo)方向。如果該viewcontroller是一個(gè)container view controller的話,它會(huì)繼續(xù)調(diào)用其content view controller的該方法。這個(gè)時(shí)候我們也可以暫時(shí)將一些view隱藏掉,等旋轉(zhuǎn)結(jié)束以后在現(xiàn)實(shí)出來。
3、window調(diào)整顯示的view controller的bounds,由于view controller的bounds發(fā)生變化,將會(huì)觸發(fā) viewwilllayoutsubviews 方法。這個(gè)時(shí)候self.interfaceorientation和statusbarorientation方向還是原來的方向。
4、接著當(dāng)前view controller的 willanimaterotationtointerfaceorientation:duration: 方法將會(huì)被調(diào)用。系統(tǒng)將會(huì)把該方法中執(zhí)行的所有屬性變化放到動(dòng)animation block中。
5、執(zhí)行方向旋轉(zhuǎn)的動(dòng)畫。
6、最后調(diào)用 didrotatefrominterfaceorientation: 方法,通知view controller旋轉(zhuǎn)動(dòng)畫執(zhí)行完畢。這個(gè)時(shí)候我們可以將第二部隱藏的view再顯示出來。
整個(gè)響應(yīng)過程如下圖所示:
以上就是uikit下一個(gè)完整的屏幕旋轉(zhuǎn)流程,我們只需要按照提示做出相應(yīng)的處理就可以完美的支持屏幕旋轉(zhuǎn)。
三、注意事項(xiàng)和建議
1)注意事項(xiàng)
當(dāng)我們的view controller隱藏的時(shí)候,設(shè)備方向也可能發(fā)生變化。例如view controller a彈出一個(gè)全屏的view controller b的時(shí)候,由于a完全不可見,所以就接收不到屏幕旋轉(zhuǎn)消息。這個(gè)時(shí)候如果屏幕方向發(fā)生變化,再dismiss b的時(shí)候,a的方向就會(huì)不正確。我們可以通過在view controller a的viewwillappear中更新方向來修正這個(gè)問題。
2)屏幕旋轉(zhuǎn)時(shí)的一些建議
•在旋轉(zhuǎn)過程中,暫時(shí)界面操作的響應(yīng)。
•旋轉(zhuǎn)前后,盡量當(dāng)前顯示的位置不變。
•對(duì)于view層級(jí)比較復(fù)雜的時(shí)候,為了提高效率在旋轉(zhuǎn)開始前使用截圖替換當(dāng)前的view層級(jí),旋轉(zhuǎn)結(jié)束后再將原view層級(jí)替換回來。
•在旋轉(zhuǎn)后最好強(qiáng)制reload tableview,保證在方向變化以后,新的row能夠充滿全屏。例如對(duì)于有些照片展示界面,豎屏只顯示一列,但是橫屏的時(shí)候顯示列表界面,這個(gè)時(shí)候一個(gè)界面就會(huì)顯示更多的元素,此時(shí)reload內(nèi)容就是很有必要的。
ios:屏幕旋轉(zhuǎn)與transform
itouch,iphone,ipad設(shè)置都是支持旋轉(zhuǎn)的,如果我們的程序能夠根據(jù)不同的方向做出不同的布局,體驗(yàn)會(huì)更好。
如何設(shè)置程序支持旋轉(zhuǎn)呢,通常我們會(huì)在程序的info.plist中進(jìn)行設(shè)置supported interface orientations,添加我們程序要支持的方向,而且程序里面每個(gè)viewcontroller也有方法
supportedinterfaceorientations(6.0及以后)
shouldautorotatetointerfaceorientation(6.0之前的系統(tǒng))
通過viewcontroller的這些方法,我們可以做到更小粒度的旋轉(zhuǎn)控制,如程序中僅僅允許個(gè)別界面旋轉(zhuǎn)。
一、屏幕旋轉(zhuǎn)背后到底做了什么呢?
下面我們看個(gè)簡(jiǎn)單的例子,用xcode新建一個(gè)默認(rèn)的單視圖工程,然后在對(duì)應(yīng)viewcontroller的響應(yīng)旋轉(zhuǎn)后的函數(shù)中輸出一下當(dāng)前view的信息,代碼如下:
svrotateviewcontroller
//
// svrotateviewcontroller.m
// svrotatebytransform
//
// created by maple on 4/21/13.
// copyright (c) 2013 maple. all rights reserved.
//
#import "svrotateviewcontroller.h"
@interface svrotateviewcontroller ()
@end
@implementation svrotateviewcontroller
- (void)viewdidload
{
[super viewdidload];
// do any additional setup after loading the view, typically from a nib.
self.view.backgroundcolor = [uicolor graycolor];
}
- (void)didreceivememorywarning
{
[super didreceivememorywarning];
// dispose of any resources that can be recreated.
}
- (bool)shouldautorotatetointerfaceorientation:(uiinterfaceorientation)interfaceorientation
{
return yes;
}
- (bool)shouldautorotate
{
return yes;
}
- (nsuinteger)supportedinterfaceorientations
{
return uiinterfaceorientationmaskall;
}
- (void)willrotatetointerfaceorientation:(uiinterfaceorientation)tointerfaceorientation duration:(nstimeinterval)duration
{
nslog(@"uiviewcontroller will rotate to orientation: %d", tointerfaceorientation);
}
- (void)didrotatefrominterfaceorientation:(uiinterfaceorientation)frominterfaceorientation
{
nslog(@"did rotated to new orientation, view information %@", self.view);
}
@end
查看代碼我們可以發(fā)現(xiàn),我們的viewcontroller支持四個(gè)方向,然后在旋轉(zhuǎn)完成的didrotatefrominterfaceorientation函數(shù)中打印了self.view的信息,旋轉(zhuǎn)一圈我們可以看到如下輸出:
二、什么是transform
transform(變化矩陣)是一種3×3的矩陣,如下圖所示:
通過這個(gè)矩陣我們可以對(duì)一個(gè)坐標(biāo)系統(tǒng)進(jìn)行縮放,平移,旋轉(zhuǎn)以及這兩者的任意組著操作。而且矩陣的操作不具備交換律,即矩陣的操作的順序不同會(huì)導(dǎo)致不同的結(jié)果。uiview有個(gè)transform的屬性,通過設(shè)置該屬性,我們可以實(shí)現(xiàn)調(diào)整該view在其superview中的大小和位置。
矩陣實(shí)現(xiàn)坐標(biāo)變化背后的數(shù)學(xué)知識(shí):
設(shè)x,y分別代表在原坐標(biāo)系統(tǒng)中的位置,x',y'代表通過矩陣變化以后在新的系統(tǒng)中的位置。其中式1就是矩陣變化的公式,對(duì)式1進(jìn)行展開以后就可以得到式2。從式2我們可以清楚的看到(x,y)到(x',y')的變化關(guān)系。
1)當(dāng)c,b,tx,ty都為零時(shí),x' = ax,y' = by;即a,d就分別代表代表x,y方向上放大的比例;當(dāng)a,d都為1時(shí),x' = x,y' = y;這個(gè)時(shí)候這個(gè)矩陣也就是傳說中的cgaffinetransformidentity(標(biāo)準(zhǔn)矩陣)。
2)當(dāng)a,d為1,c,b為零的時(shí)候,x' = x + tx,y' = y + ty;即tx,ty分別代表x,y方向上的平移距離。
3)前面兩種情況就可以實(shí)現(xiàn)縮放和平移了,那么旋轉(zhuǎn)如何表示呢?
假設(shè)不做平移和縮放操作,那么從原坐標(biāo)系中的一點(diǎn)(x,y)旋轉(zhuǎn)α°以后到了新的坐標(biāo)系中的一點(diǎn)(x',y'),那么旋轉(zhuǎn)矩陣如下:
展開以后就是x' = xcosα - ysinα,y' = xsinα + ycosα;
實(shí)際應(yīng)用中,我們將這些變化綜合起來,即可完成所有二維的矩陣變化。現(xiàn)在我們?cè)诨剡^頭來看看前面設(shè)備旋轉(zhuǎn)時(shí)的輸出,當(dāng)設(shè)備位于portrait的時(shí)候由于矩陣是標(biāo)準(zhǔn)矩陣,所以沒有進(jìn)行打印。當(dāng)轉(zhuǎn)到uiinterfaceorientationlandscapeleft方向的時(shí)候,我們的設(shè)備是順時(shí)針轉(zhuǎn)了90°(逆時(shí)針為正,順時(shí)針為負(fù)),這個(gè)時(shí)候矩陣應(yīng)該是(cos-90°,sin-90°,-sin-90°,cos-90°,tx,ty),由于未進(jìn)行平移操作所以tx,ty都為0,剛好可以跟我們控制臺(tái)輸出:"<uiview: 0x8075390; frame = (0 0; 320 480); transform = [0, -1, 1, 0, 0, 0]; autoresize = w+h; layer = <calayer: 0x8074980>>"一致。觀察其他兩個(gè)方向的輸出,發(fā)現(xiàn)結(jié)果均和分析一致。
由此可以發(fā)現(xiàn)屏幕旋轉(zhuǎn)其實(shí)就是通過view的矩陣變化實(shí)現(xiàn),當(dāng)設(shè)備監(jiān)測(cè)到旋轉(zhuǎn)的時(shí)候,會(huì)通知當(dāng)前程序,當(dāng)前程序再通知程序中的window,window會(huì)通知它的rootviewcontroller的,rootviewcontroller對(duì)其view的transform進(jìn)行設(shè)置,最終完成旋轉(zhuǎn)。
如果我們直接將一個(gè)view添加到window上,系統(tǒng)將不會(huì)幫助我們完成旋操作,這個(gè)時(shí)候我們就需要自己設(shè)置該view的transform來實(shí)現(xiàn)旋轉(zhuǎn)了。這種情況雖然比較少,但是也存在的,例如現(xiàn)在很多app做的利用狀態(tài)欄進(jìn)行消息提示的功能就是利用自己創(chuàng)建window并且自己設(shè)置transform來完成旋轉(zhuǎn)支持的,下一篇博客會(huì)介紹如何實(shí)現(xiàn)這種消息通知。