CoreGraphic框架解析 (八)—— 基于CoreGraphic的一个简单绘制示例 (四)

版本记录

版本号 时间
V1.0 2018.10.22 星期一

前言

quartz是一个通用的术语,用于描述在iOSMAC OS X 中整个媒体层用到的多种技术 包括图形、动画、音频、适配。Quart 2D 是一组二维绘图和渲染APICore Graphic会使用到这组APIQuartz Core专指Core Animation用到的动画相关的库、API和类。CoreGraphicsUIKit下的主要绘图系统,频繁的用于绘制自定义视图。Core Graphics是高度集成于UIView和其他UIKit部分的。Core Graphics数据结构和函数可以通过前缀CG来识别。在app中很多时候绘图等操作我们要利用CoreGraphic框架,它能绘制字符串、图形、渐变色等等,是一个很强大的工具。感兴趣的可以看我另外几篇。
1. CoreGraphic框架解析(一)—— 基本概览
2. CoreGraphic框架解析(二)—— 基本使用
3. CoreGraphic框架解析(三)—— 类波浪线的实现
4. CoreGraphic框架解析(四)—— 基本架构补充
5. CoreGraphic框架解析 (五)—— 基于CoreGraphic的一个简单绘制示例 (一)
6. CoreGraphic框架解析 (六)—— 基于CoreGraphic的一个简单绘制示例 (二)
7. CoreGraphic框架解析 (七)—— 基于CoreGraphic的一个简单绘制示例 (三)

源码

1. Swift

该教程最大的特点就是界面完全是用代码绘制的,asset里面没有任何东西,是一个完全由CoreGraphic进行绘制的小示例。

首先看一下项目组织结构。

CoreGraphic框架解析 (八)—— 基于CoreGraphic的一个简单绘制示例 (四)_第1张图片

接着看一下sb中的内容

CoreGraphic框架解析 (八)—— 基于CoreGraphic的一个简单绘制示例 (四)_第2张图片

下面就是源码了

1. ViewController.swift
import UIKit

class ViewController: UIViewController {
  
  //Counter outlets
  @IBOutlet weak var counterView: CounterView!
  @IBOutlet weak var counterLabel: UILabel!
  
  @IBOutlet weak var containerView: UIView!
  @IBOutlet weak var graphView: GraphView!
  
  //Label outlets
  @IBOutlet weak var averageWaterDrunk: UILabel!
  @IBOutlet weak var maxLabel: UILabel!
  @IBOutlet weak var stackView: UIStackView!
  
  @IBOutlet weak var medalView: MedalView!
  
  var isGraphViewShowing = false
  
  @IBAction func pushButtonPressed(_ button: PushButton) {
    if button.isAddButton {
      counterView.counter += 1
    } else {
      if counterView.counter > 0 {
        counterView.counter -= 1
      }
    }
    counterLabel.text = String(counterView.counter)
    
    if isGraphViewShowing {
      counterViewTap(nil)
    }
    checkTotal()
  }
  
  @IBAction func counterViewTap(_ gesture:UITapGestureRecognizer?) {
    if (isGraphViewShowing) {
      //hide Graph
      UIView.transition(from: graphView,
                        to: counterView,
                        duration: 1.0,
                        options: [.transitionFlipFromLeft, .showHideTransitionViews],
                        completion:nil)
    } else {
      //show Graph
      setupGraphDisplay()
      UIView.transition(from: counterView,
                        to: graphView,
                        duration: 1.0,
                        options: [.transitionFlipFromRight, .showHideTransitionViews],
                        completion: nil)
    }
    isGraphViewShowing = !isGraphViewShowing
  }
  
  override func viewDidLoad() {
    super.viewDidLoad()
    counterLabel.text = String(counterView.counter)
    checkTotal()
  }
  
