- 浏览: 2203917 次
- 性别:
- 来自: 北京
文章分类
- 全部博客 (1240)
- mac/IOS (287)
- flutter (1)
- J2EE (115)
- android基础知识 (582)
- android中级知识 (55)
- android组件(Widget)开发 (18)
- android 错误 (21)
- javascript (18)
- linux (70)
- 树莓派 (18)
- gwt/gxt (1)
- 工具(IDE)/包(jar) (18)
- web前端 (17)
- java 算法 (8)
- 其它 (5)
- chrome (7)
- 数据库 (8)
- 经济/金融 (0)
- english (2)
- HTML5 (7)
- 网络安全 (14)
- 设计欣赏/设计窗 (8)
- 汇编/C (8)
- 工具类 (4)
- 游戏 (5)
- 开发频道 (5)
- Android OpenGL (1)
- 科学 (4)
- 运维 (0)
- 好东西 (6)
- 美食 (1)
最新评论
-
liangzai_cool:
请教一下,文中,shell、C、Python三种方式控制led ...
树莓派 - MAX7219 -
jiazimo:
...
Kafka源码分析-序列5 -Producer -RecordAccumulator队列分析 -
hp321:
Windows该命令是不是需要安装什么软件才可以?我试过不行( ...
ImageIO读jpg的时候出现javax.imageio.IIOException: Unsupported Image Type -
hp321:
Chenzh_758 写道其实直接用一下代码就可以解决了:JP ...
ImageIO读jpg的时候出现javax.imageio.IIOException: Unsupported Image Type -
huanghonhpeng:
大哥你真强什么都会,研究研究。。。。小弟在这里学到了很多知识。 ...
android 浏览器
如果你想了解更多Storyboard的特性,那么你就来对了地方,下面我们就来接着上次的内容详细讲解Storyboard的使用方法。
在上一篇《Storyboard全解析-第一部分》中,我们介绍了如何使用storyboard来制作多种场景和如何将这些场景链接起来,我们还学习了如何自定义一个表格视图。
接下来这部分,也是最后一部分,我们将讲解联线(segue),静态单元格等内容,我们还将加入一个选手详细内容页面,和一个游戏选择页面。
Segues的介绍
现在,让我们创建一个场景使用户可以自己增加新的选手进入列表。
在Players界面中拖入一个Bar Button,放置在导航栏的右侧,在属性监视器中将他的Identifier改为“add”,这样他就会显示一个加号的按钮,当用户点击这个按钮时,他就会弹出一个新的场景让用户对新的内容进行编辑或添加。
在编辑器中拖入一个新的Table View Controller,放置在Players场景的右边,然后按住ctrl,拉动加号键到新的场景中,这样,这个场景就会自动和这个按钮建立联系,从而自动归入Navigation View Controller中。
放开鼠标之后,会出现如下选项:
选中Modal,你可以注意到出现了一种新的箭头形式:
这种链接形式被官方称为segue(pronounce: seg-way),我叫它联线,(其实是转换的意思)这种形式的联线是表示从一种场景转换到另外一种场景中,之前我们使用的连接都是描述一种场景包含另一种场景的。而对于联线来说,它会改变屏幕中显示的内容,而且必须由交互动作触发:如轻点,或其他手势。
联线真正了不起的地方在于:你不再需要写任何代码来转入一个新的场景,也不用在将你的按钮和IBAction连接到一起,我们刚才做的,直接将按钮和场景链接起来,就能够完成这项工作。
运行这个app,按下 + 键,会发现出现了一个新的列表。
这种叫做 “modal” segue(模态转换),新的场景完全盖住了旧的那个。用户无法再与上一个场景交互,除非他们先关闭这个场景,过一会我们会讨论 push segue,这种segue会把场景推入导航栈。
新的场景现在还没有什么用,你甚至不能把他关闭呢。
联线只能够把你送到新的场景,你要是想回来,就得使用delegate pattern,代理模式。我们必须首先给这个新的场景设置一个独有的类,新建一个继承UITableViewController的类,命为PlayerDetailsViewController。
为了把它和storyboard相连,回到MainStoryBoard,选择新建的那个Table View Contrller,将他的类设置喂PlayerDetailViewController,千万不要忘记这一步,这很重要。
做完这一步之后,把新场景的标题改为“Add Player”,分别加入“Done”和“Cancel”两个导航栏按钮。
修改PlayerDetailsViewController.h 如下:
这会声明一个新的代理机制,当用户点击Cancel或者done按钮时,我们将用它来交互Add Player场景和主场景通讯。
回到故事版编辑器,将Cancel和Done按钮分别与动作方法连接,一种方式是,按住Ctrl拖动到ViewController上,之后选择正确的动作。
在 PlayerDetailsViewController.m,加入如下代码:
这是两个导航栏按钮要使用的方法,现在只需要让代理知道我们刚才加入了代码,而真正关闭场景只是代理的事情。
一般来说一定要为代理制定一个对象参数,这样他才知道向那里发送信息。
不要忘记加入Synthesize语句。
现在我们已经为PlayerDetailsViewController设置了一个代理协议,我们需要将这个协议的实现方法(implement)写在什么地方,很明显应该写在PlayerViewController因为这个vc代表了Add Player场景。在PlayersViewController.h中加入如下代码:
并在PlayersViewController.m的结尾加入:
目前这个代理方法只能够跳转到这个新的场景中,接下来我们来让他做一些更为强大的事情。
iOS 5 SDK中新添加的dismissViewControllerAnimated:completion: 方法可以被用来关闭一个场景。
最后还有一件事情需要做,就是Players场景需要告诉PlayerDetailsVC他的代理在哪里,听上去这种工作在故事版编辑其中一拖就行了,实际上,你得使用代码才能完成。
将以下方法加入到 PlayersViewController 中
当使用Segue的时候,就必须加入这个名叫 prepareForSegue 的方法,这个新的ViewController在被加载的时候还是不可见的,我们可以利用这个机会来向他发送数据。
请注意,这个segue的最终目标是Navigation Controller,因为这个是我们链接在导航栏上的按钮,为了获取PlayerDetailsViewController实例,我们必须通过NavController的属性来获取。
试着运行一下这个应用,单击 + 键,然后试着关闭Add Player场景,仍然不管用。
这是因为我们没有给Segue指定一个identifier,而parepareForSegu需要检查AddPlayer的身份证,这是必须的,因为你有可能会同时使用多个联线。
为了解决这个问题,进入Storyboard的编辑器,点击Players场景和NavgationViewController场景之间的联线,你会注意到与这个连线相关的按钮会自动亮起来。
在属性监视器中,将Identifier设置喂“AddPlayer”
如果这是你再次运行这个应用,点击“Cancel”或者“Done”按钮,这个场景就会自动关闭并且返回到上一级场景。
注意:从modal场景调用dismissViewControllerAnimated:completion方法是我们在这里使用的,但是这并不意味着你必须这样做。但是,如果你不是代理来完成这个关闭窗口的工作的话,唯一需要注意的是,如果你之前使用了[self.parentViewController dismissModalViewControllerAnimated:YES] 语句来关闭窗口的话,那么这个语句就不会正常工作了。
顺便说一下,属性检查器中有一个Transition的选项,在这里你可以选择场景转换是的动画效果。
试着运行一下,看看那种动画你最喜欢吧,但事情不要改变Style这个选项,如果你改变了,这个app可能会crash哦。
我们接下来在这个教程中还会用到几次代理方法,下面我们来列一下为了完成一个连线,你需要做的几件事情。
我们在这里必须使用代理,是因为根本没有反向联线这种东西,当sugue被启动之后,他将会创造出一个目标场景的新实例。你当然可以做一个从目标场景回到原始场景的联线,但是结果可能与你希望的大相径庭。
距离来说吧,如果你做一条从cancel按钮回到原始场景的连线的话,他并不会关闭当前场景并返回原始场景,而是会创建一个原始场景的新实例,这种情况会不停循环,知道把内存耗尽为止。
所以请记住:segue只用于打开新的场景。
静态单元格
当我们全部完成之后,Add Player场景会看上去象下面的一样:
这是一种分组表格视图,但是不同的是,我们并不需要为这个表哥创建一个数据源,我们可以在故事版编辑器中直接设计这个视图,而不需要重写cellForRowAtIndex方法,使得我们可以这样做的秘诀就是静态单元格。
选中Add Player场景,之后在属性检查器中,将Content属性改为StaticCell,将Style to Grouped属性修改为2。
当你修改Section属性时,编辑器会复制一个现有的组。你也可以自己选中一个组后选择Duplicate。
我们的这个场景每个组只需要用一个行,所以选中上面的那个行之后删除。
选中顶行,修改Header的值为:“Player Name”.
拖一个新的Text Field进入这个组的单元格里,把它的边界删除掉,使用System 17字体,取消Adjust to Fit选项。
我们现在在PlayerDetailsViewController中使用Assistant Editor这个Xcode 4.x的新特性来创建一个输出口给这个Text Field,在工具栏的按钮中打开Assistant Editor,那玩意看起来像个外星人,我指的是按钮。
选中text field,按住Ctrl,将他拖到打开的文件之中。
放开鼠标,会出现一个选单。
将这个新的书出口命名为nameTextField,在你确定链接之后,Xcode会自动创建下列代码:
他还会自动创建Synthesize语句,并同时在viewDidLoad文件中创建方法。
永远别在动态表格中使用这种拖来拖去的方法,但是对于静态单元格来说就OK,对于每个静态单元格来说都必须创建一个新的实例。
将第二个组的静态单元格的Style设置为Right Detail,这将会创建一个标准的单元格,把左侧的label的内容修改为Game,设置一个Disclosure Indicator,为右侧Detail的label设置一个输出口。
最终的设计完成后是这样的:
当你使用静态单元格的时候,你的Table View Controller就不需要一个数据源了,但是因为我们使用了Xcode的模板来创造PlayerDetailsViewController这个类,他里面仍然有一些默认的数据源设置代码,让我们来删除之。在以下这个标志
和这个标志之间的代码全部删除。
现在运行这个App,效果不错吧,请注意我们不但一行代码也没写,还删除了好些。
但是我们并不能够完全避免写任何代码,你可能已经注意到了,在文本框和单元格周围有一些空间,用户在完成编辑之后单击这些区域并不会结束键盘什么的,怎么避免这个问题呢?用下面的代码代替tableView:didSelectRowatIndex方法。
这些代码就是说:如果用户点击第一个单元格后我们激活text field控件,这虽然是细节,但是细节决定成败。
同时你也需要在属性检查器的Selection Style选项改为None。
OK,我们的设计全部完成了。
增加一个选手吧
现在我们暂时先忽略Game这一行,先让用户能够编辑选手的情况之后再说。
当用户单击Cancel键的时候,不管作出什么修改都会被弃置,场景也会关闭并返回上一级菜单。这一块的程序我们已经做好了,也就是我们刚才做得一个代理方法,它接收到did cancel这个方法之后就会关闭这个视图。
但是当用户单击“Done”这个按钮时,我们应该创建一个新的选手项目然后加入他的属性,之后我们还需要通知代理器我们新增了一个选手,以便它能够更新上一级菜单。
在 PlayerDetailsViewController.m,把完成的方法改成:
这需要我们引进Player的头文件:
这个完成方法会创建一个新的Player实例,并把它发送给代理器,由于目前代理器还没有这个方法,所以我们需要在PlayerDetailsViewController的头文件中修改如下代码:
这个“Did Save”的方法的声明没有了,我们加入一个“didAddPlayer”方法。
下面我们需要在执行文件中加入执行的方法,打开PlayersViewController.m,加入:
第一个语句向players的数组中加入新的Player对象,之后他会通知表格视图:一个新的行已经被创建,这是因为table view和他的数据源必须一直是同步的才行,我们其实也可以使用[self.tableView reloadData]这个语句,但是重新创建一个单元格会有随之而来的动画,看起来更好看一些。UITableViewRowAnimationAutomatic是一个iOS 5的新特性,使各行自动选择合适的动画效果出现,非常好用。
现在试试看,你应该可以使用按钮加入新行到表视图中了。
如果你已经开始担心storyboard的性能了,那么不用担心。就算是将所有的场景都一块载入的话,也不会消耗多少资源的。storyboard不会一下子加载所有的ViewController,而是会加载起始场景,在这里是Tab View,再从起始场景加载其他与起始场景相关的场景。
但是其他场景知道联线到他们之前是不会被加载的。而这些场景在你返回之后都会卸载,所以只有当前场景会在内存中,就像你之前在用分开的nib文件一样的。
我们通过实验来看一看。在PlayerDetailsViewController.m中加入下面的方法:
我们重写了initWithCoder和dealloc方法,使得debug控制台输出一个很长的信息。这时候运行这个app,你会发现除非按下segue的按钮,否则新的场景不会被初始化,放心了吧。
还有一件关于静态单元格的事情需要注意,那就是他们只能够在UITableViewController的子类下使用,如果他的父类不是UITableViewController,Xcode会提示下面的错误:
“Illegal Configuration: Static table views are only valid when embedded in UITableViewController instances”.
原型单元格,虽然可以在普通的View Controller中使用,但是不能够在Interface Builder中使用,
很少会出现有人会想要在一个表中用静态单元格和原型单元格混合起来,目前iOS SDK还不能很好的支持这种方法。
游戏选择器场景
在Add Player场景中单击Game的单元格会打开一个新的场景,让你能够从一个列表中选择一个游戏,这意味着我们需要加入一个新的表格视图,不过不同的是,我们这次会使用push到Navigation的栈之中,而不是直接跳转。
拖拉一个新的TableViewController到编辑器中,在Add Player场景中选择一个单元格按住ctrl键拉到新的场景中,创建一个连线,选择Push,之后把新segue的identifier命名为“PickGame”。
双击导航栏,修改标题为“Choose Game”,修改原型单元格的Style为Basic,修改他的Identifier为“GameCell”,我们的试图设计就到这里。
新建一个UITableViewController的子类,命名为GamePickerViewController,在storyboard中也要设置好哦。
首先我们给这个新的场景一些数据来显示,在GamePickerViewController.h中加入下列变量:
之后转到GamePickerViewController.m,在viewDidLoad方法中加入数组的内容。
由于在viewDidLoad方法中加载了数组,所以需要在viewDidUnload中卸载之。
将模板中的数据源方法修改为如下代码:
这样我们就完成了家在数据源的方法,这时候运行这个app,之后在Add Player场景中单击Game栏,就会转入这个视图了,但这时候单击这里的单元格并不会有什么作用。
这时候,由于我们使用push方式将这个场景推进了Navigation的栈中,所以这时候我们单击返回按钮就会自动返回到上一级界面。不错吧!
当然了,如果这个场景不输送任何数据回到上一级场景的话,那他就什么用也没有了,所以我们要创造一个新的代理器来完成这项任务。在GamePickerViewController.h中加入:
我们加入了一个代理方法,其中只有一个方法和一个用于乘放目前选择的游戏的名字的属性。
现在,我们修改GamePickerViewController.m的开头:
这些代码新建了一个数组,一个选中项目的整数,并且synthesize了这些项目。
在viewDidLoad中加入如下代码:
选中的游戏名字会设置在self.game中,这里我们设置我们在表格中到底选中了哪个游戏。在这里,在场景加载之前必须首先填充self.game,由于我们在viewDidLoad之前设置了prepareForSegue这个方法,所以我们现在这么做没问题。
修改cellForRowAtIndexPath方法:
这个方法会在选中的项目的右边加上一个选中的对勾。
将 didSelectRowAtIndexPath 修改为:
首先我们取消之前点击的那一行的选中状态,这将把它的蓝色变会正常的白色,之后将对勾删除掉,之后将对勾放置在刚刚选中的那一行上,最后,我们把选中的那一行返回给代理。
现在运行这个app测试一下效果,单击一个game的名字,将会出现一个对勾,单击另一个行,对勾的位置就会改变,但是返回上一级菜单之后发现我们的修改没有保存下来,为什么?因为我们还没有将代理真正的链接起来。
在 PlayerDetailsViewController.h 中,引入
之后在 @interface 行之后加入:
在PlayerDetailsViewController.m加入prepareForSegue方法:
这和我们之前做过的很相似,但是这次的目标view Controller使game picker场景了,请记住,这个方法必须在GamePickerViewController初始化之后但是还没有加载view的时候调用。
“game”变量是新的,我们必须声明他:
我们使用这个变量来记录到底选择了哪个Game,我们得给这个String设置一个默认值,可以用initWithCoder方法来完成。
如果你之前是用过nibs的话,那么initWithCode可能会对你很熟悉,这部分在storyboard是一样的。
修改 viewDidLoad 方法如下,以便单元格能够显示选中的Game名称:
最后要做的就是执行代理方法:
这行代码很好懂,我就不多讲了。
我们的结束方法将会把选中的游戏的名字加入到新建的Player对象中。
OK,到这里我们就完成了游戏选择器的场景,不错吧。
在上一篇《Storyboard全解析-第一部分》中,我们介绍了如何使用storyboard来制作多种场景和如何将这些场景链接起来,我们还学习了如何自定义一个表格视图。
接下来这部分,也是最后一部分,我们将讲解联线(segue),静态单元格等内容,我们还将加入一个选手详细内容页面,和一个游戏选择页面。
Segues的介绍
现在,让我们创建一个场景使用户可以自己增加新的选手进入列表。
在Players界面中拖入一个Bar Button,放置在导航栏的右侧,在属性监视器中将他的Identifier改为“add”,这样他就会显示一个加号的按钮,当用户点击这个按钮时,他就会弹出一个新的场景让用户对新的内容进行编辑或添加。
在编辑器中拖入一个新的Table View Controller,放置在Players场景的右边,然后按住ctrl,拉动加号键到新的场景中,这样,这个场景就会自动和这个按钮建立联系,从而自动归入Navigation View Controller中。
放开鼠标之后,会出现如下选项:
选中Modal,你可以注意到出现了一种新的箭头形式:
这种链接形式被官方称为segue(pronounce: seg-way),我叫它联线,(其实是转换的意思)这种形式的联线是表示从一种场景转换到另外一种场景中,之前我们使用的连接都是描述一种场景包含另一种场景的。而对于联线来说,它会改变屏幕中显示的内容,而且必须由交互动作触发:如轻点,或其他手势。
联线真正了不起的地方在于:你不再需要写任何代码来转入一个新的场景,也不用在将你的按钮和IBAction连接到一起,我们刚才做的,直接将按钮和场景链接起来,就能够完成这项工作。
运行这个app,按下 + 键,会发现出现了一个新的列表。
这种叫做 “modal” segue(模态转换),新的场景完全盖住了旧的那个。用户无法再与上一个场景交互,除非他们先关闭这个场景,过一会我们会讨论 push segue,这种segue会把场景推入导航栈。
新的场景现在还没有什么用,你甚至不能把他关闭呢。
联线只能够把你送到新的场景,你要是想回来,就得使用delegate pattern,代理模式。我们必须首先给这个新的场景设置一个独有的类,新建一个继承UITableViewController的类,命为PlayerDetailsViewController。
为了把它和storyboard相连,回到MainStoryBoard,选择新建的那个Table View Contrller,将他的类设置喂PlayerDetailViewController,千万不要忘记这一步,这很重要。
做完这一步之后,把新场景的标题改为“Add Player”,分别加入“Done”和“Cancel”两个导航栏按钮。
修改PlayerDetailsViewController.h 如下:
@class PlayerDetailsViewController; @protocol PlayerDetailsViewControllerDelegate <NSObject> - (void)playerDetailsViewControllerDidCancel: (PlayerDetailsViewController *)controller; - (void)playerDetailsViewControllerDidSave: (PlayerDetailsViewController *)controller; @end @interface PlayerDetailsViewController : UITableViewController @property (nonatomic, weak) id <PlayerDetailsViewControllerDelegate> delegate; - (IBAction)cancel:(id)sender; - (IBAction)done:(id)sender; @end
这会声明一个新的代理机制,当用户点击Cancel或者done按钮时,我们将用它来交互Add Player场景和主场景通讯。
回到故事版编辑器,将Cancel和Done按钮分别与动作方法连接,一种方式是,按住Ctrl拖动到ViewController上,之后选择正确的动作。
在 PlayerDetailsViewController.m,加入如下代码:
- (IBAction)cancel:(id)sender { [self.delegate playerDetailsViewControllerDidCancel:self]; } - (IBAction)done:(id)sender { [self.delegate playerDetailsViewControllerDidSave:self]; }
这是两个导航栏按钮要使用的方法,现在只需要让代理知道我们刚才加入了代码,而真正关闭场景只是代理的事情。
一般来说一定要为代理制定一个对象参数,这样他才知道向那里发送信息。
不要忘记加入Synthesize语句。
@synthesize delegate;
现在我们已经为PlayerDetailsViewController设置了一个代理协议,我们需要将这个协议的实现方法(implement)写在什么地方,很明显应该写在PlayerViewController因为这个vc代表了Add Player场景。在PlayersViewController.h中加入如下代码:
#import "PlayerDetailsViewController.h" @interface PlayersViewController : UITableViewController <PlayerDetailsViewControllerDelegate>
并在PlayersViewController.m的结尾加入:
#pragma mark - PlayerDetailsViewControllerDelegate - (void)playerDetailsViewControllerDidCancel: (PlayerDetailsViewController *)controller { [self dismissViewControllerAnimated:YES completion:nil]; } - (void)playerDetailsViewControllerDidSave: (PlayerDetailsViewController *)controller { [self dismissViewControllerAnimated:YES completion:nil]; }
目前这个代理方法只能够跳转到这个新的场景中,接下来我们来让他做一些更为强大的事情。
iOS 5 SDK中新添加的dismissViewControllerAnimated:completion: 方法可以被用来关闭一个场景。
最后还有一件事情需要做,就是Players场景需要告诉PlayerDetailsVC他的代理在哪里,听上去这种工作在故事版编辑其中一拖就行了,实际上,你得使用代码才能完成。
将以下方法加入到 PlayersViewController 中
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { if ([segue.identifier isEqualToString:@"AddPlayer"]) { UINavigationController *navigationController = segue.destinationViewController; PlayerDetailsViewController *playerDetailsViewController = [[navigationController viewControllers] objectAtIndex:0]; playerDetailsViewController.delegate = self; } }
当使用Segue的时候,就必须加入这个名叫 prepareForSegue 的方法,这个新的ViewController在被加载的时候还是不可见的,我们可以利用这个机会来向他发送数据。
请注意,这个segue的最终目标是Navigation Controller,因为这个是我们链接在导航栏上的按钮,为了获取PlayerDetailsViewController实例,我们必须通过NavController的属性来获取。
试着运行一下这个应用,单击 + 键,然后试着关闭Add Player场景,仍然不管用。
这是因为我们没有给Segue指定一个identifier,而parepareForSegu需要检查AddPlayer的身份证,这是必须的,因为你有可能会同时使用多个联线。
为了解决这个问题,进入Storyboard的编辑器,点击Players场景和NavgationViewController场景之间的联线,你会注意到与这个连线相关的按钮会自动亮起来。
在属性监视器中,将Identifier设置喂“AddPlayer”
如果这是你再次运行这个应用,点击“Cancel”或者“Done”按钮,这个场景就会自动关闭并且返回到上一级场景。
注意:从modal场景调用dismissViewControllerAnimated:completion方法是我们在这里使用的,但是这并不意味着你必须这样做。但是,如果你不是代理来完成这个关闭窗口的工作的话,唯一需要注意的是,如果你之前使用了[self.parentViewController dismissModalViewControllerAnimated:YES] 语句来关闭窗口的话,那么这个语句就不会正常工作了。
顺便说一下,属性检查器中有一个Transition的选项,在这里你可以选择场景转换是的动画效果。
试着运行一下,看看那种动画你最喜欢吧,但事情不要改变Style这个选项,如果你改变了,这个app可能会crash哦。
我们接下来在这个教程中还会用到几次代理方法,下面我们来列一下为了完成一个连线,你需要做的几件事情。
- 首先,从起始的控件做一条联线到目标场景。
- 将这个联线制定一个独特的Identifier。
- 为目标场景制作一个代理方法。
- 在Cancel和Done按钮,以及所有其他你需要和原始场景交流的地方调用代理方法。
- 在原始场景执行代理方法,这将会在用户按下按钮后关闭场景。
- 在原始场景执行prepareForSegue方法。
我们在这里必须使用代理,是因为根本没有反向联线这种东西,当sugue被启动之后,他将会创造出一个目标场景的新实例。你当然可以做一个从目标场景回到原始场景的联线,但是结果可能与你希望的大相径庭。
距离来说吧,如果你做一条从cancel按钮回到原始场景的连线的话,他并不会关闭当前场景并返回原始场景,而是会创建一个原始场景的新实例,这种情况会不停循环,知道把内存耗尽为止。
所以请记住:segue只用于打开新的场景。
静态单元格
当我们全部完成之后,Add Player场景会看上去象下面的一样:
这是一种分组表格视图,但是不同的是,我们并不需要为这个表哥创建一个数据源,我们可以在故事版编辑器中直接设计这个视图,而不需要重写cellForRowAtIndex方法,使得我们可以这样做的秘诀就是静态单元格。
选中Add Player场景,之后在属性检查器中,将Content属性改为StaticCell,将Style to Grouped属性修改为2。
当你修改Section属性时,编辑器会复制一个现有的组。你也可以自己选中一个组后选择Duplicate。
我们的这个场景每个组只需要用一个行,所以选中上面的那个行之后删除。
选中顶行,修改Header的值为:“Player Name”.
拖一个新的Text Field进入这个组的单元格里,把它的边界删除掉,使用System 17字体,取消Adjust to Fit选项。
我们现在在PlayerDetailsViewController中使用Assistant Editor这个Xcode 4.x的新特性来创建一个输出口给这个Text Field,在工具栏的按钮中打开Assistant Editor,那玩意看起来像个外星人,我指的是按钮。
选中text field,按住Ctrl,将他拖到打开的文件之中。
放开鼠标,会出现一个选单。
将这个新的书出口命名为nameTextField,在你确定链接之后,Xcode会自动创建下列代码:
@property (strong, nonatomic) IBOutlet UITextField *nameTextField;
他还会自动创建Synthesize语句,并同时在viewDidLoad文件中创建方法。
永远别在动态表格中使用这种拖来拖去的方法,但是对于静态单元格来说就OK,对于每个静态单元格来说都必须创建一个新的实例。
将第二个组的静态单元格的Style设置为Right Detail,这将会创建一个标准的单元格,把左侧的label的内容修改为Game,设置一个Disclosure Indicator,为右侧Detail的label设置一个输出口。
最终的设计完成后是这样的:
当你使用静态单元格的时候,你的Table View Controller就不需要一个数据源了,但是因为我们使用了Xcode的模板来创造PlayerDetailsViewController这个类,他里面仍然有一些默认的数据源设置代码,让我们来删除之。在以下这个标志
#pragma mark - Table view data source
和这个标志之间的代码全部删除。
#pragma mark - Table view delegate
现在运行这个App,效果不错吧,请注意我们不但一行代码也没写,还删除了好些。
但是我们并不能够完全避免写任何代码,你可能已经注意到了,在文本框和单元格周围有一些空间,用户在完成编辑之后单击这些区域并不会结束键盘什么的,怎么避免这个问题呢?用下面的代码代替tableView:didSelectRowatIndex方法。
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.section == 0) [self.nameTextField becomeFirstResponder]; }
这些代码就是说:如果用户点击第一个单元格后我们激活text field控件,这虽然是细节,但是细节决定成败。
同时你也需要在属性检查器的Selection Style选项改为None。
OK,我们的设计全部完成了。
增加一个选手吧
现在我们暂时先忽略Game这一行,先让用户能够编辑选手的情况之后再说。
当用户单击Cancel键的时候,不管作出什么修改都会被弃置,场景也会关闭并返回上一级菜单。这一块的程序我们已经做好了,也就是我们刚才做得一个代理方法,它接收到did cancel这个方法之后就会关闭这个视图。
但是当用户单击“Done”这个按钮时,我们应该创建一个新的选手项目然后加入他的属性,之后我们还需要通知代理器我们新增了一个选手,以便它能够更新上一级菜单。
在 PlayerDetailsViewController.m,把完成的方法改成:
- (IBAction)done:(id)sender { Player *player = [[Player alloc] init]; player.name = self.nameTextField.text; player.game = @"Chess"; player.rating = 1; [self.delegate playerDetailsViewController:self didAddPlayer:player]; }
这需要我们引进Player的头文件:
#import "Player.h"
这个完成方法会创建一个新的Player实例,并把它发送给代理器,由于目前代理器还没有这个方法,所以我们需要在PlayerDetailsViewController的头文件中修改如下代码:
@class Player; @protocol PlayerDetailsViewControllerDelegate <NSObject> - (void)playerDetailsViewControllerDidCancel: (PlayerDetailsViewController *)controller; - (void)playerDetailsViewController: (PlayerDetailsViewController *)controller didAddPlayer:(Player *)player; @end
这个“Did Save”的方法的声明没有了,我们加入一个“didAddPlayer”方法。
下面我们需要在执行文件中加入执行的方法,打开PlayersViewController.m,加入:
- (void)playerDetailsViewController: (PlayerDetailsViewController *)controller didAddPlayer:(Player *)player { [self.players addObject:player]; NSIndexPath *indexPath = [NSIndexPath indexPathForRow:[self.players count] - 1 inSection:0]; [self.tableView insertRowsAtIndexPaths: [NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; [self dismissViewControllerAnimated:YES completion:nil]; }
第一个语句向players的数组中加入新的Player对象,之后他会通知表格视图:一个新的行已经被创建,这是因为table view和他的数据源必须一直是同步的才行,我们其实也可以使用[self.tableView reloadData]这个语句,但是重新创建一个单元格会有随之而来的动画,看起来更好看一些。UITableViewRowAnimationAutomatic是一个iOS 5的新特性,使各行自动选择合适的动画效果出现,非常好用。
现在试试看,你应该可以使用按钮加入新行到表视图中了。
如果你已经开始担心storyboard的性能了,那么不用担心。就算是将所有的场景都一块载入的话,也不会消耗多少资源的。storyboard不会一下子加载所有的ViewController,而是会加载起始场景,在这里是Tab View,再从起始场景加载其他与起始场景相关的场景。
但是其他场景知道联线到他们之前是不会被加载的。而这些场景在你返回之后都会卸载,所以只有当前场景会在内存中,就像你之前在用分开的nib文件一样的。
我们通过实验来看一看。在PlayerDetailsViewController.m中加入下面的方法:
- (id)initWithCoder:(NSCoder *)aDecoder { if ((self = [super initWithCoder:aDecoder])) { NSLog(@"init PlayerDetailsViewController"); } return self; } - (void)dealloc { NSLog(@"dealloc PlayerDetailsViewController"); }
我们重写了initWithCoder和dealloc方法,使得debug控制台输出一个很长的信息。这时候运行这个app,你会发现除非按下segue的按钮,否则新的场景不会被初始化,放心了吧。
还有一件关于静态单元格的事情需要注意,那就是他们只能够在UITableViewController的子类下使用,如果他的父类不是UITableViewController,Xcode会提示下面的错误:
“Illegal Configuration: Static table views are only valid when embedded in UITableViewController instances”.
原型单元格,虽然可以在普通的View Controller中使用,但是不能够在Interface Builder中使用,
很少会出现有人会想要在一个表中用静态单元格和原型单元格混合起来,目前iOS SDK还不能很好的支持这种方法。
游戏选择器场景
在Add Player场景中单击Game的单元格会打开一个新的场景,让你能够从一个列表中选择一个游戏,这意味着我们需要加入一个新的表格视图,不过不同的是,我们这次会使用push到Navigation的栈之中,而不是直接跳转。
拖拉一个新的TableViewController到编辑器中,在Add Player场景中选择一个单元格按住ctrl键拉到新的场景中,创建一个连线,选择Push,之后把新segue的identifier命名为“PickGame”。
双击导航栏,修改标题为“Choose Game”,修改原型单元格的Style为Basic,修改他的Identifier为“GameCell”,我们的试图设计就到这里。
新建一个UITableViewController的子类,命名为GamePickerViewController,在storyboard中也要设置好哦。
首先我们给这个新的场景一些数据来显示,在GamePickerViewController.h中加入下列变量:
@interface GamePickerViewController : UITableViewController { NSArray * games; }
之后转到GamePickerViewController.m,在viewDidLoad方法中加入数组的内容。
- (void)viewDidLoad { [super viewDidLoad]; games = [NSArray arrayWithObjects: @"Angry Birds", @"Chess", @"Russian Roulette", @"Spin the Bottle", @"Texas Hold’em Poker", @"Tic-Tac-Toe", nil]; }
由于在viewDidLoad方法中加载了数组,所以需要在viewDidUnload中卸载之。
- (void)viewDidUnload { [super viewDidUnload]; games = nil; }
将模板中的数据源方法修改为如下代码:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [games count]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"GameCell"]; cell.textLabel.text = [games objectAtIndex:indexPath.row]; return cell; }
这样我们就完成了家在数据源的方法,这时候运行这个app,之后在Add Player场景中单击Game栏,就会转入这个视图了,但这时候单击这里的单元格并不会有什么作用。
这时候,由于我们使用push方式将这个场景推进了Navigation的栈中,所以这时候我们单击返回按钮就会自动返回到上一级界面。不错吧!
当然了,如果这个场景不输送任何数据回到上一级场景的话,那他就什么用也没有了,所以我们要创造一个新的代理器来完成这项任务。在GamePickerViewController.h中加入:
@class GamePickerViewController; @protocol GamePickerViewControllerDelegate <NSObject> - (void)gamePickerViewController: (GamePickerViewController *)controller didSelectGame:(NSString *)game; @end @interface GamePickerViewController : UITableViewController @property (nonatomic, weak) id <GamePickerViewControllerDelegate> delegate; @property (nonatomic, strong) NSString *game; @end
我们加入了一个代理方法,其中只有一个方法和一个用于乘放目前选择的游戏的名字的属性。
现在,我们修改GamePickerViewController.m的开头:
@implementation GamePickerViewController { NSArray *games; NSUInteger selectedIndex; } @synthesize delegate; @synthesize game;
这些代码新建了一个数组,一个选中项目的整数,并且synthesize了这些项目。
在viewDidLoad中加入如下代码:
selectedIndex = [games indexOfObject:self.game];
选中的游戏名字会设置在self.game中,这里我们设置我们在表格中到底选中了哪个游戏。在这里,在场景加载之前必须首先填充self.game,由于我们在viewDidLoad之前设置了prepareForSegue这个方法,所以我们现在这么做没问题。
修改cellForRowAtIndexPath方法:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"GameCell"]; cell.textLabel.text = [games objectAtIndex:indexPath.row]; if (indexPath.row == selectedIndex) cell.accessoryType = UITableViewCellAccessoryCheckmark; else cell.accessoryType = UITableViewCellAccessoryNone; return cell; }
这个方法会在选中的项目的右边加上一个选中的对勾。
将 didSelectRowAtIndexPath 修改为:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { [tableView deselectRowAtIndexPath:indexPath animated:YES]; if (selectedIndex != NSNotFound) { UITableViewCell *cell = [tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:selectedIndex inSection:0]]; cell.accessoryType = UITableViewCellAccessoryNone; } selectedIndex = indexPath.row; UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath]; cell.accessoryType = UITableViewCellAccessoryCheckmark; NSString *theGame = [games objectAtIndex:indexPath.row]; [self.delegate gamePickerViewController:self didSelectGame:theGame]; }
首先我们取消之前点击的那一行的选中状态,这将把它的蓝色变会正常的白色,之后将对勾删除掉,之后将对勾放置在刚刚选中的那一行上,最后,我们把选中的那一行返回给代理。
现在运行这个app测试一下效果,单击一个game的名字,将会出现一个对勾,单击另一个行,对勾的位置就会改变,但是返回上一级菜单之后发现我们的修改没有保存下来,为什么?因为我们还没有将代理真正的链接起来。
在 PlayerDetailsViewController.h 中,引入
#import "GamePickerViewController.h"
之后在 @interface 行之后加入:
@interface PlayerDetailsViewController : UITableViewController <GamePickerViewControllerDelegate>
在PlayerDetailsViewController.m加入prepareForSegue方法:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { if ([segue.identifier isEqualToString:@"PickGame"]) { GamePickerViewController *gamePickerViewController = segue.destinationViewController; gamePickerViewController.delegate = self; gamePickerViewController.game = game; } }
这和我们之前做过的很相似,但是这次的目标view Controller使game picker场景了,请记住,这个方法必须在GamePickerViewController初始化之后但是还没有加载view的时候调用。
“game”变量是新的,我们必须声明他:
@implementation PlayerDetailsViewController { NSString *game; }
我们使用这个变量来记录到底选择了哪个Game,我们得给这个String设置一个默认值,可以用initWithCoder方法来完成。
- (id)initWithCoder:(NSCoder *)aDecoder { if ((self = [super initWithCoder:aDecoder])) { NSLog(@"init PlayerDetailsViewController"); game = @"Chess"; } return self; }
如果你之前是用过nibs的话,那么initWithCode可能会对你很熟悉,这部分在storyboard是一样的。
修改 viewDidLoad 方法如下,以便单元格能够显示选中的Game名称:
- (void)viewDidLoad { [super viewDidLoad]; self.detailLabel.text = game; }
最后要做的就是执行代理方法:
#pragma mark - GamePickerViewControllerDelegate - (void)gamePickerViewController: (GamePickerViewController *)controller didSelectGame:(NSString *)theGame { game = theGame; self.detailLabel.text = game; [self.navigationController popViewControllerAnimated:YES]; }
这行代码很好懂,我就不多讲了。
我们的结束方法将会把选中的游戏的名字加入到新建的Player对象中。
- (IBAction)done:(id)sender { Player *player = [[Player alloc] init]; player.name = self.nameTextField.text; player.game = game; player.rating = 1; [self.delegate playerDetailsViewController:self didAddPlayer:player]; }
OK,到这里我们就完成了游戏选择器的场景,不错吧。
发表评论
-
带你深入理解 FLUTTER 中的字体“冷”知识
2020-08-10 23:40 635本篇将带你深入理解 Flutter 开发过程中关于字体和文 ... -
Flutter -自定义日历组件
2020-03-01 17:56 1111颜色文件和屏幕适配的文件 可以自己给定 import ... -
Dart高级(一)——泛型与Json To Bean
2020-02-23 19:13 1005从 Flutter 发布到现在, 越来越多人开始尝试使用 Da ... -
flutter loading、Progress进度条
2020-02-21 17:03 1181Flutter Progress 1 条形无固定值进度条 ... -
Flutter使用Https加载图片
2020-02-21 01:39 1020Flutter使用Https加载图片 使用http加载图片出 ... -
flutter shared_preferences 异步变同步
2020-02-21 00:55 848前言 引用 在开发原生iOS或Native应用时,一般有判断上 ... -
Flutter TextField边框颜色
2020-02-19 21:31 937监听要销毁 myController.dispose(); T ... -
flutter Future的正确用法
2020-02-18 21:55 808在flutter中经常会用到异步任务,dart中异步任务异步处 ... -
记一次Flutter简单粗暴处理HTTPS证书检验方法
2020-02-18 14:13 979最近在做Flutter项目到了遇到一个无解的事情,当使用Ima ... -
flutter 获取屏幕宽度高度 通知栏高度等屏幕信息
2019-07-27 08:39 1344##MediaQuery MediaQuery.of(con ... -
Mac上制作Centos7系统U盘安装盘
2019-07-23 11:25 651Centos7 下载地址: https://www.cento ... -
关于flutter RefreshIndicator扩展listview下拉刷新的问题
2019-07-10 19:40 1141当条目过少时listview某些嵌套情况下可能不会滚动(条目 ... -
flutter listview 改变状态的时候一直无限添加
2019-07-10 16:01 790setstate的时候会一直无限的调用listview.bui ... -
Flutter Android端启动白屏问题的解决
2019-07-09 00:51 1525问题描述 Flutter 应用在 Android 端上启动时 ... -
Flutter中SnackBar使用
2019-07-08 23:43 781底部弹出,然后在指定时间后消失。 注意: build(Bui ... -
Flutter 之点击空白区域收起键盘
2019-07-08 18:43 1792点击空白处取消TextField焦点这个需求是非常简单的,在学 ... -
Flutter 弹窗 Dialog ,AlertDialog,IOS风格
2019-07-08 18:04 1383import 'package:flutter/mate ... -
flutter ---TextField 之 输入类型、长度限制
2019-07-08 14:30 2337TextField想要实现输入类型、长度限制需要先引入impo ... -
【flutter 溢出BUG】键盘上显示bottom overflowed by 104 PIXELS
2019-07-08 11:13 1567一开始直接使用Scaffold布局,body:new Colu ... -
解决Flutter项目卡在Initializing gradle...界面的问题
2019-07-07 12:53 880Flutter最近很火,我抽出了一点时间对Flutter进行了 ...
相关推荐
【iOS】Storyboad全解析-第一部分 在iOS应用开发中,Storyboard是苹果提供的一种可视化界面设计工具,它允许开发者通过图形化的方式构建应用程序的用户界面,无需编写过多的代码。本篇将深入探讨Storyboard的基本...
在iOS应用开发中,Storyboard是一种强大的工具,它允许...在第二部分的教程中,我们将进一步探讨Storyboard的高级特性和技巧,如嵌套NavigationController、TabBarController的配置,以及如何处理复杂的界面流程。
### iOS开发教程:Storyboard全解析2 #### Segue与场景过渡 在iOS开发中,Storyboard是一种非常实用的设计工具,它可以让我们直观地构建应用程序的界面,并且管理不同界面间的跳转逻辑。本文作为Storyboard系列...
在本项目"iOS项目-object-c-仿网易新闻项目.zip"中,我们主要关注的是一个使用Objective-C编程语言实现的iOS应用,它模仿了知名的网易新闻客户端。Objective-C是Apple的首选语言,用于开发iOS和macOS平台上的原生...
2. **用户界面设计**:iOS应用通常使用Interface Builder或代码来构建用户界面。在这个聊天应用中,会包含输入框、发送按钮、聊天消息列表等元素,这些都是通过UI控件实现的。 3. **MVC(Model-View-Controller)...
6. **Storyboard与XIB**:iOS应用的界面设计工具,用于可视化地创建UI元素和布局。 7. **网络请求**:可能使用URLSession或第三方库如Alamofire,获取在线PDF文件。 8. **错误处理**:良好的错误处理机制对于任何...
1. **Swift编程语言**:大部分现代的iOS应用使用Swift编写,所以了解它的语法、特性、面向对象编程原则至关重要。 2. **MVC架构**:HotelApp很可能采用了Model-View-Controller(MVC)设计模式,这是一种组织代码和...
4. **Storyboard/XIB**:iOS应用通常使用这些工具进行界面布局,它们可以图形化地创建和设计UI组件,包括Picker View。 5. **Delegate和DataSource**:Picker View的交互依赖于这两个协议,Delegate用于响应用户操作...
这些标签明确了主题涉及的领域:iOS平台的应用开发,特别是与毕业设计和学术论文相关的部分。"源码"意味着这里包含的是实际可读的编程代码,而非成品应用。"App"则泛指移动应用,强调了这是针对iOS设备的软件开发。 ...
5. 网络编程:URLSession的使用,JSON解析,XML解析,以及第三方库如Alamofire的集成。 6. 多线程:GCD(Grand Central Dispatch)的使用,异步编程和后台任务处理。 7. 第三方库集成:可能会包含CocoaPods或...
通常,直播应用需要从服务器获取流媒体数据,这部分涉及到HTTP协议、流媒体技术和JSON解析。 4. **AVFoundation框架**: 这是苹果提供的多媒体处理框架,用于处理音频和视频。在直播应用中,你需要使用AVFoundation...
【标题解析】:“IOS应用源码Demo-万能工具包-毕设学习.zip”这个标题表明,这是一个关于iOS应用程序的源代码示例,被命名为“万能工具包”,主要用于毕业设计的学习。它是一个ZIP压缩文件,包含了可用于学习和研究...
这个项目可能是为了帮助学生理解和掌握iOS开发技术,特别是涉及到用户界面(UI)设计和数据管理的部分,可能包括了如何创建一个交互式的国家选择器。 【描述】中的“前两年的IOS应用源码”暗示了这是一个相对较旧的...
在iOS开发领域,Twitter开放API的应用是一个常见的学习和实践课题,尤其对于毕业设计或论文撰写而言,这样的项目能帮助开发者深入理解网络通信、API接口调用以及数据解析等关键技术。"IOS源码应用Demo-twitter开放...
《iOS Apprentice 第三版:Checklists v3.2》是iOS开发学习系列教程的一部分,主要针对初学者,旨在帮助他们掌握iOS应用开发的基础知识。在这个版本中,作者着重讲解了如何构建一个清单应用,以此来实践iOS编程的...
2. **Storyboard与XIB**:此项目可能包含了Storyboard文件或XIB文件,它们是iOS应用中用于可视化UI设计的工具。Storyboard用于管理多个屏幕之间的导航,而XIB文件则用于单独的视图组件。 3. **自定义...
1. **iOS应用基础**:了解iOS应用的基本结构,包括AppDelegate、Storyboard、ViewController等组成部分。 2. **UIViewController**:掌握UIViewController的生命周期,如`viewDidLoad`、`viewWillAppear`、`...
6. **Storyboard和AutoLayout**:源码中可能使用了Storyboard来设计界面,并运用AutoLayout进行屏幕适配,确保应用在不同尺寸的iOS设备上都能正确显示。 7. **Navigation Controller与Tab Bar Controller**:在实现...
《iOS源码应用Demo——eBay购买Demo深度解析》 在移动互联网的浪潮中,iOS应用开发成为众多技术爱好者和专业开发者关注的焦点。本文将深入探讨一款名为"eBay购买Demo"的iOS源码应用,它是一个适用于毕业设计学习的...
#### 第二部分:iOS编程基础:Hello World应用程序如何工作的? - **Interface Builder、头文件和实现文件的作用**:解释了Interface Builder(用于UI设计)、头文件(声明类)和实现文件(实现类功能)的作用。 - *...