美文网首页iOS-SDK开发
IOS基础:常见的自定义视图

IOS基础:常见的自定义视图

作者: 时光啊混蛋_97boy | 来源:发表于2020-10-20 09:35 被阅读0次

    原创:知识点总结性文章
    创作不易,请珍惜,之后会持续更新,不断完善
    个人比较喜欢做笔记和写总结,毕竟好记性不如烂笔头哈哈,这些文章记录了我的IOS成长历程,希望能与大家一起进步
    温馨提示:由于简书不支持目录跳转,大家可通过command + F 输入目录标题后迅速寻找到你所需要的内容

    目录

    • Toast
    • 按钮垂直或者水平布局的提示框
    • 带图片的提示框
    • 窗帘
    • 侧边栏
    • 全屏视图
    • 分享视图
    • 登陆注册视图
    • 轮播图
    • 时间选择器
    • Demo
    • 参考文献

    Toast

    1、运行效果

    toast

    添加toastView.toastType = @"success";语句后运行效果如下:

    success

    2、使用Toast

    @implementation ToastViewController
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        
        ToastView *toastView = [[ToastView alloc] initWithFrame:CGRectMake(100, 300, 200, 50)];
        toastView.duration = 5;
        [toastView showToast:^{
            NSLog(@"提示");
        }];
        [self.view addSubview:toastView];
    }
    
    @end
    

    3、提供的接口

    @interface ToastView : UIView
    
    @property (nonatomic, strong, readwrite) NSString *toastType;
    @property (nonatomic, strong, readonly) UILabel *succeedToastLabel;
    @property (nonatomic, strong, readonly) UILabel *toastLabel;
    @property (nonatomic, assign, readwrite) CGFloat duration;
    
    - (void)showToast:(void(^)(void))completion;
    
    @end
    

    4、显示Toast

    - (void)showToast:(void (^)(void))completion
    {
        if (self.duration == 0.0)
        {
            self.duration = 1.0;
        }
        
        if ([self.toastType isEqualToString:@"success"])
        {
            [UIView animateWithDuration:self.duration delay:0.0 options:UIViewAnimationOptionCurveEaseIn animations:^{
                self.succeedToast.alpha = 1.0;;
            } completion:^(BOOL finished) {
                [UIView animateWithDuration:self.duration animations:^{
                    self.succeedToast.alpha = 0.0;
                } completion:^(BOOL finished) {
                    completion();
                }];
            }];
        }
        else
        {
            [UIView animateWithDuration:self.duration delay:0.0 options:UIViewAnimationOptionCurveEaseIn animations:^{
                self.toast.alpha = 1.0;;
            } completion:^(BOOL finished) {
                [UIView animateWithDuration:self.duration animations:^{
                    self.toast.alpha = 0.0;
                } completion:^(BOOL finished) {
                    completion();
                }];
            }];
        }
    }
    

    按钮垂直或者水平布局的提示框

    1、运行效果

    2020-11-09 16:16:43.370131+0800 UseUIControlFramework[92556:7149086] 城市切换的提示框
    2020-11-09 16:59:09.136253+0800 UseUIControlFramework[92556:7149086] 点击了取消按钮:取消
    2020-11-09 16:59:09.812307+0800 UseUIControlFramework[92556:7149086] 点击了提交按钮:提交
    
    垂直提示框 水平提示框

    2、提供的接口

    提示按钮

    @interface CustomAlertButton : UIButton
    
    /// 工厂方法初始化Button
    + (instancetype)buttonWithTitle:(NSString *)title handler:(void (^)(CustomAlertButton *button))handler;
    
    /// 线条颜色
    @property (nonatomic, assign) UIColor *lineColor;
    /// 线条宽度
    @property (nonatomic, assign) CGFloat lineWidth;
    /// 边缘留白 top -> 间距 / bottom -> 最底部留白(根据不同情况调整不同间距)
    @property (nonatomic, assign) UIEdgeInsets edgeInsets;
    
    @end
    

    提示框视图

    @interface CustomAlertView : UIView
    
    /// 标题
    @property (nonatomic, strong, readonly) UILabel *titleLabel;
    
    /// 内容
    @property (nonatomic, strong, readonly) UILabel *messageLabel;
    
    /// 初始化
    - (instancetype)initWithTitle:(nullable NSString *)title
                          message:(nullable NSString *)message
                    constantWidth:(CGFloat)constantWidth;
    
    /// 子视图按钮的高度,默认49
    @property (nonatomic, assign) CGFloat subOverflyButtonHeight;
    
    /// 纵向依次向下添加提示按钮
    - (void)addAlertButton:(CustomAlertButton *)alertButton;
    
    /// 水平方向两个提示按钮
    - (void)adjoinWithLeftAction:(CustomAlertButton *)leftAction rightAction:(CustomAlertButton *)rightAction;
    
    @end
    

    3、调用方式

    使用方式的代码见:IOS框架:使用UI控件类框架中的zhPopupController

    创建水平提示框

    - (CustomAlertView *)createHorizontalAlertView
    {
        CustomAlertView *alertView = [self switchCitiesAlert];
        alertView.layer.cornerRadius = 3;
        alertView.titleLabel.font = [UIFont boldSystemFontOfSize:18];
        alertView.messageLabel.textColor = [UIColor blackColor];
        alertView.messageLabel.font = [UIFont fontWithName:@"pingFangSC-light" size:16];
        
        CustomAlertButton *cancelButton = [CustomAlertButton buttonWithTitle:@"取消" handler:^(CustomAlertButton *button) {
            NSLog(@"点击了取消按钮:%@",button.currentTitle);
        }];
        
        __weak typeof(self) weakSelf = self;
        CustomAlertButton *okButton = [CustomAlertButton buttonWithTitle:@"确定" handler:^(CustomAlertButton * button) {
            [weakSelf.switchCitiesStyle dismiss];
        }];
        
        cancelButton.lineColor = [UIColor colorWithHex:@"0x70AFCE"];
        okButton.lineColor = [UIColor colorWithHex:@"0x70AFCE"];
        [cancelButton setTitleColor:[UIColor colorWithHex:@"0x70AFCE"] forState:UIControlStateNormal];
        [okButton setTitleColor:[UIColor colorWithHex:@"0x70AFCE"] forState:UIControlStateNormal];
        cancelButton.edgeInsets = UIEdgeInsetsMake(22, 0, 0, 0);
        
        [alertView adjoinWithLeftAction:cancelButton rightAction:okButton];
        
        return alertView;
    }
    

    创建垂直提示框

    - (CustomAlertView *)createVerticalAlertView
    {
        ......
        [alertView addAlertButton:cancelButton];
        [alertView addAlertButton:submitButton];
        [alertView addAlertButton:okButton];
        
        return alertView;
    }
    

    4、提示框按钮的实现

    a、创建按钮视图
    + (instancetype)buttonWithTitle:(NSString *)title handler:(void (^)(CustomAlertButton *))handler
    {
        return [[self alloc] initWithTitle:title handler:handler];
    }
    
    - (instancetype)initWithTitle:(NSString *)title handler:(void (^)(CustomAlertButton *))handler
    {
        if (self = [super init])
        {
            self.buttonClickedBlock = handler;
            
            self.titleLabel.font = [UIFont systemFontOfSize:17];
            self.titleLabel.adjustsFontSizeToFitWidth = YES;
            [self setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
            [self setTitleColor:[UIColor darkGrayColor] forState:UIControlStateHighlighted];
            [self setTitle:title forState:UIControlStateNormal];
            [self addTarget:self action:@selector(handlerClicked) forControlEvents:UIControlEventTouchUpInside];
            
            _horizontalLine = [CALayer layer];
            _horizontalLine.backgroundColor = [UIColor colorWithHex:@"bfbfbf"].CGColor;
            [self.layer addSublayer:_horizontalLine];
            
            _verticalLine = [CALayer layer];
            _verticalLine.backgroundColor = [UIColor colorWithHex:@"bfbfbf"].CGColor;
            [self.layer addSublayer:_verticalLine];
        }
        return self;
    }
    
    b、点击按钮的回调
    - (void)handlerClicked
    {
        if (self && self.buttonClickedBlock)
        {
            self.buttonClickedBlock(self);
        }
    }
    
    c、线条宽度可配置
    - (void)layoutSubviews
    {
        [super layoutSubviews];
        
        CGFloat lineWidth = self.lineWidth > 0 ? self.lineWidth : 1 / [UIScreen mainScreen].scale;
        _horizontalLine.frame = CGRectMake(0, 0, self.width, lineWidth);
        _verticalLine.frame = CGRectMake(0, 0, lineWidth, self.height);
    }
    
    d、线条颜色可配置
    - (void)setLineColor:(UIColor *)lineColor
    {
        _lineColor = lineColor;
        _verticalLine.backgroundColor = lineColor.CGColor;
        _horizontalLine.backgroundColor = lineColor.CGColor;
    }
    

    5、提示框视图的实现

    a、创建提示框视图
    - (instancetype)initWithTitle:(NSString *)title message:(NSString *)message constantWidth:(CGFloat)constantWidth
    {
        if (self = [super init])
        {
            self.backgroundColor = [UIColor whiteColor];
            self.layer.cornerRadius = 5;// 圆角
            self.clipsToBounds = NO;
            
            // 子视图按钮的高度,默认49
            self.subOverflyButtonHeight = 49;
            
            // 默认宽度200
            _contentSize.width = 200;
            if (constantWidth > 0) _contentSize.width = constantWidth;
            
            
            _paddingTop = 15; _paddingBottom = 15; _paddingLeft = 20; _spacing = 15;
            
            if (title.length)
            {
                _titleLabel = [[UILabel alloc] init];
                _titleLabel.text = title;
                _titleLabel.numberOfLines = 0;
                _titleLabel.textAlignment = NSTextAlignmentCenter;
                _titleLabel.font = [UIFont systemFontOfSize:22];
                [self addSubview:_titleLabel];
    
                _titleLabel.size = [_titleLabel sizeThatFits:CGSizeMake(_contentSize.width - 2 * _paddingLeft, MAXFLOAT)];
                _titleLabel.y = _paddingTop;
                _titleLabel.centerX = _contentSize.width / 2;
                _contentSize.height = _titleLabel.bottom;
            }
    
            if (message.length)
            {
                _messageLabel = [[UILabel alloc] init];
                _messageLabel.numberOfLines = 0;
                _messageLabel.font = [UIFont systemFontOfSize:16];
                _messageLabel.textColor = [UIColor grayColor];
                [self addSubview:_messageLabel];
                
                NSMutableAttributedString *string = [[NSMutableAttributedString alloc] initWithString:message];
                NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
                paragraphStyle.lineSpacing = 5;
                [string addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:NSMakeRange(0, message.length)];
                _messageLabel.attributedText = string;
    
                _messageLabel.size = [_messageLabel sizeThatFits:CGSizeMake(_contentSize.width - 2 * _paddingLeft, MAXFLOAT)];
                _messageLabel.y = _titleLabel.bottom + _spacing;
                _messageLabel.centerX = _contentSize.width / 2;
                _contentSize.height = _messageLabel.bottom;
            }
         
            self.size = CGSizeMake(_contentSize.width, _contentSize.height + _paddingBottom);
            if (!title.length && !message.length)
            {
                self.size = CGSizeZero;
            }
        }
        return self;
    }
    
    b、清空提示按钮
    - (void)clearAlertButtons:(NSArray *)subviews
    {
        for (UIView *subview in subviews)
        {
            if ([subview isKindOfClass:[CustomAlertButton class]])
            {
                [subview removeFromSuperview];
            }
        }
    }
    
    c、纵向依次向下添加提示按钮
    - (void)addAlertButton:(CustomAlertButton *)alertButton
    {
        // 清空提示按钮
        [self clearAlertButtons:self.adjoinAlertButtons.allObjects];
        [self.adjoinAlertButtons removeAllObjects];
        
        void (^layout)(CGFloat) = ^(CGFloat top){
            CGFloat width = self->_contentSize.width - alertButton.edgeInsets.left - alertButton.edgeInsets.right;
            alertButton.size = CGSizeMake(width, self.subOverflyButtonHeight);
            alertButton.y = top;
            alertButton.centerX = self->_contentSize.width / 2;
        };
        
        CustomAlertButton *lastAlertButton = objc_getAssociatedObject(self, AlertViewActionKey);
        if (lastAlertButton)// current
        {
            if (![alertButton isEqual:lastAlertButton])
            {
                layout(lastAlertButton.bottom + alertButton.edgeInsets.top);
            }
        }
        else// first
        {
            // 增加10间距
            layout(_contentSize.height + alertButton.edgeInsets.top + 10);
        }
        
        alertButton.verticalLine.hidden = YES;
        [self insertSubview:alertButton atIndex:0];
        self.size = CGSizeMake(_contentSize.width, alertButton.bottom + alertButton.edgeInsets.bottom);
        objc_setAssociatedObject(self, AlertViewActionKey, alertButton, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    
    d、水平方向两个提示按钮
    - (void)adjoinWithLeftAction:(CustomAlertButton *)leftAction rightAction:(CustomAlertButton *)rightAction
    {
        // 清空提示按钮
        [self clearAlertButtons:self.subviews];
        objc_setAssociatedObject(self, AlertViewActionKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        
        leftAction.size = CGSizeMake(_contentSize.width / 2, self.subOverflyButtonHeight);
        leftAction.y = _contentSize.height + leftAction.edgeInsets.top; 
        
        rightAction.frame = leftAction.frame;
        rightAction.x = leftAction.right;
        rightAction.verticalLine.hidden = NO;
        [self addSubview:leftAction];
        [self addSubview:rightAction];
        
        
        self.adjoinAlertButtons = [NSMutableSet setWithObjects:leftAction, rightAction, nil];
        self.size = CGSizeMake(_contentSize.width, leftAction.bottom);
    }
    

    带图片的提示框

    1、运行效果

    2020-11-11 15:14:44.412847+0800 UseUIControlFramework[39522:8695494] 展示火箭视图
    2020-11-11 15:14:45.804308+0800 UseUIControlFramework[39522:8695494] 点击了查看详情
    
    带图片的Alert View

    2、使用方式

    使用方式的代码见:IOS框架:使用UI控件类框架中的zhPopupController

    a、创建Alert View视图
    - (OverflyView *)overflyView
    {
        // 设置标题属性
        NSMutableAttributedString *attributedTitle = [self setupAttributedTitle:@"通知" subTitle:@"一大波福利即将到来~"];
        
        // 设置内容属性
        NSMutableAttributedString *attributedMessage = [self setupAttributedMessage:@"如果你有一个气球,而你在它的表面画上许多黑点。然后你愈吹它,那些黑点就分得愈开。这就是宇宙间各银河系所发生的现象。我们说宇宙在扩张。"];
        
        // 为使对称透明区域视觉上更加美观,需要设置顶部图片透明区域所占比,无透明区域设置为0即可
        // highlyRatio = 图片透明区域高度 / 图片高度
        CGFloat transparentHeight = 450; // 已知透明区域高度
        UIImage *fireImage = [UIImage imageNamed:@"fire_arrow"];
        OverflyView *overflyView = [[OverflyView alloc] initWithFlyImage:fireImage highlyRatio:(transparentHeight / fireImage.size.height) attributedTitle:attributedTitle attributedMessage:attributedMessage constantWidth:260];
        overflyView.layer.cornerRadius = 4;
        overflyView.messageEdgeInsets = UIEdgeInsetsMake(5, 22, 10, 22);
        overflyView.titleLabel.backgroundColor = [UIColor whiteColor];
        overflyView.titleLabel.textAlignment = NSTextAlignmentCenter;
        overflyView.splitLine.hidden = YES;
        
        return overflyView;
    }
    
    b、创建Alert Button
    - (OverflyView *)createOverflyView
    {
        OverflyView *overflyView = [self overflyView];
        
        __weak typeof(self) weakSelf = self;
        OverflyButton *ignoreOverflyButton = [OverflyButton buttonWithTitle:@"忽略" handler:^(OverflyButton * _Nonnull button) {
            [weakSelf.overflyStyle dismiss];
        }];
        [ignoreOverflyButton setTitleColor:[UIColor grayColor] forState:UIControlStateNormal];
        
        OverflyButton *viewDetailsOverflyButton = [OverflyButton buttonWithTitle:@"查看详情" handler:^(OverflyButton * _Nonnull button) {
            NSLog(@"点击了查看详情");
        }];
        [viewDetailsOverflyButton setTitleColor:[UIColor colorWithRed:236/255.0 green:78/255.0 blue:39/255.0 alpha:1.0] forState:UIControlStateNormal];
        
        [overflyView adjoinWithLeftOverflyButton:ignoreOverflyButton rightOverflyButton:viewDetailsOverflyButton];
        
        return overflyView;
    }
    

    3、提供的接口

    a、Alert Button提供的接口
    @interface OverflyButton : UIButton
    
    + (instancetype)buttonWithTitle:(NSString *)title handler:(void (^)(OverflyButton *button))handler;
    
    /// 线条颜色
    @property (nonatomic, assign) UIColor *lineColor;
    /// 线宽
    @property (nonatomic, assign) CGFloat lineWidth;
    /// 边缘留白 top -> 间距 / bottom -> 最底部留白(根据不同情况调整不同间距)
    @property (nonatomic, assign) UIEdgeInsets flyEdgeInsets;
    
    @end
    
    b、Alert View提供的接口
    @interface OverflyView : UIView
    
    /// 顶部image
    @property (nonatomic, strong) UIImageView *flyImageView;
    /// 标题
    @property (nonatomic, strong, readonly) UILabel *titleLabel;
    /// 消息文本
    @property (nonatomic, strong, readonly) UILabel *messageLabel;
    /// 分割线
    @property (nonatomic, strong, readonly) CALayer *splitLine;
    
    /// 顶部图片透明区域所占比
    @property (nonatomic, assign) CGFloat highlyRatio;
    /// 可视滚动区域高,默认200 (当message文本内容高度小于200时,则可视滚动区域等于文本内容高度)
    @property (nonatomic, assign) CGFloat visualScrollableHight;
    /// 消息文本边缘留白,默认UIEdgeInsetsMake(15, 15, 15, 15)
    @property (nonatomic, assign) UIEdgeInsets messageEdgeInsets;
    /// 子视图按钮(OverflyButton)的高度,默认49
    @property (nonatomic, assign) CGFloat subOverflyButtonHeight;
    
    /**
     * @param flyImage 顶部image
     * @param highlyRatio 为使对称透明区域视觉上更加美观,需要设置顶部图片透明区域所占比,无透明区域设置为0即可 ( highlyRatio = 图片透明区域高度 / 图片高度 )
     * @param attributedTitle 富文本标题
     * @param attributedMessage 消息文本
     * @param constantWidth 自动计算内部各组件高度,最终zhOverflyView视图高等于总高度
     */
    - (instancetype)initWithFlyImage:(UIImage *)flyImage
                         highlyRatio:(CGFloat)highlyRatio
                     attributedTitle:(NSAttributedString *)attributedTitle
                   attributedMessage:(NSAttributedString *)attributedMessage
                       constantWidth:(CGFloat)constantWidth;
    
    /// 竖直方向添加一个按钮,可增加多个按钮依次向下排列
    - (void)addOverflyButton:(OverflyButton *)button;
    
    /// 水平方向两个并列按钮
    - (void)adjoinWithLeftOverflyButton:(OverflyButton *)leftButton rightOverflyButton:(OverflyButton *)rightButton;
    
    @end
    

    4、实现接口方法

    a、初始化方法
    - (instancetype)initWithFlyImage:(UIImage *)flyImage
                         highlyRatio:(CGFloat)highlyRatio
                     attributedTitle:(NSAttributedString *)attributedTitle
                   attributedMessage:(NSAttributedString *)attributedMessage
                       constantWidth:(CGFloat)constantWidth
    {
        if (self = [super init])
        {
            self.backgroundColor = [UIColor whiteColor];
            self.width = constantWidth;// 视图宽度
            self.highlyRatio = highlyRatio;// 顶部图片透明区域所占比
            // 消息文本边缘留白,默认UIEdgeInsetsMake(15, 15, 15, 15)
            self.messageEdgeInsets = UIEdgeInsetsMake(15, 15, 15, 15);
            // 子视图按钮(OverflyButton)的高度,默认49
            self.subOverflyButtonHeight = 49;
            // 可视滚动区域高,默认200(当message文本内容高度小于200时,则可视滚动区域等于文本内容高度)
            self.visualScrollableHight = 200;
            
            // 创建子视图
            [self createSubviews];
            
            _flyImageView.image = flyImage;
            _titleLabel.attributedText = attributedTitle;
            _messageLabel.attributedText = attributedMessage;
            
            // 创建子视图的约束
            [self createSubviewConstraints];
        }
        return self;
    }
    
    b、水平方向两个并列按钮
    - (void)adjoinWithLeftOverflyButton:(OverflyButton *)leftButton rightOverflyButton:(OverflyButton *)rightButton
    {
        // 清除按钮
        [self clearOverflyButtons:self.adjoinOverflyButtons.allObjects];
        objc_setAssociatedObject(self, OverflyViewActionKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        
        // 左边按钮的尺寸
        leftButton.size = CGSizeMake(self.overflyViewSize.width / 2, self.subOverflyButtonHeight);
        leftButton.y = self.overflyViewSize.height;// 容器尺寸
        
        // 右边按钮的尺寸
        rightButton.frame = leftButton.frame;
        rightButton.x = leftButton.right;
        
        // 显示中间垂线
        rightButton.verticalLine.hidden = NO;
        
        // 添加按钮
        [self addSubview:leftButton];
        [self addSubview:rightButton];
        self.adjoinOverflyButtons = [NSMutableSet setWithObjects:leftButton, rightButton, nil];
        
        // 重新计算容器尺寸
        self.size = CGSizeMake(self.overflyViewSize.width, leftButton.bottom);
    }
    
    c、可增加多个按钮依次向下排列
    - (void)addOverflyButton:(OverflyButton *)button
    {
        // 清除按钮
        [self clearOverflyButtons:self.adjoinOverflyButtons.allObjects];
        [self.adjoinOverflyButtons removeAllObjects];
    
        void (^layout)(CGFloat) = ^(CGFloat top){
            // 子视图按钮(OverflyButton)的宽度
            CGFloat width = self.overflyViewSize.width - button.flyEdgeInsets.left - button.flyEdgeInsets.right;
            // 子视图按钮(OverflyButton)的高度,默认49
            button.size = CGSizeMake(width, self.subOverflyButtonHeight);
            button.y = top;
            // 中心位置
            button.centerX = self.overflyViewSize.width / 2;
        };
        
        OverflyButton *lastButton = objc_getAssociatedObject(self, OverflyViewActionKey);
        if (lastButton)// current
        {
            if (![button isEqual:lastButton])
            {
                // 添加新的Button到上一个Button的底部
                layout(lastButton.bottom + button.flyEdgeInsets.top);
            }
        }
        else// first
        {
            layout(self.overflyViewSize.height + button.flyEdgeInsets.top);
        }
        
        button.verticalLine.hidden = YES;// 隐藏中间垂线
        [self insertSubview:button atIndex:0];// 插入按钮到顶层
        
        // 重新计算容器尺寸
        self.size = CGSizeMake(self.overflyViewSize.width, button.bottom + button.flyEdgeInsets.bottom);
        
        // 保存作为上一个按钮
        objc_setAssociatedObject(self, OverflyViewActionKey, button, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    

    窗帘

    1、运行效果

    Curtain View

    2、Curtain View的使用方式

    使用方式的代码见:IOS框架:使用UI控件类框架中的zhPopupController

    a、创建窗帘视图
    - (CurtainView *)curtainView
    {
        CurtainView *curtainView = [[CurtainView alloc] init];
        curtainView.width = ScreenWidth;
        curtainView.height = 300 + UIApplication.sharedApplication.keyWindow.safeAreaInsets.top;
        
        // 提供按钮的图片和标题
        [curtainView.closeButton setImage:[UIImage imageNamed:@"qzone_close"] forState:UIControlStateNormal];
        NSArray *imageNames = @[@"github", @"paypal", @"pinterest", @"spotify", @"tumblr", @"twitter", @"whatsapp", @"yelp"];
        NSMutableArray *models = [NSMutableArray arrayWithCapacity:imageNames.count];
        for (NSString *imageName in imageNames)
        {
            UIImage *image = [UIImage imageNamed:[@"social-" stringByAppendingString:imageName]];
            [models addObject:[ImageButtonModel modelWithTitle:imageName image:image]];
        }
        curtainView.models = models;
        
        return curtainView;
    }
    
    b、提供窗帘视图按钮的点击事件
    - (CurtainView *)createCurtainView
    {
        CurtainView *curtainView = [self curtainView];
        
        __weak typeof(self) weakSelf = self;
        // 点击后窗帘消失
        curtainView.closeClicked = ^(UIButton *closeButton) {
            [weakSelf.qzoneStyle dismiss];
        };
        
        // 点击后弹出提示框
        curtainView.didClickItems = ^(CurtainView *curtainView, NSInteger index) {
            [self showAlert:curtainView.items[index].titleLabel.text];
        };
        
        return curtainView;
    }
    

    3、提供的接口

    a、CurtainView提供的接口
    @interface CurtainView : UIView
    
    /// 图片按钮的Model数组
    @property (nonatomic, strong) NSArray<ImageButtonModel *> *models;
    /// ImageButton数组
    @property (nonatomic, strong, readonly) NSMutableArray<ImageButton *> *items;
    /// 关闭按钮
    @property (nonatomic, strong) UIButton *closeButton;
    /// Item的尺寸
    @property (nonatomic, assign) CGSize itemSize;
    /// 点击关闭按钮的回调
    @property (nonatomic, copy) void (^closeClicked)(UIButton *closeButton);
    /// 点击Item按钮的回调
    @property (nonatomic, copy) void (^didClickItems)(CurtainView *curtainView, NSInteger index);
    
    @end
    
    b、ImageButton提供的接口
    typedef NS_ENUM(NSInteger, ImageButtonPosition) {
        ImageButtonPositionLeft = 0,    // 图片在左,文字在右,默认
        ImageButtonPositionRight,       // 图片在右,文字在左
        ImageButtonPositionTop,         // 图片在上,文字在下
        ImageButtonPositionBottom,      // 图片在下,文字在上
    };
    
    @interface ImageButton : UIButton
    
    - (void)imagePosition:(ImageButtonPosition)postion spacing:(CGFloat)spacing;
    - (void)imagePosition:(ImageButtonPosition)postion spacing:(CGFloat)spacing imageViewResize:(CGSize)size;
    
    @end
    
    c、ImageButtonModel提供的接口
    @interface ImageButtonModel : NSObject
    
    @property (nonatomic, strong) UIImage *icon;
    @property (nonatomic, strong) NSString *text;
    
    + (instancetype)modelWithTitle:(NSString *)title image:(UIImage *)image;
    
    @end
    

    4、实现CurtainView

    a、设置图片按钮的Model数组
    - (void)setModels:(NSArray<ImageButtonModel *> *)models
    {
        // 防空的默认值处理
        if (CGSizeEqualToSize(CGSizeZero, _itemSize))
        {
            _itemSize = CGSizeMake(50, 70);
        }
        
        // Item之间的间隙
        CGFloat _gap = UIApplication.sharedApplication.keyWindow.safeAreaInsets.top + 30;
        CGFloat _space = (self.width - ROW_COUNT * _itemSize.width) / (ROW_COUNT + 1);
        
        // 创建ImageButton数组
        _items = [NSMutableArray arrayWithCapacity:models.count];
        [models enumerateObjectsUsingBlock:^(ImageButtonModel * _Nonnull model, NSUInteger idx, BOOL * _Nonnull stop) {
            // Item所处的行数和列数
            NSInteger l = idx % ROW_COUNT;
            NSInteger v = idx / ROW_COUNT;
            
            // 使用Model数据配置item
            ImageButton *item = [ImageButton buttonWithType:UIButtonTypeCustom];
            item.userInteractionEnabled = YES;
            item.titleLabel.font = [UIFont fontWithName:@"pingFangSC-light" size:14];
            [item setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
            [item setTitle:model.text forState:UIControlStateNormal];
            [item setImage:model.icon forState:UIControlStateNormal];
            item.tag = idx;
            [item addTarget:self action:@selector(itemClicked:) forControlEvents:UIControlEventTouchUpInside];
            
            // item的布局属性
            [item imagePosition:ImageButtonPositionTop spacing:15 imageViewResize:CGSizeMake(32, 32)];
            item.size = CGSizeMake(_itemSize.width, _itemSize.height + 20);
            item.x = _space + (_itemSize.width  + _space) * l;
            item.y = _gap + (_itemSize.height + 40) * v + 45;
            
            // 添加到视图
            [self addSubview:item];
            [_items addObject:item];
        }];
    }
    
    b、触发按钮的回调

    触发点击关闭按钮的回调

    - (void)close:(UIButton *)sender
    {
        if (self.closeClicked)
        {
            self.closeClicked(sender);
        }
    }
    

    点击Item按钮的回调

    - (void)itemClicked:(ImageButton *)button
    {
        if (self.didClickItems)
        {
            self.didClickItems(self, button.tag);
        }
    }
    

    侧边栏

    1、运行效果

    侧边栏

    2、侧边栏的使用方式

    使用方式的代码见:IOS框架:使用UI控件类框架中的zhPopupController

    a、创建侧边栏视图
    - (SidebarView *)sidebarView
    {
        SidebarView *sidebarView = [SidebarView new];
        sidebarView.size = CGSizeMake(ScreenWidth - 90, ScreenHeight);
        sidebarView.backgroundColor = [UIColor colorWithRed:24/255.0 green:28/255.0 blue:45/255.0 alpha:0.9];
        sidebarView.models = @[@"我的故事", @"消息中心", @"我的收藏", @"近期阅读", @"离线阅读"];
        return sidebarView;
    }
    
    b、实现侧边栏按钮的点击事件
    - (SidebarView *)createSidebarView
    {
        SidebarView *sidebar = [self sidebarView];
        
        __weak typeof(self) weakSelf = self;
        sidebar.didClickItems = ^(SidebarView *sidebarView, NSInteger index) {
            [weakSelf.sidebarStyle dismiss];
            [self showAlert:sidebarView.items[index].titleLabel.text];
        };
        
        return sidebar;
    }
    

    3、提供的接口

    @interface SidebarView : UIView
    
    /// 文本数组
    @property (nonatomic, strong) NSArray<NSString *> *models;
    /// 图片按钮数组
    @property (nonatomic, strong, readonly) NSMutableArray<ImageButton *> *items;
    /// 点击图片按钮的回调
    @property (nonatomic, copy) void (^didClickItems)(SidebarView *sidebarView, NSInteger index);
    
    @end
    

    4、实现侧边栏

    a、创建容器视图和底部设置和夜间模式按钮

    初始化容器视图和底部设置和夜间模式按钮

    - (instancetype)init
    {
        if (self = [super init])
        {
            // 视图的区域比底层视图暗
            UIBlurEffect *effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleDark];
            _blurView = [[UIVisualEffectView alloc] initWithEffect:effect];
            [self addSubview:_blurView];
            
            // 设置和夜间模式按钮
            _settingItem = [self itemWithText:@"设置" imageNamed:@"sidebar_settings"];
            [self addSubview:_settingItem];
            _nightItem = [self itemWithText:@"夜间模式" imageNamed:@"sidebar_NightMode"];
            [self addSubview:_nightItem];
        }
        return self;
    }
    

    对容器视图和底部设置和夜间模式按钮进行布局

    - (void)layoutSubviews
    {
        [super layoutSubviews];
        
        // 重新布局
        // 不能放在初始化方法中,因为初始化完成后self.width才有值
        _blurView.frame = self.bounds;
        _settingItem.x =  50;
        _nightItem.right = self.width - 50;
    }
    

    创建底部设置和夜间模式按钮

    - (ImageButton *)itemWithText:(NSString *)text imageNamed:(NSString *)imageNamed
    {
        ImageButton *item = [ImageButton buttonWithType:UIButtonTypeCustom];
        item.userInteractionEnabled = YES;
        item.exclusiveTouch = YES;// 指示接收器是否以独占方式处理触摸事件
        item.titleLabel.font = [UIFont systemFontOfSize:13];
        [item setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
        item.size = CGSizeMake(60, 90);
        item.bottom = ScreenHeight - 20 - IPhoneXSafeAreaHeight;
        item.imageView.contentMode = UIViewContentModeScaleAspectFit;
        [item setImage:[UIImage imageNamed:imageNamed] forState:UIControlStateNormal];
        [item setTitle:text forState:UIControlStateNormal];
        // 上下布局
        [item imagePosition:ImageButtonPositionTop spacing:10 imageViewResize:CGSizeMake(30, 30)];
        return item;
    }
    
    b、创建中间的按钮组
    - (void)setModels:(NSArray<NSString *> *)models
    {
        _items = @[].mutableCopy;
        CGFloat _gap = 15;
        [models enumerateObjectsUsingBlock:^(NSString *text, NSUInteger idx, BOOL * _Nonnull stop) {
            
            ImageButton *item = [ImageButton buttonWithType:UIButtonTypeCustom];
            item.userInteractionEnabled = YES;
            item.exclusiveTouch = YES;
            item.titleLabel.font = [UIFont systemFontOfSize:15];
            item.imageView.contentMode = UIViewContentModeCenter;
            [item setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
            item.imageView.contentMode = UIViewContentModeScaleAspectFit;
            NSString *imageNamed = [NSString stringWithFormat:@"sidebar_%@", text];
            [item setImage:[UIImage imageNamed:imageNamed] forState:UIControlStateNormal];
            [item setTitle:text forState:UIControlStateNormal];
            item.size = CGSizeMake(150, 50);
            item.y = (_gap + item.height) * idx + 150;
            item.centerX = self.width / 2;
            // 左右布局
            [item imagePosition:ImageButtonPositionLeft spacing:25 imageViewResize:CGSizeMake(25, 25)];
            [self addSubview:item];
            [_items addObject:item];
            item.tag = idx;
            [item addTarget:self action:@selector(itemClicked:) forControlEvents:UIControlEventTouchUpInside];
        }];
    }
    

    中间按钮的点击事件

    - (void)itemClicked:(ImageButton *)sender
    {
        if (self.didClickItems)
        {
            self.didClickItems(self, sender.tag);
        }
    }
    

    全屏视图

    1、运行效果

    全屏视图

    2、全屏视图的使用方式

    使用方式的代码见:IOS框架:使用UI控件类框架中的zhPopupController

    创建全屏视图

    - (FullView *)fullView
    {
        FullView *fullView = [[FullView alloc] initWithFrame:self.view.window.bounds];
        NSArray *array = @[@"文字", @"照片视频", @"头条文章", @"红包", @"直播", @"点评", @"好友圈", @"更多", @"音乐", @"商品", @"签到", @"秒拍", @"头条文章", @"红包", @"直播", @"点评"];
        NSMutableArray *models = [NSMutableArray arrayWithCapacity:array.count];
        for (NSString *string in array)
        {
            ImageButtonModel *item = [ImageButtonModel new];
            item.icon = [UIImage imageNamed:[NSString stringWithFormat:@"sina_%@", string]];
            item.text = string;
            [models addObject:item];
        }
        fullView.models = models;
        return fullView;
    }
    

    实现全屏视图按钮的点击事件

    - (FullView *)createFullView
    {
        FullView *fullView = [self fullView];
        
        __weak typeof(self) weakSelf = self;
        // 点击屏幕
        fullView.didClickFullView = ^(FullView * _Nonnull fullView) {
            [weakSelf.fullStyle dismiss];
        };
    
        // 点击Item
        fullView.didClickItems = ^(FullView *fullView, NSInteger index) {
            [fullView endAnimationsWithCompletion:^(FullView *fullView) {
                [weakSelf.fullStyle dismiss];
                
                MBProgressHUDViewController *vc = [MBProgressHUDViewController new];
                vc.title = fullView.items[index].titleLabel.text;
                [weakSelf.navigationController pushViewController:vc animated:YES];
            }];
        };
        
        return fullView;
    }
    

    3、提供的接口

    #define ROW_COUNT 4// 每行显示4个
    #define ROWS 2// 每页显示2行
    #define PAGES 2// 共2页
    
    @interface FullView : UIView
    
    /// 图片按钮的尺寸
    @property (nonatomic, assign) CGSize itemSize;
    /// 图片按钮的Model数组
    @property (nonatomic, strong) NSArray<ImageButtonModel *> *models;
    /// 图片按钮数组
    @property (nonatomic, strong, readonly) NSMutableArray<ImageButton *> *items;
    
    /// 点击全屏视图的回调
    @property (nonatomic, copy) void (^didClickFullView)(FullView *fullView);
    /// 点击图片按钮的回调
    @property (nonatomic, copy) void (^didClickItems)(FullView *fullView, NSInteger index);
    
    /// 开始动画
    - (void)startAnimationsWithCompletion:(void (^)(BOOL finished))completion;
    
    /// 结束动画
    - (void)endAnimationsWithCompletion:(void (^)(FullView *fullView))completion;
    
    @end
    

    4、全屏视图的实现

    触发点击全屏视图的回调

    - (void)fullViewClicked:(UITapGestureRecognizer *)recognizer
    {
        __weak typeof(self) _self = self;
        [self endAnimationsWithCompletion:^(FullView *fullView) {
            if (self.didClickFullView)
            {
                _self.didClickFullView((FullView *)recognizer.view);
            }
        }];
    

    触发点击图片按钮的回调

    - (void)itemClicked:(UIButton *)sender
    {
        if (ROWS * ROW_COUNT - 1 == sender.tag)// 更多按钮
        {
            // 滚动到下一页
            [_scrollContainer setContentOffset:CGPointMake(ScreenWidth, 0) animated:YES];
        }
        else
        {
            if (self.didClickItems)
            {
                self.didClickItems(self, sender.tag);
            }
        }
    }
    

    滑动到初始位置

    - (void)slideToInitialPosition:(UIButton *)sender
    {
        [_scrollContainer setContentOffset:CGPointMake(0, 0) animated:YES];
    }
    

    UIScrollViewDelegate

    -(void)scrollViewDidScroll:(UIScrollView *)scrollView
    {
        // 首页是关闭,其余页是返回
        // index = 1时是返回按钮,此时有效,当index = 0时是关闭按钮,此时无效,点击不触发
        NSInteger index = scrollView.contentOffset.x /ScreenWidth + 0.5;
        _closeButton.userInteractionEnabled = index > 0;
        [_closeIcon setImage:[UIImage imageNamed:(index ? @"sina_返回" : @"sina_关闭")] forState:UIControlStateNormal];
    }
    

    开始动画

    - (void)startAnimationsWithCompletion:(void (^)(BOOL finished))completion
    {
        // 关闭按钮的旋转动画
        [UIView animateWithDuration:0.5 animations:^{
            self.closeIcon.transform = CGAffineTransformMakeRotation(M_PI_4);
        } completion:NULL];
        
        [_items enumerateObjectsUsingBlock:^(ImageButton *item, NSUInteger idx, BOOL * _Nonnull stop) {
            
            // 首页的item数量
            NSUInteger firstPageCount = ROW_COUNT * ROWS;
            // 只需首页的item做动画即可
            if (idx < firstPageCount)
            {
                // 透明度动画
                item.alpha = 0;
                item.transform = CGAffineTransformMakeTranslation(0, ROWS * _itemSize.height);
                [UIView animateWithDuration:0.55
                                      delay:idx * 0.035 //依次延迟呈现
                     usingSpringWithDamping:0.6 //弹簧动画的阻尼系数
                      initialSpringVelocity:0 //速度
                                    options:UIViewAnimationOptionCurveLinear //线性动画
                                 animations:^{
                                     item.alpha = 1;
                                     item.transform = CGAffineTransformIdentity;
                                 } completion:completion];
            }
        }];
    }
    

    结束动画

    - (void)endAnimationsWithCompletion:(void (^)(FullView *))completion
    {
        // 当前页数
        NSInteger flag = _scrollContainer.contentOffset.x /ScreenWidth + 0.5;
        
        // 关闭按钮的旋转动画
        if (!_closeButton.userInteractionEnabled)
        {
            [UIView animateWithDuration:0.35 animations:^{
                self.closeIcon.transform = CGAffineTransformIdentity;
            } completion:NULL];
        }
        
        [_items enumerateObjectsUsingBlock:^(ImageButton * _Nonnull item, NSUInteger idx, BOOL * _Nonnull stop) {
            NSInteger pageCount = ROW_COUNT * ROWS;// 每页最大的item数量
            NSInteger startIdx = (pageCount * flag);// 开始位置
            NSInteger endIdx = startIdx + pageCount;// 结束位置
            
            // 开始和结束位置之间的Item可以动画
            BOOL shouldAnimated = NO;
            if (idx >= startIdx && idx < endIdx)
            {
                shouldAnimated = YES;
            }
        
            if (shouldAnimated)
            {
                // 逆序依次消失
                [UIView animateWithDuration:0.25 delay:0.02f * (_items.count - idx) options:UIViewAnimationOptionCurveEaseInOut animations:^{
                    // 横向不移动,纵向下移消失
                    item.transform = CGAffineTransformMakeTranslation(0, ROWS * self.itemSize.height + 50);
                } completion:^(BOOL finished) {
                    if (finished)
                    {
                        if (idx == endIdx - 1)
                        {
                            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.15 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                                completion(self);
                            });
                        }
                    }
                }];
            }
        }];
    }
    

    分享视图

    1、运行效果

    分享视图

    2、分享视图的使用方式

    使用方式的代码见:IOS框架:使用UI控件类框架中的zhPopupController

    a、创建分享视图
    - (WallView *)wallView
    {
        CGRect rect = CGRectMake(100, 100, ScreenWidth, 300);
        WallView *wallView = [[WallView alloc] initWithFrame:rect];
        wallView.wallHeaderLabel.text = @"此网页由 http.qq.com 提供";
        wallView.wallFooterLabel.text = @"取消";
        wallView.models = [self wallModels];
        wallView.size = [wallView sizeThatFits:CGSizeMake(ScreenWidth, CGFLOAT_MAX)];
        [wallView addCornerRadius:10 byRoundingCorners:UIRectCornerTopLeft | UIRectCornerTopRight];
        return wallView;
    }
    
    b、实现分享视图底部按钮的点击事件
    - (WallView *)createShareView
    {
        WallView *wallView = [self wallView];
        wallView.delegate = self;
        
        __weak typeof(self) weakself = self;
        wallView.didClickFooter = ^(WallView * sheetView) {
            [weakself.shareStyle dismiss];
        };
        
        return wallView;
    }
    
    c、WallViewDelegateConfig(配置外观)
    - (WallViewLayout *)layoutOfItemInWallView:(WallView *)wallView
    {
        WallViewLayout *layout = [WallViewLayout new];
        layout.itemSubviewsSpacing = 9;
        return layout;
    }
    
    - (WallViewAppearance *)appearanceOfItemInWallView:(WallView *)wallView
    {
        WallViewAppearance *appearance = [WallViewAppearance new];
        appearance.textLabelFont = [UIFont systemFontOfSize:10];
        return appearance;
    }
    
    d、WallViewDelegate(点击分享按钮)
    - (void)wallView:(WallView *)wallView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
    {
        WallItemModel *model = [self wallModels][indexPath.section][indexPath.row];
        [self.shareStyle dismissWithDuration:0.25 completion:^{
            [self showAlert:model.text];
        }];
    }
    

    3、提供的接口

    a、WallViewLayout(配置布局)
    @interface WallViewLayout : NSObject
    
    /// Set item size. default is CGSizeMake(60, 90)
    @property (nonatomic, assign) CGSize itemSize;
    
    /// 设置内部image视图边长 (imageView.height = imageView.width). default is (itemSize.width - 10)
    @property (nonatomic, assign) CGFloat imageViewSideLength;
    
    /// 设置水平item之间的间距 (|item1| <- itemPadding -> |item2|). default is 5
    @property (nonatomic, assign) CGFloat itemPadding;
    
    /// 纵向item内子视图间距 (textLabel.y = imageView.bottom + itemSubviewsSpacing). default is 7
    @property (nonatomic, assign) CGFloat itemSubviewsSpacing;
    
    /// Set section insets. default is UIEdgeInsetsMake(15, 10, 5, 10)
    @property (nonatomic, assign) UIEdgeInsets itemEdgeInset;
    
    /// 设置表头高 (wallHeaderLabel.height = wallHeaderHeight). default is 30
    @property (nonatomic, assign) CGFloat wallHeaderHeight;
    
    /// 设置底部视图高 (wallFooterLabel.height = wallFooterHeight). default is 50
    @property (nonatomic, assign) CGFloat wallFooterHeight;
    
    @end
    
    b、WallViewAppearance(配置外观)
    @interface WallViewAppearance : NSObject
    
    /// default is [UIColor clearColor]
    @property (nonatomic, strong) UIColor *sectionBackgroundColor;
    
    /// default is [UIColor clearColor]
    @property (nonatomic, strong) UIColor *itemBackgroundColor;
    
    /// default is [UIColor whiteColor]
    @property (nonatomic, strong) UIColor *imageViewBackgroundColor;
    
    /// default is [UIColor grayColor]
    @property (nonatomic, strong) UIColor *imageViewHighlightedColor;
    
    /// default is UIViewContentModeScaleToFill
    @property (nonatomic, assign) UIViewContentMode imageViewContentMode;
    
    /// default is 15.0
    @property (nonatomic, assign) CGFloat imageViewCornerRadius;
    
    /// default is [UIColor clearColor]
    @property (nonatomic, strong) UIColor *textLabelBackgroundColor;
    
    /// default is [UIColor darkGrayColor]
    @property (nonatomic, strong) UIColor *textLabelTextColor;
    
    /// default is [UIFont systemFontOfSize:10]
    @property (nonatomic, strong) UIFont  *textLabelFont;
    
    @end
    
    c、Model
    @interface WallItemModel : NSObject
    
    + (instancetype)modelWithImage:(UIImage *)image text:(NSString *)text;
    @property (nonatomic, strong) UIImage *image;
    @property (nonatomic, strong) NSString *text;
    
    @end
    
    d、CollectionCell(Item)
    @interface WallViewCollectionCell : UICollectionViewCell
    
    @property (nonatomic, strong, readonly) UIButton *imageView;
    @property (nonatomic, strong, readonly) UILabel *textLabel;
    
    @end
    
    e、CollectionView(每行是一个集合视图)
    @interface WallCollectionView : UITableViewCell <UICollectionViewDataSource, UICollectionViewDelegate>
    
    @property (nonatomic, strong) UICollectionView *collectionView;
    @property (nonatomic, strong) WallViewLayout *wallLayout;
    @property (nonatomic, strong) WallViewAppearance *wallAppearance;
    
    @property (nonatomic, strong) NSArray<WallItemModel *> *models;
    
    @property (nonatomic, weak) WallView *wallView;
    @property (nonatomic, assign) NSInteger rowIndex;
    
    @end
    
    f、Delegate

    点击分享按钮

    @protocol WallViewDelegate <NSObject>
    
    @optional
    // 点击了每个item事件
    - (void)wallView:(WallView *)wallView didSelectItemAtIndexPath:(NSIndexPath *)indexPath;
    
    @end
    

    配置布局和外观

    @protocol WallViewDelegateConfig <WallViewDelegate>
    
    @optional
    // 布局相关
    - (WallViewLayout *)layoutOfItemInWallView:(WallView *)wallView;
    // 外观颜色相关
    - (WallViewAppearance *)appearanceOfItemInWallView:(WallView *)wallView;
    
    @end
    
    g、WallView

    整体是个TableView,每一行的CollectionView作为TableViewCell

    @interface WallView : UIView
    
    @property (nonatomic, weak, nullable) id <WallViewDelegate> delegate;
    @property (nonatomic, strong, readonly) UILabel *wallFooterLabel;
    @property (nonatomic, strong, readonly) UILabel *wallHeaderLabel;
    
    @property (nonatomic, strong) NSArray<NSArray<WallItemModel *> *> *models;
    
    @property (nonatomic, copy) void (^didClickHeader)(WallView *wallView);
    @property (nonatomic, copy) void (^didClickFooter)(WallView *wallView);
    
    @end
    

    4、分享视图的实现方式

    a、CollectionView
    配置cell
    - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
    {
        WallViewCollectionCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cell" forIndexPath:indexPath];
        if (indexPath.row < _models.count)
        {
            id object = [_models objectAtIndex:indexPath.row];
            [cell setModel:object withLayout:_wallLayout appearance:_wallAppearance];
        }
        cell.imageView.tag = indexPath.row;
        [cell.imageView addTarget:self action:@selector(itemClicked:) forControlEvents:UIControlEventTouchUpInside];
        return cell;
    }
    
    点击分享按钮
    - (void)itemClicked:(UIButton *)sender
    {
        WallView *wallView = self.wallView;
        if ([wallView.delegate respondsToSelector:@selector(wallView:didSelectItemAtIndexPath:)])
        {
            NSIndexPath *_indexPath = [NSIndexPath indexPathForRow:sender.tag inSection:self.rowIndex];
            [wallView.delegate wallView:wallView didSelectItemAtIndexPath:_indexPath];
        }
    }
    
    b、WallView
    配置cell
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        WallCollectionView *cell = [tableView dequeueReusableCellWithIdentifier:@"WallCollectionView"];
        if (!cell)
        {
            cell = [[WallCollectionView alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"WallCollectionView" layout:[self layout] appearance:[self appearance]];
            cell.selectionStyle = UITableViewCellSelectionStyleNone;
        }
        cell.wallView = self;
        cell.rowIndex = indexPath.row;
        id object = [_models objectAtIndex:indexPath.row];
        if ([object isKindOfClass:[NSArray class]])
        {
            cell.models = (NSArray *)object;
        }
        return cell;
    }
    
    WallViewDelegateConfig配置布局和外观

    配置布局

    - (WallViewLayout *)layout
    {
        id <WallViewDelegateConfig> config = (id <WallViewDelegateConfig>)self.delegate;
        
        if ([config respondsToSelector:@selector(layoutOfItemInWallView:)])
        {
            return [config layoutOfItemInWallView:self];
        }
        return [[WallViewLayout alloc] init];
    }
    

    配置外观

    - (WallViewAppearance *)appearance
    {
        id <WallViewDelegateConfig> config = (id <WallViewDelegateConfig> )self.delegate;
        
        if ([config respondsToSelector:@selector(appearanceOfItemInWallView:)])
        {
            return [config appearanceOfItemInWallView:self];
        }
        return [[WallViewAppearance alloc] init];
    }
    

    登陆注册视图

    1、运行效果

    登录键盘 注册键盘

    2、登陆注册视图的使用方式

    使用方式的代码见:IOS框架:使用UI控件类框架中的zhPopupController

    a、创建登录键盘
    - (CenterKeyboardView *)centerKeyboardView
    {
        if (!_centerKeyboardView)
        {
            _centerKeyboardView = [[CenterKeyboardView alloc] initWithFrame:CGRectMake(0, 0, 300, 236)];
            
            __weak typeof(self) weakSelf = self;
            // 点击登陆按钮
            _centerKeyboardView.loginClickedBlock = ^(CenterKeyboardView *keyboardView) {
                [weakSelf.centerKeyboardStyle dismiss];
            };
            
            // 点击注册按钮
            _centerKeyboardView.registerClickedBlock = ^(CenterKeyboardView *keyboardView, UIButton *button) {
                // 进入注册界面
                [UIView transitionWithView:weakSelf.centerKeyboardStyle.view duration:0.5 options:UIViewAnimationOptionTransitionFlipFromLeft animations:^{
    
                    [weakSelf.centerKeyboardStyle.view addSubview:weakSelf.registerKeyboardView];
                    [weakSelf.registerKeyboardView.phoneNumberField becomeFirstResponder];
    
                } completion:^(BOOL finished) {
                    
                    // 移除登陆界面
                    if ([weakSelf.centerKeyboardStyle.view.subviews containsObject:keyboardView]) {
                        [keyboardView removeFromSuperview];
                    }
                }];
            };
        }
        return _centerKeyboardView;
    }
    
    b、创建注册键盘
    - (RegisterKeyboardView *)registerKeyboardView
    {
        if (!_registerKeyboardView)
        {
            _registerKeyboardView = [[RegisterKeyboardView alloc] initWithFrame:CGRectMake(0, 0, 300, 236)];
            
            __weak typeof(self) weakSelf = self;
            // 点击返回按钮
            _registerKeyboardView.gobackClickedBlock = ^(RegisterKeyboardView *keyboardView, UIButton *button) {
                __strong typeof(weakSelf) strongSelf = weakSelf;
                // 进入登陆界面
                [UIView transitionWithView:strongSelf.centerKeyboardStyle.view duration:0.5 options:UIViewAnimationOptionTransitionFlipFromLeft animations:^{
    
                    [strongSelf.centerKeyboardStyle.view addSubview:strongSelf.centerKeyboardView];
                    [strongSelf.centerKeyboardView.phoneNumberField becomeFirstResponder];
    
                } completion:^(BOOL finished) {
                    // 移除注册界面
                    if ([strongSelf.centerKeyboardStyle.view.subviews containsObject:keyboardView]) {
                        [keyboardView removeFromSuperview];
                    }
                }];
            };
            
            _registerKeyboardView.nextClickedBlock = ^(RegisterKeyboardView *keyboardView, UIButton *button) {
                [weakSelf.centerKeyboardView.phoneNumberField resignFirstResponder];
                [weakSelf.registerKeyboardView.phoneNumberField resignFirstResponder];
                
                NSLog(@"注册成功");
            };
        }
        return _registerKeyboardView;
    }
    

    3、提供的接口

    a、带下划线的输入框
    @interface UnderlineTextField : UITextField
    
    /// 下划线的颜色
    @property (nonatomic, strong) UIColor *underlineColor;
    
    @end
    
    b、登陆键盘
    @interface CenterKeyboardView : UIView
    
    /// 点击注册的回调
    @property (nonatomic, copy) void (^registerClickedBlock)(CenterKeyboardView *keyboardView, UIButton *button);
    /// 点击登录的回调
    @property (nonatomic, copy) void (^loginClickedBlock)(CenterKeyboardView *keyboardView);
    
    /// 输入手机号
    @property (nonatomic, strong) UnderlineTextField *phoneNumberField;
    /// 输入密码
    @property (nonatomic, strong) UnderlineTextField *passwordField;
    /// 登录按钮
    @property (nonatomic, strong) UIButton *loginButton;
    /// 注册按钮
    @property (nonatomic, strong) UIButton *registerButton;
    /// 其他登录方式按钮数组
    @property (nonatomic, strong) NSArray<UIButton *> *otherLoginMethodButtons;
    
    @end
    
    c、注册键盘
    @interface RegisterKeyboardView : UIView
    
    /// 点击返回的回调
    @property (nonatomic, copy) void (^gobackClickedBlock)(RegisterKeyboardView *keyboardView, UIButton *button);
    /// 点击下一步的回调
    @property (nonatomic, copy) void (^nextClickedBlock)(RegisterKeyboardView *keyboardView, UIButton *button);
    
    @property (nonatomic, strong) UILabel *titleLabel;
    /// 输入手机号
    @property (nonatomic, strong) UnderlineTextField *phoneNumberField;
    /// 输入验证码
    @property (nonatomic, strong) UnderlineTextField *verificationCodeField;
    /// 发送验证码按钮
    @property (nonatomic, strong) UIButton *sendCodeButton;
    /// 下一步按钮
    @property (nonatomic, strong) UIButton *nextButton;
    /// 返回按钮
    @property (nonatomic, strong) UIButton *gobackButton;
    
    @end
    

    轮播图

    1、运行效果

    轮播图.gif

    2、轮播图的使用方式

    // 创建轮播图
    - (void)createAutoScrollView
    {
        self.autoScrollView = [[AutoScrollView alloc] initWithFrame:CGRectZero];
        
        NSMutableArray *scrollImages = [[NSMutableArray alloc] init];
        UIImage *boy = [UIImage imageNamed:@"稚气.PNG"];
        UIImage *coffee = [UIImage imageNamed:@"咖啡.JPG"];
        UIImage *luckcoffee = [UIImage imageNamed:@"luckcoffee.JPG"];
        [scrollImages addObject:boy];
        [scrollImages addObject:coffee];
        [scrollImages addObject:luckcoffee];
        self.autoScrollView.scrollImage = [NSMutableArray arrayWithArray:[scrollImages copy]];
        
        self.autoScrollView.duration = 3;
        self.autoScrollView.backgroundColor = [UIColor redColor];
        self.autoScrollView.scrollSize = CGSizeMake(self.view.frame.size.width, 200);
        
        [self.view addSubview:self.autoScrollView];
        [self.autoScrollView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.left.right.equalTo(self.view);
            make.centerY.equalTo(self.view);
            make.height.equalTo(@200);
        }];
    }
    

    3、提供的接口

    @interface AutoScrollView : UIView
    
    /** 滚动的图片 */
    @property (nonatomic, strong, readwrite) NSMutableArray *scrollImage;
    /** 滚动视图的尺寸 */
    @property (nonatomic, assign, readwrite) CGSize scrollSize;
    /** 滚动时长 */
    @property (nonatomic, assign, readwrite) CGFloat duration;
    /** 页面 */
    @property (nonatomic, strong, readonly) UIPageControl *pageView;
    /** 计时器 */
    @property (nonatomic, strong) NSTimer *timer;
    
    @end
    

    4、配置轮播图

    a、创建滚动视图
    - (void)createSubViews
    {
        // 创建滚动的方框视图
        self.scrollBox = [[UIView alloc]initWithFrame:CGRectMake(0, 0, self.scrollSize.width, self.scrollSize.height)];
        [self addSubview:self.scrollBox];
        
        // 创建滚动视图
        self.autoScrollView = [[UIScrollView alloc]initWithFrame:CGRectMake(0, 0, self.scrollSize.width, self.scrollSize.height)];
        self.autoScrollView.pagingEnabled = YES;// 能翻页
        self.autoScrollView.scrollEnabled = YES;// 能滚动
        self.autoScrollView.showsHorizontalScrollIndicator = NO;// 不显示水平滚动条
        self.autoScrollView.showsVerticalScrollIndicator = NO;// 不显示垂直滚动条
        self.autoScrollView.minimumZoomScale = 0.5;// 缩放
        self.autoScrollView.maximumZoomScale = 2.0;// 放大
        self.autoScrollView.delegate = self;// 委托
        self.autoScrollView.backgroundColor = UIColor.whiteColor;// 白色
        self.autoScrollView.userInteractionEnabled = YES;// 可交互翻页
        
        // 添加到方框视图中
        [self.scrollBox addSubview:self.autoScrollView];
        
        // 滚动视图内容范围为3倍方框视图的宽度
        CGSize viewSize = self.scrollBox.bounds.size;
        self.autoScrollView.contentSize = CGSizeMake(3 * viewSize.width, viewSize.height);
        
        // 添加图片子视图
        for (int i = 0; i < self.scrollImage.count;i++)
        {
            // 创建图片视图
            UIImageView *imageView = [self addViewToScrollView:i * viewSize.width];
            // 添加图片
            imageView.image = self.scrollImage[(i-1) % self.scrollImage.count];
            // 填充模式
            imageView.contentMode = UIViewContentModeScaleAspectFit;
        }
        // 初始化时候的偏移量
        [self.autoScrollView setContentOffset:CGPointMake(viewSize.width, 0)];
        
        // 翻页视图
        self.pageView = [[UIPageControl alloc]initWithFrame:CGRectZero];
        self.pageView.numberOfPages = self.scrollImage.count;// 页数
        self.pageView.currentPageIndicatorTintColor = [UIColor greenColor];// 页指示器颜色为绿色
        self.pageView.userInteractionEnabled = NO;// 不可点击交互
        [self addSubview:self.pageView];
        [self.pageView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.bottom.equalTo(self.autoScrollView.mas_safeAreaLayoutGuideBottom).offset(-20);
            make.height.equalTo(@44);
            make.left.equalTo(self.mas_left);
            make.right.equalTo(self.mas_right);
        }];
    }
    

    添加图片子视图到滚动视图中

    - (UIImageView *)addViewToScrollView : (CGFloat)offsetX
    {
        CGSize viewSize = self.scrollBox.bounds.size;
        UIImageView *view = [[UIImageView alloc] initWithFrame:CGRectMake(offsetX, 0, viewSize.width, viewSize.height)];
        [self.autoScrollView addSubview:view];
        return view;
    }
    
    b、UIScrollViewDelegate
    // 开始拖动的时候停止定时器
    - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
    {
        [self.timer setFireDate:[NSDate distantFuture]];
    }
    
    // 滚动了重新计算索引位置
    - (void)scrollViewDidScroll:(UIScrollView *)scrollView
    {
        [self reloadIndex];
    }
    
    // 结束拖动的时候调用
    - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
    {
        self.pageView.currentPage = self.index;// 设置当前页面
        [self.timer setFireDate:[NSDate dateWithTimeIntervalSinceNow:self.duration]];// 重新启动定时器
    }
    
    c、设置属性
    // 设置滚动视图大小
    - (void)setScrollSize:(CGSize)scrollSize
    {
        _scrollSize = scrollSize;
        
        // 判断变量状态,初始化数据
        [self preAction];
        // 创建视图
        [self createSubViews];
    }
    
    // 设置定时器
    - (void)setDuration:(CGFloat)duration
    {
        _duration = duration;
        
        if (duration > 0.0)
        {
            self.timer = [NSTimer scheduledTimerWithTimeInterval:duration target:self selector:@selector(changeNext) userInfo:nil repeats:YES];
            [self.timer setFireDate:[NSDate dateWithTimeIntervalSinceNow:duration]];
        }
    }
    
    // 设置位置
    - (void)setIndex:(NSInteger)index
    {
        _index = index;
        
        // 重新给图片视图赋值
        [self recreate];
    }
    
    d、滚动过程中需要用到的方法

    判断变量状态,初始化数据

    - (void)preAction
    {
        // 尺寸
        if (self.scrollSize.width == 0 || self.scrollSize.height == 0)
        {
            self.scrollSize = CGSizeMake(300, 200);
        }
        
        // 从0开始
        self.index = 0;
    }
    

    定时滚动函数

    - (void)changeNext
    {
        // 设置2倍偏移量进行翻页
        [self.autoScrollView setContentOffset:CGPointMake(2 * CGRectGetWidth(self.autoScrollView.bounds), 0) animated:YES];
    }
    

    重新给图片视图赋值

    - (void)recreate
    {
        if (self.scrollImage && self.scrollImage.count > 0)
        {
            NSInteger totalCount = self.scrollImage.count;// 图片数量
            NSInteger leftIndex = (self.index + totalCount - 1) % totalCount;
            NSInteger rightIndex = (self.index +  1) % totalCount;
            
            NSArray <UIImageView *> *subviews = self.autoScrollView.subviews;
            subviews[0].image = self.scrollImage[leftIndex];// 上一张图
            subviews[1].image = self.scrollImage[self.index];// 当前图
            subviews[2].image = self.scrollImage[rightIndex];// 下一张图
        }
    
        // 每次滑动完,再滑动回中心
        [self scrollCenter];
    }
    

    每次滑动完,再滑动回中心

    - (void)scrollCenter
    {
        // 设置偏移量
        [self.autoScrollView setContentOffset:CGPointMake(self.scrollBox.bounds.size.width, 0)];
        // 当前页
        self.pageView.currentPage = self.index;
    }
    

    计算页数

    - (void)reloadIndex
    {
        if (self.scrollImage && self.scrollImage.count > 0)
        {
            CGFloat pointX = self.autoScrollView.contentOffset.x;
            
            // 此处的value用于边缘判断,当imageview距离两边间距小于1时,触发偏移
            CGFloat value = 0.2f;
            
            if (pointX > CGRectGetWidth(self.autoScrollView.bounds) * 2 - value)
            {
                self.index = (self.index + 1) % self.scrollImage.count;
            }
            else if (pointX < value)
            {
                self.index = (self.index + self.scrollImage.count - 1) % self.scrollImage.count;
            }
        }
    }
    

    时间选择器

    1、运行效果

    2020-09-28 14:15:35.388930+0800 FunctionCodeBlockDemo[30440:19511223] 点击确定按钮后,执行block回调
    2020-09-28 14:15:35.389335+0800 FunctionCodeBlockDemo[30440:19511223] 选择的日期为:2020-09
    
    日期选择器

    2、选择器的使用方式

    @implementation DatePickerViewController
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        
        [DatePickerSubview showDatePickerWithTitle:@"日期选择器" minDateString:@"2019-08" resultBlock:^(NSString *selectValue) {
            NSLog(@"选择的日期为:%@",selectValue);
        }];
    }
    
    @end
    

    3、提供的接口

    a、DatePickerSuperView
    @interface DatePickerSuperView : UIView
    
    // 属性
    @property (nonatomic, strong) UIView *backgroundView;// 背景蒙层视图
    @property (nonatomic, strong) UIView *alertView;// 弹出视图
    @property (nonatomic, strong) UIView *topView;// 标题行顶部视图
    @property (nonatomic, strong) UIButton *cancelButton;// 左边取消按钮
    @property (nonatomic, strong) UIButton *sureButton;// 右边确定按钮
    @property (nonatomic, strong) UILabel *titleLabel;// 中间标题
    @property (nonatomic, strong) UIView *lineView;// 分割线视图
    
    // 点击背景遮罩图层和取消、确定按钮的点击事件实现效果在基类中都是空白的,具体效果在子类中进行重写来控制
    /** 点击背景遮罩图层事件 */
    - (void)didTapBackgroundView:(UITapGestureRecognizer *)sender;
    /** 取消按钮的点击事件 */
    - (void)clickCancelButton;
    /** 确定按钮的点击事件 */
    - (void)clickSureButton;
    
    @end
    
    b、DatePickerSubview
    // 日期选择完成之后的操作
    typedef void(^DateResultBlock)(NSString *selectValue);
    
    @interface DatePickerSubview : DatePickerSuperView
    
    // 让使用者提供选择器的标题、最小日期、日期选择完成后的操作
    + (void)showDatePickerWithTitle:(NSString *)title minDateString:(NSString *)minDateString resultBlock:(DateResultBlock)resultBlock;
    
    @end
    

    4、创建数据源

    选择器数据的加载,从设定的最小日期到当前月

    - (NSMutableArray<NSString *> *)data
    {
        if (!_data)
        {
            _data = [[NSMutableArray alloc] init];
            
            NSDate *currentDate = [NSDate date];
            NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
            [formatter setDateFormat:@"yyyy-MM"];
            NSString *dateString = [formatter stringFromDate:currentDate];
            NSDate *newDate;
            
            // 通过日历可以直接获取前几个月的日期,所以这里直接用该类的方法进行循环获取数据
            NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
            NSDateComponents *lastMonthComps = [[NSDateComponents alloc] init];
            NSInteger lastIndex = 0;
    
            // 循环获取可选月份,从当前月份到最小月份,用字符串比较来判断是否大于设定的最小日期
            while (!([dateString compare:self.minDateString] == NSOrderedAscending))
            {
                [_data addObject:dateString];
                lastIndex--;
                
                // 获取之前n个月, setMonth的参数为正则向后,为负则表示之前
                [lastMonthComps setMonth:lastIndex];
                newDate = [calendar dateByAddingComponents:lastMonthComps toDate:currentDate options:0];
                dateString = [formatter stringFromDate:newDate];
            }
        }
        return _data;
    }
    

    5、配置选择器

    a、UIPickerViewDataSource
    - (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
    {
        return 1;
    }
    
    - (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component
    {
        return self.data.count;
    }
    
    - (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
    {
        return self.data[row];
    }
    
    b、UIPickerViewDelegate
    - (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
    {
        self.selectValue = self.data[row];
    }
    
    // 高度
    - (CGFloat)pickerView:(UIPickerView *)pickerView rowHeightForComponent:(NSInteger)component
    {
        return 35.0f;
    }
    
    // 宽度
    - (CGFloat)pickerView:(UIPickerView *)pickerView widthForComponent:(NSInteger)component
    {
        return 100;
    }
    
    c、选择器按钮的点击事件
    // 取消(重写)
    - (void)clickCancelButton
    {
        [self dismissWithAnimation:YES];
    }
    
    // 确定(重写)
    - (void)clickSureButton
    {
        NSLog(@"点击确定按钮后,执行block回调");
        
        [self dismissWithAnimation:YES];
        if (_resultBlock)
        {
            _resultBlock(_selectValue);
        }
    }
    
    // 背景(重写)
    - (void)didTapBackgroundView:(UITapGestureRecognizer *)sender
    {
         // 蒙层背景点击事件看需求,有的需要和取消一样的效果,有的可能就无效果
         [self dismissWithAnimation:YES];
    }
    

    关闭选择器视图

    - (void)dismissWithAnimation:(BOOL)animation
    {
        [UIView animateWithDuration:0.2 animations:^{
            // 动画隐藏
            CGRect rect = self.alertView.frame;
            rect.origin.y += (DatePictureHeight + TopViewHeight);
            self.alertView.frame = rect;
            
            self.backgroundView.alpha = 0;
        } completion:^(BOOL finished) {
            // 父视图移除
            [self.cancelButton removeFromSuperview];
            [self.sureButton removeFromSuperview];
            [self.titleLabel removeFromSuperview];
            [self.lineView removeFromSuperview];
            [self.topView removeFromSuperview];
            
            [self.picker removeFromSuperview];
            [self.alertView removeFromSuperview];
            [self.backgroundView removeFromSuperview];
            [self removeFromSuperview];
            
            // 清空 dealloc,创建的视图要清除,避免内存泄露
            self.cancelButton = nil;
            self.sureButton = nil;
            self.titleLabel = nil;
            self.lineView = nil;
            self.topView = nil;
            
            self.picker = nil;
            self.alertView = nil;
            self.backgroundView = nil;
        }];
    }
    
    d、弹出选择器视图
    + (void)showDatePickerWithTitle:(NSString *)title minDateString:(NSString *)minDateString resultBlock:(DateResultBlock)resultBlock
    {
        DatePickerSubview *datePicker = [[DatePickerSubview alloc] initWithTitle:title minDateString:minDateString resultBlock:resultBlock];
        [datePicker showWithAnimation:YES];
    }
    

    弹出选择器视图

    - (void)showWithAnimation:(BOOL)animation
    {
        // 获取当前应用的主窗口
        UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
        [keyWindow addSubview:self];
        
        if (animation)
        {
            // 动画前初始位置
            CGRect rect = self.alertView.frame;
            rect.origin.y = SCREEN_HEIGHT;
            self.alertView.frame = rect;
            
            // 浮现动画
            [UIView animateWithDuration:0.3 animations:^{
                CGRect rect = self.alertView.frame;
                rect.origin.y -= DatePictureHeight + TopViewHeight;
                self.alertView.frame = rect;
            }];
        }
    }
    

    Demo

    Demo在我的Github上,欢迎下载。
    FunctionCodeBlock

    参考文献

    相关文章

      网友评论

        本文标题:IOS基础:常见的自定义视图

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