UITabBar badge not updated - ios

I can't seem to add a badge on a TabBarItem
Tried a lot of options (that's why the code is splitted into variables).
Thought it had something to do with the treading so the update is back on the main thread, still nothing.
The code:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSInteger badge_count = 0;
badge_count = getDataFromServer();
if (snacks_count > 0)
{
MainTabBarViewController *c = [self.storyboard instantiateViewControllerWithIdentifier:#"tabBarController"];
UINavigationController *nav = [c.viewControllers objectAtIndex:1];
dispatch_async(dispatch_get_main_queue(), ^{
nav.tabBarItem.badgeValue = [NSString stringWithFormat:#"%ld", (long)badge_count];
});
}
});

Please pay attention to the method: :instantiateViewControllerWithIdentifier" description:
Instantiates and returns the view controller with the specified identifier.
You use this method to create view controller objects that you want to manipulate and present programmatically in your application. Before you can use this method to retrieve a view controller, you must explicitly tag it with an appropriate identifier string in Interface Builder.
Namely, you create another instance.
You should change to
UINavigationController *nav = [self.viewControllers objectAtIndex:1];
in order to change the UINavigationController that you refer to

Apparently the original problem was that I didn't do it from the main thread. Probably made a wrong code with all the playing around trying to debug.
The working code:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
badge_count = getDataFromServer();
if (snacks_count > 0)
{
NSInteger badge_count = 0;
badge_count = getDataFromServer();
if (badge_count > 0)
{
dispatch_async(dispatch_get_main_queue(), ^{
[(UIViewController *)[self.tabBarController.viewControllers objectAtIndex:1] tabBarItem].badgeValue = [NSString stringWithFormat:#"%ld", (long)badge_count];
});
}
});

Related

Correct way to handle application launching with quick actions

I am having a hard time figuring out how to get my quick actions working when I launch my app with a quick action.
My quick actions work, however, if the app was in the background and re-launched with the quick action.
When I try to launch the app straight from the quick action, the app opens as if it was launched by simply tapping the app icon (i.e. it does nothing).
Here is some code from my App Delegate.
In didFinishLaunchingWithOptions:
UIApplicationShortcutItem *shortcut = launchOptions[UIApplicationLaunchOptionsShortcutItemKey];
if(shortcut != nil){
performShortcutDelegate = NO;
[self performQuickAction: shortcut fromLaunch:YES];
}
The method called:
-(BOOL) performQuickAction: (UIApplicationShortcutItem *)shortcutItem fromLaunch:(BOOL)launchedFromInactive {
NSMutableArray *meetings = [self.fetchedResultController.fetchedObjects mutableCopy];
[meetings removeObjectAtIndex:0];
unsigned long count = meetings.count;
BOOL quickActionHandled = NO;
if(count > 0){
MainViewController *mainVC = (MainViewController *)self.window.rootViewController;
if(launchedFromInactive){
mainVC.shortcut = shortcutItem;
}
else{
UINavigationController *childNav;
MeetingViewController *meetingVC;
for(int i = 0; i < mainVC.childViewControllers.count; i++){
if([mainVC.childViewControllers[i] isKindOfClass: [UINavigationController class]]){
childNav = mainVC.childViewControllers[i];
meetingVC = childNav.childViewControllers[0];
break;
}
}
self.shortcutDelegate = meetingVC;
if ([shortcutItem.type isEqual: #"Meeting"]){
NSNumber *index = [shortcutItem.userInfo objectForKey:#"Index"];
[self.shortcutDelegate switchToCorrectPageWithIndex: index launchedFromInactive:NO];
quickActionHandled = YES;
}
}
}
The only action that needs to be performed is that my page view controller (which is embedded inside the meetingVC) should switch to a certain page with respect to the shortcut chosen.
Any ideas on what causes the shortcut to not do anything when using it to launch as opposed to re-opening the app from the background??
I came to realize I was trying to call my methods on a view controller that was not in memory yet. This was causing bizarre behavior in my app. I did have the correct approach to getting access to the view controller and then it dawned on me the possibility of trying to execute the code using GCD.
__block MeetingViewController *safeSelf = self;
contentVC = [self initializeContentViewController: self.didLaunchFromInactive withPageIndex: intIndex];
NSArray *viewControllers = #[contentVC];
dispatch_async(dispatch_get_main_queue(), ^{
[safeSelf.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil];
});
The above worked like magic, and the shortcuts are leading to the correct page. Using a similar approach to mine hopefully yields the desired results for anyone else who wanted to get their shortcuts working by launching the app.

UILabel is not showing in neither in simulator or iPad

So i am showing a model controller on top of a view controller. And i have texts in the model controller, but somehow the texts are not visible. I tried everything but somehow labels are not visible. But of you stay on the page for like 30 -40 sec the text shows up. Also this model controller is called from main view controller after a successful service(REST) call. If i call the model without making the service call then labels are visible in simulator/iPad both. But if i call it after service call inside success block then labels are not visible. I tried adding the text programmatically but still same issue. I tried debugging using Color blended layers, but the label is not at all visible in the view somehow. :(
[self.serviceManager getCustDetails:account successBlock:^(NSDictionary * successDict) {
[self hideLoadingAnimation];
NSDictionary *custData = [[successDict objectForKey:#"txnData"] objectForKey:#"custData"];
self.showCurrYear = [iraContribData objectForKey:#"showCurrYear"];
if ([self.showCurrYear isEqual: #"true"]){
[self performSegueWithIdentifier:#"CSegue" sender:self];
}
} failureBlock:^(NSDictionary * failureDict) {
[self hideLoadingAnimation];
NSLog(#"Failiure Dict %#",failureDict);
}];
And this prepareForSegue method, -
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.identifier isEqualToString:#"CSegue"]) {
CustViewController *cVC = segue.destinationViewController;
cVC.delegate = self;
[cVC setModalPresentationStyle:UIModalPresentationFormSheet];
cVC.preferredContentSize = CGSizeMake(800,750);
}
}
Below is my screen in storyboard, but in simulator the label is not visible, only continue and close button is visible.
Please help!, any suggestions are most welcome. Thanks!
It is possible that the delay is due to a user interface update not made on the main thread.
Try to make sure that your code is executed on the main thread using dispatch_async like this :
[self.serviceManager getCustDetails:account successBlock:^(NSDictionary * successDict) {
dispatch_async(dispatch_get_main_queue(), ^{
[self hideLoadingAnimation];
NSDictionary *custData = [[successDict objectForKey:#"txnData"] objectForKey:#"custData"];
self.showCurrYear = [iraContribData objectForKey:#"showCurrYear"];
if ([self.showCurrYear isEqualToString:#"true"]){
[self performSegueWithIdentifier:#"CSegue" sender:self];
}
});
} failureBlock:^(NSDictionary * failureDict) {
dispatch_async(dispatch_get_main_queue(), ^{
[self hideLoadingAnimation];
NSLog(#"Failiure Dict %#",failureDict);
});
}];

How to perform s segue in this custom disclosure button?

I am a new bee in IOS development, I found JPSThumbnailAnnotation in github, which really inspired me.
https://github.com/jpsim/JPSThumbnailAnnotation
My question is how to perform a segue in this custom disclosure button?
[self performSegueWithIdentifier:#"XXX" sender:self];
can not be preformed in - (void)didTapDisclosureButton
Thanks!
ok, since you have not specified what - (void)didTapDisclosureButton is, there you go.
On the page you linked to, I found this example
JPSThumbnail *thumbnail = [[JPSThumbnail alloc] init];
thumbnail.image = [UIImage imageNamed:#"empire.jpg"];
thumbnail.title = #"Empire State Building";
thumbnail.subtitle = #"NYC Landmark";
thumbnail.coordinate = CLLocationCoordinate2DMake(40.75f, -73.99f);
thumbnail.disclosureBlock = ^{ NSLog(#"selected Empire"); };
[mapView addAnnotation:[JPSThumbnailAnnotation annotationWithThumbnail:thumbnail]];
So why not:
thumbnail.disclosureBlock = ^{
[self performSegueWithIdentifier:#"XXX" sender:self];
};
As specified on that same page
You can also set a block to be run when the disclosure button is tapped.

Value not being set the second time on iOS 6. Threading issue?

I'm writing an iOS application that populates an array using the data retrieved from the server and displays it in a picker view.
Everything goes smoothly the first time the view is displayed; However, when switching to another view which uses a camera to scan stuff and switch back using a slide-out menu (built using SWRevealViewController), it fails to populate the array. When we access it on the UI thread after the background task has finished to retrieve the records, an NSRangeException is thrown with the error index 0 beyond bounds for empty array.
I'm pretty sure the background task is being run and the data is being retrieved successfully as I log every single request to the server.
I believe it might be an issue with concurrency and the background thread not updating the variable.
As far as we have tested, this issue is only present on iOS 6, and does not happen, or at least has not yet, on iOS 7.
This is the code used to retrieve and set the array:
dispatch_queue_t queue = dispatch_queue_create("com.aaa.bbb", NULL);
dispatch_async(queue, ^{
_events = [_wi getEvents:_auth_token];
dispatch_async(dispatch_get_main_queue(), ^{
// code to be executed on the main thread when background task is finished
[_mPicker reloadComponent:0];
// Set the default on first row
[self pickerView:_mPicker didSelectRow:0 inComponent:0];
[pDialog dismissWithClickedButtonIndex:0 animated:YES];
});
});
This is the prepareforSegue method in my SidebarViewController that is responsible for switching between views when an item is selected from the slide-out menu.
- (void) prepareForSegue: (UIStoryboardSegue *) segue sender: (id) sender
{
// Set the title of navigation bar by using the menu items
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
UINavigationController *destViewController = (UINavigationController*)segue.destinationViewController;
destViewController.title = [[_menuItems objectAtIndex:indexPath.row] capitalizedString];
if ( [segue isKindOfClass: [SWRevealViewControllerSegue class]] ) {
SWRevealViewControllerSegue *swSegue = (SWRevealViewControllerSegue*) segue;
swSegue.performBlock = ^(SWRevealViewControllerSegue* rvc_segue, UIViewController* svc, UIViewController* dvc) {
UINavigationController* navController = (UINavigationController*)self.revealViewController.frontViewController;
[navController setViewControllers: #[dvc] animated: NO ];
[self.revealViewController setFrontViewPosition: FrontViewPositionLeft animated: YES];
};
}
}
The views are linked together from the storyboard for switching.
The error occurs when I try to retrieve a specific entry from my events array in the pickerView:didSelectRow:inComponent: method :
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {
// Set the current id
_currentId = [NSString stringWithFormat:#"%d", row];
_currentName = [[_events objectAtIndex:row] objectForKey:#"event_name"];
-----------^
...
Here's the list of running threads and the call stack.
From my experience in Android, I think that this might have to do something with me not finishing the background task properly the first time, or somehow the code that is supposed to be run after the background task, is run alongside it the second time.
I would appreciate any suggestions that might help me with this issue!
Thanks
The variable _events is accessed from different threads without any synchronization mechanism.
Try this:
dispatch_queue_t queue = dispatch_queue_create("com.aaa.bbb", NULL);
dispatch_async(queue, ^{
NSArray* events = [_wi getEvents:_auth_token];
dispatch_async(dispatch_get_main_queue(), ^{
_events = [events copy];
// code to be executed on the main thread when background task is finished
[_mPicker reloadComponent:0];
// Set the default on first row
[self pickerView:_mPicker didSelectRow:0 inComponent:0];
[pDialog dismissWithClickedButtonIndex:0 animated:YES];
});
});
Also _events should be an NSArray, not NSMutableArray, to avoid such problems.

ObjC: ARC releasing dynamically created view controller too early

I have a view controller, named AllThingsViewController that dynamically creates other view controllers, named ThingViewController, and adds their top level view to a UIScrollView. (I'm writing proprietary code so I've changed the names of my classes, but the structure of my code is exactly the same.)
Here's what its loadView method contains:
NSArray *things = [[ThingDataController shared] getThings];
if ([things count] == 0) {
// code in this block is not relevant as it's not being executed...
} else {
for(unsigned int i = 0; i < [things count]; ++i) {
ThingViewController *thingViewController = [[ThingViewController alloc] init];
[thingViewController loadView];
[scrollView addSubview:thingViewController.topView];
thingViewController.topView.frame = CGRectNewOrigin(thingViewController.topView.frame,
0, thingViewController.topView.frame.size.height*i);
[thingViewController displayThing:thing[i]];
}
}
ThingViewController's loadView method looks like this:
- (void)loadView
{
NSArray *topLevelObjs = nil;
topLevelObjs = [[NSBundle mainBundle] loadNibNamed:#"ThingView" owner:self options:nil];
if (topLevelObjs == nil)
{
NSLog(#"Error: Could not load ThingView xib\n");
return;
}
}
When my app starts up everything displays correctly, until I try to tap one of the buttons that exists in the xib being loaded by ThingViewController, at which point it crashes due to an exception: "unrecognized selector sent to instance". It seems that ARC is releasing my ThingViewController instances too early.
Looking at my code, I figured it was because they weren't being held on to anything, so I created an NSMutableArray as an instance variable in my AllThingsViewController class, and started adding the ThingViewControllers to it thusly:
NSArray *things = [[ThingDataController shared] getThings];
if ([things count] == 0) {
// not being executed...
} else {
for(unsigned int i = 0; i < [things count]; ++i) {
ThingViewController *thingViewController = [[ThingViewController alloc] init];
[thingViewController loadView];
[scrollView addSubview:thingViewController.topView];
thingViewController.topView.frame = CGRectNewOrigin(thingViewController.topView.frame,
0, thingViewController.topView.frame.size.height*i);
[thingViewController displayThing:thing[i]];
[allThingsViewControllers addObject:thingViewController];
}
}
However, it didn't change anything, even though those objects are being added to the array. Finally, just to confirm that this is ARC releasing it early, I changed "thingViewController" to be an instance variable in AllThingsViewController and changed:
ThingViewController *thingViewController = [[ThingViewController alloc] init];
to be:
thingViewController = [[ThingViewController alloc] init];
Sure enough, the last item in the scrollable list doesn't crash when I tap its buttons, but the other ones do, because its ThingViewController isn't being deallocated.
I'm still relatively new to ARC, but after a bunch of Googling I have no idea how to fix this. What do I do?
Couple of things.
Problem 1:
This looks like the cause of your bug:
[allBillViewControllers addObject:billViewController];
It should be:
[allBillViewControllers addObject:thingViewController];
Right?
Problem 2
You are not properly adding the view controller to your view hierarchy. It should be this:
[self addChildViewController:childViewController];
[childViewController.view setFrame:targetFrame];
[scrollView addSubview:childViewController.view];
[childViewController didMoveToParentViewController:self];
And similarly when removing a child view controller:
[childViewController willMoveToParentViewController:nil];
[childViewController.view removeFromSuperview];
[childViewController removeFromParentViewController];
Problem 3
Never call loadView explicitly on a view controller. It gets called by UIKit whenever you access the view property of a view controller.
Problem 4
You must add the view of the child view controller to your scroll view, not an arbitrary subview topView in its view hierarchy. Refactor your ThingViewController class to make this simpler for yourself. :-)
Let's look at your code:
for(unsigned int i = 0; i < [things count]; ++i) {
ThingViewController *thingViewController = [[ThingViewController alloc] init];
[thingViewController loadView];
[scrollView addSubview:thingViewController.topView];
thingViewController.topView.frame = CGRectNewOrigin(thingViewController.topView.frame,
0, thingViewController.topView.frame.size.height*i);
[billViewController displayThing:thing[i]];
[allBillViewControllers addObject:billViewController];
}
After each loop of the for loop executes, nothing will have a strong reference to the ThingViewController. Thus, it gets released and destroyed.
If ThingViewController is a subclass of UIViewController, then it should be made a "child view controller" of the scrollview's view controller. I recommend reading the section on from the View Controller Programming Guide on creating custom container view controllers (i.e., a view controller that encapsulates and displays other view controllers).

Resources