开发随笔

iOS开发常用之图像浏览及处理:https://cloud.tencent.com/developer/article/1372590

在swiftui中的View,需要根据其下的一个isToggled: Bool 属性更新界面,不能用@ObservedObject ,那怎么修饰该属性,

在 SwiftUI 中,如果你需要根据一个 Bool 类型的属性更新界面,但又不能使用 @ObservedObject (可能是因为这个属性是局部变量,或者它不是 ObservableObject 的属性),你可以使用 @State 或 @Binding 属性包装器来实现。使用 @State @State 是一个属性包装器,用于定义一个私有的状态变量,这个变量的值改变时,视图会自动更新。它适用于定义在视图或视图模型内部的状态。

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

struct MyView: View {
@State private var isToggled: Bool = false

var body: some View {
Toggle("Toggle", isOn: $isToggled)
if isToggled {
Text("Toggle is on")
} else {
Text("Toggle is off")
}
}
}

在这个例子中, isToggled 是一个 @State 变量,当它被修改时(如通过 Toggle 控件),视图会自动更新。使用 @Binding 如果你需要在多个视图之间共享状态,或者状态定义在父视图中,你可以使用 @Binding 。 @Binding 允许你创建一个指向父视图中 @State 或 @ObservableObject 属性的引用。

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

struct ParentView: View {
@State private var isToggled: Bool = false

var body: some View {
MyView(isToggled: $isToggled)
}
}

struct MyView: View {
@Binding var isToggled: Bool

var body: some View {
Toggle("Toggle", isOn: $isToggled)
if isToggled {
Text("Toggle is on")
} else {
Text("Toggle is off")
}
}
}

在这个例子中, ParentView 定义了一个 @State 变量 isToggled ,并将其通过 @Binding 传递给 MyView 。这样,当 MyView 中的 Toggle 控件改变 isToggled 的值时, ParentView 中的 isToggled 也会更新,从而导致视图的更新。这两种方法都可以让你根据 Bool 类型的属性更新 SwiftUI 视图,而不需要使用 @ObservedObject 。选择哪种方法取决于你的具体需求,比如状态是否需要在多个视图间共享,或者状态是否需要持久化。

请使用swift,自定义一个视图,当外部的shouldUpdateUI变化的时候,该视图更新

在SwiftUI中,如果你想要创建一个自定义视图,该视图可以根据外部的 shouldUpdateUI 布尔值的变化来更新,你可以使用 @Binding 来实现这个功能。下面是一个简单的自定义视图示例,它接受一个 @Binding 参数,并根据这个参数的值来决定是否显示某些内容或更新UI。

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
41
42
43
44
45
46
47
import SwiftUI

// 自定义视图
struct UpdateableView: View {
@Binding var shouldUpdateUI: Bool

var body: some View {
VStack {
if shouldUpdateUI {
Text("UI is being updated!")
.foregroundColor(.blue)
} else {
Text("UI is up to date.")
.foregroundColor(.green)
}
}
.onAppear {
// 可以在视图出现时执行某些操作
print("View appears. Should update UI? $shouldUpdateUI)")
}
.onChange(of: shouldUpdateUI) { newValue in
// 当 shouldUpdateUI 发生变化时执行某些操作
print("shouldUpdateUI changed to $newValue)")
}
}
}

// 使用自定义视图的示例
struct ContentView: View {
@State private var shouldUpdateUI = false

var body: some View {
VStack {
Toggle("Toggle UI Update", isOn: $shouldUpdateUI)
UpdateableView(shouldUpdateUI: $shouldUpdateUI)
}
}
}

@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}

在这个例子中:

• UpdateableView 是一个自定义视图,它接受一个 @Binding var shouldUpdateUI: Bool 参数。这个参数允许 UpdateableView 监听 shouldUpdateUI 值的变化,并根据这个值来更新其显示的内容。

• 在 UpdateableView 中,使用了一个 VStack 来包含一些文本视图。根据 shouldUpdateUI 的值,显示不同的文本。

• .onAppear 修饰符用于在视图出现时打印一条消息,表明视图已经出现,并显示当前 shouldUpdateUI 的状态。• .onChange(of:) 修饰符用于监听 shouldUpdateUI 值的变化,并在变化时打印一条消息。• ContentView 是一个包含 Toggle 和 UpdateableView 的视图。 Toggle 允许用户切换 shouldUpdateUI 的值,从而触发 UpdateableView 的更新。这个示例展示了如何创建一个可以根据外部状态变化来更新的自定义视图。你可以根据需要调整 UpdateableView 中的UI和逻辑。

通过这种方式 在ContentView使用 UpdateableView,需要写成 UpdateableView(shouldUpdateUI: $shouldUpdateUI),有没有其他方式 uiUpdateObserver

如果你想要避免在 ContentView 中使用 UpdateableView(shouldUpdateUI: $shouldUpdateUI) 这样的写法,可以考虑以下几种替代方案:1. 使用 @EnvironmentObject 或 @ObservedObject 你可以将 shouldUpdateUI 的状态提升到一个更高的层级,比如使用 @EnvironmentObject 或 @ObservedObject ,这样 UpdateableView 就可以直接访问这个状态,而不需要通过参数传递。

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
import SwiftUI

class EnvironmentModel: ObservableObject {
@Published var shouldUpdateUI: Bool = false
}

