图片 10

iOS UITableView 拖动排序的实现

UITbableView作为列表展示信息,除了展示的功能,有时还会用到删除,排序等功能,下面就来讲解一下如何实现排序。

UITableView除了常规的选择模式(selection
mode)外还有一个编辑模式(editing
mode),在编辑模式中可实现删除,插入,多选,重排序等。

最近比较忙,没什么时间写,断断续续写一点。

排序是当表格进入编辑状态后,在单元格的右侧会出现一个按钮,点击按钮,就可以拖动单元格,移动位置,进行手动排序。

一.进入编辑模式

UITableView是我们开发过程中比较常用的,用于显示一系列对象,UITableView继承自UIScrollViewUIScrollView可以在任意方向滑动,而UITableView只在垂直方向上滑动。UITableView中的内容是由UITableViewCell负责显示的。

图片 1图片 2

通过直接设置UITableView的editing属性或向其发送setEditing:animated:消息,可将其置于编辑模式。

1. UITableViewCell

  • UITableViewUITableViewCell组成,UITabelViewCell负责显示数据。
  • UITableView的每一行,即每一个UITableViewCell显示一条项目。
  • UITableViewCell对象的数量不受限制,仅由设备内存决定。
  • UITableViewCell类定义了单元格在UITableView中的属性和行为。

创建 UITableViewCell 的时候,你可以自定义一个 cell
,或者使用系统预定义的几种格式。系统预定义的 cell 提供了
textLabeldetailTextLabel属性和imageView属性用来设置cell的内容和图片。样式由UITableViewCellStyle枚举来控制:

枚举类型 描述
.default 包含一个左侧的可选图像视图,和一个左对齐的标签对象。
.value1 包含一个左侧的可选视图和一个左对齐的标签对象,在单元格右侧还有一个灰色、右对齐的标签对象。
.value2 包含一个左侧、右对齐的蓝色文字标签对象和一个右侧的左对齐的标签对象。
.subtitle 包含一个左侧的可选图像视图,和一个左对齐的标签对象,在这个标签对象下方,还有一个字体较小的标签对象。

图片 3

使用系统自带拖动排序功能的步骤:

self.tableview.editing = YES; [self.tableview setEditing:YES
animated:YES];

2. 创建一个UITableView

创建UITableView,首先是实例化一个UITableView对象,还要涉及到它的代理UITabelViewDataSource、UITableViewDelegate,在UITableViewDataSource代理方法中定义UITableViewCell的样式。

import UIKit

class ViewController:UIViewController,UITableViewDataSource,UITableViewDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()

        let tableView = UITableView(frame: view.bounds, style: .grouped)
        tableView.backgroundColor = UIColor.white;
        view.addSubview(tableView)
        tableView.dataSource = self
        tableView.delegate = self
    }

//MARK: UITableViewDataSource
    // cell的个数
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 10
    }
    // UITableViewCell
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cellid = "testCellID"
        var cell = tableView.dequeueReusableCell(withIdentifier: cellid)
        if cell==nil {
            cell = UITableViewCell(style: .subtitle, reuseIdentifier: cellid)
        }

        cell?.textLabel?.text = "这个是标题~"
        cell?.detailTextLabel?.text = "这里是内容了油~"
        cell?.imageView?.image = UIImage(named:"Expense_success")
        return cell!
    }

//MARK: UITableViewDelegate
    // 设置cell高度
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 44.0
    }
    // 选中cell后执行此方法
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print(indexPath.row)
    }  
}
  • 添加了代理协议UITableViewCell,主要用来给UITableView提供数据来源,并用来处理数据源的变化。
    它的主要带你方法:

    • tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath):
      初始化和复用指定索引位置的UITableViewCell必须实现
    • tableView(_ tableView: UITableView, numberOfRowsInSection section: Int):
      设置某一章节(section)中的单元格数量,必须实现
    • numberOfSections(in tableView: UITableView):
      设置表格中的章节(section)个数。
    • tableView(_ tableView: UITableView, titleForHeaderInSection section: Int):
      设置指定章节的标题文字,如果不设置或代理返回值为nil,不显示。
    • tableView(_ tableView: UITableView, titleForFooterInSection section: Int):
      设置章节脚部标题文字,如果不设置或代理返回值为nil,不显示。
    • tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath):
      设置表格中指定索引位置的cell是否可编辑,可编辑的cell会显示插入和删除的图标。
    • tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath):
      当完成插入或删除操作时会调用此方法。
    • tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath):
      设置指定索引位置的cell是否可以通过拖动的方式,改变它的位置。
    • tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath):
      cell从一个位置拖动到另一个位置时调用此方法。
  • 然后我们进行了实例化,设置位置和尺寸,然后设置UITableView的数据源为当前视图控制器对象,即设置代理UITableViewDataSource

  • 实现数据源协议定义中的方法,从而设置章节中cell的个数,以及对cell进行初始化和复用设置。

  • indexPathNSIndexPath类用来描述在嵌套数列的树种指定节点的路径,即索引路径。索引路径中的每一个索引,都用来表示一个节点的子数组中的指定索引位置。事实上,NSIndexPath描述了一整个数列,表示在表格视图中指定的章节中的指定行。
    UITableView中的索引路径包括两个元素,第一个元素section是表格的章节序号,第二个元素row表示章节中的行序号。

  • 还添加了UITableViewDelegate代理协议,它的主要作用是提供一些可选的方法,用来控制表格的选择、指定章节的头和尾的显示、单元格内容的复制和粘贴以及协助完成单元格的排序等功能。
    主要代理方法有:

    • tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath):
      设置单元格高度,每当表格需要显示时,都会调用此方法。
    • tableView(_ tableView: UITableView, heightForHeaderInSection section: Int)
      设置某一索引下的章节头部的高度。
    • tableView(_ tableView: UITableView, heightForFooterInSection section: Int):
      设置某一索引下的章节尾部的高度。
    • tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath):
      当指定索引位置上的单元格即将显示时,调用此方法。此方法是委托对象有机会在单元格显示之前重写其状态属性,如背景颜色等。
    • tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath):
      当用户点击选择指定索引位置的单元格时,调用此方法。
    • tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath):
      当用户点击一个已经被选中的单元格时,调用此方法。

1、让tableView进入编辑状态,也就是设置它的editing为YES

2、返回编辑模式,也就是实现UITableViewDelegate中的tableview:editingStyleForRowAtIndexPath:方法,在里面返回UITableViewCellEditingStyleNone模式。如果不实现,默认返回的就是删除模式

3、实现tableView:moveRowAtIndexPath:toIndexPath方法,只要实现该方法,就能实现单元格的拖动排序,但只是实现了表面的排序,并没有修改真实地数据

4、在方法中完成数据模型的更新

UIViewController本身也有editing属性和setEditing:animated:方法,在当前视图控制器由导航控制器控制且导航栏中包含editButtonItem时,若UIViewController的editing为NO,则显示为”Edit”,若editing为YES,则显示为”Done”。

3. UITableView 复用机制

复用机制在很多地方都有应用,比如去饭店吃饭,盘子、碗都不是一次性的,当客人使用过之后,会经过消毒、清洗。然后再给下一批客人使用。如果每个客人使用过之后都换一批新的话,那成本太高了。

UITableView也采用复用机制,一个UITableView可能需要显示100条数据,但屏幕尺寸有限,假设一次只能显示9条,如果滑动一下的话,会显示出第10条的一部分,所以当前屏幕在这种情况下最多只能显示出10条。
所以系统只需要创建10个UITableViewCell对象就好,当手指从下往上滑动时,回收处于屏幕之外的最上方单元格,并放置到表格最下方,作为将要显示的11个单元格。当UITableView对象从上往下滑动时,也是同样的服用机制。

在上面的代码中:

        let cellid = "testCellID"
        var cell = tableView.dequeueReusableCell(withIdentifier: cellid)

dequeueReusableCell方法的作用是从单元格对象池中获取指定类型并可复用的单元格对象。

        if cell==nil {
            cell = UITableViewCell(style: .subtitle, reuseIdentifier: cellid)
        }

如果从对象池中没有获得可复用的单元格,就调用实例化方法实例一个某一类型的、可复用的单元格。

  • style参数: 枚举常量,用于表示单元格的样式。
  • reuseIdentifier:
    作为一个字符串类型的参数,它用来标识具有相同类型的、可复用的单元格。对于相同类型的单元格,需要使用相同的reuseIdentifier参数。

代码:

可利用此按钮在设置UIViewController的editing状态时同时设置tableView的编辑状态。

4. 自定义UITableViewCell

一般对于相对复杂一些的显示内容,我们会创建一个UITableViewCell的类文件。

图片 4

Subclass of 写UITableViewCell

图片 5

上代码:

import UIKit

class NewTableViewCell: UITableViewCell {

    let width:CGFloat = UIScreen.main.bounds.width
    var userLabel:UILabel!      // 名字
    var birthdayLabel:UILabel!  // 出生日期
    var sexLabel:UILabel!       // 性别
    var iconImv:UIImageView!    // 头像

    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)

        // 头像
        iconImv = UIImageView(frame: CGRect(x: 20, y: 15, width: 44, height: 44))
        iconImv.layer.masksToBounds = true
        iconImv.layer.cornerRadius = 22.0

        // 名字
        userLabel = UILabel(frame: CGRect(x: 74, y: 18, width: 70, height: 15))
        userLabel.textColor = UIColor.black
        userLabel.font = UIFont.boldSystemFont(ofSize: 15)
        userLabel.textAlignment = .left

        // 性别
        sexLabel = UILabel(frame: CGRect(x: 150, y: 20, width: 50, height: 13))
        sexLabel.textColor = UIColor.black
        sexLabel.font = UIFont.systemFont(ofSize: 13)
        sexLabel.textAlignment = .left

        // 出生日期
        birthdayLabel = UILabel(frame: CGRect(x: 74, y: 49, width: width-94, height: 13))
        birthdayLabel.textColor = UIColor.gray
        birthdayLabel.font = UIFont.systemFont(ofSize: 13)
        birthdayLabel.textAlignment = .left

        contentView.addSubview(iconImv)
        contentView.addSubview(userLabel)
        contentView.addSubview(sexLabel)
        contentView.addSubview(birthdayLabel)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func awakeFromNib() {
        super.awakeFromNib()
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)
    } 
}
  • 上面代码中,首先给NewTableViewCell添加了五个属性:width屏幕宽度、iconImv头像、userLabel用户名、sexLabel性别和birthdayLabel生日。
  • 然后添加实例化方法:init(style: UITableViewCellStyle, reuseIdentifier: String?)并在方法中实例化定义的4个属性,将他们添加到屏幕上。
  • 最后实现继承自UITableViewCell类所必须的init?(coder aDecoder: NSCoder)构造函数。

