一发入魂:极简解决 SwiftUI 复杂视图未能正确刷新的问题(上)

news2025/7/17 5:43:02

在这里插入图片描述

概述

各位似秃非秃小码农们都知道,在 SwiftUI 中视图是状态的函数,这意味着状态的改变会导致界面被刷新。

但是,对于有些复杂布局的 SwiftUI 视图来说,它们的界面并不能直接映射到对应的状态上去。这就会造成一个问题:状态的改变并没有及时的引起 UI 的变化。

在这里插入图片描述

如上图所示:无论英雄挑战关卡的结果是成功还是失败,在视图的显示中都没有体现出来。这该如何是好呢?

在本篇博文中,您将学到如下内容:

  • 概述
  • 1. “固执己见”的 SwiftUI 视图
    • 1.1 关卡视图 StageView
    • 1.2 世界视图 WorldView
  • 总结

相信学完本课后,大家都会掌握只需寥寥几行代码就让 SwiftUI 复杂视图乖乖听话的奥义!

那还等什么呢?Let‘s go!!!😉


1. “固执己见”的 SwiftUI 视图

在上面的示例图中,英雄可以恣意挑战当前关卡,如果挑战成功则进入下一关卡,如果失败则会刷新挑战次数。前者应该在世界视图 WorldView 上有所体现,而后者则必须在关卡视图 StageView 中立即反映出来:

Button {
    if try! hero.challengeStage() {
        try! hero.moveToNextStage()
    }
} label: {
    Label("挑战关卡!", systemImage: "figure.fencing")
        .foregroundStyle(.white)
}

可惜的是,无论何种情况所有视图都将成为不舞之鹤,无动于衷。这是“肿”么回事呢?

1.1 关卡视图 StageView

我们先到 StageView 中看看与此相关的 UI 布局代码:

let records = stage.queryAllChallengingRecords()
                
if records.isEmpty {
    ContentUnavailableView("一片寂静,无人在此逗留...", systemImage: "eyes")
} else {
    ForEach(records) { record in
        if let hero = record.hero {
            VStack {
                heroCell(hero)
                
                HStack {
                    Text("失败次数: \(record.challengeFailedCount)")
                    Spacer(minLength: 0)
                    
                    if let timeString = try! hero.getStageStayRelevantTimeString(stage) {
                        Text("已徘徊 \(timeString)")
                    }
                }
                .foregroundStyle(.gray)
            }
        }
    }
}

理想的情况是:当英雄挑战关卡失败时,将会递增对应关卡挑战记录中挑战的次数,这会引起界面相关显示的变化。

在这里插入图片描述

但实际运行发现,界面并没有立即刷新。而只有当视图重建后,失败的挑战次数才能得以更新:

在这里插入图片描述

1.2 世界视图 WorldView

WorldView 视图是 StageView 的父视图,它的关键代码如下所示:

Form {
    let zones = world.zoneSortByNumberAry
    ForEach(zones) { zone in
        Section {
            HStack {
                Image(systemName: "map")
                Text("\(zone.number). \(zone.name ?? "")")
            }
            .font(.title2.bold())
            
            if let desc = zone.desc, !desc.isEmpty {
                Text(desc)
                    .foregroundStyle(.gray)
            }
            
            ScrollView {
                LazyVGrid(columns: [GridItem](repeating: .init(), count: 3)) {
                    ForEach(zone.stageSortByNumberAry) { stage in
                        NavigationLink {
                            StageView(stage: stage)
                        } label: {
                            VStack(alignment: .leading) {
                                HStack {
                                    stageLogoImage(stage)
                                    Text("\(stage.number)")
                                }
                                .frame(maxWidth: .infinity, alignment: .leading)
                                .font(.title3.bold())
                                .foregroundStyle(stageColor(stage))
                                
                                Text("\(stage.name ?? "")")
                                    .monospaced()
                                    .minimumScaleFactor(0.8)
                                    .foregroundStyle(stageColor(stage))
                                
                                Spacer()
                                
                                HStack {
                                    let challengingHeros = stage.queryAllChallengingRecords().count
                                    let victoryHeros = stage.queryAllVictoryRecords().count
                                    VStack(alignment: .leading) {
                                        Image(systemName: "person.3")
                                        Text("\(challengingHeros)/\(victoryHeros)")
                                    }
                                    .minimumScaleFactor(0.5)
                                    .font(.subheadline)
                                    .foregroundStyle(.teal)
                                    
                                    Spacer(minLength: 0)
                                    
                                    let includeHeros = stageChallengingMyHerosCount(stage)
                                    if includeHeros > 0 {
                                        HStack(spacing: 0) {
                                            Image(systemName: "star.hexagon")
                                            Text("\(includeHeros)")
                                        }
                                        .font(.subheadline)
                                        .foregroundStyle(.yellow)
                                    }
                                }
                            }
                            .padding()
                            .frame(maxWidth: .infinity)
                            .frame(height: 150)
                            .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 11))
                        }
                    }
                }
            }
            .listRowSeparator(.hidden)
        }
    }
}
.navigationTitle("世界地图")

