Hi I have simple CAShapeLayer animation
let ballFrameAnimation = CAKeyframeAnimation()
aBall.removeFromSuperlayer()
self.view.layer.addSublayer(aBall)
ballFrameAnimation.keyPath = "position"
ballFrameAnimation.duration = 3.0
ballFrameAnimation.calculationMode = kCAAnimationDiscrete
ballFrameAnimation.fillMode = kCAFillModeForwards
ballFrameAnimation.isRemovedOnCompletion = true
ballFrameAnimation.delegate = self
with a callback to keeps it repeating
func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
let name = anim.value(forKey: "name") as? String
if name == "form" {
attachAnimation()
forwardDirection = !forwardDirection
}
However this persists and loops in the background ( at high speed) even when I pop the viewcontroller off the stack. It also shows a memory leak as well on instruments.
#IBAction func unwindDismiss(_ sender: Any) {
aBall.removeAnimation(forKey: "ballAnimation")
aBall.removeFromSuperlayer()
navigationController?.popToRootViewController(animated:true)
dismiss(animated: true, completion: nil)
}
It was simple.
The endless loop animation would not be released by simply dismissing the viewcontroller so it persisted in the background.
By adding a
if navigationController?.topViewController == self {
loop check within the animation did stop cycling.
this fixed not only the accelerating background loops but also the memory leaks. also a warning to fellow NOOBs : when using the instruments repeatedly: close and restart them frequently and also clean builds otherwise you are likely to get bogus leaks.
Related
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(>k::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(¬es_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.
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.
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
I am using the following IB Action to delete data from my Core Data and TableView:
//Events
#IBAction func btnDelTask_Click(sender: UIButton){
let appDel: foodforteethAppDelegate = UIApplication.sharedApplication().delegate as foodforteethAppDelegate
let context: NSManagedObjectContext = appDel.managedObjectContext
/*
let request = NSFetchRequest(entityName: "Food")
myList = context.executeFetchRequest(request, error: nil)
*/
if let tv = tblTasks {
var bas: NSManagedObject!
for bas: AnyObject in myList
{
context.deleteObject(bas as NSManagedObject)
}
myList.removeAll(keepCapacity: false)
tv.reloadData()
}
}
So when I run this in the simulator and click the button to delete then everything seems fine. The table clears. I can go back and forth through the app to different parts of it and return to the table and everything is still clear.
HOWEVER, if I then restart the simulator (without resetting the content and settings), the rows in the table that were previously deleted, suddenly reappear :/
Why is this happening? I don't think it is actually deleting everything from the Core Data which I've found out by commenting out 'myList.removeAll(keepCapacity:false)' and then it doesn't work even normally and nothing deletes.
Any help?
In short:
I want to show a view or action sheet and only continue code execution after the user has dismissed the view / sheet. So: line one shows the view, line two reads some result variable.
In detail why I would need this:
I'm porting a Windows Forms application over to the iPad. The original implementation has a communication class which uses a web service to communicate with the server. It offers a couple of methods to get data. Conveniently it checks prior to each call if the user still has a valid connection or if he has to re-enter his password for security reasons.
If the password is required, the .NET class shows a modal dialog which blocks any further code executio and if the password was entered, retries the last call it has made before showing the dialog.
Now using CocoaTouch I'm facing a problem. I replaced the code that shows the dialog with a UIActionSheet. Works great but code execution continues immediately, whereas in Windows Forms it is blocked (the next line in Windows Forms after showing the dialogs is to read the entered password from the dialog) until the dialog has been closed.
I tried a Thread.Sleep() until the user dismisses the UIActionSheet but the Thread.Sleep() also blocks the main loop and my view won't even be drawn.
The alternative I currently see is to change all methods in the already working class and give them a return value: if password required, handle it, then retry.
But this means that all over my code I will have to add these checks because at any given moment the password might be needed. That's why it is nested in communication class in Windows Forms.
Any other ideas?
René
Yes, it is possible.
To do this, what you can do is to run the mainloop manually. I have not managed to stop the mainloop directly, so I instead run the mainloop for 0.5 seconds and wait until the user responds.
The following function shows how you could implement a modal query with the above approach:
int WaitForClick ()
{
int clicked = -1;
var x = new UIAlertView ("Title", "Message", null, "Cancel", "OK", "Perhaps");
x.Show ();
bool done = false;
x.Clicked += (sender, buttonArgs) => {
Console.WriteLine ("User clicked on {0}", buttonArgs.ButtonIndex);
clicked = buttonArgs.ButtonIndex;
};
while (clicked == -1){
NSRunLoop.Current.RunUntil (NSDate.FromTimeIntervalSinceNow (0.5));
Console.WriteLine ("Waiting for another 0.5 seconds");
}
Console.WriteLine ("The user clicked {0}", clicked);
return clicked;
}
I think this approach using async/await is much better, and doesn't suffer from freezing the app when rotating the device, or when the autoscrolling interferes and leaves you stuck in the RunUntil loop forever without the ability to click a button (at least these problems are easy to reproduce on iOS7).
Modal UIAlertView
Task<int> ShowModalAletViewAsync (string title, string message, params string[] buttons)
{
var alertView = new UIAlertView (title, message, null, null, buttons);
alertView.Show ();
var tsc = new TaskCompletionSource<int> ();
alertView.Clicked += (sender, buttonArgs) => {
Console.WriteLine ("User clicked on {0}", buttonArgs.ButtonIndex);
tsc.TrySetResult(buttonArgs.ButtonIndex);
};
return tsc.Task;
}