iOS development-CAShapeLayer and UIBezierPath realize the drop-down menu effect of WeChat home page

iOS development-CAShapeLayer and UIBezierPath realize the drop-down menu effect of WeChat home page

In the previous development, I encountered the need to use the drop-down menu effect of the WeChat homepage. CAShapeLayer and UIBezierPath are used to draw the menu frame.

insert image description here

1. Rendering

insert image description here

Two, CAShapeLayer and UIBezierPath

2.1. What is CAShapeLayer?

CAShapeLayer inherits from CALayer, and all properties of CALayer can be used
. CAShapeLayer needs to draw graphics with UIBezierPath.

Create shapeLayer

// 创建 shapeLayer
CAShapeLayer *shapeLayer = [[CAShapeLayer alloc]init];
[self.view.layer addSublayer:shapeLayer];
shapeLayer.path = path.CGPath;
shapeLayer.fillColor = [UIColor clearColor].CGColor;
shapeLayer.strokeColor = [UIColor blackColor].CGColor;
shapeLayer.lineWidth = 5;

2.2. What is UIBezierPath?

UIBezierPath is the Bezier curve
UIBezierPath class allows you to draw and render a path composed of straight lines and curves in a custom View

+ (instancetype)bezierPath;
+ (instancetype)bezierPathWithRect:(CGRect)rect;
+ (instancetype)bezierPathWithOvalInRect:(CGRect)rect;
+ (instancetype)bezierPathWithRoundedRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadius; // rounds all corners with the same horizontal and vertical radius
+ (instancetype)bezierPathWithRoundedRect:(CGRect)rect byRoundingCorners:(UIRectCorner)corners cornerRadii:(CGSize)cornerRadii;
+ (instancetype)bezierPathWithArcCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise;
+ (instancetype)bezierPathWithCGPath:(CGPathRef)CGPath;

3. Draw arrows

Arrows will be displayed when the pop-up menu pops up. Here, UIBezierPath and CAShapeLayer are used to draw the arrows.

#pragma mark - DrawShapeLayer
- (void)drawShapeLayer {
    
    
    
    CGFloat startPointX = self.contentStartPoint.x;
    CGFloat startPointY = self.contentStartPoint.y;
    
    CGFloat width = CGRectGetWidth(self.contentBGImageView.frame);
    CGFloat height = CGRectGetHeight(self.contentBGImageView.frame);

    self.path = [UIBezierPath bezierPath]; // 创建路径
    [self.path moveToPoint:CGPointMake(startPointX, startPointY)]; // 设置起始点
    
    [self.path addLineToPoint:CGPointMake(startPointX + 5.0, kAnchorHeight)];

    [self.path addLineToPoint:CGPointMake(width - 5.0, kAnchorHeight)];
    
    [self.path addArcWithCenter:CGPointMake(width - 5.0, kAnchorHeight + 5.0) radius:5 startAngle:KCP*3/2 endAngle:2*KCP clockwise:YES]; // 绘制一个圆弧
    
    [self.path addLineToPoint:CGPointMake(width, height - 5.0)];
    
    [self.path addArcWithCenter:CGPointMake(width - 5.0, height - 5.0) radius:5 startAngle:0 endAngle:KCP/2 clockwise:YES]; // 绘制一个圆弧
    [self.path addLineToPoint:CGPointMake(5, height)];
    [self.path addArcWithCenter:CGPointMake(5.0, height-5.0) radius:5 startAngle:KCP/2 endAngle:KCP clockwise:YES]; // 绘制一个圆弧
    
    [self.path addLineToPoint:CGPointMake(0.0, kAnchorHeight + 5.0)];
    
    [self.path addArcWithCenter:CGPointMake(5, kAnchorHeight + 5) radius:5 startAngle:KCP endAngle:KCP*3/2 clockwise:YES]; // 绘制一个圆弧
    
    [self.path addLineToPoint:CGPointMake(startPointX - 5.0, kAnchorHeight)];
    [self.path addLineToPoint:CGPointMake(startPointX, startPointY)];

    self.path.lineWidth     = 2.f;
    self.path.lineCapStyle  = kCGLineCapRound;
    self.path.lineJoinStyle = kCGLineCapRound;
    
    [self.path closePath]; // 封闭未形成闭环的路径
    
    UIGraphicsBeginImageContext(self.contentBGImageView.bounds.size);
    [self.path stroke];
    UIGraphicsEndImageContext();

    self.shapeLayer.path = self.path.CGPath;
}

