大师学SwiftUI第6章 - 声明式用户界面 Part 1

状态

在上一章,我们介绍了SwiftUI的主要特性,声明式语法。借助SwiftUI,我们可以按希望在屏幕上显示的方式声明视图,余下交由系统来创建所需的代码。但声明式语法不只用于组织视图,还可在应用状态发生变化时更新视图。例如,我们可以有下面图6-1中的界面,显示标题的​​Text​​​视图,用户输入新标题的输入字段以及将旧标题替换成新标题的按钮。原标题的​​Text​​视图表示我们界面的初始状态。用户在输入框中输入每个字符时状态都会发生更新(图6-1左图),点击按钮时,界面进入一个新状态,用户插入的标题会替换原标题,文本的颜色也发生变化(图6-1右图)。

图6-1:用户界面

图6-1:用户界面

每次状态发生改变时,必须更新视图来进行反馈。在之前的系统中,这要求代码保持数据及界面同步,但在声明式语法中我们只需要声明每个状态的视图配置,系统会负责生成在屏幕上显示这些改变所需的代码。

界面可能经历的状态由存储在应用中的信息决定。例如,用户在输入框架中插入的字符以及示例中使用的颜色都是存储在应用中的值。每当这些值发生改变时,应用都会进入新状态,因此界面会发生更新进行反馈。建立应用数据与界面之间的依赖需要大量的代码,但SwiftUI通过属性包装器让其保持简单。

@State

在第3章中讨论过,属性包装器让我们可以定义用赋给它们的值定义可执行任务的属性。SwiftUI实现了大量的属性包装器来存储值并向视图上报修改。设计用于存储单个视图状态的名为​​@State​​​。这个属性包装器将值存储在类型为​​State​​的结构体中,并在值发生改变时通知系统,这样视图会自动更新来在屏幕中进行反映。

属性包装器​​@State​​​是设计用于存储单个视图的状态的。因此,我们应将这个类型的属性声明为视图结构体的一部分,并使用​​private​​,这样访问就可以限定在所声明的结构体内了。

示例6-1:定义一个状态

struct ContentView: View {
    @State private var title: String = "Default Title"
    var body: some View {
        VStack {
            Text(title)
                .padding(10)
            Button(action: {
                title = "My New Title"
            }, label: {
                Text("Change Title")
            })
            Spacer()
        }.padding()
    }
}

示例6-1中的代码声明了一个​​String​​​类型名为​​title​​​的​​@State​​​属性。该属性使用​​"Default Title"​​​值进行初始化。在视图内容中,我们在垂直堆叠中以​​Text​​​视图显示这个值,并在其下放了一个​​Button​​​视图来修改其值。稍后我们会学习​​Button​​​视图,但现在读者只需要知道​​Button​​​视图显示一个标签并在用户点击按钮时操作一个操作。为展示标签,我们使用带​​"Change Title"​​​文本的​​Text​​​视图来让用户知道按钮的作用,并定义好操作,我们提供一个闭包修改​​title​​​属性的值为​​"My New Title"​​,这样在点击按钮时标题就会发生修改。

使用​​@State​​​包装器创建的​​title​​​属性在两个地方用到了,第一个是向用户显示当前值的​​Text​​​视图,第二是​​Button​​​视图中修改其值的操作。因此,每交点击按钮时,​​title​​​属性的值会发生改变化,​​@State​​​属性包装器通知系统应用的状态发生的变化,​​body​​属性的内容自动刷新在屏幕上显示新值。

图6-2:初始状态(左)和点击按钮后的状态(右)

图6-2:初始状态(左)和点击按钮后的状态(右)

✍️跟我一起做:创建一个多平台项目。使用示例6-1中的代码更新​​ContentView​​视图。确保对画布启用了实时预览(图5-18,1号图)。点击Change Title按钮将字符串赋值给​​Text​​视图。会看到像图6-2右图中的效果。

