Segue 的四种形态

  • Show:内容会被推到目前视图控制器堆叠的上方。在导览列上会显示一个返回按钮,用来导览回原来的视图控制器。一般开发者会使用这个作为导览控制器的 Segue 型态。
  • Show Detail:和 Show 样式类似,不过细节(或者是目标)视图控制器的内容将在目前视图控制器堆叠的最上方。举例来说,如果你在 App 中选取 Show Detail 型态来取代 Show 型态,在细节视图中便不会有导览列与返回按钮。
  • Present Modally:以强制回应(Modal)型态呈现。使用这个型态,细节视图控制器将会以动画方式从底部出现后并充满整个屏幕,之前的视图会向后避让。Present Modally Segue 的较佳范例是 iOS 内置的日历 App 的右上角的“新建日程”功能。当你在 App 中点选“+”按钮,它会从底部带出一个“新建日程”画面。
  • Present as popover:将内容以固定点弹跳框来呈现内容至目前视图中。弹跳框在 iPad App 中很常见。

Deprecated Segues 分类下的 “Push、Modal、Popover、Replace” 四个动作已经不推荐使用。因为在未来的 Swift 版本中可能会移除它。

Cell 滑动动作设计和样式自定义

从 iOS 11 开始,iOS 导入了两个新方法来处理表格视图 Cell 中的滑动动作:

tableView(:leadingSwipeActionsConfigurationForRowAt:) 
tableView(:trailingSwipeActionsConfigurationForRowAt:)

这两个方法是被定义在 UITableViewDelegate。第一个方法是处理 “向右滑动” 动作,而第二个是负责 “向左滑动” 动作。下面的案例是

let deleteAction = UIContextualAction(style: .destructive, title: "删除") { (action, sourceView, completionHandler) in

            self.restaurantNames.remove(at:indexPath.row)
                    self.restaurantLocations.remove(at: indexPath.row)
                    self.restaurantTypes.remove(at: indexPath.row)
                    self.restaurantIsVisited.remove(at: indexPath.row)
                    self.restaurantImgs.remove(at: indexPath.row)

                    self.tableView.deleteRows(at: [indexPath], with: .fade)

                    completionHandler(true)
                    //成功完成动作后,需要解除动作按钮
            
        }
        
        let shareAction = UIContextualAction(style: .normal, title: "分享") { (action, sourceView, completionHandler) in
            
            let defaultText = "去预定" + self.restaurantNames[indexPath.row]
            let activityController = UIActivityViewController(activityItems: [defaultText], applicationActivities: nil)
            
            self.present(activityController, animated: true, completion: nil)
            
            completionHandler(true)
            //成功完成动作后,需要解除动作按钮
            
        }

动作设计好后,可以用 UIContextualAction 继续设计它的样式,可以改变它的背景和图标。

UIKit 框架使用一个 UIColor 类别来显示颜色。许多在 UIKit 的方法,要求你使用 UIColor 物件来提供颜色资料。这个类别有内置几个标准颜色,像是 UIColor.blue 与 UIColor.red。如果要使用自己的颜色,可以使用 RGB 分量值来创建 UIColor 物件。这个色彩分量值是落在 0 与 1 之间。RGB 值通常是在 0 至 255 的范围内。为了遵循 UIColor 的要求,在创建 UIColor 物件时,必须要将每一个分量值除以 255。

        deleteAction.backgroundColor = UIColor(red: 231.0/255.0, green: 76.0/255.0, blue: 60.0/255.0, alpha: 1.0)
        deleteAction.image = UIImage(named: "delete")
        
        shareAction.backgroundColor = UIColor(red: 254.0/255.0, green: 149.0/255.0, blue: 38.0/255.0, alpha: 1.0)
        shareAction.image = UIImage(named: "share")
        //自定义删除与分享图标,并手动设置颜色。
        //需在 swipeConfiguration 实例化之前进行设置

这个样式需要在 trailingSwipeActionsConfigurationForRowAt 方法中,swipeConfiguration 实例化之前插入才能生效。

