Saturday, 26 April 2014

Design [Smile!]

Expanding your MyStuff app wont be difficult. You’ve already created the master/detail interface
and you have table views and editing working for both the iPhone and iPad interfaces. All of the hard work is done; you just need to embellish it a little. In the detail view you’ll add a UIImageView object
to display an image of the item, and in the table view you’ll add icons to show a smaller version in
the list, as shown in Figure 7-1.


Figure 7-1. Updated MyStuff design

When the user taps the image in the detail view, your app will present either a camera interface or an image picker interface. The camera interface will allow them to take a picture with the devices built-in camera. The image picker interface lets the user choose an existing image from their photo library. The new image will appear in both the detail view and the master list. Lets get started!


Extending Your Design

To extend your design, you’ll need to make small alterations to a number of existing classes and interface files. Whether you realize it or not, your MyStuff app uses a model-view-controller design pattern. I describe the model-view-controller design in the next chapter, but for now just know that some of the objects in your app are “data model” objects, some are “view” objects, and others are “controller” objects. Adding pictures to MyStuff willrequire:
1.       Extending your data model to include image objects
2.       Adding view objects to display those images
3.       Expanding your controller objects to take a picture and update the data model 

Revising the Data Model

The first step is to extend your data model. Locate your MyWhatsit.h interface file and add two new properties:

@property  (strong,nonatomic) UIImage *image;
@property  (readonly,nonatomic) UIImage *viewImage;

The first property adds a UIImage object reference to each MyWhatsit object. Now every MyWhatsit
object has an image. Gee, that was easy!

The second property requires a little more explanation. In all of the view objects (both the details view and in the table view) you want to display the image of the item. If there is no image, however, you want to display aplaceholder image—an image that says, “theres no image.” The viewImage property will contain either the items image or a placeholder image if there isnt one. Its a readonly property, which means that clients of this objectcant change it; in other words, the statement myWhatsit.viewImage = newImage is not allowed.

Adding the viewImage property to the MyWhatsit class is actually poor software design. The problem is that the MyWhatsit class is a data model class and the viewImage property is in the domain of the view classes. In plainEnglish, it solves a problem displaying the image, not in storing the image. You’re adding view-specific  functionality to a data model object, which is something you should avoid.

In a well-organized model-view-controller  (MVC) design, the domain  of each class should be pure: the data model classes should only have data model related properties and functions—nothing  else. The problem here isthat its so darned convenient to add a viewImage property to the MyWhatsit class: it encapsulates the logic of providing a “display image” for the item, which simplifies our code elsewhere. Code that encapsulates logic andmakes the object easier to use can’t be bad, right?

It isn’t bad. Its actually good, but is there a way to avoid the architectural “flaw”  of adding viewImage directly to the MyWhatsit class? The solution is to use a category. A category is an unusual feature of Objective-C thatsolves thorny domain issues like this, without making your objects more difficult to use. Using a category you can still add viewImage property to your MyWhatsit objects, but do it in a different module—a view module,separate from your MyWhatsit class. You get the benefits of adding a viewImage property to MyWhatsit, while keeping your data model code separate from your view code. I explain categories in Chapter 20.

At runtime (when your app runs) your MyWhatsit object still has a viewImage property, just as if you’d added it directly to your MyWhatsit class. So what does it matter? Not much, and for a small project like this the ramificationsare negligible, which is why I didn’t have you create a category for viewImage. Sometimes pragmatism trumps a fanatic adherence to design patterns. Just know that in a much more complex project, defining viewImage inMyWhatsit could become an obstacle and the solution would be to move it into a category. 

The viewImage property isnt a traditional property. Its what programmers refer to as a synthetic property; its a value determined by some logic, rather than simply returning the value of an instance variable. To make it work, you need to add that logic. Click on the MyWhatsit.m implementation file and add the following method:

-  (UIImage*)viewImage
{
if (self.image!=nil) return self.image;
return [UIImage  imageNamed:@"camera"];
}

This method provides the value for the viewImage property. Whenever client code requests the viewImage property (detailItem.viewImage), this method will be invoked and the value it returns will be the value of viewImage. This is commonly referred to as the propertys getter method.

