这篇文章希望记录自学苹果软件编程的全过程,写作的过程是希望实现第一个小目标上线一款个人ios应用,并实现个人创作价值的变现,赢得用户的积极反馈。
A 自学重要站点
Apple Developer

SwiftUI Pathway
SwiftUI Pathway - Apple Developer
B 自学课程
- Swift Programming Tutorial for Beginners (Full Tutorial)
https://www.youtube.com/watch?v=Ulp1Kimblg0&t=11400s
- 斯坦福CS193P 2021春季SwiftUI 2.0课程
【完结·双语字幕】斯坦福CS193P 2021春季SwiftUI 2.0课程_哔哩哔哩_bilibili
- SwiftUI Fundamentals | FULL COURSE | Beginner Friendly
https://www.youtube.com/watch?v=b1oC7sLIgpI
C 自学笔记
下面是自学的相关笔记和一些学习体会,以备查漏补缺。
1. Swift初学者编程入门
【课程简介和自学体会】这个基础课程第一次学完的时间是2023年的1月份,是一个Chris为自己的Swift编程社区引流的0基础入门课程,内容比较基础和简单,时长3个半小时左右,比较系统搭建起了SwiftUI基础语法内容,但如果继续实战App编写的话需要去到对应官网解锁完整课程和社区讨论功能,月度订阅40刀,年度订阅400刀左右,一次性买断收费1499美刀,瞬间自学的驱动力又增加了呢:)

【原作者的课程介绍】Learn how to code with Swift in one single tutorial! We'll go through all the core concepts of Swift including: Variables and Constants, Data Types, Properties, Functions and Parameters, Classes, IF Statements And a lot more!
This Swift course also comes with notes, cheatsheets, exercises and solutions which you can download here:
Swift Tutorial Resources : CodeWithChris
🚨 SPECIAL CWC+ OFFER FOR YOUTUBE:
CWC+ Welcome Offer : CodeWithChris
𝗩𝗶𝗱𝗲𝗼 𝗧𝗶𝗺𝗲s𝘁𝗮𝗺𝗽𝘀:
00:00 Variables
15:13 Data Type
22:23 If Statements
38:01 Switch Statements
44:26 Loops (I)
54:48 Loops (II)
01:01:02 Functions (I)
01:10:39 Functions (II)
01:27:16 Classes (I)
01:38:36 Classes (II)
01:50:58 UIKit
01:57:26 Initializers (I)
02:05:41 Optionals
02:20:48 Properties
02:37:44 Initializers (II)
02:44:41 Arrays
02:58:22 Dictionaries
03:09:50 Outro
03:10:14 End of Stream (Nothing to see)
MY FREE ONLINE COURSE:
⚡ How to make an app in 14 days - https://cwc.to/14days
CWC+ PROGRAM:
👩💻 All our courses in a learning path - https://cwc.to/plus
WEEKLY UPDATES VIA EMAIL:
✉️ Every Saturday, receive an email digest of new content - https://cwc.to/newsletter
CONNECT:
🌍 Website - https://codewithchris.com
😺 GitHub - https://github.com/codewithchris
📸 Instagram - / codewithchris
🐦 Twitter - / codewithchris
ABOUT CODEWITHCHRIS:
Hi I'm Chris! I'm dedicated to teaching fundamentals about how to make an app. This is important if you're trying to land an iOS job, be a freelancer, increase or start a business with an app idea. On this channel and my website, you'll find a ton of free resources and tutorials to aid you on your journey to learn iOS development. Many people have learned to code and build apps on their own! https://cwc.to/reviews
课后作业:
Introduction to Learn Swift coding challenges!
Ready to start honing those swift skills you just learned? Then look no further as these extra challenges will not only help you practice but also help you recap on what you have learned so far.
So get those brain juices flowing and get those hands busy typing because it's challenge time!
For the full playlist please use this link:
For the current updated solutions please use this link:
http://bit.ly/LearnSwiftChallengesSolution
If you don't have a mac or are having problems with XCode you can use this online playground:
=============================
- Swift Programming Tutorial for Beginners(Full Tutorial)
Swift Programming Tutorial for Beginners(Full Tutorial)_swift for beginners pdf-CSDN博客
- Swift Programming Tutorial for Beginners-14day Tutorial
Swift Programming Tutorial for Beginners-14day Tutorial_ios 14 programming for beginners: get-CSDN博客
- Swift Programming-Learning day3
Swift Programming-Learning day3-CSDN博客
- Swift Programming-Note 04
Swift Programming-Note 04_swift .padding(.horizontal)-CSDN博客
- Swift Programming-Note 05-MVVM
Swift Programming-Note 05-MVVM-CSDN博客
- Swift Programming-Note 06
https://blog.csdn.net/weixin_43464653/article/details/122904803?spm=1011.2415.3001.5331
以上笔记和内容记录于2022年年初。
- Swift 项目实践-美食护照
以下项目是本人根据个人的生活习惯,结合真实使用场景的需求研发,并且本着学以致用和用我所学的原则,玩成了大作业美食护照。
课程内容Cheetsheet

2. 斯坦福CS193P 2021春季SwiftUI 2.0课程
作为一个大学期间只学过C语言的理工科学生,我觉得这个视频的难度对我来说比较大[笑哭]可以理解代码但是组合起来就比较困难了。从寒假到现在自学两三个月了,我的建议是首先完成Swift Playground官方教程,然后去Exercism上练习,结合油管上Sean Allen的教程可以更好地学习swiftui,最后再来看这个教程可能会轻松不少。(From ZhenghangWu Bilibili)
3. SwiftUI Fundamentals
【自学体会】这个Sean Allen的课程是更注重实践的具体细节,时长12个小时,共需要做4个APP,第一个是一个天气APP,主要学习ios开发的基本视图排列方式和建立不同属性设置顺序和参数的使用情况。学完之后想学习页面间的切换和信息获取和展示方式。
第二个APP是一个工具箱APP,把开发者常用的UIKit放到一个软件内部做索引,方便开发时学习和使用。
第三个APP时一个扫码器,用来真刀真枪学习API的查阅和调用方法。
最后一个APP是一个电子开胃菜菜谱,介绍和展示美食APP,用于餐馆点餐服务等场景。
【原文课程简介】
Get your start in SwiftUI with SwiftUI Fundamentals. In this 12 hour course we build 4 apps that ramp up in difficulty. The first 3 apps are quite simple, but the 4th and final app grows substantialy in size and challenge. See the timestamps at the bottom of the description for the full curriculum.
The course has been updated for iOS 15 and iOS 16.
If you want a structured viewing experience with a comment section on each individual video, this course is also available (for free) at https://seanallen.teachable.com.
App 1 Weather:
We learn the absolute basics of SwiftUI to build the user interface of a weather app. If you have experience with SwiftUI, feel free to skip this one as it's very beginner friendly.
Weather Completed Source Code (Updated for iOS 16):
https://www.dropbox.com/scl/fo/sh092hs8fnwxwa169kkre/h?rlkey=5l9h82p95con0bu9usc2s1wg3&e=1&dl=0
App 2 - Apple Frameworks:
We learn to build a Grid and pass data from our main view to a detail view.
Framework icon images:
https://www.dropbox.com/scl/fo/lijj23qlv9eiblta13z61/h?rlkey=79h2tiegl2ysood23qcvfxo85&e=1&dl=0
Apple Frameworks Completed Source Code (Updated for iOS 16):
https://www.dropbox.com/scl/fo/5zvhvulzemu74sit9qn96/h?rlkey=pc8nycuc6eejy8rb8j9szfvhm&e=1&dl=0
App 3 - Barcode Scanner:
We learn to use UIViewRepresentable to connect SwiftUI and UIKit in case SwiftUI doesn't support a type of user interface you want.
Barcode Scanner Completed Source Code:
https://www.dropbox.com/scl/fo/iy2ymc4jcrz7vfh2wqxzk/h?rlkey=8055hc325k07rji6hapgifcw1&e=1&dl=0
App 4 - Appetizers:
This app is a big one where we put it all our learnings together to build a proper SwiftUI app that works with a network call (updated to Async/Await in iOS 15).
Appetizers Completed Source Code (updated for iOS 16):
https://www.dropbox.com/scl/fo/0h5kub6len9jkghiwj6gz/h?rlkey=uf04zyhvww722jksfrech2up4&e=1&dl=0
What's New in Xcode 15:
• Xcode 15 - What's New
What's New in Xcode 14:
• What's New in Xcode 14
What's New in Xcode 13:
• What's New in Xcode 13
What's New in SwiftUI WWDC 2021:
What's new in SwiftUI - WWDC21 - Videos - Apple Developer
What's New in SwiftUI WWDC 2022:
What's new in SwiftUI - WWDC22 - Videos - Apple Developer
What's New in SwiftUI WWDC 2023:
What's new in SwiftUI - WWDC23 - Videos - Apple Developer
Twitter:
Sean Allen - / seanallen_dev
Book and learning recommendations that help out the channel if you decide to purchase (Affiliate Links):
Paul Hudson's Hacking With Swift:
https://gumroad.com/a/762098803


Donny Wals - Combine:
https://gumroad.com/a/909014131

Mark Moeyken's SwiftUI Books:
www.bigmountainstudio.com/swiftui-views-book/fzc51

Objc.io Books (Thinking in SwiftUI & Advanced Swift):
https://gumroad.com/a/656585843
#swift #swiftui #iosdeveloper
App 1 - Weather APP
Timestamps:
0:00 - What you will learn & updates
6:30 - SwiftUI Basics - Weather App 大致介绍了Hstack Vstack和Zstack(层叠布局,后面在上,前面在下)的区别。

