SwiftUI sectioned list with large data set is sluggish - list

I am trying to create a sections list of a large data set, 20,000 plus records, but this applies for even lower data sets of 5k records,
I have looked at
https://www.raywenderlich.com/27201015-dynamic-core-data-with-swiftui-tutorial-for-ios
and it works fine for a small data set of 20 records but once I add more data by editing this function to create more records
private func addTestFriends() {
let request = Friend.fetchRequest()
let context = PersistenceManager.shared.persistentContainer.viewContext
do {
if try context.count(for: request) == 0 {
for number in 1...1000 {
print(number)
try Friend.generateTestFriends(in: context)
}
}
} catch {
print("Error generating friends: \(error)")
}
}
Which results in 20,000 records the app is really really sluggish, just closing the sections with the disclosure is slow.
Is this a flaw in SwiftUI or is there a different approach?
I also looked at Apples
https://developer.apple.com/documentation/swiftui/loading_and_displaying_a_large_data_feed
which when doing a flat fetch of 10,000+ earth quakes loads fine, but once I try to section it
#SectionedFetchRequest(sectionIdentifier: \.code, sortDescriptors: [SortDescriptor(\.time, order: .reverse)], animation: .default)
private var quakes2: SectionedFetchResults<String, Quake>
sectioning it by "code" and it takes agessssss to load and then again is slugish
This seems a major flaw if you cannot create sections for large data sets of more that 1000 records

Related

