Xcode - storyboard_OC版 06:静态单元格
静态单元格
1- 继上篇内容,我们进入 storyboard 中删除 AddWarrior Scene,相应的文件 WarriorDetailsViewController.h/WarriorDetailsViewController.m 也可一并干掉!接着我们拖进一个 TableViewController,segue 选择 push。注:当你拖进 TableViewController 且还未使用 segue 时你是无法将 BarButtonItem 拖进 TableViewController 中的,它仅仅会默认添加到底部的 ToolBar 上!因为之后当联线 push 后,此时 storyBoard 才会把新场景推入到 NavigationController 的栈中,同时在新场景中自动帮你生成导航栏
2 - 配置好 segue 后,我们为 TableViewController 添加两个 BarButtonItem,分别命名为 Cancel 和 Save
3 - 新建 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 }
4 - 在 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
5 - 把新场景 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;
注:永远不要在动态表格中使用这种拖来拖去的方法,但是对于静态单元格来说就是没问题,因为对于每个静态单元格来说都必须创建一个新的实例
7 - 在 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 }
运行效果
8 - 现在要实现这么一个功能:在 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 里使用
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)