如何使用 Swift 以编程方式使用交替条纹图案填充 UIView?

How to fill a UIView with an alternating stripe pattern programmatically using Swift?

我可以使用带有 UIColor(patternImage: UIImage(named: "pattern.png")) 的图像实现交替条纹图案填充。

但是,如何使用一些简单的紧凑代码以编程方式实现交替条纹图案填充?

这是我想要实现的两个示例。

问题

Using code, how do I fill a UIView with different alternating color stripes using Swift?

(1) a two color alternating pattern running top to bottom (90 degrees)?

(2) a three color alternating pattern running top left to bottom right (45 degrees)?

当然可以。

您可以使用 Core Image 滤镜绘制条纹,或者您可以创建 UIImage 的自定义子 class 并实现 drawRect 方法。

编辑

使用 CIFilter:

我在 Github 上有一个名为 CIFilterTest (link) 的示例项目,可让您尝试所有不同类型的 Core Image Filters。它向系统询问可用过滤器的列表,然后尝试构建一个 UI 来为每个过滤器输入不同的设置。它不支持一些更特殊用途的输入,但它可以让您尝试 CIStripesGenerator,这是为您创建交替条纹的过滤器。该程序是用 Objective-C 编写的,但它应该能为您提供大致的思路。请注意,看起来 CIStripesGenerator 滤镜只会生成 2 种交替颜色的条纹,因此您不能将它用于您的 3 色案例。

使用drawRect

drawRect 中,您将创建一系列 UIBezierPaths 并用交替颜色填充它们。对于垂直条纹,您可以使用简单的 bezierPathWithRect: 初始化方法。对于对角线,您需要使用 moveToPointlineToPoint 创建水平条纹,或者对矩形路径应用旋转变换。

编辑#2:

这是一个示例 class,它在自身内部绘制了 2 个彩色矩形:

class StripedView: UIView {
  
    override func drawRect(rect: CGRect) {
      
      //Create a rect for the lefthand stripe
      var rect = CGRectInset(bounds, 10, 10)
      rect.size.width /= 2
      var bezier = UIBezierPath(rect: rect) //Create a bezier for the left stripe
      
      //Set the fill color to blue for the first rect
      UIColor.blueColor().setFill()
      bezier.fill()
      
      //Shift the rect over for the righthand stripe
      rect.origin.x = rect.size.width
      bezier = UIBezierPath(rect: rect) //Create a bezier for the right stripe
      
      UIColor.redColor().setFill()
      bezier.fill()
  }
}

您需要扩展上面的代码以创建一系列重复条纹而不是 2 个矩形,并使用 3 种颜色而不是 2 种颜色。

将条纹旋转 45 度更复杂。可能最简单的方法是通过沿着视图的边界映射每个条纹的角来创建对角条纹,然后使用 moveToPoint 和 [=19= 为每个条纹创建 UIBezierPath ] 来电。这超出了论坛的范围 post.

可以使用以下代码实现使用具有可调节条纹宽度和旋转的两个、三个或更多颜色条纹的条纹图案填充视图。此代码给出了下面示例图像中显示的三色条纹图案。

步骤:

  1. Add the below code to ViewController.swift
  2. Add a UIView to Storyboard.
  3. Add new alignment contstraints to the UIView on the Storyboard; Horizontally in Container = 0, Vertically in Container = 0.
  4. In the Identity Inspector, set the UIView custom class to 'colorStripesView'.
  5. Connect the UIView on the Storyboard to the viewPattern IBOutlet in the code.

