Reputation: 1
I am working on some of code that try work with a set with ForEach, it is kind of working, the issue is here that with updating first character of new value it will end updating and does not let to finish the updating process. I want improve the updating logic without using @FocusState
for TextField.
My goal is that my codes waits for user to import all new character or the codes understands that the TextField is under editing und user did not press return key on keyboard.
struct ContentView: View {
@State private var set: Set<String> = ["value1", "value2", "value3", "value4"]
var body: some View {
SetforForEachWithBinding(set: $set)
}
}
struct SetforForEachWithBinding: View {
@Binding var set: Set<String>
var body: some View {
List {
ForEach(set.sorted(by: <), id: \.self) { element in
TextField("Enter value here...", text: Binding(
get: { return element },
set: { newValue in
if set.contains(element) {
set.remove(element)
set.insert(newValue)
}
}
))
}
}
}
}
Upvotes: 2
Views: 7442
Reputation: 344
Disclaimer: this answer is merely a synthesis of what we have discussed already in comments. Keep in mind that you'll really be able to see speed improvements with a number of elements N that is in the magnitude of thousands or hundreds of thousands. I don't think it suits the case of a simple view, but anyway let's cover it for the sake of the discussion.
Your SwiftUI view: ForEach(set.sorted(), id: \.self) { … }
has an overall complexity of O(N + N * Log N) where N is the count of the elements stored in the collection to sort (from now on let's establish that N will always refers to that count value). That is cause sorting the collection has a complexity of O(N * Log N) and iterating over all its elements (that's what the ForEach
does) has O(N) complexity.
Thus another user has correctly suggested to use a sorted array and keeping it sorted upon addition/removal of elements.
This will effectively reduce the ForEach
construct to have a O(N) complexity, deferring the O(NLogN) cost to when a mutation of such array is done… But still after such mutation happens, the ForEach
will be triggered again cause the state of the view has changed: hence you'll still end up with the same complexity cost. Moreover you'll still have to initially pass a sorted array to the collection, or eventually sort it at initialisation time.
So you could think to improve a bit your original code by using a sorted array, thus leveraging on a NSArray
's index(of:inSortedRange:options:usingComparator)
when mutating it (the TextField
binding):
// assuming elements is the sorted array used to build the ForEach and holding a state in the view
CustomTextFieldView(string: Binding(
get: { return element },
set: { newValue in
elements.remove(element)
let i = (elements as NSArray<NSString>)
.index(of: newValue,
inSortedRange: 0..<elements.count,
options: .insertionIndex,
usingComparator: { $0.compare($1) }
)
elements.insert(newValue, at: i)
}
))
Except… here you're also adding another two O(N) complexity factors in order to remove the old element stored in the array and to insert at the right sort position the new element… Thus your overall complexity for the ForEach
now will be: O(3N + N * Log N)).
This is worse than using the set as you originally did (Set
has amortized O(1) complexity for removal/addition of elements thus it can be taken out from the overall complexity cost calculation).
How can you improve instead the overall complexity cost? By choosing a data structure to hold your elements leveraging on Left Leaning Red-Black Tree.
This data structure keeps its elements sorted upon mutation, that is the complexity for such operation is almost O(Log N).
Thus your whole ForEach
(including the mutations) should perform in amortized O(N + Log N).
I've pointed out in the comments an implementation I made of a data structure that leverages on this kind of balanced binary search tree, which is modelled upon an associative array (Dictionary
), so it's not a Set
per-se, but you could use it in your case to store your elements as the keys and using Void
or a Bool
as the value to associate to them.
Now of course you'll lose the ability to do specific Set
operations (as intersections for example), but as you clearly stated in your question you've chosen a Set
because of its O(1) complexity for membership lookup of an element.
By adopting instead a left leaning red-black tree you'll lose that sweet O(1) complexity, trading it for O(Log N) on this particular operation, but in the overall complexity of this particular ForEach
you'll gain a lower cost.
I repeat: in your case I highly doubt the elements count will ever reach a thousands or hundreds of thousands value so to really notice any speed improvement.
Upvotes: 1
Reputation: 1
It turns out need some playing with code like this way, if you know better way I will accept your answer. thanks.
PS: For using Set in ForEach and unleashing full power of Set, we can use an identifiable type for elements for our Set, then using id: \.id
, after that we can have multiple elements with same string and deferent id's also the power of Set.
struct ContentView: View {
@State private var set: Set<String> = ["value1", "value2", "value3", "value4"]
var body: some View {
SetforForEachWithBinding(set: $set)
}
}
struct SetforForEachWithBinding: View {
@Binding var set: Set<String>
var body: some View {
List {
ForEach(set.sorted(by: <), id: \.self) { element in
CustomTextFieldView(string: Binding(
get: { return element },
set: { newValue in
set.remove(element)
set.insert(newValue)
}
))
}
}
}
}
struct CustomTextFieldView: View {
@Binding var string: String
@State private var isUnderEdit: Bool = Bool()
@State private var isUnderEditString: String = String()
var body: some View {
TextField("Enter value here...", text: Binding(
get: {
if isUnderEdit {
return isUnderEditString
}
else {
return string
}
},
set: { newValue in
if isUnderEdit {
isUnderEditString = newValue
}
}
), onEditingChanged: { value in
if value {
isUnderEdit = true
isUnderEditString = string
}
else {
isUnderEdit = false
string = isUnderEditString
}
}, onCommit: { })
}
}
Upvotes: 1