// // ChatPopoverView.m // AIIM // // Created by qitewei on 2025/5/7. // #import "ChatPopoverView.h" static CGFloat const kDefaultArrowHeight = 8.0; static CGFloat const kDefaultCornerRadius = 5.0; static CGFloat const kDefaultPopoverMargin = 10.0; static CGFloat const kDefaultBorderWidth = 1.0; @interface ChatPopoverView () @property (nonatomic, strong) UIView *contentView; @property (nonatomic, weak) UIView *sourceView; @property (nonatomic, strong) UIControl *overlayView; @property (nonatomic, assign) PopoverViewArrowDirection finalArrowDirection; @end @implementation ChatPopoverView #pragma mark - Lifecycle - (instancetype)initWithContentView:(UIView *)contentView sourceView:(UIView *)sourceView { self = [super initWithFrame:CGRectZero]; if (self) { _contentView = contentView; _sourceView = sourceView; _shouldDismissOnOutsideTap = YES; _preferredPosition = PopoverViewPositionAuto; _arrowDirection = PopoverViewArrowDirectionAuto; _sourceViewGap = 0; _showArrow = YES; _popoverBackgroundColor = [UIColor whiteColor]; _borderColor = [UIColor lightGrayColor]; _borderWidth = 1.0; _cornerRadius = 5.0; _arrowHeight = 8.0; _popoverMargin = 10.0; [self commonInit]; [self layoutViews]; } return self; } - (void)dealloc { [self.overlayView removeFromSuperview]; } #pragma mark - Public Methods - (void)show { UIWindow *window = [UIApplication sharedApplication].delegate.window; // if (!window) { // window = [[UIApplication sharedApplication].windows firstObject]; // } // 添加遮罩视图 self.overlayView.frame = window.bounds; [window addSubview:self.overlayView]; [window addSubview:self]; self.alpha = 0; self.transform = CGAffineTransformMakeScale(0.8, 0.8); self.overlayView.alpha = 0; [UIView animateWithDuration:0.2 animations:^{ self.alpha = 1; self.transform = CGAffineTransformIdentity; self.overlayView.alpha = 1; }]; } - (void)dismiss { [UIView animateWithDuration:0.2 animations:^{ self.alpha = 0; self.transform = CGAffineTransformMakeScale(0.8, 0.8); self.overlayView.alpha = 0; } completion:^(BOOL finished) { [self.overlayView removeFromSuperview]; [self removeFromSuperview]; }]; } #pragma mark - Private Methods - (void)commonInit { self.backgroundColor = [UIColor clearColor]; self.clipsToBounds = NO; [self addSubview:self.contentView]; // 初始化遮罩视图 _overlayView = [[UIControl alloc] initWithFrame:CGRectZero]; _overlayView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.2]; [_overlayView addTarget:self action:@selector(overlayTapped) forControlEvents:UIControlEventTouchUpInside]; // 计算最终弹出方向 [self calculateFinalDirection]; } - (void)overlayTapped { if (self.shouldDismissOnOutsideTap) { [self dismiss]; } } - (void)calculateFinalDirection { if (!self.showArrow) { self.finalArrowDirection = PopoverViewArrowDirectionNone; return; } CGRect sourceRect = [self.sourceView convertRect:self.sourceView.bounds toView:nil]; CGFloat spaceAbove = CGRectGetMinY(sourceRect); CGFloat spaceBelow = [UIScreen mainScreen].bounds.size.height - CGRectGetMaxY(sourceRect); if (self.preferredPosition != PopoverViewPositionAuto) { if (self.preferredPosition == PopoverViewPositionTop) { self.finalArrowDirection = PopoverViewArrowDirectionDown; } else { self.finalArrowDirection = PopoverViewArrowDirectionUp; } } else if (self.arrowDirection != PopoverViewArrowDirectionAuto) { self.finalArrowDirection = self.arrowDirection; } else { if (spaceBelow > spaceAbove) { self.finalArrowDirection = PopoverViewArrowDirectionUp; } else { self.finalArrowDirection = PopoverViewArrowDirectionDown; } } } #pragma mark - Layout - (void)layoutViews { CGRect sourceRect = [self.sourceView convertRect:self.sourceView.bounds toView:nil]; CGFloat currentArrowHeight = self.showArrow ? self.arrowHeight : 0; CGFloat popoverWidth = CGRectGetWidth(self.contentView.bounds) + 2 * self.cornerRadius; CGFloat popoverHeight = CGRectGetHeight(self.contentView.bounds) + currentArrowHeight + 2 * self.cornerRadius; // 计算x坐标,确保不超出屏幕 CGFloat x = CGRectGetMidX(sourceRect) - popoverWidth / 2; x = MAX(self.popoverMargin, MIN(x, [UIScreen mainScreen].bounds.size.width - popoverWidth - self.popoverMargin)); // 计算y坐标 CGFloat y; if (self.finalArrowDirection == PopoverViewArrowDirectionUp) { y = CGRectGetMaxY(sourceRect) + self.sourceViewGap; } else if (self.finalArrowDirection == PopoverViewArrowDirectionDown) { y = CGRectGetMinY(sourceRect) - popoverHeight - self.sourceViewGap; } else { // 无箭头时默认显示在下方 y = CGRectGetMaxY(sourceRect) + self.sourceViewGap; } // 检查是否超出屏幕边界 if (y < self.popoverMargin) { y = self.popoverMargin; } else if (y + popoverHeight > [UIScreen mainScreen].bounds.size.height - self.popoverMargin) { y = [UIScreen mainScreen].bounds.size.height - popoverHeight - self.popoverMargin; } self.frame = CGRectMake(x, y, popoverWidth, popoverHeight); [self updateContentViewFrame]; } - (void)updateContentViewFrame { CGFloat currentArrowHeight = self.showArrow ? self.arrowHeight : 0; if (self.finalArrowDirection == PopoverViewArrowDirectionUp) { self.contentView.frame = CGRectMake(self.cornerRadius, currentArrowHeight + self.cornerRadius, CGRectGetWidth(self.bounds) - 2 * self.cornerRadius, CGRectGetHeight(self.bounds) - currentArrowHeight - 2 * self.cornerRadius); } else { self.contentView.frame = CGRectMake(self.cornerRadius, self.cornerRadius, CGRectGetWidth(self.bounds) - 2 * self.cornerRadius, CGRectGetHeight(self.bounds) - currentArrowHeight - 2 * self.cornerRadius); } } #pragma mark - Drawing - (void)drawRect:(CGRect)rect { [super drawRect:rect]; UIBezierPath *path = [UIBezierPath bezierPath]; path.lineWidth = self.borderWidth; CGRect contentRect; CGFloat currentArrowHeight = self.showArrow ? self.arrowHeight : 0; if (self.finalArrowDirection == PopoverViewArrowDirectionUp) { contentRect = CGRectMake(0, currentArrowHeight, CGRectGetWidth(rect), CGRectGetHeight(rect) - currentArrowHeight); } else { contentRect = CGRectMake(0, 0, CGRectGetWidth(rect), CGRectGetHeight(rect) - currentArrowHeight); } // 绘制圆角矩形 [path moveToPoint:CGPointMake(CGRectGetMinX(contentRect) + self.cornerRadius, CGRectGetMinY(contentRect))]; [path addLineToPoint:CGPointMake(CGRectGetMaxX(contentRect) - self.cornerRadius, CGRectGetMinY(contentRect))]; [path addArcWithCenter:CGPointMake(CGRectGetMaxX(contentRect) - self.cornerRadius, CGRectGetMinY(contentRect) + self.cornerRadius) radius:self.cornerRadius startAngle:3 * M_PI_2 endAngle:0 clockwise:YES]; [path addLineToPoint:CGPointMake(CGRectGetMaxX(contentRect), CGRectGetMaxY(contentRect) - self.cornerRadius)]; [path addArcWithCenter:CGPointMake(CGRectGetMaxX(contentRect) - self.cornerRadius, CGRectGetMaxY(contentRect) - self.cornerRadius) radius:self.cornerRadius startAngle:0 endAngle:M_PI_2 clockwise:YES]; [path addLineToPoint:CGPointMake(CGRectGetMinX(contentRect) + self.cornerRadius, CGRectGetMaxY(contentRect))]; [path addArcWithCenter:CGPointMake(CGRectGetMinX(contentRect) + self.cornerRadius, CGRectGetMaxY(contentRect) - self.cornerRadius) radius:self.cornerRadius startAngle:M_PI_2 endAngle:M_PI clockwise:YES]; [path addLineToPoint:CGPointMake(CGRectGetMinX(contentRect), CGRectGetMinY(contentRect) + self.cornerRadius)]; [path addArcWithCenter:CGPointMake(CGRectGetMinX(contentRect) + self.cornerRadius, CGRectGetMinY(contentRect) + self.cornerRadius) radius:self.cornerRadius startAngle:M_PI endAngle:3 * M_PI_2 clockwise:YES]; // 绘制箭头 if (self.showArrow && self.finalArrowDirection != PopoverViewArrowDirectionNone) { CGRect sourceRect = [self.sourceView convertRect:self.sourceView.bounds toView:nil]; CGFloat arrowX = CGRectGetMidX(sourceRect) - CGRectGetMinX(self.frame); arrowX = MAX(self.cornerRadius + self.arrowHeight, MIN(arrowX, CGRectGetWidth(rect) - self.cornerRadius - self.arrowHeight)); if (self.finalArrowDirection == PopoverViewArrowDirectionUp) { [path moveToPoint:CGPointMake(arrowX - self.arrowHeight, self.arrowHeight)]; [path addLineToPoint:CGPointMake(arrowX, 0)]; [path addLineToPoint:CGPointMake(arrowX + self.arrowHeight, self.arrowHeight)]; } else if (self.finalArrowDirection == PopoverViewArrowDirectionDown) { [path moveToPoint:CGPointMake(arrowX - self.arrowHeight, CGRectGetHeight(rect) - self.arrowHeight)]; [path addLineToPoint:CGPointMake(arrowX, CGRectGetHeight(rect))]; [path addLineToPoint:CGPointMake(arrowX + self.arrowHeight, CGRectGetHeight(rect) - self.arrowHeight)]; } } [self.popoverBackgroundColor setFill]; [self.borderColor setStroke]; [path fill]; [path stroke]; } @end