Swift
//
// ContentView.swift
// SwiftUI-Weather
//
// Created by Dominic on 2025/3/24.
//
import SwiftUI
struct ContentView: View {
var body: some View {
ZStack {
Color(.blue)
LinearGradient(gradient: Gradient(colors: [.blue, .red, .green, .white]), startPoint: .topLeading, endPoint: .bottomTrailing)
.edgesIgnoringSafeArea(.all)
}
.padding()
}
}
#Preview {
ContentView()
}
去除paddings的效果

颜色梯度参数修改后效果

19:01 - Weather - Text 主要介绍怎么添加文本信息以及可以调整的参数,有font foregroundColor以及padding,frame,spacer等

Swift
//
// ContentView.swift
// SwiftUI-Weather
//
// Created by Dominic on 2025/3/24.
//
import SwiftUI
struct ContentView: View {
var body: some View {
ZStack {
Color(.blue)
LinearGradient(gradient: Gradient(colors: [.blue, .white]),
startPoint: .topLeading,
endPoint: .bottomTrailing)
.edgesIgnoringSafeArea(.all)
VStack{
Text("Cupertino, CA")
.font(.system(size: 32, weight: .medium, design: .default))
.foregroundColor(.white)
.padding()
Spacer()
}
}
}
}
#Preview {
ContentView()
}
25:54 - Weather - SF Symbols

29:13 - Weather - Main VStack 基础的icon调用方法和Imang以及Text的可用参数介绍。

Swift
//
// ContentView.swift
// SwiftUI-Weather
//
// Created by Dominic on 2025/3/24.
//
import SwiftUI
struct ContentView: View {
var body: some View {
ZStack {
Color(.blue)
LinearGradient(gradient: Gradient(colors: [.blue, .white]),
startPoint: .topLeading,
endPoint: .bottomTrailing)
.edgesIgnoringSafeArea(.all)
VStack{
Text("Cupertino, CA")
.font(.system(size: 32, weight: .medium, design: .default))
.foregroundColor(.white)
.padding()
VStack(spacing: 10){
Image(systemName: "cloud.sun.fill")
//Image(systemName: "sun.max.fill")
.renderingMode(.original)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 180, height: 150)
Text("76°")
.font(.system(size: 70, weight: .medium))
.foregroundColor(.white)
}
Spacer()
}
}
}
}
#Preview {
ContentView()
}
36:41 - Weather - HStack of Days 提取出来成一个函数视图,通过三个参数来控制视图展示信息

50:08 - Weather - Button & Refactoring
- 定义全局的lightblue颜色变量
- 一个页面里面的view不能太多,超过11个xcode会compline
- 把功能相似或者模块功能拆封单独的View视图中
- 按钮🔘提取到一个单独的WeatherButton.swift文件中。

Swift
//
// ContentView.swift
// SwiftUI-Weather
//
// Created by Dominic on 2025/3/24.
//
import SwiftUI
struct ContentView: View {
var body: some View {
ZStack{
Color(.blue)
BackgroundView(topColor: .blue, bottomColor: .white)
VStack(spacing:30){
CityExtView(cityName: "Capertino, CA")
MainWeatherStatusView(imageName: "cloud.sun.fill", temperature: 73)
// HStack {
// VStack(spacing:5){
// Text("TUE")
// .font(.system(size: 18))
// .foregroundColor(.white)
// Image(systemName: "cloud.sun.fill")
// .renderingMode(.original)
// .resizable()
// .aspectRatio(contentMode: .fit)
// .frame(width: 50, height: 50)
// Text("74°")
// .font(.system(size: 18))
// .foregroundColor(.white)
// }
// VStack(spacing:5){
// Text("TUE")
// .font(.system(size: 18))
// .foregroundColor(.white)
// Image(systemName: "sun.max.fill")
// .renderingMode(.original)
// .resizable()
// .aspectRatio(contentMode: .fit)
// .frame(width: 50, height: 50)
// Text("74°")
// .font(.system(size: 18))
// .foregroundColor(.white)
// }
// VStack(spacing:5){
// Text("TUE")
// .font(.system(size: 18))
// .foregroundColor(.white)
// Image(systemName: "wind")
// .renderingMode(.original)
// .resizable()
// .aspectRatio(contentMode: .fit)
// .frame(width: 50, height: 50)
// Text("74°")
// .font(.system(size: 18))
// .foregroundColor(.white)
// }
// VStack(spacing:5){
// Text("TUE")
// .font(.system(size: 18))
// .foregroundColor(.white)
// Image(systemName: "sunset")
// .renderingMode(.original)
// .resizable()
// .aspectRatio(contentMode: .fit)
// .frame(width: 50, height: 50)
// Text("74°")
// .font(.system(size: 18))
// .foregroundColor(.white)
// }
// VStack(spacing:2){
// Text("TUE")
// .font(.system(size: 18))
// .foregroundColor(.white)
// Image(systemName: "moon.stars.fill")
// .renderingMode(.original)
// .resizable()
// .aspectRatio(contentMode: .fit)
// .frame(width: 50, height: 50)
// Text("74°")
// .font(.system(size: 18))
// .foregroundColor(.white)
// }
// }
HStack(spacing: 20) {
WeatherDayView(dayOfWeek: "TUE", imageName: "snow", temperature: 74)
WeatherDayView(dayOfWeek: "WED", imageName: "cloud.fill", temperature: 74)
WeatherDayView(dayOfWeek: "THU", imageName: "sun.max.fill", temperature: 74)
WeatherDayView(dayOfWeek: "FRI", imageName: "sunset.fill", temperature: 74)
WeatherDayView(dayOfWeek: "SAT", imageName: "moon.stars.fill", temperature: 74)
}
Spacer()
Button {
print("tapped")
} label: {
Text("Change Day Time")
.frame(width: 280, height: 50)
.background(Color.white)
.font(.system(size: 20, weight: .bold, design: .default))
.cornerRadius(10)
}
Spacer()
}
}
}
}
#Preview {
ContentView()
}
struct WeatherDayView: View {
var dayOfWeek: String
var imageName: String
var temperature: Int
var body: some View {
VStack(spacing:5) {
Text(dayOfWeek)
.font(.system(size: 18, weight: .medium, design: .default))
.foregroundColor(.white)
Image(systemName: imageName)
.renderingMode(.original)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 50, height: 50)
Text("\(temperature)°")
.font(.system(size: 18))
.foregroundColor(.white)
}
}
}
struct BackgroundView: View {
var topColor: Color
var bottomColor: Color
var body: some View {
LinearGradient(gradient: Gradient(colors: [topColor, bottomColor]),
startPoint: .topLeading,
endPoint: .bottomTrailing)
.edgesIgnoringSafeArea(.all)
}
}
struct CityExtView: View {
var cityName: String
var body: some View {
Text(cityName)
.font(.system(size: 32, weight: .medium, design: .default))
.foregroundColor(.white)
.padding()
}
}
struct MainWeatherStatusView: View {
var imageName: String
var temperature: Int
var body: some View {
VStack(spacing: 10){
Image(systemName: imageName)
//Image(systemName: "sun.max.fill")
.renderingMode(.original)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 180, height: 150)
Text("\(temperature)°")
.font(.system(size: 70, weight: .medium))
.foregroundColor(.white)
}
.padding(20)
}
}
Swift
//
// WeatherButton.swift
// SwiftUI-Weather
//
// Created by Dominic on 2025/3/24.
//
import SwiftUI
struct WeatherButton: View{
var title: String
var textColor: Color
var backgroundColor: Color
var body: some View {
Text(title)
.frame(width: 280, height: 50)
.background(backgroundColor)
.foregroundColor(textColor)
.font(.system(size: 20, weight: .bold, design: .default))
.cornerRadius(10)
}
}

在Assets中添加自定义的颜色集合 lightBlue

1:07:35 - Weather - @State & @Binding Basics
- @State
- @Binding
- @StateObject
- @ObservedObject
- @EnvironmentObject

这节主要介绍两个比较重要的数据传递方式,通过@State和@Binding两种方式,第一种state的逻辑比较简单,就是在View视图内用State标记一个变量,通过变量值的切换来传递不同的页面展示内容,相当于一个状态开关。
binding是在子视图中可以绑定父视图中State标记的变量,通过将另外子视图的样式或者内容用父级的开关来一并控制,大致逻辑应该是这样的。
Swift
//
// ContentView.swift
// SwiftUI-Weather
//
// Created by Dominic on 2025/3/24.
//
import SwiftUI
struct ContentView: View {
@State private var isNight = false
var body: some View {
ZStack{
Color(.blue)
BackgroundView(isNight: $isNight)
VStack(spacing:30){
CityExtView(cityName: "Capertino, CA")
MainWeatherStatusView(imageName: "cloud.sun.fill", temperature: 73)
HStack(spacing: 20) {
WeatherDayView(dayOfWeek: "TUE", imageName: "snow", temperature: 74)
WeatherDayView(dayOfWeek: "WED", imageName: "cloud.fill", temperature: 74)
WeatherDayView(dayOfWeek: "THU", imageName: "sun.max.fill", temperature: 74)
WeatherDayView(dayOfWeek: "FRI", imageName: "sunset.fill", temperature: 74)
WeatherDayView(dayOfWeek: "SAT", imageName: "moon.stars.fill", temperature: 74)
}
Spacer()
Button {
isNight.toggle()
// print("tapped")
} label: {
Text("Change Day Time")
.frame(width: 280, height: 50)
.background(Color.white)
.font(.system(size: 20, weight: .bold, design: .default))
.cornerRadius(10)
}
Spacer()
}
}
}
}
#Preview {
ContentView()
}
struct WeatherDayView: View {
var dayOfWeek: String
var imageName: String
var temperature: Int
var body: some View {
VStack(spacing:5) {
Text(dayOfWeek)
.font(.system(size: 18, weight: .medium, design: .default))
.foregroundColor(.white)
Image(systemName: imageName)
.renderingMode(.original)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 50, height: 50)
Text("\(temperature)°")
.font(.system(size: 18))
.foregroundColor(.white)
}
}
}
struct BackgroundView: View {
// var topColor: Color
// var bottomColor: Color
//
// var body: some View {
//
// LinearGradient(gradient: Gradient(colors: [topColor, bottomColor]),
// startPoint: .topLeading,
// endPoint: .bottomTrailing)
// .edgesIgnoringSafeArea(.all)
// }
// version 2
@Binding var isNight: Bool
var body: some View {
LinearGradient(gradient: Gradient(colors: [isNight ? .black : .blue, isNight ? .gray : Color("lightBlue")]), startPoint: .topLeading, endPoint: .bottomTrailing)
.edgesIgnoringSafeArea(.all)
}
}
struct CityExtView: View {
var cityName: String
var body: some View {
Text(cityName)
.font(.system(size: 32, weight: .medium, design: .default))
.foregroundColor(.white)
.padding()
}
}
struct MainWeatherStatusView: View {
var imageName: String
var temperature: Int
var body: some View {
VStack(spacing: 10){
Image(systemName: imageName)
//Image(systemName: "sun.max.fill")
.renderingMode(.original)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 180, height: 150)
Text("\(temperature)°")
.font(.system(size: 70, weight: .medium))
.foregroundColor(.white)
}
.padding(20)
}
}
这节课结束代表第一个天气App的完成,有三个挑战作业:
-
将展示之后5天天气的视图参数通过数组的方式便利传递,而不是这样傻傻的写五遍。
-
从真实的天气API接口上面调用数据用来显示天气。
-
用TabBar功能来展示多个城市的天气。
解答:
- 这个作业其实可以通过一个固定struct的数组来存储天气展示的三个数据:星期缩写,天气情况的icon图名字符串和温度。
Swift
import SwiftUI
struct WeatherDay {
let dayOfWeek: String
let imageName: String
let temperature: Int
}
struct WeatherDayView: View {
let dayOfWeek: String
let imageName: String
let temperature: Int
var body: some View {
VStack {
Text(dayOfWeek)
.font(.system(size: 16, weight: .medium, design: .default))
.foregroundColor(.white)
Image(systemName: imageName)
.renderingMode(.original)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 40, height: 40)
Text("\(temperature)°")
.font(.system(size: 30, weight: .medium, design: .default))
.foregroundColor(.white)
}
}
}
struct ContentView: View {
let weatherDays: [WeatherDay] = [
WeatherDay(dayOfWeek: "TUE", imageName: "snow", temperature: 74),
WeatherDay(dayOfWeek: "WED", imageName: "cloud.fill", temperature: 74),
WeatherDay(dayOfWeek: "THU", imageName: "sun.max.fill", temperature: 74),
WeatherDay(dayOfWeek: "FRI", imageName: "sunset.fill", temperature: 74),
WeatherDay(dayOfWeek: "SAT", imageName: "moon.stars.fill", temperature: 74)
]
var body: some View {
HStack(spacing: 20) {
ForEach(weatherDays, id: \.dayOfWeek) { day in
WeatherDayView(dayOfWeek: day.dayOfWeek, imageName: day.imageName, temperature: day.temperature)
}
}
}
}
- 针对问题2,在实现中遇到了如下问题

发现调用weatherKit是要开通开发者订阅服务。应该是用于像SF symbols的研发,设计成本等等。
先标记为卡点 !!
1:18:03 - Weather App - iOS 15 & 16 Updates
需要注意WWDC每年六月到九月份的更新,因为一些开发者工具可能更新了版本和使用,调用方法,比如在ios16中SF symbols中添加了Enhanced color customization, 核心是一个叫做Palette rendering的东西可以让开发者更准确,绚丽,优雅的使用icon图标。




1:31:52 - How SwiftUI Works - Fundamental Concepts

1:31:52 - How SwiftUI Works - Fundamental Concepts

@ViewBuilder
UIButton

App 2 - Apple Frameworks
2:09:08 - Frameworks - Grid Start

2:20:35 - Frameworks - Grid End

Swift
//
// FrameworkGridView.swift
// Apple-Frameworks
//
// Created by Dominic on 2025/3/25.
//
import SwiftUI
struct FrameworkGridView: View {
@StateObject var viewModel = FrameworkGridViewModel()
var body: some View {
NavigationStack {
ScrollView {
LazyVGrid(columns: viewModel.columns) {
ForEach(MockData.frameworks) { framework in
NavigationLink(value: framework) {
FrameworkTitleView(framework: framework)
.onTapGesture {
print("Tapped")
}
}
}
}
}
.navigationTitle("🍎 Frameworks")
.navigationDestination(for: Framework.self) { framework in
FrameworkDetailView(framework: framework)
}
}
}
}
struct FrameworkGridView_Preview: PreviewProvider {
static var previews: some View {
FrameworkGridView()
.preferredColorScheme(.dark)
}
}
struct FrameworkTitleView: View {
// let name: String
// let imageName: String
let framework: Framework
var body: some View {
VStack {
Image(framework.imageName)
.resizable()
.frame(width: 90, height: 90)
Text(framework.name)
.font(.title2)
.fontWeight(.semibold)
.foregroundColor(Color(.label))
.scaledToFit()
.minimumScaleFactor(0.6)
}
// .padding()
}
}
2:32:34 - Frameworks - Detail View 这个项目设计真心觉得非常棒,把开发者需要用到的工具箱进行展示,顺便其实告诉了开发者我们有哪些工具可以服务于你的创意。简单把玩了一下:

App Clips是一款让用户可以快速了解开发者应用的效果和作用的工具箱,相当于最核心的玩法片段,最大特点是轻量化,反应灵敏,用于快速抓取用户体验直接相关的内容进行服务和产品展示。例如一个碰一下就可以调出来的点餐服务,iPhone用户可以直接在不安装软件的情况下下单或者租用小踏板车等等。

ARKit是一款利用可以用于增强现实(虚拟现实plus版本)的开发框架。可以进行运动跟踪和场景理解以及渲染。

CarPlay是苹果针对汽车车机系统研发的智能化信息娱乐系统解决方案,作为老用户,这个功能并不陌生,也是我最喜欢的车辆功能之一,最大亮点是打通iPhone和汽车系统的边界,让本来在手机上的事情可以直接在汽车的显示屏上做,手机相当于一个设备主机的感觉。

Catalyst 移植ios应用到mac上使用的框架,这也就解释了为什么在mac商店里也能看到ios设备ipadOS设备应用了。
重要接口: Catalyst提供了一些API和工具,帮助开发者将iOS应用移植到macOS上。以下是一些重要的接口和工具:
环境检测:
判断是否为Catalyst环境:[[NSProcessInfo processInfo] isMacCatalystApp]。
多窗口支持:
判断是否支持多窗口:[UIApplication sharedApplication].supportsMultipleScenes。
菜单构建:
在MacOS中构建左上角的Menu:[UIMenuSystem mainSystem] setNeedsRebuild];。
鼠标右击事件:
添加鼠标右击事件:UIContextMenuInteraction contextMenuInteraction [[[UIContextMenuInteraction alloc] initWithDelegate:self] autorelease]; [self.view addInteraction:contextMenuInteraction];。
文件导入:
在Mac中导入本地文件和图片:UIDocumentPickerViewController documentPicker [[UIDocumentPickerViewContro。
主要功能
Catalyst的主要功能是将iOS应用移植到macOS上,提供以下主要功能:
界面适配:自动适配iOS应用的界面到macOS,开发者可以调整界面以适应不同的屏幕尺寸和交互方式。
功能支持:支持iOS应用的大部分功能,包括UI控件、动画效果、多媒体播放等,使应用能够在macOS上正常运行。
性能优化:提供工具和API,帮助开发者优化应用的性能,确保在macOS上运行流畅。
开发效率:
通过Catalyst,开发者可以 reuse iOS代码,减少开发时间和成本,同时保持应用的一致性和兼容性。
通过这些功能和工具,Catalyst为开发者提供了一个便捷的方式来将iOS应用移植到macOS上,从而扩展应用的用户群体和平台覆盖范围。
ClassKit

ClassKit是苹果公司推出的一套框架,专为教育应用设计,帮助教师和学生更有效地管理和追踪学习进度。以下是关于ClassKit的详细介绍:
全称
ClassKit的全称是Apple ClassKit,是苹果公司为教育应用提供的开发框架。
重要接口
ClassKit提供了一些API和工具,帮助开发者在教育应用中集成学习管理和进度追踪功能。以下是一些重要的接口和功能 :
活动分配和进度报告:
开发者可以通过ClassKit将应用中的内容结构添加标签,以便教师可以在"课业"App中找到并分配这些内容。
完成活动后,应用需要记录学生的进度,并将这些进度报告给ClassKit,以便教师和学生可以在"课业"App中查看。
数据共享和隐私保护:
ClassKit与"课业"App在构建时充分考虑了隐私保护,只有当活动是由教师明确分配的,并且学生在设备上使用学校为他们创建的管理式Apple账户时,才会接收和显示关于这些活动的学生进度数据。
支持多种学习资源:
通过ClassKit,开发者可以组织教学材料,包括分层次的数学挑战和测验、编程概念和练习,或者图书章节等,以便教师可以为学生分配活动并查看其进度。
主要功能
ClassKit的主要功能包括:
活动管理:
教师可以通过"课业"App分配和管理学生的学习活动,学生只需轻点一下即可直接转到相应的活动。
进度追踪:
应用可以报告的进度信息包括花费的时间、开始和结束计时器、完成百分比、获得的分数、测验分数或二元值(例如,是/否、正确/错误、完成/未完成)。
隐私保护:
ClassKit确保只有教师和学生可以看到他们的信息,保护学生的隐私。
通过这些功能和工具,ClassKit为教育应用提供了一个强大的平台,帮助教师更好地管理教学活动,同时让学生能够更有效地追踪和管理自己的学习进度。
CloudKit

CloudKit是苹果公司推出的一款基于iCloud的云端数据存储服务,允许开发者在苹果的数据中心创建并托管应用程序的后端。以下是关于CloudKit的详细介绍:
全称
CloudKit的全称是Apple CloudKit,是苹果公司提供的云端数据存储服务。
重要接口
CloudKit提供了一系列API和工具,帮助开发者在应用程序和iCloud之间进行数据传输和管理。以下是一些重要的接口和功能:
CKContainer:
容器是应用运行的沙盒,每个应用有且仅有一个属于自己的container。开发者授权后,也可以访问其他应用的container。
CKDatabase:
数据库分为私有数据库和公开数据库。私有数据库用于存储敏感信息,如用户的性别、年龄等,用户只能访问自己的私有数据库。公开数据库用于存储公共信息,例如地理位置信息,所有用户都可以访问。
CKRecord:
数据库中的一条数据记录。CloudKit使用record来表示数据,record是键-值对的字典,代表开发者要保存的数据。可以通过CKRecord类来管理记录的内容。
CKRecordZone:
记录区域,用于将记录分组。开发者可以在一个记录区域中创建和管理多条记录。
CKSubscription:
用于订阅通知。当特定事件发生时,如记录更新或删除,CKSubscription可以触发推送通知。
主要功能
CloudKit的主要功能包括:
数据存储和同步:
CloudKit允许开发者将应用程序的数据存储在云端,并实现跨设备的同步。用户可以在多个设备上访问这些数据。
记录查询:
开发者可以通过API调用来查询存储在CloudKit中的数据。例如,在Swift语言中,只需几行代码就能完成数据的上传与下载操作。
订阅通知:
CloudKit支持基于记录变化的通知订阅功能,当特定事件发生时,可以触发推送通知,提醒用户或开发者。
安全性:
CloudKit提供高安全性的数据保护。用户的私有数据受到完全保护,开发者只能访问自己的私人数据库,而不能查看任何其他用户的私有数据。
跨平台支持:
CloudKit支持iOS、macOS、watchOS以及tvOS平台的应用程序,使得跨设备间的数据共享变得简单快捷。
通过这些功能和工具,CloudKit为开发者提供了一个强大且易于使用的平台,用于在云端存储、管理和同步应用程序数据,同时确保数据的安全性和隐私保护。
Core ML

Core ML是苹果公司为其开发者准备的机器学习框架,支持在iOS、macOS、tvOS和watchOS设备上运行训练好的机器学习模型。以下是关于Core ML的详细介绍:
全称
Core ML的全称是Apple Core ML,是苹果公司提供的机器学习框架。
重要接口
Core ML提供了一系列API和工具,帮助开发者在应用程序中集成和使用机器学习模型。以下是一些重要的接口和功能:
MLModel:
代表机器学习模型的所有细节。通过Xcode自动生成的接口,开发者可以与模型进行交互。
MLFeatureProvider:
用于支持自定义工作流程和高级用例。开发者可以通过采用MLFeatureProvider协议来支持自定义的数据结构和工作流程。
MLModelLoader:
用于加载Core ML模型。开发者可以使用MLModelLoader来加载并初始化模型,以便在应用程序中使用。
主要功能
Core ML的主要功能包括:
模型支持:
支持多种类型的机器学习模型,包括神经网络(Neural Network)、组合树(Tree Ensemble)、支持向量机(Support Vector Machine)以及广义线性模型(Generalized Linear Model)。
设备端性能优化:
Core ML针对设备的性能进行了优化,能够充分利用Apple芯片,最大限度地减少内存占用和功耗。
本地化处理:
所有处理都在设备上完成,无需网络连接。这不仅保护了用户数据的私密性,还使得应用能够在离线状态下正常工作。
跨平台支持:
Core ML支持在iOS、macOS、tvOS和watchOS设备上运行,使得开发者能够轻松地将机器学习模型集成到他们的应用中。
易用性:
Core ML与Xcode紧密集成,开发者可以利用自动生成的Swift和Objective-C接口,轻松地将模型整合到他们的应用中。
通过这些功能和工具,Core ML为开发者提供了一个强大且易于使用的平台,用于在应用程序中实现复杂的机器学习任务,同时确保数据的安全性和隐私保护。
HealthKit

HealthKit是苹果公司为其iOS设备提供的健康与健身数据管理框架。以下是关于HealthKit的详细介绍:
重要接口
- HealthKit提供了一系列API,帮助开发者在应用程序中管理和使用健康与健身数据。以下是一些重要的接口和功能:
HKHealthStore:这是HealthKit的主要接口,用于与HealthKit数据库进行交互。每个应用只需要一个HKHealthStore实例。通过这个接口,开发者可以查询和存储健康数据。
HKHealthDataAvailable:用于检查HealthKit是否在当前设备上可用。这通常用于在应用中确定是否可以访问健康数据。
HKSampleType:代表健康数据的类型,如步数、心率、睡眠分析等。开发者可以使用HKSampleType来指定需要查询或存储的数据类型。
HKQuantityType:用于表示可以量化测量的健康数据类型,如体重、血压等。开发者可以使用HKQuantityType来处理这些量化数据。
HKWorkoutType:专门用于处理锻炼相关的数据类型,如跑步、骑自行车等。开发者可以使用HKWorkoutType来管理锻炼数据。
主要功能
HealthKit的主要功能包括:
数据整合:HealthKit可以整合来自不同健康和健身应用以及设备的数据,为用户提供一个统一的视图。例如,用户可以在HealthKit中查看自己每天消耗的卡路里、睡眠时间、跑步距离等数据。
隐私保护:HealthKit对用户健康数据的访问有严格的权限控制,所有与健康和健身App、医疗保健机构以及健康和健身设备之间的交互都需要用户许可。此外,健康数据在设备上使用"未打开文件的保护"数据保护类储存,确保数据的安全性和隐私性。
设备兼容性:HealthKit可以直接与健康和健身设备配合工作,例如兼容的低功耗蓝牙(BLE)心率监视器和内建于许多iOS设备中的运动协处理器。这使得HealthKit能够提供更全面和准确的健康数据。
临床健康记录:用户可以在Health App内登录支持的健康系统,以获取其临床健康记录的副本。这些记录通过OAuth 2客户端凭证进行认证,并通过TLS 1.3保护的连接直接从健康机构下载,确保数据的安全性。
通过这些功能和接口,HealthKit为开发者和用户提供了一个强大且安全的平台,用于管理和使用健康与健身数据,从而促进更健康的生活方式。
Metal
Metal是苹果公司推出的一种低级图形和计算编程接口,首次在iOS 8中亮相,随后扩展到macOS和tvOS。以下是关于Metal的详细介绍:
Metal的全称是Apple Metal,是苹果公司提供的低层次图形和计算编程接口。
重要接口
Metal提供了一系列API,帮助开发者在应用程序中高效地使用GPU进行图形渲染和通用计算。以下是一些重要的接口和组件:
- MTLDevice :
- 代表GPU设备,通常使用MTLCreateSystemDefaultDevice获取默认的GPU设备。这是Metal的基础,用于创建和管理图形资源。
- MTLCommandQueue:
- 由MTLDevice创建,用于创建和组织MTLCommandBuffer。它确保指令(MTLCommandBuffer)有序地发送到GPU。
- MTLCommandBuffer :
- 用于存储和管理提交给GPU的命令。每个MTLCommandBuffer可以包含多个MTLCommandEncoder,用于执行不同的渲染或计算任务。
- MTLRenderCommandEncoder :
- 用于编码渲染指令。通过这个接口,开发者可以控制顶点着色器、片元着色器等渲染过程。
- MTLComputeCommandEncoder:
- 用于编码计算指令,适用于通用计算任务,如机器学习和图像处理。
- MTLBlitCommandEncoder :
- 用于编码缓存纹理拷贝指令。它可以将数据从一种缓存区域复制到另一种缓存区域。
- MTLRenderPassDescriptor :
- 一个轻量级的临时对象,用于存放渲染过程中的属性配置,供MTLCommandBuffer创建MTLRenderCommandEncoder对象使用。
功能介绍
Metal的主要功能包括:
图形渲染:
- Metal提供了高效的图形渲染能力,支持复杂的视觉效果和高质量的图形输出。通过直接操作GPU,Metal能够最大限度地发挥GPU的性能。
- 通用计算:
- 除了图形渲染,Metal还支持通用计算任务,如机器学习、图像处理等。通过MTLComputeCommandEncoder,开发者可以利用GPU的高性能进行并行计算。
资源管理:
- Metal提供了有效的资源管理机制,帮助开发者优化内存和缓存的使用,从而提高整体性能。
- 低开销和高效率:
- Metal设计的目标是提供比OpenGL更高效、更直接的硬件访问,从而减少CPU开销并提高图形处理和计算性能。
- 通过这些接口和功能,Metal为开发者在苹果平台上提供了一个强大且高效的图形和计算解决方案,适用于各种高性能应用场景。
SF Symbols
SF Symbols是苹果公司推出的一套矢量图标库,可与系统字体San Francisco无缝整合。以下是关于SF Symbols的详细介绍:
全称
SF Symbols的全称是San Francisco Symbols,简称SF Symbols,是苹果公司提供的矢量图标库。
重要接口
SF Symbols提供了一套易于使用的API,帮助开发者在应用程序中高效地使用图标。以下是一些重要的接口和功能:
Image(systemName:) :
在SwiftUI中,可以通过Image(systemName:)直接使用SF Symbols图标。例如,Image(systemName: "rainbow")可以展示彩虹图标。
SymbolRenderingMode :
SF Symbols图标支持不同的渲染模式,包括单色模式(monochrome)和多色模式。通过symbolRenderingMode方法,可以为SF Symbols提供不同的展示形式。
Font和foregroundStyle:
在SwiftUI中,可以通过font和foregroundStyle等modifier来调整SF Symbols的样式,如字体大小、粗细和颜色。例如:
Swift
Image(systemName: "progress.indicator")
.font(Font.system(size: 20, weight: .bold))
.foregroundStyle(.cyan)
功能介绍
SF Symbols的主要功能包括:
图标库 :
SF Symbols拥有超过6000个符号,涵盖20多个分类,包括设备、汽车指示灯、健康和健身符号、格式控制等。
多种变体 :
每个符号包含九种粗细和三种大小,可自动与文本对齐。开发者可以根据需要选择合适的变体。
动画效果 :
SF Symbols 6引入了新的动画效果,如晃动、旋转和呼吸,这些动画可以通过用户输入做出响应,传达状态变化。
本地化支持 :
新增本地化符号,包括拉丁语、希腊语、西里尔字母、希伯来语、阿拉伯语、中文、日语、韩语、泰语、天城文以及几种印度数字系统的变体。
矢量图形编辑:
- 开发者可以将符号导出并使用矢量图形编辑工具进行编辑,创建具有共享设计特征和辅助功能的自定符号。
- 通过这些功能和接口,SF Symbols为开发者在苹果平台上提供了一个丰富且易于使用的图标资源库,能够轻松地将图标集成到应用程序中,并与文本完美融合。
SiriKit

SiriKit是苹果公司提供的框架,允许开发者将应用程序的特定功能集成到Siri中,使用户可以通过语音来控制和使用App。以下是关于SiriKit的详细介绍:
全称
SiriKit的全称是Speech Interpretation and Recognition Interface,即"语音解释与识别接口"。
重要接口
SiriKit的主要接口和组件包括:
Intents框架 :
用于开发服务型扩展(Intents Extension)。在此扩展中,完成与Siri的交互,并处理相应的工作。
包括处理来自Siri的意图(Intent),每个意图对应一个INIntent的子类。
Intents UI框架 :
用于开发UI型扩展(IntentsUI Extension),在此扩展中,自定义Siri中内嵌的UI界面,展示处理结果。
IntentHandling协议:
- 每个意图对应一个协议,每种协议控制处理Intent的各个过程。
- Handler对象:
- 处理来自Siri的Intent,一个Handler对应多个Intent,一个Intent只由一个Handler处理。
- Response对象:
- 代表Handler阶段的处理结果,每个Intent对应各自Response。
- 功能介绍
- SiriKit的主要功能包括:
- 意图支持:
- SiriKit支持多种意图(Intent),包括但不限于任务管理、消息发送、注释、照片搜索、付款、健身管理、打车服务、车载管理等。
- 语音交互:
- 用户可以通过语音指令与App进行交互,实现特定功能。
- 自定义UI:
- 开发者可以在Siri中显示自定义的UI界面,展示处理结果。
- 扩展应用功能:
- 通过SiriKit,开发者可以扩展App的功能,使用户通过语音命令与应用程序进行交互。
- 通过这些接口和功能,SiriKit为开发者和用户提供了更加便捷和智能的交互方式,增强了应用程序的可用性和用户体验。
SpriteKit
SpriteKit是苹果公司提供的用于开发2D游戏和动画的框架,广泛应用于iOS、tvOS和macOS平台。以下是关于SpriteKit的详细介绍:
重要接口
SpriteKit的主要接口和组件包括:
SKView:专门用于显示SpriteKit内容的视图,是SpriteKit中所有内容的容器。
SKScene:代表游戏场景的类,每个游戏至少有一个场景,并且可以包含多个子节点(如精灵、标签等)。
SKNode:所有SpriteKit节点的基类,包括场景、精灵和标签等。它们可以作为其他节点的容器,也可以被添加到场景中。
SKSpriteNode:SKNode的子类,用于表示游戏中的图像元素。它可以显示图片、颜色或纹理,并支持各种变换操作(如缩放、旋转、移动等)。
SKLabelNode:用于在场景中显示文本的节点。
SKPhysicsBody:用于为精灵添加物理效果,使精灵能够响应物理世界的力和碰撞。
SKAction:用于为精灵添加动画和运动效果,可以定义精灵的运动轨迹、旋转、缩放等。
SKTexture:用于设置SKSpriteNode的纹理,可以是一张图片或一系列图片。
SKCameraNode:用于控制场景的视角,可以设置场景的观察点和缩放。
功能介绍
SpriteKit的主要功能包括2D游戏开发:
SpriteKit提供了一个简单易用的API,适合初学者和小型游戏开发项目。
物理模拟:
- 内置的物理引擎,使动画效果看起来更真实,支持碰撞检测和重力效果。
动画和特效:
- 丰富的动画和特效功能,包括粒子系统、音视频播放等,增强了游戏的视觉效果和用户体验。
场景和节点管理:
- 通过SKScene和SKNode,开发者可以方便地管理游戏中的场景和元素,实现复杂的游戏逻辑和交互。
- 通过这些接口和功能,SpriteKit为开发者提供了一个强大且易于使用的工具集,用于创建丰富多样的2D游戏和动画应用。

SwiftUI

WidgetKit 介绍
WidgetKit 是苹果公司推出的一款框架,用于在 iOS、iPadOS 和 macOS 上创建和管理小部件(Widgets)。这些小部件可以让用户在不打开应用程序的情况下,快速查看及时且与个人相关的信息。WidgetKit 支持多种设备和位置,包括 iPhone 的今天视图、主屏幕、锁定屏幕,iPad 的主屏幕和通知中心,以及 Mac 的桌面和通知中心。
主要功能
小部件(Widgets):
信息展示:从应用程序中提取及时且相关的信息,显示在用户一眼就能看到的地方。
功能扩展:在不启动应用程序的情况下,提供特定的应用功能。
多种尺寸:小部件有三种不同的大小(小型、中型和大型),可以显示各种信息。
智能叠放(Smart Stack):
叠放管理:用户可以叠放多个小部件,创建智能叠放。WidgetKit 利用设备端智能技术以及 App Intents 框架提供的功能,实现智能轮换,根据用户的当前情况显示相应的小部件。
Watch 复杂功能(Complications):
信息展示:用户可以将 Watch 复杂功能放在 Apple Watch 表盘中,快速查看及时、相关的信息。
实时活动(Live Activities):
动态更新:从 iOS 16 开始,WidgetKit 支持实时活动,允许小部件显示实时更新的信息,如体育比赛得分、食物配送状态等。
使用指南
要使用 WidgetKit 创建小部件,需要遵循以下步骤:
导入框架:
import WidgetKit
创建 Widget 类:
struct MyWidget: Widget {
var body: some View {
// 在这里定义你的小部件内容
Text("Hello, World!")
.font(.system(size: 20))
.foregroundColor(.blue)
}
}
定义小部件内容:
使用 SwiftUI 来构建视图,定义小部件的内容和布局。
创建数据模型:
在 MyWidgetEntryView 中定义数据模型,决定小部件的数据来源。
struct MyWidgetEntryView: View {
var entry: SimpleEntry
// 定义数据模型
}
配置小部件:
在 Xcode 中配置小部件扩展,设置必要的属性和权限。
安全性
WidgetKit 提供了多种安全性功能,以确保敏感信息不被轻易暴露:
敏感数据保护:
用户可以配置是否在锁定屏幕和全天候显示启用时显示敏感数据。可以在"设置"中配置相关选项。
数据编校:
开发者可以使用 redacted(reason:) 回调、privacy 属性和自定义占位符视图来保护敏感数据。还可以使用 unredacted() 视图修饰符让视图以未编校的形式呈现。
通过以上功能和步骤,开发者可以轻松利用 WidgetKit 创建丰富多样且安全的小部件,提升用户体验。
Test Flight

TestFlight是苹果公司开发的一款应用程序,主要用于帮助开发者测试Beta版的App和轻App。以下是关于TestFlight的详细简介和使用指南:
简介
平台与开发者:
TestFlight是iOS软件测试平台,由Apple Inc.开发,用于在App发布之前将其分发给Beta测试人员。
它允许通过Apple App Store以外的来源安装和测试正在进行的应用程序。
功能与用途:
应用测试:开发者可以将App的Beta版本分发给测试人员,以获取有价值的反馈。
内测预热:为即将上架App Store的应用提供预发布测试。
多设备支持:适用于iPhone、iPad、Mac、Apple TV、Apple Vision Pro、Apple Watch和iMessage信息。
最新更新:
苹果最近对TestFlight进行了更新,增加了新功能以简化Beta测试过程。例如,邀请页面重新设计,可以展示App截图和类别,允许未参与测试的用户留下反馈。
使用指南
开发者操作流程
注册账户:
开发者需要注册一个TestFlight账户。
管理测试团队:
添加开发者或测试用户,通过邮件邀请他们加入测试团队。
上传测试版本:
通过Xcode生成IPA文件,并上传到TestFlight。
选择测试人员:
在TestFlight中选择参与测试的人员,系统会发送邮件通知他们安装应用并进行测试。
测试者操作流程
接收邀请:
测试者会收到一封邀请邮件,点击"Accept"按钮并注册或登录TestFlight账号。
安装TestFlight:
在App Store中下载并安装TestFlight应用。
注册设备:
使用iOS设备打开Safari,登录TestFlight网站并注册设备。
安装和测试应用:
通过TestFlight应用安装测试版本的App,进行测试并反馈给开发者。
注意事项
- 邮件有效期:邀请邮件过期较快,测试者需尽快查收并操作。
- 测试类型:分为内部测试和外部测试,内部测试不需要苹果审核,外部测试需要1-3天审核。
- 通过以上步骤,开发者和测试者可以有效地使用TestFlight进行App的Beta测试,确保应用在正式发布前达到最佳状态。
Wallet

Apple Wallet 是苹果公司开发的一款数字钱包应用,主要用于 iOS 和 watchOS 平台。它不仅支持银行卡的 NFC 无接触支付(Apple Pay),还能存储和管理各种电子凭证,如交通卡、登机牌、电影票等,极大地提升了用户的便捷性和安全性。以下是关于 Apple Wallet 框架的详细介绍:
主要功能
支付功能:
用户可以将银行卡、交通卡等添加到 Wallet 中,通过 NFC 进行无接触支付,支持 Apple Pay 的线下门店、线上网站和 App。
电子交通卡:
可以添加全国交通联合卡以及多个城市的市政交通一卡通,用于公共交通的支付。
证件管理:
支持将学生证、驾照、身份证等个人身份证件添加到 Wallet 中,方便在需要时展示和扫描。
车钥匙:
与 Carkey 功能配对,允许用户使用手机打开车门和启动车辆。
优惠券和回馈卡:
用户可以存储优惠券、商店回馈卡等凭证,在购物或参与特定活动时使用。
订单与物流信息追踪:
在 iOS 16 中,Apple Wallet 增加了订单与物流信息追踪功能,用户可以通过 Wallet 查看商品状态、追踪物流进度等。
使用指南
添加银行卡:
打开 Wallet 应用,点击右上角的"+"符号,选择"添加信用卡或借记卡"。
可以通过拍照识别银行卡信息,或者手动输入卡号、有效期、安全码等详细信息。
输入完成后,系统会要求用户阅读并同意相关条款和条件,并输入银行发送的验证码进行验证。验证成功后,银行卡即被成功添加到 Wallet 应用中,并显示为已激活状态。
添加电子交通卡:
在 Wallet 应用界面,点击右上角的"+"符号,选择"添加电子交通卡"。
选择好交通卡后,进行充值操作,通常最低充值金额为10元。充值完成后,交通卡即被激活,并可在支持交通联合的城市公交、地铁或轮渡上使用。
添加票券凭证:
用户可以通过各种 App 将登机牌、电影票、火车票等电子凭证添加到 Wallet 中。这些凭证包含了有用的信息,如过期日期、座位号等,方便用户随时查看和管理。
安全性保障
所有存储在 Wallet 中的银行卡和凭证都采用了加密技术进行保护,确保用户的个人信息和支付安全。
Apple Pay 还采用了先进的生物识别技术,如 Touch ID 和 Face ID,进一步提高了支付的安全性。
通过这些功能和步骤,Apple Wallet 为用户提供了一个方便、安全的方式来管理各种电子凭证和进行快捷支付。
WidgetKit
WidgetKit 是苹果公司推出的一款框架,用于在 iOS、iPadOS 和 macOS 上创建和管理小部件(Widgets)。这些小部件可以让用户在不打开应用程序的情况下,快速查看及时且与个人相关的信息。WidgetKit 支持多种设备和位置,包括 iPhone 的今天视图、主屏幕、锁定屏幕,iPad 的主屏幕和通知中心,以及 Mac 的桌面和通知中心。
主要功能
小部件(Widgets):
信息展示:从应用程序中提取及时且相关的信息,显示在用户一眼就能看到的地方。
功能扩展:在不启动应用程序的情况下,提供特定的应用功能。
多种尺寸:小部件有三种不同的大小(小型、中型和大型),可以显示各种信息。
智能叠放(Smart Stack):
叠放管理:用户可以叠放多个小部件,创建智能叠放。WidgetKit 利用设备端智能技术以及 App Intents 框架提供的功能,实现智能轮换,根据用户的当前情况显示相应的小部件。
Watch 复杂功能(Complications):
信息展示:用户可以将 Watch 复杂功能放在 Apple Watch 表盘中,快速查看及时、相关的信息。
实时活动(Live Activities):
动态更新:从 iOS 16 开始,WidgetKit 支持实时活动,允许小部件显示实时更新的信息,如体育比赛得分、食物配送状态等。
使用指南
要使用 WidgetKit 创建小部件,需要遵循以下步骤:
导入框架:
import WidgetKit
创建 Widget 类:
struct MyWidget: Widget {
var body: some View {
// 在这里定义你的小部件内容
Text("Hello, World!")
.font(.system(size: 20))
.foregroundColor(.blue)
}
}
定义小部件内容:
使用 SwiftUI 来构建视图,定义小部件的内容和布局。
创建数据模型:
在 MyWidgetEntryView 中定义数据模型,决定小部件的数据来源。
struct MyWidgetEntryView: View {
var entry: SimpleEntry
// 定义数据模型
}
配置小部件:
在 Xcode 中配置小部件扩展,设置必要的属性和权限。
安全性
WidgetKit 提供了多种安全性功能,以确保敏感信息不被轻易暴露:
敏感数据保护:
用户可以配置是否在锁定屏幕和全天候显示启用时显示敏感数据。可以在"设置"中配置相关选项。
数据编校:
开发者可以使用 redacted(reason:) 回调、privacy 属性和自定义占位符视图来保护敏感数据。还可以使用 unredacted() 视图修饰符让视图以未编校的形式呈现。
通过以上功能和步骤,开发者可以轻松利用 WidgetKit 创建丰富多样且安全的小部件,提升用户体验。
2:47:00 - Frameworks - Intro to MVVM & Moving Data

3:06:09 - Frameworks - Integrating with UIKit 这里详情页的展示方式是sheet还是FullScreenCover是配置项,用于打开safari登陆到对应的网站渲染出想要的效果,以便查阅。

Swift
//
// FrameworkDetailView.swift
// Apple-Frameworks
//
// Created by Dominic on 2025/3/25.
//
import SwiftUI
struct FrameworkDetailView: View {
var framework: Framework
@Binding var isShowingDetailView: Bool
@State private var isShowingSafariView = false
var body: some View {
VStack() {
HStack {
Spacer()
Button {
isShowingDetailView = false
} label: {
Image(systemName: "xmark")
.foregroundColor(Color(.label))
.imageScale(.large)
.frame(width: 44, height: 44)
}
}
.padding()
Spacer()
FrameworkTitleView(framework: framework)
Text(framework.description)
.font(.body)
.padding()
Spacer()
Button {
isShowingSafariView = true
}
label: {
Text("Learn More")
.font(.title2)
.fontWeight(.semibold)
.frame(width: 280, height: 50)
.background(Color.red)
.foregroundColor(.white)
.cornerRadius(10)
}
}
.fullScreenCover(isPresented: $isShowingSafariView, content: {
// .sheet(isPresented: $isShowingSafariView, content: {
SafariView(url: URL(string: framework.urlString) ?? URL(string:"www.apple.com")!)
})
}
}
struct FrameworkDetailView_Previews: PreviewProvider {
static var previews: some View{
FrameworkDetailView(framework: MockData.sampleFramework, isShowingDetailView: .constant(false))
.preferredColorScheme(.dark)
}
}
下一节将学习列表视图用于展示工具箱的所有工具信息。

3:17:56 - Frameworks - Refactor

3:38:12 - Frameworks - iOS 15 & 16 Updates

学到这里基本上消除了对ios开发的陌生感,感觉基本上图片,文字icon图,动画效果这些都基本可以handle,最后还剩一个很关键的问题数据从那里来?比如我们要接入大模型做一些交互上的提升的话要如何实现,天气API接口的真实数据获取和展示,如何把自定义的icon或者文本框样式放到APP中去,以及如何DIY一些页面背景,是否有规范等等......Anyway, 继续出发吧~

App 3 - Barcode Scanner

这里一旦点击Privacy-Came useage discription,xcode就会崩溃退出。

4:05:24 - Barcode Scanner - Camera Setup

Delegpates
SwiftUI View <--- Coorditator <------ UIViewController

实机测试的时候遇到了上面的问题,This app has crashed because it attempted to access privacy-sensitive data without a usage description. The app's Info.plist must contain an NSCameraUsageDescription key with a string value explaining to the user how the app uses this data. 还是摄像头权限获取时的配置问题,添加权限设置就可以实现弹窗获取到用户授权
Swift
<key>NSCameraUsageDescription</key>
<string>We need to use the camera to scan barcodes.</string>
散花,现在时可以扫到码,但是不会更新,扫完就卡死了,只能退出程序才能重新扫描,不太好玩儿。(上面放左边视图是为纪念第一次用自己手机开启调试程序成功)
注意实机测试的时候如果找不到开发者模式Developer Mode也不找慌,这个设置是手机连接mac的时候才会在Setting -> General -> Privacy & ... -> Developer Mode 才会出现的,没连接的时候是没有这个开关的选项的。
4:21:44 - Barcode Scanner - Error Handling
现在来处理一旦扫到code就页面冻住的问题。这个课程里面也有讲:
---> 经验证和课程的代码是一样的,但我们手机的相机扫的画面之后不会继续更新画面。。。

FU ck, 好像找到原因了。。。

注释掉这里的stopRunning果然可以连续扫码了,但出现了在扫多个码的过程中跳这个error的问题,再试几个连续有效码的时候能否不跳error报错,连续有效更新(这里直接粘贴过来几个有效码)


不行,还是在中间转换的时候会报无效码的error,而且点击ok不会立即消失。。。这里的无效码报错太烦人了,尝试优化成无效码不更新不报错,测试成功!!🏅
Barcode Scanner test clip-CSDN直播
Swift
import UIKit
import AVFoundation
enum CameraError : String {
case invalidDeviceInput
case invalidScannedValue
}
protocol ScannerVCDelegate: AnyObject {
func didFind(barcode: String)
func didSurface(error: CameraError)
}
final class ScannerVC: UIViewController {
let captureSession = AVCaptureSession()
var previewLayer: AVCaptureVideoPreviewLayer?
weak var scannerDelegate: ScannerVCDelegate?
init(scannerDelegate: ScannerVCDelegate) {
super.init(nibName: nil, bundle: nil)
self.scannerDelegate = scannerDelegate
}
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
override func viewDidLoad() {
super.viewDidLoad()
setupCaptureSession()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
guard let previewLayer = previewLayer else {
scannerDelegate?.didSurface(error: .invalidDeviceInput)
return
}
previewLayer.frame = view.layer.bounds
}
private func setupCaptureSession() {
guard let videoCaptureDevice = AVCaptureDevice.default(for: .video) else {
scannerDelegate?.didSurface(error: .invalidScannedValue)
return
}
let videoInput: AVCaptureDeviceInput
do {
try videoInput = AVCaptureDeviceInput(device: videoCaptureDevice)
} catch {
scannerDelegate?.didSurface(error: .invalidDeviceInput)
return
}
if captureSession.canAddInput(videoInput){
captureSession.addInput(videoInput)
} else {
scannerDelegate?.didSurface(error: .invalidDeviceInput)
return
}
let metaDataOutput = AVCaptureMetadataOutput()
if captureSession.canAddOutput(metaDataOutput) {
captureSession.addOutput(metaDataOutput)
metaDataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
metaDataOutput.metadataObjectTypes = [.ean8, .ean13]
} else {
scannerDelegate?.didSurface(error: .invalidDeviceInput)
return
}
previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
previewLayer!.videoGravity = .resizeAspectFill
view.layer.addSublayer(previewLayer!)
captureSession.startRunning()
}
}
extension ScannerVC: AVCaptureMetadataOutputObjectsDelegate {
func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects:
[AVMetadataObject], from connection: AVCaptureConnection) {
guard let object = metadataObjects.first else {
scannerDelegate?.didSurface(error: .invalidScannedValue)
return
}
guard let machineReadableObject = object as? AVMetadataMachineReadableCodeObject else {
scannerDelegate?.didSurface(error: .invalidScannedValue)
return
}
guard let barcode = machineReadableObject.stringValue else {
scannerDelegate?.didSurface(error: .invalidScannedValue)
return
}
// captureSession.stopRunning() 注意注释删除用于连续扫码
scannerDelegate?.didFind(barcode: barcode)
}
}
4:26:49 - Barcode Scanner - Coordinator Setup
4:48:28 - Barcode Scanner - More Error Handling

APP加一个图标,用于区分不同的软件。(图片来源于网络)
5:02:46 - Barcode Scanner - Refactor

App 4 - Appetizers
前面所有准备就是为了这款"开胃菜" 的APP,涉及了我们前面的知识,同时也新增了很多其他小组件等功能

5:24:48 - Appetizers - App Lifecycle
这里介绍了Scene的概念,相当于定义View出现的环境,用scene就像一个大标题页面,内部你可以再细分子集页面。

这里的json数据连接:
https://seanallen-course-backend.herokuapp.com/swiftui-fundamentals/appetizers

通过插件格式化json之后:

5:29:55 - Appetizers - Model
这里Model的概念有点像把一个app视图用到的内容做封装,我们在对应的View中直接使用就行了,代码看起来更直观和简洁。这里的ViewModel的final class类都是继承自ObservableObject的一个关键协议,有点抽象吧?😄其实就是一个可以通知View其属性变化的物件。这是一种遵循响应式编程的原则,使得状态管理更加简单和直观。查了一下资料,这种关键协议有以下几个个人认为比较重要的点:
-
可以Published修饰,用于通知所有订阅了这些属性的视图。
-
应用的时候用@ObseredObject 或@StateObject来声明ObservableObject的实例。
-
和环境对象可以配合使用: ObservableObject 还可以与 @EnvironmentObject 结合使用,使得对象可以在视图层次结构中共享,而无需显式传递。
例如,你可以在应用的顶层视图创建一个 ObservableObject 的实例,并在子视图中通过 @EnvironmentObject 访问它。
5:42:19 - Appetizers - List View
5:55:32 - Appetizers - Network Manager
遇到问题是tabView预览会崩溃

6:12:40 - Appetizers - Connecting the UI

6:26:53 - Appetizers - Errors & Alerts
Swift
import SwiftUI
import Foundation
struct AlertItem: Identifiable {
let id = UUID()
let title: Text
let message: Text
let dismissButton: Alert.Button
}
struct AlertContext {
// MARK: Network
static let invalidURL = AlertItem(title: Text("Server Error"),
message: Text("There was an issue connecting to the server. Please try again later or contact support"),
dismissButton: .default(Text("OK")))
static let invalidResponse = AlertItem(title: Text("Server Error"),
message: Text("Invalid the reponse form the server. Please try again later or contact support"),
dismissButton: .default(Text("OK")))
static let invalidData = AlertItem(title: Text("Server Error"),
message: Text("The data received from the server was invalid. Please contact support"),
dismissButton: .default(Text("OK")))
static let unableToComplete = AlertItem(title: Text("Server Error"),
message: Text("Unable to complete your request at this time. Please check your internet connecetion"),
dismissButton: .default(Text("OK")))
// MARK: Account Alerts
static let invalidForm = AlertItem(title: Text("Invalid Form"),
message: Text("Please ensure all fields in the form have been filled out."),
dismissButton: .default(Text("OK")))
static let invalidEmail = AlertItem(title: Text("Invalid Email"),
message: Text("Please enruse your email is correct."),
dismissButton: .default(Text("OK")))
static let userSaveSuccess = AlertItem(title: Text("Profile Saved"),
message: Text("Your profile information was successfully saved"),
dismissButton: .default(Text("OK")))
static let invalidUserData = AlertItem(title: Text("Profile Error"),
message: Text("There was an error saving or retrieving your profile."),
dismissButton: .default(Text("OK")))
}
APError.swift
Swift
import Foundation
enum APError: Error {
case invalidURL
case invalidResponse
case invalidData
case unableToComplete
}
6:41:35 - Appetizers - Loading View
6:53:21 - Appetizers - Download Image
7:03:42 - Appetizers - Remote Image
7:16:16 - Appetizers - Project cleanup & organization
7:21:12 - Appetizers - Detail View UI Setup
7:41:09 - Appetizers - Detail View UI Connections
7:52:28 - Appetizers - Detail View Refactor
8:02:56 - Appetizers - Account View UI Setup
8:17:29 - Appetizers - View Model & Text Validation
8:33:22 - Appetizers - @AppStorage
在编写AccountViewModel的时候我们用到了guard这个关键字,guard
关键字用于转移程序的控制流,当某个条件不满足时,提前退出当前作用域。
Swift
guard inValidForm else { return }
还有关于do catch的块
Swift
do {
let data = try JSONEncoder().encode(user)
userData = data
alertItem = AlertContext.userSaveSuccess
} catch {
alertItem = AlertContext.invalidUserData
}
8:50:56 - Appetizers - Order Screen UI
首先是订单页面:
这个页面在xcode的模拟器页面是不可以预览的,不知道是不是ios18新版本的xcode和ios版本导致的,但build项目是okey的。


@State 属性包装器
@State
是一个属性包装器,它用于在视图中管理和存储状态信息。状态信息是指那些在用户交互过程中可能会发生变化的数据。
@State
的基本用法是在SwiftUI的视图结构体中声明一个变量,并使用@State
属性包装器来标记它。这样,SwiftUI就会负责这个变量的存储和更新。
【含义】
状态变量 :@State
标记的变量是状态变量,它们是视图的一部分,并且当它们发生变化时,视图会自动重新加载以反映这些变化。
绑定 :在SwiftUI中,状态变量通过在其名称前加上美元符号($
)来创建一个绑定(Binding),这样就可以在视图中传递状态,而不需要直接传递变量本身。
局部状态 :@State
用于管理局部状态,这意味着状态变量只对声明它的视图实例可见和可用。它不应该被用于跨多个视图共享状态。
【优点】
简单性:@State使得在视图中管理状态变得非常简单,不需要手动处理状态的变化和视图的更新。
响应性:SwiftUI会自动响应状态的变化,并重新渲染视图,以反映最新的状态。
【注意事项】
@State变量必须在视图的内部声明,不能作为外部参数传递给视图。
当状态变量发生变化时,SwiftUI会重新调用body属性,以生成新的视图结构。
通过使用@State,开发者可以轻松地创建动态和交互式的SwiftUI界面,使得用户交互能够直接反映在界面上,而不需要手动操作UI元素。
Swift
import SwiftUI
struct OrderView: View {
@EnvironmentObject var order: Order
// @State private var orderItems = MockData.orderItems
var body: some View {
NavigationView {
ZStack {
VStack {
List {
ForEach(order.items) { appetizer in
AppetizerListCell(appetizer: appetizer)
}
.onDelete(perform: deleteItems)
}
.listStyle(PlainListStyle())
Button {
print("order placed")
} label: {
APButton(title: "$\(order.totalPrice, specifier: "%.2f") - Place Order")
// Text("$\(order.totalPrice, specifier: "%.2f") - Place Order")
}
// .modifier(StandartButtonStyle())
.padding(.bottom, 25)
}
if order.items.isEmpty {
EmptyState(imageName: "empty-order", message: "You have no items in your order, \nPlease add an appetizer!")
}
}
}
.navigationTitle("📋 Order")
}
func deleteItems(at offesets: IndexSet) {
order.items.remove(atOffsets: offesets)
}
}
#Preview {
OrderView()
}
9:02:18 - Appetizers - Empty State

