美文网首页视图控件
iOS搜索框下的标签选择Tag,或者行业选择,标签选择(UIBu

iOS搜索框下的标签选择Tag,或者行业选择,标签选择(UIBu

作者: cocoaAhda | 来源:发表于2017-05-18 14:31 被阅读402次

    最近一直忙两个项目,有段时间没有学习新东西了,感觉已经out了.项目中用到一个行业选择的功能.(本文参照SKTagView编写,不知道出自哪位大神)。效果图:↓
    (代码纯属copy,如有不适,同志,请坚持看完!)


    TaoBaoSearch.jpeg
    标签选择.png

    我开始的思路是用collectionView去实现,不过总觉的不太好,我这里需求是单选,总觉得交互会很麻烦,所以就扒了点代码,看了大神们的实现思路模仿一下。

    整体思路:首先是有三个组成部分分别是;

    Model : 来存储所创建的每个标签的属性(后续TagModel就代表Model);
    View : 盛放标签的View,类中计算标签的行数高度,初始化方法,便利构造器等一系列(后续TagView就代表这个View);
    Button: 自定义Btn通过Model进行相应设置,在View中创建Button;

    主题流程就是:

    1.创建TagView
    2.遍历数据源(需要展示的所有标签)创建TagModel(注意此时TagView只是创建视图并没有创建视图上的标签或者说Btn),当创建一个TagModel我们就去通知TagView去根据这个TagModel(包含btn属性)去创建Btn。下图解:↓

    注解.png

    下面代码:

    TagModel
    #DzTag.h
    #import <Foundation/Foundation.h>
    #import <UIKit/UIKit.h>
    @interface DzTag : NSObject
    @property (copy, nonatomic, nullable) NSString *text;
    // 需要设置Btn的title属性时候用这个
    @property (copy, nonatomic, nullable) NSAttributedString *attributedText;
    // 字体颜色
    @property (strong, nonatomic, nullable) UIColor *textColor;
    // btn背景色
    @property (strong, nonatomic, nullable) UIColor *bgColor;
    // 高亮背景色
    @property (strong, nonatomic, nullable) UIColor *highlightedBgColor;
    // 背景图片
    @property (strong, nonatomic, nullable) UIImage *bgImg;
    @property (strong, nonatomic, nullable) UIImage *sebgColor;
    // 圆角
    @property (assign, nonatomic) CGFloat cornerRadius;
    // 边框颜色
    @property (strong, nonatomic, nullable) UIColor *borderColor;
    // 边框宽度
    @property (assign, nonatomic) CGFloat borderWidth;
    // 内边距
    @property (assign, nonatomic) UIEdgeInsets padding;
    @property (strong, nonatomic, nullable) UIFont *font;
    @property (assign, nonatomic) CGFloat fontSize;
    //默认:YES
    @property (assign, nonatomic) BOOL enable;
    - (nonnull instancetype)initWithText: (nonnull NSString *)text;
    + (nonnull instancetype)tagWithText: (nonnull NSString *)text;
    @end
    
    #DzTag.m
    #import "DzTag.h"
    static const CGFloat kDefaultFontSize = 13.0;
    @implementation DzTag
    - (instancetype)init {
        self = [super init];
        if (self) {
            _fontSize   = kDefaultFontSize;
            _textColor  = [UIColor blackColor];
            _bgColor    = [UIColor whiteColor];
            _enable     = YES;
        }
        return self;
    }
    // 初始化方法
    - (instancetype)initWithText: (NSString *)text {
        self = [self init];
        if (self) {
            _text = text;
        }
        return self;
    }
    // 遍历构造器
    + (instancetype)tagWithText: (NSString *)text {
        return [[self alloc] initWithText: text];
    }
    @end
    
    自定义Button
    #DzTagButton.h
    
    #import <UIKit/UIKit.h>
    @class DzTag;
    @interface DzTagButton : UIButton
    + (nonnull instancetype)buttonWithTag: (nonnull DzTag *)tag;
    @end
    
    #DzTagButton.m
    #import "DzTagButton.h"
    #import "DzTag.h"
    
    @implementation DzTagButton
    
    // 创建Button,并且设置Button属性
    + (instancetype)buttonWithTag: (DzTag *)tag {
        // 创建
        DzTagButton *btn = [super buttonWithType:UIButtonTypeCustom];
        // 是否使用attributedText
        if (tag.attributedText) {
            [btn setAttributedTitle: tag.attributedText forState: UIControlStateNormal];
        } else {
            [btn setTitle: tag.text forState:UIControlStateNormal];
            [btn setTitleColor: tag.textColor forState: UIControlStateNormal];
            btn.titleLabel.font = tag.font ?: [UIFont systemFontOfSize: tag.fontSize];
        }
        btn.backgroundColor = tag.bgColor;
        btn.contentEdgeInsets = tag.padding;
        btn.titleLabel.lineBreakMode = NSLineBreakByTruncatingTail;
        // 设置背景图
        if (tag.bgImg) {
            [btn setBackgroundImage: tag.bgImg forState: UIControlStateNormal];
        }
        // 设置颜色
        if (tag.sebgColor) {
            [btn setBackgroundImage:tag.sebgColor forState:(UIControlStateSelected)];
        }
        // 设置边框颜色
        if (tag.borderColor) {
            btn.layer.borderColor = tag.borderColor.CGColor;
        }
        // 设置变宽宽度
        if (tag.borderWidth) {
            btn.layer.borderWidth = tag.borderWidth;
        }
        // 是否启用
        btn.userInteractionEnabled = tag.enable;
        // 是否要高亮效果
        if (tag.enable) {
            UIColor *highlightedBgColor = tag.highlightedBgColor ?: [self darkerColor:btn.backgroundColor];
            [btn setBackgroundImage:[self imageWithColor:highlightedBgColor] forState:UIControlStateHighlighted];
        }
        // Btn圆角
        btn.layer.cornerRadius = tag.cornerRadius;
        btn.layer.masksToBounds = YES;
        return btn;
    }
    // 根据颜色生成图片
    + (UIImage *)imageWithColor:(UIColor *)color {
        CGRect rect = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f);
        UIGraphicsBeginImageContext(rect.size);
        CGContextRef context = UIGraphicsGetCurrentContext();
        
        CGContextSetFillColorWithColor(context, [color CGColor]);
        CGContextFillRect(context, rect);
        
        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        
        return image;
    }
    // 如果有颜色就返回,没有就不要
    + (UIColor *)darkerColor:(UIColor *)color {
        CGFloat h, s, b, a;
        if ([color getHue:&h saturation:&s brightness:&b alpha:&a])
            return [UIColor colorWithHue:h
                              saturation:s
                              brightness:b * 0.85
                                   alpha:a];
        return color;
    }
    @end
    
    TagView
    #DzTagView.h
    
    #import <UIKit/UIKit.h>
    #import "DzTag.h"
    @class DzTagButton;
    @interface DzTagView : UIView
    #pragma mark -- 属性
    // 视图内边距
    @property (assign, nonatomic) UIEdgeInsets padding;
    // 行间距
    @property (assign, nonatomic) CGFloat lineSpacing;
    // 每个item间距
    @property (assign, nonatomic) CGFloat interitemSpacing;
    // 最大宽
    @property (assign, nonatomic) CGFloat preferredMaxLayoutWidth;
    //!< 固定宽度
    @property (assign, nonatomic) CGFloat regularWidth;
    //!< 固定高度
    @property (nonatomic,assign ) CGFloat regularHeight;
    // 单行模式
    @property (assign, nonatomic) BOOL singleLine;
    // block点击回调
    @property (copy, nonatomic, nullable) void (^didTapTagAtIndex)(NSUInteger index,UIButton * _Nullable btn);
    
    #pragma mark -- 方法
    // 创建方法
    - (void)addTag: (nonnull DzTag *)tag;
    // 添加item(指定位置添加)
    - (void)insertTag: (nonnull DzTag *)tag atIndex:(NSUInteger)index;
    // 移除item
    - (void)removeTag: (nonnull DzTag *)tag;
    // 根据位置移除
    - (void)removeTagAtIndex: (NSUInteger)index;
    // 移除所有
    - (void)removeAllTags;
    @end
    
    #DzTagView.m代码比较多我先总结下结构和声明周期.
    .m一共分三大部分 :↓
    ①:生命周期。
    ②:getter,setter。
    ③:一些共有方法包括创建·添加·删除item等。
    其他解释我想类中注释够用了,几乎每行都有了😆
    #核心:重写 intrinsicContentSize 为内容返回恰当的大小,无论何时有任何会影响固有内容尺寸的改变发生时,调用 invalidateIntrinsicContentSize进行更新(如果想要在app运行时改变 intrinsicContentSize,可以调用invalidateIntrinsicContentSize()方法来更新)
    #DzTagView.m
    
    #import "DzTagView.h"
    #import "DzTagButton.h"
    @interface DzTagView ()
    @property (strong, nonatomic, nullable) NSMutableArray *tags;
    //用来表示是否需要重新加载一次,一般添加或者删除item时候改变此值状态,用来重新加载,节省内存使用
    @property (assign, nonatomic) BOOL didSetup;
    @property (nonatomic,assign) BOOL isIntrinsicWidth;  //!<是否宽度固定
    @property (nonatomic,assign) BOOL isIntrinsicHeight; //!<是否高度固定
    @end
    @implementation DzTagView
    
    #pragma mark - public方法
    // NSParameterAssert() ↓
    // 断言评估一个条件,如果条件为 false
    // 调用当前线程的断点句柄
    // 每一个线程有它自已的断点句柄
    // 它是一个 NSAsserttionHandler类的对象。
    // 当被调用时,断言句柄打印一个错误信息,该条信息中包含了方法名、类名或函数名。
    // 然后,它就抛出一个 NSInternalInconsistencyException 异常
    
    // 创建item方法
    - (void)addTag: (DzTag *)tag {
        // 断言
        NSParameterAssert(tag);
        DzTagButton *btn = [DzTagButton buttonWithTag: tag];
        // 添加事件
        [btn addTarget: self action: @selector(onTag:) forControlEvents: UIControlEventTouchUpInside];
        [self addSubview: btn];
        [self.tags addObject: tag];
        // 更新布局
        self.didSetup = NO;
        [self invalidateIntrinsicContentSize];
    }
    
    // 在某位置添加tag
    - (void)insertTag: (DzTag *)tag atIndex: (NSUInteger)index {
        // 断言
        NSParameterAssert(tag);
        // 如果这个位置是在最后位置直接添加
        if (index + 1 > self.tags.count) {
            [self addTag: tag];
        } else {
            // 创建BTN
            DzTagButton *btn = [DzTagButton buttonWithTag: tag];
            // 添加事件
            [btn addTarget: self action: @selector(onTag:) forControlEvents: UIControlEventTouchUpInside];
            // 相应位置添加子视图
            [self insertSubview: btn atIndex: index];
            // 数据源相应位置添加tag
            [self.tags insertObject: tag atIndex: index];
            // 更新布局
            self.didSetup = NO;
            [self invalidateIntrinsicContentSize];
        }
    }
    // 根据tag删除item
    - (void)removeTag: (DzTag *)tag {
        // 断言
        NSParameterAssert(tag);
        // 根据tag获取相应index
        NSUInteger index = [self.tags indexOfObject: tag];
        //NSNotFound表示请求操作的某个内容或者item没有发现,或者不存在。
        if (NSNotFound == index) {
            return;
        }
        // 删除数据和UI
        [self.tags removeObjectAtIndex: index];
        if (self.subviews.count > index) {
            [self.subviews[index] removeFromSuperview];
        }
        // 重新布局
        self.didSetup = NO;
        [self invalidateIntrinsicContentSize];
    }
    
    // 根据index删除某个item
    - (void)removeTagAtIndex: (NSUInteger)index {
        // 越界保护
        if (index + 1 > self.tags.count) {
            return;
        }
        // 删除相应item
        [self.tags removeObjectAtIndex: index];
        // 删除UI
        if (self.subviews.count > index) {
            [self.subviews[index] removeFromSuperview];
        }
        // 重新布局
        self.didSetup = NO;
        [self invalidateIntrinsicContentSize];
    }
    // 删除所有item
    - (void)removeAllTags {
        // 先删除数据源
        [self.tags removeAllObjects];
        // 删除所有UI子视图
        for (UIView *view in self.subviews) {
            [view removeFromSuperview];
        }
        // 重新布局
        self.didSetup = NO;
        [self invalidateIntrinsicContentSize];
    }
    
    #pragma mark - 点击item响应事件
    - (void)onTag: (UIButton *)btn {
        if (self.didTapTagAtIndex) {
            self.didTapTagAtIndex([self.subviews indexOfObject: btn], btn);
        }
    }
    #pragma mark -- 生命周期
    //TODO: 生命周期开始 第①步
    // UIView的属性intrinsicContentSize 返回一个CGSize(用来在外部计算高度)
    -(CGSize)intrinsicContentSize {
        // 如果没有数据源一个item都没有则返回长宽高位置都是0
        if (!self.tags.count) {
            return CGSizeZero;
        }
        // 计算大小需要的属性
        NSArray *subviews = self.subviews;
        // 前一视图
        UIView  *previousView = nil;
        // 上边距
        CGFloat topPadding = self.padding.top;
        // 下边距
        CGFloat bottomPadding = self.padding.bottom;
        // 左边距
        CGFloat leftPadding = self.padding.left;
        // 右边距
        CGFloat rightPadding = self.padding.right;
        // item间距
        CGFloat itemSpacing = self.interitemSpacing;
        // 行间距
        CGFloat lineSpacing = self.lineSpacing;
        // 当前X
        CGFloat currentX = leftPadding;
        // 视图内在视图高度 / 最后返回
        CGFloat intrinsicHeight = topPadding;
        // 视图内在视图宽   / 最后返回
        CGFloat intrinsicWidth = leftPadding;
        
        // 如果非单行显示 并且最大宽度大于0
        if (!self.singleLine && self.preferredMaxLayoutWidth > 0) {
            
            // 行数
            NSInteger lineCount = 0;
            // 遍历subViews
            for (UIView *view in subviews) {
                // 获取子view的size
                CGSize size = view.intrinsicContentSize;
                // 宽度和高度通过参数的0或者非0来进行赋值,却别是否用固定宽度(三目)
                CGFloat width = self.isIntrinsicWidth ? self.regularWidth : size.width;
                CGFloat height = self.isIntrinsicHeight ? self.regularHeight: size.height;
                // 如果已经有item存在
                if (previousView) {
                    // 确定x
                    currentX += itemSpacing;
                    // 如果当前itemX + 新item宽度 + 右边距 < 视图最大宽度
                    if (currentX + width + rightPadding <= self.preferredMaxLayoutWidth) {
                        // 本行排列
                        currentX += width;
                    } else { // // 如果当前itemX + 新item宽度 + 右边距 < 视图最大宽度 则 换行添加
                        // 行数自加1
                        lineCount ++;
                        // 跟新x
                        currentX = leftPadding + width;
                        // 更新View高度
                        intrinsicHeight += height;
                    }
                } else { // 添加第一个item会走
                    lineCount ++;
                    // 更新宽高
                    intrinsicHeight += height;
                    currentX += width;
                }
                // 赋值前一view
                previousView = view;
                // 更新宽度
                intrinsicWidth = MAX(intrinsicWidth, currentX + rightPadding);
            }
            // 计算最终高度
            intrinsicHeight += bottomPadding + lineSpacing * (lineCount - 1);
        } else { // 单行显示时候计算size
            for (UIView *view in subviews) {
                CGSize size = view.intrinsicContentSize;
                intrinsicWidth += self.isIntrinsicWidth ? self.regularWidth : size.width;
            }
            // 最终宽高
            intrinsicWidth += itemSpacing * (subviews.count - 1) + rightPadding;
            intrinsicHeight += ((UIView *)subviews.firstObject).intrinsicContentSize.height + bottomPadding;
        }
        // 返回一个CGSize
        return CGSizeMake(intrinsicWidth, intrinsicHeight);
    }
    
    //TODO: 生命周期 第②步
    - (void)layoutSubviews {
        // 非单行显示
        if (!self.singleLine) {
            self.preferredMaxLayoutWidth = self.frame.size.width;
        }
        [super layoutSubviews];
        [self layoutTags];
    }
    
    //TODO: 生命周期 第③步
    // 内部布局(实现过程等同于intrinsicContentSize方法中的计算)
    - (void)layoutTags {
        if (self.didSetup || !self.tags.count) {
            return;
        }
        NSArray *subviews    = self.subviews;
        UIView *previousView = nil;
        CGFloat topPadding   = self.padding.top;
        CGFloat leftPadding  = self.padding.left;
        CGFloat rightPadding = self.padding.right;
        CGFloat itemSpacing  = self.interitemSpacing;
        CGFloat lineSpacing  = self.lineSpacing;
        CGFloat currentX = leftPadding;
        if (!self.singleLine && self.preferredMaxLayoutWidth > 0) {
            for (UIView *view in subviews) {
                CGSize size = view.intrinsicContentSize;
                CGFloat width1 = self.isIntrinsicWidth?self.regularWidth:size.width;
                CGFloat height1 = self.isIntrinsicHeight?self.regularHeight:size.height;
                if (previousView) {
                    //                CGFloat width = size.width;
                    currentX += itemSpacing;
                    if (currentX + width1 + rightPadding <= self.preferredMaxLayoutWidth) {
                        view.frame = CGRectMake(currentX, CGRectGetMinY(previousView.frame), width1, height1);
                        currentX += width1;
                    } else {
                        CGFloat width = MIN(width1, self.preferredMaxLayoutWidth - leftPadding - rightPadding);
                        view.frame = CGRectMake(leftPadding, CGRectGetMaxY(previousView.frame) + lineSpacing, width, height1);
                        currentX = leftPadding + width;
                    }
                } else {
                    CGFloat width = MIN(width1, self.preferredMaxLayoutWidth - leftPadding - rightPadding);
                    view.frame = CGRectMake(leftPadding, topPadding, width, height1);
                    currentX += width;
                }
                previousView = view;
            }
        } else {
            for (UIView *view in subviews) {
                CGSize size = view.intrinsicContentSize;
                view.frame = CGRectMake(currentX, topPadding, self.isIntrinsicWidth?self.regularWidth:size.width, self.isIntrinsicHeight?self.regularHeight:size.height);
                currentX += self.isIntrinsicWidth?self.regularWidth:size.width;
                previousView = view;
            }
        }
        self.didSetup = YES;
    }
    
    #pragma mark - setting getter方法
    // 数据源
    - (NSMutableArray *)tags {
        if(!_tags) {
            _tags = [NSMutableArray array];
        }
        return _tags;
    }
    // 最大宽度setter方法
    - (void)setPreferredMaxLayoutWidth: (CGFloat)preferredMaxLayoutWidth {
        if (preferredMaxLayoutWidth != _preferredMaxLayoutWidth) {
            _preferredMaxLayoutWidth = preferredMaxLayoutWidth;
            _didSetup = NO;
            [self invalidateIntrinsicContentSize];
        }
    }
    // 重写setter给bool赋值
    - (void)setRegularWidth:(CGFloat)intrinsicWidth{
        if (_regularWidth != intrinsicWidth) {
            _regularWidth = intrinsicWidth;
            if (intrinsicWidth == 0) {
                self.isIntrinsicWidth = NO;
            }else{
                self.isIntrinsicWidth = YES;
            }
        }
    }
    - (void)setRegularHeight:(CGFloat)intrinsicHeight{
        if (_regularHeight != intrinsicHeight) {
            _regularHeight = intrinsicHeight;
            if (intrinsicHeight == 0){
                self.isIntrinsicHeight = NO;
            }
            else{
                self.isIntrinsicHeight = YES;
            }
        }
    }
    
    @end
    
    

    Demo下载地址:https://github.com/rundonkey/DzTagView.git

    Eed

    技术交流互相学习QQ号412282037,每天进步一点点🙂
    

    相关文章

      网友评论

      • MoussyL:比较好奇,为什么大家都喜欢用富文本,个人感觉这里不太需要富文本,标签的文字和边框都可以直接设置色值
      • b8e6682d009f:git上的DzTagTools还是缺失,楼主发个demo链接,🙏
        cocoaAhda:@蓝涩枫 评论里有一个地址不对吗?
      • ttdiOS:下载后DzTagTools文件夹里显示红色,运行不了,copy失败,希望楼主上传完整的demo工程地址,感谢
        35b0fedb0676:@ttdiOS 请问还有吗
        ttdiOS:@cocoaAhda 好,谢了
        cocoaAhda:@ttdiOS 链接: https://pan.baidu.com/s/1gfh753l 密码: n7i3 你先下一下 比较忙
      • 黎希:可以,可以啊
      • 13b8e0e71388:😈😈

      本文标题:iOS搜索框下的标签选择Tag,或者行业选择,标签选择(UIBu

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