实现一个简易的计数器
Swift
import SwiftUI
struct ContentView: View {
@State private var count = 0
var body: some View {
VStack {
Text("简易计数器 ")
.font(.largeTitle)
Text("\(count)")
.font(.largeTitle)
}
Button {
count += 1
} label: {
Text("点我+1")
}
.padding()
}
}
#Preview {
ContentView()
}

没啥可讲的。 注意一下count用@state就行
Swift
import SwiftUI
struct ContentView: View {
var body: some View {
}
.padding()
}
}
#Preview {
ContentView()
}
以上就是代码最初的框架。。 .padding是界内留白用的 那个#preview也是方便看界面的
contentview是个结构体 它要求遵循view协议 view就是整个界面 也就是最重要的
定义一个body元素去描述界面里的东西 some view意思就是这个body返回的是一个view的东西
这个some用的很有意思 很人文(英语大佬应该懂,额我有点懂似乎,但我不是大佬)具体可以看看前面,提到过some view. 嗯就这样
实现一个待办清单Todo
第 1 步:定义待办事项的数据模型
待办事项有 3 个特征:
- 唯一 id(列表必须要)
- 标题文字
- 是否完成
我们新建一个结构体 Todo
Swift
// 写在 ContentView 外面
struct Todo: Identifiable {
let id = UUID() // 唯一id,列表自动识别
var title: String // 待办文字
var isDone: Bool // 是否完成
}
第 2 步:在页面里加 待办列表数据源
用 @State 保存整个待办数组,页面才能刷新
Swift
struct ContentView: View {
// 🔥 核心数据:所有待办都存在这里
@State private var todoList: [Todo] = []
// 输入框里的文字
@State private var inputText = ""
var body: some View {
NavigationStack {
Text("待办清单")
}
}
}
第 3 步:搭建页面布局结构
页面分上下两部分:
- 上面:输入框 + 添加按钮
- 下面:待办列表
用 VStack 垂直排列
Swift
var body: some View {
NavigationStack {
VStack(spacing: 0) {
// 第一部分:输入框+按钮(后面填内容)
HStack {
}
.padding()
// 第二部分:待办列表(后面填内容)
List {
}
}
.navigationTitle("待办清单")
}
}
现在页面是空框架,结构已经搭好了。
第 4 步:实现上面的「输入框 + 添加按钮」
给 HStack 里补上输入框和按钮
Swift
HStack {
// 输入框:双向绑定inputText
TextField("输入待办事项", text: $inputText)
.padding(10)
.background(Color.gray.opacity(0.2))
.cornerRadius(8)
// 添加按钮
Button("添加") {
// 点击执行:新建待办,加到列表里
if !inputText.isEmpty {
let newTodo = Todo(title: inputText, isDone: false)
todoList.append(newTodo)
inputText = "" // 清空输入框
}
}
.padding(8)
.foregroundColor(.white)
.background(Color.blue)
.cornerRadius(8)
}
✅ 现在你可以打字、点添加,数据已经能进列表了,只是还没显示出来。

Swift
import SwiftUI
struct Todo: Identifiable {
let id = UUID() // 唯一id,列表自动识别
var title: String //内容
var isDone: Bool
}
struct ContentView: View {
private func deleteTodo(at offsets: IndexSet) {
todoList.remove(atOffsets: offsets)
}
@State private var todoList : [Todo] = []
@State private var inputText = ""
var body: some View {
NavigationStack {
VStack{
HStack{
TextField("输入待办事项", text: $inputText)
.padding(10)
.background(Color.gray.opacity(0.2))
.cornerRadius(8)
}
.padding()
Button("添加") {
if !inputText.isEmpty {
let newTodo = Todo(title: inputText, isDone: false)
todoList.append(newTodo)
inputText = ""
}
}
.padding(8)
.foregroundColor(.white)
.background(Color.blue)
.cornerRadius(8)
List {
ForEach($todoList) { $todo in//循环遍历
HStack {
// 勾选圆圈按钮
Button {
todo.isDone.toggle() // 点击切换完成/未完成
} label: {
Image(systemName: todo.isDone ? "checkmark.circle.fill" : "circle")
}
Text(todo.title)
.strikethrough(todo.isDone)
}
}
.onDelete(perform: deleteTodo)
}
.navigationTitle("备忘录")
.toolbar {
EditButton()
}
}
}
.padding()
}
}
#Preview {
ContentView()
}

