Swift 3 tableview.reloadData on main thread, not displaying cells properly - multithreading

In a simple TableViewController i am calling ClouKit deleteRecord in a UIAlertController.
// 2. When the user clicks OK.
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { [weak alert] (_) in
LSCloudKit.shared.deleteRecord(record: login.toCKRecord(), completion: { (success) in
let index = self.logins.index(of: login);
self.logins.remove(at: index!);
self.tableView.reloadData(); // <-problem is here. Data not displayed properly
});
}));
When i call self.tableView.reloadData(), the table seems to update but it display the wrong list, ie: twice the same cell, sometimes its the one that got deleted and sometimes its another one.
I checked that the call is being made on the main thread.
Here is the deleteRecord function too:
func deleteRecord(record: CKRecord!, completion: #escaping (Bool) -> ()) {
let container = CKContainer.default();
let privateDatabase = container.privateCloudDatabase;
privateDatabase.delete(withRecordID: record.recordID) { (recordID, error) in
DispatchQueue.main.async {
completion(error == nil)
}
}
}
If i put a breakpoint in xCode anywhere either inside DispatchQueue.main.async or inside the completion block of "deleteRecord", then everything appears fine on the device once i hit "continue".
The problem happens on simulator and real devices as well.
Please help.
Thank you.
EDIT: Adding screenshot
Here is the screenshot before and after deleting "user1" from the list, with the output:
self.logins before delete: [admin, master, user1, user2, user3]
self.logins after delete: [admin, master, user2, user3]
Before delete
After delete

I was able to work around the problem by using this instead of reloadData()
self.tableView.beginUpdates()
self.tableView.deleteRows(at: [IndexPath.init(row: index!, section: 0)], with: .automatic)
self.tableView.endUpdates()
I will not mark this as the right answer for a while since it is a work around and i still dont understand why reloadData() did not work. Its annoying me.

Related

Seemingly nothing happens when attempting to add to a `gtk::ListBox` from within an event handler in GTK-RS application

I'm attempting to to add to a gtk::ListBox container from within the event handling closure of an unrelated widget. The list box in question is fetched via a gtk::Builder like so:
let notes_list: gtk::ListBox = builder.get_object(NOTES_LIST_BOX_ID).unwrap();
And the event handler where I can't seem to add to notes_list (note that I've tried without the clone! macro, with strong and weak references, wrapping in Rc pointer, etc. but nothing seems to change):
open_menu_item.connect_activate(clone!(#strong state, #strong notes_list => move |_| {
println!("Open database menu item activated");
// Seemingly can't add to notes_list from within this closure???
notes_list.add(&gtk::Label::new(Some("TEST"))); // Doesn't work???
let dialog = gtk::FileChooserDialog::with_buttons::<gtk::Window>(
Some("Open database file"),
None,
gtk::FileChooserAction::Open,
&[("_Cancel", gtk::ResponseType::Cancel),
("_Open", gtk::ResponseType::Accept)]
);
dialog.connect_response(clone!(#weak state, #weak notes_list => move |this, res| {
if res == gtk::ResponseType::Accept {
let file = this.get_file().unwrap();
let path_buf = file.get_path().unwrap();
println!("Opening database file: {}", path_buf.as_path().display());
let mut state = state.borrow_mut();
state.db = database::database_in_file(path_buf.as_path()).ok();
state.update_notes_list(&notes_list);
}
this.close();
}));
dialog.show_all();
}));
No error message is presented - the expected behaviour (i.e. the addition of a gtk::Label to the list box) does not occur.
The full code of this module (and the rest of my messy code base): https://github.com/WiredSound/nos/blob/master/src/gui.rs
If anyone could help me figure this out then I'd really appreciate it, thanks.
Widgets, in GTK3, are hidden by default. This means that calling show_all() on a container will show all its current children. If you add a new child, you're responsible for calling show() on it in order to make it visible.
In your signal handler, you're adding a gtk::Label to the list box, but you need to make it visible as well.

jquery jtable deleteConfirmation function not working

