iOS - Changing tabbar badge from other class - ios

I've searched a lot but could not find an answer and therefore decided to ask you :).
I have an application with some views. After logging in I create a UITabBarController with 3 tabs.
Now I wish to change the second tab's badge based on how many notifications the user has.
This core works when called in the viewdidload method:
NSString *badgeValue = [NSString stringWithFormat:#"%i", [cacheNotifications count]];
if([cacheNotifications count] != 0){
[[[[[self tabBarController] viewControllers] objectAtIndex: 1] tabBarItem] setBadgeValue:badgeValue];
}
However, I have a daemon running in the background that checks for notifications every 30 seconds. It would be great if I could change the badge from this daemon.
When I call something like this:
PlatformViewController *theInstance = [[PlatformViewController alloc] init];
[theInstance updateNotificationsBadge];
It does call the function but does not update the badge. With or without the performSelectorOnMainThread.
updateNotificationsBadge:
-(void) updateNotificationsBadge{
NSString *badgeValue = [NSString stringWithFormat:#"%i", [cacheNotifications count]];
NSLog(#"Length here is: %i", [cacheNotifications count]);
if([cacheNotifications count] > 0){
NSLog(#"call..");
[self performSelectorOnMainThread:#selector(setNotification:)
withObject:badgeValue
waitUntilDone:YES];
}
}
-(void)setNotification:(NSString*)badge{
NSLog(#"Called. %#", badge);
[[[[[self tabBarController] viewControllers] objectAtIndex: 1] tabBarItem] setBadgeValue:badge];
}
How could I fix this?
Thanks in advance!
EDIT:
cacheNotifications is a global variable declared in globalVars.m. It does not get reinitialized when I call a new instance.
I call the code below from daemonClass.m
PlatformViewController *theInstance = [[PlatformViewController alloc] init];
[theInstance updateNotificationsBadge];

Instead of creating a new instance for platformViewController, you need to use existing reference. When you create a new one, cacheNotification array would not be initialized and no contents in it. So it will always returns 0.
and UITabBarController is a containerViewController contains all the viewControllers. So you don't need to change the tab badgeValue from the other class. You can simply change it from any class.
and in your setNotification: method, change the badgeValue like this.
[[[[self tabBarController] tabBar] items] objectAtIndex:1] setBadgeValue:badge];

You should use the same instance of class rather creating the new one. Which destroy the previous value. I would recommend please use NSNotificationCenter to post notification when you get a badge which will implement void getter and setter method of badge in platformViewControllerclass. then no instance would be destroy.
Hope this helps

I really don't know exactly your problem, but what I think I would use a subclass of nsobject and in each viewController I'd change the badge. Something similar to this: https://stackoverflow.com/a/9736559/2000162

func setBadge(){
let viewconrollers = self.tabBarController?.viewControllers
viewconrollers![3].tabBarItem.badgeValue = "Your String value"
viewconrollers![3].tabBarItem.badgeColor = UIColor.green
}
//call setBadge() method from view controller's viewdidload method.
// select your give number it the view controllers array for providing badge on the desired tab

Related

Passing data with delegate but still nil why?

I made 2 viewcontrollers and implemented on tabbar controller. I passed some data from A vc to B vc with using delegate. When I checked the log it showed me correct value. But when I moved to B vc the value I passed was nil. (the value is for tableview.) Here is my code.
in A vc
-(void)passData {
NSMutableDictionary *infoDic = [[NSMutableDictionary alloc] init];
[infoDic setObject:url forKey:#"file_url"];
[downloadArr addObject:fileInfoDic];
Bvc getDataFromA:downloadArr];
[Bvc reloadTableView];
}
in B vc
-(void)getDataFromA:(NSMutableArray *) downloadArr{
self.downloadArr = [downloadArr mutableCopy];
NSLog(#"my download list%#", self.downloadArr); // This time was ok.
}
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
NSLog(#"Array status %#", self.downloadArr);//This time it showed me nil
[self.tableView reloadData];
}
To have the array printed inside viewWillAppear without nil
NSLog(#"Array status %#", self.downloadArr);//This time it showed me nil
you need to give it a value before you show bVC from aVC , whatever you use present/segue/push , also don't forget to declare it as strong , you need to do this
bvc = [[self.tabBarController viewControllers] objectAtIndex:1];
[bvc loadViewIfNeeded];
[bvc getDataFromA:downloadArr];
Please check the way you move to B-VC and whether the instance of (B-VC) you used in the A -VC is same to self in the B-VC “viewWillAppear” method.
It seems when u move to B-VC, A new instance has been created.
Or you can implement the 'setDownloadArr' method, log the value and show when the value become nil.

NSNotification sends value that get reset in viewDidLoad

I have this problem:
2 views called A and B, both are TableViewController.
A listen for notification, B sends notification
I'm not going to explain in detail, but for sake of simplicity it's like this:
A can display multiple types of data, B has a list of these types, selecting one row from B makes A load in it's tableView the right list of data.
While i'm in B i'm sending a notification like this
NSNumber *section = [NSNumber numberWithInt:indexPath.row];
NSDictionary *infoDictionary = [NSDictionary dictionaryWithObject:section
forKey:#"CurrentTableView"]
NSString *UpdateTableView = #"UpdateTableView";
[[NSNotificationCenter defaultCenter] postNotificationName:UpdateTableView
object:self
userInfo:infoDictionary];
Now in A i'm listening like this
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(updateTableView:)
name:#"UpdateTableView"
object:nil];
which calls the method
#property (strong, nonatomic) NSNumber *section;
// :::: Some code here ::::
- (void)updateTableView:(NSNotification*)note
{
_section = [[note userInfo] valueForKey:#"CurrentTableView"];
NSLog(#"%d",[_section intValue]);
[self.tableView reloadData];
}
and the NSLog works fine, i mean it prints the right value.
I'm using _section to discriminate in the TableView delegate methods what kind of data to load.
The problem is that the call to this method (after the notification is received) happens BEFORE the view is actually reloaded (viewDidAppear and so) which set my #property _section to 0, in this way every time my TableView loads the data standing behind the [_section intValue] == 0.
How could i solve this? I need something that don't gets reset every time the view loads itself. Any suggestion?
EDIT: navigation controller to move from B to A
MenuNavigationController *navigationController = [self.storyboard instantiateViewControllerWithIdentifier:#"contentController"];
AViewController *homeViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"homeController"];
navigationController.viewControllers = #[homeViewController];
Do you really need NSNotification ? Notifications are used where you want to update a view, and by update i mean that the view is already created.
What i would do is fairly simple, in B i would set your NSNumber *section as an ivar (if you display controller A some place different from where you post you notification). When you posted your notification you would just instantiate your ivar.
Then A would have a similar variable and when you want to display A :
AViewController *homeViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"homeController"];
homeViewController.section = section;
navigationController.viewControllers = #[homeViewController];
Perhaps your property section isn't synthesizing to use your instance variable _section. Does your synthesize line look like this:
#synthesize section=_section;
Otherwise the compiler will implicitly create an instance variable name section
UPDATE
As #Justafinger mentioned, as of Xcode 4.4, the ivar will be named _section if you completely leave out the #synthesize line. If you do explicitly synthesize, however, and don't assign the ivar name in the #synthesize line the ivar name will default to section.

Update Label of a Custom Subclass of UITableViewCell after navigation return?

I am trying to update the score of one of the labels on my custom cell after returning from a push navigation.
In my parent UITableViewController I have the code:
In ViewDidLoad
//These are mutable strings
self.disciplineScoreString1 = [NSMutableString stringWithFormat:#""];
self.disciplineScoreString2 = [NSMutableString stringWithFormat:#""];
self.disciplineScoreString3 = [NSMutableString stringWithFormat:#""];
self.disciplineScoreArray = [[NSMutableArray alloc] init];
[self.disciplineScoreArray addObject:self.disciplineScoreString1];
[self.disciplineScoreArray addObject:self.disciplineScoreString2];
[self.disciplineScoreArray addObject:self.disciplineScoreString3];
So far so good.
In cellForRowAtIndexPath
//this is the custom subclass of UITableViewCell
DayTableViewCell *cell = (DayTableViewCell *) [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
//this is the custom label
cell.disciplineScoreLabel.text = [self.disciplineScoreArray objectAtIndex:indexPath.row];
return cell;
Still so far so good. Right now the label in each of the 3 cells is blank.
I now segue to a UIViewController from the first cell and I return from the child UIViewController successfully setting the string value of self.disciplineScoreString1 to #"10"which I NSLog'ged in the parent UITableViewController.
How do I update the label for the first cell now? I have tried reload data in ViewWillAppear but its not working.
Thankyou
EDIT
This is the code in the Child ViewController
In viewWillDisappear:
[super viewWillDisappear:animated];
[self calculateDisciplineScore];
NSInteger currentVCIndex = [self.navigationController.viewControllers indexOfObject:self.navigationController.topViewController];
DisciplineTableViewController *parent = (DisciplineTableViewController *)[self.navigationController.viewControllers objectAtIndex:currentVCIndex];
parent.disciplineScoreString1 = self.disciplineScoreText;
You must be changing the string, instead of modifying it... consider this example
NSMutableArray *array = [NSMutableArray array];
NSMutableString *disciplineScoreString1 = [NSMutableString stringWithFormat:#"original string"];
[array addObject:disciplineScoreString1];
[disciplineScoreString1 appendString:#" hello"]; // OK.. you're modifying the original string
NSLog(#"%#", [array objectAtIndex:0]);
disciplineScoreString1 = [NSMutableString stringWithFormat:#"new string"]; // WRONG!!
NSLog(#"%#", [array objectAtIndex:0]);
The ouput is:
2014-02-15 06:36:46.693 Hello[19493:903] original string hello
2014-02-15 06:36:46.694 Hello[19493:903] original string hello
The second example is wrong because you're creating a new string, and the object in the array is still pointing to the old original string.
EDIT:
// try this
[parent.disciplineScoreString1 replaceCharactersInRange:NSMakeRange(0, parent.disciplineScoreString1.length) withString:self.disciplineScoreText];
// instead of
parent.disciplineScoreString1 = self.disciplineScoreText;
Your current approach is quite rigid and not very standard. You would normally set up some kind of delegate to pass data from a child to a parent. It is very rarely correct that a child should know how to reach into the parent's innards and change it's data.
I would personally start with something like this
#interface ChildViewController : UIViewController
#property (nonatomic, copy) void (^onCompletion)(ChildViewController *viewController, NSString *disciplineText);
#end
Now I'm assuming you are dismissing by just calling [self.navigationController popViewControllerAnimated:YES] from within the child? This is a little odd as it means that the childViewController can now only ever be presented in a navigationController, this makes it less reusable.
What I would do is at the point where you normally call popViewControllerAnimated: I would call the completion instead
if (self.onCompletion) {
self.onCompletion(self, self.disciplineText);
}
Now the object who provides the onCompletion can decide how this controller get's removed and it's told about the data that we finished with, which enabled the controller to do what it wants with it.
In your case the parent controller would provide the completion as it knows how the child is presented so it will know how to dismiss it. It also know that it may want to do something with the data the child finished with
// Where you present the child
childViewController.disciplineText = self.self.disciplineScoreArray[index];
__weak __typeof(self) weakSelf = self;
childViewController.onCompletion = ^(ChildViewController *viewController, NSString *disciplineText){
weakSelf.disciplineScoreArray replaceObjectAtIndex:index withObject:disciplineText];
[self.navigationController popViewControllerAnimated:YES];
[weakSelf.tableView reloadRowsAtIndexPaths:#[ [NSIndexPath indexPathForRow:index inSection:0] ]
withRowAnimation:UITableViewRowAnimationFade];
};

How do a ViewController knows it got focus on iOS? Like a "viewDidLoad"

I have a NavigationController and one of the tabs is supposed to load a ViewController.
This ViewController (1), when loaded on "viewDidLoad" does some stuff and then pushes a new ViewController (2). The thing is that after ViewController (1) has already passed through viewDidLoad, it won't pass through it again, unless the app is restarted.
Could you guys please refer a clever way to to this?
Here's what I am really doing:
- (void)viewDidLoad
{
// Keep track of cash using NSUserDefaults
BOOL dreceived[63];
int rightData;
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
//Load cash switches
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
NSData *data = [prefs objectForKey:#"dreceived"];
memcpy(&dreceived, data.bytes, data.length);
for(int n = 72; n >= 1; n = n - 1)
{
if(dreceived[n-1]==1)
{
rightData = n;
}
}
NSLog(#"Right Data %d", rightData);
CashItem *c = [cashflow objectAtIndex:rightData];
// Go for details
CashDetailedViewController *cdetail = [[[CashDetailedViewController alloc] init] autorelease];
cdetail.cash = c;
cdetail.navigationItem.hidesBackButton = YES;
[self.navigationController pushViewController:cdetail animated:YES];
}
The thing is, this code is never called again. And if I touch the tab twice, a blank view is displayed (th original xib view).
Thanks!
It sounds like you would want to use viewWillAppear. It is called every time that your view controller is about to be onscreen.
Although, based on what you've posted, you may want to rethink what your doing. Having a view controller that immediately presents another view controller should like it would lead to a confusing user experience.
Put your code in - (void)viewWillAppear instead
Try calling
[yourViewController.view setNeedsDisplay];
Or you could spin the code out to a seperate method and call it in viewDidLoad and viewDidAppear:animated

io looking for insert and cancel pattern

I've been looking around for a good pattern to implement a insert then cancel pattern when working with a UINavigationBar and UITableView.
I have I have a "insert"button in my TeamsViewController navigation bar (screenshot)
Which when I run it runs this code:
-(void)insertTeam
{
if( !detailViewController ) {
detailViewController = [[TeamDetailViewController alloc] init];
}
if( !teams ) {
teams = [NSMutableArray array];
}
Team *team = [[Team alloc] init];
[teams addObject:team];
int lastIndex = [teams count];
[detailViewController setEditingTeam:[teams objectAtIndex:lastIndex - 1]];
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:detailViewController];
[self presentModalViewController:navigationController animated:YES];
}
Which is great if the user fills out all the info, but if they hit cancel on the next view, there's an empty object in my arrary.
I'm sure there's a great pattern to achieve this but I've looked at all the TableView sample codes, two different ios books, and tried googling it, but haven't found a pattern for this.
My thought is something like the following:
When user cancels, set a canceled ivar in my Team object to YES
Back in my TeamsViewController, when the view appears check the last object in my teams array and see if it's property canceled is YES, if so remove that last object.
But this doesn't seem so slick and I was figuring there was some better way to achieve this. TIA.
I would be tempted to make the TeamsViewController a delegate of the TeamDetailViewController. The delegate would implement a method such as - (void)teamCreated:(Team *)team; and it would update the array. Since there seems to be no point to having a Team in the array that's incomplete, I would have the TeamDetailViewController create the Team and pass it back in the delegate call. On a cancel, there would be no need to do anything except pop the controller.

Resources