374 lines
13 KiB
Objective-C
374 lines
13 KiB
Objective-C
//
|
||
// GameUniversalBannerView.m
|
||
// YuMi
|
||
//
|
||
// Created by P on 2024/10/15.
|
||
//
|
||
|
||
#import "GameUniversalBannerView.h"
|
||
|
||
#import <POP.h>
|
||
#import <SVGA.h>
|
||
#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];
|
||
|
||
[bannerView addNotification];
|
||
|
||
@kWeakify(bannerView);
|
||
[bannerView popEnterAnimation:^(BOOL finished) {
|
||
if (finished && bannerView.alreadyCancel == NO) {
|
||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||
@kStrongify(bannerView);
|
||
[bannerView popLeaveAnimation:^(bool finished) {
|
||
if (bannerView.completeDisplay) {
|
||
bannerView.completeDisplay();
|
||
}
|
||
[bannerView removeFromSuperview];
|
||
}];
|
||
});
|
||
}
|
||
}];
|
||
}
|
||
|
||
- (void)addNotification {
|
||
@kWeakify(self);
|
||
[[NSNotificationCenter defaultCenter] addObserverForName:@"SwipeOutBanner"
|
||
object:nil
|
||
queue:[NSOperationQueue mainQueue]
|
||
usingBlock:^(NSNotification * _Nonnull notification) {
|
||
@kStrongify(self);
|
||
[self dismissBanner];
|
||
}];
|
||
}
|
||
|
||
- (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
|