• Some time ago, the company APP revised the gift system of the live broadcast room. Since the income of the live broadcast did not depend on the gift sharing, the present system was just a simple display. In order to adapt to the mainstream broadcast room gift effect, specially revised from this!
  • In the first lot

1. In the gift system of all live broadcast rooms, the first step the user sees is nothing more than the gift list interface

  • Throughout the mainstream broadcast gift list should be using UICollectionView, so I am no exception, the following is a variety of video code. Results the following

  • Looks like it worked out pretty well. But but I suddenly noticed a problem. The presents were presented in a different order than I wanted them to be, and not in the same order as the data. Look at the picture,

  • The yellow order was the order we wanted, but now the order is red. Why is that? We all know that the orientation of the CollectionView is controlled by layout. The following code
    UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
    layout.itemSize = CGSizeMake(itemW, itemH);
    layout.minimumLineSpacing = 0;
    layout.minimumInteritemSpacing = 0;
    layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;

Copy the code
  • After looking at the code, we see that since we set the scrolling direction to scroll horizontally, the system by default populates vertical items first and then landscape items, which explains the sorting. What about vertical scrolling?

  • This also does not meet our needs, since the system is not good, so only take out a unique weapon, customize a Flowlayout. Let it scroll the way we want it to, let it sort.
- (void)prepareLayout {// All custom layouts must override this method [super prepareLayout]; // Set the basic property CGFloat itemW = SCREEN_WIDTH/4.0; CGFloat itemH itemW * = 105/93.8; self.itemSize = CGSizeMake(itemW, itemH); self.minimumLineSpacing = 0; self.minimumInteritemSpacing = 0; self.scrollDirection = UICollectionViewScrollDirectionHorizontal; [self.cellAttributesArray removeAllObjects]; NSInteger cellCount = [self.collectionView numberOfItemsInSection:0];for(NSInteger i = 0; i < cellCount; I++) {// get the layout of each Item. Reassign NSIndexPath *indexPath = [NSIndexPath indexPathForRow: IinSection:0]; UICollectionViewLayoutAttributes *attibute = [self layoutAttributesForItemAtIndexPath:indexPath]; NSInteger page = i / 8; NSInteger row = I % 4 + page*4; NSInteger col = I / 4 - page*2; Attibute. frame = CGRectMake(row*itemW, col*itemH, itemW, itemH); // Save all reassigned layouts [self.cellAttributesArray addObject:attibute]; } } - (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect{ // Returns the calculated layout within the current visible areareturn self.cellAttributesArray;
}

Copy the code
  • After writing out the heart smug, so should be able to achieve it. Let’s see what happens

  • Should be able to see the problem, I selected the gift of the first page and the second page unexpectedly appeared, I clearly set the page scrolling ah. See the hierarchy below

  • It was the lovely gift that got squeezed outside. Since there is no effect of setting spring, I did not pay much attention to the missing gift. What is the reason? After thinking for a long time, I realized that the scrolling range was not enough, which led to no display in the interface. I’ve also explored how to set up my custom layout for contentoffset. And finally found a way.
- (CGSize)collectionViewContentSize{
    
    NSInteger cellCount = [self.collectionView numberOfItemsInSection:0];
    NSInteger page = cellCount / 8 + 1;
    return CGSizeMake(SCREEN_WIDTH*page, 0);
}

Copy the code
  • But is it really ok? Let’s see what happens

  • So far a mainstream gift list interface has been basically implemented. Just look at the code for the gift click logic. Without further ado here (see code – JPGiftView)

2. Click the animation effect display of the gift after sending

  • The simplest implementation is to create a View, click send, and pass the current selected gift information into the View to display the gift effect, write a displacement animation to display. If sequential, count how many times the gift is clicked before the view is displayed, and then show x. As shown in figure

  • But there must be a lot of disadvantages, such as I would count a user giving one of the gifts as a complete actual gift. Different gifts from the same user count as the second complete gift. Then each complete gift is unique. If you use the logic above, you will find all kinds of bugs that make you laugh, such as the accumulation of different gifts, different gifts will replace the current gift being displayed…..
  • Now that you know the bug exists, how do you fix it? The first thing that comes to mind is a powerful queue, an object-oriented class that Apple has wrapped for us, NSOperationQueue. This way we can treat each complete gift as an operation — NSOperation. Join the queue, so that the presentation of the gift is automatically performed in order. The reason and logic are thought out, how to achieve it is to need to consider the next cough up!
  • As the saying goes, code can’t cheat, and when I queue one operation after another, I get a bug. Not as we imagine one by one in the order queue to perform. (system have the right way to rely on, but want to think too can realize demand, also can’t try) then go to Google, just know originally the system to provide the API can only join operation, at the end of an operation is not the time to perform the next operation. If you want to do it sequentially, you define a custom action, and then finish the current action after a full gift animation display is complete, so that the next action is performed sequentially!
  • The concrete code is visible in the JPGiftOperation class
  • The main thing about a custom operation is to change the two properties of the operation as shown below. The default is NO. At sign synthesize disables the system GET/SET, developer control
  • We need to override the STAR method to create the action (presentation of the gift animation)
- (void)start {
    
    if ([self isCancelled]) {
        _finished = YES;
        return;
    }
    
    _executing = YES;
    NSLog(@"Current queue -- %@",self.model.giftName);
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
    
        [self.giftShowView showGiftShowViewWithModel:self.model completeBlock:^(BOOL finished,NSString *giftKey) {
            self.finished = finished;
            if(self.opFinishedBlock) { self.opFinishedBlock(finished,giftKey); }}]; }]; }Copy the code

// When the animation ends self.finished = YES; KVO is then manually triggered to change the state of the current operation#pragma Mark - Trigger KVO manually
- (void)setExecuting:(BOOL)executing
{
    [self willChangeValueForKey:@"isExecuting"];
    _executing = executing;
    [self didChangeValueForKey:@"isExecuting"];
}

- (void)setFinished:(BOOL)finished
{
    [self willChangeValueForKey:@"isFinished"];
    _finished = finished;
    [self didChangeValueForKey:@"isFinished"];
}

Copy the code
  • So when the animation ends, we can control that the current operation is done. Then the system will automatically queue to perform the next existing operation. Basically realize the effect of queue.

  • After the queue effect is implemented, the next step is if the user combos a gift. How do you do that? See what combos look like now

  • What the hell is this? This is a combo.
  • It seems that we need a management class to manage the presentation logic of gifts, create operations according to certain rules, and queue. Thus came the JPGiftShowManager class.
  • We need to get the current click gift of information, you can judge the present specific what to display, are queuing up to show or in the current display gift combo, or is the gift of queuing to show the accumulation, and so on and so forth, so that all the logic implemented in the management class, outside at least can only sentence code to present data can perfect show Show the effect of a gift. It’s nice to think about it.
  • Let’s write a method entry for presenting gifts, rather than singletons.
/** @param backView giftModel The parent view @param giftModel The data of the giftModel @param completeBlock The display completes the callback */ - (void)showGiftViewWithBackView:(UIView *)backView info:(JPGiftModel *)giftModel completeBlock:(completeBlock)completeBlock;Copy the code
  • As mentioned earlier, each complete gift is a unique existence, and only the same complete gift can perform combos or accumulative operations. So how do you tell the difference between a single gift. I put an attribute giftKey in the Model of the gift and spliced it with the name of the gift and the ID of the gift (I spliced it with the user’S ID and the gift ID in the actual project, which guaranteed the uniqueness).
/ / @property(nonatomic,copy)NSString *giftKey; // write the get method in.m - (NSString *)giftKey {return [NSString stringWithFormat:@"% @ % @",self.giftName,self.giftId];
}

Copy the code
  • So we also need at least two containers in the management class to store the keys that have been passed in and the operations that have been created.
