当前位置:网站首页>1-gradients, shadows, and text

1-gradients, shadows, and text

2022-06-23 08:24:00 the Summer Palace

This article is about 《Core Graphics Introductory tutorial 》 The first lecture of . After learning this article , You will learn how to use... In custom controls CG Function to draw a gradient 、 Shadows and text .

ShadowButton

Suppose we want to customize a button control called ShadowButton—— It's not worth the fuss , But the special point is , We are going to use Core Graphics 2D Draw its UI.

First create this class. First , We need to inherit UIControl instead of UIView, This is for the convenience of using it state Property and handle the user's touch .

class ShadowButton: UIControl {
	// 1
    lazy var outerShadow: NSShadow = {
        let outerShadow = NSShadow()
        outerShadow.shadowColor = UIColor.black
        outerShadow.shadowOffset = CGSize(width: 3.1, height: 3.1)
        outerShadow.shadowBlurRadius = 5
        return outerShadow
    }()
    lazy var innerShadow: NSShadow = {
        let innerShadow = NSShadow()
        innerShadow.shadowColor = UIColor.white
        innerShadow.shadowOffset = CGSize(width: 3.1, height: 3.1)
        innerShadow.shadowBlurRadius = 9
        return innerShadow
    }()
    lazy var text: Text? = Text(text: "Shadow Button", font: UIFont.systemFont(ofSize: 18), color: .black)
    lazy var gradient: CGGradient = {
        let deepFillColor = UIColor(red: 0.502, green: 0.502, blue: 0.502, alpha: 1.000)
        let lightFillColor = UIColor(red: 0.871, green: 0.871, blue: 0.871, alpha: 1.000)
        let gradient = CGGradient(colorsSpace: CGColorSpaceCreateDeviceRGB(), colors: [lightFillColor.cgColor, deepFillColor.cgColor] as CFArray, locations: [0, 1])!
        return gradient
    }()
    var buttonRadius: CGFloat = 11
    // 2
    override func draw(_ rect: CGRect) {
        
        if isSelected == true {
            selectDraw(rect)
        }else {
            unselectDraw(rect)
        }
    }
    // 3
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        self.isSelected = true
        setNeedsDisplay()
    }
    // 4
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        self.isSelected = false
        setNeedsDisplay()
    }
    // 5
    public  func selectDraw(_ frame: CGRect) {

        let rect = RectanglePainter.insetFrame(frame, delta: outerShadow.shadowBlurRadius)
        RectanglePainter.drawGradient(gradient: gradient, frame: rect, cornerRadius: buttonRadius, outerShadow: outerShadow)
        RectanglePainter.drawInnerShadow(rect, cornerRadius: buttonRadius, innerShadow: innerShadow)
        if text != nil {
            TextPainter.drawText(rect, text: text!)
        }
    }
    // 6
    public func unselectDraw(_ frame: CGRect) {

        let rect = RectanglePainter.insetFrame(frame, delta: outerShadow.shadowBlurRadius)
        RectanglePainter.drawGradient(gradient: gradient, frame: rect, cornerRadius: buttonRadius, outerShadow: outerShadow)
        if text != nil {
            TextPainter.drawText(rect, text: text!)
        }
    }
}
  1. Button properties , Including rounded corners 、 Inner shadow 、 Shadow of the vulva 、 Text etc. , Easy to Customize button styles from the outside . The definition of lazy load attribute is used here , So when you first call these properties , These attribute values are initialized automatically . Please note that NSShadow、Text and CGGrandient How these structures are constructed .
  2. use Core Graphics The main method of drawing is draw(rect:) Method , Here we will judge the button isSelected state , And call the corresponding method UI The draw .
  3. touchesBegan function , Respond to the user's touch action . In this way, when the user touches the button, there will be a highlighted effect . The normal control state is unselected Of , But when the user clicks on it , We change the control isSelected by true, And then call setNeedsDisplay Redraw control —— This actually triggers draw(rect:) Method .
  4. touchesEnded function , Respond to the user's release action . This will have a darkening effect when the user releases the button . When the user releases it , We change the control isSelected by false, And call setNeedsDisplay Redraw control , In this way, the button returns to its original state .
  5. selectDraw Method is responsible for isSelected by true Drawing at . Here we call two Painter class , these Painter Class encapsulates Core Graphics Drawing method . First call RectanglePainter Draw a rectangle ( Draw the outer shadow at the same time ), Then draw the inner shadow of the rectangle , Last call TextPainter Draw text .
  6. unselectDraw Method is responsible for isSelected by false Drawing at . Same as selectDraw The method is the same , But you don't have to draw inner shadows ( Remove the highlighting effect ).

RectangleButtonPainter and TextPainter Responsible for physical work —— Draw rectangles and text .

ShadowButtonPainter

	class RectanglePainter {
	// 1
    public static func insetFrame(_ frame: CGRect, delta: CGFloat) -> CGRect {
        return CGRect(x: delta, y: delta, width: frame.width-delta*2, height: frame.height-delta*2)
    }
	// 2
    public static func drawGradient(gradient: CGGradient?, frame: CGRect, cornerRadius: CGFloat?, outerShadow: NSShadow?) {
    	 // 3
        let context = UIGraphicsGetCurrentContext()
        if let context = context {
        	  // 4
            let path = UIBezierPath(roundedRect: frame, cornerRadius: cornerRadius ?? 0)
            // 5
            context.saveGState()
            // 6
            if  let shadow = outerShadow, let color = shadow.shadowColor as? UIColor {
                context.setShadow(offset: shadow.shadowOffset, blur: shadow.shadowBlurRadius, color: color.cgColor)
            }
            // 7
            context.beginTransparencyLayer(auxiliaryInfo: nil)
            // 8
            path.addClip()
			  // 9
            if let gradient = gradient {
                context.drawLinearGradient(gradient, start: CGPoint(x: frame.midX, y: frame.minY), end: CGPoint(x: frame.midX, y: frame.maxY), options: [])
            }else {
                path.fill()
            }
            // 10
            context.endTransparencyLayer()
            context.restoreGState()
        }

    }
    // 11
    public static func drawInnerShadow(_ frame: CGRect, cornerRadius: CGFloat?, innerShadow:NSShadow) {
        let context = UIGraphicsGetCurrentContext()
        if let context = context, let color = innerShadow.shadowColor as? UIColor {
        	  // 12
            let path = UIBezierPath(roundedRect: frame, cornerRadius: cornerRadius ?? 0)
            context.saveGState()
            // 13
            context.clip(to: path.bounds)
            // 14
            context.setAlpha(color.cgColor.alpha)
            // 15
            context.beginTransparencyLayer(auxiliaryInfo: nil)
            // 16
            let rectangleOpaqueShadow = color.withAlphaComponent(1)
            // 17
            context.setShadow(offset: innerShadow.shadowOffset, blur: innerShadow.shadowBlurRadius, color: rectangleOpaqueShadow.cgColor)
            // 18
            context.setBlendMode(.sourceOut)
            context.beginTransparencyLayer(auxiliaryInfo: nil)
			  // 19
            rectangleOpaqueShadow.setFill()
            path.fill()
			  // 20
            context.endTransparencyLayer()
            context.endTransparencyLayer()
            context.restoreGState()
        }
    }
}
  1. insetFrame Method , Calculate a “ Smaller ” rectangular , The reduced scope is delta Pixel . This method is often used when drawing control with shadow effect , Because rectangular shadows tend to occupy additional controls , Therefore, when we draw the rectangular space of the control, we can't put the entire control's frame All occupied , Leave enough controls to draw shadows .

  2. drawGradient Method is responsible for drawing a gradient fill + Shadow of the vulva , Need to 4 Parameters : gradient gradient、 Rectangle range frame、 radius cornerRadius、 Shadow of the vulva outerShadow.

  3. First, get the current context context, This is the most important thing , stay CG We always need it in drawing , We can do nothing without it .

  4. Construct a rounded rectangle , As the shape of the button ( No shadows ). Call the construction method of Bezier curve to create a rounded rectangle .

  5. CG Drawing is often concerned with context The state of change . because context The object is global , Before we proceed , The best of context The original status is saved , Wait until the operation is completed , In this way, one painting will not affect the subsequent painting . Save and use the state saveGState function , Recovery of state restoreGState function .

  6. Set up context Shadow , So when we start filling the gradient rectangle , By the way, I will draw the set shadow. Of course , If outerShadow Parameter is nil, We'll do nothing .

  7. Start a new transparent layer , Transparent layers are similar to ps The concept of layers in , And with alpha passageway . It is convenient for you to combine different figures .

  8. Then path clipping . In this way, the filling content will not go beyond the inside of the path .

  9. Draw a gradient inside the path .

  10. End transparent layer , recovery context Graphics status .

  11. drawInnerShadow Responsible for drawing highlights ( Inner shadow ). It needs to 3 Parameters : Rectangle range frame, radius corderRadius, Inner shadow innerShadow.

  12. Construct a rounded rectangle , The shape that is part of the highlight . Call the construction method of Bezier curve to create a rounded rectangle .

  13. Path clipping .

  14. Set up context Of alpha value , Make the currently drawn alpha The value is equivalent to innerShadow Of color Of alpha value ( The previous value was 1).

  15. Then set a transparent layer .

  16. Set the color of the inner shadow , But you need to change the color of the inner shadow alpha Get rid of , No matter how many it is, it is set to 1. Because our inner shadow color alpha The value is also 1, We can 14 One line removed , take 15 The line should be changed to :

    rectangleOpaqueShadow = color 
    

    The effect is the same . But here is to avoid the occurrence of alpha Not for 1 The situation of , At this time, we need to manually set the inner shadow alpha Adjusted for 1, Otherwise, the effect will be unexpected .

  17. Use this alpha 1 The inner shadow color setting of context Shadow .

  18. Set the blend mode to source out. The use of mixed mode is complicated , It does not fill the color directly when filling , Instead, the color value is calculated , Calculate the new color value and use it for filling .

  19. Fill the path with the calculated opaque inner shadow color .

  20. End transparent layer , recovery context.

