FGPopupScheduler supports Cocopods, a basic component that is easy to use and efficient.

preface

A few days ago, when new users just opened the APP, due to too many pop-ups and the translucent guidance layer, pop-ups often covered each other and even blocked the normal process. To solve such problems, we not only need to clarify the dependency between popovers, but also need to deal with the conditions of popovers themselves. And every time a new popover is added, the logic of the previous popover needs to be checked. Each step takes development resources.

So our goal is to figure out how to split the dependencies between popovers and show them at the right time, freeing up the glue code of when to show/when to hide.

Demand analysis

The first is the need for popovers themselves

  • Popup window display
  • Popup window hidden
  • The pop-up window displays the conditions that need to be met

And then about popovers and popovers

  • Popover priority
  • Whether a popover is affected by a displayed popover

One feature of the popover display is that only one popover will be displayed at the same time, and it can be displayed one after another. If you use queue management, of course, you need to deal with insert, delete, empty, traverse, and so on.

This process seems to solve the problem, but in fact, when all popovers are managed by a scheduler, we have to consider when it is more reasonable to show/hide these popovers.

Of course, FGPopupScheduler can handle these trivial tasks and more.

Implementation analysis

Considering the diversity of popovers themselves, the first step is to abstract the requirements required by the queue into

by protocol.

@ protocol FGPopupView < NSObject > @ optional / * FGPopupSchedulerStrategyQueue according to - showPopupView: Please do display logic, if contain animation implementation - showPopupViewWithAnimation: methods * / - (void) showPopupView; / * FGPopupSchedulerStrategyQueue according to - dismissPopupView: Do hidden logic, if contain animation please implement - showPopupViewWithAnimation: methods * / - (void) dismissPopupView; / * FGPopupSchedulerStrategyQueue according to - showPopupViewWithAnimation: to display logic. If the block is not unexpected problems may transfer * / - (void) showPopupViewWithAnimation (FGPopupViewAnimationBlock) block; / * FGPopupSchedulerStrategyQueue according to - dismissPopupView: Do hidden logic, if contain animation please implement - dismissPopupViewWithAnimation: method, If the block is not unexpected problems may transfer * / - (void) dismissPopupViewWithAnimation (FGPopupViewAnimationBlock) block; / * * FGPopupSchedulerStrategyQueue according to - canRegisterFirstPopupView judgment, when the queue order whether it can turn become the first priority PopupView response. The default value is YES * / - (BOOL) canRegisterFirstPopupViewResponder; / * * new * / / * * 0.4.0 FGPopupSchedulerStrategyQueue according to - popupViewUntriggeredBehavior: To determine the popover display behavior when triggered, The default is FGPopupViewUntriggeredBehaviorAwait * / - (FGPopupViewUntriggeredBehavior popupViewUntriggeredBehavior); / * * FGPopupViewSwitchBehavior according to - popupViewSwitchBehavior: To determine whether popovers that have been shown will be affected by subsequent popovers of higher priority, Defaults to FGPopupViewSwitchBehaviorAwait ⚠ ️ ⚠ ️ only come into force in FGPopupSchedulerStrategyPriority * / - (FGPopupViewSwitchBehavior)popupViewSwitchBehavior; @endCopy the code

As for the order and priority of popover display, the actual operation will also involve the operation of inserting or removing midway. The data structure is more similar to a linked list, so the STL standard library of C++ is adopted here: list.

The specific strategies are as follows

typedef NS_ENUM(NSUInteger, FGPopupSchedulerStrategy) { FGPopupSchedulerStrategyFIFO = 1 << 0, / / fifo FGPopupSchedulerStrategyLIFO = 1 < < 1, / / lifo FGPopupSchedulerStrategyPriority = 1 < < 2 / / priority scheduling};Copy the code

In fact the user can also be combined FGPopupSchedulerStrategyPriority | FGPopupSchedulerStrategyFIFO used together, to deal with when choosing priority strategy, how to decide the same priority plays the sorting of the window.

In addition 0.4.0 added FGPopupViewUntriggeredBehavior and FGPopupViewSwitchBehavior

FGPopupViewUntriggeredBehavior is used to select when response chain in turn triggered the popup window, if does not meet the display condition, will be discarded directly.

Typedef NS_ENUM (NSUInteger FGPopupViewUntriggeredBehavior) {FGPopupViewUntriggeredBehaviorDiscard, / / when the pop-up trigger display logic, But did not meet the conditions will be directly discarded FGPopupViewUntriggeredBehaviorAwait, / / when the pop-up trigger display logic, but did not meet the condition will continue to wait for};Copy the code

FGPopupViewSwitchBehavior for processing when the popup window has been displayed, whether is replaced by the pop-up window lock of higher priority.

Typedef NS_ENUM (NSUInteger FGPopupViewSwitchBehavior) {FGPopupViewSwitchBehaviorDiscard, / / when the popup window has been showed that if the back to play when the higher priority to play window, Showed higher priority to play and the current window popup window will be abandoned FGPopupViewSwitchBehaviorLatent, / / when the popup window has been showed that if the back to play the higher priority to play window, show higher priority to play and the current play window back into the queue, PS: Priority phase at the same time with FGPopupViewSwitchBehaviorDiscard FGPopupViewSwitchBehaviorAwait, / / when the popup window has been showed, not affected by subsequent yields and line level of the popup window};Copy the code

HitTest is used to solve the requirement of pop-up display conditions. If the current hit popover does not pass hitTest, the next popover will be obtained from the current list for testing according to the selected scheduler strategy.

- (PopupElement *)_hitTestFirstPopupResponder{ PopupElement *element; for(auto itor=_list.begin(); itor! =_list.end();) { PopupElement *temp = *itor; id<FGPopupView> data = temp.data; __block BOOL canRegisterFirstPopupViewResponder = YES; if ([data respondsToSelector:@selector(canRegisterFirstPopupViewResponder)]) { canRegisterFirstPopupViewResponder = [data canRegisterFirstPopupViewResponder]; } if (canRegisterFirstPopupViewResponder) { element = temp; break; } else if([data respondsToSelector:@selector(popupViewUntriggeredBehavior)] && [data popupViewUntriggeredBehavior] == FGPopupViewUntriggeredBehaviorDiscard){ itor = _list.erase(itor++); } else{ itor++; } } return element; }Copy the code

Due to the unified management of all popovers through FGPopupScheduler, the component needs to handle the occurrence of popovers on its own. The author considered three trigger cases altogether

  • When you add popover objects
  • The Runloop listens for when the main thread is idle
  • User active trigger

With the above three cases, you can cover almost all usage scenarios.

In addition, the suspended state is also added to the scheduler to actively suspend/resume the popover queue, which is used to control whether the current scheduler can trigger hitTest and then display the logic.

Additionally, the component supports thread safety. Considering that the timing of an operation may be on any thread, the component is thread-safe through pthread_mutex_t. (Pthread_mutex_t cannot switch thread locks/unlocks have been replaced by semaphores.) Note that popovers are switched to the main thread, so no additional processing is required.

At this point, the business of the entire component is clear. FGPopupScheduler uses state mode. The component needs to allow these three processing modes to change freely, so it uses policy mode to handle them. The following is the UML class diagram: