├── README.md ├── README ├── 1.gif ├── 2.gif └── 3.gif └── demo ├── README.md ├── README ├── 1.gif ├── 2.gif └── 3.gif ├── demo.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── zhaoxueliang.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── zhaoxueliang.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ └── xcschememanagement.plist ├── demo ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── LYWindowScreenView │ ├── LYWindowScreenView.h │ ├── LYWindowScreenView.m │ ├── NSObject+LYSwizzle.h │ ├── NSObject+LYSwizzle.m │ ├── UIViewController+LYPageWillAppear.h │ └── UIViewController+LYPageWillAppear.m ├── SceneDelegate.h ├── SceneDelegate.m ├── ViewController.h ├── ViewController.m ├── ViewController.xib ├── ViewControllerFour.h ├── ViewControllerFour.m ├── ViewControllerFour.xib ├── ViewControllerThree.h ├── ViewControllerThree.m ├── ViewControllerThree.xib ├── ViewControllerTwo.h ├── ViewControllerTwo.m ├── ViewControllerTwo.xib └── main.m ├── demoTests ├── Info.plist └── demoTests.m └── demoUITests ├── Info.plist └── demoUITests.m /README.md: -------------------------------------------------------------------------------- 1 | > App里总会有很多的弹窗,为了美观,大多数弹窗都需要盖住导航栏;这时弹窗会添加到window上以满足需求。但添加到window上的弹窗却不方便管理,也与页面脱离关系。 2 | 3 | ###window弹窗面临的问题: 4 | 1、**多个弹窗可能会产生重叠**:如app启动的时候有2个弹窗,正巧2个弹窗都触发展示,这时候这2个弹窗就会重叠在一起。 5 | 6 | 2、**弹窗无法与页面关联**:例如网络请求弹窗的数据,弹窗的展示因此而延时,如用户在此期间跳转其他页面,因为是window弹窗,弹窗则会展示在不应该展示的页面上 7 | 8 | 3、**弹窗无法设置优先级**:一个比较简单的例子:多个弹层的新手引导,用户关闭引导页1时,按顺序呈现引导页2、引导页3, 如果中间有其他弹窗出现的逻辑,应该等待引导页结束再展示。 9 | 10 | 4、**弹窗无法留活**:一个简单的例子:一个活动弹窗含有2个活动,点击活动A进入详情页,此时window弹窗应该消失,当从详情页返回时,活动弹窗应该继续展示,才能点击进入活动B查看详情。为了避免重新触发弹窗的逻辑,应该对弹窗进行缓存。 11 | 12 | 5、**弹窗不能自动关闭**:例如用户被迫下线,此时app的所有弹窗都应该自动移除,或者弹窗展示情况下app发生页面跳转,避免弹窗忘记关闭的情况,也应该自动移除现有的弹窗。 13 | 14 | 问题4效果对比-前: 15 | ![t6znx-4jxkk.gif](https://upload-images.jianshu.io/upload_images/7174973-523ac5bfeb22a035.gif?imageMogr2/auto-orient/strip%7CimageView2/2/w/250) 16 | 17 | 18 | 19 | 问题4效果对比-后: 20 | ![tugcl-llxv1.gif](https://upload-images.jianshu.io/upload_images/7174973-65cf15cc74379153.gif?imageMogr2/auto-orient/strip%7CimageView2/2/w/250) 21 | 22 | 23 | ###解决方案: 24 | **一、多个弹框重叠冲突:** 这个问题比较好解决,简单的做法是使用信号量来限制当前弹窗的数量,让弹窗一个一个的出现。创建一个弹窗manager,添加show和dismiss方法, show方法lock, dismiss方法 Release Lock。 25 | ```` 26 | + (void)showView:(UIView *)view { 27 | if ([self shareInstance].currentView == nil) { 28 | // 当前无弹窗展示直接展示 29 | UIWindow *window = [UIApplication sharedApplication].delegate.window; 30 | [window addSubview:view]; 31 | } else { 32 | // 当前有弹窗则加入队列中 33 | LYWindowScreenModel *model = [LYWindowScreenModel new]; 34 | model.view = view; 35 | [[self shareInstance].arrayWaitViews addObject:model]; 36 | } 37 | } 38 | 39 | + (void)dismiss:(UIView *)view { 40 | if ([self shareInstance].currentView == view) { 41 | // 删除当前弹窗 42 | [view removeFromSuperview]; 43 | [[self shareInstance] setCurrentView:nil]; 44 | } else { 45 | // 删除队列中的弹窗 46 | for (int i = 0; i < [self shareInstance].arrayWaitViews.count; i++) { 47 | LYWindowScreenModel *model = [self shareInstance].arrayWaitViews[i]; 48 | if (model.view == view) { 49 | [[self shareInstance].arrayWaitViews removeObject:model]; 50 | } 51 | } 52 | } 53 | // 展示下一个弹窗 54 | if ([self shareInstance].arrayWaitViews.count > 0 && [self shareInstance].currentView == nil) { 55 | for (int i = 0; i < [self shareInstance].arrayWaitViews.count; i++) { 56 | LYWindowScreenModel *model = [self shareInstance].arrayWaitViews[i]; 57 | if (model.view) { 58 | UIWindow *window = [UIApplication sharedApplication].delegate.window; 59 | [window addSubview:view]; 60 | } 61 | } 62 | } 63 | } 64 | ```` 65 | 66 |   但是使用信号量来处理弹窗展示的数量,这种方式只能满足让弹窗一个个出现,没办法删除或者变更未展示的弹框,是不方便对弹窗进行管理的。 67 | 68 |   这时候使用队列是一个比较好的选择,在show的时候把弹窗添加进队列中, dismiss的时候从队列里移除,当上一个弹窗dimiss,从队列里选出下一个要展示的,这样也能做到弹窗始终只会有一个正在展示,而未展示的弹窗则在队列中等待展示。 69 | 70 | 71 | 72 | 73 | 74 | 75 | ```` 76 | + (void)showView:(UIView *)view { 77 | if ([self shareInstance].currentView == nil) { 78 | // 当前无弹窗展示直接展示 79 | UIWindow *window = [UIApplication sharedApplication].delegate.window; 80 | [window addSubview:view]; 81 | } else { 82 | // 当前有弹窗则加入队列中 83 | LYWindowScreenModel *model = [LYWindowScreenModel new]; 84 | model.view = view; 85 | [[self shareInstance].arrayWaitViews addObject:model]; 86 | } 87 | } 88 | 89 | + (void)dismiss:(UIView *)view { 90 | if ([self shareInstance].currentView == view) { 91 | // 删除当前弹窗 92 | [view removeFromSuperview]; 93 | [[self shareInstance] setCurrentView:nil]; 94 | } else { 95 | // 删除队列中的弹窗 96 | for (int i = 0; i < [self shareInstance].arrayWaitViews.count; i++) { 97 | LYWindowScreenModel *model = [self shareInstance].arrayWaitViews[i]; 98 | if (model.view == view) { 99 | [[self shareInstance].arrayWaitViews removeObject:model]; 100 | } 101 | } 102 | } 103 | // 展示下一个弹窗 104 | if ([self shareInstance].arrayWaitViews.count > 0) { 105 | for (int i = 0; i < [self shareInstance].arrayWaitViews.count; i++) { 106 | LYWindowScreenModel *model = [self shareInstance].arrayWaitViews[i]; 107 | if (model.view) { 108 | UIWindow *window = [UIApplication sharedApplication].delegate.window; 109 | [window addSubview:view]; 110 | } 111 | } 112 | } 113 | } 114 | ```` 115 |
116 | **二、弹窗无法与页面关联**: 弹窗要在页面A显示,因为某些延时,当触发添加弹窗逻辑时,当前页面已经变化成页面B, 弹窗不应该展示。 因为之前已经有了弹窗队列,此时应该把弹窗添加到队列中去等待展示,但是此时队列里的弹窗并没有页面限制,即使放进队列里也会在页面B出现。 所以需要对每个弹窗指定一个展示的页面, 当从队列里推出弹窗进行展示时,判断当前页面是否为可展示的页面,如果不是则继续在队列里等待。 117 | 118 | ```` 119 | + (void)showView:(UIView *)view 120 | page:(Class)page 121 | { 122 | UIViewController *currentController = [UIViewController currentViewController]; 123 | if ([self shareInstance].currentView == nil && 124 | [currentController isMemberOfClass:page]) { 125 | UIWindow *window = [UIApplication sharedApplication].delegate.window; 126 | [window addSubview:view]; 127 | } else { 128 | LYWindowScreenModel *model = [LYWindowScreenModel new]; 129 | model.view = view; 130 | model.pageClass = page; 131 | [[self shareInstance].arrayWaitViews addObject:model]; 132 | } 133 | } 134 | ```` 135 |
136 |   给弹窗指定页面后,这时候需要对当前页面的变化进行监听,当指定页面出现时弹窗应该及时呈现出来。替换UIViewController的viewWillAppear方法,在页面变化时发送通知,告诉manager页面发生变化,检索队列里是否有此页面等待展示的弹窗。 137 |   考虑到重写viewWillAppear方法后,每次页面变化都会发送通知,可能会带来一定的性能问题, 所以manager只有在队列里有等待的弹窗时才注册通知,无等待的弹窗则不需要页面的变化可以移除通知。(如果有更好的监听页面变化的方法望告之) 138 | 139 | 140 | 141 | 142 | ```` 143 | + (void)viewWillAppearNotification:(NSNotification *)notification { 144 | id identifier = notification.object[LYViewControllerClassIdentifier]; 145 | [self viewNeedShowFromQueueWithPage:identifier]; 146 | } 147 | 148 | + (void)viewNeedShowFromQueueWithPage:(UIViewController *)page { 149 | // 当前屏幕有弹框,则不显示 150 | if ([self shareInstance].currentView) { 151 | return; 152 | } 153 | // 队列里无等待显示的视图 154 | if (![self shareInstance].arrayWaitViews.count) { 155 | return; 156 | } 157 | // 推出队列中需要展示的视图进行展示 158 | __block LYWindowScreenModel *model = nil; 159 | [[self shareInstance].arrayWaitViews enumerateObjectsUsingBlock:^(LYWindowScreenModel *obj, NSUInteger idx, BOOL * _Nonnull stop) { 160 | if (obj.pageClass && obj.view) { 161 | if ([page isKindOfClass:obj.pageClass]) { 162 | UIWindow *window = [UIApplication sharedApplication].delegate.window; 163 | [window addSubview:obj.view]; 164 | model = obj; 165 | *stop = YES; 166 | } 167 | } 168 | }]; 169 | if (model) { 170 | [[self shareInstance].arrayWaitViews removeObject:model]; 171 | } 172 | } 173 | ```` 174 | 175 | 176 |
177 | **三、设置弹窗优先级**:因为现在有了弹窗等待队列,弹窗的优先级也就可以很好的解决,在添加进队列时,给弹窗设置一个level值,根据level值排序后从队列里推出展示的弹窗自然是优先级比较高的弹窗。 178 |   因为有时候无法确认其他弹框的level值,level的设定建议以场景来设置level,因为同一场景的多个弹窗大部分情况下无需按优先级展示,只要同等level能一个个展示即可。 179 | 如: 180 | ```` 181 | typedef enum : NSUInteger { 182 | LYLevelHigh = -100, // 优先级最高, 场景如开屏动画 183 | LYLevelMedium = -1, // 优先级高, 场景如启动完成广告弹窗 184 | LYLevelDefault = 0, // 优先级一般,场景如新手引导 185 | LYLevelLow = 100, // 优先级低,场景如常用弹窗 186 | } LYWindowScreenLevel; 187 | ```` 188 | 189 | 开屏动画 > 广告 > 引导 > 业务弹窗,这样可以满足app内绝大多数的弹窗展示顺序,如果有变动可再改动level值。 190 | 191 |
192 | **四、弹窗无法留活**:还是之前抛出的问题,点击活动A进入详情页,此时window弹窗应该消失,当从详情页返回时,活动弹窗应该继续展示。为了避免再一次执行弹窗的展示逻辑,所以需要对当前的弹窗进行缓存,等待页面重新回来时展示。 这种情况只是页面暂时离开,页面并未从页面路径栈里消失,如果页面已经不存在,那么缓存里的弹窗也应该移除。 193 |   新建一个弹框的缓存数组,这里并没有放入之前等待队列里, 是因为等待队列里的弹窗都是仍未展示的,无论页面是否新建,当这个页面是弹窗指定的归属类时都可以展示出来。而缓存的弹窗是与具体的页面关联的,如果页面返回再重新进入,页面已经重新构造,上次缓存的弹窗是不应该再展示的,因为页面重新构造后可能会重新触发弹窗的逻辑,这时候可能就会2个相同的弹窗。 194 | 195 | Swizzle UIViewController的viewWillDisappear方法,当有需要缓存的弹窗时添加监听,当页面离开时移除当前展示的弹框,并且添加进缓存数组里。 196 | ```` 197 | + (void)viewWillDisAppearNotification:(NSNotification *)notification { 198 | NSString *strClass = notification.object[LYViewControllerClassName]; 199 | id identifier = notification.object[LYViewControllerClassIdentifier]; 200 | if ([self shareInstance].currentView && [strClass isEqualToString:NSStringFromClass([self shareInstance].pageClass)]) { 201 | if ([self shareInstance].keepAlive) { 202 | LYWindowScreenModel *model = [LYWindowScreenModel new]; 203 | model.view = [self shareInstance].currentView; 204 | model.pageClass = [self shareInstance].pageClass; 205 | model.level = LYLevelHigh; 206 | model.keepAlive = [self shareInstance].keepAlive; 207 | model.identifier = identifier; 208 | model.addCompleted = [self shareInstance].addCompleted; 209 | // 添加进缓存数组 210 | [[self shareInstance].arrayAliveViews addObject:model]; 211 | [self addWaitShowNotification]; 212 | } 213 | [[self shareInstance].currentView removeFromSuperview]; 214 | [[self shareInstance] setCurrentView:nil]; 215 | [self removeNotification]; 216 | } 217 | } 218 | ```` 219 | 220 | 221 | 可以通过Controller是否还有navigationController或者presentingViewController来判断当前页面是否已经从页面栈里移除 222 | 223 | ```` 224 | // 如果存活弹框的归属页面已移除,则移除该页面的所有弹框 225 | + (void)viewDidDisAppearNotification:(NSNotification *)notification { 226 | if ([self shareInstance].arrayAliveViews.count) { 227 | for (int i = 0; i < [self shareInstance].arrayAliveViews.count; i++) { 228 | LYWindowScreenModel *model = [self shareInstance].arrayAliveViews[i]; 229 | BOOL exist = model.identifier.navigationController || model.identifier.presentingViewController; 230 | if (!exist) { 231 | [[self shareInstance].arrayAliveViews removeObject:model]; 232 | [self removeNotification]; 233 | } 234 | } 235 | } 236 | } 237 | ```` 238 | 239 | 240 | 当页面返回重新出现时,弹窗从缓存队列里删除,并且添加进等待队列里。 241 | ```` 242 | + (void)viewNeedShowFromQueueWithPage:(UIViewController *)page { 243 | // 判断当前页是否有存活的弹框,有则加入队列中。 244 | if ([self shareInstance].arrayAliveViews.count) { 245 | for (int i = 0; i < [self shareInstance].arrayAliveViews.count; i++) { 246 | LYWindowScreenModel *model = [self shareInstance].arrayAliveViews[i]; 247 | if (page == model.identifier) { 248 | [[self shareInstance].arrayWaitViews addObject:model]; 249 | [[self shareInstance].arrayAliveViews removeObject:model]; 250 | } 251 | } 252 | } 253 | ```` 254 | 255 |
256 | **五、自动删除弹窗**:有些app需要登录之后才能展示弹窗,如果用户下线或者被踢,这个用户的弹窗都应该移除。 因为有了队列,当用户下线时移除当前展示的弹窗和队列里等待弹窗就可以统一移除manager管理的所有弹窗。 257 | ```` 258 | + (void)removeAllQueueViews { 259 | [[self shareInstance].arrayAliveViews removeAllObjects]; 260 | [[self shareInstance].arrayWaitViews removeAllObjects]; 261 | [[self shareInstance].currentView removeFromSuperview]; 262 | [[self shareInstance] setCurrentView:nil]; 263 | [self removeNotification]; 264 | } 265 | ```` 266 | 267 |
268 | 当页面离开时,为避免忘记手动删除弹窗,window展示在其他地方,此页面的弹窗也应该自动删除,这里在问题四里面已经得到解决,在页面离开时自动移除弹窗。 269 | 270 | **这里有个坑**:iOS13的present默认是非全屏的展示,present之后页面并不会走viewWillDisappear方法,导致弹窗不会自动移除。 这种情况需要手动去移除弹窗或者走iOS13以前的present方式。 271 | 272 | 273 | 274 | ###补充: 275 | 因为最终弹窗添加到window上或者移除都是在manager里处理的,有些情况可能弹窗的出现和移除需要动画进行修饰,而等待队列里的弹窗就无法知道具体的动画。这种情况,可以添加一个block来告诉外界该弹窗刚刚被添加到window上,你可自行处理自己的动画操作 276 | ```` 277 | [LYWindowScreenView addWindowScreenView:self.label2 page:self.class level:LYLevelLow keepAlive:YES addCompleted:^{ 278 | self.label2.frame = CGRectMake(50, 700, CGRectGetWidth(self.view.frame)-100, CGRectGetHeight(self.view.frame)-270); 279 | [UIView animateWithDuration:0.3 animations:^{ 280 | self.label2.frame = CGRectMake(50, 250, CGRectGetWidth(self.view.frame)-100, CGRectGetHeight(self.view.frame)-270); 281 | }]; 282 | }]; 283 | ```` 284 | 285 |
286 | ###总结 287 | 到此,一开始提出的5个window弹窗问题都已得到解决,实现思路比较简单,主要通过队列和监听页面变化来处理指定页面和顺序的问题。由于keywindow的不确行,这里的弹框都是统一添加到appdelegate.window上。 288 | 289 | 290 | 291 | 大致效果: 292 | ![tu690-6ylp0.gif](https://upload-images.jianshu.io/upload_images/7174973-50acdcffea9209b3.gif?imageMogr2/auto-orient/strip%7CimageView2/2/w/250) 293 | 294 | 295 | 296 | 如果能带来帮助的话,麻烦亲给个星~ 297 | 298 | 299 | -------------------------------------------------------------------------------- /README/1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LYKit/LYWindowScreenView/e59637a3b8f76327dfb3443ac9ed966beb0df67c/README/1.gif -------------------------------------------------------------------------------- /README/2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LYKit/LYWindowScreenView/e59637a3b8f76327dfb3443ac9ed966beb0df67c/README/2.gif -------------------------------------------------------------------------------- /README/3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LYKit/LYWindowScreenView/e59637a3b8f76327dfb3443ac9ed966beb0df67c/README/3.gif -------------------------------------------------------------------------------- /demo/README.md: -------------------------------------------------------------------------------- 1 | > App里总会有很多的弹窗,为了美观,大多数弹窗都需要盖住导航栏;这时弹窗会添加到window上以满足需求。但添加到window上的弹窗却不方便管理,也与页面脱离关系。 2 | 3 | ###window弹窗面临的问题: 4 | 1、**多个弹窗可能会产生重叠**:如app启动的时候有2个弹窗,正巧2个弹窗都触发展示,这时候这2个弹窗就会重叠在一起。 5 | 6 | 2、**弹窗无法与页面关联**:例如网络请求弹窗的数据,弹窗的展示因此而延时,如用户在此期间跳转其他页面,因为是window弹窗,弹窗则会展示在不应该展示的页面上 7 | 8 | 3、**弹窗无法设置优先级**:一个比较简单的例子:多个弹层的新手引导,用户关闭引导页1时,按顺序呈现引导页2、引导页3, 如果中间有其他弹窗出现的逻辑,应该等待引导页结束再展示。 9 | 10 | 4、**弹窗无法留活**:一个简单的例子:一个活动弹窗含有2个活动,点击活动A进入详情页,此时window弹窗应该消失,当从详情页返回时,活动弹窗应该继续展示,才能点击进入活动B查看详情。为了避免重新触发弹窗的逻辑,应该对弹窗进行缓存。 11 | 12 | 5、**弹窗不能自动关闭**:例如用户被迫下线,此时app的所有弹窗都应该自动移除,或者弹窗展示情况下app发生页面跳转,避免弹窗忘记关闭的情况,也应该自动移除现有的弹窗。 13 | 14 | 问题4效果对比-前: 15 | ![t6znx-4jxkk.gif](https://upload-images.jianshu.io/upload_images/7174973-523ac5bfeb22a035.gif?imageMogr2/auto-orient/strip%7CimageView2/2/w/250) 16 | 17 | 18 | 19 | 问题4效果对比-后: 20 | ![tugcl-llxv1.gif](https://upload-images.jianshu.io/upload_images/7174973-65cf15cc74379153.gif?imageMogr2/auto-orient/strip%7CimageView2/2/w/250) 21 | 22 | 23 | ###解决方案: 24 | **一、多个弹框重叠冲突:** 这个问题比较好解决,简单的做法是使用信号量来限制当前弹窗的数量,让弹窗一个一个的出现。创建一个弹窗manager,添加show和dismiss方法, show方法lock, dismiss方法 Release Lock。 25 | ```` 26 | + (void)showView:(UIView *)view { 27 | if ([self shareInstance].currentView == nil) { 28 | // 当前无弹窗展示直接展示 29 | UIWindow *window = [UIApplication sharedApplication].delegate.window; 30 | [window addSubview:view]; 31 | } else { 32 | // 当前有弹窗则加入队列中 33 | LYWindowScreenModel *model = [LYWindowScreenModel new]; 34 | model.view = view; 35 | [[self shareInstance].arrayWaitViews addObject:model]; 36 | } 37 | } 38 | 39 | + (void)dismiss:(UIView *)view { 40 | if ([self shareInstance].currentView == view) { 41 | // 删除当前弹窗 42 | [view removeFromSuperview]; 43 | [[self shareInstance] setCurrentView:nil]; 44 | } else { 45 | // 删除队列中的弹窗 46 | for (int i = 0; i < [self shareInstance].arrayWaitViews.count; i++) { 47 | LYWindowScreenModel *model = [self shareInstance].arrayWaitViews[i]; 48 | if (model.view == view) { 49 | [[self shareInstance].arrayWaitViews removeObject:model]; 50 | } 51 | } 52 | } 53 | // 展示下一个弹窗 54 | if ([self shareInstance].arrayWaitViews.count > 0 && [self shareInstance].currentView == nil) { 55 | for (int i = 0; i < [self shareInstance].arrayWaitViews.count; i++) { 56 | LYWindowScreenModel *model = [self shareInstance].arrayWaitViews[i]; 57 | if (model.view) { 58 | UIWindow *window = [UIApplication sharedApplication].delegate.window; 59 | [window addSubview:view]; 60 | } 61 | } 62 | } 63 | } 64 | ```` 65 | 66 |   但是使用信号量来处理弹窗展示的数量,这种方式只能满足让弹窗一个个出现,没办法删除或者变更未展示的弹框,是不方便对弹窗进行管理的。 67 | 68 |   这时候使用队列是一个比较好的选择,在show的时候把弹窗添加进队列中, dismiss的时候从队列里移除,当上一个弹窗dimiss,从队列里选出下一个要展示的,这样也能做到弹窗始终只会有一个正在展示,而未展示的弹窗则在队列中等待展示。 69 | 70 | 71 | 72 | 73 | 74 | 75 | ```` 76 | + (void)showView:(UIView *)view { 77 | if ([self shareInstance].currentView == nil) { 78 | // 当前无弹窗展示直接展示 79 | UIWindow *window = [UIApplication sharedApplication].delegate.window; 80 | [window addSubview:view]; 81 | } else { 82 | // 当前有弹窗则加入队列中 83 | LYWindowScreenModel *model = [LYWindowScreenModel new]; 84 | model.view = view; 85 | [[self shareInstance].arrayWaitViews addObject:model]; 86 | } 87 | } 88 | 89 | + (void)dismiss:(UIView *)view { 90 | if ([self shareInstance].currentView == view) { 91 | // 删除当前弹窗 92 | [view removeFromSuperview]; 93 | [[self shareInstance] setCurrentView:nil]; 94 | } else { 95 | // 删除队列中的弹窗 96 | for (int i = 0; i < [self shareInstance].arrayWaitViews.count; i++) { 97 | LYWindowScreenModel *model = [self shareInstance].arrayWaitViews[i]; 98 | if (model.view == view) { 99 | [[self shareInstance].arrayWaitViews removeObject:model]; 100 | } 101 | } 102 | } 103 | // 展示下一个弹窗 104 | if ([self shareInstance].arrayWaitViews.count > 0) { 105 | for (int i = 0; i < [self shareInstance].arrayWaitViews.count; i++) { 106 | LYWindowScreenModel *model = [self shareInstance].arrayWaitViews[i]; 107 | if (model.view) { 108 | UIWindow *window = [UIApplication sharedApplication].delegate.window; 109 | [window addSubview:view]; 110 | } 111 | } 112 | } 113 | } 114 | ```` 115 |
116 | **二、弹窗无法与页面关联**: 弹窗要在页面A显示,因为某些延时,当触发添加弹窗逻辑时,当前页面已经变化成页面B, 弹窗不应该展示。 因为之前已经有了弹窗队列,此时应该把弹窗添加到队列中去等待展示,但是此时队列里的弹窗并没有页面限制,即使放进队列里也会在页面B出现。 所以需要对每个弹窗指定一个展示的页面, 当从队列里推出弹窗进行展示时,判断当前页面是否为可展示的页面,如果不是则继续在队列里等待。 117 | 118 | ```` 119 | + (void)showView:(UIView *)view 120 | page:(Class)page 121 | { 122 | UIViewController *currentController = [UIViewController currentViewController]; 123 | if ([self shareInstance].currentView == nil && 124 | [currentController isMemberOfClass:page]) { 125 | UIWindow *window = [UIApplication sharedApplication].delegate.window; 126 | [window addSubview:view]; 127 | } else { 128 | LYWindowScreenModel *model = [LYWindowScreenModel new]; 129 | model.view = view; 130 | model.pageClass = page; 131 | [[self shareInstance].arrayWaitViews addObject:model]; 132 | } 133 | } 134 | ```` 135 |
136 |   给弹窗指定页面后,这时候需要对当前页面的变化进行监听,当指定页面出现时弹窗应该及时呈现出来。替换UIViewController的viewWillAppear方法,在页面变化时发送通知,告诉manager页面发生变化,检索队列里是否有此页面等待展示的弹窗。 137 |   考虑到重写viewWillAppear方法后,每次页面变化都会发送通知,可能会带来一定的性能问题, 所以manager只有在队列里有等待的弹窗时才注册通知,无等待的弹窗则不需要页面的变化可以移除通知。(如果有更好的监听页面变化的方法望告之) 138 | 139 | 140 | 141 | 142 | ```` 143 | + (void)viewWillAppearNotification:(NSNotification *)notification { 144 | id identifier = notification.object[LYViewControllerClassIdentifier]; 145 | [self viewNeedShowFromQueueWithPage:identifier]; 146 | } 147 | 148 | + (void)viewNeedShowFromQueueWithPage:(UIViewController *)page { 149 | // 当前屏幕有弹框,则不显示 150 | if ([self shareInstance].currentView) { 151 | return; 152 | } 153 | // 队列里无等待显示的视图 154 | if (![self shareInstance].arrayWaitViews.count) { 155 | return; 156 | } 157 | // 推出队列中需要展示的视图进行展示 158 | __block LYWindowScreenModel *model = nil; 159 | [[self shareInstance].arrayWaitViews enumerateObjectsUsingBlock:^(LYWindowScreenModel *obj, NSUInteger idx, BOOL * _Nonnull stop) { 160 | if (obj.pageClass && obj.view) { 161 | if ([page isKindOfClass:obj.pageClass]) { 162 | UIWindow *window = [UIApplication sharedApplication].delegate.window; 163 | [window addSubview:obj.view]; 164 | model = obj; 165 | *stop = YES; 166 | } 167 | } 168 | }]; 169 | if (model) { 170 | [[self shareInstance].arrayWaitViews removeObject:model]; 171 | } 172 | } 173 | ```` 174 | 175 | 176 |
177 | **三、设置弹窗优先级**:因为现在有了弹窗等待队列,弹窗的优先级也就可以很好的解决,在添加进队列时,给弹窗设置一个level值,根据level值排序后从队列里推出展示的弹窗自然是优先级比较高的弹窗。 178 |   因为有时候无法确认其他弹框的level值,level的设定建议以场景来设置level,因为同一场景的多个弹窗大部分情况下无需按优先级展示,只要同等level能一个个展示即可。 179 | 如: 180 | ```` 181 | typedef enum : NSUInteger { 182 | LYLevelHigh = -100, // 优先级最高, 场景如开屏动画 183 | LYLevelMedium = -1, // 优先级高, 场景如启动完成广告弹窗 184 | LYLevelDefault = 0, // 优先级一般,场景如新手引导 185 | LYLevelLow = 100, // 优先级低,场景如常用弹窗 186 | } LYWindowScreenLevel; 187 | ```` 188 | 189 | 开屏动画 > 广告 > 引导 > 业务弹窗,这样可以满足app内绝大多数的弹窗展示顺序,如果有变动可再改动level值。 190 | 191 |
192 | **四、弹窗无法留活**:还是之前抛出的问题,点击活动A进入详情页,此时window弹窗应该消失,当从详情页返回时,活动弹窗应该继续展示。为了避免再一次执行弹窗的展示逻辑,所以需要对当前的弹窗进行缓存,等待页面重新回来时展示。 这种情况只是页面暂时离开,页面并未从页面路径栈里消失,如果页面已经不存在,那么缓存里的弹窗也应该移除。 193 |   新建一个弹框的缓存数组,这里并没有放入之前等待队列里, 是因为等待队列里的弹窗都是仍未展示的,无论页面是否新建,当这个页面是弹窗指定的归属类时都可以展示出来。而缓存的弹窗是与具体的页面关联的,如果页面返回再重新进入,页面已经重新构造,上次缓存的弹窗是不应该再展示的,因为页面重新构造后可能会重新触发弹窗的逻辑,这时候可能就会2个相同的弹窗。 194 | 195 | Swizzle UIViewController的viewWillDisappear方法,当有需要缓存的弹窗时添加监听,当页面离开时移除当前展示的弹框,并且添加进缓存数组里。 196 | ```` 197 | + (void)viewWillDisAppearNotification:(NSNotification *)notification { 198 | NSString *strClass = notification.object[LYViewControllerClassName]; 199 | id identifier = notification.object[LYViewControllerClassIdentifier]; 200 | if ([self shareInstance].currentView && [strClass isEqualToString:NSStringFromClass([self shareInstance].pageClass)]) { 201 | if ([self shareInstance].keepAlive) { 202 | LYWindowScreenModel *model = [LYWindowScreenModel new]; 203 | model.view = [self shareInstance].currentView; 204 | model.pageClass = [self shareInstance].pageClass; 205 | model.level = LYLevelHigh; 206 | model.keepAlive = [self shareInstance].keepAlive; 207 | model.identifier = identifier; 208 | model.addCompleted = [self shareInstance].addCompleted; 209 | // 添加进缓存数组 210 | [[self shareInstance].arrayAliveViews addObject:model]; 211 | [self addWaitShowNotification]; 212 | } 213 | [[self shareInstance].currentView removeFromSuperview]; 214 | [[self shareInstance] setCurrentView:nil]; 215 | [self removeNotification]; 216 | } 217 | } 218 | ```` 219 | 220 | 221 | 可以通过Controller是否还有navigationController或者presentingViewController来判断当前页面是否已经从页面栈里移除 222 | 223 | ```` 224 | // 如果存活弹框的归属页面已移除,则移除该页面的所有弹框 225 | + (void)viewDidDisAppearNotification:(NSNotification *)notification { 226 | if ([self shareInstance].arrayAliveViews.count) { 227 | for (int i = 0; i < [self shareInstance].arrayAliveViews.count; i++) { 228 | LYWindowScreenModel *model = [self shareInstance].arrayAliveViews[i]; 229 | BOOL exist = model.identifier.navigationController || model.identifier.presentingViewController; 230 | if (!exist) { 231 | [[self shareInstance].arrayAliveViews removeObject:model]; 232 | [self removeNotification]; 233 | } 234 | } 235 | } 236 | } 237 | ```` 238 | 239 | 240 | 当页面返回重新出现时,弹窗从缓存队列里删除,并且添加进等待队列里。 241 | ```` 242 | + (void)viewNeedShowFromQueueWithPage:(UIViewController *)page { 243 | // 判断当前页是否有存活的弹框,有则加入队列中。 244 | if ([self shareInstance].arrayAliveViews.count) { 245 | for (int i = 0; i < [self shareInstance].arrayAliveViews.count; i++) { 246 | LYWindowScreenModel *model = [self shareInstance].arrayAliveViews[i]; 247 | if (page == model.identifier) { 248 | [[self shareInstance].arrayWaitViews addObject:model]; 249 | [[self shareInstance].arrayAliveViews removeObject:model]; 250 | } 251 | } 252 | } 253 | ```` 254 | 255 |
256 | **五、自动删除弹窗**:有些app需要登录之后才能展示弹窗,如果用户下线或者被踢,这个用户的弹窗都应该移除。 因为有了队列,当用户下线时移除当前展示的弹窗和队列里等待弹窗就可以统一移除manager管理的所有弹窗。 257 | ```` 258 | + (void)removeAllQueueViews { 259 | [[self shareInstance].arrayAliveViews removeAllObjects]; 260 | [[self shareInstance].arrayWaitViews removeAllObjects]; 261 | [[self shareInstance].currentView removeFromSuperview]; 262 | [[self shareInstance] setCurrentView:nil]; 263 | [self removeNotification]; 264 | } 265 | ```` 266 | 267 |
268 | 当页面离开时,为避免忘记手动删除弹窗,window展示在其他地方,此页面的弹窗也应该自动删除,这里在问题四里面已经得到解决,在页面离开时自动移除弹窗。 269 | 270 | **这里有个坑**:iOS13的present默认是非全屏的展示,present之后页面并不会走viewWillDisappear方法,导致弹窗不会自动移除。 这种情况需要手动去移除弹窗或者走iOS13以前的present方式。 271 | 272 | 273 | 274 | ###补充: 275 | 因为最终弹窗添加到window上或者移除都是在manager里处理的,有些情况可能弹窗的出现和移除需要动画进行修饰,而等待队列里的弹窗就无法知道具体的动画。这种情况,可以添加一个block来告诉外界该弹窗刚刚被添加到window上,你可自行处理自己的动画操作 276 | ```` 277 | [LYWindowScreenView addWindowScreenView:self.label2 page:self.class level:LYLevelLow keepAlive:YES addCompleted:^{ 278 | self.label2.frame = CGRectMake(50, 700, CGRectGetWidth(self.view.frame)-100, CGRectGetHeight(self.view.frame)-270); 279 | [UIView animateWithDuration:0.3 animations:^{ 280 | self.label2.frame = CGRectMake(50, 250, CGRectGetWidth(self.view.frame)-100, CGRectGetHeight(self.view.frame)-270); 281 | }]; 282 | }]; 283 | ```` 284 | 285 |
286 | ###总结 287 | 到此,一开始提出的5个window弹窗问题都已得到解决,实现思路比较简单,主要通过队列和监听页面变化来处理指定页面和顺序的问题。由于keywindow的不确行,这里的弹框都是统一添加到appdelegate.window上。 288 | 289 | 290 | 291 | 大致效果: 292 | ![tu690-6ylp0.gif](https://upload-images.jianshu.io/upload_images/7174973-50acdcffea9209b3.gif?imageMogr2/auto-orient/strip%7CimageView2/2/w/250) 293 | 294 | 295 | 296 | 如果能带来帮助的话,麻烦亲给个星~ 297 | 298 | 299 | -------------------------------------------------------------------------------- /demo/README/1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LYKit/LYWindowScreenView/e59637a3b8f76327dfb3443ac9ed966beb0df67c/demo/README/1.gif -------------------------------------------------------------------------------- /demo/README/2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LYKit/LYWindowScreenView/e59637a3b8f76327dfb3443ac9ed966beb0df67c/demo/README/2.gif -------------------------------------------------------------------------------- /demo/README/3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LYKit/LYWindowScreenView/e59637a3b8f76327dfb3443ac9ed966beb0df67c/demo/README/3.gif -------------------------------------------------------------------------------- /demo/demo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | C1056601256F9DA600E49F77 /* NSObject+LYSwizzle.m in Sources */ = {isa = PBXBuildFile; fileRef = C10565FC256F9DA600E49F77 /* NSObject+LYSwizzle.m */; }; 11 | C1056602256F9DA600E49F77 /* UIViewController+LYPageWillAppear.m in Sources */ = {isa = PBXBuildFile; fileRef = C10565FE256F9DA600E49F77 /* UIViewController+LYPageWillAppear.m */; }; 12 | C1056603256F9DA600E49F77 /* LYWindowScreenView.m in Sources */ = {isa = PBXBuildFile; fileRef = C1056600256F9DA600E49F77 /* LYWindowScreenView.m */; }; 13 | C109FB28239695E700367E5A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = C109FB27239695E700367E5A /* AppDelegate.m */; }; 14 | C109FB2E239695E700367E5A /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C109FB2D239695E700367E5A /* ViewController.m */; }; 15 | C109FB31239695E700367E5A /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C109FB2F239695E700367E5A /* Main.storyboard */; }; 16 | C109FB33239695E900367E5A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C109FB32239695E900367E5A /* Assets.xcassets */; }; 17 | C109FB36239695E900367E5A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C109FB34239695E900367E5A /* LaunchScreen.storyboard */; }; 18 | C109FB39239695E900367E5A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = C109FB38239695E900367E5A /* main.m */; }; 19 | C109FB43239695EA00367E5A /* demoTests.m in Sources */ = {isa = PBXBuildFile; fileRef = C109FB42239695EA00367E5A /* demoTests.m */; }; 20 | C109FB4E239695EA00367E5A /* demoUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = C109FB4D239695EA00367E5A /* demoUITests.m */; }; 21 | C116ABA323B9FBD2000B5136 /* ViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = C116ABA223B9FBD2000B5136 /* ViewController.xib */; }; 22 | C15B619523BB3FA20001C35C /* ViewControllerFour.m in Sources */ = {isa = PBXBuildFile; fileRef = C15B619323BB3FA20001C35C /* ViewControllerFour.m */; }; 23 | C15B619623BB3FA20001C35C /* ViewControllerFour.xib in Resources */ = {isa = PBXBuildFile; fileRef = C15B619423BB3FA20001C35C /* ViewControllerFour.xib */; }; 24 | C187B68D23B5B22000DACBED /* ViewControllerThree.m in Sources */ = {isa = PBXBuildFile; fileRef = C187B68B23B5B22000DACBED /* ViewControllerThree.m */; }; 25 | C187B68E23B5B22000DACBED /* ViewControllerThree.xib in Resources */ = {isa = PBXBuildFile; fileRef = C187B68C23B5B22000DACBED /* ViewControllerThree.xib */; }; 26 | C1E89CCF23B1EAF400BD6C70 /* ViewControllerTwo.m in Sources */ = {isa = PBXBuildFile; fileRef = C1E89CCD23B1EAF400BD6C70 /* ViewControllerTwo.m */; }; 27 | C1E89CD023B1EAF400BD6C70 /* ViewControllerTwo.xib in Resources */ = {isa = PBXBuildFile; fileRef = C1E89CCE23B1EAF400BD6C70 /* ViewControllerTwo.xib */; }; 28 | /* End PBXBuildFile section */ 29 | 30 | /* Begin PBXContainerItemProxy section */ 31 | C109FB3F239695EA00367E5A /* PBXContainerItemProxy */ = { 32 | isa = PBXContainerItemProxy; 33 | containerPortal = C109FB1B239695E700367E5A /* Project object */; 34 | proxyType = 1; 35 | remoteGlobalIDString = C109FB22239695E700367E5A; 36 | remoteInfo = demo; 37 | }; 38 | C109FB4A239695EA00367E5A /* PBXContainerItemProxy */ = { 39 | isa = PBXContainerItemProxy; 40 | containerPortal = C109FB1B239695E700367E5A /* Project object */; 41 | proxyType = 1; 42 | remoteGlobalIDString = C109FB22239695E700367E5A; 43 | remoteInfo = demo; 44 | }; 45 | /* End PBXContainerItemProxy section */ 46 | 47 | /* Begin PBXFileReference section */ 48 | C10565FB256F9DA600E49F77 /* UIViewController+LYPageWillAppear.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIViewController+LYPageWillAppear.h"; sourceTree = ""; }; 49 | C10565FC256F9DA600E49F77 /* NSObject+LYSwizzle.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+LYSwizzle.m"; sourceTree = ""; }; 50 | C10565FD256F9DA600E49F77 /* LYWindowScreenView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LYWindowScreenView.h; sourceTree = ""; }; 51 | C10565FE256F9DA600E49F77 /* UIViewController+LYPageWillAppear.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIViewController+LYPageWillAppear.m"; sourceTree = ""; }; 52 | C10565FF256F9DA600E49F77 /* NSObject+LYSwizzle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+LYSwizzle.h"; sourceTree = ""; }; 53 | C1056600256F9DA600E49F77 /* LYWindowScreenView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LYWindowScreenView.m; sourceTree = ""; }; 54 | C109FB23239695E700367E5A /* demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = demo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 55 | C109FB26239695E700367E5A /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 56 | C109FB27239695E700367E5A /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 57 | C109FB2C239695E700367E5A /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 58 | C109FB2D239695E700367E5A /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 59 | C109FB30239695E700367E5A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 60 | C109FB32239695E900367E5A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 61 | C109FB35239695E900367E5A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 62 | C109FB37239695E900367E5A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 63 | C109FB38239695E900367E5A /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 64 | C109FB3E239695EA00367E5A /* demoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = demoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 65 | C109FB42239695EA00367E5A /* demoTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = demoTests.m; sourceTree = ""; }; 66 | C109FB44239695EA00367E5A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 67 | C109FB49239695EA00367E5A /* demoUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = demoUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 68 | C109FB4D239695EA00367E5A /* demoUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = demoUITests.m; sourceTree = ""; }; 69 | C109FB4F239695EA00367E5A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 70 | C116ABA223B9FBD2000B5136 /* ViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ViewController.xib; sourceTree = ""; }; 71 | C15B619223BB3FA20001C35C /* ViewControllerFour.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewControllerFour.h; sourceTree = ""; }; 72 | C15B619323BB3FA20001C35C /* ViewControllerFour.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewControllerFour.m; sourceTree = ""; }; 73 | C15B619423BB3FA20001C35C /* ViewControllerFour.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ViewControllerFour.xib; sourceTree = ""; }; 74 | C187B68A23B5B22000DACBED /* ViewControllerThree.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewControllerThree.h; sourceTree = ""; }; 75 | C187B68B23B5B22000DACBED /* ViewControllerThree.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewControllerThree.m; sourceTree = ""; }; 76 | C187B68C23B5B22000DACBED /* ViewControllerThree.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ViewControllerThree.xib; sourceTree = ""; }; 77 | C1E89CCC23B1EAF300BD6C70 /* ViewControllerTwo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewControllerTwo.h; sourceTree = ""; }; 78 | C1E89CCD23B1EAF400BD6C70 /* ViewControllerTwo.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewControllerTwo.m; sourceTree = ""; }; 79 | C1E89CCE23B1EAF400BD6C70 /* ViewControllerTwo.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ViewControllerTwo.xib; sourceTree = ""; }; 80 | /* End PBXFileReference section */ 81 | 82 | /* Begin PBXFrameworksBuildPhase section */ 83 | C109FB20239695E700367E5A /* Frameworks */ = { 84 | isa = PBXFrameworksBuildPhase; 85 | buildActionMask = 2147483647; 86 | files = ( 87 | ); 88 | runOnlyForDeploymentPostprocessing = 0; 89 | }; 90 | C109FB3B239695EA00367E5A /* Frameworks */ = { 91 | isa = PBXFrameworksBuildPhase; 92 | buildActionMask = 2147483647; 93 | files = ( 94 | ); 95 | runOnlyForDeploymentPostprocessing = 0; 96 | }; 97 | C109FB46239695EA00367E5A /* Frameworks */ = { 98 | isa = PBXFrameworksBuildPhase; 99 | buildActionMask = 2147483647; 100 | files = ( 101 | ); 102 | runOnlyForDeploymentPostprocessing = 0; 103 | }; 104 | /* End PBXFrameworksBuildPhase section */ 105 | 106 | /* Begin PBXGroup section */ 107 | C10565FA256F9DA600E49F77 /* LYWindowScreenView */ = { 108 | isa = PBXGroup; 109 | children = ( 110 | C10565FF256F9DA600E49F77 /* NSObject+LYSwizzle.h */, 111 | C10565FC256F9DA600E49F77 /* NSObject+LYSwizzle.m */, 112 | C10565FB256F9DA600E49F77 /* UIViewController+LYPageWillAppear.h */, 113 | C10565FE256F9DA600E49F77 /* UIViewController+LYPageWillAppear.m */, 114 | C10565FD256F9DA600E49F77 /* LYWindowScreenView.h */, 115 | C1056600256F9DA600E49F77 /* LYWindowScreenView.m */, 116 | ); 117 | path = LYWindowScreenView; 118 | sourceTree = ""; 119 | }; 120 | C109FB1A239695E700367E5A = { 121 | isa = PBXGroup; 122 | children = ( 123 | C109FB25239695E700367E5A /* demo */, 124 | C109FB41239695EA00367E5A /* demoTests */, 125 | C109FB4C239695EA00367E5A /* demoUITests */, 126 | C109FB24239695E700367E5A /* Products */, 127 | ); 128 | sourceTree = ""; 129 | }; 130 | C109FB24239695E700367E5A /* Products */ = { 131 | isa = PBXGroup; 132 | children = ( 133 | C109FB23239695E700367E5A /* demo.app */, 134 | C109FB3E239695EA00367E5A /* demoTests.xctest */, 135 | C109FB49239695EA00367E5A /* demoUITests.xctest */, 136 | ); 137 | name = Products; 138 | sourceTree = ""; 139 | }; 140 | C109FB25239695E700367E5A /* demo */ = { 141 | isa = PBXGroup; 142 | children = ( 143 | C10565FA256F9DA600E49F77 /* LYWindowScreenView */, 144 | C109FB26239695E700367E5A /* AppDelegate.h */, 145 | C109FB27239695E700367E5A /* AppDelegate.m */, 146 | C187B68A23B5B22000DACBED /* ViewControllerThree.h */, 147 | C187B68B23B5B22000DACBED /* ViewControllerThree.m */, 148 | C187B68C23B5B22000DACBED /* ViewControllerThree.xib */, 149 | C1E89CCC23B1EAF300BD6C70 /* ViewControllerTwo.h */, 150 | C1E89CCD23B1EAF400BD6C70 /* ViewControllerTwo.m */, 151 | C1E89CCE23B1EAF400BD6C70 /* ViewControllerTwo.xib */, 152 | C109FB2C239695E700367E5A /* ViewController.h */, 153 | C109FB2D239695E700367E5A /* ViewController.m */, 154 | C116ABA223B9FBD2000B5136 /* ViewController.xib */, 155 | C15B619223BB3FA20001C35C /* ViewControllerFour.h */, 156 | C15B619323BB3FA20001C35C /* ViewControllerFour.m */, 157 | C15B619423BB3FA20001C35C /* ViewControllerFour.xib */, 158 | C109FB2F239695E700367E5A /* Main.storyboard */, 159 | C109FB32239695E900367E5A /* Assets.xcassets */, 160 | C109FB34239695E900367E5A /* LaunchScreen.storyboard */, 161 | C109FB37239695E900367E5A /* Info.plist */, 162 | C109FB38239695E900367E5A /* main.m */, 163 | ); 164 | path = demo; 165 | sourceTree = ""; 166 | }; 167 | C109FB41239695EA00367E5A /* demoTests */ = { 168 | isa = PBXGroup; 169 | children = ( 170 | C109FB42239695EA00367E5A /* demoTests.m */, 171 | C109FB44239695EA00367E5A /* Info.plist */, 172 | ); 173 | path = demoTests; 174 | sourceTree = ""; 175 | }; 176 | C109FB4C239695EA00367E5A /* demoUITests */ = { 177 | isa = PBXGroup; 178 | children = ( 179 | C109FB4D239695EA00367E5A /* demoUITests.m */, 180 | C109FB4F239695EA00367E5A /* Info.plist */, 181 | ); 182 | path = demoUITests; 183 | sourceTree = ""; 184 | }; 185 | /* End PBXGroup section */ 186 | 187 | /* Begin PBXNativeTarget section */ 188 | C109FB22239695E700367E5A /* demo */ = { 189 | isa = PBXNativeTarget; 190 | buildConfigurationList = C109FB52239695EA00367E5A /* Build configuration list for PBXNativeTarget "demo" */; 191 | buildPhases = ( 192 | C109FB1F239695E700367E5A /* Sources */, 193 | C109FB20239695E700367E5A /* Frameworks */, 194 | C109FB21239695E700367E5A /* Resources */, 195 | ); 196 | buildRules = ( 197 | ); 198 | dependencies = ( 199 | ); 200 | name = demo; 201 | productName = demo; 202 | productReference = C109FB23239695E700367E5A /* demo.app */; 203 | productType = "com.apple.product-type.application"; 204 | }; 205 | C109FB3D239695EA00367E5A /* demoTests */ = { 206 | isa = PBXNativeTarget; 207 | buildConfigurationList = C109FB55239695EA00367E5A /* Build configuration list for PBXNativeTarget "demoTests" */; 208 | buildPhases = ( 209 | C109FB3A239695EA00367E5A /* Sources */, 210 | C109FB3B239695EA00367E5A /* Frameworks */, 211 | C109FB3C239695EA00367E5A /* Resources */, 212 | ); 213 | buildRules = ( 214 | ); 215 | dependencies = ( 216 | C109FB40239695EA00367E5A /* PBXTargetDependency */, 217 | ); 218 | name = demoTests; 219 | productName = demoTests; 220 | productReference = C109FB3E239695EA00367E5A /* demoTests.xctest */; 221 | productType = "com.apple.product-type.bundle.unit-test"; 222 | }; 223 | C109FB48239695EA00367E5A /* demoUITests */ = { 224 | isa = PBXNativeTarget; 225 | buildConfigurationList = C109FB58239695EA00367E5A /* Build configuration list for PBXNativeTarget "demoUITests" */; 226 | buildPhases = ( 227 | C109FB45239695EA00367E5A /* Sources */, 228 | C109FB46239695EA00367E5A /* Frameworks */, 229 | C109FB47239695EA00367E5A /* Resources */, 230 | ); 231 | buildRules = ( 232 | ); 233 | dependencies = ( 234 | C109FB4B239695EA00367E5A /* PBXTargetDependency */, 235 | ); 236 | name = demoUITests; 237 | productName = demoUITests; 238 | productReference = C109FB49239695EA00367E5A /* demoUITests.xctest */; 239 | productType = "com.apple.product-type.bundle.ui-testing"; 240 | }; 241 | /* End PBXNativeTarget section */ 242 | 243 | /* Begin PBXProject section */ 244 | C109FB1B239695E700367E5A /* Project object */ = { 245 | isa = PBXProject; 246 | attributes = { 247 | LastUpgradeCheck = 1100; 248 | ORGANIZATIONNAME = "学习"; 249 | TargetAttributes = { 250 | C109FB22239695E700367E5A = { 251 | CreatedOnToolsVersion = 11.0; 252 | }; 253 | C109FB3D239695EA00367E5A = { 254 | CreatedOnToolsVersion = 11.0; 255 | TestTargetID = C109FB22239695E700367E5A; 256 | }; 257 | C109FB48239695EA00367E5A = { 258 | CreatedOnToolsVersion = 11.0; 259 | TestTargetID = C109FB22239695E700367E5A; 260 | }; 261 | }; 262 | }; 263 | buildConfigurationList = C109FB1E239695E700367E5A /* Build configuration list for PBXProject "demo" */; 264 | compatibilityVersion = "Xcode 9.3"; 265 | developmentRegion = en; 266 | hasScannedForEncodings = 0; 267 | knownRegions = ( 268 | en, 269 | Base, 270 | ); 271 | mainGroup = C109FB1A239695E700367E5A; 272 | productRefGroup = C109FB24239695E700367E5A /* Products */; 273 | projectDirPath = ""; 274 | projectRoot = ""; 275 | targets = ( 276 | C109FB22239695E700367E5A /* demo */, 277 | C109FB3D239695EA00367E5A /* demoTests */, 278 | C109FB48239695EA00367E5A /* demoUITests */, 279 | ); 280 | }; 281 | /* End PBXProject section */ 282 | 283 | /* Begin PBXResourcesBuildPhase section */ 284 | C109FB21239695E700367E5A /* Resources */ = { 285 | isa = PBXResourcesBuildPhase; 286 | buildActionMask = 2147483647; 287 | files = ( 288 | C109FB36239695E900367E5A /* LaunchScreen.storyboard in Resources */, 289 | C109FB33239695E900367E5A /* Assets.xcassets in Resources */, 290 | C1E89CD023B1EAF400BD6C70 /* ViewControllerTwo.xib in Resources */, 291 | C187B68E23B5B22000DACBED /* ViewControllerThree.xib in Resources */, 292 | C116ABA323B9FBD2000B5136 /* ViewController.xib in Resources */, 293 | C15B619623BB3FA20001C35C /* ViewControllerFour.xib in Resources */, 294 | C109FB31239695E700367E5A /* Main.storyboard in Resources */, 295 | ); 296 | runOnlyForDeploymentPostprocessing = 0; 297 | }; 298 | C109FB3C239695EA00367E5A /* Resources */ = { 299 | isa = PBXResourcesBuildPhase; 300 | buildActionMask = 2147483647; 301 | files = ( 302 | ); 303 | runOnlyForDeploymentPostprocessing = 0; 304 | }; 305 | C109FB47239695EA00367E5A /* Resources */ = { 306 | isa = PBXResourcesBuildPhase; 307 | buildActionMask = 2147483647; 308 | files = ( 309 | ); 310 | runOnlyForDeploymentPostprocessing = 0; 311 | }; 312 | /* End PBXResourcesBuildPhase section */ 313 | 314 | /* Begin PBXSourcesBuildPhase section */ 315 | C109FB1F239695E700367E5A /* Sources */ = { 316 | isa = PBXSourcesBuildPhase; 317 | buildActionMask = 2147483647; 318 | files = ( 319 | C15B619523BB3FA20001C35C /* ViewControllerFour.m in Sources */, 320 | C187B68D23B5B22000DACBED /* ViewControllerThree.m in Sources */, 321 | C109FB2E239695E700367E5A /* ViewController.m in Sources */, 322 | C1056601256F9DA600E49F77 /* NSObject+LYSwizzle.m in Sources */, 323 | C109FB28239695E700367E5A /* AppDelegate.m in Sources */, 324 | C109FB39239695E900367E5A /* main.m in Sources */, 325 | C1056603256F9DA600E49F77 /* LYWindowScreenView.m in Sources */, 326 | C1E89CCF23B1EAF400BD6C70 /* ViewControllerTwo.m in Sources */, 327 | C1056602256F9DA600E49F77 /* UIViewController+LYPageWillAppear.m in Sources */, 328 | ); 329 | runOnlyForDeploymentPostprocessing = 0; 330 | }; 331 | C109FB3A239695EA00367E5A /* Sources */ = { 332 | isa = PBXSourcesBuildPhase; 333 | buildActionMask = 2147483647; 334 | files = ( 335 | C109FB43239695EA00367E5A /* demoTests.m in Sources */, 336 | ); 337 | runOnlyForDeploymentPostprocessing = 0; 338 | }; 339 | C109FB45239695EA00367E5A /* Sources */ = { 340 | isa = PBXSourcesBuildPhase; 341 | buildActionMask = 2147483647; 342 | files = ( 343 | C109FB4E239695EA00367E5A /* demoUITests.m in Sources */, 344 | ); 345 | runOnlyForDeploymentPostprocessing = 0; 346 | }; 347 | /* End PBXSourcesBuildPhase section */ 348 | 349 | /* Begin PBXTargetDependency section */ 350 | C109FB40239695EA00367E5A /* PBXTargetDependency */ = { 351 | isa = PBXTargetDependency; 352 | target = C109FB22239695E700367E5A /* demo */; 353 | targetProxy = C109FB3F239695EA00367E5A /* PBXContainerItemProxy */; 354 | }; 355 | C109FB4B239695EA00367E5A /* PBXTargetDependency */ = { 356 | isa = PBXTargetDependency; 357 | target = C109FB22239695E700367E5A /* demo */; 358 | targetProxy = C109FB4A239695EA00367E5A /* PBXContainerItemProxy */; 359 | }; 360 | /* End PBXTargetDependency section */ 361 | 362 | /* Begin PBXVariantGroup section */ 363 | C109FB2F239695E700367E5A /* Main.storyboard */ = { 364 | isa = PBXVariantGroup; 365 | children = ( 366 | C109FB30239695E700367E5A /* Base */, 367 | ); 368 | name = Main.storyboard; 369 | sourceTree = ""; 370 | }; 371 | C109FB34239695E900367E5A /* LaunchScreen.storyboard */ = { 372 | isa = PBXVariantGroup; 373 | children = ( 374 | C109FB35239695E900367E5A /* Base */, 375 | ); 376 | name = LaunchScreen.storyboard; 377 | sourceTree = ""; 378 | }; 379 | /* End PBXVariantGroup section */ 380 | 381 | /* Begin XCBuildConfiguration section */ 382 | C109FB50239695EA00367E5A /* Debug */ = { 383 | isa = XCBuildConfiguration; 384 | buildSettings = { 385 | ALWAYS_SEARCH_USER_PATHS = NO; 386 | CLANG_ANALYZER_NONNULL = YES; 387 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 388 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 389 | CLANG_CXX_LIBRARY = "libc++"; 390 | CLANG_ENABLE_MODULES = YES; 391 | CLANG_ENABLE_OBJC_ARC = YES; 392 | CLANG_ENABLE_OBJC_WEAK = YES; 393 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 394 | CLANG_WARN_BOOL_CONVERSION = YES; 395 | CLANG_WARN_COMMA = YES; 396 | CLANG_WARN_CONSTANT_CONVERSION = YES; 397 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 398 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 399 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 400 | CLANG_WARN_EMPTY_BODY = YES; 401 | CLANG_WARN_ENUM_CONVERSION = YES; 402 | CLANG_WARN_INFINITE_RECURSION = YES; 403 | CLANG_WARN_INT_CONVERSION = YES; 404 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 405 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 406 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 407 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 408 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 409 | CLANG_WARN_STRICT_PROTOTYPES = YES; 410 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 411 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 412 | CLANG_WARN_UNREACHABLE_CODE = YES; 413 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 414 | COPY_PHASE_STRIP = NO; 415 | DEBUG_INFORMATION_FORMAT = dwarf; 416 | ENABLE_STRICT_OBJC_MSGSEND = YES; 417 | ENABLE_TESTABILITY = YES; 418 | GCC_C_LANGUAGE_STANDARD = gnu11; 419 | GCC_DYNAMIC_NO_PIC = NO; 420 | GCC_NO_COMMON_BLOCKS = YES; 421 | GCC_OPTIMIZATION_LEVEL = 0; 422 | GCC_PREPROCESSOR_DEFINITIONS = ( 423 | "DEBUG=1", 424 | "$(inherited)", 425 | ); 426 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 427 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 428 | GCC_WARN_UNDECLARED_SELECTOR = YES; 429 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 430 | GCC_WARN_UNUSED_FUNCTION = YES; 431 | GCC_WARN_UNUSED_VARIABLE = YES; 432 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 433 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 434 | MTL_FAST_MATH = YES; 435 | ONLY_ACTIVE_ARCH = YES; 436 | SDKROOT = iphoneos; 437 | }; 438 | name = Debug; 439 | }; 440 | C109FB51239695EA00367E5A /* Release */ = { 441 | isa = XCBuildConfiguration; 442 | buildSettings = { 443 | ALWAYS_SEARCH_USER_PATHS = NO; 444 | CLANG_ANALYZER_NONNULL = YES; 445 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 446 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 447 | CLANG_CXX_LIBRARY = "libc++"; 448 | CLANG_ENABLE_MODULES = YES; 449 | CLANG_ENABLE_OBJC_ARC = YES; 450 | CLANG_ENABLE_OBJC_WEAK = YES; 451 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 452 | CLANG_WARN_BOOL_CONVERSION = YES; 453 | CLANG_WARN_COMMA = YES; 454 | CLANG_WARN_CONSTANT_CONVERSION = YES; 455 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 456 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 457 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 458 | CLANG_WARN_EMPTY_BODY = YES; 459 | CLANG_WARN_ENUM_CONVERSION = YES; 460 | CLANG_WARN_INFINITE_RECURSION = YES; 461 | CLANG_WARN_INT_CONVERSION = YES; 462 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 463 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 464 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 465 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 466 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 467 | CLANG_WARN_STRICT_PROTOTYPES = YES; 468 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 469 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 470 | CLANG_WARN_UNREACHABLE_CODE = YES; 471 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 472 | COPY_PHASE_STRIP = NO; 473 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 474 | ENABLE_NS_ASSERTIONS = NO; 475 | ENABLE_STRICT_OBJC_MSGSEND = YES; 476 | GCC_C_LANGUAGE_STANDARD = gnu11; 477 | GCC_NO_COMMON_BLOCKS = YES; 478 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 479 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 480 | GCC_WARN_UNDECLARED_SELECTOR = YES; 481 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 482 | GCC_WARN_UNUSED_FUNCTION = YES; 483 | GCC_WARN_UNUSED_VARIABLE = YES; 484 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 485 | MTL_ENABLE_DEBUG_INFO = NO; 486 | MTL_FAST_MATH = YES; 487 | SDKROOT = iphoneos; 488 | VALIDATE_PRODUCT = YES; 489 | }; 490 | name = Release; 491 | }; 492 | C109FB53239695EA00367E5A /* Debug */ = { 493 | isa = XCBuildConfiguration; 494 | buildSettings = { 495 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 496 | CODE_SIGN_STYLE = Automatic; 497 | INFOPLIST_FILE = demo/Info.plist; 498 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 499 | LD_RUNPATH_SEARCH_PATHS = ( 500 | "$(inherited)", 501 | "@executable_path/Frameworks", 502 | ); 503 | PRODUCT_BUNDLE_IDENTIFIER = "---.demo"; 504 | PRODUCT_NAME = "$(TARGET_NAME)"; 505 | TARGETED_DEVICE_FAMILY = "1,2"; 506 | }; 507 | name = Debug; 508 | }; 509 | C109FB54239695EA00367E5A /* Release */ = { 510 | isa = XCBuildConfiguration; 511 | buildSettings = { 512 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 513 | CODE_SIGN_STYLE = Automatic; 514 | INFOPLIST_FILE = demo/Info.plist; 515 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 516 | LD_RUNPATH_SEARCH_PATHS = ( 517 | "$(inherited)", 518 | "@executable_path/Frameworks", 519 | ); 520 | PRODUCT_BUNDLE_IDENTIFIER = "---.demo"; 521 | PRODUCT_NAME = "$(TARGET_NAME)"; 522 | TARGETED_DEVICE_FAMILY = "1,2"; 523 | }; 524 | name = Release; 525 | }; 526 | C109FB56239695EA00367E5A /* Debug */ = { 527 | isa = XCBuildConfiguration; 528 | buildSettings = { 529 | BUNDLE_LOADER = "$(TEST_HOST)"; 530 | CODE_SIGN_STYLE = Automatic; 531 | INFOPLIST_FILE = demoTests/Info.plist; 532 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 533 | LD_RUNPATH_SEARCH_PATHS = ( 534 | "$(inherited)", 535 | "@executable_path/Frameworks", 536 | "@loader_path/Frameworks", 537 | ); 538 | PRODUCT_BUNDLE_IDENTIFIER = "---.demoTests"; 539 | PRODUCT_NAME = "$(TARGET_NAME)"; 540 | TARGETED_DEVICE_FAMILY = "1,2"; 541 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/demo.app/demo"; 542 | }; 543 | name = Debug; 544 | }; 545 | C109FB57239695EA00367E5A /* Release */ = { 546 | isa = XCBuildConfiguration; 547 | buildSettings = { 548 | BUNDLE_LOADER = "$(TEST_HOST)"; 549 | CODE_SIGN_STYLE = Automatic; 550 | INFOPLIST_FILE = demoTests/Info.plist; 551 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 552 | LD_RUNPATH_SEARCH_PATHS = ( 553 | "$(inherited)", 554 | "@executable_path/Frameworks", 555 | "@loader_path/Frameworks", 556 | ); 557 | PRODUCT_BUNDLE_IDENTIFIER = "---.demoTests"; 558 | PRODUCT_NAME = "$(TARGET_NAME)"; 559 | TARGETED_DEVICE_FAMILY = "1,2"; 560 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/demo.app/demo"; 561 | }; 562 | name = Release; 563 | }; 564 | C109FB59239695EA00367E5A /* Debug */ = { 565 | isa = XCBuildConfiguration; 566 | buildSettings = { 567 | CODE_SIGN_STYLE = Automatic; 568 | INFOPLIST_FILE = demoUITests/Info.plist; 569 | LD_RUNPATH_SEARCH_PATHS = ( 570 | "$(inherited)", 571 | "@executable_path/Frameworks", 572 | "@loader_path/Frameworks", 573 | ); 574 | PRODUCT_BUNDLE_IDENTIFIER = "---.demoUITests"; 575 | PRODUCT_NAME = "$(TARGET_NAME)"; 576 | TARGETED_DEVICE_FAMILY = "1,2"; 577 | TEST_TARGET_NAME = demo; 578 | }; 579 | name = Debug; 580 | }; 581 | C109FB5A239695EA00367E5A /* Release */ = { 582 | isa = XCBuildConfiguration; 583 | buildSettings = { 584 | CODE_SIGN_STYLE = Automatic; 585 | INFOPLIST_FILE = demoUITests/Info.plist; 586 | LD_RUNPATH_SEARCH_PATHS = ( 587 | "$(inherited)", 588 | "@executable_path/Frameworks", 589 | "@loader_path/Frameworks", 590 | ); 591 | PRODUCT_BUNDLE_IDENTIFIER = "---.demoUITests"; 592 | PRODUCT_NAME = "$(TARGET_NAME)"; 593 | TARGETED_DEVICE_FAMILY = "1,2"; 594 | TEST_TARGET_NAME = demo; 595 | }; 596 | name = Release; 597 | }; 598 | /* End XCBuildConfiguration section */ 599 | 600 | /* Begin XCConfigurationList section */ 601 | C109FB1E239695E700367E5A /* Build configuration list for PBXProject "demo" */ = { 602 | isa = XCConfigurationList; 603 | buildConfigurations = ( 604 | C109FB50239695EA00367E5A /* Debug */, 605 | C109FB51239695EA00367E5A /* Release */, 606 | ); 607 | defaultConfigurationIsVisible = 0; 608 | defaultConfigurationName = Release; 609 | }; 610 | C109FB52239695EA00367E5A /* Build configuration list for PBXNativeTarget "demo" */ = { 611 | isa = XCConfigurationList; 612 | buildConfigurations = ( 613 | C109FB53239695EA00367E5A /* Debug */, 614 | C109FB54239695EA00367E5A /* Release */, 615 | ); 616 | defaultConfigurationIsVisible = 0; 617 | defaultConfigurationName = Release; 618 | }; 619 | C109FB55239695EA00367E5A /* Build configuration list for PBXNativeTarget "demoTests" */ = { 620 | isa = XCConfigurationList; 621 | buildConfigurations = ( 622 | C109FB56239695EA00367E5A /* Debug */, 623 | C109FB57239695EA00367E5A /* Release */, 624 | ); 625 | defaultConfigurationIsVisible = 0; 626 | defaultConfigurationName = Release; 627 | }; 628 | C109FB58239695EA00367E5A /* Build configuration list for PBXNativeTarget "demoUITests" */ = { 629 | isa = XCConfigurationList; 630 | buildConfigurations = ( 631 | C109FB59239695EA00367E5A /* Debug */, 632 | C109FB5A239695EA00367E5A /* Release */, 633 | ); 634 | defaultConfigurationIsVisible = 0; 635 | defaultConfigurationName = Release; 636 | }; 637 | /* End XCConfigurationList section */ 638 | }; 639 | rootObject = C109FB1B239695E700367E5A /* Project object */; 640 | } 641 | -------------------------------------------------------------------------------- /demo/demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /demo/demo.xcodeproj/project.xcworkspace/xcuserdata/zhaoxueliang.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LYKit/LYWindowScreenView/e59637a3b8f76327dfb3443ac9ed966beb0df67c/demo/demo.xcodeproj/project.xcworkspace/xcuserdata/zhaoxueliang.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /demo/demo.xcodeproj/xcuserdata/zhaoxueliang.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 9 | 16 | 17 | 18 | 20 | 32 | 33 | 34 | 36 | 48 | 49 | 50 | 52 | 64 | 65 | 66 | 68 | 80 | 81 | 82 | 84 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /demo/demo.xcodeproj/xcuserdata/zhaoxueliang.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | demo.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /demo/demo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // demo 4 | // 5 | // Created by langezhao on 2019/12/3. 6 | // Copyright © 2019 学习. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (nonatomic, strong) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /demo/demo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // demo 4 | // 5 | // Created by langezhao on 2019/12/3. 6 | // Copyright © 2019 学习. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "ViewController.h" 11 | 12 | @interface AppDelegate () 13 | 14 | @end 15 | 16 | @implementation AppDelegate 17 | 18 | 19 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 20 | 21 | 22 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 23 | self.window.backgroundColor = [UIColor whiteColor]; 24 | UINavigationController *navc = [[UINavigationController alloc] initWithRootViewController:[ViewController new]]; 25 | self.window.rootViewController = navc; 26 | [self.window makeKeyAndVisible]; 27 | 28 | return YES; 29 | } 30 | 31 | 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /demo/demo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /demo/demo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /demo/demo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /demo/demo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /demo/demo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /demo/demo/LYWindowScreenView/LYWindowScreenView.h: -------------------------------------------------------------------------------- 1 | // 2 | // LYWindowScreenView.h 3 | // bangjob 4 | // 5 | // Created by langezhao on 2019/12/23. 6 | // Copyright © 2019 com.58. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | typedef enum : NSInteger { 13 | LYLevelHigh = -100, 14 | LYLevelMedium = -1, 15 | LYLevelDefault = 0, 16 | LYLevelLow = 100, 17 | } LYWindowScreenLevel; 18 | 19 | @interface LYWindowScreenView : UIView 20 | 21 | 22 | /// 是否关闭所有弹窗 23 | @property (class) BOOL hideAllAlert; 24 | 25 | + (void)jsAddWindowScreenView:(UIView *)view 26 | keepAlive:(BOOL)keepAlive 27 | page:(NSString *)pageName; 28 | 29 | 30 | + (void)addWindowScreenView:(UIView *)view 31 | page:(Class)page; 32 | 33 | + (void)addWindowScreenView:(UIView *)view 34 | keepAlive:(BOOL)keepAlive 35 | page:(Class)page; 36 | 37 | + (void)addWindowScreenView:(UIView *)view 38 | page:(Class)page 39 | showCompleted:(dispatch_block_t)showCompleted; 40 | 41 | /// 添加弹框 42 | /// @param view 弹窗视图 43 | /// @param pagesClass 绑定页面归属类,指定后弹窗只会在指定页面类出现,可以指定多个页面 44 | /// @param level 优先级,默认 LYLevelDefault 45 | /// @param keepAlive 弹窗存活,对弹窗进行缓存,默认NO不缓存;离开页面时弹窗消失,重新回到该页面时,弹窗仍在该页面, 46 | /// @param controller 绑定具体的页面,指定弹框的页面实例,默认nil。未设置时未展示的弹窗都会加入等待队列中, 设置后弹窗只有当此页面还在页面栈里时才添加到等待队列中,页面消失,弹窗也移除。一般复杂异步情况下的弹窗使用。 47 | /// @param showCompleted 当视图添加到window上时的回调,即弹窗显示后的回调。 48 | + (void)addWindowScreenView:(UIView *)view 49 | pages:(NSArray *)pages 50 | level:(NSInteger)level 51 | keepAlive:(BOOL)keepAlive 52 | controller:(UIViewController *)controller 53 | showCompleted:(dispatch_block_t)showCompleted; 54 | 55 | /// 获取未展示的弹框数据 56 | + (NSArray *)waitScreenViews; 57 | 58 | /// 删除弹框 59 | + (void)removeFromSuperview:(UIView *)view; 60 | 61 | /// 删除当前及未展示的所有弹框 62 | + (void)removeAllQueueViews; 63 | 64 | /// 删除所有等待展示的弹框 65 | + (void)removeAllWaitQueueViews; 66 | 67 | /// 删除所有缓存的弹框 68 | + (void)removeAllCacheQueueViews; 69 | @end 70 | 71 | -------------------------------------------------------------------------------- /demo/demo/LYWindowScreenView/LYWindowScreenView.m: -------------------------------------------------------------------------------- 1 | // 2 | // LYWindowScreenView.m 3 | // bangjob 4 | // 5 | // Created by langezhao on 2019/12/23. 6 | // Copyright © 2019 com.58. All rights reserved. 7 | // 8 | 9 | #import "LYWindowScreenView.h" 10 | #import "UIViewController+LYPageWillAppear.h" 11 | 12 | 13 | @interface LYWindowScreenModel : NSObject 14 | @property (nonatomic, strong) UIView *view; 15 | @property (nonatomic, assign) NSInteger level; 16 | @property (nonatomic, assign) BOOL keepAlive; 17 | @property (nonatomic, assign) BOOL bindPage; 18 | @property (nonatomic, weak) id identifier; 19 | @property (nonatomic, weak) UIViewController *controller; 20 | @property (nonatomic, copy) dispatch_block_t showCompleted; 21 | @property (nonatomic, strong) NSArray *pagesClass; 22 | 23 | @end 24 | 25 | @implementation LYWindowScreenModel 26 | @end 27 | 28 | 29 | 30 | @interface LYWindowScreenView () 31 | @property (nonatomic, strong) NSMutableArray *arrayWaitViews; 32 | @property (nonatomic, strong) NSMutableArray *arrayAliveViews; 33 | 34 | @property (nonatomic, strong) UIView *currentView; 35 | @property (nonatomic, assign) BOOL bViewWillDisappear; 36 | @property (nonatomic, assign) BOOL bViewWillAppear; 37 | @property (nonatomic, assign) BOOL keepAlive; 38 | @property (nonatomic, copy) dispatch_block_t showCompleted; 39 | @property (nonatomic, strong) NSArray *pagesClass; 40 | 41 | @end 42 | 43 | @implementation LYWindowScreenView 44 | 45 | static BOOL _hideAllAlert = NO; 46 | 47 | + (BOOL)hideAllAlert 48 | { 49 | return _hideAllAlert; 50 | } 51 | 52 | + (void)setHideAllAlert:(BOOL)hideAllAlert 53 | { 54 | _hideAllAlert = hideAllAlert; 55 | } 56 | 57 | + (LYWindowScreenView *)shareInstance { 58 | static LYWindowScreenView *instance; 59 | static dispatch_once_t onceToken; 60 | dispatch_once(&onceToken, ^{ 61 | instance = [LYWindowScreenView new]; 62 | }); 63 | return instance; 64 | } 65 | 66 | - (instancetype)init 67 | { 68 | self = [super init]; 69 | if (self) { 70 | self.arrayWaitViews = [NSMutableArray array]; 71 | self.arrayAliveViews = [NSMutableArray array]; 72 | self.bViewWillDisappear = NO; 73 | self.bViewWillAppear = NO; 74 | } 75 | return self; 76 | } 77 | 78 | - (void)setCurrentView:(UIView *)currentView { 79 | _currentView = currentView; 80 | if (_currentView == nil) { 81 | _keepAlive = NO; 82 | _pagesClass = nil; 83 | _showCompleted = nil; 84 | } 85 | [LYWindowScreenView addKeepAliveNotification]; 86 | } 87 | 88 | #pragma mark - 添加视图 89 | 90 | + (void)jsAddWindowScreenView:(UIView *)view 91 | keepAlive:(BOOL)keepAlive 92 | page:(NSString *)pageName 93 | { 94 | [self addWindowScreenView:view page:NSClassFromString(pageName) level:LYLevelDefault keepAlive:keepAlive controller:nil showCompleted:nil]; 95 | } 96 | + (void)addWindowScreenView:(UIView *)view 97 | page:(Class)page 98 | { 99 | [self addWindowScreenView:view page:page level:LYLevelDefault keepAlive:NO controller:nil showCompleted:nil]; 100 | } 101 | 102 | + (void)addWindowScreenView:(UIView *)view 103 | keepAlive:(BOOL)keepAlive 104 | page:(Class)page 105 | { 106 | [self addWindowScreenView:view page:page level:LYLevelDefault keepAlive:keepAlive controller:nil showCompleted:nil]; 107 | } 108 | 109 | + (void)addWindowScreenView:(UIView *)view 110 | page:(Class)page 111 | showCompleted:(dispatch_block_t)showCompleted 112 | { 113 | [self addWindowScreenView:view page:page level:LYLevelDefault keepAlive:NO controller:nil showCompleted:showCompleted]; 114 | } 115 | 116 | 117 | + (void)addWindowScreenView:(UIView *)view 118 | page:(Class)page 119 | level:(NSInteger)level 120 | keepAlive:(BOOL)keepAlive 121 | controller:(UIViewController *)controller 122 | showCompleted:(dispatch_block_t)showCompleted 123 | { 124 | if (page) { 125 | [self addWindowScreenView:view pages:@[page] level:level keepAlive:keepAlive controller:controller showCompleted:showCompleted]; 126 | } 127 | } 128 | 129 | + (void)addWindowScreenView:(UIView *)view 130 | pages:(NSArray *)pages 131 | level:(NSInteger)level 132 | keepAlive:(BOOL)keepAlive 133 | controller:(UIViewController *)controller 134 | showCompleted:(dispatch_block_t)showCompleted 135 | { 136 | UIViewController *currentController = [UIViewController currentViewController]; 137 | UIViewController *parentViewController = currentController.parentViewController; 138 | 139 | if ([currentController isKindOfClass:[controller class]] && currentController != controller) { 140 | return; 141 | } 142 | 143 | if ([self shareInstance].currentView == nil && 144 | ([self isMemberOfClass:currentController.class pages:pages] || 145 | [self isMemberOfClass:parentViewController.class pages:pages])) 146 | { 147 | UIWindow *window = [UIApplication sharedApplication].delegate.window; 148 | NSAssert(window, @"检查window"); 149 | [window addSubview:view]; 150 | [self shareInstance].keepAlive = keepAlive; 151 | [self shareInstance].pagesClass = pages; 152 | [self shareInstance].currentView = view; 153 | [self shareInstance].showCompleted = showCompleted; 154 | if ([self shareInstance].showCompleted) { 155 | [self shareInstance].showCompleted(); 156 | } 157 | } else { 158 | LYWindowScreenModel *model = [LYWindowScreenModel new]; 159 | model.view = view; 160 | model.pagesClass = pages; 161 | model.level = level; 162 | model.keepAlive = keepAlive; 163 | model.showCompleted = showCompleted; 164 | model.controller = controller; 165 | model.bindPage = controller ? YES : NO; 166 | [[self shareInstance].arrayWaitViews addObject:model]; 167 | [self addWaitShowNotification]; 168 | } 169 | } 170 | 171 | 172 | + (BOOL)isMemberOfClass:(Class)currentClass pages:(NSArray *)pages { 173 | BOOL isMember = NO; 174 | for (Class class in pages) { 175 | if ([NSStringFromClass(currentClass) isEqualToString:NSStringFromClass(class)]) { 176 | isMember = YES; 177 | break; 178 | } 179 | } 180 | return isMember; 181 | } 182 | 183 | // 有待显示的视图时添加监听 184 | + (void)addWaitShowNotification { 185 | if (([self shareInstance].arrayWaitViews.count || 186 | [self shareInstance].arrayAliveViews.count) && 187 | ![self shareInstance].bViewWillAppear) { 188 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(viewWillAppearNotification:) name:LYViewControllerViewDidAppearNotification object:nil]; 189 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyWindowDidChange:) name:UIWindowDidBecomeKeyNotification object:nil]; 190 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyWindowDidChange:) name:UIWindowDidResignKeyNotification object:nil]; 191 | [self shareInstance].bViewWillAppear = YES; 192 | } 193 | } 194 | 195 | // 当前视图需要缓存时添加监听 196 | + (void)addKeepAliveNotification { 197 | if (![self shareInstance].bViewWillDisappear && 198 | [self shareInstance].currentView) { 199 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(viewWillDisAppearNotification:) name:LYViewControllerViewWillDisappearNotification object:nil]; 200 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(viewDidDisAppearNotification:) name:LYViewControllerViewDidDisappearNotification object:nil]; 201 | [self shareInstance].bViewWillDisappear = YES; 202 | } 203 | } 204 | 205 | // 当前无视图,并且没有等待展示的视图时,移除监听 206 | + (void)removeNotification { 207 | if (![self shareInstance].arrayWaitViews.count && 208 | ![self shareInstance].arrayAliveViews.count && 209 | [self shareInstance].currentView == nil) { 210 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 211 | [self shareInstance].bViewWillDisappear = NO; 212 | [self shareInstance].bViewWillAppear = NO; 213 | } 214 | } 215 | 216 | #pragma mark - 删除视图 217 | + (void)removeFromSuperview:(UIView *)view { 218 | if ([self shareInstance].currentView == view) { 219 | [view removeFromSuperview]; 220 | [[self shareInstance] setCurrentView:nil]; 221 | NSLog(@"删除视图"); 222 | } else { 223 | for (NSInteger i = [self shareInstance].arrayWaitViews.count-1; i >= 0; i--) { 224 | LYWindowScreenModel *model = [self shareInstance].arrayWaitViews[i]; 225 | if (model.view == view) { 226 | [[self shareInstance].arrayWaitViews removeObject:model]; 227 | } 228 | } 229 | for (NSInteger i = [self shareInstance].arrayAliveViews.count-1; i >= 0 ; i--) { 230 | LYWindowScreenModel *model = [self shareInstance].arrayAliveViews[i]; 231 | if (model.view == view) { 232 | [[self shareInstance].arrayAliveViews removeObject:model]; 233 | } 234 | } 235 | } 236 | 237 | if ([self shareInstance].arrayWaitViews.count > 0) { 238 | [self viewNeedShowFromQueueWithPage:[UIViewController currentViewController]]; 239 | } 240 | [self removeNotification]; 241 | } 242 | 243 | #pragma mark - notification 244 | + (void)viewWillAppearNotification:(NSNotification *)notification { 245 | id identifier = notification.object[LYViewControllerClassIdentifier]; 246 | [self viewNeedShowFromQueueWithPage:identifier]; 247 | } 248 | 249 | + (void)viewWillDisAppearNotification:(NSNotification *)notification { 250 | NSString *strClass = notification.object[LYViewControllerClassName]; 251 | id identifier = notification.object[LYViewControllerClassIdentifier]; 252 | if ([self shareInstance].currentView && [self isMemberOfClass:NSClassFromString(strClass) pages:[self shareInstance].pagesClass]) { 253 | 254 | // 如果当前有展示视图,保留当前视图到队列顶部,避免window出现在下个页面,返回时再存活展示 255 | if ([self shareInstance].keepAlive) { 256 | LYWindowScreenModel *model = [LYWindowScreenModel new]; 257 | model.view = [self shareInstance].currentView; 258 | model.pagesClass = [self shareInstance].pagesClass; 259 | model.level = LYLevelHigh; 260 | model.keepAlive = [self shareInstance].keepAlive; 261 | model.identifier = identifier; 262 | model.showCompleted = [self shareInstance].showCompleted; 263 | [[self shareInstance].arrayAliveViews addObject:model]; 264 | [self addWaitShowNotification]; 265 | } 266 | [[self shareInstance].currentView removeFromSuperview]; 267 | [[self shareInstance] setCurrentView:nil]; 268 | [self removeNotification]; 269 | } 270 | } 271 | 272 | + (void)viewDidDisAppearNotification:(NSNotification *)notification { 273 | // 如果存活弹框的归属页面已移除,则移除该页面的所有弹框 274 | if ([self shareInstance].arrayAliveViews.count) { 275 | for (NSInteger i = [self shareInstance].arrayAliveViews.count-1; i >= 0; i--) { 276 | LYWindowScreenModel *model = [self shareInstance].arrayAliveViews[i]; 277 | BOOL exist = [UIViewController existViewController:model.identifier]; 278 | if (!exist) { 279 | [[self shareInstance].arrayAliveViews removeObject:model]; 280 | [self removeNotification]; 281 | } 282 | } 283 | } 284 | 285 | for (NSInteger i = [self shareInstance].arrayWaitViews.count-1; i >= 0; i--) { 286 | LYWindowScreenModel *obj = [self shareInstance].arrayWaitViews[i]; 287 | if (obj.bindPage && !obj.controller) { 288 | [[self shareInstance].arrayWaitViews removeObject:obj]; 289 | [self removeNotification]; 290 | } 291 | } 292 | } 293 | 294 | 295 | + (void)keyWindowDidChange:(NSNotification *)notification { 296 | UIViewController *controller = [UIViewController currentViewController]; 297 | [self viewNeedShowFromQueueWithPage:controller]; 298 | } 299 | 300 | 301 | 302 | #pragma mark - 推出队列中需要展示的视图进行展示 303 | + (void)viewNeedShowFromQueueWithPage:(UIViewController *)page { 304 | if (!page || ![page isKindOfClass:[UIViewController class]]) { 305 | return; 306 | } 307 | 308 | // 判断当前页是否有存活的弹框,有则加入队列中。 309 | if ([self shareInstance].arrayAliveViews.count) { 310 | for (NSInteger i = [self shareInstance].arrayAliveViews.count-1; i >= 0 ; i--) { 311 | LYWindowScreenModel *model = [self shareInstance].arrayAliveViews[i]; 312 | if (page == model.identifier) { 313 | [[self shareInstance].arrayWaitViews addObject:model]; 314 | [[self shareInstance].arrayAliveViews removeObject:model]; 315 | } 316 | } 317 | } 318 | 319 | // 当前屏幕有弹框,则不显示 320 | if ([self shareInstance].currentView) { 321 | return; 322 | } 323 | // 队列里无等待显示的视图 324 | if (![self shareInstance].arrayWaitViews.count) { 325 | return; 326 | } 327 | 328 | // 有指定页面,但页面实例发生变化,都清除 329 | for (NSInteger i = [self shareInstance].arrayWaitViews.count-1; i >= 0; i--) { 330 | LYWindowScreenModel *obj = [self shareInstance].arrayWaitViews[i]; 331 | if ([self isMemberOfClass:page.class pages:obj.pagesClass]) { 332 | if (obj.bindPage && (!obj.controller || obj.controller != page)) { 333 | [[self shareInstance].arrayWaitViews removeObject:obj]; 334 | [self removeNotification]; 335 | } 336 | } 337 | } 338 | 339 | // 重新根据优先级排列队列 340 | [[self shareInstance].arrayWaitViews sortUsingComparator:^NSComparisonResult(LYWindowScreenModel *obj1, LYWindowScreenModel * obj2) { 341 | return obj1.level <= obj2.level ? NSOrderedAscending : NSOrderedDescending; 342 | }]; 343 | 344 | //所有弹窗都不展示 345 | if (LYWindowScreenView.hideAllAlert) { 346 | return; 347 | } 348 | 349 | // 推出队列中需要展示的视图进行展示 350 | __block LYWindowScreenModel *model = nil; 351 | [[self shareInstance].arrayWaitViews enumerateObjectsUsingBlock:^(LYWindowScreenModel *obj, NSUInteger idx, BOOL * _Nonnull stop) { 352 | if (obj.pagesClass && obj.view) { 353 | if ([self isMemberOfClass:page.class pages:obj.pagesClass]) { 354 | UIWindow *window = [UIApplication sharedApplication].delegate.window; 355 | NSAssert(window, @"检查window"); 356 | [window addSubview:obj.view]; 357 | [self shareInstance].keepAlive = obj.keepAlive; 358 | [self shareInstance].pagesClass = obj.pagesClass; 359 | [self shareInstance].currentView = obj.view; 360 | [self shareInstance].showCompleted = obj.showCompleted; 361 | if ([self shareInstance].showCompleted) { 362 | [self shareInstance].showCompleted(); 363 | } 364 | model = obj; 365 | *stop = YES; 366 | } 367 | } 368 | }]; 369 | if (model) { 370 | [[self shareInstance].arrayWaitViews removeObject:model]; 371 | [self removeNotification]; 372 | } 373 | } 374 | 375 | 376 | + (NSArray *)waitScreenViews { 377 | return [[self shareInstance].arrayWaitViews copy]; 378 | } 379 | 380 | 381 | + (void)removeAllWaitQueueViews { 382 | [[self shareInstance].arrayWaitViews removeAllObjects]; 383 | [self removeNotification]; 384 | } 385 | 386 | + (void)removeAllCacheQueueViews { 387 | [[self shareInstance].arrayAliveViews removeAllObjects]; 388 | [self removeNotification]; 389 | } 390 | 391 | + (void)removeAllQueueViews { 392 | [[self shareInstance].arrayAliveViews removeAllObjects]; 393 | [[self shareInstance].arrayWaitViews removeAllObjects]; 394 | [[self shareInstance].currentView removeFromSuperview]; 395 | [[self shareInstance] setCurrentView:nil]; 396 | [self removeNotification]; 397 | } 398 | 399 | @end 400 | -------------------------------------------------------------------------------- /demo/demo/LYWindowScreenView/NSObject+LYSwizzle.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+LYSwizzle.h 3 | // bangjob 4 | // 5 | // Created by langezhao on 2018/12/26. 6 | // Copyright © 2018年 com.58. All rights reserved. 7 | // 8 | 9 | 10 | #import 11 | 12 | @interface NSObject (LYSwizzle) 13 | 14 | + (void)swizzleInstanceSelector:(SEL)originalSelector withNewSelector:(SEL)newSelector; 15 | 16 | + (void)swizzleClassSelector:(SEL)orgSelector withNewSelector:(SEL)newSelector; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /demo/demo/LYWindowScreenView/NSObject+LYSwizzle.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+Swizzle.m 3 | // bangjob 4 | // 5 | // Created by langezhao on 2018/12/26. 6 | // Copyright © 2018年 com.58. All rights reserved. 7 | // 8 | 9 | #import "NSObject+LYSwizzle.h" 10 | #import 11 | 12 | @implementation NSObject (LYSwizzle) 13 | 14 | + (void)swizzleInstanceSelector:(SEL)originalSelector withNewSelector:(SEL)newSelector { 15 | Method originalMethod = class_getInstanceMethod(self, originalSelector); 16 | Method newMethod = class_getInstanceMethod(self, newSelector); 17 | 18 | BOOL methodAdded = class_addMethod([self class], 19 | originalSelector, 20 | method_getImplementation(newMethod), 21 | method_getTypeEncoding(newMethod)); 22 | 23 | if (methodAdded) { 24 | class_replaceMethod([self class], 25 | newSelector, 26 | method_getImplementation(originalMethod), 27 | method_getTypeEncoding(originalMethod)); 28 | } else { 29 | method_exchangeImplementations(originalMethod, newMethod); 30 | } 31 | } 32 | 33 | + (void)swizzleClassSelector:(SEL)orgSelector withNewSelector:(SEL)newSelector { 34 | Method orgMethod = class_getClassMethod(self, orgSelector); 35 | Method newMethod = class_getClassMethod(self, newSelector); 36 | 37 | BOOL methodAdded = class_addMethod(self, 38 | orgSelector, 39 | method_getImplementation(newMethod), 40 | method_getTypeEncoding(newMethod)); 41 | 42 | if (methodAdded) { 43 | class_replaceMethod(self, 44 | newSelector, 45 | method_getImplementation(orgMethod), 46 | method_getTypeEncoding(orgMethod)); 47 | } else { 48 | method_exchangeImplementations(orgMethod, newMethod); 49 | } 50 | } 51 | 52 | @end 53 | -------------------------------------------------------------------------------- /demo/demo/LYWindowScreenView/UIViewController+LYPageWillAppear.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+LYPageWillAppear.h 3 | // bangjob 4 | // 5 | // Created by langezhao on 2019/12/23. 6 | // Copyright © 2019 com.58. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | static NSString * const LYViewControllerViewWillAppearNotification = @"LYViewControllerViewWillAppearIdentify"; 14 | static NSString * const LYViewControllerViewDidAppearNotification = @"LYViewControllerViewDidAppearIdentify"; 15 | static NSString * const LYViewControllerViewWillDisappearNotification = @"LYViewControllerViewWillDisappearIdentify"; 16 | static NSString * const LYViewControllerViewDidDisappearNotification = @"LYViewControllerViewDidDisappearIdentify"; 17 | 18 | static NSString * const LYViewControllerClassName = @"LYViewControllerClassNameIdentify"; 19 | static NSString * const LYViewControllerClassIdentifier = @"LYViewControllerClassIdentifier"; 20 | 21 | @interface UIViewController (LYPageWillAppear) 22 | 23 | + (UIViewController *)currentViewController; 24 | 25 | + (BOOL)existViewController:(UIViewController *)identifier; 26 | @end 27 | 28 | NS_ASSUME_NONNULL_END 29 | -------------------------------------------------------------------------------- /demo/demo/LYWindowScreenView/UIViewController+LYPageWillAppear.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+LYPageWillAppear.m 3 | // bangjob 4 | // 5 | // Created by langezhao on 2019/12/23. 6 | // Copyright © 2019 com.58. All rights reserved. 7 | // 8 | 9 | #import "UIViewController+LYPageWillAppear.h" 10 | #import "NSObject+LYSwizzle.h" 11 | 12 | 13 | @implementation UIViewController (LYPageWillAppear) 14 | 15 | + (void)load { 16 | static dispatch_once_t onceToken; 17 | dispatch_once(&onceToken, ^{ 18 | SEL oldviewWillAppear = @selector(viewWillAppear:); 19 | SEL newviewWillAppear = @selector(p_viewWillAppear:); 20 | 21 | SEL oldviewDidAppear = @selector(viewDidAppear:); 22 | SEL newviewDidAppear = @selector(p_viewDidAppear:); 23 | 24 | SEL oldviewWillDisappear = @selector(viewWillDisappear:); 25 | SEL newviewWillDisappear = @selector(p_viewWillDisappear:); 26 | 27 | SEL oldviewDidDisappear = @selector(viewDidDisappear:); 28 | SEL newviewDidDisappear = @selector(p_viewDidDisappear:); 29 | 30 | SEL oldpresentDidDisappear = @selector(presentViewController:animated:completion:); 31 | SEL newpresentDidDisappear = @selector(p_presentViewController:animated:completion:); 32 | 33 | 34 | [self swizzleInstanceSelector:oldviewWillAppear withNewSelector:newviewWillAppear]; 35 | [self swizzleInstanceSelector:oldviewDidAppear withNewSelector:newviewDidAppear]; 36 | [self swizzleInstanceSelector:oldviewWillDisappear withNewSelector:newviewWillDisappear]; 37 | [self swizzleInstanceSelector:oldviewDidDisappear withNewSelector:newviewDidDisappear]; 38 | [self swizzleInstanceSelector:oldpresentDidDisappear withNewSelector:newpresentDidDisappear]; 39 | 40 | }); 41 | } 42 | 43 | //修复ios13下模态页面问题 44 | - (void)p_presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion 45 | { 46 | if (@available(iOS 13.0, *)) { 47 | if (viewControllerToPresent.modalPresentationStyle != UIModalPresentationCustom) { 48 | viewControllerToPresent.modalPresentationStyle = UIModalPresentationFullScreen; 49 | } 50 | } else { 51 | // Fallback on earlier versions 52 | } 53 | [self p_presentViewController:viewControllerToPresent animated:flag completion:completion]; 54 | } 55 | 56 | 57 | - (void)p_viewWillAppear:(BOOL)animated { 58 | [self p_viewWillAppear:animated]; 59 | [[NSNotificationCenter defaultCenter] postNotificationName:LYViewControllerViewWillAppearNotification object:@{LYViewControllerClassName:NSStringFromClass([self class]),LYViewControllerClassIdentifier:self}]; 60 | } 61 | 62 | - (void)p_viewDidAppear:(BOOL)animated { 63 | [self p_viewDidAppear:animated]; 64 | [[NSNotificationCenter defaultCenter] postNotificationName:LYViewControllerViewDidAppearNotification object:@{LYViewControllerClassName:NSStringFromClass([self class]),LYViewControllerClassIdentifier:self}]; 65 | } 66 | 67 | - (void)p_viewWillDisappear:(BOOL)animated { 68 | [self p_viewWillDisappear:animated]; 69 | [[NSNotificationCenter defaultCenter] postNotificationName:LYViewControllerViewWillDisappearNotification object:@{LYViewControllerClassName:NSStringFromClass([self class]),LYViewControllerClassIdentifier:self}]; 70 | } 71 | 72 | - (void)p_viewDidDisappear:(BOOL)animated { 73 | [self p_viewDidDisappear:animated]; 74 | [[NSNotificationCenter defaultCenter] postNotificationName:LYViewControllerViewDidDisappearNotification object:@{LYViewControllerClassName:NSStringFromClass([self class]),LYViewControllerClassIdentifier:self}]; 75 | } 76 | 77 | // 获取当前显示的Controller 78 | + (UIViewController *)currentViewController { 79 | // Find best view controller 80 | UIViewController* viewController = [UIApplication sharedApplication].keyWindow.rootViewController; 81 | return [self findBestViewController:viewController]; 82 | } 83 | 84 | + (UIViewController*)findBestViewController:(UIViewController*)vc { 85 | if (vc.presentedViewController) { 86 | return [self findBestViewController:vc.presentedViewController]; 87 | } else if ([vc isKindOfClass:[UISplitViewController class]]) { 88 | UISplitViewController* svc = (UISplitViewController*) vc; 89 | if (svc.viewControllers.count > 0) 90 | return [self findBestViewController:svc.viewControllers.lastObject]; 91 | else 92 | return vc; 93 | } else if ([vc isKindOfClass:[UINavigationController class]]) { 94 | UINavigationController* svc = (UINavigationController*) vc; 95 | if (svc.viewControllers.count > 0) 96 | return [self findBestViewController:svc.topViewController]; 97 | else 98 | return vc; 99 | } else if ([vc isKindOfClass:[UITabBarController class]]) { 100 | UITabBarController* svc = (UITabBarController*) vc; 101 | if (svc.viewControllers.count > 0) 102 | return [self findBestViewController:svc.selectedViewController]; 103 | else 104 | return vc; 105 | } else { 106 | return vc; 107 | } 108 | } 109 | 110 | 111 | + (BOOL)existViewController:(UIViewController *)identifier{ 112 | if ([identifier isKindOfClass:[UIViewController class]]) { 113 | if (identifier.navigationController || identifier.presentingViewController) { 114 | return YES; 115 | } 116 | } 117 | return NO; 118 | } 119 | 120 | @end 121 | -------------------------------------------------------------------------------- /demo/demo/SceneDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.h 3 | // demo 4 | // 5 | // Created by 赵学良 on 2019/12/3. 6 | // Copyright © 2019 学习. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SceneDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow * window; 14 | 15 | @end 16 | 17 | -------------------------------------------------------------------------------- /demo/demo/SceneDelegate.m: -------------------------------------------------------------------------------- 1 | #import "SceneDelegate.h" 2 | #import "ViewController.h" 3 | 4 | @interface SceneDelegate () 5 | 6 | @end 7 | 8 | @implementation SceneDelegate 9 | 10 | 11 | - (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions { 12 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 13 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 14 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 15 | 16 | if (@available(iOS 13.0, *)) { 17 | UIWindowScene *windowScene = (UIWindowScene *)scene; 18 | self.window = [[UIWindow alloc] initWithWindowScene:windowScene]; 19 | self.window.frame = windowScene.coordinateSpace.bounds; 20 | self.window.rootViewController = [ViewController new]; 21 | [self.window makeKeyAndVisible]; 22 | } else { 23 | // Fallback on earlier versions 24 | } 25 | } 26 | 27 | 28 | - (void)sceneDidDisconnect:(UIScene *)scene { 29 | // Called as the scene is being released by the system. 30 | // This occurs shortly after the scene enters the background, or when its session is discarded. 31 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 32 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 33 | } 34 | 35 | 36 | - (void)sceneDidBecomeActive:(UIScene *)scene { 37 | // Called when the scene has moved from an inactive state to an active state. 38 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 39 | } 40 | 41 | 42 | - (void)sceneWillResignActive:(UIScene *)scene { 43 | // Called when the scene will move from an active state to an inactive state. 44 | // This may occur due to temporary interruptions (ex. an incoming phone call). 45 | } 46 | 47 | 48 | - (void)sceneWillEnterForeground:(UIScene *)scene { 49 | // Called as the scene transitions from the background to the foreground. 50 | // Use this method to undo the changes made on entering the background. 51 | } 52 | 53 | 54 | - (void)sceneDidEnterBackground:(UIScene *)scene { 55 | // Called as the scene transitions from the foreground to the background. 56 | // Use this method to save data, release shared resources, and store enough scene-specific state information 57 | // to restore the scene back to its current state. 58 | } 59 | 60 | 61 | @end 62 | -------------------------------------------------------------------------------- /demo/demo/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // demo 4 | // 5 | // Created by langezhao on 2019/12/3. 6 | // Copyright © 2019 学习. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /demo/demo/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // demo 4 | // 5 | // Created by langezhao on 2019/12/3. 6 | // Copyright © 2019 学习. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "LYWindowScreenView.h" 11 | #import "ViewControllerTwo.h" 12 | #import "ViewControllerFour.h" 13 | 14 | @interface ViewController () 15 | @property (nonatomic, strong) UILabel *label1; 16 | @property (nonatomic, strong) UILabel *label2; 17 | @property (nonatomic, strong) UILabel *label3; 18 | @property (nonatomic, strong) UIViewController *controller; 19 | @property (strong, nonatomic) IBOutlet UIView *activityView; 20 | 21 | @end 22 | 23 | @implementation ViewController 24 | 25 | - (void)viewDidLoad { 26 | [super viewDidLoad]; 27 | self.title = @"页面A"; 28 | self.view.backgroundColor = [UIColor whiteColor]; 29 | 30 | _activityView.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.5]; 31 | 32 | } 33 | 34 | - (IBAction)didPressedClick:(id)sender { 35 | [LYWindowScreenView removeFromSuperview:_label1]; 36 | [LYWindowScreenView removeFromSuperview:_label2]; 37 | [LYWindowScreenView removeFromSuperview:_label3]; 38 | 39 | { 40 | _label1 = [UILabel new]; 41 | _label1.text = @"页面A\n\n 优先级1"; 42 | _label1.backgroundColor = [[UIColor redColor] colorWithAlphaComponent:0.3]; 43 | _label1.frame = CGRectMake(50, 250, CGRectGetWidth(self.view.frame)-100, CGRectGetHeight(self.view.frame)-270); 44 | _label1.userInteractionEnabled = YES; 45 | self.label1.numberOfLines = 0; 46 | _label1.textAlignment = NSTextAlignmentCenter; 47 | UITapGestureRecognizer *pan = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(dismissone)]; 48 | [_label1 addGestureRecognizer:pan]; 49 | [LYWindowScreenView addWindowScreenView:_label1 pages:@[self.class] level:LYLevelHigh keepAlive:NO controller:nil showCompleted:nil]; 50 | } 51 | 52 | { 53 | self.label2 = [UILabel new]; 54 | self.label2.text = @"页面A\n\n优先级4"; 55 | self.label2.backgroundColor = [[UIColor yellowColor] colorWithAlphaComponent:0.3]; 56 | self.label2.numberOfLines = 0; 57 | self.label2.userInteractionEnabled = YES; 58 | _label2.textAlignment = NSTextAlignmentCenter; 59 | UITapGestureRecognizer *pan = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(dismisstwo)]; 60 | [self.label2 addGestureRecognizer:pan]; 61 | self.label2.frame = CGRectMake(50, 250, CGRectGetWidth(self.view.frame)-100, CGRectGetHeight(self.view.frame)-270); 62 | [LYWindowScreenView addWindowScreenView:self.label2 pages:@[self.class] level:LYLevelLow keepAlive:YES controller:nil showCompleted:^{ 63 | self.label2.frame = CGRectMake(50, 700, CGRectGetWidth(self.view.frame)-100, CGRectGetHeight(self.view.frame)-270); 64 | [UIView animateWithDuration:0.3 animations:^{ 65 | self.label2.frame = CGRectMake(50, 250, CGRectGetWidth(self.view.frame)-100, CGRectGetHeight(self.view.frame)-270); 66 | }]; 67 | }]; 68 | 69 | 70 | } 71 | 72 | { 73 | self.label3 = [UILabel new]; 74 | self.label3.text = @"页面A\n\n 优先级2"; 75 | self.label3.backgroundColor = [[UIColor grayColor] colorWithAlphaComponent:1]; 76 | self.label3.numberOfLines = 0; 77 | _label3.textAlignment = NSTextAlignmentCenter; 78 | self.label3.userInteractionEnabled = YES; 79 | UITapGestureRecognizer *pan2 = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(dismissthree)]; 80 | [self.label3 addGestureRecognizer:pan2]; 81 | self.label3.frame = CGRectMake(50, 250, CGRectGetWidth(self.view.frame)-100, CGRectGetHeight(self.view.frame)-270); 82 | [LYWindowScreenView addWindowScreenView:self.label3 pages:@[self.class, [ViewControllerTwo class]] level:LYLevelDefault keepAlive:YES controller:nil showCompleted:nil]; 83 | } 84 | } 85 | 86 | - (IBAction)didPressedClickTwo:(id)sender { 87 | _controller = [ViewControllerTwo new]; 88 | [self.navigationController pushViewController:_controller animated:YES]; 89 | } 90 | 91 | - (IBAction)activity:(id)sender { 92 | [LYWindowScreenView addWindowScreenView:self.activityView pages:@[self.class] level:LYLevelDefault keepAlive:YES controller:nil showCompleted:nil]; 93 | } 94 | 95 | - (IBAction)activityOne:(id)sender { 96 | [self.navigationController pushViewController:[ViewControllerFour new] animated:YES]; 97 | } 98 | 99 | - (IBAction)activityTwo:(id)sender { 100 | [self.navigationController pushViewController:[ViewControllerFour new] animated:YES]; 101 | } 102 | 103 | - (IBAction)didPressedClose:(id)sender { 104 | [LYWindowScreenView removeFromSuperview:_activityView]; 105 | } 106 | 107 | - (void)dismissone { 108 | [LYWindowScreenView removeFromSuperview:_label1]; 109 | _label1 = nil; 110 | } 111 | - (void)dismisstwo { 112 | [LYWindowScreenView removeFromSuperview:_label2]; 113 | _label2 = nil; 114 | 115 | } 116 | - (void)dismissthree { 117 | [LYWindowScreenView removeFromSuperview:_label3]; 118 | _label3 = nil; 119 | 120 | } 121 | @end 122 | -------------------------------------------------------------------------------- /demo/demo/ViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 33 | 44 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 86 | 98 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /demo/demo/ViewControllerFour.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewControllerFour.h 3 | // demo 4 | // 5 | // Created by langezhao on 2019/12/31. 6 | // Copyright © 2019 学习. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface ViewControllerFour : UIViewController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /demo/demo/ViewControllerFour.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewControllerFour.m 3 | // demo 4 | // 5 | // Created by langezhao on 2019/12/31. 6 | // Copyright © 2019 学习. All rights reserved. 7 | // 8 | 9 | #import "ViewControllerFour.h" 10 | 11 | @interface ViewControllerFour () 12 | 13 | @end 14 | 15 | @implementation ViewControllerFour 16 | 17 | - (void)viewDidLoad { 18 | [super viewDidLoad]; 19 | self.title = @"活动详情"; 20 | } 21 | 22 | /* 23 | #pragma mark - Navigation 24 | 25 | // In a storyboard-based application, you will often want to do a little preparation before navigation 26 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 27 | // Get the new view controller using [segue destinationViewController]. 28 | // Pass the selected object to the new view controller. 29 | } 30 | */ 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /demo/demo/ViewControllerFour.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /demo/demo/ViewControllerThree.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewControllerThree.h 3 | // demo 4 | // 5 | // Created by langezhao on 2019/12/27. 6 | // Copyright © 2019 学习. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface ViewControllerThree : UIViewController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /demo/demo/ViewControllerThree.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewControllerThree.m 3 | // demo 4 | // 5 | // Created by langezhao on 2019/12/27. 6 | // Copyright © 2019 学习. All rights reserved. 7 | // 8 | 9 | #import "ViewControllerThree.h" 10 | #import "LYWindowScreenView.h" 11 | 12 | @interface ViewControllerThree () 13 | @property (nonatomic, strong) UILabel *label3; 14 | 15 | @end 16 | 17 | @implementation ViewControllerThree 18 | 19 | - (void)viewDidLoad { 20 | [super viewDidLoad]; 21 | 22 | self.view.backgroundColor = [UIColor whiteColor]; 23 | self.title = @"页面C"; 24 | self.label3 = [UILabel new]; 25 | self.label3.text = @"页面C\n\n第一个弹框,优先级一般,点击关闭"; 26 | self.label3.backgroundColor = [[UIColor purpleColor] colorWithAlphaComponent:0.3]; 27 | _label3.numberOfLines = 0; 28 | _label3.textAlignment = NSTextAlignmentCenter; 29 | self.label3.userInteractionEnabled = YES; 30 | UITapGestureRecognizer *pan2 = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(dismissthree)]; 31 | [self.label3 addGestureRecognizer:pan2]; 32 | self.label3.frame = CGRectMake(50, 250, CGRectGetWidth(self.view.frame)-100, CGRectGetHeight(self.view.frame)-270); 33 | [LYWindowScreenView addWindowScreenView:self.label3 pages:@[self.class] level:LYLevelDefault keepAlive:YES controller:nil showCompleted:^{ 34 | NSLog(@"555"); 35 | }]; 36 | 37 | 38 | } 39 | - (IBAction)dismiss:(id)sender { 40 | [self dismissViewControllerAnimated:YES completion:nil]; 41 | } 42 | 43 | - (void)dismissthree { 44 | [LYWindowScreenView removeFromSuperview:_label3]; 45 | _label3 = nil; 46 | 47 | } 48 | 49 | 50 | /* 51 | #pragma mark - Navigation 52 | 53 | // In a storyboard-based application, you will often want to do a little preparation before navigation 54 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 55 | // Get the new view controller using [segue destinationViewController]. 56 | // Pass the selected object to the new view controller. 57 | } 58 | */ 59 | 60 | @end 61 | -------------------------------------------------------------------------------- /demo/demo/ViewControllerThree.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /demo/demo/ViewControllerTwo.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewControllerTwo.h 3 | // demo 4 | // 5 | // Created by langezhao on 2019/12/24. 6 | // Copyright © 2019 学习. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface ViewControllerTwo : UIViewController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /demo/demo/ViewControllerTwo.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewControllerTwo.m 3 | // demo 4 | // 5 | // Created by langezhao on 2019/12/24. 6 | // Copyright © 2019 学习. All rights reserved. 7 | // 8 | 9 | #import "ViewControllerTwo.h" 10 | #import "LYWindowScreenView.h" 11 | #import "ViewControllerThree.h" 12 | 13 | @interface ViewControllerTwo () 14 | @property (nonatomic, strong) UILabel *label3; 15 | 16 | @end 17 | 18 | @implementation ViewControllerTwo 19 | 20 | - (void)viewDidLoad { 21 | [super viewDidLoad]; 22 | self.view.backgroundColor = [UIColor whiteColor]; 23 | self.title = @"页面B"; 24 | self.label3 = [UILabel new]; 25 | self.label3.text = @"页面B\n\n第一个弹框,优先级一般,点击关闭"; 26 | self.label3.backgroundColor = [[UIColor greenColor] colorWithAlphaComponent:0.3]; 27 | _label3.numberOfLines = 0; 28 | _label3.textAlignment = NSTextAlignmentCenter; 29 | self.label3.userInteractionEnabled = YES; 30 | UITapGestureRecognizer *pan2 = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(dismissthree)]; 31 | [self.label3 addGestureRecognizer:pan2]; 32 | self.label3.frame = CGRectMake(50, 250, CGRectGetWidth(self.view.frame)-100, CGRectGetHeight(self.view.frame)-270); 33 | 34 | [LYWindowScreenView addWindowScreenView:self.label3 pages:@[self.class] level:LYLevelDefault keepAlive:YES controller:nil showCompleted:^{ 35 | NSLog(@"444"); 36 | }]; 37 | } 38 | 39 | - (void)dismissthree { 40 | [LYWindowScreenView removeFromSuperview:_label3]; 41 | _label3 = nil; 42 | 43 | } 44 | 45 | - (void)viewWillAppear:(BOOL)animated { 46 | [super viewWillAppear:animated]; 47 | } 48 | 49 | - (void)viewWillDisappear:(BOOL)animated { 50 | [super viewWillDisappear:animated]; 51 | } 52 | 53 | - (void)viewDidDisappear:(BOOL)animated { 54 | [super viewDidDisappear:animated]; 55 | } 56 | 57 | - (IBAction)present:(id)sender { 58 | // 如果弹窗是添加到keywindow上,模态是另一个keywindow,所以弹窗本身不会出现 59 | // 如果弹框是iOS13的模态,viewWillDisappear 不会执行 60 | ViewControllerThree *vc = [ViewControllerThree new]; 61 | vc.modalPresentationStyle = UIModalPresentationFullScreen; 62 | [self presentViewController:vc animated:YES completion:nil]; 63 | } 64 | 65 | 66 | - (void)dealloc { 67 | 68 | } 69 | /* 70 | #pragma mark - Navigation 71 | 72 | // In a storyboard-based application, you will often want to do a little preparation before navigation 73 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 74 | // Get the new view controller using [segue destinationViewController]. 75 | // Pass the selected object to the new view controller. 76 | } 77 | */ 78 | 79 | @end 80 | -------------------------------------------------------------------------------- /demo/demo/ViewControllerTwo.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /demo/demo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // demo 4 | // 5 | // Created by langezhao on 2019/12/3. 6 | // Copyright © 2019 学习. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | NSString * appDelegateClassName; 14 | @autoreleasepool { 15 | // Setup code that might create autoreleased objects goes here. 16 | appDelegateClassName = NSStringFromClass([AppDelegate class]); 17 | } 18 | return UIApplicationMain(argc, argv, nil, appDelegateClassName); 19 | } 20 | -------------------------------------------------------------------------------- /demo/demoTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /demo/demoTests/demoTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // demoTests.m 3 | // demoTests 4 | // 5 | // Created by langezhao on 2019/12/3. 6 | // Copyright © 2019 学习. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface demoTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation demoTests 16 | 17 | - (void)setUp { 18 | // Put setup code here. This method is called before the invocation of each test method in the class. 19 | } 20 | 21 | - (void)tearDown { 22 | // Put teardown code here. This method is called after the invocation of each test method in the class. 23 | } 24 | 25 | - (void)testExample { 26 | // This is an example of a functional test case. 27 | // Use XCTAssert and related functions to verify your tests produce the correct results. 28 | } 29 | 30 | - (void)testPerformanceExample { 31 | // This is an example of a performance test case. 32 | [self measureBlock:^{ 33 | // Put the code you want to measure the time of here. 34 | }]; 35 | } 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /demo/demoUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /demo/demoUITests/demoUITests.m: -------------------------------------------------------------------------------- 1 | // 2 | // demoUITests.m 3 | // demoUITests 4 | // 5 | // Created by langezhao on 2019/12/3. 6 | // Copyright © 2019 学习. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface demoUITests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation demoUITests 16 | 17 | - (void)setUp { 18 | // Put setup code here. This method is called before the invocation of each test method in the class. 19 | 20 | // In UI tests it is usually best to stop immediately when a failure occurs. 21 | self.continueAfterFailure = NO; 22 | 23 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 24 | } 25 | 26 | - (void)tearDown { 27 | // Put teardown code here. This method is called after the invocation of each test method in the class. 28 | } 29 | 30 | - (void)testExample { 31 | // UI tests must launch the application that they test. 32 | XCUIApplication *app = [[XCUIApplication alloc] init]; 33 | [app launch]; 34 | 35 | // Use recording to get started writing UI tests. 36 | // Use XCTAssert and related functions to verify your tests produce the correct results. 37 | } 38 | 39 | - (void)testLaunchPerformance { 40 | if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) { 41 | // This measures how long it takes to launch your application. 42 | [self measureWithMetrics:@[XCTOSSignpostMetric.applicationLaunchMetric] block:^{ 43 | [[[XCUIApplication alloc] init] launch]; 44 | }]; 45 | } 46 | } 47 | 48 | @end 49 | --------------------------------------------------------------------------------