/** operationCache */ @property (nonatomic,strong) NSCache *operationCache; /** Current giftkey */ @property(nonatomic,strong) NSString *curentGiftKey;Copy the code
  • When we get a new gift data, we need to determine whether the gift key is the same as the curentGiftKey and whether the operation corresponding to the gift key is in the operationCache.
    if(self. CurentGiftKey && [self curentGiftKey isEqualToString: giftModel. GiftKey]) {/ / have the gift of the current informationif([self operationCache objectForKey: giftModel giftKey]) {/ / existing operations You can in the current operation effect of accumulative presents a combo}else{/ / the current operation is over To recreate the JPGiftOperation * operation = [JPGiftOperation addOperationWithView: showView OnView: backView Info:giftModel completeBlock:^(BOOL finished,NSString *giftKey) {if(self.finishedBlock) { self.finishedBlock(finished); } / / remove operation [self operationCache removeObjectForKey: giftKey]; // Empty the unique key self.curentGiftKey = @""; }]; // Store the operation information [self.operationCache]setObject:operation forKey:giftModel.giftKey]; [queue addOperation:operation]; }}else{// No gift informationif([self operationCache objectForKey: giftModel giftKey]) {/ / existing operations That is present in the queue for show}else{/ / the current display for the first time this gift JPGiftOperation * operation = [JPGiftOperation addOperationWithView: showView OnView: backView Info:giftModel completeBlock:^(BOOL finished,NSString *giftKey) {if(self.finishedBlock) { self.finishedBlock(finished); } / / remove operation [self operationCache removeObjectForKey: giftKey]; [self.curentGiftKeys removeObject:giftKey]; }]; operation.model.defaultCount += giftModel.sendCount; // Store the operation information [self.operationCache]setObject:operation forKey:giftModel.giftKey]; [queue addOperation:operation]; }}Copy the code
  • Some of you may be wondering, how did the current gift key, self. Take a look at this code
        [_giftShowView setShowViewKeyBlock:^(JPGiftModel *giftModel) {
            _curentGiftKey = giftModel.giftKey;
        }];

Copy the code
  • I call back when the star method of the operation calls the animation of the gift display, judging that the present is the first time the gift is displayed, and call back the key to the management class.
    if (self.showViewKeyBlock && self.currentGiftCount == 0) {
        self.showViewKeyBlock(giftModel);
    }

Copy the code
  • So we can get the key that we’re currently showing. Logic by deciding whether to create a new action or do a combo.
  • Although the logic has been, but specific how to achieve the effect of combos? Because our animation was hidden and removed with dispatch_after after show. In order to achieve combos, first of all, it is necessary to solve how to avoid the end of the animation of the gift display in the process of combos. So IT occurred to me that I should cancel the deferred execution method during the gift accumulation process and create the deferred execution method after the cancellation. This recreates the hidden animation method with each combos.
  • Finally, I checked the information that dispatch_after could not realize this requirement. I found a way to do that. As soon as the number of gifts currently displayed is greater than 1, this logic is executed, cancel-create. If there is only one gift then the normal logic is to unanimate it.
if(self.currentGiftCount > 1) { [self p_SetAnimation:self.countLabel]; [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(hiddenGiftShowView) object:nil]; // Can cancel successfully. [self performSelector:@selector(hiddenGiftShowView) withObject:nil afterDelay:animationTime]; }else {
        [self performSelector:@selector(hiddenGiftShowView) withObject:nil afterDelay:animationTime];
    }
Copy the code
  • How does the combo code work? There are two properties in the View that displays the gift animation. The total number of gifts currently clicked by an incoming user (which defaults to 1), and the total number of gifts currently displayed.
/** giftCount */ @property(nonatomic,assign) NSInteger giftCount; /** currentGiftCount */ @property(nonatomic,assign) NSInteger currentGiftCount;Copy the code
  • When will combos and queue accumulations occur?
  • Combo effect – the present self.curentGiftKey is the same as the new giftkey and there is an action for the current key in the action buffer pool. This will result in a combo effect. In this case, we just need to assign giftCount to the number of gifts selected by the user (currently, the default is one gift at a time).
JPGiftOperation *op = [self.operationCache objectForKey:giftModel.giftKey]; op.giftShowView.giftCount = giftModel.sendCount; // Limit the maximum number of combos per giftif(op. GiftShowView. CurrentGiftCount > = giftMaxNum) {/ / remove operation [self. OperationCache removeObjectForKey: giftModel. GiftKey]; // Empty the unique key self.curentGiftKey = @"";
            }

Copy the code
  • Let’s look at what happens after the assignment. Take the current number of gift clicks that came in and add them to the total number of gifts, and then assign. Do you see familiar code? That’s right, the delay hiding method is also controlled here. In this way, the effect of the combo is achieved.
- (void)setGiftCount:(NSInteger)giftCount {
    
    _giftCount = giftCount;
    self.currentGiftCount += giftCount;
    self.countLabel.text = [NSString stringWithFormat:@"x %zd",self.currentGiftCount];
    NSLog(@"Accumulated gifts %zd",self.currentGiftCount);
    if(self.currentGiftCount > 1) { [self p_SetAnimation:self.countLabel]; [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(hiddenGiftShowView) object:nil]; // Can cancel successfully. [self performSelector:@selector(hiddenGiftShowView) withObject:nil afterDelay:animationTime]; }else{ [self performSelector:@selector(hiddenGiftShowView) withObject:nil afterDelay:animationTime]; }}Copy the code
  • Queue accumulation – After getting the key clicked by the current user, it is different from the current display key, but the action for the key clicked exists. This means that the gift is waiting to be shown, so we need to add up the gift that is not shown. I call this queue accumulation.
JPGiftOperation *op = [self.operationCache objectForKey:giftModel.giftKey]; op.model.defaultCount += giftModel.sendCount; // Limit the maximum number of combos per giftif(op. Model. DefaultCount > = giftMaxNum) {/ / remove operation [self. OperationCache removeObjectForKey: giftModel. GiftKey]; // Empty the unique key self.curentGiftKey = @"";
            }

Copy the code
  • I don’t know if YOU’ve noticed that these two logics are handled differently. DefaultCount is the default number of hits I give each gift. It only adds up after you click. For example, if defaultCount is 1 after giving a sum, then the number to the right of the gift is the value of defaultCount when I first show it. The value of self.currentGIFtCount used only in combos.
op.giftShowView.giftCount = giftModel.sendCount;
op.model.defaultCount += giftModel.sendCount;
Copy the code
  • Looking back at the judgment logic, defaultCount was also used in the complete first creation of the gift display.
  • This method is eventually called in the show method to show the animation
        self.currentGiftCount = 0;
        [self setGiftCount:giftModel.defaultCount];
Copy the code
  • So let’s see what we have here.

  • Finally, we did. When we were ready to submit it for testing, we added another requirement to our product. Put a GIF on the first presentation of the gift. And the same gift is only shown once in combos. Yeah, yeah, yeah.
  • So you think you can beat me. Hey hey, remember the previous method, now just can be used. It just fits the product’s needs and only calls back when the current gift is first shown.
    if (self.showViewKeyBlock && self.currentGiftCount == 0) {
        self.showViewKeyBlock(giftModel);
    }

Copy the code
  • So we’re going to change the way we manage our class, because we’re going to need a callback to tell our controller, my gift is showing, you show me the GIF.
/** The parent view @param giftModel needs to display the data for the giftModel @param completeBlock callback */ - (void)showGiftViewWithBackView:(UIView *)backView info:(JPGiftModel *)giftModel completeBlock:(completeBlock)completeBlock completeShowGifImageBlock:(completeShowGifImageBlock)completeShowGifImageBlock;
Copy the code
  • So in the callback method, we just call this callback and let the controller handle the rest.
[_giftShowView setShowViewKeyBlock:^(JPGiftModel *giftModel) {
            _curentGiftKey = giftModel.giftKey;
            if (weakSelf.completeShowGifImageBlock) {
                weakSelf.completeShowGifImageBlock(giftModel); }}];Copy the code
  • Now let’s look at an effect

  • At this point in time, this feature has already fulfilled all the requirements of the product. Our project uses functionality that just goes here.
  • But I do think, now the mainstream is not to support the display of two gifts at the same time, so how to achieve.
  • Think of…
  • Since one queue displays one gift, does showing 2 or more require more queues to display? Then give it a try.
  • Two queues, two views that can display animations, and keys that are no longer NSStrings, become an array to put the keys of the two gifts that are currently being displayed.
/** queue */ @property(nonatomic,strong) NSOperationQueue *giftQueue1; @property(nonatomic,strong) NSOperationQueue *giftQueue2; /** showgift */ @property(nonatomic,strong) JPGiftShowView *giftShowView1; @property(nonatomic,strong) JPGiftShowView *giftShowView2; /** operationCache */ @property (nonatomic,strong) NSCache *operationCache; /** Current gift keys */ @property(nonatomic,strong) NSMutableArray *curentGiftKeys;Copy the code
  • You just need to determine which queue has fewer operands when the create operation is enqueued, and then add the newly created operation to the queue for display. The whole process code is as follows.
- (void)showGiftViewWithBackView:(UIView *)backView info:(JPGiftModel *)giftModel completeBlock:(completeBlock)completeBlock completeShowGifImageBlock:(completeShowGifImageBlock)completeShowGifImageBlock {
    
    self.completeShowGifImageBlock = completeShowGifImageBlock;
    
    if(self. CurentGiftKeys. Count && [self. CurentGiftKeys containsObject: giftModel. GiftKey]) {/ / have the gift of the current informationif([self operationCache objectForKey: giftModel giftKey]) {/ / existing operating JPGiftOperation * op = [self. OperationCache objectForKey:giftModel.giftKey]; op.giftShowView.giftCount = giftModel.sendCount; // Limit the maximum number of combos per giftif(op. GiftShowView. CurrentGiftCount > = giftMaxNum) {/ / remove operation [self. OperationCache removeObjectForKey: giftModel. GiftKey]; / / to empty the only key [self curentGiftKeys removeObject: giftModel. GiftKey]; }}else {
            NSOperationQueue *queue;
            JPGiftShowView *showView;
            if (self.giftQueue1.operations.count <= self.giftQueue2.operations.count) {
                queue = self.giftQueue1;
                showView = self.giftShowView1;
            }else{ queue = self.giftQueue2; showView = self.giftShowView2; } / / the current operation is over To recreate the JPGiftOperation * operation = [JPGiftOperation addOperationWithView: showView OnView: backView Info:giftModel completeBlock:^(BOOL finished,NSString *giftKey) {if(self.finishedBlock) { self.finishedBlock(finished); } / / remove operation [self operationCache removeObjectForKey: giftKey]; [self.curentGiftKeys removeObject:giftKey]; }]; operation.model.defaultCount += giftModel.sendCount; // Store the operation information [self.operationCache]setObject:operation forKey:giftModel.giftKey]; [queue addOperation:operation]; }}else{// No gift informationif([self operationCache objectForKey: giftModel giftKey]) {/ / existing operating JPGiftOperation * op = [self. OperationCache objectForKey:giftModel.giftKey]; op.model.defaultCount += giftModel.sendCount; // Limit the maximum number of combos per giftif(op. Model. DefaultCount > = giftMaxNum) {/ / remove operation [self. OperationCache removeObjectForKey: giftModel. GiftKey]; / / to empty the only key [self curentGiftKeys removeObject: giftModel. GiftKey]; }}else {
            NSOperationQueue *queue;
            JPGiftShowView *showView;
            if (self.giftQueue1.operations.count <= self.giftQueue2.operations.count) {
                queue = self.giftQueue1;
                showView = self.giftShowView1;
            }else {
                queue = self.giftQueue2;
                showView = self.giftShowView2;
            }

            JPGiftOperation *operation = [JPGiftOperation addOperationWithView:showView OnView:backView Info:giftModel completeBlock:^(BOOL finished,NSString *giftKey) {
                if(self.finishedBlock) { self.finishedBlock(finished); } / / remove operation [self operationCache removeObjectForKey: giftKey]; [self.curentGiftKeys removeObject:giftKey]; }]; operation.model.defaultCount += giftModel.sendCount; // Store the operation information [self.operationCache]setObject:operation forKey:giftModel.giftKey]; [queue addOperation:operation]; }}Copy the code
  • Results the following

  • So here we go, we’re done. The first time to write such a long article, or technical. I can feel many of the deficiencies myself. Many of them are indescribable and a little weak. There are a lot of places where you can’t be sure and you have to be clumsy to experiment with code. Finally, I was lucky enough to complete the revision within the construction period. Please give me more advice on the shortcomings.
  • Send to GitHub address GitHub