整个过程是自动完成的。我们不用对​​Text​​​视图赋新值或是告诉该视图新的值,这一切都由​​@State​​​属性包装器处理。我们可以包含多个存储界面状态的​​@State​​​属性。例如,下例中我们对视图添加了一个​​Bool​​​类型的​​@State​​​属性,在每次点击按钮时为​​title​​属性赋不同的文本。

示例6-2:定义多个状态

struct ContentView: View {
    @State private var title: String = "Default Title"
    @State private var isValid: Bool = true
    var body: some View {
        VStack {
            Text(title)
                .padding(10)
            Button(action: {
                isValid.toggle()
                title = isValid ? "Valid" : "Invalid"
            }, label: {
                Text("Change Validation")
            })
            Spacer()
        }.padding()
    }
}

​isValid​​​属性存储表示当前校验状态的布尔值,这样可以在屏幕上显示相应的文本。赋值给​​title​​​属性的字符串通过三元表达式来进行选取。使用三元运算符来设置视图是一种推荐的实践,因为它让系统可以获取视图能响应的所有可能的状态,并在状态间产生平滑的过渡。如​​isValid​​​的值为​​true​​​,将单词"Valid"赋值给​​title​​​属性,否则赋值"Invalid"。每次点击按钮时,​​isValid​​属性的值都会发变化,屏幕上会显示不同的文本(参见示例3-55了解更多有关​​toggle()​​方法的信息)。

注意:示例6-2中有两种状态,同时发生改变,但系统会接管这一情况,保障界面仅在需要时发生更新。

​@State​​属性创建自身和视图之间的依赖,因此在每次值发生改变时视图更新。说法是视图与属性发生了绑定。到目前为止我们使用的都是单向绑定。属性发生修改时视图更新。但也存在视图中值被用户修改,必须要在没有代码介入的情况下将值存回属性的情况。为此,SwiftUI支持我们定义双向绑定。双向绑定声明的方式是在属性名前添加​​$​​符号。

需要双向绑定的视图通常是控制视图,比如创建用户可打开和关闭的开关,或可插入文本的输入框。下例实现了一个​​TextField​​​视图来演示这一功能。​​TextField​​​视图创建一个输入框。其初始化方法需要的值是字符串及占位符文本,我们用绑定属性来存储用户插入的值。(稍后我们会学习​​TextField​​视图及其它控制视图。)

示例6-3:定义双向绑定

struct ContentView: View {
    @State private var title: String = "Default Title"
    @State private var titleInput: String = ""
    var body: some View {
        VStack {
            Text(title)
                .padding(10)
            TextField("Insert Title", text: $titleInput)
                .textFieldStyle(.roundedBorder)
            Button(action: {
                title = titleInput
                titleInput = ""
            }, label: {
                Text("Change Title")
            })
            Spacer()
        }.padding()
    }
}

本例中,我们向视图添加了一个存储用户插入文本的​​@State​​​属性,然后在标题和按钮之间定义了一个​​TextField​​​视图。​​TextField​​​视图使用占位文本"Insert Title"进行初始化,将新的​​titleInput​​​属性喂给视图的绑定属性(​​$titleInput​​​)。这在​​TextField​​视图和属性之间创建了一个永久的连接,每当用户在输入框中输入或删除字符时,新值都会赋值给该属性。

在​​Button​​​视图的操作中,我们做了两个修改。首先将​​titleInput​​​属性的值赋值给​​title​​​属性。这样就会用户插入的文本更新视图标题。然后将空字符会串赋值给​​titleInput​​属性,来清除输入框以便用户再次输入。

✍️跟我一起做:使用示例6-3中的代码更新​​ContentView​​视图。点击输入框,输入文本。按下Change Title按钮。标题就会修改为该段文本。

@Binding

