第一章:SwiftUI简介
SwiftUI是苹果与2019年推出的适用于iOS、watchOS、macOS等全平台的声明式UI编程框架。SwiftUI使用编码的方式来构建UI,而不是像UIKit那样可以基于storyBoard来构建。基于Xcode11之后的版本支持SwiftUI的编码,可以几乎实时预览,也可以通过预览界面以图形化的形式对UI进行修改(Xcode会自动修改与之对应的代码)。
SwiftUI是支持声明式编程范式的,其与命令式编程范式有较大的差别。指令式编程范式是使用语句来改变程序状态,就像自然语言中的命令式的语气一样。声明式编程范式是通过表达运算逻辑,而不是控制流程来开发程序。
上面偏理论的介绍,下面以实际生活的例子来说明二者。假设,你到必胜客吃披萨。你会告诉服务员,你要铁板的、厚边的、10寸的等。这个过程就是声明式的。后面的厨师在接收到这个订单之后,告诉他的徒弟,说你要把烤炉调到300度、烤10分钟、出炉之后撒上芝士.......。这就是命令式的。
上面两者的区别请仔细拼读,也许此时你还不甚理解,没有关系,让我们继续深入学习SwiftUI,在学习的过程中,你的疑问会自然而然的得到解答。
使用SwiftUI编程时不在需要storyboard(在多人协作开发时,storyboard用的本来就不多)和自动布局。虽然大家会使用Masory等三方库来实现自动布局,但其背后的AutoLayout其实是明显命令式的。大家回想一下,AutoLayout要怎么布局,它需要知道其参照对象,然后明确告知其与参考对象的头、尾、左、右等的关系。
使用UIKit时,VC是很重要的;但是在SwiftUI中VC不见了,取而代之的是Combine。
SwiftUI是适用于所有Apple的平台,但它不是写一次代码就可以了,而是学习一次、到处使用。SwiftUI已经最大程度的支持各平台移植,但是如果期望不做任何改动,那也是不现实的。另外,SwiftUI通过桥接的方式与UIKit、AppKit等代码配合使用。
遗憾的是:SwiftUI要求的iOS13以上、MacOS 10.15、tvOS 13、watchOS 6。但是,这不是我们不学习、不采用SwiftUI的理由。
第二章:SwiftUI基础元素实例
2.1 Text
在SwiftUI中Text元素与UIKit的Label类型,它可以展示一行或多行的文字且不可编辑。下面请新建一个最简单的SwiftUI工程,并在其中输入如下代码。关于Xcode的预览功能请通过百度搜索自行解决。
import SwiftUI
struct ContentView: View {
// some View 是不透明类型
var body: some View {
// 下面的链式表达式与Masory不同,它们叫做修饰符,修饰符的调用顺序与效果有紧密关系
// 可以支持多行、单行
Text("Hello SwiftUI!Hello SwiftUI!Hello SwiftUI!Hello SwiftUI!Hello SwiftUI!Hello SwiftUI!Hello SwiftUI!")
.fontWeight(.bold) // 设置文字的粗体属性
.font(.title) // 指定字体,.title使用动态字体,.system使用系统预定义字体,.custom使用自定义字体
.foregroundColor(.yellow) // 设置文字颜色
.multilineTextAlignment(.trailing) // 设置对齐方式
.lineLimit(100) // 设置允许的行数
.lineSpacing(10) // 设置行间距
.padding() // 设置View的四周留一些空间
.rotationEffect(.degrees(45), anchor: UnitPoint(x: 0, y: 0)) // 旋转View
.shadow(color: .gray, radius: 2, x: 0, y: 15) // 添加阴影
}
}
// 用于预览
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
请注意.font修饰符返回的是Text类型,而padding修饰符返回的是some View。
2.1 图片
SwiftUI提供了一个Image的视图,它可以用来展示图片。
Image(systemName: "cloud.heavyrain") // CF symbol字体所提供的图形
.font(.system(size: 200)) // 由于图形使用字体来实现的,所以可以以文字的形式来处理
.foregroundColor(.red)
.shadow(color: .gray, radius: 10, x: 0, y: 10)
Image("bigpic") // 自己添加的图形
.resizable() // 设置图形可以缩放
.aspectRatio(contentMode: .fill) //填充形式的缩放
.frame(width:300) // 图形的宽度
.clipShape(Ellipse()) // 使用椭圆对图形进行裁剪
.opacity(0.9) // 设置不透明度
.overlay(
// 使用另外的图形对原有图形进行覆盖
Image(systemName: "heart.fill")
.foregroundColor(.red)
.font(.system(size:100))
)
2.3 布局视图
在UIKit中有UIStackView,在SwiftUI中由于没有自动布局所以提供了与之类似的HStack、VStack、ZStack视图用于布局。通过从名字来看,可以看出来他们用于实现水平、垂直、垂直屏幕方向的布局。
struct ContentView: View {
var body: some View {
VStack {
HeaderView()
HStack {
PriceView(title: "Basic", price: "$9", textColor: .white, bgColor: .purple)
ZStack {
PriceView(title: "Pro", price: "$19", textColor: .white, bgColor: .gray)
Text("Best for designer")
.font(.system(.caption, design: .rounded))
.fontWeight(.bold)
.foregroundColor(.white)
.padding(5)
.background(Color(red: 1, green: 183/255, blue: 37/255))
.offset(x: 0, y: 87)
}
}
.padding(.horizontal)
ZStack {
PriceView(icon: "wand.and.rays", title: "Team", price: "$299", textColor: .white, bgColor: .blue).padding()
Text("Perfect for teams with 20 members")
.font(.system(.caption, design: .rounded))
.fontWeight(.bold)
.foregroundColor(.white)
.padding(5)
.background(Color(red: 1, green: 183/255, blue: 37/255))
.offset(x: 0, y: 87)
}
Spacer()
}
.padding()
}
}
struct HeaderView: View {
var body: some View {
HStack {
VStack(alignment: .leading, spacing: 2) {
Text("Choose").font(.largeTitle).fontWeight(.bold)
Text("Yout Plan").font(.largeTitle).fontWeight(.bold)
}
Spacer()
}
}
}
struct PriceView: View {
var icon: String?
var title: String
var price: String
var textColor: Color
var bgColor: Color
var body: some View {
VStack {
if let icon = icon { // 在SwiftUI中只能使用部分Swift的语法,例如for之类的不能使用
Image(systemName: icon)
.font(.largeTitle)
.foregroundColor(textColor)
}
Text(title)
.font(.system(.title, design: .rounded))
.fontWeight(.black)
.foregroundColor(textColor)
Text(price)
.font(.system(size:40, weight:.heavy, design: .rounded))
.foregroundColor(textColor)
Text("per month")
.font(.headline)
.foregroundColor(textColor)
}
.frame(minWidth: 0, maxWidth:.infinity, maxHeight: 100)
.padding(40)
.background(bgColor)
.cornerRadius(10)
}
}
2.5 滚动视图
这是一个基础的View,其效果如图所示。
struct CardView: View {
var image: String
var category: String
var heading: String
var author: String
var body: some View {
if #available(iOS 15.0, *) {
VStack {
Image(image)
.resizable()
.aspectRatio(contentMode: .fit)
HStack {
VStack (alignment: .leading) {
Text(category)
.font(.headline)
.foregroundColor(.secondary) // iOS13引入的,可以支持浅色模式与深色模式的切换
Text(heading)
.font(.title)
.fontWeight(.black)
.foregroundColor(.primary)
.lineLimit(3)
Text(author.uppercased())
.font(.caption)
.foregroundColor(.secondary)
}
Spacer()
}.padding(.horizontal)
}
.cornerRadius(10)
.overlay {
RoundedRectangle(cornerRadius: 10)
.stroke(Color(.sRGB, red: 100/255, green: 150/255, blue: 150/255), lineWidth: 1)
}.padding([.top, .horizontal])
} else {
// Fallback on earlier versions
}
}
}
以下视图组合了Card视图。
// 滚动视图模式是垂直方向滚动的,这里修改为水平滚动
ScrollView(.horizontal, showsIndicators: false) {
HStack {
Group {
CardView(image: "1", category: "SwiftUI", heading: "Drawing a Border with Rounded Corners", author: "Simon NG")
CardView(image: "2", category: "macOS", heading: "Building a Simple Editing App", author: "Simon NG")
CardView(image: "3", category: "watchOS", heading: "This is a Test Thing with Me", author: "Simon NG")
CardView(image: "4", category: "iOS", heading: "This is most use full thing ", author: "Simon NG")
}
.frame(width: 300)
}
}
2.6 按钮、标签&渐变层
var body: some View {
// 这是一个最简单的按钮,它包含点击事件以及外观
// 可以看出其与UIKit中的butttong差异还是比较大的——关于外观设置部分
Button(action:{
print("click me")
}) {
Text("Button")
}
}
// 以下代码效果如图:按钮1
Button(action:{
print("click me")
}) {
Text("Button")
.padding()
.background(Color.purple)
.foregroundColor(.white)
.font(.title)
}
// 以下代码实现了按钮2的效果
Button(action:{
print("click me")
}) {
Text("Button")
.padding(.all, 30)
.foregroundColor(.purple)
.background(Color.white)
.font(.title)
.padding(.all, 10)
.background(Color.purple)
}
// 以下代码使用另外一种方式实现了按钮2的效果
Button(action:{
print("click me")
}) {
Text("Button")
.foregroundColor(.purple)
.font(.title)
.padding()
.border(Color.purple, width: 5)
}
// 以下代码实现了按钮3的效果
Button(action:{
print("click me")
}) {
Text("Button")
.foregroundColor(.white)
.font(.title)
.padding()
.background(Color.purple)
.padding(10)
.border(Color.purple, width: 5)
}
// 以下代码实现了按钮4的效果
Button(action:{
print("click me")
}) {
if #available(iOS 15.0, *) {
Text("Button")
.fontWeight(.bold)
.font(.title)
.padding()
.background(Color.purple)
.cornerRadius(40)
.foregroundColor(Color.white)
.padding(10)
.overlay {
RoundedRectangle(cornerRadius: 40)
.stroke(Color.purple, lineWidth: 5)
}
} else {
// Fallback on earlier versions
}
}
// 以下按钮实现了带图标的效果,如下图,按钮4所示
Button(action:{
print("click me")
}) {
HStack {
Image(systemName: "trash")
.font(.largeTitle)
.foregroundColor(.white)
Text("Delete")
.font(.largeTitle)
.foregroundColor(Color.white)
}
.padding()
.background(Color.red)
.cornerRadius(20)
}
// 以下代码使用iOS14新增的Label组件实现了相同的功能
Button(action:{
print("click me")
}) {
Label (
title:{
Text("Delete")
.font(.largeTitle)
.foregroundColor(Color.white)
},
icon: {
Image(systemName: "trash")
.font(.largeTitle)
.foregroundColor(.white)
}
)
.padding()
.background(Color.red)
.cornerRadius(20)
}
// 以下代码实现了渐变背景的按钮,如图按钮5所示
Button(action:{
print("click me")
}) {
Label (
title:{
Text("Delete")
.font(.largeTitle)
.foregroundColor(Color.white)
},
icon: {
Image(systemName: "trash")
.font(.largeTitle)
.foregroundColor(.white)
}
)
.padding()
.background(LinearGradient(gradient:Gradient(colors: [Color.red, Color.blue]), startPoint: .leading, endPoint: .trailing))
.cornerRadius(20)
}
// 某些情况下,想要自定义符合某种要求的按钮并广泛复用,可以采用buttonStyle方式来定义
// 最终效果如按钮6所示
struct SwiftUIButton: View {
var body: some View {
Button(action:{
print("click me")
}) {
HStack {
Image(systemName: "trash")
.font(.largeTitle)
Text("Delete")
.font(.largeTitle)
}
}
.buttonStyle(GradientBackgroundStyle())
}
}
struct GradientBackgroundStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.label
.frame(minWidth: 0, maxWidth: .infinity)
.padding()
.foregroundColor(.white)
.background(LinearGradient(gradient:Gradient(colors: [Color.red, Color.blue]), startPoint: .leading, endPoint: .trailing))
.cornerRadius(40)
.padding(.horizontal, 20)
}
}
struct SwiftUIButton_Previews: PreviewProvider {
static var previews: some View {
SwiftUIButton()
}
}
2.7 状态绑定
在编写UI界面相关的额代码时,经常需要根据用户的操作、系统的状态等改变界面。采用UIKit编码时,针对此种情况,一般是需要编写一套创建UI的代码,当用户操作发生或内部状态发生变化后,再执行更新UI的代码。由于创建于更新UI的代码不是相同的,很可能会导致多种UI的错误。在SwiftUI中,通过@State@Binding这两个关键字(其实他们是属性包装器)来实现UI的创建于更新过程的一致。以下将通过实例来演示如何使用。
struct SwiftUIState: View {
// 以State标识的属性在body范围内的修改,会自动触发UI刷新
private @State var isPlaying = false
var body: some View {
Button (action:{
print("click me")
isPlaying.toggle()
}) {
Image(systemName: isPlaying ? "stop.circle.fill" : "play.circle.fill")
.font(.system(size: 50))
.foregroundColor(isPlaying ? .red : .green)
}
}
}
如果有三个按钮,且他们都能展示当前用户点击次数,为了实现这个功能需要用到@State与@Binding。
struct SwiftUIState: View {
@State var counter = 1
var body: some View {
VStack {
CounterButton(counter: $counter, color: .red) // 注意,这里要加$
CounterButton(counter: $counter, color: .green)
CounterButton(counter: $counter, color: .blue)
}
}
}
struct SwiftUIState_Previews: PreviewProvider {
static var previews: some View {
SwiftUIState()
}
}
struct CounterButton: View {
@Binding var counter: Int
var color: Color
var body: some View {
Button (action:{
print("click me")
counter += 1
}) {
if #available(iOS 15.0, *) {
Circle()
.frame(width: 100, height: 100)
.foregroundColor(color)
.overlay {
Text("\(counter)")
.font(.system(size:100, weight: .bold, design: .rounded))
.foregroundColor(.white)
}
} else {
// Fallback on earlier versions
}
}
}
}
注意
在CounterButton中添加@Binding,代表其中的counter,是由外部来传递而来的。可以简单理解为传递了一个指针。
2.8 自绘
在SwiftUI中可以使用Path进行一些绘制,其与NSBezierPath类似。
// 效果如path1所示
struct SwiftUIPath: View {
var body: some View {
VStack {
Path { path in
path.move(to: CGPoint(x: 20, y: 20))
path.addLine(to: CGPoint(x: 300, y: 20))
path.addLine(to: CGPoint(x: 300, y: 100))
path.addLine(to: CGPoint(x: 20, y: 100))
}.fill(.red)
Path { path in
path.move(to: CGPoint(x: 20, y: 20))
path.addLine(to: CGPoint(x: 300, y: 20))
path.addLine(to: CGPoint(x: 300, y: 100))
path.addLine(to: CGPoint(x: 20, y: 100))
}.stroke(.blue, lineWidth: 3)
Path { path in
path.move(to: CGPoint(x: 20, y: 20))
path.addLine(to: CGPoint(x: 300, y: 20))
path.addLine(to: CGPoint(x: 300, y: 100))
path.addLine(to: CGPoint(x: 20, y: 100))
path.closeSubpath()
}.stroke(.purple, lineWidth: 3)
Path() { path in
path.move(to: CGPoint(x: 20, y: 60))
path.addLine(to: CGPoint(x: 40, y: 60))
path.addQuadCurve(to: CGPoint(x: 210, y: 60), control: CGPoint(x: 125, y: 0))
path.addLine(to:CGPoint(x:230, y:60))
path.addLine(to:CGPoint(x:230, y:100))
path.addLine(to:CGPoint(x:20, y:100))
}
.fill(.purple)
// fill 和 stroke不可同时使用,为了实现既填充又描边,可以采用ZStack
ZStack {
Path() { path in
path.move(to: CGPoint(x: 20, y: 60))
path.addLine(to: CGPoint(x: 40, y: 60))
path.addQuadCurve(to: CGPoint(x: 210, y: 60), control: CGPoint(x: 125, y: 0))
path.addLine(to:CGPoint(x:230, y:60))
path.addLine(to:CGPoint(x:230, y:100))
path.addLine(to:CGPoint(x:20, y:100))
}
.fill(.purple)
Path() { path in
path.move(to: CGPoint(x: 20, y: 60))
path.addLine(to: CGPoint(x: 40, y: 60))
path.addQuadCurve(to: CGPoint(x: 210, y: 60), control: CGPoint(x: 125, y: 0))
path.addLine(to:CGPoint(x:230, y:60))
path.addLine(to:CGPoint(x:230, y:100))
path.addLine(to:CGPoint(x:20, y:100))
path.closeSubpath()
}.stroke(.red, lineWidth: 3)
}
Path { path in
path.move(to: CGPoint(x: 100, y: 100))
path.addArc(center: .init(x: 100, y: 100)
, radius: 50
, startAngle: Angle(degrees: 0.0)
, endAngle: Angle(degrees: 90)
, clockwise: true)
}.fill(.red)
}
}
}
// 通过实现Shape协议来实现复用图形,参加path2
struct SwiftUIPath: View {
var body: some View {
Button(action: {
}) {
Text("自定义Shape")
.font(.system(.title, design: .rounded))
.bold()
.foregroundColor(.white)
.frame(width: 250, height: 50)
.background(CustomShape().fill(Color.red))
}
}
}
struct CustomShape: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
path.move(to: CGPoint(x: 0, y:9))
path.addQuadCurve(to: CGPoint(x: rect.size.width, y:0)
, control: CGPoint(x:rect.size.width/2, y:0 - rect.size.width * 0.1))
path.addRect(CGRect(x:0, y:0, width: rect.size.width, height: rect.size.height))
return path;
}
}
除此之外,SwiftUI还内置了Circle、Rectangle、RounderRectangle、Ellipse等图形。
2.9 动画与转场
在SwiftUI中通过animation修饰器来实现隐式动画,通过withAnimation块中编写动画代码来实现显式动画。
// 效果如animation所示
struct SwiftUIAnimation: View {
@State private var circleColorChange = false
@State private var heartColorChange = false
@State private var heartSizeChange = false
var body: some View {
VStack {
// 隐式动画
ZStack {
Circle()
.frame(width: 200, height: 200)
.foregroundColor(circleColorChange ? Color(.systemGray6) : .red)
.animation(.default) // 1
Image(systemName: "heart.fill")
.foregroundColor(heartColorChange ? .red : .white)
.font(.system(size: 100))
.scaleEffect(heartSizeChange ? 1.0 : 0.5)
.animation(.default) // 2
}
// .animation(.default) 在这里写也可以,如果在这里写的话,需要把1、2两行删除
.onTapGesture {
self.circleColorChange.toggle()
self.heartSizeChange.toggle()
self.heartColorChange.toggle()
}
// 显示动画
ZStack {
Circle()
.frame(width: 200, height: 200)
.foregroundColor(circleColorChange ? Color(.systemGray6) : .red)
Image(systemName: "heart.fill")
.foregroundColor(heartColorChange ? .red : .white)
.font(.system(size: 100))
.scaleEffect(heartSizeChange ? 1.0 : 0.5)
}
.onTapGesture {
// 动画效果1
// withAnimation(.default) {
// self.circleColorChange.toggle()
// self.heartSizeChange.toggle()
// self.heartColorChange.toggle()
// }
// 动画效果2
// withAnimation(.spring(response: 0.3, dampingFraction: 0.3, blendDuration: 0.3)) {
// self.circleColorChange.toggle()
// self.heartSizeChange.toggle()
// self.heartColorChange.toggle()
// }
// 动画效果3,新型大小不做动画
withAnimation(.spring(response: 0.3, dampingFraction: 0.3, blendDuration: 0.3)) {
self.circleColorChange.toggle()
self.heartColorChange.toggle()
}
self.heartSizeChange.toggle()
}
}
}
}
// 以下是一些更具实际意义的动画实例,参见anamition2
struct SwiftUIAnimation2: View {
@State private var isLoading = false
@State private var progess: Double = 0.0
@State private var recordBegin = false
@State private var recording = false
var body: some View {
VStack {
Circle()
.trim(from: 0, to: 0.7)
.stroke(Color.green, lineWidth: 5)
.frame(width: 100, height: 100)
.rotationEffect(Angle(degrees: isLoading ? 360 : 0))
.animation(Animation.linear(duration: 1).repeatForever(autoreverses: false))
.onAppear {
isLoading = true
}
Spacer()
.frame(width: .infinity, height: 50)
ZStack {
RoundedRectangle(cornerRadius: 3)
.stroke(Color(.systemGray5), lineWidth: 3)
.frame(width: 250, height: 3)
RoundedRectangle(cornerRadius: 3)
.stroke(Color.green, lineWidth: 3)
.frame(width: 30, height: 3)
.offset(x: isLoading ? 110 : -110, y: 0)
.animation(Animation.linear(duration: 1).repeatForever(autoreverses: false))
}
Spacer()
.frame(width: .infinity, height: 50)
ZStack {
Text("\(Int(progess * 100))%")
.font(.system(.title, design: .rounded))
.bold()
Circle()
.stroke(Color(.systemGray5), lineWidth: 10)
.frame(width: 150, height: 150)
Circle()
.trim(from: 0, to: progess)
.stroke(Color.green, lineWidth: 10)
.frame(width: 150, height: 150)
.rotationEffect(Angle(degrees: -90))
}
.onAppear {
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { timer in
self.progess += 0.05
if self.progess >= 1.0 {
timer.invalidate()
}
}
}
Spacer()
.frame(width: .infinity, height: 50)
HStack {
ForEach(0...5, id:\.self) { index in
Circle()
.frame(width: 10, height: 10)
.foregroundColor(.green)
.scaleEffect(self.isLoading ? 0 : 1)
.animation(.linear(duration: 0.6).repeatForever().delay(0.2 * Double(index)))
}
}
Spacer()
.frame(width: .infinity, height: 50)
ZStack {
if #available(iOS 15.0, *) {
RoundedRectangle(cornerRadius: recordBegin ? 30 : 5)
.frame(width: recordBegin ? 60 : 250, height: 60)
.foregroundColor(recordBegin ? .red : .green)
.overlay {
Image(systemName: "mic.fill")
.font(.system(.title))
.foregroundColor(.white)
.scaleEffect(recording ? 0.7 : 1)
}
RoundedRectangle(cornerRadius: recordBegin ? 35 : 10)
.trim(from: 0, to: recordBegin ? 0.0001 : 1)
.stroke(lineWidth: 5)
.frame(width: recordBegin ? 70 : 260, height: 70)
.foregroundColor(.green)
} else {
// Fallback on earlier versions
}
}
.onTapGesture {
withAnimation {
recordBegin.toggle()
}
withAnimation(.spring().repeatForever().delay(0.5)) {
recording.toggle()
}
}
}
}
}
// 实现了转场效果
struct SwiftUITransition: View {
@State private var show = false
var body: some View {
VStack {
if #available(iOS 15.0, *) {
RoundedRectangle(cornerRadius: 10)
.frame(width: 300, height: 300)
.foregroundColor(.green)
.overlay {
Text("Show details")
.font(.system(.largeTitle, design: .rounded))
.bold()
.foregroundColor(.white)
}.onTapGesture {
withAnimation {
show.toggle()
}
}
if show {
RoundedRectangle(cornerRadius: 10)
.frame(width: 300, height: 300)
.foregroundColor(.purple)
.overlay {
Text("Well, here is the details")
.font(.system(.largeTitle, design: .rounded))
.bold()
.foregroundColor(.white)
}
// .transition(.scale(scale: 0, anchor: .bottom))
// .transition(.slide)
// .transition(AnyTransition.offset(x: -600, y: 0).combined(with: .slide).combined(with: .opacity))
// 进入与退出时使用不同的动画
.transition(.asymmetric(insertion: .scale(scale: 0.1, anchor: .bottom), removal: .offset(x: -600, y: 0)))
}
} else {
// Fallback on earlier versions
}
}
}
}
2.10 列表
// 以下是简单的列表,效果见 simpleTableView
struct SwiftUIList: View {
var body: some View {
VStack {
List {
Text("Item 1")
Text("Item 2")
Text("Item 3")
Text("Item 4")
}
List {
ForEach(1...4, id:\.self) { index in
Text("Item \(index)")
}
}
List(1...4, id: \.self) {
Text("Item \($0)") //$0是闭包的一种简略写法,其在这里代表index
}
}
}
}
// 以下代码展示了动态的列表,效果参见listView
struct SwiftUIList: View {
var tankName = ["M1A2", "T90", "T72", "99A", "LieKeLieEr", "BaoEr", "09", "10"]
var tankImage = ["1", "2", "3", "4", "5", "6", "7", "8"]
var tankInfo = [
ATankInfo(name: "M1A2", image: "1"),
ATankInfo(name: "T90", image: "2"),
ATankInfo(name: "T72", image: "3"),
ATankInfo(name: "99A", image: "4"),
ATankInfo(name: "LieKeLieEr", image: "5"),
ATankInfo(name: "BaoEr", image: "6"),
ATankInfo(name: "09", image: "7"),
ATankInfo(name: "10", image: "8"),
]
var tankInfo2 = [
ATankInfo2(name: "M1A2", image: "1"),
ATankInfo2(name: "T90", image: "2"),
ATankInfo2(name: "T72", image: "3"),
ATankInfo2(name: "99A", image: "4"),
ATankInfo2(name: "LieKeLieEr", image: "5"),
ATankInfo2(name: "BaoEr", image: "6"),
ATankInfo2(name: "09", image: "7"),
ATankInfo2(name: "10", image: "8"),
]
var tankInfo3 = [
ATankInfo3(name: "M1A2", image: "1"),
ATankInfo3(name: "T90", image: "2"),
ATankInfo3(name: "T72", image: "3"),
ATankInfo3(name: "99A", image: "4"),
ATankInfo3(name: "LieKeLieEr", image: "5"),
ATankInfo3(name: "BaoEr", image: "6"),
ATankInfo3(name: "09", image: "7"),
ATankInfo3(name: "10", image: "8"),
]
var body: some View {
VStack(alignment: .leading, spacing: 1) {
List(tankName.indices, id:\.self) { index in
HStack {
Image(tankImage[index])
.resizable()
.frame(width: 40, height: 40)
.cornerRadius(5)
Text(tankName[index])
.font(.system(size: 15))
.foregroundColor(.green)
}
}
List(tankInfo, id: \.name) { aTankInfo in
HStack {
Image(aTankInfo.image)
.resizable()
.frame(width: 40, height: 40)
.cornerRadius(5)
Text(aTankInfo.name)
.font(.system(size: 15))
.foregroundColor(.green)
}
}
List(tankInfo2, id: \.id) { aTankInfo in
HStack {
Image(aTankInfo.image)
.resizable()
.frame(width: 40, height: 40)
.cornerRadius(5)
Text(aTankInfo.name)
.font(.system(size: 15))
.foregroundColor(.green)
}
}
List(tankInfo3) { aTankInfo in
HStack {
Image(aTankInfo.image)
.resizable()
.frame(width: 40, height: 40)
.cornerRadius(5)
Text(aTankInfo.name)
.font(.system(size: 15))
.foregroundColor(.green)
}
}
List(tankInfo3) { aTankInfo in
BasicRowView(aTankInfo: aTankInfo)
}
List(tankInfo3) { aTankInfo in
BigImageRow(aTankInfo: aTankInfo)
}
List(tankInfo3.indices) { index in
if (0...1).contains(index) {
BigImageRow(aTankInfo: tankInfo3[index])
} else {
BasicRowView(aTankInfo: tankInfo3[index])
}
}
}
}
}
struct ATankInfo {
var name: String
var image: String
}
struct ATankInfo2 {
let id = UUID()
var name: String
var image: String
}
struct ATankInfo3: Identifiable {
let id = UUID()
var name: String
var image: String
}
struct SwiftUIList_Previews: PreviewProvider {
static var previews: some View {
SwiftUIList()
}
}
struct BasicRowView: View {
var aTankInfo:ATankInfo3
var body: some View {
HStack {
Image(aTankInfo.image)
.resizable()
.frame(width: 40, height: 40)
.cornerRadius(5)
Text(aTankInfo.name)
.font(.system(size: 15))
.foregroundColor(.green)
}
}
}
struct BigImageRow : View {
var aTankInfo:ATankInfo3
var body: some View {
VStack {
Image(aTankInfo.image)
.resizable()
.frame(width: .infinity, height: 100)
.cornerRadius(5)
Text(aTankInfo.name)
.font(.system(size: 15))
.foregroundColor(.green)
}
}
}