美文网首页ios漫漫学习路
iOS TableView 编程指导(三)-创建和配置Table

iOS TableView 编程指导(三)-创建和配置Table

作者: 陵无山 | 来源:发表于2018-10-19 14:04 被阅读94次

    在展示TableView前要配置好, 设置TableView的delegate和DataSource对象.
    本章用到的代码来之demo:UITableView Fundamentals for iOSTheElements

    创建TableView的基本知识


    为创建TableView, 需要创建几个相互交互的实体对象:视图控制器, TableView自己和tableView的DataSource和delegate对象, 视图控制器, DataSource和delegate通常是同一个对象. 视图控制器的启动了调用序列, 图3-1使用展示了这一过程.

    1. 视图控制器使用frame和style来创建tableView实例. frame通常为screen的frame,减去各种bar(导航/状态栏等)的高度即可. 在此刻也可设置一个全局变量表保存tableView, 或者用一个属性保存tableView的全局属性, 比如, 行高, autoresizing行为.
    2. 视图控制器给tableView设置DataSource和delegate, 然后给TableView发送reloadData消息.
    3. TableView会给DataSource发送numberOfSectionsInTableView:消息, DataSource会使用该方法返回TableView的section数, 这个方法时可选的, 但如果你的tableView中有多个section的话, 必须实现该方法来告知TableView具有多个section.
    4. 对于每一个section, DataSource都会收到TableView发送来的TableView:numberOfRowsInSection:消息, 该方法中返回每个section中的行数
    5. 之后DataSource会收到对应的可见cell的tableView:cellForRowAtIndexPath:消息, 然后返回对应cell的UITableViewCell对象, tableView使用该对象去绘制表视图中的行.
    图3-1 创建和配置TableView调用时序图

    图4-1展示了DataSource中的required方法加上numberOfSectionsInTableView:. DataSource和delegate除了实现required方法外, 可以实现其他optional方法来提供一些额外的特性, 比如实现tableView:titleForHeaderInSection:方法来给section提供title. 你可以创建plain或者grouped样式的tableView, 尽管以两种样式创建表视图的过程是相同的, 但是您可能希望执行不同类型的配置. 例如, 因为分组表视图通常呈现项目细节, 所以您可能还希望添加自定义附件视图(例如, 开关和滑块)或自定义内容(例如, 文本字段). 具体见详细地看看tableView中的cell

    创建和配置TableView的一些建议


    在APP中, tableView的用法很多, 想怎么用就怎么用, 你可以自定义一个对象去创建, 管理, 配置tableView, 但是你应该使用UIKit提供的设计好的一些关于tableView的类和技术, 也应该遵循UIKit对于tableView使用的一些规范, 比如数据/表现分离, 下面是UIKit关于创建和使用tableView的一些建议:

    • 尽量使用UITableViewController来创建和管理TableView
    • 如果你的APP中大量使用TableView的话, 那么你在创建xcode工程时, 应该使用Master-Detail应用程序模板
    • 对于展示连续的TableView, 你应该使用自定义的UITableViewController, 这样你既可以从storyboard中加载TableView, 也可以通过代码创建一个关联TableView.

    如果你的界面是多个视图组合而成, 而TableView只是组成界面的一部分, 那么你应该使用UIViewController来管理tableView, 不能用UITableViewController, 因为后者会使得tableView的size固定填充屏幕(减去各种bar的空间).

    使用storyboard来创建TableView


    使用xcode创建包含tableView的APP时, 选则模板应该包含方便创建TableView的代码存根和storyboard, 该模板提供一个骨架, 你只需添加而外的代码和设置即可, 这样很方便

    创建基于TableView的APP:

    1. 选择Xcode, 选择文件>新建>项目
    2. 在弹出的对话框的左侧,选择iOS, 选择应用程序
    3. 在对话框的中间主要区域中, 选择'Mater-Detail 应用程序', 点击'下一步'
    4. 选择项目选项(确保使用storyboard), 然后点击'下一步'
    5. 选择项目的保存位置, 然后点击'创建'

    在第四步选择设备时, 会决定项目中的storyboard数量. 点击项目导航栏中的storyboard文件, 会显示storyboard编辑视图. 如果你选择的是iPhone, 那么storyboard中包含一个TableViewController, 如图3-2所示.


    图3-2 Master-Detail应用中的master view controller对应的storyboard

    确保画布上的场景表示代码中的主视图控制器

    • 在画布上, 单击场景标题来选择表视图控制器
    • 单击工具栏区域顶部的Identity button来打开Identity inspector
    • 检查工程中的类是否包含UITableViewController的子类

    选择TableView的Display Style

    TableView风格包括两种:plain和grouped
    在storyboard中选择TableView的style

    1. 点击scene的中间来选中tableView.
    2. 在工具栏中选择Attributes inspector
    3. 在tableView的Attributes inspector中选择plain或者grouped style

    选择TableView的Content Type

    在设计tableView的内容是, storyboard中有两种便捷的方式.

    • Dynamic prototypes 设计的cell的prototype的目的是为了复用. 如果你的cell复用时, 使用的是相同的layout, 那么使用Dynamic, Dynamic的内容由表视图数据源(表视图控制器)在运行时以任意数量的单元格进行管理。图3-3显示了带有一个Dynamic的plain表视图。
      图3-3 Dynamic tableView

    注意:如果storyboard中的tableView是dynamic, 那么tableView一定需要一个DataSource对象, 也就是说UITableViewController的子类需要实现data source协议

    • Static cells 使用是static cell的话, cell的内容的layout, cell的个数已经确定. TableView的cell和内容在运行前已经确定好, 是固定的. 你还是设置section header等静态信息. 当TableView的cell的layout不会改变时, 使用static cell. 如图3-4, static cell的tableView.
    图3-4 static cell

    注意:如果storyboard中的TableView使用static cell, 那么就不需要DataSource对象了, 因为TableView的cell时确定的.

    cell的重用标识符(reuse identifier)是用来标识重用的cell的, 标识符的字符串最好是能够描述cell中包含的内容, 比如cell是用来展示鸟瞰图的, 那么他的重用标识符为@"BirdSightingCell"

    设计TableView的cell

    前面文章有讲过, UIKit为你提供了四种类型的cell. 如果系统提供的这四种类型不能满足需求, 你可以继承UITableViewCell来自定义cell. 具体如何自定义cell你将在接下来的文章学到, 请关注本系列文章.

    TableViewCell可以拥有附加视图(Accessory view). 附加视图是UIKit提供显示在cell的右边的一个视图, 比如disclosure indicator, 使用>图标来表示, 告诉用户点击该图标可以获取更多信息.

    创建多个TableView

    如果你的APP中存在和管理着多个TableView, 那么往storyboard中多拖几个TableView, 可以通过将你创建的UITableViewController子类和TableView绑定.
    往工程中添加自定义类文件

    1. 打开Xcode, 选择File > New > File
    2. 在弹出的对话框中选择iOS中的cocoa touch
    3. 在对话框中选择Object-C类, 点击下一步
    4. 输入文件名称, 选择UITableViewController, 点击下一步
    5. 选择保存的路径, 点击创建

    往storyboard中添加表视图控制器

    1. 打开要添加TableView的storyboard
    2. 在对象库中拖一个TableViewController到storyboard中
    3. 选中添加的scene, 点击utility area中的Identity button
    4. 在custom class组中, 选择刚创建的类
    5. 选择TableView的cell的style(dynamic/static)
    6. 往新的scene上添加一个segue

    通过创建一个demo APP可以学到更多

    通过Apple实战指导Your Second iOS App: Storyboards, 你可以学习如何通过storyboard技术来创建APP, 在该指导文档中, 你将会学习如何创建一个基于导航的APP, 以及如何创建使用TableView来构建界面.

    使用代码创建tableView


    使用UITableViewController的好处, 能够省写很多代码, 而且可以避免一些错误.

    实现DataSource和Delegate协议

    使一个类成为TableView的DataSource和delegate需要遵守UITableViewDatasSourceUITableViewDelegate协议. 代码清单3-1展示TableView的DataSource和Delegate.

    代码清单3-1 遵守DataSource和delegate协议

    @interface RootViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
     
    @property (nonatomic, strong) NSArray *timeZoneNames;
    @end
    

    创建和配置tableView

    第二步是alloc, init出TableView实例. 代码清单3-2展示了创建一个plain表视图, 然后设置各种属性,frame, DataSource, delegate等信息, 在使用UITableViewController时, 是不用设置这些属性的, 因为它自动帮你设置好了
    代码清单3-2 创建一个tableView

    - (void)loadView
    {
        UITableView *tableView = [[UITableView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame] style:UITableViewStylePlain];
        tableView.autoresizingMask = UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth;
        tableView.delegate = self;
        tableView.dataSource = self;
        [tableView reloadData];
     
        self.view = tableView;
    }
    

    使用数据动态填充tableView


    当TableView被创建后, TableView会收到reloadData消息, 让TableView去访问DataSourcedelegate对象, 来获取section, row等信息, 这时DataSource会告诉TableView该显示多少section和row. 之后DataSource重复调用tableView:cellForRowAtIndexPath:方法,返回一个UITableViewCell对象, 来告诉tableView该如何绘制row.(当你滚动tableView时, 也会导致tableView:cellForRowAtIndexPath:被调用, 这是因为新cell会显示).

    前面提过, 如果tableView是动态的(dynamic), 那么就必须实现DataSource协议, 代码清单3-3展示一个实现了DataSource和delegate协议的类.

    代码清单3-3 给动态tableView填充数据

    - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
        return [regions count];
    }
     
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
        // Number of rows is the number of time zones in the region for the specified section.
        Region *region = [regions objectAtIndex:section];
        return [region.timeZoneWrappers count];
    }
     
    - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
        // The header for the section is the region name -- get this from the region at the section index.
        Region *region = [regions objectAtIndex:section];
        return [region name];
    }
    
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
        static NSString *MyIdentifier = @"MyReuseIdentifier";
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
        if (cell == nil) {
            cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault  reuseIdentifier:MyIdentifier];
        }
        Region *region = [regions objectAtIndex:indexPath.section];
        TimeZoneWrapper *timeZoneWrapper = [region.timeZoneWrappers objectAtIndex:indexPath.row];
        cell.textLabel.text = timeZoneWrapper.localeName;
        return cell;
    }
    

    tableView:cellForRowAtIndexPath:方法中, 出于性能考虑, DataSource会尽可能的复用cell, 首先会发送一个dequeueReusableCellWithIdentifier:消息给到tableView, 要求TableView提供一个特定重用标识符的复用cell, 如果该cell不存在, DataSource要负责创建该cell, 并设置特定的reuse identifier. DataSource还要负责设置cell的content(上例中, 设置text)然后返回cell给TableView. 具体如何设置cell的content请看后续文章详细地看看tableView中的cell

    如果你的cell是在storyboard定义的, 使用dequeueReusableCellWithIdentifier:方法进行获取cell时总是有效的, 如果那里不需要使用复用的cell, 那么该方法会返回一个使用storyboard信息创建的新cell. 这样就不需要检查返回值是否为nil了.

    使用数据动态填充tableView


    如果TableView是静态的, 那么就不需要DataSource对象了, TableView在编译期配置了好, 不需要在运行期在去调用DataSource的方法了. 但是你需要使用data model中的数据来填充TableView的内容, 下面代码清单3-4就是一个这样的示例.(改代码示例来源Your Second iOS App: Storyboards)
    代码清单3-4 使用数据填充静态tableView

    - (void)viewDidLoad
    {
        [super viewDidLoad];
     
        BirdSighting *theSighting = self.sighting;
        static NSDateFormatter *formatter = nil;
        if (formatter == nil) {
            formatter = [[NSDateFormatter alloc] init];
            [formatter setDateStyle:NSDateFormatterMediumStyle];
        }
        if (theSighting) {
            self.birdNameLabel.text = theSighting.name;
            self.locationLabel.text = theSighting.location;
            self.dateLabel.text = [formatter stringFromDate:(NSDate*)theSighting.date];
        }
    }
    

    上面的代码实例中, 在viewDidLoad(该方法会等view加载到内存后调用)对tableView进行填充数据, 其中一些属性(birdNameLabel,locationLabel)是storyboard中的outlet.

    填充索引列表


    当需要大量按字母分类排序的数据时, 可以使用索引列表(如第一章图1-2所示). 索引列表是一种plain风格的TableView, 而且还需要对DataSource对象进行一些特殊的设置:

    • sectionIndexTitlesForTableView:
      返回一组字符串作为index使用

    • tableView:titleForHeaderInSection:
      为每一个section指定一个index, 作为section的title

    • tableView:sectionForSectionIndexTitle:atIndex:
      指定用户点击进入相应的section的index.

    在构建索引列表时, 用来填充tableView的数据结构与索引结构相符. 一般需要创建一个二维数组来保存数据. section的个数通过外层数组来反映, 内层数组代表一个section中的内容, 而且还要对这些数组按照现行索引顺序进行排序(一般按照字面表排序, 如A-Z). 可以通过类UILocalizedIndexedCollation(该类可以根据特定localization)来对二维数组排序, 这样可以简化构建顺序的二维数据这种数据结构.

    模型对象需要提供一个string属性和返回string的方法来供类UILocalizedIndexedCollation进行检验. 如果模型对象提供的是一个方法, 那么这个方法应该不需要参数. 如果一个model对象可以代表tableView中的行, 这样非常方便, 所以在定义model时, 你可以定义一个属性来保存section的index, 或者row的index. 代码清单4-5展示了这样的model的定义
    代码清单3-5 设计model类

    @interface State : NSObject
    @property(nonatomic,copy) NSString *name;
    @property(nonatomic,copy) NSString *capitol;
    @property(nonatomic,copy) NSString *population;
    @property NSInteger sectionNumber;
    @end
    

    在填充tableView数据之前, 你应该将数据创建加载好. 下面代码展示了数据的加载和数据的排序处理

    代码清单3-6 loading数据

    - (void)viewDidLoad {
        [super viewDidLoad];
        UILocalizedIndexedCollation *theCollation = [UILocalizedIndexedCollation currentCollation];
        self.states = [NSMutableArray arrayWithCapacity:1];
     
        NSString *thePath = [[NSBundle mainBundle] pathForResource:@"States" ofType:@"plist"];
        NSArray *tempArray;
        NSMutableArray *statesTemp;
        if (thePath && (tempArray = [NSArray arrayWithContentsOfFile:thePath]) ) {
            statesTemp = [NSMutableArray arrayWithCapacity:1];
            for (NSDictionary *stateDict in tempArray) {
                State *aState = [[State alloc] init];
                aState.name = [stateDict objectForKey:@"Name"];
                aState.population = [stateDict objectForKey:@"Population"];
                aState.capitol = [stateDict objectForKey:@"Capitol"];
                [statesTemp addObject:aState];
            }
        } else  {
            return;
        }
    

    当数据加载完后, 可以使用UILocalizedIndexedCollation类对数据进行处理

    代码清单3-7 对数据进行索引化

    // viewDidLoad continued...
        // (1)
        for (State *theState in statesTemp) {
            NSInteger sect = [theCollation sectionForObject:theState collationStringSelector:@selector(name)];
            theState.sectionNumber = sect;
        }
        // (2)
        NSInteger highSection = [[theCollation sectionTitles] count];
        NSMutableArray *sectionArrays = [NSMutableArray arrayWithCapacity:highSection];
        for (int i = 0; i < highSection; i++) {
            NSMutableArray *sectionArray = [NSMutableArray arrayWithCapacity:1];
            [sectionArrays addObject:sectionArray];
        }
        // (3)
        for (State *theState in statesTemp) {
            [(NSMutableArray *)[sectionArrays objectAtIndex:theState.sectionNumber] addObject:theState];
        }
        // (4)
        for (NSMutableArray *sectionArray in sectionArrays) {
            NSArray *sortedSection = [theCollation sortedArrayFromArray:sectionArray
                collationStringSelector:@selector(name)];
            [self.states addObject:sortedSection];
        }
    } // end of viewDidLoad
    

    对上面代码做一些说明:

    1. 遍历所有model数据, 调用collation对象的sectionForObject:collationStringSelector:方法, 该方法的参数是一个model对象, 和一个model对象属性或者定义的方法(用来进行检验的), 然后该方法会返回一个处于哪个section的index. 将前面的index设置为model的sectionNumber
    2. 创建一个section数组(外层), 保存每一个section中的数据.
    3. 循环遍历一个模型数组中的每个数据, 将其加入相应的section数组(内存)中.
    4. 然后调用collation manager的sortedArrayFromArray:collationStringSelector:方法来对section数组(外层)中的内层数组排序

    经过上面的步骤后, DataSource已经将数据准备好, 下面是具体设置列表的index, 代码清单3-8展示了这一过程, 该过程调用了UILocalizedIndexedCollation类的两个方法sectionIndexTitlessectionForSectionIndexTitleAtIndex:来完成任务, 注意在方法tableView:titleForHeaderInSection:方法中, 会将空的section从列表中移除.

    代码清单3-8 为tableView提供section-index数据

    - (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
        return [[UILocalizedIndexedCollation currentCollation] sectionIndexTitles];
    }
     
    - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
        if ([[self.states objectAtIndex:section] count] > 0) {
            return [[[UILocalizedIndexedCollation currentCollation] sectionTitles] objectAtIndex:section];
        }
        return nil;
    }
     
    - (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index
    {
        return [[UILocalizedIndexedCollation currentCollation] sectionForSectionIndexTitleAtIndex:index];
    }
    

    最后实现DataSource协议方法, 如代码清单3-9所示
    代码清单3-9 填充索引列表中的行

    - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
        return [self.states count];
    }
     
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
        return [[self.states objectAtIndex:section] count];
    }
     
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
        static NSString *CellIdentifier = @"StateCell";
        UITableViewCell *cell;
        cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
        if (cell == nil) {
            cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
        }
        State *stateObj = [[self.states objectAtIndex:indexPath.section] objectAtIndex:indexPath.row];
        cell.textLabel.text = stateObj.name;
        return cell;
    }
    

    如果你的TableView是索引列表, 那么请注意, 你应该将cell的accessoryType设置为UITableViewCellAccessoryNone.

    经过上面的一个步骤轮廓, 你可以调用reloadSectionIndextitles方法来reload列表的index

    tableView的可选性配置


    对于tableView, UIKit提供了很多Api来对tableView进行配置, 可以在外观和行为上对tableView进行各种控制, 比如控制section, 控制row等等. 下面就这些配置提供几个方向给你选择.

    添加一个自定义标题

    在对tableView进行配置时, 使用UITableView类的方法对tableView配置是全局性的. 下面代码展示了如何为tableView添加一个自定义的title(使用UILabel)

    代码清单3-10 为tableView添加一个标题

    - (void)loadView {
        CGRect titleRect = CGRectMake(0, 0, 300, 40);
        UILabel *tableTitle = [[UILabel alloc] initWithFrame:titleRect];
        tableTitle.textColor = [UIColor blueColor];
        tableTitle.backgroundColor = [self.tableView backgroundColor];
        tableTitle.opaque = YES;
        tableTitle.font = [UIFont boldSystemFontOfSize:18];
        tableTitle.text = [curTrail objectForKey:@"Name"];
        self.tableView.tableHeaderView = tableTitle;
        [self.tableView reloadData];
    }
    

    提供一个section表示

    代码清单3-11 返回section的title

    - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
        // Returns section title based on physical state: [solid, liquid, gas, artificial]
        return [[[PeriodicElements sharedPeriodicElements] elementPhysicalStatesArray] objectAtIndex:section];
    }
    

    缩进一行

    代码清单3-12 设置行的缩进

    - (NSInteger)tableView:(UITableView *)tableView indentationLevelForRowAtIndexPath:(NSIndexPath *)indexPath {
        if ( indexPath.section==TRAIL_MAP_SECTION && indexPath.row==0 ) {
            return 2;
        }
        return 1;
    }
    

    改变一行的高度

    代码清单3-13 控制不同行的行高

    - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
        CGFloat result;
        switch ([indexPath row])
        {
            case 0:
            {
                result = kUIRowHeight;
                break;
            }
            case 1:
            {
                result = kUIRowLabelHeight;
                break;
            }
        }
        return result;
    }
    

    自定义Cell

    关于要自定义cell, 那么主要的操作集中在DataSource的tableView:cellForRowAtIndexPath:方法中, 返回一个自定义的UITableViewCell子类的cell, 具体内容请看下篇文章详细地看看tableView中的cell

    相关文章

      网友评论

        本文标题:iOS TableView 编程指导(三)-创建和配置Table

        本文链接:https://www.haomeiwen.com/subject/oufkzftx.html