Multiple instances in custom views are messing up the text state on macOS
stefanomondino opened this issue · 8 comments
Hello and first of all thank you for this great library :)
I'm trying to replicate something similar to Xcode tabs, with some kind of top "tab bar" selecting each code file I want to edit.
Since SwiftUI's TabView
doesn't (currently) allow for tab bar customization, I've created my own using buttons in a HStack. When I click a button, the "main view" (containing the CodeEditor for selected file) changes and shows me file contents.
The bug I've found is very strange, after changing one of two tabs the global "state" starts to mixup, showing up either wrong file content (the previous one) or resetting contents after window loses focus or when my tab changes.
System's TextEditor
seems to work fine.
I have a strong suspect this is somehow related to SwiftUI.
I hope attached video is "self explaining" (window always has focus, but something strange also happens when you focus any other application).
you can find it here https://github.com/stefanomondino/CodeEditor in the Demo folder.
Registrazione.schermo.2022-05-15.alle.14.54.31.mov
Also, it's worth nothing that also CodeEditorView (a very similar project) has the same issue.
Let me know if you have any idea, thanks!
Are you actually switching the subviews, or just replace their contents?
@helje5 sorry didn't push the demo code to my fork ;)
This is my "SheetView"
https://github.com/stefanomondino/CodeEditor/blob/main/Demo/Sources/SheetView.swift
I'm passing my contents as a view builder
struct SheetView<Content: View>: View {
@State private var currentIndex: Int? = nil
let items: [SheetElement]
@ViewBuilder let contents: (Int) -> Content
init(items: [SheetElement], @ViewBuilder contents: @escaping (Int) -> Content) {
self.items = items
self.contents = contents
currentIndex = items.isEmpty ? nil : 0
}
var body: some View {
VStack(spacing: 0) {
ScrollView(.horizontal, showsIndicators: false) {
HStack(alignment: .center, spacing: 0) {
ForEach(items.indices, id: \.self) { index in
SheetButton(index: index, selection: $currentIndex, item: items[index])
Divider()
}
Spacer()
}
.padding(0)
}
.frame(height: 30)
Divider()
if let index = currentIndex {
contents(index)
} else {
Color(.clear)
}
}
.layoutPriority(1)
}
}
And this is how i use it (Editor
is an observable object holding the binding)
let items = ["Test file 1", "Another test", "and yet again"].map { Editor.init($0)}
SheetView(items: items) { index in
CodeEditor(source: items[index].binding,
language: .swift)
}
Maybe try:
contents(index)
.id(index)
This should create a new View for each index. (but even if that works, it is still a bug that should be fixed, not quite sure how/why this is happening).
@helje5 woah! and where did that id
came from? :D
seems to work fine for the moment! Also you just teached me something really important I didn't know
Maybe it's worth adding a .id(UUID())
modifier to every CodeEditor view? Or maybe it's too much.
No, that would recreate the backing View on every single run, not recommended (in general, not just here, the UUID thing is just a hack).
I still think the thing is a bug proper. Maybe diffing gets confused w/ representables.
From a very quick debug session on my project, seems like the source.projectedValue
in the representable is keeping somehow the old value.
But from my point of view it makes total sense to add an .id to my custom tab view, it would be interesting to know if TextEditor
and/or. TabView
are doing something similar in their internal implementations.
Maybe it's something we can find out with Debug Memory Graph
TabView
has the tag
, which presumably is similar to id
.
As mentioned, the thing should work w/o the id
and TextEditor
can't attach an own id
either as this is bound to the hierarchy. So I suspect TextEditor
just does something right in the reload, which CodeEditor
is doing wrong. No idea, needs debugging.