肇鑫的技术博客

业精于勤,荒于嬉

什么?AccentColor又闹幺蛾子了?

一年以前,我就踩过一次AccentColor的坑。没想到,一年之后我又掉进来了。

SwiftUI下,TextField诡异失去Focus下样式的问题

事情的起因是这样的。因为苹果的新系统发布了嘛。我不能免俗的也要改进我之前的应用,添加对于新系统特性的一些支持之类的。

但是我在修改代码后测试时发现,当使用ZStack模拟弹窗之后,弹窗后面的视图的颜色会出错。我一开始以为是ZStack的问题,于是将模拟弹窗改成了.fullScreenCover的方式。这个问题在当时看起来时解决了。但是今天我在使用中发现,这个问题又重新出现了。

于是我在Google上搜索了一下,没想到这还是一个SwiftUI长期存在的一个问题。

How do I stop the AccentColor from turning Gray when a sheet is being presented?

原来在SwiftUI中实际使用时。原本应该一致的Color.accentColor和Color("AccentColor")在实际使用中是不一致的。说得更具体些,就是Color.accentColor会使用应用设置和用户的系统设置。而Color("AccentColor")则是将AccentColor作为颜色资源从Asset文件夹直接读取。因此,虽然它的名字也叫"AccentColor",但是实际上它只是名字叫"AccentColor"的一个颜色,你改成别的名字,比如"MyAppColor"也是一样的。虽然这样会失去Color.accentColor一些独特的个性,但是能保证颜色的一致,即颜色不会莫名其妙的改变。

系统弹sheet的时候,Color.accentColor会改变的问题,应该就是sheet本身可能存在某种机制,将应用内设置的Color.accentColor从Asset文件夹设置的内容,改成了系统默认设置的内容。比如下图红色圈起来的部分,就是系统允许用户自定义用户AccentColor偏好的地方。

accent_color

大模型又暴露了……

这真的不是危言耸听,这是真的骇人听闻。盘点一下我遇到大模型的智障行为,本文不定期更新。

大模型至今不了解SwiftUI的更新机制,必须要严格约束,小心使用

今天遇到了一个sheet弹窗之后,视图更新不同步的问题。由于我已经在提示词中,告诉了我倾向使用@Observable,而不是旧版的ObservableObject,所以一开始大模型创建了正确的@Observable class,然后在视图中使用@State创建了这个model的实例。

但是为什么还是出错了呢?那就要看使用时更具体的实现了。在开启sheet之前,大模型调用了一个准备函数,在这个函数中,对于model进行了一系列的初始化。具体的步骤是这样的,先新建一个model,然后修改它,然后用这个修改完成的model,替换为系统@State里的那个model。

我首先将问题描述给大模型,sheet首次打开的时候,显示的界面并不符合预期,应该有值的地方,实际上为空。但是如果关掉,再次打开,就又正确了。

大模型思考了一番,说这可能是sheet的机制造成,sheet有时会提前锁定一些值,这会造成打开视图时的不同步。它的建议是,在sheet打开的视图内部的onAppear中新增一个fallback的调用,重新检查并赋值。然后还说,如果这样还不行,可以考虑使用GCD延迟的方式,也就是使用DispatchQueme.main.async调用来实现改动。

试了,onAppear的确不行。但是我没有继续使用它建议的GCD延迟的方式。我问它是否还有其它的办法。

大模型想了想,说可以考虑将@Observable的class改成纯粹的struct的方式。然后就其次咔嚓地修改起来,我一看改动后的代码,妈呀!连mutating都搞出来了。SwiftUI的代码哪有这么写的啊?我果断点了Undo,取消了这次修改。

正确的方式

我再次看了一下代码,突然我发现了问题的所在。前面提到,在准备函数中,大模型的改动方式是“先新建一个model,然后修改它,然后用这个修改完成model,替换为系统@State里的那个model。”这个方式在SwiftUI中显然是错误的。SwfitUI中@State的class类型,你不能替换它,因为一替换之前的跟踪就被中断了。

所以我和大模型说,我们不应该“新建model,修改,然后替换@State”,而是应该直接修改@State中的变量。

大模型想了想,觉得我说得有道理,就修改了。然后还跟我说,如果我确定这个好用,那么onAppear那里的代码,就可以删掉了。

于是我先注释掉onAppear那里的代码,然后运行应用,果然一切都正常了。

小结

大模型并没有真正了解SwiftUI的更新机制,这部分的代码写的时候,它更像是一个拙略的模仿者。虽然这部分,我之前的领悟也不怎么深刻,但是我随着持续不断地学习和使用SwiftUI,现在在这方面,我有信心可以说,我可比大模型强多了。

大模型不擅长重构

这个说法听起来是反直觉的。大模型相比于人类,应该更擅长重构吧。为什么说它不擅长呢?

事实如此。昨天我想重构一个900多行的SwiftUI的文件,里面包含一个主视图,主视图下的视图组件,以及一个弹出视图。如果是开发者来重构这个文件,之需要新建文件,然后复制,粘贴,再删掉旧的就可以了。可以很快就完成。

但是我用大模型来重构。却接连几次失败。过程一般是这样。一开始大模型计划的挺好。

  • 我将会将文件拆分为以下几个文件。
  • 然后开始拆分,但是执行到5、6个的时候。
  • 弹出出错提示,上下文已经耗尽了。

我为了节省上下文,又单独增加了对于上下文节省的办法。每次重构一个文件,复制、粘贴、删除完了,验证有没有错误。有错误修复,没有再继续。并且可以抛弃掉使用过的上下文。

再次运行,稍微好了些,多实现几个文件,但是最后还是因为上下文不够而失败了。

小结

大模型不擅长重构,不是因为它无法规划重构。而是因为大模型重构时需要考虑大量的上下文信息,最终会因为上下文耗尽而无法继续。

苹果表无法解锁macOS问题的一个奇葩的解决办法

我实在无法找到更适当的词,只能用奇葩来形容这个解决办法。

我开始使用macOS 26 beta系列也有一段时间了,目前在使用的最新版本是beta 7。长久以来,我一直遇到一个有些奇怪的问题,就是每次系统睡眠之后唤醒,苹果表的解锁总是失败。但是进入系统之后,在需要苹果表解锁的其它情况下,比如打开密码应用或者钥匙串,双击苹果表侧键解锁的这个功能却总是能成功。

不过苹果表解锁也不总是失败。我有两台Mac mini,一台是M1,一台是M4,M1的解锁就总是成功,而M4这台就总是失败。

尝试解决这个问题

今天我突发奇想,想要在AI的辅助下解决这个问题。我首先怀疑的时候VPN软件。因为更新到最新的macOS 26 beta之后,原来使用Clash X Pro不好用了,我不得不换成了Clash Verge rev。我问AI有没有可能是VPN软件造成的。AI说有可能。让我关了它再试。我试了。还是一样。

之后我又和AI一起开启了macOS控制台应用,想尝试通过读取日志来找到问题的解决方案。最后发现了一个loginwindow的一条故障日志,故障(红色)是比错误(黄色)等级更高的错误。它说在创建main文件夹的某个临时文件夹时出错,可能是沙盒的问题。我当时也信了,因为我的M4是256G的,为了节省磁盘空间,我将home文件夹设置到了外置的SSD上。我想这种比较罕见的设置,可能是苹果没有考虑过。我甚至还差一点儿就去跟苹果反馈这个问题。但是后来我放弃了。因为我觉得每次输入密码也还好,不算很麻烦,就没有反馈。同时我在想,以后我再买新电脑,一定要多花些钱,买个大一些的硬盘。

山重水复疑无路

的确。我没能解决这个问题。我只是放下了它。然后,我开始着手解决我的另外一个开源应用的小问题。

App Helper,应用助手。是我开源在GitHub上的一个macOS的助手应用。它的其中一个功能,是一键切换HDR模式,即在开启和关闭之间切换。不过,我发现,我的显示器,虽然支持HDR,但是使用不同的连接方式它对于HDR的支持不同。比如用USB-C线直接连接,就不支持HDR,用HDMI或者DisplayPort线连接,就支持。

所以,我的目标是通过系统API检测,在不支持HDR的时候,隐藏这个一键切换的按钮。因为我当前是USB-C的连接,不支持HDR,所以我首先完成了这部分的代码。

因为我还需要测试支持HDR下的部分。于是我同时使用HDMI进行连接。macOS有一个问题,就是你用两根不同的线连接同一个显示器,但是在macOS看来你就是在使用双显示器。它的显示器设置中,同时显示出两台显示器,并且不能设置禁用其中的一个。这样就比较麻烦了。因为我虽然可以通过显示器上的信号源菜单来切换到不同的接口,但是存在一个主副窗口的问题。

最终没办法,我只能将Mac mini上的USB-C连接显示器的那根拔了下来。将HDMI的连接作为唯一的连接,这样可以方便我进行调试。

柳暗花明又一村

这次HDR的选项还是不能显示。经过调试我发现,必须显示器先打开HDR模式,苹果的API才能检测出显示器支持HDR,如果没有开启,那就检测不出来。

那也无妨,大不了我就跟原来一样,不检测了。我又测试一键切换的功能。结果这个功能也不好用。这就比较郁闷了。因为苹果本身并没有提供切换的HDR切换的API,我使用的是脚本调用控件的方式,这个方法生效的前提是控件的位置必须固定。我管它叫数格子,脚本的写法类似,找到xx组的xx格子,然后把它上面的开关打开/关闭。现在系统升级了,位置变了。苹果💊。

算了,心累。我打算彻底去掉这个功能。毕竟,苹果动动手指,我就得重新数格子,并且还需要考虑不同版本macOS的兼容性,实在得不偿失。

休息,休息一会儿

休息结束之后,重新唤醒已经睡眠的Mac,手表传来熟悉的解锁声。我居然成功解锁了这台M4。又试了几次,无论是睡眠之后立即解锁,还是睡眠了几小时之后再解锁,都是次次成功。

“这是为什么呢?”(蔡明)如是说。“排除了一切不可能,那么剩下的那个无论多么不合理,就是唯一的可能。”——福尔摩斯。

我做的最大改动只有一点,使用HDMI连接显示器,并且拿掉了USB-C的连线。所以,这个问题的原因就是使用USB-C连接显示器,会导致苹果表无法解锁macOS。这谁能想到啊,你说是不是奇葩的解决办法?

特别说明,这个USB-C连接显示器,就是两边都是USB-C接口。而HDMI连接,则是用Mac mini独有的HDMI接口连接显示器的HDMI接口。

理论上,这个USB-C除了传递数据,还能同时获得显示器传来最大90瓦的电量。不过由于Mac mini的USB-C不支持反向供电,所以没啥用。如果比MacBook,是可以同时供电的。但是不知道是不是这一点影响了苹果表的解锁。我不是硬件工程师,不敢妄言。但是觉得这个值得提一下。