iOS 第三方框架-Masonry 的视图模块化

Masonry,是一个基于纯代码的AutoLayout库.初次涉及时,只是感觉它很方便,既有Xib的易读性,又有纯代码的灵活性.试用一段时间之后,突然想到: 或许借助Masonry,建立一个纯代码的不依赖Xib的AutoLayout视图组件机制.

GitHub地址:https://github.com/SnapKit/Masonry

Masonry安装:

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, "8.0"

use_frameworks!

target :testMasonry
  pod 'Masonry'

目前能得到的效果

  • 视图基于 AutoLayout;
  • 视图自动适配不同屏幕尺寸;
  • 视图完全独立于数据与业务逻辑;
  • 视图严肃仅与父视图有位置关系;
  • 可以将视图模块的元素与模块同名属性自动关联;
  • 仅需知道父视图的宽高,模块内某一个UI元素的宽高, UI元素的 bottom 与 right, 就可以唯一确定任意元素的位置.

核心理论基础: AutoLayout中,如何唯一确定元素在不同尺寸屏幕上的位置?

  • 既定方案,必须基于AutoLayout,至于AutoLayout与Frame的区别于优势,不做赘述.

  • 在不考虑多屏幕兼容的情况下, AutoLayout,可以直接使用固定的约束常量值来确定,但是 马上iPhone 7 都要出来了,指不定什么尺寸呢? 一个机型,一个UI代码?是不是想想都让人头大!

  • 考虑到多屏幕尺寸,UI设计图等比缩放的常用情况,我分享一个可以唯一确定UI元素的方案:


[subView makeConstraints:^(MASConstraintMaker *make) {
    UIView * superView = subView.superview;

    make.width.equalTo(superView).multipliedBy(subWidth / superWidth);
    make.height.equalTo(superView).multipliedBy(subHeight / superHeight);

    make.right.equalTo(superView).multipliedBy(subRight / superWidth);
    make.bottom.equalTo(superView).multipliedBy(subBottom / superHeight);
}];

以上代码,是整个代码的核心,其巧妙之处在于:不使用constant,而是使用比例来指定约束.选取的是 width,height,right,bottom,而不是其他属性,其巧妙之处,大家试用下其他属性就知道了.


核心代码,打造自己的视图模块库.

直接继承YFViewComponent类,然后实现类方法 subViewsConfig 即可.

//
//  YFViewComponent.h
//  testMasonry
//
//  Created by xiao on 16/6/6.
//  Copyright © 2016年 xiao. All rights reserved.
//

#import <UIKit/UIKit.h>

/**
 *  预定义常量的声明.
 */

//!< 同一设计图中,视图模块本身的宽度.
extern const NSString *  YFViewComponentSelfHolderWidthKey;
//!< 同一设计图中,视图模块本身的高度.
extern const NSString *  YFViewComponentSelfHolderHeightKey;
//!< 同一设计图中,模块的所有子视图.
extern const NSString *  YFViewComponentSubViewsKey;
//!< 子视图的类型.
extern const NSString *  YFViewComponentSubViewClassNameKey;
//!< 子视图对应的属性,模块中应有属性与其对应,且可通过此属性访问对应的子视图.
extern const NSString *  YFViewComponentSubViewPropNameKey;
//!< 同一设计图中,子视图的宽度.
extern const NSString *  YFViewComponentSubViewHolderWidthKey;
//!< 同一设计图中,子视图的高度.
extern const NSString *  YFViewComponentSubViewHolderHeightKey;
//!< 同一设计图中,子视图的右内边距值(right).
extern const NSString *  YFViewComponentSubViewHolderRightKey;
//!< 同一设计图中,子视图的底部边距值(bottom).
extern const NSString *  YFViewComponentSubViewHolderBottomKey;


@interface YFViewComponent : UIView

/**
 *  子视图配置信息.
 *
 *  子类应重写覆盖此方法.
 *
 一个示例:
 @{
     YFViewComponentSelfHolderWidthKey: @640.0,
     YFViewComponentSelfHolderHeightKey: @155.0,
     YFViewComponentSubViewsKey:
         @[@{
             YFViewComponentSubViewClassNameKey: NSStringFromClass([UIImageView class]) ,
             YFViewComponentSubViewPropNameKey: @"imageView", 
             YFViewComponentSubViewHolderWidthKey: @160, 
             YFViewComponentSubViewHolderHeightKey: @120, 
             YFViewComponentSubViewHolderBottomKey: @140,
             YFViewComponentSubViewHolderRightKey: @180
         }]
 }
 *
 *  @return 返回子视图的配置信息.
 */
