国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

IOS自動布局-UIStackPanel和UIGridPanel(二)

2019-11-14 20:00:18
字體:
來源:轉載
供稿:網友

在上一篇中我提到了如何使用stackpanel和gridpanel來實現自動布局。而在這一篇中我著重講解下其中的原理。

在(Uipanel   UIStackPanel  UIGridPanel)中主要是使用了NSLayoutConstraint這個類來實現的,因此為了看懂下面的代碼請務必先了解NSLayoutConstraint的使用方法。

先考慮下這樣一個場景,現在有一個自上而下垂直的布局,水平方向的寬度跟屏幕分辨率的寬度保持一致,垂直方向高度不變,各個視圖間的間距不變,在用戶切換橫屏和豎屏的時候只有視圖的寬度是改變的,而高度和視圖間的間距不變。這樣一個場景也能模擬我們的應用在不同分辨率上適配。

針對上面這個場景,那么我們勢必要給UIView兩個屬性,就是描述UIView高寬和UIView之間間距的屬性,這里定義為size和margin屬性,size的類型是CGSize,而margin的數據類型是UIEdgeInsets(描述該UIView的四個方向的間距)。這兩個屬性是以擴展屬性實現的。

代碼如下:

@interface UIView(UIPanelSubView)//設置view的大小@PRoperty (nonatomic,assign)CGSize size;//view距離左上右下的間距@property (nonatomic,assign)UIEdgeInsets margin;@end

既然有了這兩個屬性,那么意味著只要我修改了兩個屬性的任何一個屬性,都能實時的改變UIView的外觀,那么我們這里就需要有一個方法來充值UIView的實現,這里添加一個方法resetConstraints,用來重置約束。

這樣完整的class定義是這樣的

@interface UIView(UIPanelSubView)//設置view的大小@property (nonatomic,assign)CGSize size;//view距離左上右下的間距@property (nonatomic,assign)UIEdgeInsets margin;//重置約束-(void)resetConstraints;@end

完整的實現代碼如下:

