nsnotification approach for session inactivity in objective c - objective-c

In sesssion inactivity implementation for my project. I have created a NSNotification in RootViewController class of project.
- (void)viewDidLoad
{
[super viewDidLoad];
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle: #"Close"
style: UIBarButtonItemStyleDone
target: self
action: #selector(closeModal)];
UIImage *image = [UIImage imageNamed:#"fidelity_logotype"];
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, image.size.width, image.size.height)];
[imageView setImage:image];
[self.navigationItem setTitleView:imageView];
self.navigationController.view.backgroundColor = [UIColor fidelityGreen];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(applicationDidTimeout:) name:#"ApplicationTimeout" object:nil];
}
- (void) applicationDidTimeout:(NSNotification *) notif
{
NSLog(#"I m here");
BCDSessionInactivityViewController *sessionView=[[UIStoryboard storyboardWithName:#"Main" bundle:nil] instantiateViewControllerWithIdentifier:#"InactivityViewController"];
sessionView.modalPresentationStyle = UIModalPresentationFormSheet;
sessionView.preferredContentSize = CGSizeMake(838,340);
[[self topViewController] presentViewController:sessionView animated:YES completion:nil];
}
and in logoutviewcontroller, i am removing this observer written below
- (IBAction)logoutbtn:(id)sender
{
NSLog(#"logout is called");
[sessionTimer invalidate];
sessionTimer = nil;
[[BCDTimeManager sharedTimerInstance]stopIdleTimer];
//[self dismissViewControllerAnimated:YES completion:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"ApplicationTimeout" object:nil];
[self performSegueWithIdentifier:#"Thankyoupage" sender:self];
}
This is code where i posting the notification.
- (void)idleTimerExceeded {
NSLog(#"idle time exceeded");
[[NSNotificationCenter defaultCenter]
postNotificationName:#"ApplicationTimeout" object:nil];
}
for first time login, it works fine whenever timer exceeds, i post a notification and model view is presesnted perfectly, but once user logs out, after that whenever the notification is posted, selector method is getting called twice
I am pretty sure that notification is getting posted only once.
Should i create notification in every view controller and then remove it when view unloads?
what i am doing wrong here?

You are adding the notification in RootViewController and trying to remove it from LogoutViewController. So that notification observer added to the RootViewController never gets removed. So each time you logout and login, the observer call will get increased by one. For fixing the issue, you need to remove the observer from the RootViewController object.
For fixing the issue you mentioned in your comment,
If I remove the observer in RootViewController , then if timers
exceeds in some other views, and notification observer is not called.
Also, i can't add observer on app delegate because we want timer
notification to be fired only after reaching rootviewController
Write two public methods in AppDelegate
One for adding observer (addObserver)
One for removing observer (removeObserver)
When you reach RootViewController, call the addObserver method for adding the observer
When logout is pressed, call the removeObserver for removing the observer

Related

NSNotification sent once, but is received multiple times

I am communicating between two classes with NSNotificationCenter. My problem is that although I tap a button once (and that button only fires off once) I am unintentionally producing increasing numbers of notifications from only one call to the NSNotificationCenter.
Here is a better explanation of the problem, with code:
My two classes are the mainView class and the Menu class.
When a view in the mainView class is tapped, it launches a view created and governed by the Menu class. This code is called when the mainView is initialized:
menu=[[MyMenu alloc] init];
UITapGestureRecognizer * tap=[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(onTapped:)];
[tap setNumberOfTapsRequired:1];
[container addGestureRecognizer:tap];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(onChangeItem:) name:#"ItemChange" object:nil];
This Gesture Recognizer fires off this method, also in the mainView class:
- (void) onTapped: (UIGestureRecognizer*) recognizer {
NSLog(#"tap");
[menu displayMenu];
}
This is how the Menu class initializes:
- (MyMenu*) init {
self=[super init];
UICollectionViewFlowLayout * layout=[[UICollectionViewFlowLayout alloc] init];
menuView=[[UICollectionView alloc] initWithFrame:CGRectMake(0, 0, 200, 200) collectionViewLayout:layout];
[menuView setDataSource:self];
[menuView setDelegate:self];
[menuView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:#"cell"];
[menuView setAutoresizesSubviews:YES];
[menuView setAutoresizingMask:UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth];
[menuView setBackgroundColor:[UIColor clearColor]];
[menuView setIndicatorStyle:UIScrollViewIndicatorStyleWhite];
return self;
}
And this is the displayMenu method inside the Menu class:
- (void) displayMenu {
[viewForMenu addSubview:menuView];
}
The Menu class also has a clearMenu method:
- (void) clearMenu {
[menuView removeFromSuperview];
}
This is the code for each cell in the UICollectionView, contained within my Menu class:
- (UICollectionViewCell*) collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell * cell=[collectionView dequeueReusableCellWithReuseIdentifier:#"cell" forIndexPath:indexPath];
[cell setTag:indexPath.row];
UITapGestureRecognizer * tap=[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(onButtonTapped:)];
[tap setNumberOfTapsRequired:1];
[cell addGestureRecognizer:tap];
NSLog(#"button tapped : %d",indexPath.row);
return cell;
}
This calls the onButtonTapped: method, also within my Menu class:
- (void) onButtonTapped:(UIGestureRecognizer*) recognizer {
NSInteger buttonTapped=[[recognizer view] tag];
[[NSNotificationCenter defaultCenter] postNotificationName:#"ItemChange" object:nil userInfo:#{#"selected":#(buttonTapped)}];
[self clearMenu];
}
This notification is picked up by my mainView class with this code:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(onChangeItem:) name:#"ItemChange" object:nil];
This calls the onChangeItem: method, inside my mainView class:
- (void) onChangeItem: (NSNotification*) notification {
NSLog(#"change item to %d",[[[notification userInfo] objectForKey:#"clock"] intValue]);
}
So that's the code.
OK, here's the problem: the first time the menu displays I get this in my log:
...[43023:11f03] tap
...[43023:11f03] button tapped : 1
...[43023:11f03] change item to 1
And this is fine, this is what I expect. However second time around I get this:
...[43023:11f03] tap
...[43023:11f03] button tapped : 1
...[43023:11f03] change item to 1
...[43023:11f03] change item to 1
Third time around I get this:
...[43023:11f03] tap
...[43023:11f03] button tapped : 1
...[43023:11f03] change item to 1
...[43023:11f03] change item to 1
...[43023:11f03] change item to 1
...[43023:11f03] change item to 1
And so on. Each successive tap on a menu item doubles the amount of notification calls.
To begin with I thought I was adding multiple views, and thus resulting in multiple button taps, and therefore multiple notifications calls.
However as you can see from my logs, this is not the case. The buttons are only receiving 1 tap event - this is firing off only 1 notification - but receiving class gets sent multiple notifications.
Can anyone explain this to me?
Sorry for the lengthy post!
Well, I am assuming that [[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(onChangeItem:) name:#"ItemChange" object:nil]; are being added more than once.
I like to remove any potential observer before adding an observer, like so:
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"ItemChange" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(onChangeItem:) name:#"ItemChange" object:nil];
That way there will ever only be one observer callback.
Issue: I faced same problem, observer was calling twice, sometimes thrice.
Scenario
A user taps logout button
HomeViewController was dismissed and LoginViewController screen was presented
When user signed in again for second time
Observer was called twice (sometimes thrice)
The issue was [[NSNotificationCenter defaultCenter] removeObserver:self]; in dealloc method of my HomeViewController was not called at all, which actually removes an object from the notification center all together.
ℹ️ dealloc is an Objective-C selector that is sent by the
Objective-C runtime to an object when the object is no longer owned by
any part of the application.
Solution: Made your own method dispose and call it when user tap logout.
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
// ....
}
- (void)dispose {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)logoutTapped {
[self dispose];
// ....
}

How do you access a uitableview inside a uinavigationcontroller inside a uitabbarcontroller from app delegate?

Im trying to access a refresh control method in a uitableview which is inside a navigationcontroller from a tabbarcontroller which is my root, but im having trouble getting an exact handle.
This is my code so far in the AppDelegate but it doesn't work...
UITableViewController *tableView = (UITableViewController *)[[self.tabbarController viewControllers][0] tableView];
[tableView.refreshControl beginRefreshing];
I have 5 tab bar items which I believe I can access via [0],[1],[2],[3]
And my code in the UITableView (though probably doesnt matter)...
// Add Refresh Control
UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
[refreshControl addTarget:appDelegate action:#selector(forceDownload) forControlEvents:UIControlEventValueChanged];
self.refreshControl = refreshControl;
[refreshControl release];
Any help would be greatly appreciated as i cant find any online that access as deep as this.
If you need communication between objects that are unrelated, i think that the best option is to use NSNotifications. This allows you to use the singleton object[NSNotificationCenter defaultCenter], to pass notifications from one object to another (or many others).
So you can put the AppDelegate object (or other object) to observe a specific notification and then post the notification with the tableviewController when you need to refresh the control.
In app delegate class you can add the observer like:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(forceDownload)
name:#"ForceDownloadNotification"
object:nil];
And, in the tableviewController you can post de notification like:
[[NSNotificationCenter defaultCenter] postNotificationName:#"ForceDownloadNotification"
object:self];
Here, I used the name "ForceDownloadNotification" as the name for the notification. You can use the name that you want, but in order this solution work properly, you must use the same name when you start the observation and when you post the notification.
Here you have a tutorial about this subject.
I like Luis Espinoza's approach but that doesn't answer the question per se.
If you want to call a method inside your UITableViewController nested inside a UINavigationController which is the rootViewController for your App Delegate. First we create a navigationController with the UITableViewController (or a subclass):
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
CustomTableViewController *nuTableVC = [[CustomTableViewController alloc] initWithStyle:UITableViewStylePlain];
UINavigationController *nuNavController = [[UINavigationController alloc] initWithRootViewController:nuTableVC];
self.window.rootViewController = nuNavController;
[self.window makeKeyAndVisible];
return YES;
}
Then in your UITableViewController (or subclass) you setup the refreshcontrol just like you asked:
- (void)viewDidLoad {
[super viewDidLoad];
// Add Refresh Control
UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
[refreshControl addTarget:[[UIApplication sharedApplication] delegate]
action:#selector(forceDownload)
forControlEvents:UIControlEventValueChanged];
self.refreshControl = refreshControl;
}
Finally to access the UItableViewController you must check if the instances are really the classes that you want, here is my implementation for the method you created (forceDownload) in your App Delegate:
- (void)forceDownload {
NSLog(#"force download method in App Delegate");
UINavigationController *someNavController = (UINavigationController*)[_window rootViewController];
UIViewController *vcInNavController = [[someNavController viewControllers] objectAtIndex:0];
if ([vcInNavController isKindOfClass:[CustomTableViewController class]]) {
NSLog(#"it is my custom Table VC");
NSLog(#"here we can stop the refresh control, or whatever we want");
CustomTableViewController *customTableVC = (CustomTableViewController *)vcInNavController;
[customTableVC.refreshControl performSelector:#selector(endRefreshing)
withObject:nil
afterDelay:1.0f];
}
}
I personally prefer using NSNotificationCenter because is simpler, but that doesn't mean we can't access the objects the way you originally planed.
(if you want the example code just ask for it).
Regards.
If your goal is truely to just keep your refresh control synch'd with the networkActivityIndicator then one option is KVO.
In the viewController's viewDidAppear: add something like this
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
UIApplication *application = [UIApplication sharedApplication];
[application addObserver:self
forKeyPath:#"networkActivityIndicatorVisible"
options:NSKeyValueObservingOptionNew
context:myContext];
self.refreshControl.refreshing = [application isNetworkActivityIndicatorVisible];
}
Then make sure to remove this observer when the viewController doesn't need it - perhaps in viewDidDisappear:
- (void)viewWillDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[[UIApplication sharedApplication] removeObserver:self
forKeyPath:#"networkActivityIndicatorVisible"
context:myContext];
}
Now for the actual work
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;
{
if (myContext == context) {
self.refreshControl.refreshing = [change[NSKeyValueChangeNewKey] boolValue];
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}

How to reload the tableView after dismissing a NavigationController in uisplitviewcontroller?

I'm using a UISplitviewController as a template.
action for edit button:
newExViewController *editWindow =[[newExViewController alloc]initWithNibName:#"newExViewController" bundle:nil];
UINavigationController *navBar=[[UINavigationController alloc]initWithRootViewController:editWindow];
navBar.modalPresentationStyle = UIModalPresentationFormSheet;
navBar.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:navBar animated:YES];
[navBar release];
[editWindow release];
navBar has a UIBarButton for saveButton. This is called when you press SaveButton
[self dismissModalViewControllerAnimated:YES];
now is the problem:
any idea how to reload the data for both the main NavigationConteroller and the detailViewController when the modalView is dismissed??
I have no clue
thnx
You should look into NSNotificationCenter. In your view with the UITableView, create the notification listener. Then in the view that dismisses, call that notification.
To be more specific, the Notification will call a method that should contain reloadData.
Example
The following should go with the UITableView you want to reload:
This could go along with your [self dismissModalViewControllerAnimated:YES];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(someMethodToReloadTable) name:#"reloadTable" object:nil];
This is how you will call the notification center to reload the table:
[[NSNotificationCenter defaultCenter] postNotificationName:#"reloadTable" object:self];
Example of the notification method:
- (void)someMethodToReloadTable:(NSNotification *)notification
{
[myTableView reloadData];
}
And don't forget to remove the notificaiton observer:
-(void)viewDidUnload
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"reloadTable" object:nil];
}
In controllers, that contains view you want to reload, you should decline following method which will be called when the modalView will be dismissed (or when controller's main view will be first time loaded):
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// here you can reload needful views, for example, tableView:
[tableView reloadData];
}

MBProgresshud with tableview

I am making a application with a tableview in it. I would like to implement a loading screen, using MBProgressHUD such that it will display before data is read from internet. However the data's not shown using following code:
- (void)viewDidLoad
{
HUD = [[MBProgressHUDalloc] initWithView:self.view];
[self.viewaddSubview:HUD];
HUD.delegate = self;
[HUD showWhileExecuting:#selector(load_data) onTarget:self withObject:nil animated:YES];
}
the data can be shown in tableview using the function load_data alone (i.e [self load_data], but not with HUD.
In my experience, when using the HUD to display while loading or waiting for data to load, you should call the HUD in the -viewDidAppear method. I also noticed that you didn't include the [super viewDidLoad]; call in your code. If you are going to present your HUD, you will have to call it after you call on the super viewDidLoad if you want it to appear. Hopefully these help you out.
I like to present and hide the HUD with separate methods that only do that. e.g.
#pragma mark - The HUD
-(void)showHudWithText:(NSString *)text {
if (self.hud == nil) {
self.hud = [[[MBProgressHUD alloc] initWithWindow:self.window] autorelease];
[self.window addSubview:hud];
}
[self.hud setLabelText:text];
[self.hud setMode:MBProgressHUDModeIndeterminate];
[self.hud show:YES];
}
-(void)hideHud {
[self.hud hide:YES];
}
This allows the HUD to be controlled independently of the view life cycle, as well as from asynchronous methods, timers, etc. e.g:
-(void)viewDidLoad {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(showHudWithText:) name:kSomethingImportantStartedNotification object:#"Starting..."];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(hideHud) name:kSomethingImportantEndedNotification object:nil];
}
Or something like that.

Can't seem to be able to re alloc a released UIView

I have a UIView which I create programmatically roughly like so:
#implementation SlideShowView
- (id)initWithImages
{
…
if (self=[super initWithFrame:CGRectMake(0, 0, 320, 480)])
{
// init objects here
}
return self;
}
- (void)dealloc
{
printf("dealloc slide show view\n");
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"unlockUI" object:nil ];
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"setUserRating" object:nil ];
[mCurrentImageView release];
[mRightImageView release];
[mLeftImageView release];
[mImages release];
[queue cancelAllOperations];
[queue release];
[managingArray release];
[super dealloc];
}
with a uiview controller that contains this:
- (void)loadView {
{
...
self.view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 480)];
self.slideShowViewObject = [[SlideShowView alloc] initWithImages];
[self.view addSubview:slideShowViewObject];
…
}
-(void)dealloc
{
[slideShowViewObject release];
[self.slideShowViewObject removeFromSuperview];
printf("dealloc of slideshow view controller\n");
[super dealloc];
}
this seems to be able to dealloc both the view controller and view but when I push this view controller back onto the stack I get the message with zombie enabled: -[SlideShowView retain]: message sent to deallocated instance 0x43ab160
should the alloc not be creating a new instance of the view when the controller is pushed? I don't understand and after spending many hours reading through other posts as well as looking at memory guides I would say I'm thoroughly stumped! Can someone offer any pointers? I only posted the code I deemed necessary but I can post more if need be.
Thanks so much!
First, you shouldn't call both [super init] and [super initWithFrame:], only one of the two in your UIView subclass.
Secondly, you should only set self.view in a view controller in the -loadView method, which is designed for you to create your views. Normally, unless you need to do certain types of setup such as initializing variables, etc, you shouldn't need to override -init in UIViewController subclasses.
finally, after finagling with the code again tonight I finally got it working. The key piece I was not releasing appears to be
[self.view release];
I suppose this makes perfect sense since I was adding the view as a subview of this alloc'd self view, but I had tried this before. Perhaps cleaning up the code is what made it work this time.