按我的想法手搓了一遍 感觉还不错
Swift
import SwiftUI
struct Todo : Identifiable{
let id=UUID()
var isdone:Bool
var title : String
}
struct ContentView: View {
private func deletetodo(at offsets : IndexSet){
todolist.remove(atOffsets: offsets)
}
@State private var todolist : [Todo] = []
@State private var message = ""
var body: some View {
NavigationStack{
Text("备忘录")
.font(.largeTitle)
.padding()
HStack{
TextField("输入代办事件",text :$message)
.padding(10)
.background(Color.gray.opacity(0.2))
.cornerRadius(8)
}
.padding()
List{
ForEach($todolist){ $todo in
HStack{
Button{
todo.isdone.toggle()
} label: {
Image(systemName: todo.isdone ? "checkmark.circle.fill" : "circle" )
}
Text(todo.title)
}
}
.onDelete(perform: deletetodo)
}
.toolbar{
EditButton()
}
Spacer()
Button("添加"){
if !message.isEmpty{
let newTodo = Todo(isdone : false,title: message)
todolist.append(newTodo)
message = ""
}
}
.padding(8)
.foregroundColor(.white)
.background(Color.blue)
.cornerRadius(8)
}
.padding()
}
}
#Preview {
ContentView()
}

实现一个随机壁纸图库(调API的)
稍微规范、稍微像样的 App 都会用 Navigation
没有一个上线 App 是只有一个页面、点哪儿都不跳转的。
Navigation = 页面管理器它管这 3 件事:
- 页面之间跳转
- 页面顶部标题
- 页面左上角返回按钮
因为任何真实 App 都一定有:
- 首页
- 详情页
- 设置页
- 个人中心
- 列表跳详情
只要有多个页面 → 必须用 NavigationStack
Swift
import SwiftUI
// 图片模型(Identifiable 列表专用)
struct RandomImage: Identifiable {
let id = UUID()
let imageUrl: URL
}
struct ContentView: View {
@State private var imageList: [RandomImage] = []
@State private var tip = "正在加载图片..."
var body: some View {
NavigationStack {
VStack(spacing: 20) {
Text("🖼️ 随机壁纸图库")
.font(.largeTitle.bold())
Text(tip)
.foregroundColor(.secondary)
// 图片列表
List(imageList) { item in
AsyncImage(url: item.imageUrl) { phase in
switch phase {
case .empty:
ProgressView()
.frame(maxWidth: .infinity, )
case .success(let image):
image
.resizable()
.scaledToFill()
.frame(maxWidth: .infinity,)
.clipped()
case .failure:
Text("图片加载失败")
.frame(maxWidth: .infinity, )
@unknown default:
EmptyView()
}
}
}
// 刷新按钮:重新加载一批随机图
Button("刷新一批壁纸") {
Task {
await loadRandomImages()
}
}
.padding(.horizontal, 40)
.padding(.vertical, 12)
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(12)
}
.padding()
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color(.systemGray6))
.navigationTitle("随机壁纸")
.onAppear {
Task {
await loadRandomImages()
}
}
}
}
// MARK: async/await 并发网络请求(你学的重点!)
private func loadRandomImages() async {
tip = "正在请求网络..."
var list: [RandomImage] = []
// 循环生成6张随机国外壁纸图片链接(100%可访问)
for _ in 0..<6 {
let url = URL(string: "https://picsum.photos/800/1200")!
list.append(RandomImage(imageUrl: url))
}
// 主线程更新UI @MainActor
await MainActor.run {
imageList = list
tip = "加载成功"
}
}
}
#Preview {
ContentView()
}

