继续上周的Demo,让FaceView委托其数据,然后增加一个手势识别,但这一次要在controller里处理。
如何构建一个应用程序,如何修改现有的应用程序有多个MVC,然后会做一个关于此的Demo。
视图控制器的生命周期,也就是一个控制器的存在和发生,有一个出现在屏幕上和离开屏幕的周期。
在FaceView.h里定义一个协议,叫做FaceViewDataSource。在委托的方法里,当我们获取数据和把一个东西委托给另一个东西的时候,几乎都会把自己传过去,同时委托也可以向我们问一些问题。为什么要有数据源属性?如果有人想控制笑脸程度,就得把自己设为FaceView的数据源。
FaceView.h文件代码:
#import <UIKit/UIKit.h> @class FaceView; // forward declaration for use in @protocol @protocol FaceViewDataSource - (float)smileForFaceView:(FaceView *)sender; @end @interface FaceView : UIView @property (nonatomic) CGFloat scale; - (void)pinch:(UIPinchGestureRecognizer *)gesture; // resizes the face // set this property to whatever object will provide this View's data // usually a Controller using a FaceView in its View @property (nonatomic, weak) IBOutlet id <FaceViewDataSource> dataSource; @end
任何我们添加更多东西的时候,而且是要去另一个界面,我们需要一个新的MVC。不能有一个单一的MVC去支持多个界面上的视图,MVC一次只能控制一个屏幕或者更小,不会有一个MVC控制多个数据页,我们会把它分成多个单独的MVC。那如何切换两个MVC呢?要使用一个控制器群的控制器。
ios里有很多控制器群的控制器、控制器群的控制群,第一个要讲的是导航控制器。
导航控制器是控制器,这是一个UIViewController,一个UIViewController的子类。但它是一个特殊的控制器,因为它有一个outlet调用了它的根视图控制器,根视图控制器指向另一个MVC,指向这个控制器以明确另一个MVC。
根视图控制器是要去控制什么视图可以出现在导航控制器的白色区域,因此我们的第一个MVC的视图现在实际上是嵌入在导航控制器的视图里。导航控制器仍在控制顶部的这个小标题栏,它是导航控制器视图的一部分,但它从这个MVC拿出视图,并放入这个小矩形里,它将改变这个视图的bounds,以适应这个比之前MVC稍微小一点的白色矩形。
用Segue方法可以实现跳转至另一个MVC上,它的作用是pushes,把新的MVCpush到屏幕上,另一个滑出屏幕,所以它仍然存在,左侧的MVC仍然存在(它的视图仍存在,只是不在当前屏幕上)。
角落里有一个小按钮本质上是一个后退按钮。这个导航控制器,控制器群的控制器,它把它放在那了。点击按钮会回到之前的样子。右边的MVC可能会从堆上离开,左边的MVC还待在那,因为这个返回按钮,可以认为返回按钮有一个类似strong的指针指向它似的。
Segue是一个让它转移到另一个MVC的东西。创建Segue,它也是用拖动来控制,segues总是实例化一个视图控制器,永远不会用segue一个已经存在的视图控制器,Segue总是创建一个新的视图控制器,原因是因为这是我们想要的内存管理模型。我们希望当MVCS要出现在屏幕上时,它们得到创建,然后当它们离开屏幕时,它们会消失,除非有一个返回按钮回到它们,然后如果再按这个绘图按钮,它们被重新创建,所以视图控制器的生命有些短暂。
Segue有两个重要的属性是标识符,赋予segue名字。需要名字的原因,归结为两点。一,在代码中通过名字来执行segue,指定segue名字,就可以去执行该segue,此外通过segue名字来确定要准备什么。
另一个东西就是风格,segue有多种不同的风格:push意味着它是在导航控制器里,其他包括Modal、Custom。
这个箭头表示应用开始的地方。
如何在导航控制器里得到它呢?挑一个想要放进去的控制器,在xcode编辑菜单选择嵌入导航控制器。
另一个视图控制器将会出现,一个导航控制器,它把箭头移到自己身上。它后面有一个小指针,这不是segue,而是xcode里的一个特殊的小链接。导航控制器有一个指针指向根视图控制器,也就是它显示的第一个MVC,所以那个就是这个小东西。它有个小图标,像把两件事情连在一起。
运行时,导航控制器会成为第一个出现在屏幕上的东西,同时还有它的视图也出现在这个白色区域,它会有一个根视图控制器被设置为第二个视图控制器,它会把它放到这个白色的小空间,然后当我们按下按钮,显示其他视图控制器,它segue到另一个视图,并把它移进来。
这个用红色定义的区域,就是这个控制器的视图,它就好像是在导航控制器的堆栈的顶部,这是最近一次push上来的东西,所以一开始是根视图控制器。在视图控制器里有一个属性叫做title,这是一个NSString,如果你在导航控制器里设置了它,当有东西push进来的时候,它会问你的标题是什么,然后它会把得到的标题设为这里的标题,所以这个标题始终保持为被push上来的那个。底部有一个工具栏,一个UIViewController中的方法叫做toolbarItems,如果你设置了工具栏项目为UIBarButton组成的NSArray,当那个东西被push上来的时候,导航控制器会将这个工具栏放在底部,所以底部也是跟随中间的显示内容。上面角落里的后退按钮不是真的后退,它实际上只是持有之前的控制器的标题,就是如果我们按下它,将要切换到过去的东西。
把这个MVC入栈,后退按钮就出现了。它什么时候出栈呢?按后退按钮会把它弹出去,也可以通过调用方法popViewControllerAnimated来在程序里把它弹出去。
- (void)popViewControllerAnimated:(BOOL)animated;
什么时候需要在程序里这么做?不经常,但这里有一个例子:
- (IBAction)deleteCurrentRecord:(UIButton *)sender { // delete the record we are displaying // we just deleted the record we are displaying! // so it does not make sense to be on screen anymore, so pop [self.navigationController popViewControllerAnimated:YES]; }
此代码属于屏幕上的那个带删除按钮的视图控制器,这可能是这个删除按钮的target action方法,所有UIViewController都有这个属性。如果你问它这个属性的值,如果这个UIViewController当前正在导航控制器里,不管是否已经被push上来,它都会返回该导航控制器给你,让你可以做比如popViewController之类的事情,所以你总是可以通过这个属性来判断是否在导航控制器里。还有其他类似的属性,比如self.splitViewController,它可能也有同样的效果,会告诉你你在什么样的环境。
其他种类的segues:在UISplitViewController里有replace的segues,还有popovers(都只针对ipad);我们将讨论modal,这是一种特定的方式在屏幕上展现视图,所以它会类似遮住你的应用程序,直到有人回应它(比如在通讯录里添加新条目,它会跳出一个屏幕,直到点击完成或取消)。modal表达方式可能被滥用,它能很简单地把东西放到屏幕上,陷在一个模式里对用户来说很沮丧不要轻易进入模式。
现在已经知道如何通过点击一个按钮触发一个segue,它触发了segue的事情发生,但如何在代码里停掉它呢?我们知道,这些segue有名字,不管之前在inspector里为它起得是什么名字,所以其实可以写一行代码来触发这个segue。现在为什么会想这么做呢?为什么要在代码里执行segue,而不是xcode把这个按钮给关联起来就好呢,这儿有一个为什么的例子,想要segue是有条件的。
- (IBAction)rentEquipment { if (self.snowTraversingTalent == Skiing) { [self performSegueWithIdentifier:@“AskAboutSkis” sender:self]; } else { [self performSegueWithIdentifier:@“AskAboutSnowboard” sender:self]; } }
segues只存在于storyboard,storyboard只在ios5才有。
在应用程序里,在它去到屏幕上之前,只会发生一件事,意思是幕后做了很多事情。它创建了视图控制器实例,因为segue总是实例化一个新的视图控制器实例,所以当它发生时,会把所有的outlet、action等东西都连接起来,再调用这个控制器的方法prepareForSegue。
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { if ([segue.identifier isEqualToString:@“DoAParticularThing”]) { UIViewController *newController = segue.destinationViewController; } }
prepareForSegue会被发送给你,所以因为你自己的一些action,这个segue要去到屏幕上,无论它是被push上去的,还是自己出现的。然而,segue即将发生,你需要能够准备好要出现在屏幕上的视图控制器。
你需要考虑你正segueing过去的MVC,比如一个视图,它需要遵从所有视图都需要遵从的规则,主要规则之一是它不能回过来主动和你对话,视图向你发话只有两个途径,target action不能在这里使用(target action没有任何意义,因为这个东西已经接管了屏幕),但可以使用委托。所以如果你真的需要MVC来反向通信的话,它需要使用委托,你不能把这个MVC的指针直接返回给你。现在你可以设置自己为其委托,它会返回一个指针,但它是一个不可见的指针,它是一个id协议,与一个知道你所push的是什么类的指针是不同的。
prepareForSegue实现是很容易的,通常你要去查看segue的标识符,因为你可能有多个按钮,他们都会调用到prepareForSegue,所以要去看标识符才知道到底当前是哪个,然后才能做任何必要的准备工作。MVC唯一回过来向你发起对话的途径就是委托,但大多数情况它不是要去跟你对话的。
也可以自行创建一个视图控制器并放到屏幕上,这样做的方式是,给视图控制器一个名字。你可以使用storyboard里的这个方法instantiateViewControllerWithIdentifier来创建控制器,只要指定标识符,它会创建,然后为控制器连接好所有的outlet和按钮。你通常会在UIViewController里通过调用self.storyboard来获取storyboard,但UIViewController里的self.storyboard只适用于来自storyboard的视图控制器,self.storyboard是该视图控制器起源的那个storyboard。所以不能使用这个机制来实例化其他storyboard创建的视图控制器,除非那个storyboard也是你创建的。
- (IBAction)doit { DoitViewController *doit = [self.storyboard instantiateViewControllerWithIdentifier:@”doit1”]; doit.infoDoitNeeds = self.info; [self.navigationController pushViewController:doit animated:YES]; }
主要内容: