admin管理员组文章数量:1279007
Context
In a Mac (nb: NOT iOS) app, I have a complex OutlineView that is powered by SwiftUI's List
and various embedded DisclosureGroup
s. Here's what it looks like:
Question
I need control over which rows of this OutlineView can be simultaneously selected. For example, a user can select multiple items at the same level of the tree, but not a parent item and its child or grandchildren at the same time.
This was easy to achieve in AppKit with NSOutlineView
, which called a delegate method (outlineView:selectionIndexesForProposedSelection:
) allowing me to intercept the proposed selection and modify it if needed.
What is the idiomatic way to do that in SwiftUI?
Code
Because:
Each level of the OutlineView is a different model object that cannot inherit from an abstract base class,
SwiftUI cannot work with protocols instead of concrete types, and,
I need control over expansion states for each item in the list, which
OutlineGroup
conveniently does not provide,
I cannot use a single List
with the children:
parameter. Instead, I have nested DisclosureGroup
items. Here, I've simplified to get rid of custom views, expansion state tracking, etc:
struct MainWindowNavigator: View
{
@State var clients: [Client] = ...
// property observers (willSet/didSet) don't work with property wrappers. So what's the right way to validate and modify this?
// This is also just UUIDs; not an IndexSet of selected rows, so it's not possible to tell *what* they are in willSet/didSet.
@State var selectedUUIDs: Set<UUID> = []
var body: some View
{
List(clients, selection: $selectedUUIDS) { client in
DisclosureGroup
{
ForEach(client.projects) { project in
DisclosureGroup {
Text("blah")
} label: {
Text(project.name)
}
}
} label: {
Text(client.name)
}
}
}
}
Context
In a Mac (nb: NOT iOS) app, I have a complex OutlineView that is powered by SwiftUI's List
and various embedded DisclosureGroup
s. Here's what it looks like:
Question
I need control over which rows of this OutlineView can be simultaneously selected. For example, a user can select multiple items at the same level of the tree, but not a parent item and its child or grandchildren at the same time.
This was easy to achieve in AppKit with NSOutlineView
, which called a delegate method (outlineView:selectionIndexesForProposedSelection:
) allowing me to intercept the proposed selection and modify it if needed.
What is the idiomatic way to do that in SwiftUI?
Code
Because:
Each level of the OutlineView is a different model object that cannot inherit from an abstract base class,
SwiftUI cannot work with protocols instead of concrete types, and,
I need control over expansion states for each item in the list, which
OutlineGroup
conveniently does not provide,
I cannot use a single List
with the children:
parameter. Instead, I have nested DisclosureGroup
items. Here, I've simplified to get rid of custom views, expansion state tracking, etc:
struct MainWindowNavigator: View
{
@State var clients: [Client] = ...
// property observers (willSet/didSet) don't work with property wrappers. So what's the right way to validate and modify this?
// This is also just UUIDs; not an IndexSet of selected rows, so it's not possible to tell *what* they are in willSet/didSet.
@State var selectedUUIDs: Set<UUID> = []
var body: some View
{
List(clients, selection: $selectedUUIDS) { client in
DisclosureGroup
{
ForEach(client.projects) { project in
DisclosureGroup {
Text("blah")
} label: {
Text(project.name)
}
}
} label: {
Text(client.name)
}
}
}
}
Share
Improve this question
edited Feb 24 at 10:43
Joakim Danielson
52.1k5 gold badges33 silver badges71 bronze badges
asked Feb 24 at 6:23
BryanBryan
5,7794 gold badges46 silver badges79 bronze badges
4
|
1 Answer
Reset to default 2Use a type that includes which level the item is on as the selection type. e.g.
struct HierachicalID: Hashable {
let id: UUID
let level: Int
}
Add the corresponding tag
s.
@State var selectedUUIDs: Set<HierachicalID> = []
var body: some View {
List(clients, selection: $selectedUUIDs) { client in
DisclosureGroup
{
ForEach(client.projects) { project in
DisclosureGroup {
Text("blah")
.tag(HierachicalID(id: project.id, level: 2))
} label: {
Text(project.name)
}
.tag(HierachicalID(id: project.id, level: 1))
}
} label: {
Text(client.name)
}
.tag(HierachicalID(id: client.id, level: 0))
}
Then you can use onChange
to observe changes:
.onChange(of: selectedUUIDs) { oldValue, newValue in
if Set(newValue.map(\.level)).count > 1 {
selectedUUIDs = oldValue
}
}
It is also possible to use .selectionDisabled
to disable selections depending on the current selection. e.g. for the clients, you can write
.selectionDisabled(selectedUUIDs.contains { $0.level != 0 })
But this would require you to give the user a way to deselect everything, since the selection now cannot change between levels directly.
If you want even more control than this, you should probably go back to using an NSOutlineView
. Currently, the functionalities provided by SwiftUI for macOS is nowhere near enough that you can write a proper macOS app in pure SwiftUI. You are going to need a NSViewRepresentable
sooner or later.
本文标签: swiftHow do I validate and modify a proposed selection in a listStack Overflow
版权声明:本文标题:swift - How do I validate and modify a proposed selection in a list? - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1741290140a2370500.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
UUID
s as the selection type then? You can totally use a struct that tells you which level it is on. – Sweeper Commented Feb 24 at 8:01onChange
, of course. You can also do.selectionDisabled()
conditionally. – Sweeper Commented Feb 24 at 8:18Binding<Set<UUID>>
for the selection from a parent view in addition to the selection storage (Set<UUID>
), instead using a@State
. A binding has a getter and a setter. Instead letting the List directly mutate the selection, initialise the setter of the binding(UUID) -> Void
with a closure, which calls your logic implemented in the parent view (or Observable). Compute the new selection and update the binding. With the given UUID, you should be able to retrieve any properties from the model, and thus, you should be able to compute the selection. – CouchDeveloper Commented Feb 24 at 14:08