【ios】在 SwiftUI 中实现可随时调用的加载框

在 SwiftUI 项目中实现一个自定义的加载框(loading)功能,可以在任意位置调用,以便显示加载动画或者进度条。下面的教程将详细讲解如何创建一个可复用的 Loading 组件,并通过通知机制控制其显示和隐藏。

先上效果:

swift ui加载框效果演示

创建 Loading.swift 文件

在项目中创建一个名为 Loading.swift 的新文件,并粘贴以下代码:

import SwiftUIstruct Loading: ViewModifier {// 定义一些通知名称,用于在应用中控制弹窗的显示、隐藏和进度更新static let showNotification = Notification.Name("Loading.showNotification")static let hiddenNotification = Notification.Name("Loading.hiddenNotification")static let updateProgressNotification = Notification.Name("Loading.updateProgressNotification")// 全局的单例实例,确保在整个应用中只有一个 Loading 对象static let shared = Loading()// 一些状态变量,用于跟踪当前视图的显示状态和加载提示的相关信息@State private var isContentShowing = false  // 是否显示内容的标志@State private var isPresented = false       // 是否显示加载提示的标志@State private var progress: Double = 0.0    // 当前的进度值@State private var mode: LoadingMode = .standard // 当前的加载模式(标准或进度)@State private var labelText: String? = nil  // 可选的提示文本// 静态标识符,用于跟踪弹窗是否已经显示,以防止重复显示private static var isShowing = false// 定义一个枚举,表示加载提示的模式,可以是标准模式或带进度的模式enum LoadingMode {case standardcase progress}// body 方法用于构建自定义视图的内容func body(content: Content) -> some View {ZStack {// 显示原始内容contentif isPresented {// 黑透Color.black.opacity(0.5).ignoresSafeArea()// 使用 GeometryReader 获取屏幕尺寸,用于动态计算弹窗的位置和大小GeometryReader { geometry inVStack {if mode == .progress {ZStack {// 浅色带Circle().trim(from: 0.0, to: 1.0).stroke(Color.gray.opacity(0.3), lineWidth: 3).frame(width: 60, height: 60)// 环形进度条Circle().trim(from: 0.0, to: progress) // 进度值.stroke(Color.white, lineWidth: 3) // 进度条颜色和宽度.rotationEffect(.degrees(-90)) // 旋转90度,起点从顶部开始.frame(width: 60, height: 60) // 环形进度条的大小.animation(.easeInOut(duration: 0.5), value: progress) // 平滑过度动画Text("\(Int(progress * 100))%").foregroundColor(.white).font(.headline)}if let labelText = labelText {Text(labelText).foregroundColor(.white)}} else {// 标准模式下显示一个普通的加载指示器ProgressView().tint(Color.white).padding(.top, 10)// 如果有提示文本,就显示在加载指示器下面if let labelText = labelText {Text(labelText).foregroundColor(.white).padding(.top, 10)}}}.padding(.vertical, 10).padding(.horizontal, 20).background(Color.black.opacity(0.7)) // 添加一个半透明的黑色背景.cornerRadius(8) // 设置圆角.position(x: geometry.size.width / 2, y: geometry.size.height / 2) // 居中显示}}}// 当视图出现时,设置 isContentShowing 为 true.onAppear {isContentShowing = true}// 当视图消失时,设置 isContentShowing 为 false.onDisappear {isContentShowing = false}// 监听显示通知,当接收到显示通知时,显示加载提示.onReceive(NotificationCenter.default.publisher(for: Loading.showNotification)) { notification in// 如果内容没有显示,或者弹窗已经显示了,就不做任何操作guard isContentShowing, !Self.isShowing else { return }Self.isShowing = true // 标记弹窗为已显示// 解析通知中的用户信息,确定加载模式和提示文本if let userInfo = notification.userInfo,let mode = userInfo["mode"] as? LoadingMode {self.mode = modeif mode == .progress {self.progress = 0.0 // 如果是进度模式,重置进度为 0}} else {self.mode = .standard // 默认模式为标准模式}self.labelText = notification.userInfo?["label"] as? StringisPresented = true // 显示加载提示}// 监听隐藏通知,当接收到隐藏通知时,隐藏加载提示.onReceive(NotificationCenter.default.publisher(for: Loading.hiddenNotification)) { _ inguard isContentShowing else { return }isPresented = falseSelf.isShowing = false // 标记弹窗为已隐藏}// 监听进度更新通知,当接收到更新通知时,更新进度值.onReceive(NotificationCenter.default.publisher(for: Loading.updateProgressNotification)) { notification inguard isContentShowing, mode == .progress, let progressValue = notification.object as? Double else { return }self.progress = progressValue}}// 在主线程中执行static func postNotificationOnMainThread(name: Notification.Name, object: Any? = nil, userInfo: [AnyHashable: Any]? = nil) {if Thread.isMainThread {NotificationCenter.default.post(name: name, object: object, userInfo: userInfo)} else {DispatchQueue.main.async {NotificationCenter.default.post(name: name, object: object, userInfo: userInfo)}}}static func show(mode: LoadingMode = .standard, label: String? = nil) {postNotificationOnMainThread(name: Loading.showNotification, userInfo: ["mode": mode, "label": label as Any])}// 显示标准模式的加载提示static func showByStandard(label: String? = nil) {show(mode: .standard, label: label)}// 显示进度模式的加载提示static func showByProgress(label: String? = nil) {show(mode: .progress, label: label)}// 隐藏static func hidden() {postNotificationOnMainThread(name: Loading.hiddenNotification)}// 更新进度值static func updateProgress(_ progress: Double) {postNotificationOnMainThread(name: Loading.updateProgressNotification, object: progress)}
}// 给View扩展loadingable方法
extension View {func loadingable() -> some View {return self.modifier(Loading.shared)}
}