Draw text

The text is simple :

    private func drawText(_ rect: CGRect, color: UIColor?) {
        if let color = color, let string = text?.text, let font = text?.font {
        	  // 1
            let paragraphStyle = NSMutableParagraphStyle()
            paragraphStyle.alignment = .center
            // 2
            let fontAttributes = [
                .font: font,
                .foregroundColor: color,
                .paragraphStyle: paragraphStyle,
            ] as [NSAttributedString.Key: Any]
			  // 3
            let height: CGFloat = string.boundingRect(with: CGSize(width: rect.width, height: CGFloat.infinity), options: .usesLineFragmentOrigin, attributes: fontAttributes, context: nil).height
            context?.saveGState()
            context?.clip(to: rect)
            // 4
            string.draw(in: CGRect(x: rect.minX, y: rect.minY + (rect.height - height) / 2, width: rect.width, height: height), withAttributes: fontAttributes)
            context?.restoreGState()
        }
    }
  1. Set paragraph Center ( Vertical center )
  2. Set font properties .
  3. Calculate text height .
  4. Draw text .

test

stay viewDidLoad Add... To the method :

        let button = ShadowButton(frame: CGRect(x: 100,y: 200,width: 200,height: 50))
        button.gradient = gradient()
        button.buttonRadius = 11
        button.innerShadow = innerShadow()
        button.outerShadow = outerShadow()
        button.text = text("Hello")
        
        button.backgroundColor = .white
        
        addSubview(button)

The effect is as follows :

原网站

版权声明
本文为[the Summer Palace]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/174/202206230756065566.html