iOS TableView 编程指导(四)-详细地看看tableView中的cell

tableView使用cell对象(UITableViewCell的实例)绘制可见的row, 然后缓存这些cell. cell有DataSource通过方法tableView:cellForRowAtIndexPath:提供.

在本文中, 将会了解以下知识点:

  • cell的特性
  • 如何使用UITableViewCell自带能力来设置cell的内容
  • 如何创建一个自定义的UITableViewCell对象

cell的特性


一个cell包含多个不同的部分, 每一个部分可能随着tableView的模式(编辑模式/正常模式)改变而改变. 正常情况下, cell包含:text, image, 或者其他区别标识符三部分. 如图4-1展示cell的几个主要部分


图4-1 tableView中cell的组成

右边的小部分区域时用来存放AccessoryView的比如:disclosure indicator, detail disclosure control, 和其他常见的control(switch, slider等), 另外还可以是custom view.

但是当tableView处于编辑模式时, 编辑控件会显示在cell(如果该cell已经设置好)的最左边的区域, 如图4-2所示.


图4-2 处于编辑模式下cell的内容组成

这个编辑控件可以是一个删除按钮(一个红色减号加上一个圈)也可以是一个插入按钮(一个绿色的加号加上一个圈). 整个cell中的内容向右挪动以空出地方显示编辑控件. 如果cell设置可以自由排序, 那么会有一个排序控件出现在cell的右侧(在accessory view的右侧). 排序控件是几根短横线, 按住排序控件拖动cell就可以对cell进行自由排序了.

如果一个cell是复用的, 你在storyboard给该cell设置一个任意字符串作为复用标识符(一个典型的列子). 在运行期, tableView会将cell保存在一个内部队列(internal queue)中. 当tableView让DataSource去设置一个将要显示的cell时, DataSource对象通过调用tableView的dequeueReusableCellWithIdentifier:方法来访问内部队列中cell, 只需传一个标识符就行了. 然后DataSource拿到该cell后就会对其内容和其他属性的一些设置, 之后才是将该cell返回给tableView. 这样做的原因当然处于性能的考虑, 避免了重复创建大量的cell.

TableView中的row可以同时出现多种样式的cell, 可以是系统预定义的四种, 或者是用户自定义的cell, 这样在cell复用时, 需要用一个identifier来表示每种cell, 因此TableView中的内部队列中, 保存多种使用标识符标记的cell.

有三种方式为TableView提供cell:

  1. 使用系统预定的几种style创建
  2. 在storyboard中定制cell, 往cell中添加subview, 再从storyboard加载cell
  3. 继承UITableViewCell, 往cell的的contentView中添加内容.
    注意, cell的contentView是一个容器, 本身并不显示.

使用cell的预定义样式


可以使用UITableViewCell来直接创建预定义风格的cell, 系统定义了四种风格cell, 你可以使用现成的风格来创建cell, 本系列文章第一篇已详细讲解过, 下面代码展示着四种风格代码表示.

typedef enum {
    UITableViewCellStyleDefault,
    UITableViewCellStyleValue1,
    UITableViewCellStyleValue2,
    UITableViewCellStyleSubtitle
} UITableViewCellStyle;

这四种cell内部包括两部分:一个/多个text,一个image(可隐藏). 如下图


图4-3 cell的系统默认风格

UITableViewCell定义了三个属性来表示它的内容:

  • textLabel-一个label用来展示title
  • detailTextLabel-一个label用来展示subtitle(展示详细内容)
  • imageView-一个image view用来显示image

图4-4展示了直接使用UITableViewCell创建cell的例子(该例中cell的风格是UITableViewCellStyleSubtitle).

图4-4 UITableViewCellStyleSubtitle风格的cell

代码4-1, 展示tableView:cellForRowAtIndexPath:中实现图4-4中的cell.
代码清单4-1 配置UITableViewCell中的text和image

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MyIdentifier"];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"MyIdentifier"];
        cell.selectionStyle = UITableViewCellSelectionStyleNone;
    }
    NSDictionary *item = (NSDictionary *)[self.content objectAtIndex:indexPath.row];
    cell.textLabel.text = [item objectForKey:@"mainTitleKey"];
    cell.detailTextLabel.text = [item objectForKey:@"secondaryTitleKey"];
    NSString *path = [[NSBundle mainBundle] pathForResource:[item objectForKey:@"imageKey"] ofType:@"png"];
    UIImage *theImage = [UIImage imageWithContentsOfFile:path];
    cell.imageView.image = theImage;
    return cell;
}