调用示例

在顶层视图中调用 loadingable()

为了使 Loading 能够在应用的任意位置调用,我们需要在主视图中添加 .loadingable() 修饰符。例如,在 ContentView 中:

loadingable只需要在顶层视图调用即可,往后不管嵌套多少层,只要是在这个视图下,都可以调用显示!!!以下是一个示例,具体怎么用看你自己了。

import SwiftUIstruct ContentView: View {var body: some View {ZStack {VStack {Spacer()Button("展示普通加载") {Loading.showByStandard(label: "加载中")simulateProgressUpdate()}.padding()Spacer()Button("展示进度加载") {Loading.showByProgress(label: "加载中")simulateProgressUpdate()}.padding()Spacer()}}.loadingable()}private func simulateProgressUpdate() {DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {Loading.updateProgress(0.2)}DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {Loading.updateProgress(0.4)}DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {Loading.updateProgress(0.6)}DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {Loading.updateProgress(0.8)}DispatchQueue.main.asyncAfter(deadline: .now() + 2.5) {Loading.updateProgress(1.0)}DispatchQueue.main.asyncAfter(deadline: .now() + 3) {Loading.hidden()}}
}struct ContentView_Previews: PreviewProvider {static var previews: some View {ContentView()}
}

为了实现这个效果,忙活了大半天,给点个赞呗~

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

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

相关文章

MySQL【知识改变命运】10

联合查询 0.前言1.联合查询在MySQL里面的原理2.练习一个完整的联合查询2.1.构造练习案例数据2.2 案例:⼀个完整的联合查询的过程2.2.1. 确定参与查询的表,学⽣表和班级表2.2.2. 确定连接条件,student表中的class_id与class表中id列的值相等2.…

wordpress 子比主题美化 四宫格 多宫格 布局插件

wordpress 主题美化 四宫格 多宫格 布局插件(只在子比主题上测试过,其它主题没测试) A5资源网四宫格布局插件是一个功能丰富的WordPress插件,专为创建自适应的四宫格布局而设计。这个插件具有以下主要特点: 灵活的布局: 支持1到8个宫格的自定…

Springboot整合knife4j生成文档

前言 在开发过程中,接口文档是很重要的内容,用于前端对接口的联调,也用于给其他方使用。但是手写相对比较麻烦。 当然也有swagger之类的,但是界面没有那么友好。 官网: 整合步骤 整合依赖 需要根据版本进行&…

如何使用 pnpm 进行打补丁patch操作?推荐两个方法

前言 作为一个前端开发者,我们每天都在和各种各样的库和依赖打交道。node_modules 目录中存放着我们项目的各种依赖。我们有时需要对其中的一些依赖进行修改,比如修复某个 bug 或者增加某些自定义功能。这时候,给 node_modules 打补丁就显得…

为您的 WordPress 网站打造完美广告布局 A5广告单元格插件

一个为 WordPress 网站量身定制的强大工具,它将彻底改变您展示广告的方式 灵活多变的布局设计 A5 广告单元格插件的核心优势在于其无与伦比的灵活性。无论您是想要创建整齐的网格布局,还是希望打造独特的不规则设计,这款插件都能满足您的需求。 自定义网格数量:从 2…

androidStudio编译导致的同名.so文件冲突问题解决

files found with path lib/arm64-v8a/libserial_port.so from inputs: ...\build\intermediates\library_jni\debug\jni\arm64-v8a\libserial_port.so C:\Users\...\.gradle\caches\transforms-3\...\jni\arm64-v8a\XXX.so 解决方式如下: 1.将gradle缓存文件删…

TwinCAT3安装 Advanced Motion Pack库

