ios - 围绕多个 subview 的 UIBezierPath 轮廓

标签 ios objective-c xcode swift uibezierpath

我有一个包含多个 subview 的 View 。我需要在用户选择的所有 subview 周围画一个轮廓,并忽略那些没有被选中的 subview 。我曾尝试创建一个凸包,但它没有正确解决我的目的。 iOS 中是否有内置的东西可以用来在所选 View 周围绘制边界?

我找到了这个,但它仅用于相交 View :link

这是我正在尝试做的事情的草图。带有 S 的 View 表示选中,NS 表示未选中。红色标记线是轮廓。

Sketch

澄清一下,如果示例中左上角、右上角和左下角之间存在 View View ,则无法制作路径,因此不应绘制。

最佳答案

这是一个计算路径的函数(在 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()

关于ios - 围绕多个 subview 的 UIBezierPath 轮廓,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35693808/

相关文章:

ios - 根据 subview 所在的位置屏蔽掉 UIView 中的特定像素

ios - 无法转换泛型协议(protocol)函数类型实现的返回表达式

objective-c - 在 UITextView 中禁用双击

objective-c - Xcode 4.2 : xcdatamodel is marked as copied

ios - 为 XCode 6 中的企业部署导出存档

ios - Xcode Storyboard 工作流循环

ios - swift 中带有根元素的 Xml 请求 header

tableview 单元格内的 ios 文本字段

swift - 我的值不是 nil,而是 Getting Thread 1 : Fatal Error

ios - phonegap2.0 支持哪些最新的 xcode 版本