当前位置:   article > 正文

封装了一个iOS评论弹窗

封装了一个iOS评论弹窗

封装了一个iOS类似抖音效果的评论弹窗,可以跟手滑动的效果
主要有下面两需要注意的点

双手势响应

因为我们的弹窗既要支持拖动整体上下滑动,还要支持内容列表的滑动
,所以,我们需要在内容视图中添加一个滑动的手势,以此来滑动弹窗
并且要支持同时响应,同时要注意,支持同时响应并不是同时滑动,
而是支持手指在列表中的时候,可以响应弹窗的手势

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
    NSLog(@"shouldRecognizeSimultaneouslyWithGestureRecognizer: %@", gestureRecognizer);
    if (gestureRecognizer == self.panGesture) {
        if ([otherGestureRecognizer isEqual:self.scrollView.panGestureRecognizer]) {
            return YES;
        }
    }
    return NO;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

滑动弹窗的时候,禁止列表滚动

这个很好理解,因为如果我们滑动弹窗的时候,仍然滑动列表,那样的用户体验就会很差,所以我们要处理的就是在我们滚动弹窗的时候,禁止列表的滚动

代码

.h

//
//  LBSlidePopView.h
//  TEXT
//
//  Created by mac on 2024/7/28.
//  Copyright © 2024 刘博. All rights reserved.
//

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@protocol LBSlidePopViewDelegate <NSObject>

- (void)slideDismiss:(NSString *)source;

@optional

- (void)didPanGesture:(UIPanGestureRecognizer *)panGesture;

@end

typedef NS_ENUM(NSInteger, LBSlideViewExpand) {
    LBSlideViewExpandNone = 0,   // 无
    LBSlideViewExpandMin = 1,
    LBSlideViewExpandMid = 2,
    LBSlideViewExpandMax = 3
};

@protocol LBPopSlideFrameDelegate <NSObject>

- (void)frameChanged:(CGRect)frame percent:(CGFloat)percent isMax:(BOOL)isMax inAnimation:(BOOL)inAnimation finished:(BOOL)finished;
- (void)frameChangedInAnimationBlock:(CGRect)frame percent:(CGFloat)percent isMax:(BOOL)isMax;
- (void)expandStatusChanged:(LBSlideViewExpand)status lastStatus:(LBSlideViewExpand)lastStatus;

// Offer区块
- (BOOL)shouldCollopseOfferView;
- (BOOL)shouldExpandOfferView;
- (void)updateOfferViewFrame:(CGFloat)offset;
- (void)collopseOrExpandOfferView;

@end

@interface LBSlidePopView : UIView

@property (nonatomic, weak) id<LBPopSlideFrameDelegate> frameDelegate;

@property (nonatomic, weak) UIView                     *contentView;   // 主内容

@property (nonatomic, strong) UITapGestureRecognizer    *tapGesture;
@property (nonatomic, strong) UIPanGestureRecognizer    *panGesture;
@property (nonatomic, strong) UILongPressGestureRecognizer *longPressGesture;

@property (nonatomic, weak) UIScrollView                *scrollView;
@property (nonatomic, assign) CGFloat beginContentOffsetY;
@property (nonatomic, assign) BOOL                      isDragScrollView;
@property (nonatomic, assign) CGFloat                   lastTransitionY;
@property (nonatomic, assign) CGFloat                   beginY;
@property (nonatomic, assign) BOOL                      hasImpactFeedback;   // 是否已经震动过
@property (nonatomic, assign) CGFloat                    slideHPercent;   // 横向滚动的阈值:用来判断是否横向滚动还是竖向滚动
///允许横向滑动消失,默认NO
@property (nonatomic, assign) BOOL horizontalPanDismiss;

@property (nonatomic, weak) id<LBSlidePopViewDelegate> delegate;

@property (nonatomic, assign) BOOL                    disableGesture;   // 是否禁用手势

//原来的内容的高度
@property (nonatomic, assign) CGFloat contentOriginHeight;


- (instancetype)initWithFrame:(CGRect)frame contentView:(UIView *)contentView maskView:(nullable UIView *)maskView delegate:(id<LBSlidePopViewDelegate>)delegate;

- (void)updateContentFrame:(CGFloat)offset;
- (void)dismiss:(NSString *)source;

- (void)updateForSingleView;

@end

NS_ASSUME_NONNULL_END

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82

.m

//
//  LBSlidePopView.m
//  TEXT
//
//  Created by mac on 2024/7/28.
//

#import "LBSlidePopView.h"
#import "LBFunctionTestHeader.h"

@interface LBSlidePopView ()<UIGestureRecognizerDelegate>

@property (nonatomic, weak) UIView *maskView;

@end

@implementation LBSlidePopView

- (instancetype)initWithFrame:(CGRect)frame contentView:(UIView *)contentView
                     maskView:(UIView *)maskView delegate:(id<LBSlidePopViewDelegate>)delegate {
    if (self = [super initWithFrame:frame]) {
        self.backgroundColor = [UIColor clearColor];
        self.delegate = delegate;
        self.maskView = maskView;
        self.contentView = contentView;
        [self addSubview:self.maskView];
        [self addSubview:self.contentView];
        
        // 横滑幅度较大,幅度可开关控制,默认是60°夹角
        self.slideHPercent = 0.58;
        
        // 添加手势
        [self addGestureRecognizer:self.tapGesture];
        [self addGestureRecognizer:self.panGesture];
    }
    return self;
}

- (void)updateForSingleView {
    self.zf_height = self.contentView.zf_height;
    self.contentView.zf_top = 0;
    [self.maskView removeFromSuperview];
}

- (void)dealloc {
    NSLog(@"##** LIVSlidePopupView dealloc");
}

- (void)setDisableGesture:(BOOL)disableGesture {
    if (_disableGesture != disableGesture) {
        _disableGesture = disableGesture;
        if (disableGesture) {
            [self removeGestureRecognizer:self.tapGesture];
            [self removeGestureRecognizer:self.panGesture];
        } else {
            [self addGestureRecognizer:self.tapGesture];
            [self addGestureRecognizer:self.panGesture];
        }
    }
}

- (void)showWithCompletion:(void (^)(void))completion {
    [UIView animateWithDuration:0.25f animations:^{
        self.contentView.zf_bottom = self.zf_height;
    } completion:^(BOOL finished) {
        !completion ? : completion();
        [self frameChanged:NO];
    }];
}

- (void)dismiss:(NSString *)source {
    NSLog(@"");
    if (self.delegate && [self.delegate respondsToSelector:@selector(slideDismiss:)]) {
        [self.delegate slideDismiss:source];
    }
    
    [self frameChanged:YES];
}

#pragma mark - UIGestureRecognizerDelegate

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
    NSLog(@"gestureRecognizer shouldReceiveTouch: %@", gestureRecognizer);
    if (gestureRecognizer == self.panGesture) {
        UIView *touchView = touch.view;
        while (touchView != nil) {
            NSLog(@"%@", touchView);
            if ([touchView isKindOfClass:NSClassFromString(@"EmotionBoardScrollView")]
                || [touchView isKindOfClass:[UITextView class]]) {
                // 滑动Emoji键盘、输入框时不处理
                self.isDragScrollView = NO;
                return NO;
            }
            if (touchView == self.scrollView) {
                self.isDragScrollView = YES;
                break;
            } else if (touchView == self.contentView) {
                self.isDragScrollView = NO;
                break;
            }
            touchView = (UIView *)[touchView nextResponder];
        }
    }
    return YES;
}

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
    NSLog(@"gestureRecognizerShouldBegin: %@", gestureRecognizer);
    if (gestureRecognizer == self.tapGesture) {
        CGPoint point = [gestureRecognizer locationInView:self.contentView];
        if ([self.contentView.layer containsPoint:point] && gestureRecognizer.view == self) {
            return NO;
        }
    } else if (gestureRecognizer == self.panGesture) {
        CGPoint translation = [self.panGesture translationInView:self.contentView];
        if (!self.horizontalPanDismiss) {
            // 不处理禁用横滑的手势
            if (ABS(translation.y) == 0) {   // 完全横滑
                return NO;
            } else if ((ABS(translation.x) / ABS(translation.y)) > self.slideHPercent) {   // 横滑幅度较大,幅度可开关控制,默认是60°夹角
                return NO;
            }
        }
        self.beginY = self.zf_top;
    }
    self.hasImpactFeedback = NO;
    return YES;
}

//是否支持多手势触发,返回YES,则可以多个手势一起触发方法,返回NO则为互斥
//是否允许多个手势识别器共同识别,一个控件的手势识别后是否阻断手势识别继续向下传播,默认返回NO;
//如果为YES,响应者链上层对象触发手势识别后,如果下层对象也添加了手势并成功识别也会继续执行,否则上层对象识别后则不再继续传播
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
    NSLog(@"shouldRecognizeSimultaneouslyWithGestureRecognizer: %@", gestureRecognizer);
    if (gestureRecognizer == self.panGesture) {
        if ([otherGestureRecognizer isEqual:self.scrollView.panGestureRecognizer]) {
            return YES;
        }
    }
    return NO;
}

 这个方法返回YES,第一个手势和第二个互斥时,第一个会失效
//- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
//    return NO;
//}
//
这个方法返回YES,第一个和第二个互斥时,第二个会失效
//- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
//    return NO;
//}

#pragma mark - HandleGesture

- (void)handleTapGesture:(UITapGestureRecognizer *)tapGesture {
    CGPoint point = [tapGesture locationInView:self.contentView];
    if (![self.contentView.layer containsPoint:point] && tapGesture.view == self) {
        [self dismiss:@"blankArea"];
    }
}

- (void)handlePanGesture:(UIPanGestureRecognizer *)panGesture {
    CGPoint translationPoint = [panGesture translationInView:self.contentView];
    CGFloat translationOffset = translationPoint.y;
    BOOL isHorizontal = NO;
    if ((ABS(translationPoint.y) == 0 || (ABS(translationPoint.x) / ABS(translationPoint.y)) > self.slideHPercent)
        && self.horizontalPanDismiss) {
        translationOffset = translationPoint.x;
        // 根据contentOffset变化判断scrollView是否有滚动过,scrollView没有滚动过才能支持横向滑动关闭面板
        isHorizontal = ABS(self.beginContentOffsetY - self.scrollView.contentOffset.y) < 0.5;
    }
    if (self.isDragScrollView) {
        // 当UIScrollView在最顶部时,处理视图的滑动
        if (self.scrollView.contentOffset.y <= 0 || isHorizontal) {
            if (translationOffset > 0) { // 向右、向下拖拽
//                self.scrollView.contentOffset = CGPointZero;
                self.scrollView.panGestureRecognizer.enabled = NO;
                self.isDragScrollView = NO;
                
                [self updateContentFrame:translationOffset];
            }
        }
    } else {
        [self updateContentFrame:translationOffset];
    }
    
    [panGesture setTranslation:CGPointZero inView:self.contentView];
    if (panGesture.state == UIGestureRecognizerStateBegan) {
        // 手势开始时记录contentOffset初始位置
        self.beginContentOffsetY = self.scrollView.contentOffset.y;
    }
    else if (panGesture.state == UIGestureRecognizerStateEnded) {
        CGPoint velocityPoint = [panGesture velocityInView:self.contentView];
        
        self.scrollView.panGestureRecognizer.enabled = YES;
        
        // (结束时的速度>0 滑动距离> 5) 或 绝对移动距离超过150 且UIScrollView滑动到最顶部
        CGFloat velocityOffset = velocityPoint.y;
        if ((ABS(velocityPoint.y) == 0 || (ABS(velocityPoint.x) / ABS(velocityPoint.y)) > self.slideHPercent)
             && self.horizontalPanDismiss) {
            velocityOffset = velocityPoint.x;
        }
        if (((velocityOffset > 0 && self.lastTransitionY > 5) || (self.contentView.zf_bottom - self.zf_height) > 150)  && !self.isDragScrollView) {
            [self dismiss:@"swipeDown"];
        } else {
            [self showWithCompletion:nil];
        }
    }
    
    self.lastTransitionY = translationOffset;
    
    if (self.delegate && [self.delegate respondsToSelector:@selector(didPanGesture:)]) {
        [self.delegate didPanGesture:panGesture];
    }
}