但页面没有内容时给用户一个清晰的状态展示页面。
9:12:40 - Appetizers - @EnvironmentObject - Order
Swift
import SwiftUI
struct AppetizerDetailView: View {
@EnvironmentObject var order: Order
let appetizer: Appetizer
@Binding var isShowingDetail: Bool
var body: some View {
VStack {
AppetizerRemoteImage(urlString: appetizer.imageURL)
.aspectRatio(contentMode: .fit)
.frame(width: 300, height: 225)
VStack {
Text(appetizer.name)
.font(.title2)
.fontWeight(.semibold)
Text(appetizer.description)
.multilineTextAlignment(.center)
.font(.body)
.padding()
HStack(spacing: 40) {
NutritionInfo(title: "Calories", value: "\(appetizer.calories)")
NutritionInfo(title: "Carbs", value: "\(appetizer.carbs) g")
NutritionInfo(title: "Protein", value: "\(appetizer.protein) g")
}
}
Spacer()
Button {
order.add(appetizer)
isShowingDetail = false
} label: {
// APButton(title: "$\(appetizer.price, specifier: "%.2f") - Add to Order")
Text("$\(appetizer.price, specifier: "%.2f") - Add to Order")
}
.modifier(StandardButtonStyle())
// .standardButtonStyle()
// .buttonStyle(.bordered)
// .tint(.brandPrimary)
// .controlSize(.large)
.padding(.bottom, 30)
}
.frame(width: 300, height: 525)
.background(Color(.systemBackground))
.cornerRadius(12)
.shadow(radius: 40)
.overlay(Button {
isShowingDetail = false
} label: {
XDismissButton()
}, alignment: .topTrailing)
}
}
struct AppetizerDetailView_Previews: PreviewProvider {
static var previews: some View {
AppetizerDetailView(appetizer: MockData.sampleAppetizer,
isShowingDetail: .constant(true))
}
}
struct NutritionInfo: View {
let title: String
let value: String
var body: some View {
VStack(spacing: 5) {
Text(title)
.bold()
.font(.caption)
Text(value)
.foregroundColor(.secondary)
.fontWeight(.semibold)
.italic()
}
}
}
到这里整个APP已经算功能开发完成,达成了可以用户注册和菜单查看和下订单的功能。至少看起来像是一个能够满足一定需求的"作品"。
但真正的折腾之路才算刚刚开始,后面Sean Allen继续根据ios15的特性进行了app优化,美化了页面效果和细节。现在2025年来看已经到ios18了,又有了很多新的功能,后面整理一下各个wwdc的主要信息(又是一个新坑)。但我的代码好像还不能跑起来。
5 April Update:


由于ios版本的更新,现在已经到ios18,这个课程的一些代码在本地进行编译的时候会报错,需要二次调整:

这里计算价格的函数比较有意思,是一种高效获得数组元素属性数值之和的方法:
Swift
var totalPrice: Double {
items.reduce(0) {$0 + $1.price}
}
var totalPrice: Double:声明一个计算属性totalPrice,它的类型是Double。计算属性不会存储值,而是提供了一个获取器(getter)来计算并返回值。
items:假设这是一个数组,数组中的每个元素都是一个具有price属性的对象(例如,商品、订单项等)。
reduce(0) {...}:使用reduce方法来遍历items数组,并计算总和。reduce方法接受两个参数:第一个是初始值,这里是0;第二个是一个闭包,用于指定如何结合数组中的元素。
{0 +1.price}: 这是一个闭包表达式,用于定义如何将数组中的元素累加起来。0代表累加的当前值(初始值为0),1代表数组中的当前元素。$1.price表示当前元素的price属性。
9:30:21 - Appetizers - iOS 15 - Initial Run

9:38:48 - Appetizers - iOS 15 - Tabbar Badge & List Separator

9:52:25 - Appetizers - iOS 15 - @FocusState Keyboard

tip: option + click the .accentColor 可以查看这个修饰符或者函数的详细官方介绍信息,command+click是直接去到对应的Swift core代码实现。
@FocusState private var focusedTextField: FormTextField?


