템플릿 셀
tableViewController를 추가할 때 Xcode에서 경고가 표시되는 것을 확인하셨나요?
"지원되지 않는 구성: 프로토타입 가능 셀에는 재사용 식별자가 있어야 합니다.", 스토리보드에 TableViewController를 추가할 때 Xcode는 기본적으로 프로토타입 셀(템플릿 셀)을 사용합니다. 하지만 우리는 이를 구성하지 않았으므로 이 경고가 표시됩니다.
템플릿 셀은 멋진 스토리보드 기능입니다. 원래 nib 파일보다 훨씬 낫습니다. 과거에는 테이블 뷰 셀을 사용자 정의하려면 코드의 셀 객체에 고유한 하위 뷰를 추가하거나 새 nib를 생성하고 nib에서 고유한 셀을 로드해야 했습니다. 그러나 템플릿 셀의 출현으로 이 모든 것이 단순화되었으며 이제 스토리보드 편집기에서 직접 자신만의 테이블 뷰 셀을 디자인할 수 있습니다.
TableViewController에는 빈 템플릿 셀이 함께 제공됩니다. 이 셀을 클릭하면 속성 패널에서 스타일을 자막으로 설정할 수 있습니다. 그러면 셀이 두 개의 레이블이 있는 셀로 변합니다. TableViewCell을 수동으로 만든 적이 있다면 이것이 UITableViewCellStyleSubtitle 스타일이라는 것을 알아야 합니다. 템플릿 셀을 사용하면 기본 제공 스타일이 포함된 셀을 만들거나 완전히 사용자 정의된 셀을 만들 수 있습니다(이에 대해서는 잠시 후에 설명하겠습니다).
액세서리 속성을 공개 표시기로 변경하고 재사용 식별자를 "PlayerCell"로 설정합니다. 이런 식으로 Xcode는 경고를 즉시 억제합니다. 모든 템플릿 셀은 여전히 일반 UITableViewCell 개체이며 여전히 재사용 ID를 갖습니다. Xcode는 이를 설정하는 것을 잊지 말라는 메시지만 표시합니다(적어도 이 경고는 표시됩니다).
프로그램을 실행해봐도 아무 변화가 없습니다. 놀라지 마십시오. 아직 데이터 소스를 제공하지 않았으므로 테이블 보기에 행이 표시되지 않습니다.
프로젝트에 새 파일을 추가합니다. UIViewControllersubclass 템플릿을 선택합니다. 클래스 이름을 PlayersViewController로 지정하고 UITableViewController에서 상속되는지 확인하세요. 스토리보드에서 이 클래스에 대한 UI를 이미 디자인했으므로 "WithXib..." 옵션을 선택하지 마십시오. 더 이상 펜촉이 필요하지 않습니다!
스토리보드 편집기로 돌아가서 TableViewController를 선택하세요. ID 패널에서 클래스를 PlayersViewController로 설정합니다. 이 단계는 스토리보드에 있는 장면을 자체 ViewController 하위 클래스와 연결하기 때문에 중요합니다. 이 단계를 꼭 기억하세요. 그렇지 않으면 여러분이 만든 클래스가 완전히 쓸모없게 될 것입니다!
이제부터 스토리보드의 tableViewController는 프로그램 실행 후 PlayersViewController 클래스의 인스턴스가 됩니다.
PlayersViewController.h 파일에 변수 배열 속성을 추가합니다.
#import <UIKit/UIKit.h> @interface PlayersViewController : UITableViewController @property ( nonatomic, strong ) NSMutableArray * players; @end
이 배열은 애플리케이션 데이터에 모델을 저장합니다. , 이는 Player 개체입니다. 이제 Player 클래스를 만듭니다. Objective-C 클래스 템플릿을 사용하여 새 파일을 만듭니다. Player라는 이름은 NSObject를 상속합니다.
Player.h 파일:
@interface Player : NSObject @property ( nonatomic, copy ) NSString * name; @property ( nonatomic, copy ) NSString * game; @property ( nonatomic, assign ) int rating; @end
Player.m 파일
#import "Player.h" @implementation Player @synthesize name; @synthesize game; @synthesize rating; @end
이것들은 놀랄 일이 아닙니다. Player는 플레이어 이름, 플레이어가 플레이 중인 게임, 레벨(별 1~5개)이라는 3가지 속성을 가진 간단한 객체입니다.
AppDelegate에 배열을 넣고 테스트를 위해 배열에 일부 Player 객체를 넣을 것입니다. 이 배열은 PlayerViewController의 플레이어 속성에 할당됩니다.
AppDelegate.m에서 Player 클래스와 PlayersViewController 클래스의 가져오기 문을 추가하고 플레이어라는 인스턴스 변수를 추가합니다.
#import "AppDelegate.h" #import "Player.h" #import "PlayersViewController.h" @implementation AppDelegate { NSMutableArray * players; } ......
didFinishLaunchingWithOptions 메서드를 수정합니다.
- ( BOOL ) application :( UIApplication *) application didFinishLaunchingWithOptions :( NSDictionary *) launchOptions { players = [ NSMutableArray arrayWithCapacity : 20 ] ; Player * player = [[ Player alloc ] init ] ; player.name = @ "Bill Evans" ; player.game = @ "Tic-Tac-Toe" ; player.rating = 4 ; [ players addObject : player ] ; player = [[ Player alloc ] init ] ; player.name = @ "Oscar Peterson" ; player.game = @ "Spin the Bottle" ; player.rating = 5 ; [ players addObject : player ] ; player = [[ Player alloc ] init ] ; player.name = @ "Dave Brubeck" ; player.game = @ "Texas Hold’em Poker" ; player.rating = 2 ; [ players addObject : player ] ; UITabBarController * tabBarController = ( UITabBarController *) self.window.rootViewController; UINavigationController * navigationController = [[ tabBarController viewControllers ] objectAtIndex : 0 ] ; PlayersViewController * playersViewController = [[ navigationController viewControllers ] objectAtIndex : 0 ] ; playersViewController.players = players; return YES ; }
먼저 일부 Player 개체를 만들고 이를 플레이어 배열에 추가합니다. 그럼:
으르르르
그래 이게 뭐야? TabeViewController의 데이터 소스 역할을 하기 위해 PlayersViewController의 플레이어 속성에 플레이어 배열을 할당하려고 합니다. 하지만 애플리케이션 위임자는 PlayersViewController가 어디에 있는지 모르므로 스토리보드에서 이를 찾아야 합니다. 이것이 나를 짜증나게 하는 스토리보드 사용의 단점 중 하나입니다. IB를 사용하는 경우 MainWindow.xib에 애플리케이션 대리자에 대한 참조가 있으며 최상위 ViewController를 애플리케이션 대리자의 IBOutlet 속성에 연결할 수 있습니다. 하지만 이제는 스토리보드를 사용하여 이러한 작업을 수행할 수 없습니다. 애플리케이션 위임은 더 이상 최상위 ViewController에서 참조될 수 없습니다. 코드를 통해서만 참조를 얻을 수 있다는 것은 정말 불행한 일입니다.
UITabBarController * tabBarController = ( UITabBarController *) self.window.rootViewController; UINavigationController * navigationController = [[ tabBarController viewControllers ] objectAtIndex : 0 ] ; PlayersViewController * playersViewController = [[ navigationController viewControllers ] objectAtIndex : 0 ] ; playersViewController.players = players;
스토리보드의 초기 뷰 컨트롤러가 TabBarController라는 것을 알고 있으므로 창의 rootViewController에서 이에 대한 참조를 얻을 수 있습니다. 개체를 입력하고 Convert를 입력합니다.
PlayersViewController는 첫 번째 탭의 NavigationController 컨테이너에 있으므로 먼저 UINavigationController 개체를 가져옵니다.
UITabBarController * tabBarController = ( UITabBarController *) self.window.rootViewController;
그런 다음 NavigationController의 rootViewController에서 PlayersViewController를 얻을 수 있습니다:
UINavigationController * navigationController = [[ tabBarController viewControllers ] objectAtIndex : 0 ] ;
但是,UINavigationController 没有 rootViewController属性。因此我们必须从 viewControllers 数组中检索。(它有一个 topViewController 属性,但那个是位于viewControllers栈顶的 view controller。而我们要的是栈低的 view controller。虽然在程序刚启动的时候,栈顶和栈底实际上是一个,你也可以使用topViewController,但这不是那么安全)
现在我们有了 Player 数组,可以回到PlayersViewController 中创建我们的数据源了。
打开PlayersViewController.m,修改 table view 的数据源方法:
- ( NSInteger ) numberOfSectionsInTableView :( UITableView *) tableView { return 1 ; } - ( NSInteger ) tableView :( UITableView *) tableView numberOfRowsInSection :( NSInteger ) section { return [ self.players count ] ; }
重要的是cellForRowAtIndexPath方法。 Xcode 创建的模板代码是这样的:
- ( UITableViewCell *) tableView :( UITableView *) tableView cellForRowAtIndexPath :( NSIndexPath *) indexPath { static NSString * CellIdentifier = @ "Cell" ; UITableViewCell * cell = [ tableView dequeueReusableCellWithIdentifier : CellIdentifier ] ; if ( cell == nil ) { cell = [[ UITableViewCell alloc ] initWithStyle : UITableViewCellStyleDefault reuseIdentifier : CellIdentifier ] ; } // Configure the cell... return cell; }
毫无疑问,你曾经无数次地在这个地方编写自己的 table view 代码。但现在不同了。将代码修改为:
- ( UITableViewCell *) tableView :( UITableView *) tableView cellForRowAtIndexPath :( NSIndexPath *) indexPath { UITableViewCell * cell = [ tableView dequeueReusableCellWithIdentifier : @ "PlayerCell" ] ; Player * player = [ self.players objectAtIndex : indexPath.row ] ; cell.textLabel.text = player.name; cell.detailTextLabel.text = player.game; return cell; }
代码变得更简单了! 其实你只需要从这里获得新的 cell :
UITableViewCell * cell = [ tableView dequeueReusableCellWithIdentifier :@ "PlayerCell" ] ;
不再需要复用单元格了,它会自动从模板 cell 获得一份拷贝给你使用!你只需要提供复用的ID(你曾经在故事版编辑器中为模板cell 设置过的,在本例中,即“PlayerCell”)。记得设置这个ID,否则模板 cell 不会生效。
由于 PlayersViewController 不认识 Player 类,你还需要导入Player 类的头文件:
#import "Player.h"
此外还要合成 players 属性:
@synthesize players;
运行程序,如下图所示:
注意:在本例中,我们只用了一种模板 cell,如果你需要显示多种 cell,你可以加入更多的模板 cell。你可以复制已有的模板cell为新的cell,也可以增加TableView 的 Prototype Cells 属性值。注意,确保每个模板 cell 都有自己的复用ID。
使用神奇的模板cell只需一行代码,这是件了不起的事情!
设计完全自定义的模板cell
对于大部分 app,使用标准的 cell 样式就足矣。但我想在单元格右边加一张图片以显示玩家级别(以星级的形式)。UITableViewCell的标准样式中不包含可以在单元格中放入一个 ImageView,因此我只能选择定制设计。
回到 MainStoryboard.storyboard,选择模板cell,将Style 属性设置为 Custom。默认的 label 将消失。
首先增加 cell 的高度为 55 像素。拖拽它下端的拉柄可以改变它的高度,也可以修改Size 面板中的 Row height 值。
拖两个 Label 到 Cell 中,将它们放置到大致等于原先所在的位置。随意修改它们的字体和颜色。将两个label 的高亮色为白色。这样当用户点击 cell 时看起来会好一些,因为此时 cell的背景为蓝色。
拖一个 ImageView 到 cell 右端,紧靠着右箭头。调整它宽度为81,高度无所谓。设置它的 Mode 为 Center(在属性面板的 View 下面)以便当我们将图片放入时它不会被拉伸。
我将俩个 label 的宽度设置为210,这样不会遮住 ImageView。最终设计完成是这个样子:
由于是定制单元格,我们不再使用 cell 的 textLabel 和detailTextLabel 属性来显示文本。这两个标签的属性在我们的 cell 中也不再存在。
我们将通过 tag 检索我们想要的 Label。
对于 Name 标签,tag 设置为100,对于 Game 标签,tag 设置为102。你可以在属性面板中设置 tag。
打开PlayersViewController.m ,将cellForRowAtIndexPath 方法修改为:
- ( UITableViewCell *) tableView :( UITableView *) tableView cellForRowAtIndexPath :( NSIndexPath *) indexPath { UITableViewCell * cell = [ tableView dequeueReusableCellWithIdentifier : @ "PlayerCell" ] ; Player * player = [ self.players objectAtIndex : indexPath.row ] ; UILabel * nameLabel = ( UILabel *)[ cell viewWithTag : 100 ] ; nameLabel.text= player.name; UILabel * gameLabel = ( UILabel *)[ cell viewWithTag : 101 ] ; gameLabel.text = player.name; UIImageView * ratingImageView = ( UIImageView *) [ cell viewWithTag : 102 ] ; ratingImageView.image = [ self imageForRating : player.rating ] ; return cell; }
这里调用了一个新方法imageForRating,这个方法实现如下:
- ( UIImage *) imageForRating :( int ) rating { switch ( rating ) { case 1 : return [ UIImage imageNamed : @ "1StarSmall.png" ] ; case 2 : return [ UIImage imageNamed : @ "2StarsSmall.png" ] ; case 3 : return [ UIImage imageNamed : @ "3StarsSmall.png" ] ; case 4 : return [ UIImage imageNamed : @ "4StarsSmall.png" ] ; case 5 : return [ UIImage imageNamed : @ "5StarsSmall.png" ] ; } return nil ; }
再次运行程序。
啊哈,看起来有点不太对劲。我们修改了模板 cell 的高度,但tableView 并不知道。有两个办法:改变 table view 的 Row Height 属性,或者修改 heightForRowAtIndexPath 方法。前者更为简单,因此我使用了前者。
注意:如果你事先无法确定 cell 高度,或者你有不同高度的几种 cell,你应该使用 heightForRowAtIndexPath 。
返回MainStoryboard.storyboard,在TableView 的 Size 面板中,将 Row Height 设置为55
如果你用拖拽而不是直接键入的方式改变 cell 的高度,tableview 的 Row Height 属性也会自动随之改变。
再次运行程序,这次看起来就好多了。
子类化模板 Cell
我们的 Table View 看起来不错吧!但我并不喜欢用 tag 去访问 UILabel 和其他 cell 的 subview。如果这些Label 能连接到 IBOutlet 属性岂不是更好?
在项目中添加新的 File,使用 Objective-C class 模板。类名为PlayerCell ,继承自 UITableViewCell。
修改 PlayerCell.h 为:
@interface PlayerCell : UITableViewCell @property ( nonatomic, strong ) IBOutlet UILabel * nameLabel; @property ( nonatomic, strong ) IBOutlet UILabel * gameLabel; @property ( nonatomic, strong ) IBOutlet UIImageView * ratingImageView; @end
修改 PlayerCell.m 为:
#import "PlayerCell.h" @implementation PlayerCell @synthesize nameLabel; @synthesize gameLabel; @synthesize ratingImageView; @end
内容不多,仅仅是加了几个属性,nameLabel, gameLabel 以及 ratingImageView。
回到MainStoryboard.storyboard,选择模板 cell ,在 Identity 面板改变其 Class 为“PlayerCell”。这样当你用 dequeueReusableCellWithIdentifier 方法获得一个 cell时,它实际上返回一个PlayerCell 给你。
注意,我将类的名字和重用 ID 取成了一样——都叫做 PlayerCell——这仅仅是因为我喜欢这样。其实二者毫无干系,你完全让它们不一样。
选择,你可以将 label 和 ImageView 连接到IBOutlet。选中 Label 然后从它的连接面板拖一条线到 TableViewCell,或者用 Ctrl+左键从 TableViewCell 拖到 Label 上。
重点:你可以在控件和 TableViewCell 间建立连接,而不仅仅是在控件和 ViewController 间建立连接!如你所见,当你的数据源用 dequeueReusableCellWithIdentifier向 Table View 请求新的单元格时,TableView 并不真正把模板 cell 给你,它只是给你一份模板 cell 的拷贝(也可能是一个已经存在的cell——在复用的情况下)。也就是说任何时候都存在多个 PlayerCell 实例。如果你连接 cell 上的一个 Label 到ViewController 的 IBOutlet上,那么会有多个 Label 在试图使用相同的 IBOutlet。那就麻烦了。(顺便说一句,如果在你的Cell 上有一个 Custom Button 或者其他控件,你可以将模板 cell 连接到 ViewController 的 action 上。
现在,我们已经连接好这些属性。我们的代码可以变得更加简洁:
- ( UITableViewCell *) tableView :( UITableView *) tableView cellForRowAtIndexPath :( NSIndexPath *) indexPath { PlayerCell * cell = ( PlayerCell *)[ tableView dequeueReusableCellWithIdentifier : @ "PlayerCell" ] ; Player * player = [ self.players objectAtIndex : indexPath.row ] ; cell.nameLabel.text = player.name; cell.gameLabel.text = player.game; cell.ratingImageView.image = [ self imageForRating : player.rating ] ; return cell; }
这样还差不多。我们将dequeueReusableCellWithIdentifier 返回的结果转换为PlayerCell,然后用它的属性去访问 Label 和 UIImageView。我真的喜欢使用模板 cell,它使我的TableView 代码看起来整洁多了。
当然,你仍然需要导入 PlayerCell 类:
#import "PlayerCell.h"
运行程序,跟前面一模一样,但在表格中使用的是我们自己的TableViewCell 子类。
还有一些设计技巧。在设计自己的 TableViewCell 时,你需要注意一些地方。首先,你应当设置Label 的 Highlighted Color(高亮色) ,以便用户在点击表格行时感觉更好。
其次,你应当确保添加的内容能自动适应单元格尺寸的变化。例如,当你需要表格行能够被删除或移动时 ,Cell 尺寸会发生改变。
添加下列方法到 PlayerViewController.m:
- ( void ) tableView :( UITableView *) tableView commitEditingStyle :( UITableViewCellEditingStyle ) editingStyle forRowAtIndexPath :( NSIndexPath *) indexPath { if ( editingStyle == UITableViewCellEditingStyleDelete ) { [ self.players removeObjectAtIndex : indexPath.row ] ; [ tableView deleteRowsAtIndexPaths :[ NSArray arrayWithObject : indexPath ] withRowAnimation : UITableViewRowAnimationFade ] ; } }
实现这个方法后,表格的“轻扫以删除”功能被开启。运行程序,在某行上进行轻扫手势,看看会发生什么。
删除按钮出现在 cell 上,但它同时也遮住了等级图片。实际上是因为删除按钮占据了部分cell 空间,而 cell 大小随之改变,ImageView 却没有改变。
要解决这个问题,打开 MainStoryBoard.storyboard,选择 ImageView ,在 Size 面板中修改 Autosizing 以便它始终位于 superview 的右端:
Label 的 Autosizing 设置如下,因此当 cell 尺寸改变时,Label 的尺寸也随之变化:
经过这些调整,删除按钮的出现会将星级图标挤到左边:
삭제 버튼이 나타날 때 별이 사라지도록 할 수도 있고 독자에게 맡길 수도 있습니다. 중요한 것은 TableViewCell을 디자인할 때 이러한 세부 사항을 명확하게 해야 한다는 것입니다.
다음 내용
이 튜토리얼의 두 번째 부분에서는 segues, 정적 tableViewCell, 플레이어 추가 창, 게임 선택 창 및 이 튜토리얼의 샘플 코드를 다룹니다!
위 내용은 iOS 5 스토리보드 소개(2) 내용이며, 더 많은 관련 내용은 PHP 중국어 홈페이지(www.kr)를 참고해주세요. .php.cn) !