美文网首页iOS开发iOS技术收藏
iOS13 获取StatusBar并且获取网络状态

iOS13 获取StatusBar并且获取网络状态

作者: HDB_Li | 来源:发表于2019-08-27 18:50 被阅读0次

    获取StatusBar

    项目中通过StatusBar来获取手机当前状态,但是在iOS 13中便获取不到了,调试了一下发现是UIApplication无法获取到statusBar。

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

    于是改成如下的方式通过UIStatusBarManager获取statusBar。

        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)];
            }
        }
    

    如果只是往StatusBar上添加View,那么到这里就已经可以获取到StatusBar了。

    获取网络状态

    旧版本中,获取网络状态的代码如下,原理就是获取StatusBar中的网络信号图标,然后通过获取信号图标来获取网络状态。

    - (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) {
                case 0:
                    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;
    }
    

    虽然在iOS 13中已经可以获取到StatusBar,但是不断递推[StatusBar subviews]时,却不能发现任何一个有关网络信息的View,所以旧的方式并不适用与iOS 13,所以我们打印出StatusBar中所有的属性,查找接下来的思路。

    (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
    )
    

    在打印的属性中,我们只需要具体分析currentData就可以。(为什么只分析currentData,因为控制导航栏信息的数据都存在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.
    
    

    这里只是展示了一部分log,如果你想查看全部的属性,可以自己调试看看,在这些属性中,我们可以看到这里有关于时间的dateEntrytimeEntry,还有关于网络的cellularEntrywifiEntry,在所有的Entry中都有isEnabled属性,只有当isEnabledtrue时,这个属性才有意义。通过判断wifiEntry是否可用,来确定是否是WiFi,通过判断cellularEntrytype来判断具体是4G/3G,所以获取网络状态的代码如下:

    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;
                        }
                    }
                }
            }
        }
    

    总结

    完整的代码如下,当然你也可以查看LLDebugTool - LLNetworkHelper.m 来查看具体的代码。

    - (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) {
                    case 0:
                        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;
    }
    

    相关文章

      网友评论

        本文标题:iOS13 获取StatusBar并且获取网络状态

        本文链接:https://www.haomeiwen.com/subject/fxkyectx.html