Get StatusBar

In the project, the current state of the phone can be obtained through StatusBar, but it cannot be obtained in iOS 13. After debugging, IT is found that THE StatusBar cannot be obtained by UIApplication.

    UIApplication *app = [UIApplication sharedApplication];
    id _statusBar = [app valueForKeyPath:@"_statusBar"];
Copy the code

Get the statusBar from UIStatusBarManager instead.

    UIStatusBarManager *statusBarManager = [UIApplication sharedApplication].keyWindow.windowScene.statusBarManager;
    id _statusBar = nil;
    if ([statusBarManager respondsToSelector:@selector(createLocalStatusBar)]) {
        UIView *_localStatusBar = [statusBarManager performSelector:@selector(createLocalStatusBar)];
        if([_localStatusBar respondsToSelector:@selector(statusBar)]) { _statusBar = [_localStatusBar performSelector:@selector(statusBar)]; }}Copy the code

If you just added a View to the StatusBar, you would have gotten the StatusBar at this point.

Obtaining network Status

In the old version, the code to obtain the network status is as follows. The principle is to obtain the network signal icon in the StatusBar, and then obtain the network status by obtaining the signal icon.

- (LLNetworkStatus)networkStateFromStatebar {
    __block LLNetworkStatus returnValue = LLNetworkStatusNotReachable;
    if(! [[NSThread currentThread] isMainThread]) { dispatch_sync(dispatch_get_main_queue(), ^{returnValue = [self networkStateFromStatebar];
        });
        return returnValue;
    }

    UIApplication *app = [UIApplication sharedApplication];
    id _statusBar = [app valueForKeyPath:@"_statusBar"];
        
    if ([_statusBar isKindOfClass:NSClassFromString(@"UIStatusBar_Modern")]) {
        // For iPhoneX
        NSArray *children = [[[_statusBar valueForKeyPath:@"_statusBar"] valueForKeyPath:@"foregroundView"] subviews];
        for (UIView *view in children) {
            for (id child in view.subviews) {
                if ([child isKindOfClass:NSClassFromString(@"_UIStatusBarWifiSignalView")]) {
                    returnValue = LLNetworkStatusReachableViaWiFi;
                    break;
                }
                if ([child isKindOfClass:NSClassFromString(@"_UIStatusBarStringView")]) {
                    NSString *originalText = [child valueForKey:@"_originalText"];
                    if ([originalText containsString:@"G"]) {
                        if ([originalText isEqualToString:@"2G"]) {
                            returnValue = LLNetworkStatusReachableViaWWAN2G;
                        } else if ([originalText isEqualToString:@"3G"]) {
                            returnValue = LLNetworkStatusReachableViaWWAN3G;
                        } else if ([originalText isEqualToString:@"4G"]) {
                            returnValue = LLNetworkStatusReachableViaWWAN4G;
                        } else {
                            returnValue = LLNetworkStatusReachableViaWWAN;
                        }
                        break;
                    }
                }
            }
        }
    } else {
        // For others iPhone
        NSArray *children = [[_statusBar valueForKeyPath:@"foregroundView"] subviews];
        int type= 1;for (id child in children) {
            if ([child isKindOfClass:[NSClassFromString(@"UIStatusBarDataNetworkItemView") class]]) {
                type = [[child valueForKeyPath:@"dataNetworkType"] intValue];
            }
        }
        switch (type) {
            caseZero:returnValue = LLNetworkStatusNotReachable;
                break;
            case 1:
                returnValue = LLNetworkStatusReachableViaWWAN2G;
                break;
            case 2:
                returnValue = LLNetworkStatusReachableViaWWAN3G;
                break;
            case 3:
                returnValue = LLNetworkStatusReachableViaWWAN4G;
                break;
            case 4:
                returnValue = LLNetworkStatusReachableViaWWAN;
                break;
            case 5:
                returnValue = LLNetworkStatusReachableViaWiFi;
                break;
            default:
                break; }}return returnValue;
}
Copy the code

Although StatusBar can be obtained in iOS 13, no View related to network information can be found when constantly recursively [StatusBar subviews], so the old way is not applicable to iOS 13, so we print out all the attributes in StatusBar. Look for the next thought.

(lldb) po [[[_statusBar valueForKeyPath:@"_statusBar"] class] LL_getPropertyNames]
<__NSArrayM 0x600000192be0>(
items,
displayItemStates,
updateCompletionHandler,
foregroundView,
targetActionable,
accessibilityHUDGestureManager,
visualProviderClassName,
visualProviderClass,
visualProvider,
regions,
dataAggregator,
currentAggregatedData,
containerView,
animationContextId,
animationsEnabled,
styleAttributes,
action,
targetScreen,
style,
foregroundColor,
mode,
orientation,
currentData,
dependentDataEntryKeys,
overlayData,
actionGestureRecognizer,
enabledPartIdentifiers,
avoidanceFrame,
hash,
superclass,
description,
debugDescription
)
Copy the code

In the printed properties, we just need to parse currentData. (Why only currentData, because the data that controls navigation bar information is in currentData)

(lldb) po [[_statusBar valueForKeyPath:@"_statusBar"] valueForKeyPath:@"currentData"]
<_UIStatusBarData: 0x7fdc464362e0: 

mainBatteryEntry=<_UIStatusBarDataBatteryEntry: 0x600000187c30: isEnabled=1, capacity=100, state=2, saverModeActive=0, prominentlyShowsDetailString=0, detailString=100%>, 

secondaryCellularEntry=<_UIStatusBarDataCellularEntry: 0x600002b25440: isEnabled=1, rawValue=0, displayValue=0, displayRawValue=0, status=0, lowDataModeActive=0, type=5, wifiCallingEnabled=0, callForwardingEnabled=0, showsSOSWhenDisabled=0>,

dateEntry=<_UIStatusBarDataStringEntry: 0x600000f17f00: isEnabled=1, stringValue=Tue Aug 27>,

timeEntry=<_UIStatusBarDataStringEntry: 0x600000f17640: isEnabled=1, stringValue=6:34 PM>,

cellularEntry=<_UIStatusBarDataCellularEntry: 0x600002b254a0: isEnabled=1, rawValue=0, displayValue=0, displayRawValue=0, status=1, lowDataModeActive=0, type=5, string=Carrier, wifiCallingEnabled=0, callForwardingEnabled=0, showsSOSWhenDisabled=0>,

wifiEntry=<_UIStatusBarDataWifiEntry: 0x600001aa1c40: isEnabled=1, rawValue=0, displayValue=3, displayRawValue=0, status=5, lowDataModeActive=0, type=0>,

shortTimeEntry=<_UIStatusBarDataStringEntry: 0x600000f16ac0: isEnabled=1, stringValue=6:34>,

// some descriptions.

Copy the code

This is just a partial log, but if you want to see all the properties, you can debug them yourself, and among the properties, we can see dateEntry and timeEntry for time, cellularEntry and wifiEntry for network, There is an isEnabled attribute in all entries. This attribute is only meaningful if isEnabled is true. WiFi can be determined by judging whether wifiEntry is available, and 4G/3G can be determined by judging the type of cellularEntry. Therefore, the code for obtaining network status is as follows:

id _statusBar = nil;
    if(@available(iOS 13.0, *)) {/* We can still get statusBar using the following code, but this is not recommended. */#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
        UIStatusBarManager *statusBarManager = [UIApplication sharedApplication].keyWindow.windowScene.statusBarManager;
        if ([statusBarManager respondsToSelector:@selector(createLocalStatusBar)]) {
            UIView *_localStatusBar = [statusBarManager performSelector:@selector(createLocalStatusBar)];
            if([_localStatusBar respondsToSelector:@selector(statusBar)]) { _statusBar = [_localStatusBar performSelector:@selector(statusBar)]; }}#pragma clang diagnostic pop
        if (_statusBar) {
            // _UIStatusBarDataCellularEntry
            id currentData = [[_statusBar valueForKeyPath:@"_statusBar"] valueForKeyPath:@"currentData"];
            id _wifiEntry = [currentData valueForKeyPath:@"wifiEntry"];
            id _cellularEntry = [currentData valueForKeyPath:@"cellularEntry"];
            if (_wifiEntry && [[_wifiEntry valueForKeyPath:@"isEnabled"] boolValue]) {
                // If wifiEntry is enabled, is WiFi.
                returnValue = LLNetworkStatusReachableViaWiFi;
            } else if (_cellularEntry && [[_cellularEntry valueForKeyPath:@"isEnabled"] boolValue]) {
                NSNumber *type = [_cellularEntry valueForKeyPath:@"type"];
                if (type) {
                    switch (type.integerValue) {
                        case 5:
                            returnValue = LLNetworkStatusReachableViaWWAN4G;
                            break;
                        case 4:
                            returnValue = LLNetworkStatusReachableViaWWAN3G;
                            break;
                            //                        case 1: // Return 1 when 1G.
                            //                            break;
                        case 0:
                            // Return 0 when no sim card.
                            returnValue = LLNetworkStatusNotReachable;
                        default:
                            returnValue = LLNetworkStatusReachableViaWWAN;
                            break;
                    }
                }
            }
        }
    }
Copy the code

conclusion

The complete code is shown below, but you can also check LLDebugTool -llNetworkHelper. m to see the code.