4. Click and touch the mask processing

When the drop-down menu is displayed, touchesBegan is implemented on the MaskView, and the menu can be hidden by clicking and touching.

INNoteZoneOptionMaskView.h

#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>

@protocol INNoteZoneOptionMaskViewDelegate;
@interface INNoteZoneOptionMaskView : UIView

@property (nonatomic, weak) id<INNoteZoneOptionMaskViewDelegate>maskDelegate;

@end

@protocol INNoteZoneOptionMaskViewDelegate <NSObject>

- (void)optionMaskTouched;

@end

INNoteZoneOptionMaskView.m

#import "INNoteZoneOptionMaskView.h"

@implementation INNoteZoneOptionMaskView

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event {
    
    
    // 1.自己先处理事件...
    if (self.maskDelegate && [self.maskDelegate respondsToSelector:@selector(optionMaskTouched)]) {
    
    
        [self.maskDelegate optionMaskTouched];
    }
    // 2.再调用系统的默认做法,再把事件交给上一个响应者处理
    [super touchesBegan:touches withEvent:event];
}

@end

5. Menu display

Controls are displayed on the keyWindow

[[UIApplication sharedApplication].keyWindow addSubview:self];

show animation

- (void)showOption {
    
    
    
    self.hidden = NO;
    
    CGPoint anchorPoint = CGPointMake(self.contentStartPoint.x/self.contentBGImageView.frame.size.width, 0.0);
    [self resetAnChorPoint:self.contentBGImageView anchorPoint:anchorPoint];
    self.contentBGImageView.transform = CGAffineTransformMakeScale(0.1, 0.1);
    
    [UIView animateWithDuration:0.25 animations:^{
    
    
        self.contentBGImageView.transform = CGAffineTransformMakeScale(1.0, 1.0);
        self.contentBGImageView.alpha = 1.0;
    } completion:^(BOOL finished) {
    
    
        
    }];
}

hidden menu

- (void)dismissOption {
    
    
    [UIView animateWithDuration:0.25 animations:^{
    
    
        self.contentBGImageView.transform = CGAffineTransformMakeScale(0.1, 0.1);
        self.contentBGImageView.alpha = 0.0;
    } completion:^(BOOL finished) {
    
    
        self.hidden = YES;
        [self removeFromSuperview];
    }];
}

5. Complete menu code

The full code of the menu is as follows

INNoteZoneOptionView.h

#import <UIKit/UIKit.h>

/**
 元素的item
 */
typedef void(^OptionTouchBlock)(void);
@interface INNoteZoneOptionItem : NSObject

@property (nonatomic, strong) UIImage *iconImage;
@property (nonatomic, strong) NSString *title;
@property (nonatomic, copy) OptionTouchBlock block;

@end


/**
 按钮控件
 */
@interface INNoteZoneOptionButton : UIControl

@property (nonatomic, strong) UIImage *iconImage;
@property (nonatomic, strong) NSString *title;
@property (nonatomic, assign) BOOL showLine;
@property (nonatomic, strong) INNoteZoneOptionItem *item;

@end


/**
 点击➕号的选项操作
 */
@interface INNoteZoneOptionView : UIView

- (instancetype)initWithFrame:(CGRect)frame anchorPoint:(CGPoint)anchorPoint items:(NSArray *)items;

- (void)showOption;

- (void)dismissOption;

@end

INNoteZoneOptionView.m

#import "INNoteZoneOptionView.h"
#import "UIColor+Addition.h"
#import "UIImageView+WebCache.h"
#import "UIImage+YYAdd.h"
#import "INNoteZoneOptionMaskView.h"

#define KCP 3.1415926

static CGFloat kOpContentWidth = 130.0;
static CGFloat kOpItemHeight = 50.0;

static CGFloat kOpIconSize = 16.0;
static CGFloat kOpMidPadding = 10.0;
static CGFloat kAnchorHeight = 5.0;

static CGFloat kBtnMidPadding = 15.0;

static CGFloat kBtnPadding = 2.0;
static CGFloat kLineHeight = 1.0;

/**
 元素的item
 */
@interface INNoteZoneOptionItem ()


@end


@implementation INNoteZoneOptionItem


@end


/**
 按钮控件
 */
@interface INNoteZoneOptionButton ()

@property (nonatomic, strong) UIImageView *bgImageView;
@property (nonatomic, strong) UIImageView *iconImageView;
@property (nonatomic, strong) UILabel *titleLabel;
@property (nonatomic, strong) UIImageView *lineImageView;

@end

@implementation INNoteZoneOptionButton

- (instancetype)initWithFrame:(CGRect)frame
{
    
    
    self = [super initWithFrame:frame];
    if (self) {
    
    
        [self addSubview:self.bgImageView];
        [self.bgImageView addSubview:self.iconImageView];
        [self.bgImageView addSubview:self.titleLabel];
        [self.bgImageView addSubview:self.lineImageView];
        self.showLine = NO;
    }
    return self;
}

- (void)layoutSubviews {
    
    
    [super layoutSubviews];
    
    self.bgImageView.frame = self.bounds;
    self.iconImageView.frame = CGRectMake(kBtnMidPadding, (CGRectGetHeight(self.bounds) - kOpIconSize)/2, kOpIconSize, kOpIconSize);
    self.titleLabel.frame = CGRectMake(CGRectGetMaxX(self.iconImageView.frame) + kBtnMidPadding, 0.0, CGRectGetWidth(self.bgImageView.frame) - (CGRectGetMaxX(self.iconImageView.frame) + kBtnMidPadding*2), CGRectGetHeight(self.bounds));
    self.lineImageView.frame = CGRectMake(0.0, CGRectGetHeight(self.bgImageView.frame) - kLineHeight, CGRectGetWidth(self.bgImageView.frame), kLineHeight);
}

- (void)setIconImage:(UIImage *)iconImage {
    
    
    _iconImage = iconImage;
    self.iconImageView.image = iconImage;
    [self setNeedsLayout];
}

- (void)setTitle:(NSString *)title {
    
    
    _title = (title?title:@"");
    self.titleLabel.text = _title;
    [self setNeedsLayout];
}

- (void)setShowLine:(BOOL)showLine {
    
    
    _showLine = showLine;
    self.lineImageView.hidden = !showLine;
    [self setNeedsLayout];
}

- (void)setHighlighted:(BOOL)highlighted {
    
    
    [super setHighlighted:highlighted];
    if (highlighted) {
    
    
        self.bgImageView.backgroundColor = [UIColor colorWithHexString:@"f4f4f4"];
    } else {
    
    
        self.bgImageView.backgroundColor = [UIColor whiteColor];
    }
}

#pragma mark - SETTER/GETTER
- (UIImageView *)iconImageView {
    
    
    if (!_iconImageView) {
    
    
        _iconImageView = [[UIImageView alloc] initWithFrame:CGRectZero];
        _iconImageView.contentMode = UIViewContentModeScaleAspectFit;
    }
    return _iconImageView;
}

- (UILabel *)titleLabel {
    
    
    if (!_titleLabel) {
    
    
        _titleLabel = [[UILabel alloc] initWithFrame:CGRectZero];
        _titleLabel.font = [UIFont boldSystemFontOfSize:14];
        _titleLabel.textColor = [UIColor colorWithHexString:@"131619"];
        _titleLabel.backgroundColor = [UIColor clearColor];
    }
    
    return _titleLabel;
}

- (UIImageView *)bgImageView {
    
    
    if (!_bgImageView) {
    
    
        _bgImageView = [[UIImageView alloc] initWithFrame:CGRectZero];
        _bgImageView.backgroundColor = [UIColor whiteColor];
        _bgImageView.layer.cornerRadius = 2.0;
        _bgImageView.layer.masksToBounds = YES;
    }
    return _bgImageView;
}

