6
Subclassing UIView

In previous chapters, you’ve created several views: a UIButton, a UILabel, etc. But what exactly is a view?

  • A view is an instance of a UIView or one of its subclasses.
  • A view knows how to draw itself on the application’s window.
  • 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.

  • A view handles touch events.

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.

Figure 6.1  View that draws concentric circles

View that draws concentric circles

Creating a Custom View

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.

@​i​n​t​e​r​f​a​c​e​ ​H​y​p​n​o​s​i​s​V​i​e​w​ ​:​ ​U​I​V​i​e​w​

The drawRect: method

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:

-​ ​(​v​o​i​d​)​d​r​a​w​R​e​c​t​:​(​C​G​R​e​c​t​)​r​e​c​t​
{​
 ​ ​ ​ ​/​/​ ​W​h​a​t​ ​r​e​c​t​a​n​g​l​e​ ​a​m​ ​I​ ​f​i​l​l​i​n​g​?​
 ​ ​ ​ ​C​G​R​e​c​t​ ​b​o​u​n​d​s​ ​=​ ​[​s​e​l​f​ ​b​o​u​n​d​s​]​;​

 ​ ​ ​ ​/​/​ ​W​h​e​r​e​ ​i​s​ ​i​t​s​ ​c​e​n​t​e​r​?​
 ​ ​ ​ ​C​G​P​o​i​n​t​ ​c​e​n​t​e​r​;​
 ​ ​ ​ ​c​e​n​t​e​r​.​x​ ​=​ ​b​o​u​n​d​s​.​o​r​i​g​i​n​.​x​ ​+​ ​b​o​u​n​d​s​.​s​i​z​e​.​w​i​d​t​h​ ​/​ ​2​.​0​;​
 ​ ​ ​ ​c​e​n​t​e​r​.​y​ ​=​ ​b​o​u​n​d​s​.​o​r​i​g​i​n​.​y​ ​+​ ​b​o​u​n​d​s​.​s​i​z​e​.​h​e​i​g​h​t​ ​/​ ​2​.​0​;​

 ​ ​ ​ ​/​/​ ​F​r​o​m​ ​t​h​e​ ​c​e​n​t​e​r​ ​h​o​w​ ​f​a​r​ ​o​u​t​ ​t​o​ ​a​ ​c​o​r​n​e​r​?​
 ​ ​ ​ ​f​l​o​a​t​ ​m​a​x​R​a​d​i​u​s​ ​=​ ​h​y​p​o​t​(​b​o​u​n​d​s​.​s​i​z​e​.​w​i​d​t​h​,​ ​b​o​u​n​d​s​.​s​i​z​e​.​h​e​i​g​h​t​)​ ​/​ ​2​.​0​;​

 ​ ​ ​ ​/​/​ ​G​e​t​ ​t​h​e​ ​c​o​n​t​e​x​t​ ​b​e​i​n​g​ ​d​r​a​w​n​ ​u​p​o​n​
 ​ ​ ​ ​C​G​C​o​n​t​e​x​t​R​e​f​ ​c​o​n​t​e​x​t​ ​=​ ​U​I​G​r​a​p​h​i​c​s​G​e​t​C​u​r​r​e​n​t​C​o​n​t​e​x​t​(​)​;​

 ​ ​ ​ ​/​/​ ​A​l​l​ ​l​i​n​e​s​ ​w​i​l​l​ ​b​e​ ​d​r​a​w​n​ ​1​0​ ​p​o​i​n​t​s​ ​w​i​d​e​
 ​ ​ ​ ​C​G​C​o​n​t​e​x​t​S​e​t​L​i​n​e​W​i​d​t​h​(​c​o​n​t​e​x​t​,​ ​1​0​)​;​

 ​ ​ ​ ​/​/​ ​S​e​t​ ​t​h​e​ ​s​t​r​o​k​e​ ​c​o​l​o​r​ ​t​o​ ​l​i​g​h​t​ ​g​r​a​y​
 ​ ​ ​ ​[​[​U​I​C​o​l​o​r​ ​l​i​g​h​t​G​r​a​y​C​o​l​o​r​]​ ​s​e​t​S​t​r​o​k​e​]​;​

 ​ ​ ​ ​/​/​ ​D​r​a​w​ ​c​o​n​c​e​n​t​r​i​c​ ​c​i​r​c​l​e​s​ ​f​r​o​m​ ​t​h​e​ ​o​u​t​s​i​d​e​ ​i​n​
 ​ ​ ​ ​f​o​r​ ​(​f​l​o​a​t​ ​c​u​r​r​e​n​t​R​a​d​i​u​s​ ​=​ ​m​a​x​R​a​d​i​u​s​;​ ​c​u​r​r​e​n​t​R​a​d​i​u​s​ ​>​ ​0​;​ ​c​u​r​r​e​n​t​R​a​d​i​u​s​ ​-​=​ ​2​0​)​
 ​ ​ ​ ​{​
 ​ ​ ​ ​ ​ ​ ​ ​C​G​C​o​n​t​e​x​t​A​d​d​A​r​c​(​c​o​n​t​e​x​t​,​ ​c​e​n​t​e​r​.​x​,​ ​c​e​n​t​e​r​.​y​,​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​c​u​r​r​e​n​t​R​a​d​i​u​s​,​ ​0​.​0​,​ ​M​_​P​I​ ​*​ ​2​.​0​,​ ​Y​E​S​)​;​
 ​ ​ ​ ​ ​ ​ ​ ​C​G​C​o​n​t​e​x​t​S​t​r​o​k​e​P​a​t​h​(​c​o​n​t​e​x​t​)​;​
 ​ ​ ​ ​}​
}​

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.)

Figure 6.2  CGRect

CGRect

Instantiating a UIView

Recall that there are two ways to create an instance of your view:

  • visually choose and position the view while editing the XIB file
  • create it programmatically with alloc and initWithFrame: and make it a subview of the window

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:

#​i​m​p​o​r​t​ ​<​U​I​K​i​t​/​U​I​K​i​t​.​h​>​

/​/​ ​T​h​i​s​ ​i​s​ ​a​ ​"​f​o​r​w​a​r​d​ ​d​e​c​l​a​r​a​t​i​o​n​"​
@​c​l​a​s​s​ ​H​y​p​n​o​s​i​s​V​i​e​w​;​

@​i​n​t​e​r​f​a​c​e​ ​H​y​p​n​o​s​i​s​t​e​r​A​p​p​D​e​l​e​g​a​t​e​ ​:​ ​N​S​O​b​j​e​c​t​ ​<​U​I​A​p​p​l​i​c​a​t​i​o​n​D​e​l​e​g​a​t​e​>​
{​
 ​ ​ ​ ​H​y​p​n​o​s​i​s​V​i​e​w​ ​*​v​i​e​w​;​
}​
@​p​r​o​p​e​r​t​y​ ​(​n​o​n​a​t​o​m​i​c​,​ ​r​e​t​a​i​n​)​ ​I​B​O​u​t​l​e​t​ ​U​I​W​i​n​d​o​w​ ​*​w​i​n​d​o​w​;​

@​e​n​d​

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.

#​i​m​p​o​r​t​ ​"​H​y​p​n​o​s​i​s​t​e​r​A​p​p​D​e​l​e​g​a​t​e​.​h​"​

#​i​m​p​o​r​t​ ​"​H​y​p​n​o​s​i​s​V​i​e​w​.​h​"​

@​i​m​p​l​e​m​e​n​t​a​t​i​o​n​ ​H​y​p​n​o​s​i​s​t​e​r​A​p​p​D​e​l​e​g​a​t​e​

@​s​y​n​t​h​e​s​i​z​e​ ​w​i​n​d​o​w​;​

-​ ​(​B​O​O​L​)​a​p​p​l​i​c​a​t​i​o​n​:​(​U​I​A​p​p​l​i​c​a​t​i​o​n​ ​*​)​a​p​p​l​i​c​a​t​i​o​n​
 ​ ​ ​ ​d​i​d​F​i​n​i​s​h​L​a​u​n​c​h​i​n​g​W​i​t​h​O​p​t​i​o​n​s​:​(​N​S​D​i​c​t​i​o​n​a​r​y​ ​*​)​l​a​u​n​c​h​O​p​t​i​o​n​s​
{​
 ​ ​ ​ ​/​/​ ​M​a​k​e​ ​a​ ​C​G​R​e​c​t​ ​t​h​a​t​ ​i​s​ ​t​h​e​ ​s​i​z​e​ ​o​f​ ​t​h​e​ ​w​i​n​d​o​w​
 ​ ​ ​ ​C​G​R​e​c​t​ ​w​h​o​l​e​W​i​n​d​o​w​ ​=​ ​[​[​s​e​l​f​ ​w​i​n​d​o​w​]​ ​b​o​u​n​d​s​]​;​

 ​ ​ ​ ​/​/​ ​C​r​e​a​t​e​ ​a​n​ ​i​n​s​t​a​n​c​e​ ​o​f​ ​H​y​p​n​o​s​i​s​V​i​e​w​ ​t​h​a​t​ ​i​s​ ​t​h​e​ ​s​a​m​e​ ​s​i​z​e​ ​a​s​ ​t​h​e​ ​w​i​n​d​o​w​
 ​ ​ ​ ​v​i​e​w​ ​=​ ​[​[​H​y​p​n​o​s​i​s​V​i​e​w​ ​a​l​l​o​c​]​ ​i​n​i​t​W​i​t​h​F​r​a​m​e​:​w​h​o​l​e​W​i​n​d​o​w​]​;​

 ​ ​ ​ ​/​/​ ​S​e​t​ ​t​h​e​ ​b​a​c​k​g​r​o​u​n​d​ ​c​o​l​o​r​ ​o​f​ ​t​h​a​t​ ​v​i​e​w​ ​t​o​ ​"​c​l​e​a​r​"​
 ​ ​ ​ ​[​v​i​e​w​ ​s​e​t​B​a​c​k​g​r​o​u​n​d​C​o​l​o​r​:​[​U​I​C​o​l​o​r​ ​c​l​e​a​r​C​o​l​o​r​]​]​;​

 ​ ​ ​ ​/​/​ ​A​d​d​ ​t​h​e​ ​v​i​e​w​ ​t​o​ ​t​h​e​ ​v​i​e​w​ ​h​i​e​r​a​r​c​h​y​ ​s​o​ ​t​h​a​t​ ​i​t​ ​a​p​p​e​a​r​s​ ​o​n​ ​t​h​e​ ​w​i​n​d​o​w​
 ​ ​ ​ ​[​[​s​e​l​f​ ​w​i​n​d​o​w​]​ ​a​d​d​S​u​b​v​i​e​w​:​v​i​e​w​]​;​

 ​ ​ ​ ​/​/​ ​T​h​i​s​ ​l​i​n​e​ ​m​a​y​ ​s​a​y​ ​s​e​l​f​.​w​i​n​d​o​w​,​ ​d​o​n​'​t​ ​w​o​r​r​y​ ​a​b​o​u​t​ ​t​h​a​t​
 ​ ​ ​ ​[​[​s​e​l​f​ ​w​i​n​d​o​w​]​ ​m​a​k​e​K​e​y​A​n​d​V​i​s​i​b​l​e​]​;​
 ​ ​ ​ ​r​e​t​u​r​n​ ​Y​E​S​;​
}​

/​/​ ​A​ ​d​e​a​l​l​o​c​ ​m​e​t​h​o​d​ ​t​h​a​t​ ​w​i​l​l​ ​n​e​v​e​r​ ​g​e​t​ ​c​a​l​l​e​d​ ​b​e​c​a​u​s​e​
/​/​ ​H​y​p​n​o​s​i​s​t​e​r​A​p​p​D​e​l​e​g​a​t​e​ ​w​i​l​l​ ​e​x​i​s​t​ ​f​o​r​ ​t​h​e​ ​l​i​f​e​ ​o​f​ ​t​h​e​ ​a​p​p​l​i​c​a​t​i​o​n​
-​ ​(​v​o​i​d​)​d​e​a​l​l​o​c​
{​
 ​ ​ ​ ​[​v​i​e​w​ ​r​e​l​e​a​s​e​]​;​
 ​ ​ ​ ​[​_​w​i​n​d​o​w​ ​r​e​l​e​a​s​e​]​;​
 ​ ​ ​ ​[​s​u​p​e​r​ ​d​e​a​l​l​o​c​]​;​
}​

@​e​n​d​

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.)

Build and run your application.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.122.127