最近忙完了几件事,圣诞假期也临近,终于有时间好好学习并折腾一下 SwiftUI。在学习和 SwiftUI 的过程中,我遇到并发现了不少 Bug,同时对它的局限性也有了更多的了解。本文即是以 List Selection 的一处 Bug 为例来展开。
12 月 22 日 20:30 分更新
在推友 @siuying 的思路的指导下,我终于发现了这个疑似 Bug 的问题,真的是我对 SwiftUI 的不熟悉索导致的。
NavigationLink 是一个动作,而不是一个状态,根据他提供的代码,macOS 下已经成功运行正常,不过 iOS 依然没办法达成选中的效果,还需要继续探索。
demo 目标
以下是用 SwiftUI 实现的一个非常简单的 demo,它用 List 排列几个 Task 类型,然后有一个Random Select 的按钮,可以随机选中列表里的项目。
这个 demo 在 macOS 和 iOS 下几乎是一样的代码,iOS 下只要注释掉 .listStyle(SidebarListStyle())
这行就可以跑起来。
先放截图:
macOS 下的截图:
iPad 下的截图:
源代码:
import SwiftUI
struct Task: Codable, Identifiable, Hashable {
var id: Int
var name: String
}
struct ContentView: View {
@State private var tasks: [Task] = [
Task(id: 0, name: "Fix #1"),
Task(id: 1, name: "Fix #2"),
Task(id: 2, name: "Fix #3"),
Task(id: 3, name: "Fix #4"),
Task(id: 4, name: "Fix #5")
]
@State private var selection: Task?
var body: some View {
NavigationView {
List(selection: $selection) {
ForEach(tasks, id: \.self) { task in
NavigationLink(destination: VStack {
Text(task.name)
Button("Random Select") {
self.selection = self.tasks.randomElement()
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)) {
VStack {
Text(task.name)
}
}
}
}
.listStyle(SidebarListStyle())
VStack {
Text("Please select one task")
Button("Random Select") {
self.selection = self.tasks.randomElement()
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
}
发现问题和 Bug 分析
但就是这样的一个小 demo,也有很严重的 Bug。先说 macOS 端:
macOS 版本,跑起来以后,直接点 Random Select,是可以正常工作的,你会发现 List 左侧选中的项目,在随机跳动。
但是,一旦你手动选择过一个项目后,再点 Random Select,就不管用了。
而 iPadOS 下面,无论怎么点,都是不管用的…
昨天遇到这个问题后,我一直在想,我哪里做错了,是不是有什么条件未考虑到。今天我再想想,抛开 SwiftUI 还不成熟的话题,这就是 SwiftUI 的局限性,或许,我要做的任务,根本就不应该来用 SwiftUI 来完成。
SwiftUI 的局限性是什么?
SwiftUI 是什么?它是一套跨平台的框架,我上述代码,也完美的验证了同样的代码,可以跑在 macOS 和 iOS 上,并且都非常 Native——除了行为没有达到我的预期。
作为同时写过 macOS 和 iOS App 的开发者,我思考这个问题出现的原因,首先就是 AppKit 和 UIKit 下 API 和预期行为的大不同。
假如我们分别用平台对应的 API 去达到「选择列表行」的效果的话,那么要用到的 API 分别是:
- macOS:NSTableView 的
func selectRowIndexes(_ indexes: IndexSet, byExtendingSelection extend: Bool)
- iOS:UITableView 的
func selectRow(at indexPath: IndexPath?, animated: Bool, scrollPosition: UITableView.ScrollPosition)
这两个 API,不仅名字不一样,参数数量不一样,连参数类型都不一样。通过各种排列组合,有太多调用方式和结果了。
我不知道 SwiftUI 底层是怎么实现的,但是据目前了解到的信息,除了底层绘制的相关 View,大多数 SwiftUI 的控件,都是去调用对应平台的原生控件的,就像 List 在 macOS 下是 NSTableView,在 iOS 下是 UITableView。
于是,要达到预期的 List 选中(selection)的效果,我可以想象 SwiftUI 需要处理大量的逻辑,来使这个功能工作——根据目前的情况,一个小 demo 就暴露了 SwiftUI 目前的问题和不足。也就是我说的局限型。
SwiftUI 目前的使用场景
根据我有限的对 SwiftUI 的了解和实践,我觉得当前的 SwiftUI 适合的应用场景是:
- 没有复杂逻辑的使用原生界面的列表型交互;
- 需要高度自定义的、使用自定义绘制和动画的界面;
尽管 SwiftUI 没有达成我简单的「随机选择」这么一个需求(我希望是我代码的问题),但是我依然相信 SwiftUI 会在特定场景下达到生产力的极大提升,只是目前真的很不成熟,Xcode 也比较让人失望。
接下去能期待的一件事情,就是 Swift 5.2 的发布,它将极大的改进 SwiftUI 在编码过程中的错误提示。希望能早点发布。
最近在自学 Apple 生态开发,面对 SwiftUI 进退两难了。看来目前还是只做了解,等待 SwiftUI 慢慢成长,就像 Swift,也许时间可以治愈一切,哈哈。
图大,可以告知您博客所用的技术栈是什么嘛?最近想要自己写个博客,而不是用现成的博客平台。
可以慢慢学 SwiftUI,做点简单的东西,不一定要做正式的 App 才上。
我的 Blog 主要是 Django 写的。
你好 我正在开发一款Mac软件,使用到和您文中相同的 NavigationView { List { NavigationLink 的嵌套方式(类似Finder)。我想要知道当前选中的是哪个cell,请问博主知道如何获取吗?(即OC中didselectedRow:)onChange、selection暂时未能实现