如果你花费太多时间思考一件事,你永远不会完成它。至少为你的目标制定一个每天的行动计划。
-Bruce Lee
现在你知道如何处理表行选择。但是怎样删除它?我们如何才能从 UITableView删除一行?
这是当构建一个基于表的 app 时常见的问题,当处理数据时插入和更新都是基本的操作。我们已经讨论过关于选择的问题。这章我们讨论一下删除。此外,我们将添加一些新特性到 FoodPin app:
1.当用户在表行里水平滑动时添加一个自定义动作按钮。这通常被称作 Swipe for More 动作。
2.添加一个社交分享到 app,这允许用户在 Twitter 或者 Facebook 上分享餐厅。
这章有很多东西要学习,但是这将很好玩很值得。我们开始吧。
简短的介绍 Model View Controller(mvc)
在跳到代码部分之前,我将给你们介绍一下 MVC 模式,这是用户界面编程被引述得最多的设置模式之一。
我尽量保持这本书尽可能实用,很少谈论编程理论。也就是说,你无法避免学习 MVC,特别你的目标是构建伟大的应用程序或者成为一个称职的程序员。MVC 并不是一个只适用于 iOS 编程的概念。如果你学习过其他编程语言你可能已经听说过它,比如 Java 或者 Ruby。它是一个强大的设计模式用于设计一个软件应用程序,不管它是移动 app 还是 web app。
理解 MVC
MVC 的核心,对以后的框架来说最具影响力的想法,我把它叫做分开描述(Separated Presentation)。分开描述背后的想法是在域对象(domain objects)之间制造一个明确的分工,用来模拟我们现实世界的感知,演示对象是我们在屏幕上看到的 GUI 元素。域对象应该完全自包含并且在没有参考的情况下运行到演示,他们还应该能够支持多个演示,甚至可能需要同时支持。这个方法曾经也是 Unix 文化的一个重要组成部分,持续到现在它允许很多应用程序通过图像和命令行界面来操作。
— Martin Fowler
不管之前你学的哪种编程语言,你需要知道的一个重要的概念是关注分离(Separation of Concerns,SoC)。这个概念非常简单。这里,Concerns 是软件功能的不同方面。这个概念鼓励开发者把复杂的功能或者程序分解成几个方面的问题以便每个区域有它自己的职责。我们之前章节解释的委托部分,就是 SoC 的一个例子。
MVC 概念是SoC 的另一个例子。MVC 背后的核心理念是把一个用户界面分离成三个区域(或者对象群),每个区域负责一个特殊的功能。顾名思义,MVC 把一个用户界面分解成三个部分:
- Model - model 是负责保存数据或者任何数据上的操作。model 和数组对象储存表数据一样简单。添加,更新和删除都是操作的例子。在商业世界,这些操作通常被称作商业规则。
- View - view 管理着信息的视图显示。举例,UITableView列表显示数据格式。
- Controller - controller 是连接 model 和 view 的桥梁。它翻译了用户的交互从view(如水龙头)到 model 里执行适当的操作。举例,一个用户在view里按了删除按钮。因此,controller 在 model 里触发了一个删除操作。一旦完成操作,model 要求 view 刷新自己来体现数据模型的更新。
为了帮助你更好的理解 MVC,我们使用 SimpleTable app(我们在第八章构建的 app)作为例子。这个 app 在 table view 里显示了餐厅的一个清单。如果你执行一个可视化的插画,这是如何显示表数据的:
restaurantNames 对象是Model,它是一个数组。每一个表行(table row)映射到一个 restaurantNames 数组。UITableView 对象是用户可见的真实的 View。它负责所有的图形部分(如表行的颜色,tableview 的风格,分离风格,等等)。UITableViewController 方法就是 Controller,作为table view 和数据 model 之间的桥梁。它管理 table view 然后负责从 model 中读取数据。
从 UITableView 里删除一行
我希望你现在有一个对于 MVC 有一个更好的理解。让我们来到代码部分看看我们如何从 table view 删除行。我们将继续开发 FoodPin app和添加”delete”特性。
如果你理解 MVC 模式,你可能有一些行删除执行的想法。这里有我们必须做的3个主要的任务:
1、授权 table view 滑动删除的特性以便用户可以选择删除选项。
2、从数据模型中删除对应的表数据。
3、重载 table view 来反映表数据的改变。
授权滑动删除特性
在 iOS app 里,用户通常水平滑动表格行来显示删除按钮。回想一下我们曾经添加的 UITableViewDataSource 协议,有一个方法叫做 tableView(_:commitEditingStyle:forRowAtIndexPath:)。为了让table view能够使用滑动删除特性,所有你需要做的是执行这个方法。如果方法存在,当用户滑动一行时table view 会自动显示”Delete”按钮。
简单的添加下面的代码到 RestaurantTableViewController.swift 文件:
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath:NSIndexPath) {
}
现在我们进行一个快速的测试。在 iPhone 模拟器里运行 app。尽管这个方法没有任何正确的执行,但是当滑动行时你可以看到“Delete”按钮。
从模型里删除行数据
下一件事是执行这个方法和为移除真正的表数据写代码。随从方法的声明,indexPath 参数包含的单元格行数将被删除。你可以利用这些信息来从数据数组里移除对应的元素。
在 FoodPin app 里,restaurantNames,restaurantLocations,restaurantIsVisited 都是数据模型。显而易见的,我们必须从所有的数组移除选择的餐厅数据。为了从数组里移动一个项,你可以简单的调用数组对象的removeAtIndex 方法。更新方法里的代码如下:
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath:NSIndexPath) {
if editingStyle == .Delete {
//从数据源里删除行
restaurantNames.removeAtIndex(indexPath.row)
restaurantLocations.removeAtIndex(indexPath.row)
restaurantTypes.removeAtIndex(indexPath.row)
restaurantIsVisited.removeAtIndex(indexPath.row)
restaurantImages.removeAtIndex(indexPath.row)
}
}
这个方法支持两种类型的编辑风格:插入和删除。因为当用户选择删除按钮的适合,我们仅仅移除数据,我们在执行代码块之前先检查 editingStyle。
现在再次运行和测试你的 app。哎呀!app 并没有像期望中的工作。当你按删除按钮的适合,单元格没有移除。你可能会想数据没有正确的移除。在这里我们做一些调试。在方法的尾部插入下面的代码行来打印出数组的内容:
print(“Total item: (restaurantNames.count)”)
for name in restaurantNames {
print (name)
}
在 Swift 里,你使用print方法来输出一段信息给控制台。打印变量的内容是一个调试非常基本的方法。上面的代码在 restaurantNames 数组里打印所有的项数,它的内容在单元格删除之后。默认情况下,控制台隐藏在 Xcode 里。进到 Xcode 菜单,选择 View>Debug Area>Activate Console来打开控制台。
现在再次编译运行 app。从 table view 里删除第一行(如 Cafe Deadend)。你会在控制台下面的调试区域找到输出值(看下图)。一开始,我们数组里有21个餐厅项。在删除一行之后,数字减少到20。从输出值可以看到,”Cafe Deadend”完全被删除了。
如你所见,app 实际上从数组里删除了项。看起来像视图没有显示更新。是的,就是那样。我们仅仅移除了模型里的数据但是没有通知 table view 来更新它自己的内容。
重载 UITableView
一个告诉视图重载它自己内容的方法叫做 reloadData 方法。所以在方法里插入一行代码来重载 table view 里的数据:
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
//从数据源里删除行
restaurantNames.removeAtIndex(indexPath.row)
restaurantLocations.removeAtIndex(indexPath.row)
restaurantTypes.removeAtIndex(indexPath.row)
restaurantIsVisited.removeAtIndex(indexPath.row)
restaurantImages.removeAtIndex(indexPath.row)
}
}
tableView.reloadData()
print(“Total item: (restaurantNames.count)”)
for name in restaurantNames {
print(name)
}
}
当 reloadData 方法被调用时,table view 清除它自己的内容然后从餐厅数组重载正确的数据来显示。现在再次编译测试 app。当你删除一个餐厅,表格行应该也被移除了。
从 UITableView 里删除一行
app 正常工作了,但是这里有个更好的方式来刷新 table view。考虑到我们仅需要删除一个单独行,为什么我们不仅仅从 table view 里移除特殊行?你被允许来使用一个叫做 deleteRowsAtIndexPaths 的方法从 table view里删除一个特别的行(或者多数行)。用下面的代码行替换 reloadData 方法:
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
deleteRowsAtIndexPaths方法带来两个参数:一个索引路径的数组(array of index path)和行动画(row animation)。这里我们仅仅通过有正确索引路径和指定的方法来使用淡出的动画。行动画说明删除动作是如何进行的。通常使用.Fade动画。视情况而定,你可以把它改变成另外的动画比如.Right,.Left和.Top。再次编译运行app。当你确认删除一条记录时,这行会淡出 table view 外。
用 UITableViewRowAction添加更多的滑动动作
当你从一个内置邮箱 app 里划过表单元格时,你会看见一个垃圾桶按钮,一个更多按钮。更多按钮将打开一个动作表单,表单提供了一个可选列表如回复,标记等等。
这个”滑动更多“的特性在 iOS7的内置 iPhone apps里第一次被引进。在那时候,apple 没有让这个特性对开发者可用。从 iOS8开始,iOS SKD 带来了一个新的类叫做 UITableViewRowAction。你可以用这个类来给任何 table view 的表行创建自定义动作。为了添加自定义动作给 table view 的行,所有你需要做的是执行 tableView(_:editActionsForRowAtIndexPath:) 方法,设置自定义动作作为返回事件。
我们来看看它如何工作。插入下面的方法到 RestaurantTableViewController.swift 方法:
override func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? {
//Social Sharing Button
let shareAction = UITableViewRowAction(style: UITableViewRowActionStyle.Default, title:”share”, handler: {
(action, indexPath) -> Void in
let defaultText = “Just checking in at “ + self.restaurantNames[indexPath.row]
let activityController = UIActivityViewController(activityItems: [defaultText], applicationActivities: nil)
self.presentViewController(activityController, animated: true, completion: nil)
})
//删除按钮
let deleteAction = UITableViewRowAction(style: UITableViewRowActionStyle.Default, title:”Delete”,handler: {
(action,indexPath) -> Void in
//从数据源里删除行
self.restaurantNames.removeAtIndex(indexPath.row)
self.restaurantLocations.removeAtIndex(indexPath.row)
self.restaurantTypes.removeAtIndex(indexPath.row)
self.restaurantIsVisited.removeAtIndex(indexPath.row)
self.restaurantImages.removeAtIndex(indexPath.row)
self.tableView.deleteRowAtIndexPaths([indexPath], withRowAnimation: .Fade)
})
return [deleteAction, shareAction]
}
UITableViewRowAction 的用法和 UIAlertAction 很相似。当用户按下按钮时你指定标题,风格和代码块来执行。在这个例子里,我们把自定义动作命名为”Share”。当用户按下按钮时,它为社交分享引出一个活动控制器。
UIActivityViewController 类是一个标准视图控制器,它提供几个标准服务,例如复制项到剪切板,分享内容到社交媒体网站,通过消息发送项,等等。这个类用起来很简单。假设你有个消息要分享。所有你需要做的是创造一个有信息对象的 UIActivityViewController 实例,然后在屏幕上呈现控制器。那就是我们在上面代码片段做了什么。
你可能会注意到,我们添加了一个删除动作按钮。当你执行 tableView(:editActionsForRowAtIndexPath:) 方法,table view 将不再为你生成删除按钮。这就是为什么我们需要创造自己的删除按钮。
代码的最后一行可能是最重要的部分。它返回一个 UITableViewRowAction 对象的数组(如 deleteAction 和 shareAction),当有人滑动单元格时告诉 table view 创建按钮。
编译运行 app。滑动一个表格行,它将显示 Share 和 Delete 按钮。按下 Share 按钮将会弹出一个分享菜单如下图
Twitter 和 Facebook 按钮可能不会在模拟器里出现。这种情况下,按住 shift-command-H 来回到主屏幕,选择 Settings>Twitter/Facebook,登陆你的账号。这是重新登陆 app。你应该能够分享内容到社交媒体网站上。
UIActivityViewController 类并不限制你从文本格式里分享内容。如果在初始化期间你通过一个 UIImage 对象,你的app 将允许用户传送图像到 Twitter 或者 Facebook。修改 tableView(:editActionsForRowAtIndexPath: ) 方法:
override func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? {
//社交分享按钮
let shareAction = UITableViewRowAction(style: UITableViewRowActionsStyle.Default, title: “Share”, handler: { (action, indexPath) -> Void in
let defaultText = “Just checking in at “ + self.restaurantNames[indexPath.row]
if let imageToShare = UIImage(named: self.restaurantImages[indexPath.row]) {
let activityController = UIActivityViewController(activityItems: [defaultText, imageToShare], applicationActivities: nil)
self.presentViewController(activityController, animated: true, completion: nil)
}
})
//删除按钮
let deleteAction = UITableViewRowAction(style: UITableViewRowActionStyle.Default, title: “Delete”,handler: {
(action, indexPath) -> Void in
//从数据源里删除行
self.restaurantNames.removeAtIndex(indexPath.row)
self.restaurantLocations.removeAtIndex(indexPath.row)
self.restaurantTypes.removeAtIndex(indexPath.row)
self.restaurantIsVisited.removeAtIndex(indexPath.row)
self.restaurantImages.removeAtIndex(indexPath.row)
self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
})
return [deleteAction, shareAction]
}
我们仅仅在上面的代码里添加一些行来为分享创建一个 imageToShare 对象。我们用 UIImage 类来读取图片,然后在初始化期间把它传递给 UIActivityViewController。当用户分享餐厅到社交媒体网络时,UIActivityViewController 将自动嵌入图片。
为可选项(Optionals)设置 if let
当读取一张图片时,图片有可能读取失败。这就是为什么UIImage 类在初始化时返回一个可选项。在 Swift 里,我们使用”if let”来核实一个可选的内容是不是一个值。在细节上,你可以参考附录的 Optional部分。
自定义 UITableViewRowAction
默认情况下,动作按钮是红色的。UITableViewRowAction 类为开发者提供一个可选项通过 backgroundColor 属性来自定义它自己的背景色:
shareAction.backgroundColor = UIColor(red: 28.0/255.0, green: 165.0/255.0, blue: 253.0/255.0, alpha: 1.0)
UIKit 框架提供一个 UIColor 类来代表色彩。UIKit 里的许多方法要求你用 UIColor 对象提供色彩。这个类自带许多标准色彩如 UIColor.blueColor() 和 UIColor.redColor()。如果你想用自己的色彩,你可以用 RGB 的分量值创建你自己的 UIColor 对象。分量值必须包含0和1。如果你是一个网络设计师或者对于图像设计有一些经验,你知道 RGB 值通常在0到255。为了符合用户UIColor 的要求,当创建 UIColor 对象时你必须分离每个分量值。你可以在下面的代码行之前 tableView(_:editActionsForRowAtIndexPath:) 方法:
return [deleteAction, shareAction]
我们开始吧。再次测试 app 和看是否你喜欢新的颜色。另一方面,修改颜色代码和更改你的首选颜色。
Quick note:如果你像我一样不是一个设计师,你可能需要一些颜色灵感。你可以参考 Adobe Color CC(color.adobe.com) 和 Flat UIColor Picker(flatuicolorpicker.com)。你会发现许多色彩组合来设计你的 app。
网友评论