现在我们完成了NewTableViewCell的创建,再到ViewController.swift类文件中,调用这个自定义单元格类。

import UIKit

class ViewController:UIViewController,UITableViewDataSource,UITableViewDelegate {

    var dataSource = [[String:String]()]

    override func viewDidLoad() {
        super.viewDidLoad()

        let tableView = UITableView(frame: view.bounds, style: .grouped)
        tableView.backgroundColor = UIColor.white;
        view.addSubview(tableView)
        tableView.dataSource = self
        tableView.delegate = self

        dataSource = [
          ["name":"王小明","sex":"男","icon":"my_def_photo","birthday":"2017-10-11"],
          ["name":"李磊","sex":"男","icon":"my_def_photo","birthday":"2011-12-30"],
          ["name":"韩梅","sex":"女","icon":"my_def_photo","birthday":"2014-9-10"],
          ["name":"JIM","sex":"男","icon":"my_def_photo","birthday":"2008-10-1"]]
        tableView.reloadData()
    }

//MARK: UITableViewDataSource
    // cell的个数
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return dataSource.count
    }
    // UITableViewCell
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cellid = "testCellID"
        var cell:NewTableViewCell? = tableView.dequeueReusableCell(withIdentifier: cellid) as? NewTableViewCell
        if cell==nil {
            cell = NewTableViewCell(style: .subtitle, reuseIdentifier: cellid)
        }
        let dict:Dictionary = dataSource[indexPath.row]
        cell?.iconImv.image = UIImage(named: dict["icon"]!)
        cell?.userLabel.text = dict["name"]
        cell?.sexLabel.text = dict["sex"]
        cell?.birthdayLabel.text = dict["birthday"]
        return cell!
    }

//MARK: UITableViewDelegate
    // 设置cell高度
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 74.0
    }
    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return 20
    }
    // 选中cell后执行此方法
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print(indexPath.row)
    }
}
  • 这里实例了一个数组,数组内的元素是字典,用来存放需要展示的数据。
  • 然后注意tableView(_ tableView: UITableView, numberOfRowsInSection section: Int)代理方法,返回参数是dataSource.count,意思是数组中有几条数据就展示几个Cell。
  • 接下来就是修改tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)方法中的Cell了。并根据dataSource数组中的数据对cell的元素进行赋值。
  • 后面我们还修改了cell的高度,和header的高度。跑一下项目:

图片 6

//  ViewController.m
//  JRTableView删除
//
//  Created by jerehedu on 15/6/11.
//  Copyright (c) 2015年 jerehedu. All rights reserved.
//

#import "ViewController.h"
#import "Goods.h"

@interface ViewController ()<UITableViewDataSource, UITableViewDelegate>

{
    UITableView *_tableView; //列表

    NSMutableArray *_goodsAry; //商品数组

    UIButton *_editBtn; //编辑按钮
}
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    //添加标题
    UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 20, self.view.frame.size.width, 44)];
    titleLabel.text = @"购物车";
    titleLabel.textAlignment = NSTextAlignmentCenter;
    titleLabel.backgroundColor = [UIColor redColor];
    titleLabel.textColor = [UIColor whiteColor];
    [self.view addSubview:titleLabel];

    //添加编辑按钮
    _editBtn = [UIButton buttonWithType:UIButtonTypeCustom];
    _editBtn.frame = CGRectMake(self.view.frame.size.width-60, 25, 50, 34);
    [_editBtn setTitle:@"编辑" forState:UIControlStateNormal];
    [_editBtn setTitle:@"完成" forState:UIControlStateSelected];
    _editBtn.titleLabel.font = [UIFont systemFontOfSize:15];
    _editBtn.backgroundColor = [UIColor colorWithRed:0.8 green:0.8 blue:0.8 alpha:0.5];
    [self.view addSubview:_editBtn];
    [_editBtn addTarget:self action:@selector(clickEditBtn:) forControlEvents:UIControlEventTouchUpInside];

    //添加tableview
    _tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 64, self.view.frame.size.width, self.view.frame.size.height-64)];
    _tableView.dataSource = self;
    _tableView.delegate = self;
    [self.view addSubview:_tableView];

    //取数据
    NSArray *ary = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"ShoppingGoodsList" ofType:@"plist"]];

    //把数据存到模型对象中,然后把对象存到数组中
    _goodsAry = [NSMutableArray array];
    for (int i=0; i<ary.count; i++) {
        Goods *good = [Goods goodsWithDic:ary[i]];
        [_goodsAry addObject:good];
    }
}

#pragma mark 数据源  返回有几行
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return _goodsAry.count;
}

