iOS自动进行View标记的方法详解

缘起

一切都源于我的上一篇博客,我写的是一篇 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一起交流和谈论。

代码托管位置

代码传送门(本地下载)

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对我们的支持。

(0)

相关推荐

  • iOS如何获取当前View所在控制器的方法

    前言 不知道大家有没有遇到过在做轮播图的时候,有点轮播图展示的是广告,有的是活动,等等还有其他的,当前点击某个轮播的时候要跳转到不同的控制器,点击事件是在控制器写的,为了避免控制器代码过多,显示的臃肿.我创建了一个UIWindow的分类,暂且叫Model (GetCurrentVC) 实现方法 谷歌还有很多方法,下面这个方法亲测有效,有需要的可以参考借鉴. 一: @interfaceUIWindow (GetCurrentVC) - (UIViewController*)getCurrentVC

  • iOS仿简书、淘宝等App的View弹出效果

    用简书App的时候觉得这个View的弹出效果特别好,而且非常平滑,所以我就尝试写了一个,和简书App上的效果基本一致了: 下面开始讲解: 1.首先我们要知道这个页面有几个View?这个页面其实有四个View,self.view , 图中白色VC的View rootVC.view ,白色VC上的maskView maskView , 以及弹出的popView popView .我们创建它们: self.view.backgroundColor = [UIColor blackColor]; _po

  • 基于IOS实现带箭头的view

    我使用DrawRect进行的View的拉伸(是这样描述的吧??), 效果图也实现了类似于微信的View效果, 你可以看一看. 创建继承于UIView的视图 .h文件 // backGoundView @property (nonatomic, strong) UIView * _Nonnull backGoundView; // titles @property (nonatomic, strong) NSArray * _Nonnull dataArray; // images @proper

  • iOS自动进行View标记的方法详解

    缘起 一切都源于我的上一篇博客,我写的是一篇 UITableViewCell使用自动布局的"最佳实践" ,我需要给我的图片里面的UIView元素添加上边距的标记,这让我感到很为难,我觉得我得发点时间写一个程序让这个步骤自动化,我只要一键就能让我的程序自动标记边距,这个比我要手动去标记来的酷很多不是吗! 结果 所以,我发了点时间实现了我的想法,下面是实现的结果截图: 以及代码开源托管地址:代码链接 (本地下载) 预览图 过去几小时内的想法 静下心来整理我的想法和寻找方案,大概的整理下了一

  • Android 使用View Binding的方法详解

    前言 Android Studio稳定版发布了3.6版本,带来了一些新变化:首先外观,启动页变了,logo改了,更显现代化:增加Multi Preview功能,能同时预览多个尺寸屏幕的显示效果:模拟器支持多屏:也终于支持全新的视图绑定组件View Binding:等. 之前我们与视图交互的方式有findViewById.kotlin中引入Android Kotlin Extensions后直接通过id进行访问.前者模板化严重,重复代码多:后者最为方便.现在有了新的选择–View Binding,

  • Python自动操作Excel文件的方法详解

    目录 工具 读取Excel文件内容 写入Excel文件内容 Excel文件样式调整 设置表头的位置 设置单元格的宽高 总结 工具 python3.7 Pycharm Excel xlwt&xlrd 读取Excel文件内容 当前文件夹下有一个名为“股票数据.xlsx”的Excel文件,可以按照下列代码方式来操作它. import xlrd # 使用xlrd模块的open_workbook函数打开指定Excel文件并获得Book对象(工作簿) wb = xlrd.open_workbook('股票数

  • QT委托代理机制之Model View Delegate使用方法详解

    目录 本地数据加载(Data) 添加数据模型(Model) 添加代理模型(Proxy) 添加元素的代理(Delegate) 添加视图层(View) 使用效果 之前的一篇文章中介绍过QT的委托代理机制,那时候由于理解的比较浅就简单的给了一个例子.最近又做了一部分相关的工作,发现之前的理解有点问题.这里就详细的介绍一下QT的委托代理机制的用法,希望对大家有帮助. Model-View-Delegate机制可以简单的理解为将本地的一些数据以特定的UI形式呈现出来.常见的数据结构包括列表数据(list)

  • PHP中的自动加载操作实现方法详解

    本文实例讲述了PHP中的自动加载操作实现方法.分享给大家供大家参考,具体如下: what is 自动加载? 或许你已经对自动加载有所了解.简单描述一下:自动加载就是我们在new一个class的时候,不需要手动去写require来导入这个class.php文件,程序自动帮我们加载导入进来.这是php5.1.2(好像是)版本新加入一个功能,他解放了程序员的双手,不需要手动写那么多的require,变得有那么点智能的感觉. 自动加载可以说是现代PHP框架的根基,任何牛逼的框架或者架构都会用到它,它发明

  • Java自动添加重写的toString方法详解

    Java怎么自动添加重写的toString方法,这里我们将给大家介绍详细的解决方法. 首先,添加一个任意的类,具体的类型没有要求,然后在主程序中创建对象,这里要求构造方法的位置要求必须是可实例化的类或其子类对象. 然后在主程序中创建对象,这里要求构造方法的位置要求必须是可实例化的类或其子类对象. 然后,在该程序中点击鼠标右键,找到鼠标右键,找到source选项. 在第三步中找到source选项中,找到generate toString( )方法. 进入之后,什么都不用选择,直接点击界面最下方的o

  • iOS中自动实现对象序列化的方法详解

    前言 在iOS 中实现对象序列化,需要遵行NSCoding协议,然后对对象的每个属性进行归档和接档赋值,响应的操作比较繁琐.本文主要介绍 利用 runtime遍历属性 大大简化代码量,下面来看看详细的介绍吧. 具体实现代码如下: 1.先建立NSobject的分类, 定义可能用到的相关类型 static NSString *intType = @"i"; // int_32t(枚举int型) static NSString *longTpye = @"l"; //lo

  • iOS实现富文本编辑器的方法详解

    前言 富文本编辑器不同于文本编辑器,国内做的比较好的比如有百度的UEditor和kindEditor.但是这两个也有它的缺点:界面过于复杂.不够简洁.UI设计也比较落后.不够轻量化,这篇文章我们将给大家介绍利用iOS如何实现富文本编辑器. 实现的效果 解决思路 采用webview加载一个本地html文件,该html内部编写好js方法用于与oc相互调用 最终输出该富文本字符串传输给服务器 为什么选择这样的方式 服务端要求我最终返回的数据格式为: { @"Id":"当时新建模板这

  • iOS下一键调试Push的方法详解

    前言 来湾区工作的一项有趣之处,是可以和来自完全不同工程文化背景的程序员们碰撞交流,语言习惯,教育环境,思维模式,工程经验都存在不小的差异.来湾区半年有余,这段时间下来有一点我感受颇深,这边的程序员非常强调做一件事的效率.在遇到一个有挑战性的项目时,前期的设计讨论调整非常频繁以求最优路径抵达目标,平常做项目时,各个程序员都有自己的工具箱和小脚本来应付各类场景.目的是都花最少量的时间干最多的活,又或者是为了不加班 :) 闲话不提,这篇文章和大家分享一个我之前调试 APN 的方式. 场景:测试又又又

  • Swift使用WKWebView在iOS应用中调用Web的方法详解

    自从iOS8开始,Apple引入了WKWebView欲代替UIWebView.相比而言,WKWebView消耗内从更少,功能也更加强大.让我们来看看WKWebView怎么使用吧! 0.初始化 (1)首先需要引入WebKit库 复制代码 代码如下: #import <WebKit/WebKit.h> (2)初始化方法分为以下两种 复制代码 代码如下: // 默认初始化 - (instancetype)initWithFrame:(CGRect)frame; // 根据对webview的相关配置,

随机推荐