p2p.wrox.com Forums

p2p.wrox.com Forums (http://p2p.wrox.com/index.php)
-   BOOK: Beginning iOS 4 Application Development (http://p2p.wrox.com/forumdisplay.php?f=599)
-   -   Ch 6, Text re NSNotificationCenter question (http://p2p.wrox.com/showthread.php?t=82784)

gNotapipe February 28th, 2011 01:25 PM

Ch 6, Text re NSNotificationCenter question
 
In the text explaining the last Try It Out, "Shirfting Views" in Ch 6, Keyboard Inputs, there is something I find puzzling. (p176-178)
There are two methods messaging the NSNotificationCenter, viewWillAppear: and viewWillDisappear:.
The first reads:
Code:

//---before the View window appears---
-(void) viewWillAppear:(BOOL)animated {
        //---registers the notifications for keyboard---
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardDidShow:) name:UIKeyboardDidShowNotification object:self.view.window];
       
        [[NSNotificationCenter defaultCenter]
        addObserver:self selector:@selector(keyboardDidHide:) name:UIKeyboardDidHideNotification object:nil];
}

In the text, the author states:
"Finally, before the View window disappears, you remove the notifications that you set earlier:"
and then lists the 2nd method:
Code:

//---before the View window diappears---
-(void) viewWillDisappear:(BOOL)animated {
        //---remove the notifications for keyboard---
        [[NSNotificationCenter defaultCenter]
        removeObserver:self name:UIKeyboardWillShowNotification object:nil];
       
        [[NSNotificationCenter defaultCenter]
        removeObserver:self name:UIKeyboardWillHideNotification object:nil];
}

Note that the name values in the 2nd do not match those in the first. Given the explanatory statement, I would think that they should.
Is this, as I suspect, an errata, or is there something about NSNotificationCenter, or the code or the statement, I'm not understanding?
The code does work, so it certainly could be my newbness!

thepianoguy February 28th, 2011 03:04 PM

You are correct the book is wrong. The reason it still works should pretty apparent. The observer to the notification is added and never removed. What is removed was never added.

Check the following with the added lines to illustrate:

Code:

//---when the keyboard appears---
-(void) keyboardDidShow:(NSNotification *) notification {
       
        NSLog(@"The notification name is %@",[notification name]);
        if (keyboardIsShown) return;
       
    NSDictionary* info = [notification userInfo];
       
        //---obtain the size of the keyboard---
        NSValue *aValue = [info objectForKey:UIKeyboardFrameEndUserInfoKey];       
        CGRect keyboardRect = [self.view convertRect:[aValue CGRectValue] fromView:nil];

        NSLog(@"%f", [aValue CGRectValue].size.height);

        NSLog(@"%f", keyboardRect.size.height);
       
    //---resize the scroll view (with keyboard)---
    CGRect viewFrame = [scrollView frame];
    viewFrame.size.height -= keyboardRect.size.height;
    scrollView.frame = viewFrame;
       
    //---scroll to the current text field---
    CGRect textFieldRect = [currentTextField frame];
    [scrollView scrollRectToVisible:textFieldRect animated:YES];
               
    keyboardIsShown = YES;
}

//---when the keyboard disappears---
-(void) keyboardDidHide:(NSNotification *) notification {
        NSDictionary* info = [notification userInfo];
       
        NSLog(@"The notification name is %@",[notification name]);
       
        //---obtain the size of the keyboard---
    NSValue* aValue = [info objectForKey:UIKeyboardFrameEndUserInfoKey];
        CGRect keyboardRect = [self.view convertRect:[aValue CGRectValue] fromView:nil];
           
    //---resize the scroll view back to the original size (without keyboard)---
    CGRect viewFrame = [scrollView frame];
    viewFrame.size.height += keyboardRect.size.height;
    scrollView.frame = viewFrame;
       
    keyboardIsShown = NO;
}

-(void) viewWillAppear:(BOOL)animated {   
    //---registers the notifications for keyboard---
       
        static BOOL FirstTime = YES;
        if (FirstTime) {
                NSLog(@"Registering the observer");

    [[NSNotificationCenter defaultCenter] addObserver:self
                                                                                        selector:@selector(keyboardDidShow:)
                                                                                                name:UIKeyboardDidShowNotification
                                                                                          object:self.view.window];
   
    [[NSNotificationCenter defaultCenter]        addObserver:self
                                                                                          selector:@selector(keyboardDidHide:)
                                                                                                  name:UIKeyboardDidHideNotification
                                                                                                object:nil];
                FirstTime = NO;
        }
}

Bob

gNotapipe March 1st, 2011 11:26 AM

...& so the observer to the notification is simply over-written when used again, sure.

Thanks, Bob, for helping me polish a bit more roughness off my newbness.
Your examples of debugging techniques are invaluable.

Chet

thepianoguy March 1st, 2011 10:53 PM

A follow up, since my answer is not fully accurate. (still exploring)
The code as written functions because the observer is added and never removed. Your response about the observer being overwritten is not correct. The -viewWillDisappear is never called, so no observers are ever removed until the program quits.
To illustrate the need to correctly pair notification names when adding and removing observers, I figured that the simplest thing to do would be to add a second view and log the behavior as the views were switched out since the documentation suggests that the -viewWillAppear and -viewWillDisappear will be called when this occurs. Things don't exactly work as expected. Stay tuned…
Bob

gNotapipe March 2nd, 2011 01:15 PM

No? Would it be the case then that each time a text field is used or exited that a separate observer+notification is added for that object? --I guess I mean observer & name:UIKeyboardDidShowNotification when a text field is entered & the name:UIKeyboardDidHideNotification for that observer when the keyboard is dismissed.--

-viewWillDisappear is never called? So it's simply that the program exits?
Is the keyboard a view? I would think it would be, but, to be honest, I haven't tried to find out (yet).
Interesting.
Staying tuned...

thepianoguy March 2nd, 2011 02:00 PM

The keyboard is not a treated as a view (as far as I can tell). I made a similar assumption that that was the view appearing and disappearing, and that the scrollView, being the delegate of the textView received messages as a result of this. In retrospect, not great reasoning on my part.(The method would probably had a signature like -keyboardViewDidShow if it was) It is the scrollView itself that receives these notifications and responds with the methods assigned to the particular notification when registering. The notifications are sent whether there are observers or not. Once registered an object will receive the notifications it is registered for until it removes itself as an observer. It is sort of like "If a tree falls in a forest…" You only hear it if you are set up to listen for it.

What is going on in the program is the following
-viewWillAppear is called and the scrollView is registered as an observer to the -UIKeyboardDidShow and -UIKeyboardDidHide notifications. -viewDidLoad is called after this. The keyboard shows and hides automatically as the textField becomes and relinquishes FirstResponder status. The scrollView receives the notifications from this and adjusts its view accordingly. The concept behind the program relinquishing observer status when the scrollView hides makes sense, since it will receive the notifications whether it is visible or not (showing and hiding the keyboard even though not visible). So offscreen - relinquish, return - reestablish.
-viewDidLoad is called only when the view is initialized, so the idea of using -viewWillAppear is that it is called each time the view returns. The issue arises - do
-viewWillAppear and -viewWillDisappear actually get called after the view is hidden? The answer is - it depends. If you add a secondViewController.view as a subView to the scrollView, it does not (There is a way to call it directly, but it is a one way street, try to call from the subView and design and encapsulation problems start to pop up and the idea of loose coupling goes out the window) Dismissing the subView and returning to the original view does not call -viewWillAppear even though the original view is no longer hidden.
I will be doing examples that makes use navigation and tabbar controllers to verify that things behave as expected when assigned to a main controller. (From some internet research, the calling of these delegate methods is tied into to an objects location on the stack)
More to come…
Bob

gNotapipe March 4th, 2011 10:27 PM

So the notifications are firing away like gangbusters all the time. That's interesting.

Re "...when the scrollView hides...", isn't the scrollView always visible?

Thanks for your detailed discussion. Although I think I've followed you in a general sense, I suspect I'll understand this in much greater depth after I've worked through more of the book & apple docs. It will be interesting to reread your post later on. (Oh! that's what Bob was saying....) :)

thepianoguy March 7th, 2011 03:34 PM

Followup on various points.

In the NotificationCenter when adding and removing an object as an observer, -addObserver and -removeObserver notifications should always be paired (the same name). If the names are mismatched then the observer is added and never removed. The documentation underscores the need to remove observers before the observer or any observed objects are deallocated. So the book is incorrect when using DidShow and DidHide when adding and WillShow and WillHide when removing. As a side note, if you observe the visual result, in the book's example, the animation is better if WillShow is used. As noted previously, adding the observer in -viewWillAppear and removing the observer in -viewWillDisappear is done since these methods are usually called automatically when the view comes into view, and is removed. A key point is that these methods must be called directly under some circumstances. If a main controller is used (TabBar, Navigation, SplitView) then these methods are called automatically as delegate methods when the view is added (pushed) and removed (popped) from the stack. On the other hand, if a view is added as a subview to an existing view the -viewWillAppear method should be called directly by the class allocating the instance of the subview. When removing itself the subview should call the -viewWillDisappear on itself.

To demonstrate this I have applied a variant of the ScrollerView example from the book to various mutliple view scenarios within a TabBar example.
1. FirstViewController assigned to Tab 1 adds the ScrollerView as a subview to itself. When adding theScrollerView subview, FirstViewController must call
-viewWillDisappear
on itself as ScrollerView appears, and -viewWillAppear on the ScrollerView instance it creates. When the ScrollerView subview removes itself it calls -viewWillDisappear on itself (ScrollerView) and sends a notification to FirstViewController to call -viewWillAppear on itself (FirstView). If these direct calls are omitted (or commented out in my project example), the methods are not called. The other situation that is taken into account is switching tabs while the subview is still visible. Switching views in the TabBar calls -viewWillDisappear on the viewController in the tab that it is leaving (FirstViewController in this case). FirstView has no need for the message and should forward it. In the example, the message is passed on to the subview, since that view is in front. When the tab is returned to (Tab 1 in this case), -viewWillAppear is called on FirstViewController which again, must pass it on to its subview.
2. SecondViewController is the basic implementation from the book. As you can see, the -viewWillAppear and -viewWillDisappear methods are called automatically when the tab selection changes.
3. Tab number 3 installs a navigationController into the 3rd tab. Again, the -viewWillAppear and -viewWillDisappear methods are called automatically, as you drill down from and return to the RootController.

All this can be tracked through the NSLog statements.

The link to the project.
https://files.me.com/thepianoguy88/wednub


In addition, to get a sense of how many notifications are constantly being passed around, uncomment
// [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(listNotifications:) name:nil object:nil];
in in the TabBar delegate and check the console as you run the program. You can envision it somewhat like Twitter. All types of messages are constantly being posted, but you only receive the ones you follow.

Finally, the UIKeyboard is a view. More on that later.

Bob

gNotapipe March 7th, 2011 11:00 PM

Hi Bob,
I must pretty much echo my last comment. I believe I'm following what you're saying, though, again, I think it will have much more meaning for me when I'm more advanced, have a greater familiarity w/ the overall environment & can more readily place things in context. I am trying to balance the work-through-the-steps learning of the book w/ reading enough but not too much of the documentation along the way. One of the things which I feel could be better in this book is if it were to point out more specifically in its text which methods are being written from scratch & which are a utilization of the platform. One can work it out by digging back through & checking w/ the option+mouseOver of course, but I think many beginners will not think of this. Further, I think it's rather a missed opportunity--an elucidation of the reasoning behind added methods could help greatly w/ learning. OTOH, there's only so much one can include, :)

I'm almost mixed on looking at your project; I think it's likely a bit much for me w/ how far along I am w/ iOS. Yet I don't want lose track of it & fail to come back to it. Hmm...

In any case, I look forward to your book... You are writing one, right?
;)
C.

thepianoguy March 8th, 2011 10:07 AM

Feel free to check it out at your own pace.
As far as the from scratch/preexisting method problem, I think for any object you are using it is valuable to browse its class reference. Be aware of its properties, constants, notifications and whether it has delegate methods. You don't need to have everything in your head obviously, but after a while you can start to predict (guess?) what methods probably exist for an object and even what the signature will most likely be. The big "How was I supposed to know about that method?" question was one of the big stumbling blocks for me when I began dabbling in Cocoa. Foundation oriented stuff was very similar to libraries in C and was obvious, but GUI interaction was a whole new ballgame, with its heavy reliance on delegates and notifications in order to maintain the loose coupling that is such an important conceptual backbone of the object oriented design philosophy of iOS and Cocoa. Then along came bindings…

The project that I set up is actually pretty straightforward and primarily utilizes things already covered in the book up to the ScrollerView example. I don't think you would have any problem following it. It is the ScrollerView example with the addObserver/removeObserver conflict corrected combined with the TabBar example. By adding a second view there is now a reason to actually implement the -viewDidAppear and -viewDidDisappear methods instead of just putting the notification registration code in the -viewDidLoad and -viewDidUnload methods. The SecondView in the project is the corrected ScrollerView from the book. By switching to the other tabs you can observe (in the console) the notifications being sent and the observers being added and removed in SecondView, something you can't do in the book's single view project. So if you "get" the project in the book this will make sense and you can see the mechanics in action. The ThirdView uses a NavigationController to show that the behavior is consistent with the TabBar. The only view that might require some thought is the FirstView. This was based on the author's approach to programmatically add a view in Chapter 3. This is included to demonstrate that views added directly as subviews and not through controllers behave differently in terms of what delegate messages they receive, and that some methods must be called directly.


New link, since there was a tiny bug in the project.
https://files.me.com/thepianoguy88/xg6nbp

Bob


All times are GMT -4. The time now is 07:39 AM.

Powered by vBulletin®
Copyright ©2000 - 2020, Jelsoft Enterprises Ltd.
© 2013 John Wiley & Sons, Inc.