在SwiftUI中,我怎么知道选择器选择何时更改?为什么Set不能正常工作?

问题描述 投票:1回答:3

我在SwiftUI Picker中有一个Form,并且每当选择器发生更改时,我都试图处理该值。我希望能够使用代表当前所选值的变量上的didSet来执行此操作。

import SwiftUI

struct ContentView: View {

    enum TransmissionType: Int {
        case automatic
        case manual
    }

    @State private var selectedTransmissionType: Int = TransmissionType.automatic.rawValue {
        didSet {
            print("selection changed to \(selectedTransmissionType)")
        }
    }

    private let transmissionTypes: [String] = ["Automatic", "Manual"]

    var body: some View {
        NavigationView {
            Form {
                Section {
                    Picker(selection: $selectedTransmissionType,
                           label: Text("Transmission Type")) {
                        ForEach(0 ..< transmissionTypes.count) {
                            Text(self.transmissionTypes[$0])
                        }
                    }
                }
            }
        }
    }
}

Picker UI(通常)按预期工作:我可以看到已选择默认值,点击选择器,它打开一个新视图,我可以选择其他值,然后返回到主窗体并显示新值已被选择。但是,永远不会调用didSet

我看过this question,但是对我来说,向变量View添加更多代码而不是仅仅在变量更改时处理新值似乎是很奇怪的,即使有可能。即使使用onReceive会导致更复杂的视图,还是更好?我的主要问题是:防止调用didSet的代码有什么问题?

我使用this example达到了这一点。

除了我的常规问题外,我还有其他一些有关此示例的内容:

[A)似乎很奇怪,我如何同时具有enumArray来表示相同的两个值。有人还能建议一种更好的方法来构造它以避免这种冗余吗?我考虑过TransmissionType对象,但是与enum相比,这似乎有些过分……也许不是吗?

[B)轻按选择器时,带有选择器选项的屏幕就会滑过,然后两个选项会稍微跳一下。这让人感到震撼,跳动,并带来糟糕的用户体验。我在这里做错了什么导致UX错误吗?还是可能是SwiftUI错误?每次更改选择器时都会出现此错误:

[TableView] Warning once only: UITableView was told to layout its visible cells and other contents without being in the view hierarchy (the table view or one of its superviews has not been added to a window). This may cause bugs by forcing views inside the table view to load and perform layout without accurate information (e.g. table view bounds, trait collection, layout margins, safe area insets, etc), and will also cause unnecessary performance overhead due to extra layout passes. Make a symbolic breakpoint at UITableViewAlertForLayoutOutsideViewHierarchy to catch this in the debugger and see what caused this to occur, so you can avoid this action altogether if possible, or defer it until the table view has been added to a window.

ios swift swiftui picker
3个回答
1
投票

我从较早开始输入此字,然后返回以发现LuLuGaGa击败了我。 :D但是由于我还是有这个...

主要问题:来自Swift Language Guide

“当您为存储的属性分配默认值或设置其默认值时初始化程序中的初始值,该属性的值已设置直接调用,而无需调用任何属性观察器。“

因此构造视图时,属性观察器将不会触发。但是,当@State变量更改时,将构造视图的新实例(请记住,视图是结构或值类型)。因此,实际上didSet属性观察者对@State属性没有用。

您要做的是创建一个符合ObservableObject的类,并使用@ObservedObject属性包装器从您的视图中引用它。因为该类存在于struct之外,所以您可以在其属性上设置属性观察器,它们会像您期望的那样触发。

问题A:如果使它符合CaseIterable,则只能使用枚举(请参见下面的示例)

问题B:据我所知,这似乎是一个SwiftUI错误,正如[C​​0] / Picker组合内的任何NavigationView一样发生。我建议Form

这是删除枚举和数组冗余并将选择保存在UserDefaults中的方法:

reporting it to Apple

然后您的视图看起来像

extension ContentView {
    // CaseIterable lets us use .allCases property in ForEach
    enum TransmissionType: String, CaseIterable, Identifiable, CustomStringConvertible {
        case automatic
        case manual

        // This lets us omit 'id' parameter in ForEach
        var id: TransmissionType {
            self
        }

        // This just capitalizes the first letter for prettier printing
        var description: String {
            rawValue.prefix(1).uppercased() + rawValue.dropFirst()
        }
    }

    class SelectionModel: ObservableObject {
        // Save selected type to UserDefaults on change
        @Published var selectedTransmissionType: TransmissionType {
            didSet {
                UserDefaults.standard.set(selectedTransmissionType.rawValue, forKey: "TransmissionType")
            }
        }

        // Load selected type from UserDefaults on initialization
        init() {
            if let rawValue = UserDefaults.standard.string(forKey: "TransmissionType") {
                if let transmissionType = TransmissionType(rawValue: rawValue) {
                    self.selectedTransmissionType = transmissionType
                    return
                }
            }
            // Couldn't load from UserDefaults
            self.selectedTransmissionType = .automatic
        }
    }
}

1
投票

didSet不会在@State上调用,因为它是包装器-您正在状态内设置一个值,而不是状态本身。他的重要问题是,为什么您想知道它已经设置好?

您的视图可以稍微简化:

如果您将枚举声明为具有String的原始类型,则不需要名称数组。如果将其声明为CaseIterable,则可以通过调用Array(TransmissionType.allCases)获得所有案例。如果它也被声明为“可识别”,您将能够将所有情况直接传递到ForEach。接下来,您需要将rawValue传递到文本中,并记住将标签放在文本上,以便可以进行选择:

struct ContentView: View {
    @ObservedObject var model = SelectionModel()

    var body: some View {
        NavigationView {
            Form {
                Section {
                    Picker(selection: $model.selectedTransmissionType, label: Text("Transmission Type")) {
                        ForEach(TransmissionType.allCases) { type in
                            Text(type.description)
                        }
                    }
                }
            }
        }
    }
}

我看不到奇怪的跳跃来自何处-您是否也在设备上复制了它?


1
投票

这里有两个问题。

1)如果标题样式为“ .inline”,则可以解决跳转问题。

2)OnReceive()是用组合框架方法替换struct ContentView: View { enum TransmissionType: String, CaseIterable, Identifiable { case automatic case manual var id: String { return self.rawValue } } @State private var selectedTransmissionType = TransmissionType.automatic var body: some View { NavigationView { Form { Section { Picker(selection: $selectedTransmissionType, label: Text("Transmission Type")) { ForEach(Array(TransmissionType.allCases)) { Text($0.rawValue).tag($0) } } } } } } } 请求的最简单方法之一,这是SwiftUI的核心技术。

didSet
© www.soinside.com 2019 - 2024. All rights reserved.