Swift for Xojo Developers : Part 5 - Graphic Primitives

This topic will show a class (and some subclasses) to create Graphic Primitive objects.
Future topics will get into drawing on CGContext (ie. Canvas like controls)

These objects replicate the Line, Oval, Rectangle and RoundRect controls in Xojo


//************************************************************
//** rapidGRAPHIC - a custom graphics class to provide      **
//**                easy creation of LINE, ARC, OVAL and    **
//**                RECTANGLE graphic primitives as objects **
//** Copyright © 2018 Dave Sisemore. All rights reserved.   **
//** updated 19Feb2020                                      **
//************************************************************
import UIKit
public enum shapes : Int {
	case Arc       = 1
	case Line      = 2
	case Oval      = 3
	case Rectangle = 4
	case RoundRect = 5
}
//
// Arc Subclass
// Syntax : arcUIView = (frame[,color:UIColor][,direction: Int])
//
class rapidARC : rapidGRAPHIC {
	convenience init(frame:CGRect, color:UIColor = UIColor.black, direction : Int = 0) {
		self.init(frame, type : .Arc, direction : direction)
		self.lineColor = color
	}
}

//
// Line Subclass
// Syntax : lineUIView = rapidLINE(frame[,color:UIColor][,direction: Int])
//
class rapidLINE : rapidGRAPHIC {
	convenience init(frame:CGRect,color:UIColor = UIColor.black, direction : Int = 0) {
		self.init(frame, type : .Line, direction : direction)
		self.lineColor = color
	}
}

//
// Oval Subclass
// Syntax : ovalUIView = rapidOVAL(frame[,borderColor][,fillColor])
//
class rapidOVAL : rapidGRAPHIC {
	convenience init(frame:CGRect,borderColor:UIColor = UIColor.black,fillColor:UIColor = UIColor.clear) {
		self.init(frame, type : .Oval)
		self.lineColor   = borderColor
		self.fillColor   = fillColor
	}
}

//
// Rectangle Subclass
// Syntax : ovalUIView = rapidRECTANGLE(frame[,borderColor][,fillColor])
//
class rapidRECTANGLE : rapidGRAPHIC {
	convenience init(frame:CGRect,borderColor:UIColor = UIColor.black,fillColor:UIColor = UIColor.clear) {
		self.init(frame, type : .Rectangle)
		self.lineColor   = borderColor
		self.fillColor   = fillColor
	}
}