在上面方法中, 每次复用cell时, 都应该刷新cell的内容.

在设置cell时, 你还可以:

  • selectionStyle- 控制cell选中时的外观
  • accessoryTypeaccessoryView- 让你可以设置标准(非编辑状态)的accessoryView(disclosure indicator or detail disclosure control)或者自定义的accessoryView, 自定义时, 你需要提供UIView对象, 比如switch, slider等.
  • editingAccessoryTypeeditingAccessoryView- 让你可以设置标准(编辑状态)的accessoryView(disclosure indicator or detail disclosure control)或者自定义的accessoryView, 自定义时, 你需要提供UIView对象, 比如switch, slider等.
  • showsReorderControl-控制cell是否显示排序控件(编辑模式下). 有个与之相关的属性editingStyle, 该属性代表cell中编辑控件的风格, 是只读的, 需要通过来delegate方法tableView:editingStyleForRowAtIndexPath:来控制.
  • backgroundViewselectedBackgroundView- 为cell提供一个背景(分选中和非选中)
  • indentationLevelindentationWidth- 设置cell中内容的缩进等级和缩进等级的width

因为cell继承自UIView, 所以你也可以对cell设置UIView中的属性, 比如设置背景颜色. 代码4-2展示了在delegate方法tableView:willDisplayCell:forRowAtIndexPath:来修改行的背景颜色
代码清单4-2 修改cell的背景颜色

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
    if (indexPath.row%2 == 0) {
        UIColor *altCellColor = [UIColor colorWithWhite:0.7 alpha:0.1];
        cell.backgroundColor = altCellColor;
    }
}

代码4-2说明了TableView API设计的一个重要特性. 在TableView将要绘制一行时, 会发送一个tableView:willDisplayCell:forRowAtIndexPath:给到delegate, 这样可以在绘制前提供一个做最后的修改的机会给你. 在该方法中你应该对cell只做一些基于状态的修改, 而不是修改cell的content.

自定义cell


使用系统预定义的四种样式的cell, 虽然可以满足很多场景的需求, 但是如果你想更改cell中的内容组成(不单单是text, image, 或者数量上的变化), 内容的layout变化, 或者cell的一些特性, 那么此时预定义的样式没法满足需求. 下面有两个解决方案:

  • 往cell的contentView中添加subview
  • 创建自定义cell,新建一个UITableViewCell的子类
    接下来的内容是对上面两个方案的讲解.

从storyboard中加载tableView的cell

在storyboard中, TableView有两种cell, dynamic和static. 如果TableView需要显示大量的cell, 那么选中dynamic; 如果在编译期就知道cell的数量, 那么选择static; 选中TableView中的cell, 下一级展示该item的详细信息时, static是不错的选择.

你可以通过TableView对象直接设置cell的static或者dynamic样式. 图4-5中, 展示master-detail类型的APP中TableView设计例子, 在该列中, master tableView用的dynamic, detail TableView用的是static.


图4-5 storyboard中的TableView cell
设置动态(dynamic)cell

在本小节中, 你使用custom的cell原型创建TableView, 在运行期, DataSource对象dequeue cell, 设置cell, 然后丢给TableView去绘制, 效果如图4-6


图4-6 使用custom prototype的cell

DataSource可以用两种不同的方式去访问cell中的subview. 第一种是使用UIView的tag属性, 另外一种是使用storyboard中的outlet. 使用tag是较方便的, 但是这样会使得代码中的tag和storyboard中的tag数字耦合在一起, 使得代码缺乏灵活性. 使用第二方法比较麻烦点, 不仅需要设置storyboard还需要创建UITableViewCell的子类, 下面就这两方法具体讲解.

