LKS_LocalInspectViewController.m 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745
  1. #ifdef SHOULD_COMPILE_LOOKIN_SERVER
  2. //
  3. // LKS_LocalInspectViewController.m
  4. // LookinServer
  5. //
  6. // Created by Li Kai on 2019/5/15.
  7. // https://lookin.work
  8. //
  9. #import "LKS_LocalInspectViewController.h"
  10. #import "LKS_LocalInspectPanelLabelView.h"
  11. #import "LookinIvarTrace.h"
  12. #import "LookinHierarchyInfo.h"
  13. #import "UIImage+LookinServer.h"
  14. #import "LookinServerDefines.h"
  15. #import "UIColor+LookinServer.h"
  16. #import "LKSConfigManager.h"
  17. static CGRect const kInvalidRect = (CGRect){-2, -2, 0, 0};
  18. @interface LKS_LocalInspectViewController ()
  19. @property(nonatomic, strong) CALayer *highlightLayer;
  20. @property(nonatomic, strong) CALayer *referLayer;
  21. @property(nonatomic, assign) CGRect highlightRect;
  22. @property(nonatomic, assign) CGRect referRect;
  23. @property(nonatomic, copy) NSArray<CALayer *> *rulerLayers;
  24. @property(nonatomic, copy) NSArray<UILabel *> *rulerLabels;
  25. @property(nonatomic, strong) UIButton *titleButton;
  26. @property(nonatomic, strong) UIView *panelView;
  27. @property(nonatomic, strong) LKS_LocalInspectPanelLabelView *titleLabelView;
  28. @property(nonatomic, strong) NSArray<LKS_LocalInspectPanelLabelView *> *contentLabelViews;
  29. @property(nonatomic, strong) UITapGestureRecognizer *tapRecognizer;
  30. @property(nonatomic, strong) UIPanGestureRecognizer *panRecognizer;
  31. @end
  32. @implementation LKS_LocalInspectViewController {
  33. CGFloat _panelContentsMarginTop;
  34. CGFloat _panelInsetTop;
  35. CGFloat _panelInsetBottom;
  36. }
  37. - (void)viewDidLoad {
  38. [super viewDidLoad];
  39. _panelContentsMarginTop = 3;
  40. _panelInsetTop = 1;
  41. _panelInsetBottom = 5;
  42. self.view.layer.lks_isLookinPrivateLayer = YES;
  43. self.highlightLayer = [CALayer layer];
  44. self.highlightLayer.backgroundColor = [UIColor colorWithRed:69/255.0 green:143/255.0 blue:208/255.0 alpha:.4].CGColor;
  45. [self.highlightLayer lookin_removeImplicitAnimations];
  46. [self.view.layer addSublayer:self.highlightLayer];
  47. self.referLayer = [CALayer layer];
  48. self.referLayer.backgroundColor = [UIColor colorWithRed:69/255.0 green:143/255.0 blue:208/255.0 alpha:.09].CGColor;
  49. [self.referLayer lookin_removeImplicitAnimations];
  50. [self.view.layer addSublayer:self.referLayer];
  51. self.titleButton = [UIButton new];
  52. self.titleButton.hidden = YES;
  53. self.titleButton.clipsToBounds = YES;
  54. self.titleButton.contentEdgeInsets = UIEdgeInsetsMake(6, 10, 6, 10);
  55. self.titleButton.layer.backgroundColor = [UIColor colorWithRed:208/255.0 green:2/255.0 blue:27/255.0 alpha:9].CGColor;
  56. [self.titleButton addTarget:self action:@selector(_handleExitButton) forControlEvents:UIControlEventTouchUpInside];
  57. [self.titleButton setAttributedTitle:({
  58. NSAttributedString *str1 = [[NSAttributedString alloc] initWithString:LKS_Localized(@"Tap or swipe to inspect") attributes:@{NSForegroundColorAttributeName:[UIColor whiteColor], NSFontAttributeName:[UIFont boldSystemFontOfSize:13]}];
  59. NSAttributedString *str2 = [[NSAttributedString alloc] initWithString:@" | " attributes:@{NSForegroundColorAttributeName:[UIColor colorWithWhite:1 alpha:.5], NSFontAttributeName:[UIFont systemFontOfSize:13]}];
  60. NSAttributedString *str3 = [[NSAttributedString alloc] initWithString:LKS_Localized(@"Exit") attributes:@{NSForegroundColorAttributeName:[UIColor whiteColor], NSFontAttributeName:[UIFont boldSystemFontOfSize:13]}];
  61. NSMutableAttributedString *combinedStr = [NSMutableAttributedString new];
  62. [combinedStr appendAttributedString:str1];
  63. [combinedStr appendAttributedString:str2];
  64. [combinedStr appendAttributedString:str3];
  65. combinedStr;
  66. }) forState:UIControlStateNormal];
  67. [self.view addSubview:self.titleButton];
  68. self.rulerLayers = [NSArray lookin_arrayWithCount:4 block:^id(NSUInteger idx) {
  69. CALayer *layer = [CALayer new];
  70. [layer lookin_removeImplicitAnimations];
  71. layer.backgroundColor = [UIColor colorWithRed:69/255.0 green:143/255.0 blue:208/255.0 alpha:.4].CGColor;
  72. [self.view.layer addSublayer:layer];
  73. return layer;
  74. }];
  75. self.rulerLabels = [NSArray lookin_arrayWithCount:4 block:^id(NSUInteger idx) {
  76. UILabel *label = [UILabel new];
  77. label.userInteractionEnabled = NO;
  78. label.backgroundColor = [UIColor colorWithRed:26/255.0 green:154/255.0 blue:251/255.0 alpha:1];
  79. label.font = [UIFont systemFontOfSize:10];
  80. label.textColor = [UIColor whiteColor];
  81. label.textAlignment = NSTextAlignmentCenter;
  82. label.clipsToBounds = YES;
  83. [self.view addSubview:label];
  84. return label;
  85. }];
  86. self.panelView = [UIView new];
  87. self.panelView.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:.9];
  88. self.panelView.layer.borderColor = [UIColor lightGrayColor].CGColor;
  89. self.panelView.layer.borderWidth = 1 / [[UIScreen mainScreen] scale];
  90. self.panelView.userInteractionEnabled = NO;
  91. self.panelView.layer.cornerRadius = 5;
  92. self.panelView.hidden = YES;
  93. [self.view addSubview:self.panelView];
  94. self.titleLabelView = [LKS_LocalInspectPanelLabelView new];
  95. self.titleLabelView.verInset = 10;
  96. self.titleLabelView.leftLabel.font = [UIFont boldSystemFontOfSize:13];
  97. self.titleLabelView.rightLabel.font = [UIFont systemFontOfSize:13];
  98. self.titleLabelView.leftLabel.textColor = [UIColor blackColor];
  99. self.titleLabelView.rightLabel.textColor = [UIColor blackColor];
  100. self.titleLabelView.interspace = 20;
  101. [self.titleLabelView addBottomBorderLayer];
  102. [self.panelView addSubview:self.titleLabelView];
  103. self.contentLabelViews = [NSArray array];
  104. self.tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(_handleTapRecognizer:)];
  105. [self.view addGestureRecognizer:self.tapRecognizer];
  106. self.panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(_handlePanRecognizer:)];
  107. [self.view addGestureRecognizer:self.panRecognizer];
  108. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleEnterForegound) name:UIApplicationWillEnterForegroundNotification object:nil];
  109. }
  110. - (void)viewDidLayoutSubviews {
  111. [super viewDidLayoutSubviews];
  112. self.highlightLayer.frame = self.highlightRect;
  113. self.referLayer.frame = self.referRect;
  114. if (!self.panelView.hidden) {
  115. CGRect contentRect = CGRectEqualToRect(kInvalidRect, self.referRect) ? self.highlightRect : self.referRect;
  116. [self _layoutPanelViewWithContentRect:contentRect];
  117. }
  118. [self _renderAndLayoutRulersWithHighlightRect:self.highlightRect referRect:self.referRect];
  119. if (!self.titleButton.hidden) {
  120. [self _layoutTitleButtonReferToPanelView];
  121. }
  122. }
  123. - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
  124. [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
  125. [self clearContents];
  126. }
  127. - (void)dealloc {
  128. [[NSNotificationCenter defaultCenter] removeObserver:self];
  129. }
  130. - (void)highlightLayer:(CALayer *)layer {
  131. BOOL isValidLayer = ([layer isKindOfClass:[CALayer class]] && self.view.window && layer.lks_window);
  132. if (!isValidLayer) {
  133. self.highlightRect = kInvalidRect;
  134. self.referRect = kInvalidRect;
  135. self.panelView.hidden = YES;
  136. [self.view setNeedsLayout];
  137. return;
  138. }
  139. self.highlightRect = [layer lks_frameInWindow:self.view.window];
  140. self.referRect = ({
  141. CALayer *referLayer = nil;
  142. CALayer *temp_referLayer = layer.superlayer;
  143. while (temp_referLayer) {
  144. if (temp_referLayer && !CGRectEqualToRect(temp_referLayer.bounds, layer.frame)) {
  145. referLayer = temp_referLayer;
  146. break;
  147. }
  148. temp_referLayer = temp_referLayer.superlayer;
  149. }
  150. referLayer ? [referLayer lks_frameInWindow:self.view.window] : kInvalidRect;
  151. });
  152. self.titleLabelView.leftLabel.text = [self _titleStringForLayer:layer];
  153. self.titleLabelView.rightLabel.text = [self _subtitleStringForLayer:layer];
  154. NSArray<NSArray<NSString *> *> *contents = [self _contentStringsForLayer:layer];
  155. self.contentLabelViews = [self.contentLabelViews lookin_resizeWithCount:contents.count add:^LKS_LocalInspectPanelLabelView *(NSUInteger idx) {
  156. LKS_LocalInspectPanelLabelView *view = [LKS_LocalInspectPanelLabelView new];
  157. view.verInset = 4;
  158. view.leftLabel.font = [UIFont systemFontOfSize:13];
  159. view.rightLabel.font = [UIFont systemFontOfSize:13];
  160. view.leftLabel.textColor = [UIColor grayColor];
  161. view.rightLabel.textColor = [UIColor blackColor];
  162. [self.panelView addSubview:view];
  163. return view;
  164. } remove:^(NSUInteger idx, LKS_LocalInspectPanelLabelView *obj) {
  165. [obj removeFromSuperview];
  166. } doNext:^(NSUInteger idx, LKS_LocalInspectPanelLabelView *obj) {
  167. NSArray<NSString *> *strings = contents[idx];
  168. obj.leftLabel.text = strings.firstObject;
  169. obj.rightLabel.text = strings.lastObject;
  170. }];
  171. self.panelView.hidden = NO;
  172. [self.view setNeedsLayout];
  173. }
  174. - (void)setShowTitleButton:(BOOL)showTitleButton {
  175. _showTitleButton = showTitleButton;
  176. if (showTitleButton) {
  177. self.titleButton.hidden = NO;
  178. [self _layoutTitleButtonReferToPanelView];
  179. } else {
  180. self.titleButton.hidden = YES;
  181. [self.titleButton.layer removeAllAnimations];
  182. }
  183. }
  184. - (void)startTitleButtonAnimIfNeeded {
  185. if (self.titleButton.hidden) {
  186. return;
  187. }
  188. CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"backgroundColor"];
  189. anim.fromValue = (id)[UIColor colorWithRed:208/255.0 green:2/255.0 blue:27/255.0 alpha:9].CGColor;
  190. anim.toValue = (id)[UIColor colorWithRed:208/255.0 green:2/255.0 blue:27/255.0 alpha:.7].CGColor;
  191. anim.duration = .8;
  192. anim.repeatCount = HUGE_VALF;
  193. anim.autoreverses = YES;
  194. [self.titleButton.layer removeAllAnimations];
  195. [self.titleButton.layer addAnimation:anim forKey:nil];
  196. }
  197. #pragma mark - Setter
  198. - (void)setReferRect:(CGRect)referRect {
  199. _referRect = referRect;
  200. [self _didSetReferOrHighlightLayer];
  201. }
  202. - (void)setHighlightRect:(CGRect)highlightRect {
  203. _highlightRect = highlightRect;
  204. [self _didSetReferOrHighlightLayer];
  205. }
  206. - (void)_didSetReferOrHighlightLayer {
  207. if (!CGRectEqualToRect(kInvalidRect, self.referRect) && !CGRectIntersectsRect(self.referRect, self.highlightRect)) {
  208. self.referLayer.backgroundColor = [UIColor colorWithRed:69/255.0 green:143/255.0 blue:208/255.0 alpha:.4].CGColor;
  209. } else {
  210. self.referLayer.backgroundColor = [UIColor colorWithRed:69/255.0 green:143/255.0 blue:208/255.0 alpha:.07].CGColor;
  211. }
  212. }
  213. #pragma mark - Layout
  214. - (void)_layoutPanelViewWithContentRect:(CGRect)contentRect {
  215. if (CGRectEqualToRect(contentRect, CGRectNull)) {
  216. self.panelView.frame = kInvalidRect;
  217. return;
  218. }
  219. CGSize containerSize = self.view.bounds.size;
  220. CGFloat panelWidth = self.titleLabelView.lks_bestWidth;
  221. panelWidth = [self.contentLabelViews lookin_reduceCGFloat:^CGFloat(CGFloat accumulator, NSUInteger idx, LKS_LocalInspectPanelLabelView *obj) {
  222. return MAX(accumulator, obj.lks_bestWidth);
  223. } initialAccumlator:panelWidth];
  224. CGFloat screenInset = 10;
  225. panelWidth = MIN(panelWidth, containerSize.width - screenInset * 2);
  226. self.titleLabelView.frame = CGRectMake(0, _panelInsetTop, panelWidth, self.titleLabelView.lks_bestHeight);
  227. __block CGFloat posY = CGRectGetMaxY(self.titleLabelView.frame) + _panelContentsMarginTop;
  228. [self.contentLabelViews enumerateObjectsUsingBlock:^(LKS_LocalInspectPanelLabelView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
  229. obj.frame = CGRectMake(0, posY, panelWidth, obj.lks_bestHeight);
  230. posY = CGRectGetMaxY(obj.frame);
  231. }];
  232. posY += _panelInsetBottom;
  233. CGSize panelSize = CGSizeMake(panelWidth, posY);
  234. CGFloat panelMargin = 10;
  235. CGFloat panelY = ({
  236. CGFloat y = 0;
  237. CGFloat panelMinY = screenInset;
  238. if (@available(iOS 11.0, tvOS 11.0, *)) {
  239. panelMinY = self.view.safeAreaInsets.top;
  240. }
  241. CGFloat panelMinBottomNeeded = screenInset;
  242. if (@available(iOS 11.0, tvOS 11.0, *)) {
  243. panelMinBottomNeeded = self.view.safeAreaInsets.bottom;
  244. }
  245. panelMinBottomNeeded += panelSize.height;
  246. if (contentRect.origin.y - panelSize.height >= panelMinY) {
  247. // 放到目标上方
  248. y = contentRect.origin.y - panelMargin - panelSize.height;
  249. } else {
  250. CGFloat targetBottom = containerSize.height - CGRectGetMaxY(contentRect);
  251. if (targetBottom > panelMinBottomNeeded) {
  252. // 放到目标下方
  253. y = CGRectGetMaxY(contentRect) + panelMargin;
  254. } else {
  255. // 放到目标内部的上方
  256. y = contentRect.origin.y + panelMargin;
  257. if (@available(iOS 11.0, tvOS 11.0, *)) {
  258. y = MAX(y, self.view.safeAreaInsets.top);
  259. }
  260. }
  261. }
  262. y;
  263. });
  264. CGFloat panelX = ({
  265. CGFloat x = 0;
  266. // 先尝试和目标居中
  267. x = CGRectGetMidX(contentRect) - panelSize.width / 2.0;
  268. if (x <= 0) {
  269. // 如果超出了左边屏幕,则挪到距离屏幕左边
  270. x = screenInset;
  271. } else if (x + panelSize.width > containerSize.width) {
  272. // 如果超出了右边屏幕,则挪到距离屏幕右边 labelMargin 的距离
  273. x = containerSize.width - screenInset - panelSize.width;
  274. }
  275. x;
  276. });
  277. self.panelView.frame = CGRectMake(panelX, panelY, panelSize.width, panelSize.height);
  278. }
  279. - (void)_renderAndLayoutRulersWithHighlightRect:(CGRect)highlightRect referRect:(CGRect)referRect {
  280. BOOL showRulers = !CGRectEqualToRect(highlightRect, kInvalidRect) && !CGRectEqualToRect(referRect, kInvalidRect);
  281. if (!showRulers) {
  282. [self.rulerLayers enumerateObjectsUsingBlock:^(CALayer * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
  283. obj.frame = CGRectZero;
  284. }];
  285. [self.rulerLabels enumerateObjectsUsingBlock:^(UILabel * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
  286. obj.frame = CGRectZero;
  287. }];
  288. return;
  289. }
  290. CGFloat horInset = 8;
  291. CGFloat verInset = 4;
  292. CALayer *layer = self.rulerLayers[0];
  293. UILabel *label = self.rulerLabels[0];
  294. // top
  295. if (CGRectGetMinY(highlightRect) > CGRectGetMinY(referRect)) {
  296. CGFloat value = 0;
  297. if (CGRectGetMinY(highlightRect) > CGRectGetMaxY(referRect)) {
  298. value = CGRectGetMinY(highlightRect) - CGRectGetMaxY(referRect);
  299. } else {
  300. value = CGRectGetMinY(highlightRect) - CGRectGetMinY(referRect);
  301. }
  302. layer.frame = CGRectMake(CGRectGetMidX(highlightRect), CGRectGetMinY(highlightRect) - value, 1, value);
  303. label.text = [NSString lookin_stringFromDouble:value decimal:1];;
  304. CGSize bestSize = label.lks_bestSize;
  305. CGSize adjustedSize = CGSizeMake(bestSize.width + horInset, bestSize.height + verInset);
  306. label.layer.cornerRadius = adjustedSize.height / 2.0;
  307. label.frame = CGRectMake(CGRectGetMaxX(layer.frame) + 2, CGRectGetMidY(layer.frame) - adjustedSize.height / 2.0, adjustedSize.width, adjustedSize.height);
  308. } else {
  309. layer.frame = CGRectZero;
  310. label.frame = CGRectZero;
  311. }
  312. // left
  313. layer = self.rulerLayers[1];
  314. label = self.rulerLabels[1];
  315. if (CGRectGetMinX(highlightRect) > CGRectGetMinX(referRect)) {
  316. CGFloat value = 0;
  317. if (CGRectGetMinX(highlightRect) > CGRectGetMaxX(referRect)) {
  318. value = CGRectGetMinX(highlightRect) - CGRectGetMaxX(referRect);
  319. } else {
  320. value = CGRectGetMinX(highlightRect) - CGRectGetMinX(referRect);
  321. }
  322. layer.frame = CGRectMake(CGRectGetMinX(highlightRect) - value, CGRectGetMidY(highlightRect), value, 1);
  323. label.text = [NSString lookin_stringFromDouble:value decimal:1];;
  324. CGSize bestSize = label.lks_bestSize;
  325. CGSize adjustedSize = CGSizeMake(bestSize.width + horInset, bestSize.height + verInset);
  326. label.layer.cornerRadius = adjustedSize.height / 2.0;
  327. label.frame = CGRectMake(CGRectGetMidX(layer.frame), CGRectGetMinY(layer.frame) - 2 - adjustedSize.height, adjustedSize.width, adjustedSize.height);
  328. } else {
  329. layer.frame = CGRectZero;
  330. label.frame = CGRectZero;
  331. }
  332. // bottom
  333. layer = self.rulerLayers[2];
  334. label = self.rulerLabels[2];
  335. if (CGRectGetMaxY(highlightRect) < CGRectGetMaxY(referRect)) {
  336. CGFloat value = 0;
  337. if (CGRectGetMaxY(highlightRect) < CGRectGetMinY(referRect)) {
  338. value = CGRectGetMinY(referRect) - CGRectGetMaxY(highlightRect);
  339. } else {
  340. value = CGRectGetMaxY(referRect) - CGRectGetMaxY(highlightRect);
  341. }
  342. layer.frame = CGRectMake(CGRectGetMidX(highlightRect), CGRectGetMaxY(highlightRect), 1, value);
  343. label.text = [NSString lookin_stringFromDouble:value decimal:1];;
  344. CGSize bestSize = label.lks_bestSize;
  345. CGSize adjustedSize = CGSizeMake(bestSize.width + horInset, bestSize.height + verInset);
  346. label.layer.cornerRadius = adjustedSize.height / 2.0;
  347. label.frame = CGRectMake(CGRectGetMaxX(layer.frame) + 2, CGRectGetMidY(layer.frame) - adjustedSize.height / 2.0, adjustedSize.width, adjustedSize.height);
  348. } else {
  349. layer.frame = CGRectZero;
  350. label.frame = CGRectZero;
  351. }
  352. // right
  353. layer = self.rulerLayers[3];
  354. label = self.rulerLabels[3];
  355. if (CGRectGetMaxX(highlightRect) < CGRectGetMaxX(referRect)) {
  356. CGFloat value = 0;
  357. if (CGRectGetMaxX(highlightRect) < CGRectGetMinX(referRect)) {
  358. value = CGRectGetMinX(referRect) - CGRectGetMaxX(highlightRect);
  359. } else {
  360. value = CGRectGetMaxX(referRect) - CGRectGetMaxX(highlightRect);
  361. }
  362. layer.frame = CGRectMake(CGRectGetMaxX(highlightRect), CGRectGetMidY(highlightRect), value, 1);
  363. label.text = [NSString lookin_stringFromDouble:value decimal:1];;
  364. CGSize bestSize = label.lks_bestSize;
  365. CGSize adjustedSize = CGSizeMake(bestSize.width + horInset, bestSize.height + verInset);
  366. label.layer.cornerRadius = adjustedSize.height / 2.0;
  367. label.frame = CGRectMake(CGRectGetMidX(layer.frame) - adjustedSize.width / 2.0, CGRectGetMinY(layer.frame) - 2 - adjustedSize.height, adjustedSize.width, adjustedSize.height);
  368. } else {
  369. layer.frame = CGRectZero;
  370. label.frame = CGRectZero;
  371. }
  372. }
  373. - (void)_layoutTitleButtonReferToPanelView {
  374. /**
  375. 0: 放到屏幕顶部或底部都可以
  376. 1:需要放到屏幕底部
  377. 2: 需要放到屏幕顶部
  378. */
  379. NSUInteger positionType = 0;
  380. if (self.panelView.hidden) {
  381. positionType = 0;
  382. } else {
  383. if (CGRectGetMinY(self.panelView.frame) <= 200) {
  384. positionType = 1;
  385. } else if (CGRectGetMaxY(self.panelView.frame) >= self.view.bounds.size.height - 200) {
  386. positionType = 2;
  387. } else {
  388. positionType = 0;
  389. }
  390. }
  391. if (positionType == 0) {
  392. if (self.titleButton.frame.origin.y <= 0 || self.titleButton.frame.origin.y > self.view.bounds.size.height / 2.0) {
  393. positionType = 1;
  394. } else {
  395. positionType = 2;
  396. }
  397. }
  398. self.titleButton.frame = ({
  399. CGSize size = [self.titleButton sizeThatFits:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)];
  400. CGFloat x = self.view.bounds.size.width / 2.0 - size.width / 2.0;
  401. CGFloat y;
  402. if (positionType == 1) {
  403. // 放到屏幕底部
  404. if (@available(iOS 11.0, tvOS 11.0, *)) {
  405. y = self.view.bounds.size.height - size.height - MAX(self.view.safeAreaInsets.bottom, 20);
  406. } else {
  407. y = self.view.bounds.size.height - size.height - 20;
  408. }
  409. } else {
  410. NSAssert(positionType == 2, @"");
  411. // 放到屏幕顶部
  412. if (@available(iOS 11.0, tvOS 11.0, *)) {
  413. y = MAX(self.view.safeAreaInsets.top, 20);
  414. } else {
  415. y = 20;
  416. }
  417. }
  418. CGRectMake(x, y, size.width, size.height);
  419. });
  420. self.titleButton.layer.cornerRadius = self.titleButton.bounds.size.height / 2.0;
  421. }
  422. #pragma mark - Event Handler
  423. - (void)_handleTapRecognizer:(UITapGestureRecognizer *)recognizer {
  424. CGPoint point = [recognizer locationInView:self.view];
  425. UIView *view = [self _targetViewAtPoint:point];
  426. if (view) {
  427. [self highlightLayer:view.layer];
  428. } else {
  429. self.highlightRect = kInvalidRect;
  430. self.referRect = kInvalidRect;
  431. [self.view setNeedsLayout];
  432. NSLog(@"LookinServer - No valid view was found at tap position %@ in 2D inspecting.", NSStringFromCGPoint(point));
  433. }
  434. }
  435. - (void)_handlePanRecognizer:(UIPanGestureRecognizer *)recognizer {
  436. self.panelView.hidden = YES;
  437. if (recognizer.state == UIGestureRecognizerStateBegan) {
  438. CGPoint point = [recognizer locationInView:self.view];
  439. UIView *view = [self _targetViewAtPoint:point];
  440. if (view) {
  441. CGRect newHighlightRect = [view.layer lks_frameInWindow:self.view.window];
  442. CGFloat offsetX = ABS(CGRectGetMidX(self.highlightRect) - CGRectGetMidX(newHighlightRect));
  443. CGFloat offsetY = ABS(CGRectGetMidY(self.highlightRect) - CGRectGetMidY(newHighlightRect));
  444. if (offsetX > 200 || offsetY > 200) {
  445. self.highlightRect = newHighlightRect;
  446. }
  447. }
  448. self.referRect = kInvalidRect;
  449. } else if (recognizer.state == UIGestureRecognizerStateChanged) {
  450. if (CGRectEqualToRect(kInvalidRect, self.highlightRect)) {
  451. return;
  452. }
  453. CGPoint point = [recognizer locationInView:self.view];
  454. UIView *endView = [self _targetViewAtPoint:point];
  455. if (endView) {
  456. self.referRect = [endView.layer lks_frameInWindow:self.view.window];
  457. } else {
  458. self.referRect = kInvalidRect;
  459. }
  460. [self.view setNeedsLayout];
  461. }
  462. }
  463. - (void)_handleExitButton {
  464. if (self.didSelectExit) {
  465. self.didSelectExit();
  466. }
  467. }
  468. - (void)_handleEnterForegound {
  469. [self startTitleButtonAnimIfNeeded];
  470. }
  471. #pragma mark - Others
  472. /// 该 point 是在 self.view 的坐标系下
  473. - (UIView *)_targetViewAtPoint:(CGPoint)point {
  474. __block UIView *targetView = nil;
  475. [[UIApplication sharedApplication].windows enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(__kindof UIWindow * _Nonnull window, NSUInteger idx, BOOL * _Nonnull stop) {
  476. if (targetView) {
  477. *stop = YES;
  478. return;
  479. }
  480. if (self.includedWindows.count) {
  481. if (![self.includedWindows containsObject:window]) {
  482. return;
  483. }
  484. } else if ([self.excludedWindows containsObject:window]) {
  485. return;
  486. }
  487. if (window == self.view.window) {
  488. return;
  489. }
  490. if (window.hidden) {
  491. return;
  492. }
  493. CGPoint newPoint = [window convertPoint:point fromWindow:self.view.window];
  494. if (window == self.prevKeyWindow) {
  495. targetView = [window lks_subviewAtPoint:newPoint preferredClasses:[self _preferredClassesInSelecting]];
  496. } else {
  497. targetView = [window hitTest:point withEvent:nil];
  498. }
  499. }];
  500. if (!targetView) {
  501. return nil;
  502. }
  503. // 特殊处理一下
  504. if ([NSStringFromClass([targetView class]) isEqualToString:@"UITableViewCellContentView"] && [targetView.superview isKindOfClass:[UITableViewCell class]]) {
  505. targetView = targetView.superview;
  506. } else if ([targetView.superview isKindOfClass:[UITableView class]] && targetView == ((UITableView *)targetView.superview).backgroundView) {
  507. targetView = targetView.superview;
  508. }
  509. return targetView;
  510. }
  511. - (NSArray<Class> *)_preferredClassesInSelecting {
  512. static dispatch_once_t onceToken;
  513. static NSArray<Class> *classes = nil;
  514. dispatch_once(&onceToken,^{
  515. #if TARGET_OS_TV
  516. NSMutableArray<Class> *array = @[[UILabel class], [UIProgressView class], [UIActivityIndicatorView class], [UITextView class], [UITextField class], [UIVisualEffectView class]].mutableCopy;
  517. #else
  518. NSMutableArray<Class> *array = @[[UILabel class], [UIProgressView class], [UIActivityIndicatorView class], [UITextView class], [UITextField class], [UISlider class], [UISwitch class], [UIVisualEffectView class]].mutableCopy;
  519. #endif
  520. NSArray<NSString *> *custom = [LKSConfigManager collapsedClassList];
  521. if (custom.count) {
  522. NSArray<Class> *customClasses = [custom lookin_map:^id(NSUInteger idx, NSString *value) {
  523. return NSClassFromString(value);
  524. }];
  525. [array addObjectsFromArray:customClasses];
  526. }
  527. classes = array;
  528. });
  529. return classes;
  530. }
  531. - (void)clearContents {
  532. self.highlightRect = kInvalidRect;
  533. self.referRect = kInvalidRect;
  534. self.panelView.hidden = YES;
  535. [self.view setNeedsLayout];
  536. }
  537. #pragma mark - Strings
  538. - (NSString *)_titleStringForLayer:(CALayer *)layer {
  539. NSObject *targetObject = layer.lks_hostView ? : layer;
  540. NSString *classNameString = NSStringFromClass([targetObject class]) ? : @"";
  541. return classNameString;
  542. }
  543. - (NSString *)_subtitleStringForLayer:(CALayer *)layer {
  544. NSString *traceString = nil;
  545. NSObject *targetObject = layer.lks_hostView ? : layer;
  546. if (layer.lks_hostView.lks_hostViewController) {
  547. traceString = [NSString stringWithFormat:@"%@.view", NSStringFromClass([layer.lks_hostView.lks_hostViewController class])];
  548. } else {
  549. if (targetObject.lks_specialTrace.length) {
  550. traceString = targetObject.lks_specialTrace;
  551. } else if (targetObject.lks_ivarTraces.count) {
  552. traceString = [[[targetObject.lks_ivarTraces lookin_map:^id(NSUInteger idx, LookinIvarTrace *value) {
  553. return value.ivarName;
  554. }] lookin_nonredundantArray] componentsJoinedByString:@", "];
  555. }
  556. }
  557. return traceString;
  558. }
  559. - (NSArray<NSArray<NSString *> *> *)_contentStringsForLayer:(CALayer *)layer {
  560. NSMutableArray<NSArray<NSString *> *> *resultArray = [NSMutableArray array];
  561. [resultArray addObject:@[@"Frame", [NSString lookin_stringFromRect:layer.frame]]];
  562. if (layer.backgroundColor) {
  563. [resultArray addObject:@[@"BackgroundColor", [NSString lookin_rgbaStringFromColor:[UIColor lks_colorWithCGColor:layer.backgroundColor]]]];
  564. }
  565. if ([layer.lks_hostView isKindOfClass:[UIImageView class]]) {
  566. UIImage *image = ((UIImageView *)layer.lks_hostView).image;
  567. if (image.lks_imageSourceName.length) {
  568. [resultArray addObject:@[@"ImageName", [NSString stringWithFormat:@"\"%@\"", image.lks_imageSourceName]]];
  569. }
  570. } else if ([layer.lks_hostView isKindOfClass:[UIButton class]]) {
  571. UIButton *button = (UIButton *)layer.lks_hostView;
  572. // 不要直接访问 button.titleLabel。因为如果 title 不存在的话,访问 button.titleLabel 会触发初始化 titleLabel 进而触发 dynamic hierarhy push
  573. if ([button titleForState:UIControlStateNormal].length) {
  574. [resultArray addObject:@[@"FontSize", [NSString lookin_stringFromDouble:button.titleLabel.font.pointSize decimal:2]]];
  575. [resultArray addObject:@[@"FontName", button.titleLabel.font.fontName]];
  576. [resultArray addObject:@[@"TextColor", [NSString lookin_rgbaStringFromColor:button.titleLabel.textColor]]];
  577. }
  578. // 不要直接访问 button.imageView。因为如果 image 不存在的话,访问 button.image 会触发初始化 imageView 进而触发 dynamic hierarhy push
  579. if ([button imageForState:UIControlStateNormal]) {
  580. NSString *imageSourceName = button.imageView.image.lks_imageSourceName;
  581. if (imageSourceName.length) {
  582. [resultArray addObject:@[@"ImageName", [NSString stringWithFormat:@"\"%@\"", imageSourceName]]];
  583. }
  584. }
  585. } else if ([layer.lks_hostView isKindOfClass:[UILabel class]]) {
  586. UILabel *label = (UILabel *)layer.lks_hostView;
  587. [resultArray addObject:@[@"FontSize", [NSString lookin_stringFromDouble:label.font.pointSize decimal:2]]];
  588. [resultArray addObject:@[@"FontName", label.font.fontName]];
  589. [resultArray addObject:@[@"TextColor", [NSString lookin_rgbaStringFromColor:label.textColor]]];
  590. [resultArray addObject:@[@"NumberOfLines", [NSString stringWithFormat:@"%@", @(label.numberOfLines)]]];
  591. } else if ([layer.lks_hostView isKindOfClass:[UIScrollView class]]) {
  592. UIScrollView *scrollView = (UIScrollView *)layer.lks_hostView;
  593. [resultArray addObject:@[@"ContentSize", [NSString lookin_stringFromSize:scrollView.contentSize]]];
  594. [resultArray addObject:@[@"ContentOffset", [NSString lookin_stringFromPoint:scrollView.contentOffset]]];
  595. [resultArray addObject:@[@"ContentInset", [NSString lookin_stringFromInset:scrollView.contentInset]]];
  596. if (@available(iOS 11.0, tvOS 11.0, *)) {
  597. [resultArray addObject:@[@"AdjustedContentInset", [NSString lookin_stringFromInset:scrollView.adjustedContentInset]]];
  598. }
  599. if ([scrollView isKindOfClass:[UITextView class]]) {
  600. UITextView *textView = (UITextView *)scrollView;
  601. [resultArray addObject:@[@"FontSize", [NSString lookin_stringFromDouble:textView.font.pointSize decimal:2]]];
  602. [resultArray addObject:@[@"FontName", textView.font.fontName]];
  603. [resultArray addObject:@[@"TextColor", [NSString lookin_rgbaStringFromColor:textView.textColor]]];
  604. }
  605. } else if ([layer.lks_hostView isKindOfClass:[UITextField class]]) {
  606. UITextField *textField = (UITextField *)layer.lks_hostView;
  607. [resultArray addObject:@[@"FontSize", [NSString lookin_stringFromDouble:textField.font.pointSize decimal:2]]];
  608. [resultArray addObject:@[@"FontName", textField.font.fontName]];
  609. [resultArray addObject:@[@"TextColor", [NSString lookin_rgbaStringFromColor:textField.textColor]]];
  610. }
  611. if (layer.borderColor && layer.borderWidth > 0) {
  612. [resultArray addObject:@[@"BorderColor", [NSString lookin_rgbaStringFromColor:[UIColor lks_colorWithCGColor:layer.borderColor]]]];
  613. [resultArray addObject:@[@"BorderWidth", [NSString lookin_stringFromDouble:layer.borderWidth decimal:2]]];
  614. }
  615. if (layer.cornerRadius > 0) {
  616. [resultArray addObject:@[@"CornerRadius", [NSString lookin_stringFromDouble:layer.cornerRadius decimal:2]]];
  617. }
  618. if (layer.opacity < 1) {
  619. [resultArray addObject:@[@"Opacity", [NSString lookin_stringFromDouble:layer.opacity decimal:2]]];
  620. }
  621. if (layer.shadowColor && layer.shadowOpacity > 0) {
  622. [resultArray addObject:@[@"ShadowColor", [NSString lookin_rgbaStringFromColor:[UIColor lks_colorWithCGColor:layer.shadowColor]]]];
  623. [resultArray addObject:@[@"ShadowOpacity", [NSString lookin_stringFromDouble:layer.shadowOpacity decimal:2]]];
  624. [resultArray addObject:@[@"ShadowOffset", [NSString lookin_stringFromSize:layer.shadowOffset]]];
  625. [resultArray addObject:@[@"ShadowRadius", [NSString lookin_stringFromDouble:layer.shadowRadius decimal:2]]];
  626. }
  627. return resultArray;
  628. }
  629. @end
  630. #endif /* SHOULD_COMPILE_LOOKIN_SERVER */