0

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 enter image description 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.

Edu
  • 25
  • 9
  • NSUserDefaults are used to store app specific settings (i.e., last updated time, user turned off syncing, ... (and do not store any sensitive information)). If you want to store the 'Object' inside the AppDelegate, you have to create a delegate using [protocol](https://developer.apple.com/library/mac/documentation/cocoa/conceptual/ProgrammingWithObjectiveC/WorkingwithProtocols/WorkingwithProtocols.html). – chuthan20 Nov 29 '13 at 04:05
  • @chuthan20 in this case AppDelegate would receive and send data. Do I need to create a delegate as sender and create another delegate in TemplateController as a sender as well? – Edu Nov 29 '13 at 04:10
  • An app delegate is just an object. You define it by adopting the UIApplicationDelegate protocol with your own class, then create an instance of your class in `main`. Once you've done that you can get the address of the app delegate from the UIApplication and access properties and methods in it. This is a good place to store globally-referenced data. (From this standpoint it's just another object, though one you can get the address of from anywhere in the app.) – Hot Licks Nov 29 '13 at 04:12
  • You should neither use `NSUserDefaults` nor the `AppDelegate` to pass data between view controllers. Have a look at [this answer](http://stackoverflow.com/a/9736559/1489997) on how to pass data between view controllers. – Sebastian Nov 29 '13 at 04:12
  • @Sebastian is correct. It is the most encouraged way and you should follow that if you want a good architecture. – chuthan20 Nov 29 '13 at 04:16
  • @HotLicks "[The app delegate] is a good place to store globally-referenced data." No, it's not. That's not what the app delegate is for. If you need global data, encapsulate it in your own global singleton. – Sebastian Nov 29 '13 at 04:18
  • @Sebastian then what is AppDelegate used for ? – Edu Nov 29 '13 at 04:27
  • @Edu To quote from [the docs](http://developer.apple.com/library/ios/documentation/uikit/reference/UIApplicationDelegate_Protocol/Reference/Reference.html): "One of the main jobs of the app delegate is to track the state transitions the app goes through while it is running." – Sebastian Nov 29 '13 at 04:35
  • @Edu I've tried to give an example of the delegation pattern you will see often. As a best practice Apple encourages developers to only initialize and hold data in memory when it is needed. Putting the data object in the App delegate would keep it active the entire length of the project and cause other issues as you need to constantly update the data stored in the object. Let me know if my answer is understandable and acceptable. If any questions please follow up below. – Tommie C. Nov 29 '13 at 04:42
  • @Sebastian - And why is a singleton better? – Hot Licks Nov 29 '13 at 12:25
  • @HotLicks It's simply not the app delegate's responsibility to store global data. I'm not saying that you should pass data between objects via global data, but it's one step better than (ab)using the app delegate. – Sebastian Nov 29 '13 at 22:55
  • And why is a singleton better? – Hot Licks Nov 29 '13 at 23:34
  • @HotLicks Because it is not the app delegate's responsibility to store that data. It's as simple as that. The app delegate is responsible to manage/react to changes in the app's state. If you want to store global data, create a new class and encapsulate that behaviour there. – Sebastian Dec 01 '13 at 02:52
  • So who made the rules about what an app delegate should and shouldn't do? – Hot Licks Dec 01 '13 at 03:51
  • @HotLicks The [single responsibility principle](http://en.wikipedia.org/wiki/Single_responsibility_principle) and the [separation of concerns principle](http://en.wikipedia.org/wiki/Separation_of_concerns) – Sebastian Dec 01 '13 at 20:16
  • Depends on how you define "responsibility", doesn't it? – Hot Licks Dec 01 '13 at 21:53
  • @HotLicks The Oxford Dictionary of English defines it as "the state or fact of having a duty to deal with something or of having control over someone". It's the duty of the app delegate to ["track the state transitions the app goes through"](http://ow.ly/rlcyo). Have a look at [Singletons, AppDelegates and top-level data.](http://www.cocoawithlove.com/2008/11/singletons-appdelegates-and-top-level.html) and [Don’t Abuse the App Delegate](http://www.hollance.com/2012/02/dont-abuse-the-app-delegate/) – Sebastian Dec 01 '13 at 22:32
  • So a parent should be responsible for changing nappies or feeding, but not both? – Hot Licks Dec 01 '13 at 23:19
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/42294/discussion-between-sebastian-and-hot-licks) – Sebastian Dec 01 '13 at 23:26
  • That last reference is pretty amusing. – Hot Licks Dec 02 '13 at 00:02
  • @Sebastian Singleton was the solution. Thanks! – Edu Dec 02 '13 at 12:54

1 Answers1

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

Tommie C.
  • 12,895
  • 5
  • 82
  • 100
  • What you did is how my code looks right now. Very similar. Now, when the user enters the TemplateController, selects a frame then goes back to the main controller, snaps the picture and when I try to pass the value to the PreviewController is gone. So I want to keep it alive and just make a reference in the AppDelegate so it can be accessed by other controllers as well. – Edu Nov 29 '13 at 04:53
  • @Edu I think I understand now. Before we break the correct pattern can I suggest we find out how to keep a reference to the PreviewController? Check my code again and you will see it implies a segue connecting the two view controllers. Your image shows that you do not have the segue in place. Doing so will fix the current issue without causing a bigger problem. I would prefer to not pass along a bad practice if we can help it. – Tommie C. Nov 29 '13 at 04:58
  • I updated part of my code. If you want to see how the .h files are organized I can edit. I am not using segue with the TemplateController, something was wrong. While debugging everything was on its correct place but frame was not displaying so to reach TemplateController I am using these lines(that solved the problem) storyboard = [UIStoryboard storyboardWithName:@"MainStoryboard_iPhone" bundle:nil]; TemplateController *templateController = [storyboard instantiateViewControllerWithIdentifier:@"TemplateController"]; – Edu Nov 29 '13 at 05:15
  • @Edu I thought as much and have edited my answer at the same time. Please give it a read through and see if it fixes your issues completely. If it does don't forget to accept. If not let me know and I will clarify further. Welcome to objective-c :-) – Tommie C. Nov 29 '13 at 05:19
  • With the current code you should get the entire picture. Thank you, any idea will be very much appreciated :) – Edu Nov 29 '13 at 05:26
  • @Edu The key is to add a manual segue to the controllers, just ctrl-drag between the two orange view controller icons to make the manual segue. Name the segue. Repost your picture so we can confirm and then comment out your IBAction method and use what I've provided above. You should see proper behavior and using the code above you should have a good reference to both controllers and any data you want to assign. – Tommie C. Nov 29 '13 at 05:30
  • I added the segue and res-posted the storyboard. – Edu Nov 29 '13 at 05:39
  • So now you will need to actually implement the protocol code and the delegation method that I discussed in my answer. Also name both of your segues with a string identifier and change your action method as indicated. Just comment your existing method that is creating the storyboard etc. and use the performSegueWithIdentifier.. – Tommie C. Nov 29 '13 at 05:44
  • I get "instance method performSegueWithIdentifier not found" fixed with [self performSegueWithIdentifier:@"pushToFrameController" sender:self]; – Edu Nov 29 '13 at 05:47
  • The complete method name is performSegueWithIdentifier:sender: you can type [self performSegueWithIdentifier:@"TheNameYouAssignedInTheStoryBoard" sender:sender]; into your IBAction method and it should be good.. – Tommie C. Nov 29 '13 at 05:51
  • You may also want to review this article http://www.appcoda.com/storyboards-ios-tutorial-pass-data-between-view-controller-with-segue/ – Tommie C. Nov 29 '13 at 05:56
  • There is no done button in the TemplateController. The user taps on the UIImage and then it calls the -(void)collectionView method. – Edu Nov 29 '13 at 06:14
  • @Edu Added a comment to the code that explains. Normally I'd move to chat but your reputation is low. You should consider accepting more answers to your earlier questions (in case you forgot) and removing the question with negative score. This will improve your reputation and allow more privileges (including extended chat). Good luck, if really stuck, search for answers or if necessary ask new (different) question. You are very close to where you need to be. Also search youtube, google, etc for more examples of objc delegation. – Tommie C. Nov 29 '13 at 12:03
  • changed the code to what you told me. Then I created a Singleton in my project and then it worked. Thank you for the advise. – Edu Dec 03 '13 at 01:30