Need Help with applicationDidBecomeActive - iphone

I have been trying for days to get this code to work, but I have no idea what I am doing wrong. Everytime the app wakes up from sleep, or the user closes the app and opens it again (without closing the app from multitasking), I want a label value to change.
In my applicationDidBecomeActive, I am running a counter, which I want to display on whatever viewcontroller is open at that moment.
Code:
- (void)applicationDidBecomeActive:(UIApplication *)application {
counter = counter + 1;
W1G1 *view1 = [[[W1G1 alloc] initWithNibName:#"W1G1" bundle:nil] retain];
[view1 setlabel];
}
In my viewcontroller W1G1, I have the following code:
Code:
- (void) setlabel {
NSString *string = [NSString stringWithFormat:#"%d", counter];
vocabword.text = string;
}
I have imported W1G1 in my appdelegate, but the code does not run :( Please help!
Thanks

In the AppDelegate.m file, where you have
- (void)applicationDidBecomeActive:(UIApplication *)application {
counter = counter + 1;
W1G1 *view1 = [[[W1G1 alloc] initWithNibName:#"W1G1" bundle:nil] retain];
[view1 setlabel];
}
the variable counter being incremented is confined to the AppDelegate. In other words, your view controller doesn't know that it has been incremented.
I would suggest that you use NSUserDefaults to store the value of counter so that you can easily pass it between these view controllers. Either that, or you could allow for an input into the method setLabel, e.g.
- (void) setlabel:(int)counter {
NSString *string = [NSString stringWithFormat:#"%d", counter];
vocabword.text = string;
}
and then in the AppDelegate you'll want to do:
- (void)applicationDidBecomeActive:(UIApplication *)application {
counter = counter + 1;
W1G1 *view1 = [[[W1G1 alloc] initWithNibName:#"W1G1" bundle:nil] retain];
[view1 setlabel:counter]; // <-- now you're using counter
[self.window addSubview:view1];
}

1) When you say 'the code does not run' do you mean that? That is, if you put NSLogs in applicationDidBecomeActive: and in setLabel does it show the code is run?
2) I would suspect the code is running. But your code won't "show the counter on whatever view controller is open at that moment". Your code creates a new view (view1), but that view won't be displayed. It is not added as a subview to anything. Your code will also leak. You create a W1G1 object, but it is never released and you throw away any reference you have to it.
To achieve what you want, you could add a subview to the application's window. Depending how your app delegate is set up, something like the following should do the trick:
counter++;
W1G1 *viewController1 = [[W1G1 alloc] initWithNibName:#"W1G1" bundle:nil];
[viewController1 setlabel: counter];
[[self window] addSubview: [viewController1 view]]
// you'll want to save a reference to the viewController somehow so you can release it at a later date
Then in W1G1
- (void) setlabel: (int) counter;
{
NSString *string = [NSString stringWithFormat:#"%d", counter];
vocabword.text = string;
}
There are, of course, lots of other approaches you could take towards this problem. And you'll need some strategy for removing the W1G1 view that you are adding at some stage, otherwise you'll just get more and more views added.
Update: You ask (in comments) how to keep track of your viewController throughout lifetime of the app... One approach is to keep track of it in your appDelegate. In the header have something like:
#class W1G1;
#interface MyAppDelegate : : NSObject <UIApplicationDelegate>
{
// other decelerations
int counter;
W1G1 * _myW1G1
}
#property (nonatomic, retain) W1G1* theW1G1
In the .m file include
#synthesize theW1G1 = _myW1G1;
Probably in application:didFinishLaunchingWithOptions: create the viewController, set the property to refer to it, and add its view to the view hierarchy.
W1G1* theViewController = [[W1G! alloc] initWithNibName: #"W1G1" bundle: nil];
[[self window] addSubview: [theViewController view]];
[self setTheW1G1: theViewController];
[theViewController release];
Then when you want to access the viewController again from with the app delegate use [self theW1G1], e.g.
[[self W1G1] setlabel: counter];

Related

Object is nil when called from another class

I want to change properties of another object, when a method is called in another class.
The code to change the properties of this object sits in a method of the first class, and works when calling it from it's own class, but when called from the other class the object in the method returns nil.
Here is the code:
ViewController.h
#interface ViewController : UIViewController {
UIView *menuView; //the object
}
#property (nonatomic, retain) IBOutlet UIView *menuView;
-(void)closeMenu; //the method
#end
ViewController.m
#implementation ViewController
#synthesize menuView;
-(void)closeMenu{
[menuView setFrame:CGRectMake(menuView.frame.origin.x, -menuView.frame.size.height, menuView.frame.size.width, menuView.frame.size.height)];
NSLog(#"%f", menuView.frame.size.height); //returns height when method is called from it's own class. But returns 0 (nil) when called from the other class.
}
SDNestedTableViewController.h (nothing too important, but might help?)
#interface SDMenuViewController : SDNestedTableViewController{
}
SDNestedTableViewController.m
#import "SDMenuViewController.h"
#import "ViewController.h"
- (void) item:(SDGroupCell *)item subItemDidChange:(SDSelectableCell *)subItem
{
ViewController *firstViewController = [[[ViewController alloc] init] autorelease];
SelectableCellState state = subItem.selectableCellState;
NSIndexPath *indexPath = [item.subTable indexPathForCell:subItem];
switch (state) {
case Checked:
NSLog(#"Changed Sub Item at indexPath:%# to state \"Checked\"", indexPath);
[firstViewController closeMenu]; //called from other class
break;
case Unchecked:
NSLog(#"Changed Sub Item at indexPath:%# to state \"Unchecked\"", indexPath);
break;
default:
break;
}
}
What you posted looks like:
-(void)closeMenu{
// menuView is never initialized, == nil
[nil setFrame:CGRectMake(0, -0, 0, 0)];
NSLog(#"%f", 0); //returns height when method is called from it's own class. But returns 0 (nil) when called from the other class.
}
So you are doing NSLog(#"%f", 0);.
If you do load the view by accessing the view property, the menuView will be initialized by IB rules.
For the details of viewController view loading/unloading see the reference docs.
I think this may help you.
At Your AppDelegate class, you have to declare an object of ViewController class. Make it as a property of the YourAppDelegate class. like below. (This would import ViewController class and creates a shared object of YourAppDelegate class so that you can access the members of YourAppDelegate class globally by simply importing the YourAppDelegate.h).
#import "ViewController.h"
#define UIAppDelegate ((YourAppDelegate *)[UIApplication sharedApplication].delegate)
#interface YourAppDelegate : NSObject <UIApplicationDelegate>
{
ViewController *objViewController;
}
#property (nonatomic, retain) ViewController *objViewController;
#end
And synthesize the property at YourAppDelegate.m file.
#implementation YourAppDelegate
#synthesize objViewController;
#end
Then the tricky part is, you have to backup the object of ViewController class in the YourAppDelegate class at the time you are loading the ViewController class.
For that first import the YourAppDelegate.h in the ViewController.h class and at the ViewController.m implement viewWillAppear: delegate as follows.
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
UIAppDelegate.objViewController = self;
}
Then at SDNestedTableViewController.m,
#import "SDMenuViewController.h"
#import "ViewController.h"
- (void) item:(SDGroupCell *)item subItemDidChange:(SDSelectableCell *)subItem
{
ViewController *firstViewController = (ViewController *)UIAppDelegate.objViewController;
if(firstViewController && [firstViewController isKindOfClass:[ViewController class]])
{
SelectableCellState state = subItem.selectableCellState;
NSIndexPath *indexPath = [item.subTable indexPathForCell:subItem];
switch (state) {
case Checked:
NSLog(#"Changed Sub Item at indexPath:%# to state \"Checked\"", indexPath);
[firstViewController closeMenu]; //called from other class
break;
case Unchecked:
NSLog(#"Changed Sub Item at indexPath:%# to state \"Unchecked\"", indexPath);
break;
default:
break;
}
}
}
Try this way. I am not saying this as the right way but, this should works. Glad if this helps you.
EDIT 2:
Well, you shipped your code over to me, so now I can no longer say that I don't have enough information to solve your problem.
Let's see.
Now I see that your ViewController is the rootViewController of your app, like so:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
// Override point for customization after application launch.
self.viewController = [[[ViewController alloc] initWithNibName:#"ViewController" bundle:nil] autorelease];
self.window.rootViewController = self.viewController;
[self.window makeKeyAndVisible];
return YES;
}
Good, now how does the ViewController relate to your SDNestedTableViewController?
You have this in your ViewController's viewDidLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
SDMenuViewController *mvc = [[[SDMenuViewController alloc] initWithNibName:#"SDNestedTableView" bundle:nil] autorelease];
[self addChildViewController:mvc];
[mvc didMoveToParentViewController:self];
[menuView addSubview:mvc.view];
// Some other stuff with gesture recognizers I'm omitting...
[self openMenu];
}
Alright, so it looks like SDMenuViewController is the child of ViewController. Now, you have a method in SDMenuViewController called item:subItemDidChange:
- (void) item:(SDGroupCell *)item subItemDidChange:(SDSelectableCell *)subItem
{
ViewController *firstViewController = [[[ViewController alloc] initWithNibName:#"ViewController" bundle:nil] autorelease];
SelectableCellState state = subItem.selectableCellState;
NSIndexPath *indexPath = [item.subTable indexPathForCell:subItem];
switch (state) {
case Checked:
NSLog(#"Changed Sub Item at indexPath:%# to state \"Checked\"", indexPath);
//close the menuView
[firstViewController closeMenu];
break;
case Unchecked:
NSLog(#"Changed Sub Item at indexPath:%# to state \"Unchecked\"", indexPath);
break;
default:
break;
}
}
So, you want the reference back to the existing ViewController object, right? Because right there you're making another one. So, you can do this:
ViewController *firstViewController = self.parentViewController;
That gets you a reference to SDMenuViewController's parent, which is the instance of ViewController. This property is set when you do your addChildViewController: call.
Okay, this is confusing though:
In your post, you say that your item:subItemDidChange: method is in SDNestedTableViewController, but in the code you sent me it's in the SDMenuViewController.
In the SDNestedTableViewController, I found this method:
- (void) mainItem:(SDGroupCell *)item subItemDidChange: (SDSelectableCell *)subItem forTap:(BOOL)tapped
{
if(delegate != nil && [delegate respondsToSelector:#selector(item:subItemDidChange:)] )
{
[delegate performSelector:#selector(item:subItemDidChange:) withObject:item withObject:subItem];
}
}
So it looks like you're not using the same code as in the original post, but close enough, whatever.
Now, if you want to get a reference to the ViewController instance from anywhere in the app, not just your SDMenuViewController (which happens to be the child of the ViewController instance) you should use #Mathew Varghese's answer.
Here's a restatement of this method:
Add the line + (AppDelegate *)instance; to your AppDelegate.h file.
Add the following method to your AppDelegate.m file.
Like so:
+ (AppDelegate *)instance
{
AppDelegate *dg = [UIApplication sharedApplication].delegate;
return dg;
}
Then, in whatever object you want that reference, you #import AppDelegate.h and say ViewController *vc = AppDelegate.instance.firstViewController;
Anyway, it's just another way of saying what Mathew mentioned earlier.
the problem is:
- (void) item:(SDGroupCell *)item subItemDidChange:(SDSelectableCell *)subItem {
ViewController *firstViewController = [[[ViewController alloc] init] autorelease];
...
[firstViewController closeMenu];
}
When you call closeMenu from there, it is never initialized, because not enough time has passed to initialize view of view controller, viewDidLoad method of your firstViewController is not called at this point either. menuView is not created from nib either, so this is the reason why it is nil.
Maybe for some reason there might be a delay long enough so menuView is created, but this is not how you should do things in iOS.
So, if you don't want to show your menuView, just add some boolean value to your firstViewController and instead of closeMenu do:
firstViewController.shouldCloseMenu = YES;
Then in your ViewController in viewDidLoad method do something like:
if (self.shouldCloseMenu ) {
[self closeMenu];
}
Maybe this is not the best way to do it, but now you have an idea how it suppose to work.
I believe your problem is the related to the way you have initialized the viewController.
Instead of
ViewController *firstViewController = [[[ViewController alloc] init] autorelease];
use
ViewController *firstViewController = [[[ViewController alloc] initWithNibName:#"yourNibName" bundle:nil] autorelease];
I'm assuming you have a nib because you are using an IBOutlet. But I believe the IBOutlet is never setup because you have not loaded the nib file.
Also double check your IBOutlet connection with interface builder and use "self.menuView"
I would suggest you to solve this problem in the following steps.
Do not use any instance or variable of firstViewController in the SDMenuViewController.
In the case check block, post a message to the NSNotificationCenter
In the ViewController register the message with the same message Id, use the closeMenu method as its handler.
For me, use the message center to dispatch the handling can decouple the relationship between controllers. This is a better way that you would concern less about the lifecycle of the controller within another one.
Hope it would be helpful.
There is a difference between alloc-init'ing a ViewController and alloc-init'ing that view controller's properties.
Regarding your second example (calling from another class). Your current code indicates that you alloc-init firstViewController, but then don't do anything with it. Assuming you have not overriden your ViewController's init method, its properties and iVars should be nil (or undefined at worst). You need to alloc-init your firstViewController.menuView first. I.e:
firstViewController.menuView = [[UIView alloc] initWithFrame]; // Don't do this.
The problem with this approach is that you're setting up firstViewController's properties form another class, and that's generally fairly average design practice. This required setup would usually happen in viewDidLoad but because you haven't done anything with firstViewController yet, it never gets called.
In contrast, when you call closeMenu from its own View Controller, the odds are you are actually doing something with the view and viewDidLoad (or wherever menuView = [[UIView alloc] init];is found) is called first, thus initialising your menuView object.
You need to ensure that your menuView object is initialised first before you try and do anything with it, just initialising the View Controller that contains it is not enough.
#import "SDMenuViewController.h"
#import "ViewController.h"
- (void) item:(SDGroupCell *)item subItemDidChange:(SDSelectableCell *)subItem
{
// why are we allocating this object here, if it is only required in case Checked :
ViewController *firstViewController = [[[ViewController alloc] init] autorelease];
SelectableCellState state = subItem.selectableCellState;
NSIndexPath *indexPath = [item.subTable indexPathForCell:subItem];
switch (state) {
case Checked:
NSLog(#"Changed Sub Item at indexPath:%# to state \"Checked\"", indexPath);
[firstViewController closeMenu]; //called from other class
break;
case Unchecked:
NSLog(#"Changed Sub Item at indexPath:%# to state \"Unchecked\"", indexPath);
break;
default:
break;
}
}
Change it to
#import "SDMenuViewController.h"
#import "ViewController.h"
- (void) item:(SDGroupCell *)item subItemDidChange:(SDSelectableCell *)subItem
{
// why are we allocating this object here, if it is only required in case Checked :
SelectableCellState state = subItem.selectableCellState;
NSIndexPath *indexPath = [item.subTable indexPathForCell:subItem];
switch (state) {
case Checked:
NSLog(#"Changed Sub Item at indexPath:%# to state \"Checked\"", indexPath);
// here no need to put object in autorelease mode.
ViewController *firstViewController = [[ViewController alloc] init];
[firstViewController closeMenu]; //called from other class
[firstViewController release];
break;
case Unchecked:
NSLog(#"Changed Sub Item at indexPath:%# to state \"Unchecked\"", indexPath);
break;
default:
break;
}
}
try to remove UIView *menuView; //the object from the interface file
#interface ViewController : UIViewController {
// try to remove this line
UIView *menuView; //the object
}
and update this method
-(void)closeMenu{
[self.menuView setFrame:CGRectMake(self.menuView.frame.origin.x, -self.menuView.frame.size.height, self.menuView.frame.size.width, self.menuView.frame.size.height)];
NSLog(#"%f", self.menuView.frame.size.height);
}
Everything is correct, change the -(void)closeMenu method like...
-(void)closeMenu
{
menuView=[[UIView alloc]initWithFrame:CGRectMake(50.0,50.0,200.0,200.0)]
NSLog(#"%f", menuView.frame.size.height); //returns height when method is called from it's own class. But returns 0 (nil) when called from the other class.
}
Try this and let me know.
I suggest you use this:
if(menuView) {
[menuView setFrame:CGRectMake(menuView.frame.origin.x, -menuView.frame.size.height, menuView.frame.size.width, menuView.frame.size.height)];
} else {
NSLog(#"menuView is nil");
}

Use of MBProgressHUD Globally + make it singleton

In my Project, each of the user interaction events make a network call (Which is TCP, not HTTP). I need Activity Indicator to be global to show from a random UIViewController and hide from NetworkActivityManager Class (a custom class to handle network activities, Which is not a subclass of UIViewController or UIView).
After searching the web I found out that MBProgressHUD is used for the same purpose, but I wasn't able to find out an example on how would I use it globally. (By saying global I mean a singleton object of MBProgressHUD and class methods to SHOW and HIDE it.)
Following is what I have tried yet, but, failed:
In AppDelegate.h:
#property (nonatomic, retain) MBProgressHUD *hud;
In AppDelegate.m:
#synthesize hud;
In some random UIViewController object:
appDelegate.hud = [MBProgressHUD showHUDAddedTo:appDelegate.navigationController.topViewController.view animated:YES];
appDelegate.hud.labelText = #"This will take some time.";
And while hiding it, from NetworkActivityManager Class:
[MBProgressHUD hideHUDForView:appDelegate.navigationController.topViewController.view animated:YES];
This makes the project to crash after some time (due to memory issues.)
I am using ARC in my project and also, I am using the ARC version of MBProgressHUD.
Am I missing something?
Important Question:
Can I make MBProgressHUD work like UIAlertView? (Saying that I mean implementation of MBProgressHUD independent of UIView -- sa it uses showHUDAddedTo: to present itself) ???
Please Note: In the above code of hiding MBProgressHUD, View may be changed from what it was when showing MBProgressHUD.
Any Help greatly appreciated.
You could add this to a class of your liking:
+ (MBProgressHUD *)showGlobalProgressHUDWithTitle:(NSString *)title {
UIWindow *window = [[[UIApplication sharedApplication] windows] lastObject];
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:window animated:YES];
hud.labelText = title;
return hud;
}
+ (void)dismissGlobalHUD {
UIWindow *window = [[[UIApplication sharedApplication] windows] lastObject];
[MBProgressHUD hideHUDForView:window animated:YES];
}
This can be than called on any class. You don't need to keep a strong reference to the HUD when using those class convenience methods.
Depending on your specific situation you'll probably also want to handle cases where a new hud is requested before the other one is hidden. You could eater hide the previous hud when a new comes in or come up with some sort of queueing, etc.
Hiding the previous HUD instance before showing a new one is pretty straightforward.
+ (MBProgressHUD *)showGlobalProgressHUDWithTitle:(NSString *)title {
UIWindow *window = [[[UIApplication sharedApplication] windows] lastObject];
[MBProgressHUD hideAllHUDsForView:window animated:YES];
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:window animated:YES];
hud.labelText = title;
return hud;
}
NOTE...
as with many iOS issues, this is now drastically, totally out of date.
These days you certainly just use a trivial
Container view
for any issue like this.
Full container view tutorial for beginners .. tutorial!
MBProgressHUD was a miraculous solution back in the day, because there was a "drastic hole" in Apple's pipeline.
But (as with many wonderful things from the past), this is only history now. Don't do anything like this today.
Just FWIW, 2014, here's a very simple setup we use. Per David Lawson...
UIWindow *window = [[UIApplication sharedApplication] delegate].window
as Matej says, just use AppDelegate...
#define APP ((AppDelegate *)[[UIApplication sharedApplication] delegate])
AppDelegate.h
// our convenient huddie system (messages with a hud, spinner)
#property (nonatomic, strong) MBProgressHUD *hud;
-(void)huddie;
AppDelegate.m
-(void)huddie
{
// centralised location for MBProgressHUD
[self.hud hide:YES];
UIWindow *windowForHud = [[UIApplication sharedApplication] delegate].window;
self.hud = [MBProgressHUD showHUDAddedTo:windowForHud animated:YES];
self.hud.dimBackground = YES;
self.hud.minShowTime = 0.1;
self.hud.labelText = #"";
self.hud.detailsLabelText = #"";
}
Set the titles in your code where you are using it - because you very often change them during a run. ("Step 1" ... "Step 2" etc)
-(void)loadBlahFromCloud
{
[APP huddie];
APP.hud.labelText = #"Connecting to Parse...";
APP.hud.detailsLabelText = #"step 1/2";
[blah refreshFromCloudThen:
^{
[... example];
}];
}
-(void)example
{
APP.hud.labelText = #"Connecting to the bank...";
APP.hud.detailsLabelText = #"step 2/2";
[blah sendDetailsThen:
^{
[APP.hud hide:YES];
[... showNewDisplay];
}];
}
Change huddle to take the texts as an argument if you wish
You always want self.hud.minShowTime = 0.1; to avoid flicker
Almost always self.hud.dimBackground = YES; which also blocks UI
Conceptually of course you usually have to "slightly wait" to begin work / end work when you bring up such a process, as with any similar programming with the UI.
So in practice code will usually look like this...
-(void)loadActionSheets
{
[APP huddie];
APP.hud.labelText = #"Loading json from net...";
dispatch_after_secs_on_main(0.1 ,
^{
[STUBS refreshNowFromCloudThen:
^{
[APP.hud hide:YES];
dispatch_after_secs_on_main(0.1 , ^{ [self buildActionsheet]; });
}];
}
);
}
Handy macro ..
#define dispatch_after_secs_on_main( SS, BB ) \
dispatch_after( \
dispatch_time(DISPATCH_TIME_NOW, SS*NSEC_PER_SEC), \
dispatch_get_main_queue(), \
BB \
)
This is all history now :) https://stackoverflow.com/a/23403979/294884
This answer is what I've been using for 5-6 Apps now because it works perfectly inside blocks too. However I found a problem with it. I can make it shown, but can't make it disappear if a UIAlertView is also present. If you look at the implementation you can see why. Simply change it to this:
static UIWindow *window;
+ (MBProgressHUD *)showGlobalProgressHUDWithTitle:(NSString *)title {
window = [[[UIApplication sharedApplication] windows] lastObject];
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:window animated:YES];
hud.labelText = title;
return hud;
}
+ (void)dismissGlobalHUD {
[MBProgressHUD hideHUDForView:window animated:YES];
}
This will make sure you're removing the HUD from the same windows as it was shown on.
I found #Matej Bukovinski 's answer very helpful, since I just started using Swift and my purpose using his methods was to set a global font for the MBProgressHUD, I have converted the code to swift and am willing to share the code below:
class func showGlobalProgressHUDWithTitle(title: String) -> MBProgressHUD{
let window:UIWindow = UIApplication.sharedApplication().windows.last as! UIWindow
let hud = MBProgressHUD.showHUDAddedTo(window, animated: true)
hud.labelText = title
hud.labelFont = UIFont(name: FONT_NAME, size: 15.0)
return hud
}
class func dismissGlobalHUD() -> Void{
let window:UIWindow = UIApplication.sharedApplication().windows.last as! UIWindow
MBProgressHUD.hideAllHUDsForView(window, animated: true)
}
The above code is put into a global file where I keep all my global helpers and constants.
I've used it as below..Hope it helps you..
in appDelegate.m
-(void)showIndicator:(NSString *)withTitleString currentView:(UIView *)currentView
{
if (!isIndicatorStarted) {
// The hud will dispable all input on the view
self.progressHUD = [[[MBProgressHUD alloc] initWithView:currentView] autorelease];
// Add HUD to screen
[currentView addSubview:self.progressHUD];
self.progressHUD.labelText = withTitleString;
[window setUserInteractionEnabled:FALSE];
[self.progressHUD show:YES];
isIndicatorStarted = TRUE;
}
}
-(void)hideIndicator
{
[self.progressHUD show:NO];
[self.progressHUD removeFromSuperview];
self.progressHUD = nil;
[window setUserInteractionEnabled:TRUE];
isIndicatorStarted = FALSE;
}
From Random Views:-
[appDel showIndicator:#"Loading.." currentView:presentView.view];
Note: Considering the views this Question is getting I decided to post the the way I did choose as a solution. This is NOT an answer to my question. (Hence, the accepted answer remains accepted)
At that time I ended up using SVProgressHUD as it was very simple to integrate and use.
All you need to do is just drag the SVProgressHUD/SVProgressHUD folder into your project. (You may choose to go for cocoapods OR carthage, as well)
In Objective-C:
[SVProgressHUD show]; // Show
[SVProgressHUD dismiss]; // Dismiss
In Swift:
SVProgressHUD.show() // Show
SVProgressHUD.dismiss() // Dismiss
Additionally, Show and hide HUD needs to be executed on main thread. (Specifically you would need this to hide the HUD in some closure in background)
e.g.:
dispatch_async(dispatch_get_main_queue(), ^{
[SVProgressHUD dismiss]; // OR SHOW, whatever the need is.
});
There are additional methods for displaying custom messages with HUD, showing success/failure for short duration and auto dismiss.
MBProgressHUD still remains a good choice for developers. It's just that I found SVProgressHUD to suit my needs.
I was using the code from #Michael Shang and having all kinds of inconsistent behavior with showing HUDs. Turns out using the last window is unreliable as the iOS keyboard may just hide it. So in the majority of cases you should get the window using the AppDelegate as mentioned by #David Lawson.
Here's how in Swift:
let window = UIApplication.sharedApplication().delegate!.window!!
let hud = MBProgressHUD.showHUDAddedTo(window, animated: true)
However, with the above your HUD will show up behind the iOS keyboard (if they overlap). If you need your HUD to overlay the keyboard use the last window method.
In my case, what was happening is I would show the HUD then call resignFirstResponder() immediately hiding the window the HUD was added to. So this is something to be aware of, the only window guaranteed to stick around is the first one.
I ended up creating a method that could optionally add the HUD above the keyboard if needed:
func createHUD(size: CGSize, overKeyboard: Bool = false) -> MBProgressHUD {
let window = overKeyboard ? UIApplication.sharedApplication().windows.last!
: UIApplication.sharedApplication().delegate!.window!!
let hud = MBProgressHUD.showHUDAddedTo(window, animated: true)
hud.minSize = size
hud.bezelView.style = .SolidColor
hud.bezelView.color = UIColor(white: 0, alpha: 0.8)
return hud
}
To show the one MBProgressHUD at one time, you can check weather HUD is already added in same view or not. If not, then add the HUD otherwise do not add new HUD.
-(void)showLoader{
dispatch_async(dispatch_get_main_queue(), ^{
BOOL isHudAlreadyAdded = false;
UIWindow *window = [[[UIApplication sharedApplication] windows] lastObject];
NSEnumerator *subviewsEnum = [window.subviews reverseObjectEnumerator];
for (UIView *subview in subviewsEnum) {
if ([subview isKindOfClass:[MBProgressHUD class]]) {
isHudAlreadyAdded = true;
}
}
if(isHudAlreadyAdded == false){
[MBProgressHUD showHUDAddedTo:window animated:YES];
}
});
}
-(void)hideLoader{
dispatch_async(dispatch_get_main_queue(), ^{
UIWindow *window = [[[UIApplication sharedApplication] windows] lastObject];
[MBProgressHUD hideHUDForView:window animated:YES];
});
}
Add these two methods to show or hide loader in your singleton class
- (void)startLoaderWithText:(NSString *)title View:(UIView *)view{
progressHud = [MBProgressHUD showHUDAddedTo:view animated:YES];
progressHud.labelText = title;
progressHud.activityIndicatorColor = [UIColor grayColor];
progressHud.color = [UIColor clearColor];
[progressHud show:YES];
}
- (void)stopLoader{
[progressHud hide:YES];
}

iOS Accessing views data from delegate without allocating a new view

I need to change a data (a label) from the app's delegate method ApplicationDidEnterForeground without allocating a new view. The view is called "Reminder", so I imported it into the delegate and I can access its data only if I allocate it (Reminder *anything = [Reminder alloc...etc), but since I want to change the current view loaded I need to have direct access to the view that's already loaded.
How would I do to change the main view's label from the delegate as soon as my application enters foreground?
obs: I know I can do it on -(void)ViewDidLoad or -(void)ViewWillAppear but it won't solve my problem, since it won't change the label if, for example, the user opens the app through a notification box (slide icon when phone is locked). In that case, none of the above methods are called if the app was open in background.
I don't know if I was clear, hope I was. Thank you in advance.
IF you are using storyboards, you can do this to access the current view being seen
- (void)applicationDidEnterBackground:(UIApplication *)application
{
UINavigationController *a=_window.rootViewController;
Reminder *rem = a.topViewController;
rem.label.text=#"test";
}
IF not using story boards
When I create views that I need to access later, I define them as a property, like this
on AppDelegate.h
//#interface SIMCAppDelegate : UIResponder <..........>
//{
//Some variables here
//}
//Properties here
#property (strong, nonatomic) Reminder *reminder;
//Some method declaration here
//eg: -(void) showSomething;
on AppDelegate.m
//#implementation AppDelegate
#synthesize reminder;
so when I alloc/init the view like this
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//codes before here
self.reminder = [[Reminder alloc] init];
self.reminder.label.text = #"OLD LABEL";
//codes after here
}
I will be able to access it again after allocation on other methods, like this
- (void)applicationWillEnterForeground:(UIApplication *)application
{
self.reminder.label.text = #"NEW LABEL";
}
just send a notification from your ApplicationDidEnterForeground: method and receive it on that class where you want to update the label... Like this..
//Your ApplicationDidEnterForeground:
[[NSNotificationCenter defaultCenter] postNotificationWithName:#"UpdateLabel" withObject:nill];
and add observer in it viewDidLoad: of that controller where you want to update label
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(updateLabel:)
name:#"UpdateLabel"
object:nil];
made your method in same class ...
- (void)updateLabel:(NSNotification *)notification{
update label
}
Might be you can try following code -
NSMutableArray *activeControllerArray = [self.navigationController.viewControllers mutableCopy];
for (int i = 0; i< [activeControllerArray count]; i++) {
if ([[activeControllerArray objectAtIndex:i] isKindOfClass:[Reminder Class]) {
Reminder *object = [activeControllerArray objectAtIndex:i];
//Perform all the task here which you want.
break; //Once found break the loop to do further processing.
}
}

Scroll View inside a View

i need your help. Basically I created a small scrollView and a pageControl inside my main view controller. Now when ever a button inside a scroll view is pressed I lose the value of every property in my mainViewController. To help you get a clearer picture let me explain:
(NoteViewController.m) This is the action the button that is pressed from the scrollview responds to
- (IBAction)removePerson:(UIButton *)sender {
MainViewController *remover = [[MainViewController alloc] init];
[remover removePersonWithPage:pageNumber];
[self.view removeFromSuperview];
[remover release]; }
(MainViewController.m)
- (void)removePersonWithPage:(int)page {
// The managedObjectContext is lost the moment it leaves MainViewController.m and goes to NoteViewController.m
// so you need to reload the managedObjectContext
if (managedObjectContext == nil)
{
managedObjectContext = [(OrdersAppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
}
// Get the list of people (Persons) from the managed Object Context
arrayOfPeople = [[NSMutableArray alloc] initWithArray:[self fetchDataWithEntity:#"Person" andSortKey:#"pageId"]];
// Find a specific person to delete using their page number and delete it finally remove it from array
NSManagedObject *personToDelete = [arrayOfPeople objectAtIndex:page];
[managedObjectContext deleteObject:personToDelete];
[arrayOfPeople removeObjectAtIndex:page];
// kNumberOfPages is replaced with the new number of people
kNumberOfPages = arrayOfPeople.count;
/* This is where problem occurs */
self.pageControl.numberOfPages = kNumberOfPages;
NSLog(#"The number of pages in the page control in remove is: %d", self.pageControl.numberOfPages);
[self saveObjectContext];
}
So everything works but when I get to the NSLog at the end there, it returns 0 when it ought to be returning the number of pages in the database. I've been working on this for days now and can't figure it out, please help. Thanks

Update UILabel from applicationDidBecomeActive?

I want to update UILabel by clicking a reload button. Additionally, I want to update the label in background, because it is fetching the new data via XML from my website. Of course it would be nice to auto-update the label when the application is opened. And there is my problem:
I was able to make it work well when user were clicking the button manually. But I don't understand how to do the same by calling my method via "applicationDidBecomeActive". I tried to do it the same way, but it obviously doesn't work because my label is returned nil.
I suppose there is a problem of my understanding and the solution should be quite easy. Thanks for your input! Note: I am a beginner with Objective-C and have sometimes problems with "easy" things. ;-)
Below is a summary of the important code parts:
AppDelegate
- (void)applicationDidBecomeActive:(UIApplication *)application {
[[MyViewController alloc] reloadButtonAction];
}
MyViewController
#synthesize label
- (void)reloadButtonAction {
[self performSelectorInBackground:#selector(updateData) withObject:nil];
}
- (void)updateData {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Parse the XML File and save the data via NSUserDefaults
[[XMLParser alloc] parseXMLFileAtURL];
// Update the labels
[self performSelectorOnMainThread:#selector(updateLabels) withObject:nil waitUntilDone:NO];
[pool release];
}
- (void)updateLabels {
NSUserDefaults *variable = [NSUserDefaults standardUserDefaults];
myLabel.text = [variable stringForKey:#"myLabelText"];
// myLabel is nil when calling all of this via AppDelegate
// so no changes to the myLabel are done in that case
// but: it works perfectly when called via button selector (see below)
NSLog(#"%#",myLabel.text);
}
-(void)viewDidLoad {
// Reload button in the center
UIButton *reloadButton = [UIButton buttonWithType:UIBarButtonSystemItemRefresh];
reloadButton.frame = CGRectMake(145,75,30,30);
[reloadButton setTitle:#"" forState:UIControlStateNormal];
[reloadButton addTarget:self action:#selector(reloadButtonAction) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:reloadButton];
}
First:
[[MyViewController alloc] reloadButtonAction];
Doesn't make sense. You allocate memory, without initializing an object. And then you want to call a method on it. Doesn't work
Use an instance for it:
[myViewControllerInstance reloadButtonAction];
In your app delegate you should have an reference to your rootcontroller instance if that is the object contains the reload method, use that instance.
Note:
Alloc only reserves space in the memory for an object which size the size of MyViewController instance. An init method will fill it.