news 2026/4/24 9:35:28

SwiftUI学习笔记5-列表和文本字段

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SwiftUI学习笔记5-列表和文本字段

本节内容

  • 为未知数量的数据创建列表视图
  • 将一组可变数据存储在数组中
  • 使用ForEach来创建与数组中每个元素对应的视图
  • 学习用于(用户)输入文本的TextField.onSubmit用法
  • 跨视图的数据绑定
  • 按钮的自定义标签(这种方法让标签名以Text的形式呈现,可以修改属性,携带闭包)

List视图

一个List视图向其 body 中的内容添加其他视图

VStack { List { Text("Elisha") Text("Andre") Text("Jasmine") Text("Po-Chun") } }

使用应用添加和删除名称

ForEach视图使用带参数的闭包来表示你提供(数组或其他数据集)中的每个值。

name in表示“一个值进入闭包。”

@State private var nameToAdd = ""//类型推断,通过""表示类型为String

TextField用法:输入框

在列表下方创建一个TextField

struct ContentView: View { @State private var names: [String] = ["Elisha", "Andre", "Jasmine", "Po-chun"]//把这个数组设定为UI变动根据之一 @State private var nameToAdd = "" //类型推断,通过""表示类型为String,此处表示用来存放即时输入的内容 var body: some View { VStack { List { ForEach(names, id: \\.description) { name in Text(name) } } TextField("Add Name", text: $nameToAdd)//这是下面的添加框 .onSubmit { //使用闭包,onSubmint修饰符,将输入的东西(nameToAdd)添加到指定数组中 names.append(nameToAdd) nameToAdd = "" } } .padding() } }

将文本字段绑定到该属性(nameToAdd),以便在用户在键盘上输入时可以修改该属性

绑定是一种让你将一个视图访问另一个视图拥有的状态的方式。要创建一个绑定到状态属性,属性名前缀为$

当输入TextField时,它会改变nameToAdd的值,而 SwiftUI 会更新视图。按换行的时候就会自动运行.使用.onSubmit修饰符与闭包来执行当用户点击返回时的操作

TextField("Add Name", text: $nameToAdd)//这是下面的添加框 .autocorrectionDisabled() .onSubmit { //使用闭包,onSubmint修饰符,将输入的东西(nameToAdd)添加到指定数组中 if !nameToAdd.isEmpty { names.append(nameToAdd) nameToAdd = "" } }
但是空字符串可能会被添加进去,需要防止这种情况。使用!.isEmpty避免

!表示否的意思

TextField("Add Name", text: $nameToAdd)//这是下面的添加框 .onSubmit { //使用闭包,onSubmint修饰符,将输入的东西(nameToAdd)添加到指定数组中 if !nameToAdd.isEmpty { names.append(nameToAdd) nameToAdd = "" } }
Autocorrection(自动更正)

是一种文本输入功能,旨在帮助用户在输入时自动识别并纠正拼写错误或不规范的词汇。在许多应用和设备中,自动更正可以提高输入效率和准确性,尤其是在输入常见词汇或短语时。

这里需要禁用。把它disabled掉

TextField("Add Name", text: $nameToAdd)//这是下面的添加框 .autocorrectionDisabled() .onSubmit { //使用闭包,onSubmint修饰符,将输入的东西(nameToAdd)添加到指定数组中 if !nameToAdd.isEmpty { names.append(nameToAdd) nameToAdd = "" } }

从数组中随机选择一个元素

添加一个属性来跟踪选定的名称

@State private var pickedName = ""

添加一个按钮供用户选择元素

Button("Pick Random Name") { //这个按钮用于选择数组内的随机一个元素 if let randomName = names.randomElement() {//randomElement表示在(数组)内随机获取元素.配合if else如果数组为空,将返回nil} pickedName = randomName } else { pickedName = "" } }

添加Divider()分割线
Divider()

List中会自动使用Divider()分割项目,在其他地方需要手动添加。

使用开关(Toggles)

提供选项让用户在选定姓名后可以移除。要实现此功能,为用户添加一个Toggle以在两种行为之间进行选择。

添加一个布尔状态属性来跟踪姓名移除功能。

@State private var shouldRemovePickedName = false

Toggles开关,与文本类似

两者都需要一个单一的绑定。因为开关控制开/关状态,其绑定是一个Bool。(文本是isEmpty)

Toggle("Romove when picked", isOn: $shouldRemovePickedName)

removeAll 方法用于从数组中移除元素。

这里的闭包 name in return (name == randomName) 将对 names 数组中的每个元素进行检查: 如果 name 等于 randomName,则返回 true,表示该元素应该被移除。 该方法会移除所有与 randomName 相等的元素。

if let是一种可选绑定语法。它的作用是将 randomElement() 返回的值绑定到一个新的常量 randomName 上,同时检查这个值是否为 nil。

这里其实就是可选链的内容,if let xx = xx() { code } else { code }

Button("Pick Random Name") { //这个按钮用于选择数组内的随机一个元素 if let randomName = names.randomElement() { //randomElement表示在(数组)内随机获取元素. //配合if else如果数组为空,将返回nil} pickedName = randomName //使用 removeAll 在数组中的每个元素上运行闭包中的代码 //这个闭包用 name 参数表示元素,并返回一个 Bool 值来指示是否应该移除该元素。 if shouldRemovePickedName { names.removeAll { name in return (name == randomName) } } }else { pickedName = "" } }

就是一个开关,如果打开,就表示在选中的同时移除内容。

使用自定义按钮标签

使用一个新的初始化器,该初始化器接受两个闭包来定制按钮标签的大小:一个用于操作,一个用于标签。

.buttonStyle(.borderedProminent)

突出显示按钮

使用自定义标签的按钮,其实就是把名字标记为label

这个Button包含两个闭包,操作闭包包含命令式代码,标签闭包包含声明式视图代码。

Button { if let randomName = names.randomElement() { pickedName = randomName if shouldRemovePickedName { names.removeAll { name in return (name == randomName) } } } else { pickedName = "" }//label:添加在其他闭包后面 } label: { Text("Pick Random Name") } .buttonStyle(.borderedProminent) .font(.title2)

为label的Text添加内边距

} label: { Text("Pick Random Name") .padding(.vertical, 8) //为标签视图添加内边距,这将扩大整个按钮. //如果修饰符放在Button的层级,则不会对Text的内边距产生影响 .padding(.horizontal, 16) }


补充标题和图标等

Hierarchical分层渲染模式:改变符号组件的不透明度(注意小人)

SF Symbols 有多种不同的渲染模式。层级符号渲染模式会改变符号组件的不透明度。

.symbolRenderingMode(.hierarchical)

使用分层渲染后(右):

更改一下所选名称的样式

在列表中添加圆角矩形剪裁形状.clipShape(RoundedRectangle(cornerRadius: 8))
List { ForEach(names, id: \\.description) { name in Text(name) } } .clipShape(RoundedRectangle(cornerRadius: 8)) //为List添加圆角矩形剪裁形状

如图所示

.tint默认调用accentcolor,accentcolor可以在assets里设置,更改accentcolor

使用空数组作为names的默认值,这样这就是一个记录软件了。

其实就是把原数组中的内容设置为空。

这种思想应该广泛用在设计应用里…一开始假设其有,最后再消除全部,默认为空。

选一个删一个,这根本就是——

练习:

  1. 当前设计的一个问题是你可以多次输入相同的名称——当随机选中该名称时,两者都会被删除。修改.onSubmit中的逻辑,以确保每个新输入都是唯一的。(提示:可以像使用.removeAll一样,用.contains在数组中进行操作。)
  2. 修改你的应用,以在输入名称时去除开头和结尾的空白字符。查看CharacterSet以找到正确的参数值类型。去除空白有助于确保应用中的数据集干净。
  3. 添加保存列表和加载列表按钮,实现名称列表复用(即退出后可以重新加载)。(提示:需要一个新的状态属性。)
功能1与功能2:使用&&限制重复元素、使用.trimmingCharacters去除空白

.trimmingCharacters一定需要一个参数接收它,所以只能创建临时变量

let qukongbai = nameToAdd.trimmingCharacters(in: .whitespacesAndNewlines)
TextField("Add Name", text: $nameToAdd)//这是下面的添加框 .autocorrectionDisabled() .onSubmit { let qukongbai = nameToAdd.trimmingCharacters(in: .whitespacesAndNewlines) //使用闭包,onSubmint修饰符,将输入的东西(nameToAdd)添加到指定数组中 if !qukongbai.isEmpty && !names.contains(qukongbai) { names.append(qukongbai) nameToAdd = "" }

功能3:使用UserDefaults临时存储数据

UserDefaults 是一个方便的类,用于存储小型数据(例如,用户设置、应用状态等),数据会在应用关闭后依然保持。适合存储键值对数据,例如 String、Int、Bool、Double 等基本数据类型以及 Array 和 Dictionary 形式的集合。

用法:

使用 set(参数一: , forKey:) 方法来保存数据,接受两个参数:

第一个参数:你想要保存的数据。 第二个参数:一个字符串,作为键,用于标识该数据。

let userDefaults = UserDefaults.standard // 获取标准 UserDefaults 实例 userDefaults.set("John Doe", forKey: "username") // 保存字符串 userDefaults.set(25, forKey: "age") // 保存整数 userDefaults.set(true, forKey: "isPremiumUser") // 保存布尔值

注意:如果不用临时常量,则必须要用.standard才能在UserDefaults里储存数据

创建两个按钮,按下按钮的时候会调用某个函数。功能分别为保存和加载列表

Button("Save List") { saveList() } .padding(.top) Button("Load List") { loadList() }

如果要使用UserDefaults,则需要储存的值必须要有一个键名称(为了指明需要储存或访问的数据)。

创建需要储存的值列表

@State private var savenamekey = "saveNames"

savenamekey是一个键(key)的名称,它用于在 UserDefaults 中唯一标识一项数据。

编写两个函数的内容

UserDefaults是 iOS 提供的一种轻量级存储机制,主要用于保存用户的偏好设置和小型数据。

standardUserDefaults的一个实例,允许开发者对系统默认的用户设置进行读写操作。

set(names, forKey: savedNamesKey)set是一个方法,把数据保存到UserDefaults中

names是要保存的实际数据,这代表名字数组。

forKey: savedNamesKey指定了一个字符串作为标识符(这个标识符称为“键”),savedNamesKey是一个常量,代表我们在存储和读取数据时使用的唯一名称。

func saveList(){//保存列表到 UserDefaults UserDefaults.standard.set(names, forKey: savenamekey) }
接下来尝试从 UserDefaults 中读取数据,并在需要用的时候重新加载。

stringArray(forKey:) 是UserDefaults的一个方法,用于从 UserDefaults 中获取一个存储的字符串数组。

使用UserDefaults读取数据,注意,要使用UserDefaults.standard用于(在UserDefaults内)储存数据,读取数据。我想这是个固定用法…

if let 这是可选链,建立一个临时常量saveNames,调取(储存进了)UserDefaults里的值

表示如果能获取到一个非nil的数组,则把savedNames赋值给name(带有显示功能的原数组)

func loadList(){ if let savedNames = UserDefaults.standard.stringArray(forKey: savenamekey) { names = savedNames //if let这是可选链同时使用闭包,表示如果能获取到一个非nil的数组,则赋值给savedNames } }

如果希望按钮实现一些操作,需要在里面写一写函数/闭包,来实现。

练习总代码:
// // ContentView.swift // Pick-a-Pal // // Created by sakiko on 2026/4/23. // import SwiftUI struct ContentView: View { @State private var names: [String] = []//把这个数组设定为UI变动根据之一 @State private var nameToAdd = "" //类型推断,通过""表示类型为String,此处表示用来存放即时输入的内容 @State private var pickedName = "" //添加一个属性来跟踪选定的名称 @State private var shouldRemovePickedName = false //这是为了控制删除数据 @State private var savenamekey = "saveNames" //这是为了使用UserDefaults而设置的键名称 var body: some View { VStack { VStack(spacing: 8) { Image(systemName: "person.3.sequence.fill") .foregroundStyle(.tint)//改变图片前景色 .symbolRenderingMode(.hierarchical) Text("Death Note?") } .font(.title) .bold() Text(pickedName.isEmpty ? " " : pickedName)//这是一个三元运算符,如果是空的,则返回空格字符串,否则返回选定的名称 .font(.title2) .bold() .foregroundStyle(.tint) List { ForEach(names, id: \\.description) { name in Text(name) } } .clipShape(RoundedRectangle(cornerRadius: 8)) //为List添加圆角矩形剪裁形状 TextField("Add Name", text: $nameToAdd)//这是下面的添加框 .autocorrectionDisabled() .onSubmit { let qukongbai = nameToAdd.trimmingCharacters(in: .whitespacesAndNewlines) //使用闭包,onSubmint修饰符,将输入的东西(nameToAdd)添加到指定数组中 if !qukongbai.isEmpty && !names.contains(qukongbai) { names.append(qukongbai) nameToAdd = "" } } Divider() Toggle("Remove when picked", isOn: $shouldRemovePickedName) Button { //这个按钮用于选择数组内的随机一个元素 if let randomName = names.randomElement() { //randomElement表示在(数组)内随机获取元素.配合if else如果数组为空,将返回nil} pickedName = randomName //使用 removeAll 在数组中的每个元素上运行闭包中的代码 //这个闭包用 name 参数表示元素,并返回一个 Bool 值来指示是否应该移除该元素。 if shouldRemovePickedName { //如果开关状态是On,就把它从里面移除 names.removeAll { name in return (name == randomName) } } } else { pickedName = "" } } label: { Text("Pick Random Name") .padding(.vertical, 8) //为标签视图添加内边距,这将扩大整个按钮. //如果修饰符放在Button的层级,则不会对Text的内边距产生影响 .padding(.horizontal, 16) } .padding(.top) .buttonStyle(.borderedProminent) .font(.title2) Button("Save List") { saveList() } .padding(.top) .buttonStyle(.borderedProminent) .font(.title3) Button("Load List") { loadList() } .buttonStyle(.borderedProminent) .font(.title3) } .padding() } func saveList(){//保存列表到 UserDefaults UserDefaults.standard.set(names, forKey: savenamekey) } func loadList(){ if let savedNames = UserDefaults.standard.stringArray(forKey: savenamekey) { names = savedNames //stringArray(forKey:) 是一个方法,用于从 UserDefaults 中获取一个存储的字符串数组。 //if let 这是可选链,建立一个临时常量saveNames,调取(储存进了)UserDefaults里的值 //表示如果能获取到一个非nil的数组,则把savedNames赋值给name(带有显示功能的原数组) } } } #Preview { ContentView() }

最终效果

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/24 9:30:06

ERPNext自动化部署系统:企业级应用的一键式解决方案

ERPNext自动化部署系统:企业级应用的一键式解决方案 【免费下载链接】erpnext_quick_install Unattended install script for ERPNext Versions, 13, 14 and 15 项目地址: https://gitcode.com/gh_mirrors/er/erpnext_quick_install 在当今数字化转型浪潮中&…

作者头像 李华
网站建设 2026/4/24 9:26:36

量子机器学习中的不确定性量化与对抗鲁棒性实践

1. 量子机器学习中的不确定性量化实战量子机器学习(QML)作为量子计算与经典机器学习的交叉领域,近年来在化学模拟、金融预测和药物发现等领域展现出巨大潜力。然而,量子系统的固有噪声和测量不确定性给模型可靠性带来了严峻挑战。我们基于变分量子分类器…

作者头像 李华
网站建设 2026/4/24 9:26:32

Qwen3-4B-Thinking镜像免配置:CUDA 12.1+cuDNN 8.9兼容性验证

Qwen3-4B-Thinking镜像免配置:CUDA 12.1cuDNN 8.9兼容性验证 1. 模型概述 Qwen3-4B-Thinking-2507-Gemini-2.5-Flash-Distill是基于通义千问Qwen3-4B官方模型开发的高效推理版本。这个镜像特别针对CUDA 12.1和cuDNN 8.9环境进行了优化,实现了开箱即用的…

作者头像 李华