创建使用故事板加载自定义表视图单元格的项目

  1. 使用“Master-Detail应用程序模板”创建项目并选择“使用故事板”选项
  2. 在故事板画布上,选择主视图控制器
  3. 在身份检查器(Identity inspector)中,验证该类是否被设置为自定义MasterViewController控制器类
  4. 选择主视图控制器内部的表视图
  5. 在属性检查器中,验证内容弹出菜单是否设置为动态原型(Dynamic Prototypes)
  6. 选择原型单元格
  7. 在属性检查器中,在样式弹出菜单中选择“自定义”
  8. 在标识符文本字段中输入重用标识符。
    这是您在dequeueReusableCellWithIdentifier::消息中发送到表视图的相同的 reuse identifier. 例如,请参见清单4-3。
  9. 在Accessory弹出菜单中选择Disclosure Indicator
  10. 将对象从库拖动到单元格上。
    对于这个例子,拖动两个标签对象并将它们放置在单元格的结尾附近(为附件视图留出空间)。
  11. 选择对象并设置它们的属性、大小和自动恢复特性。
    此步骤一个关键点是为cell中的每个部分设置一个tag。在属性检查器的视图部分中找到此属性,并为每个对象分配唯一的整数。

现在, 你开始手动获取TableView的数据(对应本例而言, 只需要获取每个cell的行数), 代码4-3是tableView:cellForRowAtIndexPath:的实现, 使用当前行号来填充tableView.
代码清单4-3 使用tag来为cell设置数据

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MyIdentifier"];
 
    UILabel *label;
    label = (UILabel *)[cell viewWithTag:1];
    label.text = [NSString stringWithFormat:@"%d", indexPath.row];
 
    label = (UILabel *)[cell viewWithTag:2];
    label.text = [NSString stringWithFormat:@"%d", NUMBER_OF_ROWS - indexPath.row];
 
    return cell;
}

注意:因为使用storyboard, 所以dequeueReusableCellWithIdentifier:总会返回一个有效的cell, 所以不会对cell做nil判断.

如果不喜欢使用tag,可以使用另一种方法来设置单元格中的内容。为cell定义一个UITableViewCell子类, 然后定义一些自定义属性。在故事板中,将新类与原型单元格相关联,并将outlet连接到单元格中的相应对象。

为自定义单元格的内容使用outlet

  1. 将Objective-C类名为MyTableViewCell单元格添加到项目中。

  2. 将以下代码添加到MyTableViewCell.h:

    @interface MyTableViewCell : UITableViewCell
    
    @property (nonatomic, weak) IBOutlet UILabel *firstLabel;
    @property (nonatomic, weak) IBOutlet UILabel *secondLabel;
    @end
    
  3. 将以下代码行添加到实现DataSource协议的源文件中:

     #import "MyTableViewCell.h"
    
  4. 使用身份检查器将原型单元格的类设置为MyTableViewCell

  5. 使用连接检查器将原型cell中的两个outlet连接到相应的标签


    image
  6. 按照代码4-4所示来实现tableView:cellForRowAtIndexPath:方法

代码清单4-4 使用outlet来为cell添加数据

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    MyTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MyIdentifier"];
 
    cell.firstLabel.text = [NSString stringWithFormat:@"%d", indexPath.row];
    cell.secondLabel.text = [NSString stringWithFormat:@"%d", NUMBER_OF_ROWS - indexPath.row];
 
    return cell;
}
设置静态(static)cell

本小节讲解使用静态cell来创建tableView, 效果如图4-7所示:


图4-7 静态cell

和前面dynamic步骤一样, 首先是往项目中添加UITableViewController的子类. 为第一个cell中的master row label和最后一个cell中的slider, slider value label创建三个outlet, 如代码4-5所示
代码清单4-5 为static cell定义三个属性outlet

@interface DetailViewController : UITableViewController
 
@property (strong, nonatomic) id detailItem;
@property (weak, nonatomic) IBOutlet UILabel *masterRowLabel;
@property (weak, nonatomic) IBOutlet UILabel *sliderValueLabel;
@property (weak, nonatomic) IBOutlet UISlider *slider;
 
- (IBAction)logHello;
- (IBAction)sliderValueChanged:(UISlider *)slider;
 
@end