struct UpdateableView: View {
@EnvironmentObject var environmentModel: EnvironmentModel

var body: some View {
VStack {
if environmentModel.shouldUpdateUI {
Text("UI is being updated!")
.foregroundColor(.blue)
} else {
Text("UI is up to date.")
.foregroundColor(.green)
}
}
}
}

struct ContentView: View {
@StateObject var environmentModel = EnvironmentModel()

var body: some View {
UpdateableView()
.environmentObject(environmentModel)
}
}

@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}

在这个例子中, EnvironmentModel 是一个包含 shouldUpdateUI 状态的 ObservableObject 。 UpdateableView 通过 @EnvironmentObject 直接访问这个状态,而不需要通过参数传递。

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

class UIUpdateModel: ObservableObject {
@Published var shouldUpdateUI: Bool = false
}


struct ContentView: View {
//@ObservedObject var model: UIUpdateModel /// 方法1:
@EnvironmentObject var model: UIUpdateModel /// 方法2:

var body: some View {
Text("Should Update UI: $model.shouldUpdateUI)")
}
}

使用swiftui, 在一个视图A中,有一个dateModel的属性对象。且视图A中有子视图B,希望只接收dateModel的date属性。视图A中还有另一个子视图C,点击子视图C会更新dateModel的date值为当前时间,现在希望视图B也能够更新。我的要求是DateModel 不继承ObservableObject, 其属性也都不能用@Published修饰

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
class DateModel {
var date: Date = Date()
}

struct ContentView: View {
@EnvironmentObject var dateModel: DateModel
@State private var date: Date? // 可选类型,无需初始值

var body: some View {
VStack {
Text("主视图 A").font(.headline)
if let date = date {
SubViewB(date: Binding(get: { date }, set: { self.date = $0 }))
} else {
Text("日期尚未加载")
}
SubViewC(dateModel: dateModel) {
self.date = dateModel.date
}
}
.onAppear {
self.date = dateModel.date // 初始化时同步值
}
.padding()
}
}

这时候如果要将ContentView作为SuperContainer的子视图怎么办,要求在 SuperContainer中使用ContentView的时候不用传入参数。如何 让ContentView 中的 @State private var date: Date 可以不提供默认值吗

在 SwiftUI 中,@State 属性通常需要一个初始值,因为它是用来管理视图状态的本地存储。SwiftUI 需要在视图的生命周期中始终能访问到一个有效的值。如果不提供默认值,编译器会报错。

替代方法

1
2
// 方法1:
@State private var date: Date? // 可选类型,无需初始值

如果您确实希望在 ContentView 中的 date 初始化时无需默认值,可以考虑以下方法:

子视图需要父视图中模型中的一个属性来更新界面

子视图 B 接收 @Binding 的 date,只负责显示,不直接修改:

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
struct SubViewB: View {
@Binding var date: Date

var body: some View {
Text("当前日期: \(date, formatter: dateFormatter)")
.padding()
.background(Color.yellow.opacity(0.3))
.cornerRadius(10)
}
}

private let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .medium
return formatter
}()


struct SubViewC: View {
var dateModel: DateModel
var onUpdate: () -> Void // 回调通知主视图

var body: some View {
Button(action: {
dateModel.date = Date() // 更新模型
onUpdate() // 通知主视图更新
}) {
Text("更新日期到当前时间")
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(10)
}
}
}

方法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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81

struct ContentView: View {
@EnvironmentObject var dateModel: DateModel
@State private var date: Date? // 可选类型,无需初始值

var body: some View {
VStack {
Text("主视图 A").font(.headline)
if let date = date {
SubViewB(date: Binding(get: { date }, set: { self.date = $0 }))
}
SubViewC(dateModel: dateModel) {
self.date = dateModel.date
}
}
.onAppear {
self.date = dateModel.date // 初始化时同步值
}
.padding()
}
}


// 优化:可以省去额外多开辟了一个 date 变量。
struct ContentView: View {
@EnvironmentObject var dateModel: DateModel
@EnvironmentObject var dateSettingViewUpdateObserver: UIUpdateModel

var body: some View {
VStack {
Text("主视图 A").font(.headline)

SubViewB(date: Binding(get: { dateModel.date }, set: { dateModel.date = $0 })) // 1、保证视图触发更新时候使用到新数据
.environmentObject(uiUpdateObserver) // 2、保证能触发视图更新

SubViewC(dateModel: dateModel) {
dateModel.date = Date() // 在这里更新日期


}
}
.padding()
}
}

func updateUI() {
dateModel.date = dateModel.date.addingTime(hours: 24)

//shouldUpdateUI.toggle()
uiUpdateObserver.updateListeners()
}

// 此时C的操作不会引起B的变化,需要额外处理
struct SubViewC: View {
//@Binding var shouldUpdateUI: Bool
@EnvironmentObject var uiUpdateObserver: UIUpdateModel

var dateModel: DateModel
var onUpdate: () -> Void // 回调通知主视图

var body: some View {
Button(action: {
dateModel.date = Date() // 更新模型
onUpdate() // 通知主视图更新
}) {
Text("更新日期到当前时间")
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(10)
}
}
}

class UIUpdateModel: ObservableObject {
@Published var shouldUpdate: Bool = false

func updateListeners() {
shouldUpdate.toggle()
}
}