32

I have a toggle in my app that's "download on WiFi only". However, that toggle is useless for iPod touch or WiFi-iPads.

Is there a way to know if the device has cellular data capabilities in code? Something that would work in the future would be great too (like if an iPod touch 5th gen with 3G comes out).

gcamp
  • 14,622
  • 4
  • 54
  • 85

8 Answers8

37

Hi you should be able to check if it has the pdp_ip0 interface

#import <ifaddrs.h>

- (bool) hasCellular {
    struct ifaddrs * addrs;
    const struct ifaddrs * cursor;
    bool found = false;
    if (getifaddrs(&addrs) == 0) {
        cursor = addrs;
        while (cursor != NULL) {
            NSString *name = [NSString stringWithUTF8String:cursor->ifa_name];
            if ([name isEqualToString:@"pdp_ip0"]) {
                found = true;
                break;
            }
            cursor = cursor->ifa_next;
        }
        freeifaddrs(addrs);
    }
    return found;
}

This doesn't use any private APIs.

Nate
  • 31,017
  • 13
  • 83
  • 207
bentech
  • 1,226
  • 15
  • 19
  • I found a similar answer [here](http://stackoverflow.com/a/1480867/244343) that seems to be pretty robust. Good thinking! –  Jan 29 '13 at 00:36
  • Several errors in the given code sample, but the idea is sound. I provided correction in an edit. – isoiphone Mar 27 '13 at 01:15
  • 1
    @DTs It was corrected already. Make sure you #import – bentech Oct 25 '13 at 12:06
17

3G by itself seems tough to find. You can find out whether a device can make calls using [[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"tel://"]]. You can check whether a device can get to the internet, period (and by which method that can currently happen) using Reachability code:

NetworkStatus currentStatus = [[Reachability reachabilityForInternetConnection] 
                               currentReachabilityStatus];

if(currentStatus == kReachableViaWWAN) // 3G

else if(currentStatus == kReachableViaWifi) // ...wifi

else if(currentStatus == kNotReachable) // no connection currently possible

..but aside from that, I don't think you can check for the existence of a 3G modem in the device.***** If it can't make a call, and doesn't currently have cell data turned on and wifi turned off, you won't be able to find out if it's 3G-capable.

An alternative way (not forward-compatible though, so you probably don't want to do this) is to compare the device's model with an exhaustive list, knowing which ones have 3G modems in them, as shown here.

***** As per bentech's answer, if you want to go digging around with device names (this may stop working with no advance warning if Apple decide to change the 3g interface name), call getifaddrs and check for the pdp_ip0 interface.

Community
  • 1
  • 1
  • 1
    I know everything you listed… The first two are just not acceptable : it's not because you can't phone or that you are currently on WiFi that you can't have cellular data. The third is not future proof... – gcamp Aug 18 '11 at 03:58
  • 8
    I'm very aware, which is why my answer is "no, there is no way to check just for 3G". –  Aug 18 '11 at 04:08
  • 2
    You should go with the third option, with the default being 'yes it has 3G' and then make updates as new devices come out. If the toggle won't do anything on a device without 3G, it's harmless for future devices to see it (and you'll still have some window of time to update before they could). – MaxGabriel Jan 18 '13 at 08:35
7

Swift 3.0 (UIDevice+Extension) of @bentech's answer

Add this line to your BridgingHeader.h:

#import <ifaddrs.h>

Somewhere else:

extension UIDevice {
    /// A Boolean value indicating whether the device has cellular data capabilities (true) or not (false).
    var hasCellularCapabilites: Bool {
        var addrs: UnsafeMutablePointer<ifaddrs>?
        var cursor: UnsafeMutablePointer<ifaddrs>?

        defer { freeifaddrs(addrs) }

        guard getifaddrs(&addrs) == 0 else { return false }
        cursor = addrs

        while cursor != nil {
            guard
                let utf8String = cursor?.pointee.ifa_name,
                let name = NSString(utf8String: utf8String),
                name == "pdp_ip0"
                else {
                    cursor = cursor?.pointee.ifa_next
                    continue
            }
            return true
        }
        return false
    }
}
Mark Bourke
  • 9,806
  • 7
  • 26
  • 30
  • This worked for me. Tested on an ipod (no cellular radio) and phones. The only change I made is returning nil when cellular network cannot be determined (due to error or whatever the reason): "guard getifaddrs(&addrs) == 0 else { return nil }" (and making return type optional). – GK100 Oct 11 '17 at 18:28
  • Still works on iOS 16, and you don't need the bridging header nor the `NSString` anymore. – pommy Nov 06 '22 at 14:25
4

In iOS 6.1, I've been able to use Core Telephony to successfully check for the presence of cellular baseband capabilities. This works on all iPads I tested: Verizon with service activated and without, AT&T with service currently deactivated, SIM card in and out, and a Wi-Fi-only iPad.

The code I used looks like this:

CTTelephonyNetworkInfo* ctInfo = [[CTTelephonyNetworkInfo alloc] init];
CTCarrier* carrier = ctInfo.subscriberCellularProvider;
self.hasWWANCapability = carrier != nil;

For all the iPads with cellular baseband hardware, carrier is not nil. For the Wi-Fi-only iPad, carrier is nil.

Rick
  • 3,298
  • 3
  • 29
  • 47
  • 3
    This is not correct, you will get nil if there is no cellular access, even thoughts your device has cellular capabilities. (Tested a first iPad with iOS5.1.1) – Laszlo Feb 24 '14 at 09:58
2

I'd think you should be able to use the CoreTelephony Framework.

It does call out that it is for carriers to use, so I am not sure if it is against TOS to access it.

Carriers can use this information to write applications that provide services only for their own subscribers

Chris Wagner
  • 20,773
  • 8
  • 74
  • 95
  • I don't see anything that says it's only for carriers to use; it looks like something provided to address carrier needs, but nothing about the wording suggests it's only for them. – Rick Jul 12 '13 at 21:24
1

One way of doing it is to ask for the users location. When it is as accurate as possibLe, you will know if the device have GPS. All devices that have GPS will have 3G. And those that don't GPS won't have 3G.

Liye Zhang
  • 183
  • 5
  • What do you compare it with to make this determination? – CuriousRabbit Jan 24 '13 at 19:23
  • That's also a very long and expensive process to get that information. Also, when indoor, most GPS devices uses WiFi anyway since precision is better in theses conditions. – gcamp Jan 26 '13 at 02:21
0

Apple provided code here. https://developer.apple.com/library/ios/samplecode/Reachability/Introduction/Intro.html

You should copy Reachability.h and Reachability.m to your project and import Reachability.h to your class,then

Reachability *networkReachability = [Reachability reachabilityForInternetConnection];
NetworkStatus networkStatus = [networkReachability currentReachabilityStatus];

while (networkStatus==NotReachable) {
    NSLog(@"not reachable");
//no  internet connection 
    return;
}
while (networkStatus==ReachableViaWWAN) {
    NSLog(@" ReachableViaWWAN ");
}

while (networkStatus==ReachableViaWiFi) {
    NSLog(@"ReachableViaWiFi");
}
Suraj K Thomas
  • 5,773
  • 4
  • 52
  • 64
  • 2
    This is to know what's the current internet connection is, not what are the capabilities of the device. – gcamp Feb 26 '14 at 17:15
-3

Another way is to extend this: https://github.com/monospacecollective/UIDevice-Hardware/blob/master/UIDevice-Hardware.m with this:

-(bool) hasCellular:(NSString*)modelIdentifier {
    if ([modelIdentifier hasPrefix:@"iPhone"]) return YES;
    if ([modelIdentifier hasPrefix:@"iPod"]) return NO;

    if ([modelIdentifier isEqualToString:@"iPad1,1"])      return NO;
    if ([modelIdentifier isEqualToString:@"iPad2,1"])      return NO;
    if ([modelIdentifier isEqualToString:@"iPad2,2"])      return YES;
    if ([modelIdentifier isEqualToString:@"iPad2,3"])      return YES;
    if ([modelIdentifier isEqualToString:@"iPad2,4"])      return NO;
    if ([modelIdentifier isEqualToString:@"iPad2,5"])      return NO;
    if ([modelIdentifier isEqualToString:@"iPad2,6"])      return YES;
    if ([modelIdentifier isEqualToString:@"iPad2,7"])      return YES;
    if ([modelIdentifier isEqualToString:@"iPad3,1"])      return NO;
    if ([modelIdentifier isEqualToString:@"iPad3,2"])      return YES;
    if ([modelIdentifier isEqualToString:@"iPad3,3"])      return YES;
    if ([modelIdentifier isEqualToString:@"iPad3,4"])      return NO;
    if ([modelIdentifier isEqualToString:@"iPad3,5"])      return YES;
    if ([modelIdentifier isEqualToString:@"iPad3,6"])      return YES;
    if ([modelIdentifier isEqualToString:@"iPad4,1"])      return NO;
    if ([modelIdentifier isEqualToString:@"iPad4,2"])      return YES;
    if ([modelIdentifier isEqualToString:@"iPad2,5"])      return NO;
    if ([modelIdentifier isEqualToString:@"iPad2,6"])      return YES;
    if ([modelIdentifier isEqualToString:@"iPad2,7"])      return YES;
    if ([modelIdentifier isEqualToString:@"iPad4,4"])      return NO;
    if ([modelIdentifier isEqualToString:@"iPad4,5"])      return YES;

    if ([modelIdentifier isEqualToString:@"i386"])         return NO;
    if ([modelIdentifier isEqualToString:@"x86_64"])       return NO;

return YES;

}

(Clearly it could be edited down to remove either the NO or YES only depending on which way you want to err in case there is a new model...)

middleseatman
  • 222
  • 2
  • 5