- (UIImageView *)lineImageView {
    
    
    if (!_lineImageView) {
    
    
        _lineImageView = [[UIImageView alloc] initWithFrame:CGRectZero];
        _lineImageView.backgroundColor = [UIColor colorWithHexString:@"f1f1f1"];
    }
    return _lineImageView;
}

@end

/**
 点击➕号的选项操作
 */
@interface INNoteZoneOptionView ()<INNoteZoneOptionMaskViewDelegate>

@property (nonatomic, strong) INNoteZoneOptionMaskView *maskBGView;
@property (nonatomic, strong) UIImageView *contentBGImageView;
@property (nonatomic, strong) CAShapeLayer *shapeLayer;
@property (nonatomic, strong) UIBezierPath *path;

@property (nonatomic) CGPoint anchorPoint;
@property (nonatomic) CGPoint contentStartPoint;
@property (nonatomic, strong) NSArray *items;

@end

@implementation INNoteZoneOptionView

- (instancetype)initWithFrame:(CGRect)frame anchorPoint:(CGPoint)anchorPoint items:(NSArray *)items {
    
    
    self = [super initWithFrame:frame];
    if (self) {
    
    
        
        self.anchorPoint = anchorPoint;
        self.items = items;
        
        [self addSubview:self.maskBGView];
        [self addSubview:self.contentBGImageView];
        [self.contentBGImageView.layer addSublayer:self.shapeLayer];
        
        [[UIApplication sharedApplication].keyWindow addSubview:self];
        self.frame = [UIScreen mainScreen].bounds;
        self.hidden = YES;
        
        [self layoutOptionSubviews];

        [self configContentPoint];
        
        [self drawShapeLayer];
        
        [self setupOptionButtons];
    }
    return self;
}

- (void)configContentPoint {
    
    
    CGFloat scale = self.anchorPoint.x/self.frame.size.width;
    CGFloat contentWidth = CGRectGetWidth(self.contentBGImageView.frame);
    self.contentStartPoint = CGPointMake(scale*contentWidth - kOpMidPadding, 0.0);
}

- (void)layoutOptionSubviews {
    
    
    self.maskBGView.frame = self.bounds;
    self.contentBGImageView.frame = CGRectMake(CGRectGetWidth(self.bounds) - kOpContentWidth - kOpMidPadding, self.anchorPoint.y, kOpContentWidth, self.items.count*kOpItemHeight + kAnchorHeight + 2*kBtnPadding);
    self.shapeLayer.frame = self.contentBGImageView.bounds;
}

#pragma mark - Setup Buttons
- (void)setupOptionButtons {
    
    
    NSInteger index = 0;
    for (INNoteZoneOptionItem *item in self.items) {
    
    
        INNoteZoneOptionButton *button = [[INNoteZoneOptionButton alloc] initWithFrame:CGRectZero];
        button.frame = CGRectMake(kBtnPadding, kBtnPadding + kAnchorHeight + index*kOpItemHeight, CGRectGetWidth(self.contentBGImageView.frame) - 2*kBtnPadding, kOpItemHeight);
        
        button.iconImage = item.iconImage;
        button.title = item.title;
        button.item = item;
        [self.contentBGImageView addSubview:button];
        [button addTarget:self action:@selector(optionButtonAction:) forControlEvents:UIControlEventTouchUpInside];
        
        button.showLine = YES;
        if (index == self.items.count - 1) {
    
    
            button.showLine = NO;
        }
        
        index++;
    }
}

- (void)optionButtonAction:(INNoteZoneOptionButton *)button {
    
    
    NSLog(@"点击栏目");
    if (button.item && button.item.block) {
    
    
        button.item.block();
    }
}

#pragma mark - Show AND Dismiss
- (void)showOption {
    
    
    
    self.hidden = NO;
    
    CGPoint anchorPoint = CGPointMake(self.contentStartPoint.x/self.contentBGImageView.frame.size.width, 0.0);
    [self resetAnChorPoint:self.contentBGImageView anchorPoint:anchorPoint];
    self.contentBGImageView.transform = CGAffineTransformMakeScale(0.1, 0.1);
    
    [UIView animateWithDuration:0.25 animations:^{
    
    
        self.contentBGImageView.transform = CGAffineTransformMakeScale(1.0, 1.0);
        self.contentBGImageView.alpha = 1.0;
    } completion:^(BOOL finished) {
    
    
        
    }];
}

- (void)dismissOption {
    
    
    [UIView animateWithDuration:0.25 animations:^{
    
    
        self.contentBGImageView.transform = CGAffineTransformMakeScale(0.1, 0.1);
        self.contentBGImageView.alpha = 0.0;
    } completion:^(BOOL finished) {
    
    
        self.hidden = YES;
        [self removeFromSuperview];
    }];
}

#pragma mark - ResetAnChorPoint
/**
 设置动画图钉位置
 
 @param view subView
 */
- (void)resetAnChorPoint:(UIView *)view anchorPoint:(CGPoint)anchorPoint {
    
    
    CGPoint oldAnchorPoint = view.layer.anchorPoint;
    view.layer.anchorPoint = anchorPoint;
    [view.layer setPosition:CGPointMake(view.layer.position.x + view.layer.bounds.size.width * (view.layer.anchorPoint.x - oldAnchorPoint.x), view.layer.position.y + view.layer.bounds.size.height * (view.layer.anchorPoint.y - oldAnchorPoint.y))];
}


#pragma mark - INNoteZoneOptionMaskViewDelegate
- (void)optionMaskTouched {
    
    
    [self dismissOption];
}

#pragma mark - SETTER/GETTER
- (INNoteZoneOptionMaskView *)maskBGView {
    
    
    if (!_maskBGView) {
    
    
        _maskBGView = [[INNoteZoneOptionMaskView alloc] initWithFrame:CGRectZero];
        _maskBGView.backgroundColor = [UIColor clearColor];
        _maskBGView.userInteractionEnabled = YES;
        _maskBGView.maskDelegate = self;
    }
    return _maskBGView;
}

- (UIImageView *)contentBGImageView {
    
    
    if (!_contentBGImageView) {
    
    
        _contentBGImageView = [[UIImageView alloc] initWithFrame:CGRectZero];
        _contentBGImageView.userInteractionEnabled = YES;
        _contentBGImageView.backgroundColor = [UIColor clearColor];
    }
    return _contentBGImageView;
}

- (CAShapeLayer *)shapeLayer {
    
    
    if (!_shapeLayer) {
    
    
        _shapeLayer = [CAShapeLayer layer];
        _shapeLayer.fillColor = [UIColor whiteColor].CGColor;
        //设置线条的宽度和颜色
        _shapeLayer.lineWidth = 1.0f;
        _shapeLayer.strokeColor = [UIColor colorWithHexString:@"9bb9ef" alpha:0.55].CGColor;
        _shapeLayer.shadowColor = [UIColor colorWithHexString:@"9bb9ef"].CGColor;
        _shapeLayer.shadowOffset = CGSizeMake(-3, 3);
        _shapeLayer.shadowOpacity = 0.3;
        _shapeLayer.shadowRadius = 3.0;
    }
    return _shapeLayer;
}

#pragma mark - DrawShapeLayer
- (void)drawShapeLayer {
    
    
    
    CGFloat startPointX = self.contentStartPoint.x;
    CGFloat startPointY = self.contentStartPoint.y;
    
    CGFloat width = CGRectGetWidth(self.contentBGImageView.frame);
    CGFloat height = CGRectGetHeight(self.contentBGImageView.frame);

    self.path = [UIBezierPath bezierPath]; // 创建路径
    [self.path moveToPoint:CGPointMake(startPointX, startPointY)]; // 设置起始点
    
    [self.path addLineToPoint:CGPointMake(startPointX + 5.0, kAnchorHeight)];

    [self.path addLineToPoint:CGPointMake(width - 5.0, kAnchorHeight)];
    
    [self.path addArcWithCenter:CGPointMake(width - 5.0, kAnchorHeight + 5.0) radius:5 startAngle:KCP*3/2 endAngle:2*KCP clockwise:YES]; // 绘制一个圆弧
    
    [self.path addLineToPoint:CGPointMake(width, height - 5.0)];
    
    [self.path addArcWithCenter:CGPointMake(width - 5.0, height - 5.0) radius:5 startAngle:0 endAngle:KCP/2 clockwise:YES]; // 绘制一个圆弧
    [self.path addLineToPoint:CGPointMake(5, height)];
    [self.path addArcWithCenter:CGPointMake(5.0, height-5.0) radius:5 startAngle:KCP/2 endAngle:KCP clockwise:YES]; // 绘制一个圆弧
    
    [self.path addLineToPoint:CGPointMake(0.0, kAnchorHeight + 5.0)];
    
    [self.path addArcWithCenter:CGPointMake(5, kAnchorHeight + 5) radius:5 startAngle:KCP endAngle:KCP*3/2 clockwise:YES]; // 绘制一个圆弧
    
    [self.path addLineToPoint:CGPointMake(startPointX - 5.0, kAnchorHeight)];
    [self.path addLineToPoint:CGPointMake(startPointX, startPointY)];

    self.path.lineWidth     = 2.f;
    self.path.lineCapStyle  = kCGLineCapRound;
    self.path.lineJoinStyle = kCGLineCapRound;
    
    [self.path closePath]; // 封闭未形成闭环的路径
    
    UIGraphicsBeginImageContext(self.contentBGImageView.bounds.size);
    [self.path stroke];
    UIGraphicsEndImageContext();

    self.shapeLayer.path = self.path.CGPath;
}

@end

6. Use the display drop-down menu

Use the display drop-down menu code to determine the displayed arrow position according to anchorPoint

#pragma mark - INNoteZoneNavbarViewDelegate
- (void)addButtonDidAction:(UIButton *)button {
    
    
    CGRect btnRect = [button convertRect:button.bounds toView:[UIApplication sharedApplication].keyWindow];
    
    __weak typeof(self) weakSelf = self;
    INNoteZoneOptionItem *postItem = [[INNoteZoneOptionItem alloc] init];
    postItem.iconImage = [UIImage imageNamed:@"ic_op_editpost"];
    postItem.title = @"发布帖子";
    postItem.block = ^{
    
    
        NSLog(@"发布帖子");
    };
    
    INNoteZoneOptionItem *scanItem = [[INNoteZoneOptionItem alloc] init];
    scanItem.iconImage = [UIImage imageNamed:@"ic_op_scan"];
    scanItem.title = @"扫一扫";
    scanItem.block = ^{
    
    
        NSLog(@"扫一扫");
    };
    
    INNoteZoneOptionItem *calItem = [[INNoteZoneOptionItem alloc] init];
    calItem.iconImage = [UIImage imageNamed:@"ic_op_cal"];
    calItem.title = @"日历";
    calItem.block = ^{
    
    
        NSLog(@"日历");
    };
    
    INNoteZoneOptionItem *pubWordItem = [[INNoteZoneOptionItem alloc] init];
    pubWordItem.iconImage = [UIImage imageNamed:@"ic_op_edit"];
    pubWordItem.title = @"发布美句";
    pubWordItem.block = ^{
    
    
        NSLog(@"发布美句");
    };

    INNoteZoneOptionView *optionView = [[INNoteZoneOptionView alloc] initWithFrame:CGRectZero anchorPoint:CGPointMake(CGRectGetMidX(btnRect), CGRectGetMaxY(btnRect)) items:@[postItem,scanItem,calItem,pubWordItem]];
    [optionView showOption];
}

7. Summary

iOS development-CAShapeLayer and UIBezierPath realize the drop-down menu effect of WeChat homepage. CAShapeLayer and UIBezierPath are used to draw the menu frame.
Learning records, keep improving every day.

Guess you like

Origin blog.csdn.net/gloryFlow/article/details/131993997