关于 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)会以弹出样式来呈现。这点在开发跨平台应用时应该格外注意。

自适应视图经验总结

标签元素由于 Stack View 设置的元素间距过大,而被截断

为了避免元素被截断,在这个案例中的顶级 Stack View(容纳了一个 Image View 和装着 Name Label 和 Type Label 的 Stack View) 的元素间距设置可以设置为缺省。右图示例就是在 iPhone 11 模拟器下,左右间距为8,元素间距设置为 270 的显示效果。

包含 Name 和 Type 的 Stack View 的元素间距设为缺省
Image View 的显示模式设置为 Aspect Fill
全部的元素和约束关系

Imageview 区块设置了固定高度,并上、左、右和 SuperView 的间距设为0。并和下方的堆叠视图设置好间距。

下方的堆叠视图设置了与 Superview 的左右各8,下部大于5的间距,并在设置时勾选了 「Constrain to margins」。

最后的成品

自定义 TableViewCell

class RestaurantTableViewCell: UITableViewCell {
    
    @IBOutlet var nameLabel: UILabel!
    @IBOutlet var locationLabel: UILabel!
    @IBOutlet var typeLabel: UILabel!
    @IBOutlet var thumbnailImageView: UIImageView!
    // @IBOutlet 宣告一个属性,后续可以用于链接视图组件,为其赋值

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

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

    }

}