49

Is the below code reliable to be used to determine whether a device can support phone calls or not? My concern is if apple changes the iphone string to anything else let's say they decide to have "iphone 3g", "iphone 4" etc.

[[UIDevice currentDevice].model isEqualToString:@"iPhone"]
Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
aryaxt
  • 76,198
  • 92
  • 293
  • 442

9 Answers9

100

The iPhone supports the tel:// URI scheme. So you could use:

[[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"tel://"]];

canOpenURL: explicitly checks whether there's an application capable of opening that URL scheme, not that the URL is correct. So it doesn't matter that no phone number is specified. The method returns a BOOL, so check that for YES or NO.

That should literally answer whether there's any application present capable of making a telephone call. So it should be okay against any future changes in device segmentation.

Tommy
  • 99,986
  • 12
  • 185
  • 204
  • 21
    I want to note something people may overlook with this method. If the device is in Airplane mode, or the SIM card is removed, or the SIM cards service is deactivated, then "technically" the device supports phone calls, but it doesn't mean it "can" place phone calls at the moment. As rare as it might sound, people use old iPhones without service as iPod Touches. Just something to think about depending on what your trying to accomplish. – AlBeebe Jul 21 '12 at 19:06
  • 1
    Completely agree with AlBeebe, for my app the key is whether the phone can make a call RIGHT NOW not in general. I think AlBeebe's answer below is the correct one. – ToddB Jun 11 '14 at 16:39
  • @ToddB it got a plus vote from me at the time but I now think it's the wrong answer since FaceTime will route voice calls by IP from a `tel://` link without a carrier connection, and we don't yet know how VoIP is going to factor into things if and when Apple implements it. – Tommy Jun 11 '14 at 16:55
  • I spoke too soon. In airplane mode I get a carrier code of 410, which is the same carrier code when not in airplane mode. Apple says: If you configure a device for a carrier and then remove the SIM card, this property retains the name of the carrier. However, Apple also says for isoCountryCode: The value for this property is nil if any of the following apply: The device is in Airplane mode, there is no SIM card in the device, The device is outside of cellular service range. I will test this and add an answer if it works. – ToddB Jun 11 '14 at 17:15
  • isoCountryCode is not nil for me in Airplane Mode. This directly contradicts Apple's documentation. :-(. Still looking for a solution. – ToddB Jun 11 '14 at 17:33
  • 3
    Note this does not work in IOS8 betas. But likely to be a bug in the betas. If not then the CTTelephonyNetworkInfo checks will required. – kasuku Aug 27 '14 at 08:18
  • 3
    Does not work also o iOS8GM, so looks like not a bug in betas. – Diogo T Sep 09 '14 at 22:51
  • 5
    This returns TRUE on my iPad2 running iOS 8.1. However, it returns FALSE (correct behaviour) on my iPad2 8.1 simulator. So unfortunately it doesn't work, where it counts... – FranticRock Jan 14 '15 at 17:07
  • @Alex at a grand guess, this is because, per https://www.apple.com/ios/whats-new/continuity/ "Now you can make and receive phone calls on [your iPad and Mac] as long as your iPhone running iOS 8 is on the same Wi-Fi network." Did you have an iPhone on the same Wi-Fi network? It's possible the iPad would say 'YES' regardless of course, depending on how dynamic Apple's own apps are permitted to be about supported URL schemes. – Tommy Jan 14 '15 at 21:23
  • see [this SO](http://stackoverflow.com/questions/25873240/how-to-check-if-device-can-make-a-phone-call-ios-8) thread for iOS 8 discussion.. hopefully an answer will come up that's better in the future – manroe Jul 01 '15 at 16:48
  • To reiterate: iPads will return YES to this check in iOS 8 and up. – LordParsley Nov 27 '15 at 09:10
  • 1
    ... but that's possible because since iOS 8 iPads can make phone calls? https://support.apple.com/en-us/HT204681 — "With Continuity, you can make and receive cellular phone calls from your iPad". – Tommy Nov 27 '15 at 13:13
65

Simply checking if a device "supports" phone calls might not be the best way to go about things depending on what your trying to accomplish. Believe it or not, some people use old iPhones without service as if they were an iPod Touch. Sometimes people don't have SIM cards installed in their iPhones. In my app I wanted to dial a phone number if the users device was able to, otherwise I wanted to display the phone number and prompt the user to grab a phone and dial it. Here is a solution I came up with that has worked so far. Feel free to comment and improve it.

// You must add the CoreTelephony.framework
#import <CoreTelephony/CTTelephonyNetworkInfo.h>
#import <CoreTelephony/CTCarrier.h>

-(bool)canDevicePlaceAPhoneCall {
    /*

     Returns YES if the device can place a phone call

     */

    // Check if the device can place a phone call
    if ([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"tel://"]]) {
        // Device supports phone calls, lets confirm it can place one right now
        CTTelephonyNetworkInfo *netInfo = [[[CTTelephonyNetworkInfo alloc] init] autorelease];
        CTCarrier *carrier = [netInfo subscriberCellularProvider];
        NSString *mnc = [carrier mobileNetworkCode]; 
        if (([mnc length] == 0) || ([mnc isEqualToString:@"65535"])) {
            // Device cannot place a call at this time.  SIM might be removed.
            return NO;
        } else {
            // Device can place a phone call
            return YES;
        }
    } else {
        // Device does not support phone calls
        return  NO;
    }
}