//
// Rounded Rectangle Subclass
// Syntax : ovalUIView = rapidRECTANGLE(frame,radius[,borderColor][,fillColor])
//
class rapidROUNDRECT : rapidGRAPHIC {
	convenience init(frame:CGRect,radius:CGFloat,borderColor:UIColor = UIColor.black,fillColor:UIColor = UIColor.clear) {
		self.init(frame, type : .RoundRect)
		self.lineColor    = borderColor
		self.fillColor    = fillColor
		self.cornerRadius = radius
	}
}
//
// Common Graphic Object Class
//
class rapidGRAPHIC : UIView {
	var zType      : shapes  = .Line
	var zDirection : Int     = 0
	var zRadius    : CGFloat = 0
	var zLineStyle : Int     = 1
	var zLineWidth : CGFloat = 1
	var zLineColor : UIColor = UIColor.black
	var zFillColor : UIColor = UIColor.clear
	required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) }
	//
	init(_ frame:CGRect,type : shapes, direction : Int = 0) {
		super.init(frame:frame)
		self.backgroundColor = UIColor.clear
		zType       = type
		zRadius     = 0 // for RoundRect
		zLineStyle  = 1 // not yet implemented
		zLineWidth  = 1
		zDirection  = direction
		zLineColor  = UIColor.black
		zFillColor  = UIColor.clear
	}

	override func draw(_ rect: CGRect) {
		var pt1 = CGPoint(x:0,y:0)
		var pt2 = CGPoint(x:0,y:0)
		var pt3 = CGPoint(x:0,y:0)

		super.draw(rect)
		let context:CGContext = UIGraphicsGetCurrentContext()!
		let offset=zLineWidth/2
		let innerRect=CGRect(x:rect.minX+offset,y:rect.minY+offset,width:rect.width-2*offset,height:rect.height-2*offset)
		if zType == .Arc || zType == .Line { zFillColor=UIColor.clear}
		context.clear(rect)
		context.setFillColor(zFillColor.cgColor)
		context.setStrokeColor(zLineColor.cgColor)
		context.setLineWidth(zLineWidth)

		switch zType {
			case .Arc:
			switch (zDirection & 3) {
				case 0:
				pt1=CGPoint(x:innerRect.minX,y:innerRect.maxY)
				pt2=CGPoint(x:innerRect.maxX,y:innerRect.minY)
				pt3=CGPoint(x:innerRect.minX,y:innerRect.minY)
				case 1:
				pt1=CGPoint(x:innerRect.minX,y:innerRect.minY)
				pt2=CGPoint(x:innerRect.maxX,y:innerRect.maxY)
				pt3=CGPoint(x:innerRect.maxX,y:innerRect.minY)
				case 2:
				pt1=CGPoint(x:innerRect.minX,y:innerRect.minY)
				pt2=CGPoint(x:innerRect.maxX,y:innerRect.maxY)
				pt3=CGPoint(x:innerRect.minX,y:innerRect.maxY)
				case 3:
				pt1=CGPoint(x:innerRect.maxX,y:innerRect.minY)
				pt2=CGPoint(x:innerRect.minX,y:innerRect.maxY)
				pt3=CGPoint(x:innerRect.maxX,y:innerRect.maxY)
				default: break
			}
			context.move(to: pt1)
			context.addQuadCurve(to: pt2, control: pt3)
			context.strokePath()
			break
			case .Line:
			// 0 - 1 | 2 / 3 \
			switch (zDirection & 3) {
				case 0: pt1=CGPoint(x:0,y:rect.midY); pt2=CGPoint(x:rect.maxX,y:rect.midY)
				case 1: pt1=CGPoint(x:rect.midX,y:0); pt2=CGPoint(x:rect.midX, y:rect.maxY)
				case 2: pt1=CGPoint(x:rect.maxX,y:0); pt2=CGPoint(x:0, y:rect.maxY)
				case 3: pt1=CGPoint(x:0,y:0);         pt2=CGPoint(x:rect.maxX, y:rect.maxY)
				default: break
			}
			context.move(to: pt1)
			context.addLine(to: pt2)
			context.strokePath()
			case .Oval:
			context.fillEllipse(in: rect)
			context.strokeEllipse(in: innerRect)
			case .Rectangle:
			context.fill(rect)
			context.stroke(innerRect,width: zLineWidth)
			case .RoundRect:
			let fillPath: CGPath = UIBezierPath(roundedRect: rect, cornerRadius: zRadius).cgPath
			context.addPath(fillPath)
			context.closePath()
			context.fillPath()
			context.beginPath()
			let clipPath: CGPath = UIBezierPath(roundedRect: innerRect, cornerRadius: zRadius).cgPath
			context.addPath(clipPath)
			context.closePath()
			context.strokePath()
		}
	}

	//
	// Set Corner Radius for ROUNDRECT
	//
	var cornerRadius : CGFloat {
		get { return zRadius }
		set { if newValue>=0 { zRadius=newValue } }
	}
	//
	// Set drawing Direction for ARC and LINE
	//
	var Direction : Int {
		get { return zDirection }
		set { if newValue != zDirection { zDirection=newValue; updateGraphic() } }
	}
	//
	// Set Linestyle for Border of ALL Objects [NOTE : THIS IS FOR FUTURE EXPANSION]
	//
	var lineStyle : Int {
		get { return zLineStyle }
		set { if newValue != zLineStyle { zLineStyle=newValue; updateGraphic() } }
	}
	//
	// Set Line Width for Border of ALL Objects
	//
	var lineWidth : CGFloat {
		get { return zLineWidth }
		set { if newValue != zLineWidth && newValue>=0 { zLineWidth=newValue; updateGraphic() } }
	}

	//
	// Set Line Color for Border of ALL Objects
	//
	var lineColor : UIColor {
		get { return zLineColor }
		set { if newValue != zLineColor  { zLineColor=newValue; updateGraphic() } }
	}

	//
	// Set Fill Color for Border for OVAL, RECT and ROUNDRECT
	//
	var fillColor : UIColor {
		get { return zFillColor }
		set { if newValue != zFillColor  { zFillColor=newValue; updateGraphic() } }
	}

	func updateGraphic() { self.setNeedsDisplay() }
}
2 Likes

Why the “rapid” in the class names?

I myself prefer to prefix classes with a c, as in

Dim Monitor as cMonitor”

RAPID = Rapid Application Prototype IOS Devices

its the name of the App for which I developed most of this code