IOS基础工具:自定义视图

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

目录

  • 一、Toast 提示框
  • 二、按钮垂直或者水平布局的提示框
  • 三、带图片的提示框
  • 四、窗帘
  • 五、侧边栏
  • 六、全屏视图
  • 七、分享视图
  • 八、登陆注册视图
  • 九、轮播图
  • 十、时间选择器
  • 十一、签到码
  • Demo
  • 参考文献

一、Toast 提示框

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

使用Toast
ToastView *toastView = [[ToastView alloc] initWithFrame:CGRectMake(100, 300, 200, 50)];
toastView.duration = 5;
[toastView showToast:^{
    NSLog(@"提示");
}];
[self.view addSubview:toastView];

提供的接口
@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
显示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();
            }];
        }];
    }
}

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

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] 点击了提交按钮:提交
垂直提示框
水平提示框

提供的接口

提示按钮
@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] 点击了查看详情

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、运行效果


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

a、创建全屏视图
- (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;
}
b、实现全屏视图按钮的点击事件
- (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、全屏视图的实现

a、触发点击全屏视图的回调
- (void)fullViewClicked:(UITapGestureRecognizer *)recognizer
{
    __weak typeof(self) _self = self;
    [self endAnimationsWithCompletion:^(FullView *fullView) {
        if (self.didClickFullView)
        {
            _self.didClickFullView((FullView *)recognizer.view);
        }
    }];
b、触发点击图片按钮的回调
- (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);
        }
    }
}
c、滑动到初始位置
- (void)slideToInitialPosition:(UIButton *)sender
{
    [_scrollContainer setContentOffset:CGPointMake(0, 0) animated:YES];
}
d、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];
}
e、开始动画
- (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];
        }
    }];
}
f、结束动画
- (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作为TableView的Cell
@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;
}
c、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;
        }];
    }
}

十一、签到码

1、展示效果

视图布局
布局就是一个数字编码UILabel在上面,最下面一个UIView的下划线
运行效果
签到码
使用签到码功能
VerificationCode *verificationCode = [[VerificationCode alloc] initWithFrame:CGRectMake(100, 200, 200, 80)];
verificationCode = [verificationCode initWithCodeBits:3];
[self.view addSubview:verificationCode];
verificationCode.inputCompleted = ^(NSString * _Nonnull content) {
    
    // 当四位签到码全部输入时,提交按钮是可以提交的,否则提交按钮失效,不允许提交
    NSLog(@"输入完成,四位签到码为:%@",content);
    button.enabled = YES;
};

verificationCode.inputUnCompleted = ^(NSString * _Nonnull content) {
    NSLog(@"输入尚未完成,已输入的签到码为:%@",content);
    button.enabled = NO;
};
输出结果
2020-09-25 17:51:54.371982+0800 FunctionCodeBlockDemo[12110:18487495] currentIndex:0
2020-09-25 17:51:54.372300+0800 FunctionCodeBlockDemo[12110:18487495] 输入尚未完成,已输入的签到码为:5
2020-09-25 17:51:54.957179+0800 FunctionCodeBlockDemo[12110:18487495] currentIndex:1
2020-09-25 17:51:54.957459+0800 FunctionCodeBlockDemo[12110:18487495] 输入尚未完成,已输入的签到码为:55
2020-09-25 17:51:55.474404+0800 FunctionCodeBlockDemo[12110:18487495] currentIndex:2
2020-09-25 17:51:55.474686+0800 FunctionCodeBlockDemo[12110:18487495] 输入完成,四位签到码为:555
2020-09-25 17:51:56.822271+0800 FunctionCodeBlockDemo[12110:18487495] 输入尚未完成,已输入的签到码为:55
2020-09-25 17:51:57.716892+0800 FunctionCodeBlockDemo[12110:18487495] 输入尚未完成,已输入的签到码为:5
2020-09-25 17:51:58.514644+0800 FunctionCodeBlockDemo[12110:18487495] 输入尚未完成,未输入签到码

typedef void(^InputCompleted)(NSString *content);
typedef void(^InputUnCompleted)(NSString *content);

/** 设置签到码输入完成的处理方案 */
@property(nonatomic, copy) InputCompleted inputCompleted;
/** 设置签到码输入未完成对应的处理方案 */
@property(nonatomic, copy) InputUnCompleted inputUnCompleted;
/** 提供了一个可以修改签到码位数的入口 */
- (instancetype)initWithCodeBits:(NSInteger)codeBits;
@interface VerificationCode () <UITextFieldDelegate>

// 监听内容输入。用一个透明的UITextField来接收键盘的输入信息
@property (strong, nonatomic) UITextField *contentTextField;
// 显示输入内容的codeView数组。用4个CodeView来分别展示输入的签到码信息,放在一个数组中,方便后续的访问和调用
@property (strong, nonatomic) NSArray<Code *> *codeArray;
// 当前待输入的codeView的下标
@property (assign, nonatomic) NSInteger currentIndex;
// 位数
@property (assign, nonatomic) NSInteger codeBits;

@end

在初始化签到码视图的时候传入签到位数codeBits,有几个codeBits我们就创建几个Code视图,每个Code视图上面是用于展示数字的Label,下面是条可以变换颜色的下划线,当用于输入之后下划线立刻变成蓝色。签到码默认是4位,所以默认创建4个Code视图。

if (self.codeBits < 1)
{
    self.codeBits = 4;
}

for (NSInteger i = 0; i < self.codeBits; I++)
{
    Code *codeView = self.codeArray[i];
    [self addSubview:codeView];
}

Code视图如何根据Label中是否有内容让下划线联动进行颜色变化呢?其实很简单只需要重写text的设置方法就OK了。当设置的text内容不为空时,我们就设置对应的颜色为需要的颜色(蓝色),否则设置为灰色。

- (void)setText:(NSString *)text
{
    if (text.length > 0)
    {
        self.codeLabel.text = [text substringToIndex:1];
        self.lineView.backgroundColor = [UIColor blueColor];
    }
    else
    {
        self.codeLabel.text = @"";
        self.lineView.backgroundColor = [UIColor grayColor];
    }
}

layoutSubviews方法中bounds才会有值,所以我们此方法中对多个Code视图进行了布局,其实主要是计算了其所在的x轴位置而已。 设定每个数字之间的间距为数字view宽度的一半,总宽度就是 bits + (bits - 1)* 0.5

- (void)layoutSubviews
{
    [super layoutSubviews];
    
    CGFloat codeViewWidth = self.bounds.size.width / (self.codeBits * 1.5 - 0.5);
    for (NSInteger i=0; i<self.codeBits; i++)
    {
        Code *codeView = self.codeArray[i];
        [codeView mas_updateConstraints:^(MASConstraintMaker *make) {
            CGFloat left = codeViewWidth * 1.5 * I;
            make.left.mas_equalTo(self).mas_offset(left);
            make.top.bottom.mas_equalTo(self).mas_offset(0.0f);
            make.width.mas_equalTo(codeViewWidth);
        }];
    }
}

用户如何输入数字到文本框中呢?我们在Code视图下面还放了一个UITextField视图,只是将其颜色置为透明的而已,背景颜色、字体颜色、光标颜色都设置为透明的,这样在界面上就看不到了,哈哈有点儿小聪明吧。

_contentTextField.backgroundColor = [UIColor clearColor];
_contentTextField.textColor = [UIColor clearColor];
_contentTextField.tintColor = [UIColor clearColor];
_contentTextField.keyboardType = UIKeyboardTypeNumberPad;// 数字键盘
_contentTextField.returnKeyType = UIReturnKeyDone;// 完成

UITextField不能进行复制、粘贴、选择等操作。canPerformAction该函数控制是否允许选择、全选、剪切、粘贴等功能,可以针对不同功能进行限制,返回YES表示允许对应的功能,直接返回NO则表示不允许任何编辑。

- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
    return NO;
}

在输入和删除时进内容进行判断,并将对应的内容获取到展示出来,注意完成、删除操作的判断一定要在是否是纯数字以及位数过长判断之前,否则可能会导致完成、删除操作失效。

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
    ...
    return YES;
}
1.完成则收回键盘
if ([string isEqualToString:@"\n"])
{
    [textField resignFirstResponder];
    return NO;
}
2.删除操作

待输入的下标为0时,删除按钮不起作用,删除时下标不变化,否则当删除一个签到码时,currIndex要减1。

if ([string isEqualToString:@""])
{
    // 待输入的下标为0时,删除按钮不起作用,删除时下标不变化
    if (self.currentIndex == 0)
    {
        self.codeArray[self.currentIndex].text = string;
        NSLog(@"输入尚未完成,未输入签到码");
    }
    else
    {
        self.codeArray[self.currentIndex].text = string;
        if (self.inputUnCompleted)
        {
            NSString *content = [textField.text substringToIndex:self.currentIndex];
            self.inputUnCompleted(content);
        }
        self.currentIndex--;
    }
    return YES;
}
3.判断输入的是否是纯数字,不是纯数字则输入无效
if (![string judgeIsPureInt])
{
    return NO;
}
4.如果输入的内容超过了签到码的长度则输入无效
if ((textField.text.length + string.length) > self.codeBits)
{
    return NO;
}
5.判断是否输入完成

通过判断currIndex 是否等于self.codeBits来确定签到码输入是否完成,相等则完成,否则没有完成,调用对应的block进行处理。在完成时提供输入的签到码信息。

if (self.currentIndex == (self.codeBits - 1) && self.inputCompleted)
{
    self.codeArray[self.currentIndex].text = string;
    NSString *content = [NSString stringWithFormat:@"%@%@", textField.text, string];
    self.inputCompleted(content);
}

当输入一个合法的签到码时,当前待输入的下标对应的view中添加输入的数字stringcurrIndex要加1。

else
{
    self.codeArray[self.currentIndex].text = string;
    self.currentIndex++;
    
    if (self.inputUnCompleted)
    {
        NSString *content = [NSString stringWithFormat:@"%@%@", textField.text, string];
        self.inputUnCompleted(content);
    }
}

Demo

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

参考文献

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349