iOS-SDK开发

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

2020-10-20  本文已影响0人  时光啊混蛋_97boy

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

目录


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

参考文献

上一篇下一篇

猜你喜欢

热点阅读