I can achieve an alternating stripe pattern fill using an image with UIColor(patternImage: UIImage(named: "pattern.png"))
.
However, how can I achieve an alternating stripe pattern fill drawing it programmatically with some simple compact code?
Here are two examples I'd like to achieve.
Question
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)?
Filling a view with a stripe pattern with two, three or more color stripes with adjustable stripe widths and rotation can be achieved with the below code. This code gives a three colour stripe pattern shown in the example image below.
Steps:
- Add the below code to ViewController.swift
- Add a UIView to Storyboard.
- Add new alignment contstraints to the UIView on the Storyboard; Horizontally in Container = 0, Vertically in Container = 0.
- In the Identity Inspector, set the UIView custom class to 'colorStripesView'.
- Connect the UIView on the Storyboard to the viewPattern IBOutlet in the code.
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)
}
}
Simulator:
For my application it was more helpful and performant to create a pattern UIColor
that can be used as a background color.
Once I add this as an extension to UIColor
I can easily put this anywhere in my code and it will always fill perfectly, even as views rotate, shrink and grow!
You use it like this:
view.backgroundColor = UIColor.red.patternStripes()
view.backgroundColor = UIColor.red.patternStripes(color2: .darkGray)
view.backgroundColor = UIColor.red.patternStripes(color2: .darkGray, barThickness: 25.0)
and get this beauty:
Here is an extension to 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 { $0.0.setFill(); $0.1.fill() }
// move down and paint again
context.cgContext.translateBy(x: 0.0, y: 2.0 * t)
bars.forEach { $0.0.setFill(); $0.1.fill() }
}
return UIColor(patternImage: img)
}
}
Basically, I am creating a temporary drawing space for one little rectangle to paint. I paint it with a pattern that will match seamlessly on all sides when repeated. Then I snap an image of this and tell UIColor
to use it as a pattern.
The trick I that you have to make your pattern image big enough to paint each bar two times. That turns out to be bar thickness * 2 * sqrt(2).
I know I could get beaten up a little about style. Here are my thoughts:
t
, which is not very descriptive. My defense: I think it makes the equations more readable and feel like it's okay when it's limited to only the next few lines.4.0
instead of just 4
). I would prefer to not use the decimals but I have found better compile times with the decimal. On bigger projects it can make a huge difference. I will also admit that it may not read as pretty but being less ambiguous about type can even be helpful to humans.3 Colors: I just realized that the original question was asking for 3 colors, so here is a version for that...
First, the math:
Updated code:
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 { $0.0.setFill(); $0.1.fill() }
// move down and paint again
context.cgContext.translateBy(x: 0.0, y: 3.0 * t)
bars.forEach { $0.0.setFill(); $0.1.fill() }
}
return UIColor(patternImage: img)
}
}
Usage:
view.backgroundColor = UIColor.green.pattern3Stripes(color2: .white, color3: .red, barThickness: 25.0)
Result:
Sure you can.
You might be able to draw your stripes using a Core Image filter, or you could create a custom subclass of UIImage and implement the drawRect
method.
CIFilter
:I have a sample project called CIFilterTest (link) on Github that lets you try out all different kinds of Core Image Filters. It interrogates the system for a list of available filters and then tries to build a UI for entering the different settings for each filter. It doesn't support some of the more special-purpose inputs, but it will let you try out the CIStripesGenerator
, which is the filter that will create alternating stripes for you. The program is written in Objective-C but it should give you the general idea. Note that it looks like the CIStripesGenerator
filter will only generate stripes in 2 alternating colors, so you wouldn't be able to use it for your 3-color case.
drawRect
:In drawRect
you'd create a series of UIBezierPaths and fill them with alternating colors. For the vertical stripes you could use the simple bezierPathWithRect:
init method. For the diagonal lines you'd either need to create the horizontal stripes using moveToPoint
& lineToPoint
, or apply a rotation transform to rectangle paths.
Here is a sample class that draws 2 colored rectangles inside itself:
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()
}
}
You'll need to expand on the code above to create a series of repeating stripes rather than 2 rectangles, and to use 3 rather than 2 colors.
Rotating the stripes 45 degrees is more complicated. Probably the simplest way to do that would be to create diagonal stripes by mapping out the corners of each stripe along the borders of the view and then creating a UIBezierPath
for each stripe using moveToPoint
and lineToPoint
calls. That's beyond the scope of a forum post.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With