@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() 上重置变量的主要内容,如果未能解决你的问题,请参考以下文章
在 Angular2 ngModel 值未在自定义指令的 onBlur 事件上更新
Firefox 输入类型 = 日期最小值未在最小有效月份打开
CoreData 和 Codable 类编译器错误:在从初始化程序返回之前,未在所有路径上调用“self.init”