| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256 |
- //
- // 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
|