- 主线程 :负责显示 UI、按钮、图片(不能卡住)
- 后台线程 :负责网络、耗时操作(不卡界面)
- async / await / Task :
- Task = 开一个 "后台任务"
- async = 这个函数放后台跑
- await = 等它跑完,不卡界面
按运行顺序讲整个 APP 的逻辑
第 1 步:APP 启动 → 显示界面
Swift
NavigationStack {
VStack {
Text("🖼️ 随机壁纸")
List(...)
Button(...)
}
}
这一步跑在:主线程(UI 线程) 界面瞬间画出来 此时列表是空的,因为 wallpapers 还是空数组 []
第 2 步:界面出现 → 触发 .onAppear
Swift
.onAppear {
Task {
await loadWallpapers()
}
}
这里发生了什么?
onAppear是 "页面显示完了" 的通知- Task { ... } = 开一个后台任务
- 这个任务会去执行:await loadWallpapers()
第 3 步:进入并发函数 loadWallpapers()
Swift
private func loadWallpapers() async {
var list: [Wallpaper] = []
for _ in 0..<6 {
let url = URL(string: "https://picsum.photos/800/1200")!
list.append(Wallpaper(imageUrl: url))
}
await MainActor.run {
wallpapers = list
}
}
重点来了:
这个函数前面写了 async → 它会自动跑在后台线程!
里面做了啥?
- 造一个空数组
- 循环 6 次,生成 6 个图片网址
- 全部都在后台跑,完全不卡 UI!
第 4 步:最关键一句 → await MainActor.run
Swift
await MainActor.run {
wallpapers = list
}
这句话的意思:
我现在在后台线程,我要回到主线程去更新 UI!
为什么必须回去?
因为:修改 @State 变量 → 必须在主线程否则会崩溃、乱码、不刷新。
第 5 步:回到主线程 → 更新数据
Swift
wallpapers = list
数据一变SwiftUI 自动刷新界面列表立刻显示 6 张图片!
第 6 步:你点按钮 → 再次刷新
Swift
Button("刷新壁纸") {
Task {
await loadWallpapers()
}
}
和 onAppear 逻辑一模一样:
- 点按钮
- 开 Task
- 进后台跑
loadWallpapers - 造新的图片链接
- 回到主线程赋值
- UI 刷新
自己搓了一遍
Swift
import SwiftUI
struct Wallpaper: Identifiable {
let id = UUID()
let imageUrl: URL
}
struct ContentView: View {
@State private var wallpapers: [Wallpaper] = []
var body: some View {
NavigationStack {
VStack(spacing: 20) {
Text("🖼️ 随机壁纸")
.font(.largeTitle.bold())
List(wallpapers) { wp in
AsyncImage(url: wp.imageUrl) { image in
image
.resizable()
.scaledToFill()
.frame(height: 250)
.clipped()
} placeholder: {
ProgressView()
}
}
Button("刷新壁纸") {
Task {
await loadWallpapers()
}
}
.buttonStyle(.borderedProminent)
}
.padding()
.navigationTitle("壁纸库")
.onAppear {
Task {
await loadWallpapers()
}
}
}
}
private func loadWallpapers() async {
var list: [Wallpaper] = []
for _ in 0..<6 {
let url = URL(string: "https://picsum.photos/800/1200")!//调的国外网站
list.append(Wallpaper(imageUrl: url))
}
await MainActor.run {
wallpapers = list
}
}
}
#Preview {
ContentView()
}
实现个人笔记app
Swift
import SwiftUI
// 1. 数据模型
struct Note: Identifiable, Codable {
let id = UUID()
var title: String
var content: String
let createTime: Date
}
// 2. 持久化管理
class NoteStorage: ObservableObject {
@Published var notes: [Note] = []
private let saveKey = "SavedNotes"
init() {
loadNotes()
}
func loadNotes() {
if let data = UserDefaults.standard.data(forKey: saveKey) {
if let decoded = try? JSONDecoder().decode([Note].self, from: data) {
notes = decoded
return
}
}
notes = []
}
func addNote(title: String, content: String) {
let newNote = Note(title: title, content: content, createTime: Date())
notes.insert(newNote, at: 0)
saveNotes()
}
func deleteNote(at offsets: IndexSet) {
notes.remove(atOffsets: offsets)
saveNotes()
}
private func saveNotes() {
if let encoded = try? JSONEncoder().encode(notes) {
UserDefaults.standard.set(encoded, forKey: saveKey)
}
}
}
// 3. 主页面
struct ContentView: View {
@StateObject private var storage = NoteStorage()
@State private var newTitle = ""
@State private var newContent = ""
var body: some View {
NavigationStack {
VStack(spacing: 0) {
// Form 表单
Form {
Section("添加新笔记") {
TextField("标题", text: $newTitle)
TextEditor(text: $newContent)
.frame(height: 120)
Button("保存笔记") {
if !newTitle.isEmpty {
storage.addNote(title: newTitle, content: newContent)
newTitle = ""
newContent = ""
}
}
.disabled(newTitle.isEmpty)
}
}
.frame(height: 250)
// 笔记列表
List {
ForEach(storage.notes) { note in
NavigationLink {
NoteDetailView(note: note)
} label: {
VStack(alignment: .leading) {
Text(note.title)
.font(.headline)
Text(note.content)
.font(.caption)
.foregroundColor(.gray)
.lineLimit(1)
}
}
}
.onDelete(perform: storage.deleteNote)
}
}
.navigationTitle("个人笔记")
.toolbar {
EditButton()
}
}
}
}
// 4. 详情页
struct NoteDetailView: View {
let note: Note
var body: some View {
ScrollView {
VStack(alignment: .leading, spacing: 20) {
Text(note.title)
.font(.title.bold())
Text(note.content)
.font(.body)
}
.padding()
}
.navigationTitle("笔记详情")
}
}
#Preview {
ContentView()
}
总目标
做一个能保存内容的笔记 App,有这 3 个核心点:
- Form:做添加笔记的表单
- TextEditor:写长文
- UserDefaults:持久化(重启 App 还在)
第 0 步:先搭页面骨架
先写一个空页面,有标题,有导航。
Swift
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationStack {
VStack {
Text("个人笔记")
}
.navigationTitle("我的笔记")
}
}
}
#Preview {
ContentView()
}
👉 现在你有了一个空的导航页面。
第 1 步:造数据模型
笔记需要有:id、标题、内容、时间。我们让它遵守 Identifiable (方便列表) 和 Codable (方便持久化)。
Swift
// 写在 ContentView 外面
struct Note: Identifiable, Codable {
let id = UUID()
var title: String
var content: String
let createTime: Date
}
👉 这代表一条笔记的数据结构。
第 2 步:造持久化管理(核心!)
这是让笔记 "不掉" 的关键。我们用 UserDefaults。新建一个管理类,它负责增、删、查、存。
Swift
// 写在 ContentView 外面
class NoteStorage: ObservableObject {
// 发布笔记数组,数据变了就刷新UI
@Published var notes: [Note] = []
let saveKey = "SavedNotes"
init() {
// 一初始化就加载本地数据
loadNotes()
}
// 1. 加载数据
func loadNotes() {
if let data = UserDefaults.standard.data(forKey: saveKey) {
if let decoded = try? JSONDecoder().decode([Note].self, from: data) {
notes = decoded
return
}
}
// 如果没数据,就是空数组
notes = []
}
// 2. 保存数据
private func saveNotes() {
if let encoded = try? JSONEncoder().encode(notes) {
UserDefaults.standard.set(encoded, forKey: saveKey)
}
}
// 3. 添加笔记
func addNote(title: String, content: String) {
let newNote = Note(title: title, content: content, createTime: Date())
notes.insert(newNote, at: 0) // 加到最前面
saveNotes() // 保存
}
// 4. 删除笔记
func deleteNote(at offsets: IndexSet) {
notes.remove(atOffsets: offsets)
saveNotes() // 保存
}
}
👉 到这里,我们有了数据存储的逻辑。
第 1 块:创建类 + 声明数组
Swift
class NoteStorage: ObservableObject {
@Published var notes: [Note] = []
let saveKey = "SavedNotes"
}
意思:
ObservableObject:让界面能感知数据变化@Published:一改动就刷新界面notes:存所有笔记saveKey:存本地的 "钥匙"
@State 只能管自己页面里的数据 @Published 是给 "外面的管理类" 用的,让它也能刷新界面!
- @State 是啥?
- 放在 View 里面
- 管自己页面的小变量
- 比如:开关、输入框、数字、颜色
- 只能自己用,不能共享给别的地方
Swift
struct ContentView: View {
@State private var name = "" // 只能在这个View里用
}
- @Published 是啥?
- 放在 ObservableObject 类里面 (比如你的
NoteStorage) - 管跨页面共享的数据(笔记、列表、用户信息...)
- 一变,所有用到它的页面都会自动刷新!
Swift
class NoteStorage: ObservableObject {
@Published var notes: [Note] = [] // 共享数据!
}
- 为什么你的笔记项目必须用 @Published?
因为:你的笔记数据,不能写在 View 里面
- 增、删、改、存本地
- 这些是业务逻辑
- 不能堆在页面里
所以专门建了一个类 NoteStorage 来管理笔记
第 2 块:一启动就加载本地数据
Swift
init() {
loadNotes()
}
意思:
APP 一打开,就自动读本地笔记
第 3 块:加载函数(读数据)
Swift
func loadNotes() {
if let data = UserDefaults.standard.data(forKey: saveKey) {
if let decoded = try? JSONDecoder().decode([Note].self, from: data) {
notes = decoded
return
}
}
notes = []
}
我先给你整句翻译
尝试把本地取出来的二进制 data,重新还原成笔记数组 [Note]。 如果成功了,就把还原出来的数组赋值给 notes,显示到界面上。
我拆成 5 小块,一块一块讲
JSONDecoder()
JSON 解码器 作用:把Data(二进制) 变回 Swift 对象(Note) 就是:数据 → 笔记
.decode( [Note].self, from: data )
这句是核心:
[Note].self= 我要转成 **"笔记数组"**from: data= 从哪个二进制数据转
翻译:把 data 还原成 [Note] 数组
try?
try= 尝试执行(可能失败)?= 失败了不崩溃,返回 nil
翻译:尽量去转,转失败就算了,别崩
if let decoded = ...
if let= 成功解包- 如果解码成功,就把结果放进
decoded
翻译:如果转换成功了......
notes = decoded
把解析成功的笔记数组,赋值给 UI 要用的数组→ 列表马上显示笔记
Swift
// 我试试把存进去的二进制数据,变回笔记数组
if let 还原出来的笔记们 = 尝试? 解码器.解码(目标: [Note], 来源: 数据) {
// 如果成功了!
界面显示的笔记数组 = 还原出来的笔记们
}
为什么要写这么复杂?
因为:
- 本地存的是 Data(二进制)
- 界面要用 [Note](模型数组)
- 中间必须 Codable + JSONDecoder 转换
整个流程
- 写笔记
JSONEncoder编码 → DataUserDefaults存起来- 重启 App
- 取出 Data
JSONDecoder().decode([Note].self, from: data)- 变回
[Note] - 显示在列表
你现在卡的这句代码,我再浓缩成 1 句:
把本地存的二进制,重新变回笔记数组,并安全赋值给界面使用。
意思:
- 从本地取数据
- 把二进制 → 转回笔记数组
- 成功就赋值给界面显示
- 失败就显示空数组
第 4 块:保存函数(写数据)
Swift
private func saveNotes() {
if let encoded = try? JSONEncoder().encode(notes) {
UserDefaults.standard.set(encoded, forKey: saveKey)
}
}
理解 UserDefaults
**UserDefaults = 手机本地的【简易小仓库】**专门存:设置、笔记、配置、偏好、简单数据特点:
- 关闭 APP 不丢
- 重启手机还在
- 卸载 APP 才消失
- 超级简单,不用数据库
它就是苹果官方原生、新手必学持久化方案。
2. UserDefaults 能存什么?
只能存简单基础类型 :String、Int、Bool、Double、Data、Array、Dictionary👉 不能直接存你自己写的 Note 结构体!!
这就是为什么我们要 Codable!
你的Note结构体 → Codable转成Data → UserDefaults存Data
读取时:UserDefaults取出Data → Codable转回Note
意思:
- 把笔记 → 转成二进制
- 存进本地
- 退出 APP 还在
- encoded:转好的二进制 Data
- forKey:钥匙 / 名字,以后靠这个钥匙取出来
第 5 块:添加笔记 + 删除笔记
Swift
func addNote(title: String, content: String) {
let newNote = Note(title: title, content: content, createTime: Date())
notes.insert(newNote, at: 0)
saveNotes()
}
func deleteNote(at offsets: IndexSet) {
notes.remove(atOffsets: offsets)
saveNotes()
}
意思:
- 加笔记 → 存本地
- 删笔记 → 存本地
第 3 步:在页面里使用持久化
现在,在主页面里,我们需要拿到这个存储对象。
Swift
struct ContentView: View {
// 拿到持久化管理器
@StateObject private var storage = NoteStorage()
// 表单输入的临时变量
@State private var newTitle = ""
@State private var newContent = ""
var body: some View {
NavigationStack {
VStack(spacing: 0) {
// 这里后面加 Form
// 这里后面加 List
}
.navigationTitle("个人笔记")
}
}
}
👉 现在准备好了输入和存储。
第 4 步:实现 Form 表单(你要的重点)
我们用 Form 来做输入区域。
Swift
// 放在 VStack 里
Form {
Section("添加新笔记") {
TextField("请输入标题", text: $newTitle)
// 多行文本编辑
TextEditor(text: $newContent)
.frame(height: 120) // 设置高度
Button("保存") {
if !newTitle.isEmpty {
storage.addNote(title: newTitle, content: newContent)
// 保存后清空输入框
newTitle = ""
newContent = ""
}
}
.disabled(newTitle.isEmpty) // 标题空着不能点
}
}
.frame(height: 250) // 限制Form高度
👉 现在你有了能输入内容的表单。
第 5 步:实现笔记列表
把保存的笔记展示出来。
Swift
// 放在 Form 下面
List {
ForEach(storage.notes) { note in
NavigationLink {
// 跳转到详情页
NoteDetailView(note: note)
} label: {
VStack(alignment: .leading) {
Text(note.title)
.font(.headline)
Text(note.content)
.font(.caption)
.foregroundColor(.gray)
.lineLimit(1)
}
}
}
.onDelete(perform: storage.deleteNote)
}
👉 现在你有了能展示所有笔记的列表,并且能左滑删除。
第 6 步:实现详情页
点进笔记看完整内容。
Swift
struct NoteDetailView: View {
let note: Note
var body: some View {
ScrollView {
VStack(alignment: .leading, spacing: 20) {
Text(note.title)
.font(.title.bold())
Text(note.content)
.font(.body)
}
.padding()
}
.navigationTitle("笔记详情")
}
}
