最近忙完了几件事,圣诞假期也临近,终于有时间好好学习并折腾一下 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 写的。