Why is WKInterfaceLabel text not refreshing - objective-c

The WatchApp receives data from the iPhone.
I refresh the label text with the data received, nothing happens, the UI is not refreshing.
Other threads suggested pushing it to the main thread and that seems to do nothing either.
Any thoughts most welcome.
-(void)session:(nonnull WCSession *)session didReceiveApplicationContext:(nonnull NSDictionary *)applicationContext
{
dispatch_async(dispatch_get_main_queue(), ^{
[self.lblTitleBorH setText:#"test"];
});
}

Are you using
[*your session* updateApplicationContext:*your dictionary* error:nil];
correctly?
try putting a NSLog inside your above didReceiveApplicationContext code and see if it is printing anything out.

In my case, when I tried to refresh the UI, I found that the outlet references were nil. The problem was caused by two interfaces on the storyboard, belonging to the same WKInterfaceController class.
When I assigned the second screen interface to another WKInterfaceController class it worked fine.
remember to call the UI objects from the main thread by using
dispatch_async(dispatch_get_main_queue(), ^{
...
});
or by using methods like performSelectorOnMainThread: withObject: waitUntilDone:

Related

How to make [NsApp run] not block?

I'm a complete Cocoa newbie.
Right now my simple Hello World app blocks after calling [NsApp run] from main().
All I need is create a window and not block main().
I want my application to behave like glfw:
https://github.com/glfw/glfw/blob/master/src/cocoa_window.m#L1022
For some reason, it doesn't block there. In fact, you can remove this line, and it will still work.
I've been playing with a glfw source to figure out what they do differently. And for example, [NsApp run] blocks if I remove [NSApp setDelegate:_glfw.ns.delegate];
But that's not it.
According to the Apple docs:
The NSApplication class sets up #autorelease block during
initialization and inside the event loop—specifically, within its
initialization (or shared) and run() methods.
Typically, an app creates objects either while the event loop is
running or by loading objects from nib files, so this lack of access
usually isn’t a problem. However, if you do need to use Cocoa classes
within the main() function itself (other than to load nib files or to
instantiate NSApplication), you should create an #autorelease block to
contain the code using the classes.
I guess that's what I need, but I have no idea how to use the #autorelease block.
Thanks for your help.
I figured it out.
GLFW implements its own event loop, so calling [NSApp run] is not needed:
NSEvent *event = [NSApp nextEventMatchingMask:NSEventMaskAny
untilDate:[NSDate distantFuture]
inMode:NSDefaultRunLoopMode
dequeue:YES];
[NSApp sendEvent:event];
I've been looking at this problem myself, and decided to organize everything everyone said and I have found into one answer.
You should probably note that the reason run blocks is because it has something functionally similar to a while(1) loop inside of it which updates the window and handles events and such. Calling [NSApp stop] stops this loop so it wont block anymore (good), but we also have no more events (bad).
Overall, what GLFW does is the following:
Create an NSApplication, create a delegate, and set the delegate to the application.
Call run and stop on the NSApp. stop is called inside applicationDidFinishLaunching of the delegate.
On each frame get the next event from queue and dispatch it down the application.
More in-depth with code: PS. All code is (altered for simplicity) from the GLFW github
// src/cocoa_init.m
int _glfwPlatformInit(void)
{
...
//setup NSApplication, then init and set delegate
[NSApplication sharedApplication];
GLFWApplicationDelegate* del = [[GLFWApplicationDelegate alloc] init];
[NSApp setDelegate:del];
//This is a guard to make sure run is only called once
if (![[NSRunningApplication currentApplication] isFinishedLaunching])
[NSApp run];
}
Note the isFinishedLaunching guard exists because applicationDidFinishLaunching in the delegate only gets called after the first run call. If for some reason the user called glfwInit() again, without the guard run would be called, causing it to block again.
// src/cocoa_init.m
#interface GLFWApplicationDelegate : NSObject <NSApplicationDelegate>
#end
#implementation GLFWApplicationDelegate
...
- (void)applicationDidFinishLaunching:(NSNotification *)notification
{
_glfwPlatformPostEmptyEvent();
[NSApp stop:nil];
}
#end
// src/cocoa_window.m
void _glfwPlatformPollEvents(void)
{
#autoreleasepool {
for (;;)
{
NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny
untilDate:[NSDate distantPast]
inMode:NSDefaultRunLoopMode
dequeue:YES];
if (event == nil)
break;
[NSApp sendEvent:event];
}
} // autoreleasepool
}
Note glfwPollEvents retrieves the next event from the queue, then sends the event to the application to be propogated through the responders. It functions as the main loop explained in the documentation about event architecture.
In the main event loop, the application object (NSApp) continuously gets the next (topmost) event in the event queue, converts it to an NSEvent object, and dispatches it toward its final destination
I just experienced the same scenario.
I and found a little different solution.
Just call [[NSApplication sharedApplication] run]; and when your window is created, call [[NSApplication sharedApplication] stop:nil];, the run call will now exit. And then you can still call the sendEvent method.

