I'm using AsyncSocket to connect to a server from my iPhone App. In the delegate that received data from the server, I post a notification that would tell the tableView's delegate to trigger a reloadData on the tableView:
- (void)onSocket:(AsyncSocket *)sock didReadData:(NSData*)data withTag:(long)tag {
[[NSNotificationCenter defaultCenter] postNotificationName:#"PEERSTATUSCHANGED" object:self];
[sock readDataToData:[AsyncSocket CRLFData] withTimeout:-1 tag:0];
}
and on the viewController:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(peerStatusDidChange:) name:#"PEERSTATUSCHANGED" object:nil];
}
return self;
}
- (void)peerStatusDidChange:(NSNotification *)notification {
NSLog(#"NOTIFICATION RECEIVED");
}
Now, this doesn't work at all. The notification is posed but not recognized by the ViewController. However, when I do the same thing in applicationDidFinishLaunching:
- (void)applicationDidFinishLaunching:(UIApplication *)application {
protocol = [[XBBProtocol alloc] init];
SourceListViewController *sourceListVC = [[[SourceListViewController alloc] initWithNibName:#"SourceListViewController" bundle:nil] autorelease];
UINavigationController *navigationController = [[[UINavigationController alloc] initWithRootViewController:sourceListVC] autorelease];
[[NSNotificationCenter defaultCenter] postNotificationName:#"PEERSTATUSCHANGED" object:self];
[protocol connectToServer];
// Override point for customization after application launch
[window addSubview:[navigationController view]];
[window makeKeyAndVisible];
}
I got the notification received in viewController.
Anyone knows why? does it have something to do with delegate methods of AsyncSocket being in different thread?
Thanks in advance.
One possibility is that your initWithNibName:bundle: method is not actually being called. If you instantiate the view controller in a NIB (rather than in code), then it calls initWithCoder: instead.
A quick way to check is to put a breakpoint in initWithNibName:bundle:.
Try putting the method that sends the notification in a different method, and call it with "performSelectorOnMainThread". It's very likely your network code is getting called in a background thread and thus when the notification fires, it informs the table view on the same thread...
You can't make UI calls on anything but the main thread.
Related
I would like to refresh a UIWebView whenever my app comes to the foreground. All I really have in my ViewController.m is a method that checks for internet access (hasInternet) and viewDidLoad.
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
#synthesize webview;
-(BOOL)hasInternet{
Reachability *reach = [Reachability reachabilityWithHostName:#"www.google.com"];
NetworkStatus internetStats = [reach currentReachabilityStatus];
if (internetStats == NotReachable) {
UIAlertView *alertOne = [[UIAlertView alloc] initWithTitle:#"You're not connected to the internet." message:#"Please connect to the internet and restart the app." delegate:self cancelButtonTitle:#"Dismiss" otherButtonTitles:nil];
[alertOne show];
}
return YES;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self hasInternet];
[webView loadRequest: [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://warm-chamber-7399.herokuapp.com/"]] ];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
Any advice on how to enable this functionality? Does it go in AppDelegate or do I create another method within ViewController.m?
You should register a UIApplicationWillEnterForegroundNotification in your ViewController's viewDidLoad method and whenever app comes back from background you can do whatever you want to do in the method registered for notification. ViewController's viewWillAppear or viewDidAppear won't be called when app comes back from background to foreground.
-(void)viewDidLoad{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(doYourStuff)
name:UIApplicationWillEnterForegroundNotification object:nil];
}
-(void)doYourStuff{
[webview reload];
}
Don't forget to unregister the notification you are registered for.
-(void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
Note if you register your viewController for UIApplicationDidBecomeActiveNotification then your method would be called everytime your app becomes active, It would not be appropriate to register for this notification.
Register for the UIApplicationDidBecomeActiveNotification or the UIApplicationWillEnterForegroundNotification.
what method within a ViewController's class can I call to check when it has been brought to the foreground?
For example Im looking at a page on my application and I decide to close the application and go back to it later. When I go back to it the same view as I was looking at was on the screen. However... As soon as I open the application I want to segue over to another view.
How can I do this?
Currently trying this:
- (void) applicationDidBecomeActive:(NSNotification*) notification
{
[self checkActivity];
// Do your stuff here
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationWillEnterForeground:)
name:UIApplicationWillEnterForegroundNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationDidBecomeActive:)
name:UIApplicationDidBecomeActiveNotification
object:nil];
}
return self;
}
- (void)checkActivity{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSLog(#"Checking if re-authentication required...");
if([[defaults objectForKey:#"shouldgotologin"] isEqualToString:#"yes"]){
NSLog(#"View Should go to login...performing segue");
[defaults setObject:#"no" forKey:#"shouldgotologin"];
[defaults synchronize];
[self performSegueWithIdentifier:#"backtologin" sender:self];
} else {
NSLog(#"Should go to login is not true.");
}
}
Register your view controller to observe UIApplicationWillEnterForegroundNotification:
1) Inside view controller's init method:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationWillEnterForeground:)
name:UIApplicationWillEnterForegroundNotification
object:nil];
2) Inside view controller's dealloc method:
[[NSNotificationCenter defaultCenter] removeObserver:self];
3) Also, have your view controller implement this method:
- (void) applicationWillEnterForeground:(NSNotification*) notification
{
// This method will be called just before entering the foreground;
// Do your stuff here
}
If the timing of UIApplicationWillEnterForegroundNotification doesn't suit you, check all the available notifications for UIApplication here:
http://developer.apple.com/library/ios/#DOCUMENTATION/UIKit/Reference/UIApplication_Class/Reference/Reference.html
Jump To ApplicationDelegate File, you will find following methods.
- (void)applicationWillResignActive:(UIApplication *)application
{
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
}
Remember, It is not the viewController who receives the notifications related to the Application states like willResignActive,didEnterBackground,willEnterForeground. ApplicationDelegate object is going to handle those notifications. So, Try putting your logic in above methods.
Hope that helps. If not, add your queries using comments below my answer.
I'm trying to present a Modal View Controller when the app enters in foreground.. These are my files:
AppDelegate.m :
#import "AppDelegate.h"
#import "MainViewController.h"
- (void)applicationWillEnterForeground:(UIApplication *)application
{
[self.window makeKeyAndVisible];
MainViewController * vc = [[MainViewController alloc]init];
[vc myMethodHere];
}
MainViewController.h :
//[..]
-(void) myMethodHere;
MainViewController.m :
-(void)myMethodHere{
NSLog(#"myMethodHere Activated.");
TWTweetComposeViewController *tweetViewController = [[TWTweetComposeViewController alloc] init];
[self presentModalViewController:tweetViewController animated:YES];
}
NSLog(#"myMethodHere Activated.") works.. so I can't understand why "presentModalViewController" doesn't! What should I edit/add? Maybe a delay? Thanks for your help..
p.s. I know my english sucks.. Forgive me :)
I wouldn't rely on the methods in your app delegate for this (even though it seems like the obvious solution) because it creates unnecessary coupling between your application delegate and the view controller. Instead, you can have MainViewController listen for the UIApplicationDidBecomeActive notification, and present the tweet composer view controller in response to this notification.
First, register for the notification in -viewDidLoad.
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(myMethodHere) name:UIApplicationDidBecomeActiveNotification object:nil];
}
Now, when this notification is received when your app returns from the background, myMethodHere will be invoked.
Lastly, remember to remove yourself as an observer when the view unloads.
- (void)viewDidUnload
{
[super viewDidUnload];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil];
}
On Facebook's iPhone app, the news feed refreshes every time the app becomes active. I would like to do something similar, but I'm concerned about a race condition. The general bootstrapping of my app is as follows:
UIApplicationDelegate
- (void)applicationDidFinishLaunching:(UIApplication*)application
{
[window addSubview:[self.navigationController view];
[window makeKeyAndVisible];
}
- (void)applicationDidBecomeActive:(UIApplication*)application
{
[rootViewController refresh];
}
RootViewController
#pragma mark custom
- (void)refresh
{
if (self.newsFeedModel == nil) {
self.newsFeedModel = [[NewsFeedModel alloc] initWithDelegate:self];
}
[self.newsFeedModel request];
}
#pragma mark UIViewController
- (void)viewDidLoad
{
// initialize the table
// add subviews and whatnot
}
#pragma mark NewsFeedDelegate
- (void)newsFeedSucceeded:(NSMutableArray*)feed
{
// reload table view with new feed data
}
After sprinkling NSLog everywhere, I determined the order of operations to be:
applicationDidFinishLaunching
applicationDidBecomeActive
refresh
viewDidLoad
newsFeedSucceeded
Notice how refresh is called before the root view has been loaded. While we're busy querying the server, the root view loads. When the server responds, the root view is populated with the feed. This works in most cases because the network operation takes a long time. However, if the network operation finishes faster than view can be loaded, then I will be attempting to construct the news feed before the view has been loaded. This would be bad. What is the best Cocoa Touch practice for solving this race condition? I would just set a bunch of flags to determine what state we're in and refresh the news feed depending on the state, but I'm wondering if there were built in events in Cocoa Touch to handle this for me.
I think you want to take a look at applicationWillEnterForeground: instead.
applicationDidBecomeActive: can be called while your app is still running in the foreground. For instance if a text message comes while your app is in the foreground and the user dismisses it, applicationDidBecomeActive: will get called.
You can subscribe to the UIApplicationWillEnterForegroundNotification event in your RootViewController using NSNotificationCenter. I would do this in RootViewController initWithNibName: or whichever init method you are using.
Now you just need to call refresh in 2 places. Once at the end of viewDidLoad and again whenever applicationWillEnterForeground: is called.
This should solve your race condition problem. Since RootViewController is handling it's own refreshing when it knows it is ok to do so.
RootViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if(self) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(applicationWillEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
}
return self;
}
- (void)viewDidLoad
{
// initialize the table
// add subviews and whatnot
[self refresh];
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
[self refresh];
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
I have a tabbar application and let's say that I want to switch to the second tab and popup an alert at 12:00, even if my application is not running.
I got all the code for UILocalNotification working correctly, but then I thought that the best way to do that would be by posting a notification from the app delegate:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Add the tab bar controller's view to the window and display.
[window addSubview:tabBarController.view];
[window makeKeyAndVisible];
// Handle launching from a notification when the app is NOT running
UILocalNotification *localNotification = [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];
if (localNotification) {
[tabBarController setSelectedIndex:1];
[[NSNotificationCenter defaultCenter] postNotificationName:#"AlertNotification" object:self];
}
return YES;
}
Then, in my SecondViewController.m, I have:
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(popUpAlert:) name:#"AlertNotification" object:nil];
}
But this does not work. I suspect that the notification is sent while the viewDidLoad of the SecondViewController has not been called yet, right? Is it possible to work this out? And do you agree on my approach of using NSNotificationCenter in this case?
Thanks in advance.
I quickly created a test project and got it working by putting the notification registration in awakeFromNib (assuming SecondViewController is created in a xib file)
- (void)awakeFromNib {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(popUpAlert:) name:#"AlertNotification" object:nil];
}
I guess, you are right. It doesn't work because you are posting the notification before adding the view controller as observer for it.
Another approach would be to add a bool property to the app delegate to indicate whether the app has been started from the local notification.
The app delegate can be requested from anywhere in the app with [[UIApplication sharedApplication] delegate].
UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes: UIUserNotificationTypeAlert | UIUserNotificationTypeBadge categories:nil];
[[UIApplication shareApplication] registerUserNotificationSettings: settings];