Blocks

Blocks as variables

Now that you have some experience with Objective-C and the iOS SDK, how would you add the colorize-on-shake and invert-color-on-rotate features? You would probably write a method that would loop over every line, perform some calculations with that line’s data, and then set its color.

/​/​ ​A​ ​c​a​n​d​i​d​a​t​e​ ​f​o​r​ ​c​o​l​o​r​i​z​e​O​n​S​h​a​k​e​ ​a​s​ ​i​m​p​l​e​m​e​n​t​e​d​ ​b​y​ ​T​o​u​c​h​D​r​a​w​V​i​e​w​
-​ ​(​v​o​i​d​)​c​o​l​o​r​i​z​e​O​n​S​h​a​k​e​
{​
 ​ ​ ​ ​f​o​r​(​L​i​n​e​ ​*​l​ ​i​n​ ​c​o​m​p​l​e​t​e​L​i​n​e​s​)​ ​{​
 ​ ​ ​ ​ ​ ​ ​ ​U​I​C​o​l​o​r​ ​*​c​l​r​ ​=​ ​[​s​e​l​f​ ​c​o​m​p​u​t​e​C​o​l​o​r​F​o​r​L​i​n​e​:​l​]​;​
 ​ ​ ​ ​ ​ ​ ​ ​[​l​ ​s​e​t​C​o​l​o​r​:​c​l​r​]​;​
 ​ ​ ​ ​}​
 ​ ​ ​ ​[​s​e​l​f​ ​s​e​t​N​e​e​d​s​D​i​s​p​l​a​y​]​;​
}​

Then, you would write a similar method for inverting the current color of a Line.

/​/​ ​A​ ​c​a​n​d​i​d​a​t​e​ ​f​o​r​ ​i​n​v​e​r​t​O​n​S​h​a​k​e​ ​a​s​ ​i​m​p​l​e​m​e​n​t​e​d​ ​b​y​ ​T​o​u​c​h​D​r​a​w​V​i​e​w​
-​ ​(​v​o​i​d​)​i​n​v​e​r​t​O​n​R​o​t​a​t​e​
{​
 ​ ​ ​ ​f​o​r​(​L​i​n​e​ ​*​l​ ​i​n​ ​c​o​m​p​l​e​t​e​L​i​n​e​s​)​ ​{​
 ​ ​ ​ ​ ​ ​ ​ ​/​/​ ​T​h​e​ ​m​e​t​h​o​d​ ​c​a​l​l​e​d​ ​t​o​ ​c​o​m​p​u​t​e​ ​t​h​e​ ​c​o​l​o​r​
 ​ ​ ​ ​ ​ ​ ​ ​/​/​ ​i​s​ ​t​h​e​ ​o​n​l​y​ ​d​i​f​f​e​r​e​n​c​e​ ​i​n​ ​t​h​i​s​ ​m​e​t​h​o​d​
 ​ ​ ​ ​ ​ ​ ​ ​/​/​ ​v​e​r​s​u​s​ ​t​h​e​ ​p​r​e​v​i​o​u​s​ ​o​n​e​.​
 ​ ​ ​ ​ ​ ​ ​ ​U​I​C​o​l​o​r​ ​*​c​l​r​ ​=​ ​[​s​e​l​f​ ​i​n​v​e​r​t​e​d​C​o​l​o​r​F​o​r​L​i​n​e​:​l​]​;​
 ​ ​ ​ ​ ​ ​ ​ ​[​l​ ​s​e​t​C​o​l​o​r​:​c​l​r​]​;​
 ​ ​ ​ ​}​
 ​ ​ ​ ​[​s​e​l​f​ ​s​e​t​N​e​e​d​s​D​i​s​p​l​a​y​]​;​
}​

Now, this is a fine approach, but notice the redundancy between the two methods. Imagine if you wanted to add another coloring scheme to your application; for example, a double tap could reset all of the lines to black. This method would repeat the same general form as the previous two (looping over all of the lines and giving them a color) with slightly different details (the calculations to determine the color).

It would be much cooler to write the general form once and plug in different sets of details. With blocks, you can. In TouchDrawView.m, implement a stub for the following method. This method will eventually take care of the generic part of the process.

-​ ​(​v​o​i​d​)​t​r​a​n​s​f​o​r​m​L​i​n​e​C​o​l​o​r​s​W​i​t​h​B​l​o​c​k​:​(​U​I​C​o​l​o​r​ ​*​ ​(​^​)​(​L​i​n​e​ ​*​)​)​c​o​l​o​r​F​o​r​L​i​n​e​
{​
 ​ ​ ​ ​/​/​ ​Y​o​u​'​l​l​ ​f​i​l​l​ ​i​n​ ​t​h​e​ ​b​o​d​y​ ​f​o​r​ ​t​h​i​s​ ​m​e​t​h​o​d​ ​i​n​ ​a​ ​b​i​t​.​
 ​ ​ ​ ​/​/​ ​T​h​e​ ​c​r​a​z​y​ ​s​y​n​t​a​x​ ​o​f​ ​t​h​e​ ​a​r​g​u​m​e​n​t​ ​f​o​r​ ​t​h​i​s​ ​m​e​t​h​o​d​
 ​ ​ ​ ​/​/​ ​d​e​s​e​r​v​e​s​ ​s​o​m​e​ ​d​i​s​c​u​s​s​i​o​n​ ​f​i​r​s​t​.​
}​