这里的toolbar让交互更friendly~
10:01:01 - Appetizers - iOS 15 - Async/Await Network Calls
Swift
import UIKit
final class NetworkManager {
static let shared = NetworkManager()
private let cache = NSCache<NSString, UIImage>()
static let baseURL = "https://seanallen-course-backend.herokuapp.com/swiftui-fundamentals/"
private let appetizerURL = baseURL + "appetizers"
private init() {}
func getAppetizers() async throws -> [Appetizer] {
guard let url = URL(string: appetizerURL) else {
throw APError.invalidURL
}
let (data, _) = try await URLSession.shared.data(from: url)
do {
let decoder = JSONDecoder()
return try decoder.decode(AppetizerResponse.self, from: data).request
} catch {
throw APError.invalidData
}
}
func downloadImage(fromURLString urlString: String, completed: @escaping (UIImage?) -> Void) {
let cacheKey = NSString(string: urlString)
if let image = cache.object(forKey: cacheKey) {
completed(image)
return
}
guard let url = URL(string: urlString) else {
completed(nil)
return
}
let task = URLSession.shared.dataTask(with: URLRequest(url: url)) {data, response, error in
guard let data = data, let image = UIImage(data:data) else {
completed(nil)
return
}
self.cache.setObject(image, forKey: cacheKey)
completed(image)
}
task.resume()
}
}
Async way for loading:

