This question appears to be asked and answered many times but with no specific or accurate answer. Hence I will reframe the question for iOS7 and hope for some help.
I need to use AudioServicesPlaySystemSound to play sounds as timing is critical and this is only way to play simultaneous sound effect accurately with variable timing (try every other option).
This works well but I would like to adjust the volume. The only way it appears to be able to do this is with the buttons although some say use MPVolumeView (only works for music), some say use MPMusicPlayerController (but this also only works for music and is now depreciated), and others just say it cannot be done - which is looking more likely.
However, with iOS7 there is a slide control in settings>sounds for the ringer alert volume. Is there any way I can subclass, replicate, or access this slide control to change this volume from within the app?
Apple recommends using MPVolumeView, so I came up with this:
Add volumeSlider property:
#property (nonatomic, strong) UISlider *volumeSlider;
Init MPVolumeView and add somewhere to your view (can be hidden, without frame, or empty because of showsRouteButton = NO and showsVolumeSlider = NO):
MPVolumeView *volumeView = [MPVolumeView new];
volumeView.showsRouteButton = NO;
volumeView.showsVolumeSlider = NO;
[self.view addSubview:volumeView];
Find and save reference to UISlider:
__weak __typeof(self)weakSelf = self;
[[volumeView subviews] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if ([obj isKindOfClass:[UISlider class]]) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
strongSelf.volumeSlider = obj;
*stop = YES;
}
}];
Add target action for UIControlEventValueChanged:
[self.volumeSlider addTarget:self action:#selector(handleVolumeChanged:) forControlEvents:UIControlEventValueChanged];
And then update your custom control when the volume has been changed (i.e. by the hardware volume controls):
- (void)handleVolumeChanged:(id)sender
{
NSLog(#"%s - %f", __PRETTY_FUNCTION__, self.volumeSlider.value);
self.myCustomVolumeSliderView.value = self.volumeSlider.value;
}
and also other way around:
- (IBAction)myCustomVolumeSliderViewValueChanged:(id)sender {
NSLog(#"set volume to: %f", self.myCustomVolumeSliderView.value);
self.volumeSlider.value = self.myCustomVolumeSliderView.value;
}
NOTE: Make sure that setting the self.volumeSlider.value doesn't loop back to setting self.myCustomVolumeSliderView.value.
Hope this helps someone (and that Apple doesn't remove MPVolumeSlider from MPVolumeView).
I think you want to control your volume through program
- (void)setVolume:(float)Level
{
OSStatus errorMsg = AudioQueueSetParameter(audioQueue, kAudioQueueParam_Volume, Level);
if (errorMsg) {
NSLog(#"AudioQueueSetParameter returned %d when setting the volume.", errorMsg);
}
}
use this code to set volume level passing from your code by which button you want to control.
Related
I am attempting to set the volume to max on my initial view controller, while hiding the iOS volume overlay/hud. For whatever reason, if I use this code in my .m file:
-(void)preventSystemVolumePopup {
// Prevent Audio-Change Popus
MPVolumeView *volumeView = [[MPVolumeView alloc] initWithFrame:CGRectMake(-2000., -2000., 0.1f, 0.1f)];
NSArray *windows = [UIApplication sharedApplication].windows;
volumeView.alpha = 0.1f;
volumeView.userInteractionEnabled = NO;
[[MPMusicPlayerController applicationMusicPlayer] setVolume:1.0];//set system vol to max
if (windows.count > 0) {
[[windows objectAtIndex:0] addSubview:volumeView];
}
}
The volume doesn't change. However, if I add the system volume change:
[[MPMusicPlayerController applicationMusicPlayer] setVolume:1.0];
To my viewDidLoad it works fine...except the volume overlay appears.
Can someone explain what I'm doing wrong? I've also tried adding a UIview in IB for MPVolumeView and sending that to the back of the view to hide, but it's still not hiding the overlay.
[[MPMusicPlayerController applicationMusicPlayer] setVolume:1.0];
it only work in device,not work in simulator
I am using MPVolumeView for showing Airplay icon and it works fine.
But I need to show an animation when Airplay network comes, and hide that animation when airplay network hides.
Is there a notification that will let me know when Airplay starts and ends?
This is exactly what you're looking for - https://github.com/StevePotter/AirPlayDetector
It is a single class that provides a property to determine whether airplay devices are active. And a notification when availability changes.
Using it is simple. Like, to determine availability you write:
[AirPlayDetector defaultDetector].isAirPlayAvailable
Enjoy!
To be precise:
To check exactly for airplay with public API: NO
All you can do with public API is to check for available wireless routes, which includes airplay in it: (In simple case when you have a MPVolumeView instance hooked up somewhere to your view, you can just call volumeView.areWirelessRoutesAvailable;)
If you are curious how to check if exactly airplay is available with private API:
- (BOOL)isAirplayAvailable
{
Class MPAVRoutingController = NSClassFromString(#"MPAVRoutingController");
id routingController = [[MPAVRoutingController alloc] init];
NSArray* availableRoutes = [routingController performSelector:#selector(availableRoutes)];
for (id route in availableRoutes) {
NSDictionary* routeDescription = [route performSelector:#selector(avRouteDescription)];
if ([routeDescription[#"AVAudioRouteName"] isEqualToString:#"AirTunes"])
return true;
}
return false;
}
(And in fact MPVolumeView has an MPAVRoutingController instance as its ivar, so the -areWirelessRoutesAvailable is just an accessor exactly for [volumeView->_routingController wirelessDisplayRoutesAvailable])
Also AVAudioSession exposes currentRoute to you, so you do can check if airplay is active easily with:
- (BOOL)isAudioSessionUsingAirplayOutputRoute
{
AVAudioSession* audioSession = [AVAudioSession sharedInstance];
AVAudioSessionRouteDescription* currentRoute = audioSession.currentRoute;
for (AVAudioSessionPortDescription* outputPort in currentRoute.outputs){
if ([outputPort.portType isEqualToString:AVAudioSessionPortAirPlay])
return true;
}
return false;
}
(the answer about AirPlayDetector doesn't guarantee that Airplay is available - all it does it checks the alpha value of MPVolumeView's routeSelection button, which will be shown in any case when wireless routes are available, bluetooth for example. It will do exactly the same as volumeView.areWirelessRoutesAvailable;)
There's a MPVolumeViewWirelessRoutesAvailableDidChangeNotification since iOS 7 you can register for.
It can be done much easier with ReactiveCocoa. Check it out:
MPVolumeView *myVolumeView = [[MPVolumeView alloc] initWithFrame:CGRectMake(0, 0, 180, 22)];
for (UIView *view in myVolumeView.subviews) {
if ([view isKindOfClass:[UIButton class]]) {
[[RACAbleWithStart(view, alpha) distinctUntilChanged] subscribeNext:^(id x) {
NSLog(#"airplay button visibility changed %#", x);
}];
[[RACAbleWithStart(view, frame) distinctUntilChanged] subscribeNext:^(id x) {
NSLog(#"airplay button connection changed %#", x);
}];
}
}
6 years later.
I think Sankar Siva did not ask for detecting, but for activating an airplay route.
I've upped #Alf because he placed me on the right direction, but he is not answering to the question.
MPVolumeViewWirelessRoutesAvailableDidChangeNotification fires when MPVolumeView detects a new route.
On the other hand, MPVolumeViewWirelessRouteActiveDidChangeNotification fires when a new route is taken, eg: when you select your Apple TV for example.
No need of private API.
If you want a notification here is the way to do it
[[NSNotificationCenter defaultCenter]
addObserver:self
selector: #selector(deviceChanged:)
name:AVAudioSessionRouteChangeNotification
object:[AVAudioSession sharedInstance]];
- (void)deviceChanged:(NSNotification *)sender {
NSLog(#"Enters here when connect or disconnect from Airplay");
}
I want to get a pointer reference to UIKeyboard *keyboard to the keyboard on screen so that I can add a transparent subview to it, covering it completely, to achieve the effect of disabling the UIKeyboard without hiding it.
In doing this, can I assume that there's only one UIKeyboard on the screen at a time? I.e., is it a singleton? Where's the method [UIKeyboard sharedInstance]. Brownie points if you implement that method via a category. Or, even more brownie points if you convince me why it's a bad idea to assume only one keyboard and give me a better solution.
Try this:
// my func
- (void) findKeyboard {
// Locate non-UIWindow.
UIWindow *keyboardWindow = nil;
for (UIWindow *testWindow in [[UIApplication sharedApplication] windows]) {
if (![[testWindow class] isEqual:[UIWindow class]]) {
keyboardWindow = testWindow;
break;
}
}
// Locate UIKeyboard.
UIView *foundKeyboard = nil;
for (UIView *possibleKeyboard in [keyboardWindow subviews]) {
// iOS 4 sticks the UIKeyboard inside a UIPeripheralHostView.
if ([[possibleKeyboard description] hasPrefix:#"<UIPeripheralHostView"]) {
possibleKeyboard = [[possibleKeyboard subviews] objectAtIndex:0];
}
if ([[possibleKeyboard description] hasPrefix:#"<UIKeyboard"]) {
foundKeyboard = possibleKeyboard;
break;
}
}
}
How about using -[UIApplication beginIgnoringInteractionEvents]?
Also, another trick to get the view containing the keyboard is to initialize a dummy view with CGRectZero and set it as the inputAccessoryView of your UITextField or UITextView. Then, get its superview. Still, such shenanigans is private/undocumented, but I've heard of apps doing that and getting accepted anyhow. I mean, how else would Instagram be able to make their comment keyboard interactive (dismiss on swipe) like the Messages keyboard?
I found that developerdoug's answer wasn't working on iOS 7, but by modifying things slightly I managed to get access to what I needed. Here's the code I used:
-(UIView*)findKeyboard
{
UIView *keyboard = nil;
for (UIWindow* window in [UIApplication sharedApplication].windows)
{
for (UIView *possibleKeyboard in window.subviews)
{
if ([[possibleKeyboard description] hasPrefix:#"<UIPeripheralHostView"])
{
keyboard = possibleKeyboard;
break;
}
}
}
return keyboard;
}
From what I could make out, in iOS 7 the keyboard is composed of a UIPeripheralHostView containing two subviews: a UIKBInputBackdropView (which provides the blur effect on whatever's underneath the keyboard) and a UIKeyboardAutomatic (which provides the character keys). Manipulating the UIPeripheralHostView seems to be equivalent to manipulating the entire keyboard.
Discaimer: I have no idea whether Apple will accept an app that uses this technique, nor whether it will still work in future SDKs.
Be aware, Apple has made it clear that applications which modify private view hierarchies without explicit approval beforehand will be rejected. Take a look in the Apple Developer Forums for various developers' experience on the issue.
If you're just trying to disable the keyboard (prevent it from receiving touches), you might try adding a transparent UIView that is the full size of the screen for the current orientation. If you add it as a subview of the main window, it might work. Apple hasn't made any public method of disabling the keyboard that I'm aware of - you might want to use one of your support incidents with Apple, maybe they will let you in on the solution.
For an app I am currently developing I am using a really quick and easy method:
Add this in the header file:
// Add in interface
UIWindow * _window;
// Add as property
#property (strong, nonatomic) IBOutlet UIView * _keyboard;
Then add this code in the bottom of the keyboardWillShow function:
-(void) keyboardWillShow: (NSNotification *) notification {
.... // other keyboard will show code //
_window = [UIApplication sharedApplication].windows.lastObject;
[NSTimer scheduledTimerWithTimeInterval:0.05
target:self
selector:#selector(allocateKeyboard)
userInfo:nil
repeats:NO];
}
This code look for when the keyboard is raised and then allocates the current window. I have then added a timer to allocate the keyboard as there were some issues when allocated immediately.
- (void)allocateKeyboard {
if (!_keyboard) {
if (_window.subviews.count) {
// The keyboard is always the 0th subview
_keyboard = _window.subviews[0];
}
}
}
We now have the keyboard allocated which gives you direct "access" to the keyboard as the question asks.
Hope this helps
Under iOS 8 it appears you have to jump down the chain more than in the past. The following works for me to get the keyboard, although with custom keyboards available and such I wouldn't rely on this working unless you're running in a controlled environment.
- (UIView *)findKeyboard {
for (UIWindow* window in [UIApplication sharedApplication].windows) {
UIView *inputSetContainer = [self viewWithPrefix:#"<UIInputSetContainerView" inView:window];
if (inputSetContainer) {
UIView *inputSetHost = [self viewWithPrefix:#"<UIInputSetHostView" inView:inputSetContainer];
if (inputSetHost) {
UIView *kbinputbackdrop = [self viewWithPrefix:#"<_UIKBCompatInput" inView:inputSetHost];
if (kbinputbackdrop) {
UIView *theKeyboard = [self viewWithPrefix:#"<UIKeyboard" inView:kbinputbackdrop];
return theKeyboard;
}
}
}
}
return nil;
}
- (UIView *)viewWithPrefix:(NSString *)prefix inView:(UIView *)view {
for (UIView *subview in view.subviews) {
if ([[subview description] hasPrefix:prefix]) {
return subview;
}
}
return nil;
}
For a cutom camera overlay I need to find out, when the iris is opened, because my overlay will allways shown while the iris is close (and then animating to open).
Any ideas ?
You can listen for the PLCameraViewIrisAnimationDidEndNotification notification. Since this is not officially documented, you might be in violation of the Apple TOS, but I think so long as you write your code so that it's defensive against the possibility that the name or contract of this notification might change (so in the future you might not get the event) you'll probably be ok. In other words, use a timer or other technique to ensure that the thing you want done when the iris is open will definitely happen eventually even if you never get the notification...
Trivial example without the defensive programming. (Of course, you can register an interest only for this specific notification as well, see the docs for the notification center.)
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(notificationCallback:)
name:nil
object:nil
];
- (void) notificationCallback:(NSNotification *) notification {
if ([[notification name] isEqualToString:#"PLCameraViewIrisAnimationDidEndNotification"]) {
NSLog(#"Iris open");
// we don't need to listen any more
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
}
It seems that PLCameraViewIrisAnimationDidEndNotification no longer gets notified in iOS5.
I can't figure out what is a suitable solution when the iris has finished opening, there must another option rather than using a 3 second timer.
Check here: https://devforums.apple.com/message/561008#561008
I have a ViewController (ALImagePickerController) which holds, initializes and presents the UIImagePickerController as a child view controller (I have another child view controller for presenting the taken image which is not shown here) and I present (as a modal) the ALImagePickerController when I want to use the camera. So during this the viewDidAppear of the ViewContoller I add an animation to bring in the camera overlay gracefully as the shutter animation disappears.
#interface ALImagePickerController ()
#property (nonatomic) UIImagePickerController *cameraController;
#property (nonatomic) CameraOverlayView *overlayView;
....
#end
#implementation ALImagePickerController
....
- (void)viewDidLoad {
[super viewDidLoad];
[UIApplication sharedApplication].statusBarHidden = YES;
self.cameraController = [UIImagePickerController new];
self.cameraController.sourceType = UIImagePickerControllerSourceTypeCamera;
self.cameraController.delegate = self;
self.cameraController.allowsEditing = NO;
self.cameraController.showsCameraControls = NO;
....
self.overlayView = [CameraOverlayView new];
....
self.overlayView.alpha = 0;
self.cameraController.cameraOverlayView = self.overlayView;
....
// add as child view controller
[self addChildViewController:self.cameraController];
[self.view addSubview:self.cameraController.view];
[self.cameraController didMoveToParentViewController:self];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[UIApplication sharedApplication].statusBarHidden = NO;
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// smoothly bring in the overlay as the native camera shutter animation opens.
[UIView animateWithDuration:0.2 delay:0.3 options:UIViewAnimationCurveEaseOut animations:^{
self.overlayView.alpha = 1.f;
} completion:nil];
}
....
#end
The way I solved this problem is I initialize all the elements with the hidden property set to YES, then call a 3-second delayed selector after I call the camera, where I set all the elements to hidden = NO. It's not an ideal solution but it seems to work, and any lag after the iris is opened is negligible.
You should already know when the camera is ready to take a picture. At least the way I use a custom camera overlay, I init the view with something like self.sourceType = UIImagePickerControllerSourceTypeCamera; and the other usual setup, and the camera is ready (or "iris is open") at that point.
In summary, if one is using a custom camera overlay the way I am used to using it, one will know when the iris is open because it is under your control.
I have a UIScrollView that has a set of images loaded side-by-side inside it. You can see an example of my app here: http://www.42restaurants.com. My problem comes in with memory usage. I want to lazy load the images as they are about to appear on the screen and unload images that aren't on screen. As you can see in the code I work out at a minimum which image I need to load and then assign the loading portion to an NSOperation and place it on an NSOperationQueue. Everything works great apart from a jerky scrolling experience.
I don't know if anyone has any ideas as to how I can make this even more optimized, so that the loading time of each image is minimized or so that the scrolling is less jerky.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
[self manageThumbs];
}
- (void) manageThumbs{
int centerIndex = [self centerThumbIndex];
if(lastCenterIndex == centerIndex){
return;
}
if(centerIndex >= totalThumbs){
return;
}
NSRange unloadRange;
NSRange loadRange;
int totalChange = lastCenterIndex - centerIndex;
if(totalChange > 0){ //scrolling backwards
loadRange.length = fabsf(totalChange);
loadRange.location = centerIndex - 5;
unloadRange.length = fabsf(totalChange);
unloadRange.location = centerIndex + 6;
}else if(totalChange < 0){ //scrolling forwards
unloadRange.length = fabsf(totalChange);
unloadRange.location = centerIndex - 6;
loadRange.length = fabsf(totalChange);
loadRange.location = centerIndex + 5;
}
[self unloadImages:unloadRange];
[self loadImages:loadRange];
lastCenterIndex = centerIndex;
return;
}
- (void) unloadImages:(NSRange)range{
UIScrollView *scrollView = (UIScrollView *)[[self.view subviews] objectAtIndex:0];
for(int i = 0; i < range.length && range.location + i < [scrollView.subviews count]; i++){
UIView *subview = [scrollView.subviews objectAtIndex:(range.location + i)];
if(subview != nil && [subview isKindOfClass:[ThumbnailView class]]){
ThumbnailView *thumbView = (ThumbnailView *)subview;
if(thumbView.loaded){
UnloadImageOperation *unloadOperation = [[UnloadImageOperation alloc] initWithOperableImage:thumbView];
[queue addOperation:unloadOperation];
[unloadOperation release];
}
}
}
}
- (void) loadImages:(NSRange)range{
UIScrollView *scrollView = (UIScrollView *)[[self.view subviews] objectAtIndex:0];
for(int i = 0; i < range.length && range.location + i < [scrollView.subviews count]; i++){
UIView *subview = [scrollView.subviews objectAtIndex:(range.location + i)];
if(subview != nil && [subview isKindOfClass:[ThumbnailView class]]){
ThumbnailView *thumbView = (ThumbnailView *)subview;
if(!thumbView.loaded){
LoadImageOperation *loadOperation = [[LoadImageOperation alloc] initWithOperableImage:thumbView];
[queue addOperation:loadOperation];
[loadOperation release];
}
}
}
}
EDIT:
Thanks for the really great responses. Here is my NSOperation code and ThumbnailView code. I tried a couple of things over the weekend but I only managed to improve performance by suspending the operation queue during scrolling and resuming it when scrolling is finished.
Here are my code snippets:
//In the init method
queue = [[NSOperationQueue alloc] init];
[queue setMaxConcurrentOperationCount:4];
//In the thumbnail view the loadImage and unloadImage methods
- (void) loadImage{
if(!loaded){
NSString *filename = [NSString stringWithFormat:#"%03d-cover-front", recipe.identifier, recipe.identifier];
NSString *directory = [NSString stringWithFormat:#"RestaurantContent/%03d", recipe.identifier];
NSString *path = [[NSBundle mainBundle] pathForResource:filename ofType:#"png" inDirectory:directory];
UIImage *image = [UIImage imageWithContentsOfFile:path];
imageView = [[ImageView alloc] initWithImage:image andFrame:CGRectMake(0.0f, 0.0f, 176.0f, 262.0f)];
[self addSubview:imageView];
[self sendSubviewToBack:imageView];
[imageView release];
loaded = YES;
}
}
- (void) unloadImage{
if(loaded){
[imageView removeFromSuperview];
imageView = nil;
loaded = NO;
}
}
Then my load and unload operations:
- (id) initWithOperableImage:(id<OperableImage>) anOperableImage{
self = [super init];
if (self != nil) {
self.image = anOperableImage;
}
return self;
}
//This is the main method in the load image operation
- (void)main {
[image loadImage];
}
//This is the main method in the unload image operation
- (void)main {
[image unloadImage];
}
I'm a little puzzled by the "jerky" scrolling. Since NSOperationQueue runs operations on separate thread(s) I'd have expected at worst you might see empty UIImageViews showing up on the screen.
First and foremost I'd be looking for things that are impacting the processor significantly as NSOperation alone should not interfere with the main thread. Secondly I'd be looking for details surrounding the NSOperation setup and execution that might be causing locking and syncing issues which could interrupt the main thread and therefore impact scrolling.
A few items to consider:
Try loading your ThumbnailView's with a single image at the start and disabling the NSOperation queuing (just skip everything following the "if loaded" check. This will give you an immediate idea whether the NSOperation code is impacting performance.
Keep in mind that -scrollViewDidScroll: can occur many times during the course of a single scroll action. Depending on how for the scroll moves and how your -centerThumbIndex is implemented you might be attempting to queue the same actions multiple times. If you've accounted for this in your -initWithOperableImage or -loaded then its possible you code here is causing sync/lock issues (see 3 below). You should track whether an NSOperation has been initiated using an "atomic" property on the ThumbnailView instance. Prevent queuing another operation if that property is set and only unset that property (along with loaded) at the end of the NSOperation processes.
Since NSOperationQueue operates in its own thread(s) make sure that none of your code executing within the NSOperation is syncing or locking to the main thread. This would eliminate all of the advantages of using the NSOperationQueue.
Make sure your "unload" operation has a lower priority than your "load" operation, since the priority is the user experience first, memory conservation second.
Make sure you keep enough thumbnails for at least a page or two forward and back so that if NSOperationQueue falls behind, you have a high margin of error before blank thumbnails become visible.
Make sure your load operation is only loading a "pre-scaled" thumbnail and not loading a full size image and rescaling or processing. This would be a lot of extra overhead in the middle of a scrolling action. Go even further and make sure you've converted them to PNG16 without an alpha channel. This will give at least a (4:1) reduction in size with hopefully no detectable change in the visual image. Also consider using PVRTC format images which will take the size down even further (8:1 reduction). This will greatly reduced the time it takes to read the images from "disk".
I apologize if any of this doesn't make sense. I don't see any issues with the code you've posted and problems are more likely to be occurring in your NSOperation or ThumbnailView class implementations. Without reviewing that code, I may not be describing the conditions effectively.
I would recommend posting your NSOperation code for loading and unloading and at least enough of the ThumbnailView to understand how it interacts with the NSOperation instances.
Hope this helped to some degree,
Barney
One option, although less visually pleasing, is to only load images when the scrolling stops.
Set a flag to disable image loading in:
-scrollViewWillBeginDragging:
Re-enable loading images when the scrolling stops using the:
-scrollViewDidEndDragging:willDecelerate:
UIScrollViewDelegate method. When the willDecelerate: parameter is NO, the movement has stopped.
the problem is here:
UIImage *image = [UIImage imageWithContentsOfFile:path];
It seems that threaded or not when you load a file from disk (which maybe that happens on the main thread regardless, I'm not totally sure) everything stalls. You normally don't see this in other situations because you don't have such a large area moving if any at all.
While researching this problem, I found two more resources that may be of interest:
Check out the iPhone sample project "PageControl": http://developer.apple.com/iphone/library/samplecode/PageControl/index.html
It lazy loads view controllers in a UIScrollView.
and -
Check out the cocoa touch lib: http://github.com/facebook/three20 which has a 'TTPhotoViewController' class that lazy loads photos/thumbnails from web/disk.
Set shouldRasterize = YES for the sub content view adde to the scrollview. It is seen to remove the jerky behavior of custom created scroll view like a charm. :)
Also do some profiling using the instruments in the Xcode. Go through the tutorials created for profiling by Ray Wenderlich it helped me a lot.