I'm using the new (in iOS 7) UIPopoverController.backgroundColor setting in my app to change the color of my popovers as needed, but I'm finding that using this setting is causing a "flash" of color change whenever I open my popovers -- in about half a second it starts from the default of translucent white and fades to the color I choose. This is not desired; it should just be the color I set it to when I open it.
The documentation states:
Use this property to customize the background color of your popover. Changing the value of this property while the popover is visible triggers an animated changeover to the new color. The default value of this property is nil, which corresponds to the default background color.
However, even if I set it when my app opens and don't set it again, it will still flash each time I open any of the popovers.
I'm open to using UIPopoverBackgroundView, but I'm not sure that it allows me to change the background color on-the-fly since it seems to be a static solution for just changing the style of all popovers in an app. Thanks in advance for any suggestions.
Edit (The Code):
When my main view controller is loaded and preparing the rest of the UI (this is one of many popover inits):
fileOptionsController = [[FileOptionsViewController alloc] initWithNibName:#"FileOptionsViewController" bundle:nil];
fileOptionsController.delegate = self;
self.fileOptionsPopoverController = [[UIPopoverController alloc] initWithContentViewController:fileOptionsController];
[popoverControllers addObject:self.fileOptionsPopoverController];
After my popovers are initialized, I'm running this (still in the main init code) for the purpose of testing with a long delay between setting backgroundColor and the interaction (note: changing the alpha has no effect, still happens when set to 1):
for (UIPopoverController *po in popoverControllers) {
[po setBackgroundColor:[UIColor colorWithWhite:0.3f alpha:0.90f]];
}
Then, this is called when the user taps a button to show the popover:
- (void)showPopover:(UIPopoverController *)popover from:(UIButton *)btn {
[popover presentPopoverFromRect:CGRectMake(btn.frame.origin.x + 5.0f, btn.frame.origin.y - 1.0f, btn.frame.size.width, btn.frame.size.height) inView:btn.superview permittedArrowDirections:UIPopoverArrowDirectionLeft animated:YES];
}
Pretty straight forward. These are the only relevant places where this or any popover is accessed, except where to dismiss it if it's already showing.
I solved this issue dismissing the popover before changing the backgroundColor and then presenting it again:
[popover dismissPopoverAnimated:NO];
if ([popover respondsToSelector:#selector(backgroundColor)]) {
popover.backgroundColor = [UIColor someColor];
}
[popover setContentViewController:viewController animated:NO];
[popover setPopoverContentSize:CGSizeMake(320.0f, 480.0f) animated:NO];
[popover presentPopoverFromRect:popoverRect
inView:self.view
permittedArrowDirections:UIPopoverArrowDirectionRight
animated:NO];
Doing so the popover is shown again and you won't see any unwanted transition effect.
Since the documentation says that the backgroundColor will animate it your color so this seems be causing this flash/animation (Apple should have given setBackgroundColor with/without animation).
If you can afford to present the popover without animation, I would suggest you could try to turn off animation when you present it. Like one of these two -
Option 1 -
// last parameter as 'NO'
[popover presentPopoverFromRect:CGRectMake(btn.frame.origin.x + 5.0f, btn.frame.origin.y - 1.0f, btn.frame.size.width, btn.frame.size.height) inView:btn.superview permittedArrowDirections:UIPopoverArrowDirectionLeft animated:NO]; // Don't animate to present
Option 2 - (use setAnimationsEnabled method of UIView to turn off animation temporarily)
- (void)showPopover:(UIPopoverController *)popover from:(UIButton *)btn {
[UIView setAnimationsEnabled:NO]; // Turn off animation
[popover presentPopoverFromRect:CGRectMake(btn.frame.origin.x + 5.0f, btn.frame.origin.y - 1.0f, btn.frame.size.width, btn.frame.size.height) inView:btn.superview permittedArrowDirections:UIPopoverArrowDirectionLeft animated:YES];
[UIView setAnimationsEnabled:YES]; // Turn back on
}
Logically this should work unless Apple is further hacking this property inside it's sdk.
It would be better if you adding your code... Without code I can just say if your are setting color after view appear, then it will animate to that color. So change background color first and than make popover visible.
Related
I have my app setup to show this view when it is loading data:
self.loadingView = [UIView new];
self.loadingView.frame = CGRectMake(0, 0, self.tableView.frame.size.width, self.view.frame.size.height);
self.loadingView.backgroundColor = [UIColor groupTableViewBackgroundColor];
[self.view addSubview:self.loadingView];
[self.view bringSubviewToFront:self.loadingView];
self.activityIndicator = [UIActivityIndicatorView new];
self.activityIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyleGray;
self.activityIndicator.center = CGPointMake(self.view.frame.size.width / 2.0, self.view.frame.size.height / 2.0);
[self.view addSubview:self.activityIndicator];
[self.view bringSubviewToFront:self.activityIndicator];
[self.activityIndicator startAnimating];
Then, I remove it from its superview. It works on iPhone. It works on iPad sometimes too, except for when I'm using the same code in a UISplitViewController. I've tried various adjustments to centering the views, etc., but can't figure it out. What's going wrong?
I've add trouble with activity indicators in the past as well. Make sure you are not calling the startAnimating or stopAnimating while any animations are taking place. I recommend calling the startAnimating selector in viewDidLayoutSubviews.
The line where you set the center of the indicator looks like the source of the problem. self.view.frame.size is probably equal to screen size at this point and so when you show that view controller over the whole screen it's ok, but inside a split view controller it's not because indicator is off-bounds. You can check that from Xcode's Debug -> View Debugging -> Capture View Hierarchy (while the app is running).
Try setting activity indicator's center using autolayout and it should work.
I'm using TheSidebarController to implement add a sliding menu into an iOS application. This is the library I'm using, but I've found the same issue in other libraries, like ECSlidingViewController, etc. They essentially work by adding multiple view controllers onto a containing view controller, nothing too crazy.
The issue is, when you make the app a landscape app, all the screens in the container- the menu, the content screen- seem to think they're in portrait mode, and get cut off half way. You can see the issue in this screenshot where the table is cut off:
http://imgur.com/xD5MUei
I've been trying to get this to work in any way I can, and no luck.
The library I'm using + example project can be found here:
https://github.com/jondanao/TheSidebarController
Any help is greatly appreciated :)
EDIT: people are saying I can stretch the table out to make it look normal, but this just masks the underlying problem, which is the app and/or the screens still think they're in portrait orientation. As a quick example, if I take the example project, and in LeftViewController substitute the following code:
- (void)dismissThisViewController
{
UIViewController* vc = [[UIViewController alloc] init];
UINavigationController* pulldown = [[UINavigationController alloc] initWithRootViewController:vc];
pulldown.view.frame = CGRectMake(pulldown.view.frame.origin.x, -[[UIApplication sharedApplication] delegate].window.frame.size.height,
pulldown.view.frame.size.width, pulldown.view.frame.size.height);
[[[UIApplication sharedApplication] delegate].window addSubview:pulldown.view];
[UIView animateWithDuration:.5 animations:^{
pulldown.view.frame = CGRectMake(pulldown.view.frame.origin.x, 0,
pulldown.view.frame.size.width, pulldown.view.frame.size.height);
} completion:^(BOOL finished) {
;
}];
}
The viewcontroller comes in sideways, not from the top.
This was a strange one... I had to set the frame of the content view controller, which made sense, but then I had to reset it every time the content was refreshed:
- (void)setContentViewController:(UIViewController *)contentViewController
{
// Old View Controller
UIViewController *oldViewController = self.contentViewController;
[oldViewController willMoveToParentViewController:nil];
[oldViewController.view removeFromSuperview];
[oldViewController removeFromParentViewController];
// New View Controller
UIViewController *newViewController = contentViewController;
[self.contentContainerViewController addChildViewController:newViewController];
[self.contentContainerViewController.view addSubview:newViewController.view];
[newViewController didMoveToParentViewController:self.contentContainerViewController];
_contentViewController = newViewController;
if ([DeviceDetection isDeviceiPad]) {
_contentViewController.view.frame = CGRectMake(0, 0, 1024, 768);
}
}
Did you check if it's has something to do with the new interface orientation?
https://developer.apple.com/library/ios/releasenotes/General/WhatsNewIniOS/Articles/iOS8.html
chapter -> Supporting New Screen Sizes and Scales
In CenterViewController.h make the class a subclass of a UITableViewController instead.
Then comment out [self.view addSubview:self.tableView]; in CenterViewController.m.
Done!
In centerViewController.m, when you create the tableview, add this line:
self.tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
So in my app I have a popover control with an embedded navigation control. In different parts of the navigation stack, I want the popover to be different colors depending on where the user is. The weird thing is sometimes setting the popover background color makes this terrible looking box around it, sometimes it doesn't. It looks like this:
This is the look I am trying to get:
It seems if I change the background color before displaying the popover it seems to work and transition correctly, but if I don't set the popover color before showing it, then change it after it has been shown it has the box effect. I've also noticed other cases where it seems to happen randomly, but I can't really explain what is causing it (my real app is much more complex than this demo). Here is the relevant code:
- (IBAction)buttonPressed:(id)sender {
UIViewController *vc = [[UIViewController alloc] init];
UIButton *b = [[UIButton alloc] init];
[b addTarget:self action:#selector(innerButtonPressed) forControlEvents:UIControlEventTouchUpInside];
[b setTitle:#"Button" forState:UIControlStateNormal];
[b setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
[b setFrame:CGRectMake(0,0,100,100)];
[vc.view addSubview:b];
_innerNav = [[UINavigationController alloc] initWithRootViewController:vc];
_popOver = [[UIPopoverController alloc] initWithContentViewController:_innerNav];
//If this line is here, everything works fine
_popOver.backgroundColor = [UIColor yellowColor];
[_popOver presentPopoverFromBarButtonItem:sender permittedArrowDirections:UIPopoverArrowDirectionUp animated:YES];
//If this line is here (and the above line is commented out), the transition will look wrong
//_popOver.backgroundColor = [UIColor yellowColor];
}
-(void)innerButtonPressed {
_controller = [[UIViewController alloc] init];
UIButton *b = [[UIButton alloc] init];
[b addTarget:self action:#selector(test) forControlEvents:UIControlEventTouchUpInside];
[b setTitle:#"Make Purple" forState:UIControlStateNormal];
[b setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
[b setFrame:CGRectMake(0,0,200,200)];
[_controller.view addSubview:b];
[_popOver setBackgroundColor:[UIColor orangeColor]];
[_innerNav pushViewController:_controller animated:YES];
}
-(void)test{
_popOver.backgroundColor = [UIColor purpleColor];
}
Any idea what is causing this issue? And what steps to safely update the background color of a popover without ever getting into this state? I have a full project demonstrating the problem, I thought you could attach projects to questions but apparently you cannot. If someone wants it I can probably host it somewhere.
After looking at your sample project, Apple's "Popover Controllers in iOS" sample project, perusing Apple's Documentation, and trying a few different things I have come to the following observations:
The UIPopoverController only exhibits this odd behavior when it is presented without a valid value for the backgroundColor property. From this I am guessing that since UIPopoverController's backgroundColor property is nil by default it must use different drawing code than when the backgroundColor property is valid.
Triggering some sort of redraw (e.x. Setting popoverContentSize) will get the colored box overlay to go away (it looks like it clips a color layer).
Conclusion: For the time being I would set a backgroundColor prior to the UIPopoverController being presented and then update it as needed. If this is not an option try updating the UIPopoverController such that it redraws (As a note: I was not able to get this to look good and it seems hacky). Lastly, I would report it as a bug to apple.
I hope this helps.
UIPopoverController is now deprecated. I found a similar issue when updating it to use the new popoverPresentationController. In the past I was able to set the backgroundColor of UIPopoverController before presenting. The popover presentation controller also has a backgroundColor property but didn't work like it did before where I could assign it before presentation. To get it to work I had to assign it after it starts presenting for some reason:
contentViewController.modalPresentationStyle = UIModalPresentationPopover;
[[self presentViewController:contentViewController animated:YES completion:^{
// completion code
}];
contentViewController.popoverPresentationController.backgroundColor = [UIColor orangeColor];
For your particular scenario where you are changing the background color after presentation is finished I don't think you'd be able to do that by just changing the popoverPresentationController's backgroundColor. The only solution I can think of is to dismiss and re-present the popover without animating:
[self dismissViewControllerAnimated:NO completion:^{
contentViewController.modalPresentationStyle = UIModalPresentationPopover;
[[self presentViewController:contentViewController animated:NO completion:^{
// completion code
}];
contentViewController.popoverPresentationController.backgroundColor = [UIColor purpleColor];
}];
I currently have a particle view connected to storyboard. The problem that I'm facing is that I'm displaying an alert at the same time I show this view, and the alert is always shown in front of the particle view. Is there a way where I can always place the particle view in front? I'm using a third party alert library titled SIAlertView, so I'm assuming it may be possible.
I've logged the zPosition of the alertView and it's always 0, so I set the zPosition of my particle view's layer to 10, but it is still shown beneath the alert view.
Here's the storyboard hierarchy:
I do not know about SIAlertView but normal UIAlertView is shown via separate window. If you want to overlap it you can not do it by changing zpozition, you have to also use a separate window:
// do not forget to keep strong reference for it somewhere!
UIWindow *notificationWindow;
//your frame here!
notificationWindow = [[UIWindow alloc] initWithFrame: some_cgrect];
notificationWindow.backgroundColor = [UIColor clearColor]; // your color if needed
notificationWindow.userInteractionEnabled = NO; // if needed
// IMPORTANT PART!
notificationWindow.windowLevel = UIWindowLevelAlert + 1;
notificationWindow.rootViewController = [UIViewController new];
notificationWindow.hidden = NO; // This is also important!
UPDATE:
To overlap also a status bar use
notificationWindow.windowLevel = UIWindowLevelStatusBar;
to dismiss UIWindow just invalidate strong pointer to it. Something like:
self.strongPointerToYourWindow = nil;
I'm trying to display a UIDocumentInteractionController on my app. Everything is working perfectly on iPhone, but nothing is happening on iPad. Here is my code:
interactionController = [UIDocumentInteractionController interactionControllerWithURL:imageFile];
interactionController.UTI = #"com.instagram.photo";
interactionController.annotation = [NSDictionary dictionaryWithObject:[self commentForInstagram] forKey:#"InstagramCaption"];
[interactionController presentOpenInMenuFromRect:self.view.frame inView:self.view animated:YES];
interactionController is a strong reference to an instance, and imageFile exists. On iPhone, it brings up the 'Open With..' dialog and Instagram is present. On iPad, absolutely nothing happens when the above code runs. Yes, I do have Instagram installed and working on my iPad.
What could be the reason that nothing is happening when the code is executed? self.view and self.view.frame are valid objects (tested on debug).
Thanks, Can.
On iPad UIDocumentInteractionController appearing like Pop Up
Try something like this:
-(void)shareClick:(UIButton*)sender {
/*some code*/
CGRect rectForAppearing = [sender.superview convertRect:sender.frame toView:self.view];
[interactionController presentOptionsMenuFromRect:rect inView:self.view animated:YES];
}
For iPad you have to meet these 2 things:
Define area for DocumentActionMenu
CGRect rect = CGRectMake(0.0, 0.0, 0.0, 0.0);
[interactionController presentOpenInMenuFromRect:rect inView:self.view animated:YES];
Use iPad, not simulator
Use presentOptionsMenuFromRect:inView:animated:.
For example, if you want the menu to be presented from the bottom, try
[interactionController presentOptionsMenuFromRect:self.view.bounds inView:self.view animated:YES];
I had the same problem earlier today.
First of all, do not pass the frame of your view to presentOptionsMenuFromRect:inView:animated. The given rect is supposed to be in the coordinates of the view. The frame of the view is in the coordinates of the view's superview.
On iPhone, passing the bounds of the view worked, but on iPad, Xcode (7.2.1) would complain about unsatisfiable constraints and not display the document interaction controller's view (DIC).
Instead of the bounds, I tried to pass CGRectZero as the first parameter which anchors the DIC in the upper left corner of the view. This works but it looks bad.
In order to position the DIC at the center of the bottom edge of the view, you can specify a rect of size CGSizeZero positioned at the center of the bottom edge of the view (use the view's bounds to compute the position). This works and looks ok.
presentOptionsMenuFromRect:inView: - This is talking about where the popover arrow points.
The view is the view that the arrow points at, and the rect is the rect inside the view that the arrow points at.
Once you understand that this is easy.
- (void)shareClick:(UIButton*)sender
{
/*some code*/
[interactionController presentOptionsMenuFromRect:sender.bounds inView:sender animated:YES];
}
So now the arrow will point to an edge of the bounds of the button.
Most of the time this is what you want but you could for example inset this rect to have the arrow point inside the button.
I had the same issue. I checked the frame that is being passed and saw that x and y are set to 0. Next I tried to change those values (by keeping width and height as passed) and the popup showed up. Here the code:
-(void)openDocument:(UIView*)senderView {
CGRect rectForAppearing = [senderView convertRect:senderView.frame toView:senderView];
if (isIPAD)
rectForAppearing = CGRectMake(100, 100, rectForAppearing.size.width, rectForAppearing.size.height);
[interactionController presentOptionsMenuFromRect:rect inView:self.view animated:YES];
}
The popup appears on an Ipad in the top right corner. You can of course twist those 100,100 parameters to your desire. On iPhones i leave the popover as is (at the bottom)