#pragma mark 每行显示内容
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *idGood = @"goods";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:idGood];

    if (cell==nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:idGood];
    }

    Goods *good = _goodsAry[indexPath.row];

    cell.imageView.image = [UIImage imageNamed:good.icon];
    cell.textLabel.text = good.name;
    cell.detailTextLabel.text = good.details;
    cell.detailTextLabel.numberOfLines = 6;
    cell.detailTextLabel.textColor = [UIColor brownColor];

    return cell;
}

#pragma mark 选中行
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    // 取消选中状态
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
}

#pragma mark 设置行高
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return 110;
}

#pragma mark 点击编辑按钮
- (IBAction)clickEditBtn:(UIButton *)sender {

    //设置tableview编辑状态
    BOOL flag = !_tableView.editing;
    [_tableView setEditing:flag animated:YES];
    _editBtn.selected = flag;
}

#pragma mark 选择编辑模式,添加模式很少用,默认是删除
-(UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return UITableViewCellEditingStyleNone;
}

#pragma mark 排序 当移动了某一行时候会调用
//编辑状态下,只要实现这个方法,就能实现拖动排序
-(void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath
{
    // 取出要拖动的模型数据
    Goods *goods = _goodsAry[sourceIndexPath.row];
    //删除之前行的数据
    [_goodsAry removeObject:goods];
    // 插入数据到新的位置
    [_goodsAry insertObject:goods atIndex:destinationIndexPath.row];
}

@end

– (void)viewDidLoad {    [super viewDidLoad];    ….

5. 添加索引和章节(Section)

最常见的带有索引的TableView就是通讯录了吧,在TableView的右侧有一个垂直的索引序列,点击索引序列的元素可在表格中迅速定位到指定的位置,尤其是拥有大量数据的时候。

先来看一下索引需要用到的代理方法:

  • numberOfSections(in tableView: UITableView)
    设置TableView中章节(Section的数量)不设置默认为1。
  • tableView(_ tableView: UITableView, numberOfRowsInSection section: Int)
    在指定章节中,cell的个数。
    tableView(_ tableView: UITableView, titleForHeaderInSection section: Int)
    设置章节标题文字,返回结果为字符串,如果返回为nil,则不显示标题。
  • sectionIndexTitles(for tableView: UITableView)
    设置在表格右侧显示的索引序列的内容,返回结果为一个字符串数组
  • tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)
    TableViewCell初始化和复用

开始之前,需要先创建索引表格所需的数据源,刚才的例子中是添加了一个
数组 作为数据源,这里索引的话需要一个 字典 来作为数据源。
开发中需要数据源通常各种各样,不如加载本地文本文件和plist文件,或者从服务器请求书院,通过返回的JSON或XML作为数据源。这里我们仅创建一个字典作为数据源。

代码搞完了,上代码:

import UIKit

class IndexsViewController: UIViewController,UITableViewDataSource {

    let contents:Dictionary<String,[String]> =
        ["A":["安其拉"],
         "B":["步惊云","不知火舞","白起","扁鹊"],
         "C":["程咬金","成吉思汗","蔡文姬","曹操"],
         "D":["妲己","狄仁杰","典韦","貂蝉","达摩","大乔","东皇太一"],
         "G":["高渐离","关羽","宫本武藏","干将莫邪","鬼谷子"],
         "H":["韩信","后羿","花木兰","黄忠"],
         "J":["荆轲","姜子牙"],
         "L":["老夫子","刘邦","刘婵","鲁班七号","兰陵王","露娜","廉颇","李元芳","刘备","李白","吕布"],
         "M":["墨子","芈月"],
         "N":["牛魔","娜可露露","哪吒","女娲"],
         "P":["庞统",""],
         "S":["孙膑","孙尚香","孙悟空"],
         "W":["王昭君","武则天"],
         "X":["项羽","小乔"],
         "Y":["亚瑟","虞姬","嬴政"],
         "Z":["周瑜","庄周","甄姬","钟无艳","张飞","张良","钟馗","赵云","诸葛亮"]]
    var keys:[String] = []

    override func viewDidLoad() {
        super.viewDidLoad()
        navigationController?.isNavigationBarHidden = false
        // 把字典里的key拿出来放到一个数组中,备用,作为章节的标题
        keys = contents.keys.sorted()

        let tableView = UITableView(frame: view.bounds, style: .plain)
        tableView.dataSource = self
        view.addSubview(tableView)
    }
//MARK: UITableViewDataSource
    //MARK: 章节的个数
    func numberOfSections(in tableView: UITableView) -> Int {
        return keys.count
    }
    //MARK: 某一章节cell个数
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        let arr = contents[keys[section]]
        return (arr?.count)!
    }
    //MARK: 初始化cell
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        var cell = tableView.dequeueReusableCell(withIdentifier: "indexsCellId")
        if cell==nil {
            cell = UITableViewCell(style: .default, reuseIdentifier: "indexsCellId")
        }
        let arr = contents[keys[indexPath.section]]
        cell?.textLabel?.text = arr?[indexPath.row]
        return cell!
    }
    //MARK: 每一个章节的标题
    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        return keys[section]
    }
    //MARK: 设置索引序列内容
    func sectionIndexTitles(for tableView: UITableView) -> [String]? {
        return keys
    }  
}
  • 首先我们来确定一下数据源,本来是想用人名的,不过想想万一暴露了什么被发现~~,然后就用了农药里的英雄,劳逸结合,劳逸结合。这不是重点
  • 然后我们定义了一个数组keys,用来存放数据源里面的key
  • 实例化TableView,设置代理,实现需要用到的代理方法。这还不是重点。
  • 实现代理方法,都有注释,就不细说了,设置章节个数、设置每个章节的cell个数,初始化cell、设置每一个章节的头部标题。这也不是重点
  • 然后实现代理,设置索引内容:sectionIndexTitles(for tableView: UITableView)这才是重点,添加了这个方法,右侧才会出现索引序列。
    点击索引条目,会迅速的到达点击索引内容的部分。

