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

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];
}

Related

NSNotificationCenter and Tap Method calling

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:

refresh uitable of a viewcontroller from other viewcontroller

I have a uitable in one of the Tabview of a tabbarcontroller application. Now depending on some action in other tabview, the uitable should reload (refresh) for updated data at backgrouns. However, I am not able to get it using either reloadData or beginupdates-endUpdates.
Can someone please help in this kind of scenario.
Thanks in advance.
I would recommend use a combination of NSNotification as well as viewWillAppear/viewDidAppear for this.
When the viewWillAppear - reload the tableview (You may refine it by looking for change in data since you last time displayed the data)
- (void)viewWillAppear:(BOOL)animated
{
// Your other code here...
[self.tableview reloadData];
}
Once the view has appeared and in background the data is changed by some other object - ask that object to send notification and in your tableview's viewcontroller register for that notificaiton in viewWillAppear & deregister in viewWillDisappear
The other object should send/post notification like this (just after the data change)-
[[NSNotificationCenter defaultCenter] postNotificationName:#"com.yourcompany.appname.XYZdataChangeNotification" object:nil];
All the below code in your viewController (which has table to be updated) -
Register like this -
- (void)viewWillAppear:(BOOL)animated
{
// Your other code here...
[self.tableview reloadData];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleNewDataReceivedNotification:) name:#"com.yourcompany.appname.XYZdataChangeNotification" object:nil];
}
Notification handler in your view controller -
- (void)handleNewDataReceivedNotification:(NSNotification *)notification
{
// Your other code here...
[self.tableview reloadData];
}
And de-register like this -
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"com.yourcompany.appname.XYZdataChangeNotification" object:nil];
}
All the above code can be refined but it should give you an idea. Please feel free to ask if any questions/concerns.

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
}

UITableViewCell not hiding progress bar

I have a UITableView that is supposed to show progress bars at various times, and then hide them when finished. However, the progress bars aren’t disappearing when they’re supposed to.
Background
The app I’m working on is designed to allow users to create multimedia-heavy projects. One of the views shows a list of all the audio files that are currently available, and also gives users the option to import additional files from a number sources, including their music library.
Details
To handle the import from the music library, I’m using a variation of the code posted by Chris Adamson in his blog here. The user can queue up as many imports as necessary, and code will automatically import them in sequence. The read/write part is performed in the background, with notifications sent out to let the rest of the app know the status of the process.
All of the audio files available to the user are shown in a UITableView. The table view uses custom cells containing a label (to display the file’s name) and a progress bar (which is always hidden unless the song is in mid-import).
Here is the code for the cell:
#interface AudioTableCell()
#property (nonatomic, strong) NSString *importingFileName;
#end
#implementation AudioTableCell
// ---------------------------------------------------------------------------------------------------------------------
- (void) prepareForReuse
{
// remove notification listeners to avoid creating duplicates
[[NSNotificationCenter defaultCenter] removeObserver:self name:kNotifMusicLibMgrUpdateProgress object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:kNotifMusicLibMgrFinishedImportingSong object:nil];
// reset cell properties to default values
self.isClickable = YES;
self.downloadProgress.hidden = YES;
}
// ---------------------------------------------------------------------------------------------------------------------
// called by view controller prior to deleting a cell
- (void) prepareCellForDeletion
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:kNotifMusicLibMgrUpdateProgress object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:kNotifMusicLibMgrFinishedImportingSong object:nil];
}
// ---------------------------------------------------------------------------------------------------------------------
// called by the view controller's cellForRowAtIndexPath method
- (void) configureCell
{
// by default, cell should be clickable and the progress bar should be hidden
self.isClickable = YES;
self.downloadProgress.hidden = YES;
// listen to the import manager for updates on the import's progress
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(importWasUpdated:)
name:kNotifMusicLibMgrUpdateProgress
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(importWasFinished:)
name:kNotifMusicLibMgrFinishedImportingSong
object:nil];
// when the cell is scrolled out of view, the progress bar is hidden
// need to remember if depicted song was in process of being downloaded
if (self.importingFileName.length > 0) {
if ([self.trackLabel.text isEqualToString:[self.importingFileName stringByAppendingPathExtension:#"m4a"]]) {
self.downloadProgress.hidden = NO;
self.isClickable = NO;
}
}
}
// ---------------------------------------------------------------------------------------------------------------------
// triggered when receiving a notification that a song has been imported successfully
- (void) importWasFinished:(NSNotification *)notification
{
// the notification will pass the name of the song that was imported
NSString *fileName = [notification.userInfo objectForKey:#"audioFileName"];
// if the song that was imported matches the song being displayed by the cell, hide the progress bar
// and make the cell clickable
if ([self.trackLabel.text isEqualToString:[fileName stringByAppendingPathExtension:#"m4a"]]) {
self.downloadProgress.hidden = YES;
self.isClickable = YES;
}
// if the song that was imported matches the song that was "saved" by the cell, clear the saved song
if ([self.importingFileName isEqualToString:fileName]) {
self.importingFileName = #"";
}
}
// ---------------------------------------------------------------------------------------------------------------------
// triggered when receiving a an update notification (currently 1/second)
- (void) importWasUpdated:(NSNotification *)notification
{
// the notification will pass the name of the song being imported, its progress, and the number of songs queued
NSString *fileName = [notification.userInfo objectForKey:#"audioFileName"];
double completion = [[notification.userInfo objectForKey:#"progress"] doubleValue];
// if the cell is displaying the song being imported, disable the cell and show a progress bar
// also, store the name of the song - this way, if the cell is reused for a different song, it will still remember
// the song being downloaded and can immediately display the progress bar when brought back into view
if ([self.trackLabel.text isEqualToString:[fileName stringByAppendingPathExtension:#"m4a"]])
{
self.isClickable = NO;
self.downloadProgress.hidden = NO;
[self.downloadProgress setProgress:completion];
self.importingFileName = fileName;
} else {
self.downloadProgress.hidden = YES;
self.isClickable = YES;
}
}
#end
As you can see, the cell listens for notifications from the importer to determine when to hide/show the progress bar. If I only try to import a single song, there are no major issues (although there does seem to be slight delay between the time the song finishes and the time the progress bar hides). However, if I queue up multiple songs, the progress bar doesn’t go away at all until all of them are finished. Even worse, scrolling the cell out of view will cause the progress bar to appear for a different song when the cell gets reused.
I’ve tested the notification system using both NSLog statements and the debugger, and it’s working correctly (the “self.downloadProgressBar.hidden = YES;” section is getting called at the right time), but the progress bar remains visible. At this point I am completely out of ideas. Any and all help would be greatly appreciated.
Thanks in advance.
Please check that your code is executed on main thread. NSNotification can be posted on background thread so your code will be executed on background thread which won't update the UI.

How do you handle keyboardDidShow on multiple views?

I have an app where I add a new item to a table view by having the user tap an edit button which shows a textfield cell at the bottom of the table, similar to the built in Notifications app. I need to adjust the table when the keyboard is shown so that it is not obstructed when their are many rows in the table. I am doing this by subscribing to the notification for when the keyboard shows:
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector (keyboardDidShow:)
name: UIKeyboardDidShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector (keyboardDidHide:)
name: UIKeyboardDidHideNotification
object:nil];
}
...
...
-(void) keyboardDidShow: (NSNotification *)notif
{
// If keyboard is visible, return
if (self.keyboardVisible)
{
return;
}
// Get the size of the keyboard.
NSDictionary* info = [notif userInfo];
NSValue* aValue = [info objectForKey:UIKeyboardFrameBeginUserInfoKey];
CGSize keyboardSize = [aValue CGRectValue].size;
// Adjust the table view by the keyboards height.
self.tableView.contentInset = UIEdgeInsetsMake(0, 0, keyboardSize.height, 0);
NSIndexPath *path = [NSIndexPath indexPathForRow:self.newsFeeds.count inSection:0];
[self.tableView scrollToRowAtIndexPath:path atScrollPosition:UITableViewScrollPositionTop animated:YES];
self.keyboardVisible = YES;
}
However, the table that I let a user add a row to can also be tapped and a new view is pushed on to the app. This view also has a text view and when the user taps in it and the keyboard shows the first viewcontroller still gets the notification, which causes a crash.
How can I either ignore the notification or get it to not fire when a new view is pushed?
You could add the class as an observer in viewDidAppear and remove it in viewWillDisappear.

Resources