服务器之家:专注于服务器技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - IOS - iOS自动进行View标记的方法详解

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

2021-05-21 15:34aron1992 IOS

这篇文章主要给大家介绍了关于iOS自动进行View标记的相关资料,文中通过示例代码介绍的非常详细,对各位iOS开发者们具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧

缘起

一切都源于我的上一篇博客,我写的是一篇 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的边距就能得到体现了,不用再做多余的处理,这是一个讨巧的方案。

?
1
2
3
4
5
6
7
8
9
10
11
12
+ (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对象

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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的情况,这种不需要处理

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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种关系

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
+ (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差值小于某个阈值,选择线段长的那条删除,最终获取到了一个需要进行展示的结果数组,数组保存的是一系列的间距线段对象

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// 查找重复的射入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 的绘制

?
1
2
3
// 绘制view
taggingview* taggingview = [[taggingview alloc] initwithframe:view.bounds lines:lines];
[view addsubview:taggingview];

taggingview 在drawrect绘制线段以及线段长度的文字

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//
// 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一起交流和谈论。

代码托管位置

代码传送门

总结

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

原文链接:https://juejin.im/post/5ca5d48af265da30be228ef1

延伸 · 阅读

精彩推荐
  • IOSiOS中tableview 两级cell的展开与收回的示例代码

    iOS中tableview 两级cell的展开与收回的示例代码

    本篇文章主要介绍了iOS中tableview 两级cell的展开与收回的示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...

    J_Kang3862021-04-22
  • IOS关于iOS自适应cell行高的那些事儿

    关于iOS自适应cell行高的那些事儿

    这篇文章主要给大家介绍了关于iOS自适应cell行高的那些事儿,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的...

    daisy6092021-05-17
  • IOSiOS布局渲染之UIView方法的调用时机详解

    iOS布局渲染之UIView方法的调用时机详解

    在你刚开始开发 iOS 应用时,最难避免或者是调试的就是和布局相关的问题,下面这篇文章主要给大家介绍了关于iOS布局渲染之UIView方法调用时机的相关资料...

    windtersharp7642021-05-04
  • IOS解析iOS开发中的FirstResponder第一响应对象

    解析iOS开发中的FirstResponder第一响应对象

    这篇文章主要介绍了解析iOS开发中的FirstResponder第一响应对象,包括View的FirstResponder的释放问题,需要的朋友可以参考下...

    一片枫叶4662020-12-25
  • IOSiOS通过逆向理解Block的内存模型

    iOS通过逆向理解Block的内存模型

    自从对 iOS 的逆向初窥门径后,我也经常通过它来分析一些比较大的应用,参考一下这些应用中某些功能的实现。这个探索的过程乐趣多多,不仅能满足自...

    Swiftyper12832021-03-03
  • IOSIOS开发之字典转字符串的实例详解

    IOS开发之字典转字符串的实例详解

    这篇文章主要介绍了IOS开发之字典转字符串的实例详解的相关资料,希望通过本文能帮助到大家,让大家掌握这样的方法,需要的朋友可以参考下...

    苦练内功5832021-04-01
  • IOSiOS 雷达效果实例详解

    iOS 雷达效果实例详解

    这篇文章主要介绍了iOS 雷达效果实例详解的相关资料,需要的朋友可以参考下...

    SimpleWorld11022021-01-28
  • IOSIOS 屏幕适配方案实现缩放window的示例代码

    IOS 屏幕适配方案实现缩放window的示例代码

    这篇文章主要介绍了IOS 屏幕适配方案实现缩放window的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要...

    xiari5772021-06-01