Sine



The View’s init method sets the backgroundColor to white. The drawRect(_:) method draws the X and Y axes in blue and the graph of

y = sin(x)
in black.

Source code in Sine.zip

  1. Class AppDelegate.swift: unchanged.
  2. Class ViewController.swift: unchanged.
  3. Class View.swift: added the init that takes an NSCoder, and drawRect(_:).
  4. Info.plist: the UISupportedInterfaceOrientations property now allows only one orientation.

Create the project

To launch the app in landscape orientation, select the Sine project at the top of the Xcode Project Navigator. At the top of the center panel of Xcode, select General. Under Deployment Info → Device Orientation, check Landscape Left (home button on the left) and uncheck the other three orientations.

Stroke the path

The graph of sin passes through the origin at a perfect 45° angle. The iPhone Y axis points downwards by default, so the uprightTransform has a negative vertical scale to make it point upwards. (Yes, I know the loop counter should have been an integer to avoid roundoff errors.)

Since we are drawing lines instead of filling in areas, we must call CGContextSetRGBStrokeColor instead of CGContextSetRGBFillColor. At the end, we must call CGContextStrokePath instead of CGContextFillPath. A minor bother when connecting a series of points with lines is that we have to call CGContextMoveToPoint for the first point and CGContextAddLineToPoint for each subsequent point.

Things to try

  1. Would it look more Bauhaus with a line width of 10?

  2. Graph a different function. Change the punchline
    		let y: CGFloat = sin(x);
    
    to any one of the following.
    		let y: CGFloat = cos(x)
    
    		let y: CGFloat = x * x;	//parabola
    		let y: CGFloat = x * x / 4;	//shorter parabola
    		let y: CGFloat = -x * x / 4;	//upside down parabola
    		let y: CGFloat = sqrt(abs(x));	//sideways half parabola
    		let y: CGFloat = sqrt(4 * 4 - x * x);	//semicircle
    
    		let y: CGFloat = x;		//diagonal line
    		let y: CGFloat = floor(x);	//Walk like an Egyptian.
    		let y: CGFloat = floor(4 * sin(x));	//Mayan ruins.
    		let y: CGFloat = x * sin(3 * x);	//CERN.
    		let y: CGFloat = 3 * sin(80 / x);	//The Outer Limits.
    
    Then change it back to
    		let y: CGFloat = sin(x);
    

  3. Draw a caption in the upper left corner immediately after creating c. The expression " y = sin(x)" is an object of type String.
    	let attributes: [String: AnyObject] = [NSFontAttributeName: UIFont.systemFontOfSize(24)];
    	" y = sin(x)".drawAtPoint(bounds.origin, withAttributes: attributes);
    

  4. Use a pattern to draw graph paper in the background, a bit lighter than the axes. Insert the following code into the drawRect: method of class View immediately before the “Draw the axes in faint blue” comment.
    		//Create a callback that will be called over and over
    		//to draw each cell of the graph paper.
    		//Each cell consists of two lines:
    		//   |
    		//   |
    		//   |
    		//   +-------------
    
    		let drawPattern: CGPatternDrawPatternCallback = {s, c in
    			//draw pattern using context...
    			let scale = UnsafePointer<CGFloat>(s).memory;
    			CGContextBeginPath(c);
    			CGContextSetLineWidth(c, 1 / scale);
    			CGContextMoveToPoint(c, 0, 1);                    //top of L
    			CGContextAddLineToPoint(c, 0, 0);                 //vertical line
    			CGContextAddLineToPoint(c, CGFloat(M_PI / 2), 0); //horizontal line
    			CGContextStrokePath(c);
    		}
    
    		var callbacks = CGPatternCallbacks(version: 0, drawPattern: drawPattern, releaseInfo: nil);
    		let rect: CGRect = CGRect(x: 0, y: 0, width: pi/2, height: 1); //pattern's bounding box
    
    		let pattern: CGPatternRef = CGPatternCreate(
    			&scale,		//first argument passed to drawPattern
    			rect,
    			transform,
    			rect.size.width, rect.size.height,	//no distance between cells
    			CGPatternTiling.ConstantSpacing,
    			false,	//Graph paper is monochromatic (a "stencil" pattern).
    			&callbacks
    		)!;
    
    		//Draw the background graph paper in very faint blue.
    		CGContextBeginPath(c);
    
    		//Tell it that the pattern's colors are RGB, not CYMK or grayscale.
    		let colorSpace: CGColorSpaceRef = CGColorSpaceCreateDeviceRGB()!;
    		let patternColorSpace: CGColorSpaceRef = CGColorSpaceCreatePattern(colorSpace)!;
    		CGContextSetFillColorSpace(c, patternColorSpace);
    		
    		//Fill the background with the pattern.
    		let color: [CGFloat] = [0, 0, 1, 0.25];	//rgb alpha
    		CGContextSetFillPattern(c, pattern, color);
    		CGContextFillRect(c, CGRectApplyAffineTransform(bounds, CGAffineTransformInvert(transform)));
    
    Change the scale that is decared right after pi from a let to a var.

    If we had just said

    		CGContextFillRect(c, bounds);
    
    instead of
    		CGContextFillRect(c, CGRectApplyAffineTransform(bounds, CGAffineTransformInvert(transform)));
    
    then only the first quadrant of the plane (to the upper right of the origin) would have been filled with graph paper. That’s because the pattern starts at the origin and moves towards higher x and y values.


  5. We had to pass scale as a parameter to the callback because the callback has no access to the variables in the surrounding method. Could we also pass rect to the callback? If so, we could change the third argument of CGContextMoveToPoint to rect.size.height, and we could change the second argument of the second CGContextAddLineToPoint to rect.size.width.

  6. How could we find out how many pixels per point there really are?