// // GameUniversalBannerView.m // YuMi // // Created by P on 2024/10/15. // #import "GameUniversalBannerView.h" #import #import #import "AttachmentModel.h" #import "PIUniversalBannerModel.h" @interface GameUniversalBannerView () @property (nonatomic, strong) NetImageView *bgImageView; @property (nonatomic, strong) SVGAImageView *bgSVGA; @property (nonatomic, strong) NetImageView *gameIcon; @property (nonatomic, strong) NetImageView *avatarIcon; @property (nonatomic, strong) UILabel *titleLabel; @property (nonatomic, strong) UIButton *goButton; @property (nonatomic, assign) NSString *gameID; @property (nonatomic, strong) SVGAParser *parser; @property(nonatomic, assign) bool alreadyCancel; @property (nonatomic, copy) void(^completeDisplay)(void); @property (nonatomic, copy) void(^didTapGo)(NSInteger gameID); @end @implementation GameUniversalBannerView + (void)display:(UIView *)superView with:(AttachmentModel *)attachment complete:(void(^)(void))complete goToGame:(void(^)(NSInteger gameID))go { CGFloat width = KScreenWidth; CGFloat height = kGetScaleWidth(70); PIUniversalBannerModel *model = [PIUniversalBannerModel modelWithDictionary:attachment.data]; GameUniversalBannerView *bannerView = [[GameUniversalBannerView alloc] initWithFrame:CGRectMake(KScreenWidth, 0, width, height)]; bannerView.model = model; bannerView.completeDisplay = complete; bannerView.didTapGo = go; bannerView.gameID = model.skipContent; [superView addSubview:bannerView]; @kWeakify(bannerView); [bannerView popEnterAnimation:^(BOOL finished) { @kStrongify(bannerView); [bannerView addNotification]; if (finished && bannerView.alreadyCancel == NO) { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [bannerView popLeaveAnimation:^(bool finished) { if (bannerView.completeDisplay) { bannerView.completeDisplay(); } [bannerView removeNotification]; [bannerView removeFromSuperview]; }]; }); } }]; } - (void)handleSwipeNotification { [self dismissBanner]; } - (void)handleTapNotification:(NSNotification *)note { NSValue *value = note.userInfo[@"point"]; CGPoint point = [value CGPointValue]; NSLog(@"🔄 GameUniversalBannerView: 接收到点击点 %@ (bannerContainer坐标系)", NSStringFromCGPoint(point)); // 将 bannerContainer 坐标系中的点转换为 GameUniversalBannerView 坐标系中的点 CGPoint bannerPoint = [self convertPoint:point fromView:self.superview]; NSLog(@"🔄 GameUniversalBannerView: 转换为 banner 坐标系 %@", NSStringFromCGPoint(bannerPoint)); NSLog(@"%@", CGRectContainsPoint(self.goButton.frame, bannerPoint) ? @"YES" : @"NO"); // 检查点击是否与 go 按钮重合 CGPoint goButtonPoint = [self.goButton convertPoint:bannerPoint fromView:self]; if ([self.goButton pointInside:goButtonPoint withEvent:nil]) { NSLog(@"🎯 GameUniversalBannerView: tap 点与 go 按钮重合,触发游戏跳转事件"); [self handleTapGo]; } else { CGPoint screenPoint = [self convertPoint:point toView:nil]; // 发送通知给 FunctionContainer 处理,传递屏幕坐标 [[NSNotificationCenter defaultCenter] postNotificationName:@"BannerTapToFunctionContainer" object:nil userInfo:@{@"point": [NSValue valueWithCGPoint:screenPoint]}]; } } - (void)addNotification { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleSwipeNotification) name:@"SwipeOutBanner" object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleTapNotification:) name:@"TapBanner" object:nil]; } - (void)removeNotification { [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (void)dismissBanner { NSLog(@"🚨 GameUniversalBannerView dismissBanner 被调用"); self.alreadyCancel = YES; [self pop_removeAllAnimations]; // 停止所有动画 [self popLeaveAnimation:^(bool finished) { NSLog(@"🚨 GameUniversalBannerView 动画完成,调用回调"); if (self.completeDisplay) { self.completeDisplay(); } else { NSLog(@"🚨 警告: completeDisplay 回调为空"); } [self removeFromSuperview]; }]; } - (void)popEnterAnimation:(void(^)(BOOL finished))finish { NSInteger height = kGetScaleWidth(70); POPSpringAnimation *enterAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPViewFrame]; enterAnimation.toValue = [NSValue valueWithCGRect:CGRectMake(0, 0, KScreenWidth, height)]; enterAnimation.springBounciness = 10; // 弹性系数 enterAnimation.springSpeed = 12; // 动画速度 enterAnimation.completionBlock = ^(POPAnimation *anim, BOOL finished) { if (finish) { finish(finished); } }; [self pop_addAnimation:enterAnimation forKey:@"enterAnimation"]; } - (void)popLeaveAnimation:(void(^)(bool finished))finish { NSInteger height = kGetScaleWidth(70); POPBasicAnimation *exitAnimation = [POPBasicAnimation animationWithPropertyNamed:kPOPViewFrame]; exitAnimation.toValue = [NSValue valueWithCGRect:CGRectMake(-KScreenWidth, 0, KScreenWidth, height)]; exitAnimation.duration = 0.25; // 动画持续时间 exitAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; exitAnimation.completionBlock = ^(POPAnimation *anim, BOOL finished) { if (finish) { finish(finished); } }; [self pop_addAnimation:exitAnimation forKey:@"exitAnimation"]; } - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { [self addSubview:self.bgSVGA]; [self.bgSVGA mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.mas_equalTo(self); }]; [self addSubview:self.bgImageView]; [self.bgImageView mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.mas_equalTo(self); }]; [self addSubview:self.gameIcon]; [self.gameIcon mas_makeConstraints:^(MASConstraintMaker *make) { make.width.mas_equalTo(44); make.height.mas_equalTo(44); make.centerY.mas_equalTo(self); make.leading.mas_equalTo(35); }]; [self addSubview:self.avatarIcon]; [self.avatarIcon mas_makeConstraints:^(MASConstraintMaker *make) { make.width.mas_equalTo(28); make.height.mas_equalTo(28); make.centerY.mas_equalTo(self); make.leading.mas_equalTo(self.gameIcon.mas_trailing).offset(5); }]; [self addSubview:self.goButton]; [self.goButton mas_makeConstraints:^(MASConstraintMaker *make) { make.centerY.mas_equalTo(self); make.trailing.mas_equalTo(-36); make.size.mas_equalTo(CGSizeMake(35, 20)); }]; [self addSubview:self.titleLabel]; [self.titleLabel mas_makeConstraints:^(MASConstraintMaker *make) { make.leading.mas_equalTo(117); make.trailing.mas_equalTo(-80); make.centerY.mas_equalTo(self.goButton); make.height.mas_equalTo(70); }]; } return self; } - (void)setEntity:(SVGAVideoEntity *)entity { _entity = entity; self.bgSVGA.videoItem = entity; [self.bgSVGA autoPlay]; } - (void)setModel:(PIUniversalBannerModel *)model { _model = model; NSDictionary *textDic = model.template; if(textDic.allKeys.count == 0) { return ; } NSString *key = [NSBundle uploadLanguageText]; NSString *title = textDic[key] == nil ? textDic[textDic.allKeys.firstObject] : textDic[key]; if(title.length == 0) { return; } [title stringByReplacingOccurrencesOfString:@"\n" withString:@""]; if (_model.fontSize <= 0){ _model.fontSize = 12; } CGFloat font = _model.fontSize; NSMutableAttributedString * attribute = [[NSMutableAttributedString alloc] initWithString:title attributes:@{ NSFontAttributeName:[UIFont systemFontOfSize:font], NSForegroundColorAttributeName:[DJDKMIMOMColor colorWithHexString:_model.textColor]}]; for (PIUniversalBannerItemModel *model in _model.contents) { if([model.type isEqualToString:@"TEXT"]){ NSDictionary *subTextDic = model.text; if(subTextDic.allKeys.count > 0){ NSString *subText = subTextDic[key] == nil ? subTextDic[subTextDic.allKeys.firstObject] : subTextDic[key]; NSAttributedString *attText = [[NSAttributedString alloc]initWithString:subText attributes:@{NSForegroundColorAttributeName:[DJDKMIMOMColor colorWithHexString:model.textColor],NSFontAttributeName:[UIFont systemFontOfSize:font]}]; if ([attribute.string containsString:[NSString stringWithFormat:@"{%@}",model.key]]){ [attribute replaceCharactersInRange:[attribute.string rangeOfString:[NSString stringWithFormat:@"{%@}",model.key]] withAttributedString:attText]; } } } else if ([model.type isEqualToString:@"IMAGE"]) { if ([model.key isEqualToString:@"avatar"]) { [self.avatarIcon loadImageWithUrl:model.image completion:^(UIImage * _Nullable image, NSURL * _Nonnull url) { if (!image) { [self.avatarIcon mas_updateConstraints:^(MASConstraintMaker *make) { make.width.mas_equalTo(0); }]; } else { self.avatarIcon.image = image; } }]; } else if ([model.key isEqualToString:@"gameIcon"]) { [self.gameIcon loadImageWithUrl:model.image completion:^(UIImage * _Nullable image, NSURL * _Nonnull url) { if (!image) { [self.gameIcon mas_updateConstraints:^(MASConstraintMaker *make) { make.width.mas_equalTo(0); }]; } else { self.gameIcon.image = image; } }]; } if ([attribute.string containsString:[NSString stringWithFormat:@"{%@}",model.key]]){ [attribute replaceCharactersInRange:[attribute.string rangeOfString:[NSString stringWithFormat:@"{%@}",model.key]] withAttributedString:[NSAttributedString new]]; } } } self.titleLabel.attributedText = attribute; BOOL isSvga = [model.resourceType.uppercaseString isEqualToString:@"SVGA"]; @kWeakify(self); if (isSvga == YES) { self.bgSVGA.hidden = NO; self.bgImageView.hidden = YES; if (!_parser) { _parser = [SVGAParser new]; } [self.parser parseWithURL:[NSURL URLWithString:model.resourceContent] completionBlock:^(SVGAVideoEntity * _Nonnull videoItem) { @kStrongify(self); self.bgSVGA.videoItem = videoItem; } failureBlock:^(NSError * _Nonnull error) { @kStrongify(self); [self loadLocalSVGA]; }]; }else{ self.bgSVGA.hidden = YES; self.bgImageView.hidden = NO; @kWeakify(self); [self.bgImageView loadImageWithUrl:model.resourceContent completion:^(UIImage * _Nullable image, NSURL * _Nonnull url) { @kStrongify(self); if (image) { self.bgImageView.image = image; } else { [self loadLocalSVGA]; } }]; } } - (void)loadLocalSVGA { self.bgSVGA.hidden = NO; self.bgImageView.hidden = YES; @kWeakify(self); [self.parser parseWithNamed:@"game_floating_bg" inBundle:[NSBundle mainBundle] completionBlock:^(SVGAVideoEntity * _Nonnull videoItem) { @kStrongify(self); self.bgSVGA.videoItem = videoItem; } failureBlock:^(NSError * _Nonnull error) { }]; } - (void)setupNotification { } - (void)handleTapGo { if (self.didTapGo) { self.didTapGo(self.gameID.integerValue); } } #pragma mark - - (NetImageView *)bgImageView { if (!_bgImageView) { _bgImageView = [[NetImageView alloc] init]; _bgImageView.layer.cornerRadius = 2; _bgImageView.layer.masksToBounds = YES; } return _bgImageView; } - (SVGAImageView *)bgSVGA { if (!_bgSVGA) { _bgSVGA = [[SVGAImageView alloc] init]; _bgSVGA.autoPlay = YES; _bgSVGA.loops = -1; } return _bgSVGA; } - (NetImageConfig *)avatarConfig { NetImageConfig * config = [[NetImageConfig alloc]init]; config.imageType = ImageTypeUserIcon; config.placeHolder = [UIImageConstant defaultAvatarPlaceholder]; return config; } - (NetImageView *)gameIcon { if (!_gameIcon) { _gameIcon = [[NetImageView alloc] initWithConfig:[self avatarConfig]]; _gameIcon.layer.cornerRadius = 2; _gameIcon.layer.masksToBounds = YES; } return _gameIcon; } - (NetImageView *)avatarIcon { if (!_avatarIcon) { _avatarIcon = [[NetImageView alloc] initWithConfig:[self avatarConfig]]; _avatarIcon.layer.cornerRadius = 28/2; _avatarIcon.layer.masksToBounds = YES; } return _avatarIcon; } - (UILabel *)titleLabel { if (!_titleLabel) { _titleLabel = [[UILabel alloc] init]; _titleLabel.textAlignment = NSTextAlignmentCenter; _titleLabel.numberOfLines = 0; _titleLabel.adjustsFontSizeToFitWidth = YES; _titleLabel.minimumScaleFactor = 0.5; } return _titleLabel; } - (UIButton *)goButton { if (!_goButton) { _goButton = [UIButton buttonWithType:UIButtonTypeCustom]; [_goButton setImage:kImage(@"game_banner_go") forState:UIControlStateNormal]; [_goButton addTarget:self action:@selector(handleTapGo) forControlEvents:UIControlEventTouchUpInside]; } return _goButton; } // 在主实现体内添加事件穿透逻辑 //- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { // if (!self.userInteractionEnabled || self.hidden || self.alpha <= 0.01) { // return nil; // } // CGPoint goButtonPoint = [self.goButton convertPoint:point fromView:self]; // if ([self.goButton pointInside:goButtonPoint withEvent:event]) { // return self.goButton; // } // // 其他区域返回self,允许触摸事件被处理(包括手势识别器) // return self; //} @end