UIGestureRecognizer crashes on < OS3.2 - iphone

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.

Related

iPhone 5 device modifier for dynamic xib loading

I'm working on a universal iOS app and wanting to use different xib files for iPhone 5. The way it currently works is by automatically selecting the file with the right device modifier (as specified here: http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/LoadingResources/Introduction/Introduction.html#//apple_ref/doc/uid/10000051i-CH1-SW2). However there's no mention there of how to handle the iPhone 5. I'm surprised because I'm under the impression they'd prefer developers to create a different/unique experience on the 5 instead of just auto scaling...
Anyone aware of a modifier that works? (eg myxib~iphone5.xib) It would be more convenient than having to handle the device detection and switching myself.
Cheers!
There's no naming convention unfortunately, but there's a way. This is what I'm using:
I have a global .h file called GlobalConstants that I put all my #define macros in. There I have this:
static BOOL is4InchRetina()
{
if ((![UIApplication sharedApplication].statusBarHidden && (int)[[UIScreen mainScreen] applicationFrame].size.height == 548) || ([UIApplication sharedApplication].statusBarHidden && (int)[[UIScreen mainScreen] applicationFrame].size.height == 568))
return YES;
return NO;
}
then in any of my view controller classes - I override the init method like this:
#import GlobalConstants.h
-(id) init {
if(is4InchRetina()) self = [super initWithNibName:#"myNibName5" bundle:[NSBundle mainBundle]];
else self = [super initWithNibName:#"myNibName" bundle:[NSBundle mainBundle]];
return self
}

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

Problems building for both iOS4.3 and iOS5.0

I'm running up against problems trying to incorporate some iOS5-specific libraries into an app targeted at both iOS5 and iOS4.3. I've gone through the following steps:
weakly-linked the Twitter framework by setting it as optional in 'Link Binary with Libraries"
added it as a framework for the iOS5.0 SDK in Other Linker Flags with `-framework Twitter.framework'
conditionally linked the framework in the class header:
#if defined(__IPHONE_5_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_5_0
#import <Twitter/Twitter.h>
#import <Accounts/Accounts.h>
#endif
then in the method itself, I'm then checking whether the user's set up for Twitter:
if ([TWTweetComposeViewController class]) {
self.canTweet = [TWTweetComposeViewController canSendTweet];
}
This works beautifully on both the 5.0 and 4.3 simulators. However, I've got problems getting it to run on, or archive for, actual devices.
When I've got either a 3GS running 5.0, or a 4 running 5.0 attached, both show up twice in the Scheme dropdown. Selecting the top one, and attempting build or run the project fails with an Use of unidentified identifier 'TWTweetComposeViewController' error.
Using the second device entry, the build fails with a ld: framework not found Twitter.framework error.
I'm sure there's something I'm missing here, but I'm stumped. Can anyone advise?
If you are using a week linking then you have to check first availability of API using
NSClassFromString, respondsToSelector, instancesRespondToSelector etc.
So, change your if condition. First try to get your class object using above specified runtime function.
here is a link explaining in detail how to do such.
link
The code for presenting twitter controller
Before this you have to add the frameworks as optional and make the import in h file if iOS is min iOS 5
Class TWTweetComposeViewControllerClass = NSClassFromString(#"TWTweetComposeViewController");
if (TWTweetComposeViewControllerClass != nil) {
if([TWTweetComposeViewControllerClass respondsToSelector:#selector(canSendTweet)]) {
UIViewController *twitterViewController = [[TWTweetComposeViewControllerClass alloc] init];
[twitterViewController performSelector:#selector(setInitialText:)
withObject:NSLocalizedString(#"TwitterMessage", #"")];
[twitterViewController performSelector:#selector(addURL:)
withObject:url];
[twitterViewController performSelector:#selector(addImage:)
withObject:[UIImage imageNamed:#"yourImage.png"]];
[self.navigationController presentModalViewController:twitterViewController animated:YES];
[twitterViewController release];
}
}
Further digging into the error thrown back by the compiler suggested that it was ignoring the weak link flag. Although I've no idea how or why, it was fixed by a reinstallation of XCode.
if you link to 4.2 or later and deploy to 3.1 or later, you can use the new weak linking features to make this check simple.
you have to add Twitter frameworks as optional and then
Class TWTweetComposeViewControllerClass = NSClassFromString(#"TWTweetComposeViewController");
if (TWTweetComposeViewControllerClass != nil)
{
if([TWTweetComposeViewControllerClass respondsToSelector:#selector(canSendTweet)])
{
UIViewController *twitterViewController = [[TWTweetComposeViewControllerClass alloc] init];
[twitterViewController performSelector:#selector(setInitialText:)
withObject:NSLocalizedString(#"TwitterMessage", #"")];
[twitterViewController performSelector:#selector(addURL:)
withObject:url];
[twitterViewController performSelector:#selector(addImage:)
withObject:[UIImage imageNamed:#"yourImage.png"]];
[self.navigationController presentModalViewController:twitterViewController animated:YES];
[twitterViewController release];
}
}

RespondsToSelector returning a false-negative?

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
{
}

Universal iOS app crashing on iPhone/iTouch 3.1.3 due to UIPopoverController

I just updated my app so that it's a universal app. In doing so I added support for UIPopoverController in a certain place. Now the app seems to be crashing on 3.1.3 iPhone/iTouch devices:
OS Version: iPhone OS 3.1.3 (7E18)
Report Version: 104
Exception Type: EXC_BREAKPOINT (SIGTRAP)
Exception Codes: 0x00000001, 0xe7ffdefe
Crashed Thread: 0
Dyld Error Message:
Symbol not found: _OBJC_CLASS_$_UIPopoverController
What I don't get is that I'm only trying to call UIPopoverController if the hardware is an iPad:
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
UIPopoverController *popover = [[UIPopoverController alloc] initWithContentViewController:btc];
CGSize popoverSize = { 300.0, 500.0 };
popover.delegate = self;
popover.popoverContentSize = popoverSize;
self.bmPopover = popover;
[popover release];
[self.bmPopover presentPopoverFromBarButtonItem:self.bmBarButtonItem permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
} else {
[self presentModalViewController:nav animated:YES];
}
I do have an iVar and a property of type UIPopoverController declared but I wouldn't have expected this to cause an issue at runtime if I didn't actually try to call methods in the class.
What am I supposed to do to make sure that the system doesn't try to link with UIPopoverController at runtime when this isn't supported?
For Universal app you should not only check if this is ipad or does this class exists but you should link UIKit as Weak reference a not default ( strong ), to do this:
get to target info
select general
in linked libraries change UIKit required to UIKit weak
Have fun making universal apps :]
Even though this code would most likely never run on the iPhone, it will still be linked and thus you are receiving the error. Before instantiating, you need to check if the class exists. You can modify your code above to the following which will fix it.
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
Class UIPopoverControllerClass = NSClassFromString(#"UIPopoverController");
if (UIPopoverControllerClass != nil) {
UIPopoverController *popover = [[UIPopoverControllerClass alloc] initWithContentViewController:btc];
CGSize popoverSize = { 300.0, 500.0 };
popover.delegate = self;
popover.popoverContentSize = popoverSize;
self.bmPopover = popover;
[popover release];
[self.bmPopover presentPopoverFromBarButtonItem:self.bmBarButtonItem permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
}
} else {
[self presentModalViewController:nav animated:YES];
}
Also, you could weak link against the UIKit framework, which would also solve the problem. I prefer the code solution as it is more safe.
You need to stop the compiler errors by referencing the class from a string. This should be used in tandem with UI_USER_INTERFACE_IDIOM() and will compile on an SDK that does not know about UIPopoverController. Here is an example:
Class popClass = NSClassFromString(#"UIPopoverController");
if(popClass) {
id infoPop = [[popClass alloc] initWithContentViewController:popViewController];
[infoPop presentPopoverFromRect:CGRectMake(20, 70, 10, 10) inView:self.view permittedArrowDirections:4 animated:YES];
}
The easy answer:
In your Xcode project, Select the top level project icon. Do a Get Info, go to the BUILD panel. set Configurations to All Configurations, set Show: to ** All Settings**
Set the iOS Deployment Target to iOS 3.1.
Recompile. Your program is failing because the minimum OS is set too high, so the loader can't resolve the symbol UIPopoverController. The change I just walked you through makes that symbol weak: it will resolve to NULL, but at least your program will load.
Are you loading a NIB file that implicitly creates a UIPopoverController? That's another way to crash.
I found it useful to add this, so that I could use UI_USER_INTERFACE_IDIOM() on pre-3.2 systems.
#ifndef __IPHONE_3_2 // if iPhoneOS is 3.2 or greater then __IPHONE_3_2 will be defined
typedef enum {
UIUserInterfaceIdiomPhone, // iPhone and iPod touch style UI
UIUserInterfaceIdiomPad, // iPad style UI
} UIUserInterfaceIdiom;
#define UI_USER_INTERFACE_IDIOM() UIUserInterfaceIdiomPhone
#endif // ifndef __IPHONE_3_2