需要注意的是:
实例化的时候,init方法第二个参数有两个值:.plain.grouped
如果不添加章节头部的话,基本看不出这两个值给tableView带来的变化。
但在这里,是有区别的:

  • .plain:如果传的是这个参数,向上滑动,当章节头部滑动到UITableVeiw的上方边界时,章节头部会停在边界位置,知道下一个章节头部到达它的位置,它才会继续向上滑动,下一个章节头部会占据它的位置。
  • . grouped:就正常滑动,没啥影响。

哦,除了这个还是有别的区别的,当设置的是plain,如果cell的个数不够扑满屏幕,系统会一直创建空的cell来扑满,可以试一下,能看到一条一条的横线,cell的分割线,如果是设置的grouped就不会有这种情况。

上张图:

图片 7

self.navigationItem.rightBarButtonItem = self.editButtonItem; }

6. cell的选择和取消选择

本来想等着看WWDC的,结果睡着了,早上看了新闻,又特么要做适配了,还特么这么贵。

这个有很多场景会遇到的,比如说,我们之前项目里有支付功能,需要设置一下默认支付方式,默认微信还是支付宝,产品给的UI就是一表格。

需求:三种支付方式,只能单选。

import UIKit

class SelectViewController: UIViewController,UITableViewDataSource,UITableViewDelegate {

    // 数据源,
    var dataSource = [["微信支付":"select"],["支付宝支付":"on"],["银联支付":"no"]]

    override func viewDidLoad() {
        super.viewDidLoad()

        let tableView = UITableView(frame: view.bounds, style: .grouped)
        tableView.backgroundColor = UIColor.white
        tableView.delegate = self
        tableView.dataSource = self
        view.addSubview(tableView)
    }

    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return 0.01
    }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return dataSource.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        var cell = tableView.dequeueReusableCell(withIdentifier: "selectCell")
        if cell == nil {
            cell = UITableViewCell(style: .default, reuseIdentifier: "selectCell")
            cell?.selectionStyle = .none
        }
        let dic = dataSource[indexPath.row] as Dictionary
        cell?.textLabel?.text = dic.keys.first
        if dic.values.first == "select" {
            cell?.accessoryType = .checkmark
        } else {
            cell?.accessoryType = .none
        }
        return cell!
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

        var i = 0
        for var dict in dataSource {

            if i == indexPath.row {
                dict[dict.keys.first!] = "select"
                dataSource[i] = dict
            } else {
                dict[dict.keys.first!] = "no"
                dataSource[i] = dict
            }
            i = i+1
        }
        tableView.reloadData()
    }

}
  • 先说一下思路:首先定义了一个数据源,因为是单选,所以用的是数组嵌套字典,key就是支付方式,value是代表是否选中个状态的string,如果选中,就把数据源里这个位置的value变成select,但是只能单选,所以还需要把其他的都变成no

  • 直接说重点:cell的实例化和复用代理方法中,可以看到,如果数据源里的valueselecrt,就把
    accessoryType属性设置成checkmark

  • tableView(_:, didSelectRowAt:)方法中,可以看到,用For循环来修改元数据中选中状态的value,然后调用reloadData()方法刷新cell。

  • cellaccessoryType属性的值是枚举UITableViewCellAccessoryType:

枚举类型 说明
none 没有任何的样式
detailButton 右侧蓝色的圆圈,中间带叹号
detailDisclosureButton 右侧蓝色圆圈带叹号,它的右侧还有一个灰色向右的箭头
disclosureIndicator 右侧一个灰色的向右箭头
checkmark 右侧蓝色对号

图片 8

-(void)setEditing:(BOOL)editing animated:(BOOL)animated {    [super

7. cell的插入和删除

插入和删除设计到的两个代理方法:

  • tableView(_ tableView:, editingStyleForRowAt indexPath:)
    确定编辑模式,Add or Delete
  • tableView(_ tableView:, commit editingStyle:, forRowAt indexPath:)
    当执行编辑操作时,调用此方法

和一个开启TableView编辑模式的方法:

  • setEditing(_ editing:, animated:)
    • editing: 是否开启编辑状态
    • animated: 是否有动画效果

import UIKit

class AddViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {

    var dataSource = [["微信","支付宝","银联"],["微信","支付宝","银联"]]
    var tableView:UITableView!
    override func viewDidLoad() {
        super.viewDidLoad()
        self.title = "支付方式"

        let rightBar = UIBarButtonItem.init(barButtonSystemItem: .add, target: self, action: #selector(addButtonClick))
        navigationItem.rightBarButtonItem = rightBar

        tableView = UITableView(frame: view.bounds, style: .grouped)
        tableView.delegate = self
        tableView.dataSource = self
        view.addSubview(tableView)

    }

    //MARK: 导航栏右侧按钮,点击开启或关闭编辑模式
    func addButtonClick() {
        tableView.setEditing(!tableView.isEditing, animated: true)
    }

    func numberOfSections(in tableView: UITableView) -> Int {
        return dataSource.count
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return dataSource[section].count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        var cell = tableView.dequeueReusableCell(withIdentifier: "addCell")
        if cell == nil {
            cell = UITableViewCell(style: .default, reuseIdentifier: "addCell")
            cell?.selectionStyle = .none
        }
        let arr = dataSource[indexPath.section] as Array
        cell?.textLabel?.text = arr[indexPath.row] as String
        return cell!
    }

    //MARK: 编辑模式,增加还是删除
    func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle {
        if indexPath.section == 1 {
            return .delete
        }
        return .insert
    }
    //MARK: 执行编辑操作时,调用此方法
    func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
        var arr = dataSource[indexPath.section] as Array
        if editingStyle == .insert {
            arr.insert("Apple Pay", at: indexPath.row)
            dataSource[indexPath.section] = arr
            tableView.insertRows(at: [indexPath], with: .right)
        } else {
            arr.remove(at: indexPath.row)
            dataSource[indexPath.section] = arr
            tableView.deleteRows(at: [indexPath], with: .left)
        }
    }

}
  • 说下思路,为了方便举例,我设置了两个Section,第一个Section做增加操作,第二个Section做删除操作,所以数据源里放的是两个数组。
  • 在导航条右侧添加了一个按钮,用来确定编辑状态是否开启。
  • setEditing方法上面说过了,就不说了。看下面两个代理方法。
  • tableView(_: ,editingStyleForRowAt:)方法返回参数是UITableViewCellEditingStyle:
    • insert: 添加操作
    • delete: 删除操作
    • none: 没有任何操作
  • tableView(_:, commit editingStyle:, forRowAt:),当执行了编辑操作,就会调起这个方法,你可以通过编辑状态对TableView和数据源进行操作。注意一定要把数据源和视图显示操作保持一致,不然很容易数组越界导致崩溃。
  • insertRows(at indexPaths: [IndexPath], with animation: UITableViewRowAnimation)deleteRows(at indexPaths: [IndexPath], with animation: UITableViewRowAnimation)方法的作用都是对TableViewCell的条数进行操作,一个增加一个删除
  • UITableViewRowAnimation枚举的作用是控制操作的动画:
属性 说明
fade 以淡入淡出的方式显示或移除
right 添加或删除时,从右侧滑入或划出
left 添加或删除时,从左侧滑入或划出
top 添加或删除时,从上方滑入或划出
bottom 添加或删除时,从底部滑入或划出
middle 表格视图将尽量使新旧cell居中显示在曾经或将要显示的位置
automatic 自动选择适合自身的动画方式
none 采用默认动画方式

图片 9

setEditing:editing animated:animated];    [self.tableView

8. cell位置移动功能

支持重新排序(Reordering)功能的TableView,允许用户拖动位于单元格右侧的排序图标,来重新排序TableView中的单元格。
排序功能一般用到的还是比较多的,我曾经做过一个类似PPT的功能,要求可以更换演示版页的排列方式,就是用的这个功能。

移动功能同样设计到了两个代理方法:

  • tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath)
    设置cell是否可移动
  • tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath)
    每次移动结束后会调用此方法

移动功能同样需要开启编辑模式setEditing(_: ,animated:)

import UIKit

class ReorderViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {

    var dataSource = ["微信","支付宝","银联","易宝"]
    var tableView:UITableView!
    override func viewDidLoad() {
        super.viewDidLoad()

        let rightBar = UIBarButtonItem.init(barButtonSystemItem: .add, target: self, action: #selector(ReorderButtonClick))
        navigationItem.rightBarButtonItem = rightBar

        tableView = UITableView(frame: view.bounds, style: .grouped)
        tableView.delegate = self
        tableView.dataSource = self
        view.addSubview(tableView)
    }

    func ReorderButtonClick() {
        tableView.setEditing(!tableView.isEditing, animated: true)
    }

    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return 0.01
    }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return dataSource.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        var cell = tableView.dequeueReusableCell(withIdentifier: "addCell")
        if cell == nil {
            cell = UITableViewCell(style: .default, reuseIdentifier: "addCell")
            cell?.selectionStyle = .none
        }
        cell?.textLabel?.text = dataSource[indexPath.row] as String
        return cell!
    }
    //MARK: 选择编辑模式,不删除也不添加就设置为none
    func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle {
        return .none
    }
    //MARK: 设置cell是否可移动
    func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
        return true
    }
    //MARK: 移动结束后调用此方法
    func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {

        let data = dataSource[sourceIndexPath.row]
        dataSource.remove(at: sourceIndexPath.row)
        dataSource.insert(data, at: destinationIndexPath.row)
    }
}
  • 说下思路,先开启TableView的编辑模式,但我们现在不需要添加或删除,只需要移动功能。
    直接看后面三个代理方法:
  • tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath):
    因为我们不需要添加或删除操作,所以调用此方法,并设置返回none
  • tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath):
    移动返回true
  • tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath):
    主要说这个,这个方法的作用是移动结束后调用,一般我们用它来同步数据源中的数据保持与视图同步,从代码里可以看出,三个参数的作用:

    • 第一个就是移动的tableView了,如果当前视图只有一个tableView不用管他。
    • 第二个参数是移动的cell曾经的位置。
    • 第三个参数是移动的最后位置。

