需求背景
开发一个类似抖音评论弹窗交互效果的弹窗,支持滑动消失,
滑动查看评论
效果如下图
思路
创建一个视图,该视图上面放置一个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