@State 初始值未在 init() 上重置变量

Posted

技术标签:

【中文标题】@State 初始值未在 init() 上重置变量【英文标题】:@State initial value not resetting variable on init() 【发布时间】:2020-03-11 05:09:18 【问题描述】:

我有一个分段控件,用作我的应用程序工具栏中的选项卡,链接到 selectedTab 变量。当我切换选项卡时,帐户列表会更改,但不会重置行列表。我尝试在所选对象上使用 initialValue 以确保它重置为 0 但这并没有影响它。我尝试在 init 中打印以确保在 init 之后 selected 的值为 0。每次都是,但它仍然没有刷新 foreach 列表的行。

我错过了什么?

import SwiftUI
import SQLite3

struct ContentView:View 

    @EnvironmentObject var shared:SharedObject

    var body: some View 
        VStack 
            if shared.selectedTab == 0 
                LedgerView(ledger: .Accounts)
             else if shared.selectedTab == 1 
                LedgerView(ledger: .Budgets)
             else if shared.selectedTab == 2 
                ReportsView()
            
        .frame(maxWidth: .infinity, maxHeight: .infinity)
    



struct LedgerView:View 

    @EnvironmentObject var shared:SharedObject

    let ledger:LedgerType
    @State var selected:Int = 0

    init(ledger:LedgerType) 
        self.ledger = ledger
        self._selected = State(initialValue: 0)
    

    var body:some View 
        HStack 
            VStack(alignment: HorizontalAlignment.leading) 
                ForEach(shared.accounts.filter($0.ledger == ledger))  account in
                    Text(account.name)
                        .background(account.id == self.selected ? Color.accentColor : Color.clear)
                        .onTapGesture self.selected = account.id
                
            
            Divider()
            VStack(alignment: HorizontalAlignment.leading) 
                ForEach(shared.journalLines.filter($0.accountID == selected))  line in
                    Text("Line#\(line.id)")
                
            
        
    



struct ReportsView:View 
    var body:some View 
        Text("Under Construction ...")
    


class SharedObject:ObservableObject 

    @Published var accounts:[Account] = []
    @Published var journalLines:[JournalLine] = []
    @Published var selectedTab:Int = 0

    init() 
        loadData()    
    



enum LedgerType:Int 
    case Accounts=0,Budgets=1
    var name:String 
        switch(self) 
        case .Accounts: return "Accounts"
        case .Budgets: return "Budgets"
        
    

struct Account:Identifiable 
    var id:Int
    var name:String
    var ledger:LedgerType

struct Payee:Identifiable 
    var id:Int
    var name:String

struct Journal:Identifiable 
    var id:Int
    var date:Date
    var payeeID:Int
    var memo:String?

struct JournalLine:Identifiable 
    var id:Int
    var journalID:Int
    var accountID:Int
    var amount:Double

编辑删节的演示代码以尝试隔离问题

import SwiftUI

struct ContentView: View 

    @EnvironmentObject var shared:SharedObject

    var body: some View 
        VStack 
            Picker(selection: $shared.selectedTab, label: Text("")) 
                Text("Accounts").tag(0)
                Text("Budgets").tag(1)
            .pickerStyle(SegmentedPickerStyle())
            Divider()
            if shared.selectedTab == 0 || shared.selectedTab == 1 
                LedgerView()
            
            Spacer()
        
        .padding()
        .frame(maxWidth: .infinity, maxHeight: .infinity)
    



struct LedgerView:View 

    @State var selected:Int = 0

    init() 
        self._selected = State(initialValue: 0)
        print("LedgerView.init()")
    

    var body:some View 
        VStack(alignment: HorizontalAlignment.leading) 
            Text("Selected: \(selected)")
            Picker(selection: $selected, label: Text("")) 
                Text("Account#1").tag(1)
                Text("Account#2").tag(2)
                Text("Account#3").tag(3)
            
        
    



class SharedObject: ObservableObject 
    @Published var selectedTab:Int = 0

【问题讨论】:

首先尝试通过 LedgerView(ledger: .Accounts).environmentObject(shared) 您能否指出意外结果发生在哪里,因为在我的测试环境中我没有找到任何结果?并提供一些模型数据,让你看到问题。 【参考方案1】:

根据您最近的评论,您需要的是@Binding 而不是@State。解释一下:结构 LedgerView 只是选择器在单独视图中的 extract。当您调用 LedgerView() 时,它不会实例化一个新对象。它只是在那个地方添加了选择器视图。因此,当您需要在切换选项卡时重置选择器时,您需要使用绑定来重置选择器。这是工作代码。希望能帮助到你。

struct ContentView: View 

    @EnvironmentObject var shared:SharedObject
    @State var selected: Int = 0

    var body: some View 
        VStack 
            Picker(selection: $shared.selectedTab, label: Text("")) 
                Text("Accounts").tag(0)
                Text("Budgets").tag(1)
            .pickerStyle(SegmentedPickerStyle())
            Divider()
            if shared.selectedTab == 0 || shared.selectedTab == 1 
                LedgerView(selected: $selected)
            
            Spacer()
        
        .padding()
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .onReceive(shared.$selectedTab)  newValue in
            self.selected = 0
        
    


struct LedgerView:View 

    @Binding var selected: Int

    var body:some View 
        VStack(alignment: HorizontalAlignment.leading) 
            Text("Selected: \(selected)")
            Picker(selection: $selected, label: Text("")) 
                Text("Account#1").tag(1)
                Text("Account#2").tag(2)
                Text("Account#3").tag(3)
            
        
    


[包含替代解决方案的早期答案]

我已修改您的代码以使这些行正常工作。我添加了示例数据以使代码正常工作。我怀疑问题是否出在您的数据上。我还修改了枚举 LedgerType 以使其可迭代。这是工作代码。

我在代码中修改了以下内容:

    删除了将 ledgerType 传递给 LedgerView 作为事实来源

    @EnvironmentObject var shared: SharedObject

    添加了切换标签时默认选择第一个帐户的代码。这会在选项卡之间切换时刷新行。查看.onReceive中的代码

希望这会有所帮助。如果您需要什么,请告诉我。

struct ContentView:View 

    @EnvironmentObject var shared: SharedObject

    var body: some View 
        VStack 
            Picker(selection: $shared.selectedTab, label: Text("")) 
                ForEach(0 ..< LedgerType.allCases.count)  index in
                    Text(LedgerType.allCases[index].rawValue).tag(index)
                
            
            .pickerStyle(SegmentedPickerStyle())

            if shared.selectedTab == 0 || shared.selectedTab == 1 
                LedgerView()
             else if shared.selectedTab == 2 
                ReportsView()
            
            Spacer()
        .frame(maxWidth: .infinity, maxHeight: .infinity)
    



struct LedgerView:View 
    @EnvironmentObject var shared:SharedObject
    @State var selected: Int = 0

    var body:some View 
        HStack 
            VStack(alignment: HorizontalAlignment.leading) 
                ForEach(shared.accounts.filter( $0.ledger == LedgerType.allCases[shared.selectedTab] ))  account in
                    Text(account.name)
                        .background(account.id == self.selected ? Color.accentColor : Color.clear)
                        .onTapGesture self.selected = account.id
                
            
            Divider()
            VStack(alignment: HorizontalAlignment.leading) 
                ForEach(shared.journalLines.filter($0.accountID == selected))  line in
                    Text("Line#\(line.id)")
                
            
        
        .onReceive(shared.$selectedTab)  newValue in
            if let id = self.shared.getInitialAccountId(tabIndex: newValue) 
                self.selected = id
            
        
    


struct ReportsView:View 
    var body:some View 
        Text("Under Construction ...")
    


