LKS_PerspectiveLayer.m 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. //
  2. // LKS_PerspectiveLayer.m
  3. // LookinServer
  4. //
  5. // Created by Li Kai on 2019/5/17.
  6. // https://lookin.work
  7. //
  8. #import "LKS_PerspectiveLayer.h"
  9. #import "LKS_PerspectiveDataSource.h"
  10. #import "LKS_PerspectiveItemLayer.h"
  11. #import "LookinAppInfo.h"
  12. #import "LookinHierarchyInfo.h"
  13. #import "LookinServerDefines.h"
  14. @interface LookinDisplayItem (LKS_PerspectiveLayer)
  15. @property(nonatomic, weak) LKS_PerspectiveItemLayer *lks_itemLayer;
  16. @end
  17. @implementation LookinDisplayItem (LKS_PerspectiveLayer)
  18. - (void)setLks_itemLayer:(LKS_PerspectiveItemLayer *)lks_itemLayer {
  19. [self lookin_bindObjectWeakly:lks_itemLayer forKey:@"lks_itemLayer"];
  20. }
  21. - (LKS_PerspectiveItemLayer *)lks_itemLayer {
  22. return [self lookin_getBindObjectForKey:@"lks_itemLayer"];
  23. }
  24. @end
  25. @interface LKS_PerspectiveLayer ()
  26. @property(nonatomic, strong) CALayer *rotateLayer;
  27. @property(nonatomic, copy) NSArray<LKS_PerspectiveItemLayer *> *itemLayers;
  28. @property(nonatomic, strong) LKS_PerspectiveDataSource *dataSource;
  29. @property(nonatomic, strong) LKS_PerspectiveItemLayer *selectedLayer;
  30. @end
  31. @implementation LKS_PerspectiveLayer
  32. - (instancetype)initWithDataSource:(LKS_PerspectiveDataSource *)dataSource {
  33. if (self = [self init]) {
  34. self.dataSource = dataSource;
  35. dataSource.perspectiveLayer = self;
  36. // [self lookin_removeImplicitAnimations];
  37. self.rotateLayer = [CALayer layer];
  38. [self addSublayer:self.rotateLayer];
  39. self.itemLayers = [NSArray array];
  40. [self _rebuildPreviewLayers];
  41. }
  42. return self;
  43. }
  44. - (void)layoutSublayers {
  45. [super layoutSublayers];
  46. LookinAppInfo *appInfo = self.dataSource.rawHierarchyInfo.appInfo;
  47. CGSize size = CGSizeMake(appInfo.screenWidth, appInfo.screenHeight);
  48. self.rotateLayer.bounds = CGRectMake(0, 0, size.width, size.height);
  49. self.rotateLayer.anchorPoint = CGPointMake(.5, .5);
  50. self.rotateLayer.position = CGPointMake(self.rotateLayer.superlayer.bounds.size.width / 2.0, self.rotateLayer.superlayer.bounds.size.height / 2.0);
  51. }
  52. - (void)setDimension:(LKS_PerspectiveDimension)dimension {
  53. _dimension = dimension;
  54. if (dimension == LKS_PerspectiveDimension2D) {
  55. self.rotateLayer.sublayerTransform = CATransform3DIdentity;
  56. [self.itemLayers enumerateObjectsUsingBlock:^(LKS_PerspectiveItemLayer * _Nonnull layer, NSUInteger idx, BOOL * _Nonnull stop) {
  57. layer.transform = CATransform3DIdentity;
  58. }];
  59. } else if (dimension == LKS_PerspectiveDimension3D) {
  60. CGFloat targetRotation = (self.rotation == 0 ? .6 : self.rotation);
  61. [self setRotation:targetRotation animated:YES completion:nil];
  62. [self _updateZIndex];
  63. } else {
  64. NSAssert(NO, @"");
  65. }
  66. }
  67. - (void)setRotation:(CGFloat)rotation {
  68. _rotation = rotation;
  69. CATransform3D transform = CATransform3DIdentity;
  70. transform.m34 = - 1 / 3000.0;
  71. transform = CATransform3DRotate(transform, rotation, 0, 1, 0);
  72. self.rotateLayer.sublayerTransform = transform;
  73. }
  74. - (void)setRotation:(CGFloat)rotation animated:(BOOL)animated completion:(void (^)(void))completionBlock {
  75. [CATransaction begin];
  76. [CATransaction setCompletionBlock:completionBlock];
  77. [CATransaction setDisableActions:!animated];
  78. [self setRotation:rotation];
  79. [CATransaction commit];
  80. }
  81. - (void)_rebuildPreviewLayers {
  82. NSArray<LookinDisplayItem *> *validItems = [self.dataSource.flatItems lookin_filter:^BOOL(LookinDisplayItem *obj) {
  83. return !obj.inNoPreviewHierarchy;
  84. }];
  85. self.itemLayers = [self.itemLayers lookin_resizeWithCount:validItems.count add:^LKS_PerspectiveItemLayer *(NSUInteger idx) {
  86. LKS_PerspectiveItemLayer *layer = [LKS_PerspectiveItemLayer new];
  87. [self.rotateLayer addSublayer:layer];
  88. return layer;
  89. } remove:^(NSUInteger idx, LKS_PerspectiveItemLayer *layer) {
  90. [layer removeFromSuperlayer];
  91. } doNext:^(NSUInteger idx, LKS_PerspectiveItemLayer *layer) {
  92. LookinDisplayItem *item = validItems[idx];
  93. layer.displayItem = item;
  94. layer.frame = item.frameToRoot;
  95. item.lks_itemLayer = layer;
  96. if (item.isSelected) {
  97. self.selectedLayer = layer;
  98. }
  99. }];
  100. [self _updateZIndex];
  101. }
  102. /**
  103. 重新计算每个 item 的 zIndex,并依 zIndex 设置对应的图层在 z 轴上的 translation。同时根据 fold 等属性来显示或隐藏图层。
  104. */
  105. - (void)_updateZIndex {
  106. [[self.dataSource.flatItems lookin_filter:^BOOL(LookinDisplayItem *obj) {
  107. return !obj.inNoPreviewHierarchy;
  108. }] enumerateObjectsUsingBlock:^(LookinDisplayItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
  109. [self _updateZIndexForItem:obj];
  110. }];
  111. [self _updateZTranslationByZIndex];
  112. }
  113. - (void)_updateZIndexForItem:(LookinDisplayItem *)item {
  114. item.previewZIndex = -1;
  115. if (item.displayingInHierarchy) {
  116. LookinDisplayItem *referenceItem = [self _maxZIndexForOverlappedItemUnderItem:item];
  117. if (referenceItem) {
  118. // 如果 item 和另一个 itemA 重叠了,则 item.previewZIndex 应该比 itemA.previewZIndex 高一级
  119. item.previewZIndex = referenceItem.previewZIndex + 1;
  120. } else {
  121. item.previewZIndex = 0;
  122. }
  123. } else {
  124. if (item.superItem) {
  125. item.previewZIndex = item.superItem.previewZIndex;
  126. } else {
  127. NSAssert(NO, @"");
  128. }
  129. }
  130. if (item.previewZIndex < 0) {
  131. NSAssert(NO, @"");
  132. item.previewZIndex = 0;
  133. }
  134. }
  135. - (void)_updateZTranslationByZIndex {
  136. CGFloat interspace = 20;
  137. // key 是 zIndex,value 是该 zIndex 下有多少 item,作用是避免下文提到的 offsetToAvoidOverlapBug
  138. NSMutableDictionary<NSNumber *, NSNumber *> *zIndexAndCountDict = [NSMutableDictionary dictionary];
  139. __block NSUInteger maxZIndex = 0;
  140. [self.itemLayers enumerateObjectsUsingBlock:^(LKS_PerspectiveItemLayer * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
  141. maxZIndex = MAX(maxZIndex, obj.displayItem.previewZIndex);
  142. }];
  143. [self.itemLayers enumerateObjectsUsingBlock:^(LKS_PerspectiveItemLayer * _Nonnull layer, NSUInteger idx, BOOL * _Nonnull stop) {
  144. LookinDisplayItem *item = layer.displayItem;
  145. // 将 "1, 2, 3, 4, 5 ..." 这样的 zIndex 排序调整为 “-2,-1,0,1,2 ...”,这样旋转时 Y 轴就会位于 zIndex 为中间值的那个 layer 的位置
  146. NSInteger adjustedZIndex = item.previewZIndex - round(maxZIndex / 2.0);
  147. NSUInteger countOfCurrentZIndex = [[zIndexAndCountDict objectForKey:@(adjustedZIndex)] unsignedIntegerValue];
  148. countOfCurrentZIndex++;
  149. [zIndexAndCountDict setObject:@(countOfCurrentZIndex) forKey:@(adjustedZIndex)];
  150. /// 当两个重叠的 layer 在 z 轴上具有相同的 translate 时,理论上更远离用户的那个 layer 应该被遮住,但不知道为什么某些旋转角度下会出现不合理论的情况,感觉是系统 bug,因此这里把更靠近用户的那个 layer 的 translate 增大一丁点,避免两个 layer 有完全相同的 translate 从而避免这个 bug
  151. CGFloat offsetToAvoidOverlapBug = countOfCurrentZIndex * 0.01;
  152. layer.transform = CATransform3DTranslate(CATransform3DIdentity, 0, 0, interspace * adjustedZIndex + offsetToAvoidOverlapBug);
  153. }];
  154. }
  155. /**
  156. 传入 itemA,返回另一个 itemB,itemB 满足以下条件:
  157. - itemB 在 preview 中可见
  158. - itemB 的层级比 itemA 要低(即 itemB 在 flatItems 里的 index 要比 itemA 小)
  159. - itemB 和 itemA 的 frameToRoot 有重叠,即视觉上它们是彼此遮挡的
  160. - itemB 是满足以上两个条件中的所有 items 里的 zIndex 值最高的
  161. @note 如果没有找到任何符合条件的 itemB,则返回 nil
  162. */
  163. - (LookinDisplayItem *)_maxZIndexForOverlappedItemUnderItem:(LookinDisplayItem *)item {
  164. NSArray<LookinDisplayItem *> *flatItems = [self.dataSource.flatItems lookin_filter:^BOOL(LookinDisplayItem *obj) {
  165. return !obj.inNoPreviewHierarchy;
  166. }];
  167. NSUInteger itemIndex = [flatItems indexOfObject:item];
  168. if (itemIndex == 0) {
  169. return nil;
  170. }
  171. if (itemIndex == NSNotFound) {
  172. NSAssert(NO, @"");
  173. return nil;
  174. }
  175. NSIndexSet *indexesBelow = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, itemIndex)];
  176. __block LookinDisplayItem *targetItem = nil;
  177. [flatItems enumerateObjectsAtIndexes:indexesBelow options:NSEnumerationReverse usingBlock:^(LookinDisplayItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
  178. if (!obj.inHiddenHierarchy) {
  179. if (CGRectIntersectsRect(item.frameToRoot, obj.frameToRoot)) {
  180. if (!targetItem) {
  181. targetItem = obj;
  182. } else {
  183. if (obj.previewZIndex > targetItem.previewZIndex) {
  184. targetItem = obj;
  185. }
  186. }
  187. }
  188. }
  189. }];
  190. return targetItem;
  191. }
  192. #pragma mark - <LKS_PerspectiveDataSourceDelegate>
  193. - (void)dataSourceDidChangeSelectedItem:(LKS_PerspectiveDataSource *)dataSource {
  194. [self.selectedLayer reRender];
  195. LookinDisplayItem *item = dataSource.selectedItem;
  196. [item.lks_itemLayer reRender];
  197. self.selectedLayer = item.lks_itemLayer;
  198. }
  199. - (void)dataSourceDidChangeDisplayItems:(LKS_PerspectiveDataSource *)dataSource {
  200. [self _updateZIndex];
  201. [self.itemLayers enumerateObjectsUsingBlock:^(LKS_PerspectiveItemLayer * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
  202. [obj reRender];
  203. }];
  204. }
  205. - (void)dataSourceDidChangeNoPreview:(LKS_PerspectiveDataSource *)dataSource {
  206. [self _rebuildPreviewLayers];
  207. }
  208. @end