iOS自動(dòng)進(jìn)行View標(biāo)記的方法詳解
緣起
一切都源于我的上一篇博客,我寫的是一篇 UITableViewCell使用自動(dòng)布局的“最佳實(shí)踐” ,我需要給我的圖片里面的UIView元素添加上邊距的標(biāo)記,這讓我感到很為難,我覺得我得發(fā)點(diǎn)時(shí)間寫一個(gè)程序讓這個(gè)步驟自動(dòng)化,我只要一鍵就能讓我的程序自動(dòng)標(biāo)記邊距,這個(gè)比我要手動(dòng)去標(biāo)記來的酷很多不是嗎!
結(jié)果
所以,我發(fā)了點(diǎn)時(shí)間實(shí)現(xiàn)了我的想法,下面是實(shí)現(xiàn)的結(jié)果截圖:
預(yù)覽圖
過去幾小時(shí)內(nèi)的想法
靜下心來整理我的想法和尋找方案,大概的整理下了一個(gè)可行性的方案以及這個(gè)方案中需要使用到的步驟,其中一些細(xì)節(jié)沒有在這個(gè)步驟中體現(xiàn)
- 獲取水平的間距:遍歷父View的子View,獲取某個(gè)子sourceView的右邊到其他子targetView的左邊的距離,把結(jié)果保存到子targetView的入度數(shù)組中
- 獲取垂直的間距:遍歷父View的子View,獲取某個(gè)子sourceView的下邊到其他子targetView的上邊的距離,把結(jié)果保存到子targetView的入度數(shù)組中
- 篩選出targetView的入度數(shù)組中所以不符合的結(jié)果,刪除這些結(jié)果
- 最終獲取到了一個(gè)需要進(jìn)行展示的結(jié)果數(shù)組,數(shù)組保存的是一系列的間距線段對象
- 創(chuàng)建一個(gè)顯示標(biāo)記的TagView層,把結(jié)果的線段繪制在TagView上面,然后把TabView添加到父View上
代碼實(shí)現(xiàn)解析
注入測試邊框View
我的方案中所有的間距都是基于子View考慮的,所以子View和父View的邊距需要特殊的計(jì)算,可以使用在父View的旁邊添加一個(gè)物理像素的子View,最終只要處理所有這些子View,子View和父View的邊距就能得到體現(xiàn)了,不用再做多余的處理,這是一個(gè)討巧的方案。
+ (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的右邊對應(yīng)的其他View的左邊;2、尋找View的下邊對應(yīng)的其他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的右邊對應(yīng)的其他View的左邊 MMLine* hLine = [self horizontalLineWithFrameObj1:sourceFrameObj frameObj2:targetFrameObj]; if (hLine) { [lines addObject:hLine]; [targetFrameObj.leftInjectedObjs addObject:hLine]; } // 尋找View的下邊對應(yīng)的其他View的上邊 MMLine* vLine = [self verticalLineWithFrameObj1:sourceFrameObj frameObj2:targetFrameObj]; if (vLine) { [lines addObject:vLine]; [targetFrameObj.topInjectedObjs addObject:vLine]; } } }
獲取間距線段的實(shí)現(xiàn)
以獲取水平的間距線段為例,這種情況,只需要處理一個(gè)子View在另一個(gè)子View的右邊的情況,否則返回nil跳過。獲取水平間距線段,明顯的線段的X軸是確定的,要,只要處理好Y軸就行了,問題就抽象為了兩個(gè)線段的問題,這部分是在approperiatePointWithInternal方法中處理的,主要步驟是一長的線段為標(biāo)準(zhǔn),然后枚舉短的線段和長的線段存在的5種情況,相應(yīng)的計(jì)算合適的值,然后給Y軸使用。
兩條線段的5種關(guān)系
+ (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; }
過濾線段
一個(gè)子View對象的入度可能有好幾個(gè),需要篩選進(jìn)行刪除,我使用的篩選策略是:以水平的間距線段為例,兩條線段的Y差值小于某個(gè)閾值,選擇線段長的那條刪除,最終獲取到了一個(gè)需要進(jìn)行展示的結(jié)果數(shù)組,數(shù)組保存的是一系列的間距線段對象
// 查找重復(fù)的射入line // hLine:Y的差值小于某個(gè)值,leftInjectedObjs->取最小一條 // vLine:X的差值小于某個(gè)值,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); //設(shè)置起始點(diǎn) CGContextMoveToPoint(context, line.point1.point.x, line.point1.point.y); //增加點(diǎn) 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
以上就是我的的思路以及實(shí)現(xiàn),有什么好的建議希望可以收到issue一起交流和談?wù)摗?br />
代碼托管位置
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對腳本之家的支持。
相關(guān)文章
iOS開發(fā)之WKWebViewJavascriptBridge Xcode9中導(dǎo)致crash的解決
大家都知道WebViewJavascriptBridge它主要幫助我們優(yōu)雅的實(shí)現(xiàn)OC與JS的交互,下面這篇文章主要給大家介紹了關(guān)于iOS開發(fā)之WKWebViewJavascriptBridge Xcode9中導(dǎo)致crash的解決方法,需要的朋友可以參考借鑒,下面來一起看看吧。2017-10-10IOS正則表達(dá)式之驗(yàn)證密碼身份證手機(jī)號
這篇文章主要介紹了IOS正則表達(dá)式之驗(yàn)證密碼身份證手機(jī)號的相關(guān)資料,希望通過本文能幫助到大家,需要的朋友可以參考下2017-10-10IOS開發(fā)筆記整理49之詳解定位CLLocation
在項(xiàng)目功能中有一個(gè)定位CLLocation的需求,遇到了一些知識(shí)難點(diǎn),經(jīng)過各位大俠的幫助,問題解決,特此分享供大家學(xué)習(xí),希望大家共同學(xué)習(xí)進(jìn)步2015-11-11iOS獲取短信驗(yàn)證碼倒計(jì)時(shí)的兩種實(shí)現(xiàn)方法
本篇文章主要介紹了iOS獲取短信驗(yàn)證碼倒計(jì)時(shí)的兩種實(shí)現(xiàn)方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-05-05iOS開發(fā)之App主題切換解決方案完整版(Swift版)
這篇文章主要為大家詳細(xì)介紹了iOS開發(fā)之App主題切換完整解決方案,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-02-02iOS中解決Xcode9的Log日志無法輸出中文的問題小結(jié)
這篇文章主要介紹了iOS中解決Xcode9的Log日志無法輸出中文的問題小結(jié),需要的朋友可以參考下2017-11-11