首页 > 技术文章 > Xcode - storyboard_OC版 06:静态单元格

self-epoch 2017-11-03 17:56 原文

静态单元格

1- 继续上篇章的内容,我们进入 storyboard 中删除 AddWarrior Scene,相应文件 WarriorDetailsViewController.h/WarriorDetailsViewController.m 也可一并干掉

2 - 拖进一个 TableViewController 控件,segue 联线选择 push。注:当你拖进 TableViewController 且还未使用 segue 时你是无法将 BarButtonItem 拖进 TableViewController 中的,它仅仅会默认添加到底部的 ToolBar 上

3 - 配置好 segue 后我们为 TableViewController 添加两个 BarButtonItem,分别命名为 Cancel 和 Save

说明:联线 push 后 storyBoard 会自动把新场景推入到 NavigationController 的栈中,同时在新场景中自动帮你生成导航栏。这就是你能拖进 BarButtonItem 的原因

4 - 新建 UITableViewController 的子类 WarriorAddedVC。实现的功能就是点击 Cancel 或 Save 按钮返回上级页面,使用代理实现返回功能(其实 Push 出的新场景自带返回按钮,这里我们重点熟悉代理)

// - WarriorAddedVC.h 

 1 #import <UIKit/UIKit.h>
 2 @class WarriorAddedVC;
 3 
 4 @protocol WarriorAddedDelegate <NSObject>
 5 
 6  - (void)warriorAddedDidCancel:(WarriorAddedVC *)controller;
 7  - (void)warriorAddedDidSave:(WarriorAddedVC *)controller;
 8 
 9 @end
10 
11 @interface WarriorAddedVC : UITableViewController
12 
13 @property (nonatomic,weak) id <WarriorAddedDelegate> delegate; // 代理
14 
15 - (IBAction)willSave:(id)sender;// 默认参数
16 - (IBAction)willCancel:(UIBarButtonItem *)sender;// 自定义参数
17 
18 @end

// - WarriorAddedVC.m 

1 - (IBAction)willCancel:(UIBarButtonItem *)sender {
2     
3       [self.delegate warriorAddedDidCancel:self];
4 }
5 
6 - (IBAction)willSave:(id)sender {
7     
8     [self.delegate warriorAddedDidSave:self];
9 }

5 - 在 WarriorsViewController.m 文件中接受协议、实现代理

    在此之前要为 segue 设置 identifier 名为 warriorAdded,并把场景 Warrior AddedVC Scene 的 Custom Calss  和 WarriorAddedVC 进行关联

  1 #import "WarriorsViewController.h"
  2 #import "Warrior.h"
  3 #import "WarriorCell.h"
  4 #import "WarriorAddedVC.h"
  5 @interface WarriorsViewController ()<WarriorAddedDelegate>// 接受协议
  6 
  7 @end
  8 
  9 @implementation WarriorsViewController{
 10     
 11     NSMutableArray *_warriorsArray;// 数据源
 12 }
 13 
 14 - (void)viewDidLoad {
 15     [super viewDidLoad];
 16     // 创建数组
 17     _warriorsArray = [NSMutableArray arrayWithCapacity:0];
 18     
 19     // 制造数据
 20     Warrior *player1 = [[Warrior alloc] init];
 21     player1.nameStr = @"カカロット";
 22     player1.fightingCapacityStr = [NSString stringWithFormat:@"戦力:%d",FC_Base];
 23     player1.rating = 1;
 24     [_warriorsArray addObject:player1];
 25     
 26     player1 = [[Warrior alloc] init];
 27     player1.nameStr = @"セクシー";
 28     player1.fightingCapacityStr= [NSString stringWithFormat:@"戦力:%d",FC_Base];
 29     player1.rating = 2;
 30     [_warriorsArray addObject:player1];
 31     
 32     player1 = [[Warrior alloc] init];
 33     player1.nameStr = @"ベジータ";
 34     player1.fightingCapacityStr = [NSString stringWithFormat:@"戦力:%d",FC_Base];
 35     player1.rating = 3;
 36     [_warriorsArray addObject:player1];
 37 }
 38 
 39 #pragma mark - Table view data source
 40 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
 41 
 42     return 1;
 43 }
 44 
 45 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
 46 
 47     return _warriorsArray.count;
 48 }
 49 
 50 // 直接使用属性
 51 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
 52 
 53     WarriorCell *cell = [tableView dequeueReusableCellWithIdentifier:@"WarriorCell" forIndexPath:indexPath];
 54     Warrior *player = [_warriorsArray objectAtIndex:indexPath.row];
 55     cell.warriorName.text = player.nameStr;
 56     cell.warriorValues.text = player.fightingCapacityStr;
 57     cell.warriorIcon.layer.masksToBounds = YES;
 58     cell.warriorIcon.layer.cornerRadius = 3.2;
 59     cell.warriorIcon.image = [self imageForRating:player.rating];
 60     return cell;
 61 }
 62 
 63 // 编辑
 64 - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath{
 65     if (editingStyle == UITableViewCellEditingStyleDelete){
 66         // 删除数据
 67         [_warriorsArray removeObjectAtIndex:indexPath.row];
 68         // 删除UI
 69         [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
 70     }
 71 }
 72 
 73 #pragma mark - self_defined_methods
 74 // 选取图片
 75 - (UIImage *)imageForRating:(int)rating{
 76 
 77     switch (rating){
 78         case 1: return [UIImage imageNamed:@"11.png"];
 79         case 2: return [UIImage imageNamed:@"22.png"];
 80         case 3: return [UIImage imageNamed:@"33.png"];
 81     }
 82     return nil;
 83 }
 84 
 85 #pragma mark - prepareForSegue
 86 // 当使用 segue 的时候就必须加入此方法
 87 - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
 88     if([segue.identifier isEqualToString:@"warriorAdded"]){
 89         
 90         WarriorAddedVC *vc = (WarriorAddedVC*)segue.destinationViewController;
 91         vc.delegate = self;
 92     }
 93 
 94 }
 95 
 96 #pragma mark - <WarriorAddedDelegate>
 97 - (void)warriorAddedDidCancel:(WarriorAddedVC *)controller{
 98 
 99     [self.navigationController popViewControllerAnimated:YES];
100 }
101 
102 - (void)warriorAddedDidSave:(WarriorAddedVC *)controller{
103     
104    [self.navigationController popViewControllerAnimated:YES];
105 }
106 
107 @end

6 - 把新场景 Item 的 Title 更名成 WarriorAdded;将 TableView 的 Content 属性改为 Static Cell,并把 Style 改成 Grouped;将 TableView 的 Sections 修改成 2 并把首个 TableViewSection 的 Header 更名成 WarriorName

7 - 在第一分区下的第一、二组单元格中,分别向 ContentView 里拖进一个 TextField,并在场景中使用别给这两个 TextField 创建一个输出口

// - WarriorAddedVC.h

@property (weak, nonatomic) IBOutlet UITextField *inputName;
@property (weak, nonatomic) IBOutlet UITextField *inputAge;

注:永远不要在动态表格中使用这种拖来拖去的方法,但是对于静态单元格来说就是没问题(因为对于每个静态单元格来说都必须创建一个新的实例)

8 - 在 WarriorAddedVC.m 中实现 Table view data source 两个方法后,运行程序即产生效果

1 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
2 
3     return 2;// 等于在 storyboard 中的分区
4 }
5 
6 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
7 
8     return 3;// 等于在 storyboard 中所创建的静态单元格的个数
9 }

运行效果

9 - 要实现这么一个功能:在 WarriorAddedVC 的两个 TextField 中键入信息,点击 Save 后把信息显示在上级页面中

// -  WarriorAddedVC.h:引入数据对象 Warrior 并新增保存信息的代理方法

 1 #import <UIKit/UIKit.h>
 2 @class WarriorAddedVC;
 3 @class Warrior;// 数据对象
 4 @protocol WarriorAddedDelegate <NSObject>
 5 
 6 - (void)warriorAddedDidCancel:(WarriorAddedVC *)controller;
 7 - (void)warriorAddedDidSave:(WarriorAddedVC *)controller;
 8 
 9 // 新增代理方法:保存输入的信息
10 - (void)warriorAddedDidSave: (WarriorAddedVC *)controller didAddWarrior:(Warrior *)fighter;
11 
12 @end
13 
14 @interface WarriorAddedVC : UITableViewController
15 
16 @property (nonatomic,weak) id <WarriorAddedDelegate> delegate; // 代理
17 
18 - (IBAction)willSave:(id)sender;// 默认参数
19 - (IBAction)willCancel:(UIBarButtonItem *)sender;// 自定义参数
20 
21 @property (weak, nonatomic) IBOutlet UITextField *inputName;
22 @property (weak, nonatomic) IBOutlet UITextField *inputAge;
23 
24 @end

// - WarriorAddedVC.m:修改 willSave 方法

 1 - (IBAction)willSave:(id)sender {
 2     
 3     Warrior *player = [[Warrior alloc] init];
 4     player.nameStr = self.inputName.text;
 5     player.fightingCapacityStr = self.inputAge.text;
 6     player.rating = arc4random()%3+1;// 随机数
 7     // 随便做个空判断
 8     if (player.nameStr.length == 0 || player.fightingCapacityStr.length == 0 ) {
 9          player.nameStr = [NSString stringWithFormat:@"killer%d",arc4random()%30];
10          player.fightingCapacityStr = [NSString stringWithFormat:@"戦力:%d",FC_Base];
11     }
12    
13     // [self.delegate warriorAddedDidSave:self];
14     [self.delegate warriorAddedDidSave:self didAddWarrior:player];
15 }

// - WarriorsViewController.m:实现新增的代理方法

 1 - (void)warriorAddedDidSave: (WarriorAddedVC *)controller didAddWarrior:(Warrior *)fighter{
 2     
 3     // 第一步:添加数据
 4     [_warriorsArray addObject:fighter];
 5     
 6     // 第二步:刷新 UI
 7 //    // 方式一:没有动画效果且刷新的是整个 tableView
 8 //    [self.tableView reloadData];
 9     
10     // 方式二:刷新指定行
11     NSIndexPath *indexPath = [NSIndexPath indexPathForRow:[_warriorsArray count] - 1 inSection:0];
12     [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
13     
14     // 返回页面
15     [self.navigationController popViewControllerAnimated:YES];
16 }

运行效果:初始化 | 添加完成

  

小结

1 - storyboard 不会一下子加载所有的 ViewController,而是会加载起始场景,再从起始场景加载其他与起始场景相关的场景

2 - 其他场景在联线到它们之前是不会被加载的,而这些场景在你返回之后都会卸载掉,所以只有当前场景会在内存中

3 - 我们通过实验来看一看:在 WarriorAddedVC.m 中加入下面的方法

 1 - (instancetype)initWithCoder:(NSCoder *)coder{
 2     self = [super initWithCoder:coder];
 3     if (self) {
 4         NSLog(@"init addedVC");
 5     }
 6     return self;
 7 }
 8 
 9 -(void)dealloc{
10 
11     NSLog(@"dealloc addedVC");
12 }

说明:运行代码你会发现除非按下 segue 的按钮( WarriorsViewController 中的 Add)否则新出的场景是不会被初始化,同样也只有点击 cancel 或save 返回上级页面时才触发 dealloc 方法

4 - 关于静态单元格需要注意是:只能够在 TableViewController 的子类下使用,如果它的父类不是 TableViewController,Xcode 会提示下面的错误信息 Illegal Configuration: Static table views are only valid when embedded in UITableViewController instances

5 - 原型单元格可以在普通的 ViewController 中使用,但是不能在 InterfaceBuilder 里使用

推荐阅读