In previous chapters, you’ve created several views: a UIButton, a UILabel, etc. But what exactly is a view?
A view exists within a hierarchy. The window (an instance of UIWindow) is a view and the root of the hierarchy. It has subviews (that appear on the window). Those views can also have subviews.
In this chapter, you are going to create your own UIView subclass that fills the screen with concentric circles, as shown in Figure 6.1. You will also learn how to draw text and enable scrolling and zooming.
In Xcode, create a new iOS Window-based Application for iPhone. Name it Hypnosister.
Create a new iOS Objective-C class named HypnosisView. On the second pane of the assistant, you’ll be asked to choose a superclass; select NSObject, even though UIView is listed as an option. Choosing NSObject here tells Xcode to use the most basic template to create your class files. Almost every class and project in this book uses the simplest template available in Xcode.
Why do we do this? Templates are great for speeding up development, but they get in the way when you’re learning. Typing in every line of code instead of relying on the “magic” of a template will make you more comfortable when you’re writing your own iOS applications in the future. After you become more experienced and understand what the templates are doing, you can use them to speed things up.
Open HypnosisView.h in the editor area. Now that Xcode has created your class files, change the superclass from NSObject to UIView.
@interface HypnosisView : UIView
Every UIView subclass implements the method drawRect:, which contains the drawing code for the view. For example, a UIButton’s drawRect: method draws a rounded rectangle with a title string in the center.
Each time an instance of UIView needs to be drawn (or redrawn), the system prepares a graphics context specifically for that view. Then the context is activated, and the message drawRect: is sent to the instance of UIView that is being drawn. The graphics context’s type is CGContextRef (Core Graphics Context Reference), and it is responsible for aggregating drawing commands and producing an image as a result. This image is the appearance of the view instance. A graphics context also stores its drawing state, which includes things like the current drawing color, coordinate system, and the current line width.
Sometimes when drawing a view, you will use Objective-C to make calls defined in the UIKit framework that implicitly use the active graphics context. Other times, you will get hold of the graphics context explicitly and draw using the C functions of the Core Graphics framework. In this chapter, you will do both.
In HypnosisView.m, override the drawRect: method:
- (void)drawRect:(CGRect)rect { // What rectangle am I filling? CGRect bounds = [self bounds]; // Where is its center? CGPoint center; center.x = bounds.origin.x + bounds.size.width / 2.0; center.y = bounds.origin.y + bounds.size.height / 2.0; // From the center how far out to a corner? float maxRadius = hypot(bounds.size.width, bounds.size.height) / 2.0; // Get the context being drawn upon CGContextRef context = UIGraphicsGetCurrentContext(); // All lines will be drawn 10 points wide CGContextSetLineWidth(context, 10); // Set the stroke color to light gray [[UIColor lightGrayColor] setStroke]; // Draw concentric circles from the outside in for (float currentRadius = maxRadius; currentRadius > 0; currentRadius -= 20) { CGContextAddArc(context, center.x, center.y, currentRadius, 0.0, M_PI * 2.0, YES); CGContextStrokePath(context); } }
Notice that you are passed a CGRect structure. This is the rectangle that needs to be redrawn, sometimes called the dirty rectangle. Typically, you ignore the dirty rectangle and issue the drawing instructions as though the entire view needs to be redrawn. However, if your drawing code is intricate, you might only redraw the parts in the dirty rectangle to speed up drawing.
A CGRect structure contains the members origin and size (Figure 6.2). These two members are also structures. The origin is of type CGPoint and contains two float members: x and y. The size is of type CGSize and also has two float members: width and height. These structures are the basic building blocks of Core Graphics routines. (Remember that a structure is not an Objective-C object, so you can’t send it messages.)
Recall that there are two ways to create an instance of your view:
In Quiz and Whereami, you visually created views in the XIB file. In this chapter, you are going to create views programmatically.
Open HypnosisterAppDelegate.h and add an instance variable for the new view:
#import <UIKit/UIKit.h> // This is a "forward declaration" @class HypnosisView; @interface HypnosisterAppDelegate : NSObject <UIApplicationDelegate> { HypnosisView *view; } @property (nonatomic, retain) IBOutlet UIWindow *window; @end
Notice the @class directive after the import statement. This is a forward declaration for the class HypnosisView. When you forward declare a class, you aren’t going as far as importing the header file; you are just informing HypnosisterAppDelegate.h of the class HypnosisView so the compiler can validate it. Forward declaring a class saves time when compiling – especially with large projects.
HypnosisterAppDelegate.m, on the other hand, needs to know more about HypnosisView, so you will import the header file. In HypnosisterAppDelegate.m, import HypnosisView.h, create the new instance, and place it on the window.
#import "HypnosisterAppDelegate.h" #import "HypnosisView.h" @implementation HypnosisterAppDelegate @synthesize window; - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Make a CGRect that is the size of the window CGRect wholeWindow = [[self window] bounds]; // Create an instance of HypnosisView that is the same size as the window view = [[HypnosisView alloc] initWithFrame:wholeWindow]; // Set the background color of that view to "clear" [view setBackgroundColor:[UIColor clearColor]]; // Add the view to the view hierarchy so that it appears on the window [[self window] addSubview:view]; // This line may say self.window, don't worry about that [[self window] makeKeyAndVisible]; return YES; } // A dealloc method that will never get called because // HypnosisterAppDelegate will exist for the life of the application - (void)dealloc { [view release]; [_window release]; [super dealloc]; } @end
Notice that you are calling initWithFrame:, the designated initializer for UIView. This gives the view a size and position. When the view is added to a view hierarchy (addSubview:), its position will be in the bounds of its superview (window).
(Retain count trivia: Because you created the view with alloc in HypnosisterAppDelegate.m and then added it to the window, the view is being retained by HypnosisterAppDelegate and the window, and so has a retain count of two. Also note that neither HypnosisterAppDelegate nor the window will ever get deallocated because they exist the entire time the application is running.)
3.133.122.127