pushViewController Taking too much to show the view

I have a really light ViewController, it does nothing in viewDidLoad. I'm pushing this view on top of a navigationController. The method who does this action is called from inside a block. After the call to showView I added an NSLog, and that log prints in the console really fast, but the view takes a lot to load... I really don't understand what maybe happening... any idea???
ABAddressBookRequestAccessWithCompletion(addressBookRef, ^(bool granted, CFErrorRef error) {
[self showView];
NSLog(#"EXECUTED");
});
- (void) showView{
TestViewController *test = [[TestViewController alloc]init];
[self.navigationController pushViewController:test animated:NO];
}
From the docs for ABAddressBookRequestAccessWithCompletion:
The completion handler is called on an arbitrary queue. If your app uses an address book throughout the app, you are responsible for ensuring that all usage of that address book is dispatched to a single queue to ensure correct thread-safe operation.
You should make sure your UI code is called on the main thread.
ABAddressBookRequestAccessWithCompletion(addressBookRef, ^(bool granted, CFErrorRef error {
dispatch_async(dispatch_get_main_queue(), ^{
[self showView];
NSLog(#"EXECUTED");
});
});
This might not be the only problem, but according to the docs, the completion handler passed to ABAddressBookRequestAccessWithCompletion is called on an arbitrary queue. -showView should only be called on the main queue since it is dealing with UIViewController objects.
The other thing to ask is what else is happening on the main queue? Are there other long-running tasks that could be blocking UI updates?

addSubview waits for rest of method

I would like show my view immediately when I call it. I don't know how to make the view show.
-(IBAction) showProgress: (id) sender {
progressViewController *progress = [[progressViewController alloc] initWithNibName:#"progressViewController" bundle:NULL];
[self.view addSubview:progress.view];
[self someFunctionWhichTakesAgesToBeDone];
}
It's called from current UIViewController. And the view appears after the long function. How can I show it before the long funcion? Thanks for answer.
Use GCD (Grand Central Dispatch) which is the simplest way (and recommended by Apple), the code will then be:
-(IBAction) showProgress: (id) sender {
progressViewController *progress = [[progressViewController alloc] initWithNibName:#"progressViewController" bundle:NULL];
[self.view addSubview:progress.view];
// Heavy work dispatched to a separate thread
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(#"dispatched");
// Do heavy or time consuming work
[self someFunctionWhichTakesAgesToBeDone];
// When finished call back on the main thread:
dispatch_async(dispatch_get_main_queue(), ^{
// Return data and update on the main thread
});
});
}
It´s two blocks. The first one does the heavy work on a separate thread and then a second block is called when the heavy work is finished so that changes and UI updates are done on the main thread, if needed.
https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/nsobject_Class/Reference/Reference.html
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait`
use
[self.view performSelectorOnMainThread:#selector(addSubview:) withObject:progress.view waitUntilDone:YES]
or put your Sleep() function (i hope it's anything else, Sleep() func is really bad, as its been told) into another function MySleepFunc and call
[self performSelector:#selector(MySleepFunc) withObject:nil afterDelay:0.003]
instead of Sleep(3).
Read about multi-threading. In short, there's single UI thread that does drawing, accepting user events and so on. If you pause it with sleep() or any other blocking method, nothing will be shown/redrawn and no events will be processed. You have to make your HTTP request from background thread.

WebView did finish launching does not work

what is wrong with this method?
- (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame {
[activityIndicator stopAnimation:self];
}
I want to stop the Circular Progress Indicator (activityIndicator). But there is something wrong with - (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame. I am coding for mac osx and not for iOS. I heard something from Delegates, what does that mean?
Check to be sure that your UIWebView has set its delegate. Setting a delegate is basically telling the program who you want to handle events (like taps, gestures, or, in this case, the loading of a webView). Thus when an event is fired, it will inform the delegate and the delegate can process it. Maybe if you post more of your code it would help, but I would check your declaration of the UIWebView in question. Be sure that after you allocate it and initialize it, you set its delegate to self (assuming that this method is in the same class), like so:
UIWebView *myWebView = [[UIWebView alloc] init];
[myWebView setDelegate:self];
If you have not set the delegate, it is firing off events and no one is receiving them to process them. The method you are using is waiting for the specific event sent by any webView. When it is sent an event message it passes, as a parameter, the webView that triggered. In any case, put in a log statement to be sure you are entering the method. That will tell you if it is receiving the event messages.
- (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame {
NSLog(#"Did finish loading...");
[activityIndicator stopAnimation:self];
}
NOTE: This is as per iOS experience, but should work for Mac OS as well. Let me know what your log result is, if the method is getting called or not.

NSOperationQueue and UITableView release is crashing my app

This is by far the weirdest problem I've been stuck with.
I have a UIViewController on a UINavigationController and I want to call a method at viewDidAppear using NSInvocationOperation so it can run on a back thread when the view becomes visible.
The problem is that if I pop the view controller BEFORE the operation (in this case the testMethod method) completes running, the app crashes.
Everything works fine if I pop the view controller AFTER the operation runs it's course.
When the app crashes, it stops at [super dealloc] with "EXC-BAD-ACCESS" and gives me the following error.
bool _WebTryThreadLock(bool), xxxxxxxxx: Tried to obtain the web lock
from a thread other than the main thread or the web thread. This may
be a result of calling to UIKit from a secondary thread. Crashing
now...
And this is my code (super simplified)..
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSInvocationOperation *theOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(testMethod) object:nil];
[operationQueue addOperation:theOperation];
[theOperation release];
}
- (void)testMethod
{
NSLog(#"do some stuff that takes a few seconds to complete");
}
- (void)dealloc
{
[_tableView release];
[super dealloc];
}
The testMethod has some code that takes a few seconds to complete. I only have a few clues and I really don't know how and where to start debugging this.
Clue #1: The funniest thing is that if I remove the [_tableView release]; from dealloc then the app doesn't crash. But of course this would cause a leak and I can't remove it.
Clue #2: I've tested this code on a separate "clean" UIViewController with a UITableView and to my surprise it didn't crash.
Clue #3: The app doesn't crash is the UITableView's datasource is set to nil in viewDidLoad.
Clue #4: The app doesn't seem crash if I use the same code in viewDidAppear somewhere else like an IBAction.
Clue #5: I've tried looking over stack data with NSZombie but it gives me tons of data and it leads me nowhere.
I have some very complicated code within my UITableViewDelegate and UITableViewDataSource and I really don't know where to start debugging this. I really hope I don't have to go through line by line or rewrite the entire thing because of this.
Any pointers on where I should be looking?
The problem is likely that your view controller's last reference is the operation queue holding onto it, which means you are technically calling (or having the system call) some UIKit methods in a background thread (a big no-no) when the operation cleans up.
To prevent this from happening, you need to send a keep-alive message to your controller on the main thread at the end of your operation, by adding something like this to the last line in your testMethod:
[self performSelectorOnMainThread:#selector(description) withObject:nil waitUntilDone:NO];
There is still a chance that this message may get processed before the operation queue releases your view controller, but that chance is pretty remote. If it's still happening, you could do something like this:
[self performSelectorOnMainThread:#selector(keepAlive:)
withObject:[NSNumber numberWithBool:YES]
waitUntilDone:NO];
- (void)keepAlive:(NSNumber *)fromBackground
{
if (fromBackground)
[self performSelector:#selector(keepAlive:) withObject:nil afterDelay:1];
}
By sending a message to your view controller on the main thread, it will keep the object alive (NSObject retains your view controller until the main thread handles the message). It will also keep the view controller alive if you perform a selector after a delay.
You're crashing because the controller is still trying to use your tableView reference and since you poped the viewController, everything will go away in the dealloc and the tableView is still populating itself.
You can try asking in your dealloc method if your operation is still running, so you can cancel it and the everything should be fine.
Once you add an operation to a queue, the operation is out of your
hands. The queue takes over and handles the scheduling of that task.
However, if you decide later that you do not want to execute the
operation after all—because the user pressed a cancel button in a
progress panel or quit the application, for example—you can cancel the
operation to prevent it from consuming CPU time needlessly. You do
this by calling the cancel method of the operation object itself or by
calling the cancelAllOperations method of the NSOperationQueue class.
Cancelling an operation does not immediately force it to stop what it
is doing. Although respecting the value returned by the isCancelled is
expected of all operations, your code must explicitly check the value
returned by this method and abort as needed. The default
implementation of NSOperation does include checks for cancellation.
For example, if you cancel an operation before its start method is
called, the start method exits without starting the task.