代码:

    ////   ViewController.swift

    //// 1. Add the below code to ViewController.swift
    //// 2. Add a UIView to Storyboard.
    //// 3. Add new alignment contstraints to the UIView on the Storyboard; Horizontally in Container = 0, Vertically in Container = 0.
    //// 4. In the Identity Inspector, set the UIView custom class to 'colorStripesView'.
    //// 5. Connect the UIView on the Storyboard to the viewPattern IBOutlet in the code.

    import UIKit

    class ViewController: UIViewController {
        @IBOutlet weak var viewPattern: UIView!

        override func viewDidLoad() {
            super.viewDidLoad()

            //// Extend width and height constraints by factor of 2 for viewPattern rotation.
            let widthConstraint = NSLayoutConstraint(item: viewPattern, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: (max(view.bounds.height, view.bounds.width)*2))
            viewPattern.addConstraint(widthConstraint)
            let heightConstraint = NSLayoutConstraint(item: viewPattern, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: (max(view.bounds.height, view.bounds.width)*2))
            viewPattern.addConstraint(heightConstraint)

            //// Rotate pattern 0 degrees - vertical.
            //viewPattern.transform = CGAffineTransformMakeRotation(CGFloat(M_PI*0/180))
            //// Rotate pattern 45 degrees - diagonal top right to bottom left.
            //viewPattern.transform = CGAffineTransformMakeRotation(CGFloat(M_PI*45/180))
            //// Rotate pattern 90 degrees - horizontal.
            //viewPattern.transform = CGAffineTransformMakeRotation(CGFloat(M_PI*90/180))
            //// Rotate pattern 135 degrees - diagonal top left to bottom right.
            viewPattern.transform = CGAffineTransformMakeRotation(CGFloat(M_PI*135/180))

            //// Set view color
            viewPattern.backgroundColor = UIColor.clearColor()
        }

        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
        }
    }

    class colorStripesView: UIView {
        override func drawRect(rect: CGRect) {
            //// Set pattern tile colors width and height; adjust the color width to adjust pattern.
            let color1 = UIColor(red: 255/255, green: 255/255, blue: 10/255, alpha: 1.0)
            let color1Width: CGFloat = 10
            let color1Height: CGFloat = 10

            let color2 = UIColor(red: 0/255, green: 0/255, blue: 254/255, alpha: 1.0)
            let color2Width: CGFloat = 10
            let color2Height: CGFloat = 10

            let color3 = UIColor(red: 0/255, green: 128/255, blue: 128/255, alpha: 1.0)
            let color3Width: CGFloat = 10
            let color3Height: CGFloat = 10

            //// Set pattern tile orientation vertical.
            let patternWidth: CGFloat = (color1Width + color2Width + color3Width)
            let patternHeight: CGFloat = min(color1Height, color2Height, color3Height)

            //// Set pattern tile size.
            let patternSize = CGSize(width: patternWidth, height: patternHeight)

            //// Draw pattern tile
            let context = UIGraphicsGetCurrentContext()
            UIGraphicsBeginImageContextWithOptions(patternSize, false, 0.0)

            let color1Path = UIBezierPath(rect: CGRect(x: 0, y: 0, width: color1Width, height: color1Height))
            color1.setFill()
            color1Path.fill()

            let color2Path = UIBezierPath(rect: CGRect(x: color1Width, y: 0, width: color2Width, height: color2Height))
            color2.setFill()
            color2Path.fill()

            let color3Path = UIBezierPath(rect: CGRect(x: color1Width + color2Width, y: 0, width: color3Width, height: color3Height))
            color3.setFill()
            color3Path.fill()

            let image = UIGraphicsGetImageFromCurrentImageContext()
            UIGraphicsEndImageContext()

            //// Draw pattern in view
            UIColor(patternImage: image).setFill()
            CGContextFillRect(context, rect)
        }
    }

模拟器:

对于我的应用程序,创建可用作背景颜色的图案 UIColor 更有帮助且性能更高。

一旦我将它添加为 UIColor 的扩展,我就可以轻松地将它放在我的代码中的任何位置,它总是会完美填充,即使视图旋转、缩小和增大也是如此!

你这样使用它:

view.backgroundColor = UIColor.red.patternStripes()
view.backgroundColor = UIColor.red.patternStripes(color2: .darkGray)
view.backgroundColor = UIColor.red.patternStripes(color2: .darkGray, barThickness: 25.0)