// 更新内容区域的frame
- (void)updateContentFrame:(CGFloat)offset {
    CGFloat topY = (self.zf_height - self.contentView.zf_height);
    self.contentView.zf_top = MAX(topY, self.contentView.zf_top + offset);
    
    // 下拉时蒙层透明度变化
    CGFloat rate = 1.f;
    if (self.contentView.zf_height > 0 && self.contentView.zf_bottom > self.zf_height) {
        rate = MIN(1.f, (self.zf_height - self.contentView.zf_top) / self.contentView.zf_height);
        rate = MAX(0.f, rate);
    }
    self.maskView.alpha = 0.55 * rate;
    
    [self frameChanged:NO];
}

- (void)frameChanged:(BOOL)dismiss {
    if (self.frameDelegate && [self.frameDelegate respondsToSelector:@selector(frameChanged:percent:isMax:inAnimation:finished:)]) {
        CGRect frame = CGRectZero;
        if (!dismiss) {
            frame.size.height = self.contentOriginHeight - self.contentView.zf_top;
        }
        [self.frameDelegate frameChanged:frame percent:!dismiss isMax:!dismiss inAnimation:YES finished:dismiss];
    }
}

#pragma mark - 懒加载

- (UITapGestureRecognizer *)tapGesture {
    if (!_tapGesture) {
        _tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture:)];
        _tapGesture.delegate = self;
    }
    return _tapGesture;
}

- (UIPanGestureRecognizer *)panGesture {
    if (!_panGesture) {
        _panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGesture:)];
        _panGesture.delegate = self;
    }
    return _panGesture;
}

@end

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262

弹窗中的代码

//
//  LBSlidePopViewController.m
//  TEXT
//
//  Created by mac on 2024/8/18.
//  Copyright © 2024 刘博. All rights reserved.
//

#import "LBSlidePopViewController.h"
#import "LBSlidePopView.h"
#import "LBFunctionTestHeader.h"
#import "LBview.h"

@interface LBSlidePopViewController () <UITableViewDelegate, UITableViewDataSource, LBSlidePopViewDelegate>

@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, strong) LBSlidePopView *slideView;

@end

@implementation LBSlidePopViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self setUpUI];
    [self.tableView reloadData];
    // Do any additional setup after loading the view.
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    [self.navigationController setNavigationBarHidden:true animated:animated];
}

- (void)setUpUI
{
    self.view.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.4];
    CGFloat top = SCREEN_HEIGHT - 400;
    self.viewContent = [[LBview alloc] initWithFrame:CGRectMake(0, top, SCREEN_WIDTH, 400)];
    self.viewContent.backgroundColor = [UIColor purpleColor];
    self.view.backgroundColor = [UIColor whiteColor];
    
    //半透明背景
    self.viewDismiss = [[UIView alloc] initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT)];
    self.viewDismiss.alpha = 0.0;
    self.slideView = [[LBSlidePopView alloc] initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT) contentView:self.viewContent maskView:self.viewDismiss delegate:self];
    self.view = self.slideView;
    [self.slideView addGestureRecognizer:self.slideView.tapGesture];
    self.view.backgroundColor = [UIColor clearColor];
    self.view.window.backgroundColor = [UIColor clearColor];
    [self.viewContent addSubview:self.tableView];
    
    self.slideView.scrollView = self.tableView;
}

#pragma mark - UITableViewDelegate, UITableViewDataSource

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return 100;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return 40;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"kkk"];
    return cell;
}

#pragma mark - LBSlidePopViewDelegate

- (void)slideDismiss:(NSString *)source
{
    [self dismissViewControllerAnimated:YES completion:nil];
}

#pragma mark - lazy load

- (UITableView *)tableView
{
    if (!_tableView) {
        _tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, 400) style:UITableViewStylePlain];
        [_tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"kkk"];
        _tableView.delegate = self;
        _tableView.dataSource = self;
    }
    return _tableView;
}

/*
#pragma mark - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.
}
*/

@end

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106

代码
链接: link

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/1016520
推荐阅读
相关标签
  

闽ICP备14008679号