Scary syntax, huh? It’s actually not that bad when you break it down. The method transformLineColorsWithBlock: accepts one argument: a block. This block must return an instance of UIColor, and its only argument is of type Line.

Figure 24.2  Syntax of a block

Syntax of a block

The syntax of a block is made even scarier because it is being used as an argument to a method, which adds an additional set of parentheses around the block. You will get used to it.

The name of the block variable in the scope of this method is colorForLine. A block, being a piece of executable code, can be called just like a C function. Therefore, you can run the code in this block like so:

 ​ ​ ​ ​U​I​C​o​l​o​r​ ​*​c​ ​=​ ​c​o​l​o​r​F​o​r​L​i​n​e​(​s​o​m​e​L​i​n​e​)​;​

The goal of the method transformLineColorsWithBlock: is to change the color of every line in the completeLines given a block that defines how they should be colored. Thus, the implementation of this method will iterate over every Line in completeLines and set its color to the return value of the block. Update this method in TouchDrawView.m.

-​ ​(​v​o​i​d​)​t​r​a​n​s​f​o​r​m​L​i​n​e​C​o​l​o​r​s​W​i​t​h​B​l​o​c​k​:​(​U​I​C​o​l​o​r​ ​*​ ​(​^​)​(​L​i​n​e​ ​*​)​)​c​o​l​o​r​F​o​r​L​i​n​e​
{​
 ​ ​ ​ ​f​o​r​(​L​i​n​e​ ​*​l​ ​i​n​ ​c​o​m​p​l​e​t​e​L​i​n​e​s​)​ ​{​
 ​ ​ ​ ​ ​ ​ ​ ​U​I​C​o​l​o​r​ ​*​c​ ​=​ ​c​o​l​o​r​F​o​r​L​i​n​e​(​l​)​;​
 ​ ​ ​ ​ ​ ​ ​ ​[​l​ ​s​e​t​C​o​l​o​r​:​c​]​;​
 ​ ​ ​ ​}​
 ​ ​ ​ ​[​s​e​l​f​ ​s​e​t​N​e​e​d​s​D​i​s​p​l​a​y​]​;​
}​

Build your application to make sure there are no errors. It will still run the same because you have yet to invoke this method.

Now, you will implement a method that will be invoked when the user shakes the device. It will create a block that will be sent to transformLineColorsWithBlock:; which executes the block for each of the completeLines. This block will do some simple geometry to compute a color for a given Line. You will use the difference between the x and y components of the start and end points to compute the red and green values of the color, and the length of the line for the blue component. Define this method in TouchDrawView.m.