在故事板中,将表视图控制器对象从库拖动到画布上。选择表视图并在属性检查器中设置以下属性:

  1. 将内容弹出菜单设置为静态单元格。
  2. 将节数设置为2。
  3. 将样式弹出菜单设置为分组

对于表视图中的每个部分,使用属性检查器在标题字段中输入字符串。然后,对于cell,完成以下步骤:

  1. 删除第一个表视图部分中的三个单元格和第二节中的一个单元格
  2. 根据需要增加每个剩余单元的高度。
  3. 从库中拖动对象以组成每个单元的子视图,如图4-7所示
    不必为这些单元格分配重用标识符,因为您不打算实现DataSource方法tableView:cellForRowAtIndexPath:
  4. 设置这些对象的任何你期望的属性
    该列中的slider具有7.5的初始值, 范围是0-10.

选择表格视图控制器和显示器的连接检查。在表视图控制器和对应对象之间的三个outlet之间建立连接,如图4-8所示。在完成此操作时,实现清单4-5中声明的两个动作方法,并将目标动作连接至button和slider。


图4-8 给你static cell中的内容建立连接

为了给cell填充数据, 你需要实现detail controller中configureView方法
代码清单4-6 设置数据

- (void)configureView {
    if (self.detailItem) {
        self.masterRowLabel.text = [self.detailItem description];
    }
    self.sliderValueLabel.text = [NSString stringWithFormat:@"%1.1f", self.slider.value];
}

detail view Controller 会在viewDidLoad中调用configureViewsetDetailItem:方法.

使用代码的方式往cell中contentView中添加subview

一个cell是用来展示tableView中的行的, cell是一个view(UITableViewCell继承自UIView). 作为一个view, 一个cell拥有一个contentView(cell中各种content的superview, 一个容器), 通过往content view中添加subview, 和对这些subview进行layout来自定义tableView中row的外观, 要访问cell的contentView可以通过cell的属性contentView.

这种方式相对简单; 因为不需要创建UITableViewCell的子类来实现自定义cell中的内容. 但是, 在使用这种方式时, 你尽可能地避免将view的设置为透明, 因为透明子视图会影响滚动性能, 因为这会增加合成视图的成本. subview应该是设置为不透明, 通常设置和cell有相同的BackgroundColor. 如果cell是可选的话, 当选中cell时注意要将cell的content设置为highlighted. 当cell中的subview实现了属性highlightedaccessor method(set/get方法)的话, 当cell选中时cell的content也会自动被选中的.

假设你cell中的text和Image的位置都需要自定义, 例如, 你想将Image置于cell的右边, title和subtitle右对齐并和Image紧靠. 如图4-9所示


图4-9 自定义cell中subview的位置

代码4-7描述了DataSource的代码实现方法tableView:cellForRowAtIndexPath:. 首先是使用重用标识符来获取复用cell. 如果复用cell不存在, 就创建两个label(subtitle和title)和imageView, 设置content的frame, 然后将这些content放入新创建的cell中, 并用tag标记每一个content. 如果复用cell存在, 就直接使用viewWithTag来获取上面创建的三个content, 最后对content赋值.

代码清单4-7 往cell中添加subview