  func setupGraphDisplay() {
    
    let maxDayIndex = stackView.arrangedSubviews.count - 1
    
    //1 - replace last day with today's actual data
    graphView.graphPoints[graphView.graphPoints.count-1] = counterView.counter
    //2 - indicate that the graph needs to be redrawn
    graphView.setNeedsDisplay()
    maxLabel.text = "\(graphView.graphPoints.max()!)"
    
    //3 - calculate average from graphPoints
    let average = graphView.graphPoints.reduce(0, +) / graphView.graphPoints.count
    averageWaterDrunk.text = "\(average)"
    
    //4 - setup date formatter and calendar
    let today = Date()
    let calendar = Calendar.current
    
    let formatter = DateFormatter()
    formatter.setLocalizedDateFormatFromTemplate("EEEEE")
    
    //5 - set up the day name labels with correct day
    for i in (0...maxDayIndex) {
      if let date = calendar.date(byAdding: .day, value: -i, to: today),
        let label = stackView.arrangedSubviews[maxDayIndex - i] as? UILabel {
        label.text = formatter.string(from: date)
      }
    }
  }
  
  func checkTotal() {
    if counterView.counter >= 8 {
      medalView.showMedal(show: true)
    } else {
      medalView.showMedal(show: false)
    }
  }
  
}
2. PushButton.swift
import UIKit

@IBDesignable
class PushButton: UIButton {
  
  private struct Constants {
    static let plusLineWidth: CGFloat = 3.0
    static let plusButtonScale: CGFloat = 0.6
    static let halfPointShift: CGFloat = 0.5
  }
  
  private var halfWidth: CGFloat {
    return bounds.width / 2
  }
  
  private var halfHight: CGFloat {
    return bounds.height / 2
  }
  
  @IBInspectable var fillColor: UIColor = UIColor.green
  @IBInspectable var isAddButton: Bool = true
  
  override func draw(_ rect: CGRect) {
    let path = UIBezierPath(ovalIn: rect)
    fillColor.setFill()
    path.fill()
    
    //set up the width and height variables
    //for the horizontal stroke
    let plusWidth: CGFloat = min(bounds.width, bounds.height) * Constants.plusButtonScale
    let halfPlusWidth = plusWidth / 2
    
    //create the path
    let plusPath = UIBezierPath()
    
    //set the path's line width to the height of the stroke
    plusPath.lineWidth = Constants.plusLineWidth
    
    //move the initial point of the path
    //to the start of the horizontal stroke
    plusPath.move(to: CGPoint(
      x: halfWidth - halfPlusWidth + Constants.halfPointShift,
      y: halfHight + Constants.halfPointShift))
    
    //add a point to the path at the end of the stroke
    plusPath.addLine(to: CGPoint(
      x: halfWidth + halfPlusWidth + Constants.halfPointShift,
      y: halfHight + Constants.halfPointShift))
    
    //Vertical Line
    
    if isAddButton {
      plusPath.move(to: CGPoint(
        x: halfWidth + Constants.halfPointShift,
        y: halfHight - halfPlusWidth + Constants.halfPointShift))
      
      plusPath.addLine(to: CGPoint(
        x: halfWidth + Constants.halfPointShift,
        y: halfHight + halfPlusWidth + Constants.halfPointShift))
    }
    //existing code
    //set the stroke color
    UIColor.white.setStroke()
    plusPath.stroke()
  }
  
}
3. CounterView.swift
import UIKit

@IBDesignable class CounterView: UIView {
  
  private struct Constants {
    static let numberOfGlasses = 8
    static let lineWidth: CGFloat = 5.0
    static let arcWidth: CGFloat = 76
    
    static var halfOfLineWidth: CGFloat {
      return lineWidth / 2
    }
  }
  
  @IBInspectable var counter: Int = 5 {
    didSet {
      if counter <=  Constants.numberOfGlasses {
        //the view needs to be refreshed
        setNeedsDisplay()
      }
    }
  }
  @IBInspectable var outlineColor: UIColor = UIColor.blue
  @IBInspectable var counterColor: UIColor = UIColor.orange
  
  override func draw(_ rect: CGRect) {
    // 1
    let center = CGPoint(x: bounds.width / 2, y: bounds.height / 2)
    
    // 2
    let radius: CGFloat = max(bounds.width, bounds.height)
    
    // 3
    let startAngle: CGFloat = 3 * .pi / 4
    let endAngle: CGFloat = .pi / 4
    
    // 4
    let path = UIBezierPath(arcCenter: center,
                            radius: radius/2 - Constants.arcWidth/2,
                            startAngle: startAngle,
                            endAngle: endAngle,
                            clockwise: true)
    
    // 5
    path.lineWidth = Constants.arcWidth
    counterColor.setStroke()
    path.stroke()
    
    
    //Draw the outline
    
    //1 - first calculate the difference between the two angles
    //ensuring it is positive
    let angleDifference: CGFloat = 2 * .pi - startAngle + endAngle
    //then calculate the arc for each single glass
    let arcLengthPerGlass = angleDifference / CGFloat(Constants.numberOfGlasses)
    //then multiply out by the actual glasses drunk
    let outlineEndAngle = arcLengthPerGlass * CGFloat(counter) + startAngle
    
    //2 - draw the outer arc
    let outlinePath = UIBezierPath(arcCenter: center,
                                   radius: bounds.width/2 - Constants.halfOfLineWidth,
                                   startAngle: startAngle,
                                   endAngle: outlineEndAngle,
                                   clockwise: true)
    
    //3 - draw the inner arc
    outlinePath.addArc(withCenter: center,
                       radius: bounds.width/2 - Constants.arcWidth + Constants.halfOfLineWidth,
                       startAngle: outlineEndAngle,
                       endAngle: startAngle,
                       clockwise: false)
    
    //4 - close the path
    outlinePath.close()
    
    outlineColor.setStroke()
    outlinePath.lineWidth = Constants.lineWidth
    outlinePath.stroke()
    
    //Counter View markers
    let context = UIGraphicsGetCurrentContext()
    
    //1 - save original state
    context?.saveGState()
    outlineColor.setFill()
    
    let markerWidth: CGFloat = 5.0
    let markerSize: CGFloat = 10.0
    
    //2 - the marker rectangle positioned at the top left
    let markerPath = UIBezierPath(rect: CGRect(x: -markerWidth/2, y: 0, width: markerWidth, height: markerSize))
    
    //3 - move top left of context to the previous center position
    context?.translateBy(x: rect.width/2, y: rect.height/2)
    
    for i in 1...Constants.numberOfGlasses {
      //4 - save the centred context
      context?.saveGState()
      //5 - calculate the rotation angle
      let angle = arcLengthPerGlass * CGFloat(i) + startAngle - .pi/2
      //rotate and translate
      context?.rotate(by: angle)
      context?.translateBy(x: 0, y: rect.height/2 - markerSize)
      
      //6 - fill the marker rectangle
      markerPath.fill()
      //7 - restore the centred context for the next rotate
      context?.restoreGState()
    }
    
    //8 - restore the original state in case of more painting
    context?.restoreGState()
  }
}
4. GraphView.swift
import UIKit

@IBDesignable class GraphView: UIView {
  
  private struct Constants {
    static let cornerRadiusSize = CGSize(width: 8.0, height: 8.0)
    static let margin: CGFloat = 20.0
    static let topBorder: CGFloat = 60
    static let bottomBorder: CGFloat = 50
    static let colorAlpha: CGFloat = 0.3
    static let circleDiameter: CGFloat = 5.0
  }
  
  //1 - the properties for the gradient
  @IBInspectable var startColor: UIColor = .red
  @IBInspectable var endColor: UIColor = .green
  
  //Weekly sample data
  var graphPoints: [Int] = [4, 2, 6, 4, 5, 8, 3]
  
