How to customize the pop up menu in iPhone? - iphone

The application I'm creating needs a function like user selects some text, a pop up menu shows, and user clicks "search" menu to perform a search directly.
Problem is the current pop up menu provided by UIMenuController doesn't support to be extended. So my thought is to subscribe "UIMenuControllerDidShowMenuNotification", get the frame of pop up menu, and display the "search" button right aside.
But during the implementation, I met a strange problem, the notification seems never be sent, means after the menu shown, I still cannot be notified, following are the key section of code.
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(menuDidShow:)
name:UIMenuControllerWillShowMenuNotification
object:nil];
}
- (void)viewDidUnload {
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIMenuControllerDidShowMenuNotification
object:nil];
self.textView = nil;
self.searchBar = nil;
}
- (void)menuDidShow:(NSNotification *)notification {
NSLog(#"menu did show!");
}
The code is too simple to make mistake, can someone help me to understand what's going on? Or what did I miss?

Related

ModalViewController not in the window hierarchy

After a long period of trying and searching the Internet and StackOverflow I could not find the answer I need.
My Problem is a warning:
Warning: Attempt to present <PAPasscodeViewController: 0x81cc8a0> on <ServerViewController: 0x75864b0> whose view is not in the window hierarchy!
I am using a Custom Control called "PAPasscodeViewController", the thing itself is working but when I try to display the view on the ApplicationDidBecomeActive method it displays this warning in the Consoleoutput.
I am using this method to keep track of the Notification:
[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(wait)
name:UIApplicationDidBecomeActiveNotification object:nil];
my -(void)wait is containing this:
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil];
NSLog(#"waiting");
[self performSelector:#selector(enterPasscode) withObject:nil afterDelay:.3f];
The App is having a RootViewController and a GeneralViewController, if the User already downloaded a Config package the GeneralViewController gets pushed in after starting as a modalView like this (in the viewDidLoad):
GeneralView = [[GeneralViewController alloc] initWithNibName:#"GeneralViewController" bundle:[NSBundle mainBundle]];
GeneralView.modalTransitionStyle=UIModalTransitionStyleCrossDissolve;
[self presentViewController:GeneralView animated:YES completion:nil];
In the GeneralView the User has more Options to open new Views through Buttons and they get pushed in through IBActions like the GeneralViewController.
So the Problem is occuring when this happens:
I am starting the App, Config Package was already installed and the User wants to track his ServerData on "ServerViewController", so he touches the appropriate Button for this.
Then the View gets pushed in (its now RootViewController (managed by AppDelegate)-> GeneralViewController(pushed in)-> ServerViewController(pushed in)), everything works fine.
Now when the User goes back to his Homescreen I am closing the modal View "ServerViewController" as follows:
-(void)viewDidAppear:(BOOL)animated{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(close:)
name:UIApplicationDidEnterBackgroundNotification object:nil];}
-(IBAction)close:(id)sender{
[self dismissViewControllerAnimated:YES completion:nil];
}
so when the App enters Backgrounding the ServerViewController gets dismissed, after resuming the App the GeneralViewController is now the active View Controller, and in his -(void)viewDidAppear I am calling the -(void)wait from above, it calls this function:
- (void)enterPasscode{
PAPasscodeViewController *passcodeViewController = [[PAPasscodeViewController alloc] initForAction:PasscodeActionEnter];
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
passcodeViewController.backgroundView = [[UITableView alloc] initWithFrame:[UIScreen mainScreen].bounds style:UITableViewStyleGrouped];
}
passcodeViewController.delegate = self;
passcodeViewController.passcode = [configArray objectAtIndex:3];
passcodeViewController.simple = YES;
[self presentViewController:passcodeViewController animated:YES completion:nil];
}
This is working fine but then I am getting the annoying warning from above (here again so you dont have to scroll up)
Warning: Attempt to present <PAPasscodeViewController: 0x75d1c10> on <ServerViewController: 0x818b390> whose view is not in the window hierarchy!
actually this is confusing because I think my GeneralViewController is now the Active ViewController again...
Maybe I am just blinded by the simplicity of this Problem... but maybe someone here can help me understand.
I did NOT find any solution in any other post here in StackOverflow or other sites concerning that exact problem!
You have to remove self as observer when your ServerViewController is not shown. Add the following code to ServerViewController:
- (void)viewDidDisappear:(BOOL)animated
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}

playing youtube video inside uiwebview. How to handle the "done" button?

I have a uiwebview that plays a youtube video. How can I handle the done button action?
Right now, when I tap the done button it changes back to my app main menu (not the menu that was supposed to dismiss to) and it just freezes. Can anyone help me please?
Ps: the menu where the uiwebview is located, was previously presented modally.
The YouTube plug-in player is itself a modal view controller. It is returning to its presentingViewController when the done button is pressed. Its presentingViewController is not your modal view controller but is instead the viewController that called [presentModalViewController:animated:] to present your modal view controller. Since the original modal view controller is still active, the app behaves badly.
To fix the problem,
1) Track whether the modal view controller has been presented but not dismissed.
2) In the viewDidAppear method of the presenting view controller, if the modal view controller was presented and not dismissed, dismiss and present it again.
For example, in controller that is presenting the modal web view controller:
- (void) presentModalWebViewController:(BOOL) animated {
// Create webViewController here.
[self presentModalViewController:webViewController animated:animated];
self.modalWebViewPresented = YES;
}
- (void) dismissModalWebViewController:(BOOL) animated {
self.modalWebViewPresented = NO;
[self dismissModalViewControllerAnimated:animated];
}
- (void) viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if (self.modalWebViewPresented) {
// Note: iOS thinks the previous modal view controller is displayed.
// It must be dismissed first before a new one can be displayed.
// No animation is needed as the YouTube plugin already provides some.
[self dismissModalWebViewController:NO];
[self presentModalWebViewController:NO];
}
}
This thread is very useful and help me to find the problem!
The answer of lambmj works fine, but I found a better way.
In presenting view controller:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if (self.presentedViewController) {
UIViewController *vc = self.presentedViewController;
[vc dismissModalViewControllerAnimated:NO];
[self presentModalViewController:vc
animated:NO];
}
}
Hope this helps!
#Gdx Wu
#lambmj
Thanks for your methods, they work fine. But there is some small problem that after clicking the done button & jumping directly to the presenting view controller, we need to dismiss the presented modal view controller and present it again, which would bring some dither(like flash) between these view controller switches.
Based on this, I highly recommend #IsaacCisneros 's method which would switch seamlessly.
Simply remove UIWebView when it enters full screen; add back UIWebView when it exit full screen. Sample code below assuming a UIViewController with subview of UIWebView, and your UIWebView should have youtube iframe.
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// Add observer for "Done" button click
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playerWillExitFullscreen:)
name:#"UIMoviePlayerControllerWillExitFullscreenNotification"
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playerDidEnterFullscreen:)
name:#"UIMoviePlayerControllerDidEnterFullscreenNotification"
object:nil];
}
- (void)viewDidDisappear:(BOOL)animated {
// Remove observers for "Done" button click
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"UIMoviePlayerControllerWillExitFullscreenNotification" object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"UIMoviePlayerControllerDidEnterFullscreenNotification" object:nil];
}
- (void)playerWillExitFullscreen:(NSNotification *)notification {
// Before exit full screen, add back UIWebView that have been removed earlier
[self.view addSubview:self.webView];
}
- (void)playerDidEnterFullscreen:(NSNotification *)notification {
if (self.presentingViewController) { // UIWebView is presenting the build-in movie player controller
[self.webView removeFromSuperview]; // Built-in movie player controller is already entering full screen mode
}
}

Having trouble hiding keyboard using invisible button which sits on top of uiscrollview

I have 3 items in play...
1) UIView sits at the base of the hierarchy and contains the UIScrollview.
2) UIScrollview that is presenting a lengthy user form.
3) An invisible button on the UIScrollview that I'm using to provide "hide the keyboard" features.
Notice in the code below that I'm registering to be notified when the keyboard is going to appear and again when it's going to disappear. These are working great.
My problem is seemingly one of "layers". See below where I insert the button into the view atIndex:0. This causes the button to be activated and "stuffed" behind the scrollview so that when you click on it, the scrollview grabs the touch and the button is unaware. There is no way to "reach" the button and suppress the keyboard.
However, if I insert atIndex:1, the button gets super imposed on top of the text entry fields and so any touch at all is acted upon by the button, which immediately suppresses the keyboard and then disappears.
How do I insert the button on top of the UIScrollview but behind the UITextfields that sit there?
other logistics: I have a -(void) hidekeyboard function that I have setup with the UIButtion as an IBAction(). And I have the UIButton connected to "files owner" via a ctrl-drag/drop. (Do I need both of those conventions?)
This code in ViewDidLoad()...
[[NSNotificationCenter defaultCenter] addObserverForName:UIKeyboardWillShowNotification object:nil queue:nil usingBlock:^(NSNotification *notification){
[self.view insertSubview:self.keyboardDismissalButton atIndex:0];
}];
Here's a nifty way to do this without even needing the invisible button. This approach will only work on devices north of 3.2, but since we're already using the block methods on NSNotificationCenter, we know that we're at least at 4.0.
In lieu of a button, we instead add a tap gesture recognizer to our view. This tap gesture recognizer calls -dismissKeyboard: and then we ask our view to end all editing. This method is only available on 3.2 and later and works through it's subviews until it locates the current firstResponder and sends it -resignFirstResponder. The boolean argument that -endEditing: takes determines whether the resignation of firstResponder is forced or not. The documentation is a little vague, but I take it to mean that if forced, the -textFieldShouldEndEditing delegate method on UITextField will not be called.
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserverForName:UIKeyboardWillShowNotification object:nil queue:nil usingBlock:^(NSNotification *notification) {
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(dismissKeyboard:)];
tap.numberOfTapsRequired = 1;
tap.numberOfTouchesRequired = 1;
[self.view addGestureRecognizer:tap];
}];
[[NSNotificationCenter defaultCenter] addObserverForName:UIKeyboardWillHideNotification object:nil queue:nil usingBlock:^(NSNotification *notification) {
[self.view removeGestureRecognizer:[self.view.gestureRecognizers lastObject]];
}];
}
- (void)dismissKeyboard:(UIGestureRecognizer *)gesture
{
[self.view endEditing:NO];
}
There is no way to have a view be in front of a sibling but behind the sibling's children. You can insert the button as a child of the scroll view (behind all the text fields), or you can override pointInside:withEvent: on the upper view to return false where it is over a text field and true otherwise (effectively "punching holes" in it).
Figured it out....
Here is the answer.
The code below is all within the ViewDidLoad() of the UIViewController. Begin by removing the button altogether from the UIView. (it will be rendered when the keyboard is activated).
Notice in the keyboard "WillShow" notification below that I am inserting the button onto my UIScrollView layer rather than the UIView layer, as before. However, once the button is clicked and the keyboard is about to be dismissed, I remove the button altogether from the UIView (i.e. self).
[self.keyboardDismissalButton removeFromSuperview];
[[NSNotificationCenter defaultCenter] addObserverForName:UIKeyboardWillShowNotification object:nil queue:nil usingBlock:^(NSNotification *notification){
[theScroller insertSubview:self.keyboardDismissalButton atIndex:0];
}];
[[NSNotificationCenter defaultCenter] addObserverForName:UIKeyboardWillHideNotification object:nil queue:nil usingBlock:^(NSNotification *notification){
[self.keyboardDismissalButton removeFromSuperview];
}];