#define MAINLABEL_TAG 1
#define SECONDLABEL_TAG 2
#define PHOTO_TAG 3
 
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
 
    static NSString *CellIdentifier = @"ImageOnRightCell";
 
    UILabel *mainLabel, *secondLabel;
    UIImageView *photo;
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
        cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;
 
        mainLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.0, 0.0, 220.0, 15.0)];
        mainLabel.tag = MAINLABEL_TAG;
        mainLabel.font = [UIFont systemFontOfSize:14.0];
        mainLabel.textAlignment = UITextAlignmentRight;
        mainLabel.textColor = [UIColor blackColor];
        mainLabel.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleHeight;
        [cell.contentView addSubview:mainLabel];
 
        secondLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.0, 20.0, 220.0, 25.0)];
        secondLabel.tag = SECONDLABEL_TAG;
        secondLabel.font = [UIFont systemFontOfSize:12.0];
        secondLabel.textAlignment = UITextAlignmentRight;
        secondLabel.textColor = [UIColor darkGrayColor];
        secondLabel.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleHeight;
        [cell.contentView addSubview:secondLabel];
 
        photo = [[UIImageView alloc] initWithFrame:CGRectMake(225.0, 0.0, 80.0, 45.0)];
        photo.tag = PHOTO_TAG;
        photo.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleHeight;
        [cell.contentView addSubview:photo];
    } else {
        mainLabel = (UILabel *)[cell.contentView viewWithTag:MAINLABEL_TAG];
        secondLabel = (UILabel *)[cell.contentView viewWithTag:SECONDLABEL_TAG];
        photo = (UIImageView *)[cell.contentView viewWithTag:PHOTO_TAG];
    }
    NSDictionary *aDict = [self.list objectAtIndex:indexPath.row];
    mainLabel.text = [aDict objectForKey:@"mainTitleKey"];
    secondLabel.text = [aDict objectForKey:@"secondaryTitleKey"];
    NSString *imagePath = [[NSBundle mainBundle] pathForResource:[aDict objectForKey:@"imageKey"] ofType:@"png"];
    UIImage *theImage = [UIImage imageWithContentsOfFile:imagePath];
    photo.image = theImage;
 
    return cell;
}

上面的代码中使用tag标记cell中的subview的好处时, 在delegate活DataSource中获取指定的cell后, 你可以随时通过tag来访问cell的subview, 然后对这些subview赋值呀, 重新设置等等操作, 这方很方便.

然后你看到了, 上面创建cell时使用的UITableViewCell使用系统样式UITableViewCellStyleDefault而不是自定义的cell, 这是因为系统预定义的cell中的textLabel,detailTextLabelimageView这些subview在你给它们设置内容之前是nil的, 所以你往系统的cell添加subview时, 不会和系统的subview造成冲突.

注意:实现上面的功能还有另外一种办法就是, 继承UITableViewCell, 并在实现文件中重写layoutSubViews方法, 在该方法中重新布局textLabel,detailTextLabelimageView, 记得要先调用super方法.

使用文本内容实现“attributed string”效果的一种方法是布局UITabeleCell中contentView中UILabel子视图。每个Label的文本可以有自己的字体,颜色,大小,对齐方式和其他特征。如果您想在标签对象中使用这种变体,请创建多个标签并将它们相对于彼此放置

增强表视图单元的可访问性


该内容, 是和VoiceOver(残疾人用的到)有关的, 我这里不作讲解, 有兴趣可以自己去看Enhancing the Accessibility of Table View Cells

关于cell和tableView的性能 ※※※


关于如何使用tableView的cell, 比如使用系统的cell还是自定义, 有个关键因素是tableView的性能, 在使用tableView时, 应该关注下面几个点:

  • 复用cell 创建一个对象是需要消耗的, 特别是在大量重复创建对象时, 比如用户滚动tableView浏览大量数据时. 所以如果你复用一个cell而不是重复创建, 这样对app的性能提升有很大的帮助.
  • 避免重复布局content 当重复使用具有自定义子视图的cell时,每次tableView请求cell时不要布置这些子视图。创建单元格时,将子视图布置一次就好。
  • 使用不透明的subview 创建cell的subview,尽量设置这些cell的opaque为YES, 这样对性能提高有帮助.
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    阳明先生x阅读 15,967评论 3 119
  • 1.目的 整理产品设计通用规范。在产品设计的过程中不断补充完善,实现平台的统一性,提升团队工作效率。 2.通用规范...
    瘦小妞儿要知足阅读 386评论 0 1
  • 起形起了蛮久的画完之后发现还是有瑕疵 快一个月没去上课了希望可以让张大大觉得 矮油进步很大哟 复习到冲刺阶段了这两...
    TrembleLala阅读 226评论 6 3
  • 余生总有空闲,寻你说声再见。 在你以后,喜欢的每一个人都有你的影子。 我拥抱了时间,然后背对你,不...
    一纸疯癫阅读 205评论 6 6
  • 去今天下午尚老师让我们演老鼠嫁女儿的小同学们,排列的都有太阳、乌云、大风、围墙。我演的是大风,上午排列的都是小熊拔牙的。
    魏雅轩阅读 186评论 0 0