获取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,如果你想查看全部的属性,可以自己调试看看,在这些属性中,我们可以看到这里有关于时间的dateEntry
和timeEntry
,还有关于网络的cellularEntry
和wifiEntry
,在所有的Entry
中都有isEnabled
属性,只有当isEnabled
为true
时,这个属性才有意义。通过判断wifiEntry
是否可用,来确定是否是WiFi,通过判断cellularEntry
的type
来判断具体是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;
}