lattner/TaskConcurrencyManifesto.md
@MainActor和DispatchQueue.main.async 含义一致,都是异步状态更新。
10:26:19 - Appetizers - iOS 15 - AsyncImage

趁着异步加载的问题修复了示例图片的圆角,算一个强迫症选项,这样舒服一些。这个orderView预览的bug好像是稳定复现的,和视频里老师的一样的问题。
10:32:52 - Appetizers - iOS 16 - Regex, guard let
Swift
var isValidEmail: Bool {
// let emailFormat = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
// let emailPredicate = NSPredicate(format: "SELF MATCHES %@", emailFormat)
// return emailPredicate.evaluate(with: self)
let emailRegex = Regex {
OneOrMore {
CharacterClass(
.anyOf("._%+-"),
("A"..."Z"),
("0"..."9"),
("a"..."z")
)
}
"@"
OneOrMore {
CharacterClass(
.anyOf(".-"),
("A"..."Z"),
("a"..."z"),
("0"..."9")
)
}
"\\"
/./
Repeat(2...64) {
CharacterClass(
("A"..."Z"),
("a"..."z")
)
}
}
return self.wholeMatch(of: emailRegex) != nil
}
10:45:58 - App Optimizations
Framework的项目有个优化,点击链接直接跳转浏览器查看Framework详情。
11:19:41 - Data Flow Review
@State 第一个app: @State private var isNight = false (struct ContentView: View { } 包装器
@Binding
@StateObject
@ObservedObject
@EnvironmentObject
11:38:47 - iOS 17 @Observable
@ObservablObject
@StateObject
@ObservedObject
@EnvironmentObject 这个的经典案例是AppetizerTabView,需要在多个view中作为父级对象,以环境对象存在的订单Order变量。







11:45:36 - What's Next?
Review & Changle



至此,我们的四个APP已完美完成,每一个都包含了很多关于swiftUI的哲学和响应逻辑,感谢Sean Allen,A big hug for thanks to this beard funny guy! Love and peace~

参考来源
除以上声明的资料来源,一些内容还参考和使用的工具有:
-
AI大模型-【智谱清言】&【豆包】并经过了个人核对和整理
-
图片转换工具,用于生成想要的iso icon图标1024x1024的icon 图片Image Resizer | Easily Resize Images Online for FREE