LKS_LocalInspectViewController.m 31 KB

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