From 0b89480a77346c66be16985868befda414c3c7fe Mon Sep 17 00:00:00 2001 From: edwinQQQ Date: Wed, 20 Aug 2025 18:07:34 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=20RoomAnimationView=20?= =?UTF-8?q?=E5=92=8C=20PIGiftBravoGiftBroadcastView=20=E7=9A=84=E5=86=85?= =?UTF-8?q?=E5=AD=98=E7=AE=A1=E7=90=86=EF=BC=8C=E6=96=B0=E5=A2=9E=E5=8A=A8?= =?UTF-8?q?=E7=94=BB=E6=8E=A7=E5=88=B6=E6=96=B9=E6=B3=95=E4=BB=A5=E6=8F=90?= =?UTF-8?q?=E5=8D=87=E7=94=A8=E6=88=B7=E4=BD=93=E9=AA=8C=E3=80=82=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E4=BA=86=E8=BF=9E=E5=87=BB=E8=A7=86=E5=9B=BE=E7=9A=84?= =?UTF-8?q?=E6=B8=85=E7=90=86=E9=80=BB=E8=BE=91=EF=BC=8C=E7=A1=AE=E4=BF=9D?= =?UTF-8?q?=E8=B5=84=E6=BA=90=E7=9A=84=E6=AD=A3=E7=A1=AE=E9=87=8A=E6=94=BE?= =?UTF-8?q?=EF=BC=8C=E9=81=BF=E5=85=8D=E5=86=85=E5=AD=98=E6=B3=84=E6=BC=8F?= =?UTF-8?q?=E3=80=82=E5=90=8C=E6=97=B6=EF=BC=8C=E6=96=B0=E5=A2=9E=20NetIma?= =?UTF-8?q?geView=20=E7=9A=84=E5=8F=96=E6=B6=88=E5=8A=A0=E8=BD=BD=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=EF=BC=8C=E5=A2=9E=E5=BC=BA=E5=9B=BE=E7=89=87=E5=8A=A0?= =?UTF-8?q?=E8=BD=BD=E7=9A=84=E7=81=B5=E6=B4=BB=E6=80=A7=E5=92=8C=E6=80=A7?= =?UTF-8?q?=E8=83=BD=E3=80=82=E9=87=8D=E6=9E=84=E7=9B=B8=E5=85=B3=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E4=BB=A5=E6=94=AF=E6=8C=81=E6=96=B0=E7=9A=84=E9=80=BB?= =?UTF-8?q?=E8=BE=91=E6=B5=81=E7=A8=8B=EF=BC=8C=E6=8F=90=E5=8D=87=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E5=8F=AF=E7=BB=B4=E6=8A=A4=E6=80=A7=E5=92=8C=E5=8F=AF?= =?UTF-8?q?=E8=AF=BB=E6=80=A7=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DOC/log.txt | 105 ++---- YuMi/CustomUI/UIImageView/NetImageView.h | 3 + YuMi/CustomUI/UIImageView/NetImageView.m | 4 + .../View/AnimationView/RoomAnimationView.m | 200 ++++++++--- .../View/PIGiftBravoGiftBroadcastView.h | 7 + .../View/PIGiftBravoGiftBroadcastView.m | 319 ++++++++++++------ .../View/SendGiftView/View/XPSendGiftView.m | 4 +- .../View/XPRoomInviteUserViewController.m | 7 +- 8 files changed, 419 insertions(+), 230 deletions(-) diff --git a/DOC/log.txt b/DOC/log.txt index faec3b15..b02d2a8a 100644 --- a/DOC/log.txt +++ b/DOC/log.txt @@ -1,79 +1,26 @@ --[XPSendGiftView xPGiftBarView:didClickSendGift:] [Line 771][Combo effect] 🎁 开始送礼物流程 --[XPSendGiftView readyForCombo:gift:] [Line 727][Combo effect] 🔧 准备连击状态 - giftType: 18, segmentType: 8 --[XPSendGiftView readyForCombo:gift:] [Line 745][Combo effect] ✅ 礼物支持连击,启用连击功能 --[GiftComboManager activate] [Line 197][Combo effect] 🔧 激活连击功能 --[GiftComboManager configureWithGiftInfo:targetUIDs:roomUID:sessionID:userInfo:countModel:sourceType:sendType:giftNum:] [Line 696][Combo effect] 🔧 统一配置连击参数 --[GiftComboManager configureWithGiftInfo:targetUIDs:roomUID:sessionID:userInfo:countModel:sourceType:sendType:giftNum:] [Line 709][Combo effect] ✅ 连击参数配置完成 - giftId: 2263, targetCount: 1 --[XPSendGiftView readyForCombo:gift:] [Line 761][Combo effect] ✅ 连击状态准备完成 --[GiftComboManager printComboState] [Line 377][Combo effect] 📊 当前连击状态: --[GiftComboManager printComboState] [Line 378][Combo effect] - isCombing: NO --[GiftComboManager printComboState] [Line 379][Combo effect] - enableCombo: YES --[GiftComboManager printComboState] [Line 380][Combo effect] - combo: 1 --[GiftComboManager printComboState] [Line 381][Combo effect] - hasGiftInfo: YES --[GiftComboManager printComboState] [Line 382][Combo effect] - targetCount: 1 --[GiftComboManager printComboState] [Line 383][Combo effect] - errorMessage: --[XPSendGiftView xPGiftBarView:didClickSendGift:] [Line 778][Combo effect] ✅ 连击功能已启用,准备调用resetCombo --[XPSendGiftView sendGiftSuccess:originDic:uidCount:] [Line 1198][Combo effect] 📱 检查连击状态 - enableCombo: YES --[XPSendGiftView sendGiftSuccess:originDic:uidCount:] [Line 1207][Combo effect] 📱 originDic 连击计数检查 - comboCount: (null) --[XPSendGiftView sendGiftSuccess:originDic:uidCount:] [Line 1210][Combo effect] 📱 启用连击模式,重置连击状态 --[GiftComboManager reset] [Line 141][Combo effect] 🔄 开始连击重置 - combo: 1 -> 1, enableCombo: YES, actionCallback: 为空 --[GiftComboManager reset] [Line 154][Combo effect] 🔍 重置后验证 - combo: 1 --[GiftComboManager reset] [Line 173][Combo effect] ⚠️ actionCallback为空,不显示连击面板 --[GiftComboManager reset] [Line 177][Combo effect] 🎮 隐藏房间UI元素 --[GiftComboManager reset] [Line 186][Combo effect] ✅ 连击重置完成 - isCombing: NO --[XPSendGiftView sendCustomMessage:oringinDic:] [Line 425][Combo effect] 📨 云信消息连击计数检查 - comboCount: 1, giftId: 2263 --[GiftComboView setupTimer] [Line 284][Combo effect] ⏰ 设置连击倒计时 --[CountdownRingView startCountdown] [Line 97][Combo effect] ⏰ 开始倒计时 --[CountdownRingView animateRing] [Line 246][Combo effect] 🎬 环形动画已启动,时长: 5.0秒 --[CountdownRingView startCountdown] [Line 109][Combo effect] ⏰ 倒计时已启动 --[GiftComboView setupTimer] [Line 293][Combo effect] ⏰ 连击倒计时已启动 --[GiftComboView updateCount] [Line 171][Combo effect] 🔢 更新连击次数显示 - combo: 1 --[GiftComboManager printComboState] [Line 377][Combo effect] 📊 当前连击状态: --[GiftComboManager printComboState] [Line 378][Combo effect] - isCombing: NO --[GiftComboManager printComboState] [Line 379][Combo effect] - enableCombo: YES --[GiftComboManager printComboState] [Line 380][Combo effect] - combo: 1 --[GiftComboManager printComboState] [Line 381][Combo effect] - hasGiftInfo: YES --[GiftComboManager printComboState] [Line 382][Combo effect] - targetCount: 1 --[GiftComboManager printComboState] [Line 383][Combo effect] - errorMessage: --[GiftComboManager forceBoomStateReset] [Line 275][Combo effect] 🚨 执行强制Boom连击状态重置 --[GiftComboManager forceBoomStateReset] [Line 278][Combo effect] ⏰ 停止所有定时器 --[GiftComboManager forceStopAllTimers] [Line 321][Combo effect] ⏰ 强制停止所有Timer --[GiftComboManager forceBoomStateReset] [Line 282][Combo effect] 🗑️ 清空所有队列 --[GiftComboManager forceBoomStateReset] [Line 287][Combo effect] 🔄 重置状态标志 - isCombing: NO -> NO --[GiftComboManager forceBoomStateReset] [Line 292][Combo effect] 🔄 combo计数重置为0 --[GiftComboManager forceBoomStateReset] [Line 301][Combo effect] 📢 发送强制重置通知 --[GiftComboManager forceBoomStateReset] [Line 309][Combo effect] 🎮 恢复房间UI元素 --[GiftComboManager forceBoomStateReset] [Line 316][Combo effect] ✅ 强制重置完成 - enableCombo保持: YES, actionCallback保持: 为空 --[XPSendGiftView removeAllComboRelatedViews] [Line 137][Combo effect] 🗑️ 开始移除连击相关视图 --[XPSendGiftView removeAllComboRelatedViews] [Line 148][Combo effect] 🗑️ comboView存在但无superview,直接清理 --[CountdownRingView stopCountdown] [Line 200][Combo effect] ⏰ 停止倒计时开始 --[CountdownRingView stopCountdown] [Line 208][Combo effect] ⏰ Timer已停止 --[CountdownRingView stopCountdown] [Line 214][Combo effect] ⏰ 动画已停止 --[CountdownRingView stopCountdown] [Line 230][Combo effect] ⏰ 停止倒计时完成 --[GiftComboView dealloc] [Line 43][Combo effect] 🗑️ GiftComboView dealloc开始 - 0x13624e600 --[CountdownRingView stopCountdown] [Line 196][Combo effect] ⚠️ 倒计时未运行,无需停止 --[GiftComboView dealloc] [Line 72][Combo effect] 🗑️ GiftComboView dealloc完成 - 0x13624e600 --[XPSendGiftView removeAllComboRelatedViews] [Line 166][Combo effect] 🗑️ 连击相关视图移除完成 --[CountdownRingView dealloc] [Line 32][Combo effect] 🗑️ CountdownRingView dealloc开始 - 0x13624e800 --[CountdownRingView cleanup] [Line 251][Combo effect] 🗑️ 完全清理开始 --[CountdownRingView stopCountdown] [Line 196][Combo effect] ⚠️ 倒计时未运行,无需停止 --[CountdownRingView cleanup] [Line 260][Combo effect] 🗑️ 完全清理完成 --[CountdownRingView dealloc] [Line 37][Combo effect] 🗑️ CountdownRingView dealloc完成 - 0x13624e800 --[RoomAnimationView _handleGiftMessage:] [Line 1737][Combo effect] 📨 收到礼物消息 - second: 121 --[RoomAnimationView _handleGiftMessage:] [Line 1757][Combo effect] 📨 礼物消息解析完成 - giftId: 2263, combo: 1, uid: 3184 --[RoomAnimationView _handleGiftMessage:] [Line 1824][Combo effect] 🎁 处理普通礼物动画 --[GiftAnimationManager enqueueGift:] [Line 237][Combo effect] 🎬 添加礼物到动画队列 - giftId: 2263, combo: 1 --[GiftComboManager receiveGiftInfoForDisplayComboFlags:container:] [Line 407][Combo effect] 🎪 收到连击飘屏请求 - combo: 1, giftId: 2263 --[GiftAnimationManager enqueueGift:]_block_invoke [Line 241][Combo effect] 📊 动画队列当前数量: 1 --[GiftComboManager receiveGiftInfoForDisplayComboFlags:container:] [Line 410][Combo effect] 📊 连击飘屏队列数量: 1 --[GiftComboManager processGiftFlagQueue] [Line 462][Combo effect] 🎪 处理连击飘屏 - combo: 1, giftId: 2263 --[GiftComboManager processGiftFlagQueue] [Line 464][Combo effect] �� 移除后UI动画队列数量: 0 --[GiftAnimationManager processNextGift]_block_invoke [Line 100][Combo effect] 🎬 开始处理动画 - giftId: 2263, combo: 1 --[GiftAnimationManager processNextGift]_block_invoke [Line 104][Combo effect] 📊 移除后动画队列数量: 0 --[GiftAnimationManager distributeGiftAnimation:] [Line 119][Combo effect] 🎯 开始分发动画 - uid: 3184 --[GiftAnimationManager distributeGiftAnimation:] [Line 122][Combo effect] 🎯 目标用户数量: 1 --[GiftAnimationManager distributeGiftAnimation:] [Line 126][Combo effect] 🎯 是否使用连击动画: NO --[GiftAnimationManager distributeGiftAnimation:] [Line 133][Combo effect] 🎯 使用普通动画起点: {207, 285.79999999999995} --[GiftAnimationManager distributeGiftAnimation:] [Line 136][Combo effect] 🎯 动画延迟时间: 0.30 --[GiftAnimationManager distributeGiftAnimation:] [Line 140][Combo effect] 🎯 为目标用户 3184 创建动画 - 终点: {207, 285.79999999999995} --[GiftAnimationManager processNextGift]_block_invoke [Line 82][Combo effect] 📭 动画队列为空,停止处理 \ No newline at end of file +-[XPRoomViewController dealloc] [Line 314]🔄 XPRoomViewController: 清理 RoomAnimationView +-[RoomAnimationView removeItSelf] [Line 193]�� RoomAnimationView: 开始销毁 +-[RoomAnimationView cleanupAllSubviews] [Line 239] 清理所有子视图 +-[RoomAnimationView cleanupAllSubviews] [Line 250]�� 标记移除视图: BravoGiftBannerView (从容器: XPRoomAnimationHitView) +-[RoomAnimationView cleanupAllSubviews] [Line 250]�� 标记移除视图: BravoGiftWinningFlagView (从容器: XPRoomAnimationHitView) +-[RoomAnimationView cleanupAllSubviews] [Line 250]�� 标记移除视图: BravoGiftWinningFlagView (从容器: XPRoomAnimationHitView) +-[RoomAnimationView cleanupAllSubviews] [Line 250]�� 标记移除视图: BravoGiftWinningFlagView (从容器: XPRoomAnimationHitView) +-[RoomAnimationView cleanupAllSubviews] [Line 250]�� 标记移除视图: BravoGiftWinningFlagView (从容器: XPRoomAnimationHitView) +-[RoomAnimationView cleanupAllSubviews] [Line 250]�� 标记移除视图: BravoGiftWinningFlagView (从容器: XPRoomAnimationHitView) +-[RoomAnimationView cleanupAllSubviews] [Line 250]�� 标记移除视图: NetImageView (从容器: XPRoomAnimationHitView) +-[RoomAnimationView cleanupAllSubviews] [Line 250]�� 标记移除视图: NetImageView (从容器: XPRoomAnimationHitView) +-[RoomAnimationView cleanupAllSubviews] [Line 250]�� 标记移除视图: NetImageView (从容器: XPRoomAnimationHitView) +-[RoomAnimationView cleanupAllSubviews] [Line 250]�� 标记移除视图: NetImageView (从容器: XPRoomAnimationHitView) +-[RoomAnimationView cleanupAllSubviews] [Line 250]�� 标记移除视图: NetImageView (从容器: XPRoomAnimationHitView) +-[RoomAnimationView cleanupAllSubviews] [Line 250]�� 标记移除视图: NetImageView (从容器: XPRoomAnimationHitView) +-[RoomAnimationView cleanupAllSubviews] [Line 250]�� 标记移除视图: NetImageView (从容器: XPRoomAnimationHitView) +-[RoomAnimationView cleanupAllSubviews] [Line 265]🔄 所有子视图清理完成 +-[RoomAnimationView removeItSelf] [Line 220]�� 清理 BannerScheduler +-[RoomAnimationView cleanupGestureRecognizers] [Line 3690]🔄 清理手势识别器 +-[RoomAnimationView cleanupGestureRecognizers] [Line 3713]🔄 手势识别器清理完成 +-[RoomAnimationView cleanupCacheManagers] [Line 3717]🔄 清理缓存管理器 +-[RoomAnimationView cleanupCacheManagers] [Line 3730]🔄 缓存管理器清理完成 +-[RoomAnimationView removeNotificationObservers] [Line 3743]🔄 移除通知监听 +-[RoomAnimationView removeNotificationObservers] [Line 3753]🔄 通知监听移除完成 +-[RoomAnimationView removeItSelf] [Line 235]�� RoomAnimationView: 销毁完成 +-[RoomAnimationView playBroveBanner:]_block_invoke [Line 752]🔄 BravoGiftBannerView complete 回调被调用 \ No newline at end of file diff --git a/YuMi/CustomUI/UIImageView/NetImageView.h b/YuMi/CustomUI/UIImageView/NetImageView.h index 7b4e3bf1..d299c75d 100644 --- a/YuMi/CustomUI/UIImageView/NetImageView.h +++ b/YuMi/CustomUI/UIImageView/NetImageView.h @@ -37,6 +37,9 @@ typedef NS_ENUM(NSInteger, NetImageState){ - (void)loadImageWithUrl:(NSString * _Nonnull)imageUrl completion:(LoadCompletion _Nullable)completion fail:(LoadFail _Nullable)fail; - (void)updateConfigPlaceHolder:(UIImage *)image; + +- (void)cancelLoadImage; + @end NS_ASSUME_NONNULL_END diff --git a/YuMi/CustomUI/UIImageView/NetImageView.m b/YuMi/CustomUI/UIImageView/NetImageView.m index f53d07b5..d773ec0f 100644 --- a/YuMi/CustomUI/UIImageView/NetImageView.m +++ b/YuMi/CustomUI/UIImageView/NetImageView.m @@ -43,6 +43,10 @@ return self; } +- (void)cancelLoadImage { + [self sd_cancelCurrentImageLoad]; +} + - (void)initImageUrl:(NSString *)imageUrl { _imageUrl = imageUrl; _innerConfigUrl = [UIImageConstant configUrl:_imageUrl type:self.config.imageType radius:self.config.radius]; diff --git a/YuMi/Modules/YMRoom/View/AnimationView/RoomAnimationView.m b/YuMi/Modules/YMRoom/View/AnimationView/RoomAnimationView.m index 41ca2944..debc33e4 100644 --- a/YuMi/Modules/YMRoom/View/AnimationView/RoomAnimationView.m +++ b/YuMi/Modules/YMRoom/View/AnimationView/RoomAnimationView.m @@ -186,50 +186,145 @@ BannerSchedulerDelegate - (void)dealloc { - + NSLog(@" RoomAnimationView: dealloc 开始"); + [self removeItSelf]; + NSLog(@" RoomAnimationView: dealloc 完成"); } - (void)removeItSelf { NSLog(@"�� RoomAnimationView: 开始销毁"); + + // 🔧 新增:取消所有动画,防止异步回调 + [self cancelAllAnimations]; // 移除广播代理 [[NIMSDK sharedSDK].broadcastManager removeDelegate:self]; + + // 取消所有延迟执行 + [NSObject cancelPreviousPerformRequestsWithTarget:self]; + + // 清理所有 POP 动画 + [self pop_removeAllAnimations]; + + // 清理定时器 + if (_giftEffectTimer && !dispatch_source_testcancel(_giftEffectTimer)) { + dispatch_source_cancel(_giftEffectTimer); + _giftEffectTimer = nil; + } + + // 清理队列 + if (_giftEffectsQueue) { + self.giftEffectsQueue = NULL; + } + + // �� 新增:清理所有 banner 视图,防止 block 强引用 + [self cleanupAllSubviews]; + + // 🔧 新增:清理 BannerScheduler + if (self.bannerScheduler) { + NSLog(@"�� 清理 BannerScheduler"); + [self.bannerScheduler clearQueue]; + [self.bannerScheduler pause]; + self.bannerScheduler = nil; + } + + // 清理手势识别器 + [self cleanupGestureRecognizers]; + + // 清理缓存管理器 + [self cleanupCacheManagers]; + + // 移除通知监听 + [self removeNotificationObservers]; + + NSLog(@"�� RoomAnimationView: 销毁完成"); +} + +- (void)cancelAllAnimations { + NSLog(@" 取消所有动画"); - // 取消所有延迟执行 - [NSObject cancelPreviousPerformRequestsWithTarget:self]; - - // 清理所有 POP 动画 + // 取消所有 POP 动画 [self pop_removeAllAnimations]; - // 清理定时器 - if (_giftEffectTimer && !dispatch_source_testcancel(_giftEffectTimer)) { - dispatch_source_cancel(_giftEffectTimer); - _giftEffectTimer = nil; + // 取消所有子视图的 POP 动画 + NSArray *containers = @[self.bannerContainer, self.topContainer, self.middleContainer, self.bottomContainer]; + for (UIView *container in containers) { + if (!container) continue; + for (UIView *subview in container.subviews) { + [subview pop_removeAllAnimations]; + } } - // 清理队列 - if (_giftEffectsQueue) { - self.giftEffectsQueue = NULL; + // 取消所有 UIView 动画 + [UIView setAnimationsEnabled:NO]; + + NSLog(@"🔄 所有动画取消完成"); +} + +- (void)cleanupAllSubviews { + NSLog(@" 清理所有子视图"); + + // 清理所有容器的子视图 + NSArray *containers = @[self.bannerContainer, self.topContainer, self.middleContainer, self.bottomContainer]; + + for (UIView *container in containers) { + if (!container) continue; + + NSMutableArray *viewsToRemove = [NSMutableArray array]; + for (UIView *subview in container.subviews) { + [viewsToRemove addObject:subview]; + NSLog(@" 标记移除视图: %@ (从容器: %@)", NSStringFromClass([subview class]), NSStringFromClass([container class])); + } + + for (UIView *view in viewsToRemove) { + // 取消视图上的所有动画 + [view pop_removeAllAnimations]; + + // 清理视图上的手势识别器 + if (view.gestureRecognizers.count > 0) { + NSLog(@" 清理视图 %@ 上的 %lu 个手势识别器", NSStringFromClass([view class]), (unsigned long)view.gestureRecognizers.count); + for (UIGestureRecognizer *gesture in view.gestureRecognizers.copy) { + [view removeGestureRecognizer:gesture]; + } + } + + // 强制移除视图 + [view removeFromSuperview]; + } } - // 🔧 新增:清理 BannerScheduler - if (self.bannerScheduler) { - NSLog(@"�� 清理 BannerScheduler"); - [self.bannerScheduler clearQueue]; - [self.bannerScheduler pause]; - self.bannerScheduler = nil; + NSLog(@"🔄 所有子视图清理完成"); +} + +- (void)__cleanupAllBannerViews { + NSLog(@"�� 清理所有 Banner 视图"); + + // 清理 bannerContainer 中的所有子视图 + NSMutableArray *viewsToRemove = [NSMutableArray array]; + for (UIView *subview in self.bannerContainer.subviews) { + [viewsToRemove addObject:subview]; + NSLog(@"🔄 标记移除 banner: %@", NSStringFromClass([subview class])); } - // 清理手势识别器 - [self cleanupGestureRecognizers]; + for (UIView *view in viewsToRemove) { + [view removeFromSuperview]; + } - // 清理缓存管理器 - [self cleanupCacheManagers]; + // 清理 topContainer 中的所有子视图(可能包含 CP 相关的 banner) + viewsToRemove = [NSMutableArray array]; + for (UIView *subview in self.topContainer.subviews) { + if ([subview isKindOfClass:[CPBindingAnimation class]] || + [subview isKindOfClass:[CPLevelUpAnimation class]]) { + [viewsToRemove addObject:subview]; + NSLog(@"�� 标记移除 CP banner: %@", NSStringFromClass([subview class])); + } + } - // 移除通知监听 - [self removeNotificationObservers]; + for (UIView *view in viewsToRemove) { + [view removeFromSuperview]; + } - NSLog(@"�� RoomAnimationView: 销毁完成"); + NSLog(@"🔄 Banner 视图清理完成"); } -(void)resumeTimer{ @@ -541,7 +636,7 @@ BannerSchedulerDelegate return; } - #if DEBUG + #if 0 // DEBUG环境:复制banner数据用于测试 BOOL enableBannerCopy = obj.second == Custom_Message_Sub_Super_Gift_Banner;// YES; // 设置为NO可以禁用复制功能 NSInteger copyCount = 10; // 可以调整这个数字来改变复制数量 @@ -669,25 +764,40 @@ BannerSchedulerDelegate - (void)playBroveBanner:(AttachmentModel *)obj { if (!obj.data) { - self.isRoomBannerV2Displaying = NO; - [self.bannerScheduler markBannerFinished]; - return; - } - self.isRoomBannerV2Displaying = YES; - @kWeakify(self); - RoomInfoModel *roomInfo = self.hostDelegate.getRoomInfo; - [BravoGiftBannerView display:self.bannerContainer - inRoomUid:roomInfo.uid - with:obj - complete:^{ - @kStrongify(self); - NSLog(@"🔄 BravoGiftBannerView complete 回调被调用"); - self.isRoomBannerV2Displaying = NO; - [self.bannerScheduler markBannerFinished]; - } exitCurrentRoom:^{ - @kStrongify(self); - [self.hostDelegate exitRoom]; - }]; + self.isRoomBannerV2Displaying = NO; + [self.bannerScheduler markBannerFinished]; + return; + } + self.isRoomBannerV2Displaying = YES; + @kWeakify(self); + RoomInfoModel *roomInfo = self.hostDelegate.getRoomInfo; + + // �� 新增:检查是否正在销毁 + if (!self || !self.superview) { + NSLog(@"⚠️ RoomAnimationView 正在销毁,跳过 banner 播放"); + return; + } + + [BravoGiftBannerView display:self.bannerContainer + inRoomUid:roomInfo.uid + with:obj + complete:^{ + @kStrongify(self); + if (!self || !self.superview) { + NSLog(@"⚠️ RoomAnimationView 已被释放,跳过 complete 回调"); + return; + } + NSLog(@"🔄 BravoGiftBannerView complete 回调被调用"); + self.isRoomBannerV2Displaying = NO; + [self.bannerScheduler markBannerFinished]; + } exitCurrentRoom:^{ + @kStrongify(self); + if (!self || !self.superview) { + NSLog(@"⚠️ RoomAnimationView 已被释放,跳过 exitCurrentRoom 回调"); + return; + } + [self.hostDelegate exitRoom]; + }]; } - (void)playLuckyPackageBanner:(AttachmentModel *)obj { diff --git a/YuMi/Modules/YMRoom/View/SendGiftView/View/PIGiftBravoGiftBroadcastView.h b/YuMi/Modules/YMRoom/View/SendGiftView/View/PIGiftBravoGiftBroadcastView.h index bb73cf0a..3fe7b91f 100644 --- a/YuMi/Modules/YMRoom/View/SendGiftView/View/PIGiftBravoGiftBroadcastView.h +++ b/YuMi/Modules/YMRoom/View/SendGiftView/View/PIGiftBravoGiftBroadcastView.h @@ -11,6 +11,13 @@ NS_ASSUME_NONNULL_BEGIN @interface PIGiftBravoGiftBroadcastView : UIView +// 开始动画循环 +- (void)startAnimation; + +// 停止动画循环 +- (void)stopAnimation; + +// 兼容旧方法 - (void)endloop; @end diff --git a/YuMi/Modules/YMRoom/View/SendGiftView/View/PIGiftBravoGiftBroadcastView.m b/YuMi/Modules/YMRoom/View/SendGiftView/View/PIGiftBravoGiftBroadcastView.m index 36bc895d..7b3a97bb 100644 --- a/YuMi/Modules/YMRoom/View/SendGiftView/View/PIGiftBravoGiftBroadcastView.m +++ b/YuMi/Modules/YMRoom/View/SendGiftView/View/PIGiftBravoGiftBroadcastView.m @@ -8,24 +8,123 @@ #import "PIGiftBravoGiftBroadcastView.h" #import "XPSkillCardPlayerManager.h" #import "BravoGiftTabInfomationModel.h" + + +@interface PIGiftBravoGiftBroadcastItemView : UIView + +@property (nonatomic, strong) BravoGiftTabInfomationModel *model; +- (void)cancelLoading; + +@end + +@implementation PIGiftBravoGiftBroadcastItemView +{ + NetImageView *avatar; + UILabel *content; +} + +- (instancetype)init +{ + self = [super init]; + if (self) { + NetImageConfig *config = [[NetImageConfig alloc] init]; + config.placeHolder = [UIImageConstant defaultAvatarPlaceholder]; + config.imageType = ImageTypeUserIcon; + avatar = [[NetImageView alloc] initWithConfig:config]; + + content = [[UILabel alloc] init]; + + [self addSubview:avatar]; + [self addSubview:content]; + + [avatar mas_makeConstraints:^(MASConstraintMaker *make) { + make.centerY.mas_equalTo(self); + make.leading.mas_equalTo(8); + make.size.mas_equalTo(CGSizeMake(24, 24)); + }]; + + [content mas_makeConstraints:^(MASConstraintMaker *make) { + make.centerY.mas_equalTo(self); + make.leading.mas_equalTo(avatar.mas_trailing).offset(4); + make.trailing.mas_equalTo(self).offset(-4); + }]; + } + return self; +} + +- (void)setModel:(BravoGiftTabInfomationModel *)model { + _model = model; + + avatar.imageUrl = model.avatar; + + NSTextAttachment *coinAttachment = [[NSTextAttachment alloc] init]; + coinAttachment.image = kImage(@"moli_money_icon"); + coinAttachment.bounds = CGRectMake(0, -3, 18, 18); + NSAttributedString *coinString = [NSAttributedString attributedStringWithAttachment:coinAttachment]; + NSString *giftName = model.giftName; + NSString *coin = @(model.coin).stringValue; + NSString *oringinalString = [NSString stringWithFormat:@" %@ %@ %@ %@ %@ ", + [NSString trimString:model.nick lengthLimit:4], + YMLocalizedString(@"Combo_7"), + giftName, YMLocalizedString(@"Combo_4"), coin]; + + NSMutableAttributedString *string = [[NSMutableAttributedString alloc] init]; + [string appendAttributedString:[[NSAttributedString alloc] initWithString:oringinalString attributes:@{ + NSFontAttributeName: kFontRegular(13), + NSForegroundColorAttributeName: [UIColor whiteColor] + }]]; + + // 找到 giftName 和 coin 在字符串中的位置 + NSRange giftNameRange = [oringinalString rangeOfString:giftName]; + NSRange coinRange = [oringinalString rangeOfString:coin]; + + // 创建黄色文本的属性 + NSDictionary *yellowAttributes_name = @{ + NSFontAttributeName: kFontRegular(13), + NSForegroundColorAttributeName: UIColorFromRGB(0xffec6f) + }; + NSDictionary *yellowAttributes_coin = @{ + NSFontAttributeName: kFontSemibold(16), + NSForegroundColorAttributeName: UIColorFromRGB(0xffec6f) + }; + // 创建包含黄色文本的可变字符串 + NSMutableAttributedString *yellowGiftName = [[NSMutableAttributedString alloc] initWithString:giftName attributes:yellowAttributes_name]; + NSMutableAttributedString *yellowCoin = [[NSMutableAttributedString alloc] initWithString:coin attributes:yellowAttributes_coin]; + + // 替换原始字符串中的 giftName 和 coin 为黄色文本 + [string replaceCharactersInRange:giftNameRange withAttributedString:yellowGiftName]; + [string replaceCharactersInRange:coinRange withAttributedString:yellowCoin]; + + [string appendAttributedString:coinString]; + + [content setAttributedText:string.copy]; +} + +- (void)cancelLoading { + [avatar cancelLoadImage]; +} + +@end + @interface PIGiftBravoGiftBroadcastView() @property (nonatomic, strong) UIImageView *speaker; @property (nonatomic, strong) MarqueeLabel *label; @property (nonatomic, strong) UIView *container; @property (nonatomic, copy) NSArray * source; -@property (nonatomic, strong) NSMutableArray *labels; +@property (nonatomic, strong) NSMutableArray *labels; @property (nonatomic, assign) NSInteger index; -@property (nonatomic, assign) NSInteger loopStatus; // -1: 頁面回收,0:未啟動,1:啟動中 -@property (nonatomic, strong) NSMutableArray *imageLoaders; +@property (nonatomic, assign) BOOL isAnimating; // 动画状态控制 +@property (nonatomic, assign) BOOL shouldStopAnimation; // 停止动画标志 @property (nonatomic, strong) UIButton *tipsButton; +@property (nonatomic, strong) NSTimer *animationTimer; // 动画定时器 @end @implementation PIGiftBravoGiftBroadcastView - (void)dealloc { - + [self endloop]; } - (instancetype)initWithFrame:(CGRect)frame { @@ -59,124 +158,142 @@ }]; self.labels = [[NSMutableArray alloc] init]; - self.imageLoaders = [[NSMutableArray alloc] init]; + self.isAnimating = NO; + self.shouldStopAnimation = NO; + @kWeakify(self); [self.source enumerateObjectsUsingBlock:^(BravoGiftTabInfomationModel * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { @kStrongify(self); - NetImageView *avatar = [[NetImageView alloc] init]; - [avatar loadImageWithUrl:obj.avatar completion:^(UIImage * _Nullable image, NSURL * _Nonnull url) { - NSTextAttachment *avatarAttachment = [[NSTextAttachment alloc] init]; - avatarAttachment.image = [image roundedImageWithCornerRadius:12*3 size:CGSizeMake(24*3, 24*3)]; - avatarAttachment.bounds = CGRectMake(0, -5, 24, 24); - NSTextAttachment *coinAttachment = [[NSTextAttachment alloc] init]; - coinAttachment.image = kImage(@"moli_money_icon"); - coinAttachment.bounds = CGRectMake(0, -3, 18, 18); - NSAttributedString *avatarString = [NSAttributedString attributedStringWithAttachment:avatarAttachment]; - NSAttributedString *coinString = [NSAttributedString attributedStringWithAttachment:coinAttachment]; - NSString *giftName = obj.giftName; - NSString *coin = @(obj.coin).stringValue; - NSString *oringinalString = [NSString stringWithFormat:@" %@ %@ %@ %@ %@ ", - [NSString trimString:obj.nick lengthLimit:4], - YMLocalizedString(@"Combo_7"), - giftName, YMLocalizedString(@"Combo_4"), coin]; - - NSMutableAttributedString *string = [[NSMutableAttributedString alloc] init]; - [string appendAttributedString:[[NSAttributedString alloc] initWithString:oringinalString attributes:@{ - NSFontAttributeName: kFontRegular(13), - NSForegroundColorAttributeName: [UIColor whiteColor] - }]]; - - // 找到 giftName 和 coin 在字符串中的位置 - NSRange giftNameRange = [oringinalString rangeOfString:giftName]; - NSRange coinRange = [oringinalString rangeOfString:coin]; - - // 创建黄色文本的属性 - NSDictionary *yellowAttributes_name = @{ - NSFontAttributeName: kFontRegular(13), - NSForegroundColorAttributeName: UIColorFromRGB(0xffec6f) - }; - NSDictionary *yellowAttributes_coin = @{ - NSFontAttributeName: kFontSemibold(16), - NSForegroundColorAttributeName: UIColorFromRGB(0xffec6f) - }; - // 创建包含黄色文本的可变字符串 - NSMutableAttributedString *yellowGiftName = [[NSMutableAttributedString alloc] initWithString:giftName attributes:yellowAttributes_name]; - NSMutableAttributedString *yellowCoin = [[NSMutableAttributedString alloc] initWithString:coin attributes:yellowAttributes_coin]; - - // 替换原始字符串中的 giftName 和 coin 为黄色文本 - [string replaceCharactersInRange:giftNameRange withAttributedString:yellowGiftName]; - [string replaceCharactersInRange:coinRange withAttributedString:yellowCoin]; - - [string insertAttributedString:avatarString atIndex:0]; - [string appendAttributedString:coinString]; - - UILabel *label = [[UILabel alloc] init]; - label.attributedText = string.copy; - [self.labels addObject:label]; - if (self.loopStatus>-1) { - [self startLoop]; - } - }]; - [self.imageLoaders addObject:avatar]; + PIGiftBravoGiftBroadcastItemView *item = [[PIGiftBravoGiftBroadcastItemView alloc] init]; + item.frame = CGRectMake(0, 0, KScreenWidth - 25 - 42, 26); + item.model = obj; + [self.labels addObject:item]; }]; + + // 开始动画 + [self startLoop]; } - (void)startLoop { - if (self.loopStatus > 0 || self.labels.count == 0) { + if (self.isAnimating || self.labels.count == 0 || self.shouldStopAnimation) { return; } - self.loopStatus = 1; - // 获取当前索引的标签 - UILabel *label = [self.labels xpSafeObjectAtIndex:self.index]; - if (label) { - // 设置标签的初始位置(屏幕右侧) - label.frame = CGRectMake(KScreenWidth, 2, KScreenWidth - 25 - 42, 26); - // 将标签添加到容器视图 - [self.container addSubview:label]; - - // 动画:标签从屏幕右侧移动到左侧 - [UIView animateWithDuration:0.5 animations:^{ - label.frame = CGRectMake(25, 2, KScreenWidth - 25 - 42, 26); - } completion:^(BOOL finished) { - // 延迟动画:标签从左侧移出屏幕 - [UIView animateWithDuration:0.5 delay:2 options:UIViewAnimationOptionCurveEaseIn animations:^{ - label.frame = CGRectMake(-KScreenWidth, 2, KScreenWidth - 25 - 42, 26); - } completion:^(BOOL finished) { - // 移除标签 - [label removeFromSuperview]; - // 索引递增 - self.index += 1; - - // 检查是否到达最后一个标签 - if (self.index >= self.labels.count) { - // 重置索引为0 - self.index = 0; - } - - // 设置循环标志为NO - self.loopStatus = 0; - // 递归调用startLoop继续循环 - [self startLoop]; - }]; - }]; + self.isAnimating = YES; + [self playCurrentAnimation]; +} + +// 公共方法:开始动画循环 +- (void)startAnimation { + if (self.shouldStopAnimation) { + self.shouldStopAnimation = NO; + } + + if (self.labels.count > 0) { + [self startLoop]; } } +// 公共方法:停止动画循环 +- (void)stopAnimation { + [self endloop]; +} + +- (void)playCurrentAnimation { + if (self.shouldStopAnimation) { + self.isAnimating = NO; + return; + } + + // 获取当前索引的视图 + PIGiftBravoGiftBroadcastItemView *item = [self.labels xpSafeObjectAtIndex:self.index]; + if (!item) { + self.isAnimating = NO; + return; + } + + // 设置初始位置(屏幕右侧) + item.frame = CGRectMake(KScreenWidth, 2, KScreenWidth - 25 - 42, 26); + // 添加到容器视图 + [self.container addSubview:item]; + + @kWeakify(self); + // 入场动画:0.5秒 + [UIView animateWithDuration:0.5 animations:^{ + item.frame = CGRectMake(25, 2, KScreenWidth - 25 - 42, 26); + } completion:^(BOOL finished) { + @kStrongify(self); + if (!finished || self.shouldStopAnimation) { + [item removeFromSuperview]; + self.isAnimating = NO; + return; + } + + // 停留时间:2秒延迟后执行出场动画 + [UIView animateWithDuration:0.5 delay:2.0 options:UIViewAnimationOptionCurveEaseIn animations:^{ + item.frame = CGRectMake(-KScreenWidth, 2, KScreenWidth - 25 - 42, 26); + } completion:^(BOOL finished) { + @kStrongify(self); + // 移除视图 + [item removeFromSuperview]; + + if (self.shouldStopAnimation) { + self.isAnimating = NO; + return; + } + + // 索引递增 + self.index += 1; + if (self.index >= self.labels.count) { + self.index = 0; + } + + // 使用定时器延迟下一个动画,避免递归调用 + self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(scheduleNextAnimation) userInfo:nil repeats:NO]; + }]; + }]; +} + +- (void)scheduleNextAnimation { + [self.animationTimer invalidate]; + self.animationTimer = nil; + + if (self.shouldStopAnimation) { + self.isAnimating = NO; + return; + } + + [self playCurrentAnimation]; +} + - (void)endloop { - self.loopStatus = -1; + // 设置停止标志 + self.shouldStopAnimation = YES; + self.isAnimating = NO; + + // 停止并清理定时器 + if (self.animationTimer) { + [self.animationTimer invalidate]; + self.animationTimer = nil; + } // 取消所有动画 [self.container.layer removeAllAnimations]; - // 清理图片加载器 - [self.imageLoaders removeAllObjects]; + // 取消每个条目的图片加载 + for (PIGiftBravoGiftBroadcastItemView *item in self.labels) { + [item cancelLoading]; + } - // 清理标签 - for (UILabel *label in self.labels) { - [label removeFromSuperview]; + // 清理视图 + for (PIGiftBravoGiftBroadcastItemView *item in self.labels) { + [item.layer removeAllAnimations]; + [item removeFromSuperview]; } [self.labels removeAllObjects]; + + // 重置状态 + self.index = 0; } - (void)didTapTips { diff --git a/YuMi/Modules/YMRoom/View/SendGiftView/View/XPSendGiftView.m b/YuMi/Modules/YMRoom/View/SendGiftView/View/XPSendGiftView.m index ce605d73..2eb5e2e7 100644 --- a/YuMi/Modules/YMRoom/View/SendGiftView/View/XPSendGiftView.m +++ b/YuMi/Modules/YMRoom/View/SendGiftView/View/XPSendGiftView.m @@ -124,12 +124,10 @@ UIKIT_EXTERN NSString * const kFreeGiftCountdownNotification; [[GiftComboManager sharedManager] registerActions:nil]; if (_bravoGiftView) { - [self.bravoGiftView endloop]; + [_bravoGiftView endloop]; } } - - #pragma mark - 连击状态管理 // 移除连击相关视图 diff --git a/YuMi/Modules/YMRoom/View/Setting/View/XPRoomInviteUserViewController.m b/YuMi/Modules/YMRoom/View/Setting/View/XPRoomInviteUserViewController.m index 8ed869bc..ba7148dc 100644 --- a/YuMi/Modules/YMRoom/View/Setting/View/XPRoomInviteUserViewController.m +++ b/YuMi/Modules/YMRoom/View/Setting/View/XPRoomInviteUserViewController.m @@ -95,7 +95,7 @@ [regularList enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { @kStrongify(self); NIMChatroomMember * member = obj; - if (member.type == NIMTeamMemberTypeOwner) { + if (member.type == NIMChatroomMemberTypeCreator) { [self.datasource insertObject:member atIndex:0]; }else { [self.datasource addObject:member]; @@ -129,7 +129,7 @@ NSArray * regularList = x; [regularList enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { NIMChatroomMember * member = obj; - if (member.type == NIMTeamMemberTypeOwner) { + if (member.type == NIMChatroomMemberTypeCreator) { [self.datasource insertObject:member atIndex:0]; }else { [self.datasource addObject:member]; @@ -326,6 +326,9 @@ } - (void)handleUpMicAction:(NIMChatroomMember *)member { + + // TODO : 发送类似 31 的消息 + if ([AccountInfoStorage instance].getUid.integerValue == member.userId.integerValue) { [Api getUserInfo:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) { if (code == 200) {