在上面的代码中,我们主要做了这样几件事:

  • 获取 World 中的所有区域(Zones),并将结果存入 zones 局部变量中;
  • 获取每个 Zone 中的所有关卡(Stages),并将它们放入 LazyVGrid 容器中以便显示;
  • 如果我们的英雄恰巧正在挑战某一关卡,则在对应关卡 StageCell 里用黄色的五角星表示出来;

WorldView 视图的显示效果如下所示:

在这里插入图片描述

理想情况下,当英雄成功挑战某一关卡并从关卡视图返回世界视图后,世界视图中关卡 StageCell 中的黄色五角星应该自动移动到下一关。但是,从演示图中可以看到,这些都没有发生。这表示 WorldView 视图内容在 Hero 挑战成功后也没有被及时地刷新:

在这里插入图片描述

那么,我们不禁要问:到底是什么导致了 StageView 和 WorldView 视图刷新不及时呢?

在下一篇博文中,我们将继续介绍导致上述问题的根本原因,并先提出几个不那么优雅地解决方案唏嘘一番。不见不散!

总结

在本篇博文中,我们发现了一个 SwiftUI 复杂视图中状态的改变并未正确引起界面刷新的现象,并随后深入代码初步分析了故事的前因后果。

感谢观赏,我们下一篇再见!😎

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

软件设计师-下午题-试题4(15分)

目录 1 回溯法 1.1 N皇后问题 1.1.1 非递归求解N皇后问题 1.1.2 递归求解N皇后问题 1.2 真题 2 分治法 2.1 真题 3 动态规划法 3.1 0-1背包问题 3.2 真题 1 回溯法 1.1 N皇后问题 上图Q4与Q2在同一列且与Q1在同一斜线,先回溯到上一个皇后改变Q3皇后的位置…

leetcode二叉树相关题目复习(C语言版)

