随着有关 Apple 新款 iPad Pro 和 iOS 9 中新的 Core Image 滤镜 (一些由 Metal Performance Shaders 提供支持!)的激动人心的消息,现在是我开始开发基于节点的图像处理应用程序 Nodality 的第 3 版的最佳时机.
我开始编写 Nodality 的 Swift 版本是在我有点不知所措的时候,坦率地说,为了让它在 Swift 2 下很好地工作,它可以从完全重写中获益。代码中基于节点的用户界面部分与图像过滤逻辑紧密耦合,不可能重用于其他类型的应用程序。
因此,考虑到这一点,我重建了该代码以将显示逻辑与图像处理完全分开,并将其打开以接受可注入渲染器。这意味着我的基于节点的 UI 组件 ShinpuruNodeUI 可以很容易地在另一种类型的应用程序中重用。例如,它可以呈现数据库模式、业务流程工作流甚至音频设备和过滤器。
这篇文章着眼于如何在您自己的应用程序中实现 ShinpuruNodeUI,而不是讨论组件本身的内部结构。如果您对实际组件有任何疑问,请随时对这篇文章发表评论或通过 Twitter 与我联系,我是 @FlexMonkey 。
该 项目 捆绑了一个简单的演示计算器应用程序,自从我 在 2008 年第一次开始编写它们 以来,它一直是我基于节点的用户界面的默认“入门”应用程序!计算器代码说明了如何在基于节点的应用程序中实现自己的业务逻辑。
交互设计
主要的用户手势是:
- 要创建新节点,请在背景上长按
- 切换两个节点之间的关系
- 长按源节点直到背景颜色变为浅灰色
- 该组件现在处于“关系创建模式”
- 点击目标上的输入(浅灰色行)并创建关系(如果该关系存在则删除)
- 要删除节点,请点击节点工具栏中的小垃圾桶图标
- 整个画布可以缩放和平移
我玩过更传统的拖动手势来创建关系,但发现这种“关系创建模式”模式适用于触摸设备。在此版本中,没有任何东西可以阻止用户创建导致应用程序崩溃的循环关系 - 有一个针对此待定的修复程序。
安装
ShinpuruNodeUI 是手动安装的,需要您将以下文件复制到您的项目中:
- SNNodeWidget 节点小部件显示组件
- SNNodesContainer 节点小部件和背景网格的超级视图
- SNRelationshipCurvesLayer 渲染关系曲线的 CAShapeLayer
- SNView ShinpuruNodeUI 的主要组件
- ShinpuruNodeDataTypes 包含支持类和协议:
- SNNode 一种节点数据类型
- SNDelegate 委托协议
- SNItemRenderer、SNOutputRowRenderer、SNInputRowRenderer 渲染器的基类,您可以为自己的实现进行扩展
随着组件的发展,这必然会发生变化,我会不断更新自述文件。
实例化组件
设置非常简单。实例化一个实例:
let shinpuruNodeUI = SNView()
将其添加为子视图:
let shinpuruNodeUI = SNView()
...并设置其界限:
let shinpuruNodeUI = SNView()
SNDelegate 概述
为了让 ShinpuruNodeUI 做任何有趣的事情,它需要一个类型为 SNDelegate 的 nodeDelegate 。 nodeDelegate 负责从充当数据源到提供渲染器的所有事情,并包括以下方法:
- dataProviderForView(view: SNView) -> [SNNode]?返回一个 SNNode 实例数组
- itemRendererForView(view: SNView, node: SNNode) -> SNItemRenderer 返回视图的主要项目渲染器。在我的演示应用程序中,这些是显示节点值的红色和蓝色方块。
- inputRowRendererForView(view: SNView, node: SNNode, index: Int) -> SNInputRowRenderer 返回输入行的渲染器。在我的演示应用程序中,这些是显示输入节点值的浅灰色行。
- outputRowRendererForView(view: SNView, node: SNNode) -> SNOutputRowRenderer 返回输出行的渲染器。在我的演示应用程序中,这是节点小部件底部的深灰色行。
- nodeSelectedInView(view: SNView, node: SNNode?) 当用户选择一个节点时调用该方法。在我的演示应用程序中,它是我更新底部工具栏中的控件的时候。
- nodeMovedInView(view: SNView, node: SNNode) 当移动节点时调用此方法。这可能是一个保存状态的机会。
- nodeCreatedInView(view: SNView, position: CGPoint) 当用户在后台长按创建新节点时调用该方法。 ShinpuruNodeUI 不负责更新节点数据提供程序,因此这是一个向您的数组添加新节点的机会。
- nodeDeletedInView(view: SNView, node: SNNode) 当用户单击垃圾桶图标时调用此方法。同样,因为 ShinpuruNodeUI 完全负责表示,现在是从模型中删除该节点并根据需要重新计算其他节点值的时候了。
- relationshipToggledInView(view: SNView, sourceNode: SNNode, targetNode: SNNode, targetNodeInputIndex:Int) 当用户切换两个视图之间的关系时调用此方法,就像删除节点一样,您需要重新计算受影响节点的值.
- defaultNodeSize(view: SNView) -> CGSize 返回新创建的节点小部件的大小,以确保新的节点小部件很好地定位在用户的手指(或者可能是 Apple Pencil )下
在我的演示应用程序中,它是充当 nodeDelegate 的 视图控制器 。
因此,尽管 ShinpuruNodeUI 呈现您的节点及其关系并报告用户手势,但主机应用程序仍需负责相当一部分。幸运的是,我的演示应用程序包含启动和运行所需的一切的基本实现。
渲染器
节点小部件需要三种不同类型的渲染器,由上面的 SNDelegate 定义。我的演示项目包括 DemoRenderers 文件中所有三个的基本实现。
这三个渲染器应该是 SNItemRenderer、SNOutputRowRenderer、SNInputRowRenderer 的子类,并且至少实现一个 reload() 方法并覆盖 intrinsicContentSize()。当 ShinpuruNodeUI 需要渲染器更新自身时调用 reload() (在演示应用程序的情况下,这通常只是更新标签的 文本 属性。
实施计算器应用程序
我的演示计算器应用程序包含一个结构体 DemoModel ,它管理在值或节点间关系发生变化时更新其节点的逻辑。视图控制器在 DemoModel 实例和 ShinpuruNodeUI 实例之间进行调解。
当一个数字(红色)节点被选中并且它的值被底部工具栏中的滑块改变时,视图控制器的 sliderChangeHandler() 被调用。此方法仔细检查 ShinpuruNodeUI 是否具有选定的节点,更新该节点的值,然后调用 DemoModel 的 updateDescendantNodes() 方法。 updateDescendantNodes() 返回受此更新影响的所有节点的数组,然后将其传递回 ShinpuruNodeUI 以更新用户界面:
let shinpuruNodeUI = SNView()
Similarly, when a blue operator node (ie add, subtract, multiply or divide) is selected and the operator is changed, the selected node's type is changed and updateDescendantNodes() is executed followed by reloading the affected nodes:
let shinpuruNodeUI = SNView()
ShinpuruNodeUI 可以调用需要 DemoModel 更新其节点的委托方法 - 特别是删除节点或更改关系。为此, DemoModel 的 deleteNode() 和 relationshipToggledInView() 都会返回一个受影响的节点数组,这些节点会传递回 ShinpuruNodeUI:
let shinpuruNodeUI = SNView()
结论
ShinpuruNodeUI 仍在不断发展,但处于可以为各种用例提供基于 iOS 节点的应用程序基础的状态。它在节点关系的表示、实际数据的表示和计算之间提供了清晰的关注点分离。
完整的项目可以在 我的 GitHub 存储库 中找到。请留意 我的博客 或 Twitter 以获取更新!