If the MyWhatsit object has a value for its image property, viewImage returns that same object.
If not (self.image==nil), then it returns the image in the camera.png resource file. For this to workyou need toadd that placeholder image file to your project. Find the camera.png file in the MyStuff (Resources) folder 
and drag itinto the group list of your Images.xcassets asset catalog, as shown in Figure 7-2.


Figure 7-2. Adding camera.png resource

MyWhatsit is finished, so its time to add the new view objects to your interface.

Adding an Image View

The next step is to add the view objects to your detail interface. This should feel like familiar territory by now:

n     Add an imageView outlet to your MSDetailViewController class

n     Add label and image view objects to your MSDetailViewController interface file

n     Connect the imageView outlet to the image view object

Start in your MSDetailViewController.h interface file. Add the following property:

@property  (weak,nonatomic) IBOutlet  UIImageView *imageView;

Start with the iPhone interface by selecting the Main_iPhone.storyboard file. From the object library, add a new label object. Position it below the location text field, and resize it so it's the width of the text field above it, as shownin Figure 7-3. Change the labels title to “Picture.”


Figure 7-3. Adding the picture label


Add an image view object and drop it anywhere in the lower portion of the interface. Select it. Using the size inspector set its width and height to 128, as shown in Figure 7-4.


Figure 7-4. Fixing the size of the image view


Now you can drag the image into position. Place it so that it is centered in the display, the recommended distance below the label object, as shown in Figure 7-5.


Figure 7-5. Positioning the image view

The guides will indicate when the image object is centered and a comfortable distance from the label object. (You made the label object the width of the display so the image object would “bump” against it, acquiring therecommended spacing.) Now turn these suggested positions into constraints:
1.       Control-click/right-click in the image view, drag down a little, release, and choose Height from the constraint menu. This will fix the height of the image view object.
2.       Repeat for the width, dragging horizontally and choosing Width.
3.       Select the image view and the label object (the ones you just added). Do this by selecting one, holding down the Shift key, and selecting the other. Alternatively, you can drag out a selection rectangle that touches both objects. With the two selected, choose Add Missing Constraints from the Resolve Auto Layout Issues button.

The last step is to select the Detail View Controller. Switch to the connections inspector and locate the imageView outlet you added to the controller. Connect it to the image view object, as shown in Figure 7-6.


Figure 7-6. Connecting the imageView outlet

With the view objects in place, its time to add the code to make your item images appear.

Updating the View Controller

You need to modify the code in the master view controller to add the image to the table cell, and in the detail view controller to make the image appear in the new image view. Start with MSMasterViewController.m. Locate thefollowing code in -tableView:cellForRowAtIndexPath:  and add the one bold line:

cell.textLabel.text = thing.name; cell.detailTextLabel.text = thing.location; cell.imageView.image = thing.viewImage; return cell;

The new line sets the image for the cell (cell.imageView.image) to the viewImage of the rowMyWhatsit object. Remember that the view image will be either the items actual image or a placeholder. The act of setting the cellsimage view will alter the cells layout so the image appears on the left. (Refer to the “Cell Style” section in Chapter 5.)

You’re all done with MSMasterViewController. Click on MSDetailViewController.m and locate the
-configureView method. Find the following code and add the one bold line:

self.nameField.text = self.detailItem.name; self.locationField.text = self.detailItem.location; self.imageView.image = self.detailItem.viewImage; 

This new line sets the image of the UIImageView object (connected to the imageView outlet) to the image of the MyWhatsit object being edited.

From a data model and view standpoint, everything is ready to go, so give it a try. Set the scheme to the iPhone simulator and run the project. You’ll see the placeholder images appear in the table and the detail view, as shownin Figure 7-7.


Figure 7-7. Placeholder images

 So far everything is working great—theres just no way to change  the picture. To accomplish that, you’ll need to create an action.

Connecting a Choose Image Action

You want the camera, or photo library picker, interface to appear when the user taps on the image
in the detail view. Thats simple enough to hook up: create an action method and connect the image view to it. Start by defining a new action in MSDetailViewController.h (you dont need to write it yet, just declare it):

-  (IBAction)choosePicture:(id)sender;

Now switch back to the Main_iPhone.storyboard interface, select the image view object, and connect its action outlet to the -choosePicture: action in the Detail View Controller.

Uh oh, we seem to have a problem. The image view object isnt a button, or any other kind of control view; it doesnt send an action message. In fact, by default, it ignores all touch events (its User Interaction Enabled property isNO). So how do you get the image view object to send an action to your view controller?