完整的左滑功能实现如下

    override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
        
        let deleteAction = UIContextualAction(style: .destructive, title: "删除") { (action, sourceView, completionHandler) in

            self.restaurantNames.remove(at:indexPath.row)
                    self.restaurantLocations.remove(at: indexPath.row)
                    self.restaurantTypes.remove(at: indexPath.row)
                    self.restaurantIsVisited.remove(at: indexPath.row)
                    self.restaurantImgs.remove(at: indexPath.row)

                    self.tableView.deleteRows(at: [indexPath], with: .fade)

                    completionHandler(true)
                    //成功完成动作后,需要解除动作按钮
            
        }
        
        let shareAction = UIContextualAction(style: .normal, title: "分享") { (action, sourceView, completionHandler) in
            
            let defaultText = "去预定" + self.restaurantNames[indexPath.row]
            let activityController = UIActivityViewController(activityItems: [defaultText], applicationActivities: nil)
            
            self.present(activityController, animated: true, completion: nil)
            
            completionHandler(true)
            //成功完成动作后,需要解除动作按钮
            
        }
        
        deleteAction.backgroundColor = UIColor(red: 231.0/255.0, green: 76.0/255.0, blue: 60.0/255.0, alpha: 1.0)
        deleteAction.image = UIImage(named: "delete")
        
        shareAction.backgroundColor = UIColor(red: 254.0/255.0, green: 149.0/255.0, blue: 38.0/255.0, alpha: 1.0)
        shareAction.image = UIImage(named: "share")
        //自定义删除与分享图标,并手动设置颜色。
        //需在 swipeConfiguration 实例化之前进行设置
        
        
        let swipeConfiguration = UISwipeActionsConfiguration(actions: [deleteAction, shareAction])
        //创建 UIContextualAction 物件的阵列,包括了两个动作, deleteAction 与 shareAction
        
        return swipeConfiguration
        //回传 UIContextualAction 物体阵列,告知表格视图在使用者向左滑过 Cell 时创建按钮。
    }
    //左滑功能重做,并实现分享功能

关于 iOS 和 Mac OS 跨平台程序的弹窗 bug

UIActionController 的 .actionSheet 样式,在 iPhone 和 iPad 上会有不同的呈现方式。在 iPad 和 Mac 上不会以强制回应(Modal)对话视窗的方式呈现提示控制器(alert controller),而是使用弹出(popover)的样式。

这个呈现的过程是由 iOS 或 Mac OS 自动处理,当呼叫以下这行程序时,iOS 或 Mac OS 将会检查装置是 iPhone 还是 iPad。

present(optionMenu, animated: true, completion: nil)

如果装置是 iPhone 的话,提示控制器会以强制回应对话视窗来呈现,而在 iPad 和 Mac,提示控制器则会使用储存在 popoverPresentationController 属性中的 UIPopoverPresentationController 物件。

在使用弹出样式来呈现提示视图控制器时,你必须配置 popoverPresentationController 的来源视图,这个来源视图标示了所弹出的矩形视图。如果没有配置的话,在 iPad 运行时会报错闪退(或者卡死)。在把程序设置为可以在 macOS 10.15 上运行,并对 mac 系统进行构建,会发现出现和 iPad 上一样的 bug,会出现卡死。

You must provide location information for this popover through the alert controller’s popoverPresentationController. You must provide either a sourceView and sourceRect or a barButtonItem.

在 iPad 上运行时,会报错如上

现在在 tableView(_:didSelectRowAt:) 的方法中插入以下的程序,将程序放在 optionMenu 的实例之后。

if let popoverController = optionMenu.popoverPresentationController {
    if let cell = tableView.cellForRow(at: indexPath) {
        popoverController.sourceView = cell
        popoverController.sourceRect = cell.bounds
    }
}

当 App 在 iPhone 执行时,提示控制器(也就是 optionMenu)的 popoverPresentationController 属性是设为 nil,反之,在 iPad 执行时,则会存放弹出显示视图控制器(popover presentation controller),因此,我们使用 if let 来检查 popoverPresentationController 属性是否有一个值。如果是的话,我们配置 sourceView 为 Cell,并使用 Cell 来触发动作。另外,我们设定 sourceRect 属性为 Cell 的边界,因此,窗口弹出的原点是来自 Cell 的中心。

这时,这个应用在 iPad 和 macos 上才能无障碍运行。UIActivityViewController 在较大的装置(iPad 和 Mac)会以弹出样式来呈现。这点在开发跨平台应用时应该格外注意。