• 如何快速的开发一个完整的iOS直播app(礼物篇)


    搭建礼物列表

    • 使用modal,设置modal样式为custom,就能做到从小往上显示礼物列表,并且能看见前面的直播界面
    • 礼物模型设计
    • 一开始创建3个礼物模型,保存到数组,传入给礼物View展示,本来礼物数据应该从服务器获取,这里没做了。
    • 到时候拿到礼物View就能拿到对应按钮,传给服务器就好了.

    礼物模型设计

    礼物模型

    • 用户模型(userID,userName),用于标志哪个用户发送,这里为方便测试,保证UserID一样
    • 礼物ID(giftID),用于标志当前礼物
    • 礼物名称(giftName)
    • 礼物总数(giftCount),用于记录礼物连发数,总共发了多少礼物
    • 发送礼物的房间Key(roomKey),用于知道是发送个哪个房间
    @interface XMGGiftItem : NSObject
    
    // 礼物ID
    @property (nonatomic, assign) NSInteger giftId;
    
    // 用户模型:记录哪个用户发送
    @property (nonatomic, strong) XMGUserItem *user;
    
    // 礼物名称
    @property (nonatomic, strong) XMGUserItem *giftName;
    
    // 礼物个数,用来记录礼物的连击数
    @property (nonatomic, assign) NSInteger giftCount;
    
    // 发送哪个房间
    @property (nonatomic, strong) NSString *roomKey;
    
    + (instancetype)giftWithGiftId:(NSInteger)giftId userId:(NSInteger)userId giftCount:(NSInteger)giftCount roomKey:(NSString *)roomKey;
    
    @end
    
    
    + (instancetype)giftWithGiftId:(NSInteger)giftId giftCount:(NSInteger)giftCount roomKey:(NSString *)roomKey giftName:(NSString *)giftName
    {
        XMGGiftItem *item = [[self alloc] init];
        item.giftId = giftId;
        item.user = [[XMGUserItem alloc] init];
        // ID一样,模拟只有一个用户
        item.user.id = 1;
        item.user.userName = @"用户1";
        item.giftCount = giftCount;
        item.roomKey = roomKey;
        item.giftName = giftName;
        return item;
    
    }
    
    
    

    监听礼物点击

    点击礼物的时候,发送礼物

    • 这里使用了websocket搭建的后台服务器,进行礼物发送
        // 发送礼物
        SocketIOClient *socket = [SocketIOClient shareSocketIOClient];
        
        XMGGiftItem *gift = [XMGGiftItem giftWithGiftId:sender.tag userId:socket.user.id giftCount:1 roomKey:socket.roomKey];
        
        [socket emit:@"gift" with:@[gift.mj_keyValues]];
    

    三、礼物界面监听礼物发送

       // 监听礼物
        SocketIOClient *socket = [SocketIOClient shareSocketIOClient];
        
        [socket on:@"gift" callback:^(NSArray * _Nonnull data, SocketAckEmitter * _Nonnull ask) {
            NSLog(@"接收到礼物%@",data);
            XMGGiftItem *item = [XMGGiftItem mj_objectWithKeyValues:data[0]];
            
            // 显示礼物动画
            [self setupGiftAnim:item];
        }];
    

    四、设置礼物动画

    • 显示礼物业务逻辑

      • 1.并不是每次接收到礼物,都需要创建对应礼物动画View,一次最多显示2个礼物View,当执行完一个礼物,就判断是否还有未执行的礼物,继续执行.
      • 2.需要搞个礼物队列(数组)保存所有需要执行的礼物模型,并不是只保存未执行的礼物模型.
        • 2.1 什么是需要执行的礼物模型?每一个需要执行的礼物模型都对应一个礼物View
        • 2.2 如果只保存未执行的礼物,不记录之前的执行礼物,没法判断下一个礼物是否是连发礼物,因为拿不到之前的做判断。
        • 2.3 什么是连发礼物,同一个用户,连续发送相同的礼物。
        • 2.4 因此每接收一个新的礼物,需要与之前的礼物对比,是否是同一个人发送的相同礼物。
      @property (nonatomic, strong) NSMutableArray *giftQueue;
      
      - (NSMutableArray *)giftQueue
      

    {
    if (_giftQueue == nil) {
    _giftQueue = [NSMutableArray array];
    }
    return _giftQueue;
    }

    ```
    
    *   3.判断是否是连发礼物
        * 3.1 遍历礼物队列中所有礼物,判断当前接收的礼物与之前礼物是否有相同的UserID和相同的礼物ID。
        * 3.2 如果有相同的UserID和相同的礼物ID,就表示是连发礼物,,把礼物模型的礼物总数+1.
        * 3.3 不需要把连发礼物添加到礼物队列中,因为只要是连发礼物就表示之前已经有相同的礼物,会和之前礼物共用同一个礼物View,不需要创建新的礼物View.
        * 3.4 因此只要是连发礼物,就直接return,不做操作.
    
    ```
    

    pragma mark - 判断当前接收礼物是否属于连发礼物

    • (BOOL)isComboGift:(XMGGiftItem *)gift
      {
      XMGGiftItem *comboGift = nil;

      for (XMGGiftItem *giftItem in self.giftQueue) {

        // 如果是连发礼物就记录下来
        if (giftItem.giftId == gift.giftId && giftItem.userId == gift.userId) {
            comboGift = giftItem;
        }
      

      }

      if (comboGift) { // 连发礼物有值
      // 礼物模型的礼物总数+1
      comboGift.giftCount += 1;
      return YES;
      }

      return NO;
      }

      
      *   4.如果不是连发礼物,直接把接收到的礼物添加到礼物队列
      *   5.搞个数组记录当前显示的动画View
          *   5.1 最多显示两个礼物动画View,记录当前正在做动画的View
          *   5.2 如果超过2个显示的View,就先不创建礼物View,直接retun
      
      

      @property (nonatomic, strong) NSMutableArray *giftAnimViews;

      • (NSMutableArray *)giftAnimViews
        {
        if (_giftAnimViews == nil) {
        _giftAnimViews = [NSMutableArray array];
        }
        return _giftAnimViews;
        }
      *   6.过滤掉以上2个条件之后,`处理礼物动画`
          *   6.1 创建礼物View
          *   6.2 设置礼物View的frame
              *   6.2.1 分为上下两部分,先显示到底部,在显示顶部
              *   6.2.2 怎么才知道当前礼物View显示在哪部分,搞个位置数组,每次从数组中取出一个位置,取完,就移除,这样下次就不会显示重复的地方了。
          *   6.3 添加礼物View到控制器的View中
          *   6.4 做礼物平移动画
          *   6.5 礼物平移动画做完,开始做连击动画
          
      

      // 处理礼物动画

    • (void)handleGiftAnim:(XMGGiftItem *)gift
      {
      // 1.创建礼物动画的View
      XMGGiftAnimView *giftView = [XMGGiftAnimView giftAnimView];

      CGFloat h = self.view.bounds.size.height * 0.5;
      CGFloat w = self.view.bounds.size.width;

      // 取出礼物位置
      id position = self.positions.lastObject;

      // 从数组移除位置
      [self.positions removeObject:position];

      CGFloat y = [position floatValue] * h;
      // 2.设置礼物View的frame
      giftView.frame = CGRectMake(0, y, w, h);

      // 3.传递礼物模型
      giftView.gift = gift;

      // 记录当前位置
      giftView.tag = [position floatValue];

      // 添加礼物View
      [self.view addSubview:giftView];

      // 添加记录礼物View数组
      [self.giftAnimViews addObject:giftView];

      __weak typeof(self) weakSelf = self;

      giftView.dismissView = ^(XMGGiftAnimView *giftView){
      [weakSelf dismissView:giftView];
      };

      // 设置动画
      giftView.transform = CGAffineTransformMakeTranslation(-w, 0);
      [UIView animateWithDuration:.25 delay:0 usingSpringWithDamping:0.6 initialSpringVelocity:1 options:UIViewAnimationOptionCurveLinear animations:^{
      giftView.transform = CGAffineTransformIdentity;
      } completion:^(BOOL finished) {
      // 开始连击动画
      [giftView startComboAnim];
      }];
      }

      
      *   7.礼物连击动画
          *   7.1 封装到礼物View中,礼物需要拿到礼物连击Label做事情
          *   7.2 每隔一段时间,需要修改连击数,搞个定时器,每隔0.3秒做事情
          *   7.3 连击动画,也需要控制在0.3秒刚好做完,就能直接做下一次动画。
          *   7.4 搞个属性记录当前连击数,没执行一次连击就++,当当前连击数大于礼物总数的时候,表示连击动画执行完毕,需要销毁定时器,销毁当前礼物View
          *   7.5 `注意点`:当当前连击数大于礼物总数的时候,不能马上确定连击动画执行完毕,因为电脑执行速度大于用户点击速度,有可能用户在点的时候,没有电脑执行快,电脑执行完直接把礼物View移除了,就看不到连击效果了。
          *   7.6 因此需要延迟销毁定时器,而且只要有新的连击数了,需要取消销毁定时器,要不然可能连击数还没显示完,定时器就销毁了
          
      
      • (void)startComboAnim{
        if (_timer == nil) {
        _timer = [NSTimer scheduledTimerWithTimeInterval:0.3 target:self selector:@selector(combo) userInfo:nil repeats:YES];
        _curComboCount = 1;
        }
        }

      // 连击

    • (void)combo
      {
      // 当前连发数,已经显示完毕
      if (_curComboCount > _gift.giftCount) { // 停止定时器
      // 停止定时器
      [self performSelector:@selector(cancel) withObject:nil afterDelay:1];
      } else {

        // 修改label显示
        _comboView.text = [NSString stringWithFormat:@"x%ld",_curComboCount];
        
        [UIView animateWithDuration:0.15 animations:^{
            _comboView.transform = CGAffineTransformMakeScale(3, 3);
        } completion:^(BOOL finished) {
           [UIView animateWithDuration:0.15 animations:^{
               _comboView.transform = CGAffineTransformIdentity;
           }];
        }];
        
        // 取消定时器销毁
        [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(cancel) object:nil];
        
        _curComboCount++;
      

      }
      }

      
      *   8.连击动画做完,
          *   8.1 需要停止定时器
          *   8.2 需要移除礼物动画的View
          *   8.3 把礼物动画的View和礼物都移除数组,需要回到之前控制器,用Block
          *   `注意点`:cancel方法可能会调用多次,定时器没有销毁,就会一直调用cancel方法,但是只需要执行一次,需要搞个属性记录下.
          *   原因:因为要在1秒之后才会调用cancle,那在这一秒内,肯定又会调用定时器方法,而且这时候当前连击数已经大于礼物总数,就会在1秒内多少执行cancle方法,导致cancle在1秒内调用多次.
          
          
          ``` 
          - (void)cancel
          {
              if (_isCancel == NO) {
                  
                  _isCancel = YES;
                  
                  [_timer invalidate];
                  _timer = nil;
                  
                  if (_dismissView) {
                      _dismissView(self);
                  }
              }
          }
          ```
          
      *   9.连击动画结束后执行的DismissBlock.
          *   9.1 做礼物View移除动画,往上移动,透明度为0
          *   9.2 把礼物模型从队列移除
          *   9.3 把礼物View从显示的礼物View数组移除
          *   9.4 移除当前View
          *   9.5 恢复位置到位置数组
              *   9.3.1 怎么知道恢复哪个位置?可以用礼物View的tag记录当前礼物View的位置
              *   9.3.2 如果当前tag为0,需要插入第0个位置,其他情况使用addObject.
          *   9.6 当一个礼物做完动画,查看队列中是否还有未执行的礼物。
          
          ```
                  - (void)dismissView:(XMGGiftAnimView *)giftView
          {
              
              [UIView animateWithDuration:0.25 animations:^{
                  
                  giftView.alpha = 0;
                  giftView.transform = CGAffineTransformMakeTranslation(0, -20);
                  
              } completion:^(BOOL finished) {
                  
                  // 移除当前礼物
                  [self.giftQueue removeObject:giftView.gift];
                  
                  // 移除当前动画的View
                  [giftView removeFromSuperview];
                  
                  // 移除礼物动画View数组
                  [self.giftAnimViews removeObject:giftView];
                  
                  // 恢复当前位置
                  if (giftView.tag == 0) {
                      // 插入第0个位置
                      [self.positions insertObject:@(0) atIndex:0];
                  } else {
                      [self.positions addObject:@(giftView.tag)];
                  }
                  
                  // 判断队列中是否还有未处理的礼物
                  XMGGiftItem *item = [self fetchNoHandleGiftItemOfQueue];
                  
                  // 处理礼物动画
                  if (item) {
                      [self handleGiftAnim:item];
                  }
                  
              }];
              
          }
          ```
      *   10.执行完一个礼物,判断礼物队列是否还有未执行的礼物
          *   10.1 遍历礼物队列中所有礼物,查看是否有未执行的礼物
          *   10.2 取出的礼物,有可能是当前正在执行的礼物,需要排除掉
              *   10.2.1 遍历当前正在执行的礼物View,查看取出的礼物是否和它的礼物相同,相同表示当前礼物在执行
          *   10.3 获取到未执行的礼物,直接处理礼物
          ```
          // 搜索礼物队列中未执行的礼物
          - (XMGGiftItem *)fetchNoHandleGiftItemOfQueue
          {
          
              // 取出队列中的礼物
              for (XMGGiftItem *item in self.giftQueue) {
                  // 当前礼物模型有可能在执行
                  if (![self isExcutingGift:item]) return item;
              }
              
              return nil;
          }
          
          // 判断当前礼物是否正在执行
          - (BOOL)isExcutingGift:(XMGGiftItem *)gift
          {
              // 判断当前模型是否已经在执行,执行就不需要在做动画
              for (XMGGiftAnimView *giftView in self.giftAnimViews) {
                  
                  if (giftView.gift == gift) return YES;
              }
              
              return NO;
          }
          
          ```
          
      
    • 礼物整体业务逻辑

          - (void)setupGiftAnim:(XMGGiftItem *)gift
          {
              // 1.判断当前接收的礼物是否属于连发礼物 属于直接return,不需要在重新创建新的动画View
              if ([self isComboGift:gift]) return;
              
              // 2.添加到礼物队列
              [self.giftQueue addObject:gift];
              
              // 3.判断当前显示多少个礼物动画View
              if (self.giftAnimViews.count == 2) return;
              
              // 4.处理礼物动画
              [self handleGiftAnim:gift];
          }
      
      
     
     
  • 相关阅读:
    [译] 我最终是怎么玩转了 Vue 的作用域插槽
    通俗易懂的Git使用入门教程
    JS取出两个数组的不同或相同元素
    jQuery中四种事件监听的区别
    vuex里mapState,mapGetters使用详解
    php 获取时间今天明天昨天时间戳
    Linux crontab定时执行任务
    php返回json数据函数实例_php技巧_脚本之家
    mysql查看表结构命令
    Mysql命令大全
  • 原文地址:https://www.cnblogs.com/hanease/p/16253180.html
Copyright © 2020-2023  润新知