GitHub address: FGPopupScheduler
supports cocopods, a basic component that is easy to use and efficient.
foreword
A few days ago, the test feedback was that when a new user first opened the APP, due to too many pop-up windows and a translucent guide layer, the pop-up windows often covered each other and even blocked the normal process. To solve such problems, it is not only necessary to clarify the dependencies between pop-up windows, but also to deal with the conditions under which the pop-up windows themselves appear. And every time a new pop-up window is added, you need to check the logic of the previous pop-up window. Each step consumes development resources.
So our purpose is to solve, how to split the dependencies between each pop-up window, and display the pop-up windows in sequence at the right moment, liberate the glue code of when to show/when to hide.
demand analysis
The first is the needs of the pop-up window itself
- popup display
- Popup hidden
- A pop-up window displays the conditions that need to be met
Then it's about pop-ups and pop-ups
- Popup priority
- Whether the popup will be affected by the displayed popup
The pop-up window display has a feature, that is, only one pop-up window is displayed at the same time, and it can be displayed one after another. If a queue is used for management, it is a matter of course that additional behaviors such as insertion, deletion, emptying, and traversal need to be handled.
This set of processes seems to be solved, but in fact, when the unified management of all pop-up windows is handed over to a scheduler, we must consider when it is more reasonable to show/hide these pop-up windows.
Of course, FGPopupScheduler can help with these trivial things, and more.
Implementation Analysis
Considering the diversity of the pop-up window itself, first of all, the abstract processing of the requirements required by the queue is put into it through the protocol <FGPopupView>
.
@protocol FGPopupView <NSObject>
@optional
/*
FGPopupSchedulerStrategyQueue会根据 -showPopupView: 做显示逻辑,如果含有动画请实现-showPopupViewWithAnimation:方法
*/
- (void)showPopupView;
/*
FGPopupSchedulerStrategyQueue会根据 -dismissPopupView: 做隐藏逻辑,如果含有动画请实现-showPopupViewWithAnimation:方法
*/
- (void)dismissPopupView;
/*
FGPopupSchedulerStrategyQueue会根据 -showPopupViewWithAnimation: 来做显示逻辑。如果block不传可能会出现意料外的问题
*/
- (void)showPopupViewWithAnimation:(FGPopupViewAnimationBlock)block;
/*
FGPopupSchedulerStrategyQueue会根据 -dismissPopupView: 做隐藏逻辑,如果含有动画请实现-dismissPopupViewWithAnimation:方法,如果block不传可能会出现意料外的问题
*/
- (void)dismissPopupViewWithAnimation:(FGPopupViewAnimationBlock)block;
/**
FGPopupSchedulerStrategyQueue会根据-canRegisterFirstPopupView判断,当队列顺序轮到它的时候是否能够成为响应的第一个优先级PopupView。默认为YES
*/
- (BOOL)canRegisterFirstPopupViewResponder;
/** 0.4.0 新增*/
/**
FGPopupSchedulerStrategyQueue 会根据 - popupViewUntriggeredBehavior:来决定触发时弹窗的显示行为,默认为 FGPopupViewUntriggeredBehaviorAwait
*/
- (FGPopupViewUntriggeredBehavior)popupViewUntriggeredBehavior;
/**
FGPopupViewSwitchBehavior 会根据 - popupViewSwitchBehavior:来决定已经显示的弹窗,是否会被后续更高优先级的弹窗锁影响,默认为 FGPopupViewSwitchBehaviorAwait ⚠️⚠️ 只在FGPopupSchedulerStrategyPriority生效
*/
- (FGPopupViewSwitchBehavior)popupViewSwitchBehavior;
@end
复制代码
Regarding the order and priority of the pop-up window display, the actual operation will also involve the operation of inserting or removing in the middle. The data structure is more similar to the linked list, so the C++ STL standard library is used here: list.
The specific strategy is as follows
typedef NS_ENUM(NSUInteger, FGPopupSchedulerStrategy) {
FGPopupSchedulerStrategyFIFO = 1 << 0, //先进先出
FGPopupSchedulerStrategyLIFO = 1 << 1, //后进先出
FGPopupSchedulerStrategyPriority = 1 << 2 //优先级调度
};
复制代码
In fact, the user can also use it FGPopupSchedulerStrategyPriority | FGPopupSchedulerStrategyFIFO
together to deal with how to decide the order of the same priority pop-up window when selecting the priority policy.
In addition, 0.4.0 added FGPopupViewUntriggeredBehavior and FGPopupViewSwitchBehavior
FGPopupViewUntriggeredBehavior is used to select whether it will be discarded directly if the display conditions are not met when the pop-up window is triggered in the response chain.
typedef NS_ENUM(NSUInteger, FGPopupViewUntriggeredBehavior) {
FGPopupViewUntriggeredBehaviorDiscard, //当弹窗触发显示逻辑,但未满足条件时会被直接丢弃
FGPopupViewUntriggeredBehaviorAwait, //当弹窗触发显示逻辑,但未满足条件时会继续等待
};
复制代码
FGPopupViewSwitchBehavior is used to handle whether the popup will be replaced by a higher priority popup lock when the popup is already displayed.
typedef NS_ENUM(NSUInteger, FGPopupViewSwitchBehavior) {
FGPopupViewSwitchBehaviorDiscard, //当该弹窗已经显示,如果后面来了弹窗优先级更高的弹窗时,显示更高优先级弹窗并且当前弹窗会被抛弃
FGPopupViewSwitchBehaviorLatent, //当该弹窗已经显示,如果后面来了弹窗优先级更高的弹窗时,显示更高优先级弹窗并且当前弹窗重新进入队列, PS:优先级相同时同 FGPopupViewSwitchBehaviorDiscard
FGPopupViewSwitchBehaviorAwait, //当该弹窗已经显示时,不会被后续高优线级的弹窗影响
};
复制代码
It hitTest
solves the requirements of the pop-up window display conditions. If the current pop-up window fails to pass hitTest
, it will obtain the next pop-up window in 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;
}
复制代码
Because FGPopupScheduler
of the unified management of all pop-up windows, when the pop-up window is triggered, the component itself needs to be processed. This author considers a total of 3 triggering situations
- When adding a popup object
- Monitor the idle time of the main thread through Runloop
- User active trigger
Through the above three situations, almost all usage scenarios can be covered.
In addition, a state is added to the scheduler suspended
to actively suspend/restore the popup queue, which is used to control whether the current scheduler can trigger hitTest
and display the logic.
Additionally the component supports thread safety. Considering that the timing of the operation may be in any thread,Components . ( pthread_mutex_t
ensure thread safety bypthread_mutex_t
Unable to switch thread lock/unlock has been replaced with semaphore) It is worth noting that the display process of the popup window will be switched to the main thread, so no additional processing is required.
So far, the business of the entire component is relatively clear. FGPopupScheduler adopts the state mode. The component needs to allow these three processing methods to be freely changed, so it adopts the strategy mode to process. The following is the UML class diagram: