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


    需求背景

    开发一个类似抖音评论弹窗交互效果的弹窗,支持滑动消失,
    滑动查看评论
    效果如下图
    请添加图片描述

    思路

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

    • (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
      支持同时响应手势,就是为了我们tableView滚动到顶部的时候,继续滚动父亲视图,达到连续滑动的效果,如果不是设置同时响应的话,我们滚动到tableView顶部,继续向下滑动的话,整个弹窗是不会向下滑动的,同时,滚动到顶部的时候,要设置tableView.pangesture.enabled = NO,否则反复来回滑动的时候,会造成两个视图同时滚动的效果
      这里要注意,同时响应是说滑动tableView的同时,会响应 我们添加的滑动手势,并不是
      会导致两个视图同时滑动,具体外层手势怎么响应,需要我们自己根据tableView的滑动位置进行自定义处理

    代码

    //
    //  LBCommentPopView.m
    //  TEXT
    //
    //  Created by mac on 2024/7/7.
    //  Copyright © 2024 刘博. All rights reserved.
    //
    
    #import "LBCommentPopView.h"
    #import "LBFunctionTestHeader.h"
    
    @interface LBCommentPopView () 
    
    @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

  • 相关阅读:
    [C++ 网络协议] I/O流分离所带来的半关闭问题
    整理自己平时用到的【快捷键】
    大数据hadoop_HDFS概述(1)
    大数据-玩转数据-Flink定时器
    二十五、MySQL事务的四大特性和常见的并发事务问题
    webman 事务回滚失效问题记录
    Ant-Design-Vue 动态表头
    如何在Linux部署Portainer并结合内网穿透远程管理本地Docker容器
    Java+JSP+MySQL基于SSM的雷锋车队管理系统的设计与实现-计算机毕业设计
    edu 154 div2 c ( 模拟
  • 原文地址:https://blog.csdn.net/LIUXIAOXIAOBO/article/details/140246423