Passing data back and forth using AppDelegate

2019-04-17 03:54发布

问题:

To start I am building an app to learn the basics of Objective-C. If there is anything unclear please let me know and I will edit my question.

The app is supposed to have the next functionality. Open the camera preview when the app is executed. On the top there is a button to go to a TemplateController where the user can select an array of frames to select from a UICollectionView. User selects the Template and returns to the Camera Preview. User takes a picture and the picture with the frame selected is shown in the PreviewController. If the user doesn't like the frame and wants to switch it for another one. PreviewController has button on top to go to the TemplateController, select the frame and go back again to the PreviewController with the new frame.

I do not want to create an object for the frame everytime. I want the AppDelegate to hold that object. To keep it alive per say?(sorry, English is not my mother tongue).

I was thinking to use NSUserDefaults BUT I really want to do it using the AppDelegate. So at this point NSUserDefaults is not an option.

Now, I am using storyboards with a navigation controller. A screenshot is available here Right now when I pass from the TemplateController to my PreviewController my code looks like this:

Reaching TemplateController from MainController or PreviewController

- (IBAction)showFrameSelector:(id)sender 
{
    UIStoryboard *storyboard;
    storyboard = [UIStoryboard storyboardWithName:@"MainStoryboard_iPhone" bundle:nil];
    TemplateController *templateController = [storyboard instantiateViewControllerWithIdentifier:@"TemplateController"];
    templateController.frameDelegate = self;
    [self presentViewController:templateController animated:YES completion:nil];
}

Passing the data from TemplateController to its controller's destiny (Either MainController or PreviewController)

- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
    _selectedLabelStr = [self.frameImages[indexPath.section] objectAtIndex:indexPath.row];
    [self.collectionView deselectItemAtIndexPath:indexPath animated:NO];

    [self dismissViewControllerAnimated:YES completion:^{
    if ([self.frameDelegate respondsToSelector:@selector(templateControllerLoadFrame:)]) 
    {
            [self.frameDelegate performSelector:@selector(templateControllerLoadFrame:) withObject:self];
    }
    }];
}

This loads the selected frame in PreviewController

- (void)templateControllerLoadFrame:(TemplateController *)sender
{
    UIImage *tmp = [UIImage imageNamed:sender.selectedLabelStr];
    _frameImageView.image = tmp;      
}

My problem is, I don't have very clear what changes I have to do on the AppDelegate(it is untouched right now). What would be the best approach to accomplish this? Main issue is when Tamplate is chosen before taking the still image. If I select the frame after taking the picture then it displays.

回答1:

I am not certain that I understand your question. Stuffing an object into the app delegate solution may not be the best way forward. In fact I believe you ought to look at the delegation pattern that is used by Apple to communicate between view controllers. Please note that you appear to be doing half of the delegate pattern already. For example you make your PreviewController a frameDelegate of the TemplateController.

So I would think you'd have something like the following to transfer information from TemplateController back to the PreviewController. Note that I've included prepare for segue as that is a common pattern to push a data object forward (it will be called if you connect a segue from the PreviewController to the TemplateController and in your action method call performSegueWithIdentifier:@"SegueTitle"). Use of the "templateControllerDidFinish" delegation method is a common pattern used to push information back from TemplateController when it closes.

TemplateController.h

@class TemplateController;
@protocol TemplateControllerDelegate <NSObject>
-(void) templateControllerDidFinish :(TemplateController*)controller;
@end

@interface TemplateController : UIViewController 
@property (nonatomic, weak) id <TemplateControllerDelegate>delegate;
...
@end

TemplateController.m
 //! The internals for this method can also be called from wherever in your code you need to dismiss the TemplateController by copying the internal 
-(IBAction)doneButtonAction:(id)sender
{

    __weak TemplateController*weakSelf = self;
    [self dismissViewControllerAnimated:YES completion:^{
        [self.delegate templateControllerDidFinish:weakSelf];
    }];
}

PreviewController.h

#import "TemplateController.h"

@interface PreviewController<TemplateControllerDelegate>
...
@end

PreviewController.m

@implementation
...
-(void) templateControllerDidFinish :(TemplateController*)controller
{
    self.dataProperty = controller.someImportantData;
    ...
}

...

-(void)prepareForSegue:(UIStoryboardSegue*)segue sender:(id)sender
{
    if ( [[segue identifier]isEqualToString:@""] ) 
    {
         TemplateController *tc = [segue destinationViewController];
         tc.delegate = self;
         tc.data = [someDataObjectFromPreviewController];
    }
}

To fix this situation a bit more:

  1. Add a segue from the PreviewController to the TemplateController (Ctrl-drag from Preview view controller to the Template Controller in the document outline mode)
  2. Name the segue identifier in the identity inspector
  3. Change your code that presents the view controller from:

    • (IBAction)showFrameSelector:(id)sender { UIStoryboard *storyboard; storyboard = [UIStoryboard storyboardWithName:@"MainStoryboard_iPhone" bundle:nil]; TemplateController *templateController = [storyboard instantiateViewControllerWithIdentifier:@"TemplateController"]; templateController.frameDelegate = self; [self presentViewController:templateController animated:YES completion:nil]; }

to

- (IBAction)showFrameSelector:(id)sender 
{
    [self performSegueWithIdentifier:@"SegueTitle"];
}

Add your data object to the target view controller as noted in prepareForSegue and you will be in good shape. Then use the delegate method to catch any data returned from your template (just add the data as properties to the controller and you should be golden)

You can see a better example of this delegation in a utility project template from Xcode (I just keyed this in..) I hope this information helps. You can get more information at these resources and also by searching Google and SO for iOS delegation :

Concepts in Objective C (Delegates and Data Sources)

Cocoa Core Competencies