​@State​​​属性属于声明它的结构体,应仅在结构体内部的代码访问(因此我们将其声明为​​private​​​),但在第5章中我们学到,在视图急剧增长时,建议将它们分别整合到独立的结构体中。这样整理视图的问题是其它结构体就无法再引用这些​​@State​​​属性了,也就无法再读取或修改它们的值了。在其它视图中定义新的​​@State​​​属性也不是解决方案,因为这创建的是新状态。我们需要的是建立一个视图中​​@State​​​属性与其它视图中代码的双向绑定。为此,SwiftUI内置了​​Binding​​​结构体和​​@Binding​​属性包装器。

以下示例和前例相同,但这次我们将​​Text​​​和​​TextField​​​视图放到一个单独的​​HeaderView​​​中。这个视图中包含两个​​@Binding​​​属性,用于访问​​ContentView​​​视图中的​​@State​​属性,这样处理就是同样的状态了。

示例6-4:使用​​@Binding​​属性

struct ContentView: View {
    @State private var title: String = "Default Title"
    @State private var titleInput: String = ""
    var body: some View {
        VStack {
            HeaderView(title: title, titleInput: $titleInput)
            Button(action: {
                title = titleInput
                titleInput = ""
            }, label: {
                Text("Change Title")
            })
            Spacer()
        }.padding()
    }
}

struct HeaderView: View {
    var title: String
    @Binding var titleInput: String
    var body: some View {
        VStack {
            Text(title)
                .padding(10)
            TextField("Insert Title", text: $titleInput)
                .textFieldStyle(.roundedBorder)
        }
    }
}

​@Binding​​​属性总是会从​​@State​​​属性中接收值,因此不用为其赋默认值,但建立的连接是双向的,因此要记住在​​@State​​​属性的前面添加​​$​​​符号来与​​@Binding​​​属性进行连接(​​HeaderView(title: title, titleInput: $titleInput)​​​)。因​​@Binding​​​属性和​​@State​​​属性之间是双向绑定,用户输入的值存储在同一个地方,每当系统识别到按钮点击变化时,​​HeaderView​​​结构体的​​body​​属性会再次进行处理,新的值就会显示到屏幕上了。

✍️跟我一起做:使用示例6-4中的代码更新​​ContentView​​​视图。记住保留底部的​​#Preview​​宏以便在画布中进行预览。在输入框中插入值,点击Change Title按钮。效果和之前相同。

绑定结构体

前面讨论过,属性包装器以结构体进行定义,因此包含自己的属性。SwiftUI允许通过在属性名前加下划线(如​​_title​​​)来访问属性的底层结构体。访问到结构体后,就可以处理其属性了。定义​​@State​​​属性包装器的结构体为​​State​​。这是一个泛型结构体,可以处理任意类型的值。以下是该结构体定义用于存储状态值的属性。

  • wrappedValue:此属性返回由​​@State​​属性管理的值。
  • projectedValue:此属性返回​​Binding​​类型的结构体,创建与视图间的双向绑定。

​wrappedValue​​​属性存储赋给​​@State​​​属性的值,就像是上例中赋值给​​title​​​属性的"Default Title"字符串。​​projectedValue​​​属性存储​​Binding​​​类型的结构体,创建将值存回到属性的双向绑定。如果直接读取​​@State​​​属性(如​​title​​​),返回的值存储在​​wrappedValue​​​属性中,如果在属性名前加上​​$​​​符号(如​​$title​​​),我们访问的是存储在​​projectedValue​​​属性中的​​Binding​​​结构体。这是SwiftUI推荐的使用​​@State​​属性的方式,但在理论上我们也可以直接访问这些属性,如下例所示。

示例6-5:访问​​State​​结构体的属性

struct ContentView: View {
    @State private var title: String = "Default Title"
    @State private var titleInput: String = ""
    var body: some View {
        VStack {
            Text(_title.wrappedValue)
                .padding(10)
            TextField("Inserted Title", text: _titleInput.projectedValue)
                .textFieldStyle(.roundedBorder)
            Button(action: {
                _title.wrappedValue = _titleInput.wrappedValue
                _titleInput.wrappedValue = ""
            }, label: {
                Text("Change Title")
            })
            Spacer()
        }.padding()
    }
}

它和前面的示例效果一样,但没有使用SwiftUI的快捷方式,而是直接读取了​​State​​​结构体中的​​wrappedValue​​​和​​projectedValue​​​属性。当前不必这么做,但有时可用于克服SwiftUI自身的缺陷。比如,SwiftUI不允许我们在赋值给​​body​​​的闭包外处理​​@State​​​属性,但我们可以用一个​​State​​​结构体来替换另一个。为此,可以使用以下由​​State​​结构体所提供的初始化方法。

  • State(initialValue: Value):该初始化方法使用​​initialValue​​​所提供的值创建一个​​State​​属性。
  • State(wrappedValue: Value):该初始化方法使用​​wrappedValue​​​所提供的包装值创建一个​​State​​属性。

例如,我们希望对前例的输入框赋一个初始值,可以对​​ContentView​​​结构体添加一个初始化方法,并用它对该属性赋一个新的​​State​​结构体。

示例6-6:初始化​​@State​​属性

init() {
        _titleInput = State(initialValue: "Hello World")
    }

✍️跟我一起做:使用示例6-5中的代码更新​​ContentView​​​视图。在​​ContentView​​结构体中添加示例6-6的初始化方法(放在​​@State​​属性下面)。这时会看到输入框初始化为了"Hello World"。

注意:这样访问绑定属性的内容仅在没有其它选择时才推荐使用。只要有可能,就应使用SwiftUI所提供的属性包装器或在第5章(示例5-58)中介绍过的​​onAppear()​​​修饰符中安装始化​​@State​​属性,或者是在可观测对象是中存储状态,这个稍后会学到。

我们可以按访问​​@State​​​属性同样的方式访问​​@Binding​​属性。如果只像示例6-5那样读取该属性,返回值就是其中存储的值,如果在名称前面加​​$​​​,返回值是属性包装器用于建立与视图双向绑定的​​Binding​​​结构体。但如果在​​@Binding​​​属性的名称前添加下划线(如​​_title​​​),返回值就不是​​State​​​结构体而不是​​Binding​​​结构体。这是因为​​@Binding​​​属性包装器在类型为​​Binding​​的结构体中定义。当然,结构体中还包含访问这些值的属性。

  • wrappedValue:该属性返回由​​@Binding​​属性管理的值。
  • projectedValue:该属性返回创建与视图间双向绑定的类型为​​Binding​​的结构体。

和​​State​​​属性一样,我们可以访问及处理​​Binding​​结构体中存储的值。比如,下例中又实现了一个单独的视图,和示例6-4一样管理标题和输入框。在初始化了​​HeaderView​​​之后,我们通过​​wrappedValue​​​属性获取到​​Binding​​结构体中存储的值,对字符串的字符数计数,然后将结果与标题共同显示出来。

示例6-7:访问​​@Binding​​属性的值

struct ContentView: View {
    @State private var title: String = "Default Title"
    @State private var titleInput: String = ""
    
    var body: some View {
        VStack {
            HeaderView(title: $title, titleInput: $titleInput)
            Button(action: {
                title = titleInput
                titleInput = ""
            }, label: {
                Text("Change Title")
            })
            Spacer()
        }.padding()
    }
}

struct HeaderView: View {
    @Binding var title: String
    @Binding var titleInput: String
    let counter: Int
    
    init(title: Binding<String>, titleInput: Binding<String>) {
        _title = title
        _titleInput = titleInput
        
        let sentence = _title.wrappedValue
        counter = sentence.count
    }
    
    var body: some View {
        VStack {
            Text("\(title) (\(counter))")
                .padding(10)
            TextField("Inserted Title", text: $titleInput)
                .textFieldStyle(.roundedBorder)
        }
    }
}