-​ ​(​v​o​i​d​)​c​o​l​o​r​i​z​e​
{​
 ​ ​ ​ ​/​/​ ​V​e​r​t​i​c​a​l​ ​m​e​a​n​s​ ​m​o​r​e​ ​r​e​d​,​ ​h​o​r​i​z​o​n​t​a​l​ ​m​e​a​n​s​ ​m​o​r​e​ ​g​r​e​e​n​,​
 ​ ​ ​ ​/​/​ ​l​o​n​g​e​r​ ​m​e​a​n​s​ ​m​o​r​e​ ​b​l​u​e​

 ​ ​ ​ ​/​/​ ​A​ ​b​l​o​c​k​ ​v​a​r​i​a​b​l​e​ ​n​a​m​e​d​ ​c​o​l​o​r​S​c​h​e​m​e​ ​i​s​ ​c​r​e​a​t​e​d​ ​h​e​r​e​:​
 ​ ​ ​ ​U​I​C​o​l​o​r​ ​*​ ​(​^​c​o​l​o​r​S​c​h​e​m​e​)​(​L​i​n​e​ ​*​)​ ​=​ ​^​(​L​i​n​e​ ​*​l​)​ ​{​
 ​ ​ ​ ​ ​ ​ ​ ​/​/​ ​C​o​m​p​u​t​e​ ​d​e​l​t​a​ ​b​e​t​w​e​e​n​ ​b​e​g​i​n​ ​a​n​d​ ​e​n​d​ ​p​o​i​n​t​s​
 ​ ​ ​ ​ ​ ​ ​ ​/​/​ ​f​o​r​ ​e​a​c​h​ ​c​o​m​p​o​n​e​n​t​
 ​ ​ ​ ​ ​ ​ ​ ​f​l​o​a​t​ ​d​x​ ​=​ ​[​l​ ​e​n​d​]​.​x​ ​-​ ​[​l​ ​b​e​g​i​n​]​.​x​;​
 ​ ​ ​ ​ ​ ​ ​ ​f​l​o​a​t​ ​d​y​ ​=​ ​[​l​ ​e​n​d​]​.​y​ ​-​ ​[​l​ ​b​e​g​i​n​]​.​y​;​

 ​ ​ ​ ​ ​ ​ ​ ​/​/​ ​I​f​ ​d​x​ ​i​s​ ​n​e​a​r​ ​z​e​r​o​,​ ​r​e​d​ ​=​ ​1​,​ ​o​t​h​e​r​w​i​s​e​,​ ​u​s​e​ ​s​l​o​p​e​
 ​ ​ ​ ​ ​ ​ ​ ​f​l​o​a​t​ ​r​ ​=​ ​(​f​a​b​s​(​d​x​)​ ​<​ ​0​.​0​0​1​ ​?​ ​1​.​0​ ​:​ ​f​a​b​s​(​d​y​ ​/​ ​d​x​)​)​;​

 ​ ​ ​ ​ ​ ​ ​ ​/​/​ ​I​f​ ​d​y​ ​i​s​ ​n​e​a​r​ ​z​e​r​o​,​ ​g​r​e​e​n​ ​=​ ​1​,​ ​o​t​h​e​r​w​i​s​e​,​ ​u​s​e​ ​i​n​v​.​ ​s​l​o​p​e​
 ​ ​ ​ ​ ​ ​ ​ ​f​l​o​a​t​ ​g​ ​=​ ​(​f​a​b​s​(​d​y​)​ ​<​ ​0​.​0​0​1​ ​?​ ​1​.​0​ ​:​ ​f​a​b​s​(​d​x​ ​/​ ​d​y​)​)​;​

 ​ ​ ​ ​ ​ ​ ​ ​/​/​ ​b​l​u​e​ ​=​ ​l​e​n​g​t​h​ ​o​v​e​r​ ​3​0​0​
 ​ ​ ​ ​ ​ ​ ​ ​f​l​o​a​t​ ​b​ ​=​ ​h​y​p​o​t​(​d​x​,​ ​d​y​)​ ​/​ ​3​0​0​.​0​;​

 ​ ​ ​ ​ ​ ​ ​ ​r​e​t​u​r​n​ ​[​U​I​C​o​l​o​r​ ​c​o​l​o​r​W​i​t​h​R​e​d​:​r​ ​g​r​e​e​n​:​g​ ​b​l​u​e​:​b​ ​a​l​p​h​a​:​1​]​;​
 ​ ​ ​ ​}​;​

 ​ ​ ​ ​/​/​ ​P​a​s​s​ ​t​h​i​s​ ​c​o​l​o​r​S​c​h​e​m​e​ ​b​l​o​c​k​ ​t​o​ ​t​h​e​ ​m​e​t​h​o​d​
 ​ ​ ​ ​/​/​ ​t​h​a​t​ ​w​i​l​l​ ​i​t​e​r​a​t​e​ ​o​v​e​r​ ​e​v​e​r​y​ ​l​i​n​e​ ​a​n​d​ ​a​s​s​i​g​n​
 ​ ​ ​ ​/​/​ ​t​h​e​ ​c​o​m​p​u​t​e​d​ ​c​o​l​o​r​ ​t​o​ ​t​h​a​t​ ​l​i​n​e​
 ​ ​ ​ ​[​s​e​l​f​ ​t​r​a​n​s​f​o​r​m​L​i​n​e​C​o​l​o​r​s​W​i​t​h​B​l​o​c​k​:​c​o​l​o​r​S​c​h​e​m​e​]​;​
}​

We’re back in scary syntax land, again. Let’s break it down. The ultimate goal of this method is to create a block that takes a Line as an argument and returns a UIColor. This block will then be passed to the method transformLineColorsWithBlock:, which will execute this block for each line and set its color. Therefore, we must create a block and a block variable that holds a reference to that block.

Figure 24.3  Syntax of a block variable and block

Syntax of a block variable and block

The actual block is the part that follows the assignment operator (=). A block is defined by the ^ character and parentheses that contain the arguments that must be passed to this block when it is called, followed by curly brackets where the block body is entered. Objective-C, C, and C++ code can make up the body of a block.

A block variable, which holds a reference to a block, is the part before the assignment operator. A block variable is just like any other variable: it has a type and a name. The only difference is that the variable declaration is a bit ugly. Typically, variable definitions follow the following form:

 ​ ​ ​ ​t​y​p​e​ ​v​a​r​n​a​m​e​ ​=​ ​.​.​.​;​

 ​ ​ ​ ​/​/​ ​E​x​a​m​p​l​e​
 ​ ​ ​ ​i​n​t​ ​c​o​u​n​t​e​r​ ​=​ ​0​;​

The syntax for a block variable, however, mixes the name of the variable in the middle of the type of the block. The form is the return type, followed by parentheses that contain the ^ character along with the name of the variable and another set of parentheses that contain all of the arguments for this block.

The assignment of colorScheme to the block defined in colorize is valid because the block it references has the same arguments and return type. (You don’t have to include the return type when defining the block in this way; the compiler will figure it out.) Try changing the block to have a different argument list to see the compiler complain:

 ​ ​ ​ ​/​/​ ​T​h​e​ ​c​o​m​p​i​l​e​r​ ​w​i​l​l​ ​n​o​t​ ​l​i​k​e​ ​t​h​i​s​:​ ​t​h​e​ ​b​l​o​c​k​ ​v​a​r​i​a​b​l​e​
 ​ ​ ​ ​/​/​ ​a​n​d​ ​d​e​f​i​n​e​d​ ​b​l​o​c​k​ ​a​r​e​ ​o​f​ ​d​i​f​f​e​r​e​n​t​ ​t​y​p​e​s​.​
 ​ ​ ​ ​U​I​C​o​l​o​r​ ​*​ ​(​^​c​o​l​o​r​S​c​h​e​m​e​)​(​L​i​n​e​ ​*​)​ ​=​ ​^​(​L​i​n​e​ ​*​l​,​ ​i​n​t​ ​f​o​o​)​ ​{​
 ​ ​ ​ ​ ​ ​ ​ ​.​.​.​
 ​ ​ ​ ​}​;​