Share multiple items (PDF's) with ShareLink and Transferable in SwiftUI

I want to share multiple PDF Documents with apples new ShareLink.
I can share a single PDF Document by making my object transferable using the FileReprestation and giving my object into shareLink:
extension item: Transferable {
public static var transferRepresentation: some TransferRepresentation {
FileRepresentation(exportedContentType: .pdf) { item in
return SentTransferredFile(item.url, allowAccessingOriginalFile: false)
}
}
}
and using ShareLink in my view:
ShareLink(item: item, preview: SharePreview(Text(""))) {
Label("",systemImage: "square.and.arrow.up")
}
Another working option is giving ShareLink the URL of my PDF.
Now I want to share multiple PDF docs at the same time. I have an Array of Items ([Item]). Sadly ShareLink does not take and array of objects as input...
How can I solve the problem?

What is the best way to demux a publisher's Output?

I have an ObservableObject with a few publishers:
private class ViewModel: ObservableObject {
#Published var top3: [SearchResult] = []
#Published var albums: [SearchResult.Album] = []
#Published var artists: [SearchResult.Artist] = []
}
The endpoint is a URLSessionDataPublisher that sends a single collection of values that can be either an album or an artist (there are actually more types but I'm reducing the problem set here.) What is the best way in Combine to separate this collection out into 3 collections: [Album], [Artist], and an array of 3 results that can be either Artist or Album?
DatabaseRequest.Search(for: searchTerm)
.publisher()
// now i want to separate the collection out into [Album] and [Artist] and assign to my 3 #Published vars
.receive(on: DispatchQueue.main)
.sink { }
.store(in: bag)
You are hitting a bit of a (common) fallacy that Combine is responsible for passing the changed data in SwiftUI. It isn't. The only thing Combine is doing here is providing the content-less message that some data has changed, and then the SwiftUI components that are using the relevant model object go and look for their data.
The data transfer in SwiftUI is entirely using Binding, which are essentially get and set closures under the covers.
So you don't really need to worry about demuxing a combine stream - and there isn't one that has "one" of these kinds of data in it. Combine would have trouble with that since it's strongly typed for both Output type and Failure type.
There's a bit more written about this in Using Combine under the chapter
SwiftUI and Combine (chapter link to the free HTML version)

How can I do a "where in" type query using ember-data

How can I perform a where-in type query using ember-data?
Say I have a list of tags - how can I use the store to query the API to get all relevant records where they have one of the tags present?
Something like this:
return this.store.find('tags', {
name: {
"in": ['tag1', 'tag2', 'tag3']
}
})
There isn't built in support for something like that. And, I don't think its needed.
The result that you are after can be obtained in two steps.
return this.store.find('posts'); // I guess its a blog
and then in your controller you use a computed property
filteredPosts: function('model', function() {
var tags = ['tag1', 'tag2', 'tag3'];
return this.get('model').filter(function(post) {
if ( /* post has one of tags */ ) {
}
return false;
});
});
Update: What if there are tens of thousands of tags?!
Amother option is to send a list of tags as a single argument to the back end. You'll have to do a bit of data processing before sending a request and before querying.
return this.store.find('tags', {
tags: ['tag1', 'tag2', 'tag3'].join(', ')
})
In your API you'll know that the tags argument needs to be converted into an array before querying the DB.
So, this is better because you avoid the very expensive nested loop caused by the use of filter. (expensive !== bad, it has its benefits)
It is a concern to think that there will be tens of thousands of tags, if those are going to be available in your Ember app they'll have a big memory footprint and maybe something much more advanced is needed in terms of app design.

CouchDB filter timestamps in a reduce function - some sort of Date.now?

Can a Date.now type function be used in either map or reduce functions? Can it be used anywhere at all?
More specifically, the view must not cache the Date.now value.
Here is what I tested that only worked for the first run after a change to any view function:
function (doc){
var n = new Date();
if(doc.TimeStamp > n.getTime() - 30000){
emit(doc._id, doc);
}
}
The view rows will be refreshed only when the particular doc gets updated. But you can request the view for that result: emit the doc.TimeStamp as key and request the view with ?startkey=timestamp where timestamp is the value of now.getTime() - 30000.
Yes. var now = new Date() should work.
The condition must result in false. You can test it with the view:
function (doc) {
var now = new Date()
var timestamp = now.getTime()
emit(timestamp,null)
}
It will respond something like
{
"total_rows":1,
"offset":0,
"rows" :[{
"id":"ecd99521eeda9a79320dd8a6954ecc2c",
"key":1429904419591, // timestamp as key
"value":null
}]
}
Make sure that doc.TimeStamp is a number (maybe you have to execute parseInt(doc.TimeStamp)) and greater then timestamp - 30000
Two words about your line of code emit(doc._id, doc);:
To emit doc._id as key means maybe you doesn't need the view. Simply request the doc by GET /databasename/:id. Also to include doc._id in multipart keys or the value of the view row is mostly not necessary because its included in every row automatically as additional property. One valid reason would be when you want to sort the view over the doc ids.
To emit the doc as value is not recommended for performance reasons of the view. Simply add ?include_docs=true when you request the view and every row will have an additional property doc with whole doc in it.

Couchbase Map/Reduce to count total by document type

I'm storing event data in Couchbase documents like this:
{
user: {
id: '0BE2DA2B-9C8F-432D-88C2-B2C1D8D0E4B4',
device: { 'manufacturer': 'Apple', 'os': 'iOS', 'name': 'iPhone', 'version': '5S' }
},
event_type: 'INTERACTION_A',
country: 'GB',
timestamp: 1398781631233
}
I have created Map/Reduce queries to tell me how many events iPhone users have submitted. However, is it possible to use Map/Reduce to query how many unique devices by OS are submitting events?
Each individual device might have submitted 1000s of events, but the result would show how many unique devices, by OS, the system has seen. I'm trying to end up with a data that looks something like this:
{ 'iOS': 2343, 'Android': 6343 }
Is it possible to do this in a single Couchbase view?
Yes, it's possible. You just need to use group=true&group_level=1 in your query.
Create a view like:
map : function(){
emit(doc.os, null);
}
reduce: _count
Then add group=true&group_level=1 to your query:
http://127.0.0.1:8092/default/_design/dev_<designDocName>/_view/<viewName>?connection_timeout=60000&limit=10&skip=0&group=true&group_level=1
Also check this links for more examples:
Writing a simple group by with map-reduce (Couchbase)
http://hardlifeofapo.com/basic-couchbase-querying-for-sql-people/
http://blog.couchbase.com/understanding-grouplevel-view-queries-compound-keys
I think my original question might have been too vague. However, I have reached this solution:
map: function (doc, meta) {
emit([doc.user.device.os, doc.user.id], null);
}
reduce: function (keys, values, rereduce) {
var os = {};
keys.forEach(function (k) { os[k] = 1; });
return Object.keys(os).length;
}
Running this view with group=true&group_level=1 gives me what I wanted.
I'm not confident it will scale, or whether it needs to consider rereduce, however it works for my test data set.