There are a couple of ways. One solution would be to subclass UIImageView and override its touch event methods, as was described in Chapter 4 (“Coming Events”). But theres a much simpler way: attach a gesture recognizer object to the view.

In the object library, locate the tap gesture recognizer. Drag a new tap gesture recognizer object into the interface and drop it into the image view object, as shown in Figure 7-8.


Figure 7-8. Attaching a tap gesture recognizer to the image view

When you drop a gesture recognizer into a view object, Interface Builder creates a new gesturrecognizer object and connects the view object to it. This is a one-to-many relationship: a view can be connected to multiplegesture recognizers, but a recognizer only works on a single view object. To see the relationship, select the view object and use the connections inspector to see
its recognizers, as shown in upper-right of Figure 7-9. Hover your cursor over the connection and Interface Builder will highlight the object its connected to, shown at the bottom of Figure 7-9.


Figure 7-9. Examining the gesture recognizer connection of the image view object

By default, a new tap gesture recognizer is configured to recognize single finger tap events, which is exactly what you want. You do, however, need to change the attributes of the image view object. Even though you have itconnected to a gesture recognizer, the view object is still set to ignore touch events, so it will never receive any events to recognize. Rectify this by selecting the image view
object and use the attributes inspector to check the User  Interaction Enabled property, as shown in Figure 7-10.


Figure 7-10. Enabling touch events for the image view

The last step is to connect the gesture recognizer to the -choosePicture: action. Holding down the control key, drag from the gesture recognizer in the scenes dock, as shown in Figure 7-11, or from the object outline. Bothrepresent the same object. Drag the connection to the Detail View Controller and connect it to the -choosePicture: action, also shown in Figure 7-11.


Figure 7-11. Connecting the -choosePicture: action

A -choosePicture: message will now be sent to the detail view controller when the user taps on the image. Now you have to implement the choosePicture: method, which brings you to the fun part: letting the user take a picture.

Taking Pictures

The UIImagePickerController class provides simple, self-contained, interfaces for taking a picture, recording a movie, or choosing an existing image from the users photo library. The image picker controller does all of the hardwork. For the most part, all your app has to do is create UIImagePickerController object and present it as you would any other view controller. The delegate of the controller will receive messages that contain the image the userpicked, the photo they took, or the movie they recorded.

Thats not to say the image picker controller can do everything. There are a number of decisions and considerations that your app must make before, and after, the image picker has done its thing. This will be the bulk of thelogic in your app, and I’ll explain these decisions as you work
through the code. Start by adding this -choosePicture: method to your MSDetailViewController.m
implementation:

-  (IBAction)choosePicture:(id)sender
{
if (self.detailItem==nil) return;

BOOL  hasPhotoLibrary = [UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary];
BOOL  hasCamera  = [UIImagePickerController  isSourceTypeAvailable:UIImagePickerControllerSourceTy peCamera];
if (!hasPhotoLibrary &&   !hasCamera) return;

if (hasPhotoLibrary &&   hasCamera)
{
UIActionSheet *actionSheet  = [[UIActionSheet alloc] initWithTitle:nil
delegate:self cancelButtonTitle:@"Cancel"
destructiveButtonTitle:nil
otherButtonTitles:@"Take  Picture",@"Choose  Photo",nil]; [actionSheet  showInView:self.view];
return;
}
[self  presentImagePickerUsingCamera:hasCamera];
}

The first decision is easy: this action only does something if the detail view is currently editing MyWhatsit object. If not (self.detailItem==nil), then return and do nothing. This can happen
in the iPad interface when the detailview is visible, but the user has yet to select an item to edit.

You Can’t Always Get What You Want

The rest of the code deals with deciding what image picker interfaces are available to your app. This is the intersection of what your user wants to do and what your app can do. The UIImagePickerController has the potential topresent a still camera, video camera, combination still and video camera, photo library picker, or camera roll (saved) photo picker. That doesnt, however, mean it can do all of those things. Different iOS devices have differenthardware. Some have a camera, some dont, some have two. Some cameras are capable of taking movies, while others arent. Even on devices that have cameras and photo libraries, security or restrictions may prohibit your appfrom using those resources.

