弹出框在项目中是随处可见的一种设计,消息提醒、操作交互、功能选择等很多需求中都会使用到。
在 UIKit 中,实现方式有很多,如 覆盖UIView、模态UIViewController、keyWindow / rootController 添加等。
今天来研究下在 SwiftUI 中有哪些实现方式。
系统自带
-
Alert、ActionSheet:这些系统自带弹窗就不多细说了,很方便,却不怎么支持自定义。一般在项目中用到的不会很多,毕竟大多数项目都会有自己的一套UI警告弹出框效果。但大多也都是参考其样式来实现的。 -
.sheet(...)、.fullScreenCover(...):模态常用的Modifier,不过几乎不怎么用在弹窗上。为什么是‘几乎’,因为写到这里的时候,我特意去试了一下,可以实现,但有局限性。下面会讲到。
自定义
开始
在 SwiftUI 上实现一个弹窗的效果很简单。只需要用 ZStrck 嵌套俩 View 就行了。
没错,很丑。但是确实算作是个弹窗。稍微美化一点点。再加个动画显示
| 代码 | 效果 |
|---|---|
彳亍!完成了。
通过不同的 modifier 可以实现不同弹窗效果。
本文到此结束(手动狗头)
问题
如果只是用做写个 Demo 的话,上面那样就行了。
但是在实际的写项目过程中,UI 都是比较复杂的组件控件组合嵌套。尤其是在 NavigationView、TabView 以及 NavigationView + TabView 相互结合的情况下,这种效果就无法满足需求。
这并没有达到预期的要求,且在实际项目中,基本都离不开导航栏,所以问题就出现了。
我也在网上找了些类似的代码,当我给代码加上 NavigationView 和 TabView 后,或多或少的都出现了类似的状况。事实证明,写 Demo 和实际写项目还是会有些出入的。
解决方案
-
将需要弹出的
view放在最外层这个解决方案是最容易想到也是最简单的。参考上面的代码,直接将
ZStack放到NavigationView的外面就行了。局限性:如果页面
Apush 到了页面B,这种方式就就会出现问题。因为B页面的导航栏是由A页面赋予的。没有办法将ZStack放到导航栏之外。一般情况下,项目的层级都是
NavigationView或TabView,如果想用用ZStack实现全屏弹窗的话,得将ZStack放到最外层。放一个两个的还可以接受,一旦弹窗多了的话,可能就比较难受了:而且这还只是展示,没有涉及到交互。真要这么实现的话,就需要考虑一下逻辑和封装了。
这里推荐一个:PartialSheet 就是基于最外层
View封装的底部弹出框。感兴趣的可以参考下或者直接使用。建议:如果当前
View没有或隐藏了导航栏、弹出框不需要背景等情况下。可以直接用这种方式实现。如 Hud、Toast 等。 -
基于
SwiftUI中的模态跳转实现SwiftUI中用于模态的 Modifier 有.sheet(...)和.fullScreenCover(...) (iOS 14),通过UIViewRepresentable给需要弹出的View添加background的方式来清除模态视图的背景色。实现效果
Modifier代码 效果 .fullScreenCover().sheet()局限性:由于使用系统模态,都是从底部动画弹出,且目前没有修改弹出动画方式。所以限制了弹窗的出现方式。
SwiftUI中的这两个modifier等同于UIKit中UIModalPresentationStyle为automatic和fullScreen的模态效果。故此种方式可以忽略项目中的层级关系,高效快速实现整体弹窗效果。建议:由于弹出方式受限制为底部,所以较适用于一些自定义的弹出视图。
-
通过获取主控制器自定义模态跳转
有了第二个解决方案,假设一下:如果去掉了第二个解决方案的限制条件,似乎就可以解决这个问题了。
既然
SwiftUI中无法修改弹出方式。那就通过获取当前控制器,并修改跳转过程中的参数进行模态。首先通过自定义环境变量设置当前控制器:
然后修改
UIViewController的模态跳转参数:上述代码只是针对弹窗所设置的。你也可以暴露出其他参数进行自定义。由于
SwiftUI在iOS 13上没有全屏模态跳转,所以根据个人需要可以通过此方式进行自定义。接下来就是在
View中实现和调用了,通过@Environment(\.viewController)取到主控制器,在所需要交互的地方实现调用。代码 效果 局限性:由于是模态出新的控制器,所以多个弹窗交互的间隙时间稍微长了一些,毕竟要先
dismiss后再重新present。上述代码只是一个简单的显示,没有涉及到多少的功能和效果。实际写项目中会比这要复杂的多,不过只要注意多弹窗间显示或交互时候的逻辑处理,就可以处理大多数的弹窗问题。当然写起来就相对来说会复杂一些,毕竟属于整体自定义,没有借助于
SwiftUI自带的modifier。建议:根据需求,简单的方法处理不了的,就用这种方式吧。
总结
弹出框问题是我在具体些项目当中遇到的,也只找到了目前的这几个解决方案。一定还有其他更好的方式没有被我发现。如果你发现上述文章或代码中有不合理的地方,欢迎留言评论。如果你有更好的解决方案,也请不吝赐教。
通常有些问题在我们写 Demo 的时候是不会发现的。而在写实际项目的时候,涉及到的逻辑和交互多了,有些问题往往就自己暴露出来了,此时通过解决问题的过程就会学习和获得更多的知识。
最后,感谢您的阅读。祝好!