SwiftUI 撸码常见错误 2 例漫谈
概述
在 SwiftUI 日常撸码过程中,头发尚且还算茂盛的小码农们经常会犯这样那样的错误。虽然犯这些错的原因都很简单,但有时想要快速准确的定位它们却并不容易。
况且这些错误还可能在模拟器和 Xcode 预览(Preview)表现的行为不甚一致,这无疑加大了“驯服”它们的难度。
在本篇博文中,您将学到如下内容:
- 概述
- 1. TabView 每个标签页 tag 类型需要谨慎对待
- 2. 视图中 @FetchRequest 定义不完整导致的崩溃
- 总结
本文出现的问题在 Xcode 16.1 + iOS 18.1 中仍会存在。
相信学完本课后,小伙伴们倘若以后再遇到与此类似的问题,必将胸有成竹、迎刃而解。
那还等什么呢?Let‘s fix it!!!😉
本文对应的视频课在此,欢迎小伙伴们恣意观赏:
SwiftUI 撸码常见错误 2 例漫谈
1. TabView 每个标签页 tag 类型需要谨慎对待
在下面这个简单的例子中,点击 TabView 内部每个标签页竟然毫无反应!
下面上源代码,你能看出是哪里的问题吗?
enum TabTag: Int {case home = 0, worry, game, user
}@available(iOS 17, *)
@Observable
class Model {var currentSelectingTab = 0
}@available(iOS 17.0, *)
struct Main: View {@State var model = Model()var body: some View {NavigationStack {TabView(selection: $model.currentSelectingTab) {Text("Home").tabItem {Label("Home", systemImage: "house")}.tag(TabTag.home)Text("Worry").tabItem {Label("Worry", systemImage: "figure.fall")}.tag(TabTag.worry)Text("Game").tabItem {Label("游戏", systemImage: "gamecontroller.fill")}.tag(TabTag.game)Text("User").tabItem {Label("用户", systemImage: "person.fill")}.tag(TabTag.user)}.navigationTitle("SwiftUI 初学者常见错误")} }
}
其实原因很简单:问题就出在 TabView 中每个标签页的 tag 类型上。我们实际使用的是 TabTag 类型,但是向 TabView 构造器 selection 形参绑定的却是整形类型。
所以这个问题解决起来也很容易,只需要将 Model 中对应的属性改为 TabTag 类型即可:
@available(iOS 17, *)
@Observable
class Model {var currentSelectingTab = TabTag.home
}
再次运行代码一切都回归正常了。
但是故事到这里并没有结束。假如我们将代码修改为如下形式,却是能够选择 TabView 中每个标签页的:
struct Main: View {@State var currentSelectingTab = 0var body: some View {NavigationStack {TabView(selection: $currentSelectingTab) {Text("Home").tabItem {Label("Home", systemImage: "house")}.tag(TabTag.home)Text("Worry").tabItem {Label("Worry", systemImage: "figure.fall")}.tag(TabTag.worry)Text("Game").tabItem {Label("游戏", systemImage: "gamecontroller.fill")}.tag(TabTag.game)Text("User").tabItem {Label("用户", systemImage: "person.fill")}.tag(TabTag.user)}}}
}
在上面的代码中,我们仅仅将原来在 Model 中 Int 类型的 currentSelectingTab 直接放到视图 @State 中,并将其与 TabView 的选中操作绑定起来而已。但是这样的话,同样造成了 TabView 标签页 tag 类型的不一致,为何又没有问题呢?
其实,这波“走位”表面看起来貌似可以恣意切换 TabView 各个标签页,但实际却是有问题的。为了拨开迷雾见青天,我们特地为 currentSelectingTab 状态增加了 onChange 监听器:
struct Main: View {@State var currentSelectingTab = 0var body: some View {NavigationStack {TabView(selection: $currentSelectingTab) {//...}}.onChange(of: currentSelectingTab) {_,new in// 永远不会进入此闭包中print("\(new)")}}
}
运行代码可以发现:尽管我们可以切换到不同标签页中,但我们的 currentSelectingTab 状态却从未发生过改变!
所以,最终我们发现了 SwiftUI 中一个“不一致”的场景:在 @Observable 对象中属性类型和 TabView 标签页 tag 不匹配会导致“正确”的交互行为(标签页无法切换),但在 @State 同样的属性却不能。
希望苹果在将来可以将它们一致化。
2. 视图中 @FetchRequest 定义不完整导致的崩溃
另一个隐蔽的问题涉及到 CoreData 为 SwiftUI 视图添加的 @FetchRequest 属性包装器。
@available(iOS 17, *)
struct WorriesView: View { @FetchRequest(sortDescriptors: [.init(keyPath: \Worry.occurrenceTime, ascending: false)]) var worriesvar body: some View {NavigationStack {List {Section("最近担忧") {}Section("严重担忧") {}Section("其它担忧") {}}.navigationTitle("担忧终结者")}}
}
上面的代码貌似人畜无害,而且在 Xcode 预览中的显示也是无懈可击:
不过,如果我们编译并胆敢在模拟器或真机上运行上述代码,App 就会立即崩溃:
而且从提示来看,很难发现究竟是哪里出了问题。如果不是我们已将问题局限在 @FetchRequest 那行代码上,大家恐怕很难轻易找出罪魁祸首,更何况如果它匿影藏形隐身在海量视图中了。
经常在 SwiftUI 中使用 @FetchRequest 属性修饰器来获取 CoreData 数据的小伙伴们,应该能一眼看出上面代码中的问题,实际上它缺少了 FetchedResults 后半部分的定义,是不完整的:
@FetchRequest(sortDescriptors:
[.init(keyPath: \V3_Worry.occurrenceTime, ascending: false)])
var worries: FetchedResults<V3_Worry>
只要将其补全即可。
该问题的另一个特点是它在 Xcode 预览中讳莫如深、深藏不露,只为在实际运行时给秃头码农们“当头一棒”,实属可恨!
不过,通过上面条分缕析的介绍,现在小伙伴们对它们一定能够火眼金睛、无所畏惧,棒棒哒!💯
更多 Xcode 预览调试中的技巧和陷阱,请小伙伴们移步如下链接观赏精彩的内容:
- Xcode编写SwiftUI代码时一个编译通过但导致预览(Preview)崩溃的小陷阱
- Xcode如何在预览(Preview)调试中避免与SwiftUI正常运行时环境不一致导致的崩溃
- Xcode预览(Preview)显示List视图内容的一个Bug及解决
- Xcode 15 预览 SwiftUI 视图中 @FetchRequest 查询结果不能正确刷新的解决
总结
在本篇博文中,我们讨论了 Xcode 16.1(iOS 18.1)中仍然存在 SwiftUI 的两个“鸱张鼠伏”、较难发现缘由小问题的“症状”和解决之道,希望可以帮助到大家。
感谢观赏,再会啦!😎
相关文章:

SwiftUI 撸码常见错误 2 例漫谈
概述 在 SwiftUI 日常撸码过程中,头发尚且还算茂盛的小码农们经常会犯这样那样的错误。虽然犯这些错的原因都很简单,但有时想要快速准确的定位它们却并不容易。 况且这些错误还可能在模拟器和 Xcode 预览(Preview)表现的行为不甚…...
JavaScript系列(9)-- Set数据结构专题
JavaScript Set数据结构专题 🎲 在前八篇文章中,我们探讨了JavaScript的语言特性、ECMAScript标准、引擎工作原理、数值类型、字符串处理、Symbol类型、Object高级特性和Array高级操作。今天,让我们深入了解JavaScript中的Set数据结构。Set是…...

开发培训-慧集通(iPaaS)集成平台脚本开发Groovy基础培训视频
Groovy是一种基于Java虚拟机(JVM)的敏捷开发语言,结合了Python、Ruby和Smalltalk的许多强大特性。它旨在提高开发者的生产力,通过简洁、熟悉且易于学习的语法,Groovy能够与Java代码无缝集成,并提供强大…...

【软考网工笔记】计算机基础理论与安全——网络规划与设计
HFC 混合光纤同轴电缆网 HFC: Hybrid Fiber - Coaxial 的缩写,即混合光纤同轴电缆网。是一种经济实用的综合数字服务宽带网接入技术。 HFC 通常由光纤干线、同轴电缆支线和用户配线网络三部分组成,从有线电视台出来的节目信号先变成光信号在干线上传输…...

【设计模式】 基本原则、设计模式分类
设计模式 设计模式是软件工程中的一种通用术语,指的是针对特定问题的经过实践验证的解决方案。设计模式并不是最终的代码实现,而是描述了如何解决某一类问题的思路和方法。 如果熟悉了设计模式,当遇到类似的场景,我们可以快速地…...

mac m2 安装 docker
文章目录 安装1.下载安装包2.在downloads中打开3.在启动台打开打开终端验证 修改国内镜像地址小结 安装 1.下载安装包 到官网下载适配的安装包:https://www.docker.com/products/docker-desktop/ 2.在downloads中打开 拖过去 3.在启动台打开 选择推荐设置 …...
Vue3-pinia的具体使用和刷新页面状态保持解决方案
在 Vue 3 中,Pinia 是一个官方推荐的状态管理库,它替代了 Vuex(Vuex在Vue3中依然可以正常使用),提供了更加简洁和现代的 API,同时能够与 Vue 3完美配合。在本回答中,我们将详细介绍 Pinia 的使用…...

用ResNet50+Qwen2-VL-2B-Instruct+LoRA模仿Diffusion-VLA的论文思路,在3090显卡上训练和测试成功
想一步步的实现Diffusion VLA论文的思路,不过论文的图像的输入用DINOv2进行特征提取的,我先把这个部分换成ResNet50。 老铁们,直接上代码: from PIL import Image import torch import torchvision.models as models from torch…...
创建.net core 8.0项目时,有个启用原生AOT发布是什么意思
启用原生 AOT 发布(Native AOT publishing) 是指在 .NET 6 及更高版本中使用 Ahead-of-Time (AOT) 编译 技术,将应用程序提前编译为本地机器代码,从而生成更高效、更快速启动的可执行文件。 1. AOT 编译是什么? AOT …...

2.1.7-1 io_uring的使用
一、背景 (1)下面几个有关异步操作的例子: a)客户端和服务端的异步关系,就是客户端发送请求后不需要等待结果,接下来发送其他请求。 b)对于服务端,客户端来请求后,服务…...

群论学习笔记
什么是对称? 对称是一个保持对象结构不变的变换,对称是一个过程,而不是一个具体的事物,伽罗瓦的对称是对方程根的置换,而一个置换就是对一系列事物的重排方式,严格的说,它也并不是这个重排本身…...
深入解析-正则表达式
学习正则,我们到底要学什么? 正则表达式(RegEx)是一种强大的文本匹配工具,广泛应用于数据验证、文本搜索、替换和解析等领域。学习正则表达式,我们不仅要掌握其语法规则,还需要学会如何高效地利…...

yolov5核查数据标注漏报和误报
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、误报二、漏报三、源码总结 前言 本文主要用于记录数据标注和模型预测之间的漏报和误报思想及其源码 提示:以下是本篇文章正文内容,…...

日志聚类算法 Drain 的实践与改良
在现实场景中,业务程序输出的日志往往规模庞大并且类型纷繁复杂。我们在查询和查看这些日志时,平铺的日志列表会让我们目不暇接,难以快速聚焦找到重要的日志条目。 在观测云中,我们在日志页面提供了聚类分析功能,可以…...

如何让用户在网页中填写PDF表格?
在网页中让用户直接填写PDF表格,可以大大简化填写、打印、扫描和提交表单的流程。通过使用复选框、按钮和列表等交互元素,PDF表格不仅让填写过程更高效,还能方便地在电脑或移动设备上访问和提交数据。 以下是在浏览器中显示可填写PDF表单的四…...

GXUOJ-算法-补题:22级《算法设计与分析》第一次课堂练习
2.最大子数组和 问题描述 代码解答 #include<bits/stdc.h> using namespace std; const int N1005; int sum,n,a[N]; int res-1;int result(){for(int i0;i<n;i){if(sum<0) suma[i];else{suma[i];resmax(res,sum);}}return res; } int main(){cin>>n;for(i…...
源代码编译安装X11及相关库、vim,配置vim(3)
一、vim插件安装 首先安装插件管理器Vundle ()。参照官网流程即可。vim的插件管理器有多个,只用Vundle就够了。然后~/.vimrc里写上要安装的插件: filetype offset rtp~/.vim/bundle/Vundle.vim call vundle#begin() Plugin VundleVim/Vundle.vim Plugin powerline…...

uniapp 微信小程序 自定义日历组件
效果图 功能:可以记录当天是否有某些任务或者某些记录 具体使用: 子组件代码 <template><view class"Accumulate"><view class"bx"><view class"bxx"><view class"plank"><…...

EdgeX规则引擎eKuiper
EdgeX 规则引擎eKuiper 一、架构设计 LF Edge eKuiper 是物联网数据分析和流式计算引擎。它是一个通用的边缘计算服务或中间件,为资源有限的边缘网关或设备而设计。 eKuiper 采用 Go 语言编写,其架构如下图所示: eKuiper 是 Golang 实现的轻量级物联网边缘分析、流式处理开源…...
react 优化方案
更详细的 React 优化方案可以分为性能优化、代码结构优化、开发效率提升等多个方面,结合实际项目需求,逐步应用这些优化策略。 一、性能优化 1. 避免不必要的重新渲染 React.memo: 缓存组件,防止组件在父组件重新渲染时无意义的重新渲染。 const ChildComponent = Reac…...

C++课设:高效的日程管理系统
名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 专栏介绍:《编程项目实战》 目录 一、C日程管理系统的时代价值1. 为什么选…...

基于SpringBoot的房屋租赁系统的设计与实现(thymeleaf+MySQL)
💗博主介绍💗:✌在职Java研发工程师、专注于程序设计、源码分享、技术交流、专注于Java技术领域和毕业设计✌ 温馨提示:文末有 CSDN 平台官方提供的老师 Wechat / QQ 名片 :) Java精品实战案例《700套》 2025最新毕业设计选题推荐…...

【C语言预处理详解(下)】--#和##运算符,命名约定,命令行定义 ,#undef,条件编译,头文件的包含,嵌套文件包含,其他预处理指令
目录 五.#和##运算符 5.1--#运算符 5.2--##运算符 六.命名约定,#undef,命令行定义 6.1--命名约定 6.2--#undef 6.3--命名行定义 七.条件编译 常见的条件编译指令: 1.普通的条件编译: 2.多个分支的条件编译(可以利用条…...

Git忽略规则.gitignore不生效解决
我在gitlab中新建了一个项目仓库,先把项目文件目录绑定到仓库,并全部文件都上传到了仓库中。 然后又从别的项目复制了忽略文件配置过来,怎么搞他都不能生效忽略我不要提交仓库的文件。 从网上查到说在本地仓库目录中,打开命…...

【Linux】Linux 环境变量
参考博客:https://blog.csdn.net/sjsjnsjnn/article/details/125533127 一、环境变量 1.1 基本概念 环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数如:我们在编写C/C代码的时候,在链接的时候&am…...

adb 连不上真机设备问题汇总
问题一、无法弹出 adb 调试授权弹窗 详细描述: 开发者选项中已打开 usb 调试,仅充电模式下 usb 调试也已打开,电脑通过 usb 连上手机后,一直弹出 adb 调试授权弹窗,尝试取消授权再次连接,还是无法弹出问题…...

