UIBezierPath围绕多个子视图进行概述

时间:2016-02-29 07:12:40

标签: ios objective-c xcode swift uibezierpath

我有一个包含多个子视图的视图。我需要围绕用户选择的所有子视图绘制轮廓,并忽略未选择的子视图。我试过创建一个凸包,但它没有正确解决我的目的。 iOS中是否有可用于绘制所选视图边界的内容?

我找到了这个,但它仅用于交叉视图:link

这是我想要做的草图。选择S均值的视图和NS意味着未选择。红色标记的线是轮廓。

Sketch

为了澄清,如果示例中的左上角,右上角和左下角之间存在视图视图,则无法创建路径,因此不应绘制。

2 个答案:

答案 0 :(得分:2)

这是计算路径的函数(在Playground中)。我没有时间添加排除逻辑。我相信可以通过将顶部和底部线转换为可以测试交叉点的矩形列表来完成。 (如果我找到时间,我会编辑我的帖子添加。)

 import Foundation
 import UIKit
 import XCPlayground

 // compute enclosing Path for list of views
 // ----------------------------------------
 // - path is composed of a top line that hugs the topmost views
 //   and of a bottom line that hugs the bottom most views
 // - The two lines span the minimum and maximum x coordinates of
 //   the views in the list
 // NOTE: to do this cleanly, all four sides should be considered
 //       (I merely showed top and bottom to give an idea of the method)
 //
 func enclosingPathForViews(views:[UIView], margin:CGFloat = 3) -> UIBezierPath
 { 
   let frames = views.map({$0.frame.insetBy(dx: -margin, dy: -margin)})
   var path = UIBezierPath()

   // top left and right corners of each view
   // sorted from left to right, top to bottom
   var topPoints:[CGPoint] = frames.reduce(  Array<CGPoint>(),
                           combine: { $0 + [ CGPoint(x:$1.minX,y:$1.minY),
                                             CGPoint(x:$1.maxX,y:$1.minY) ] })
   topPoints = topPoints.sort({ $0.x == $1.x ? $0.y < $1.y : $0.x < $1.x })

   // trace top line from left to right
   // moving up or down when appropriate                                          
   var previousPoint = topPoints.first!
   path.moveToPoint(previousPoint) 
   for point in topPoints
   {
      guard point.y == previousPoint.y
         || point.y < previousPoint.y
            && frames.contains({$0.minX == point.x && $0.minY < previousPoint.y })
         || point.y > previousPoint.y
            && !frames.contains({ $0.maxX > point.x && $0.minY < point.y })
      else  { continue }

      if point.y < previousPoint.y
      { path.addLineToPoint(CGPoint(x:point.x, y:previousPoint.y)) }
      if point.y > previousPoint.y
      { path.addLineToPoint(CGPoint(x:previousPoint.x, y:point.y)) }
      path.addLineToPoint(point)
      previousPoint = point
   }

   // botom left and right corners of each view
   // sorted from right to left, bottom to top
   var bottomPoints:[CGPoint] = frames.reduce(  Array<CGPoint>(),
                                combine: { $0 + [ CGPoint(x:$1.minX,y:$1.maxY),
                                                  CGPoint(x:$1.maxX,y:$1.maxY) ] })
   bottomPoints = bottomPoints.sort({ $0.x == $1.x ? $0.y > $1.y : $0.x > $1.x })

   // trace bottom line from right to left
   // starting where top line left off (rightmost top corner)
   // moving up or down when appropriate                                          
   for point in bottomPoints
   {
      guard point.y == previousPoint.y
         || point.y > previousPoint.y
            && frames.contains({$0.maxX == point.x && $0.maxY > previousPoint.y })
         || point.y < previousPoint.y
            && !frames.contains({ $0.minX < point.x && $0.maxY > point.y })
      else  { continue }

      if point.y > previousPoint.y
      { path.addLineToPoint(CGPoint(x:point.x, y:previousPoint.y)) }
      if point.y < previousPoint.y
      { path.addLineToPoint(CGPoint(x:previousPoint.x, y:point.y)) }
      path.addLineToPoint(point)
      previousPoint = point
   }

   // close back to leftmost point of top line
   path.closePath()

   return path
 }

 // TESTS:
 // ======

 // UIView (container)
 // ------------------
 let viewSize    = CGSize(width: 300, height: 300)
 let view:UIView = UIView(frame: CGRect(origin: CGPointZero, size: viewSize))
 view.backgroundColor = UIColor.whiteColor()

 XCPlaygroundPage.currentPage.liveView = view


 // Selected Views
 // --------------
 var selectedViews:[UIView] = 
 [
    UIView(frame:CGRect(x: 130, y: 50, width: 50, height: 50)),
    UIView(frame:CGRect(x: 60, y: 30, width: 50, height: 50)),
    UIView(frame:CGRect(x: 20, y: 110, width: 50, height: 50))
 //   , UIView(frame:CGRect(x: 150, y: 150, width: 50, height: 50))
 ]

 for subView in selectedViews 
 { 
    subView.backgroundColor = UIColor.greenColor()
    view.addSubview(subView)
 }

 // Excluded views (non-selected)
 // --------------
 var excludedViews:[UIView] = 
 [
    UIView(frame:CGRect(x: 150, y: 110, width: 50, height: 50)),
 ]
 for subView in excludedViews 
 { 
    subView.backgroundColor = UIColor.redColor()
    view.addSubview(subView)
 }


 // CoreGraphics drawing
 // --------------------
 UIGraphicsBeginImageContextWithOptions(viewSize, false, 0)

 UIColor.blackColor().setStroke()
 let path = enclosingPathForViews(selectedViews)
 path.stroke()

 // set image to view layer 
 view.layer.contents = UIGraphicsGetImageFromCurrentImageContext().CGImage
 UIGraphicsEndImageContext()

答案 1 :(得分:0)

如果您只需要设置边界,则应该对对象进行子类化或扩展,例如UIButton,要在IBAction或手势回调中覆盖控制事件调用或不进行子类化,请在按钮上设置button.layer.borderWidth = 1.0。如果要隐藏它,请将其设置回0.0。

您还可以设置borderColor和cornerRadius。

根据按钮内容,您可能需要设置clipsSubviews = true