iPhone SDK: handling keybaord appearance

I need to move UI elements of my view controller when keyboard appears. I do this by registering for the keyboard notifications in my app delegate:
[[NSNotificationCenter defaultCenter] addObserver:observer
selector:#selector(keyboardWasShown:)
name:UIKeyboardDidShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:observer
selector:#selector(keyboardWasHidden:)
name:UIKeyboardDidHideNotification object:nil];
and then handling notification as prescribed by Apple (I have similar code for keyboard was shown) to scroll the view up and down:
- (void)keyboardWasHidden:(NSNotification*)aNotification
{
CGRect viewFrame = [self.view frame];
viewFrame.origin.y += keyboardSize.height - TOOLBAR_HEIGHT;
self.view.frame = viewFrame;
}
So far so good. Now problem description:
When I execute this code to show OS 3.0 specific message UI:
MFMailComposeViewController *picker = [[MFMailComposeViewController alloc] init];
picker.mailComposeDelegate = self;
[self presentModalViewController:picker animated:YES];
and when keyboard shows in the actual mail UI, I still get keyboard notification which scrolls my view and therefore breaking my UI (note that mail controller takes entire screen and my view is not even visible at this point).
I was hoping to temporary disable keyboard notification, so my scrolling code would not get called with this line:
[[NSNotificationCenter defaultCenter] removeObserver:self];
But it does not help, keyboard even still get posted.
What should I do avoid reacting on the keyboard when it created by the message UI?
Add a BOOL property or instance variable: careAboutKeyboard that's accessible to both your keyboardWasShown: and keywardWasHidden: methods, likely in the view controller those methods are in.
Have it set to YES when in the viewWillAppear method, and set to NO when you show the mail view and in viewWillDisappear.
Then put all of your scrolling logic in an if block:
if(careAboutKeyboard) {
// Scrolling logic
}

Activity indicator in a UITabBar application on the iPhone

I have a UITabBar + UINavigationController application which often needs data from the internet. Sometimes it takes quite a while before it gets it, so I would like to show an activity indicator.
What I was trying is to add a activityView to my window in my applicationDidFinishLaunching method:
[window addSubview:tabBarController.view];
fullscreenLoadingView.hidden = YES;
[window addSubview:fullscreenLoadingView];
And then I add the application delegate as a observer to the default notification center:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(startFullscreenLoading:) name:#"startFullscreenLoading" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(stopFullscreenLoading:) name:#"stopFullscreenLoading" object:nil];
and implement the methods:
- (void)startFullscreenLoading:(NSNotification *)notification {
fullscreenLoadingView.hidden = NO;
}
- (void)stopFullscreenLoading:(NSNotification *)notification {
fullscreenLoadingView.hidden = YES;
}
When I then use this directly in the applicationDidFinishLaunching method the loading indicator view shows upp as expected:
[[NSNotificationCenter defaultCenter] postNotificationName:#"startFullscreenLoading" object:self];
But when I use it from one of the navigation controllers the startFullscreenLoading: method is called but I don't see the loading indicator view. Why is that?
Are you loading your data on a background thread? Your main thread will be blocked & won't be able to redraw or process any other events.
The most likely thing I can guess is that your indicator view is below some other view. Try
[[fullScreenLoadingView superView] bringSubviewToFront:fullScreenLoadingView]
If that doesn't work, I would suggest breaking inside of -startFullscreenLoading to make sure the fullscreenLoadingView is still valid.
In my app I did this by adding the UIActivityView in IB and making sure it was above everything else (as a top-level object). It's set to be invisible when stopped.
Then in my code I make it appear with [activityIndicator startAnimating] and make it disappear with [activityIndicator stopAnimating];