编辑代码

真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 标准库中的类型,比如 StringIntDouble 和 Foundation 框架中
DataDateURL 都是默认支持 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)