獲取StatusBar
項(xiàng)目中通過StatusBar來獲取手機(jī)當(dāng)前狀態(tài)窄俏,但是在iOS 13中便獲取不到了洪鸭,調(diào)試了一下發(fā)現(xiàn)是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,那么到這里就已經(jīng)可以獲取到StatusBar了。
獲取網(wǎng)絡(luò)狀態(tài)
舊版本中不恭,獲取網(wǎng)絡(luò)狀態(tài)的代碼如下,原理就是獲取StatusBar中的網(wǎng)絡(luò)信號(hào)圖標(biāo)财饥,然后通過獲取信號(hào)圖標(biāo)來獲取網(wǎng)絡(luò)狀態(tài)换吧。
- (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中已經(jīng)可以獲取到StatusBar,但是不斷遞推[StatusBar subviews]時(shí)钥星,卻不能發(fā)現(xiàn)任何一個(gè)有關(guān)網(wǎng)絡(luò)信息的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乖篷,因?yàn)榭刂茖?dǎo)航欄信息的數(shù)據(jù)都存在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,如果你想查看全部的屬性透且,可以自己調(diào)試看看撕蔼,在這些屬性中豁鲤,我們可以看到這里有關(guān)于時(shí)間的dateEntry
和timeEntry
,還有關(guān)于網(wǎng)絡(luò)的cellularEntry
和wifiEntry
鲸沮,在所有的Entry
中都有isEnabled
屬性琳骡,只有當(dāng)isEnabled
為true
時(shí),這個(gè)屬性才有意義讼溺。通過判斷wifiEntry
是否可用楣号,來確定是否是WiFi,通過判斷cellularEntry
的type
來判斷具體是4G/3G怒坯,所以獲取網(wǎng)絡(luò)狀態(tài)的代碼如下:
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;
}
}
}
}
}
總結(jié)
完整的代碼如下炫狱,當(dāng)然你也可以查看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;
}