How to fill a UIView
like this (with some diagonally drawn white lines).
PS: My intentions is about the fill not the border.
Any help?
How to fill a UIView
like this (with some diagonally drawn white lines).
PS: My intentions is about the fill not the border.
Any help?
One way of achieving this would be to override the draw(_:)
method of UIView
and do your custom drawing there.
Drawing diagonal lines is fairly simple, you just need to:
Stride from 0 to width + height (along the horizontal edge of the rect, then up the vertical), by the gap + line width, converted from being a diagonal (at 45º) length to being parallel to the edge of the rect to draw in.
At each iteration, draw a line from the given point for that iteration to the point on the edge opposite (at 45º). We get this point by simply working up the vertical edge of the rect, then along the horizontal)
Something like this should achieve the desired result:
class StripeyView : UIView {
let lineGap: CGFloat = 7
let lineWidth: CGFloat = 3
let lineColor = UIColor.white
override func draw(_ rect: CGRect) {
let ctx = UIGraphicsGetCurrentContext()!
// flip y-axis of context, so (0,0) is the bottom left of the context
ctx.scaleBy(x: 1, y: -1)
ctx.translateBy(x: 0, y: -bounds.size.height)
// generate a slightly larger rect than the view,
// to allow the lines to appear seamless
let renderRect = bounds.insetBy(dx: -lineWidth * 0.5, dy: -lineWidth * 0.5)
// the total distance to travel when looping (each line starts at a point that
// starts at (0,0) and ends up at (width, height)).
let totalDistance = renderRect.size.width + renderRect.size.height
// loop through distances in the range 0 ... totalDistance
for distance in stride(from: 0, through: totalDistance,
// divide by cos(45º) to convert from diagonal length
by: (lineGap + lineWidth) / cos(.pi / 4)) {
// the start of one of the stripes
ctx.move(to: CGPoint(
// x-coordinate based on whether the distance is less than the width of the
// rect (it should be fixed if it is above, and moving if it is below)
x: distance < renderRect.width ?
renderRect.origin.x + distance :
renderRect.origin.x + renderRect.width,
// y-coordinate based on whether the distance is less than the width of the
// rect (it should be moving if it is above, and fixed if below)
y: distance < renderRect.width ?
renderRect.origin.y :
distance - (renderRect.width - renderRect.origin.x)
))
// the end of one of the stripes
ctx.addLine(to: CGPoint(
// x-coordinate based on whether the distance is less than the height of
// the rect (it should be moving if it is above, and fixed if it is below)
x: distance < renderRect.height ?
renderRect.origin.x :
distance - (renderRect.height - renderRect.origin.y),
// y-coordinate based on whether the distance is less than the height of
// the rect (it should be fixed if it is above, and moving if it is below)
y: distance < renderRect.height ?
renderRect.origin.y + distance :
renderRect.origin.y + renderRect.height
))
}
// stroke all of the lines added
ctx.setStrokeColor(lineColor.cgColor)
ctx.setLineWidth(lineWidth)
ctx.strokePath()
}
}
Output:
(Assuming the view has a red backgroundColor
)
You can adjust the lineGap
and lineWidth
properties to generate varying results.
Say you have these values ...
let T: CGFloat = 15 // desired thickness of lines
let G: CGFloat = 30 // desired gap between lines
let W = rect.size.width
let H = rect.size.height
amazingly, it's this simple ...
var p = -(W > H ? W : H) - T
while p <= W {
c.move( to: CGPoint(x: p-T, y: -T) )
c.addLine( to: CGPoint(x: p+T+H, y: T+H) )
c.strokePath()
p += G + T + T
}
Here is a complete UIView class:
class Ruled: UIView {
override func draw(_ rect: CGRect) {
let T: CGFloat = 15 // desired thickness of lines
let G: CGFloat = 30 // desired gap between lines
let W = rect.size.width
let H = rect.size.height
guard let c = UIGraphicsGetCurrentContext() else { return }
c.setStrokeColor(UIColor.orange.cgColor)
c.setLineWidth(T)
var p = -(W > H ? W : H) - T
while p <= W {
c.move( to: CGPoint(x: p-T, y: -T) )
c.addLine( to: CGPoint(x: p+T+H, y: T+H) )
c.strokePath()
p += G + T + T
}
}
}
That's it !
The entire fundamental algorithm:
Nice and easy! :)
The class above simply draws it the "size of the UIView". Often, you want to draw a number of the "boxes" actually within the view, at different coordinates. (A good example is for a calendar).
Furthermore, this example explicitly draws "both stripes" rather than drawing one stripe over the background color:
func simpleStripes(x: CGFloat, y: CGFloat, width: CGFloat, height: CGFloat) {
let stripeWidth: CGFloat = 20.0 // whatever you want
let m = stripeWidth / 2.0
guard let c = UIGraphicsGetCurrentContext() else { return }
c.setLineWidth(stripeWidth)
let r = CGRect(x: x, y: y, width: width, height: height)
let longerSide = width > height ? width : height
c.saveGState()
c.clip(to: r)
var p = x - longerSide
while p <= x + width {
c.setStrokeColor(pale blue)
c.move( to: CGPoint(x: p-m, y: y-m) )
c.addLine( to: CGPoint(x: p+m+height, y: y+m+height) )
c.strokePath()
p += stripeWidth
c.setStrokeColor(pale gray)
c.move( to: CGPoint(x: p-m, y: y-m) )
c.addLine( to: CGPoint(x: p+m+height, y: y+m+height) )
c.strokePath()
p += stripeWidth
}
c.restoreGState()
}
1, To offset, simply subtract from the pointer when you start it. Amazingly, nothing else needs to be changed.
var p = x - longerSide - offset // animate offset from 0 to stripeWidth
2, Careful programmers would prefer an offset equal to the miter to avoid the "pointy top-left corner" problem:
var p = x - longerSide - offset - m // for better-looking top-left corner
3, You can use any number of stripes in various colors, and indeed you can use different stripe widths in any combination. Amazingly, the algorithm still works and is safe. (If you have more than one width, just set the miter m
as the maximum width.)
The simplest code to draw slash lines:
import UIKit
@IBDesignable
class SlashPatternView: UIView {
@IBInspectable
var lineWidth: CGFloat = 1 { didSet { setNeedsLayout() } }
@IBInspectable
var squareSize: CGFloat = 1 { didSet { setNeedsLayout() } }
@IBInspectable
var lineColor: UIColor = .white { didSet { setNeedsLayout() }}
var slashes: UIImage {
let size = squareSize
let renderer = UIGraphicsImageRenderer(size: CGSize(width: size, height: size))
return renderer.image { context in
let cgcontext = context.cgContext
cgcontext.addLines(between: [CGPoint(x: 0, y: size/2), CGPoint(x: size/2, y: 0)])
cgcontext.addLines(between: [CGPoint(x: size/2, y: size), CGPoint(x: size, y: size/2)])
cgcontext.setStrokeColor(lineColor.cgColor)
cgcontext.setLineCap(.square)
cgcontext.setLineWidth(lineWidth)
cgcontext.strokePath()
}
}
override func layoutSubviews() {
super.layoutSubviews()
}
override func draw(_ rect: CGRect) {
backgroundColor?.setFill()
UIRectFill(rect)
slashes.drawAsPattern(in: rect)
}
}
The simplest way for me is to put a background image in your UIView (like this). The second solution is to draw the lines using Core Graphics Framework (more efficient, but more code to write).
Hope this help !