Qt 仪表盘源码分享
Qt 仪表盘源码分享 一、效果展示二、优点三、源码分享四、使用方法 一、效果展示 二、优点 直观性 数据以图表或数字形式展示,一目了然。用户可以快速获取关键信息,无需深入阅读大量文字。 实时性 仪表盘通常支持实时更新,确保数据的时效性。…...

用电脑控制keysight示波器
KEYSIGHT示波器HD304MSO性能 亮点: 体验 200 MHz 至 1 GHz 的带宽和 4 个模拟通道。与 12 位 ADC 相比,使用 14 位模数转换器 (ADC) 将垂直分辨率提高四倍。使用 10.1 英寸电容式触摸屏轻松查看和分析您的信号。捕获 50 μVRMS …...

【Redis】zset 类型
zset 一. zset 类型介绍二. zset 命令zaddzcard、zcountzrange、zrevrange、zrangebyscorezpopmax、zpopminzrank、zrevrank、zscorezrem、zremrangebyrank、zremrangebyscorezincrby阻塞版本命令:bzpopmax、bzpopmin集合间操作:zinterstore、zunionstor…...
分类与逻辑回归 - 一个完整的guide
线性回归和逻辑回归其实比你想象的更相似 😃 它们都是所谓的参数模型。让我们先看看什么是参数模型,以及它们与非参数模型的区别。 线性回归 vs 逻辑回归 线性回归:用于回归问题的线性参数模型。逻辑回归:用于分类问题的线性参数模型。参数回归模型: 假设函数形式 模型假…...