+ (NSDictionary *) subViewsConfig;

@end
//
//  YFViewComponent.m
//  testMasonry
//
//  Created by xiao on 16/6/6.
//  Copyright © 2016年 xiao. All rights reserved.
//

#import "YFViewComponent.h"
#import "Masonry.h"

/**
 *  预定义常量的定义.
 */
const NSString *  YFViewComponentSelfHolderWidthKey = @"YFViewComponentSelfHolderWidthKey";
const NSString *  YFViewComponentSelfHolderHeightKey = @"YFViewComponentSelfHolderHeightKey";
const NSString *  YFViewComponentSubViewsKey = @"YFViewComponentSubViewsKey";
const NSString *  YFViewComponentSubViewClassNameKey = @"YFViewComponentSubViewClassNameKey";
const NSString *  YFViewComponentSubViewPropNameKey = @"YFViewComponentSubViewPropNameKey";
const NSString *  YFViewComponentSubViewHolderWidthKey = @"YFViewComponentSubViewHolderWidthKey";
const NSString *  YFViewComponentSubViewHolderHeightKey = @"YFViewComponentSubViewHolderHeightKey";
const NSString *  YFViewComponentSubViewHolderRightKey = @"YFViewComponentSubViewHolderRightKey";
const NSString *  YFViewComponentSubViewHolderBottomKey = @"YFViewComponentSubViewHolderBottomKey";

@implementation YFViewComponent

- (instancetype)init
{
    self = [super init];

    if (nil != self) {
        UIView * holderView = self;

        NSDictionary * config = [[self class] subViewsConfig];

        CGFloat superHeight = [[config objectForKey: YFViewComponentSelfHolderHeightKey] floatValue];
        CGFloat superWidth = [[config objectForKey: YFViewComponentSelfHolderWidthKey] floatValue];;

        NSArray * locatArray = [config objectForKey: YFViewComponentSubViewsKey];

        [locatArray enumerateObjectsUsingBlock:^(NSDictionary * obj, NSUInteger idx, BOOL *stop) {
            NSString * classString = [obj objectForKey: YFViewComponentSubViewClassNameKey];

            Class viewClass = NSClassFromString(classString);

            if (YES != [viewClass  isSubclassOfClass:[UIView class]]) {
                return;
            }

            UIView * subView = [[viewClass alloc] init];
            [holderView addSubview: subView];

            NSString * viewKey = [obj objectForKey: YFViewComponentSubViewPropNameKey];

            [holderView setValue: subView forKey: viewKey];

            CGFloat subWidth = [[obj objectForKey: YFViewComponentSubViewHolderWidthKey] floatValue];
            CGFloat subHeight = [[obj objectForKey: YFViewComponentSubViewHolderHeightKey] floatValue];
            CGFloat subBottom = [[obj objectForKey: YFViewComponentSubViewHolderBottomKey] floatValue];
            CGFloat subRight = [[obj objectForKey: YFViewComponentSubViewHolderRightKey] floatValue];

            [subView mas_makeConstraints:^(MASConstraintMaker *make) {
                UIView * superView = subView.superview;

                make.width.equalTo(superView).multipliedBy(subWidth / superWidth);
                make.height.equalTo(superView).multipliedBy(subHeight / superHeight);

                make.right.equalTo(superView).multipliedBy(subRight / superWidth);
                make.bottom.equalTo(superView).multipliedBy(subBottom / superHeight);
            }];
        }];
    }

    return self;
}


+ (NSDictionary *) subViewsConfig{
    return nil;
}

@end

一个示例: 仿网易新闻的新闻单元格.

网易原图

这个示例,取材自网易新闻.图示中已经标注了单元格的宽高,单元格内各个UI元素的width,height,bottom,right.此处UI设计师可根据屏幕尺寸出图,我们根据一份跟定的设计图,直接使用 MarkMan(一个非常好用的标准工具)丈量标记即可. 因为我们是基于比例来添加约束,不同屏幕下,会自动等比变换.

元素效果图

这是一个简单的示例,为了方便演示,临时加上了:

//
//  YFAutoTransView.h
//  testMasonry
//
//  Created by xiao on 16/6/6.
//  Copyright © 2016年 xiao. All rights reserved.
//

#import "YFViewComponent.h"

@interface YFAutoTransView : YFViewComponent

@property (weak, nonatomic) UIImageView * imageView;
@property (weak, nonatomic) UILabel * titleLabel;
@property (weak, nonatomic) UILabel * detailLabel;
@property (weak, nonatomic) UIButton * chatBtn;

@end

//
//  YFAutoTransView.m
//  testMasonry
//
//  Created by xiao on 16/6/6.
//  Copyright © 2016年 xiao. All rights reserved.
//

#import "YFAutoTransView.h"

@implementation YFAutoTransView

+ (NSDictionary *) subViewsConfig{

    NSNumber * holderWidth = @640.0;
    NSNumber * holderHeight = @155.0;

    NSArray * subConfig = @[
                            @[NSStringFromClass([UIImageView class]), @"imageView",
                              @160, @120, @140, @180],
                            @[NSStringFromClass([UILabel class]), @"titleLabel",
                              @420, @31, @55, @615],
                            @[NSStringFromClass([UILabel class]), @"detailLabel",
                              @410, @60, @136, @605],
                            @[NSStringFromClass([UIButton class]), @"chatBtn",
                              @120, @32, @141, @628]];

    NSMutableArray * subViewsConfig = [NSMutableArray arrayWithCapacity: 42];

    [subConfig enumerateObjectsUsingBlock:^(NSArray * obj, NSUInteger idx, BOOL *stop) {
        if (6 != obj.count) {
            return;
        }

        NSDictionary * configDict =
        @{
          YFViewComponentSubViewClassNameKey: obj[0],
          YFViewComponentSubViewPropNameKey: obj[1],

          YFViewComponentSubViewHolderWidthKey: obj[2],
          YFViewComponentSubViewHolderHeightKey: obj[3],
          YFViewComponentSubViewHolderBottomKey: obj[4],
          YFViewComponentSubViewHolderRightKey: obj[5]
          };

        [subViewsConfig addObject: configDict];
    }];

    NSDictionary * config = @{
                              YFViewComponentSelfHolderWidthKey: holderWidth,
                              YFViewComponentSelfHolderHeightKey: holderHeight,
                              YFViewComponentSubViewsKey: subViewsConfig};

    return config;
}

@end

这是与数据结合之后的效果图.只是个初稿,还需要进一步调试.也就是说,以后再写UI界面,你的注意力将可以集中在 数据与视图本身的交互处理上.

//
//  ViewController.m
//  testMasonry
//
//  Created by xiao on 16/6/6.
//  Copyright © 2016年 xiao. All rights reserved.
//

#import "ViewController.h"
#import "YFAutoTransView.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];


    YFAutoTransView * autoTestView = [[YFAutoTransView alloc] init];
    autoTestView.backgroundColor = [UIColor grayColor];

    autoTestView.frame = CGRectMake(0, 100, [UIScreen mainScreen].bounds.size.width, 155.0/2);

    autoTestView.imageView.image = [UIImage imageNamed:@"autoTrans.png"];
    autoTestView.imageView.backgroundColor = [UIColor yellowColor];

    autoTestView.titleLabel.text = @"爱马仕版苹果表开售8688元起";
    autoTestView.titleLabel.font = [UIFont systemFontOfSize:15];
    [autoTestView.titleLabel adjustsFontSizeToFitWidth];

    autoTestView.detailLabel.text = @"爱马仕版苹果表盘和表带并不会单独销售.";
    autoTestView.detailLabel.numberOfLines = 0;
    autoTestView.detailLabel.font = [UIFont systemFontOfSize:12];

    [autoTestView.chatBtn setTitle:@"跟帖" forState: UIControlStateNormal];
    autoTestView.chatBtn.backgroundColor = [UIColor greenColor];

    [self.view addSubview: autoTestView];
}


@end

运行结果:

这里写图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值