NSNotificationCenter and Tap Method calling - ios

So i have an ios app which has Two Tabs. First tab has a UIGestureRecognizer which calls a countdown Method once its TAPPED.
Second tab has a UIPickerView with Three selections.
This is what i have in my secondTab's ViewController in code:
FirstViewController *firstvs = [[FirstViewController alloc] init];
NSInteger selectedRow = [self.pickerView selectedRowInComponent: 0];
if (selectedRow == 0) {
NSLog(#"Invalid Row"); // This block will not do nothing
}
else if (selectedRow == 1) {
// Call a method in firstViewController
[firstvs smallSelection];
[[NSNotificationCenter defaultCenter]
postNotificationName:#"smallSelection2"
object:self];
}
else if (selectedRow == 2) {
[firstvs mediumSelection];
[[NSNotificationCenter defaultCenter]
postNotificationName:#"mediumSelection3"
object:self];
}
else if (selectedRow == 3){
[firstvs largeSelection];
[[NSNotificationCenter defaultCenter]
postNotificationName:#"largeSelection"
object:self];
}
What this basically does is if the USER selects lets say, Row 2, Within the same secondTab it dispays in a UILabel what the user has selected.
Now then, that selection is broadcasted in a NSNotificationCenter as shown in the code under each if and else block.
*I have 3 NSnotificationCenter for each if Statement which i clearly dont know if its safe to do this or not aside from my problem.
So when the user selects Option 2 which would be row 2, In the First tab's ViewController, it calls a method called mediumSelection.
In FirstViewController.m :
-(void)mediumSelection {
// Other functions
}
But as you may notice i rather use an NSNotificationCenter to keep the firstViewController Listening to the secondViewController's instead of just executing this method as i been recommended to use the NSNotificationCenter.
Notice this NSNotification Broadcaster in the selection 2 on secondViewController:
[[NSNotificationCenter defaultCenter]
postNotificationName:#"mediumSelection3"object:self];
This Broadcaster then sends a message to the listener as i show the code from the firstViewController Now:
- (id) init
{
self = [super init];
if (!self) return nil;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(smallSelection2:)
name:#"smallSelection2"
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(mediumSelection3:)
name:#"mediumSelection3"
object:nil];
return self;
}
- (void) smallSelection2:(NSNotification *) notification
{
if ([[notification name] isEqualToString:#"smallSelection2"])
NSLog (#"SmallSelection is successfully received!");
}
- (void) mediumSelection3:(NSNotification *) notification
{
if ([[notification name] isEqualToString:#"mediumSelection3"])
NSLog (#"mediumSelection is successfully received!");
}
So this works, It logs it into the Console as the the UIPicker is being used to scroll it recieves the selection. Now for the BIG problem and the main reason this NSNotification is meant for.
Before this Reciever code, Above i have the UIGestureRecognizer and the countdown method called from the Tap as Follows:
-(void) viewDidLoad {
UITapGestureRecognizer *tapScreen = [[UITapGestureRecognizer alloc] initWithTarget: self action:#selector(tapScreen:)];
[self.view addGestureRecognizer: tapScreen];
}
- (void) tapScreen:(UIGestureRecognizer *)gr
{
NSLog(#"Screen has been tapped");
/*
CODE RELATED TO LABELS, NOTHING IMPORTANT
*/
timer = [NSTimer scheduledTimerWithTimeInterval: 1 target: self selector: #selector(countdown) userInfo: nil repeats: YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode: NSRunLoopCommonModes];
}
-(void) countdown {
/*
MORE CODE BEING EXECUTED ON THE VIEW
*/
NSLog(#"Program success: counting... %i", mainInteger);
self.hoursLeft.text = [NSString stringWithFormat:#"%i hours", mainInteger];
self.percentGauge.text = [NSString stringWithFormat:#"%1.2f", subInteger];
// FIGURE 1:
[self smallSelection];
}
Now the Problem is i want the countdown to begin according to the selection Made from the NSNotificationCenter. So if the user selected Row 2 from the Picker, the countdown goes from that selection according to the methods "Small, Medium, Large".
as you can see, in the end of the countdown method where it says "FIGURE 1:" I am manually calling the smallSelection Method which i would like to embed an If statement to check the 3 possible choices according to the UIPicker selection, i am using NSNotificationCenter because i was testing if this was would work aside from the instance i set on secondViewController
called: [firstvs smallSelection].
So is there anyway to add an if statement to check the 3 possible choices and to execute that method on a tap ?

I would use a global object where all values are written down and change these values from different the viewcontrollers.
So you can get the values from all viewcontrollers using this global object:

Related

Getting EXC_BAD_ACCESS error (code = 1) when posting a notification with Notification Center

I have a view controller with this method:
- (void)getImages
{
if (self.myEntities != nil) {
if (self.myEntities.count > 0) {
if (self.imagesDownloader == nil) {
self.imagesDownloader = [[ImagesDownloader alloc] initWithListener];
}
}
for (MyEntity *myEntity in self.myEntities) {
if (![myEntity.imageUrl isEqualToString:#""] && (myEntity.imageUrl != nil)) {
[self.imagesDownloader getImageFromServiceUrl:myEntity.imageUrl];
}
}
}
}
And ImagesDownloader is an NSObject subclass like this:
#implementation ImagesDownloader
- (id)initWithListener
{
self = [super init];
if (self) {
[self registerNotifications];
}
return self;
}
- (void)registerNotifications
{
[[NSNotificationCenter defaultCenter] removeObserver:self
name:#"getImageDidFinish"
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(getImageDidFinish:)
name:#"getImageDidFinish"
object:nil];
}
- (void)getImageFromServiceUrl:(NSString *)imageUrl
{
GetImage *getImage = [[GetImage alloc] init];
[getImage queryServiceWithImageUrl:imageUrl];
}
// More instance methods
#end
In turn, GetImage is another NSObject subclass that calls a RESTful web service by using an NSURLConnection object, and then, in its connectionDidFinishLoading: delegate method, it posts the notification the imagesDownloader object is observing via Notification Center:
[[NSNotificationCenter defaultCenter] postNotificationName:#"getImageDidFinish"
object:nil
userInfo:serviceInfoDict];
This is the call where I sometimes get the EXC_BAD_ACCESS error.
The scenario is like this: the view controller is loaded (it is pushed into a navigation stack) and getImages is called. Right now, its self.myEntities has 3 objects, so self.imagesDownloader is initialized and call 3 times to getImageFromServiceUrl. The service is called 3 times as well, and in connectionDidFinishLoading:, the notification is posted the 3 times with no error.
But I then go back and forth the view controller, and it is loaded again and same calls made. But this time, I get the error the 2nd time the notification is going to be posted from connectionDidFinishLoading:. I don't undersatnd why, what could I be missing?
Thanks in advance
As #PhillipMills said, adding this to ImagesDownloader seems to solve the problem:
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}

Objective C: Changing tab bar controller index through NSNotificationCenter causes problems

I have a table view. when a certain row is selected (log out button), a check is done for any current sync going on between server and iPhone. If it is still syncing, I use an observer in viewDidLoad to notify me when it finishes. The observer then calls logMeOut. If it is not syncing, logMeOut is called immediately.
Changing tab index works perfectly fine when called in didSelectRowAtIndexPath. But when called from the NSNotificationObserver, I can see that both the tabs at index 0 and 3 are highlighted blue (which means somehow the system selected index 0, but failed to cancel index 3). The page is not changed; it still displays the same page at index 3. I can't go to index 0 by tapping the tab. I have to tap one of the indexes 1-3 and then tap index 0 again to navigate there.
Setting breakpoints and logging statements show that NSNotificationCenter is called correctly. I tested this on both iPhone and Xcode Simulator.
//When called from observer
//when called from didselectrowatindexpath
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(logMeOut) //does not work when called here
name:#"canLogOutNow"
object:nil];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
if (indexPath.row == 0) {
SyncEngine *syncengine = [SyncEngine sharedEngine];
if (![syncengine syncInProgress]) {
[self logMeOut];//works fine when called here
} else {
syncengine.needToLogOut = YES; //tells sync engine to call notification center
}
}
}
- (void)logMeOut {
[PFUser logOut];
[self.parentViewController.tabBarController setSelectedIndex:0]; //Current index is 3
}
//snippet of syncengine.m
if (self.needToLogOut) {
NSNotification *notification = [NSNotification notificationWithName:#"canLogOutNow" object:nil];
[[NSNotificationCenter defaultCenter] postNotification:notification];
}

setText in UIlabel after notification call has no effect

i want update the text of my label every time i receive notification from nsmanageObjContext.
this is my code for add the observer:
- (IBAction)requestFotPhoto {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(updateLabel) name:NSManagedObjectContextDidSaveNotification
object:self.facebook.managedObjectContext];
and this is the method for update the label:
-(void)updateLabel
{
NSString *text = [NSString stringWithFormat:#"Downalad %i pictures",[Photo NumeberOfAllPhotosFromContext:self.facebook.managedObjectContext]];
dispatch_async(dispatch_get_main_queue(), ^{
//UIKIT method
NSLog(#"text %#",text);
[self.downlaodLabel setText:text];
});
}
i assume that updateLabel is execute in a another thread, so i execute the instructions for update the label on the main thread, but this code has no effect. where is the problem?
obviously the NSlog print the right message!
thanks!
In your situation you don't need to use dispatch_async, because notification handlers are run in the main thread. They are executed in a main loop on idle moments — sorry if I'm wrong with techincal words, english is not native for me.
And one more thing: you should't reference self from blocks, because self points to your block, and block points to self — they're not going to be released. If you really want to do it, you can read this question.
seems like:
your should move your NSNotificationCenter addObserver code, from your (IBAction)requestFotPhoto (seems is some button click event handler, which only run after user tapped) to viewDidLoad
shold like this:
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(updateLabel) name:NSManagedObjectContextDidSaveNotification object:self.facebook.managedObjectContext];
}
and for noficacation handler, not use dispatch_async
should like this:
- (void)updateLabel:(NSNotification *) notification {
NSLog (#"updateLabel: notification=%#", notification);
if ([[notification name] isEqualToString: NSManagedObjectContextDidSaveNotification]) {
NSDictionary *passedInUserInfo = notification.userInfo;
NSString *yourText = [passedInUserInfo objectForKey:#"dataKey"];
//UIKIT method
NSLog(#"yourText=%#",yourText);
[self.downlaodLabel setText:yourText];
}
}
and somewhere else should send the text:
NSString *newText = #"someNewText";
NSDictionary *passedInfo = #{#"dataKey": newText};
[[NSNotificationCenter defaultCenter] postNotificationName:NSManagedObjectContextDidSaveNotification object: self userInfo:passedInfo];
for more detail pls refer another post answer

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
}

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