I have a popover that displays from a UITableViewCell in a UITableView, which is the only view in a modal dialog. This works fine in both landscape and portrait:
UIViewController *content = [[SetTimeClockTimeViewController alloc] init];
popover = [[UIPopoverController alloc] initWithContentViewController:content];
[popover setDelegate:self];
UITableViewCell *cell = [dataTable cellForRowAtIndexPath:[dataTable indexPathForSelectedRow]];
[popover presentPopoverFromRect:cell.frame inView:cell.superview permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
However, when the screen is rotated while the popover remains up, the popover shifts away from the cell (if I rotate back it shifts to the proper location). This happens if either orientation is the starting orientation. I tried implementing popoverController:willRepositionPopoverToRect:inView:, but nothing I've put in it appears to have fixed the problem. For example:
- (void)popoverController:(UIPopoverController *)popoverController willRepositionPopoverToRect:(inout CGRect *)rect inView:(inout UIView *__autoreleasing *)view {
// TODO: Popover moves wrong when rotating
UITableViewCell *cell = [dataTable cellForRowAtIndexPath:[dataTable indexPathForSelectedRow]];
*rect = cell.frame;
*view = cell.superview;
}
I've tried telling the table to reloadData when rotating, using convertRect:toView to have self.view as the view, and calling presentPopoverFromRect:inView:permittedArrowDirections:animated in didRotateFromInterfaceOrientation:, but none of those seemed to fix the improper placement.
How can I ensure the popover is displayed from the new cell location after rotation?
Example of the popover displaying in portrait (arrow points to "Time" row):
After orientation to landscape (arrow should point to "Time" row):
Popovers displayed from a view do not maintain a relationship with the view, and might more often than not shift to incorrect places after rotation. This is unlike displaying from a bar button item, where the popover will move after layout. You should move the popover to the correct location in viewDidLayoutSubviews of the view controller.
Make sure to convert the returned rectangle to the view's correct coordinate system by using convertRect:toView: or convertRect:fromView:.
You should translate the rect to the superview using convertRect:toView. The most important part is to call it in willAnimateToInterfaceOrientation method.
The best solution I have seen for this is to use the cell(or in my case a UIButton) as the sourceView and the cell's bounds as the sourceRect.
i.e.
[popover
presentPopoverFromRect:cell.bounds //vs cell.frame
inView:cell //vs cell.superView
permittedArrowDirections:UIPopoverArrowDirectionAny
animated:YES];
Related
I have a UITableViewController with many cells, each cell contains four text fields vertical on each other, a pop over is presented by tapping on any text field, however, this pop over contains a text field when tapped the keyboard is fired and most likely the pop over will be shifted up to prevent the keyboard from hiding its text field (this is the default behavior of the pop over), but in the background (the dimmed view), the tableViewController loses its correct scrolling behavior to keep the pop over presenting-textField on the track while the keyboard is visible ..
a sample project can be downloaded here.
how can I offset the table view to keep the pop over presenting-textField on screen while keyboard is visible in this case ?
I tried the well-known TPKeyboardAvoiding library but it didn't solve the issue.
p.s. tableViewController works well for the first 3 or 4 keyboard firings, but loses precise scrolling on later attempts.
Screenshot (the green text field is the text field which presented the pop over, but tableViewController scrolls to the incorrect text field indicated in red):
any help would be highly appreciated.
EDIT:
this question is not a duplicate for: Making a UITableView scroll when text field is selected
because the text field that I need the table view to scroll to is the one that fires a pop over not a keyboard, and scrollToRowAtIndexPath does not work precisely in this case because each cell contains 4 text fields.
use (CGRect)convertRect:(CGRect)rect toView:(UIView *)view to get cell position on tableviews superview and accordingly handle the tableview offset
Here is my solution :
Change into
TableViewController.m
1. Register for keyboard Notifications (UIKeyboardWillShowNotification, UIKeyboardWillHideNotification)
2. Create local variables:
CGSize _currentPopoverContentSize; //if you want to have custom size for popover
UIView *_currentPopoverSender; //to remember from wich view you will present popover
BOOL _keyboardIsShown; //enable in keyboardWillShow, and disable in keyboardWillHide
3. In my presentPopover method:
- (void)presentPopoverControllerWithSize:(CGSize)size fromView:(UIView *)sender{
MyController *controller = [[[MyController alloc] init] autorelease];
if (self.popover)
{
[_popover release];
_popover = nil;
}
_popover = [[UIPopoverController alloc] initWithContentViewController:controller];
_popover.popoverContentSize = size;
_popover.delegate = self;
//checking if keyboard is shown - if NO, than present popover, if YES - just `resignFirstResponder` for your _`activeTextField`(you can set it in -textFieldDidBeginEditing: and nullify in -textFieldDidEndEditing:)
if (!_keyboardIsShown)
{
[_popover presentPopoverFromRect:[sender bounds]
inView:sender
permittedArrowDirections:UIPopoverArrowDirectionUp
animated:YES];
_popOver.popoverContentSize = CGSizeMake(320, 700);
}
else
{
[_activeTextField resignFirstResponder];
}
_currentPopoverContentSize = size;
_currentPopoverSender = sender;
}
4. Than:
- (void)keyboardWillBeHidden:(NSNotification*)aNotification{
[UIView animateWithDuration:0.3
animations:^{
//do some stuff
_popOver.popoverContentSize = CGSizeMake(320, 700);
} completion:^(BOOL finished) {
if (_popover && _currentPopoverSender)
{
[_popover presentPopoverFromRect:[_currentPopoverSender bounds]
inView:_currentPopoverSender
permittedArrowDirections:UIPopoverArrowDirectionUp
animated:YES];
}
}];
_keyboardIsShown = NO;
}
5.:
-(void)textFieldDidBeginEditing:(UITextField *)textField{
[textField resignFirstResponder];
[self presentPopoverControllerWithSize:textField.frame.size fromView:_activeText];
// self.vc = [[self storyboard] instantiateViewControllerWithIdentifier:#"ProductSourcePopOver"];
//
// if(_popOver == nil){ //make sure popover isn't displayed more than once in the view
// _popOver = [[UIPopoverController alloc]initWithContentViewController:self.vc];
// }
// _popOver.popoverContentSize = CGSizeMake(320, 700);
//
// [_popOver presentPopoverFromRect:_activeText.frame inView:_activeText permittedArrowDirections:UIPopoverArrowDirectionLeft animated:YES];
// _popOver.delegate = self;
}
This might helps you :)
Change the entire view frame size.
For example, keyboard height is 100 then,
CGRect frame = self.view.frame;
self.view.frame = CGRectMake(frame.origin.x, frame.origin.y, frame.size.width, frame.size.height - keyboardHeight);
Then call didSelectRowAtIndexPath method again for the same indexPath, so it will reload the popover.
This is just a base idea, there might be some modifications required to achieve exact behavior.
Hope this will help you.
How about add the tag(00 01 02 03, 10 11 12 13...etc) for each UITextfield in the cell, From tens digit you can know which cell, from single digits you know which textfield.
Register for UIKeyboardWillShowNotification and when that selector gets triggered, call your pop over code:
[self presentPopoverControllerWithSize:textField.frame.size fromView:_activeText];
The reason for this is that the iOS handles itself presenting the popover and handling it's frame. So it's generally a good practice to present the pop over after the keyboard is shown.
Hope this helps..
I 'm stuck with a problem...
I have a View, that uses UICollectionView and I am using number of images to display. On each of cell Image's right top I am displaying a small Icon, Which will have UITapGestureRecognizer on it(Small icon). Whenever user taps on small I have to show a UIPopoverController with nib file which is another class file with .h, .m and xib file. The problem is I cannot pin-point UIPopoverController on any(which small image I click on.), Instead what happens is that when ever I clicking on any of small icon image UITapGestureRecognizer always points to the first cell's small Image...
May anyone tell me what should I do to point UIPopoverController over on small image that I click on, please???
Try this :
- (void)didRecognizeTapGesture:(UITapGestureRecognizer*)gesture
{
if (gesture.state == UIGestureRecognizerStateEnded)
{
CGPoint point = [gesture locationInView:self.collectionView];
// Do some workaround with the POINT to achieve your task
}
Since you mentioned that a UITapGestureRecognizer was attached to each icon, you could use that gesture recognizer's view property to display the popover:
- (void)didTapCellIcon:(UITapGestureRecognizer *)gesture
{
UIView *iconView = gesture.view;
// Create myContentViewController and setup
UIPopoverController *popoverController = [[UIPopoverController alloc] initWithContentViewController:myContentViewController];
[popoverController presentPopoverFromRect:iconView.bounds
inView:iconView
permittedArrowDirections:UIPopoverArrowDirectionAny
animated:YES];
}
Because i have more than one element to display at a given location in a mapview, i'd like to display a WEPopoverController in the MKMapView when a given MKPinAnnotationView has been selected.
Presenting the content, which is a tableview works fine so far. I've subclassed the MKPinAnnotationView and when the annotation view gets clicked, i call my custom presentation method.
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view {
[view openCustomPopoverInFrame:self.view.frame];
[mapView deselectAnnotation:view.annotation animated:NO];
if (_lastAnnotationView != view) {
[_lastAnnotationView closeCustomPopover];
_lastAnnotationView = view;
}
}
- (void) openCustomPopoverInFrame:(CGRect) frame {
CGRect fromFrame = self.frame; //Pin Annotation View Frame
WEPopoverContentViewController *contentViewController = [[WEPopoverContentViewController alloc] initWithStyle:UITableViewStylePlain];
contentViewController.delegate = self;
WEPopoverController *viewController = [[WEPopoverController alloc] initWithContentViewController:contentViewController];
//[viewController setPassthroughViews:[NSArray arrayWithObjects:contentViewController.view, contentViewController.tableView, nil]];
viewController.delegate = self;
//set the displayed content
....
_myPopoverController = [viewController retain];
[viewController presentPopoverFromRect:fromFrame inView:self.superview permittedArrowDirections:UIPopoverArrowDirectionUp | UIPopoverArrowDirectionDown animated:YES];
[contentViewController release];
[viewController release];
}
Also selecting buttons in the tableview cells work find and i get the expected response (the method gets called and the popovercontroller stays on top of the view hierarchy).
My problem is, when i click on the WEPopoverController Content (one of the table view cell, in the center, where no button is located), the tap event gets passed through to the map view and therefore hides the popover controller. What can i do, to prevent passed through tap events?
I tried several solutions e.g. set the passThroughViews and also manipulating the hitTest:withEvent: method in the WETouchableView and always return nil, but that also doesn't help me any further.
Best
Nic
EDIT
I've now debugged the hitTest:withEvent: method in the WETouchableView a little more in dept and it seems, that it always returns an instance of UITableViewCellContentView, which i think should be correct so far.
EDIT
So to be a little bit more clear, i want to display a popover controller and when it gets tapped it should neighter disappear like it is the usual annotation behavior nor the underlying view should get called. It should stay on top of the view and the tap event should get caught by the table view and call the specific method (tableView:didSelectRowAtIndexpath:).
I've looked at this solution aswell, but it seems not to work for me and the underlying view (wich is the map view) gets called.
Ok, what worked for me, was to add a UIButton with a transparent background color as the first subview of the table view cell. All Tap Events on the cell now get caught by the UIButton. Btw all swipe events get caught by the table view, so at least that works fine.
To be honest, this isn't a really nice solution, but it works for me. I'd really appreciate any further suggestions!
Best
Nic
I have a UIPopOverController for which the content controller is a UINavigationController.
I'm resizing the popover size according to the content size of the controller pushed/popped into it. Initally i'm presenting the popover by using the method presentPopoverFromRect:inView:permittedArrowDirections:animated:. The anchor position is pointing at the center of the rect which i passed as an argument. If i push a controller(whose content size is small) into the navigationController , the popover shrinks from the bottom and moves above the rect which i mentioned earlier.
I tried to present the popover everytime(for push/pop) , anchor position remains # same point But the animation gets affected , doesnt looks good.
what needs to be done to make the anchor position remains same irrespective of the change in popover size variation ?
I've encountered the same issue and it seems that calling the presentPopoverFromRect method again will keep the anchor at the same position
e.g.
[self.myPopOver presentPopoverFromRect:rectOrigin inView:self.view permittedArrowDirections:UIPopoverArrowDirectionAny animated:NO];
Hope it works in your case as well
Change frame after presenting UIPopoverController:
[popupController presentPopoverFromRect:btn.frame inView:self.view permittedArrowDirections:0 animated:YES];
CGRect popupFrame = popupController.contentViewController.view.superview.superview.superview.frame;
popupFrame.origin.y = btn.frame.origin.y + btn.frame.size.height+75;
popupController.contentViewController.view.superview.superview.superview.frame = popupFrame;
I would like to create a new keyboard for the iPad.
I have designed a view that has the keys and all the underlying work to fill in a textbox as the user presses the keys, and passes the value back to the calling routine when the user presses the return button.
This all works ok.
Now - I want to create this view as a popover.
I have the basic operations complete (pops up and is dismissed)
But now I need some fine tuning help.
Here are my questions...
1) How do I create a popover window centered on the screen without a UIPopoverArrowDirectionAny selection?
2) Ensure the popover window is the size that I created it with in the XIB file (currently, it resizes and removes some of the rightmost size of the window)
Thanks
tony
To present it with no arrows pass 0 for "permittedArrowDirections":
[popOverController presentPopoverFromRect:rect inView:view permittedArrowDirections:0 animated:YES];
To center it, pass a 1x1 rect located at the center of your view:
CGRect rect = CGRectMake(viewWidth/2, viewHeight/2, 1, 1);
[popOverController presentPopoverFromRect:rect inView:view permittedArrowDirections:0 animated:YES];
Then you have a centered and arrow-free popover.
The bit about removing the arrow could break at any time. They didn't provide a UIPopoverArrowDirectionNone option for a reason, they may choose to throw an exception in the future when 0 is passed, or default to something. Use it at your own risk.
If you are using a UIPopoverController, it will have an arrow. Here are your choices.
UIPopoverArrowDirectionAny
UIPopoverArrowDirectionUp
UIPopoverArrowDirectionDown
UIPopoverArrowDirectionLeft
UIPopoverArrowDirectionRight
As you can see there is no UIPopoverArrowDirectionNone
As for the XIB size, in your ViewDidLoad Method of the implementation file, you can set the following.
self.contentSizeForViewInPopover = CGSizeMake(320.0, 360.0);
You can set the size any way you like for the popover.
I've just been working this one out but wanted to do as much in the storyboard as possible which changes the approach a bit:
1) Select the popover in the storyboard and un-tick Directions Up/Down/Left/Right to get rid of the arrows.
To center it on screen override prepare for segue and do something like this:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
UIStoryboardPopoverSegue* popover = (UIStoryboardPopoverSegue*)segue;
CGFloat horizontalInset = (self.view.frame.size.width - popover.popoverController.popoverContentSize.width) / (CGFloat)2.0;
CGFloat verticalInset = (self.view.frame.size.height - popover.popoverController.popoverContentSize.height) / (CGFloat)2.0;
popover.popoverController.popoverLayoutMargins =
UIEdgeInsetsMake(
verticalInset,
horizontalInset,
verticalInset,
horizontalInset);
}
In my case I also found that the popover segue's corner radius didn't match my design so I change the background color to clear to sort that out i.e. add this to the above block:
popover.popoverController.backgroundColor = [UIColor clearColor];
2) In the destination controller for the segue find the attribute 'Popover' and tick 'Use Explicit Size' and put the size of you want it to appear in popover mode.