rootViewController instance method only works when app enters foreground - ios

I have a method on my rootViewController that disables horizontal scrolling within my app. When the user opens a search form within one page, I want the horizontal scrolling to be disabled. This is on the [search] page, like below:
[settings]-[search]-[people]-[chat]
The method is working properly, but only when I close out the app after first launch and reopen.
Here is the method on my rootViewController:
// .h
-(void)setScrollDisabled;
// .m
- (void)setScrollDisabled {
_mainScrollView.scrollEnabled = NO;
}
I call it when the searchBar is active in my searchViewController:
-(void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar {
...
[self disableHorizontalScroll];
}
-(void)disableHorizontalScroll {
TREAppDelegate *appDelegate = (TREAppDelegate *)[[UIApplication sharedApplication] delegate];
[[appDelegate rootViewController] setScrollDisabled];
}
I know that it does work, I just want it to work when the app launches in addition to when it enters the foreground. How do I make sure the same result is achieved in all cases?

Figured it out. Needed a notification such as the following in my viewController:
[[NSNotificationCenter defaultCenter] postNotificationName:TRESearchControllerOpenedNotification object:nil userInfo:#{TRESearchControllerSearchIsActive : #(YES)}];
and then in my rootViewController:
[[NSNotificationCenter defaultCenter] addObserverForName:TREActivityControllerOpenedNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
if ([note userInfo][TRESearchControllerSearchIsActive]) {
BOOL searchIsActive = [[note userInfo][TRESearchControllerSearchIsActive] boolValue];
if (searchIsActive) {
[self setScrollDisabled];
}
}
}];
along with the proper declarations...

Related

View doesn't appear if displayed in response to a notification but does otherwise

I have a view I want to display on a certain event. My view controller is listening for a broadcast notification sent by the model and it attempts to display the view when it receives the broadcast.
However the view is not appearing. BUT if I run the exact same view code from elsewhere within the View Controller then it will be displayed. Here's some code from the VC to illustrate:
- (void) displayRequestDialog
{
MyView *view = (MyView*)[[[NSBundle mainBundle] loadNibNamed:#"MyView" owner:self options:nil] objectAtIndex:0];
view.backgroundColor = [UIColor lightGrayColor];
view.center = self.view.window.center;
view.alpha = 1.0;
[self.view addSubview:view];
}
- (void) requestReceived: (NSNotification*) notification
{
[self displayRequestDialog];
}
When the above code is run the view does not appear. However if I add the call to displayRequestDialog elsewhere, for example to viewDidAppear:
- (void) viewDidAppear
{
[self displayRequestDialog];
}
Then it is displayed.
My question therefore obviously is why can I get the view to successfully appear if I call displayRequestDialog from viewDidLoad, but it will not display if called from within requestReceived?
(Note that I am not calling requestReceived prematurely before the view controller / its view has loaded and displayed)
At first I was posting the notification like this:
[[NSNotificationCenter defaultCenter] postNotificationName: kMyRequestReceived
object: self
userInfo: dictionary];
Then I tried this:
NSNotification *notification = [NSNotification notificationWithName:kMyRequestReceived object:self userInfo:dictionary];
NSNotificationQueue *queue = [NSNotificationQueue defaultQueue];
[queue enqueueNotification:notification postingStyle:NSPostWhenIdle];
Then I tried this:
dispatch_async(dispatch_get_main_queue(),^{
[[NSNotificationCenter defaultCenter] postNotificationName: kMyRequestReceived
object: self
userInfo: dictionary];
});
Then I tried this:
[self performSelectorOnMainThread:#selector(postNotificationOnMainThread:) withObject:dictionary waitUntilDone:NO];
- (void) postNotificationOnMainThread: (NSDictionary*) dict
{
[[NSNotificationCenter defaultCenter] postNotificationName: kMyRequestReceived
object: self
userInfo: dict];
}
And I have tried invoking displayRequestDialog like this:
dispatch_async(dispatch_get_main_queue(),^{
[self displayRequestDialog];
});
I have found the cause of the view not displaying - the frame's origin is getting negative values when invoked via the notification code but positive values when invoked otherwise and thus was being displayed off the screen.
No idea why there should be a difference however.
You are not listening for the notification. Do so like this:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(displayRequestDialog) name:kMyRequestReceived object:nil];
As far as we cannot see the code you use to register your controller to receive notifications I would recommend you to use the observer registration method which enforce getting notifications on the main thread "for free"
[[NSNotificationCenter defaultCenter] addObserverForName:#"Notification" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
NSLog(#"Handle notification on the main thread");
}];

view controller variables in ApplicationWillResignActive

I'm trying to make my timer "run" in the background by saving the time to disk on entering background and retrieving it on entering foreground. Each view controller has a timer and a timeInterval specified by the user. The problem is, I don't know how to access the timeInterval variable. I think I can get the difference of time by using something like this (would this work?):
NSTimeInterval idleTime = [dateReturnedToForeground timeIntervalSinceDate:dateEnteredBackground];
NSTimeInterval elapsedTime = [[NSDate date] timeIntervalSinceDate:startDate];
elapsedTime -= idleTime;
Each view controller (and timer/time interval) is accessed like this:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
DetailViewController *detailVC;
if (![self.detailViewsDictionary.allKeys containsObject:indexPath]){
detailVC = [[DetailViewController alloc]initWithNibName:#"DetailViewController" bundle:nil];
[self.detailViewsDictionary setObject:detailVC forKey:indexPath];
detailVC.context = self.managedObjectContext;
}else{
detailVC = self.detailViewsDictionary[indexPath];
}
Tasks *task = [[self fetchedResultsController] objectAtIndexPath:indexPath];
detailVC.testTask = task;
[[self navigationController] pushViewController:detailVC animated:YES];
NSLog(#"%#", self.detailViewsDictionary);
[[NSNotificationCenter defaultCenter] addObserver:detailVC forKeyPath:self.detailViewsDictionary[indexPath] options:nil context:nil];
}
I am adding each view controller to the Notification Center so it can be accessed in the app delegate (i think this is right?). Problem is, I don't know how to combine the first code with the view controller code, because I can't access the view controller's properties in the app delegate. Any suggestions so that I can make my timer "run" in the background?
You are going about this all wrong. There is no need to do any of this in the app delegate.
Have each view controller listen for the UIApplicationWillResignActiveNotification notification. Then each view controller can do whatever it feels is appropriate to save its data when the notification is received.
Update:
In the view controller's init... method, register for the notification:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(resigningActive) name:UIApplicationWillResignActiveNotification object:nil];
In the view controller's dealloc method, unregister:
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillResignActiveNotification object:nil];
}
Then implement the resigningActive method:
- (void)resigningActive {
// The app is resigning active - do whatever this view controller needs to do
}

Notification Center gets called but value of UILabel is not updated in IOS

I am using push notifications in my code and whenever a notification comes, I want to update the value of a label in another ViewController.
My code in AppDelegate is:
- (void)addMessageFromRemoteNotification:(NSDictionary*)userInfo updateUI:(BOOL)updateUI
{
NSLog(#"Notification arrived");
//[mydeals setCode1_id:mydeals.code1_id withString:#"123456"];
mydeals=[[MyDealsViewController alloc]init];
NSDictionary* codeDetails=[[NSDictionary alloc] initWithObjectsAndKeys:#"123456",#"Code_id", nil];
[[NSNotificationCenter defaultCenter] postNotificationName:#"CodeArrived" object:self userInfo:codeDetails];
}
then in my other view controller I have this code:
#implementation MyDealsViewController
-(id) init
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(receiveCode:)
name:#"CodeArrived"
object:nil];
return self;
}
-(void) receiveCode:(NSNotification*)notification
{
NSLog(#"received Code: %#",notification.userInfo);
self.code1_id.text=[NSString stringWithFormat:#"%#",[[notification userInfo] valueForKey:#"Code_id"]];
}
the log is printed correctly but when I manually go into that screen I see the default value, like the label is not updated at all. What should I do?
You have to make sure that when you "manually go" to MyDealsViewController, whatever how you do it, it got to be the same instance of MyDealsViewController wich has been called receiveCode. Otherwise it's going to init with it's default values.
You might also try calling [self.code1_id setNeedsLayout];

Problems with NSNotificationCenter and loading the UIViewControlletr

I have a UIViewController with button that brings another UIViewController. with clicking on button , as shown in my NSLog, and when this is done, I want to send a notification to load another viewcontroller . Well, although it seems everything is done right, somehow it does not work and the UIViewController not appear. Here is the code:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(infoPage:)
name:#"InfoPage" object:nil ];
-(void) infoPage:(NSNotification*)notification
{
NSLog(#"Code executing in Thread %#",[NSThread currentThread] );
InfoCtrol *i = [[InfoCtrol alloc] init];
i.hidesBottomBarWhenPushed = YES;
[self.navigationController pushViewController:i animated:YES];
}
my tabbaritem button
-(void)info {
[[NSNotificationCenter defaultCenter] postNotificationName:#"InfoPage"
object:nil
userInfo:nil];
NSLog(#"test not");
}
I think my problem is that: It's not in a mainThread but I do n't know how should I solved that:
I also used this but it didn't bring the UIViewController:
[self performSelectorOnMainThread:#selector(test) withObject:nil waitUntilDone:NO];
-(void)test{
[[NSNotificationCenter defaultCenter] postNotificationName:#"InfoPage"
object:nil
userInfo:nil];
}
If I just put this code in button, it displays the UIViewController, but I want to use NSNotificationCenter
InfoCtrol *i = [[InfoCtrol alloc] init];
i.hidesBottomBarWhenPushed = YES;
[self.navigationController pushViewController:i animated:YES];
My Log:
Code executing in Thread <NSThread: 0x1fd7c7e0>{name = (null), num = 1}
Update:
How should i remove last thread from mainThread
I don't know why you want to use a notification here, when you can perform the action directly without issue. But a simple thing you can do in notification methods that need to update UI is to just have them call themselves on the main thread if they're not already running on that thread:
-(void)myNotificationMethod:(NSNotification*)note {
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:#selector(myNotificationMethod:)
withObject:note
waitUntilDone:NO];
return;
}
// ... do some UI stuff
InfoCtrol *i = [[InfoCtrol alloc] init];
[self.navigationController pushViewController:i animated:YES];
}

Dismissing UIAlertViews when entering background state

Apple recommends dismissing any UIAlertViews/UIActionSheets when entering background state in iOS 4. This is to avoid any confusion on the user's part when he relaunches the application later. I wonder how I could elegantly dismiss all UIAlertViews at once, without retaining a reference to it everytime I set one up...
Any idea ?
My call would be to add a category to UIAlertview adding the following function :
- (void) hide {
[self dismissWithClickedButtonIndex:0 animated:YES];
}
And to suscribe to UIApplicationWillResignActiveNotification :
[[NSNotificationCenter defaultCenter] addObserver:alertView selector:#selector(hide) name:#"UIApplicationWillResignActiveNotification" object:nil];
I was intrigued by Dad's answer (funny username :), and curious why it was down-voted.
So I tried it.
Here is the .m part of a subclass of UIAlertView.
Edit: (Cédric) I have added a way to catch calls to delegate methods and remove the observer then to avoid multiple registrations to the notification center.
Everything bundled in a class in this github repo: https://github.com/sdarlington/WSLViewAutoDismiss
#import "UIAlertViewAutoDismiss.h"
#import <objc/runtime.h>
#interface UIAlertViewAutoDismiss () <UIAlertViewDelegate> {
id<UIAlertViewDelegate> __unsafe_unretained privateDelegate;
}
#end
#implementation UIAlertViewAutoDismiss
- (id)initWithTitle:(NSString *)title
message:(NSString *)message
delegate:(id)delegate
cancelButtonTitle:(NSString *)cancelButtonTitle
otherButtonTitles:(NSString *)otherButtonTitles, ...
{
self = [super initWithTitle:title
message:message
delegate:self
cancelButtonTitle:cancelButtonTitle
otherButtonTitles:nil, nil];
if (self) {
va_list args;
va_start(args, otherButtonTitles);
for (NSString *anOtherButtonTitle = otherButtonTitles; anOtherButtonTitle != nil; anOtherButtonTitle = va_arg(args, NSString *)) {
[self addButtonWithTitle:anOtherButtonTitle];
}
privateDelegate = delegate;
}
return self;
}
- (void)dealloc
{
privateDelegate = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];
[super dealloc];
}
- (void)setDelegate:(id)delegate
{
privateDelegate = delegate;
}
- (id)delegate
{
return privateDelegate;
}
- (void)show
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationDidEnterBackground:)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
[super show];
}
- (void)applicationDidEnterBackground:(NSNotification *)notification
{
[super dismissWithClickedButtonIndex:[self cancelButtonIndex] animated:NO];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];
}
#pragma mark - UIAlertViewDelegate
// The code below avoids to re-implement all protocol methods to forward to the real delegate.
- (id)forwardingTargetForSelector:(SEL)aSelector
{
struct objc_method_description hasMethod = protocol_getMethodDescription(#protocol(UIAlertViewDelegate), aSelector, NO, YES);
if (hasMethod.name != NULL) {
// The method is that of the UIAlertViewDelegate.
if (aSelector == #selector(alertView:didDismissWithButtonIndex:) ||
aSelector == #selector(alertView:clickedButtonAtIndex:))
{
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIApplicationDidEnterBackgroundNotification
object:nil];
}
return privateDelegate;
}
else {
return [super forwardingTargetForSelector:aSelector];
}
}
#end
It works nicely.
It's great, because you can just start using it the same way that you used to use UIAlertView.
I haven't had time to test it thoroughly, but I didn't notice any side effect.
A totally different approach is a recursive search.
Recursive function for your application delegate
- (void)checkViews:(NSArray *)subviews {
Class AVClass = [UIAlertView class];
Class ASClass = [UIActionSheet class];
for (UIView * subview in subviews){
if ([subview isKindOfClass:AVClass]){
[(UIAlertView *)subview dismissWithClickedButtonIndex:[(UIAlertView *)subview cancelButtonIndex] animated:NO];
} else if ([subview isKindOfClass:ASClass]){
[(UIActionSheet *)subview dismissWithClickedButtonIndex:[(UIActionSheet *)subview cancelButtonIndex] animated:NO];
} else {
[self checkViews:subview.subviews];
}
}
}
Calling it from the applicationDidEnterBackground procedure
[self checkViews:application.windows];
huh. Haven't tried this yet, but I wonder if it would make sense to create a subclass of UIAlertView that listens for this Notification and closes itself if so...
That'd have the "automatically" without retaining / keeping it around characteristic OP is requesting. Make sure to unregister for the notification on close (else boom!)
As someone mentioned in a comment: the accepted answer isn't the best/cleanest one since iOS 4.0 when we have blocks! Here's how I do it:
UIAlertView* alert = [[UIAlertView alloc] initWithTitle:#"Alert!" message:#"This alert will dismiss when application resigns active!" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillResignActiveNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification* notification){
[alert dismissWithClickedButtonIndex:0 animated:NO];
}];
UIAlertView was deprecated in iOS 8 in favor of the UIAlertController. Unfortunately, this proves to be a tricky problem because the accepted solution won't work, as Apple explicitly doesn't support subclassing UIAlertController:
The UIAlertController class is intended to be used as-is and does not support subclassing. The view hierarchy for this class is private and must not be modified.
My solution is to simply traverse the view controller tree and dismiss all UIAlertControllers that you find. You can enable this globally by creating an extension of UIApplication and then calling it in the AppDelegate applicationDidEnterBackground method.
Try this (in Swift):
extension UIApplication
{
class func dismissOpenAlerts(base: UIViewController? = UIApplication.sharedApplication().keyWindow?.rootViewController)
{
//If it's an alert, dismiss it
if let alertController = base as? UIAlertController
{
alertController.dismissViewControllerAnimated(false, completion: nil)
}
//Check all children
if base != nil
{
for controller in base!.childViewControllers
{
if let alertController = controller as? UIAlertController
{
alertController.dismissViewControllerAnimated(false, completion: nil)
}
}
}
//Traverse the view controller tree
if let nav = base as? UINavigationController
{
dismissOpenAlerts(nav.visibleViewController)
}
else if let tab = base as? UITabBarController, let selected = tab.selectedViewController
{
dismissOpenAlerts(selected)
}
else if let presented = base?.presentedViewController
{
dismissOpenAlerts(presented)
}
}
}
And then in your AppDelegate:
func applicationDidEnterBackground(application: UIApplication)
{
UIApplication.dismissOpenAlerts()
}
I Have had solved this with the following code:
/* taken from the post above (Cédric)*/
- (void)checkViews:(NSArray *)subviews {
Class AVClass = [UIAlertView class];
Class ASClass = [UIActionSheet class];
for (UIView * subview in subviews){
NSLog(#"Class %#", [subview class]);
if ([subview isKindOfClass:AVClass]){
[(UIAlertView *)subview dismissWithClickedButtonIndex:[(UIAlertView *)subview cancelButtonIndex] animated:NO];
} else if ([subview isKindOfClass:ASClass]){
[(UIActionSheet *)subview dismissWithClickedButtonIndex:[(UIActionSheet *)subview cancelButtonIndex] animated:NO];
} else {
[self checkViews:subview.subviews];
}
}
}
/*go to background delegate*/
- (void)applicationDidEnterBackground:(UIApplication *)application
{
for (UIWindow* window in [UIApplication sharedApplication].windows) {
NSArray* subviews = window.subviews;
[self checkViews:subviews];
}
}
The straightforward way is to hold a reference to the UIAlertView so you can dismiss it. Of course as petert mentioned you can do it with a Notification or use the delegate method on UIApplication
applicationWillResignActive:
does not always mean that you are going to the background. You will for example also receive that delegate call and notification (you get both) when the user gets a phone call or receives and SMS. So you have to decide what should happen if the user gets an SMS and presses cancel to stay in your app. You maybe want to make sure that your UIAlertView is still there.
So I would dismiss the UIAlertView and save the state in the delegate call when you really go into the background:
applicationDidEnterBackground:
Have a look at Session 105 - Adopting Multitasking on iOS4 of WWDC10 available for free at developer.apple.com. It gets interesting at 16:00 min
Check out this graphic to understand the different states of an application
I have this on my TODO list, but my first instinct would be to listen out for the notifcation UIApplicationWillResignActiveNotification (see UIApplication) in the views where you have things like UIAlertView - here you can programmatically remove the alert view with:
(void)dismissWithClickedButtonIndex:(NSInteger)buttonIndex animated:(BOOL)animated
The discussion for this method even suggests what it's for in iOS4!
In iPhone OS 4.0, you may want to call this method whenever your application moves to the background. An alert view is not dismissed automatically when an application moves to the background. This behavior differs from previous versions of the operating system, where they were canceled automatically when the application was terminated. Dismissing the alert view gives your application a chance to save changes or abort the operation and perform any necessary cleanup in case your application is terminated later.
if you only have one or two specific alert windows you show (as do most apps), then you can just create an assign ivar to the alert:
#property (nonatomic, assign) UIAlertView* alertview;
Then, in the app delegate:
[self.viewController.alertview dismissWithClickedButtonIndex:[self.viewController.alertview cancelButtonIndex] animated:NO];
You can put this in applicationDidEnterBackground: or wherever you see fit. It closes the alert programmatically upon application exit. I've been doing this and it works great.
Create category on UIAlert View
Use http://nshipster.com/method-swizzling/
Swizzle "show" method
Keep track of alert view shown by keeping week references in array.
-
When you want to remove all data call Dismiss on saved alert views and empty an array.
An alternative solution, based on plkEL's, answer, where the observer is removed when the app is put in the background. If user dismisses the alert by pressing a button, the observer will still be active, but only until the app is put in the background (where the block is run - with an "nil alertView" - and the observer removed).
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title
message:message
delegate:alertDelegate
cancelButtonTitle:cancelButtonText
otherButtonTitles:okButtonText, nil];
[alert show];
__weak UIAlertView *weakAlert = alert;
__block __weak id observer = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillResignActiveNotification object:nil queue: [NSOperationQueue mainQueue] usingBlock:^(NSNotification* notification){
[weakAlert dismissWithClickedButtonIndex:[weakAlert cancelButtonIndex] animated:NO];
[[NSNotificationCenter defaultCenter] removeObserver:observer];
observer = nil;
}];

Resources