文档章节

IOS自动进行View标记

aron1992
 aron1992
发布于 2017/05/27 16:50
字数 1808
阅读 465
收藏 9

缘起

一切都源于我的上一篇博客,我写的是一篇 UITableViewCell使用自动布局的“最佳实践” ,我需要给我的图片里面的UIView元素添加上边距的标记,这让我感到很为难,我觉得我得发点时间写一个程序让这个步骤自动化,我只要一键就能让我的程序自动标记边距,这个比我要手动去标记来的酷很多不是吗!

结果

所以,我发了点时间实现了我的想法,下面是实现的结果截图:
以及代码开源托管地址:代码链接
预览图

过去几小时内的想法

静下心来整理我的想法和寻找方案,大概的整理下了一个可行性的方案以及这个方案中需要使用到的步骤,其中一些细节没有在这个步骤中体现

  • 获取水平的间距:遍历父View的子View,获取某个子sourceView的右边到其他子targetView的左边的距离,把结果保存到子targetView的入度数组中
  • 获取垂直的间距:遍历父View的子View,获取某个子sourceView的下边到其他子targetView的上边的距离,把结果保存到子targetView的入度数组中
  • 筛选出targetView的入度数组中所以不符合的结果,删除这些结果
  • 最终获取到了一个需要进行展示的结果数组,数组保存的是一系列的间距线段对象
  • 创建一个显示标记的TagView层,把结果的线段绘制在TagView上面,然后把TabView添加到父View上

代码实现解析

注入测试边框View

我的方案中所有的间距都是基于子View考虑的,所以子View和父View的边距需要特殊的计算,可以使用在父View的旁边添加一个物理像素的子View,最终只要处理所有这些子View,子View和父View的边距就能得到体现了,不用再做多余的处理,这是一个讨巧的方案。

+ (void)registerBorderTestViewWithView:(UIView*)view {
    CGFloat minWH = 1.0/[UIScreen mainScreen].scale;
    MMBorderAttachView* leftBorderView = [[MMBorderAttachView alloc] initWithFrame:CGRectMake(0, 0, minWH, view.bounds.size.height)];
    [view addSubview:leftBorderView];
    MMBorderAttachView* rightBorderView = [[MMBorderAttachView alloc] initWithFrame:CGRectMake(view.bounds.size.width-minWH, 0, minWH, view.bounds.size.height)];
    [view addSubview:rightBorderView];
    
    MMBorderAttachView* topBorderView = [[MMBorderAttachView alloc] initWithFrame:CGRectMake(0, 0, view.bounds.size.width, minWH)];
    [view addSubview:topBorderView];
    MMBorderAttachView* bottomBorderView = [[MMBorderAttachView alloc] initWithFrame:CGRectMake(0, view.bounds.size.height - minWH, view.bounds.size.width, minWH)];
    [view addSubview:bottomBorderView];
}

获取父View的所有子View,抽象为MMFrameObject对象

NSMutableArray* viewFrameObjs = [NSMutableArray array];
    NSArray* subViews = view.subviews;
    for (UIView* subView in subViews) {
        // 过滤特殊的View,不属于注入的View
        if (![subView conformsToProtocol:@protocol(MMAbstractView)]) {
            if (subView.alpha<0.001f) {
                continue;
            }
            
            if (subView.frame.size.height <= 2) {
                continue;
            }
        }
        
        MMFrameObject* frameObj = [[MMFrameObject alloc] init];
        frameObj.frame = subView.frame;
        frameObj.attachedView = subView;
        [viewFrameObjs addObject:frameObj];
    }

获取View之间的间距

需要处理两种情况:1、寻找View的右边对应的其他View的左边;2、寻找View的下边对应的其他View的上边,特殊滴需要处理两者都是MMAbstractView的情况,这种不需要处理

NSMutableArray<MMLine*>* lines = [NSMutableArray array];
    for (MMFrameObject* sourceFrameObj in viewFrameObjs) {
        for (MMFrameObject* targetFrameObj in viewFrameObjs) {
            
            // 过滤特殊的View
            if ([sourceFrameObj.attachedView conformsToProtocol:@protocol(MMAbstractView)]
                && [targetFrameObj.attachedView conformsToProtocol:@protocol(MMAbstractView)]) {
                continue;
            }
            
            // 寻找View的右边对应的其他View的左边
            MMLine* hLine = [self horizontalLineWithFrameObj1:sourceFrameObj frameObj2:targetFrameObj];
            if (hLine) {
                [lines addObject:hLine];
                [targetFrameObj.leftInjectedObjs addObject:hLine];
            }
            
            // 寻找View的下边对应的其他View的上边
            MMLine* vLine = [self verticalLineWithFrameObj1:sourceFrameObj frameObj2:targetFrameObj];
            if (vLine) {
                [lines addObject:vLine];
                [targetFrameObj.topInjectedObjs addObject:vLine];
            }
        }
    }

获取间距线段的实现

以获取水平的间距线段为例,这种情况,只需要处理一个子View在另一个子View的右边的情况,否则返回nil跳过。获取水平间距线段,明显的线段的X轴是确定的,要,只要处理好Y轴就行了,问题就抽象为了两个线段的问题,这部分是在approperiatePointWithInternal方法中处理的,主要步骤是一长的线段为标准,然后枚举短的线段和长的线段存在的5种情况,相应的计算合适的值,然后给Y轴使用。

两条线段的5种关系

+ (MMLine*)horizontalLineWithFrameObj1:(MMFrameObject*)frameObj1 frameObj2:(MMFrameObject*)frameObj2 {
    if (ABS(frameObj1.frame.origin.x - frameObj2.frame.origin.x) < 3) {
        return nil;
    }
    
    // frameObj2整体在frameObj1右边
    if (frameObj1.frame.origin.x + frameObj1.frame.size.width >= frameObj2.frame.origin.x) {
        return nil;
    }
    
    CGFloat obj1RightX = frameObj1.frame.origin.x + frameObj1.frame.size.width;
    CGFloat obj1Height = frameObj1.frame.size.height;
    
    CGFloat obj2LeftX = frameObj2.frame.origin.x;
    CGFloat obj2Height = frameObj2.frame.size.height;
    
    CGFloat handle = 0;
    CGFloat pointY = [self approperiatePointWithInternal:[[MMInterval alloc] initWithStart:frameObj1.frame.origin.y length:obj1Height] internal2:[[MMInterval alloc] initWithStart:frameObj2.frame.origin.y length:obj2Height] handle:&handle];
    
    MMLine* line = [[MMLine alloc] initWithPoint1:[[MMShortPoint alloc] initWithX:obj1RightX y:pointY handle:handle] point2:[[MMShortPoint alloc] initWithX:obj2LeftX y:pointY handle:handle]];
    
    return line;
}

+ (CGFloat)approperiatePointWithInternal:(MMInterval*)internal1 internal2:(MMInterval*)internal2 handle:(CGFloat*)handle {
    CGFloat MINHandleValue = 20;
    CGFloat pointValue = 0;
    CGFloat handleValue = 0;
    MMInterval* bigInternal;
    MMInterval* smallInternal;
    if (internal1.length > internal2.length) {
        bigInternal = internal1;
        smallInternal = internal2;
    } else {
        bigInternal = internal2;
        smallInternal = internal1;
    }
    
    // 线段分割法
    if (smallInternal.start < bigInternal.start && smallInternal.start+smallInternal.length < bigInternal.start) {
        CGFloat tmpHandleValue = bigInternal.start - smallInternal.start+smallInternal.length;
        pointValue = bigInternal.start - tmpHandleValue/2;
        handleValue = MAX(tmpHandleValue, MINHandleValue);
    }
    if (smallInternal.start < bigInternal.start && smallInternal.start+smallInternal.length >= bigInternal.start) {
        CGFloat tmpHandleValue = smallInternal.start+smallInternal.length - bigInternal.start;
        pointValue = bigInternal.start + tmpHandleValue/2;
        handleValue = MAX(tmpHandleValue, MINHandleValue);
    }
    if (smallInternal.start >= bigInternal.start && smallInternal.start+smallInternal.length <= bigInternal.start+bigInternal.length) {
        CGFloat tmpHandleValue = smallInternal.length;
        pointValue = smallInternal.start + tmpHandleValue/2;
        handleValue = MAX(tmpHandleValue, MINHandleValue);
    }
    if (smallInternal.start >= bigInternal.start && smallInternal.start+smallInternal.length > bigInternal.start+bigInternal.length) {
        CGFloat tmpHandleValue = bigInternal.start+bigInternal.length - smallInternal.start;
        pointValue = bigInternal.start + tmpHandleValue/2;
        handleValue = MAX(tmpHandleValue, MINHandleValue);
    }
    if (smallInternal.start >= bigInternal.start+bigInternal.length && smallInternal.start+smallInternal.length > bigInternal.start+bigInternal.length) {
        CGFloat tmpHandleValue = smallInternal.start - (bigInternal.start+bigInternal.length);
        pointValue = smallInternal.start - tmpHandleValue/2;
        handleValue = MAX(tmpHandleValue, MINHandleValue);
    }
    
    if (handle) {
        *handle = handleValue;
    }
    
    return pointValue;
}

过滤线段

一个子View对象的入度可能有好几个,需要筛选进行删除,我使用的筛选策略是:以水平的间距线段为例,两条线段的Y差值小于某个阈值,选择线段长的那条删除,最终获取到了一个需要进行展示的结果数组,数组保存的是一系列的间距线段对象

// 查找重复的射入line
    // hLine:Y的差值小于某个值,leftInjectedObjs->取最小一条
    // vLine:X的差值小于某个值,topInjectedObjs->取最小一条
    CGFloat minValue = 5;
    for (MMFrameObject* sourceFrameObj in viewFrameObjs) {
        
        {
            // 排序:Y值:从大到小
            [sourceFrameObj.leftInjectedObjs sortUsingComparator:^NSComparisonResult(MMLine*  _Nonnull obj1, MMLine*  _Nonnull obj2) {
                return obj1.point1.point.y > obj2.point1.point.y;
            }];
            int i = 0;
            NSLog(@"\n\n");
            MMLine* baseLine, *compareLine;
            if (sourceFrameObj.leftInjectedObjs.count) {
                baseLine = sourceFrameObj.leftInjectedObjs[i];
            }
            while (i<sourceFrameObj.leftInjectedObjs.count) {
                NSLog(@"lineWidth = %.1f == ", baseLine.lineWidth);
                if (i + 1 < sourceFrameObj.leftInjectedObjs.count) {
                    compareLine = sourceFrameObj.leftInjectedObjs[i + 1];
                    
                    if (ABS(baseLine.point1.point.y - compareLine.point1.point.y) < minValue) {
                        // 移除长的一条
                        if (baseLine.lineWidth > compareLine.lineWidth) {
                            [lines removeObject:baseLine];
                            baseLine = compareLine;
                        } else {
                            [lines removeObject:compareLine];
                        }
                    } else {
                        baseLine = compareLine;
                    }
                }
                i++;
            }
        }
        
        {
            // 排序:X值从大到小
            [sourceFrameObj.topInjectedObjs sortUsingComparator:^NSComparisonResult(MMLine*  _Nonnull obj1, MMLine*  _Nonnull obj2) {
                return obj1.point1.point.x >
                obj2.point1.point.x;
            }];
            int j = 0;
            MMLine* baseLine, *compareLine;
            if (sourceFrameObj.topInjectedObjs.count) {
                baseLine = sourceFrameObj.topInjectedObjs[j];
            }
            while (j<sourceFrameObj.topInjectedObjs.count) {
                if (j + 1 < sourceFrameObj.topInjectedObjs.count) {
                    compareLine = sourceFrameObj.topInjectedObjs[j + 1];
                    
                    if (ABS(baseLine.point1.point.x - compareLine.point1.point.x) < minValue) {
                        // 移除长的一条
                        // 移除长的一条
                        if (baseLine.lineWidth > compareLine.lineWidth) {
                            [lines removeObject:baseLine];
                            baseLine = compareLine;
                        } else {
                            [lines removeObject:compareLine];
                        }
                    } else {
                        baseLine = compareLine;
                    }
                }
                j++;
            }
        }
    }

TagView 的绘制

    // 绘制View
    TaggingView* taggingView = [[TaggingView alloc] initWithFrame:view.bounds lines:lines];
    [view addSubview:taggingView];

TaggingView 在drawRect绘制线段以及线段长度的文字

//
//  TaggingView.m
//  AutolayoutCell
//
//  Created by aron on 2017/5/27.
//  Copyright © 2017年 aron. All rights reserved.
//

#import "TaggingView.h"
#import "MMTagModel.h"

@interface TaggingView ()
@property (nonatomic, strong) NSArray<MMLine*>* lines;;
@end

@implementation TaggingView

- (instancetype)initWithFrame:(CGRect)frame lines:(NSArray<MMLine*>*)lines {
    self = [super initWithFrame:frame];
    if (self) {
        self.backgroundColor = [UIColor colorWithRed:255 green:255 blue:255 alpha:0.05];
        _lines = lines;
    }
    return self;
}

- (void)drawRect:(CGRect)rect {
    [super drawRect:rect];
    //1.获取上下文
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    for (MMLine* line in _lines) {
        // 绘制线段
        CGContextSetLineWidth(context, 2.0f/[UIScreen mainScreen].scale);  //线宽
        CGContextSetAllowsAntialiasing(context, true);
        CGContextSetRGBStrokeColor(context, 255.0 / 255.0, 0.0 / 255.0, 70.0 / 255.0, 1.0);  //线的颜色
        CGContextBeginPath(context);
        //设置起始点
        CGContextMoveToPoint(context, line.point1.point.x, line.point1.point.y);
        //增加点
        CGContextAddLineToPoint(context, line.point2.point.x, line.point2.point.y);
        CGContextStrokePath(context);
        
        // 绘制文字
        NSString *string = [NSString stringWithFormat:@"%.0f px", line.lineWidth];
        UIFont *fount = [UIFont systemFontOfSize:7];
        CGPoint centerPoint = line.centerPoint;
        NSDictionary* attrDict = @{NSFontAttributeName : fount,
                               NSForegroundColorAttributeName: [UIColor redColor],
                               NSBackgroundColorAttributeName: [UIColor colorWithRed:1 green:1 blue:0 alpha:0.5f]};
        [string drawInRect:CGRectMake(centerPoint.x - 15, centerPoint.y - 6, 30, 16) withAttributes:attrDict];
    }
}

@end

以上就是我的的思路以及实现,有什么好的建议希望可以收到issue一起交流和谈论。

代码托管位置

代码传送门

© 著作权归作者所有

aron1992

aron1992

粉丝 66
博文 112
码字总数 183269
作品 0
厦门
程序员
私信 提问
加载中

评论(1)

你最好永远天真
你最好永远天真
厉害
iOS 9应用开发教程之创建iOS 9项目与模拟器介绍

iOS 9应用开发教程之创建iOS 9项目与模拟器介绍 编写第一个iOS 9应用 本节将以一个iOS 9应用程序为例,为开发者讲解如何使用Xcode 7.0去创建项目,以及iOS模拟器的一些功能、编辑界面等内容。...

大学霸
2015/08/06
197
0
iOS开发框架MVVM 1理解开发模式

好了,我作大死的连续第五天奋斗在电脑跟前了,好的。 接着上次写的iOS开发框架MVVM 1理解开发模式(占坑,未写完)来继续聊聊我们的MVVM。 昨天聊了网络层的封装,今天聊聊开发模式,架构师应...

testHs
2019/09/29
0
0
使用 Dojo Mobile 为 iOS 智能终端开发 Native-like Web 应用

简介 iOS 是 Apple 公司为 iPhone、iPod Touch、iPad 以及 iTV 等系列数码产品推出的一套基于 Mac OS X 并高度定制化的操作系统。随着这些数码产品的日益普及,越来越多的开发者加入到为其编...

IBMdW
2011/09/14
1.9K
1
使用 Interface Builder 兼容 iOS6 和iOS7

当你在更新你的App到iOS 7的平台时遇到最大的挑战之一就是确保不要遗忘那些还在使用iOS 6平台的用户,在此我们提供一些建议使你的App应用在iOS 6和iOS 7上同时保留视觉吸引力和技术功能. 此图...

isaced
2014/01/15
1.3W
12
UIViewPropertyAnimator 简介(一)

目前在 UIKit 至少有三种创建动画的方法。iOS 4 之前的 begin/commit 方式,以及在 iOS 4 中引入的 block 方式。在 iOS 10 发布的时候,UIKit 新增了一个创建动画的专用类 UIViewPropertyAn...

智小融
2018/07/09
0
0

没有更多内容

加载失败,请刷新页面

加载更多

每天AC系列(六):有效的括号

1 题目 LeetCode第20题,这题比较简单,匹配括号. 2 栈 这是栈的典型应用,括号匹配,当然不需要直接使用栈,使用一个StringBuilder即可: if(s.isEmpty()) return true;char a = s.charAt(0);...

Blueeeeeee
今天
27
0
Spring AOP-06-切入点类型

切入点是匹配连接点的拦截规则。之前使用的是注解@Pointcut,该注解是AspectJ中的。除了这个注解之外,Spring也提供了其他一些切入点类型: • 静态方法切入点StaticMethodMatcherPointcut •...

moon888
昨天
90
0
Class Loaders in Java

1. Introduction to Class Loaders Class loaders are responsible for loading Java classes during runtime dynamically to the JVM (Java Virtual Machine). Also, they are part of the ......

Ciet
昨天
96
0
以Lazada为例,看电商系统架构演进

什么是Lazada? Lazada 2012年成立于新加坡,是东南亚第一电商,2016年阿里投资10亿美金,2017年完成对lazada的收购。 业务模式上Lazada更偏重自营,类似于亚马逊,自建仓储和为商家提供服务...

春哥大魔王的博客
昨天
60
0
【自用】 Flutter Timer 简单用法

dart: void _startTime() async { _timer = Timer(Duration(seconds: sec), () { fun(xxx,yyy,zzz); }); } @override void dispose() { _timer.cancel()......

Tensor丨思悟
昨天
65
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部