文章目录 一.简介二.安装方式1. 下载地址2. 双击下载好的安装包3. 选择语言(只有英文和德语)4. 点击Next5. 选择Accept6. 填写公司和组织名称(随意)7. 点击Install8. 等待安装完成9. 点击Finish 一.简介 TF5420 TC3 Motion Pick-…

深度学习-1:逻辑回归和梯度下降

逻辑回归 逻辑回归是一个二分分类问题 比如判断一张图片中是否是猫就是一个二类分类问题 图像由像素值组成,要将图像输入模型,就将其变为一个向量,该向量存储三个通道上的所有像素值,若图像尺寸为64x64x3,则向量维度…

RequestBody接收参数报错com.fasterxml.jackson.databind.exc.MismatchedInputException

目录: 1、错误现象2、解决办法3、最终验证 1、错误现象 报错的现象和代码如下: 2、解决办法 查了很多都说参数类型对不上,但是明明是对上的,没有问题,最后只有换接收方式后验证是可以的;最终想了一下&…

Unity3D 观察者模式

Unity3D 泛型事件系统 观察者模式 观察者模式是一种行为设计模式,通过订阅机制,可以让对象触发事件时,通知多个其他对象。 在游戏逻辑中,UI 界面通常会监听一些事件,当数据层发生变化时,通过触发事件&am…

网络参考模型总结

物理层: 电信号,有中继器、集线器等设备。 数据链路层:帧,有网桥、交换机等设备,通过mac地址。 网络层:包,有路由器等设备,通过IP地址。 传输层:段,有网…

rootless模式下istio ambient的流量管理测试

接上一篇rootless模式下istio ambient鉴权策略,本次测试管理流量的功能。 服务流量分割 Bookinfo应用程序有三个版本的reviews服务,接下来对这些版本进行分配流量控制测试。 longtdsubuntu:~$ kubectl get pod |grep reviews reviews-v1-746f96c9d4-2…

出类拔萃的四款录屏工具你pick哪一个?

在这个屏幕时代,录屏已经成为我们日常工作和娱乐中不可或缺的一部分。无论是为了记录游戏精彩瞬间,还是为了分享教程和经验,一个好的录屏工具都是必不可少的。今天,就让我为大家推荐几款电脑免费录屏工具,并分享一下使…

DDD通用语言、多尿和尿频-《分析模式》漫谈41

“Analysis Patterns”的第3章有这么一句: A patient with observations of the presence of thirst, weight loss, and polyuria indicates diabetes. 2004(机械工业出版社)中译本的译文为: 虽然没有完全使用类图(类…

【C++_string类练习】仅仅反转字母

题目链接:仅仅反转字母 解题思路: 这种反转字符的题目我第一个想到的方法就是:双指针 一个指针在前start,一个指针在后back, 如果指针所指向的位置的值是字母,那么两个指针位置的值就进行交换&#xff0…

矿山企业电能智能化管理怎么实现

0引言 电力工业是国民经济和社会发展的基础,随着高质量发展要求的提升,提高能源效率对工业企业至关重要。有效提高电力能效的方法包括技术创新、监测用能行为和探索技术解决方案。智能化的电能管理是应对资源和环境压力的关键措施,对提高用电…

点餐小程序实战教程20广告管理

目录 1 创建数据源2 添加轮播容器3 创建变量4 绑定变量5 预览应用总结 一般餐厅需要有一些宣传,在我们的点餐页面可以在顶部加载广告位。广告主要是用轮播图的形式进行展示,本节我们介绍一下如果显示广告。 1 创建数据源 打开控制台,点击应用…

10-Docker安装Redis

10-Docker安装Redis Docker安装Redis 以 Redis 6.0.8 为例: docker pull redis:6.0.8直接pull会出现以下错误 [rootdocker ~]# docker pull redis:6.0.8 Error response from daemon: Get "https://registry-1.docker.io/v2/": net/http: request can…

【Cadence27】HDL拷贝工程➕Allegro导出DXF和3D文件STP

【转载】Cadence Design Entry HDL 使用教程 【Cadence01】Cadence PCB Edit相对延迟与绝对延迟的显示问题 【Cadence02】Allegro引脚焊盘Pin设置为透明 【Cadence03】cadence不小心删掉钢网层怎么办? 【Cadence04】一般情况下Allegro PCB设计时的约束规则设置&a…

卸载 Git

目录 打开 Geek找到 Git右键卸载 Git卸载完成等待扫描注册表点击完成清除Git在本地的配置文件 打开 Geek 找到 Git 右键卸载 Git 卸载完成 等待扫描注册表 点击完成 已经删除完毕 清除Git在本地的配置文件 进入 C:\Users\用户名 删除框起来的配置文件 👇 源码资料…