0

At several points in my project, I need to perform some synchronous web service calls or read from CoreData. It may take a couple seconds to preform these operations, so I created a LoadingView class that is a subclass of UIView that has a simple message and a UIActivityIndicatorView. For normal UIButtons, I just [myLoadingView setHidden:NO] on the Touch Down event, and [myLoadingView setHidden:YES] on the Touch Up Inside event. This all works exactly how I want.

The problem is I cannot find out how to do this with the Return key on the keyboard. From what I can tell, there is only 1 method that gets called when the user touches the Return key (the UITextFieldDelegate protocol textFieldShouldReturn:), and I need two methods to be able to get my [myLoadingView setHidden:NO] -> [myLoadingView setHidden:YES] technique to work, since Objective-C doesn't update the screen until the very end of the method, as opposed to continuously updating the screen like other languages.

How can I make my Loading screen show up as soon as the user touches the Return key, perform some operations, and they hide the Loading screen once the operations are done?


EDIT:

I've tried looking at using NSNotificationCenter, but I seem to be running into the same problem. For example, if I have a UIButton Touch Up Inside method:

- (void) btnClick:(id) sender
{
    [[NSNotificationCenter defaultCenter] postNotificationName:@"Show Loading" object:nil];

    // Do stuff that takes a long time here

    [[NSNotificationCenter defaultCenter] postNotificationName:@"Hide Loading" object:nil];
}

Where in my LoadingView.m I have:

- (id) init
{
    self = [super init];
    if (self)
    {
        // Do normal init stuff here

        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(showLoading) name:@"Show Loading" object:nil];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(hideLoading) name:@"Hide Loading" object:nil];
    }
    return self;
}

- (void) showLoading
{
    // Do stuff to set up the loading screen 

    [self setHidden:NO];
}

- (void) hideLoading
{
    [self setHidden:YES];
}

With it set up like this, when I click the button, I never see the loading screen. When I post the notification, does it execute and change the screen right away? If it does, I must be doing something wrong, maybe in my // Do stuff parts. If it does not, I don't think NSNotificationCenter is going to help me any =(.


EDIT 2:

I set up a quick test project, and I confirmed that the notifications DO NOT update the screen right away. The btnClick code I posted in my last EDIT behaves exactly the same as simply doing

- (void) btnClick:(id) sender
{
    [loadingView setHidden:NO];

    // Do stuff that takes a long time here

    [loadingView setHidden:YES];
}

So that confirms that nothing that relies on NSNotificationCenter is going to help me.

Really, it just looks like there isn't any support for changing the screen during a synchronous operation, which is really disappointing. I'm out of ideas at this point.

GeneralMike
  • 2,951
  • 3
  • 28
  • 56
  • Rather than binding the show/hide logic to potentially lots of UI events, how about binding the show/hide logic to the process that requires it. When process begins, fire off a notification which can be observed by, say, your app delegate which would then show/hide the loading indicator. This way, the logic is in one spot. Same scenario for when your process has completed. – Jeremy Dec 09 '13 at 14:47
  • I think I see where you are going, but there are actually many different processes that will be showing/hiding the loading screen. So really I think the logic is going to be in many spots no matter what. – GeneralMike Dec 09 '13 at 14:54
  • The show/hide would be in a single spot, but yes, you will need to post a notification for each process that needs this functionality, something to the effect of `[[NSNotificationCenter defaultCenter] postNotificationName:PROCESS_DID_BEGIN object:nil]`. Suppose you decide to change the name of your showHide method. You will have to search/replace throughout your app to do this, else you can simply go to the object whose sole responsibility is to manage the display of the loading indicator. – Jeremy Dec 09 '13 at 15:07
  • Just food for thought as I use this quite extensively in my app (8 webservices and ~50 service methods). Suppose you have a background process, like some sort of background update engine which requires the loading indicator..you wouldn't have that bound to any specific UI element. – Jeremy Dec 09 '13 at 15:08
  • @Jeremy: Yeah I was thinking about it some more, and the more I think about it the more I think I should be just using `NSNotificationCenter`. I would want to do all the `NSNotificationCenter` setup in the `init` for my `LoadingView`, then `postNotification` from the view controllers that I want to show/hide the Loading screen over, correct? – GeneralMike Dec 09 '13 at 15:17
  • Yes that is one way of doing it. – Jeremy Dec 09 '13 at 15:33
  • @Jeremy: I'm having some issues with the `NSNotificationCenter` - please see the edit to my OP. Do you have any thoughts? – GeneralMike Dec 09 '13 at 18:17
  • Firstly, don't use spaces in your notification name. Secondly, put some trace in your show/hide methods to see if they're ever called. – Will Jenkins Dec 19 '13 at 16:05
  • @WillJenkins: I tested as you suggested, see EDIT 2 to my OP. – GeneralMike Dec 19 '13 at 17:37

3 Answers3

2

You need a callback from whatever processes you're calling to let you know they've completed. When the callback fires, you can hide the loading screen.

Will Jenkins
  • 9,507
  • 1
  • 27
  • 46
  • I don't think I've had to set up these callbacks before. Are these built in methods I need to call? Or are you talking about using `NSNotificationCenter`? – GeneralMike Dec 09 '13 at 15:29
  • And if the processes I'm running are running synchronously, will I see my loading screen to show and then hide, or will it just stay hidden? – GeneralMike Dec 19 '13 at 15:41
  • Possibly, possibly not. You really shouldn't do stuff like web service calls synchronously, though. – Will Jenkins Dec 19 '13 at 16:07
  • The web services I'm calling are to get data updates. If the user tries to continue while it's updating, they would be using old data. In my industry, using old data is a big no-no. So it's safer for me to do the updates synchronously. My users understand that they will need to update, and they know they won't be able to use my app while it's updating. I just need to show my loading screen so they know when the updates are happening, and hide it to let them know when it's done. – GeneralMike Dec 19 '13 at 16:18
  • 1
    Updates to the UI happen on the main thread. If you're doing other stuff on the main thread that blocks until it completes, then naturally your UI won't update in between. This is why asynchronous calls are used - so your UI can be updated and be responsive in the meantime. If you need to prevent users interacting with old data, you should manage this yourself, not by making your app hang until your data appears. – Will Jenkins Dec 23 '13 at 18:52
1

You can ofcourse also use didEndOnExit event of textfield, that gets fired when you press the return key.

You can start showing the activity indicator view in this method and when the loading completes, you can use some callback(in case of web service- NSURLConnectionDataDelegate Protocol method: connectionDidFinishLoading) to hide the indicator view.

Puneet Sharma
  • 9,369
  • 1
  • 27
  • 33
  • I was looking at the [Apple Docs](https://developer.apple.com/library/Mac/DOCUMENTATION/Foundation/Reference/NSURLConnectionDelegate_Protocol/Reference/Reference.html), and I couldn't find anything like `connectionDidFinishLoading`. It also doesn't auto-complete when I try to add it to my code. Has that maybe been depreciated? And is there something that gets called when CoreData finishes saving/loading as well? – GeneralMike Dec 09 '13 at 15:32
  • If you are using NSURLConnection for hitting web-services, then use protocol NSURLConnectionDataDelegate and its method: - (void)connectionDidFinishLoading:(NSURLConnection *)connection. Refer: http://developer.apple.com/library/ios/documentation/Foundation/Reference/NSURLConnectionDataDelegate_protocol/Reference/Reference.html#//apple_ref/occ/intfm/NSURLConnectionDataDelegate/connectionDidFinishLoading: To know when data is saved in core data, you can make use of NSManagedObjectContextDidSaveNotification, by adding observer via NSNotificationCenter for this notification – Puneet Sharma Dec 10 '13 at 06:12
0

I ended up using Grand Central Dispatch to handle this. The simplest way is to

- (void) doStuff
{
    // Show wait on start
    [myLoadingView setHidden:NO];

    dispatch_queue_t queue = dispatch_queue_create("com.myDomain.myApp",null);
    dispatch_async(queue, ^{
        // Code to execute
        {
            //... Do my time consuming stuff here ...
            // For testing purposes, I'm using
            int i = 0;
            while (i < 1000000000)
            {
                i++;
            }
        }
        dispatch_async(dispatch_get_main_queue(), ^{
            // Hide Wait Screen on End
            // ... also need to do any other UI stuff here ...
            [myLoadingView setHidden:YES];
        });
    });
}

Unfortunately for me, my actual time consuming stuff has some GUI changes in it (like showing alerts, preforming segues, changing labels, etc.). Instead of rewriting the whole thing to try to pull all the GUI stuff out, I discovered that nesting it all in another dispatch_async, I can still get the waiting screen to show while the operations are performing, and all the other GUI stuff that is done in time consuming stuff will update when it's done (after the waiting screen disappears).

- (void) doStuff
{
    // Show wait on start
    [myLoadingView setHidden:NO];

    dispatch_queue_t queue = dispatch_queue_create("com.myDomain.myApp",null);
    dispatch_async(queue, ^{
        dispatch_async(dispatch_get_main_queue(), ^{
            // Double nesting the dispatches seems to allow me to do UI changes as part of 'Code to execute' below.
            // If I do not double nest like this, GUI behavior in "time consuming stuff" will be erratic
            dispatch_queue_t queue2 = dispatch_queue_create("com.myDomain.myApp",null);
            dispatch_async(queue2, ^{
                dispatch_async(dispatch_get_main_queue(), ^{

                    // Code to execute
                    {
                        //... Do my time consuming stuff here - GUI changes will appear when all the code has finished running ...
                        // For testing purposes, I'm using
                        int i = 0;
                        while (i < 1000000000)
                        {
                            i++;
                        }
                    }

                    // Hide Wait Screen on End
                    [myLoadingView setHidden:YES];
                });
            });
        });
    });
}

Note that if doStuff needs to return a value, you'll probably need to change it up a little to use a "completion block", detailed in the accepted answer here.

Community
  • 1
  • 1
GeneralMike
  • 2,951
  • 3
  • 28
  • 56