示例6-7的​​HeaderView​​​中,我们定义了一个属性​​counter​​​,并使用​​wrappedValue​​​属性返回的字符串的字符数进行初始化。因为​​@Binding​​​属性没有初始值,必须使用​​ContentView​​​视图接收到的值进行初始化(​​_title = title​​​)。注意​​HeaderView​​​结构体接收到值是可管理​​String​​​类型值的​​Binding​​​结构体,因此参数的类型必须用​​Binding<String>​​来声明。

初始化完值之后,我们可以在视图中进行显示。标题现在显示 为用户插入的文本以及字符串的字符数。

图6-3:@Binding属性的值定义的标题

图6-3:​​@Binding​​属性的值定义的标题

✍️跟我一起做:使用示例6-7中的代码更新​​ContentView.swift​​文件。插入标题。会看到如图6-3所示的标题及其右侧的字符数。

​HeaderView​​​视图的​​@Binding​​​属性与​​ContentView​​​视图的​​@State​​​属性相连接,因此可接收到该属性的值,但有时这种结构体实例是单独创建的,因而需要一个绑定值。要定义这种值,可以自己创建一个​​Binding​​结构体。结构体中包含如下初始化方法和类型方法。

  • Binding(get: Closure, set: Closure):这一初始化方法创建一个​​Binding​​​结构体。​​get​​​参数是一个会返回当前值的闭包,​​set​​参数是一个接收存储或处理新值的闭包。
  • constant(Value):这个类型方法创建一个带不可变值的​​Binding​​结构体。该参数是我们希望赋值给该结构体的值。

很多场景下需要用到​​Binding​​​值。例如,我们希望创建一个​​HeaderView​​​的预览,必须为​​title​​​和​​titleInput​​​属性提供值。以下示例描绘了如何创建一个新的​​Binding​​结构体提供这些值,以及如何定义这一视图的预览。

示例6-8:创建一个​​Binding​​结构体

#Preview("Header") {
    let constantTitle = Binding<String>(
        get: { return "My Preview Title" },
        set: { value in
            print(value)
        })
    let constantInput = Binding<String>(
        get: { return "" },
        set: { value in
            print(value)
        })
    return HeaderView(title: constantTitle, titleInput: constantInput)
}

​Binding​​​结构体包含一个getter和一个setter。getter返回当前值,setter接收赋值给结构体的值。本例中,返回的是字符串,因为没对结构体赋新值,只是在控制台中打印出了这个值。实例赋值给常量​​constantTitle​​​和​​constantInput​​​,然后发送给​​HeaderView​​结构体,因此在画布该视图有值可以显示。

本例中的​​Binding​​​结构体没有多大用处,只是提供了​​Binding​​​结构体所需要的值。在这种场景,可以使用​​constant()​​​方法来简化代码。这一类型方法使用不可变值创建并返回一个​​Binding​​结构体,所以我们不用自己创建结构体。

示例6-9:通过不可变值创建一个​​Binding​​结构体

#Preview("Header") {
    HeaderView(title: .constant("My Preview Title"), titleInput: .constant(""))
}

✍️跟我一起做:在​​ContentView.swift​​文件中添加示例6-9中的结构体。此时会在画布顶部多出现一个按钮,显示​​HeaderView​​视图的预览。

其它相关内容请见​虚拟现实(VR)/增强现实(AR)&visionOS开发学习笔记​​

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/303794.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

sentinel入门,转载的,不记得在哪复制的了

sentinel 基本概念 开发的原因&#xff0c;需要对吞吐量&#xff08;TPS&#xff09;、QPS、并发数、响应时间&#xff08;RT&#xff09;几个概念做下了解&#xff0c;查自百度百科&#xff0c;记录如下&#xff1a; 响应时间(RT)   响应时间是指系统对请求作出响应的时间。…

第18课 移植FFmpeg和openCV到Android环境

要在Android下从事音视频开发&#xff0c;同样也绕不开ffmpegopencv&#xff0c;不管是初学者还是有一定经验的程序&#xff0c;面临的首要问题就是环境的搭建和库文件的编译配置等问题&#xff0c;特别是初学者&#xff0c;往往会在实际开发前浪费大量的时间来编译ffmpeg及ope…

neo4j图数据库的简单操作记录

知识图谱文件导出 首先停止运行sudo neo4j stop然后导出数据库 导出格式为&#xff1a; 具体命令如下sudo neo4j-admin database dump --to-path/home/ neo4j最后重启sudo neo4j start知识图谱外观修改 在网页点击节点&#xff0c;选中一个表情后点击&#xff0c;可修改其颜…

python接口自动化(八)--发送post请求的接口(详解)

1.简介 上篇介绍完发送get请求的接口&#xff0c;大家必然联想到发送post请求的接口也不会太难&#xff0c;被聪明的你又猜到了。答案是对的&#xff0c;虽然发送post请求的参考例子很简单&#xff0c;但是实际遇到的情况却是很复杂的&#xff0c;因为所有系统或者软件、网站都…

带前后端H5即时通讯聊天系统源码

带有前后端的H5即时通讯聊天系统源码。该源码是一个开源的即时通信demo&#xff0c;需要前后端配合使用。它的主要目的是为了促进学习和交流&#xff0c;并为大家提供开发即时通讯功能的思路。尽管该源码提供了许多功能&#xff0c;但仍需要进行自行开发。该项目最初的开发初衷…

社交心不死:支付宝内测兴趣社交

关注卢松松&#xff0c;会经常给你分享一些我的经验和观点。 支付宝又双叒做社交了&#xff0c;这次的“兴趣社区”能成吗&#xff1f; 支付宝做社交心不死&#xff0c;近期支付宝又开始内测名为“兴趣社区”的功能。主打找同频玩伴&#xff0c;徒步、骑行、钓鱼&#xff0c…

C#高级 10 Linq操作

1.Linq操作介绍 Linq操作是C#集成的类似于数据库语言的操作&#xff0c;是通过将数据库的表名映射为类&#xff0c;把数据库的列名映射为属性。 Linq查询主要分为3类&#xff1a;Linq to object(数组、list集合) --内存里面的数据 Linq to sql(查询数据库用的) --在数据库数据…

VScode/Xshell连接学校服务器

vscode连学校服务器 1.连接atrust VPN2.Xshell连接服务器2.1创建一个自己的用户 3.xftp传文件4.vscode连接服务器4.1下载remote-ssh4.2连接服务器4.3激活conda环境4.4运行代码 5. pytorch版本不兼容解决方案 1.连接atrust VPN 如果是使用的是校园网&#xff0c;可以不连接 2…

数据权限-模型简要分析

权限管控可以通俗的理解为权力限制&#xff0c;即不同的人由于拥有不同权力&#xff0c;他所看到的、能使用的可能不一样。对应到一个应用系统&#xff0c;其实就是一个用户可能拥有不同的数据权限&#xff08;看到的&#xff09;和操作权限&#xff08;使用的&#xff09;。 …

VUE3相比VUE2升级了哪些内容

目录 一、Vue 3 、Vue 2 对比及提升项 二、 Vue 3 创建app.vue示例 三、Vue3 的setup、Vue2 的 data对比 一、Vue 3 、Vue 2 对比及提升项 性能提升&#xff1a;Vue 3 做了大量的优化工作&#xff0c;提升了运行时的性能。例如&#xff0c;在模板编译时进行的静态分析和优化…

第16集《佛法修学概要》

&#xff08;三&#xff09;定不定业&#xff08;2&#xff09; 请大家打开讲义第四十页&#xff0c;我们讲到定业跟不定业。 定业就是说&#xff0c;这个业的结构非常坚固&#xff0c;它有主动得果报的力量&#xff0c;不必有其他的因缘就会主动跑出来&#xff0c;甚至于在今…

Qt构建MSVC2015环境过程

Qt构建MSVC2015环境过程 前言 之前用的Qt都是基于默认的MinGW编译器&#xff0c;由于目前工作的QT界面主要是跑在X86上&#xff0c;所以记录一下Qt配置MSVC2015的配置过程。根据查阅了解以后&#xff0c;个人理解的MinGW跟MSVC的区别在于前者主要是用于跨平台程序构建&#x…

Vue项目在本地跑起来 所有路径前面想加入前缀进行访问配置

一、业务场景&#xff1a; 在本地项目跑起来了&#xff0c;访问时想在所有路径后面加dev进行访问 二、目前效果 三、具体实现步骤&#xff1a; &#xff08;1&#xff09;实现静态文件加前缀 在vue.config.js文件里改变路径 publicPath: process.env.NODE_ENV "product…

苹果Find My查找芯片-伦茨科技ST17H6x支持苹果Find My认证

Apple「查找」Find My可通过庞大的“Apple Find My Network” 实现全球查找功能。无数iOS、iPadOS、macOS、watchOS激活设备与Find My 设备结合在一起&#xff0c;无需连接到Wi-Fi或者蜂窝网络&#xff0c;用户也可以给遗失的设备定位。对于任何iOS、iPadOS、macOS、watchOS设备…

C语言中的预处理

欢迎关注博主 Mindtechnist 或加入【Linux C/C/Python社区】一起学习和分享Linux、C、C、Python、Matlab&#xff0c;机器人运动控制、多机器人协作&#xff0c;智能优化算法&#xff0c;滤波估计、多传感器信息融合&#xff0c;机器学习&#xff0c;人工智能等相关领域的知识和…

Golang 交叉编译之一文详解

博客原文 文章目录 Golang 中的交叉编译不同操作系统间的编译Linux 下编译windowsmacos windows 下编译Linuxmacos macos 下编译Linuxwindows 不同架构下的编译amd64x86 参考 Golang 中的交叉编译 在 Golang 中&#xff0c;交叉编译指的是在同一台机器上生成针对不同操作系统或…

MySQL之子查询、连接查询(内外)以及分页查询(实操)

文章目录 前言一、SQL脚本二、实操以及实现思路 前言 续上篇博主MySQL之视图&索引&执行计划这篇给大家讲解MySQL之子查询、连接查询(内&外)以及分页查询 一、SQL脚本 /*Navicat Premium Data TransferSource Server : localhostSource Server Type :…

文字识别与光学字符识别有什么区别?

随着科技的不断发展&#xff0c;文字识别和光学字符识别技术已经成为我们日常生活和工作中不可或缺的一部分。然而&#xff0c;许多人对于这两者之间的区别并不十分清楚。本文将详细探讨文字识别与光学字符识别之间的差异&#xff0c;以帮助读者更好地理解这两种技术。 文字识…

ASP.NET中小型超市管理系统源码

ASP.NET中小型超市管理系统源码 超市管理系统是专门为中小型超市打造的管理系统&#xff0c;可以方便管理时更加准确清晰的查看商品信息&#xff0c; 仓库出售与进货的信息&#xff0c;还有每一个部门员工的信息&#xff0c;也更加直观的体现出每一阶段的商品销售情况&#xf…

拦截器HandlerInterceptor | springmvc系列

拦截器&#xff0c;通俗来来将&#xff0c;就是我们将访问某个路径的请求给拦截下来&#xff0c;然后可以对这个请求做一些操作 基本使用 创建拦截器类 让类实现HandlerInterceptor接口&#xff0c;重写接口中的三个方法。 Component //定义拦截器类&#xff0c;实现Handle…