A long time ago in a galaxy far, far away… I was a computer graphics artist. Specifically I was a Genigraphics
console operator. I worked the night shift creating illustrations and slides with vector graphics for big corporations. This was the early 1980s and fast, cheap personal computers with color graphics had not quite been invented yet. Later when I learned to program the Apple Macintosh I had a head start–I had created hundreds of vector images using computer graphic primitives by hand! The ability to draw lines, circles, and rectangles with code blew my mind.
I’m thinking it would be fun to do some graphics exercises with Swift and Apple’s Core Graphics. Maybe recreate some of those Genigraphics images from 30 years ago. Somewhere in the basement I have a box of 35mm slides and 8″ floppy disks. Probably not readable but I should be able to create a few images from memory. Genigraphics was the ancient ancestor of PowerPoint and Keynote. Every element in an illustration or on a slide had to be created from a graphic primitive. A gradient color fill was created with 30 or 40 overlapping rectangles, each with an incrementally different color. And remember all the primitives were created by a human hand!
One of the most time consuming effects involved creating a grid for a background. It involved much duplicating, aligning, and counting.
So let’s create a clunky Genigraphcis grid in Swift using Core Graphics and display it in a UIView on and iOS device!
Launch Xcode and create a new iOS single view application. Make sure you choose Swift as the language. It doesn’t matter what you call your app. Run the new blank app just to make sure Xcode did it’s job.
Kill the app and go back to Xcode. Create a new iOS Cocoa Touch Class source file. Call it BackgroundView and make it a subclass of UIView. Go to your main.storyboard and select the View in your View Controller. In the Identity Inspector set the class of the View to BackgroundView.
Run the app again just make sure it builds and then kill it and return to Xcode.
Select BackgroundView.swift. You should see a drawRect function provided by Apple. Delete all the comments so it’s ready for some grid code…
override func drawRect(rect: CGRect) { }
This function is called whenever iOS needs to paint your view on the screen. The rect is the area that needs to be painted. The first time drawRect is called the rect is the same size as your view. Later on when drawRect is called the rect is just the area of the view that needs to be updated. So I get the view width and height from the bounds. I’m also assuming the origin (left, top corner) is always 0,0. Add the following code to your drawRect function so you can get the the bounds width and height and compare it to the rect (technically called the “dirty rect” because that is the part of the view that needs to redrawn).
let viewWidth:CGFloat = self.bounds.width print("viewWidth \(viewWidth)") print("rect.size.width \(rect.size.width)") let viewHight:CGFloat = self.bounds.height print("viewHight \(viewHight)") print("rect.size.height \(rect.size.height)")
I like to define all my important variables together near the top of my functions because I always know where to find them. Swift let’s you declare and define variable throughout the code. (I only use that freedom for unimportant “throw away” variables that are used inside loops and other control structures.)
Add the code to define your gridlines starting points, lengths, and line width.
let x1:CGFloat = 0.0 let x2:CGFloat = viewWidth let y1:CGFloat = 0.0 let y2:CGFloat = viewHight
Core Graphics likes all your coordinates to be CGFloats. Since the resolution of an iOS device can be just about anything size these coordinates represent an idealize space as points. On a high resolution iOS device, like an iPhone 6s Plus you’re going to get 3 pixels for every point. My iPhone 6 Plus has a resolution of 1242 × 2208 but Core Graphics gives me 414 × 736 to work with. I’m just going to live with that for now.
I want a nice square grid so I use the either the viewWidth or viewHeight (which ever is smaller) divided 15 as the length of a grid cell.
let cellLength:CGFloat = min(viewHight, viewWidth) / 15 print("rowHeight \(cellLength)")
Now it’s time to set the style of our lines: color and width. As I recall Genigraphics only had 64 colors or maybe 128. I’ll figure that out next weekend. In the meantime I choose blue with a width of 2.
let lineWidth:CGFloat = 2.0 let context = UIGraphicsGetCurrentContext() let colorSpace = CGColorSpaceCreateDeviceRGB() let components: [CGFloat] = [0.0, 0.0, 1.0, 1.0] let color = CGColorCreate(colorSpace, components) CGContextSetLineWidth(context, lineWidth) CGContextSetStrokeColorWithColor(context, color)
Wow! That’s a lot of code just to set the color and line width. It’s a little complicated because Core Graphics is context-based. You have to get the current graphics context and modify it to draw. In the code above I’m also creating a color the hard way via a color space and RGBA (Red Green Blue Alpha) color values. All this work will let me have a fine degree of control of the color of the gridlines later on.
Finally, I’m going to draw my horizontal and vertical gridlines!
// draw horizontal lines var i:CGFloat = 0.0 repeat { CGContextMoveToPoint(context, x1, cellLength + i) CGContextAddLineToPoint(context, x2, cellLength + i) i = i + cellLength } while i < rect.height - cellLength // draw vertical lines var j:CGFloat = 0.0 repeat { CGContextMoveToPoint(context, cellLength + j, y1) CGContextAddLineToPoint(context, cellLength + j, y2) j = j + cellLength } while j < rect.width - cellLength
I’m just fooling around at this point so I don’t know if two repeat loops is the most efficient way to draw a grid. Right now I’m just happy to see a grid show up. I’ll optimize it later–if needed! To draw my lines I start at 0 and draw each line a cellLengh apart. CoreGraphics is a bit like Logo’s turtle graphics: I have to add drawing commands to the context, building up the image step by step behind the scenes.
If you run the code that this you will see nothing! Go ahead… I dare you!
That’s because you need to tell the graphics context to blast your drawing commands from the context on to the view. One line of code does it.
CGContextStrokePath(context)
If you have been typing along you now have a custom UIView that draws a grid properly no matter now you hold your iPhone.
If you want to see the grid on your storyboard add
@IBDesignable
just above the class declaration. But be careful! Buggy Core Graphics rendering can freeze up Xcode. (It happened to me while writing this code.) I keep @IBDesignable commented out until I know my drawing code is working.
You can find all the code on GitHub.
Next week I’ll add some more complex drawing in the OG Genigraphics style. This is a good start for your own explorations. Have fun and don’t worry! You can’t blow up your computer by writing code.