I am trying to use the deleteConfimation function option but I find that the default confirmation box pops up before I even get into the deleteConfimation function - what am I missing?
In the code below I can set break points and watch the data object being set up correctly with its new defaultConfirmMessage, but the basic jtable default delete confirmation box has already appeared and I never see an altered one.
$(container).jtable({
title: tablename,
paging: true,
pageSize: 100,
sorting: true,
defaultSorting: sortvar + ' ASC',
selecting: false,
deleteConfirmation: function(data) {
var defaultMessage = 'This record will be deleted - along with all its assignments!<br>Are you sure?';
if(data.record.Item) { // deleting an item
// Check whether item is in any preset lists
var url = 'CampingTablesData.php?action=CheckPresets&Table=items';
$.when(
ReturnAjax(url, {'ID':data.record.ID}, MyError)
).done(
function(retdata, status) {
if(status=='success') {
if(retdata.PresetList) {
data.deleteConfirmMessage = 'Item is in the following lists: ' + retdata.PresetList + 'Do you still want to delete it?';
}
} else {
data.cancel = true;
data.cancelMessage = retdata.Message;
}
}
);
} else {
data.deleteConfirmMessage = defaultMessage;
}
},
messages: {
addNewRecord: 'Add new',
deleteText: deleteTxt
},
actions: {
listAction: function(postData, jtParams) {
<list action code>
},
createAction: function(postData) {
<create action code>
},
updateAction: 'CampingTablesData.php?action=update&Table=' + tablename,
deleteAction: 'CampingTablesData.php?action=delete&Table=' + tablename
},
fields: tableFields --- preset variable
});
==========
After further testing the problem is only when deleting an item and it goes through the $.when().done() section of code. The Ajax call to the deletion url does not wait for this to complete - how do I overcome this?
i don't think you can get your design to work. What does the A in ajax stand for? Asynchronous! Synchronous Ajax has been deprecated for all sorts of good design and performance reasons.
You need to design you application to function asynchronously. Looking at your code, it feels you are misusing the deleteConfirmation event.
Consider changing the default deleteConfirmation message to inform the user, that the delete might not succeed if certain condition are met. Say
messages: {
deleteConfirmation: "This record will be deleted - along with all its assignments, unless in a preset list. Do you wish to try to delete this record?"
},
Then on the server, check the preset lists, and if not deletable, return an error message for jTable to display.
Depending on how dynamic your preset lists are, another approach might be to let the list function return an additional flag or code indicating which, if any, preset lists the item is already in, then your confirmation function can check this flag / indicator without further access to the server.
Thanks to MisterP for his observation and suggestions. I also considered his last approach but ended up setting deleteConfirmation to false (so as not to generate a system prompt) then writing a delete function that did not actually delete, but returned the information I needed to construct my own deleteConfimation message. Then a simple if confirm(myMessage) go ahead and delete with another Ajax call.

How to display standby dialog in XPages view when expanding a section

In my XPages application I am using the xe:dynamicViewPanel control and would like to add a standby/wait dialog/popup when a section is being expanded by the user (click on the expand-icon to open the section).
Sometimes the view index is not up-to-date and opening a category holding a lot of documents will last a while, in the meantime I want to display some "loading dialog" (which I already have, so, no need to explain how to do this).
My problem is, that I can not find any event or entry point where to start from.
Thank you all !
Alex
You can try code from this link:
https://openntf.org/XSnippets.nsf/snippet.xsp?id=standby-dialog-custom-control
If you want to show stanby dialog on the current section, replace the 79 line
var forms=dojo.body()
with some other container. For example, a partial refresh element
var forms = dojo.byId(refreshId)
In this case you need to replace lines 75 and 140 to pass the id parameter
function StandbyDialog_Started(refreshId) {
try{
if(StandbyDialog_Do==true){
if(this.StandbyDialog_Obj==null) {
var forms= (refreshId)?dojo.byId(refreshId):dojo.body();
this.StandbyDialog_Obj = new dojox.widget.Standby({
target: forms,
zIndex: 10000
});
document.body.appendChild(this.StandbyDialog_Obj.domNode);
this.StandbyDialog_Obj.startup();
}
StandbyDialog_StoreField()
setTimeout("if(StandbyDialog_Do==true){StandbyDialog_StoreField()}",50);
setTimeout("if(StandbyDialog_Do==true){this.StandbyDialog_Obj.show()}",200);
}
}catch(e){
console.log("StandbyDialog_Started:"+e.toString())
}
}
and
dojo.subscribe( 'partialrefresh-start', null, function( method, form, refreshId ){
StandbyDialog_Do=true
StandbyDialog_Started(refreshId)
});
I didn't test it, but I hope it can help you to go further.