目录 1.单值二叉树 2.相同的树 3.对称二叉树 4.二叉树的前序遍历 5.另一颗树的子树 1.单值二叉树 思路1: 判断根节点、左节点与右节点的值是否相等,因为正向判断(即判断三值相等返回true)比较麻烦(不能根节点满足…

第十九次博客打卡

今天学习的内容是Java中的常见循环。 在 Java 中,常见的循环结构主要有以下几种:for 循环、while 循环、do-while 循环以及增强型 for 循环(也称为 for-each 循环)。 1. for 循环 for 循环是一种非常灵活的循环结构&#xff0c…

浅聊一下数据库的索引优化

背景 这里的索引说的是关系数据库(MSSQL)中的索引。 本篇不是纯技术性的内容,只是聊一次性能调优的经历,包含到一些粗浅的实现和验证手段,所以,大神忽略即可。 额…对了,笔者对数据库的优化手段…

山东大学软件学院软件工程计算机图形学复习笔记(2025)

写在前面: 现在是考完试的第二天,考试的内容还是有一部分没有复习到的…… 根据三角形的3个顶点坐标和内部某点坐标D,写出点D的基于面积的权重坐标Bresenham的算法描述与改进策略(这里ppt上很不清晰)以及直线反走样的…

【Docker】Docker Compose方式搭建分布式内存数据库(Redis)集群

文章目录 开发环境开发流程运行效果Docker Desktop桌面中的Redis结点启动图Redis结点1的打印日志情况图 配置代码命令行启动配置文件: README.md删除集群信息新建数据目录本地Redis的结点的域名,并添加到/etc/hosts文件的末尾域名映射启动集群结点创建集群关闭集群结点 redis-c…

如何在 Bash 中使用 =~ 操作符 ?

在 Bash 脚本世界中,有各种操作符可供我们使用,使我们能够操作、比较和测试数据。其中一个操作符是 ~ 操作符。这个操作符经常被忽视,但功能非常强大,它为我们提供了一种使用正则表达式匹配字符串模式的方法。 ~ 操作符语法 语法…

科学养生指南:打造健康生活

在快节奏的现代生活中,健康养生成为人们关注的焦点。科学养生无需复杂理论,掌握以下几个关键要素,就能为身体构筑坚实的健康防线。​ 合理饮食是健康的基础。世界卫生组织建议,每天应摄入至少 5 份蔬菜和水果,保证维生…

华为OD机试真题——单词接龙(首字母接龙)(2025A卷:100分)Java/python/JavaScript/C/C++/GO最佳实现

2025 A卷 100分 题型 本专栏内全部题目均提供Java、python、JavaScript、C、C++、GO六种语言的最佳实现方式; 并且每种语言均涵盖详细的问题分析、解题思路、代码实现、代码详解、3个测试用例以及综合分析; 本文收录于专栏:《2025华为OD真题目录+全流程解析+备考攻略+经验分…

计算机网络-MPLS VPN基础概念

前面几篇文章我们学习了MPLS的标签转发原理,有静态标签分发和LDP动态标签协议,可以实现LSR设备基于标签实现数据高效转发。现在开始学习MPLS在企业实际应用的场景-MPLS VPN。 一、MPLS VPN概念 MPLS(多协议标签交换)位于TCP/IP协…

【Linux系列】bash_profile 与 zshrc 的编辑与加载

💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

Spring Boot中的拦截器!

每次用户请求到达Spring Boot服务端,你是否需要重复写日志、权限检查或请求格式化代码?这些繁琐的“前置后置”工作让人头疼!好在,Spring Boot拦截器如同一道智能关卡,统一处理请求的横切逻辑,让代码优雅又…

基于 Spring Boot 瑞吉外卖系统开发(十五)

基于 Spring Boot 瑞吉外卖系统开发(十五) 前台用户登录 在登录页面输入验证码,单击“登录”按钮,页面会携带输入的手机号和验证码向“/user/login”发起请求。 定义UserMapper接口 Mapper public interface UserMapper exte…

计算机网络笔记(二十三)——4.5IPv6

4.5.1IPv6的基本首部 IPv6 的基本首部相对于 IPv4 进行了重大简化和优化,固定长度为 40 字节,大幅提升了路由器的处理效率。以下是各字段的详细说明: IPv6 基本首部字段组成 字段名位数作用描述版本 (Version)4 bits固定值为 6&#xff0c…

推荐一个Winform开源的UI工具包

从零学习构建一个完整的系统 推荐一个开源、免费的适合.NET WinForms 控件的套件。 项目简介 Krypton是一套开源的.Net组件,用于快速构建具有丰富UI交互的WinForms应用程序。 丰富的UI控件,提供了48个基础控件,如按钮、文本框、标签、下拉…

位与运算

只有当除数是 2 的幂次方(如 2、4、8、16...)时,取模运算才可以转换为位运算。 int b 19;int a1 b % 16; // 传统取模运算int a2 b & 15; // 位运算替代取模printf("b %d\n", b);printf("b %% 8 %d\n",…

趣味编程:四叶草

概述:在万千三叶草中寻觅,只为那一抹独特的四叶草之绿,它象征着幸运与希望。本篇博客主要介绍四叶草的绘制。 1. 效果展示 绘制四叶草的过程是一个动态的过程,因此博客中所展示的为绘制完成的四叶草。 2. 源码展示 #define _CR…

城市生命线综合管控系统解决方案-守护城市生命线安全

一、政策背景 国务院办公厅《城市安全风险综合监测预警平台建设指南》‌要求:将燃气、供水、排水、桥梁、热力、综合管廊等纳入城市生命线监测体系,建立"能监测、会预警、快处置"的智慧化防控机制。住建部‌《"十四五"全国城市基础…

# 2-STM32F103-复位和时钟控制RCC

STM32-复位和时钟控制RCC 2-STM32-复位和时钟控制RCC摘要说明本文参考资料如下: 一、STM32最小系统回顾STM32F103C8T6核心板原理图 二、复位三、时钟3.1 时钟树3.2 STM32启动过程3.2 SystemInit()函数3.2.1 SystemInit()第1句:3.2.2 SystemInit()第2句&a…

多模态大语言模型arxiv论文略读(七十五)

PosterLLaVa: Constructing a Unified Multi-modal Layout Generator with LLM ➡️ 论文标题:PosterLLaVa: Constructing a Unified Multi-modal Layout Generator with LLM ➡️ 论文作者:Tao Yang, Yingmin Luo, Zhongang Qi, Yang Wu, Ying Shan, C…