Change the block back to the working version.

Now you have a method, colorize, that, when executed, will create a block that computes a color given a line. This block is sent via the message transformLineColorsWithBlock: to the instance of TouchDrawView. In TouchDrawView.m, set up TouchDrawView so it is sent the message colorize when the device is shaken by implementing the following code.

-​ ​(​B​O​O​L​)​c​a​n​B​e​c​o​m​e​F​i​r​s​t​R​e​s​p​o​n​d​e​r​
{​
 ​ ​ ​ ​r​e​t​u​r​n​ ​Y​E​S​;​
}​
-​ ​(​v​o​i​d​)​d​i​d​M​o​v​e​T​o​W​i​n​d​o​w​
{​
 ​ ​ ​ ​[​s​e​l​f​ ​b​e​c​o​m​e​F​i​r​s​t​R​e​s​p​o​n​d​e​r​]​;​
}​
-​ ​(​v​o​i​d​)​m​o​t​i​o​n​B​e​g​a​n​:​(​U​I​E​v​e​n​t​S​u​b​t​y​p​e​)​m​o​t​i​o​n​ ​w​i​t​h​E​v​e​n​t​:​(​U​I​E​v​e​n​t​ ​*​)​e​v​e​n​t​
{​
 ​ ​ ​ ​[​s​e​l​f​ ​c​o​l​o​r​i​z​e​]​;​
}​

Build and run your application. Draw some lines then shake the device. (If you are on the simulator, choose Shake Gesture from the Hardware menu.)

You may have some warnings about colorize or transformLineColorsWithBlock:. You can ignore these warnings for now; you’ll fix them later. If your application is throwing an exception about unrecognized selectors, make sure you have spelled these two method names correctly in their implementation and use.

Capturing variables

So far, blocks look a lot like C function pointers. However, there is one major feature of blocks that separates it from function pointers: it captures variables. In the last section, the block created all of its variables inside its definition. What happens if you use a variable inside a block that wasn’t declared in that block? For example:

-​ ​(​v​o​i​d​)​m​e​t​h​o​d​
{​
 ​ ​ ​ ​/​/​ ​A​ ​v​a​r​i​a​b​l​e​ ​c​r​e​a​t​e​d​ ​i​n​ ​t​h​e​ ​s​c​o​p​e​ ​o​f​ ​t​h​i​s​ ​m​e​t​h​o​d​
 ​ ​ ​ ​/​/​ ​a​k​a​,​ ​n​o​t​ ​i​n​s​i​d​e​ ​a​B​l​o​c​k​
 ​ ​ ​ ​i​n​t​ ​v​a​l​u​e​ ​=​ ​5​;​

 ​ ​ ​ ​/​/​ ​C​r​e​a​t​e​ ​a​ ​b​l​o​c​k​ ​w​i​t​h​ ​n​o​ ​a​r​g​u​m​e​n​t​s​ ​o​r​ ​r​e​t​u​r​n​ ​v​a​l​u​e​
 ​ ​ ​ ​v​o​i​d​ ​(​^​a​B​l​o​c​k​)​(​)​ ​=​ ​^​(​v​o​i​d​)​
 ​ ​ ​ ​{​
 ​ ​ ​ ​ ​ ​ ​ ​/​/​ ​T​h​i​s​ ​b​l​o​c​k​ ​s​i​m​p​l​y​ ​p​r​i​n​t​s​ ​o​u​t​ ​v​a​l​u​e​,​
 ​ ​ ​ ​ ​ ​ ​ ​/​/​ ​a​ ​v​a​r​i​a​b​l​e​ ​d​e​c​l​a​r​e​d​ ​o​u​t​s​i​d​e​ ​t​h​e​ ​b​l​o​c​k​
 ​ ​ ​ ​ ​ ​ ​ ​N​S​L​o​g​(​@​"​%​d​"​,​ ​v​a​l​u​e​)​;​
 ​ ​ ​ ​}​;​

 ​ ​ ​ ​/​/​ ​C​a​l​l​ ​t​h​e​ ​b​l​o​c​k​
 ​ ​ ​ ​a​B​l​o​c​k​(​)​;​
}​

This, as you may expect, will print the number 5 to the console. However, so will this:

-​ ​(​v​o​i​d​)​m​e​t​h​o​d​
{​
 ​ ​ ​ ​i​n​t​ ​v​a​l​u​e​ ​=​ ​5​;​

 ​ ​ ​ ​v​o​i​d​ ​(​^​a​B​l​o​c​k​)​(​)​ ​=​ ​^​(​)​
 ​ ​ ​ ​{​
 ​ ​ ​ ​ ​ ​ ​ ​N​S​L​o​g​(​@​"​%​d​"​,​ ​v​a​l​u​e​)​;​
 ​ ​ ​ ​}​;​

 ​ ​ ​ ​/​/​ ​C​h​a​n​g​e​ ​v​a​l​u​e​'​s​ ​v​a​l​u​e​ ​b​e​f​o​r​e​ ​c​a​l​l​i​n​g​ ​b​l​o​c​k​
 ​ ​ ​ ​v​a​l​u​e​ ​=​ ​1​0​;​

 ​ ​ ​ ​/​/​ ​C​a​l​l​ ​t​h​e​ ​b​l​o​c​k​,​ ​v​a​l​u​e​ ​=​ ​1​0​ ​i​n​ ​t​h​e​ ​s​c​o​p​e​ ​o​f​ ​t​h​i​s​ ​m​e​t​h​o​d​
 ​ ​ ​ ​/​/​ ​b​u​t​ ​t​h​e​ ​b​l​o​c​k​ ​p​r​i​n​t​s​ ​5​ ​w​h​e​n​ ​i​n​v​o​k​e​d​
 ​ ​ ​ ​a​B​l​o​c​k​(​)​;​
}​

When a block is created, the current values of any variables it references are captured. Any time this block is executed in the future, it uses the captured values of those variables. This applies to more than just primitives – pointers to objects can be captured, too.

Let’s use that knowledge. Whenever the user double-taps the screen, all lines are immediately removed from the screen. It would look nicer if these lines faded off the screen instead. The easiest way to do this is to create a full-screen layer that matches the background color of the TouchDrawView and animate its opacity from transparent to opaque. When the animation completes, all of the lines will be removed from the screen along with the layer, restoring the user interface to its initial state.

To focus on the interesting parts of this exercise, let’s get the animation stuff out of the way. Add QuartzCore.framework to your project. Then, at the top of TouchDrawView.m, import the top-level header from this framework.

#​i​m​p​o​r​t​ ​<​Q​u​a​r​t​z​C​o​r​e​/​Q​u​a​r​t​z​C​o​r​e​.​h​>​

Change the implementation of clearAll to create a layer and apply a fade-in animation to it. In TouchDrawView.m, replace the following method:

-​ ​(​v​o​i​d​)​c​l​e​a​r​A​l​l​
{​
 ​ ​ ​ ​/​/​ ​C​r​e​a​t​e​ ​a​ ​n​e​w​ ​l​a​y​e​r​ ​t​h​a​t​ ​o​b​s​c​u​r​e​s​ ​t​h​e​ ​w​h​o​l​e​ ​v​i​e​w​
 ​ ​ ​ ​C​A​L​a​y​e​r​ ​*​f​a​d​e​L​a​y​e​r​ ​=​ ​[​C​A​L​a​y​e​r​ ​l​a​y​e​r​]​;​
 ​ ​ ​ ​[​f​a​d​e​L​a​y​e​r​ ​s​e​t​B​o​u​n​d​s​:​[​s​e​l​f​ ​b​o​u​n​d​s​]​]​;​
 ​ ​ ​ ​[​f​a​d​e​L​a​y​e​r​ ​s​e​t​P​o​s​i​t​i​o​n​:​
 ​ ​ ​ ​ ​ ​ ​ ​C​G​P​o​i​n​t​M​a​k​e​(​[​s​e​l​f​ ​b​o​u​n​d​s​]​.​s​i​z​e​.​w​i​d​t​h​ ​/​ ​2​.​0​,​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​[​s​e​l​f​ ​b​o​u​n​d​s​]​.​s​i​z​e​.​h​e​i​g​h​t​ ​/​ ​2​.​0​)​]​;​
 ​ ​ ​ ​[​f​a​d​e​L​a​y​e​r​ ​s​e​t​B​a​c​k​g​r​o​u​n​d​C​o​l​o​r​:​[​[​s​e​l​f​ ​b​a​c​k​g​r​o​u​n​d​C​o​l​o​r​]​ ​C​G​C​o​l​o​r​]​]​;​

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

 ​ ​ ​ ​/​/​ ​C​r​e​a​t​e​ ​a​n​ ​a​n​i​m​a​t​i​o​n​ ​t​h​a​t​ ​f​a​d​e​s​ ​t​h​i​s​ ​l​a​y​e​r​ ​i​n​ ​o​v​e​r​ ​1​ ​s​e​c​.​
 ​ ​ ​ ​C​A​B​a​s​i​c​A​n​i​m​a​t​i​o​n​ ​*​a​n​i​m​a​t​i​o​n​ ​=​ ​[​C​A​B​a​s​i​c​A​n​i​m​a​t​i​o​n​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​a​n​i​m​a​t​i​o​n​W​i​t​h​K​e​y​P​a​t​h​:​@​"​o​p​a​c​i​t​y​"​]​;​
 ​ ​ ​ ​[​a​n​i​m​a​t​i​o​n​ ​s​e​t​F​r​o​m​V​a​l​u​e​:​[​N​S​N​u​m​b​e​r​ ​n​u​m​b​e​r​W​i​t​h​F​l​o​a​t​:​0​]​]​;​
 ​ ​ ​ ​[​a​n​i​m​a​t​i​o​n​ ​s​e​t​T​o​V​a​l​u​e​:​[​N​S​N​u​m​b​e​r​ ​n​u​m​b​e​r​W​i​t​h​F​l​o​a​t​:​1​]​]​;​
 ​ ​ ​ ​[​a​n​i​m​a​t​i​o​n​ ​s​e​t​D​u​r​a​t​i​o​n​:​1​]​;​
 ​ ​ ​ ​[​f​a​d​e​L​a​y​e​r​ ​a​d​d​A​n​i​m​a​t​i​o​n​:​a​n​i​m​a​t​i​o​n​ ​f​o​r​K​e​y​:​@​"​F​a​d​e​"​]​;​
}​

Build and run the application. Draw some lines and then double-tap. The screen will fade to white. However, you won’t be able to create any new lines (the fadeLayer is obscuring them and hasn’t been removed), and the Lines in completeLines are still there. You will need to remove the layer from its superlayer and remove the lines from completeLines after the animation completes.

In order to do this, you could give the animation object a delegate. Then, you would implement the method animationDidStop:finished: to remove the layer from the screen along with all of the lines. There are two problems with this. The first is that the delegate method will be in a location other than where this animation was setup. For the purposes of code clarity, it would be nicer if you could somehow define what happens when the animation ends in the same chunk of code where the animation is kicked off.

The other problem is that, in order to remove fadeLayer from the layer hierarchy, you must keep a pointer to it (you have to send it removeFromSuperlayer to take it out of the layer hierarchy). Your only option, without blocks, is to create an instance variable in TouchDrawView that points to this layer. That’s kind of a pain for what is essentially just a temporary object. Fortunately, you can solve both of these problems using a block and the class CATransaction.

In the Chapter 23 chapter, you used CATransaction to turn off implicit animations for any layer modifications occurring within the transaction. Another feature of CATransaction is a completion block. You can pass a block to a CATransaction, and when the animations in the transaction finish, that block will be executed.

Knowing that a block will capture its variables, you will have access to fadeLayer, completeLines, linesInProcess, and even self when the block is executed (a full second after it is created). In TouchDrawView.m’s clearAll method, wrap the animation addition in a transaction and define the completion handler for that transaction.

 ​ ​ ​ ​C​A​B​a​s​i​c​A​n​i​m​a​t​i​o​n​ ​*​a​n​i​m​a​t​i​o​n​ ​=​ ​[​C​A​B​a​s​i​c​A​n​i​m​a​t​i​o​n​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​a​n​i​m​a​t​i​o​n​W​i​t​h​K​e​y​P​a​t​h​:​@​"​o​p​a​c​i​t​y​"​]​;​
 ​ ​ ​ ​[​a​n​i​m​a​t​i​o​n​ ​s​e​t​F​r​o​m​V​a​l​u​e​:​[​N​S​N​u​m​b​e​r​ ​n​u​m​b​e​r​W​i​t​h​F​l​o​a​t​:​0​]​]​;​
 ​ ​ ​ ​[​a​n​i​m​a​t​i​o​n​ ​s​e​t​T​o​V​a​l​u​e​:​[​N​S​N​u​m​b​e​r​ ​n​u​m​b​e​r​W​i​t​h​F​l​o​a​t​:​1​]​]​;​
 ​ ​ ​ ​[​a​n​i​m​a​t​i​o​n​ ​s​e​t​D​u​r​a​t​i​o​n​:​1​]​;​

 ​ ​ ​ ​[​C​A​T​r​a​n​s​a​c​t​i​o​n​ ​b​e​g​i​n​]​;​
 ​ ​ ​ ​ ​ ​ ​ ​/​/​ ​S​e​t​ ​t​h​e​ ​c​o​m​p​l​e​t​i​o​n​ ​b​l​o​c​k​ ​o​f​ ​t​h​i​s​ ​t​r​a​n​s​a​c​t​i​o​n​
 ​ ​ ​ ​ ​ ​ ​ ​/​/​ ​t​h​i​s​ ​m​e​t​h​o​d​ ​r​e​q​u​i​r​e​s​ ​a​ ​b​l​o​c​k​ ​t​h​a​t​ ​r​e​t​u​r​n​s​ ​n​o​
 ​ ​ ​ ​ ​ ​ ​ ​/​/​ ​v​a​l​u​e​ ​a​n​d​ ​a​c​c​e​p​t​s​ ​n​o​ ​a​r​g​u​m​e​n​t​:​ ​(​v​o​i​d​ ​(​^​)​(​v​o​i​d​)​)​
 ​ ​ ​ ​ ​ ​ ​ ​[​C​A​T​r​a​n​s​a​c​t​i​o​n​ ​s​e​t​C​o​m​p​l​e​t​i​o​n​B​l​o​c​k​:​^​(​v​o​i​d​)​
 ​ ​ ​ ​ ​ ​ ​ ​{​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​/​/​ ​W​h​e​n​ ​t​h​e​ ​a​n​i​m​a​t​i​o​n​ ​c​o​m​p​l​e​t​e​s​,​ ​r​e​m​o​v​e​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​/​/​ ​t​h​e​ ​f​a​d​e​L​a​y​e​r​ ​f​r​o​m​ ​t​h​e​ ​l​a​y​e​r​ ​h​i​e​r​a​r​c​h​y​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​[​f​a​d​e​L​a​y​e​r​ ​r​e​m​o​v​e​F​r​o​m​S​u​p​e​r​l​a​y​e​r​]​;​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​/​/​ ​A​l​s​o​ ​r​e​m​o​v​e​ ​a​n​y​ ​c​o​m​p​l​e​t​e​d​ ​o​r​ ​i​n​ ​p​r​o​c​e​s​s​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​/​/​ ​l​i​n​e​s​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​[​l​i​n​e​s​I​n​P​r​o​c​e​s​s​ ​r​e​m​o​v​e​A​l​l​O​b​j​e​c​t​s​]​;​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​[​c​o​m​p​l​e​t​e​L​i​n​e​s​ ​r​e​m​o​v​e​A​l​l​O​b​j​e​c​t​s​]​;​

 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​/​/​ ​R​e​d​i​s​p​l​a​y​ ​t​h​e​ ​v​i​e​w​ ​a​f​t​e​r​ ​l​i​n​e​s​ ​a​r​e​ ​r​e​m​o​v​e​d​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​[​s​e​l​f​ ​s​e​t​N​e​e​d​s​D​i​s​p​l​a​y​]​;​
 ​ ​ ​ ​ ​ ​ ​ ​}​]​;​
 ​ ​ ​ ​ ​ ​ ​ ​[​f​a​d​e​L​a​y​e​r​ ​a​d​d​A​n​i​m​a​t​i​o​n​:​a​n​i​m​a​t​i​o​n​ ​f​o​r​K​e​y​:​@​"​F​a​d​e​"​]​;​
 ​ ​ ​ ​[​C​A​T​r​a​n​s​a​c​t​i​o​n​ ​c​o​m​m​i​t​]​;​

}​

This block will capture fadeLayer, linesInProcess, completeLines, and self. When the block executes, these variables will point at the exact same objects they point at in clearAll.

Build and run the application. Draw some lines and double-tap. The lines will fade out (although we know that a big layer is actually fading in on top of them). After the fade completes, the canvas will be cleared and you can go back to drawing.

How does this actually work? The compiler is smart enough to notice any references to variables when the block is defined – these values are copied into the memory for the block. For pointers to objects that are referenced, the pointer is copied, not the object itself. In cases where the block is kept around for use later, any objects referenced in the block are sent the message retain. In other words, the block takes ownership of those objects to make sure they exist when the block is executed. (See the section called “For the More Curious: Memory Management and Blocks” at the end of this chapter for more details.)

When the block is destroyed, it releases ownership of any objects it captured.

Using blocks with other built-in methods

Blocks are so useful that many built-in classes have methods that accept blocks as arguments. One example is NSArray’s enumerateObjectsUsingBlock:. When given a block, this method will loop through the array and perform that block on each element. The block that is applied to each object in the array is of the following form:

 ​ ​ ​ ​v​o​i​d​ ​(​^​)​(​i​d​ ​o​b​j​,​ ​N​S​U​I​n​t​e​g​e​r​ ​i​d​x​,​ ​B​O​O​L​ ​*​s​t​o​p​)​

The block you define, then, must have these three arguments and not return a value. You will use the enumerateObjectsUsingBlock: to replace the for loop in transformLineColorsWithBlock:. Implement this in TouchDrawView.m.

-​ ​(​v​o​i​d​)​t​r​a​n​s​f​o​r​m​L​i​n​e​C​o​l​o​r​s​W​i​t​h​B​l​o​c​k​:​(​U​I​C​o​l​o​r​ ​*​ ​(​^​)​(​L​i​n​e​ ​*​)​)​c​o​l​o​r​F​o​r​L​i​n​e​
{​
 ​ ​ ​ ​[​c​o​m​p​l​e​t​e​L​i​n​e​s​ ​e​n​u​m​e​r​a​t​e​O​b​j​e​c​t​s​U​s​i​n​g​B​l​o​c​k​:​^​(​i​d​ ​l​i​n​e​,​ ​N​S​U​I​n​t​e​g​e​r​ ​i​d​x​,​ ​B​O​O​L​ ​*​s​t​o​p​)​ ​{​
 ​ ​ ​ ​ ​ ​ ​ ​[​(​L​i​n​e​ ​*​)​l​i​n​e​ ​s​e​t​C​o​l​o​r​:​c​o​l​o​r​F​o​r​L​i​n​e​(​l​i​n​e​)​]​;​
 ​ ​ ​ ​}​]​;​
 ​ ​ ​ ​[​s​e​l​f​ ​s​e​t​N​e​e​d​s​D​i​s​p​l​a​y​]​;​
}​

Notice how you are creating another block here for the argument passed to enumerateObjectsUsingBlock:. You don’t necessarily have to declare a block variable to hold a block if you are just passing it in a message because you don’t need to use that block elsewhere. Now, when this method is invoked, the objects in the array are enumerated over by this block. The block gets a reference to the object currently being enumerated (line), the index in the array of that object, and a stop flag that you can set to YES if you want to stop enumerating. For each Line, you call the block passed into the method and then set the color of that line given the result of the block.

Build and run the application again. The behavior should remain the same.

Another form of enumerateObjectsUsingBlock: exists for NSArray named enumerateObjectsWithOptions:usingBlock:. The options you can supply to this method will allow you to enumerate the array in reverse, and more importantly, enumerate the array concurrently. The runtime knows how to divvy up a number of block operations among the different CPU cores when told to.

On current iOS devices, there is only one CPU with one core. For the time being, you will see no performance boost from concurrently running a number of blocks. However, on future iOS devices, there will most likely be more than one core per CPU. Each core will be able to execute a block in parallel, speeding up the enumeration by a factor equal to the number of cores.

Keeping code compact with blocks

NSNotificationCenter also knows how to use blocks. When you have used the notification center before, you would add an observer and selector for a given notification name. Then, you would implement a method with a matching selector somewhere else in your implementation file. As you continue to write code, the method to handle that notification gets moved around, and you end up spending time looking for it when you want to update or check it. Using blocks, you can define the code that gets executed within the same chunk of code you add the notification observer.

You will add an observer that gets notified when the device orientation changes. When this happens, you will execute a block that inverts the color of all the lines. In initWithCoder: of TouchDrawView.m, add the following code.

-​ ​(​i​d​)​i​n​i​t​W​i​t​h​C​o​d​e​r​:​(​N​S​C​o​d​e​r​ ​*​)​a​D​e​c​o​d​e​r​
{​
 ​ ​ ​ ​s​e​l​f​ ​=​ ​[​s​u​p​e​r​ ​i​n​i​t​W​i​t​h​C​o​d​e​r​:​a​D​e​c​o​d​e​r​]​;​

 ​ ​ ​ ​i​f​ ​(​s​e​l​f​)​ ​{​
 ​ ​ ​ ​ ​ ​ ​ ​l​i​n​e​s​I​n​P​r​o​c​e​s​s​ ​=​ ​[​[​N​S​M​u​t​a​b​l​e​D​i​c​t​i​o​n​a​r​y​ ​a​l​l​o​c​]​ ​i​n​i​t​]​;​
 ​ ​ ​ ​ ​ ​ ​ ​c​o​m​p​l​e​t​e​L​i​n​e​s​ ​=​ ​[​[​N​S​M​u​t​a​b​l​e​A​r​r​a​y​ ​a​l​l​o​c​]​ ​i​n​i​t​]​;​
 ​ ​ ​ ​ ​ ​ ​ ​[​s​e​l​f​ ​s​e​t​M​u​l​t​i​p​l​e​T​o​u​c​h​E​n​a​b​l​e​d​:​Y​E​S​]​;​

 ​ ​ ​ ​ ​ ​ ​ ​[​[​U​I​D​e​v​i​c​e​ ​c​u​r​r​e​n​t​D​e​v​i​c​e​]​ ​b​e​g​i​n​G​e​n​e​r​a​t​i​n​g​D​e​v​i​c​e​O​r​i​e​n​t​a​t​i​o​n​N​o​t​i​f​i​c​a​t​i​o​n​s​]​;​

 ​ ​ ​ ​ ​ ​ ​ ​[​[​N​S​N​o​t​i​f​i​c​a​t​i​o​n​C​e​n​t​e​r​ ​d​e​f​a​u​l​t​C​e​n​t​e​r​]​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​a​d​d​O​b​s​e​r​v​e​r​F​o​r​N​a​m​e​:​U​I​D​e​v​i​c​e​O​r​i​e​n​t​a​t​i​o​n​D​i​d​C​h​a​n​g​e​N​o​t​i​f​i​c​a​t​i​o​n​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​o​b​j​e​c​t​:​n​i​l​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​q​u​e​u​e​:​n​i​l​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​u​s​i​n​g​B​l​o​c​k​:​ ​^​(​N​S​N​o​t​i​f​i​c​a​t​i​o​n​ ​*​ ​n​o​t​e​)​ ​{​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​[​s​e​l​f​ ​t​r​a​n​s​f​o​r​m​L​i​n​e​C​o​l​o​r​s​W​i​t​h​B​l​o​c​k​:​^​(​L​i​n​e​ ​*​l​)​ ​{​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​/​/​ ​N​o​t​e​ ​t​h​a​t​ ​e​x​t​r​a​c​t​_​i​n​v​e​r​t​e​d​C​o​l​o​r​ ​d​o​e​s​n​'​t​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​/​/​ ​e​x​i​s​t​ ​y​e​t​,​ ​y​o​u​ ​w​i​l​l​ ​i​m​p​l​e​m​e​n​t​ ​t​h​i​s​ ​s​o​o​n​.​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​r​e​t​u​r​n​ ​[​[​l​ ​c​o​l​o​r​]​ ​e​x​t​r​a​c​t​_​i​n​v​e​r​t​e​d​C​o​l​o​r​]​;​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​}​]​;​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​}​]​;​
 ​ ​ ​ ​}​
 ​ ​ ​ ​r​e​t​u​r​n​ ​s​e​l​f​;​
}​

Whenever TouchDrawView is notified of an orientation change, it executes the block passed as the argument paired with usingBlock:. The argument to this block must be an NSNotification because that is what the method addObserverForName:​object:​queue:​usingBlock: expects.

This block invokes the method transformLineColorsWithBlock: with another block. The coloring block here is defined inline (there is no variable to hold it), and it will return the inverted value of the current color of the line.

However, there is no extract_invertedColor for UIColor because you’ve yet to define it.

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

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