【问题标题】:@State initial value not resetting variable on init()@State 初始值未在 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)
  • 您能否指出意外结果发生在哪里,因为在我的测试环境中我没有找到任何结果?并提供一些模型数据,让你看到问题。

标签: swiftui


【解决方案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 以使其可迭代。这是工作代码。

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

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

    @EnvironmentObject var shared: SharedObject

  2. 添加了切换标签时默认选择第一个帐户的代码。这会在选项卡之间切换时刷新行。查看.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 应用程序运行,它的工作方式与上面的模拟器完全相同,所以我认为我不明白这个问题。如果可能,请进一步向我解释。或者也包括分段控件
猜你喜欢
  • 2020-03-04
  • 1970-01-01
  • 2021-09-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多