简体   繁体   English

SwiftUI:根据环境初始化 ObservableObject

[英]SwiftUI: Init ObservableObject based on Environment

In this example, the blue rectangle should be initially visible on devices with .regular size class and hidden on devices with .compact size class.在此示例中,蓝色矩形最初应在 .regular 大小 class 的设备上可见,而在.regular大小 class 的设备上.compact隐藏。

I'm using an ObservableObject called Settings and the @Published variable isVisible to manage visibilty of the rectangle.我正在使用一个名为SettingsObservableObject@Published变量isVisible来管理矩形的可见性。 My problem is that I don't know how I can init Settings with the correct horizontalSizeClass from my ContentView .我的问题是我不知道如何从ContentView使用正确的horizontalSizeClass ntalSizeClass 初始化Settings Right now I am using .onAppear to change the value of isVisible but this triggers .onReceive .现在我正在使用.onAppear来更改isVisible的值,但这会触发.onReceive On compact devices this causes the rectangle to be visible and fading out when the view is presented instead of being invisible right away.在紧凑型设备上,这会导致矩形在视图呈现时可见并淡出,而不是立即不可见。

How can I init Settings based on Environment values like horizontalSizeClass so that isVisible is correct from the start?如何根据 Horizo horizontalSizeClass等环境值初始化Settings ,以便isVisible从一开始就正确?

struct ContentView: View {
    @Environment(\.horizontalSizeClass) var horizontalSizeClass
    @StateObject var settings = Settings()
    @State var opacity: CGFloat = 1

    var body: some View {
        VStack {
            Button("Toggle Visibility") {
                settings.isVisible.toggle()
            }
            .onReceive(settings.$isVisible) { _ in
                withAnimation(.linear(duration: 2.0)) {
                    opacity = settings.isVisible ? 1 : 0
                }
            }
            Rectangle()
                .frame(width: 100, height: 100)
                .foregroundColor(.blue)
                .opacity(opacity)
        }
        .onAppear {
            settings.isVisible = horizontalSizeClass == .regular // too late
        }
    }
}

class Settings: ObservableObject {
    @Published var isVisible: Bool = true // can't get size class here
}

The rectangle should not be visible on start:矩形在开始时不应该是可见的:

在此处输入图像描述

withAnimation should be done on the Button action changing the state, eg withAnimation应该在更改 state 的按钮操作上完成,例如

import SwiftUI

struct RectangleTestView: View {

    @Environment(\.horizontalSizeClass) var horizontalSizeClass
    @State var settings = Settings()
    
    var body: some View {
        VStack {
            Button("Toggle Visibility") {
                withAnimation(.linear(duration: 2.0)) {
                    settings.isVisible.toggle()
                }
            }
            Rectangle()
                .frame(width: 100, height: 100)
                .foregroundColor(.blue)
                .opacity(settings.opacity)
        }
        .onAppear {
            settings.isVisible = horizontalSizeClass == .regular
        }
    }
}

struct Settings {
    var isVisible: Bool = true
    var opacity: CGFloat {
        isVisible ? 1 : 0
    }
}

FYI we don't really use onReceive anymore since they added onChange .仅供参考,我们不再使用onReceive ,因为他们添加了onChange Also its best to keep view data in structs not move it to expensive objects.最好将视图数据保留在结构中,而不是将其移动到昂贵的对象中。 "Views are very cheap, we encourage you to make them your primary encapsulation mechanism" Data Essentials in SwiftUI WWDC 2020 at 20:50. “视图非常便宜,我们鼓励您将它们作为主要封装机制” SwiftUI WWDC 2020 中的 Data Essentials 20:50。

We need just perform dependency injection (environment is known is parent so easily can be injected into child), and it is simple to do with internal view, like我们只需要执行依赖注入(已知环境是父环境,因此可以很容易地注入子环境),并且使用内部视图很简单,例如

struct ContentView: View {
    @Environment(\.horizontalSizeClass) var horizontalSizeClass

    struct MainView: View {
        // later knonw injection
        @EnvironmentObject var settings: Settings

        var body: some View {
            VStack {
                Button("Toggle Visibility") {
                    settings.isVisible.toggle()
                }
                Rectangle()
                    .frame(width: 100, height: 100)
                    .foregroundColor(.blue)
                    .opacity(settings.isVisible ? 1 : 0) // << direct dependency !!
            }
            .animation(.linear(duration: 2.0), value: settings.isVisible) // << explicit animation
        }
    }

    var body: some View {
        MainView()          // << internal view
            .environmentObject(
                Settings(isVisible: horizontalSizeClass == .regular) // << initial injecttion !!
            )
    }
}

Tested with Xcode 13.4 / iOS 15.5使用 Xcode 13.4 / iOS 15.5 测试

演示

Test code is here 测试代码在这里

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM