题目:找出两个 UIView 的最近的公共 View,如果不存在,则输出 nil 。

本文介绍三种算法,用于寻找两个UIView对象的最近公共父视图。第一种算法基于两个视图的父链条长度,第二种利用NSSet进行优化,第三种采用类似归并排序的思想。这些算法的时间复杂度均为O(N)。
- (NSArray *)superCalsses:(UIView *)class{
    
    if (!class) {
        return @[];
    }
    
    NSMutableArray * result = [NSMutableArray array];
    
    while (class) {
        [result addObject:class];
        class = [class superview];
    }
    return [result copy];
}
/**
 *基于两个view的父视图链条长度来判断 时间复杂度O(N) 空间复杂度O(1)
 **/

- (UIView *)commonClass1:(UIView *)classA andClass:(UIView *)classB{
    
    NSArray* arr1 = [self superCalsses:classA];
    NSArray* arr2 = [self superCalsses:classB];
    
    NSInteger count = arr1.count < arr2.count ? arr1.count : arr2.count;
    
    UIView * resultClass;
    
    for (int i = 0; i < count; ++i) {
        
        UIView * clA = arr1[arr1.count - i - 1];
        UIView * clB = arr2[arr2.count - i - 1];
        if (clA == clB) {
            resultClass = clA;
        }else{
            break;
        }
    }
    
    return resultClass;
    
}

/**
 * 我们将一个路径中的所有点先放进 NSSet 中。因为 NSSet 的内部实现是一个 hash 表,所以查找元素的时间复杂度变成了 O(1),我们一共有 N 个节点,所以总时间复杂度优化到了 O(N)。
 *
 **/

- (UIView *)commonClass1_V2:(UIView *)classA andClass:(UIView *)classB{
    
    NSArray* arr1 = [self superCalsses:classA];
    NSArray* arr2 = [self superCalsses:classB];
    
    NSSet *set = [NSSet setWithArray:arr2];
    
    for (int i = 0; i < arr1.count; ++i) {
        UIView *targetView = arr1[i];
        if ([set containsObject:targetView]) {
            return targetView;
        }
    }
    return nil;
    
}
/**
 * 除了使用 NSSet 外,我们还可以使用类似归并排序的思想,用两个「指针」,分别指向两个路径的根节点,然后从根节点开始,找第一个不同的节点,第一个不同节点的上一个公共节点,就是我们的答案。
 * O(N)
 */

- (UIView *)commonClass1_V3:(UIView *)classA andClass:(UIView *)classB{
    
    NSArray* arr1 = [self superCalsses:classA];
    NSArray* arr2 = [self superCalsses:classB];
    
    NSInteger p1 = arr1.count - 1;
    NSInteger p2 = arr2.count - 1;
    
    UIView *answer = nil;
    
    while (p1 >=0 && p2 >= 0) {
        if (arr1[p1] == arr2[p2]) {
            answer = arr1[p1];
        }
        p1--;
        p2--;
    }
    
    return answer;
}

 

下面我实现的一个页面调用TABAnimated库实现骨架屏的代码(只有前半部分)与TABAnimated库的说明,现在我想规范这个代码,实现一个TPBSkeletonManager封装这些操作,一个封装TABAnimated库。提供自动生成、自定义样式骨架、默认样式骨架的骨架屏模组,使用和维护便捷,可以传入需要实现骨架屏的view、tableview、collection view,就能返回默认的骨架生成方法。只需要简单命令就能在骨架屏需要展示的时候启动对应的骨架屏,在骨架屏需要消失的时候也是一个明确简单的命令就能关闭骨架屏。 一个页面调用TABAnimated库实现骨架屏的代码: @implementation SDNV6SiteDashboardViewController - (instancetype)init { self = [super init]; if (self) { [self bindDelegate]; } return self; } // 目前直接对加载到Cell的UIView构建骨架屏并按顺序直接画出遮挡 // 对于无法加载识别的自定义view:SDNV6SiteDashboardDensityView *apDensityContainerView无法应用也难以快速修改 - 解决,使用手动方案一 // 对于没有声明在主程序的view:SDNInsightHeadView *headView,其“Wi-Fi Activity”也无法解决 - 尚未解决 - (void)viewDidLoad { // 骨架屏bug:新增页面部署骨架屏然整体消失速度异常 self.WifiActivityHeadView = [[SDNInsightHeadView alloc] init]; // 正常页面生成 [super viewDidLoad]; [self updatePermission]; [self.tableView layoutIfNeeded]; [self reloadView]; [self omdRegisterForSkipLifeCycleEvent]; // 对每个业务 view 设置骨架动画 // self.deviceOverView.tabAnimated = [[TABViewAnimated alloc] init]; self.networkActivityView.tabAnimated = [[TABViewAnimated alloc] init]; self.WifiActivityHeadView.tabAnimated = [[TABViewAnimated alloc] init]; self.wifiActivityChartView.tabAnimated = [[TABViewAnimated alloc] init]; self.apDensityContainerView.tabAnimated = [[TABViewAnimated alloc] init]; // // 对无法识别的业务view和错误使用空白占位view辅助识别 // SDNV6SiteDashboardDensitySkeletonView *skeletonView = [[SDNV6SiteDashboardDensitySkeletonView alloc] init]; // // // 添加到wifiView // [self.wifiActivityChartView addSubview:skeletonView]; // // 设置约束覆盖整个容器 // [skeletonView mas_makeConstraints:^(MASConstraintMaker *make) { // make.edges.equalTo(self.wifiActivityChartView); // }]; // 启动骨架动画 // [self.wifiActivityChartView layoutIfNeeded]; // skeletonView.tabAnimated = [[TABViewAnimated alloc] init]; // 调整骨架屏view // deviceView调整:只要启动回调,整个页面逻辑都会被破坏,TABA库的应用还是需要整体调整 // 先从整个dashboard页面出发,找出出现问题的原因,实在行再尝试手动方案2 // 使用方案二 SDNSiteDashboardDeviceOverViewSkeletonView *deviceSkeletonView = [[SDNSiteDashboardDeviceOverViewSkeletonView alloc] init]; // 添加骨架 [_deviceOverView addSubview:deviceSkeletonView]; // 绑定约束 [deviceSkeletonView mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.equalTo(_deviceOverView); }]; // 启动骨架动画 [self.deviceOverView layoutIfNeeded]; deviceSkeletonView.tabAnimated = [[TABViewAnimated alloc] init]; // 添加骨架波浪动画效果 deviceSkeletonView.tabAnimated.superAnimationType = TABViewSuperAnimationTypeShimmer; // networkView调整 _networkActivityView.tabAnimated.superAnimationType = TABViewSuperAnimationTypeShimmer; _networkActivityView.tabAnimated.adjustBlock = ^(TABComponentManager * _Nonnull manager) { // 整体移除需要和显示清晰的标签 manager.animations(21, 3).remove(); // 底部两个重叠的骨架块 // 对顶部0-15部分的修改:标签栏部分与坐标 manager.animation(0).reducedWidth(-20).reducedHeight(-8).radius(4); // 与安卓对齐 manager.animation(1).remove(); manager.animation(3).remove(); manager.animation(4).remove(); manager.animation(6).remove(); manager.animation(7).remove(); manager.animation(10).remove(); manager.animation(11).remove(); manager.animation(13).remove(); manager.animation(14).remove(); // // 对2、5、8、9、12、15作为目前的主题进行修改 // 左侧标识修改 manager.animation(2).reducedWidth(-60).reducedHeight(-5).radius(4); manager.animation(9).reducedWidth(-60).reducedHeight(-5).radius(4); // 右侧标识修改 manager.animation(5).x_offset(-60).reducedWidth(-40).reducedHeight(-5).radius(4); manager.animation(8).x_offset(-20).reducedWidth(-40).reducedHeight(-5).radius(4); manager.animation(12).x_offset(-60).reducedWidth(-40).reducedHeight(-5).radius(4); manager.animation(15).x_offset(-20).reducedWidth(-40).reducedHeight(-5).radius(4); // 对底部16-24的修改 manager.animation(20).remove(); manager.animations(17, 2).remove(); manager.animation(16).radius(4); manager.animation(19).remove(); // manager.animation(19).x_offset(100).reducedWidth(40).reducedHeight(25).radius(4); }; // wifiheadView调整 _WifiActivityHeadView.tabAnimated.superAnimationType = TABViewSuperAnimationTypeShimmer; // wifiView调整:调整wifiView的骨架屏以遮蔽“WiFi Aciivity”标签尝试-失败 _wifiActivityChartView.tabAnimated.superAnimationType = TABViewSuperAnimationTypeShimmer; _wifiActivityChartView.tabAnimated.adjustBlock = ^(TABComponentManager * _Nonnull manager) { CGRect screenBounds = [UIScreen mainScreen].bounds; CGFloat screenWidth = CGRectGetWidth(screenBounds); // 移除需要与显示清晰的骨架组件 // manager.animation(1).remove(); manager.animation(2).remove(); manager.animation(3).remove(); // 3和4是底边骨架识别来源,但识别位置过远 manager.animation(4).remove(); // 调整2 iconView的位置 manager.animation(1).radius(4); // 创建标识6和7:对应Upload manager.create(6).leftEqualTo_offset(1, screenWidth/2 + 70).y(6).width(50).height(16).radius(4); manager.create(7).leftEqualTo_offset(1, screenWidth/2 + 40).y(6).width(20).height(16).radius(4); // 创建标识8和9:对应Download manager.create(8).leftEqualTo_offset(1, screenWidth/2 - 30).y(6).width(50).height(16).radius(4); manager.create(9).leftEqualTo_offset(1, screenWidth/2 - 60).y(6).width(20).height(16).radius(4); }; // ApView调整:创建ApView的骨架 _apDensityContainerView.tabAnimated.superAnimationType = TABViewSuperAnimationTypeShimmer; _apDensityContainerView.tabAnimated.adjustBlock = ^(TABComponentManager * _Nonnull manager) { CGRect screenBounds = [UIScreen mainScreen].bounds; CGFloat screenWidth = CGRectGetWidth(screenBounds); // 标识0:titleBlockView manager.create(0) .width(120) .height(20) .x(16) .y(16) .radius(4); // // 标识1:indicatorBlockView // manager.create(1) // .width(24) // .height(20) // .x(screenWidth - 16 - 32 - 24) // 右边缘减去边距和宽度 // .topEqualTo_offset(0, 0) // 与 titleBlockView 顶部对齐 // .radius(4); // 标识2:densityBlockView manager.create(2) .width(screenWidth - 32 - 32 - 8) .height(60) // 可根据实际内容高度调整 .x(16) .topEqualTo_offset(0, 36) // titleBlockView 底部 + 间距16 .radius(4); }; // 启动骨架动画(逐个) // [self.deviceOverView tab_startAnimation]; [self.networkActivityView tab_startAnimation]; [self.WifiActivityHeadView tab_startAnimation]; [self.wifiActivityChartView tab_startAnimation]; [self.apDensityContainerView tab_startAnimation]; // 启动替代骨架 [deviceSkeletonView tab_startAnimation]; // // 延迟 10 秒后结束骨架动画并加载 tableView // dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ //// [self.deviceOverView tab_endAnimation]; // [self.networkActivityView tab_endAnimation]; // [self.WifiActivityHeadView tab_endAnimation]; // [self.wifiActivityChartView tab_endAnimation]; // [self.apDensityContainerView tab_endAnimation]; // // // // 关闭并移除替代骨架 // [deviceSkeletonView tab_endAnimation]; // [deviceSkeletonView removeFromSuperview]; // }); // 第 0 秒:关闭并移除骨架容器 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [deviceSkeletonView tab_endAnimation]; [deviceSkeletonView removeFromSuperview]; }); // 第 2 秒:networkActivityView dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self.networkActivityView tab_endAnimation]; }); // 第 4 秒:WifiActivityHeadView dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(6 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self.WifiActivityHeadView tab_endAnimation]; }); // 第 6 秒:wifiActivityChartView dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(6 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self.wifiActivityChartView tab_endAnimation]; }); // 第 8 秒:apDensityContainerView dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(8 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self.apDensityContainerView tab_endAnimation]; }); } 参考的TABAnimated用法: // 自动生成+手动调整 self.networkActivityView.tabAnimated = [[TABViewAnimated alloc] init]; // networkView调整 _networkActivityView.tabAnimated.superAnimationType = TABViewSuperAnimationTypeShimmer; _networkActivityView.tabAnimated.adjustBlock = ^(TABComponentManager * _Nonnull manager) { // 整体移除需要和显示清晰的标签 manager.animations(21, 3).remove(); // 底部两个重叠的骨架块 // 对顶部0-15部分的修改:标签栏部分与坐标 manager.animation(0).reducedWidth(-20).reducedHeight(-8).radius(4); // 与安卓对齐 manager.animation(1).remove(); manager.animation(3).remove(); manager.animation(4).remove(); manager.animation(6).remove(); manager.animation(7).remove(); manager.animation(10).remove(); manager.animation(11).remove(); manager.animation(13).remove(); manager.animation(14).remove(); // // 对2、5、8、9、12、15作为目前的主题进行修改 // 左侧标识修改 manager.animation(2).reducedWidth(-60).reducedHeight(-5).radius(4); manager.animation(9).reducedWidth(-60).reducedHeight(-5).radius(4); // 右侧标识修改 manager.animation(5).x_offset(-60).reducedWidth(-40).reducedHeight(-5).radius(4); manager.animation(8).x_offset(-20).reducedWidth(-40).reducedHeight(-5).radius(4); manager.animation(12).x_offset(-60).reducedWidth(-40).reducedHeight(-5).radius(4); manager.animation(15).x_offset(-20).reducedWidth(-40).reducedHeight(-5).radius(4); // 对底部16-24的修改 manager.animation(20).remove(); manager.animations(17, 2).remove(); manager.animation(16).radius(4); manager.animation(19).remove(); // manager.animation(19).x_offset(100).reducedWidth(40).reducedHeight(25).radius(4); }; // 启动骨架屏 [self.networkActivityView tab_startAnimation]; // 关闭骨架屏 [self.networkActivityView tab_endAnimation]; 使用替代骨架屏视图,需要自己传递一个遮挡原有视图的骨架屏 替代骨架屏方案的TABAnimated库骨架屏实现 // 调整骨架屏view // deviceView调整:只要启动回调,整个页面逻辑都会被破坏,TABA库的应用还是需要整体调整 // 先从整个dashboard页面出发,找出出现问题的原因,实在行再尝试手动方案2 // 使用方案二 SDNSiteDashboardDeviceOverViewSkeletonView *deviceSkeletonView = [[SDNSiteDashboardDeviceOverViewSkeletonView alloc] init]; // 添加骨架 [_deviceOverView addSubview:deviceSkeletonView]; // 绑定约束 [deviceSkeletonView mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.equalTo(_deviceOverView); }]; // 启动骨架动画 [self.deviceOverView layoutIfNeeded]; deviceSkeletonView.tabAnimated = [[TABViewAnimated alloc] init]; // 添加骨架波浪动画效果 deviceSkeletonView.tabAnimated.superAnimationType = TABViewSuperAnimationTypeShimmer; // 启动替代骨架 [deviceSkeletonView tab_startAnimation]; // 关闭替代骨架屏[deviceSkeletonView tab_endAnimation]; [deviceSkeletonView removeFromSuperview]; UITableView:TableView的TABAnimated库骨架屏实现 // 在慢加载中初始化 - (UITableView *)tableView { if (!_tableView) { _tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, kScreenWidth, kScreenHeight) style:UITableViewStyleGrouped]; _tableView.dataSource = self; _tableView.delegate = self; _tableView.estimatedRowHeight = 0; _tableView.estimatedSectionFooterHeight = 0; _tableView.estimatedSectionHeaderHeight = 0; _tableView.separatorStyle = UITableViewCellSeparatorStyleNone; _tableView.backgroundColor = [UIColor tab_normalDynamicBackgroundColor]; // 设置tabAnimated相关属性 // 部分section有动画 _tableView.tabAnimated = [TABTableAnimated animatedWithCellClass:[TestTableViewCell class] cellHeight:100 animatedCount:1 toSection:1]; [_tableView.tabAnimated addHeaderViewClass:[LineTableViewHeaderFooterView class] viewHeight:60 toSection:1]; _tableView.tabAnimated.adjustWithClassBlock = ^(TABComponentManager *manager, __unsafe_unretained Class targetClass) { if (targetClass == TestTableViewCell.class) { manager.animation(1).down(3).height(12); manager.animation(2).height(12).width(110); manager.animation(3).down(-5).height(12); }else if (targetClass == LineTableViewHeaderFooterView.class) { manager.animation(2).right(3).height(14).down(16).reducedWidth(30).radius(2); } }; } return _tableView; } // 在UI初始化中启动骨架屏 /** initize view 视图初始化 */ - (void)initUI { [self.view addSubview:self.tableView]; [self.tableView tab_startAnimation]; // 开启动画 } // 获取到数据后进行对应骨架屏取消 /** 获取到数据后 */ - (void)afterGetData { [dataArray removeAllObjects]; // 模拟数据 for (int i = 0; i < 10; i ++) { Game *game = [[Game alloc]init]; game.gameId = [NSString stringWithFormat:@"%d",i]; game.title = [NSString stringWithFormat:@"这里是测试数据%d",i+1]; game.cover = @"test.jpg"; [dataArray addObject:game]; } // 停止动画,并刷新数据 [self.tableView tab_endAnimationWithIndex:1]; } UICollectionView的TABAnimated库骨架屏实现 // 1、初始化_collectionView.tabAnimated = [TABCollectionAnimated animatedWithCellClass:[NewsCollectionViewCell class] cellSize:[NewsCollectionViewCell cellSize]]; // 2、控制骨架屏开关 // 开启动画 [self.collectionView tab_startAnimation]; // 关闭动画 [self.collectionView tab_endAnimation]; // 3、预处理回调+链式语法用于修改骨架元素的属性 // 用变量名 _tableView.tabAnimated.adjustBlock = ^(TABComponentManager * _Nonnull manager) { manager.animationN(@"titleImageView").down(3).radius(12); manager.animationN(@"nameLabel").height(12).width(110); manager.animationN(@"timeButton").down(-5).height(12); }; // 用index修改 _tableView.tabAnimated.adjustBlock = ^(TABComponentManager * _Nonnull manager) { manager.animation(1).down(3).radius(12); manager.animation(2).height(12).width(110); manager.animation(3).down(-5).height(12); }; // 使用swift tableView.tabAnimated?.adjustBlock = { manager in manager.animation()?(1)?.down()(3)?.radius()(12) manager.animation()?(2)?.height()(12)?.width()(110) manager.animation()?(3)?.down()(-5)?.height()(12) }
最新发布
11-01
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值