2015年1月25日 星期日

在iOS中按比例佈置UI (UI layout with ratio In iOS)


iOS和Android在UI佈局上最大的不同點就是iOS有座標,座標可以幫助開發者精確的定位,但是隨著apple產品的螢幕尺寸多樣化,座標就不再好用。像是這個網頁提到的,目前光是座標就有4種不同大小。

好在apple推出了size class和auto layout協助開發者處理座標上的差異,不過這些工具依然沒有辦法將所有的UI做到等比例縮放,因為他們的定位基本上還是建立在座標之上。要做到將UI在不同大小的螢幕上等比例縮放,甚至在旋轉後還能等比例縮放我目前知道的唯一辦法就是用程式寫。

之前我用程式寫,在程式中有許多計算比例的算式和CGRectMake...導致可讀性降低,比較好的處理方式是將計算位置的方法寫在方法裡面,我們可以用上次提到的Category,實作一個UIView Category加入計算的方法,或是直接寫個物件方法或是函示(C Function),解決的方法很多種,難度也不高。之所以寫在這個網誌是要提醒自己,不要在程式中遺留ㄧ堆計算位置的code,程式亂到我自己看都會怕。

開始看程式碼之前,先看看執行的效果,不管螢幕的尺寸改變或是旋轉螢幕,這些View相對於superView的長寬比例都不會改變。


如果用C寫的話可以參考以下的程式碼:
這個函示需要兩個參數:
第一個是superView,要計算UIView在superView中的相對位置需要superView的size。
第二的參數是一個CGRect,我用一個CGRect的結構來表達UIView.frame之於superView的比例。
#pragma mark Function

CGRect layoutInSuperViewwithRatios(UIView * superView, CGRect rectRatios){
    CGSize superViewSize = superView.bounds.size;
    CGFloat originX = rectRatios.origin.x*superViewSize.width;
    CGFloat originY = rectRatios.origin.y*superViewSize.height;
    CGFloat width = rectRatios.size.width * superViewSize.width;
    CGFloat height = rectRatios.size.height * superViewSize.height;

    return CGRectMake(originX, originY, width, height);
}
或者選擇用 Category可以在檔案的開頭寫下以下的程式碼:
計算的方式基本上和上面介紹的函示一模一樣。

#pragma mark Category

@implementation UIView (RelativeLayout)

+(CGRect)relativeLayoutinView:(UIView*)superView ratios:(CGRect)rectRatios{
    CGSize superViewSize = superView.bounds.size;
    CGFloat originX = rectRatios.origin.x*superViewSize.width;
    CGFloat originY = rectRatios.origin.y*superViewSize.height;
    CGFloat width = rectRatios.size.width * superViewSize.width;
    CGFloat height = rectRatios.size.height * superViewSize.height;
     CGRectMake(originX, originY, width, height);
    return CGRectMake(originX, originY, width, height);
}

@end
接下來就是ViewController部分的程式碼:
為了方便,我將這些UIView都宣告為物件變數,我在viewDidLoad方法中將這些UIView實體化,然後在viewWillLayoutSubviews方法中計算他們的位置和長寬。為了Demo前兩個View用了函示後兩個View用了類別方法,但基本上作用一模一樣,甚至你想把類別方法直接改成物件方法也行。

#pragma mark ViewController

@interface ViewController ()
{
    UIView * testView1, * testView2, * testView3, * testView4;
}

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    testView1 = [[UIView alloc]init];
    testView1.backgroundColor = [UIColor darkGrayColor];
    [self.view addSubview:testView1];
    
    testView2 = [[UIView alloc]init];
    testView2.backgroundColor = [UIColor blueColor];
    [self.view addSubview:testView2];
    
    testView3 = [[UIView alloc]init];
    testView3.backgroundColor = [UIColor redColor];
    [self.view addSubview:testView3];
    
    testView4 = [[UIView alloc]init];
    testView4.backgroundColor = [UIColor yellowColor];
    [self.view addSubview:testView4];

}

-(void)viewWillLayoutSubviews{
    [super viewWillLayoutSubviews];
    testView1.frame = layoutInSuperViewwithRatios(self.view,(CGRect){0.1,0.1,0.4,0.9});
    testView2.frame = layoutInSuperViewwithRatios(self.view,(CGRect){0.5,0.1,0.4,0.4});
    testView3.frame = [UIView relativeLayoutinView:self.view ratios:(CGRect){0.5,0.5,0.4,0.25}];
    testView4.frame = [UIView relativeLayoutinView:self.view ratios:(CGRect){0.5,0.75,0.4,0.25}];
}

@end

結論:計算UI位置的方式百百種,重要的是提高程式的可讀性,往後在維護時不會一頭霧水。
使用時機:UI設計師出的設計圖只給比例不給座標或是懶得用storyboard or Xib時。

1 則留言: