Navigator 是一个用于跟踪应用程序状态并在视图之间切换的 URL 路由器。与使用 key/:id -> view
映射提供孤立转换的现有解决方案不同, Navigator 将 URL 区分为一系列更新,可以执行任何数量的堆栈更改和执行任意动画。这提高了视图控制器和动画模块化,并减轻了实现深链接或基于状态的分析等功能的工作量。
您可以使用 CocoaPods 安装 Navigator。
pod 'Navigator', '~> 0.3'
Navigator 有两个核心类: NAVRouter
和 NAVViewController
。路由器声明从字符串键到视图控制器类的映射,由这些键组成的 URL 将视图推入堆栈、以模态呈现它们或执行任意动画。
让我们分析一个例子。这是一个电视应用 URL,它有一个主屏幕、一个在主屏幕之上推送的节目详细屏幕,以及在一个模态中呈现的视频播放器。类似于以下内容:
television://home/show::2?video=v
分析如下:
television
是路由器的方案,并且所有 URL 都必须有方案。home
和 show
是 URL 的组件,它们对应于导航堆栈上的视图。在这种情况下,home
是根视图,而 show
是可见视图。show
还有一个数据元素 2
。这可以是一个字符串,在这种情况下它是一个数字 id。这些在视图出现在屏幕之前传递给视图,以便它有时间进行准备。video=v
是这个 URL 的唯一参数。参数是键值对,其中键是视图的键,值是视图的状态。在这种情况下,视图是 video
,其状态是 visible
。声明一个新的 NAVRouter 子类,并在实现中导入 NAVRouter_Subclass.h
。
最重要的是路由器需要一个方案,您可以 通过实现 +scheme
来指定一个方案。
+ (NSString *)scheme
{
return @"demoapp";
}
路由器还需要一些路由,并且您可以使用 -routes:
定义其初始路由。
- (void)routes:(NAVRouteBuilder *)route
{
route.to(@"red").controller(RedViewController.class);
route.to(@"purple").controller(PurpleViewController.class).as(NAVRouteTypeModal);
}
此方法接受一个NAVRouteBuilder
实例,您可以用它来构建路由。通常,路由是通过键创建的to
字符串,并给出目的地对象,而在所有这些情况下,目的地对象是传递给controller
的视图控制器类(继承自NAVViewController
)。
路由构建的详细信息将在后面介绍。可以随时使用-updateRoutes:
将路由添加到/从路由器中删除。
路由器需要创建视图控制器,而控制器自身定义了它们应该如何被创建。默认情况下,路由器会从一个Storyboard及Storyboard ID创建NAVViewController的子类。
如果您对此表示同意,则至少在您的子类中使用+storyboardName
指定Storyboard名称。
+ (NSString *)storyboardName
{
return @"MainStoryboard";
}
然后路由器会尝试使用控制器类名的字符串版本作为默认ID从这个Storyboard中创建视图控制器。要自定义此行为,重写+storyboardIdentifier
。无论您的ID是什么,确保在IB中的“身份检查器”面板中为每个视图控制器指定它。
如果一个控制器需要完全自定义的实例化,它可以通过重写+instance
来绕过Storyboard加载过程。
+ (instancetype)instance
{
return [[self alloc] initWithNibName:NSStringFromClass(self) bundle:nil];
}
要启用路由器正常运行,最后的步骤是将它与一个导航控制器关联起来。您可以通过显式设置它来做到这一点。
[DemoRouter router].navigationController = self.navigationController;
或者,如果路由器没有导航控制器并且您将其delegate
设置为一个既是UINavigationController
的实例或有一个-navigationController
方法的实例,路由器将尝试使用它作为其导航控制器。
可以通过从NAVAnimation
中进行子类化并使用该动画的实例创建一个路由来将自定义动画钩接到路由器上。实际视图更新由-updateIsVisible:animated:completion:
驱动。当路由变化引起动画更新时,路由器会调用此方法。
例如,要实现一个简单的侧边菜单动画,您可以定义以下类
@implementation MenuAnimation
- (void)updateIsVisible:(BOOL)isVisible animated:(BOOL)animated completion:(void (^)(BOOL))completion
{
[UIView animateWithDuration:0.4f delay:0.0f usingSpringWithDamping:0.75f initialSpringVelocity:0.0f options:0 animations:^{
self.animatingView.transform = CGAffineTransformMakeTranslation(isVisible ? 300.0f : 0.0f, 0.0f);
} completion:completion];
}
@end
然后向该动画的实例添加一个路由来显示它
MenuAnimation *menuAnimation = [MenuAnimation new];
menuAnimation.animatingView = self.containerView;
[[DemoRouter router] updateRoutes:(NAVRouteBuilder *route) {
route.to(@"menu").animation(menuAnimation);
}];
如果动画需要在路由器之外发生,例如通过交互式手势,也应该通过NAVAnimation
子类来驱动其逻辑。在这种情况下,当交互完成时,动画应该适当地设置动画的isVisible
属性。
您的路由器子类通过+router
获得一个隐式(并且非线程安全)的共享实例,您可以使用它来执行转换、更新路由等操作。
如果路由器不能在视图之间转换,那么它就没有多少用处。它指定了一个用于启动转换的-transition
方法,该方法返回一个灵活的构造器,用于构建和启动路由变化。让我们分析一些示例转换。
[DemoRouter router].transition
.root(@"red")
.animated(NO)
.start(nil);
此转换将路由器转换到一个新的root
视图,从@"red"
映射而来,并丢弃堆栈上的任何其他视图。您可能第一次启动应用程序时会做类似的事情。
-start
方法完成构建并尝试立即运行转换。它接受一个完成块,当转换完成时调用,或者如果已经有一个正在运行的转换,立即以错误调用。
[DemoRouter router].transition
.push(@"green")
.present(@"purple")
.enqueue(nil).
过渡可以从多个URL变化中组合而成。这个过渡将@"green"
视图推入栈中,然后完成时以模态的方式展示@"purple"
视图。
这种方法也使用-enqueue
而不是-start
,它将等待所有正在运行或排队的过渡完成后再解析。如果没有这样的过渡存在,它将立即开始。
您还可以在过渡期间传递数据字符串、对象(例如,模型)和处理程序,这些数据将被传递到视图。
[DemoRouter router].transition
.push(@"red")
.data(demoModel.identifier)
.object(demoModel)
.start(nil)
这些数据通过-updateWithAttributes:
传递给NAVViewController
或NAVAnimation
的子类。此方法接收一个包含相关过渡数据的NAVAttributes
实例,并且之后会被丢弃。
- (void)updateWithAttributes:(NAVAttributes *)attributes
{
[super updateWithAttributes:attributes];
NSLog(@"%@: %@", attributes.data, attributes.userObject);
}
虽然最初是在NAVRouter
子类的-routes:
方法中定义的,但可以使用-updateRoutes:
在任何时候更改路由。此方法接受一个被传递并包含NAVRouteBuilder
实例的块。
路由可以配置动画、控制器类别和自定义类型。
[[DemoRouter router] updateRoutes:^(NAVRouteBuilder *route) {
route.to(@"video").controller(VideoViewController.class).as(NAVRouteTypeModal);
route.to(@"menu").animation(menuAnimation);
}];
如果给路由传入了控制器或动画,它的类型将被隐式地设定为NAVRouteTypeStack
或NAVRouteTypeAnimation
。可以使用-as
进进一步指定,例如在NAVRouteTypeModal
的情况下。这允许展示视图控制器,而不是推送。