美文网首页
iOS 模仿某宝商品规格选择弹框

iOS 模仿某宝商品规格选择弹框

作者: Yuency | 来源:发表于2018-10-30 13:53 被阅读78次

    前言:

    代码生涯里总能碰到一些稍微复杂点的东西,先是花时间弄出来了,然后又发现跟不上需求了,调整了一下代码思路,然后是重构重写,最后应该是丢弃了这份代码。在这里做个笔记,下辈子应该也用不上。

    1.我们这是一个卖电子产品的项目
    2.我们有一个选择商品规格的页面(选颜色,内存,套餐,数量...)
    3.我们打算抄袭某宝的弹框
    4.这是目前我觉得最好的方案去实现这个页面
    5.过了两个星期,需求又改了,有些商品规格文字超长,要做多行显示。(***)

    效果图


    商品选择.gif

    其实我们的业务逻辑有点复杂,本次代码只是研究一下,这样的 UI 如何去写。大体上就跟 UITableView 一样。主要有两个类, 一个类 继承自 UIScrollView, 一个类继承自 UIView。

    以下是代码的重点部分,次要的代码没有贴出来,完整的示例代码在 https://github.com/gityuency/ObjectiveCTools

    上代码: ItemView.h

    1.协议:ItemViewDelegate 是当你点击了这个小格子触发的代理方法
    2.暴露 NSIndexPath 这个属性,这个方便索引当前view 位置(第几行的第几个)
    3.暴露选中和未选中属性,这个可以用来设置选中和未选中的样式
    4.暴露不可点击属性,用于设置不可点击样式
    5.暴露方法用于设置文本,字体,宽度,这是影响这个 View 大小的因素,所以放到外面设置。

    #import <UIKit/UIKit.h>
    @class ItemView;
    
    /// 视图的协议
    @protocol ItemViewDelegate <NSObject>
    
    @optional
    -(void)didSelected:(ItemView *)itemView;
    
    @end
    
    /// 格子
    @interface ItemView : UIView
    /// 按钮事件代理
    @property (nonatomic, weak) id<ItemViewDelegate> delegate;
    /// 是否是多行
    @property (nonatomic, assign) BOOL isMultiLines;
    /// 位置
    @property (nonatomic, strong) NSIndexPath *indexPath;
    /// 设置选中状态
    @property (nonatomic, assign) BOOL itemSelected;
    /// 设置不可选
    @property (nonatomic, assign) BOOL itemDisable;
    /// 设置 文本框的文字 文字的最小宽度 最大宽度 文字的字体
    - (void)setText:(NSString *)text minWith:(CGFloat)minWith maxWith:(CGFloat)maxWith font:(UIFont *)font;
    
    @end
    
    ItemView.m

    1.添加一个点击手势,子视图的用户交互都关闭
    2.在设置视图文字的时候,计算他们的宽度
    3.重写 setter 方法,去改变样式

    #import "ItemView.h"
    
    @interface ItemView ()
    
    /// 文本框
    @property (nonatomic, strong) UILabel *label;
    /// 装载内容
    @property (nonatomic, strong) UIView *contentView;
    /// 最小宽度
    @property (nonatomic, assign) CGFloat *minWidth;
    /// 选中的标题颜色  边框色
    @property (nonatomic, strong) UIColor *selectedTitleColro;
    /// 选中背景色
    @property (nonatomic, strong) UIColor *selectedBgColor;
    /// 未选中背景色
    @property (nonatomic, strong) UIColor *unSelectedBgColor;
    /// 未选中的标题颜色
    @property (nonatomic, strong) UIColor *unSelectedTitleColor;
    /// 不可点击的标题颜色
    @property (nonatomic, strong) UIColor *disableTitleColor;
    /// 点击事件(手势)
    @property (nonatomic, strong) UITapGestureRecognizer *g;
    
    @end
    
    @implementation ItemView
    
    - (instancetype)init {
        
        self = [super init];
        if (self) {
            
            _disableTitleColor = [UIColor grayColor];
            _selectedBgColor = [UIColor colorWithRed:1.00 green:0.91 blue:0.91 alpha:1.00];
            _unSelectedBgColor = [UIColor colorWithRed:0.95 green:0.95 blue:0.95 alpha:1.00];
            _unSelectedTitleColor = [UIColor darkTextColor];
            _selectedTitleColro = [UIColor redColor];
            _minWidth = 0;
            
            _contentView = [[UIView alloc] init];
            _contentView.userInteractionEnabled = NO;
            _contentView.layer.cornerRadius = 5;
            [self addSubview:_contentView];
            
            _label = [[UILabel alloc] init];
            _label.textAlignment = NSTextAlignmentCenter;
            _label.userInteractionEnabled = NO;
            _label.numberOfLines = 0;
            [self addSubview:_label];
            
            _isMultiLines = NO;
    
            _g = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(click)];
            _g.numberOfTapsRequired = 1;
            [self addGestureRecognizer:_g];
        }
        return self;
    }
    
    - (void)click{
        if (self.delegate && [self.delegate respondsToSelector:@selector(didSelected:)]) {
            [self.delegate didSelected:self];
        }
    }
    
    /// 设置 文本框的文字 文字的最小宽度 文字的字体
    - (void)setText:(NSString *)text minWith:(CGFloat)minWith maxWith:(CGFloat)maxWith font:(UIFont *)font {
        CGFloat offset = 10;
        
        NSDictionary *dic = @{NSFontAttributeName: font};
        
        CGFloat maxW = [UIScreen mainScreen].bounds.size.width - offset * 2;
        
        CGSize stringSize = [text boundingRectWithSize:CGSizeMake(maxW, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:dic context:nil].size;
        
        CGFloat singleLineWidth = [text sizeWithAttributes:dic].width;  //计算单行字符串长度
        
        if (singleLineWidth > maxW) { //判断是否多行
            stringSize.width = maxW;
            self.isMultiLines = YES;
        } else if (stringSize.width < minWith) {  //判断是否小于最小宽度
            stringSize.width = minWith;
        }
    
        self.label.frame = CGRectMake(offset, offset, stringSize.width, stringSize.height);
        self.contentView.frame = CGRectMake(offset * 0.5, offset * 0.5, stringSize.width + offset, stringSize.height + offset);
        self.frame = CGRectMake(0, 0, self.label.frame.size.width + offset * 2, self.label.frame.size.height + offset * 2);
        self.label.text = text;
        self.label.font = font;
    }
    
    - (void)setItemSelected:(BOOL)itemSelected {
        _itemSelected = itemSelected;
        if (_itemSelected) {
            [self.g setEnabled:YES];
            self.contentView.layer.borderWidth = 1;
            self.contentView.layer.borderColor = self.selectedTitleColro.CGColor;
            self.contentView.backgroundColor = self.selectedBgColor;
            self.label.textColor = self.selectedTitleColro;
        } else {
            [self.g setEnabled:YES];
            self.contentView.layer.borderWidth = 0;
            self.contentView.backgroundColor = self.unSelectedBgColor;
            self.label.textColor = self.unSelectedTitleColor;
        }
    }
    
    - (void)setItemDisable:(BOOL)itemDisable {
        _itemDisable = itemDisable;
        if (_itemDisable) {
            [self.g setEnabled:NO];
            self.contentView.layer.borderWidth = 0;
            self.contentView.backgroundColor = self.unSelectedBgColor;
            self.label.textColor = self.disableTitleColor;
        }
    }
    
    @end
    
    ItemCollectionView.h

    1.仿照 UITableView 写一下代理,必选方法得到 个数,格子是什么, 可选方法得到 头视图,点击事件,行数。
    2.暴露一个方法用于创建这个视图。

    #import <UIKit/UIKit.h>
    #import "ItemView.h"
    
    @class ItemCollectionView;
    
    /// 视图的协议
    @protocol ItemViewDataSource <NSObject>
    @required
    /// 每一个格子是什么
    - (ItemView *)itemCollectionView:(ItemCollectionView *)itemCollectionView cellForRowAtIndexPath:(NSIndexPath *)indexpath;
    /// 每一行有多少个格子
    - (NSInteger)itemCollectionView:(ItemCollectionView *)itemCollectionView numberOfRowsInSection:(NSInteger)section;
    @optional
    /// 一共有多少行
    - (NSInteger)numberOfSectionsAt:(ItemCollectionView *)itemCollectionView;
    /// 设置每一行的头视图
    - (UIView *)itemCollectionView:(ItemCollectionView *)itemCollectionView headerInSection:(NSInteger)section;
    /// 点击事件
    - (void)itemCollectionView:(ItemCollectionView *)itemCollectionView didSelectedIndexPath:(NSIndexPath *)indexpath;
    @end
    
    /// 装载所有的小格子
    @interface ItemCollectionView : UIScrollView
    /// 数据代理
    @property (nonatomic, weak) id<ItemViewDataSource> dataSource;
    /// 重新创作视图
    - (void)createCollectionView;
    @end
    
    ItemCollectionView.m
    1. 考虑到初始化可能使用代码或者 XIB,所以两个初始化经过的函数都调用了一个公共的初始化属性方法 setUp

    2.在方法 createCollectionView 里面先强制进行布局,来得到实际的尺寸,因为从 XIB 加载会有情况还没拿到尺寸就开始布局了

    3.在initView方法里面创建这些小格子。考虑到转屏等一些影响布局的因素, 每次进这个函数的时候,就把原来已经创建的格子全部扔掉,重新再来。这当然是一个不好的设计,但是没有去优化了。如果因为数据的改变(比如文字长度变了)那还是得重新算,干脆都重新算,少写点代码。

    4.两层For循环,然后从代理函数里面去取得这个格子,就可以拿到这个格子的尺寸。当然个数也都能拿到。把这些小格子都放到数组里面。

    5.当有格子被点击了,点击的格子设置为选中状态,其余的格子设置为未选中状态。在小格子的代理方法里面, 直接取得小格子属性 indexPath.section, 然后在小格子数字里找到对应的行,循环遍历,先全都设置为未选中(不可选的格子跳过),再把点击的格子设置为选中。

    #import "ItemCollectionView.h"
    
    @interface ItemCollectionView () <ItemViewDelegate>
    
    /// 内容视图
    @property (nonatomic, strong) UIView *contentView;
    /// 每一行有多少个 默认 0 个
    @property (nonatomic, assign) CGFloat rowCountOfSection;
    /// 一共有多少行 默认1行
    @property (nonatomic, assign) CGFloat sectionCount;
    /// 临时数组
    @property (nonatomic, strong) NSMutableArray *viewsArray;
    /// 内部控件集合
    @property (nonatomic, strong) NSArray *itemViewArray;
    
    @end
    
    @implementation ItemCollectionView
    
    - (instancetype)initWithFrame:(CGRect)frame {
        self = [super initWithFrame:frame];
        if (self) {
            [self setUp];
        }
        return self;
    }
    
    - (void)awakeFromNib {
        [super awakeFromNib];
        [self setUp];
    }
    
    - (void)setUp {
        _rowCountOfSection = 0;
        _sectionCount = 1;
        _viewsArray = [NSMutableArray array];
    }
    
    - (void)initView {
        
        if (self.contentView != nil) {
            [self.contentView removeFromSuperview];
        }
        self.contentView = [[UIView alloc] init];
        [self addSubview:self.contentView];
        
        /// 有多少行, 可选代理
        if (self.dataSource && [self.dataSource respondsToSelector:@selector(numberOfSectionsAt:)]) {
            self.sectionCount = [self.dataSource numberOfSectionsAt:self];
        }
        
        /// 每一行有多少个格子,必选
        if (self.dataSource) {
            BOOL a = [self.dataSource respondsToSelector:@selector(itemCollectionView:numberOfRowsInSection:)];
            BOOL b = [self.dataSource respondsToSelector:@selector(itemCollectionView:cellForRowAtIndexPath:)];
            NSAssert((a && b), @"代理对象没有遵守协议!");
        } else {
            NSAssert(NO, @"代理对象为空!");
        }
        
        CGFloat allSectionHeight = 0;
        for (NSInteger i = 0; i < self.sectionCount; i++) {
            UIView *sectionView = [[UIView alloc] init];
            UIView *headView = [self.dataSource itemCollectionView:self headerInSection:i];
            headView.frame = CGRectMake(headView.frame.origin.x, 0, self.bounds.size.width, headView.frame.size.height);
            [sectionView addSubview:headView];
            CGFloat offsetY = headView.frame.size.height;
            UIView *lastrowCell;
            
            self.rowCountOfSection = [self.dataSource itemCollectionView:self numberOfRowsInSection:i];
            
            NSMutableArray *array = [NSMutableArray array];
            
            ItemView *lastCell = nil; //最后一个单元
            
            for (NSInteger k = 0; k < self.rowCountOfSection; k++) {
                NSIndexPath *index = [NSIndexPath indexPathForRow:k inSection:i];
                ItemView *rowCell = [self.dataSource itemCollectionView:self cellForRowAtIndexPath: index];
                rowCell.delegate = self;
                rowCell.indexPath = index;
                
                
                if (lastCell) {  //最后一个单元存在
                    
                    if (CGRectGetMaxX(lastCell.frame) + rowCell.frame.size.width > self.bounds.size.width) { //新来的这个在后面放不下
                        
                        offsetY += lastCell.frame.size.height;  //放不下就放到下一行
                        
                        rowCell.frame = CGRectMake(0, offsetY, rowCell.frame.size.width, rowCell.frame.size.height);
                        
                    } else {
                        
                        if (lastCell.isMultiLines) { //上一个是多行, 当前就另起一行
                            offsetY += lastCell.frame.size.height;
                        }
                        
                        rowCell.frame = CGRectMake(CGRectGetMaxX(lastCell.frame), offsetY, rowCell.frame.size.width, rowCell.frame.size.height);
                    }
                    
                } else { //最后一个单元不存在
                    
                    rowCell.frame = CGRectMake(0, offsetY, rowCell.frame.size.width, rowCell.frame.size.height);
                }
                
                lastCell = rowCell;
    
                
                [sectionView addSubview:rowCell];
                lastrowCell = rowCell;
                [array addObject:rowCell];
            }
            CGFloat sectionHeight = CGRectGetMaxY(lastrowCell.frame) + 5; // +5是为了底部多留点空白
            sectionView.frame = CGRectMake(0, allSectionHeight, self.bounds.size.width, sectionHeight);
            [self.contentView addSubview:sectionView];
            allSectionHeight += sectionHeight;
            
            if (i != self.sectionCount - 1) { //最后一行不要分割线
                UIView *line = [[UIView alloc] initWithFrame:CGRectMake(0, sectionView.frame.size.height - 1, sectionView.frame.size.width, 1)];
                line.backgroundColor = [UIColor darkTextColor];
                [sectionView addSubview:line];
            }
            [self.viewsArray addObject:array];
        }
        self.contentView.frame = CGRectMake(0, 0, self.bounds.size.width, allSectionHeight);
        self.contentSize = self.contentView.frame.size;
        self.itemViewArray = self.viewsArray;
    }
    
    
    #pragma mark - ItemViewDelegate
    - (void)didSelected:(ItemView *)itemView {
        
        for (ItemView *v in self.itemViewArray[itemView.indexPath.section]) {
            if (v.itemDisable) { continue; }
            v.itemSelected = NO;
        }
        itemView.itemSelected = YES;
        
        if (self.dataSource && [self.dataSource respondsToSelector:@selector(itemCollectionView:didSelectedIndexPath:)]) {
            [self.dataSource itemCollectionView:self didSelectedIndexPath:itemView.indexPath];
        }
    }
    
    - (void)createCollectionView {
        [self setNeedsLayout];
        [self layoutIfNeeded];
        [self initView];
    }
    
    @end
    

    相关文章

      网友评论

          本文标题:iOS 模仿某宝商品规格选择弹框

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