封装了一个仿照抖音效果的iOS评论弹窗

需求背景

开发一个类似抖音评论弹窗交互效果的弹窗,支持滑动消失,

滑动查看评论

效果如下图

思路

创建一个视图,该视图上面放置一个tableView, 该视图上添加一个滑动手势,同时设置代理,实现代理方法

  • (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
    支持同时响应手势,就是为了我们tableView滚动到顶部的时候,继续滚动父亲视图,达到连续滑动的效果,如果不是设置同时响应的话,我们滚动到tableView顶部,继续向下滑动的话,整个弹窗是不会向下滑动的,同时,滚动到顶部的时候,要设置tableView.pangesture.enabled = NO,否则反复来回滑动的时候,会造成两个视图同时滚动的效果

代码

复制代码
//
//  LBCommentPopView.m
//  TEXT
//
//  Created by mac on 2024/7/7.
//  Copyright © 2024 刘博. All rights reserved.
//

#import "LBCommentPopView.h"
#import "LBFunctionTestHeader.h"

@interface LBCommentPopView () <UIGestureRecognizerDelegate>

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

@property (nonatomic, weak) UIScrollView *scrollView;
@property (nonatomic, assign) BOOL isDragScrollView;
@property (nonatomic, assign) CGFloat lastTransitionY;

@end

@implementation LBCommentPopView

- (instancetype)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        [self createRecognizer];
    }
    return self;
}

- (void)createRecognizer {
    [self addGestureRecognizer:self.tapGesture];
    [self addGestureRecognizer:self.panGesture];
}

- (void)show:(void (^)(void))completion {
    self.hidden = NO;
    [UIView animateWithDuration:0.25f animations:^{
        CGRect frame = self.containerView.frame;
        frame.origin.y = self.frame.size.height - frame.size.height;
        self.containerView.frame = frame;
    } completion:^(BOOL finished) {
        !completion ? : completion();
    }];
}

- (void)dismiss {
    [UIView animateWithDuration:0.25f animations:^{
        CGRect frame = self.containerView.frame;
        frame.origin.y = ScreenHeight;
        self.containerView.frame = frame;
    }completion:^(BOOL finished) {
        self.hidden = YES;
    }];
}

#pragma mark - UIGestureRecognizerDelegate
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
    if (gestureRecognizer == self.panGesture) {
        UIView *touchView = touch.view;
        while (touchView != nil) {
            if ([touchView isKindOfClass:[UIScrollView class]]) {
                self.scrollView = (UIScrollView *)touchView;
                self.isDragScrollView = YES;
                break;
            }else if (touchView == self.containerView) {
                self.isDragScrollView = NO;
                break;
            }
            touchView = (UIView *)[touchView nextResponder];
        }
    }
    return YES;
}

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
    if (gestureRecognizer == self.tapGesture) {
        CGPoint point = [gestureRecognizer locationInView:self.containerView];
        if ([self.containerView.layer containsPoint:point] && gestureRecognizer.view == self) {
            return NO;
        }
    }else if (gestureRecognizer == self.panGesture) {
        return YES;
    }
    return YES;
}

// 是否与其他手势共存
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
    if (gestureRecognizer == self.panGesture) {
        if ([otherGestureRecognizer isKindOfClass:NSClassFromString(@"UIScrollViewPanGestureRecognizer")] || [otherGestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
            if ([otherGestureRecognizer.view isKindOfClass:[UIScrollView class]]) {
                return YES;
            }
        }
    }
    return NO;
}

#pragma mark - HandleGesture
- (void)handleTapGesture:(UITapGestureRecognizer *)tapGesture {
    CGPoint point = [tapGesture locationInView:self.containerView];
    if (![self.containerView.layer containsPoint:point] && tapGesture.view == self) {
        [self dismiss];
    }
}

- (void)handlePanGesture:(UIPanGestureRecognizer *)panGesture {
    CGPoint translation = [panGesture translationInView:self.containerView];
    if (self.isDragScrollView) {
        // 当UIScrollView在最顶部时,处理视图的滑动
        if (self.scrollView.contentOffset.y <= 0) {
            if (translation.y > 0) { // 向下拖拽
                self.scrollView.contentOffset = CGPointZero;
                self.scrollView.panGestureRecognizer.enabled = NO;
                self.isDragScrollView = NO;
                
                CGRect contentFrame = self.containerView.frame;
                contentFrame.origin.y += translation.y;
                self.containerView.frame = contentFrame;
            }
        }
    }else {
        CGFloat contentM = (self.frame.size.height - self.containerView.frame.size.height);
        
        if (translation.y > 0) { // 向下拖拽
            CGRect contentFrame = self.containerView.frame;
            contentFrame.origin.y += translation.y;
            self.containerView.frame = contentFrame;
        }else if (translation.y < 0 && self.containerView.frame.origin.y > contentM) { // 向上拖拽
            CGRect contentFrame = self.containerView.frame;
            contentFrame.origin.y = MAX((self.containerView.frame.origin.y + translation.y), contentM);
            self.containerView.frame = contentFrame;
        }
    }
    
    [panGesture setTranslation:CGPointZero inView:self.containerView];
    
    if (panGesture.state == UIGestureRecognizerStateEnded) {
        CGPoint velocity = [panGesture velocityInView:self.containerView];
        
        self.scrollView.panGestureRecognizer.enabled = YES;
        
        // 结束时的速度>0 滑动距离> 5 且UIScrollView滑动到最顶部
        NSLog(@"%f", self.lastTransitionY);
        if (velocity.y > 0 && self.lastTransitionY > 5 && !self.isDragScrollView) {
            [self dismiss];
        }else {
            [self show:^{
                
            }];
        }
    }
    
    self.lastTransitionY = translation.y;
}

#pragma mark - lazy load

- (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

demo link

相关推荐
2501_9159090610 分钟前
“绑定 HTTPS” 的工程全流程 从证书配置到真机验证与故障排查
网络协议·http·ios·小程序·https·uni-app·iphone
2501_915918411 小时前
iOS 混淆实战 多工具组合完成 IPA 混淆、加固与工程化落地(iOS混淆|IPA加固|无源码混淆|Ipa Guard|Swift Shield)
android·ios·小程序·https·uni-app·iphone·webview
Aurora-silas3 小时前
LLM微调尝试——MAC版
人工智能·pytorch·深度学习·macos·机器学习·语言模型·自然语言处理
游戏开发爱好者83 小时前
如何系统化掌握 iOS 26 App 耗电管理,多工具协作
android·macos·ios·小程序·uni-app·cocoa·iphone
2501_915921435 小时前
运营日志驱动,在 iOS 26 上掌握 App 日志管理实践
android·macos·ios·小程序·uni-app·cocoa·iphone
马拉萨的春天6 小时前
iOS的多线程下数据安全和内存泄漏以及工具使用监测内存泄漏
macos·ios·cocoa·多线程
2501_9151063214 小时前
HTTPS 爬虫实战指南 从握手原理到反爬应对与流量抓包分析
爬虫·网络协议·ios·小程序·https·uni-app·iphone
2501_9160074714 小时前
iOS 上架技术支持全流程解析,从签名配置到使用 开心上架 的实战经验分享
android·macos·ios·小程序·uni-app·cocoa·iphone
QMY52052014 小时前
深度优先遍历策略
macos·jupyter·postman
非专业程序员Ping1 天前
一文读懂字体文件
ios·swift·assembly·font