滑动过渡和快速浏览– Christian Tietze –中

(在https://christiantietze.de/posts/2017/01/reswift-swipe-transitions/中阅读格式正确的故事)

在昨天的一次客户会议上,我们尝试找出当ReSwift是应用程序状态的唯一真实来源时,如何通过向左/向右滑动来为场景过渡设置动画。 应用进入什么状态? 您如何制作动画? 出于某种原因,转换百分比是否应该成为应用程序状态的一部分? (剧透:没有)

滑动首先具有挑战性,因为从视图控制器AB的这种交互式过渡需要同时显示两者:滑动时,需要在视觉上“拖动” B。 添加自定义导航控件时,最终会得到一个主视图控制器,该控制器包含一个子视图控制器以显示实际的表视图(下图中的绿色框)。 这些表格视图应从左侧或右侧刷入并触发导航更改。

在此示例中,用户看到给定日期的数据。 她应该使用滑动和导航按钮自由导航到前一天和后一天,直到时间的开始或结束。 (或我们的数据限制,以先到者为准。)

让我们分析一下实现这一点。

单状态替换,无过渡

在没有过渡的静态世界中,只有导航栏的“上一个”和“下一个”按钮(以蓝色表示)会触发导航更改:您点击该按钮,请求来自服务器的新数据,也许您会显示一个加载指示器,然后替换UITableDataSource的内容。

现在,如果您使用ReSwift,则当前可见的数据集合将成为您应用状态的一部分。 保持简单,表格视图的单元格将显示文本。 状态看起来像这样:

  struct AppState:ReSwift.StateType { 
var内容:[String]
}

想象一下,您有一些动作,减速器以及对导航更改做出的适当反应。 (这本身可能是一个挑战,并且可能是另一天的话题。提示:您将需要“更改日”操作来触发网络请求,并需要“替换数据”操作来更新内容。)

为了显示最新状态更改,您设置了一个Presenter,它是ReSwift.StoreSubscriber。 当您从服务器接收数据并替换AppState.contents时,将调用newState回调。 然后,此字符串数组将传递到用户界面进行显示。 我们将该方法称为updateView(linesOfText :)。

这是一个体系结构上的注释: updateView(linesOfText 🙂方法,我想要演示的调用者应该由视图控制器公开。 反过来,这可以委派给其当前的视图控制器,该控制器处理表的实际显示。 但是,从长远来看,将表示者(表示层(!)外部的服务对象)耦合到子视图控制器可能会对您造成伤害。 主视图控制器是整个组件的外壳,因此它负责公开可用的接口。 内部组件的数量和对其的委派是其他对象不关心的实现细节。 (您一会儿就会明白为什么。)

此设置非常简单。 AppState更改通过Presenter的流程,该Presenter会在必要时创建视图模型,然后将其传递到其视图组件。 结果,UITableView重新加载了新数据,您就完成了。

那是最准方法。 在添加交互式过渡之前,让我们首先使其具有更高的响应速度。 现在,每次点击按钮都会触发一个网络请求,该请求使用户的交互停止。 “走走走走”导航在孩子们中并不普遍,因此我们将在下一步中预取隔天的数据。

预取相邻天的数据

在表示层中,我想象情况会有所变化,如下所示:

从上面对简单方法的更改是:

  1. 主视图控制器具有3个子视图控制器,而不是1个。所有子视图控制器均已准备就绪,可以显示。
  2. 轻按按钮现在有两件事:像以前一样触发“更改日”导航操作, 立即将正确的子视图控制器放在顶部。
  3. 为了使所有这些成为可能,Presenter组装了一个具有3个内容数组而不是1的ViewModel。

视图模型仍然非常简单:

  struct ViewModel { 
让previousDayData:[String]
让currentDayData:[字符串]
让nextDayData:[String]
}

主视图控制器在新的updateView(viewModel:ViewModel)方法中接受此方法。

  MasterViewController类:UIViewController,视图{ 
让previousDayViewController:ChildViewController
让currentDayViewController:ChildViewController
让nextDayViewController:ChildViewController

// ...设置子视图控制器等...

func updateView(viewModel:ViewModel){
//将每个数据数组分配给其子视图控制器
prepareChildViewControllers(viewModel:viewModel)

//将“当前日期”放在顶部,隐藏其他
resetTopmostViewController()
}
}

应用程序状态也必须反映此整体更改,因此Presenter可以首先组装视图模型。 我称这个三元组为模型数据的“甲板”。 在这个人为的示例中,模型数据与视图模型一样简单。 通常,真实模型数据会更复杂,并且会使用更多自定义类型,并且在视图模型中,您会使用易于显示的类型。 因此,尽管这里的ViewModel和Deck都同样简单,但我想强调的一点是,为模型类型提供合理的名称,而不仅仅是在应用程序最核心的UI层中重用ViewModel类型。

  struct Deck { 
让上一个:[String]
让当前:[String]
接下来:[String]
}

struct AppState:ReSwift.StateType {
VAR Deck:甲板
}

最后,组装所有内容的演示者:

 协议视图{ 
func updateView(viewModel:ViewModel)
}

演示者类:ReSwift.StoreSubscriber {
让视图:视图

func newState(_ state:Deck){
让viewModel = ViewModel(
previousDayData:state.previous,
currentDayData:state.current
nextDayData:state.next)

view.updateView(viewModel:viewModel)
}
}

更改的影响:“上一个” /“下一个”内容更改立即发生,尽管请求可能需要几秒钟,但用户已经可以与预取的数据集进行交互。

最初,重置和替换当前可见的子视图控制器及其内容将感觉不正确。

将会发生什么:

  • 用户点击“上一个”
  • 立即显示另一个带有新数据的表视图,多么令人愉快!
  • 用户向下滚动一点
  • (同时,请求已完成,状态更新立即触发)
  • 视图闪烁并重置,表格滚动到顶部,显示与以前相同的数据; 嗯?!

在视图层中提供即时转换,然后从应用程序的核心中对其进行硬重置会导致用户交互问题。 需要花费一些努力才能使此过程顺利进行。 简而言之,这就是我要做的事情:

  • 使用Dwifft之类的数组来计算屏幕上显示的内容对传入的viewModel.currentDayData的更改; 如果数据不是陈旧的,则无需重新加载表视图。 如果过时,Dwifft将提供增量更新 ,这意味着您将获得生气勃勃的插入和删除表更改,而不是完全重置。
  • 调用updateView时,切换子视图控制器引用。 在此更改之前,previousDayViewController变为可见。 但是没有“以前的”视图控制器。 现在重置哪个位于顶部可以变成“将currentDayViewController替换为previousDayViewController”,然后执行内容区分。 这可能比将可见表的控件转移到currentDayViewController或类似控件要容易。 并且您将需要使currentDayViewController指向最顶层的子视图控制器,以使导航按钮再次起作用。

添加滑动手势

我不是交互式视图控制器转换的专家。 所以我对这部分的建议真的很粗略:

  • 只有您的视图图层负责处理滑动过渡。 它不应泄漏到您应用的状态或其他状态。 这只是一种特殊的动画。
  • 在过渡期间,除了屏幕内容动画外,什么都没有发生。
  • 手势和过渡完成后,说“从左向右滑动”以获取前一天的数据,然后才触发状态更新。
  • 轻扫过渡的完成与轻按“上一个”或“下一个”按钮相似,为100%。

通过上一步的设置,这应该已经是您需要做的所有事情。

由于用户希望在一段时间内不间断地滑动,因此您可能希望在两个方向上都增加Deck和ViewModel的范围:您可能希望预取3个或更多数据,而不是预取1组数据。 或者默认情况下,您在两个方向上都预取2(总共2 +1 + 2 = 5),然后在用户滑动的方向上将其更改为4(总共2 +1 + 4 = 7)。

最后,您将要确保无论预取多少,请求-响应周期都不会花费太长时间,否则交互将再次停止。 在另一个迭代中,您可以优化流程并调度更详细的状态更新:首先,请求新的“当前”状态,并确保数据迅速达到您的AppState,然后为直接邻居触发另一个请求; 然后再换一个屏幕。

结论

事实证明,“在屏幕之间滑动”归结为:

  1. 屏幕转换:在预取的数据集视图之间切换以提高响应速度。
  2. 调整应用状态以代表您的应用需求,而不是域模型; 如果您需要预取数据,请将数据集存储为状态的一部分。
  3. 交互式过渡和动画过渡仅是UI层职责的一部分,并不影响状态。

我想假设一下:每当您看到“动画”一词时,它只是一个演示细节。 它应该由UI层专门管理。 如果您的域模型(最内层的核心)知道动画的进度,那么您会在整个过程中陷入困境。 同样,应用程序状态(如果需要,则介于模型和UI之间的中介层)不应了解动画,而只能了解离散状态。

现在证明我错了!

通过Christian Tietze的工作日志 http://ift.tt/2iGRuEj