Tuesday, 6 May 2014

Color My (Social) World [Networking, the Social Kind]

Start with the final ColorModel app from Chapter 8. You will add a button that shares the chosen color with the world. iOS includes a standard “Activity” toolbar item, just for this purpose, so use that. In the Main.storyboardinterface file, add a toolbar to the bottom of the view controllers interface, as shown in Figure 13-1. The toolbar comes with one bar button item pre-installed. Select the new toolbar and choose Editor  Resolve Auto LayoutIssues  Add Missing Constraints.


Figure 13-1. Adding a toolbar and toolbar button item


  Select the included bar button item and change its identifier property to Action.


Switch to the assistant editor view (View  Assistant Editor  Show Assistant Editor). Switch to the
CMViewController.h file (in the right pane) and add a new action:

-  (IBAction)share:(id)sender;

Connect the action to the button by dragging the action connection to the button. I wont include a figure for that, because if you dont know what that looks like by now you’ve clearly skipped most of the earlier chapters.


Having Something to Share

Start out by sharing the red-green-blue code for the color. Currently, the code that generates the HTML value for the color is in the -observeValueForKeyPath:ofObject:change:context:
method (CMViewController.m). You now havea second method that needs that conversion; consider
reorganizing the code so this conversion is more readily accessible. Translating the current color into its equivalent HTML value feels as if it lies in the domain of the data model, so add this method declaration to CMColor.h:

-  (NSString*)rgbCodeWithPrefix:(NSString*)prefix;

Switch to the CMColor.m implementation file, and add the new method to the @implementation
section:

-  (NSString*)rgbCodeWithPrefix:(NSString*)prefix
{
if (prefix==nil) prefix = @"";
CGFloat  red, green, blue, alpha;
[self.color getRed:&red green:&green  blue:&blue  alpha:&alpha]; return  [NSString stringWithFormat:@"%@%02lx%02lx%02lx",
prefix, lroundf(red*255), lroundf(green*255), lroundf(blue*255)];
}

Now that your data model object will return the colors HTML code, replace the code in CMViewController.m to use the new method. Edit the end of -observeValueForKeyPath:ofObject: change:context: so it looks like this(replacement code in bold):

else if ([keyPath isEqualToString:@"color"])
{
[self.colorView  setNeedsDisplay];
self.webLabel.text = [self.colorModel rgbCodeWithPrefix:@"#"];
}
}

Presenting the Activity View Controller

While still in the CMViewController.m implementation file, add the new action method:

-  (IBAction)share:(id)sender
{
NSString   *shareMessage = [NSString stringWithFormat:
@"I wrote  an  iOS app  to share  color!"
@"  RGB=%@",
[self.colorModel rgbCodeWithPrefix:nil]]; NSArray *itemsToShare = @[shareMessage];



UIActivityViewController *activityViewController;
activityViewController = [[UIActivityViewController alloc]  initWithActivityItems:itemsToShare applicationActivities:nil];
[self presentViewController:activityViewController animated:YES
completion:nil];
}

The method starts out by collecting the items to share. Items can be a message (string), an image, a video, a document, a URL, and so on. Basically, you can include any message, link, media object, or attachment that wouldmake sense to share. These are collected together into a single NSArray.

The next section is just as straightforward. You create a UIActivityViewController, initializing it with the items you want to share. You then modally present the view controller.

Thats all there is to it! Run the project and tap on the share button, as shown in Figure 13-2.


Figure 13-2. Sharing a message


Tapping the share button presents a picker that lets the user decide how they want to share this message. While your goal is to add sharing to your app, the motivation behind the UIActivityViewController is to allow theuser to do any number of things with the data items you passed in, all external to your application. This includes actions like copying the message to the clipboard, which is why its named UIActivityViewController andnot UIPokeMyFriendsViewController.

Tapping the Twitter activity presents a tweet sheet. Tapping Mail composes a new mail message. Each activity has its own interface and options.
Some, like the copy to clipboard action, have no user interface at all; they just do their thing and dismiss the controller.

Like the photo image picker you used in Chapter 7, you must present a UIActivityViewController in a popover on the iPad. This isn’t an issue for this app, because ColorModel only runs on an iPhone. For a universalapplication—one designed to run on both the iPhone and iPad—you would check the idiom of the device to determine how to present the view controller. You’d replace the last statement in -share: with something like this:

if (UIDevice.currentDevice.userInterfaceIdiom==UIUserInterfaceIdiomPad)
{
UIPopoverController  *popover;
popover  = [[UIPopoverController alloc] initWithContentViewController:activityViewController];
[popover presentPopoverFromBarButtonItem:sender permittedArrowDirections:UIPopoverArrowDirectionAny
animated:YES];
else
{
[self presentViewController:activityViewController animated:YES
completion:nil];
}


When presenting a popover that was initiated by touching an item in the toolbar, use the -presentPopoverFromBarBu tton:permittedArrowDirection:animated: method. This code assumes that view object that sent the -share: message(sender) is the bar button item. This is a safe assumption for this app, but if -share: was sent by other kinds of view objects, you’d have to case that out and decide which
UIPopoverController method to send.

Sharing More

I admit, sharing a hexadecimal color code probably isnt going to get you a lot of “likes” on Facebook. Its pretty boring. When you share a color, what you want to share is a color. You can improve the user experience bypreparing as much content as you can for the activity view controller. Include not just plain text messages, but images, video, URLs, and so on. The more the merrier.

Its a shame you cant attach the image displayed in the CMColorView. You went to a lot of
work to create a beautiful hue/saturation chart, with the selected color spotlighted. But all that
-drawRect: code draws into a Core Graphics context; its not a UIImage object you can pass to
UIActivityViewController.

Or could you?

If you remember the section “Creating Bitmaps From Drawings” in Chapter 11, its possible to create a UIImage object by first creating an off-screen graphics context, drawing into it, and then saving the results as an image object. That would let you turn your drawing into a sharable UIImage object! So what are you waiting for?


Extracting Code

What you need now is a method that does the work of drawing the hue/saturation image in a graphics context. Lets call it -drawColorInRect:context:. You then need to change the -drawRect: method so it calls -drawColorInRect:context: to draw the UIView. Then you need a second method that creates an off-screen graphics context, calls -drawColorInRect:context: to draw the h/s chart into that, and saves the result as a UIImage.

So, how do you get from the -drawRect: method you have now to the three you need? Xcode is
here to help! When you encounter refactoring problems like this, look to Xcodes Refactor command. Theres an extract tool just for this situation. It takes a selected section of code in a method and extracts that into a separatemethod (or function). The result is a second method with the same logic, which the first method then calls. Once the code is extracted, you can then write other methods to call it.

Start by finding the -drawRect: method in CMColorView.m.
In the editor, select the code that starts with the statement if (hsImageRef!=NULL && and ends with the last statement in the method, but not the terminating } of themethod, as shown in Figure 13-3.
Its important that the first two lines of code are not selected—you’ll see why in a moment.
In the figure, a couple of if blocks have been collapsed so you can see the entire range of theselected code.


Figure 13-3. Selecting the code to extract

Choose the Refactor  Extract ... command from either the Edit menu or by right/control+clicking  on the selected text, as shown in Figure 13-3.
The extract tool analyzes the selected code, determines what local variables thecode depends on,
and converts those local variables into parameters. It then asks you to name the new method, as shown in Figure 13-4.


Figure 13-4. Extracting code into a new method

Edit the methods declaration so it is - (void)drawColorInRect:(CGRect)bounds context:(CGContextRef)context, make sure the New  method option is selected, and click the
Preview button. The refactoring tool will let you preview the changes its about to make, as shown in Figure 13-5. Review all of the changes carefully to make sure this is what you want, and then click the Save button.


Figure 13-5. Previewing refactoring changes

The extract tool has created a new -drawColorInRect:context: method with just the code that was selected in -drawRect:. It has also rewritten -drawRect: so it is now:

-  (void)drawRect:(CGRect)rect
{
CGRect bounds  = self.bounds;
CGContextRef  context = UIGraphicsGetCurrentContext(); [self drawColorInRect:bounds context:context];
}

The important fact, from a refactoring standpoint, is that -drawRect: still does exactly what it did before the change. The only difference is that a portion of its code has been relocated to a new method that can be reused byother methods.

And thats exactly what you want to do next. Add a new property getter method (to CMColorView.m) named -image  that returns a UIImage object containing the same image that -drawRect: draws in the interface:

-  (UIImage*)image
{
CGRect bounds  = self.bounds; CGSize  imageSize = bounds.size;  CGFloat  margin  = kCircleRadius/2+2; imageSize.width += margin*2; imageSize.height += margin*2;
bounds  = CGRectOffset(bounds,margin,margin);

UIGraphicsBeginImageContext(imageSize);
CGContextRef  context = UIGraphicsGetCurrentContext(); [[UIColor clearColor]  set]; CGContextFillRect(context,CGRectMake(0,0,imageSize.width,imageSize.height)); [self drawColorInRect:bounds context:context];
UIImage *image  = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext();

return  image;
}

The new method starts out by getting the existing dimensions of the view object. It then creates rectangle that is kCircleRadius/2+2 larger on every side. It does this so that when the color “spot” is drawn into the image, itwont get clipped if its close to the edge of the h/s graph. (All UIView drawing is clipped to the bounds of the view.) A bounds rectangle is then offset so it is centered in the image.

The next step is to create an off-screen drawing context, the size of the final image. You then fill the context with transparent pixels ([UIColor  clearColor]). This is done so that any pixels that arent drawn end up as transparentpixels in the image. You then reuse the -drawColorInRect:context: method you just extracted to draw the h/s chart and selected color.

The final step is to extract whats been drawn into the graphics context as a UIImage. The context can then be discarded, and you return the finished UIImage object to the sender.

Switch to the CMColorView.h interface file, and add a readonly property declaration for the new image getter:

@property  (readonly,nonatomic) UIImage *image;

No comments:

Post a Comment