I want to conform to the UIAlertController used in iOS 8 since UIAlertView is now deprecated. Is there a way that I can use this without breaking support for iOS 7? Is there some kind of if condition I can do to check for iOS 8 otherwise do something else for iOS 7 support?
I think a much better way to check if a class exists (since iOS 4.2) is:
if([ClassToBeChecked class]) {
// use it
} else {
// use alternative
}
In your case, that would be:
if ([UIAlertController class]) {
// use UIAlertController
} else {
// use UIAlertView
}
Objective C (as mentioned above)
if ([UIAlertController class]) {
// use UIAlertController
} else {
// use UIAlertView
}
Swift
if objc_getClass("UIAlertController") == nil {
// use UIAlertView
} else {
// use UIAlertController
}
Don't use if NSClassFromString("UIAlertController") == nil It is not working because the signature for this method is func NSClassFromString(_ aClassName: String!) -> AnyClass!
Please see the answer of Erwan (below my answer) as I see it is the best.
--
You can check the iOS version to use appropriate control like this:
if (([[[UIDevice currentDevice] systemVersion] compare:#"8.0" options:NSNumericSearch] == NSOrderedAscending)) {
// use UIAlertView
}
else {
// use UIAlertController
}
As others have already mentioned - always check whether a feature exists. I believe the safest approach is following:
if (NSClassFromString(#"UIAlertController")) {
// use UIAlertController
} else {
// use UIAlertView
}
With the obvious risk of entering a class name with a typo. :)
From documentation of NClassFromString:
[Returns] The class object named by aClassName, or nil if no class by that name is currently loaded. If aClassName is nil, returns nil.
Availability iOS (2.0 and later)
Solution for checking iOS version in Swift
switch (UIDevice.currentDevice().systemVersion.compare("8.0.0", options: NSStringCompareOptions.NumericSearch)) {
case .OrderedAscending:
println("iOS < 8.0")
case .OrderedSame, .OrderedDescending:
println("iOS >= 8.0")
}
Con of this solution: it is simply bad practice to check against OS version numbers, whichever way you do it. One should never hard code dependencies in this way, always check for features, capabilities or the existence of a class. Consider this; Apple may release a backwards compatible version of a class, if they did then the code you suggest would never use it as your logic looks for an OS version number and NOT the existence of the class.
(Source of this information)
Solution for checking the class' existence in Swift
if (objc_getClass("UIAlertController") == nil) {
// iOS 7
} else {
// iOS 8+
}
Do not use if (NSClassFromString("UIAlertController") == nil) because it works correctly on the iOS simulator using iOS 7.1 and 8.2, but if you test on a real device using iOS 7.1, you will unfortunately notice that you will never pass through the else part of the code snippet.
// Above ios 8.0
float os_version = [[[UIDevice currentDevice] systemVersion] floatValue];
if (os_version >= 8.000000)
{
//Use UIAlertController
}
else
{
//UIAlertView
}
Create simple utility function to reduce code
CODE :
// pass minimum required iOS version
BOOL isOSSupported(NSString *minRequiredVersion)
{
NSString *currSysVer = [[UIDevice currentDevice] systemVersion];
BOOL isOSSupported = ([currSysVer compare:minRequiredVersion options:NSNumericSearch] != NSOrderedAscending) &&
![currSysVer isEqualToString:#"Unknown"];
return isOSSupported;
}
USE :
if(isOSSupported("8.0")
{
// Code for iOS8 and above
}
else
{
// Code for iOS7 and below
}
Or Use system constant NSFoundationVersionNumber_iOS_7_1 as below
if(floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_7_1)
{
// Code for iOS8 and above
}
else
{
// Code for iOS7 and below
}
for more options Link
I have created very simple wrapper in Objective-C, that supports both - old iOS UIAlertView and new one UIAlertViewController
https://github.com/MartinPerry/UIAlert/
It also brings the new action blocks usage to old UIAlertView
Sample:
MyAlertMessage * a = [[MyAlertMessage alloc] initWithTitle:#"Hello" WithMessage:#"World"];
[a addButton:BUTTON_OK WithTitle:#"OK" WithAction:^(void *action) {
NSLog(#"Button OK at index 0 click");
}];
[a addButton:BUTTON_CANCEL WithTitle:#"Cancel" WithAction:^(void *action) {
NSLog(#"Button Cancel at index 1 click");
}];
[a show];
I have written one class that wrap the UIAlertView and use UIAlertController. For the programmer is transparently hence is sufficient import this classes in the project. The utility of this classes is when in a old project there are more UIAlertView to change. Link: https://github.com/kennymuse/UIAlertView
Method one
by ios system version check
#define iOSVersionLessThan(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending)
// below ios8 ,create UIAlertView
if(iOSVersionLessThan(#"7.0")){
// todo
// ios8 and above ,UIActionController avaliable
}else{
// todo
}
Method two
by system feature detect
// create UIActionController
if([UIActionController class]){
// todo
// create UIAlertView
}else{
// todo
}
But,there's a third lib named PSTAlertController that deal with backwards compatible to iOS 7 of UIActionSheet and UIAlertView.
ref to
Supporting Multiple Versions of iOS
Supporting iOS 6
Supporting Multiple iOS Versions and Devices
Try below code. It works fine for both iOS 8 and below version.
if (IS_OS_8_OR_LATER) {
UIAlertController *alertVC = [UIAlertController alertControllerWithTitle:title message:msg preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *cancelAction = [UIAlertAction
actionWithTitle:#"OK"
style:UIAlertActionStyleCancel
handler:^(UIAlertAction *action)
{
}];
[alertVC addAction:cancelAction];
[[[[[UIApplication sharedApplication] windows] objectAtIndex:0] rootViewController] presentViewController:alertVC animated:YES completion:^{
}];
}
else{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title message:msg delegate:self cancelButtonTitle:#"Ok" otherButtonTitles:nil, nil];
[alert show];
}
Related
The YES and NO buttons function work as expected, the only problem is that the question No GPS hardware use Triangulation? does not appear to inform the user what the alert is about. The application is tabbed.
The entire code for the project including the xcode project files and Info.plist files can be
found on GitHub, in case you want to build or debug it.
The title and message of the UIAlertController do not appear for the following UIAlertController:
- (UIAlertController*) alertUserNoGPSHardware {
UIAlertController *alertToPresent = nil;
NSString* alertTitleString = #"GPS Alert";
NSString* alertMessage = #"No GPS hardware use Triangulation?";
if (!hardwareExistsOnDevice && mustUseGPSHardware) {
alertToPresent = [UIAlertController alertControllerWithTitle: alertTitleString message:alertMessage
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* yesButton = [UIAlertAction actionWithTitle:#"YES" style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {mustUseGPSHardware = NO;}];
[alertToPresent addAction:yesButton];
UIAlertAction* noButton = [UIAlertAction actionWithTitle:#"NO" style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {mustUseGPSHardware = YES;}];
[alertToPresent addAction:noButton];
}
return alertToPresent;
}
I've also tried to embed the above code in the function that calls this library routine. The same problem occurs.
- (void) setLabelWithGPSLatitudeAndLongitudeWithTimeStampData {
NSString *actionString = nil;
if (displayDataModel) {
if (isFirstGpsClick) {
// Call to the DataModel library that receives a pointer UIAlertView object from the GPS library implementation
// If the UIAlertView pointer is nil proceed with the displaying the latitude, longitude and timestamp.
// If the UIAlertView has a value show the alert, the alert should contain a function to update data in the GPS model.
// This will enable the user to approve of using WiFi or Radio triangulation when the GPS is not available.
/*
* BUG - title and message are not appearing in the alert, the buttons in the alert work as expected
* clicking the YES button removes the warning message that there is no GPS hardware and just
* returns the data. Clicking the no message displays displays the warning message every time.
*/
isFirstGpsClick = NO;
UIAlertController* gpsAlert = [displayDataModel provideGPSAlerters];
if (gpsAlert) {
[self presentViewController:gpsAlert animated:NO completion:nil];
return;
}
}
actionString = [displayDataModel provideGPSLocationData];
}
else {
actionString = #"GPS Button Action Failure: Data Model not created";
}
[displayButtonAction setText:actionString];
}
I've also tried moving the embedded code into the following 2 functions
- (void)viewWillLayoutSubviews {
/*
* Get the tab bar height from the Application Delegate so that the total vertical space
* can be calculated.
*/
AppDelegate* appDelegate = (AppDelegate*)[[UIApplication sharedApplication]delegate];
if (appDelegate) {
UITabBarController *TempTabBar = appDelegate.tabBarController;
if (TempTabBar) {
// Tab Bar Height is larger than myDelegate.tabBarController.tabBar.frame.size.height indicates
tabBarHeight = TempTabBar.tabBar.frame.size.height * 2.5;
}
}
[self setSubViewSizeVariablesBasedOnViewBounds];
[self addButtonAndLabels];
self.view.backgroundColor = [UIColor whiteColor];
}
- (void)viewDidLoad {
[super viewDidLoad];
if (self.displayModelLibraryInitialization) {
NSLog(#"In Objective-C Implementation viewDidLoad - unable to initialize displayModelLibrary");
}
}
When I move the UIAlertController into viewWillLayoutSubviews() or viewDidLoad() I get the black screen, not
the alert and not the buttons and labels that should be there.
This question does not apply because the current problem is in Objective-c and not Swift.
This question does not apply because no textfield is getting updated.
The code does not use alert builder so this question doesn't apply.
Background
I am new to programming in Xcode, iOS, Objective-c and Swift. This is my first iOS project. It was
an interview coding challenge.
OSX - El Capitan
Xcode - Version 8.2 (8C38)
Running in the simulator.
I am only answering this so that it doesn't add to StackOverflow unanswered questions (on CodeReview we would call an unaswered question a zombie).
#DavidH helped provide the answer.
It seems that there are a few minor quirks to the iPhone 7 plus simulator in Xcode 8.2. DavidH was able to see the message in the alert in the iPhone 7 simulator in Xcode 8.3. I switched to the iPhone 7 simulator from the iPhone 7 plus simulator and saw the message in the alert.
This indicates there may not have been a problem in the code and that the iPhone 7 plus simulator in Xcode 8.2 may be buggy.
I'm working on an old iOS app originally written for iOS 6, and it had some UIActionSheets that needed to be changed, so I've been working to move them over to UIAlertControllers, using UIAlertActions. This has worked perfectly fine on a iPad2 Simulator, however when testing on an iPad Air (the only iPad I have access to) my UIAlertAction, and UIAlertController become nil directly after being created (looked at in the debugger, it receives a pointer on creation, however as soon as it executes the next line it becomes null). Here's a code sample:
//When hovering over the next line, alert has a pointer
UIAlertController* alert = [UIAlertController
alertControllerWithTitle:#"info"
message:#"testmessage"
preferredStyle:UIAlertControllerStyleActionSheet];
//alert pointer is now nil
//test pointer shows up
UIAlertAction* test= [UIAlertAction
actionWithTitle:#"I'm a Button"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action){
[alert dismissViewControllerAnimated: YES completion:nil];
}
];
//test pointer is nil, test2 pointer exists
UIAlertAction* test2 = [UIAlertAction
actionWithTitle:#"I'm a Button"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action){
[alert dismissViewControllerAnimated: YES completion:nil];
}
];
//test2 pointer is nil
[alert addAction:test];
[self presentViewController:alert animated:YES completion:nil]; //app crashes, because I'm trying to present a nil modal.
Any thoughts or help would be much appreaciated!
UIAlertController is for iOS8 upwards. Use UIAlertView and UIActionSheet for prior to iOS8
You are best to check wether your device responds to the class,
if ([UIAlertController class]) {
// use UIAlertController for the action sheets as you have already posted
// For all operating systems (like iOS8 upwards) will land in here.
} else {
// use UIActionSheet - like you already used for iOS6
}
It's not wise to check the operating system deployment number, like if 8.0 etc, checking the if it responds to the class is the proper way to do it.
It prevents a crash means you're not relying on float numbers which are not guaranteed to be reliable, as if they change the way the operating systems is named in future, your code would crash.
There is an issue with an UIAlertView when i add to this a lot of buttons. Then the alertView seems to be destroyed. This happens only to prior version of iOS 7. On iOS 7 and posterior versions it seems ok.Here is a screenshot of my problem.Can i fix it?
- (void) sortTotalMnhmeia{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Ταξινόμηση" message:#"Επιλέξτε είδος ταξινόμησης" delegate:self cancelButtonTitle:#"Cancel" otherButtonTitles:#"Αξιοθέατα",#"Δραστηριότητες",#"Διαμονή",#"Χωριά",#"Προϊόντα",#"Όλες οι κατηγορίες",nil];
[alert show];
}
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
if (buttonIndex == 0)
{
NSLog(#"Cancel Tapped.");
}
else if (buttonIndex == 1)
{
[self.mapView removeAnnotations:self.annotations];
[self.annotations removeAllObjects];
self.annotations=[[NSMutableArray alloc] init];
NSLog(#"yo %d",self.annotations.count);
for(int i=0; i<self.allGroups.count; i++){
Group *assistantGroup=assistantGroup=[self.allGroups objectAtIndex:i];
if ([assistantGroup.secondLevel intValue]==1) {
if ([assistantGroup.thirdLevel intValue]==1) {
self.chooseMarker=#"Museum";
}
else if ([assistantGroup.thirdLevel intValue]==2) {
self.chooseMarker=#"Art";
}
else if ([assistantGroup.thirdLevel intValue]==3) {
self.chooseMarker=#"Religious";
}
else if ([assistantGroup.thirdLevel intValue]==4) {
self.chooseMarker=#"Monument";
}
else if ([assistantGroup.thirdLevel intValue]==5) {
self.chooseMarker=#"Natural";
}
else if ([assistantGroup.thirdLevel intValue]==6) {
self.chooseMarker=#"Beach";
}
NSLog(#"************ %# %# %#",assistantGroup.title,assistantGroup.latitude,assistantGroup.longitude);
Annotation *ann = [[Annotation alloc] initWithLong:[assistantGroup.longitude doubleValue] Lat:[assistantGroup.latitude doubleValue] iconNumber:0];
ann.title = assistantGroup.title;
ann.subtitle=#"";
ann.type=self.chooseMarker;
[self.annotations addObject:ann];
}
//ann.type=assistantGroup.kind;
}
[self.mapView addAnnotations:self.annotations];
}
.....
}
You should consider using a modal view controller instead since using lots of buttons in an AlertView can be confusing. I don't see the reason adding lots of buttons to a single alert view. A modal VC has all the flexibility you are looking for. Otherwise an action sheet would be preferable too.
But to answer the question: Add a "More" button last that spawns a second AlertView with the buttons that don't fit the first alert view. Alert VCs don't rescale the way they do on iOS versions above 7.0.
I was using a code snippet in my project answered here: UIAlertView without having reference to it
Here's the code:
+ (UIAlertView *) getUIAlertViewIfShown {
if ([[[UIApplication sharedApplication] windows] count] == 1) {
return nil;
}
UIWindow *window = [[[UIApplication sharedApplication] windows] objectAtIndex:1];
if ([window.subviews count] > 0) {
UIView *view = [window.subviews objectAtIndex:0];
if ([view isKindOfClass:[UIAlertView class]]) {
return (UIAlertView *) view;
}
}
return nil;
}
Unfortunately its not working in iOS 7 and I'm unable to dismiss an alert view. While debugging I found that in the loop its showing that view is of class UITransitionView. Pretty confusing because I couldn't find any quick documentation for this view class.
Any ideas how can I fix this problem?
In iOS7, the UIAlertView window does not appear in -[UIApplication windows]. In fact, the UIAlertView itself is never added to any window, -[UIAlertView window] is always nil. Instead, the alert view manages a variety of undocumented views placed in -[UIApplication keyWindow] with no reference back to the alert view.
Your only real option in iOS7 is to actually keep track of your alert views.
iOS 7 solution
Class UIAlertManager = objc_getClass("_UIAlertManager");
UIAlertView *topMostAlert = [UIAlertManager performSelector:#selector(topMostAlert)];
I am not sure if it is approvable by AppStore, but works
UPD single line code:
UIAlertView *topMostAlert = [NSClassFromString(#"_UIAlertManager") performSelector:#selector(topMostAlert)];
I have faced similar issue and in my case alerts are displayed from different instance of view controller, As Brian has already mentioned that UIAlertView window does not appear in -[UIApplication windows] in iOS7.
So to keep track of this following approach can be followed -
Define a BOOL constant in App Delegate -
#property (nonatomic, assign) BOOL isAlertVisibleOnAppWindow;
Where 'UIAlerView` is present, check earlier instance existence -
AppDelegate *delegate = (AppDelegate *) [UIApplication sharedApplication].delegate;
if (!delegate.isAlertVisibleOnAppWindow) {
delegate.isAlertVisibleOnAppWindow = YES;
UIAlertView *alertView = [[UIAlertView alloc] init…//alert init code
// Either handle alert cancel/completeion click here via blocks, or use alert delegates to reset the isAlertVisibleOnAppWindow BOOL variable to NO.
}
This might be helpful to some other people, thought of sharing this.
UIAlertView *topMostAlert = [NSClassFromString(#"_UIAlertManager") performSelector:#selector(topMostAlert)];
This will NOT be allowed to publish into Apple Store. During build validation Xcode will throw an error, something like: "access to undocumented method.."
So you can't use it, however this code works well.
You can register to UIWindowDidBecomeVisibleNotification:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(aWindowBecameVisible:)
name:UIWindowDidBecomeVisibleNotification
object:nil];
and in aWindowBecameVisible check the window description for _UIModalItemHostingWin:
if ([[theWindow description] hasPrefix:#"<_UIModalItemHostingWin"])
{
// This is the alert window
}
I want to make a "terms and conditions" screen that pops up the very first time that app is opened. On this view I would add a button that says "agree," and upon clicking it the code would execute:
[self dismissViewControllerAnimated:YES completion:nil];
...and would go to the first view of the app.
I am currently using a Tab Bar Controller that has 4 ViewControllers. So basically, I just need to have some method in viewWillAppear on my first ViewController that checks for an NSUserDefault key:value. The first time the app opens, it will be zero. After they click agree, I'll set it to 1 and the bit of code would never execute again.
Can you please offer some code to accomplish the task of routing the view from the firstViewController's view to this alternate view controller upon loading the app?
Thanks!
In the viewWillAppear method in your FirstViewController, check NSUserDefaults then present your TermsViewController. After user click agree in TermsViewController, set NSUserDefaults then call
[self.presentingViewController dismissModalViewControllerAnimated:YES]
The use of the popover window can get complicated. Try something like the following if you have little experience with Objective-C.
- (void)viewDidLoad {
if ([termsvalue == 0]) {
NSString *msg = [NSString stringWithFormat:#"Do you agree with the terms of use?"];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"- Confirmation -"
message:msg
delegate:self
cancelButtonTitle:#"Disagree"
otherButtonTitles:#"I agree", nil];
[alert setTag:100];
[alert show];
}
}
- (BOOL)alertViewShouldEnableFirstOtherButton:(UIAlertView *)alertView { // Validation
if ([alertView tag] == 100) {
return YES;
}
}
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex { // Go
if ([alertView tag] == 100) {
if (buttonIndex == 1) {
// The user has agreed
}
}
}