I've put a MPVolumeView over a movie I'm playing. The trouble is, whenever I adjust the vol using the MPVolumeView, then the standard grey volume overlay appears (the one that appears when you use the rocker switch on the iPhone). Is there a way to disable the standard grey overlay?
I might be a bit late, but I think I have something useful to add. If you're just looking for an answer, skip to How did I fix it?
What is MPVolumeView?
The MPVolumeView class is used to put a slider onscreen that controls the system volume. It's supposed to prevent the system volume overlay from appearing while it is onscreen, but sometimes does not. I ran into this issue when I added my own MPVolumeView to the view hierarchy, but still saw the overlay appear when I dragged the slider.
How does MPVolumeView tell when it is visible?
This is the question I asked myself. Somehow, the class detects whether or not it is visible, and tells the system to hide or display the system volume overlay accordingly. This is because Apple does not really want you using the class just to prevent the overlay from appearing, without actually displaying the slider UI to the user (as in myell0w's answer).
Here's how I believe MPVolumeView tries to check if it is really visible:
When the view is added to a superview, as detected by viewWillMoveToSuperview: and viewDidMoveToSuperview, it starts a short timer.
When the timer fires, the view traverses its view ancestor tree by recursively examining the superview property.
MPVolumewView checks that each of its ancestors has hidden = NO and alpha greater than some small value (likely 0.05).
There could be other checks that I'm not aware of, since this code is of course closed-source. The timer in step 1 is there so that code like the following will not "fool" the view:
MPVolumeView *volView = [[MPVolumeView alloc] init];
[self.view addSubview:volView];
volView.hidden = YES;
because it will check the hidden property not immediately, but in a bit, after it is already set to YES.
How did I figure all of this out? A key find was that the instance of MPVolumeView was calling isHidden on its superview, as shown in the following stack trace:
What was my problem?
In short, I did something like this:
- (void)viewDidLoad {
// Add an MPVolumeView to my view
self.volView = [[MPVolumeView alloc] init];
[self.view addSubview:self.volView];
self.volView.hidden = YES;
}
- (void)someButtonTouched {
// Show the MPVolumeView (and hopefully hide the system overlay)
self.volView.hidden = NO;
}
But when I dragged the slider of the newly revealed MPVolumeView, the overlay still appeared. I realized that this was because during the MPVolumeView's instantiation, its superview was hidden.
How did I fix it?
Once I had realized how the MPVolumeView class judged whether it was visible, I had an easy way to "fool" it. I added the following method to the class responsible for the MPVolumeView:
- (void)refreshVolumeView {
[self.volView willMoveToSuperview:self];
[self.volView didMoveToSuperview];
}
and called it each time I need to force the view to reevaluate whether it was visible. This method simply pretends to re-add the view to the hierarchy. Once I've satisfied the conditions that the view evaluates (each ancestor is not hidden or of very low alpha), I call it, and the overlay stops showing up. In my example code above, I would add one line:
- (void)viewDidLoad {
// Add an MPVolumeView to my view
self.volView = [[MPVolumeView alloc] init];
[self.view addSubview:self.volView];
self.volView.hidden = YES;
}
- (void)someButtonTouched {
// Show the MPVolumeView (and hopefully hide the system overlay)
self.volView.hidden = NO;
[self refreshVolumeView]; // <<< This line was added
}
Normally when a MPVolumeView is visible the HUD doesn't appear. I'm using the following method in my App and it is working fine:
+ (void)preventSystemVolumePopup {
// Prevent Audio-Change Popus
MPVolumeView *volumeView = [[MPVolumeView alloc] initWithFrame:CGRectMake(-2000., -2000., 0.f, 0.f)];
NSArray *windows = [UIApplication sharedApplication].windows;
volumeView.alpha = 0.1f;
volumeView.userInteractionEnabled = NO;
if (windows.count > 0) {
[[windows objectAtIndex:0] addSubview:volumeView];
}
}
It basically just adds a volume view to the first window at a position where nobody can see it and thus prevents the system volume HUD from showing. I wonder why you see this HUD, even though you put a volume view above your movie.
This may help.
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
self.volumeView.hidden = NO;
[self.volumeView willMoveToSuperview:self.volumeView.superview];
[self.volumeView didMoveToSuperview];
}
Easiest way to do this.....
- (void) viewDidLoad
{
[super viewDidLoad];
MPVolumeView *volumeView = [[MPVolumeView alloc] initWithFrame: CGRectZero];
[self.view addSubview: volumeView];
...
}
taken refrence from this thread applicationMusicPlayer volume notification
Related
I have a view-controller that I'm re-using (for memory limitation reasons.) So, rather than push a new UIViewController, I just set a few parameters, then force this VC to reload its view. The code is something like this (triggered by a Notification callback):
- (void) reloadView: (NSNotification*) note
{
// Save parameters for reloaded view
UIWindow *window = (UIWindow*) self.view.superview;
//CGAffineTransform xfrm = self.view.transform; // Doesn't do what I want.
// Trash this one & reload the view
[self.view removeFromSuperview];
self.view = nil;
// force view reload
if (self.view == nil)
{
NSLog(#"%s ***** Why didn't self.view reload?!", __PRETTY_FUNCTION__);
}
else
{
// restore params
//self.view.transform = xfrm; // Boo-hoo!
[window addSubview: self.view];
}
}
Everything works fine except that the app is landscape only, and the newly reloaded view is added to the Window as portrait.
I tried forcing the old view's transform onto the new view but, oddly, it gave the rotation but a goofy translation offset.
Is there a way to tell a UIViewController "do your rotation, now"...?
EDIT:
I added this rather silly hack:
// restore params
self.view.transform = xfrm;
self.view.center = CGPointMake(window.bounds.size.width / 2., window.bounds.size.height / 2.);
[window addSubview: self.view];
which gives the desired result, but I'm really displeased with having such a thing in my code base. Surely there's a better way to do this?!?!
Thanks!
EDIT:
After some discussion with JPH, then answer turned out to be "don't do things that way." See comments for some of the details and the redesign that took place.
Your problem is in using this:
[window addSubview: self.view];
From the documentation:
If you add an additional view controller's UIView property to UIWindow
(at the same level as your primary view controller) via the following:
[myWindow addSubview:anotherController.view];
this additional view controller will not receive rotation events and
will never rotate. Only the first view controller added to UIWindow
will rotate.
https://developer.apple.com/library/ios/#qa/qa2010/qa1688.html
I would much prefer a design with a root view controller and the subviews being added to the root view controller's view.
Another option is to NOT kill the view and re-add it, but rather update everything that needs to be updated in that view. I am not sure I understand why you would want to kill a view and re-add it right away.
Is it possible to add a UIView on the staus bar of size (320 x 20)? I don't want to hide the status bar, I only want to add it on top of the status bar.
You can easily accomplish this by creating your own window above the existing status bar.
Just create a simple subclass of UIWindow with the following override of initWithFrame:
#interface ACStatusBarOverlayWindow : UIWindow {
}
#end
#implementation ACStatusBarOverlayWindow
- (id)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
// Place the window on the correct level and position
self.windowLevel = UIWindowLevelStatusBar+1.0f;
self.frame = [[UIApplication sharedApplication] statusBarFrame];
// Create an image view with an image to make it look like a status bar.
UIImageView *backgroundImageView = [[UIImageView alloc] initWithFrame:self.frame];
backgroundImageView.image = [UIImage imageNamed:#"statusBarBackground.png"];
[self addSubview:backgroundImageView];
[backgroundImageView release];
// TODO: Insert subviews (labels, imageViews, etc...)
}
return self;
}
#end
You can now, for example in a view controller in your application, create an instance of your new class and make it visible.
overlayWindow = [[ACStatusBarOverlayWindow alloc] initWithFrame:CGRectZero];
overlayWindow.hidden = NO;
Be aware of messing with the window key status by using - (void)makeKeyAndVisible or similar. If you make your main window (the UIWindow in your Application Delegate) loose key status, you will encounter problems with scrolling scrollviews to top when tapping the status bar etc.
I wrote a static library mimicing Reeders status bar overlay, you can find it here: https://github.com/myell0w/MTStatusBarOverlay
It currently supports iPhone and iPad, default and opaque black status bar styles, rotation, 3 different anymation modes, history-tracking and lots of more goodies!
Feel free to use it or send me a Pull Request to enhance it!
All answers looks like working, but in iOS6.0 I have next problems:
1/ Rotations looks bad
2/ Window (status bar is kind of Window) needed rootViewController
I'm using answer from myell0w, but rotate works not good. I've just remove one extra window and using UIWindow from AppDelegate to implement status bar.
May be this solution is ok only for one UIViewController-app...
Ive implemented by the next way:
1/ In ApplicationDelegate:
self.window.windowLevel = UIWindowLevelStatusBar + 1;
self.window.backgroundColor = [UIColor clearColor];
self.window.rootViewController = _journalController;
2/ Create custom UIView and implement all that you need inside:
For an example touchable statusbar:
#interface LoadingStatusBar : UIControl
And easily create and add to your controller view:
_loadingBar = [[LoadingStatusBar alloc] initWithFrame:topFrame];
[self addSubview:_loadingBar];
3/ Some magic when add your controller view (in initWithFrame:)
CGRect mainFrame = self.bounds;
mainFrame.origin.y = 20;
self.bounds = mainFrame;
Your controller view will has 2 views - content view and status bar view. You can show status bar, or hide it when you want.
Frame of content view will be:
_contentView.frame = CGRectMake(0, 20, self.bounds.size.width, self.bounds.size.height);
4/ And one last magic here :)
To detect touches in non touchable area I've used:
-(id)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if (point.y < 20) return _loadingBar;
return [super hitTest:point withEvent:event];
}
For now it works fine on iPad/iPhone and all iOS's from 4 to 6.
Just to dismiss the "You cannot do this comments"...
I don't know how but I know it is doable. The Feed reader app called Reeder does that.
As you can see from the screenshot, Reeder puts a small dot on the top right of the screen. When you tap it. The bar will fill the whole statusbar until you tap it again to make it small.
First of all, a big thank you to #Martin Alléus for providing the code for this implementation.
I'm just posting for a problem that I faced and the solution I used, as I believe others might experience the same issue.
If the App is started while an call is in place, the status bar height will be 40 pixels and this means that the custom status bar will be initialized with that height.
But if the call is ended while you are still in the app, the status bar height will remain still 40 pixels and it will look weird.
So the solution is simple: I've used the Notification center to subscribe to the status bar frame change delegate of the app and adjust the frame:
- (void)application:(UIApplication *)application didChangeStatusBarFrame:(CGRect)oldStatusBarFrame {
//an in call toggle was done
//fire notification
[[NSNotificationCenter defaultCenter] postNotificationName:kStatusBarChangedNotification object:[NSValue valueWithCGRect:oldStatusBarFrame]];
}
And in the ACStatusBarOverlayWindow we subscribe to the notification:
-(id)initWithFrame:(CGRect)frame
{
if ((self = [super initWithFrame:frame]))
{
// Place the window on the correct level & position
self.windowLevel = UIWindowLevelStatusBar + 1.0f;
self.frame = [UIApplication sharedApplication].statusBarFrame;
self.backgroundColor = [UIColor blackColor];
//add notification observer for in call status bar toggling
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(statusBarChanged:) name:kStatusBarChangedNotification object:nil];
}
return self;
}
and our code to adjust the frame:
- (void)statusBarChanged:(NSNotification*)notification {
//adjust frame...
self.frame = [UIApplication sharedApplication].statusBarFrame;
//you should adjust also the other controls you added here
}
The kStatusBarChangedNotification is just a constant I've used for easy referrence, you can simply replace it with a string, or declare the constant globally.
#Jonah pointed me to the following explanation he has provided:
Question on View Controller's view
I currently have a MainViewController whose main(root?) view contains an Add Record UIButton which is linked to the following method and is dependent on which page the user currently has open in a UIScrollView:
#pragma mark -
#pragma mark Add View Support
- (void)add:(id)sender {
MyAddViewController *controller = nil;
// Dynamically creating and allocating the Add View Controller
if (dataViewPageControl.currentPage == 0) {
controller = [[MyAddViewController alloc] initWithAddType:1];
} else {
controller = [[MyAddViewController alloc] initWithAddType:0];
}
controller.delegate = self;
// Slide the new AddViewController's view in from the right
CGPoint initialCenter = self.view.center;
controller.view.center = CGPointMake(initialCenter.x + 320, initialCenter.y);
[self.view addSubview:controller.view];
[UIView beginAnimations:#"animation" context:NULL];
[UIView setAnimationDuration:0.35];
[UIView setAnimationDelegate:controller];
controller.view.center = CGPointMake(initialCenter.x, initialCenter.y);
[UIView commitAnimations];
}
MainViewController is the delegate for MyAddViewController (which has a CANCEL/CLOSE button) and when it's close button is clicked the MyAddViewController is released through the close method in this MainViewController.
As you can see, MyAddViewController's view becomes a subview to the current root view (Bad? Bad? Bad?) MyAddViewController's view is as a FULL size view (320x416) but whose top half is transparent, so that MainViewController's view can be seen below on the top half and the bottom half contains the AddViewController's view and textfields etc....
Based on what I've read from #Jonah just now at the link at the very top this sort of design is BAD, BAD, BAD. I've noticed when testing in Instruments that MyAddViewController sometimes lags when sliding in. I'll click the ADD button to fire this method and there is sometimes a 3 - 5 sec delay before the view slides in. Some sort of delay in all the memory management and cleanup?
I'm assuming I can fix this by making a single MyAddViewController property inside MainViewController which will then prevent me from allocating and deallocating on every button click. That should make things smoother? My only worry is that this will cause a greater memory imprint at application load time. Instruments seems to show a 516KB allocation every time I click ADD for the first time in my app.
Any recommendations though on design and how to improve this? The reason I like having separate view controllers with nested views like this is because MyAddViewController has a lot of logic with Core Data and such and MainViewController is pretty busy enough already as it is. Trying to keep things modular.
Thanks in advance!
I think the best solution is to redesign your MyAddViewController to be a controller which inherits from NSObject rather than UIViewController. The MyAddViewController can then manage a subview of your MainViewController allowing you to keep your controller logic nicely encapsulated without abusing UIViewController.
I've tried to describe why nesting the views of multiple custom UIViewController classes is problematic here: http://blog.carbonfive.com/2011/03/09/abusing-uiviewcontrollers/
An add view controller is the perfect type of thing to use modal presentation for. Instead of handling all that animation yourself, just present it like this:
[self presentModalViewController:controller animated:YES];
Edit
Then I would recommend making MyAddViewController a property on MainViewController and write a custom accessor. Additionally I would defer setting it's addType (if possible) until the point at which you will actually animate the view in. This way we can just have one generic MyAddViewController hanging around, instead of having to create a new one every time the detailPageControl changes.
- (MyAddViewController *)addViewController
{
if (!addViewController)
addViewController = [[MyAddViewController alloc] init];
}
Then before you animate it...
self.addViewController.addType = 0;
if (dataViewPageControl.currentPage == 0) {
self.addViewController.addType = 1;
}
I have created scroll view and set the buttons in the scroll view. The buttons are scrolling horizontally. I created table view as subview for view controller. On clicking the buttons, the datas are displaying in the table view from RSS feeds using XML Parsing. SO changing the datas in the table view which depends the button clicking. When i changed to select the next button, the parsing will be started and displayed the datas. so it takes some times. In the mean time i want to display some view or disable the view(that means,on that parsing time the view is disable or user cant do anything like freeze with activity indicator). On changing the each buttons, the action will be happened. I referred some tutorials, but i cant get any idea? Some people told me to use synchronous method to solved the problem. But i don't have any idea about it. Please guide me and help me out.Give me some sample apps and Links.
see my below image,
Thanks in Advance!
Regards,
Pugal
Don't use synchronous network calls to disable user input. Whoever suggested that gave you very bad advice.
If you just want to disable input for your current view and its subviews, you can do self.view.userInteractionEnabled = NO; in your view controller.
If you want to disable input for the entire window, you can do self.view.window.userInteractionEnabled = NO;
You won't need to disable user interaction at all if you overlay a full-screen view on top of your user interface. Based on your mock-up image, I think this is what you're trying to do. To do this, you can do something like this:
self.overlayView = [[[UIView alloc] initWithFrame:self.view.window.bounds] autorelease];
self.overlayView.backgroundColor = [UIColor blackColor];
self.overlayView.alpha = 0.5f;
[self.view.window addSubview:self.overlayView];
self.activityIndicator = [[[UIActivityIndicator alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite] autorelease];
self.activityIndicator.center = self.view.window.center;
[self.view.window addSubview:self.activityIndicator];
[self.activityIndicator startAnimating];
self.activityLabel = [[[UILabel alloc] initWithFrame:CGRectZero] autorelease];
self.activityLabel.text = #"Loading...";
[self.activityLabel sizeToFit];
self.activityLabel.center = CGPointMake(self.activityIndicator.center.x, self.activityIndicator.center.y - self.activityIndicator.frame.size.height);
[self.view.window addSubview:self.activityLabel];
I'm trying to do something that shouldn't be that complicated, but I can't figure it out.
I have a UIViewController displaying a UITableView. I want to present a context menu when the user press on a row. I want this to be a semi-transparent view with labels and buttons.
I could use an AlertView, but I want full control on the format of the labels and buttons and will like to use Interface Builder.
So I created my small view 250x290, set the alpha to .75 and create a view controller with the outlets to handle the different user events.
Now I want to present it.
If I use presentModalViewController two (undesired) things happen
1) the view covers all of the screen (but the status bar).
2) It is semi-transparent, but what I see "behind" it its not the parent view but the applications root view.
Ive tried adding it as a subview, but nothing happens, so Im not doing something right:
RestaurantContextVC* modalViewController = [[[RestaurantContextVC alloc] initWithNibName:#"RestaurantContextView" bundle:nil] autorelease];
[self.view addSubview:modalViewController.view];
Is it possible to do what I want?
Thanks in advance.
Gonso
I'm coding similar thing. My approach include.....
Not using dismissModalViewControllerAnimated and presentModalViewController:animated.
Design a customized full sized view in IB. In its viewDidLoad message body, set the background color to clearColor, so that space on the view not covered by controllers are transparent.
I put a UIImageView under the controllers of the floating view. The UIImageView contains a photoshoped image, which has rounded corners and the background is set to transparent. This image view serves as the container.
I uses CoreAnimation to present/dismiss the floating view in the modal view style: (the FloatingViewController.m)
- (void)viewDidLoad
{
[super viewDidLoad];
[self.view setBackgroundColor:[UIColor clearColor]];
[UIView beginAnimations:nil context:nil];
[self.view setFrame:CGRectMake(0, 480, 320, 480)];
[UIView setAnimationDuration:0.75f];
[self.view setFrame:CGRectMake(0, 0, 320, 480)];
[UIView commitAnimations];
}
wangii
Thats pretty much the solution I found.
I load the view with loadNibNamed and then just add it on top with addSubView, like this:
//Show a view on top of current view with a wait indicator. This prevents all user interactions.
-(void) showWaitView{
NSArray* nibViews = [[NSBundle mainBundle] loadNibNamed:#"WaitView" owner:self options:nil];
#ifdef __IPHONE_2_1
waitView = [ nibViews objectAtIndex: 0];
#else
waitView = [ nibViews objectAtIndex: 1];
#endif
CGFloat x = self.view.center.x - (waitView.frame.size.width / 2);
CGFloat y = self.view.center.y - (waitView.frame.size.height / 2);
[waitView setFrame:CGRectMake(x,y,waitView.bounds.size.width,waitView.bounds.size.height)];
[self.view addSubview:waitView];
}
Could you elaborate on points 3 and 4?
What I did to give the view the round rect aspect is put it inside a round rect button.
This code will actually allow you to have a small floating view, but if the view is smaller that its parent, the user could interact with the visible part of the parent.
In the end I create my view with the same size, but kept the code just in case.
Gonso
I would strongly consider using a navigation controller to slide in your subview instead of overlaying it. This is the expected model and any small benefit you may think you'll get by doing it your own way will be greatly offset by the principle of (least) surprise.
If you really really have to do it this way, I believe the trick is to add the first table view as a subview of a transparent "holding" view that the view controller maintains. Then add your new sub view as another subview of that.
Again, if you really want to do this, instead of adding a transparent "holding" view, since this pop-up is essentially modal, I would make it a subview directly of the window.
You might want to put in a transparent black shield behind it to prevent touches on the background and focus input on the popup.
But seriously, consider either popping a controller on the stack or using that alert view. Unless you've hired a $$ designer, it's probably not going to look appropriate on the iPhone.
What I did was create a UIViewController on top of my UINavigation controller in my app delegate and made it a property of a singleton object for convenience:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//--- create root navigation controller
self.window.rootViewController = self.navigationController;
//--- create view controller for popups:
popupViewController = [[BaseViewController alloc] init];
popupViewController.view.backgroundColor = [UIColor clearColor];
popupViewController.view.hidden = true; //for rendering optimisation
[self.window addSubview:popupViewController.view];
[AppState sharedInstance].popupViewController = self.popupViewController;
//--- make all visible:
[self.window makeKeyAndVisible];
return YES;
}
At any point in my app, I can then call e.g.
MyViewController * myVC = [[UIViewController alloc] init];
//... set up viewcontroller and its view...
// add the view of the created view controller to the popup view:
[AppState sharedInstance].popupViewController.view.hidden = false;
[[AppState sharedInstance].popupViewController.view addSubview:myVC.view];
The BaseViewController used on the top just inherits from UIViewController and sets up a full-screen view:
//----- in BaseViewController implementation
- (void)loadView {
//------- create root view:
CGRect frame = [[AppState sharedInstance] getScreenFrame];
rootView = [[VCView alloc] initWithFrame:frame];
rootView.backgroundColor = [UIColor whiteColor];
self.view = rootView;
}