封装了一个仿照抖音效果的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

相关推荐
吴佳浩20 小时前
OpenClaw macOS 完整安装与本地模型配置教程(实战版)
人工智能·macos·agent
开心就好20251 天前
iOS App 安全加固流程记录,代码、资源与安装包保护
后端·ios
开心就好20251 天前
iOS App 性能测试工具怎么选?使用克魔助手(Keymob)结合 Instruments 完成
后端·ios
zhongjiahao2 天前
面试常问的 RunLoop,到底在Loop什么?
ios
wvy3 天前
iOS 26手势返回到根页面时TabBar的动效问题
ios
RickeyBoy3 天前
iOS 图片取色完全指南:从像素格式到工程实践
ios
aiopencode4 天前
使用 Ipa Guard 命令行版本将 IPA 混淆接入自动化流程
后端·ios
二流小码农4 天前
鸿蒙开发:路由组件升级,支持页面一键创建
android·ios·harmonyos
vi_h4 天前
在 macOS 上通过 Docker 安装并运行 Ollama(详细可执行教程)
macos·docker·ollama
iceiceiceice5 天前
iOS PDF阅读器段评实现:如何从 PDFSelection 精准还原一个自然段
前端·人工智能·ios