真TM扫兴,本来高高兴兴要测试最简单的布局的内容的
结果,刚开局,示例里面的“.foregroundColor(.themeBlue)”
放进Xcode里,报错“Type 'Color?' has no member 'themeBlue'”
在官方文档和网络搜索 themeBlue,都搜不到,怪胎了
于是下载示例的源文件,奇怪的是用XCode打开,里面的themeBlue是有颜色的,
唯独自己单独建立的项目,输入的是白色,直接复制过来也是白色的,
只能是配置文件上存在差异了,暂时又无从下手,真的衰,浪费时间
有意思了,我把下载的源文件一个一个地删除,删到某个的时候themeBlue变成白色了
原来不是文件的基础配置有问题,而是:这是一个作者自定义的东西!!!
这个才是网上搜索不出来的原因。排除一切可能,那剩下的那个有多不可能都是可能!!
To use both text and a symbol to represent a single element in your app,
use a Label.
Graphical elements, such as images and shapes,
can add a level of visual enhancement for your app.
a level of一定程度
XXX……
.border(Color.blue)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.border(Color.green)
终于明白教程里面说的,frame也是一个View,都有border,
前者相当于实物轮廓,后者相当于作用域轮廓
悟性还是比较差,
2,结构申明的变量为绑定值时,预览结构里面参数填什么?
Codable 的引入简化了JSON 和 Swift 类型之间相互转换的难度,
能够把 JSON 这种弱类型数据转换成代码中使用的强类型数据。
如数据遵守这个协议,并且数据中所有成员都是 encodable,
编译器会自动生成相关实现。 如不可编码,需自定义,自己实现。
由于 Swift 标准库中的类型,比如 String,Int,Double 和 Foundation 框架中
Data,Date,URL 都是默认支持 Codable 协议的,所以只需声明支持协议即可。
还是掼蛋游戏简单,规则简单易懂,就是各种组合,各种博弈
不像这个,一会JSON,一会儿await的,很多时候内心活动:
它是什么?为什么要用它?为什么会有这么奇怪的语法?
只有多读英文文档,多啃,看多了就不惧怕工具文档了,也能看懂了,
也快起来了,然后才能逐渐打通任督二脉
比如读懂AsyncImage的官方介绍。
SwiftUI provides you with phase data, which updates you on the state of image loading.
后一句翻译:它可以更新图片加载状态。没有you on 什么事。伤人呐
怎么看懂别人的代码?网络:写伪代码
自想:整个APP的功能是什么?这种类型就像现实的什么?
异步地从服务器抓取结构化数据,这是指JSON格式的?
本示例,你将学会异步抓取数据是怎么工作的,以及怎么使用
第一部分:分享APP数据
探索这个APP是如何让它抓取的数据在整个视图架构可用
要抓取数据,APP使用了一个可观察对象,这是所有数据的抓取行为发生的地方。
struct MemeCreatorApp: App {
@StateObject private var fetcher = PandaCollectionFetcher()
要分享它的数据, MemeCreator传递PandaCollectionFetcher作为一个环境对象,
这让MemeCreator的所有子视图都可使用。
MemeCreator()
.environmentObject(fetcher)
一个环境对象必定也是一个可观察对象
第二部分:创造Panda模型
学习怎样模型化一个数据资源中的结构化的JSON数据作为一个Swift结构体
这是Panda模型对象,它将从这个URL返回的JSON数据镜像结构化。
struct Panda: Codable {
var description: String
var imageUrl: URL?
static let defaultPanda = Panda(description: "Cute Panda",
imageUrl: URL(string: "https://assets.devpubs.apple.com/playgrounds/_assets/pandas/pandaBuggingOut.jpg"))
每一个panda包含一个文本描述和一个指向熊猫图片的imageUrl。
它们是你将用于下载熊猫图片的数据。
struct PandaCollection: Codable {
var sample: [Panda]
PandaCollection是Panda模型对象数组的组合。 这镜像了JSON数据的格式,
它允许你轻易地解码URLs和JSON数据的描述文本到一个PandaCollection实例。
第三部分:抓取Panda数据
要抓取这些图片和它们的元数据,你将使用一个异步抓取数据的可观察对象。
你将在这个示例学习更多关于结构化异步函数的知识。
PandaCollectionFetcher处理此APP中的数据抓取。
一个可观察的对象,允许向所有观察它们的 UI 元素publish其值的变化。
在此示例中,你将有一个等待新Panda数据的图像视图,以便它能更换图像和描述。
这有两个Published值:
imageData:一个你能植入JSON数据的PandaCollection,
currentPanda:一个你在APP的UI中显示的Panda模型对象。
class PandaCollectionFetcher: ObservableObject {
@Published var imageData = PandaCollection(sample: [Panda.defaultPanda])
@Published var currentPanda = Panda.defaultPanda
fetchData函数抓取JSON数据,标记为async表示这函数异步运行,因为它从
互联网抓取数据可能需要一点时间,异步函数会暂停直到数据返回,同时,你的APP代码持续在后台运行。
这个函数同样标记为throws,这表明当你调用它的时候可能抛出一个错误,
在MemeCreator结构体中,用try? 表明用忽视抛出值的方式处理错误。
func fetchData() async
throws {
guard let url = URL(string: urlString) else { return }
检查是否得到一个合法的URL。
let (data, response) = try await URLSession.shared.data(for: URLRequest(url: url))
调用另一个异步函数URLSession.shared.data(for:),
它使用你已经定义的URL传递一个URL请求。
这个调用同样标记为await,因为这是函数暂停等待URL请求响应的地方。
guard (response as? HTTPURLResponse)?.statusCode == 200 else { throw FetchError.badRequest }
数据返回之后,你将会核实URL响应是否接收了一个错误,
如果响应的statusCode不等于200将生成一个糟糕请求错误,反之是一个成功请求。
Task { @MainActor in
imageData = try JSONDecoder().decode(PandaCollection.self, from: data)
}
最后,解码JSON数据并且将它赋值给published值:imageData。
现在,你有了创建panda memes的所有数据。
第四部分:创建异步图片
研究如何使用URL异步地下载图像。
当你有了JSON数据,你能够用它下载panda图像,要做到这,可以从AsyncImage 构建 LoadableImage 视图,
它是一个可异步下载图像的视图。
struct LoadableImage: View {
var imageMetadata: Panda
要创建一个图像,LoadableImage需要下载的Panda数据,
metadata在这里提供了包含image URL和描述的数据。
var body: some View {
AsyncImage(url: imageMetadata.imageUrl) { phase in
当图像下载时,需要显示些什么在位置上,然后如果图像加载失败
得显示些别的什么,你将在后面的if语句里面处理这些逻辑
当你创建AsyncImage的一个实例,swiftUI给你提供了phase数据,它在图像下载的时候更新。
phase.error给你提供了可能出现的错误,同时如果图像可用,
phase.image提供一个图像,你可以根据phase状态使用phase数据显示合适的UI。
if let image = phase.image {
image
检查是否图像可用,如果可用,这就是你将使用描述作为
可访问文本显示的panda图像。
} else if phase.error != nil {
检查当下载图像时是否有错误发生,如果发生,提供一个视图告诉用户something went wrong.
} else {
ProgressView()
如果没有接收到图像且没有错误,意味着图像在下载中,让
用户知道图像正在下载,使用进度条视图显示一个动画。
第五部分:生成Meme创建者
你将使用APP的数据创建一个meme生成UI,在示例里面探索怎么组合视图。
MemeCreator是你组合所有一切创建panda memes的地方。
作为你APP的顶级视图,这是你将显示panda图像伴随着添加和编辑文本工具的地方。
struct MemeCreator: View, Sendable {
@EnvironmentObject var fetcher: PandaCollectionFetcher
在MemeCreatorApp结构中,你把PandaCollectionFetcher作为一个环境对象传入顶级视图。
在这里,你通过定义一个fetcher变量(使用EnvironmentObject属性包装器)访问环境对象。
@State private var memeText = ""
@State private var textSize = 60.0
@State private var textColor = Color.white
@FocusState private var isFocused: Bool
.task {
try? await fetcher.fetchData()
在开始下载panda图像之前,fetcher需要抓取JSON数据。 当视图开始出现的时候,
task修改器定义了一个任务来完成工作。 这是你将调用fetcher.fetchData()来抓取JSON数据的地方。
try? await与你定义async throws的保持一致。
因为函数是异步的,await意味着将等待async函数返回的结果,
try?意味着你将试着调用函数,但是忽略它抛出的任何错误。
LoadableImage(imageMetadata: fetcher.currentPanda)
.overlay(alignment: .bottom) {
TextField(
"Meme Text",
text: $memeText,
prompt: Text("")
)
.focused($isFocused)
.font(.system(size: textSize, weight: .heavy))
.shadow(radius: 10)
.foregroundColor(textColor)
.padding()
.multilineTextAlignment(.center)
在这个视图UI部分,你根据data fetcher's currentPanda,使用LoadableImage异步下载图像.
尽管JSON数据还没有下载完成,APP提供了一个默认的currentPanda下载作为第一张图。
这个图像有一个覆盖文本,它你能增加一个修改器。 它显示meme文本。
文本域使用三个状态变量,memeText, textSize, and textColor,用来动态改变文本域,响应用户编辑动作。
文本域通过传递@FocusState 变量到 .focused修改器中来获得焦点。
Button {
if let randomImage = fetcher.imageData.sample.randomElement() {
fetcher.currentPanda = randomImage
}
} label: {
VStack {
Image(systemName: "photo.on.rectangle.angled")
.font(.largeTitle)
.padding(.bottom, 4)
Text("Shuffle Photo")
}
.frame(maxWidth: 180, maxHeight: .infinity)
}
要改变图像,你可以用一个按钮从PandaCollection中获取一个随机panda,
然后设置作为currentPanda。
因为currentPanda是一个published值,当LoadableImage改变时,
它使用最近一次currentPanda数据视图自动更新。
Button {
isFocused = true
} label: {
VStack {
Image(systemName: "textformat")
.font(.largeTitle)
.padding(.bottom, 4)
Text("Add Text")
}
.frame(maxWidth: 180, maxHeight: .infinity)
}
要增加文本,使用一个按钮改变文本域焦点状态为true,这会在文本域中
自动插入光标然后你能增加文本。
VStack {
HStack {
Text("Font Size")
.fontWeight(.semibold)
Slider(value: $textSize, in: 20...140)
}
HStack {
Text("Font Color")
.fontWeight(.semibold)
ColorPicker("Font Color", selection: $textColor)
.labelsHidden()
.frame(width: 124, height: 23, alignment: .leading)
Spacer()
最后,你可以使用在UI底部的滑块和颜色格来修改textSize and textColor状态变量,
这些控件修改状态变量的值来自动更新meme文本的外表。
说是可以做火和尘
代码运行未见反应,等待
https://www.hackingwithswift.com/articles/246/special-effects-with-swiftui
液化?
https://alexdremov.me/swiftui-advanced-animation/
这篇文章简单,就是介绍混合模式,举了Multiply、 screen、 saturation这三种常见模式。
https://www.hackingwithswift.com/books/ios-swiftui/special-effects-in-swiftui-blurs-blending-and-more
The reason for this is that Color.red, Color.green, and Color.blue aren’t fully those colors;
you’re not seeing pure red when you use Color.red. Instead, you’re seeing SwiftUI’s adaptive
colors that are designed to look good in both dark mode and light mode, so they are
a custom blend of red, green, and blue rather than pure shades.
难怪苹果的红绿蓝这么好看,原来不是纯色,哈哈哈
有机会读一读:Shadows and glows
https://www.hackingwithswift.com/plus/swiftui-special-effects/shadows-and-glows
文字看懂了,代码内容没看懂
代码确实,原理未知
https://www.hackingwithswift.com/books/ios-swiftui/animating-simple-shapes-with-animatabledata
drawingGroup()方法可离屏渲染,it is powered by Metal, which is Apple’s framework for working directly with the GPU 。
尴尬了,看了wwdc2021,它里面说drawingGroup修改器是组合所有视图在单个图层中绘制。。。且说了这只适用于图形绘制。。
代码确实,后续再研究它的彩虹
https://www.hackingwithswift.com/books/ios-swiftui/enabling-high-performance-metal-rendering-with-drawinggroup
Image("Example")不能用于border一个图形,但是ImagePaint(image:scale)可以
https://www.hackingwithswift.com/books/ios-swiftui/creative-borders-and-fills-using-imagepaint
多次重叠时,决定基数顺序的图形可以fill。Swift UI makes it trivial to use, because whenever we call fill() on a shape
we can pass a FillStyle struct that asks for the even-odd rule to be enabled.但是花瓣的生成方法需要研究一下
https://www.hackingwithswift.com/books/ios-swiftui/transforming-shapes-using-cgaffinetransform-and-even-odd-fills
.shadow(color: .black, radius: 10)
.shadow(color: .black, radius: 10)
重复调用多个shadow修改器,可以让阴影加深,你个老6
通过点esc代码提示,发现shadow修改器还有两个参数是xy,试了下,是我想要的效果
path.addArc(center: CGPoint(x: rect.midX, y: rect.midY ), radius: rect.width / 2,
startAngle: startAngle, endAngle: endAngle, clockwise: clockwise)
这个方法,我们知道SwiftUI是以左上角为坐标原点的。 但是clockwise取true时,按照字母意思是顺时针绘制,
但是显示的却是逆时针绘制,没找到原因,目前只有反着记忆了,用的时候取反了。
正在看的这个文档也有同样的疑惑,嘿嘿,我不是一个人……
What’s happening here is two-fold
不是两个折叠,而是两个方面。
The value that the view returns is an element that SwiftUI draws onscreen.
A modifier is nothing more than a method called on a particular view. The method returns a new,
altered view that effectively takes the place of the original in the view hierarchy.
这段时间对SwiftUI的理解:它是抽象之上的抽象,集成了很多常用的控件,以View的形式展现,有自动布局,
同时强大的修改器,可以方便做动画和响应事件。 方便重绘--- 即快速的多次重绘,还引入了类似于框架的canvas。
但,这些都不是我需要的功能,估计我需要学习UIKit了,向下一层,难度估计有点大。
现在我在最后思考,canvas能不能给到我需要的东西?
不同于众多的内置控件,SwiftUI 没有采用对 UIGestureRecognizer(或 NSGestureRecognizer)进行包装的形式,
而是重构了自己的手势体系。SwiftUI 手势在某种程度上降低了使用门槛,但由于缺乏提供底层数据的 API,严重制约了开发者的深度定制能力。
没有swipe手势
光凭这一点,估计都要抛弃SwiftuI了
读文档提到一个好用的软件'A Companion for SwiftUI',结果官方售价328,囧
https://swiftui-lab.com/view-extensions-for-better-code-readability/
extension view protocol,1,可以用作出modifier选择(此处的代码实现和参数命名挺有意思);
2,重构View 的初始化器(例子用的Image 视图);3,简化视图Preferences (没看懂);
4,ViewModifier can do something that View extensions can’t.(比如接受 .onTapGesture,
比如ViewModifiers can have a @State like Views);5,也可以组合extension和viewmodifier一起用。
http://swiftwithmajid.com/2019/06/12/understanding-property-wrappers-in-swiftui/
之所以用$符号,因为如果没有它,Swift会传副本而不是引用。
.edgesIgnoringSafeArea(.all)这个修饰符是 SwiftUI 早期版本中的用法。
从 iOS 14 开始,SwiftUI 引入了新的 ignoresSafeArea() 修饰符。这是一个更简洁的方式来忽略视图的安全区。
它的默认行为是忽略所有边缘的安全区域,但可以通过参数指定要忽略的边缘。.ignoresSafeArea(edges: .top)
init(_ vec: CGVector) {
self = CGPoint(x: vec.dx, y: vec.dy)
}
百度解释:这个初始化器的作用是将一个 CGVector 转换为 CGPoint。
如:let vector = CGVector(dx: 3, dy: 4)
let point = CGPoint(vector)