Main thread doesn't wait background thread to finish in Swift

Here's my problem : I'm doing a background work, where I parse some JSON and write some Objects into my Realm, and in the main thread I try to update the UI (reloading the TableView, it's linked to an array of Object). But when I reload the UI, my tableView doesn't update, like my Realm wasn't updated. I have the reload my View to see the updates. Here's my code :
if (Realm().objects(Objects).filter("...").count > 0)
{
var results = Realm().objects(Objects) // I get the existing objects but it's empty
tableView.reloadData()
}
request(.GET, url).responseJSON() {
(request, response, data, error) in
let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_async(dispatch_get_global_queue(priority, 0)) {
// Parsing my JSON
Realm().write {
Realm().add(object)
}
dispatch_sync(dispatch_get_main_queue()) {
// Updating the UI
if (Realm().objects(Objects).filter("...").count > 0)
{
results = Realm().objects(Objects) // I get the existing objects but it's empty
tableView.reloadData()
}
}
}
}
I have to do something bad with my threads, but I couldn't find what. Can someone know what's wrong?
Thank you for your answer!
such workflow makes more sense to me for your case:
let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_async(dispatch_get_global_queue(priority, 0)) {
// Parsing my JSON
Realm().write {
Realm().add(object)
dispatch_sync(dispatch_get_main_queue()) {
// Updating the UI
if (Realm().objects(Objects).filter("...").count > 0)
{
results = Realm().objects(Objects) // I get the existing objects but it's empty
tableView.reloadData()
}
}
}
}
NOTE: you have a problem with timing in your original workflow: the UI might be updated before the write's block executed, that is why your UI looks abandoned; this idea above would be a more synchronised way between tasks, according their performance's schedule.
You are getting some new objects and storing them into "results".
How is tableView.reloadData () supposed to access that variable? You must change something that your tableView delegate will access.
PS. Every dispatch_sync () is a potential deadlock. You are using one that is absolutely pointless. Avoid dispatch_sync unless you have a very, very good reason to use it.

UIView element not updating

I need to get data from a API I’ve used debug prinln’s and these print almost instantly including the information gotten from the API, but when I try to display this info to the user in the ViewController this takes around 30 seconds to display.
I will get the information from the API here
func httpGet(request: NSURLRequest!, callback: (NSData?, String?) -> Void) {
var session = NSURLSession.sharedSession()
var task = session.dataTaskWithRequest(request){
(data, response, error) -> Void in
if error != nil {
callback(nil, error.localizedDescription)
} else {
callback(data, nil)
}
}
task.resume()
}
I invoke the request and return a string for debugging
func loadSchedule() {
httpGet(request) {
(data, error) -> Void in
onComplete(“Milo Cesar”)
}
And here I will display the data to the user
#IBAction func settingsButtonPress(sender: AnyObject) {
println("Settings Button Pressed: Invoking Schedule")
Schedule().loadSchedule(){
(scheduleData) in
println("Found Schedule: Setting Title to \(scheduleData)")
self.settingsButton.setTitle(scheduleData, forState: UIControlState.Normal)
}
}
The whole process from the “Settings Button Pressed: Invoking Schedule” statement in my console till I get “Found Schedule: Settings Title to Milo Cesar” takes less than a second.
Since the name already gets displayed in that console log I think that the information has been loaded. Though it takes up to 35 seconds for the Button to change it’s title.
Why does it take so long for my UIButton to update it’s title after the data has been retrieved?
Are you sure you are updating its title in main thread? try to wrap it up in GCD
dispatch_async(dispatch_get_main_queue()){
self.settingsButton.setTitle(scheduleData, forState: UIControlState.Normal)
}
EDIT
it fixes it because everything UI related must be performed by main thread, its a restriction that comes directly from Apple. You can use background threads for long taking tasks(download data, perform long taking calculations or sort large arrays) but everything that needs to be displayed on your screen must be performed by main thread(tableview reload, animations, inserting text into label).
Also here is a great tutorial on GCD and its functionality: http://www.raywenderlich.com/60749/grand-central-dispatch-in-depth-part-1

Resources