- (LLNetworkStatus)networkStateFromStatebar {
    __block LLNetworkStatus returnValue = LLNetworkStatusNotReachable;
    if(! [[NSThread currentThread] isMainThread]) { dispatch_sync(dispatch_get_main_queue(), ^{returnValue = [self networkStateFromStatebar];
        });
        return returnValue;
    }
    id _statusBar = nil;
    if(@available(iOS 13.0, *)) {/* We can still get statusBar using the following code, but this is not recommended. */#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
        UIStatusBarManager *statusBarManager = [UIApplication sharedApplication].keyWindow.windowScene.statusBarManager;
        if ([statusBarManager respondsToSelector:@selector(createLocalStatusBar)]) {
            UIView *_localStatusBar = [statusBarManager performSelector:@selector(createLocalStatusBar)];
            if([_localStatusBar respondsToSelector:@selector(statusBar)]) { _statusBar = [_localStatusBar performSelector:@selector(statusBar)]; }}#pragma clang diagnostic pop
        if (_statusBar) {
            // _UIStatusBarDataCellularEntry
            id currentData = [[_statusBar valueForKeyPath:@"_statusBar"] valueForKeyPath:@"currentData"];
            id _wifiEntry = [currentData valueForKeyPath:@"wifiEntry"];
            id _cellularEntry = [currentData valueForKeyPath:@"cellularEntry"];
            if (_wifiEntry && [[_wifiEntry valueForKeyPath:@"isEnabled"] boolValue]) {
                // If wifiEntry is enabled, is WiFi.
                returnValue = LLNetworkStatusReachableViaWiFi;
            } else if (_cellularEntry && [[_cellularEntry valueForKeyPath:@"isEnabled"] boolValue]) {
                NSNumber *type = [_cellularEntry valueForKeyPath:@"type"];
                if (type) {
                    switch (type.integerValue) {
                        case 5:
                            returnValue = LLNetworkStatusReachableViaWWAN4G;
                            break;
                        case 4:
                            returnValue = LLNetworkStatusReachableViaWWAN3G;
                            break;
                            //                        case 1: // Return 1 when 1G.
                            //                            break;
                        case 0:
                            // Return 0 when no sim card.
                            returnValue = LLNetworkStatusNotReachable;
                        default:
                            returnValue = LLNetworkStatusReachableViaWWAN;
                            break;
                    }
                }
            }
        }
    } else {
        UIApplication *app = [UIApplication sharedApplication];
        _statusBar = [app valueForKeyPath:@"_statusBar"];
        
        if ([_statusBar isKindOfClass:NSClassFromString(@"UIStatusBar_Modern")]) {
            // For iPhoneX
            NSArray *children = [[[_statusBar valueForKeyPath:@"_statusBar"] valueForKeyPath:@"foregroundView"] subviews];
            for (UIView *view in children) {
                for (id child in view.subviews) {
                    if ([child isKindOfClass:NSClassFromString(@"_UIStatusBarWifiSignalView")]) {
                        returnValue = LLNetworkStatusReachableViaWiFi;
                        break;
                    }
                    if ([child isKindOfClass:NSClassFromString(@"_UIStatusBarStringView")]) {
                        NSString *originalText = [child valueForKey:@"_originalText"];
                        if ([originalText containsString:@"G"]) {
                            if ([originalText isEqualToString:@"2G"]) {
                                returnValue = LLNetworkStatusReachableViaWWAN2G;
                            } else if ([originalText isEqualToString:@"3G"]) {
                                returnValue = LLNetworkStatusReachableViaWWAN3G;
                            } else if ([originalText isEqualToString:@"4G"]) {
                                returnValue = LLNetworkStatusReachableViaWWAN4G;
                            } else {
                                returnValue = LLNetworkStatusReachableViaWWAN;
                            }
                            break;
                        }
                    }
                }
            }
        } else {
            // For others iPhone
            NSArray *children = [[_statusBar valueForKeyPath:@"foregroundView"] subviews];
            int type= 1;for (id child in children) {
                if ([child isKindOfClass:[NSClassFromString(@"UIStatusBarDataNetworkItemView") class]]) {
                    type = [[child valueForKeyPath:@"dataNetworkType"] intValue];
                }
            }
            switch (type) {
                caseZero:returnValue = LLNetworkStatusNotReachable;
                    break;
                case 1:
                    returnValue = LLNetworkStatusReachableViaWWAN2G;
                    break;
                case 2:
                    returnValue = LLNetworkStatusReachableViaWWAN3G;
                    break;
                case 3:
                    returnValue = LLNetworkStatusReachableViaWWAN4G;
                    break;
                case 4:
                    returnValue = LLNetworkStatusReachableViaWWAN;
                    break;
                case 5:
                    returnValue = LLNetworkStatusReachableViaWiFi;
                    break;
                default:
                    break; }}}return returnValue;
}
Copy the code