class SharedObject:ObservableObject 

    @Published var accounts:[Account] = []
    @Published var journalLines:[JournalLine] = []
    @Published var selectedTab:Int = 0

    func getInitialAccountId(tabIndex: Int) -> Int? 
        if tabIndex == 0 
            return accounts.filter(
                $0.ledger == LedgerType.Accounts
            ).first?.id
        
        else if tabIndex == 1 
            return accounts.filter(
                $0.ledger == LedgerType.Budgets
            ).first?.id
        
        else 
            return accounts.filter(
                $0.ledger == LedgerType.Reports
            ).first?.id
        
    

    init() 
        accounts = [
            Account(id: 1, name: "Sales", ledger: .Accounts),
            Account(id: 2, name: "Purchase", ledger: .Accounts),
            Account(id: 3, name: "Forecast", ledger: .Budgets)
        ]
        journalLines = [
            // Line for sales
            JournalLine(id: 1, journalID: 10, accountID: 1, amount: 200),
            JournalLine(id: 2, journalID: 20, accountID: 1, amount: 400),
            // Line for purchase
            JournalLine(id: 3, journalID: 30, accountID: 2, amount: 600),
            JournalLine(id: 4, journalID: 40, accountID: 2, amount: 800)
        ]
    



enum LedgerType: String, CaseIterable 
    case Accounts = "Accounts"
    case Budgets = "Budgets"
    case Reports = "Reports"


struct Account:Identifiable 
    var id:Int
    var name:String
    var ledger:LedgerType


struct Payee:Identifiable 
    var id:Int
    var name:String


struct Journal:Identifiable 
    var id:Int
    var date:Date
    var payeeID:Int
    var memo:String?


struct JournalLine:Identifiable 
    var id:Int
    var journalID:Int
    var accountID:Int
    var amount:Double

【讨论】:

我确实看到/同意账本/选定标签的真实来源是共享对象。我进行了更改以及您建议的其他一些更改,但问题仍然存在。请参阅原始帖子中添加的演示代码。我在一个新项目中做了一个示例,该示例删除了许多变量以尝试隔离问题,并且至少在我的机器上它仍然存在。更改选项卡时,它会运行LedgerView.Init,该LedgerView.Init应更改为0,但不持续其在选项卡上的值。 span> 我已根据您的评论更新了答案。让我知道它是否适合您。 如果我遗漏了一些明显的东西,请原谅我,但你改变了什么?我看不到任何会导致不同行为的东西。 有两件事,我已经改变了。 1. ContentView 中的 onReceive。 2. 在 LedgerView 中将 State 更改为 Binding。 我现在看到了抱歉。绑定可能有效,但它是一个黑客。 contentview 应该与所选帐户无关,它仅在 ledgerview 中。所以@State 应该可以正常工作吗?我问是因为我认为这是一个错误,而不是我的代码有什么问题。【参考方案2】:

shared.accounts 不存在。你从来没有初始化过它

[...]
  if shared.selectedTab == 0 
        LedgerView(ledger: .Accounts).environmentObject(SharedObject())
   else if shared.selectedTab == 1 
        LedgerView(ledger: .Budgets).environmentObject(SharedObject())

[...]

编辑:确保您的 SceneDelgate 也启动 SharedObject 否则它将无法在物理/模拟器设备上运行

--

编辑:运行您的代码并确认您正在加载后,我运行您的代码没有任何变化,我没有看到任何问题,您能否查看下面的 gif 并评论我的错误看到了吗?

【讨论】:

是的,我只是没有把loaddata方法放在帖子里,因为它太长了,所以我认为它没有帮助 知道了,我通过从 Felix Marianayagam 加载虚假数据运行了您的代码,并且能够运行它。你能告诉我有什么问题吗? 我在做一个 Mac 应用程序。我认为这与此有关。并且 selectedtab 来自 nstoolbar 的分段控件 知道了,我会尝试复制您的编辑并运行 mac 应用程序并回复您。 我只是将您的代码作为 Mac 应用程序运行,它的工作方式与上面的模拟器完全相同,所以我认为我不明白这个问题。如果可能,请进一步向我解释。或者也包括分段控件

以上是关于@State 初始值未在 init() 上重置变量的主要内容,如果未能解决你的问题,请参考以下文章

来自父级的 TextField 默认值未在子级上呈现

在 Angular2 ngModel 值未在自定义指令的 onBlur 事件上更新

Firefox 输入类型 = 日期最小值未在最小有效月份打开

CoreData 和 Codable 类编译器错误:在从初始化程序返回之前,未在所有路径上调用“self.init”

Laravel 密码重置电子邮件未在 Heroku 上使用 gmail 发送

道具值未在 vuejs 中呈现