Files
ln-ios/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Class/AAddHPopView.m
2026-04-13 16:13:23 +08:00

360 lines
13 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// AAddHPopView.m
// AMapNavIOSSDK
//
#import "AAddHPopView.h"
#import <Masonry/Masonry.h>
#import "UIColor+ANavMap.h"
// ─── 尺寸常量 ────────────────────────────────────────────────────────────────
static const CGFloat kBubbleWidth = 200.f; // 气泡宽度
static const CGFloat kHeaderHeight = 50.f; // 顶部绿色标题区高度
static const CGFloat kOptionHeight = 60.f; // 每行选项高度
static const CGFloat kArrowWidth = 50.f; // 箭头底边宽aw = ah*2 使左右45度对称
static const CGFloat kArrowHeight = 15.f; // 箭头高度
static const CGFloat kBubbleRadius = 15.f; // 气泡圆角半径
static const CGFloat kBubbleGap = 5.f; // 气泡底部与 sourceView 顶部间距
static const CGFloat kScreenPadding = 15.f; // 气泡距屏幕边缘最小距离
// ─── 自定义气泡容器:自绘白色气泡 + 底部箭头 ─────────────────────────────────
@interface _ABubbleContainerView : UIView
/// 箭头中心 X相对于本 view 坐标系),用于 showInView 精确定位
@property (nonatomic, assign) CGFloat arrowCenterX;
@end
@implementation _ABubbleContainerView
- (instancetype)init {
self = [super init];
if (self) {
self.backgroundColor = [UIColor clearColor];
_arrowCenterX = kBubbleWidth / 2.f;
}
return self;
}
- (void)setArrowCenterX:(CGFloat)arrowCenterX {
_arrowCenterX = arrowCenterX;
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect {
CGFloat bw = kBubbleWidth; // 160
CGFloat bh = kHeaderHeight + kOptionHeight * 2.f; // 150
CGFloat r = kBubbleRadius; // 10
CGFloat aw = kArrowWidth; // 16 箭头底边宽
CGFloat ah = kArrowHeight; // 10 箭头高度
// 箭头在底部偏右指向右下45度
// 箭头底边: 从 (arrowX-aw/2, bh) 到 (arrowX, bh)
// 箭头尖端: (arrowX + ah, bh + ah) — 相对于底边中心向右下45度
CGFloat arrowX = bw - 15.f; // 箭头底边右端 X偏右下
CGFloat arrowTipX = arrowX + ah; // 箭头尖端 X向右偏移ah = 45度
CGFloat arrowTipY = bh + ah; // 箭头尖端 Y向下偏移ah = 45度
UIBezierPath *path = [UIBezierPath bezierPath];
// ── 顶部左圆角 → 顶部右圆角 ──
[path moveToPoint:CGPointMake(r, 0)];
[path addLineToPoint:CGPointMake(bw - r, 0)];
[path addArcWithCenter:CGPointMake(bw - r, r) radius:r
startAngle:-M_PI_2 endAngle:0 clockwise:YES];
// ── 右边 → 右下圆角 ──
[path addLineToPoint:CGPointMake(bw, bh - r)];
[path addArcWithCenter:CGPointMake(bw - r, bh - r) radius:r
startAngle:0 endAngle:M_PI_2 clockwise:YES];
// ── 底边 → 箭头底边右端 ──
[path addLineToPoint:CGPointMake(arrowX, bh)];
// 箭头斜边45度向右下到尖端
[path addLineToPoint:CGPointMake(arrowTipX, arrowTipY)];
// 箭头底边左端(垂直向上)
[path addLineToPoint:CGPointMake(arrowX - ah, bh)];
// ── 左下圆角 ──
[path addArcWithCenter:CGPointMake(r, bh - r) radius:r
startAngle:M_PI_2 endAngle:M_PI clockwise:YES];
// ── 左边 → 左上圆角 ──
[path addLineToPoint:CGPointMake(0, r)];
[path addArcWithCenter:CGPointMake(r, r) radius:r
startAngle:M_PI endAngle:-M_PI_2 clockwise:YES];
[path closePath];
// ── 阴影 + 白色填充 ──
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSaveGState(ctx);
CGContextSetShadowWithColor(ctx, CGSizeMake(0, 3), 10.f,
[[UIColor blackColor] colorWithAlphaComponent:0.15f].CGColor);
[[UIColor whiteColor] setFill];
[path fill];
CGContextRestoreGState(ctx);
[[UIColor whiteColor] setFill];
[path fill];
// 保存箭头尖端坐标用于定位
_arrowTipX = arrowTipX;
_arrowTipY = arrowTipY;
}
// 供外部访问的箭头尖端坐标
static CGFloat _arrowTipX = 0;
static CGFloat _arrowTipY = 0;
@end
// ─── AAddHPopView ────────────────────────────────────────────────────────────
@interface AAddHPopView ()
@property (nonatomic, strong) _ABubbleContainerView *bubbleContainer;
@property (nonatomic, strong) UIView *headerView;
@property (nonatomic, strong) UILabel *titleLabel;
@property (nonatomic, strong) UIView *separatorLine1;
@property (nonatomic, strong) UILabel *option1Label;
@property (nonatomic, strong) UIView *separatorLine2;
@property (nonatomic, strong) UILabel *option2Label;
@end
@implementation AAddHPopView
#pragma mark - Init
- (instancetype)init {
return [self initWithFrame:CGRectZero];
}
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self p_setupUI];
}
return self;
}
#pragma mark - Setup UI
- (void)p_setupUI {
self.backgroundColor = [UIColor clearColor];
// ── 点击空白处self消失 ──
UITapGestureRecognizer *bgTap = [[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(dismiss)];
bgTap.cancelsTouchesInView = NO;
[self addGestureRecognizer:bgTap];
// ── 气泡容器 ──
[self addSubview:self.bubbleContainer];
// ── header绿色标题区仅裁剪上两角 ──
[self.bubbleContainer addSubview:self.headerView];
[self.headerView addSubview:self.titleLabel];
// ── 分隔线 1 ──
[self.bubbleContainer addSubview:self.separatorLine1];
// ── 选项 1 ──
[self.bubbleContainer addSubview:self.option1Label];
// ── 分隔线 2 ──
[self.bubbleContainer addSubview:self.separatorLine2];
// ── 选项 2 ──
[self.bubbleContainer addSubview:self.option2Label];
// ── 阻止气泡内点击冒泡到 bgTap ──
UITapGestureRecognizer *bubbleTap = [[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(p_bubbleTapped)];
[self.bubbleContainer addGestureRecognizer:bubbleTap];
// ── Masonry 内部约束(基于 bubbleContainer ──
// 子视图宽度统一约束到 kBubbleWidth160气泡主体实际宽度
[self.headerView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.left.equalTo(self.bubbleContainer);
make.width.mas_equalTo(kBubbleWidth);
make.height.mas_equalTo(kHeaderHeight);
}];
[self.titleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.headerView);
}];
[self.separatorLine1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.headerView.mas_bottom);
make.left.mas_equalTo(@0);
make.width.mas_equalTo(kBubbleWidth);
make.height.mas_equalTo(0.5);
}];
[self.option1Label mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.separatorLine1.mas_bottom);
make.left.mas_equalTo(@0);
make.width.mas_equalTo(kBubbleWidth);
make.height.mas_equalTo(kOptionHeight);
}];
[self.separatorLine2 mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.option1Label.mas_bottom);
make.left.mas_equalTo(@0);
make.width.mas_equalTo(kBubbleWidth);
make.height.mas_equalTo(0.5);
}];
[self.option2Label mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.separatorLine2.mas_bottom);
make.left.mas_equalTo(@0);
make.width.mas_equalTo(kBubbleWidth);
make.height.mas_equalTo(kOptionHeight);
}];
}
#pragma mark - Show / Dismiss
- (void)showInView:(UIView *)view sourceView:(UIView *)sourceView {
self.frame = view.bounds;
[view addSubview:self];
// ── 计算 sourceView 在 view 坐标系中的 frame ──
CGRect srcFrame = [sourceView convertRect:sourceView.bounds toView:view];
// ── 容器尺寸 ──
CGFloat containerW = kBubbleWidth; // 160
CGFloat containerH = kHeaderHeight + kOptionHeight * 2.f; // 150
CGFloat ah = kArrowHeight; // 16 箭头高度
// ── 箭头尖端坐标(相对于 bubbleContainer 左下角为原点)──
// 与 drawRect 中一致arrowX = bw - 35, arrowTipX = arrowX + ah
CGFloat arrowX_inContainer = containerW - 25.f; // 箭头底边右端 X
CGFloat arrowTipX_inContainer = arrowX_inContainer + ah; // 箭头尖端 X右下45度
CGFloat arrowTipY_inContainer = containerH + ah; // 箭头尖端 Y
// ── 位置计算:气泡在按钮左上方,箭头尖端指向按钮中心 ──
CGFloat srcCenterX = CGRectGetMidX(srcFrame);
CGFloat srcCenterY = CGRectGetMidY(srcFrame);
// 箭头尖端应指向按钮中心
CGFloat containerX = srcCenterX - arrowTipX_inContainer;
containerX = MAX(kScreenPadding, MIN(containerX, view.bounds.size.width - containerW - kScreenPadding));
// 气泡顶部位置
CGFloat containerY = srcCenterY - arrowTipY_inContainer;
containerY = MAX(kScreenPadding, containerY);
self.bubbleContainer.bounds = CGRectMake(0, 0, containerW, containerH + ah);
// 锚点设置在箭头尖端位置(相对于容器),实现箭头精准指向
self.bubbleContainer.layer.anchorPoint = CGPointMake(arrowTipX_inContainer / containerW,
arrowTipY_inContainer / (containerH + ah));
self.bubbleContainer.center = CGPointMake(srcCenterX - 25, srcCenterY - 20);
// ── 入场动画 ──
self.alpha = 0.f;
self.bubbleContainer.transform = CGAffineTransformMakeScale(0.85f, 0.85f);
[UIView animateWithDuration:0.25f
delay:0.f
usingSpringWithDamping:0.72f
initialSpringVelocity:0.3f
options:UIViewAnimationOptionCurveEaseOut
animations:^{
self.alpha = 1.f;
self.bubbleContainer.transform = CGAffineTransformIdentity;
} completion:nil];
}
- (void)dismiss {
[UIView animateWithDuration:0.18f
delay:0.f
options:UIViewAnimationOptionCurveEaseIn
animations:^{
self.alpha = 0.f;
self.bubbleContainer.transform = CGAffineTransformMakeScale(0.85f, 0.85f);
} completion:^(BOOL finished) {
[self removeFromSuperview];
}];
}
#pragma mark - Private Actions
- (void)p_bubbleTapped {
// 阻止冒泡,气泡内点击不消失
}
#pragma mark - Lazy Load
- (_ABubbleContainerView *)bubbleContainer {
if (!_bubbleContainer) {
_bubbleContainer = [[_ABubbleContainerView alloc] init];
}
return _bubbleContainer;
}
- (UIView *)headerView {
if (!_headerView) {
_headerView = [[UIView alloc] init];
_headerView.backgroundColor = [UIColor hp_colorWithRGBHex:0x1BA855];
if (@available(iOS 11.0, *)) {
_headerView.layer.cornerRadius = kBubbleRadius;
_headerView.layer.maskedCorners = kCALayerMinXMinYCorner | kCALayerMaxXMinYCorner;
_headerView.clipsToBounds = YES;
}
}
return _headerView;
}
- (UILabel *)titleLabel {
if (!_titleLabel) {
_titleLabel = [[UILabel alloc] init];
_titleLabel.text = @"加氢规划模式";
_titleLabel.textColor = [UIColor whiteColor];
_titleLabel.font = [UIFont boldSystemFontOfSize:15.f];
_titleLabel.textAlignment = NSTextAlignmentCenter;
}
return _titleLabel;
}
- (UIView *)separatorLine1 {
if (!_separatorLine1) {
_separatorLine1 = [[UIView alloc] init];
_separatorLine1.backgroundColor = [UIColor colorWithWhite:0.88f alpha:1.f];
}
return _separatorLine1;
}
- (UILabel *)option1Label {
if (!_option1Label) {
_option1Label = [[UILabel alloc] init];
_option1Label.text = @"送货规划模式";
_option1Label.textColor = [UIColor hp_colorWithRGBHex:0xC9CDD4];
_option1Label.font = [UIFont boldSystemFontOfSize:14.f];
_option1Label.textAlignment = NSTextAlignmentCenter;
}
return _option1Label;
}
- (UIView *)separatorLine2 {
if (!_separatorLine2) {
_separatorLine2 = [[UIView alloc] init];
_separatorLine2.backgroundColor = [UIColor colorWithWhite:0.88f alpha:1.f];
}
return _separatorLine2;
}
- (UILabel *)option2Label {
if (!_option2Label) {
_option2Label = [[UILabel alloc] init];
_option2Label.text = @"成本计算模式";
_option2Label.textColor = [UIColor hp_colorWithRGBHex:0xC9CDD4];
_option2Label.font = [UIFont boldSystemFontOfSize:14.f];
_option2Label.textAlignment = NSTextAlignmentCenter;
}
return _option2Label;
}
@end