英文:
Selection with SwiftUI Table
问题
I have some data, that is displayed in a Table
. The user can go forward and backwards to see the different data. I want, that the selection is getting saved. In the background it is working, the main problem now is, that the selection will not get updated on the table. I simplified the code to show the problem:
struct TableRow: Identifiable {
let id: Int
let col1: String
let col2: String
}
struct ContentView: View {
@State var selection: Int? = nil
@State private var savedSelection1: Int? = nil
@State private var savedSelection2: Int? = nil
let infos1 = [
TableRow(id: 1, col1: "Infos 1 - Row 1 - Col 1", col2: "Infos 1 - Row 1 - Col 2"),
TableRow(id: 2, col1: "Infos 1 - Row 2 - Col 1", col2: "Infos 1 - Row 2 - Col 2"),
TableRow(id: 3, col1: "Infos 1 - Row 3 - Col 1", col2: "Infos 1 - Row 3 - Col 2")
]
let infos2 = [
TableRow(id: 4, col1: "Infos 2 - Row 1 - Col 1", col2: "Infos 2 - Row 1 - Col 2"),
TableRow(id: 5, col1: "Infos 2 - Row 2 - Col 1", col2: "Infos 2 - Row 2 - Col 2"),
TableRow(id: 6, col1: "Infos 2 - Row 3 - Col 1", col2: "Infos 2 - Row 3 - Col 2")
]
@State var infos = [TableRow]()
var body: some View {
VStack {
Table(self.infos, selection: self.$selection) {
TableColumn("Col 1", value: \.col1)
TableColumn("Col 2", value: \.col2)
}
HStack {
Button("Infos 1") {
self.savedSelection2 = self.selection
self.infos = infos1
self.selection = self.savedSelection1
}
Button("Infos 2") {
self.savedSelection1 = self.selection
self.infos = infos2
self.selection = self.savedSelection2
}
}
}
.padding()
}
}
The problem is: SwiftUI changes the content and immediately after the selection. This change of selection is too fast. So I tried to make a test and this code proved it:
struct ContentView: View {
@State var selection: Int? = nil
@State private var savedSelection1: Int? = nil
@State private var savedSelection2: Int? = nil
let infos1 = [
TableRow(id: 1, col1: "Infos 1 - Row 1 - Col 1", col2: "Infos 1 - Row 1 - Col 2"),
TableRow(id: 2, col1: "Infos 1 - Row 2 - Col 1", col2: "Infos 1 - Row 2 - Col 2"),
TableRow(id: 3, col1: "Infos 1 - Row 3 - Col 1", col2: "Infos 1 - Row 3 - Col 2")
]
let infos2 = [
TableRow(id: 4, col1: "Infos 2 - Row 1 - Col 1", col2: "Infos 2 - Row 1 - Col 2"),
TableRow(id: 5, col1: "Infos 2 - Row 2 - Col 1", col2: "Infos 2 - Row 2 - Col 2"),
TableRow(id: 6, col1: "Infos 2 - Row 3 - Col 1", col2: "Infos 2 - Row 3 - Col 2")
]
@State var infos = [TableRow]()
var body: some View {
VStack {
Table(self.infos, selection: self.$selection) {
TableColumn("Col 1", value: \.col1)
TableColumn("Col 2", value: \.col2)
}
HStack {
Button("Infos 1") {
self.savedSelection2 = self.selection
self.infos = infos1
Task {
try? await Task.sleep(nanoseconds: 100_000)
self.selection = self.savedSelection1
}
}
Button("Infos 2") {
self.savedSelection1 = self.selection
self.infos = infos2
Task {
try? await Task.sleep(nanoseconds: 100_000)
self.selection = self.savedSelection2
}
}
}
}
.padding()
}
}
The only change is, that I have a Task.sleep
before change the selection. this works.
My question is now: This is a pretty ugly solution. The sleep must not be too short, because otherwise it will not work. If it is too long, I will see this delay.
Any ideas how I can solve this problem better?
EDIT
I am using this code on macOS. As told in one answer, the problem doesn't occur in iOS.
英文:
I have some data, that is displayed in a Table
. The user can go forward and backwards to see the different data. I want, that the selection is getting saved. In the background it is working, the main problem now is, that the selection will not get updated on the table. I simplified the code to show the problem:
struct TableRow: Identifiable {
let id: Int
let col1: String
let col2: String
}
struct ContentView: View {
@State var selection: Int? = nil
@State private var savedSelection1: Int? = nil
@State private var savedSelection2: Int? = nil
let infos1 = [
TableRow(id: 1, col1: "Infos 1 - Row 1 - Col 1", col2: "Infos 1 - Row 1 - Col 2"),
TableRow(id: 2, col1: "Infos 1 - Row 2 - Col 1", col2: "Infos 1 - Row 2 - Col 2"),
TableRow(id: 3, col1: "Infos 1 - Row 3 - Col 1", col2: "Infos 1 - Row 3 - Col 2")
]
let infos2 = [
TableRow(id: 4, col1: "Infos 2 - Row 1 - Col 1", col2: "Infos 2 - Row 1 - Col 2"),
TableRow(id: 5, col1: "Infos 2 - Row 2 - Col 1", col2: "Infos 2 - Row 2 - Col 2"),
TableRow(id: 6, col1: "Infos 2 - Row 3 - Col 1", col2: "Infos 2 - Row 3 - Col 2")
]
@State var infos = [TableRow]()
var body: some View {
VStack {
Table(self.infos, selection: self.$selection) {
TableColumn("Col 1", value: \.col1)
TableColumn("Col 2", value: \.col2)
}
HStack {
Button("Infos 1") {
self.savedSelection2 = self.selection
self.infos = infos1
self.selection = self.savedSelection1
}
Button("Infos 2") {
self.savedSelection1 = self.selection
self.infos = infos2
self.selection = self.savedSelection2
}
}
}
.padding()
}
}
The problem is: SwiftUI changes the content and immediately after the selection. This change of selection is too fast. So I tried to make a test and this code proved it:
struct ContentView: View {
@State var selection: Int? = nil
@State private var savedSelection1: Int? = nil
@State private var savedSelection2: Int? = nil
let infos1 = [
TableRow(id: 1, col1: "Infos 1 - Row 1 - Col 1", col2: "Infos 1 - Row 1 - Col 2"),
TableRow(id: 2, col1: "Infos 1 - Row 2 - Col 1", col2: "Infos 1 - Row 2 - Col 2"),
TableRow(id: 3, col1: "Infos 1 - Row 3 - Col 1", col2: "Infos 1 - Row 3 - Col 2")
]
let infos2 = [
TableRow(id: 4, col1: "Infos 2 - Row 1 - Col 1", col2: "Infos 2 - Row 1 - Col 2"),
TableRow(id: 5, col1: "Infos 2 - Row 2 - Col 1", col2: "Infos 2 - Row 2 - Col 2"),
TableRow(id: 6, col1: "Infos 2 - Row 3 - Col 1", col2: "Infos 2 - Row 3 - Col 2")
]
@State var infos = [TableRow]()
var body: some View {
VStack {
Table(self.infos, selection: self.$selection) {
TableColumn("Col 1", value: \.col1)
TableColumn("Col 2", value: \.col2)
}
HStack {
Button("Infos 1") {
self.savedSelection2 = self.selection
self.infos = infos1
Task {
try? await Task.sleep(nanoseconds: 100_000)
self.selection = self.savedSelection1
}
}
Button("Infos 2") {
self.savedSelection1 = self.selection
self.infos = infos2
Task {
try? await Task.sleep(nanoseconds: 100_000)
self.selection = self.savedSelection2
}
}
}
}
.padding()
}
}
The only change is, that I have a Task.sleep
before change the selection. this works.
My question is now: This is a pretty ugly solution. The sleep must not be too short, because otherwise it will not work. If it is too long, I will see this delay.
Any ideas how I can solve this problem better?
EDIT
I am using this code on macOS. As told in one answer, the problem doesn't occur in iOS.
答案1
得分: 1
I solved the issue on macOS by wrapping the selection setting in DispatchQueue.main.async
:
HStack {
Button("Infos 1") {
self.savedSelection2 = self.selection
self.infos = infos1
DispatchQueue.main.async { // here
self.selection = self.savedSelection1
}
}
Button("Infos 2") {
self.savedSelection1 = self.selection
self.infos = infos2
DispatchQueue.main.async { // here
self.selection = self.savedSelection2
}
}
}
(Note: I've provided the translation for the code block you provided.)
英文:
I could only reproduce the issue when running the code on macOS – on iOS (in Simulator) for me it worked fine as is.
I solved the issue on macOS by wrapping the selection setting in DispatchQueue.main.async
:
HStack {
Button("Infos 1") {
self.savedSelection2 = self.selection
self.infos = infos1
DispatchQueue.main.async { // here
self.selection = self.savedSelection1
}
}
Button("Infos 2") {
self.savedSelection1 = self.selection
self.infos = infos2
DispatchQueue.main.async { // here
self.selection = self.savedSelection2
}
}
}
答案2
得分: 0
为了避免依赖于某种时间问题,可以通过引入一个新的状态属性并使用 task(id:)
来处理选择。
首先创建一个新的属性来保存最新按下的按钮:
@State private var buttonPressed = .none
我创建了一个简单的枚举来跟踪按钮:
enum ButtonState {
case button1, button2, none
}
然后,在每个按钮的动作中更新它:
HStack {
Button("Infos 1") {
self.infos = infos1
buttonPressed = .button1
}
Button("Infos 2") {
self.infos = infos2
buttonPressed = .button2
}
}
然后,通过使用 .task(id:)
并将我们的新属性作为 id 值来处理 buttonPressed
的更改:
.task(id: buttonPressed) {
switch buttonPressed {
case .button1:
savedSelection2 = selection
selection = savedSelection1
case .button2:
savedSelection1 = selection
selection = savedSelection2
case .none:
selection = nil // 或者是 break
}
}
这足以使 UI 正确更新,无需任何休眠功能。
英文:
To avoid being dependent on some timing issue this could be solved by introducing a new state property and using task(id:)
to handle selections
First create a new property that holds the latest button being pressed
@State private var buttonPressed = .none
I created a simple enum for keeping track of the buttons
enum ButtonState {
case button1, button2, none
}
Then we update it in the Button
action for each button
HStack {
Button("Infos 1") {
self.infos = infos1
buttonPressed = .button1
}
Button("Infos 2") {
self.infos = infos2
buttonPressed = .button2
}
}
And then we act on changes of buttonPressed
by using .task(id:)
with our new property as the id value
.task(id: buttonPressed) {
switch buttonPressed {
case .button1:
savedSelection2 = selection
selection = savedSelection1
case .button2:
savedSelection1 = selection
selection = savedSelection2
case .none:
selection = nil // or break
}
}
This is enough for the UI to be updated properly without needing any time of sleep functionality.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论