RespondsToSelector returning a false-negative? - iphone

I'm currently using -respondsToSelector: like so:
if (![moviePlayer respondsToSelector:#selector(currentPlaybackTime)]) {
NSLog(#"Cannot get current playbackTime on %#", moviePlayer);
return;
}
where moviePlayer is an instantiated MPMoviePlayerController object. I do a lot of other similar selector checks, so I know that pretty much everything else is working fine, but for some reason, this respondsToSelector check returns false, even though if I do something like time = [moviePlayer currentPlaybackTime], it works fine. This is on 4.0+ iOS, so there's no reason for it to return false.
Any reasons why this would happen?

According to the iOS class reference, currentPlaybackTime is a property of MPMusicPlayerController, not MPMoviePlayerController.
MPMusicPlayerController:
http://developer.apple.com/library/ios/#documentation/MediaPlayer/Reference/MPMusicPlayerController_ClassReference/Reference/Reference.html
MPMoviePlayerController:
http://developer.apple.com/library/ios/#documentation/MediaPlayer/Reference/MPMoviePlayerController_Class/MPMoviePlayerController/MPMoviePlayerController.html
It may be a private property of MPMoviePlayerController that does not have an accessor.
EDIT (see comments)
Determine OS version:
float iPhoneOSVersion = [[[UIDevice currentDevice] systemVersion] floatValue];
if ( iPhoneOSVersion < 3.2 )
{
}
else
{
}

Related

iPhone iOS7 system sound (ringer and alert) volume control

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.

Obj-C, conditionally run code only if iOS5 is available?

How can I check and conditionally only compile / run code if iOS5 is available ?
You can either check the systemVersion property of UIDevice like so:
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 5.0f) {
// Do something
}
But personally I don't like that method as I don't like the parsing of the string returned from systemVersion and the comparison done like that.
The best way is to check that whatever class / method it is that you want to use, exists. For example:
If you want to use TWRequest from the Twitter framework:
Class twRequestClass = NSClassFromString(#"TWRequest");
if (twRequestClass != nil) {
// The class exists, so we can use it
} else {
// The class doesn't exist
}
Or if you want to use startMonitoringForRegion: from CLLocationManager which was brought in in iOS 5.0:
CLLocationManager *locationManager = [[CLLocationManager alloc] init];
...
if ([locationManager respondsToSelector:#selector(startMonitoringForRegion:)]) {
// Yep, it responds
} else {
// Nope, doesn't respond
}
In general it's better to do checks like that than to look at the system version.
Try out this code:
if([[[UIDevice currentDevice] systemVersion] floatValue] >= 5.0)
{
//Do stuff for iOS 5.0
}
Hope this helps you.

Is there any notification for detecting AirPlay in Objective-C?

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");
}

UIGestureRecognizer crashes on < OS3.2

This is to be expected but I can't seem to find a runtime that works properly as it seems it was a private API before!!!!
At the moment I have and OS3.1.3 responds to the addGestureRecognizer selector!!!!
if ( [self.view respondsToSelector:#selector(addGestureRecognizer:)] ) {
UIGestureRecognizer *recognizer;
recognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(morePress)];
[self.view addGestureRecognizer:recognizer];
recognizer.delegate = self;
[recognizer release];
}
You should check for the system version explicitly:
NSString *currentSystemVersion = [[UIDevice currentDevice] systemVersion];
if([currentSystemVersion compare:#"3.2"] == NSOrderedAscending) {
//add gesture recognizer
} else {
// :(
}
UIGestureRecognizer is not supported prior to iOS 3.2. Even if the method addGestureRecognizer: exists, that doesn't mean it's safe to use.
It was indeed a private api and not supported in versions prior to 3.2.
Apple's doc says:
To determine at runtime whether you can use gesture recognizers in
your application, test whether the class exists and, if it does,
allocate an instance and see check if it responds to the selector
locationInView:. This method was not added to the class until iOS 3.2.
Sample code:
UIGestureRecognizer *gestureRecognizer = [[UIGestureRecognizer alloc] initWithTarget:self action:#selector(myAction:)];
if (![gestureRecognizer respondsToSelector:#selector(locationInView:)]) {
[gestureRecognizer release];
gestureRecognizer = nil;
}
// do something else if gestureRecognizer is nil
Explenation:
To determine whether a class is available at runtime in a given iOS
release, you typically check whether the class is nil. Unfortunately,
this test is not cleanly accurate for UIGestureRecognizer. Although
this class was publicly available starting with iOS 3.2, it was in
development a short period prior to that. Although the class exists in
an earlier release, use of it and other gesture-recognizer classes are
not supported in that earlier release. You should not attempt to use
instances of those classes.
Check out the full text here.

Check iOS version at runtime?

This is sort of a follow on from my last question. I am using beginAnimations:context: to setup an animation block to animate some UITextLabels. However I noticed in the docs that is says: "Use of this method is discouraged in iOS 4.0 and later. You should use the block-based animation methods instead."
My question is I would love to use animateWithDuration:animations: (available in iOS 4.0 and later) but do not want to exclude folks using iOS 3.0. Is there a way to check to iOS version of a device at runtime so that I can make a decision as to which statement to use?
Simpler solution for anyone who'll need help in the future:
NSArray *versionCompatibility = [[UIDevice currentDevice].systemVersion componentsSeparatedByString:#"."];
if ( 5 == [[versionCompatibility objectAtIndex:0] intValue] ) { /// iOS5 is installed
// Put iOS-5 code here
} else { /// iOS4 is installed
// Put iOS-4 code here
}
In many cases you do not need to check iOS version directly, instead of that you can check whether particular method is present in runtime or not.
In your case you can do the following:
if ([[UIView class] respondsToSelector:#selector(animateWithDuration:animations:)]){
// animate using blocks
}
else {
// animate the "old way"
}
to conform to version specified in system defines
//#define __IPHONE_2_0 20000
//#define __IPHONE_2_1 20100
//#define __IPHONE_2_2 20200
//#define __IPHONE_3_0 30000
//#define __IPHONE_3_1 30100
//#define __IPHONE_3_2 30200
//#define __IPHONE_4_0 40000
You can write function like this
( you should probably store this version somewhere rather than calculate it each time ):
+ (NSInteger) getSystemVersionAsAnInteger{
int index = 0;
NSInteger version = 0;
NSArray* digits = [[UIDevice currentDevice].systemVersion componentsSeparatedByString:#"."];
NSEnumerator* enumer = [digits objectEnumerator];
NSString* number;
while (number = [enumer nextObject]) {
if (index>2) {
break;
}
NSInteger multipler = powf(100, 2-index);
version += [number intValue]*multipler;
index++;
}
return version;
}
Then you can use this as follows:
if([Toolbox getSystemVersionAsAnInteger] >= __IPHONE_4_0)
{
//blocks
} else
{
//oldstyle
}
Xcode 7 added the available syntax making this relatively more simple:
Swift:
if #available(iOS 9, *) {
// iOS 9 only code
}
else {
// Fallback on earlier versions
}
Xcode 9 also added this syntax to Objective-C
Objective-C:
if (#available(iOS 9.0, *)) {
// iOS 9 only code
} else {
// Fallback on earlier versions
}
Most of these solutions on here are so overkill. All you need to do is [[UIDevice currentDevice].systemVersion intValue]. This automatically removes the decimal, so there is no need to split the string.
So you can just check it like:
if ([[UIDevice currentDevice].systemVersion intValue] >= 8) {
// iOS 8.0 and above
} else {
// Anything less than iOS 8.0
}
You can also define a macro with this code:
#define IOS_VERSION [[UIDevice currentDevice].systemVersion intValue];
or even include your check:
#define IOS_8PLUS ([[UIDevice currentDevice].systemVersion intValue] >= 8)
Then you just need to do:
if (IOS_8PLUS) {
// iOS 8.0 and above
} else {
// Anything less than iOS 8.0
}
Discouraged is not the same as deprecated.
If you need to support earlier versions of iOS that do not have the block based methods, there is nothing wrong with using the older methods (as long as they haven't been removed, of course).
You can use the version of the Foundation framework to determine the current system version.
if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_6_1){
//for earlier versions
} else {
//for iOS 7
}
For my purposes I've written a tiny library that abstracts away the underlying C calls and presents an Objective-C interface.
GBDeviceDetails deviceDetails = [GBDeviceInfo deviceDetails];
if (deviceDetails.iOSVersion >= 6) {
NSLog(#"It's running at least iOS 6"); //It's running at least iOS 6
}
Apart from getting the current iOS version, it also detects the hardware of the underlying device, and gets info about the screen size; all at runtime.
It's on github: GBDeviceInfo. Licensed under Apache 2.
Put this in your Prefix.pch file
#define IOS_VERSION [[[[[UIDevice currentDevice] systemVersion] componentsSeparatedByString:#"."] firstObject] intValue]
And then you can check iOS versions like:
if(IOS_VERSION == 8)
{
// Hello 8!
}
else
{
// Hello some other version!
}
Off course if you can use feature detection (and it makes sense for your use case) you should do that.
In MonoTouch:
To get the Major version use:
UIDevice.CurrentDevice.SystemVersion.Split('.')[0]
For minor version use:
UIDevice.CurrentDevice.SystemVersion.Split('.')[1]
A bit nicer and more efficient adaptation to the above solutions:
-(CGPoint)getOsVersion
{
static CGPoint rc = {-1,-1};
if (rc.x == -1) {
NSArray *versionCompatibility = [[UIDevice currentDevice].systemVersion componentsSeparatedByString:#"."];
rc.x = [versionCompatibility[0] intValue];
rc.y = [versionCompatibility[1] intValue];
}
return rc;
}
now you can
if ([self getOsVersion].x < 7) {
}
HTH