You'll notice I check if the mobileNetworkCode is 65535. In my testing, it appears that when you remove the SIM card, then the mobileNetworkCode is set to 65535. Not 100% sure why that is.

The Guardian
  • 374
  • 1
  • 7
  • 22
AlBeebe
  • 8,101
  • 3
  • 51
  • 65
  • 2
    This didn't work for me on iOS 8.1.3, mnc was always equal to "02" whether in airplane mode or not. But I found a solution which works on iOS 7 and later, see my answer below. – flo von der uni Feb 06 '15 at 16:44
  • I know this is an old topic, but I just wanted to point it out that 65535 MNC is an absolutely valid MNC in South Africa. So depending on your market you need to be careful with this approach. – Gabor Furedi Feb 26 '15 at 13:47
  • 1
    I think you're incorrect @GaborFuredi because in South Africa the MCC is 655 (mobile country code) and 35 would be an MNC (mobile network code) . Two distinct things. In my example i'm checking if an MNC is 65536, and according to http://en.wikipedia.org/wiki/Mobile_country_code theres no MNC greater then 1,000 – AlBeebe Feb 26 '15 at 16:34
  • 1
    This method is great, but still not perfect. I'm testing something on a 3G iPad which will accept the tel: url and will return with a valid mmc. It will handle the url just fine when you click on the relevant button in the app, but upon selecting the call option in the resulting pop up, nothing happens. I've actually combined your detection with a device name check, so all iPads are excluded. It might not 100% future proof, but it's given the required function. – Martin Cassidy May 01 '15 at 11:49
  • Following on @martin-cassidy, data SIMs in iPads will give a false positive, so I suggest adding a check for UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad – LordParsley Nov 27 '15 at 09:07
  • Thankyou! I've been round the houses looking for this... canOpenUrl is definitely not enough – Magoo Apr 19 '16 at 07:00
  • 1
    @GaborFuredi @albeebe Note that the MCC is a 10bit value[1], which someone clearly crammed into a 16bit signed int, so 65535 is someone setting it to `FF` to indicate that the value isn’t set [1] https://standards.ieee.org/content/dam/ieee-standards/standards/web/documents/tutorials/bopid.pdf – alfwatt Mar 13 '20 at 17:32
11

I need to make sure that incoming phone calls cannot interrupt the recordings that my clients make, so I prompt them to go to airplane mode but still turn on wifi. The method above from AlBeebe didn't work for me on iOS 8.1.3, but If found this solution which should work in iOS 7 and later:

You must add and import the CoreTelephony.framework.

#import <CoreTelephony/CTTelephonyNetworkInfo.h>
#import <CoreTelephony/CTCarrier.h>

Define the property on your class if you want to track changes

@property (strong, nonatomic) CTTelephonyNetworkInfo* networkInfo;  

Init the CTTelephonyNetworkInfo:

self.networkInfo = [[CTTelephonyNetworkInfo alloc] init];
NSLog(@"Initial cell connection: %@", self.networkInfo.currentRadioAccessTechnology);
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(radioAccessChanged) name:CTRadioAccessTechnologyDidChangeNotification object:nil];

And then you will receive callback when it changes:

- (void)radioAccessChanged {
    NSLog(@"Now you're connected via %@", self.networkInfo.currentRadioAccessTechnology);
}

The values for currentRadioAccessTechnology are defined in CTTelephonyNetworkInfo.h and you'll get back null / nil when there is no cell tower connection.

This is where I found it: http://www.raywenderlich.com/48001/easily-overlooked-new-features-ios-7

flo von der uni
  • 738
  • 9
  • 19
10

Based on @the-guardian's response, I have come up with the following (in swift):