The first step to using the image picker is to decide what you want to doand then finout what yocan doFor this app, you want to present either a still camera interface or present a picker interfacto choose an existing imagefrom the photo library. Use the UIImagePickerController method
-isSourceTypeAvailable: to finout if you can deither of those. You pass the message a constant
indicating the kind of interface you’d like tpresent, and the method tells you if thainterface can be used.

The next two lineof code save the result of asking if the photo library picker interface cabe used in 
has Photo Library variable. The hasCamera variable will remember if the live camera interface is available.

The next line of code considers the situation where neither interface is available. In that situation, theres nothing to present and the action returns without doing anything.

The next block of code considers the more likely situation where both the camera and the photo library picker are available. So which interface do you present? Thats a question for the user to answer, so ask them.

A UIActionSheet is a pop-up controller that presents a series of buttons and asks the user to pick one. You create the object with a (optional) title, a delegate, and the titles of the buttons you want to appear. In this app, you ask the user if they want to Take a Picture” or “Choose a Photo.” You then send -showInView:  to present those choices to the user. Its delegate object will receive a message when the user taps one, so this method returns andwaits for that to happen.



The last line of code handles the situation where there is only one interface available (either the camera or the photo library picker is available, but not both). In this situation theres no point in asking the user, just start the oneinterface they can use. But before you get to that, add the code to respond to the action sheet.

A UIActionSheet delegate must adopt the UIActionSheetDelegate protocol. Add that to the
MSDetailViewController class definition in MSDetailViewController.h:

@interface  MSDetailViewController : UIViewController <UISplitViewControllerDelegate,
UIActionSheetDelegate>

Back in MSDetailViewController.m, add the only UIActionSheetDelegate method you’re interested in:

-(void)actionSheet:(UIActionSheet  *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
switch (buttonIndex)  {
case   0: // camera  button case   1: // photo  button
[self presentImagePickerUsingCamera:(buttonIndex==0)]; break;
}
}

When the user chooses one of the action sheet buttons, your delegate receives an
-actionSheet:clickedButtonAtIndex: message. The buttonIndex parameter tells you which button the user tapped. Use that to decide which interface to present.

To review, you’ve queried the UIImagePickerController to determine which interfaces, in the subset of interfaces you’d like to present, are available. If none, do nothing. If only one is available, present that interface immediately. Ifmore than one is available, ask the user which one they would like to use, wait for their answer, and present that. The next big task is to present the interface.

Presenting the Image Picker

Now add a -presentImagePickerUsingCamera: method to your class. Start by adding its method prototype to the private @interface  MSDetailViewController () section at the top of the MSDetailViewController.m file:

@interface  MSDetailViewController ()
@property  (strong, nonatomic) UIPopoverController *masterPopoverController;
-  (void)configureView;
-  (void)presentImagePickerUsingCamera:(BOOL)useCamera;
@end

Now add the -presentImagePickerUsingCamera: method to its @implementation:


-  (void)presentImagePickerUsingCamera:(BOOL)useCamera
{
UIImagePickerController *cameraUI  = [UIImagePickerController  new];



cameraUI.sourceType = ( useCamera   UIImagePickerControllerSourceTypeCamera
: UIImagePickerControllerSourceTypePhotoLibrary ); cameraUI.mediaTypes = @[(NSString*)kUTTypeImage];
cameraUI.delegate = self;
[self presentViewController:cameraUI animated:YES  completion:nil];
}

This method starts by creating a new UIImagePickerController object.

The sourceType property determines which interface the image picker will present. It should only be set to values that returned YES when sent to -isSourceTypeAvailable:. In this code, its set to eitherUIImagePickerControllerSourceTypeCamera or UIImagePickerControllerSourceTypePhotoLibrary, which you’ve already determined is available.

The mediaTypes property is an array of data types that your app is prepared to accept. Your choices are kUTTypeImage, kUTTypeMovie, or both. This property modifies the interface (camera or picker) so that only those image types are allowed. Setting only kUTTypeImage when presenting the camera interface limits the controls so the user can only take still images. If you included both types (kUTTypeImage and kUTTypeMovie), then the camera interface would allow the user to switch between still and movie capture as they please (assuming their device was capable of video capture).

Theres also one little problem with this code: the constants for kUTTypeImage and kUTTypeMovie arent defined by the standard Cocoa Touch framework. To pull these constants into this module, add this import statementat the very top of your source file: #import  <MobileCoreServices/UTCoreTypes.h>