  override func draw(_ rect: CGRect) {
    
    let width = rect.width
    let height = rect.height
    
    let path = UIBezierPath(roundedRect: rect,
                            byRoundingCorners: UIRectCorner.allCorners,
                            cornerRadii: Constants.cornerRadiusSize)
    path.addClip()
    
    //2 - get the current context
    let context = UIGraphicsGetCurrentContext()!
    let colors = [startColor.cgColor, endColor.cgColor]
    
    //3 - set up the color space
    let colorSpace = CGColorSpaceCreateDeviceRGB()
    
    //4 - set up the color stops
    let colorLocations: [CGFloat] = [0.0, 1.0]
    
    //5 - create the gradient
    let gradient = CGGradient(colorsSpace: colorSpace,
                              colors: colors as CFArray,
                              locations: colorLocations)!
    
    //6 - draw the gradient
    var startPoint = CGPoint.zero
    var endPoint = CGPoint(x: 0, y: self.bounds.height)
    context.drawLinearGradient(gradient,
                               start: startPoint,
                               end: endPoint,
                               options: CGGradientDrawingOptions(rawValue: 0))
    
    //calculate the x point
    let margin = Constants.margin
    let columnXPoint = { (column:Int) -> CGFloat in
      //Calculate gap between points
      let spacer = (width - margin * 2 - 4) / CGFloat((self.graphPoints.count - 1))
      var x: CGFloat = CGFloat(column) * spacer
      x += margin + 2
      return x
    }
    
    // calculate the y point
    let topBorder: CGFloat = Constants.topBorder
    let bottomBorder: CGFloat = Constants.bottomBorder
    let graphHeight = height - topBorder - bottomBorder
    let maxValue = graphPoints.max()!
    let columnYPoint = { (graphPoint:Int) -> CGFloat in
      var y:CGFloat = CGFloat(graphPoint) / CGFloat(maxValue) * graphHeight
      y = graphHeight + topBorder - y // Flip the graph
      return y
    }
    
    // draw the line graph
    UIColor.white.setFill()
    UIColor.white.setStroke()
    
    //set up the points line
    let graphPath = UIBezierPath()
    //go to start of line
    graphPath.move(to: CGPoint(x:columnXPoint(0), y:columnYPoint(graphPoints[0])))
    
    //add points for each item in the graphPoints array
    //at the correct (x, y) for the point
    for i in 1..
5. BackgroundView.swift
import UIKit

@IBDesignable
class BackgroundView: UIView {
  
  //1
  @IBInspectable var lightColor: UIColor = UIColor.orange
  @IBInspectable var darkColor: UIColor = UIColor.yellow
  @IBInspectable var patternSize: CGFloat = 200
  
  override func draw(_ rect: CGRect) {
    //2
    let context = UIGraphicsGetCurrentContext()!
    
    //3
    context.setFillColor(darkColor.cgColor)
    
    //4
    context.fill(rect)
    
    
    let drawSize = CGSize(width: patternSize, height: patternSize)
    
    UIGraphicsBeginImageContextWithOptions(drawSize, true, 0.0)
    let drawingContext = UIGraphicsGetCurrentContext()!
    
    //set the fill color for the new context
    darkColor.setFill()
    drawingContext.fill(CGRect(x: 0, y: 0, width: drawSize.width, height: drawSize.height))
    
    let trianglePath = UIBezierPath()
    //1
    trianglePath.move(to: CGPoint(x: drawSize.width/2, y: 0))
    //2
    trianglePath.addLine(to: CGPoint(x: 0, y: drawSize.height/2))
    //3
    trianglePath.addLine(to: CGPoint(x: drawSize.width, y: drawSize.height/2))
    
    //4
    trianglePath.move(to: CGPoint(x: 0,y: drawSize.height/2))
    //5
    trianglePath.addLine(to: CGPoint(x: drawSize.width/2, y: drawSize.height))
    //6
    trianglePath.addLine(to: CGPoint(x: 0, y: drawSize.height))
    
    //7
    trianglePath.move(to: CGPoint(x: drawSize.width, y: drawSize.height/2))
    //8
    trianglePath.addLine(to: CGPoint(x: drawSize.width/2, y: drawSize.height))
    //9
    trianglePath.addLine(to: CGPoint(x: drawSize.width, y: drawSize.height))
    
    lightColor.setFill()
    trianglePath.fill()
    
    let image = UIGraphicsGetImageFromCurrentImageContext()!
    UIGraphicsEndImageContext()
    
    UIColor(patternImage: image).setFill()
    context.fill(rect)
  }
}
6. MedalView.swift
import UIKit

class MedalView: UIImageView {
  
  lazy var medalImage: UIImage = self.createMedalImage()
  
  func createMedalImage() -> UIImage {
    debugPrint("creating Medal Image")
    let size = CGSize(width: 120, height: 200)
    
    UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
    let context = UIGraphicsGetCurrentContext()!
    
    
    //Gold colors
    let darkGoldColor = UIColor(red: 0.6, green: 0.5, blue: 0.15, alpha: 1.0)
    let midGoldColor = UIColor(red: 0.86, green: 0.73, blue: 0.3, alpha: 1.0)
    let lightGoldColor = UIColor(red: 1.0, green: 0.98, blue: 0.9, alpha: 1.0)
    
    //Add Shadow
    let shadow:UIColor = UIColor.black.withAlphaComponent(0.80)
    let shadowOffset = CGSize(width: 2.0, height: 2.0)
    let shadowBlurRadius: CGFloat = 5
    
    context.setShadow(offset: shadowOffset, blur: shadowBlurRadius, color: shadow.cgColor)
    
    context.beginTransparencyLayer(auxiliaryInfo: nil)
    
    //Lower Ribbon
    let lowerRibbonPath = UIBezierPath()
    lowerRibbonPath.move(to: CGPoint(x: 0, y: 0))
    lowerRibbonPath.addLine(to: CGPoint(x: 40, y: 0))
    lowerRibbonPath.addLine(to: CGPoint(x: 78, y: 70))
    lowerRibbonPath.addLine(to: CGPoint(x: 38, y: 70))
    lowerRibbonPath.close()
    UIColor.red.setFill()
    lowerRibbonPath.fill()
    
    //Clasp
    let claspPath = UIBezierPath(roundedRect: CGRect(x: 36, y: 62, width: 43, height: 20), cornerRadius: 5)
    claspPath.lineWidth = 5
    darkGoldColor.setStroke()
    claspPath.stroke()
    
    //Medallion
    let medallionPath = UIBezierPath(ovalIn: CGRect(x: 8, y: 72, width: 100, height: 100))
    context.saveGState()
    medallionPath.addClip()
    
    let colors = [darkGoldColor.cgColor, midGoldColor.cgColor, lightGoldColor.cgColor] as CFArray
    let gradient = CGGradient(colorsSpace: CGColorSpaceCreateDeviceRGB(), colors: colors, locations: [0, 0.51, 1])!
    context.drawLinearGradient(gradient, start: CGPoint(x: 40, y: 40), end: CGPoint(x: 100, y: 160), options: [])
    context.restoreGState()
    
    //Create a transform
    //Scale it, and translate it right and down
    var transform = CGAffineTransform(scaleX: 0.8, y: 0.8)
    transform = transform.translatedBy(x: 15, y: 30)
    medallionPath.lineWidth = 2.0
    
    //apply the transform to the path
    medallionPath.apply(transform)
    medallionPath.stroke()
    
    //Upper Ribbon
    let upperRibbonPath = UIBezierPath()
    upperRibbonPath.move(to: CGPoint(x: 68, y: 0))
    upperRibbonPath.addLine(to: CGPoint(x: 108, y: 0))
    upperRibbonPath.addLine(to: CGPoint(x: 78, y: 70))
    upperRibbonPath.addLine(to: CGPoint(x: 38, y: 70))
    upperRibbonPath.close()
    
    UIColor.blue.setFill()
    upperRibbonPath.fill()
    
    //Number One
    
    //Must be NSString to be able to use draw(in:)
    let numberOne = "1" as NSString
    let numberOneRect = CGRect(x: 47, y: 100, width: 50, height: 50)
    let font = UIFont(name: "Academy Engraved LET", size: 60)!
    let numberOneAttributes = [
      NSAttributedStringKey.font: font,
      NSAttributedStringKey.foregroundColor: darkGoldColor
    ]
    numberOne.draw(in: numberOneRect, withAttributes: numberOneAttributes)
    
    context.endTransparencyLayer()
    
    //This code must always be at the end of the playground
    let image = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    
    return image!
  }
  
  func showMedal(show:Bool) {
    image = show == true ? medalImage : nil
  }
  
}
7. MedalDrawing.playground
import UIKit

let size = CGSize(width: 120, height: 200)

UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
let context = UIGraphicsGetCurrentContext()!


//Gold colors
let darkGoldColor = UIColor(red: 0.6, green: 0.5, blue: 0.15, alpha: 1.0)
let midGoldColor = UIColor(red: 0.86, green: 0.73, blue: 0.3, alpha: 1.0)
let lightGoldColor = UIColor(red: 1.0, green: 0.98, blue: 0.9, alpha: 1.0)

//Add Shadow
let shadow:UIColor = UIColor.black.withAlphaComponent(0.80)
let shadowOffset = CGSize(width: 2.0, height: 2.0)
let shadowBlurRadius: CGFloat = 5

context.setShadow(offset: shadowOffset, blur: shadowBlurRadius, color: shadow.cgColor)

context.beginTransparencyLayer(auxiliaryInfo: nil)

//Lower Ribbon
let lowerRibbonPath = UIBezierPath()
lowerRibbonPath.move(to: CGPoint(x: 0, y: 0))
lowerRibbonPath.addLine(to: CGPoint(x: 40, y: 0))
lowerRibbonPath.addLine(to: CGPoint(x: 78, y: 70))
lowerRibbonPath.addLine(to: CGPoint(x: 38, y: 70))
lowerRibbonPath.close()
UIColor.red.setFill()
lowerRibbonPath.fill()

//Clasp
let claspPath = UIBezierPath(roundedRect: CGRect(x: 36, y: 62, width: 43, height: 20), cornerRadius: 5)
claspPath.lineWidth = 5
darkGoldColor.setStroke()
claspPath.stroke()

//Medallion
let medallionPath = UIBezierPath(ovalIn: CGRect(x: 8, y: 72, width: 100, height: 100))
context.saveGState()
medallionPath.addClip()

let colors = [darkGoldColor.cgColor, midGoldColor.cgColor, lightGoldColor.cgColor] as CFArray
let gradient = CGGradient(colorsSpace: CGColorSpaceCreateDeviceRGB(), colors: colors, locations: [0, 0.51, 1])!
context.drawLinearGradient(gradient, start: CGPoint(x: 40, y: 40), end: CGPoint(x: 100, y: 160), options: [])
context.restoreGState()

//Create a transform
//Scale it, and translate it right and down
var transform = CGAffineTransform(scaleX: 0.8, y: 0.8)
transform = transform.translatedBy(x: 15, y: 30)
medallionPath.lineWidth = 2.0

//apply the transform to the path
medallionPath.apply(transform)
medallionPath.stroke()

//Upper Ribbon
let upperRibbonPath = UIBezierPath()
upperRibbonPath.move(to: CGPoint(x: 68, y: 0))
upperRibbonPath.addLine(to: CGPoint(x: 108, y: 0))
upperRibbonPath.addLine(to: CGPoint(x: 78, y: 70))
upperRibbonPath.addLine(to: CGPoint(x: 38, y: 70))
upperRibbonPath.close()

UIColor.blue.setFill()
upperRibbonPath.fill()

//Number One

//Must be NSString to be able to use draw(in:)
let numberOne = "1" as NSString
let numberOneRect = CGRect(x: 47, y: 100, width: 50, height: 50)
let font = UIFont(name: "Academy Engraved LET", size: 60)!
let numberOneAttributes = [
  NSAttributedStringKey.font: font,
  NSAttributedStringKey.foregroundColor: darkGoldColor
]
numberOne.draw(in: numberOneRect, withAttributes: numberOneAttributes)

context.endTransparencyLayer()

//This code must always be at the end of the playground
let image = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()

后记

本篇主要讲述了基于CoreGraphic的一个简单绘制示例,感兴趣的给个赞或者关注~~~

CoreGraphic框架解析 (八)—— 基于CoreGraphic的一个简单绘制示例 (四)_第3张图片

你可能感兴趣的:(CoreGraphic框架解析 (八)—— 基于CoreGraphic的一个简单绘制示例 (四))