缘起

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

代码托管位置

代码传送门(本地下载)

总结

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

iOS自动进行View标记的方法详解的更多相关文章

  1. HTML5在微信内置浏览器下右上角菜单的调整字体导致页面显示错乱的问题

    HTML5在微信内置浏览器下,在右上角菜单的调整字体导致页面显示错乱的问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧

  2. iOS实现拖拽View跟随手指浮动效果

    这篇文章主要为大家详细介绍了iOS实现拖拽View跟随手指浮动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  3. ios – containerURLForSecurityApplicationGroupIdentifier:在iPhone和Watch模拟器上给出不同的结果

    我使用默认的XCode模板创建了一个WatchKit应用程序.我向iOSTarget,WatchkitAppTarget和WatchkitAppExtensionTarget添加了应用程序组权利.(这是应用程序组名称:group.com.lombax.fiveminutes)然后,我尝试使用iOSApp和WatchKitExtension访问共享文件夹URL:延期:iOS应用:但是,测试NSURL

  4. ios – Testflight无法安装应用程序

    我有几个测试人员注册了testflight并连接了他们的设备……他们有不同的ios型号……但是所有这些都有同样的问题.当他们从“safari”或“testflight”应用程序本身单击应用程序的安装按钮时……达到约90%并出现错误消息…

  5. ibm-mobilefirst – 在iOS 7.1上获取“无法安装应用程序,因为证书无效”错误

    当我的客户端将他们的设备更新到iOS7.1,然后尝试从AppCenter更新我们的应用程序时,我收到了上述错误.经过一番搜索,我找到了一个类似问题的帖子here.但是后来因为我在客户端使用AppCenter更新应用程序的环境中,我无法使用USB插件并为他们安装应用程序.在发布支持之前,是否有通过AppCenter进行下载的解决方法?

  6. ios – 视图的简单拖放?

    我正在学习iOS,但我找不到如何向UIView添加拖放行为.我试过了:它说“UIView没有可见的接口声明选择器addTarget”此外,我尝试添加平移手势识别器,但不确定这是否是我需要的它被称为,但不知道如何获得事件的坐标.在iOS中注册移动事件回调/拖放操作的标准简单方法是什么?

  7. ios – 什么控制iTunes中iPhone应用程序支持的语言列表?

    什么控制iPhone应用程序的iTunes页面中支持的语言?

  8. ios – 获得APNs响应BadDeviceToken或Unregistered的可能原因是什么?

    我知道设备令牌在某些时候是有效的.用户如何使其设备令牌变坏?从关于“未注册”的文档:Thedevicetokenisinactiveforthespecifiedtopic.这是否意味着应用程序已被删除?.您应该看到四种分发方法:如果您选择AppStore或Enterprise,您将在后面的对话框中看到Xcode将APNS权利更改为生产:如果选择AdHoc或Development,则aps-environment下的文本将是开发,然后应与后端的配置匹配.

  9. ios – 当我关闭应用程序时,我从调试器获得消息:由于信号15而终止

    我怎么能解决这个问题,我不知道这个链接MypreviousproblemaboutCoredata对我的问题有影响吗?当我cmd应用程序的Q时,将出现此消息.Messagefromdebugger:Terminatedduetosignal15如果谁知道我以前的问题的解决方案,请告诉我.解决方法>来自调试器的消息:每当用户通过CMD-Q(退出)或STOP手动终止应用程序(无论是在iOS模拟器中还是

  10. ios – NSUbiquityIdentityDidChangeNotification和SIGKILL

    当应用程序被发送到后台时,我们会删除观察者吗?我遇到的问题是,当UbiquityToken发生变化时,应用程序终止,因为用户已经更改了iCloud设置.你们如何设法订阅这个通知,如果你不这样做,你会做什么来跟踪当前登录的iCloud用户?

随机推荐

  1. iOS实现拖拽View跟随手指浮动效果

    这篇文章主要为大家详细介绍了iOS实现拖拽View跟随手指浮动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  2. iOS – genstrings:无法连接到输出目录en.lproj

    使用我桌面上的项目文件夹,我启动终端输入:cd然后将我的项目文件夹拖到终端,它给了我路径.然后我将这行代码粘贴到终端中找.-name*.m|xargsgenstrings-oen.lproj我在终端中收到此错误消息:genstrings:无法连接到输出目录en.lproj它多次打印这行,然后说我的项目是一个目录的路径?没有.strings文件.对我做错了什么的想法?

  3. iOS 7 UIButtonBarItem图像没有色调

    如何确保按钮图标采用全局色调?解决方法只是想将其转换为根注释,以便为“回答”复选标记提供更好的上下文,并提供更好的格式.我能想出这个!

  4. ios – 在自定义相机层的AVFoundation中自动对焦和自动曝光

    为AVFoundation定制图层相机创建精确的自动对焦和曝光的最佳方法是什么?

  5. ios – Xcode找不到Alamofire,错误:没有这样的模块’Alamofire’

    我正在尝试按照github(https://github.com/Alamofire/Alamofire#cocoapods)指令将Alamofire包含在我的Swift项目中.我创建了一个新项目,导航到项目目录并运行此命令sudogeminstallcocoapods.然后我面临以下错误:搜索后我设法通过运行此命令安装cocoapodssudogeminstall-n/usr/local/bin

  6. ios – 在没有iPhone6s或更新的情况下测试ARKit

    我在决定下载Xcode9之前.我想玩新的框架–ARKit.我知道要用ARKit运行app我需要一个带有A9芯片或更新版本的设备.不幸的是我有一个较旧的.我的问题是已经下载了新Xcode的人.在我的情况下有可能运行ARKit应用程序吗?那个或其他任何模拟器?任何想法或我将不得不购买新设备?解决方法任何iOS11设备都可以使用ARKit,但是具有高质量AR体验的全球跟踪功能需要使用A9或更高版本处理器的设备.使用iOS11测试版更新您的设备是必要的.

  7. 将iOS应用移植到Android

    我们制作了一个具有2000个目标c类的退出大型iOS应用程序.我想知道有一个最佳实践指南将其移植到Android?此外,由于我们的应用程序大量使用UINavigation和UIView控制器,我想知道在Android上有类似的模型和实现.谢谢到目前为止,guenter解决方法老实说,我认为你正在计划的只是制作难以维护的糟糕代码.我意识到这听起来像很多工作,但从长远来看它会更容易,我只是将应用程序的概念“移植”到android并从头开始编写.

  8. ios – 在Swift中覆盖Objective C类方法

    我是Swift的初学者,我正在尝试在Swift项目中使用JSONModel.我想从JSONModel覆盖方法keyMapper,但我没有找到如何覆盖模型类中的Objective-C类方法.该方法的签名是:我怎样才能做到这一点?解决方法您可以像覆盖实例方法一样执行此操作,但使用class关键字除外:

  9. ios – 在WKWebView中获取链接URL

    我想在WKWebView中获取tapped链接的url.链接采用自定义格式,可触发应用中的某些操作.例如HTTP://我的网站/帮助#深层链接对讲.我这样使用KVO:这在第一次点击链接时效果很好.但是,如果我连续两次点击相同的链接,它将不报告链接点击.是否有解决方法来解决这个问题,以便我可以检测每个点击并获取链接?任何关于这个的指针都会很棒!解决方法像这样更改addobserver在observeValue函数中,您可以获得两个值

  10. ios – 在Swift的UIView中找到UILabel

    我正在尝试在我的UIViewControllers的超级视图中找到我的UILabels.这是我的代码:这是在Objective-C中推荐的方式,但是在Swift中我只得到UIViews和CALayer.我肯定在提供给这个方法的视图中有UILabel.我错过了什么?我的UIViewController中的调用:解决方法使用函数式编程概念可以更轻松地实现这一目标.

返回
顶部