The last two lines of -presentImagePickerUsingCamera: set your controller as the delegate for the picker and start its interface. The controller slides into view and waits for the user to take a picture, pick an image, or cancel theoperation. When one of those happens, your controller receives the appropriate delegate message. But to be the image picker delegate, your controller must adopt both the UIImagePickerControllerDelegate andUINavigationControllerDelegate protocols. Add those
to your MSDetailViewController class declaration now:
  
@interface  MSDetailViewController : UIViewController <UISplitViewControllerDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate, UIActionSheetDelegate>

 With the picker up and running, you’re now ready to deal with the image the user takes or picks.

Importing the Image

Ultimately, the user will take or choose a picture. This results in a -imagePickerController:didFini shPickingMediaWithInfo: message sent to your controller. This is the method where you’ll take the image the user took/selected andadd it to the MyWhatsit object. All of the information about what the user did is contained in a dictionary, passed to your method via the info parameter. Add this method to your MSDetailViewController.m file. The method startsout simply enough:

-  (void)imagePickerController:(UIImagePickerController  *)picker  didFinishPickingMediaWithInfo:(NSDictionary *)info
{
NSString   *mediaType  = info[UIImagePickerControllerMediaType]; if ([mediaType isEqualToString:(NSString*)kUTTypeImage])
{
UIImage *whatsitImage = info[UIImagePickerControllerEditedImage]; if (whatsitImage==nil)
whatsitImage = info[UIImagePickerControllerOriginalImage];

The first task is to get the media type of the data being returned by the image picker. You specified only one type (kUTTypeImage), so thats the only thing the picker should return, but its a good idea to check anyway. Once you’re sure you’re getting back a still image from the picker, the next step is to obtain the image object.

There are, potentially, two possible images: the original one and the edited one. If the user cropped, or performed any other in-camera editing, the one you want is the edited version. Start by requesting that one(UIImagePickerControllerEditedImage) from the info dictionary. If that value is nil, then the original (UIImagePickerControllerOriginalImage) is the only image being returned.

The next couple of lines consider the case where the user has taken a picture. When users take a picture, especially using the standard iOS camera interface, they expect their photo to appear in their camera roll. This isnt arequirement, and another app might act differently, but here you meet the users expectations by adding the picture they just took to their camera roll.

if (picker.sourceType==UIImagePickerControllerSourceTypeCamera) UIImageWriteToSavedPhotosAlbum(whatsitImage,nil,nil,nil);

You dont want to do this if the user picked an existing image from their photo library, which is why you first test to see if the interface was UIImagePickerControllerSourceTypeCamera.

Cropping and Resizing

Now that you have the image, what do you do with it? You could just set the MyWhatsit image property to the returned image object and return. While that would work, its a bit crude. First, modern iOS devices have high-resolution cameras that produce big images, consuming several megabytes of memory for each one. It wont take too many such pictures before your app will run out of memory and crash. Also, the images are rectangular, and both the details interface and the table view would look better using square images.

To solve both of these problems, you’ll want to scale down and crop the users image. Start by cropping the image with this code, which is the next part of your -imagePickerController:didFinish PickingMediaWithInfo:  method:

CGImageRef  coreGraphicsImage = whatsitImage.CGImage; CGFloat  height = CGImageGetHeight(coreGraphicsImage); CGFloat  width  = CGImageGetWidth(coreGraphicsImage); CGRect crop;
if (height>width)
{
crop.size.height = crop.size.width = width; crop.origin.x = 0;
crop.origin.y = floorf((height-width)/2);
else
{
crop.size.height = crop.size.width = height; crop.origin.y = 0;
crop.origin.x = floorf((width-height)/2);
}
CGImageRef  croppedImage  = CGImageCreateWithImageInRect(coreGraphicsImage,crop);

The first step is to get a Core Graphics image reference from the UIImage object. UIImage is a convenient and simple to use object that handles all kinds of convoluted image storage, conversion, and drawing details for you. Itdoes not, however, let you manipulate or modify the image in any significant wayTo do that, you need to “step down” into the lower-level Core Graphics frameworks, where the real image manipulation and drawing functionsreside. A CGImageRef is a reference (think of it like an object reference) that contains primitive image data.

The next step is to get the height and width (in pixels) of the image. Thats accomplished by calling the functions CGImageGetHeight()  and CGImageGetWidth().

Many of the methods of Cocoa Touch objects are actually  written  in C, not Objective-C. C is the procedural language that the object-oriented Objective-C language is built on top of. In Chapter 6 I spoke of writing programsentirely by defining structures and passing those structures to functions. This is exactly how you program using C and the framework  of
C functions called Core Foundation.

While C is not an object-oriented  language, you can still write object-oriented  programs; its just more work. In Core Foundation, a class is called a type and an object is a reference. Instead of sending messages to theobject, you call
a function and pass it a reference (typically as the first parameter). In other words, instead of writing myImage.height
to the get height of an image, you write CGImageGetHeight(myImageRef).

While most Core Foundation types will only work with Core Foundation functions, a few fundamental types are interchangeable with Objective-C objects. These include NSString/CFStringRef, NSNumber/CFNumberRef,NSArray/CFArrayRef, NSDictionary/CFDictionaryRef, NSURL/CFURLRef, and others. Any function or
Objective-C method that expects one will accept the other as-is. This is called the toll-free bridge, and you’ve already

used it in this app. The kUTTypeImage string is really a CFStringRef, not an NSString  object. But since the two are interchangeable, it was possible to pass the Core Foundation kUTTypeImage string value in the parameter thatexpected an NSString  object.

The if block decides if the image is horizontal (width>height) or vertical (height>width). Based on this, it sets up a CGRect that describes a square in the middle of the image. If horizontal, it makes the rectangle the height of theimage and insets the left and right edges. If vertical, the rectangle is the width of the image, and the top and bottom are trimmed.

The function after the if/else block does all of the work. The CGImageCreateWithImageInRect() function takes an existing Core Graphics image, picks out just the pixels in the rectangle, and copies them to a new Core Graphics image. The end result is a square Core Graphics image with just the middle section of the original image.

The next step is to turn the CGImageRef back into a UIImage object, so it can be stored in the
MyWhatsit object. At the same time, you’re going to scale it down so its not so big.

whatsitImage = [UIImage  imageWithCGImage:croppedImage scale:MAX(crop.size.height/512,1.0)
orientation:whatsitImage.imageOrientation];

The UIImage class method -imageWithCGImage:scale:orientation: creates a new UIImage object from an existing CGImageRef. At the same time, it can scale the image and change its orientation. The scale calculates a ratio betweenthe size of the original image and a 512-pixel one. This scales down the (probably) larger image size from the devices camera down to a 512x512 pixel image, which is
a manageable size. The MAX() macro is used to keep the ratio from dropping below 1.0  (1:1); this prevents an image thats already smaller than 512 pixels from being made larger.

The very last detail is to release the Core Graphics image reference created by the CGImageCreateWithImageInRect() function. This is memory management, and something that Objective-C usually takes care of for you. When using the Core Foundation functions, however, you’re responsible for doing this yourself. See Chapter 21 for more details.

CGImageRelease(croppedImage);

Winding Up

All of the hard part is over. The only thing left for this method to do is store the cropped and resized image in the MyWhatsit object and dismiss the image picker controller:

_detailItem.image = whatsitImage; self.imageView.image = whatsitImage; [_detailItem  postDidChangeNotification];
}

[self  dismissImagePicker];
}

The first line stores the new image in the new image property of the MyWhatsit object. The second updates the image view in the detail view, so it reflects the same change. Finally, you must remember to post a change notification so the master list knows to redraw this items row with the new image.

I’ve encapsulated the image picker dismissal code in a separate function. There are twplaces where the image picker needs to be dismissed: here, after a successful new imaghas been imported, and ifthe user taps the “cancel button, indicating that they dont
want to change the image after all. When the latter happens your controller will receive a -imagePickerControllerDidCancel: messageHandle that by adding the appropriatdelegate method:

-  (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
{
[self  dismissImagePicker];
}

This method does nothing but dismiss the controller, making no change to your MyWhatsit object.
The last piece of this puzzle is the method to dismiss the controller:

-  (void)dismissImagePicker
{
[self dismissViewControllerAnimated:YES completion:nil];
}

You’ll also want to add a prototype for the -dismissImagePicker method in the private @interface MSDetailViewController () section at the beginning of the file.

No comments:

Post a Comment