A list will scroll vertically. However, I don't see a way to get horizontal scrolling or selecting of elements within the list. Does anyone know how to do these things?
Post
Replies
Boosts
Views
Activity
In what way was it fixed? Are there new methods available for specifying width and heights for sidebars and detail views
UPDATE:
I believe I found what was consuming memory as I updated the UI...
I have a View that contains a Text object that displays the number of sequences read from a file of DNA sequences as well as a List of sequence names. These files are typically very large because each individual sequence can consist of thousands to millions of nucleotides. However, the number of sequences in a file can be relatively small and manageable. My goal is to read these files and display the number of sequences as well as a list of the sequence names and statistics about each sequence (but not all the nucleotides because that is too much data).
I update the Text object periodically by setting a property that I call sequenceCount. This property is a member of the following class:
class ObservableSequences: ObservableObject {
@Published var sequenceCount: String? = "0"
@Published var sequenceData: [SequenceData] = []
}
sequenceCount contains the number of sequences read from the file while sequenceData contains information about each sequence in the file. I periodically update sequenceCount from a background thread that is reading the file. I do this to keep the user informed about the progress being made processing the file.
I have a master/detail UI. My master View is declared as follows:
struct MasterView: View {
@ObservedObject public var observableSequences = ObservableSequences()
var body: some View {
VStack {
Text("Number of Lines: " + observableSequences.sequenceCount!)
List(self.observableSequences.sequenceData, id: \.self) {sequenceData in
NavigationLink(destination: DetailView(sequence: sequenceData.sequence)) {
Text(sequenceData.sequence)
}.background(Color.red)
}
}
}
}
For reasons that I don't remember, I had originally written this View as follows:
struct MasterView: View {
@ObservedObject public var observableSequences = ObservableSequences()
var body: some View {
VStack {
Text("Number of Lines: " + observableSequences.sequenceCount!)
List(self.observableSequences.sequenceData, id: \.self) {sequenceData in
NavigationLink(destination: DetailView(sequence: sequenceData.sequence)) {
Text(sequenceData.sequence)
}.background(Color.red)
}.id(UUID()) <------------------------NOTE THE USE OF .id
}
}
}
When I removed .id(UUID()), memory no longer grew out of control and remained stable.
It seems that each time I updated sequenceCount, the UI would be recreated (as I would expect). And each List that is created got a new .id. My guess (and it is only a guess) is that each new List was being retained in memory. Without the unique id, a new List is either not created or the previous List is being disposed of and not retained in memory.
In any case, my code is now behaving nicely even though I am reading exceptionally large files.
I have some code that is very similar. In my case I am reading a large file in a background thread and updating the published property (rather than using a timer). But I get the same result - a memory leak. After about 400 updates my memory usage doubles. I've tried using autoreleasepools in my ContentView. However, they don't seem to have any effect.
Thanks for this additional information. You raised points that I wasn’t aware of. I’ll incorporate them into another example.
I was able to simplify the call to getline a bit with the following code. What makes the call a bit tricky is the first argument which is a pointer to a pointer. I find that I can use an ampersand on standard types such as Int. For example, if I declare linecapp to be an integer, then passing &linecapp causing linecapp to be encapsulated within a UnsafeMutablePoint. Similarly, if I declare line to be an array, then passing &line encapsulates the array in an UnsafeMutablePointer. Notice how I use &line when initializing linep, the pointer to line. And notice how I pass &linecapp as the second parameter to linecapp. At the moment I don't see any other simplifications that I can perform on the code.
// declare an array to hold an incoming line
var line = Array<Int8>(repeating: 0, count: 200)
// declare a pointer to the line
let linep = UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>.allocate(capacity: 1)
linep.initialize(to: &line)
// specify the size of the line
var linecapp = 200
// open the file
let fp = fopen(fileName, "r")
// loop to read the file
while (true) {
let r = getline(linep, &linecapp, fp)
if r == -1 {
break
}
// do something with the line
// convert the line to a string
let str = String(cString: line)
// print the string
print(str)
}
linep.deinitialize(count: 1)
linep.deallocate
I was able to simplify the call to getline a bit with the following code. What makes the call a bit tricky is the first argument which is a pointer to a pointer. I find that I can use an ampersand on standard types such as Int. For example, if I declare linecapp to be an integer, then passing &linecapp causing linecapp to be encapsulated within a UnsafeMutablePoint. Similarly, if I declare line to be an array, then passing &line encapsulates the array in an UnsafeMutablePointer. Notice how I use &line when initializing linep, the pointer to line. And notice how I pass &linecapp as the second parameter to linecapp. At the moment I don't see any other simplifications that I can perform on the code.
// declare an array to hold an incoming line
var line = Array<Int8>(repeating: 0, count: 200)
// declare a pointer to the line
let linep = UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>.allocate(capacity: 1)
linep.initialize(to: &line)
// specify the size of the line
var linecapp = 200
// open the file
let fp = fopen(fileName, "r")
// loop to read the file
while (true) {
let r = getline(linep, &linecapp, fp)
if r == -1 {
break
}
// do something with the line
// convert the line to a string
let str = String(cString: line)
// print the string
print(str)
}
linep.deinitialize(count: 1)
linep.deallocate
Thanks for these additional pointers! Greatly appreciated.
Ideally a tap or click on an item should also cause the item to be highlighted.
Here is an experiment I did...
TextItem is the item I want to display in a list. For grins I gave it a property called tapped.
TextItemView is a View for presenting a TextItem.
Finally, there is TextListView for presenting the list of TextItemViews.
This code as written does display the list of items. And the onTapGesture is fired whenever an item is clicked. However, the background color of the TextItemView does not change. Why not? Because the view is not refreshed. I force a refresh of the view by changing the refresh state variable in TextListView. However, that has no effect. Why is this the case?
Turns out that the refresh state variable is not used in the body of the TextListView. If I simply add a Text struct to the VStack to display refresh, then the TextListView is updated whenever refresh is changed in the onTapGesture.
That said, this is not a satisfactory solution. I can click on items and change toggle their background color with each click. However, this needs to be coordinated with all the other items in the list. To do this I have to write code to turn off of the tapped state of each item and then set the selected items tapped state to true.
import SwiftUI
class TextItem: Identifiable {
var id = UUID()
var name: String = ""
var tapped = false
init(name: String) {
self.name = name
}
}
struct TextItemView: View {
var item: TextItem
var body: some View {
Text("\(item.name)")
}
}
struct TextListView: View {
var Items: [TextItem] = []
@State private var refresh = 0;
var body: some View {
VStack {
List(Items) {item in
if item.tapped {
TextItemView(item: item)
.onTapGesture {
print("tap")
item.tapped.toggle()
self.refresh += 1
}.background(Color.blue)
} else {
TextItemView(item: item)
.onTapGesture {
print("tap")
item.tapped.toggle()
self.refresh += 1
}.background(Color.white)
}
}
}
}