I am very new to threads so please be gracious.
In the Login View Controller after the user is authenticated, I launch a thread to get the users geolocation every 30 seconds (only want to do this when the user is logged in) and then move to the main view controller of the application which displays the applications main information.
When the user logs out, I want to cancel the thread created to gather the geolocation every 30 seconds.
How do I do this?
Am I approaching this correct? If not, code examples and explanation pleases
Thanks so much!!!!
Loggin View Controller
...
- (IBAction)loginButton:(id)sender {
NSInteger success = 0;
//Check to see if the username or password texfields are empty or email field is in wrong format
if([self validFields]){
//Try to login user
success = [self loginUser];
}
//If successful, go to the MainView
if (success) {
//Start getting users Geolocation in a thread
[NSThread detachNewThreadSelector:#selector(startGeolocation:) toTarget:self withObject:nil];
//Go to Main view controller
[self performSegueWithIdentifier:#"loginSuccessSegue" sender:self];
}
else
{
//Reset password text field
self.passwordTextField.text = #"";
}
}
...
//Thread to get Geolocation every 30 seconds
-(void)startGeolocation:(id)param{
self.geoLocation = [[GeoLocation alloc] init];
while(1)
{
//****************START GEOLOCATION*******************************//
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
[appDelegate.databaseLock lock];
NSLog(#"Geolocation:(%f,%f)", [self.geoLocation getLatitude], [self.geoLocation getLongitude]);
sleep(30);
[appDelegate.databaseLock unlock];
}
}
Main View Controller
...
//When the Logout Button in MenuView is pressed this method will be called
- (void)logoutButton{
//Cancel the geolocation tread
//????????????????????????????
//Log the user out
[self logoutUser]
}
...
I would recommend using GCD for this.
dispatch_queue_t dq = dispatch_queue_create("bkgrndQueue", NULL);
dispatch_async(dq, ^{
#autoreleasepool{
while(SEMAPHORE_NAME){
// do stuff in here
}
}
}
and then in your other view controller
SEMAPHORE_NAME = NO;
Related
So i am showing a model controller on top of a view controller. And i have texts in the model controller, but somehow the texts are not visible. I tried everything but somehow labels are not visible. But of you stay on the page for like 30 -40 sec the text shows up. Also this model controller is called from main view controller after a successful service(REST) call. If i call the model without making the service call then labels are visible in simulator/iPad both. But if i call it after service call inside success block then labels are not visible. I tried adding the text programmatically but still same issue. I tried debugging using Color blended layers, but the label is not at all visible in the view somehow. :(
[self.serviceManager getCustDetails:account successBlock:^(NSDictionary * successDict) {
[self hideLoadingAnimation];
NSDictionary *custData = [[successDict objectForKey:#"txnData"] objectForKey:#"custData"];
self.showCurrYear = [iraContribData objectForKey:#"showCurrYear"];
if ([self.showCurrYear isEqual: #"true"]){
[self performSegueWithIdentifier:#"CSegue" sender:self];
}
} failureBlock:^(NSDictionary * failureDict) {
[self hideLoadingAnimation];
NSLog(#"Failiure Dict %#",failureDict);
}];
And this prepareForSegue method, -
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.identifier isEqualToString:#"CSegue"]) {
CustViewController *cVC = segue.destinationViewController;
cVC.delegate = self;
[cVC setModalPresentationStyle:UIModalPresentationFormSheet];
cVC.preferredContentSize = CGSizeMake(800,750);
}
}
Below is my screen in storyboard, but in simulator the label is not visible, only continue and close button is visible.
Please help!, any suggestions are most welcome. Thanks!
It is possible that the delay is due to a user interface update not made on the main thread.
Try to make sure that your code is executed on the main thread using dispatch_async like this :
[self.serviceManager getCustDetails:account successBlock:^(NSDictionary * successDict) {
dispatch_async(dispatch_get_main_queue(), ^{
[self hideLoadingAnimation];
NSDictionary *custData = [[successDict objectForKey:#"txnData"] objectForKey:#"custData"];
self.showCurrYear = [iraContribData objectForKey:#"showCurrYear"];
if ([self.showCurrYear isEqualToString:#"true"]){
[self performSegueWithIdentifier:#"CSegue" sender:self];
}
});
} failureBlock:^(NSDictionary * failureDict) {
dispatch_async(dispatch_get_main_queue(), ^{
[self hideLoadingAnimation];
NSLog(#"Failiure Dict %#",failureDict);
});
}];
I wish to present a particular view Controller when the user taps on a view controller. I know there are many questions for the same thing that have been posted before and there have been many answers as well but none of it seems to be working for me. Here is the code in the 'didReceiveRemoteNotification' method in my 'AppDelegate'
if(application.applicationState == UIApplicationStateInactive) {
NSLog(#"Inactive");
//Show the view with the content of the push
if(userInfo)
{
NSLog(#"In inactive payload");
// Create a pointer to the C object
NSString *cId = [userInfo objectForKey:#"c"];
NSLog(cId);
PFObject *targetC = [PFObject objectWithoutDataWithClassName:#"C" objectId:cId];
// Fetch C object
[targetC fetchInBackgroundWithBlock:^(PFObject *object, NSError *error) {
C *c = [[C alloc] initWithPFObject:object];
// Show c view controller
if (!error) {
NSLog(c.title);
CDetailsViewController *viewController = [[CDetailsViewController alloc] initWithC:c];
//[self.navigationController pushViewController:viewController animated:YES];
[self.navigationController presentViewController:viewController animated:YES completion:nil];
}
}];
}
handler(UIBackgroundFetchResultNewData);
I am getting the exact data I have sent in my push as I can see from the log prints I get. Only problem is the view controller that I am trying to present/push is never seen when the notification is tapped. Any workaround for this? Any thing that I'm doing wrong? Any help is highly appreciated.
If your app is in background, the method "application:didReceiveRemoteNotification:" is never called.
You need add this, in the method: "application:didFinishLaunchingWithOptions:"
// At the end of the method you force call didNotification:
// Handle any notifications.
if ([launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey]!=nil)
{
NSDictionary * aPush =[launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
[self application:application didReceiveRemoteNotification:aPush];
}
return YES;
}
If your app is started due to the user tap a notification, in the launchOptions dict, the notification payload will be attached.
I assume its because didReceiveRemoteNotification: is called on a background thread and presentViewController might need to be called on the main thread.
The below code, forces the view controller to be presented in the main thread asynchronously Note: a block should never be synchronously run on the main thread. To learn more about Grand Central Dispatch, see here.
dispatch_async(dispatch_get_main_queue(), ^
{
CDetailsViewController *viewController = [[CDetailsViewController alloc] initWithC:c];
[self.navigationController presentViewController:viewController animated:YES completion:nil];
}
This function is designed to simulate a wait if the user is successful logging in. As you can see I dismiss the keyboard first but that doesn't stop NSThread from sleeping before the keyboard is dismissed. I think I need to harness the power of the dispatch queue but not quite sure. Any way I can dismiss the keyboard before sleep occurs?
-(IBAction)userLoginButtonPressed:(id)sender
{
/* resign first responders so that the user
can see the label of the server trying to log in */
[self.usernameField resignFirstResponder];
[self.passwordField resignFirstResponder];
self.statusLabel.text = #"Logging In...";
// create the server object and pass in the username and password values
IONServer *server = [[IONServer alloc] init];
NSString *user = self.usernameField.text;
NSString *pw = self.passwordField.text;
[server loggingInWithUserName:user password:pw];
// redirect based on result
if (server.result.success) {
[self serverSuccess];
[NSThread sleepForTimeInterval:2.0f];
} else {
[self serverFailure];
}
// store the server object as this class' server var
self.server = server;
NSLog(#"Result From Server: %#", server.result);
}
dismissing the keyboard is animated and this is enough for us to know that it happens async-ly, on main thread. In other words - you code block starts, dismissing the keyboard being added to main thread runloop, the thread sleeps for 2 seconds because you said so (Terrible thing to do if you ask me), and only than it's the keyboard's turn to get animated down and be dismissed.
A nice trick can be
[UIView animateWithDuration:0 animations: ^{
[self.usernameField resignFirstResponder];
[self.passwordField resignFirstResponder];
} completion: ^(BOOL finished) {
// Do whatever needed...
[NSThread sleepForTimeInterval:2.0f];
}];
BUT - I highly recommend finding a better solution than freezing the main thread.
Also - no what you asked for, but, assuming all your text fields are subviews of self.view you can just call [self.view endEditing:YES]; and you don't need to care about which text field is corrently the responder.
What i want to achieve :
Create processing screen with some custom icons, label etc. with following behaviour.
Add view over window which will not allow user to touch anything in application until it is removed. (like processing/loading screen)
When this view is displayed all other operation like adding subview, performing segue etc should work as they work normally but below my loading view.
Want method showProcessingScreen to work on any thread (Whatever thread switching code etc should be in respective show/hide method).
It should be displayed/removed immediately in after calling respective methods.
Code :
-(void) showProcessingScreen
{
dispatch_async(dispatch_get_main_queue(),
^{
UIStoryboard *mystoryboard = [UIStoryboard storyboardWithName:#"Main_iPhone" bundle:nil];
processingScreen = [mystoryboard instantiateViewControllerWithIdentifier:#"loadingViewController"];
UIWindow* mainWindow = [[UIApplication sharedApplication] keyWindow];
[mainWindow addSubview: processingScreen.view];
[mainWindow bringSubviewToFront:processingScreen.view];
});
}
-(void) hideProcessingScreen
{
dispatch_async(dispatch_get_main_queue(),
^{
[processingScreen.view removeFromSuperview];
});
}
Issue:
I want code above to work with showing/hiding loading screen immediately.
- (IBAction)proceedBtnPressed:(id)sender
{
[[GUIUtilities sharedObj] showProcessingScreen];
//Some other code here
}
When i call showProcessingScreen like above processing screen takes around 2-3 sec to show.
But when i remove other code below it (//Some other code) it shows screen immediately.
What i have tried:
Putting code in showProcessingScreen in other method and calling that on main thread using performSelectorOnMainThread.
Calling showProcessingScreen on background and executing show code on main thread using performSelector.
This works
//code
-(IBAction)proceedBtnPressed:(id)sender
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0),
^{
[[GUIUtilities sharedObj] showProcessingScreen];
//Some other code here
});
}
But i don't want any thread switching mechanism outside of showProcessingScreen.
This is common screen almost used in every application. I used similar codes with xib, custom views in my previous apps which were not using storyboard etc.,
I know this is related to threading, what i am doing wrong here ? what is best practice to achieve this ?
Any help will be aprreciatead.
The problem is that when you're dispatching, you put the creation of your loading view at the end of the run loop. Whatever your "other code" is is blocking the main thread for a few seconds.
If you move this "other code" to a different thread, this will resolve your issue. (This solution is ideal.)
You could also only switch to the main thread conditionally, which would resolve the issue if you call the loading view from the main thread:
-(void) showProcessingScreen
{
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:#selector(showProcessingScreen) withObject:nil waitUntilDone:FALSE];
return;
}
UIStoryboard *mystoryboard = [UIStoryboard storyboardWithName:#"Main_iPhone" bundle:nil];
processingScreen = [mystoryboard instantiateViewControllerWithIdentifier:#"loadingViewController"];
UIWindow* mainWindow = [[UIApplication sharedApplication] keyWindow];
[mainWindow addSubview: processingScreen.view];
[mainWindow bringSubviewToFront:processingScreen.view];
}
Instead of adding an overlay view that intercepts touch events, you can just call
– beginIgnoringInteractionEvents
– endIgnoringInteractionEvents
You could try with this:
- (void)doSomeOtherCode {
//Some other code here
}
- (IBAction) showProcessingScreenAndDoSomeOtherCode:(id)sender
{
[[GUIUtilities sharedObj] showProcessingScreen];
[self performSelector:#selector(doSomeOtherCode) withObject:nil afterDelay:0.0];
}
- (IBAction)proceedBtnPressed:(id)sender
{
[self showProcessingScreenAndDoSomeOtherCode:self];
}
and it will work. About your remark:
But i don't want any thread switching mechanism outside of showProcessingScreen.
The above solution will not cause any thread switching outside of showProcessingScreen. performSelector will just add an entry in the event loop queue.
EDIT:
If you are using blocks, the same result can be achieved through:
dispatch_async(dispatch_get_main_queue(),
^{
[[GUIUtilities sharedObj] showProcessingScreen];
dispatch_async(dispatch_get_main_queue(), ^{
//Some other code here
});
});
or:
dispatch_async(dispatch_get_main_queue(),
^{
[[GUIUtilities sharedObj] showProcessingScreen];
});
dispatch_async(dispatch_get_main_queue(), ^{
//Some other code here
});
Since the main queue is a serial queue, showProcessingScreen and "Some other code here" would be executed serially.
According to the Apple docs we should do something like this to handle GC authentication:
- (void) authenticateLocalUser
{
GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
if(localPlayer.authenticated == NO)
{
[localPlayer setAuthenticateHandler:(^(UIViewController* viewcontroller, NSError *error) {
if (!error && viewcontroller)
{
DLog(#"Need to log in");
AppDelegate *appDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
[appDelegate.window.rootViewController presentViewController:viewcontroller animated:YES completion:nil];
}
else
{
DLog(#"Success");
}
})];
}
}
And we are given this information:
If the device does not have an authenticated player, Game Kit passes a view controller to your authenticate handler. When presented, this view controller displays the authentication user interface. Your game should pause other activities that require user interaction (such as your game loop), present this view controller and then return. When the player finishes interacting with it, the view controller is dismissed automatically.
My question is, how do we know when this view controller gets dismissed, and how do we know if the authentication succeeded or not?
Obviously I need to know if the authentication worked or not, and I need to know when to resume the game if I had to pause it because the magic GC view controller was presented.
There is a problem with your code: First and foremost, you should set the authentication handler as soon as your app loads. This means that regardless of whether the localPlayer is authenticated or not, you set the handler so that it is automatically called if the player is logged out and logged back in again. If your player switches from your app to the game center app, and logs out / in, then the handler in your app won't be called (if he was already authenticated when the app first started up). The point of setting the handler is so that every time there is an auth change (in / out), your app can do the right thing.
Secondly, you shouldn't be relying on the error for anything. Even if an error is returned, game kit may still have enough cached information to provide an authenticated player to your game. The errors are only to assist you with debugging.
To answer your questions, first review my code example below.
-(void)authenticateLocalPlayer
{
GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
//Block is called each time GameKit automatically authenticates
localPlayer.authenticateHandler = ^(UIViewController *viewController, NSError *error)
{
[self setLastError:error];
if (viewController)
{
self.authenticationViewController = viewController;
[self disableGameCenter];
}
else if (localPlayer.isAuthenticated)
{
[self authenticatedPlayer];
}
else
{
[self disableGameCenter];
}
};
}
-(void)authenticatedPlayer
{
GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
[[NSNotificationCenter defaultCenter]postNotificationName:AUTHENTICATED_NOTIFICATION object:nil];
NSLog(#"Local player:%# authenticated into game center",localPlayer.playerID);
}
-(void)disableGameCenter
{
//A notification so that every observer responds appropriately to disable game center features
[[NSNotificationCenter defaultCenter]postNotificationName:UNAUTHENTICATED_NOTIFICATION object:nil];
NSLog(#"Disabled game center");
}
In my app, the call to authenticateLocalPlayer is made only once, when the app is launched. This is because the handler is invoked automatically after that.
how do we know when this view controller gets dismissed,
You won't know when this view controller gets dismissed.
The code example in the documentation says to show the view controller at the appropriate time. This means that you shouldn't necessarily show the view controller every time that game center isn't able to log in. In fact, you probably shouldn't present it immediately in the handler. You should show the view controller only when it is necessary for your player to proceed with the task at hand. It shouldn't pop up at a weird time. That is why I save the view controller, so I can display later when it makes sense to.
I need to know when to resume the game if I had to pause it because
the magic GC view controller was presented.
If you setup your authentication handler to post notifications based on status changes, you can listen for the event and show a "pause menu" or something, that remains until the user chooses to resume.
how do we know if the authentication succeeded
If the authentication succeeded, then the view controller is nil, and localPlayer.isAuthenticated is true.
or not ?
If authentication failed, then localPlayer.isAuthenticated is false, and the view controller was nil. Authentication failing could have happened for a number of reasons (network etc), and you shouldn't be presenting the view controller in this case, which is why the view controller will be nil.In this scenario, you should disable game center features until the user is next logged in. Since the authentication handler is called automatically, most of the time you shouldn't need to do anything. You can always provide a means to launch the game center app from your app, if you want to prompt the user to do something in game center, which you can't do automatically through your code.
EDIT: using a flag like self.isAuthenticated (as I did above)to keep track of whether you are logged in or not is not a great idea (I didn't want to cause any confusion, so I didn't remove it). It is better to always check [GKLocalPlayer localPlayer].isAuthenticated
EDIT: Cleaned up code a bit - removed unnecessary self.isAuthenticated, and block variable which isn't required.
For some reason, the Game Center authentication view controller is an instance of GKHostedAuthenticateViewController which is a private class we're not allowed to use or reference. It doesn't give us any way to cleanly detect when it is dismissed (unlike instances of GKGameCenterViewController which allow us to via the GKGameCenterControllerDelegate protocol.
This solution (read workaround) works by testing in the background every quarter of a second for when the view controller has been dismissed. It's not pretty, but it works.
The code below should be part of your presentingViewController, which should conform to the GKGameCenterControllerDelegate protocol.
Swift and Objective-C provided.
// Swift
func authenticateLocalUser() {
if GKLocalPlayer.localPlayer().authenticateHandler == nil {
GKLocalPlayer.localPlayer().authenticateHandler = { (gameCenterViewController: UIViewController?, gameCenterError: NSError?) in
if let gameCenterError = gameCenterError {
log.error("Game Center Error: \(gameCenterError.localizedDescription)")
}
if let gameCenterViewControllerToPresent = gameCenterViewController {
self.presentGameCenterController(gameCenterViewControllerToPresent)
}
else if GKLocalPlayer.localPlayer().authenticated {
// Enable GameKit features
log.debug("Player already authenticated")
}
else {
// Disable GameKit features
log.debug("Player not authenticated")
}
}
}
else {
log.debug("Authentication Handler already set")
}
}
func testForGameCenterDismissal() {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(0.25 * Double(NSEC_PER_SEC))), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) {
if let presentedViewController = self.presentedViewController {
log.debug("Still presenting game center login")
self.testForGameCenterDismissal()
}
else {
log.debug("Done presenting, clean up")
self.gameCenterViewControllerCleanUp()
}
}
}
func presentGameCenterController(viewController: UIViewController) {
var testForGameCenterDismissalInBackground = true
if let gameCenterViewController = viewController as? GKGameCenterViewController {
gameCenterViewController.gameCenterDelegate = self
testForGameCenterDismissalInBackground = false
}
presentViewController(viewController, animated: true) { () -> Void in
if testForGameCenterDismissalInBackground {
self.testForGameCenterDismissal()
}
}
}
func gameCenterViewControllerDidFinish(gameCenterViewController: GKGameCenterViewController!) {
gameCenterViewControllerCleanUp()
}
func gameCenterViewControllerCleanUp() {
// Do whatever needs to be done here, resume game etc
}
Note: the log.error and log.debug calls are referencing XCGLogger: https://github.com/DaveWoodCom/XCGLogger
// Objective-C
- (void)authenticateLocalUser
{
GKLocalPlayer* localPlayer = [GKLocalPlayer localPlayer];
__weak __typeof__(self) weakSelf = self;
if (!localPlayer.authenticateHandler) {
[localPlayer setAuthenticateHandler:(^(UIViewController* viewcontroller, NSError* error) {
if (error) {
DLog(#"Game Center Error: %#", [error localizedDescription]);
}
if (viewcontroller) {
[weakSelf presentGameCenterController:viewcontroller];
}
else if ([[GKLocalPlayer localPlayer] isAuthenticated]) {
// Enable GameKit features
DLog(#"Player already authenticated");
}
else {
// Disable GameKit features
DLog(#"Player not authenticated");
}
})];
}
else {
DLog(#"Authentication Handler already set");
}
}
- (void)testForGameCenterDismissal
{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
if (self.presentedViewController) {
DLog(#"Still presenting game center login");
[self testForGameCenterDismissal];
}
else {
DLog(#"Done presenting, clean up");
[self gameCenterViewControllerCleanUp];
}
});
}
- (void)presentGameCenterController:(UIViewController*)viewController
{
BOOL testForGameCenterDismissalInBackground = YES;
if ([viewController isKindOfClass:[GKGameCenterViewController class]]) {
[(GKGameCenterViewController*)viewController setGameCenterDelegate:self];
testForGameCenterDismissalInBackground = NO;
}
[self presentViewController:viewController animated:YES completion:^{
if (testForGameCenterDismissalInBackground) {
[self testForGameCenterDismissal];
}
}];
}
- (void)gameCenterViewControllerDidFinish:(GKGameCenterViewController*)gameCenterViewController
{
[self gameCenterViewControllerCleanUp];
}
- (void)gameCenterViewControllerCleanUp
{
// Do whatever needs to be done here, resume game etc
}
I might be wrong, but I think there actually is a way to know when the authentication view controller gets dismissed. I believe the initial authenticate handler that you set will be called when the user dismisses the authentication view controller, except this time the viewController parameter of the handler will be nil.
The way my app works is: the authenticate handler is set at the beginning of the application, but the authentication view controller is only displayed when the user asks to view the Leaderboards. Then, when this authentication view controller is dismissed, the initial authenticate handler either displays the leaderboards if the user was authenticated or doesn't if he wasn't.
The Game Center DELEGATE method: 'gameCenterViewControllerDidFinish' is called automatically when the Game Center viewController is 'Done'. (This is a compulsory method for the delegate.)
You can put whatever you need for your app, in this method.