图片 10

没有PS工具,我是把图片放到word里面截图出来的。

setEditing:editing animated:animated]; }

也可自定义其他按钮,将其响应设为修改tableView进入编辑模式。

– (void)editAction:(id)sender {    [self.tableView setEditing:YES
animated:YES]; }

UITableView接收到setEditing:animated:消息时,会发送同样的消息到所有可见的cell,设置其编辑模式。

二.插入和删除

进入编辑模式后,UITableView向其DataSource发送tableView:canEditRowAtIndexPath:消息询问每个indexPath是否可编辑,在此方法中对不可以编辑的cell返回NO,可以编辑的cell返回YES,若全部可编辑,可不实现,大部分应用不实现此方法。

-(BOOL)tableView:(UITableView *)tableView

canEditRowAtIndexPath:(NSIndexPath *)indexPath {    if (indexPath.row

== 1) {        return NO;    }    return YES; }

然后,UITableView
向其delegate发送tableView:editingStyleForRowAtIndexPath:消息询问EditingStyle,这里返回删除(UITableViewCellEditingStyleDelete)或者插入(UITableViewCellEditingStyleInsert);若不实现此方法,则默认为删除模式,即UITableViewCellEditingStyleDelete。

– (UITableViewCellEditingStyle)tableView:(UITableView *)tableView
editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {    return
UITableViewCellEditingStyleDelete;    //return
UITableViewCellEditingStyleInsert; }

当返回的是UITableViewCellEditingStyleDelete时,所有可编辑的Cell左侧都会显示红色的”减号”标示;

点击左边的“减号”,减号会旋转90度变竖形,并且cell右侧出现”Delete”按钮。

当返回的是UITableViewCellEditingStyleInsert时,在cell的左边会显示绿色”加号”按钮。

当点击”Delete”按钮或者”加号”按钮时,UITableView向其DataSource发送tableView:commitEditingStyle:forRowAtIndexPath:消息,根据传递editingStyle来执行实际的删除或插入操作,其流程是先修改tableView的数据模型,向其中删除或插入对应数据项,然后再调整tableView的显示,删除或插入对应的cell。

-(void)tableView:(UITableView *)tableView
commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
forRowAtIndexPath:(NSIndexPath *)indexPath {    if (editingStyle ==
UITableViewCellEditingStyleDelete)

{        [dataArray removeObjectAtIndex:indexPath.row];

[tableview deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]

withRowAnimation:UITableViewRowAnimationFade];    }else if(editingStyle
== UITableViewCellEditingStyleInsert)

{        [dataArray insertObject:@”new Item”

atIndex:indexPath.row];        [tableview

insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]

withRowAnimation:UITableViewRowAnimationFade];    } }

当要删除或插入section时,需调用deleteSections:withRowAnimation:或insertSections:withRowAnimation:方法。

插入删除流程的方法调用时序如图:

三.重排序

若当前tableView允许重排序,则会在每个cell的右侧出现三条灰色横线的控件,拖动此空间可将cell移动到不同的位置。重排序模式和删除/插入是并行的,即可在显示重排序空间的同时显示删除或插入控件。

当tableView的dataSource实现tableView:moveRowAtIndexPath:toIndexPath:方法后,tableView进入编辑模式后就会在右侧显示“重排序”控件,如图所示。

其消息处理流程为:

tableView收到setEditing:animated:消息并将同样的消息发送给可见的cell。

tableView向其DataSource发送tableView:canMoveRowAtIndexPath:消息,询问每一行是否可显示重排序空间,若为NO,则不显示,若为YES则显示。此方法不实现时默认所有行都可显示重排序控件。这时就会在每一行的右侧显示重排序控件。

因为重排序没有使用向delegate发送tableView:editingStyleForRowAtIndexPath:消息询问编辑模式,所以其
与删除、插入控件可同时存在,在一般情况下不应该同时出现,所以应实现了
tableView:editingStyleForRowAtIndexPath:并返回
UITableViewCellEditingStyleNone;若不实现
tableView:editingStyleForRowAtIndexPath:则会默认使用删除模式,即右侧出现“排序”控件时,左侧会出现”删
除”控件。

用户可拖动每行右侧的空间来移动该行的位置。

用户拖动某行经过目标行上方时,tableView会向delegate发送tableView:targetIndexPathForMoveFromRowAtIndexPath:toProposedIndexPath:(若delegate有实现)消息询问是否可移动到此位置(ProposedIndexPath),若不可移动到此位置则返回一个新的目的indexPath,可以的话直接将ProposedIndexPath返回即可。一般情况下不需实现此方法。

-(NSIndexPath *)tableView:(UITableView *)tableView
targetIndexPathForMoveFromRowAtIndexPath:(NSIndexPath *)sourceIndexPath
toProposedIndexPath:(NSIndexPath *)proposedDestinationIndexPath {    if
(proposedDestinationIndexPath.row == 5) {        return [NSIndexPath
indexPathForRow:8 inSection:0];      }    return
proposedDestinationIndexPath; }

tableView向其DataSource发送tableView:moveRowAtIndexPath:toIndexPath:消息,在此方法中更改tableView的数据模型,移动里面数据项的位置。

– (void)tableView:(UITableView *)tableView

moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath

toIndexPath:(NSIndexPath *)destinationIndexPath{    if(sourceIndexPath

== destinationIndexPath)        return;    id object = [dataArray

objectAtIndex:sourceIndexPath.row];    [dataArray

removeObjectAtIndex:sourceIndexPath.row];      [dataArray

insertObject:object atIndex:destinationIndexPath.row]; }

总体消息流程为:

四.Swipe to Delete

当用户在tableView的一行上滑动时,会在右侧直接出现删除按钮,点击删除按钮可删除此行。启用Swipe
to
Delete模式的条件时tableView的DataSource实现了tableView:commitEditingStyle:forRowAtIndexPath:方法;在iOS5中还要保证tableView的allowsMultipleSelectionDuringEditing属性不为YES(见后面解释)。

满足上述条件后,当用户在tableView的行上滑动时,tableView会向自身发送setEditing:animated:消息进入编
辑模式,同时向可见的cell发送setEditing:animated:消息,在当前滑动的行右侧会出现红色”Delete”按钮。

同样在点击”Delete”按钮时会向tableView的DataSource发送tableView:commitEditingStyle:forRowAtIndexPath:消息,执行实际的删除操作。

“Delete”按钮上显示的文字可以更改,包括普通删除模式下的”Delete”按钮。若要显示不同的内容,可在tableView
Delegate中实现tableView:titleForDeleteConfirmationButtonForRowAtIndexPath:方法,返回”Delete”按钮中需要显示的内容

tableView在向自身发送setEditing:animated:消息的前后,会向其delegate分别发送tableView:willBeginEditingRowAtIndexPath:,tableView:didEndEditingRowAtIndexPath:消息。在这些方法中可相应更新tableView的显示。How
does the Twitter iPhone app implement side swiping on a
table?中通过实现tableView:willBeginEditingRowAtIndexPath:方法使得用户在tableView的行上swipe时可滑出菜单。

五.多行选取模式

在iphone自带的邮件程序中,点击编辑按钮后会出现使用”红勾”多选的效果,如图所示

有几种方法可以实现这种效果

1.苹果公共API

在iOS5.0中UITableView增加了allowsMultipleSelectionDuringEditing属性和indexPathsForSelectedRows方
法,allowsMultipleSelectionDuringEditing属性默认为NO,当此值为YES时,UITableView进入编辑模式
不会向dataSource查询editStyle,而会直接每个Cell的左边显示圆形选择框,点击选择该行后圆形框里面为对勾。可使用
indexPathsForSelectedRows方法获取到所有选择行的indexPath。

苹果公司提供了使用此种方式的实例代码:TableMultiSelect

注:当allowsMultipleSelectionDuringEditing属性为YES时,不管当前在不在编辑模式内,swipe
to delete都不起作用,若要同时使用swipe to
delete,需在完成选择任务后,将tableView的allowsMultipleSelectionDuringEditing恢复为NO。另
外,多选”控件可与”重排序”控件同时出现。

2.苹果私用API

在iOS5之前,苹果并没有提供多行选取的API,但其内部确实实现了,我们可以通过使用私有API实现。

在tableView:editingStyleForRowAtIndexPath:方
法中若返回的是
UITableViewCellEditingStyleDelete|UITableViewCellEditingStyleInsert则可以进入
多选模式,效果同allowsMultipleSelectionDuringEditing设为YES时相同。这也是“多选”控件不会与“插入”控
件,”删除”控件同时出现,却可以和”重排序”控件同时存在的原因。

获取到选择的行时,同样可以使用私有方法indexPathsForSelectedRows获取,或者使用公开的tableView:didSelectRowAtIndexPath:,tableView:didDeselectRowAtIndexPath:方法在选择/取消选择时逐个获取并保存。

注:以上两种方式均需保证UITableViewCell的selectionStyle属性不为UITableViewCellSelectionStyleNone,否则选择后的“红勾”无法显示。

3.完全定制方法

一些文章中介绍了不使用tableView本身的方法而完全自己定制实现多选效果的方法。

如:Table View Multi-Row Edit
Mode

Multiple row selection and editing in a
UITableView

参考:

Table View Programming Guide for iOS – Inserting and Deleting Rows and
Sections

Table View Programming Guide for iOS – Managing the Reordering of
Rows

UITableView Class
Reference

UITableViewDelegate Protocol
Reference

UITableViewDataSource Protocol
Reference

UITableViewCell Class
Reference

How does the Twitter iPhone app implement side swiping on a
table?

UITableView划动删除的实现

UITableView多选删除,类似mail中的多选删除效果

iPhone开发技巧之私有API(2)—
UITableView

iOS 5 Dev Warning: UITableView’s Multiple Select During Editing Doesn’t
Work with Swipe to
Delete

Table View Multi-Row Edit
Mode

Multiple row selection and editing in a
UITableView

http://linglong117.blog.163.com/blog/static/277145472012103075527791/

发表评论

电子邮件地址不会被公开。 必填项已用*标注