Shane D
Shane D

Reputation: 884

How to tell if 'Mobile Network Data' is enabled or disabled (even when connected by WiFi) in iOS?

I have an app that I want to be able to use to get a connection status report after some certain time interval. Even when I am connected or associated to a Wifi network, I would like to know if data access is enabled over cellular network. This means, if after a check, I can gracefully disassociate from the wifi network, knowing that there is an available cellular connection to which the device will get connected to.

Current Reachability methods will only give me information about the availability of cellular only when am connected to that and there is not much information about getting this data before actually connecting to the interface.

Looking for a similar solution as available in android, as explained in this link.

CLARIFICATION

I am NOT looking to see if my device is having cellular capabilities. I AM trying to establish whether or not the user has enabled / disabled Data access over mobile network, and would like to know this information even if I am connected to Wifi. User can turn this on and off by going to Settings.

Upvotes: 12

Views: 4432

Answers (7)

Ol Sen
Ol Sen

Reputation: 3348

my solution looks a 'little' like overkill, maybe someone find it useful. But the idea is to take users cellular data app settings into account.. It makes use of a singleton to avoid doubled initiation and keeps a BOOL to rely on and posts Notification that can be observed anywhere in the app.

//  MobileDataPolicy.h
#ifndef MobileDataPolicy_h
#define MobileDataPolicy_h

#import <Foundation/Foundation.h>
@import CoreTelephony;

NS_ASSUME_NONNULL_BEGIN

extern NSNotificationName const kMobileDataPolicyNotification;
extern NSString * const kMobileDataPolicyAllowedKey;

@interface MobileDataPolicy : NSObject
+(BOOL)isAllowed;
@end

NS_ASSUME_NONNULL_END

#endif

and

//  MobileDataPolicy.m
#import "MobileDataPolicy.h"

NSNotificationName const kMobileDataPolicyNotification = @"kMobileDataPolicyNotification";
NSString * const kMobileDataPolicyAllowedKey = @"kMobileDataPolicyAllowedKey";

@interface MobileDataPolicy ()
@property (nonatomic, readwrite) BOOL cellularDataAllowed;
@end

@implementation MobileDataPolicy

+(instancetype)singleton {
    static MobileDataPolicy * singletonInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        if ( !singletonInstance ) {
            singletonInstance = [[MobileDataPolicy alloc] initReal];
            if (singletonInstance) {
                [singletonInstance setupCellularDataPolicyHandler];
            }
        }
    });
    return singletonInstance;
}

_Pragma("clang diagnostic push")
_Pragma("clang diagnostic ignored \"-Wobjc-designated-initializers\"")
-(instancetype)init {
    NSAssert(NO, @"init is not the designated initializer for instances of MobileDataPolicy. use [MobileDataPolicy isAllowed]");
    return nil;
}
_Pragma("clang diagnostic pop")

-(instancetype)initReal {
    if (!(self=[super init])) return nil;
    _cellularDataAllowed = NO;
    return self;
}

// ask for policy with [MobileDataPolicy allowed]
+(BOOL)isAllowed {
    //we need only one handler per App.
    return [MobileDataPolicy singleton].cellularDataAllowed;
}

#pragma mark setup - Cellular Data Policy Handler
- (void)setupCellularDataPolicyHandler {
    if (@available(iOS 9.0, *)) {
        CTCellularData *cellularData = [[CTCellularData alloc] init];
        
        //following handler block will run on default priority global dispatch queue
        [cellularData setCellularDataRestrictionDidUpdateNotifier:^(CTCellularDataRestrictedState state) {
            switch (state) {
                case kCTCellularDataRestrictedStateUnknown: self->_cellularDataAllowed = NO;
                    break;
                case kCTCellularDataRestricted: self->_cellularDataAllowed = NO;
                    break;
                case kCTCellularDataNotRestricted: self->_cellularDataAllowed = YES;
                    break;
                default:
                    break;
            }
            [[NSNotificationCenter defaultCenter] postNotificationName:kMobileDataPolicyNotification object:nil userInfo:@{kMobileDataPolicyAllowedKey:@(state)}];
        }];
    }
}
@end

use in example like..

 //class method that inits singleton and returns state
 BOOL reachCellularData = MobileDataPolicy.isAllowed;
 NSLog(@"initial cellular data allowed for app = %@",reachCellularData ? @"YES" : @"NO");
 
 //start observing
 id<NSObject> noteKeeper = [[NSNotificationCenter defaultCenter] addObserverForName:kMobileDataPolicyNotification object:nil queue:[NSOperationQueue currentQueue] usingBlock:^(NSNotification * _Nonnull note) {
     // do something on cellular Data Policy State change..
     int cellularDataState = [note.userInfo[kMobileDataPolicyAllowedKey] intValue];
 }];
 
 //stop observing, possibly in -(void)dealloc
 [[NSNotificationCenter defaultCenter] removeObserver:noteKeeper];

Upvotes: 0

Diego Jim&#233;nez
Diego Jim&#233;nez

Reputation: 1526

Some time ago I tried to figure out how to solve this problem, and I am sad to say that there is no API no solve this question fast and in a simple way, so I decide to make a workaround. I thought then track the whole device cellular data usage, so if I store the cellular usage in my app, I can see if user has or not active cellular data on its device. I did it like this, first the extension:

extension SystemDataUsage {
    public static var wwanCompelete: UInt64 {
        return SystemDataUsage.getDataUsage().wirelessWanDataSent + SystemDataUsage.getDataUsage().wirelessWanDataReceived
    }
}

Then the class:

class SystemDataUsage {

    private static let wwanInterfacePrefix = "pdp_ip"

    class func getDataUsage() -> DataUsageInfo {
        var ifaddr: UnsafeMutablePointer<ifaddrs>?
        var dataUsageInfo = DataUsageInfo()

        guard getifaddrs(&ifaddr) == 0 else { return dataUsageInfo }
        while let addr = ifaddr {
            guard let info = getDataUsageInfo(from: addr) else {
                ifaddr = addr.pointee.ifa_next
                continue
            }
            dataUsageInfo.updateInfoByAdding(info)
            ifaddr = addr.pointee.ifa_next
        }

        freeifaddrs(ifaddr)

        return dataUsageInfo
    }

    private class func getDataUsageInfo(from infoPointer: UnsafeMutablePointer<ifaddrs>) -> DataUsageInfo? {
        let pointer = infoPointer
        let name: String! = String(cString: pointer.pointee.ifa_name)
        let addr = pointer.pointee.ifa_addr.pointee
        guard addr.sa_family == UInt8(AF_LINK) else { return nil }

        return dataUsageInfo(from: pointer, name: name)
    }

    private class func dataUsageInfo(from pointer: UnsafeMutablePointer<ifaddrs>, name: String) -> DataUsageInfo {
        var networkData: UnsafeMutablePointer<if_data>?
        var dataUsageInfo = DataUsageInfo()

        if name.hasPrefix(wwanInterfacePrefix) {
            networkData = unsafeBitCast(pointer.pointee.ifa_data, to: UnsafeMutablePointer<if_data>.self)
            if let data = networkData {
                dataUsageInfo.wirelessWanDataSent += UInt64(data.pointee.ifi_obytes)
                dataUsageInfo.wirelessWanDataReceived += UInt64(data.pointee.ifi_ibytes)
            }
        }
        return dataUsageInfo
    }
}

Last one the struct:

struct DataUsageInfo {
    var wirelessWanDataReceived: UInt64 = 0
    var wirelessWanDataSent: UInt64 = 0

    mutating func updateInfoByAdding(_ info: DataUsageInfo) {
        wirelessWanDataSent += info.wirelessWanDataSent
        wirelessWanDataReceived += info.wirelessWanDataReceived
    }
}

I used the code of this answer, you can check it too: Track cellular data usage using swift

Upvotes: 0

Alessandro Martin
Alessandro Martin

Reputation: 898

If you are targeting iOS 12 or later, Apple has introduced (as part of the Network framework) the NWPathMonitor class. You can (as I did) instantiate two different monitors, one for cellular and another one for wifi:

let wifiMonitor = NWPathMonitor(requiredInterfaceType: .wifi)
let cellularMonitor = NWPathMonitor(requiredInterfaceType: .cellular)

Supposing you have a class that keeps track of the two connection statuses with two simple Booleans, with the pathUpdateHandler properties you could do:

wifiMonitor.pathUpdateHandler = { path in
    self.isWifiConnected = path.status == .satisfied                
}
cellularMonitor.pathUpdateHandler = { path in 
    self.isCellularConnected = path.status == .satisfied
}

and then handle yourself as you prefer. Remember to kickoff the monitors by calling start(_:) and providing serial queues (I provided two separate queues); there's a quirk so that, once you cancel a monitor instance, you have to instantiate a new one because it cannot be restarted.

Upvotes: 7

user2428552
user2428552

Reputation: 81

There is no available api's by which the app can query whether mobile data is enabled. You can use CTCellularData's cellularDataRestrictionDidUpdateNotifier and restrictedState to know if user has enabled or disabled cellular data access for your application. That is the max iOS allows for an application. And even this is not reliable as if you remove the sim from the device it will still give you the earlier restricted state status.

Upvotes: 6

Andreas Oetjen
Andreas Oetjen

Reputation: 10199

I think you have to rethink about whatever you are planning: You'll never ever be able to determine if a reliable (data) connection will be available at any future point in time:

  • The user could have disabled data transfer via its cellular provider, so if WiFi goes down, the cellular connection still won't provide data.
  • Or the user could immediately change this in the settings.
  • Or the cellular connection could go down in the very same moment as the WiFi is disconnected (while entering a shielded room or sth.)
  • And many more things could happen, meanwhile in a secret room™

You'll simply have to deal with disconnectivity and provide a benevolent behavoir in such a case (instead of e.g. crashing).

Upvotes: 0

NAVEEN KUMAR
NAVEEN KUMAR

Reputation: 667

u can know all the scenarios:

 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNetworkChange:) name:kReachabilityChangedNotification object:nil];
        Reachability *reachablity=[Reachability reachabilityWithHostName:@"google.com"];

        [reachablity startNotifier];

        //    reachablity = [Reachability reachabilityForInternetConnection];

        NetworkStatus remoteHostStatus = [reachablity currentReachabilityStatus];
        if(remoteHostStatus == NotReachable) {
            NSLog(@"network not available ");


        }
        else if (remoteHostStatus == ReachableViaWiFi) {
            NSLog(@"connect with wifi");


        }
        else if (remoteHostStatus == ReachableViaWWAN) {
            NSLog(@"Cellulor network ");


        }
        return netwrokCheck;

Upvotes: -2

DraganescuValentin
DraganescuValentin

Reputation: 872

https://github.com/ashleymills/Reachability.swift Reachability has a method to determine if the network is reachable via WWAN

var isReachableViaWWAN: Bool {
    // Check we're not on the simulator, we're REACHABLE and check we're on WWAN
    return isRunningOnDevice && isReachableFlagSet && isOnWWANFlagSet
}

Upvotes: -1

Related Questions