第七章:table单元格的选择和UIAlertController
译者注:由于本人英语水平有限,尽可能描述出作者的本意。如有错误,及时指出。文中会省略部分技术无关的赘述
处理cell选择
到目前为止 , 我们集中于在table view中展示数据 。 我们怎么处理cell的点击呢? 这个就是我们本章要讨论的一个问题 。
我们继续打磨我们的FoodPin app 。
我们将要增进一些功能:
理解 UITableViewDelegate
我们在第五章第一次构建一个简单的tableview app的时候 ,我们实现了两个协议 UITableViewDelegate 和 UITableViewDataSource 。
前面已经讨论过UITableViewDataSource 而只是仅仅提了一下UITableViewDelegate 。
如前所述 , 代理模式是IOS中非常常用的一个设计模式 。 每一个代理都表示一个具体的角色或者具体的任务保证系统简单和干净 。当一个对象要执行固定的任务 ,依赖另一个对象去处理 。 这在软件设计中叫做”关注点分离”
UITableView也应用了这个概念 。 哪两个协议用作不同的目的 。UITableViewDataSource 中的方法用来管理表数据 。 依靠代理去提供数据 。另一方面 , UITableViewDelegate处理header 、 footer 、row 选择 、cell的重新排序等等
这个代理就有些方法是用来处理row选择的 。 我们这里将会实现一些方法处理row选择 。
处理表的行选择
在实现具体方法之前 , 你也许会想 :
我们怎么知道UITableViewDelegate有哪些方法可以实现呢 ?
答案是“阅读文档” 。 你可以去官网查看 官网
作为一名IOS开发者 ,你需要经常读API文档 。 没有任何其他的书能涵盖所有的IOS SDK 。 大多数时候 ,特别是SDK有新的变化的时候 ,你需要参考API 文档 。苹果提供一个简单的方式在xcode中访问文档 。 把鼠标放在类或者协议上 按住“control-command-?” 它将会弹出一个框显示这个class实现了哪些协议 。 点击UITableViewDelegate 会出新一个更深层次的文档浏览器 。在那里你可以找到所有协议中定义的方法 。
扫视下文档 , 你会发现下面两个用于处理row 选择的方法 :
• tableView(_:willSelectRowAtIndexPath:)
• tableView(_:didSelectRowAtIndexPath:)
这两个方法都是用来处理row选择的 ,不同点是 tableView(_:willSelectRowAtIndexPath:)
是一个指定的行被选择的时候调用 。 使用此方法组织特定区域的cell被选择 。一般情况下 ,我们使用tableView(_:didSelectRowAtIndexPath:)
方法处理row选择时间,当用户选择后调用 。
在代码中管理行选择
OK ,上面已经解释的够多了 , 我们开始coding
在xcode中,打开RestaurantTableViewController.swift ,添加下面方法 :
override func tableView(tableView: UITableView,didSelectRowAtIndexPath indexPath:
NSIndexPath) {
// Create an option menu as an action sheet
let optionMenu = UIAlertController(title: nil,message: "What do you want to do?",preferredStyle: .ActionSheet)
// Add actions to the menu
let cancelAction = UIAlertAction(title: "Cancel",style: .Cancel,handler: nil)
optionMenu.addAction(cancelAction)
// Display the menu
self.presentViewController(optionMenu,animated: true,completion: nil)
}
上面的代码使用UIAlertController 创建了一个选择菜单 。当我们点击没行的时候 , 都会弹出一个sheet 展示”What do you want to do” 信息 和一个cancel 按钮 。运行app ,自己试试选择cell
更多关于UIAlertController
再继续研究之前 , 我们需要更多的了解一下UIAlertController 。我们前面已经用过它了,但是并没有深入的讨论 。UIAlertController是在iOS 8 引入用来替代UIAlertView 和 UIActionSheet 的 。用来显示弹出框的 。
参照上面的代码片段 , 我们可以指定UIAlertController的preferredStyle 。 可以设置为“.ActionSheet” 或者 “.Alert”.
下图显示出两种弹出框的风格 。
此外,给用户显示信息的同时还可以连接一些动作(Actions) 提供一个用户交互的方式 。为了那么做 ,你需要创建一个UIAlertAction对象 (起一个你喜欢的标题 , 风格) 。代码块去处理这个动作执行的操作 。在上面的代码片段中 ,我们创建了一个取消动作 。我们这里并没有处理任何事情,所以handler设置位nil 。创建好动作后可以使用addAction将动作和UIAlertController连接起来 。
当这个UIAlertController配置好了以后,你就可以简单的使用presentViewController方法来展示这个弹出框了。
这就是使用UIAlertController的方法 。最为一个新手 ,我应该有一些问题:
我怎么知道在创建UIAlertController时需要给preferredStyle提供哪些可用的value
为啥不写成UIAlertControllerStyle.ActionSheet?
两个都是好问题 。
第一个问题 ,答案还是“参考文档” 。在xcode中 ,你可以将鼠标放在preferredStyle上然后按control-command-?
,xcode将会展示方法声明 你可以点击UIAlertControllerStyle深入查看UIAlertControllerStyle的API ,你会发现UIAlertControllerStyle是一个枚举 ,有两个值ActionSheet 和 Alert.
所以你可以这样创建一个UIAlertController
let optionMenu = UIAlertController(title: nil,message: "What do you want to do?",preferredStyle: UIAlertControllerStyle.ActionSheet)
上面的代码没有一点错 。swift帮我们让我们写更少的代码 ,preferredStyle参数已经知道是UIAlertControllerStyle 类型 ,所以UIAlertControllerStyle可以省略
let optionMenu = UIAlertController(title: nil,preferredStyle: .ActionSheet)
为Alert Controller 添加动作
现在我们为Alert Controller再添加连个动作 :
Call
I’ve been here
在tableView(_:didSelectRowAtIndexPath:)
方法中 添加以下代码 ,添加到Cancel动作后面:
let callActionHandler = { (action:UIAlertAction!) -> Void in
let alertMessage = UIAlertController(title: "Service Unavailable",message: "Sorry,the call feature is not available yet. Please retry later.",preferredStyle: .Alert)
alertMessage.addAction(UIAlertAction(title: "OK",style: .Default,handler: nil))
self.presentViewController(alertMessage,animated: true,completion: nil)
}
let callAction = UIAlertAction(title: "Call " + "123-000-\(indexPath.row)",style:
UIAlertActionStyle.Default,handler: callActionHandler)
optionMenu.addAction(callAction)
这部分代码对你来说比较生疏的部分就是callActionHandler 。当创建一个UIAlertAction对象的时候我们可以指定一个闭包 ,闭包代码当用户选择row的时候调用 。这里只是弹出一个框显示电话功能不能用。
闭包是一个自我包含有一定功能的代码块 。可以在代码中传递 。跟Objective - C 中的block非常类似 。如上面代码所示 , 提供闭包参数的一种方式是声明一个闭包常量或者变量 。第一部分使传入的参数 ,in关键字说明参数和返回值类型已经完成 ,主体将要开始 。下图说明了闭包的语法
UIAlertAction的title是假的电话号码,是用123-000-和行号产生的 。swift允许开发之使用两种方式连接字符串
"Call " + "123-000-\(indexPath.row)"
let isVisitedAction = UIAlertAction(title: "I've been here",style: .Default,handler: {
(action:UIAlertAction!) -> Void in
let cell = tableView.cellForRowAtIndexPath(indexPath)
cell?.accessoryType = .Checkmark
})
optionMenu.addAction(isVisitedAction)
上面的代码展示了另一种使用闭包的方式 。你可以把闭包当一个内嵌的参数去使用 。这种方式也是最常用的一种比较清晰和可读 。
Swift中的可选类型(Optionals)
你可能会疑惑那个问号是干啥用得 ?cell在swift中被声明为一个可选类型 。可选类型是swift中心引入的一个类型 。一个可选类型简单的意味着”有值” 或者 “没有值 ” . cell是通过tableView.cellForRowAtIndexPath方法得到的。有可能没有值。这里使用?表示如果有值就执行它的方法 ,如果没有值就不执行
当一个用户选择“I’ve been here” , 我们添加一个对选择的cell添加一个checkmark标记 。 对一个table view cell , 右边的部分是为一些小附件保留的 。有四种内建的附件包括
disclosure indicator,detail disclosure
button,checkmark and detail.
第一行代码使用indexPath来检索正在使用的cell 。第二行更新了accessoryType属性为checkmark
运行app ,应该如下图
现在 ,当你选择一行,那行会高亮成灰色保持那样。将下面代码添加到tableView(_:didSelectRowAtIndexPath:)
方法后面 去取消选择一行
tableView.deselectRowAtIndexPath(indexPath,animated: false)
我们有一个Bug
app 看起来不错 ,如果你详细的看 ,然而 app中有一个bug 。你mark“Cafe Deadend”。然后滚动下去,你会发现另一个餐馆被mark了 。什么情况 ?
这个问题是根重用cell相关, UITableview 有30行需要展示 ,我们也许只创建了10个cells 然后重用他们 。在这种情况下 ,UITableview使用第一个cell去显示其他餐馆的信息 ,在我们的代码中 ,我们只是去改变了imagview 和 labels 。病名有该表accessory view 。所以后面重用的会带着前面的checkmark。
那么我们怎么解决这个bug呢 ?
我们需要找个方法记住这些checked元素 。我们这里创建了一个数组 。
var restaurantIsVisited = [Bool](count: 21,repeatedValue: false)
上面代码的意思和下面的一样
var restaurantIsVisited = [false,false,false]
首先我们在餐馆被check的时候贯标array对应的值 。
let isVisitedAction = UIAlertAction(title: "I've been here",handler: {
(action:UIAlertAction!) -> Void in
let cell = tableView.cellForRowAtIndexPath(indexPath)
cell?.accessoryType = .Checkmark
self.restaurantIsVisited[indexPath.row] = true
})
然后在tableView(_:cellForRowAtIndexPath:)
方法的return前添加如下代码
if restaurantIsVisited[indexPath.row] {
cell?.accessoryType = .Checkmark
} else {
cell?.accessoryType = .None
}
这里我们检查了餐馆是否被checked 。
现在运行app 。bug已经解决
其实这里还可以用三元运算符
cell?.accessoryType = restaurantIsVisited[indexPath.row] ? .Checkmark : .None
你的作业
目前app还不能取消check 。想想怎么去实现 。你还可以不用系统的对勾 , 用一个心形的图片之类的表示mark。
总结
在此刻,你应该对责备他不过创建一个table view有个固定的了解 ,自定义table view cells 和处理row选择 ,你可以尝试做表视图的简单app , 比如展示一些景点或者你喜欢的别的东西 。反正就是多写 。不要怕犯错 , 犯错解决问题才能更快的成长 。
下一章将继续探索tableview。