I'm having some difficulty understanding exactly how the responder chain works in an iPhone application.
My situation is as follows. I have two UIViewControllers that are installed on a tab bar controller. Call them view controller A and B. They are unrelated in the sense that neither of them has a reference or knows about the other.
Both A and B need to respond to remote control events (the play/pause/stop buttons). Controller A wants to respond to these events all the time, whereas B only wants to respond to them when the user selects a certain function. When my app starts, A becomes the first responder immediately and is able to receive the remote control events. When B wants to receive the events, it becomes first responder, and then begins to get the events instead of A.
The problem occurs when B is done using the remote control. At this point, B calls resignFirstResponder on itself. Thereafter, neither A nor B gets any remote control events.
I assumed that when B resigned first responder status, the thing that previously was the first responder would be restored. Is this not how it works?
If not, how do I restore A to first responder? Remember that A and B are separate tabs, so B doesn't have a reference to A or know that A is supposed to be the first responder. So I don't want to explicitly call becomeFirstResponder on A. What I need instead is a way to get the previous first responder and restore it (I think). I'm a bit puzzled as to why this doesn't just happen automatically.
Thanks,
Frank
The docs on UIResponder as well as the Event handling guide for iPhone OS indicate that the responder chain isn't exactly laid out how you expect it to be. It's not a linked chain of potential responders which can be tacked to and pulled from. Rather, it is directly associated with the view hierarchy currently presented to the user.
What this means is that when B resigns first responder, B's view controller (if there is one) or its superview becomes first responder, presuming is has implemented canBecomeFirstResponder:. If it can't, its view controller or superview becomes first responder, all the way up to the UIApplication.
If A and B are not in a hierarchy where one is a subview of the other, the responder chain will not return first responder to A when B resigns. Instead, B will resign first responder to its superview. What you most likely want to do is to implement becomeFirstResponder: in the view that is above both A and B. The method would simply hand first responder over to A. That way, B can grab first responder, and later, when it resigns it, the superview will hand it back to A.
Related
Currently, due to the way my controllers flow into each other on the storyboard, I have one controller that doesn't immediately update with the back button unless I go to the original navigation/entry point. In other words, the controllers are set up as:
A (entrypoint) -> B -> C
If I do something in C that updates B, and I hit the back button in C, B doesn't automatically update. Is there a simple way to do this in Swift 5.1? Thanks!
Your question is not particularly related to Swift 5.1.
A long time ago the property presentingViewController was introduced to get the reference to the presenting view controller.
Pressing the back button dismisses the actual view controller and viewWillDisappear / viewDidDisappear is called.
Add a logic to indicate there are changes and update B – which is represented by presentingViewController – in viewWillDisappear / viewDidDisappear.
I've created an instance of NSObjectController (MenuObject on the image below) and method(test5:) for item's action there.
NSMenuItem is gray when I create a connection to First responder.
And it works fine when I create a IBAction directly.
I think it's because my NSObjectController(MenuObject) doesn't part of responder chain. No one can responds to selector and that's why item is grey. But how to fix it?
Thanks.
NSObjectController is a data-flow controller. Putting it in the responder chain does not make real sense.
However, you should read about the responder-chain for action messages. Doing so, you will prefer to put the action method into a window controller.
I have a UINavigationController with two UITableViewControllers (A and B).
A is displayed first and when the user taps a row on the display, A loads UITableViewController B and pushes it onto the stack.
The user can then update some data in B's UITableView which may result in the tapped row on A's UITableView needing to be updated.
The problem I'm trying to nut out is the best way for UITableViewController B to let UITableViewController A know that it needs to update the row.
I've thought of a number of different ways of doing this including:
Having A store the IndexPath and a reference to B so that when ViewWillAppear is called, it can check properties of B and then reload the cell at the IndexPath.
Have A monitor the core data NSMangedObjectContext through the notification system. Both controllers feed their data from core data. A would still need to store the IndexPath.
Having B somehow (through the UINavigationController?) look up the previous controller (A) and tell it that it needs to update the row.
None of these seem that eligant.
Is there a better way to do this?
The second one is the most elegant to me (and you could tweak it with KVO instead of notifications).
And it is since it does not load B with more responsibilities that he needs (B cannot even know about A) whereas the third option makes B notify A. Plus it will be more independent from changes than the first one (because you are obliged to check the situation before A is loaded, and you use ViewWillApperar:, but this situation can change in the future)
why not use NSNotificationCenter?
Say I have UITableViewController A, and UITableViewController B.
Both A and B loads UIView C.
At the back button in C, how do I make sure it always goes back to B, rather than where it came from?
Here is a concrete example:
A=Contacts window in iphone skype.
B=Chats window, each row is a chat history with a different person
C = Chat window displays a conversation with the same person .
C can be loaded from A or B, but I want the backbutton on the Chat window ( C ) goes back to Chats (B) window only.
Cheers.
Your going to find this hard to implement largely because this is a bad UI design and the API does not support it.
You user will expect a back button to take them "back" to the previous view just as in every other app they use. Going to any other view will confuse them all the more so because it isn't a hierarchy but a loop. Users will sometimes go B-->C-->B but other times, A-->C-->B-->C. (How do they get back to A?)
Instead of a back button in C, you should have a button on the right hand side that always takes you to B regardless of how you got to C. The same button in the same context should always produce the same result. Users shouldn't have to remember what invisible mode they are in to predict what action a button will have.
Edit01:(Response to comments below)
(This is all off the top of my head so take it with a grain of salt.)
You will need to abandon using the navigation controller and instead manage the views yourself. You will need to swap the views out via the tabbar by substituting the C view for the A and B views in each tab's view property.
I think you will have to start with master view that is invisible and then add the tabbar to that. In the master view controller, create attributes/outlets for each view. In each view, have an attribute/outlet linked to the master view controller. Then have the "back button" (which I strongly suggest you label "Chats") of C view call method in A and B that then calls a method in the master view controller that (1)removes the C view from either tab A or tab B (2) switches the tab to the B tab and then (3) loads view B into tab B.
I can't emphasis how ungainly I think this design is. It doesn't matter if other apps use it. In my experience major companies are more likely to make interface mistakes because their marketing departments want the UI to look unique.
By comparison, look at how the phone app handles the same situation. No matter which tab you use to make a call, favorites, contacts, keypad etc, you still come back that tab's view when the call is done. If you want to make a call with another method, you just hit the appropriate tab.
Ignore the bad example of others. Why spend so much time and effort trying to reproduce someone else's mistake?
You'll always wind up going back to the view that called pushViewController
Could you have A send B a message that causes B to call pushViewController?
I may not understand your architecture, but I believe that would work.
If you really want to do this you could create a method in B that pushes C then push B from A (without animation) and call the method that pushes C. Of course when you pop C to B, A will still be under it.
I got the answer from google group and followed his suggestion, it works:
From Sukima:
I believe (although I have no real idea) that what these applications like skype and Beejive are doing is when view A wants view C it will message your UITabBar to move to the chats tab then push in the detail view C. This is actually better because then the user will see that the view has changed via the fact that the tabbar has changed highlights. from: I further read the stackoverflow thread. I understand now what your asking.
So the the problem is that when someone touches the back button on the UINavigationControler, I would like to run some code to update the datasource.
The problem is that i cant seem to find the right delegate to do it. only these are available on the nav controller delegate, and i want the 'didfinishshowing' type method.
– navigationController:willShowViewController:animated: optional method
– navigationController:didShowViewController:animated: optional method
The next best place i thought was the nav bar but when i try that.
Terminating app due to uncaught
exception
'NSInternalInconsistencyException',
reason: 'Cannot manually set the
delegate on a UINavigationBar managed
by a controller
This makes sense retrospectively, as you don't want some hacker messing around with the internals of the nav controller and stopping it from working.
This must is a common problem, and i have missed something simple.
Just so we're clear: view A is the starting point. User taps something and you slide right to view B. User taps the back button and you're going from B back to A and you want to do something as a result of the 'back' action.
There are three ways to do it (and on neither do you have to go near the navigationController -- these apply to the underlying viewControllers themselves):
As dmercredi suggests override viewWillAppear on view controller A so when you're heading back to it, it refreshes itself. Problem is that viewWillAppear is also called when A is called the very first time. So you'll have to set some sort of flag to distinguish between the first viewWillAppear and any subsequent ones when returning from B.
Override viewWillDisappear on view controller B and do your refreshing there. This will only get called when B is about to go away. If there's something on B that goes one level deeper or brings up a modal dialog on top, viewWillDisappear is going to get called so again you'll have to distinguish between the coming and the going.
Decouple the various views and use the delegate pattern. View controller A sets itself as a delegate of B and when B updates something it invokes the delegate method, so A is notified of the change and can update whatever it needs to. You can invoke the delegate method any time the user makes a change inside B or override viewWillDisappear and just do it one time on the way out.
Add your refresh code to the viewWillAppear:(BOOL)animated method on the view controller that is about to be displayed. In your case, that is the view controller that's already on the navigation stack.