import CoreTelephony

/**
 Indicates if the device can make a phone call.

 - seealso: [Source](http://stackoverflow.com/a/11595365/3643020)

 - returns: `true` if the device can make a phone call. `false` if not.
 */
final class func canMakePhoneCall() -> Bool {
    guard let url = URL(string: "tel://") else {
        return false
    }

    let mobileNetworkCode = CTTelephonyNetworkInfo().subscriberCellularProvider?.mobileNetworkCode

    let isInvalidNetworkCode = mobileNetworkCode == nil
        || mobileNetworkCode?.count == 0
        || mobileNetworkCode == "65535"

    return UIApplication.shared.canOpenURL(url)
        && !isInvalidNetworkCode
}

This code has been tested on an iPad Air 2 Wifi, an iPad Air 2 Simulator, an iPhone 6S Plus, and seems to work appropriately. Will determine on an iPad with mobile data soon.

Alok Nair
  • 3,994
  • 3
  • 24
  • 30
Campbell_Souped
  • 871
  • 10
  • 22
2

This UIApplication.shared.openURL((URL(string: "tel://\(phoneNumber)")!)) will not say if it has SIM or Not this will just say, if the device has options to make a call. For example : A iPhone with or without SIM it'll return true, but in iPod Touch it will always return false, like wise if an ipad doesn't have sim option it will return false.

Here is the code that checks everything comprehensively ! (Using Swift 3.0)

if UIApplication.shared.canOpenURL(URL(string: "tel://\(phoneNumber)")!) {
            var networkInfo = CTTelephonyNetworkInfo()
            var carrier: CTCarrier? = networkInfo.subscriberCellularProvider
            var code: String? = carrier?.mobileNetworkCode
            if (code != nil) {
                UIApplication.shared.openURL((URL(string: "tel://\(phoneNumber)")!))
            }
            else {
                var alert = UIAlertView(title: "Alert", message: "No SIM Inserted", delegate: nil, cancelButtonTitle: "ok", otherButtonTitles: "")
                alert.show()
            }
        }
        else {
            var alert = UIAlertView(title: "Alert", message: "Device does not support phone calls.", delegate: nil, cancelButtonTitle: "ok", otherButtonTitles: "")
            alert.show()
        }

By this way, we can make sure, device supports calling or not.

2

I don't think your method is reliable, as device names may change in the future. If your concern is to prevent the app from running on non-iPhone devices, you may add the 'telephony' to the UIRequiredDeviceCapabilities dictionary in your Info.plist. That will disallow devices other than the iPhone to download your app from the App Store.

Alternatively, if what you need is checking for 3G connectivity at a particular moment, you can use Apple's Reachability utility class to ask about current 3G/WIFI connection status.

Julio Gorgé
  • 10,056
  • 2
  • 45
  • 60
2

I think that generally it is. I would go for a more generic string comparison (just to be safer in case of a future update). I've used it with no problems (so far...).

If you want to be more certain about whether the device can actually make calls, you should also take advantage of the Core Telephony API. The CTCarrier class can tell you whether you can actually make a call at any particular moment.

Nick Toumpelis
  • 2,717
  • 22
  • 38
0

In case you are asking in order to call a phone number and show an error on devices that have no telephony:

void openURL(NSURL *url, void (^ __nullable completionHandler). (BOOL success))
{
    if (@available(iOS 10.0, *)) {
        [application openURL:url options:@{} completionHandler:^(BOOL success) {
            completionHandler(success);
        }];
    } else
    {
        if([application openURL:url]) {
            completionHandler(YES);
        } else {
            completionHandler(NO);
        }
    }
}

usage

        p97openURL(phoneURL, ^(BOOL success) {
            if(!success) {
                  show message saying there is no telephony on device

            }
        }
Anton Tropashko
  • 5,486
  • 5
  • 41
  • 66
-1

Based on @TheGuardian's answer, I think this might be a simpler approach:

   private final func canMakePhoneCall() -> Bool {
       guard UIDevice.currentDevice().userInterfaceIdiom == UIUserInterfaceIdiom.Phone else {
        return false
       }

       let mobileNetworkCode = CTTelephonyNetworkInfo().subscriberCellularProvider?.mobileNetworkCode
       let isInvalidNetworkCode = mobileNetworkCode == nil || mobileNetworkCode?.characters.count <= 0
                                                        || mobileNetworkCode == "65535"
                                                        //When sim card is removed, the Code is 65535

       return !isInvalidNetworkCode
    }
user1107173
  • 10,334
  • 16
  • 72
  • 117