@implementation UIView(UIPanelSubView)char* const uiviewSize_str = "UIViewSize";-(void)setSize:(CGSize)size{    objc_setAssociatedObject(self, uiviewSize_str, NSStringFromCGSize(size), OBJC_ASSOCIATION_RETAIN);    //先將原來的高寬約束刪除    for(NSLayoutConstraint *l in self.constraints){        switch (l.firstAttribute) {            case NSLayoutAttributeHeight:            case NSLayoutAttributeWidth:                [self removeConstraint:l];                break;            default:                break;        }    }    //添加高度約束    [self addConstraint:[NSLayoutConstraint constraintWithItem:self                                                     attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0f constant:size.height]];    //添加寬度約束    [self addConstraint:[NSLayoutConstraint constraintWithItem:self                                                     attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0f constant:size.width]];}-(CGSize)size{    return CGSizeFromString(objc_getAssociatedObject(self, uiviewSize_str));}char* const uiviewMargin_str = "UIViewMargin";-(void)setMargin:(UIEdgeInsets)margin{    objc_setAssociatedObject(self, uiviewMargin_str, NSStringFromUIEdgeInsets(margin), OBJC_ASSOCIATION_RETAIN);        if(self.superview){//只有在有父視圖的情況下,才能更新約束        [self.superview updateConstraints];    }}-(UIEdgeInsets)margin{    return UIEdgeInsetsFromString(objc_getAssociatedObject(self, uiviewMargin_str));}-(void)resetConstraints{    [self removeConstraints:self.constraints];}@end

現在有了這個擴展類就可以繼續上面的布局需求了。我們希望當把UIView添加到superview的時候對該UIView添加各種約束信息。代碼如下:

-(void)didAddSubview:(UIView *)subview{    [super didAddSubview:subview];    subview.translatesAutoresizingMaskIntoConstraints=NO;//要實現自動布局,必須把該屬性設置為no    [self removeConstraintsWithSubView:subview];//先把subview的原來的約束信息刪除掉    [self updateSubViewConstraints:subview];//添加新的約束信息}

上面提到布局是垂直自上而下的,而且寬度需要隨著屏幕的寬度改變而改變。從這里我們可以得出兩個結論。

  1. 寬度上要有一個約束,約束的具體信息是寬度隨著父視圖的寬度變化,還要把間距考慮進去。
  2. 所有添加到同一個父視圖中的subviews按照順序自上而下依序排列。

具體代碼如下

-(void)updateSubViewConstraints:(UIView *)subView{    UIEdgeInsets margin=subView.margin;    NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"|-left-[view]-right-|" options:0 metrics:@{ @"left" : @(margin.left),@"right":@(margin.right)} views:@{ @"view" : subView}];//添加寬度的約束    [self addConstraints:constraints];    //獲取同級下的上一個視圖的,以便做垂直的自上而下排列    NSInteger index=[self.subviews indexOfObject:subView];    UIView *preView=index==0?nil:[self.subviews objectAtIndex:index-1];    if(preView){//如果該subview有排序比它更靠前的視圖        //該subview的頂部緊靠上一個視圖的底部        [self addConstraint:[NSLayoutConstraint constraintWithItem:subView                                                         attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:preView attribute:NSLayoutAttributeBottom multiplier:1.0f constant:(margin.top+preView.margin.bottom)]];            }else{        //該subview的頂部緊靠父視圖的頂部        [self addConstraint:[NSLayoutConstraint constraintWithItem:subView                                                         attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeTop multiplier:1.0f constant:margin.top]];    }    }

至此,我們已經實現了一個可以自動自上而下排列的stackpanel。繼續考慮一個問題,如果我們動態的刪除其中的一個子視圖,我們會發現所有的約束機會都失效了,為什么?因為從上面的代碼中可以看出,我們之所以能夠實現自上而下的布局,完全是依賴余有序的前后視圖的各種"依賴約束",也就是NSLayoutConstraint中的relatedBy是上一個視圖,這就好比一條鏈子上的各個有序節點,一旦你把鏈子上的一個節點拿掉,那么原來的前后關系就改變了,因此約束也就失效了。

而為了能夠實現在UIView從superview中移除的時候不影響整個的約束信息,那么我們必須重置約束信息,也就是我們應該在superview的didRemoveSubview這個方法中來重置,但是很遺憾,沒有這個方法,蘋果只給了我們willRemoveSubview方法,我目前沒有想到其他方法,只能在willRemoveSubview這個方法上考慮去實現?,F在問題又來了,willRemoveSubview這個方法被調用的時候該subview事實上還沒有被刪掉,只是告訴你將要被刪除了。這里我采用了一個取巧的方法,說實話這樣的代碼不應該出現的,但是沒辦法,只能先將就用下。也就是在willRemoveSubview的方法里面,再調一次subview的removeFromSuperview的方法,這樣當removeFromSuperview調用完畢的時候就表明該subview已經被移除了,但是這樣一來就會造成循環調用了,因此我們還需要一個bool參數來標記該subview是有已經被刪除了,因此我們需要在上面提到的UIPanelSubView類中添加一個不公開的屬性isRemoved,該屬性在UIVIew被添加到superview中的時候設置為no,被remove的時候設置為yes。

具體代碼如下:

-(void)didAddSubview:(UIView *)subview{    [super didAddSubview:subview];    [subview setIsRemoved:NO];//標記為未刪除    subview.translatesAutoresizingMaskIntoConstraints=NO;//要實現自動布局,必須把該屬性設置為no    [self removeConstraintsWithSubView:subview];//先把subview的原來的約束信息刪除掉    [self updateSubViewConstraints:subview];//添加新的約束信息}-(void)willRemoveSubview:(UIView *)subview{    if(![subview isRemoved]){//因為沒有didRemoveSubView方法,所以只能采用這樣的方式來達到目的了        [subview setIsRemoved:YES];//標記為已刪除        [subview removeFromSuperview];//再調用一次removeFromSuperview,這樣調用完畢該方法,那么表明該subview已經被移除了        [self updateConstraints];//重置約束    }}
-(void)updateConstraints{    [super updateConstraints];    for(UIView * v in self.subviews) {        [self updateSubViewConstraints:v];    }}

這樣就實現了subview被移除的時候仍然能有效約束。

現在當我們把UIStackPanel添加ViewController的view中的時候,發現旋轉屏幕的時候里面的布局沒有跟著變。這是因為我們以上的約束信息都是UIStackPanel和它的子視圖的,但是UIStackPanel沒有建立起跟它的父視圖的約束,這樣當然不能實現自動布局啦。要解決這個問題,也很簡單。對UIStackPanel添加一個屬性isBindSizeToSuperView,是否把UIStackPanel的高寬跟父視圖的高寬綁定。

-(void)setIsBindSizeToSuperView:(BOOL)isBindSizeToSuperView{    if(_isBindSizeToSuperView!=isBindSizeToSuperView){        _isBindSizeToSuperView=isBindSizeToSuperView;        if(isBindSizeToSuperView){            self.translatesAutoresizingMaskIntoConstraints=NO;            if(self.superview){                [self bindSizeToSuperView];            }        }else{            self.translatesAutoresizingMaskIntoConstraints=YES;        }    }}-(void)bindSizeToSuperView{    UIEdgeInsets margin=self.margin;    NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"|-left-[view]-right-|" options:0 metrics:@{ @"left" : @(margin.left),@"right":@(margin.right)} views:@{ @"view" : self}];    [self.superview addConstraints:constraints];    constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-top-[view]-bottom-|" options:0 metrics:@{ @"top" : @(margin.top),@"bottom":@(margin.bottom)} views:@{ @"view" : self}];    [self.superview addConstraints:constraints];}

這樣我們已經完全實現了開頭提到的布局要求。

下面貼出完整的代碼

@interface UIView(UIPanelSubView)//設置view的大小@property (nonatomic,assign)CGSize size;//view距離左上右下的間距@property (nonatomic,assign)UIEdgeInsets margin;//重置約束-(void)resetConstraints;@end@interface UIPanel : UIView@property (nonatomic,assign)BOOL isBindSizeToSuperView;//是否把高寬綁定到父視圖//更新某個字視圖的約束信息-(void)updateSubViewConstraints:(UIView *)subView;//刪除屬于subView的NSLayoutConstraint-(void)removeConstraintsWithSubView:(UIView *)subView;@end@interface UIStackPanel : UIPanel@property (nonatomic,assign)BOOL isHorizontal;//是否水平布局@end
@implementation UIView(UIPanelSubView)char* const uiviewSize_str = "UIViewSize";-(void)setSize:(CGSize)size{    objc_setAssociatedObject(self, uiviewSize_str, NSStringFromCGSize(size), OBJC_ASSOCIATION_RETAIN);    //先將原來的高寬約束刪除    for(NSLayoutConstraint *l in self.constraints){        switch (l.firstAttribute) {            case NSLayoutAttributeHeight:            case NSLayoutAttributeWidth:                [self removeConstraint:l];                break;            default:                break;        }    }    //添加高度約束    [self addConstraint:[NSLayoutConstraint constraintWithItem:self                                                     attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0f constant:size.height]];    //添加寬度約束    [self addConstraint:[NSLayoutConstraint constraintWithItem:self                                                     attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0f constant:size.width]];}-(CGSize)size{    return CGSizeFromString(objc_getAssociatedObject(self, uiviewSize_str));}char* const uiviewMargin_str = "UIViewMargin";-(void)setMargin:(UIEdgeInsets)margin{    objc_setAssociatedObject(self, uiviewMargin_str, NSStringFromUIEdgeInsets(margin), OBJC_ASSOCIATION_RETAIN);        if(self.superview){//只有在有父視圖的情況下,才能更新約束        [self.superview updateConstraints];    }}-(UIEdgeInsets)margin{    return UIEdgeInsetsFromString(objc_getAssociatedObject(self, uiviewMargin_str));}//用來標記該視圖一否已經被刪除char* const uiviewIsRemoved_str = "UIViewIsRemoved";-(void)setIsRemoved:(BOOL)isRemoved{    objc_setAssociatedObject(self, uiviewIsRemoved_str, @(isRemoved), OBJC_ASSOCIATION_RETAIN);}-(BOOL)isRemoved{     return [objc_getAssociatedObject(self, uiviewIsRemoved_str) boolValue];}-(void)resetConstraints{    [self removeConstraints:self.constraints];    if(self.superview && [self.superview respondsToSelector:@selector(updateSubViewConstraints:)]){        [self.superview performSelector:@selector(removeConstraintsWithSubView:) withObject:self];        [self.superview performSelector:@selector(updateSubViewConstraints:) withObject:self];        [self updateConstraints];    }}@end@implementation UIPanel-(void)setIsBindSizeToSuperView:(BOOL)isBindSizeToSuperView{    if(_isBindSizeToSuperView!=isBindSizeToSuperView){        _isBindSizeToSuperView=isBindSizeToSuperView;        if(isBindSizeToSuperView){            self.translatesAutoresizingMaskIntoConstraints=NO;            if(self.superview){                [self bindSizeToSuperView];            }        }else{            self.translatesAutoresizingMaskIntoConstraints=YES;        }    }}-(void)didAddSubview:(UIView *)subview{    [super didAddSubview:subview];    [subview setIsRemoved:NO];//標記為未刪除    subview.translatesAutoresizingMaskIntoConstraints=NO;//要實現自動布局,必須把該屬性設置為no    [self removeConstraintsWithSubView:subview];//先把subview的原來的約束信息刪除掉    [self updateSubViewConstraints:subview];//添加新的約束信息}-(void)updateSubViewConstraints:(UIView *)subView{    UIEdgeInsets margin=subView.margin;    NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"|-left-[view]-right-|" options:0 metrics:@{ @"left" : @(margin.left),@"right":@(margin.right)} views:@{ @"view" : subView}];    [self addConstraints:constraints];        constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-top-[view]-bottom-|" options:0 metrics:@{ @"top" : @(margin.top),@"bottom":@(margin.bottom)} views:@{ @"view" : subView}];    [self addConstraints:constraints];}-(void)willRemoveSubview:(UIView *)subview{    if(![subview isRemoved]){//因為沒有didRemoveSubView方法,所以只能采用這樣的方式來達到目的了        [subview setIsRemoved:YES];//標記為已刪除        [subview removeFromSuperview];//再調用一次removeFromSuperview,這樣調用完畢該方法,那么表明該subview已經被移除了        [self updateConstraints];//重置約束    }}-(void)removeConstraintsWithSubView:(UIView *)subView{    for(NSLayoutConstraint *l in self.constraints){        if(l.firstItem==subView){            [self removeConstraint:l];        }    }}-(void)updateConstraints{    [super updateConstraints];    for(UIView * v in self.subviews) {        [self updateSubViewConstraints:v];    }}-(void)bindSizeToSuperView{    UIEdgeInsets margin=self.margin;    NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"|-left-[view]-right-|" options:0 metrics:@{ @"left" : @(margin.left),@"right":@(margin.right)} views:@{ @"view" : self}];    [self.superview addConstraints:constraints];    constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-top-[view]-bottom-|" options:0 metrics:@{ @"top" : @(margin.top),@"bottom":@(margin.bottom)} views:@{ @"view" : self}];    [self.superview addConstraints:constraints];}-(void)didMoveToSuperview{    [super didMoveToSuperview];    if(self.isBindSizeToSuperView){        [self bindSizeToSuperView];    }}@end@implementation UIStackPanel-(void)setIsHorizontal:(BOOL)isHorizontal{    if(_isHorizontal!=isHorizontal){        _isHorizontal=isHorizontal;        [self updateConstraints];    }}-(void)updateSubViewConstraints:(UIView *)subView{    UIEdgeInsets margin=subView.margin;    if(self.isHorizontal){        NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-top-[view]-bottom-|" options:0 metrics:@{ @"top" : @(margin.top),@"bottom":@(margin.bottom)} views:@{ @"view" : subView}];        [self addConstraints:constraints];                NSInteger index=[self.subviews indexOfObject:subView];        UIView *preView=index==0?nil:[self.subviews objectAtIndex:index-1];                if(preView){            [self addConstraint:[NSLayoutConstraint constraintWithItem:subView                                                             attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:preView attribute:NSLayoutAttributeRight multiplier:1.0f constant:(margin.left+preView.margin.left)]];                    }else{            [self addConstraint:[NSLayoutConstraint constraintWithItem:subView                                                             attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeLeft multiplier:1.0f constant:margin.left]];        }            }else{        NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"|-left-[view]-right-|" options:0 metrics:@{ @"left" : @(margin.left),@"right":@(margin.right)} views:@{ @"view" : subView}];//添加寬度的約束        [self addConstraints:constraints];        //獲取同級下的上一個視圖的,以便做垂直的自上而下排列        NSInteger index=[self.subviews indexOfObject:subView];        UIView *preView=index==0?nil:[self.subviews objectAtIndex:index-1];        if(preView){//如果該subview有排序比它更靠前的視圖            //該subview的頂部緊靠上一個視圖的底部            [self addConstraint:[NSLayoutConstraint constraintWithItem:subView                                                             attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:preView attribute:NSLayoutAttributeBottom multiplier:1.0f constant:(margin.top+preView.margin.bottom)]];                    }else{            //該subview的頂部緊靠父視圖的頂部            [self addConstraint:[NSLayoutConstraint constraintWithItem:subView                                                             attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeTop multiplier:1.0f constant:margin.top]];        }    }  }@end

 

下一篇介紹uigridpanel的原理

 


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 舟山市| 达州市| 麻城市| 陆河县| 京山县| 卢湾区| 逊克县| 水富县| 温泉县| 洪湖市| 米泉市| 天全县| 汉源县| 乐平市| 炎陵县| 聊城市| 莱阳市| 鹿泉市| 台中县| 察雅县| 游戏| 徐州市| 乐昌市| 晋州市| 射阳县| 西畴县| 定襄县| 闸北区| 武穴市| 武夷山市| 泾川县| 保康县| 青浦区| 托克逊县| 桑植县| 胶南市| 五指山市| 米泉市| 遵化市| 广西| 乌兰察布市|