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 = 0
    
    var 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 = 0
    
    var 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 worries
        
    var 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 的两个“鸱张鼠伏”、较难发现缘由小问题的“症状”和解决之道,希望可以帮助到大家。

感谢观赏,再会啦!😎

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/948542.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

米哈游可切换角色背景动态壁纸

米哈游可切换角色背景动态壁纸 0. 视频 B站演示: 米哈游可切换角色背景动态壁纸-wallpaper 1. 基本信息 作者: 啊是特嗷桃系列: 复刻系列 (衍生 wallpaper壁纸引擎 用)网站: 网页版在线预览 (没有搞大小适配, 建议横屏看; 这个不能切角色, 只能在wallpaper中切)仓库: GitHub…

OWASP ZAP之API 请求基础知识

ZAP API 提供对 ZAP 大部分核心功能的访问,例如主动扫描器和蜘蛛。ZAP API 在守护进程模式和桌面模式下默认启用。如果您使用 ZAP 桌面,则可以通过访问以下屏幕来配置 API: Tools -> Options -> API。 ZAP 需要 API 密钥才能通过 REST API 执行特定操作。必须在所有 …

Elasticsearch: 高级搜索

这里写目录标题 一、match_all匹配所有文档1、介绍&#xff1a; 二、精确匹配1、term单字段精确匹配查询2、terms多字段精确匹配3、range范围查询4、exists是否存在查询5、ids根据一组id查询6、prefix前缀匹配7、wildcard通配符匹配8、fuzzy支持编辑距离的模糊查询9、regexp正则…

齿轮缺陷检测数据集VOC+YOLO格式485张3类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;485 标注数量(xml文件个数)&#xff1a;485 标注数量(txt文件个数)&#xff1a;485 标注…

ArkTs之NAPI学习

1.Node-api组成架构 为了应对日常开发经的网络通信、串口访问、多媒体解码、传感器数据收集等模块&#xff0c;这些模块大多数是使用c接口实现的&#xff0c;arkts侧如果想使用这些能力&#xff0c;就需要使用node-api这样一套接口去桥接c代码。Node-api整体的架构图如下&…

MySQL(五)MySQL图形化工具-Navicat

1. MySQL图形化工具-Navicat Navicat是一套快速、可靠的数据库管理工具&#xff0c;Navicat是以直觉化的图形用户界面而建的&#xff0c;可以兼容多种数据库&#xff0c;支持多种操作系统。   Navicat for MySQL是一款强大的 MySQL 数据库管理和开发工具&#xff0c;它为专业…

【OceanBase】通过 OceanBase 的向量检索技术构建图搜图应用

文章目录 一、向量检索概述1.1 关键概念① 非结构化数据② 向量③ 向量嵌入(Embedding)④ 向量相似性检索 1.2 应用场景 二、向量检索核心功能三、图搜图架构四、操作步骤4.1 使用 Docker 部署 OceanBase 数据库4.2 测试OceanBase数据库连通性4.3 开启数据库向量检索功能4.4 克…

微信流量主挑战:用户破16!新增文档转换(新纪元3)

朋友们&#xff0c;报告好消息&#xff01;我的小程序用户数量已经涨到16个了&#xff01;没错&#xff0c;真没拉朋友圈亲戚好友来撑场子&#xff0c;全靠实力&#xff08;和一点点运气&#xff09;吸引了16位陌生小伙伴光临&#xff01;这波进步&#xff0c;连我自己都感动了…

Python:交互式物质三态知识讲解小工具

学着物理写着Python 以下是一个使用Python的Tkinter库实现的简单示例程序&#xff0c;通过图形界面展示并讲解固态、液态、气态的一些特点&#xff0c;代码中有详细的注释来帮助你理解各部分功能&#xff1a; 完整代码 import tkinter as tk from tkinter import ttk import …

ESP32-S3遇见OpenAI:OpenAI官方发布ESP32嵌入式实时RTC SDK

目录 OpenAI RTC SDK简介应用场景详解智能家居控制系统个人健康助手教育玩具 技术亮点解析低功耗设计快速响应高精度RTC安全性保障开发者指南 最近&#xff0c;OpenAI官方发布了一款针对ESP32-S3的嵌入式实时RTC&#xff08;实时时钟&#xff09;SDK&#xff0c;这标志着ESP32-…

