【转】iOS开发中的UITableView编辑模式

文章写得很好,转载自:http://my.oschina.net/plumsoft/blog/53271

这篇文章主要讲的表格的操作包括:标记行、移动行、删除行、插入行。

这次就不从头建立工程了,在http://www.oschina.net/code/snippet_164134_9876下载工程。这个工程就是最简单的产生一个表格并向其中写入数据。用Xcode 4.2打开它,在这个工程基础上实现以上操作。

1、标记行

这里讲的标记行指的是单击此行,可以实现在此行右边出现一个勾,如下图所示:

为了实现标记功能,在ViewController.m中@end之前添加代码:

#pragma mark -
#pragma mark Table Delegate Methods
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 
    UITableViewCell *oneCell = [tableView cellForRowAtIndexPath: indexPath];
    if (oneCell.accessoryType == UITableViewCellAccessoryNone) {
        oneCell.accessoryType = UITableViewCellAccessoryCheckmark;
    } else 
        oneCell.accessoryType = UITableViewCellAccessoryNone;
    [tableView deselectRowAtIndexPath:indexPath animated:YES]; 
}

该代码实现:单击某行时,若此行未被标记,则标记此行;若此行已经被标记,则取消标记。

运行效果如上图。

上面的代码实际上就是修改某行的accessoryType属性,这个属性可以设为四个常量:

UITableViewCellAccessoryCheckmark
UITableViewCellAccessoryDetailDisclosureButton
UITableViewCellAccessoryDisclosureIndicator
UITableViewCellAccessoryNone

效果依次如下图所示:

            

   UITableViewCellAccessoryCheckmark            UITableViewCellAccessoryDetailDisclosureButton

                

UITableViewCellAccessoryDisclosureIndicator                   UITableViewCellAccessoryNone

注意,上面第二张图片中的蓝色圆圈不仅仅是一个图标,还是一个控件,点击它可以触发事件,在上一篇博客《iOS开发16:使用Navigation Controller切换视图》使用过。

2、移动行

想要实现移动或者删除行这样的操作,需要启动表格的编辑模式。使用的是setEditing:animated:方法。

2.1 打开ViewController.xib,将其中的表格控件映射成Outlet到ViewController.h,名称为myTableView。

2.2 打开ViewController.m,在viewDidLoad方法最后添加代码:

//启动表格的编辑模式
[self.myTableView setEditing:YES animated:YES];

2.3 在@end之前添加代码:

//打开编辑模式后,默认情况下每行左边会出现红的删除按钮,这个方法就是关闭这些按钮的
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView
           editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath { 
    return UITableViewCellEditingStyleNone; 
} 

//这个方法用来告诉表格 这一行是否可以移动
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath { 
    return YES; 
}

//这个方法就是执行移动操作的
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)
        sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath {
    NSUInteger fromRow = [sourceIndexPath row]; 
    NSUInteger toRow = [destinationIndexPath row]; 
    
    id object = [list objectAtIndex:fromRow]; 
    [list removeObjectAtIndex:fromRow]; 
    [list insertObject:object atIndex:toRow]; 
}

editingStyleForRowAtIndexPath这个方法中用到了常量UITableViewCellEditingStyleNone,它表示不可编辑,这里的编辑指的是删除和插入。表示表格行的编辑模式的常量有:

UITableViewCellEditingStyleDelete
UITableViewCellEditingStyleInsert
UITableViewCellEditingStyleNone

顾名思义,第一个表示删除,第二个表示插入,第三个表示不可编辑。

若将editingStyleForRowAtIndexPath方法中的UITableViewCellEditingStyleNone依次换成上面三个值,则它们运行的效果依次如下图所示:

     

2.4 运行,从下图可以看到实现了行的移动:

但是也会发现,现在无法对每行进行标记了。这说明,在编辑模式下,无法选择行,从而didSelectRowAtIndexPath这个方法不会执行。

3、删除行

从第2步过来,实现删除某行,其实比较简单了。

3.1将editingStyleForRowAtIndexPath方法中的UITableViewCellEditingStyleNone修改成UITableViewCellEditingStyleDelete。

3.2 在@end之前添加代码:

//这个方法根据参数editingStyle是UITableViewCellEditingStyleDelete
//还是UITableViewCellEditingStyleDelete执行删除或者插入
- (void)tableView:(UITableView *)tableView commitEditingStyle:
    (UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
    NSUInteger row = [indexPath row];
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        [self.list removeObjectAtIndex:row]; 
        [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                         withRowAnimation:UITableViewRowAnimationAutomatic]; 
    }
}

在这个方法中又出现了一个常量:UITableViewRowAnimationAutomatic,它表示删除时的效果,类似的常量还有:

UITableViewRowAnimationAutomatic
UITableViewRowAnimationTop
UITableViewRowAnimationBottom
UITableViewRowAnimationLeft
UITableViewRowAnimationRight
UITableViewRowAnimationMiddle
UITableViewRowAnimationFade
UITableViewRowAnimationNone

它们的效果就不一一介绍了,可以在实际使用时试试。

3.3 运行,看看效果:

     

刚运行时显示如左边的图片,点击某一行左边的圆圈图标,会显示如中间图片所示。然后点击Delegate按钮,那一行就会被删除掉,如右边的那张图片所示,它显示的是删除时的效果。

4、插入行

这个与删除行类似。

4.1 首先将editingStyleForRowAtIndexPath方法中的UITableViewCellEditingStyleDelete修改成UITableViewCellEditingStyleInsert。

4.2在3.2添加的方法中添加代码:

else {
    //我们实现的是在所选行的位置插入一行,因此直接使用了参数indexPath
    NSArray *insertIndexPaths = [NSArray arrayWithObjects:indexPath,nil];
    //同样,将数据加到list中,用的row
    [self.list insertObject:@"新添加的行" atIndex:row];
    [tableView insertRowsAtIndexPaths:insertIndexPaths withRowAnimation:UITableViewRowAnimationRight];
}

上面的代码中也可以不用insertRowsAtIndexPaths方法,而直接使用[tableView reloadData];语句,但是这样就没有添加的效果了。

4.3 好了,运行一下:

     

刚运行时如上面左图所示,单击了某个加号后,新的一行就从右边飞进来了,因为在insertRowsAtIndexPaths中用了参数UITableViewRowAnimationRight。

 

解决iOS开发中调用UIScrollView或UITableView的setContentOffset方法产生的抖动

现在“下拉刷新”和“上拉加载更多”都是常用的移动端程序的设计了。

实现的话,下拉刷新有现成的开源库:EGOTableViewPullRefresh

上拉加载更多的话,自己研究,写了一个类似的tableViewCell。但是在ScrollViewDidEndDragging方法中修改scrollView的setContentInset时发生了抖动。

花了挺长时间研究,最后发现原因在于当调用setContentInset的时候scrollView的contentOffset会跟着一起变化(由于是设置inset的bottom,所以scrollview会自动到最底端),然后由于是在ScrollViewDidEndDragging,视图还要从以前的位置动画回到scrollview的末尾(bounce效果)。于是修改inset之后造成的当前contentOffset位置就和bounce动画的初值不等,造成了抖动。

解决办法也很简单,就是在setContentInset之后,立马重设一下contentOffset。

        float contentTop = scrollView.contentOffset.y;
        [scrollView setContentInset:oriIns];
        [scrollView setContentOffset:CGPointMake(0, contentTop) animated:NO];

UITableView的背景颜色以及层次研究

UITableView的背景颜色,并不是UITableView的backgroundColor。
其内部实际上创建了一个UIView来专门呈现设定的背景颜色。
感觉它的subview层次从下往上是这样的:

  1. 这个呈现backgroundColor的view
  2. 放置其中的cell们
  3. scroll indicators

而且每次添加一个cell,tableView会自动将这个cell放置在backgroundView的上面一层,而且UITableView的sendViewToBack和bringToFront函数应该是重新定义过的,并不会按照普通UIView的方式去执行。

iOS开发中使用平铺图像作为UIScrollView或者UITableView的可滚动背景

UIScroView和TableView都可以设置一张图片作为背景,或者设置一个颜色。
但是这两种方法设定的颜色都是固定的,不会随着表格或者Scroll的滚动而滚动。
不过,要使用一张图片以平铺的形式贴在可滚动区域上也是可以的,只需要一个语句即可:

self.tableView.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"loginBkgWithoutLogo"]];

如何解决iOS瀑布流(UIScrollView或UITableView)运行不流畅

写的一个程序中用到了瀑布流的展现方式,但是发现当图片数量太大的时候,在iPhone4上会不流畅,这点很不爽。

写代码之初是做了一些优化的,比如cell重用,异步加载,但是还是很卡。

终于后来发现了症结所在,那就是,如果滑动太快,可能同时就发出了比如10个图片请求。这些请求虽然都在后台运行,但是它们可能在同一个时间点返回UI线程。这个时候如果加载图片到UIImageView太频繁,就会造成UI卡得严重。(虽然在new iPad和iPhone4s上看不出来)

在找到这个问题的同时,也发现performSelectorAfterDelay这个方法,会堆积到UI线程空闲的时候执行。而dispatch_after或者dispatch_async都会直接插入UI线程当场执行。所以这个问题其实可以用performSelectorAfterDelay来解决,测试也是非常流畅,感觉不出一点点的卡。但会出现新的问题,那就是在滑动过程中,不会加载任何图片。知道scrollView停止的时候,图片才会出来。当然这不是理想的解决方法了。这个方法也没有解决异步过程集中到达UI线程的问题。然后采用了NSOperationQueue来解决这个问题。

问题本身和UITableView加载不流畅是一样的。

解决办法

    主要要做到一下几个方面:

  1. 除了UI部分,所有的加载操作都在后台完成。
    这一点可以通过dispatch或者performSelectorInBackground或者NSOperationQueue来实现。见:
    在iOS开发中利用GCD进行多线程编程
    iOS开发中使用NSOperationQueue进行多线程操作
  2. 避免后台加载完成多个资源之后集中到达占用UI线程的处理时间太长。
    这一点可以通过NSOperationQueue来实现,将资源到UI的展现过程放在队列中逐个执行,且在每个操作完成之后进行强制等待,可以用usleep(int microSeconds)来解决。
  3. 重用cell。
    创建cell一般是很慢的,一定要重用,甚至为了performance,可以在view创建之初就创建足够多的cell在重用队列中。