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

缘起

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

结果

所以,我发了点时间实现了我的想法,下面是实现的结果截图:

以及代码开源托管地址:代码链接 (本地下载)

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

预览图

过去几小时内的想法

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

  • 获取水平的间距:遍历父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,minWH,view.bounds.size.height)];
 [view addSubview:leftBorderView];
 MMBorderAttachView* rightBorderView = [[MMBorderAttachView alloc] initWithFrame:CGRectMake(view.bounds.size.width-minWH,view.bounds.size.height)];
 [view addSubview:rightBorderView];

 MMBorderAttachView* topBorderView = [[MMBorderAttachView alloc] initWithFrame:CGRectMake(0,view.bounds.size.width,minWH)];
 [view addSubview:topBorderView];
 MMBorderAttachView* bottomBorderView = [[MMBorderAttachView alloc] initWithFrame:CGRectMake(0,view.bounds.size.height - minWH,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轴使用。

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

两条线段的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一起交流和谈论。

代码托管位置

代码传送门(本地下载)

总结

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


当我们远离最新的 iOS 16 更新版本时,我们听到了困扰 Apple 最新软件的错误和性能问题。
欧版/美版 特别说一下,美版选错了 可能会永久丧失4G,不过只有5%的概率会遇到选择运营商界面且部分必须连接到iTunes才可以激活
一般在接外包的时候, 通常第三方需要安装你的app进行测试(这时候你的app肯定是还没传到app store之前)。
前言为了让更多的人永远记住12月13日,各大厂都在这一天将应用变灰了。那么接下来我们看一下Flutter是如何实现的。Flutter中实现整个App变为灰色在Flutter中实现整个App变为灰色是非常简单的,只需要在最外层的控件上包裹ColorFiltered,用法如下:ColorFiltered(颜色过滤器)看名字就知道是增加颜色滤镜效果的,ColorFiltered( colorFilter:ColorFilter.mode(Colors.grey, BlendMode.
flutter升级/版本切换
(1)在C++11标准时,open函数的文件路径可以传char指针也可以传string指针,而在C++98标准,open函数的文件路径只能传char指针;(2)open函数的第二个参数是打开文件的模式,从函数定义可以看出,如果调用open函数时省略mode模式参数,则默认按照可读可写(ios_base:in | ios_base::out)的方式打开;(3)打开文件时的mode的模式是从内存的角度来定义的,比如:in表示可读,就是从文件读数据往内存读写;out表示可写,就是把内存数据写到文件中;
文章目录方法一:分别将图片和文字置灰UIImage转成灰度图UIColor转成灰度颜色方法二:给App整体添加灰色滤镜参考App页面置灰,本质是将彩色图像转换为灰度图像,本文提供两种方法实现,一种是App整体置灰,一种是单个页面置灰,可结合具体的业务场景使用。方法一:分别将图片和文字置灰一般情况下,App页面的颜色深度是24bit,也就是RGB各8bit;如果算上Alpha通道的话就是32bit,RGBA(或者ARGB)各8bit。灰度图像的颜色深度是8bit,这8bit表示的颜色不是彩色,而是256
领导让调研下黑(灰)白化实现方案,自己调研了两天,根据网上资料,做下记录只是学习过程中的记录,还是写作者牛逼
让学前端不再害怕英语单词(二),通过本文,可以对css,js和es6的单词进行了在逻辑上和联想上的记忆,让初学者更快的上手前端代码
用Python送你一颗跳动的爱心
在uni-app项目中实现人脸识别,既使用uni-app中的live-pusher开启摄像头,创建直播推流。通过快照截取和压缩图片,以base64格式发往后端。
商户APP调用微信提供的SDK调用微信支付模块,商户APP会跳转到微信中完成支付,支付完后跳回到商户APP内,最后展示支付结果。CSDN前端领域优质创作者,资深前端开发工程师,专注前端开发,在CSDN总结工作中遇到的问题或者问题解决方法以及对新技术的分享,欢迎咨询交流,共同学习。),验证通过打开选择支付方式弹窗页面,选择微信支付或者支付宝支付;4.可取消支付,放弃支付会返回会员页面,页面提示支付取消;2.判断支付方式,如果是1,则是微信支付方式。1.判断是否在微信内支付,需要在微信外支付。
Mac命令行修改ipa并重新签名打包
首先在 iOS 设备中打开开发者模式。位于:设置 - 隐私&安全 - 开发者模式(需重启)
一 现象导入MBProgressHUD显示信息时,出现如下异常现象Undefined symbols for architecture x86_64: "_OBJC_CLASS_$_MBProgressHUD", referenced from: objc-class-ref in ViewController.old: symbol(s) not found for architecture x86_64clang: error: linker command failed wit
Profiles >> 加号添加 >> Distribution >> "App Store" >> 选择 2.1 创建的App ID >> 选择绑定 2.3 的发布证书(.cer)>> 输入描述文件名称 >> Generate 生成描述文件 >> Download。Certificates >> 加号添加 >> "App Store and Ad Hoc" >> “Choose File...” >> 选择上一步生成的证书请求文件 >> Continue >> Download。
今天有需求,要实现的功能大致如下:在安卓和ios端实现分享功能可以分享链接,图片,文字,视频,文件,等欢迎大佬多多来给萌新指正,欢迎大家来共同探讨。如果各位看官觉得文章有点点帮助,跪求各位给点个“一键三连”,谢啦~声明:本博文章若非特殊注明皆为原创原文链接。