Windows 11 关闭 VBS(基于虚拟化的安全性)

注&#xff1a;本文为 “Windows 11 关闭 VBS” 相关方法文章合辑。 重传部分 csdn 转储异常图片&#xff0c;未整理去重。 Win11 关闭 VBS 的几种方法 适用机型&#xff1a;台式 / ThinkCentre / 笔记本 / ThinkPad 分析 Virtualization-based Security (VBS) 基于虚拟化的…

小程序租赁系统的优势与应用探索

内容概要 小程序租赁系统&#xff0c;听起来很高大上&#xff0c;但实际上它比你想象的要实用得多&#xff01;设想一下&#xff0c;几乎所有的租赁需求都能通过手机轻松解决。这种系统的便捷性体现在让用户随时随地都能发起租赁请求&#xff0c;而不再受制于传统繁琐的手续。…

简历_专业技能_熟悉Redis常用数据结构及其操作命令

系列博客目录 文章目录 系列博客目录1.Redis通用命令2.String类型3.Hash类型4.List类型5.Set类型6.Sorted类型7.StringRedisTemplate 1.Redis通用命令 通用指令是部分数据类型的&#xff0c;都可以使用的指令&#xff0c;常见的有&#xff1a; KEYS&#xff1a;查看符合模板的…

USB 驱动开发 --- Gadget 设备连接 Windows 免驱

环境信息 测试使用 DuoS(Arm CA53&#xff0c; Linux 5.10) 搭建方案验证环境&#xff0c;使用 USB sniff Wirekshark 抓包分析&#xff0c;合照如下&#xff1a; 注&#xff1a;左侧图中设备&#xff1a;1. 蓝色&#xff0c;USB sniff 非侵入工 USB 抓包工具&#xff1b;2. …

2025年1月4日蜻蜓q旗舰版st完整开源·包含前后端所有源文件·开源可商用可二开·优雅草科技·优雅草kir|优雅草星星|优雅草银满|优雅草undefined

2025年1月4日蜻蜓q旗舰版st完整开源包含前后端所有源文件开源可商用可二开优雅草科技优雅草kir|优雅草星星|优雅草银满|优雅草undefined 产品介绍&#xff1a; 本产品主要贡献者优雅草科技优雅草kir|优雅草星星|优雅草银满|优雅草undefined-青史留名&#xff0c;时光如川浪淘…

【文献精读笔记】Explainability for Large Language Models: A Survey (大语言模型的可解释性综述)(三)

****非斜体正文为原文献内容&#xff08;也包含笔者的补充&#xff09;&#xff0c;灰色块中是对文章细节的进一步详细解释&#xff01; 3.2 全局解释&#xff08;Global Explanation&#xff09; 与旨在解释模型个体预测的局部解释不同&#xff0c;全局解释提供了对语言模型…

体验谷歌最新Gemini 2.0 Flash原生多模态音视频对话桌面分享功能

Gemini 2.0是谷歌最新推出的原生多模态输入输出的AI模型。Gemini 2.0 Flash是2.0家族第一个模型&#xff0c;以多模态输入输出和Agent技术为核心&#xff0c;速度比 1.5 Pro快两倍&#xff0c;关键性能指标超过 1.5 Pro。模型支持原生工具调用和实时音视频流输入&#xff0c;提…

Leecode刷题C语言之我的日程安排表③

执行结果:通过 执行用时和内存消耗如下&#xff1a; typedef struct {int size;int maxIntersection;int** books;// #ifdef DEBUG// int runCount;// #endif } MyCalendarThree;void insert(MyCalendarThree*, int, int, int, int); int* binarySearch(int*, int, int);MyCal…

C++ 函数名字后面带const

C++中,在函数名后面加上const关键字表示该函数是一个常量成员函数。 常量成员函数,可以在const对象上被调用,并且不会修改对象的状态。 VC6新建一个单文档工程;添加一个一般类; 把类的代码做好; // MyClass.h: interface for the MyClass class. // //#if !defined(AFX_…

SMTP发送邮件的过程

&#xff08;1&#xff09;SMTP客户端首先请求与服务器端的25号端口建立TCP连接(1分)。&#xff08;2&#xff09;连接建立成功后&#xff0c;客户端和服务器通过握手阶段验证双方身份(1分)。&#xff08;3&#xff09;验证成功后&#xff0c;客户端首先向服务器端通告邮件发送…