并得到这个美丽:

这是 UIColor 的扩展:

extension UIColor {
    
    /// make a diagonal striped pattern
    func patternStripes(color2: UIColor = .white, barThickness t: CGFloat = 25.0) -> UIColor {
        let dim: CGFloat = t * 2.0 * sqrt(2.0)

        let img = UIGraphicsImageRenderer(size: .init(width: dim, height: dim)).image { context in

            // rotate the context and shift up
            context.cgContext.rotate(by: CGFloat.pi / 4.0)
            context.cgContext.translateBy(x: 0.0, y: -2.0 * t)

            let bars: [(UIColor,UIBezierPath)] = [
                (self,  UIBezierPath(rect: .init(x: 0.0, y: 0.0, width: dim * 2.0, height: t))),
                (color2,UIBezierPath(rect: .init(x: 0.0, y: t, width: dim * 2.0, height: t)))
            ]

            bars.forEach {  [=11=].0.setFill(); [=11=].1.fill() }
            
            // move down and paint again
            context.cgContext.translateBy(x: 0.0, y: 2.0 * t)
            bars.forEach {  [=11=].0.setFill(); [=11=].1.fill() }
        }
        
        return UIColor(patternImage: img)
    }
}

基本上,我正在创建一个临时绘图 space 用于绘制一个小矩形。我给它涂上了一种图案,这种图案在重复时会在所有方面无缝匹配。然后我拍下这张照片并告诉 UIColor 将其用作图案。

诀窍 I,您必须使图案图像足够大,以便为每个条绘制两次。结果是 bar thickness * 2 * sqrt(2).

我知道我可能会在风格方面受到一些打击。以下是我的想法:

  1. 我用了一个字母变量,比如t,描述性不是很好。我的辩护意见:我认为它使方程式更具可读性,并且当它仅限于接下来的几行时感觉没问题。
  2. 元组。同#1
  3. 使用小数(4.0 而不仅仅是 4)。我宁愿 而不是 使用小数,但我发现小数的编译时间更好。在更大的项目中,它可以产生巨大的不同。我也承认它可能读起来不那么漂亮,但在类型上减少歧义甚至对人类有帮助。

3 种颜色: 我刚刚意识到原来的问题是要求 3 种颜色,所以这里有一个版本...

首先,数学:

更新代码:

extension UIColor {
    
    /// make a diagonal striped pattern
    func pattern3Stripes(color2: UIColor, color3: UIColor, barThickness t: CGFloat = 25.0) -> UIColor {
        let sqrt2: CGFloat = sqrt(2.0)
        let dim: CGFloat = t * 3.0 * sqrt2
        let size: CGSize = .init(width: dim, height: dim)
        
        let img = UIGraphicsImageRenderer(size: size).image { context in
            
            // rotate the context and shift up
            context.cgContext.rotate(by: CGFloat.pi / 4.0)
            context.cgContext.translateBy(x: 0.0, y: -3.0 * t)
            
            let bars: [(UIColor,UIBezierPath)] = [
                (self,  UIBezierPath(rect: .init(x: 0.0, y: 0.0, width: dim * sqrt2, height: t))),
                (color2,UIBezierPath(rect: .init(x: 0.0, y: t, width: dim * sqrt2, height: t))),
                (color3,UIBezierPath(rect: .init(x: 0.0, y: 2.0 * t, width: dim * sqrt2, height: t)))
            ]
            
            bars.forEach {  [=12=].0.setFill(); [=12=].1.fill() }
            
            // move down and paint again
            context.cgContext.translateBy(x: 0.0, y: 3.0 * t)
            bars.forEach {  [=12=].0.setFill(); [=12=].1.fill() }
        }
        
        return UIColor(patternImage: img)
    }
}

用法:

    view.backgroundColor = UIColor.green.pattern3Stripes(color2: .white, color3: .red, barThickness: 25.0)

结果: