Figure 8-10. Connecting the colorView outlet
Now connect the actions of the three sliders to the controller’s -changeHue:, -changeSaturation:, and -changeBrightness: methods. Select the top slider. Using the connections inspector, connect the Value Changed event to thecontroller’s -changedHue: action. Repeat, connecting the middle slider to the -changeSaturation: method, and the bottom slider to the -changeBrightness: method, as shown in Figure 8-11.
Figure 8-11. Connecting slider actions
There’s one last, cosmetic, detail to attend to. The values for the hue, saturation, and brightness in the data model all initialize to 0.0 (black). The default color in the color view is not black, and the initial positions of the sliders are all 0.5.
So that your view objects are consistent with your data model from the beginning, select the sliders and use the attributes inspector to set the Current property to 0.0. Select the color view object and set its background attribute to Black Color, as shown in Figure 8-12.
Figure 8-12. Finished ColorModel interface
Run your app in the iPhone simulator. It appears with the color black and all three sliders set to their minimum values. Change the values of the sliders to explore different combinations of hue, saturation, and brightness, asshown on the right in Figure 8-13.
Figure 8-13. First ColorModel app
Having Multiple Views
One reason the MVC design pattern separates the data model from the view objects is to avoid a one-to-one relationship between the two. With MVC you can create a one-to-many, or even a many-to-many, relationshipbetween your data model and view objects. Exploit this by creating more view objects that display the same data model and in different ways.
Start by selecting your Main.storyboard Interface Builder file. Using the right resizing handle, make the width of the three sliders considerably shorter. You want to temporarily create some room to add new view objects to their right, as shown in Figure 8-14.
Figure 8-14. Making room for new view objects
Find the label object in the library and add three new labels, to the right of each slider and aligned with the right edge of the color view, as shown in Figure 8-15.
Figure 8-15. Adding HSB value labels
Each label will display the textual value of one property. Edit the text property of the three labels, either using the attributes inspector or by double-clicking on the label object. Change the top label to 360° (press shift+option+8to get the degrees symbol), and the other two to 100%, as shown in
Figure 8-16. If the labels shift position after editing, drag them so their right edges are once again aligned with the right edge of the color view.
Figure 8-16. Setting placeholder values
Select all three labels. Using the attributes inspector, change their Alignment property to right justified (the rightmost of the three alignment buttons). This will keep the values neatly lined up.
Select the top slider. Select the right-edge constraint, created by Xcode, just to the right of the slider, as shown in Figure 8-17. Using the attributes inspector, set its value to -60. This changes the constraint so the right edge ofthe top slider is now inset from the right edge of the color view by 60 pixels, leaving room for the labels you just added.
Figure 8-17. Adjusting slider constraint
If you want to see the effects of this change, select all three sliders and choose Update Frames from the Resolve Auto Layout Issues control. To finish the layout, choose Add Missing Constraints in View Controller from theResolve Auto Layout Issues control.
You’ll need outlets to use these three views, so add these to your CMViewController.h interface file:
@property (weak,nonatomic) IBOutlet UILabel *hueLabel;
@property (weak,nonatomic) IBOutlet UILabel *saturationLabel;
@property (weak,nonatomic) IBOutlet UILabel *brightnessLabel;
Connect these three outlets in Interface Builder. Switch back to the Main.storyboard file, select the View Controller, and use the connections inspectorto connect the outlets to their respective UILabel objects, as shown in Figure8-18.
Figure 8-18. Connecting the label outlets
Switch to your implementation file (CMViewController.m), and modify the three actions so each also updates its respective label view, by adding the following bold code:
- (IBAction)changeHue:(UISlider*)sender
{
self.colorModel.hue = sender.value; self.colorView.backgroundColor = self.colorModel.color; self.hueLabel.text = [NSString stringWithFormat:@"%.0f\u00b0",
self.colorModel.hue];
}
- (IBAction)changeSaturation:(UISlider*)sender
{
self.colorModel.saturation = sender.value; self.colorView.backgroundColor = self.colorModel.color; self.saturationLabel.text = [NSString stringWithFormat:@"%.0f%%",
self.colorModel.saturation];
}
- (IBAction)changeBrightness:(UISlider*)sender
{
self.colorModel.brightness = sender.value; self.colorView.backgroundColor = self.colorModel.color; self.brightnessLabel.text = [NSString stringWithFormat:@"%.0f%%",
self.colorModel.brightness];
}
These three new statements change the text in the label fields to display the textual value of each property. The %.0f format specifier rounds the data model’s floating point value to the nearest integer. Literally, it means“format (%) the floating point value (f), so there are zero (.0) digits to the right of its radix point.”
Now run your app again. This time, whenever you adjust the value of one of the sliders, both the color and the textual HSB value are updated too, as shown in Figure 8-19.
Figure 8-19. ColorModel with HSB values
Consolidating Updates
Now your data model appears, in different forms, in four different views. But why stop there? In the CMViewController.xib file, add two more labels. Set the text of one to #000000 and the other to Web:. Position them as shown in Figure 8-20, and set the alignment property of the one on the right to right justified. Choose Add Missing Constraints in View Controller from the Resolve Auto Layout Issues control.
Figure 8-20. Adding web-safe color view
You’ll use this label to display the “web” color selected. This is the RGB value of the chosen color,
as an HTML short color constant. You should be able to do the next two steps in your sleep: Add the following outlet property to CMViewController.h:
@property (weak,nonatomic) IBOutlet UILabel *webLabel;
Switch back to Main.storyboard and connect the webLabel outlet to the #000000 label object, as shown in Figure 8-21.
Figure 8-21. Connecting webLabel outlet
Now switch to the CMViewController.m implementation file and consider what needs to change.
Here’s the code to set the webLabel view to display the hex value of the color:
CGFloat red, green, blue, alpha;
[self.colorModel.color getRed:&red green:&green blue:&blue alpha:&alpha]; self.webLabel.text = [NSString stringWithFormat:@"#%02lx%02lx%02lx",
lroundf(red*0xff), lroundf(green*0xff), lroundf(blue*0xff)];
This code extracts the individual red, green, and blue values from the UIColor object. It then uses those values (in the range of 0.0 to 1.0) to create a string of six hexadecimal digits, two for each color, in the range of 00 to ff,rounding to the closest integer.
While that’s not a lot of code, it is a lot of code to repeat three times, because each action method
(-changeHue:, -changeSaturation:, -changeBrightness:) must also update the new web value view.
There’s an old programming adage that says:
If you’re repeating yourself, refactor.
It means that if you find yourself writing the same code, again and again, it’s probably a good time
to reorganize and consolidate your code. It’s a truism that the more code you write, the more chance you have of introducing a bug. A common goal of software engineers is to minimize the amount of code they write. Not justbecause we’re lazy (at least, many of us are), but because it results in more succinct solutions.
Consolidate the updates to your various view objects into a single method named -updateColor.
Start by adding a prototype for the new method at the beginning of the CMViewController.m file:
@interface CMViewController ()
- (void)updateColor;
@end
Replace the individual updates in each action with a single message to update all of the view objects:
- (IBAction)changeHue:(UISlider*)sender
{
self.colorModel.hue = sender.value;
[self updateColor];
}
- (IBAction)changeSaturation:(UISlider*)sender
{
self.colorModel.saturation = sender.value;
[self updateColor];
}
- (IBAction)changeBrightness:(UISlider*)sender
{
self.colorModel.brightness = sender.value;
[self updateColor];
}
Finally, write the -updateColor method:
- (void)updateColor
{
self.colorView.backgroundColor = self.colorModel.color; self.hueLabel.text = [NSString stringWithFormat:@"%.0f\u00b0",
self.colorModel.hue]; self.saturationLabel.text = [NSString stringWithFormat:@"%.0f%%",
self.colorModel.saturation]; self.brightnessLabel.text = [NSString stringWithFormat:@"%.0f%%",
self.colorModel.brightness]; CGFloat red, green, blue, alpha;
[self.colorModel.color getRed:&red green:&green blue:&blue alpha:&alpha]; self.webLabel.text = [NSString stringWithFormat:@"#%02lx%02lx%02lx",
lroundf(red*255), lroundf(green*255), lroundf(blue*255)];
}
The first line updates the background color of the color view object, a task that had been repeated in each of the three actions. The next three statements update the three HSB label views, and the block of code at the endcalculates the hexadecimal RGB value and updates webLabel.
Run your app again, as shown in Figure 8-22. Each change to the data model updates five different view objects, and your controller code is arguably simpler and easier to maintain than it was before. You can easily add new actions that update the data model; all you have to do is send -updateColor before returning. Similarly, new view objects could be added and you’d only have to add an outlet and modify-updateColor.
Figure 8-22. ColorModel with web value
Complex View Objects
So far, the view objects you’ve used in ColorModel display relatively trivial (NSString or UIColor) values. Sometimes view objects display much more complex data types. It’s not uncommon for complex view objects tomaintain a reference to the data model. This gives them direct access to all of the information they need.
To make ColorModel a little more interesting, you’re going to replace the simple UIView object with a custom view object that displays a hue/saturation color chart, in addition to identifying the exact color selected by the hue,saturation, and brightness sliders. Revising your design, your new app should look like the one in Figure 8-23.
Figure 8-23. Updated ColorModel design
Replacing UIView with CMColorView
Your new design will replace the UIView object in your current design with your own custom CMColorView object. Start by adding a new Objective-C class to your project. Select the ColorModel group (folder) in the projectnavigator and choose the File ➤ New ➤ File . . . command (or right/ control+click on the group and choose New File . . .). Select the Objective-C class template, name it CMColorView, make it a subclass of UIView, and add itto your project.
Upgrade the plain view in your interface from a UIImage object to your new CMColorView object.
In Main.storyboard, select the UIImage view object. Use the identity inspector to change the class of the object from UIView to CMColorView, as shown in Figure 8-24.
Figure 8-24. Changing the UIView into a CMColorView
In your CMViewController.h interface file, find the colorView property that refers to this object. Add an #include statement toward the top of the file so that CMViewController knows about CMColorView objects:
#import "CMColorView.h"
Now change the type of the colorView property from UIView to CMColorView (modified code shown in bold). Now your controller is connected to a CMColorView object instead:
@property (weak,nonatomic) IBOutlet CMColorView *colorView;
Connecting the View to Your Data Model
Unlike the view objects you’ve used so far, your CMColorView object will both understand and refer to your CMColor data model. So that it understands CMColor, add this #include statement near the top
of your new CMColorView.h interface file:
#include "CMColor.h"
Now add a property to the @interface so that CMColorView has a connection to the CMColor object: @property
(strong,nonatomic) CMColor *colorModel;















No comments:
Post a Comment