Selection with SwiftUI Table 选择在SwiftUI表格中

huangapple go评论63阅读模式
英文:

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.

huangapple
  • 本文由 发表于 2023年5月6日 20:21:56
  • 转载请务必保留本文链接:https://go.coder-hub.com/76188863.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定