技术解读
PanelKit:给 iOS 补上一块缺失的「窗口层」
项目背景与痛点
iPad 上的多任务能力长期处于「半成品」状态。UIKit 给出的解决方案无非两把斧子:UISplitViewController 和 popover。前者只能切两栏(后来勉强三栏),后者是转瞬即逝的临时浮层。可一旦你想做真正意义上的「工作区」——绘图软件左侧工具栏、右侧图层、中间画布,每个面板可拖、可停、可收——系统什么也不给你。
PanelKit 正是在这个缝隙里长出来的。它把「面板」抽象成一等公民:一个面板既是 UIViewController,又是可以被拖拽、缩放、吸附、停靠、隐藏的视图容器。作者 Louis D’hauwe 当时在做 iPad 上的开发类应用,缺这套基础设施,于是自己造了一套并开源。它本质上不是「又一个 UI 组件」,而是对 UIKit 容器层级的一次补全。
核心技术揭秘
PanelKit 的整体结构是经典的容器视图控制器(Container View Controller)模式,但需要说清楚一点:PanelManager 本身并不是 UIViewController,它扮演的是协调者角色,负责持有宿主控制器与面板清单,把各面板的视图挂载到一个覆盖在宿主内容之上的容器中。
面板被建模成三种稳定状态:floating(浮动)、docked(停靠)、hidden(隐藏)。floating 走自由 frame 定位,docked 走固定停靠位加宽度约束,hidden 则把视图移出可视区。状态迁移是整个框架复杂度的重心,也是最容易出 bug 的地方。
几个关键难点值得拆开讲:
手势归属判定。 面板内部的 UITableView、UIScrollView 天然会和面板拖拽手势打架。PanelKit 的做法是要求内容控制器实现 PanelContentViewController 协议,把内部滚动视图显式暴露出来,再由 manager 在手势的 shouldBegin 阶段做仲裁:触摸点落在滚动视图内且 contentOffset 尚未触顶时,拖拽让位给滚动;否则 manager 接管,进入面板移动流程。这是个很务实的取舍——不做全自动的手势联动,而是把决定权通过协议交回业务方。
吸附与停靠求解。 拖拽结束时,manager 计算面板 frame 与屏幕边缘的距离,低于阈值即吸附;若目标落在侧边停靠区,则切换为 docked 并重建约束。停靠面板还支持拖动内侧边缘改宽度,本质是实时改写宽度约束并触发内容重排。
状态持久化。 每个面板的 frame、状态与停靠位会被写进 PanelStateStore,最终落到 UserDefaults,冷启动时还原。这看似小事,却是「工作区」类应用体验的分水岭——用户不希望每次打开都要重新摆一遍布局。
适配与降级。 屏幕旋转、traitCollection 变化时,所有 docked 面板要按比例重算尺寸与位置;进入 compact 环境(iPhone)后,浮动面板直接退化为全屏模态,因为小屏上的「浮动」毫无意义。
功能亮点与差异
同类方案里,UIWindow 浮窗能浮但不受控制器管理,生命周期和内存都要自己兜底;popover 又只适合短暂交互。PanelKit 的差异化在于把「浮动面板」和「停靠侧栏」统一到同一个抽象之下,并且把工程细节做足了:拖拽时提升 z-order 并加深阴影、面板间焦点切换、面板内嵌 UINavigationController 的独立导航栈、退出后自动恢复布局。
真正稀缺的是它的声明式配置——面板允许的尺寸、可拖拽区域、停靠位都通过协议属性暴露,业务方几乎不用写手势代码。对于一个 UI 框架来说,这比多几个动画效果重要得多。
应用场景与落地建议
它适合 iPad 优先的生产力工具:绘图、剪辑、笔记类的工具栏与图层管理,数据看板的可折叠侧栏,以及任何需要「多面板工作区」形态的应用。
落地前有几件事必须想清楚。第一,项目停留在 Swift 4 时代且已归档,接进现代工程基本要自己 fork 修语法和 API,成本不低。第二,阴影和圆角会触发离屏渲染,多面板叠加时务必设置 layer.shadowPath,谨慎使用 shouldRasterize,否则滚动帧率会崩。第三,手势冲突需要预留调试时间,尤其是嵌套 UICollectionView 或横向滚动列表的场景。第四,如果产品形态以 iPhone 为主,别用它——降级成全屏模态之后,这套框架的价值几乎归零。
综合评价
PanelKit 的设计干净、抽象克制,把 UIKit 缺失的那层「窗口管理」补得很有分寸。停靠/浮动双形态与状态持久化是真正的加分项,架构上的参考价值远大于直接复用价值。
缺点同样明显:文档基本靠一篇长 README,维护早已停摆,边缘 case 全得自己填。它更像一份「如何在 UIKit 上从零实现一套面板系统」的完整参考答案,而不是一个可以闭眼接入的依赖。想清楚这一点,再决定是拿来用,还是拿來读。
评论