objective-c Novice - Needs help with changing outlets in different classes - iphone

My main class MMAppViewController has an "IBOutlet UIImageView *padlock". This controller pushes a Level1View view which is my quiz game. The MMAppViewContoller has 2 buttons level 1 and level 2. Level 2 has the padlock on it and will unlock when a certain score is reached. When the MMAppViewController is pushed back, is there a way to hide the padlock. I know the following code will do this but my problem lies in where to put the code:
if(theScore>4){
[padlock setHidden:TRUE];
}
With my Level1View i can put code in the "viewdidload()" section, but it does not work with my main view because it only seems to load once! I tried puting the code in my Level1View class but keep getting errors about tokens or it being undeclared:
[MMAppViewController padlock setHidden:TRUE];
or
[padlock setHidden:TRUE];
Is there a way of either putting this code in my Level1View class, or is there a way of having the code in my MMAppViewContoller class that will work when Level1View is "unpushed"?? (not sure of terminology)

Not knowing more about the structure of your program it's hard to know the right way to achieve this.
There are several possible approaches, but viewDidLoad is only going to be called once and should be used for setting up the view initially, and not for this sort of repeated logic. You probably have a model object somewhere that holds the score. (If you don't, i.e. if theScore is an instance variable on your ViewController, as your snippets might imply, you should move it to it's own model object.) The best way to go about this would be for your ViewController to "observe" the model object that holds the score using Key-Value Observing. Here's how you might achieve that:
Let's say you have the following model object to hold your game session data (here, only the current score):
#interface GameSession : NSObject
#property (readwrite) double score;
#end
... and its corresponding implementation ...
#implementation GameSession
#synthesize score;
#end
And then assuming you have a ViewController declaration that looks something like this:
#class GameSession;
#interface MyViewController : UIViewController
{
GameSession *game;
IBOutlet UIImageView *padlock;
}
#end
You could set up the following methods on the ViewController, such that every time the score value of the model object is modified, the ViewController will automatically update the hidden state of the padlock image view:
- (void)viewDidLoad
{
[super viewDidLoad];
game = [[GameSession alloc] init];
[game addObserver:self forKeyPath:#"score" options:NSKeyValueObservingOptionInitial context: [RootViewController class]];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (context == [RootViewController class])
{
if ([keyPath isEqualToString: #"score"])
{
NSNumber* newValue = [change objectForKey: NSKeyValueChangeNewKey];
double currentScore = [newValue doubleValue];
[padlock setHidden: (currentScore < 4.)];
}
}
else
{
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
- (void)dealloc
{
[game removeObserver:self forKeyPath:#"score"];
[game release];
game = nil;
[super dealloc];
}
For a full explanation of Key-Value Observing, see this web page: http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/KeyValueObserving/
Let me know if this isn't clear.

The simple option is to put the code in viewWillAppear:.

Related

Move a value between UITabBarController /UIViewControllers

I have a project i'm working on which involves 3 tabs in a UITabBarController (all done in a storyboard).
Each tab is running off a different view controller.
I have a button on tab 1 that performs a calculation and returns a result in a text box. I want it so that when I hit calculate, the result is also returned in a text box in tab 2.
I'm not really sure how to pass data between UIViewControllers so any help is appreciated.
as per vshall says you can do this stuff like bellow:-
yourAppdelegate.h
#interface yourAppdelegate : UIResponder <UIApplicationDelegate,UITabBarControllerDelegate>
{
NSString *myCalResult;
}
#property (nonatomic,retain) NSString *myCalResult;
yourAppdelegate.m
#implementation yourAppdelegate
#synthesize myCalResult,
yourCalclass.h
#import "yourAppdelegate.h"
#interface yourCalclass : UIViewController
{
yourAppdelegate *objAppdelegate;
}
yourCalclass.m
- (void)viewDidLoad
{
objAppdelegate = (yourAppdelegate *) [[UIApplication sharedApplication]delegate];
[super viewDidLoad];
}
-(IBAction)ActionTotal
{
objAppdelegate.myCalResult=result;
}
Now you result stored in objAppdelegate.myCalResult you can use this variable in your another tab with creating object of yourAppdelegat. Hope its helps you
You can define a variable in app delegate and store the result in that variable for class one. And once you switch the class you can fetch that value in your class two by creating an instance of your appDelegate and assign it to your textfield.
As Sanjit has suggested, NSUserDefaults is also a very convenient and clean way to achieve this.
Thanks.
If you don't really need to store the computed value but just notify the other controller in tab2 that the value changed, you can use NSNotificationCenter to post an NSNotification.
When you initialize the controller in tab2 you'll need to add it as an observer of the notification.
Something like that:
in tab1:
NSNumber *value = nil; // the computed value
[[NSNotificationCenter defaultCenter]
postNotificationName:#"com.company.app:ValueChangedNotification"
object:self
userInfo:#{#"value" : value}];
in tab2: register as an observer (in init or viewDidLoad methods)
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(valueChanged:)
name:#"com.company.app:ValueChangedNotification"
object:nil];
the method that will be called when the notification is posted:
- (void)valueChanged:(NSNotification *)note
{
NSDictionary *userInfo = note.userInfo;
NSNumber *value = userInfo[#"value"];
// do something with value
}
Don't forget to remove the controller from the observers in viewDidUnload or sooner:
[[NSNotificationCenter defaultCenter] removeObserver:self];

it possible to Pass Data with popViewControllerAnimated?

I came across an interesting problem, i have main ViewController let's call him MainVC with navigationController and i am doing performSegueWithIdentifier from him to Mine second ViewController let's call him SecVC. so when i am trying to do the popViewControllerAnimated i want to pass some data from the SecVC to the MainVc.. i know i can do it with appDelegate Param or with singletons class but my question is : can i do it with more Elegant solution? like i use prepareForSegue and use local parmeters..
Thank you...
The best way to do it is by using a delegate.
//SecVCDelegate.h
#import <Foundation/Foundation.h>
#protocol SecVSDelegate <NSObject>
#optional
- (void)secVCDidDismisWithData:(NSObject*)data;
#end
//SecVC.h
#import <UIKit/UIKit.h>
#import "SecVSDelegate.h"
#interface SecVC : UIViewController
/** Returns the delegate */
#property (nonatomic, assign) id<SecVSDelegate> delegate;
#end
//SecVC.M
...
- (void) dealloc
{
...
delegate = nil
...
}
When ever you popViewControllerAnimated, right after it (or before it) you do this
if(_delegate && [_delegate respondsToSelector:#selector(secVCDidDismisWithData:)])
{
[_delegate secVCDidDismisWithData:myDataObject];
}
And in the MainVC you must be certain that you implement the delegate function
//MainVC.m
- (void)secVCDidDismisWithData
{
//do whatever you want with the data
}
To avoid any warnings you must tell that the MainVC class implements the delegate like this:
//MainVC.h
#import "SecVCDelegate.h"
...
#interface MainVC : UIViewController <SecVCDelegate>
...
secVCInstance.delegate = self;
[self.navigationController pushViewController:secVCInstance];
...
While I agree that the best option is to use Delegate,
but still if any one is looking for something different, he can use NSNotificationCenter as an alternative.
In viewDidLoad of MainVC:
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(recvData:)
name:#"SecVCPopped"
object:nil];
}
And add method recvData in MainVC.m
- (void) recvData:(NSNotification *) notification
{
NSDictionary* userInfo = notification.userInfo;
int messageTotal = [[userInfo objectForKey:#"total"] intValue];
NSLog (#"Successfully received data from notification! %i", messageTotal);
}
Now in SecVC, before popping, add this line
NSMutableDictionary* userInfo = [NSMutableDictionary dictionary];
[userInfo setObject:[NSNumber numberWithInt:messageTotal] forKey:#"total"];
NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
[nc postNotificationName:#"SecVCPopped" object:self userInfo:userInfo];
I would do it in one of the following ways, but I'm not sure if it's elegant enough...
In SecVC, add an #property MainVC *mainVC; Use [self.mainVC setSomeValue:...]; before calling [self.navigationController popViewControllerAnimated:...];
Use [self.navigationController viewControllers]; to find out the MainVC *mainVC, and call [mainVC setSomeValue:...]; before the line of code that pop the ViewController.
Is this what you want?
I simply set up a protocol in the view being dismissed (example in Swift):
protocol ExampleTableViewControllerDismissDelegate {
func didDismiss(withData: Any)
}
var delegate: SearchableTableViewControllerDismissDelegate?
You can then call this when you dismiss/pop your view like this
self.navigationController?.popViewController(animated: true)
delegate?.didDismiss(withData: Any)
Then in the view being dismissed to (the parent in the hierarchy), we can conform to the delegate and essentially get a callback with the data after the view has been dismissed.
//MARK: ExampleTableViewControllerDismissDelegate
func didDismiss(withData: Any) {
//do some funky stuff
}
And don't forget to subscribe to the delegate in the parent view by using
viewController.delegate = self
There is another way to pass data between views including popViewControllerAnimated and it's with a global var instance, so If you modify that Var in your detail view and after do the popViewControllerAnimated, you can call the new data in the viewWillAppear method.
The first step is declare the Global var in main.h
NSMutableArray * layerList;
And now you have to call it in detail view.
SecondView.m
extern NSString *layerList;
SecondView.h
-(void)back{
layerList = #"Value to send";
[self.navigationController popViewControllerAnimated:YES];
}
Now you can use the information in the Master View after detect the pop action.
FirstView.m
extern NSString *layerList;
FirstView.h
-(void)viewWillAppear:(BOOL)animated{
NSLog(#"This is what I received: %#",layerList);
}

Sharing UIImageView between View Controllers in TabBar

I have a UITabBarController based app. I've made a masterVC Class, all ViewControllers that make up the tabBarController are subclasses of the masterVC. I want to set an UIImageView (which is a property of masterVC) that once set shows that image on each view within the tabBarController.
If I was instantiating each VC I could pass the image as a property (this would be simple). However, there's no method to do this between tabBarController ViewControllers.
The easiest conceptual example of what I'm trying to do is by this example in each view Controller within the tabBarController. There has to be a better way:
-(void)viewDidAppear:(BOOL)animated
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *data = [defaults objectForKey:kMainImageData];
if (data) {
self.mainImageView.image = [NSKeyedUnarchiver unarchiveObjectWithData:data];
}
}
-(void)viewWillDisappear:(BOOL)animated
{
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:self.mainImageView.image];
[[NSUserDefaults standardUserDefaults] setObject:data forKey:kMainImageData];
}
Well there is, create a proper data source, where each UIViewController can request data. There are some ways:
Save the image to disk, use a helper class to retrieve it.
Save the image as property of a Singleton and retrieve it.
You could overload the method
-(id) initWithCoder:(NSCoder*)aDecoder
With something like this:
self = [super initWithCoder:aDecoder];
if ( self )
{
static UIImage *reuseImage = nil;
if ( reuseImage == nil )
{
//Init reuseImage with your image
}
myImageViewProperty.image = reuseImage;
...
return self;
If I'm understanding you correctly, I think you can do this all in your superclass with key-value observing.
In your masterVC header, declare:
#property (strong) UIImage *sharedImage;
And in the implementation:
#synthesize sharedImage;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
[self addObserver:self forKeyPath:#"sharedImage" options:NSKeyValueObservingOptionNew context:NULL];
}
return self;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqualToString:#"sharedImage"]) {
self.mainImageView.image = self.sharedImage;
}
}
- (void)dealloc {
[self removeObserver:self forKeyPath:#"sharedImage"];
}
Then, whenever the sharedImage property is set on any of the subclasses, your image view will update its contents accordingly.
I think the easiest thing would be to move the viewDidAppear method you posted into your masterVC class. Then in your ViewControllers that subclass masterVC you can:
Remove the viewDidAppear method if you have no other setup to do, it will then call the viewDidAppear that is in your masterVC.
OR
Change viewDidAppear to the following so that it will call viewDidAppear in masterVC then do all your other setup.
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
// your other code...
}

call this -(void) from another .m file

How do I call this -(void){} (function, method? sorry, I forgot the terminology) from another .m file?
I would also like to be able to call it in the local .m file like such [self closeMenu];
Here's the -(void){} :
-(void)closeMenu{
//close the menu
[UIView animateWithDuration:0.6 animations:^{
[UIView setAnimationBeginsFromCurrentState:YES]; // so it doesn't cut randomly, begins from where it is
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[menuView setFrame:CGRectMake(menuView.frame.origin.x, -menuView.frame.size.height, menuView.frame.size.width, menuView.frame.size.height)];
}];
}
You have to declare the method in a .h file that has the interface.
In YourClass.h
#interface YourClass:NSObject
- (void)closeMenu;
#end
In YourClass.m
#implementation YourClass
- (void)closeMenu
{
//Close the menu
}
#end
Then you have to import (#import "YourClass.h") in the other file that you want to call this method from.
#import "YourClass.h"
#implementation OtherClass
- (void)otherMethod
{
YourClass *foo = [[YourClass alloc] init];
[foo closeMenu];
}
#end
Seems like you're talking about some kind of a controller method to collapse some kind of a menuView and looks like this is a global menuView that you would like to share with the rest of your App.
Whenever I have "shared" widgets I use a manager for them.
Can you describe the kind of architecture that you're talking about?
Because the dirtiest kind of "Apple Sample code" approach that pops from my mind is to keep a reference to this baby in the App delegate and from anywhere in your App you could do this:
MyDelegate * delegate= (MyDelegate)[UIApplication sharedApplication] delegate];
[delegate.myCoolMenuController closeMenu];
Hope this works for ya.
Cheers

"Swapping" a UIView Instance variable - cannot dealloc "previous" view

I want to organize somehow my iPhone game's level-views, but I simply cannot (without expanding Object Allocations). I made a really "skeleton" of my code (this game has 2 levels, the goal is to release the iPhone display). I just cannot dealloc the previous level, so Instrunments shows incrementing BGTangramLevel instances.
Please, take a look on it, I need some helpful ideas on designing (my 3rd question on it).
viewcontroller.h
#interface compactTangramViewController : UIViewController
{
//The level.
BGTangramLevel *currentLevel;
UIColor *levelColor;
}
//It is to be just a reference, therefore I use assign here.
#property (nonatomic, retain) BGTangramLevel *currentLevel;
-(void) notificationHandler: (NSNotification*) notification;
-(void) finishedCurrentLevel;
#end
viewcontroller.m
#implementation compactTangramViewController
#synthesize currentLevel;
//Initializer functions, setting up view hierarchy.
-(void) viewDidLoad
{
//Set up levelstepper.
levelColor = [UIColor greenColor];
//Set up "state" classes.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(notificationHandler:) name:#"finishedCurrentLevel" object:nil];
//Attach level 1.
currentLevel = [BGTangramLevel levelWithColor: levelColor frame:self.view.frame];
[self.view addSubview:currentLevel];
[super viewDidLoad];
}
//Release objects.
-(void) dealloc
{
[currentLevel release];
[super dealloc];
}
//Notification handling.
-(void) notificationHandler: (NSNotification*) notification
{
//Execute level swap.
if ([notification name] == #"finishedCurrentLevel") [self finishedCurrentLevel];
}
-(void) finishedCurrentLevel
{
//Remove previous level.
[currentLevel removeFromSuperview];
//[currentLevel release];
//Step level.
if (levelColor == [UIColor greenColor]) levelColor = [UIColor blueColor]; else levelColor = [UIColor greenColor];
//Attach level 2.
currentLevel = [BGTangramLevel levelWithColor: levelColor frame:self.view.frame];
[self.view addSubview:currentLevel];
}
#end
BGTangramLevel.h
#interface BGTangramLevel : UIView
{
BOOL puzzleCompleted;
}
//Initializer.
+(BGTangramLevel*)levelWithColor: (UIColor*) color frame: (CGRect) frame;
//Test if the puzzle is completed.
-(void) isSolved;
#end
BGTangramLevel.m
#implementation BGTangramLevel
//Allocated instance.
+(BGTangramLevel*)levelWithColor: (UIColor*) color frame: (CGRect) frame
{
BGTangramLevel *allocatedLevel = [[BGTangramLevel alloc] initWithFrame:frame];
allocatedLevel.backgroundColor = color;
return allocatedLevel;
}
//Finger released.
-(void) touchesEnded: (NSSet*)touches withEvent: (UIEvent*)event
{
//The completement condition is a simple released tap for now...
puzzleCompleted = YES;
[self isSolved];
}
//Test if the puzzle is completed.
-(void) isSolved
{
//"Notify" viewController if puzzle has solved.
if (puzzleCompleted) [[NSNotificationCenter defaultCenter] postNotificationName:#"finishedCurrentLevel" object:nil];
}
-(void) dealloc
{
NSLog(#"Will ever level dealloc invoked."); //It is not.
[super dealloc];
}
#end
So what should I do? I tried to mark autorelease the returning level instance, release currentLevel after removeFromSuperview, tried currentLevel property synthesized in (nonatomic, assign) way, but Object Allocations still grow. May I avoid Notifications? I'm stuck.
You need to follow retain/release rules more closely. You definitely should not experimentally add retain and release and autorelease in places just to find something that works. There's plenty written about Cocoa memory management already, I won't repeat it here.
Specifically, BGTangramLevel's levelWithColor:frame: method should be calling [allocatedLevel autorelease] before returning allocatedLevel to its caller. It doesn't own the object, it's up to the caller to retain it.
You also need to know the difference between accessing an instance variable and accessing a property. Cocoa's properties are just syntactic-sugar for getter and setter methods. When you reference currentLevel in your view controller you are dealing with the instance variable directly. When you reference self.currentLevel you are dealing with the property.
Even though you've declared a property, currentLevel = [BGTangram ...] simply copies a reference into the variable. In viewDidLoad, you need to use self.currentLevel = [BGTangram ...] if you want to go through the property's setter method, which will retain the object (because you declared the property that way). See the difference?
I think your leak is happening in finishedCurrentLevel. If you had used self.currentLevel = [BGTangram ...], the property's setter method would be called, which would release the old object and retain the new one. Because you assign to the instance variable directly, you simply overwrite the reference to the old level without releasing it.
Calling [currentLevel release] in the dealloc method of your view controller is correct.