美文网首页iOS开发技术iOSiOS
UIStackView学习分享, 纯代码实现

UIStackView学习分享, 纯代码实现

作者: CoderLXWang | 来源:发表于2016-07-10 20:23 被阅读7098次

    最近看叶孤城的书,里面提到了UIStackView,说起这种布局,也是很早就知道了,但是一直没有研究过,开发中也没有使用过,周末上网翻看一下了大家的文章,发现大多都是Storyboard讲解的,实际上使用代码或者Storyboard对于UIStackView区别不大,因为UIStackView本身的属性就很少,由于本人开发中从不使用Storyboard或者xib, 本篇demo就用纯代码完成吧, 关键是网上的一些布局给的示例也不是特别清楚,由于开发中也没有实际用过,本文内容仅限交流学习, 大家交流指正。

    一,UIStackView是什么?
    在iOS9中苹果在UIKit框架中引入了一个新的视图类UIStackView。UIStackView 类提供了一个高效的接口用于平铺一行或一列的视图组合。Stack视图管理着所有在它的 arrangedSubviews 属性中的视图的布局。这些视图根据它们在 arrangedSubviews 数组中的顺序沿着 Stack 视图的轴向排列。
    简而言之,即UIStackView,就是一个ContainerView,可以沿横向或纵向按照一定的规则布局内部的子View。
    为了避免太过无聊, 先放出demo中实现的一个效果, demo地址

    效果

    二,一个快速示例

    下面用一个很简单的示例快速演示一下,


    示例1

    想一下要布局上图中的View,如果用Frame,或者autolayout的过程,都是比较复杂,masonry中有一种相对简单的方式, 可以看这篇文章masonry-等间距布局,以上方式都不演示了, 没什么可说的。

    如果使用UIStackView, 如下为实现代码:

    - (void)viewDidLoad {
        [super viewDidLoad];
        
        containerView = [[UIStackView alloc] initWithFrame:CGRectMake(0, 100, ScreenWidth, 200)];
        containerView.axis = UILayoutConstraintAxisHorizontal;
        containerView.distribution = UIStackViewDistributionFillEqually;
        containerView.spacing = 10;
        containerView.alignment = UIStackViewAlignmentFill;
        
        for (NSInteger i = 0; i < 4; i++) {
            UIView *view = [[UIView alloc] init];
            view.backgroundColor = [UIColor colorWithRed:random()%256/255.0 green:random()%256/255.0 blue:random()%256/255.0 alpha:1];
            [containerView addArrangedSubview:view];
        }
        
        [self.view addSubview:containerView];
    }
    

    以上就是所有代码, 是不是看起来异常简单, 前面说过了StackView其实就是一个容器View,可以管理内部子控件的布局, 那么看一下代码, init不用说了, for循环创建子控件不用说了(addArrangedSubview:view见下文), addSubViews也不用说, 那么久只剩下了四行代码, 这四个也就是UIStackView全部四个属性

    [containerView addArrangedSubview:view]相关介绍:
    UIStackView使用arrangedSubviews数组来管理子视图。
    需要注意的是这个数组是一个readonly的属性,我们需要调用方法对arrangedSubviews数组进行操作。
    初始化数组:
    - (instancetype)initWithArrangedSubviews:(NSArray<__kindof UIView *> *)views;
    添加子视图: 
    - (void)addArrangedSubview:(UIView *)view;
    移除子视图:
    - (void)removeArrangedSubview:(UIView *)view;
    根据下标插入视图:
    - (void)insertArrangedSubview:(UIView *)view atIndex:(NSUInteger)stackIndex;
    
    注意: addArrangedSubview 和 insertArrangedSubview, 会把子控件加到arrangedSubviews数组的同时添加到StackView上, 
          但是removeArrangedSubview, 只会把子控件从arrangedSubviews数组中移除,
          不会从subviews中移除,如果需要可调用removeFromSuperview
    

    三,具体讲解StackView的属性

    stackView属性示例图

    1, axis:子控件的布局方向,水平或垂直, 这个不用过多解释了
    2, spacing:子控件之间的最小间距,之所以说是最小间距,因为stackView会根据一定的规则对内部空间布局,有的时候不能满足所有要求,比如stackView 本身宽度100,内部两个控件,宽度都为50,50+50+10就超过了本身宽度, 这时会压缩其中一个子控件的宽度来满足最小间距。
    3, distribution:子控件依据何种规则布局, 这个比较抽象, 看示例理解的快一点,以下示例均使用UILabel演示, 原因见插播

    插播:先了解一下什么是Intrinsic Content Size(固有尺寸),
    因为UIStackView对子控件的布局是建立在Autolayout基础之上的,
    会涉及到Intrinsic Content Size
    想一下当为一个Label创建约束时,是不是经常只指定上边距和左边距,
    相信原因大家都知道,label内部的文字自会撑开宽高, 
    这个根据内容自己撑开的宽高就是Intrinsic Content Size。
    怎样给一个UIView设置Intrinsic Content Size 或者改变label的Intrinsic Content Size?
    继承然后重写
    - (CGSize)intrinsicContentSize
    {
        CGSize originalSize = [super intrinsicContentSize];
        CGSize size = CGSizeMake(originalSize.width+20, originalSize.height+20);
        return size;
    }
    
    

    UIStackViewDistribution是个枚举值, 各个值如下, 配有示例图, 看不懂就只能自求多福了:
    UIStackViewDistributionFill :它就是将 arrangedSubviews 填充满整个 StackView ,如果设置了spacing,那么这些 arrangedSubviews 之间的间距就是spacing。如果减去所有的spacing,所有的 arrangedSubview 的固有尺寸( intrinsicContentSize )不能填满或者超出 StackView 的尺寸,那就会按照 Hugging 或者 CompressionResistance 的优先级来拉伸或压缩一些 arrangedSubview 。如果出现优先级相同的情况,就按排列顺序来拉伸或压缩。

    UIStackViewDistributionFillEqually :这种就是 StackView 的尺寸减去所有的spacing之后均分给 arrangedSubviews ,每个 arrangedSubview 的尺寸是相同的。

    UIStackViewDistributionFillProportionally :这种跟FillEqually差不多,只不过这个不是讲尺寸均分给 arrangedSubviews ,而是根据 arrangedSubviews 的 intrinsicContentSize 按比例分配。

    UIStackViewDistributionEqualSpacing :这种是使 arrangedSubview 之间的spacing相等,但是这个spacing是有可能大于 StackView 所设置的spacing,但是绝对不会小于。这个类型的布局可以这样理解,先按所有的 arrangedSubview 的 intrinsicContentSize 布局,然后余下的空间均分为spacing,如果大约 StackView 设置的spacing那这样就OK了,如果小于就按照 StackView 设置的spacing,然后按照 CompressionResistance 的优先级来压缩一个 arrangedSubview 。

    UIStackViewDistributionEqualCentering :这种是使 arrangedSubview 的中心点之间的距离相等,这样没两个 arrangedSubview 之间的spacing就有可能不是相等的,但是这个spacing仍然是大于等于 StackView 设置的spacing的,不会是小于。这个类型布局仍然是如果 StackView 有多余的空间会均分给 arrangedSubviews 之间的spacing,如果空间不够那就按照 CompressionResistance 的优先级压缩 arrangedSubview 。

    4,alignment 子控件对其方式,类似UIlabel的textAlignment, 可以做一个类比, UILabel对应UIStackView, label的内容对应StackView的子控件。

    UIStackViewDistribution是个枚举值, 各个值如下:

    UIStackViewAlignmentFill, 默认方式, 如果子控件水平布局, 则指子控件的垂直方向填充满stackView. 反之亦然

    UIStackViewAlignmentLeading, 如果子控件竖直布局, 则指子控件左边对齐stackView左边. 反之亦然, 即 UIStackViewAlignmentTop = UIStackViewAlignmentLeading。

    UIStackViewAlignmentTop = UIStackViewAlignmentLeading,

    UIStackViewAlignmentFirstBaseline, 根据上方基线布局所有子视图的y值(适用于Horizontal模式), 这种模式没搞懂, 有知道怎么回事的求教了

    UIStackViewAlignmentLastBaseline, 根据下方基线布局所有子视图的y值(适用于Horizontal模式)

    UIStackViewAlignmentCenter, 中心对齐

    UIStackViewAlignmentTrailing, 如果子控件竖直布局, 则指子控件左边对齐stackView右边. 反之亦然, 即UIStackViewAlignmentBottom = UIStackViewAlignmentTrailing

    UIStackViewAlignmentBottom = UIStackViewAlignmentTrailing,

    四, 另一个有意思一点的示例, 同时演示stackView的嵌套

    即文章开头放出的动态图,
    这个效果使用了两个StackView, 一个horizontalView, 一个verticalView,horizontalView即示例下方水平排布的View, 这个horizontalView也添加到verticalView的arrangedSubviews数组中, 具体实现看代码吧, 不多说了
    github地址 : UIStackVIewDemo

    相关文章

      网友评论

      • 某非著名程序员:想问下,这在什么场景下会用到
        大大盆子:动态布局的时候很方便,隐藏一个视图,其他视图自动更新约束,如果只用masonry的话还要手动去更新约束,内容一多就很麻烦,所以这玩意儿是布局神器
        某非著名程序员:@CoderLXWang :smile: ,看了一下觉得挺厉害,仔细一想没用过
        CoderLXWang:@大灰狼杭州 基本没用,当时我也就研究了一下,鸡肋
      • touch释然:我们现在的需求是,横向展示图片,每个图片的大小相同,图片间的距离也要相同,一行最多展示5个图片,所以不管有多少个图片,图片大小都是 【(屏幕尺寸)- (图片间距) 】 / 5 。一张图片也是这个尺寸。 然后是不管有几个图片都需要居中显示。这样应该怎么设置这个控件呢。试了几下都没法实现。
        touch释然:@CoderLXWang 嗯,项目里没用masonry,我是用了一个view,让这个view的高度等于头像高度,让宽度等于 (头像个数)*(头像宽度)+ (头像个数 - 1)* (头像间距) 然后把这个view居中就可以了。
        CoderLXWang: @touch释然 不要用stackview,直接masonry布局,我也就是研究一下,stackview项目里从来不用
      • Jepson: UIStackView *stackView = [[UIStackView alloc] initWithFrame:CGRectMake(50, 50, 200, 50)];
        stackView.axis = UILayoutConstraintAxisHorizontal;//设置水平方向
        [self.view addSubview:stackView];

        UIView *view1 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 50, 50)];
        view1.backgroundColor = [UIColor redColor];
        [stackView addArrangedSubview:view1];

        UIView *view2 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 50, 50)];
        view2.backgroundColor = [UIColor blueColor];
        [stackView addArrangedSubview:view2];
        想问问楼主,为什么我这样子写,只显示view1,view2显示不出来
        CoderLXWang:@Jepson 给stackView 加上这个属性就行了
        stackView.distribution = UIStackViewDistributionFillEqually; ,具体原因看上文第三大部分,图片下面那里 “插播:先了解一下什么是Intrinsic Content Size(固有尺寸),因为UIStackView对子控件的布局是建立在Autolayout基础之上的……”,其实就是因为stackView不是已frame为基础,而是以自动布局,自动布局要依靠所谓的固有尺寸,而一个单独的View是没有固有尺寸的,所以不想,我的demo里用的都是Label,还有btn,imageView等这些控件,都是有内容的,既有本身的固有尺寸,可以理解成内容会自动把View撑开
        Jepson:@CoderLXWang 也是不行哦,跟上面情况一样,view2显示不出来
        CoderLXWang:view1和2不指定frame,试试看
      • 6a6db38116bc:叶孤城啥书
      • 吃蘑菇De大灰狼:关于Alignment的 UIStackViewAlignmentFirstBaseline 和 UIStackViewAlignmentLastBaseline 感觉是UIStackViewAlignmentFirstBaseline是让arrangedSubviews按照firstBaseline对齐,UIStackViewAlignmentLastBaseline是按照lastBaseline对齐,都只能出现在水平的StackView中。可以在demo中把label的numberOfLines设为0 ,然后加上换行, [str appendString:@"测试\n"]; 就可以看到效果了;
        文档可以看到: A horizontal UIStackView will return its tallest view for -viewForFirst/LastBaselineLayout,
        or if that is another stack view, then the relevant viewForFirst/LastBaselineLayout from that
        stack view.

        A vertical UIStackView will return its first view for -viewForFirstBaselineLayout and its
        last view for -viewForLastBaselineLayout, or if that is another stack view, then the relevant
        viewForFirst/LastBaselineLayout from that stack view.
        水平方向的UIStackView是按照最高的view来算的,竖直方向的UIStackView是按照首位的view来算的
      • 吃蘑菇De大灰狼:在讲4、alignment的时候,"UIStackViewDistribution是个枚举值"这句应该说的是UIStackViewAlignment吧?

        CoderLXWang: @MichaelMao 谢谢啦,是写错了,等会修改一下
      • 吃蘑菇De大灰狼:十分受教,谢谢作者的工作~
      • xxttw:分析的不错
        CoderLXWang: @Unc1eWang 😁
      • NateLam:demo下载了, 十分感谢, 让我好好研究一下 :+1:
      • 清蒸鱼跃龙门:你的label例子里没有设置过intrinsicContentSize,为什么用UIStackViewDistributionFillProportionally的时候大小会不一样啊
        CoderLXWang:@lianguowu 不是特别理解你的需求,大概可以有几种方法,自定义一个VIew,重写intrinsicContentSize,返回你要的尺寸,或者不用View,就用label,文字颜色透明色,通过文字多少来改变这个view(就是label)的宽度
        lianguowu:UIStackView中我添加的是UIView, 需要将其中的一个view的宽度变化和其他的view不一样,就像文章中的UIStackViewDistributionFillProportionally模式,有什么办法可以实现吗。
        CoderLXWang:@清蒸鱼跃龙门 intrinsicContentSize这个固有尺寸对于label或者btn这种有内容的控件是不用设置的, 会根据里面的内容自动给出一个intrinsicContentSize, 使用UIStackViewDistributionFillProportionally这种模式, 就是根据固有尺寸比例分配,比如图中的label字多点的, 他的View占得宽度就大点
      • wg689:iOS8的设备用不了吧
        CoderLXWang: @haojingxue_iOS 不行,ios9以上支持
      • visual_:like
        CoderLXWang: @MacChark thanks

      本文标题:UIStackView学习分享, 纯代码实现

      本文链接:https://www.haomeiwen.com/subject/vqudjttx.html