swiftUI

OK,我们先学习官方的swiftUI教程

Creating and combining views

创建一个项目

image-20250326205350722

  • interface中的storyboard可以使用swift或者objective-C进行编写

项目初建

File > New > File (文件 > New > File) 以再次打开模板选择器。在用户界面部分,选择“SwiftUI 视图”,然后单击下一步。将文件命名为 CircleImage.swift,然后单击 Create。

image-20250326214928242

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import SwiftUI
// `@main` 属性标识应用程序的入口点。
@main
struct LandmarksApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
// 视图声明结构和预览要符合 `View` 协议
struct ContentView: View {
// 结构的 `body` 属性返回一个或多个场景
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("Hello, world!")
}
.padding()
}
}
// preview 声明将为该视图创建预览。
#Preview {
ContentView()
}

@main 属性标识应用程序的入口点。

结构的 body 属性返回一个或多个场景

视图声明结构和预览要符合 View 协议

preview 声明将为该视图创建预览。

使用inspector 检查器调整视图

  • 默认情况下,画布以实时模式显示预览,实时模式可以与它们进行交互,但是不能编辑 **。在 Live 模式下工作,可以在 Source 中进行编辑时轻松跟踪视图行为.使用 Option 键单击并拖动控件查看周围区域。

  • 点击这个可以改为 Selectable (可选) 模式来启用编辑

    image-20250326205911628

  • 在Selectable (可选) 模式的预览中,按住 Command-Control 键点需要调整的模块调出结构化编辑弹出窗口,然后选择**“Show SwiftUI Inspector”。**检查器更改或移除修饰符时,Xcode 会立即更新您的代码以匹配

image-20250326210050507

stacks

body 属性仅返回单个视图。可以将多个视图组合并嵌入到堆栈中,这些堆栈将视图水平、垂直或从后到前分组在一起。

  • 按住 Control 键单击编辑器的模块以显示上下文菜单,然后选择“Embed in VStack”(嵌入到 VStack 中)。

    image-20250326210749320

  • 击 Xcode 窗口右上角的加号按钮 (+) 打开库,然后将 Text 视图拖动到代码中“Turtle Rock”文本视图正下方的位置。(这个好方便)

    image-20250326210934487

  • import SwiftUI
    
    struct ContentView: View {
        var body: some View {
            // 按视图的前导边缘对齐视图。
            // 默认情况下,堆栈沿其轴将其内容居中
            VStack(alignment: .leading) {
                Text("Turtle Rock")
                    .font(.title)
                HStack {
                    Text("Joshua Tree National Park")
                        .font(.subheadline)
                    // 将 Spacer 添加到包含两个文本视图的水平堆栈中来分隔 park 和 state。
                    Spacer()
                    Text("California")
                        .font(.subheadline)
                }
    
            }
            //使用 padding() 修饰符将vstack在两边有空隙
            .padding()
        }
    }
    
    #Preview {
        ContentView()
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26

    ## 添加一个圆形发光图片

    在项目文件的 Resources 文件夹中找到 `turtlerock@2x.jpg`;将其拖动到 Asset Catalog 的编辑器中。Resource随便改,但是Asset是固定位置。

    ```swift
    import SwiftUI

    struct CircleImage: View {
    var body: some View {
    // Image(_:)
    Image("turtlerock")
    // Circle 类型是一种形状
    .clipShape(Circle())
    // 创建另一个带有白色描边的圆圈,然后将其添加为叠加层
    // 白色在一个阴影上面,就会表现出发光的效果
    .overlay {
    Circle().stroke(.white, lineWidth: 4)
    }
    .shadow(radius: 7)
    }
    }

    #Preview {
    CircleImage()
    }

image-20250326214831233

使用MapKit

使用来自其他框架的 SwiftUI 视图-使用 MapKit 中的地图视图来渲染地图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import SwiftUI
import MapKit

struct MapView: View {
var body: some View {
// Map 视图,该视图采用您使用区域初始化的摄像机位置。
Map(initialPosition: .region(region))
}
// 用于保存映射的区域信息
private var region: MKCoordinateRegion {
MKCoordinateRegion(
center: CLLocationCoordinate2D(latitude: 34.011_286, longitude: -116.166_868),
span: MKCoordinateSpan(latitudeDelta: 0.2, longitudeDelta: 0.2)
)
}
}

#Preview {
MapView()
}

融合各个view

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
MapView()
.frame(height: 300)

CircleImage()
.offset(y: -130)
.padding(.bottom, -130)

VStack(alignment: .leading) {
Text("Turtle Rock")
.font(.title)
HStack {
Text("Joshua Tree National Park")
.font(.subheadline)
Spacer()
Text("California")
.font(.subheadline)
}

Divider()

Text("About Turtle Rock")
.font(.title2)
Text("Descriptive text goes here.")
}
.padding()

Spacer()
}
}
}

#Preview {
ContentView()
}

image-20250326215248169

Building lists and navigation

创建数据

首先将下载文件的 Resources 文件夹中的 landmark.json 拖动到项目的导航窗格中;在出现的对话框中,选择“Copy items if needed”和“Landmark”目标,然后单击“完成”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

import Foundation
import SwiftUI
import CoreLocation

// 依靠 Codable 协议的 Decodable 组件从文件中读取数据。
struct Landmark: Hashable, Codable {
var id: Int
var name: String
var park: String
var state: String
var description: String
private var imageName: String
// 属性设为私有,因为 Landmarks 结构的用户只关心图像本身
var image: Image {
Image(imageName)
}
private var coordinates: Coordinates
// 用于与 MapKit 框架交互的 locationCoordinate 属性
var locationCoordinate: CLLocationCoordinate2D {
CLLocationCoordinate2D(
latitude: coordinates.latitude,
longitude: coordinates.longitude)
}
struct Coordinates: Hashable, Codable {
var latitude: Double
var longitude: Double
}
}

创建一个 load(_:) 方法,用于从应用程序的 main bundle 中获取具有给定名称的 JSON 数据。

load 方法依赖于返回类型对 Decodable 协议的一致性,该协议是 Codable 协议的一个组件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import Foundation

func load<T: Decodable>(_ filename: String) -> T {
let data: Data

guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
else {
fatalError("Couldn't find \(filename) in main bundle.")
}

do {
data = try Data(contentsOf: file)
} catch {
fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
}

do {
let decoder = JSONDecoder()
return try decoder.decode(T.self, from: data)
} catch {
fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
}
}

一个简单的列表中的一个元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import SwiftUI

struct LandmarkRow: View {
var landmark: Landmark
var body: some View {
HStack {
landmark.image
.resizable()
.frame(width: 50, height: 50)
Text(landmark.name)

Spacer()
}
}
}
#Preview {
LandmarkRow(landmark: landmarks[0])
}

image-20250326220000851

改成一个滚动的列表

1
2
3
4
5
6
7
// `Group` 是用于对视图内容进行分组的容器。Xcode 在画布中将组的子视图堆叠为一个预览。
#Preview {
Group {
LandmarkRow(landmark: landmarks[0])
LandmarkRow(landmark: landmarks[1])
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import SwiftUI
// 通过从闭包返回 LandmarkRow 来完成动态生成的列表。
struct LandmarkList: View {
var body: some View {
List(landmarks, id: \.id) { landmark in
LandmarkRow(landmark: landmark)
}
}
}

#Preview {
LandmarkList()
}

// 切换到 `Landmark.swift` 并声明符合 `Identifiable` 协议。`Landmark` 数据已具有 `Identifiable` 协议所需的 `id` 属性;您只需在读取数据时添加一个属性来解码它。
import SwiftUI
struct LandmarkList: View {
var body: some View {
// 这里不再需要id而是只需要landmark
List(landmarks) { landmark in
LandmarkRow(landmark: landmark)
}
}
}
#Preview {
LandmarkList()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import Foundation
import SwiftUI
import CoreLocation
// 这里只需要加一个identifiable协议
struct Landmark: Hashable, Codable, Identifiable {
var id: Int
var name: String
var park: String
var state: String
var description: String

private var imageName: String
var image: Image {
Image(imageName)
}

private var coordinates: Coordinates
var locationCoordinate: CLLocationCoordinate2D {
CLLocationCoordinate2D(
latitude: coordinates.latitude,
longitude: coordinates.longitude)
}

struct Coordinates: Hashable, Codable {
var latitude: Double
var longitude: Double
}
}

image-20250326220607618

创建列表元素和另一个页面的链接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import SwiftUI

struct LandmarkList: View {
var body: some View {
NavigationSplitView {
// 元素是什么,这个只是数据
List(landmarks) { landmark in
// 指向到哪里
NavigationLink {
//这个是详情页
LandmarkDetail()
} label: {
// 列表中的图表是什么
LandmarkRow(landmark: landmark)
}
}
//上方的标题
.navigationTitle("Landmarks")
} detail: {
// 这个只在轻轻触碰但是没触碰上的时候其作用
Text("Select a Landmark")
}
}
}

#Preview {
LandmarkList()
}

image-20250326221002629

将数据传递到子视图

LandmarkDetail 视图仍然使用硬编码的详细信息来显示其地标。与 LandmarkRow 一样,LandmarkDetail 类型及其包含的视图需要使用 landmark 属性作为其数据源。

就是说刚才的list只有列表元素接收到landmark这个数据,现在子视图也需要这个数据

这个不看,就是简单的工程问题

渲染不同设备配置的列表视图预览

Handling user input 处理用户输入

如果用户点击了收藏,则本地要储存收藏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import SwiftUI

struct LandmarkList: View {
// 添加一个名为 showFavoritesOnly 的 @State 属性,其初始值设置为 false
//当您更改视图的结构(例如添加或修改属性)时,画布会自动刷新。
@State private var showFavoritesOnly = false
var filteredLandmarks: [Landmark] {
landmarks.filter { landmark in
(!showFavoritesOnly || landmark.isFavorite)
}
}
var body: some View {
NavigationSplitView {
List {
// 先设置一个恩牛
Toggle(isOn: $showFavoritesOnly) {
Text("Favorites only")
}
// 恩牛下面才是浏览
ForEach(filteredLandmarks) { landmark in
NavigationLink {
LandmarkDetail(landmark: landmark)
} label: {
LandmarkRow(landmark: landmark)
}
}
}
// 如果这个值改变则使用动画
.animation(.default, value: filteredLandmarks)
.navigationTitle("Landmarks")
} detail: {
Text("Select a Landmark")
}
}
}


#Preview {
LandmarkList()
}

image-20250326221921200

使用 observation 进行存储

在视图中采用 model 对象

为每个路标创建收藏夹按钮