How to change the UIImage of a UIImageView from a subview? - iphone

I want to change an image on a view, from a popup dialog of 4-6 icons (imagine like changing your image on a messenger application).
The way I implement this modal popup is by creating a new view at IB, with opacity on the background, and then I load this as a subview:
IconsViewController *iconsViewController = [[IconsViewController alloc] initWithNibName:#"IconsView" bundle:nil];
[self.view addSubview:iconsViewController.view];
So, when the user touches an icon, I have
- (IBAction)iconIsSelected:(id)sender {
switch ([sender tag]) {
case 1:
[(ParentViewController*)[self superview] changeIcon];
break;
case 2:
// same here..
break;
default:
break;
}
[self.view removeFromSuperview];
[self release];
}
The changeIcon just sets the image to a corresponding icon.
As you can guess, this is not working - the changeIcon message never works.
I can't understand what am I doing wrong, any help much appreciated!

You have a few choices here...
First one is create a property on your IconsViewController of type ParentViewController*, for example:
#property (readwrite,nonatomic,assign) ParentViewController* parentController; // weak reference
To break this down further:
readwrite because we want to be able to access the value via [self parentController] but also change it via [iconsViewController setParentController:self]
nonatomic because I'm not too worried about threading
assign to make it a "weak reference" where the parent will not be retained by the child. If they each retain the other, it could lead to memory leaks later because unless explicitly released you'd end up with a retain circle causing neither object to hit a zero retain count.
When you load from nib, set the property:
IconsViewController *iconsViewController = [[IconsViewController alloc] initWithNibName:#"IconsView" bundle:nil];
iconsViewController.parentController = self;
Then, call to it from inside of iconIsSelected like this:
[[self parentController] changeIcon];
Alternatively, you can create a delegate protocol:
#protocol IconViewSelectedDelegate (NSObject)
- (void) changeIcon;
#end
And use that protocol as a property, instead of the parent view controller type. This is more abstract, but it keeps the design cleaner. The parent view controller would then implement that delegate protocol, as one of many others.
Another option is to use NSNotificationCenter and publish/subscribe to events from your dynamic view. This is the "loosest" coupling between the two objects, but it might be overkill for this scenario.

The superview of a view is a view, not a view controller, yet you cast the superview to be of class ParentViewController. If the view has no superview, it returns nil, and message to nil are no-ops (which explains why you don't crash there).
BTW, that [self release] at the end is highly suspicious.

Related

why does my UIViewController get retained an extra time when using initWithNibName?

I'm working on an app that has a Main view that wants to spawn a child view when a button is touched. So when I receive the button event, the MainViewController spawns the child view by calling initWithNibName and storing the ChildViewController in an ivar. I then show the ChildView by attaching an animation and setting childVC.view.hidden = NO.
This works, but I noticed that the ChildViewController was never getting released after closing the ChildView. I realized that the ChildVC's retain count went from 1 to 2 when I first access the child view. So something in the nib loading guts appears to be retaining my ChildVC again (in addition to the initial retain I expect during object initialization).
Can somebody help me figure out why the ChildVC is getting retained the extra time, and how can I make sure that it gets fully released when I want to close the child view?
Edit: here's some code, only slightly simplified. These are methods on the parent view controller.
-(IBAction)onLaunchChildButtonTouched:(id)sender
{
m_childViewController = [[ChildViewController alloc] initWithNibName:#"ChildViewController" bundle:nil];
[m_childViewController setParentDelegate:self]; // this is a weak reference
// m_childViewController retain count here is 1, as expected
m_childViewController.view.hidden = YES;
// m_childViewController retain count is now 2, not expected
[self.view addSubview:m_childViewController.view];
[self addTransitionEntrDir:YES]; // code omitted
m_childViewController.view.hidden = NO;
}
-(void)onChildWantsToClose:(id)child
{
NSAssert( child == m_childViewController, #"unexpected childVC" );
// if child view is now hidden, we should remove it.
if( m_childViewController != nil && m_childViewController.view.hidden )
{
[m_childViewController.view removeFromSuperview];
[m_childViewController release]; m_childViewController = nil;
// BUG: m_childViewController retain count is still 1 here, so it never gets released
}
}
Without code it is difficult to say exactly, but are you sure you are not assigning your ChildVC to a retain property of some other object? This would explain the unexpected retain you see.
Sorry for the previous answer, where I tried to convey this same message but I mixed everything up.
OLD ANSWER:
keep in mind that the view property of a UIViewController is retained:
view
The view that the controller manages.
#property(nonatomic, retain) UIView *view
so, if you assign to it like this:
childVC.view = [[xxxxx alloc] initWithNibName:...];
this explains what you are seeing.
Use instead:
childVC.view = [[[xxxxx alloc] initWithNibName:...] autorelease];
I found the problem, the leaky ChildViewController was instantiating an object that retained a ref back to it.
The interesting part is that I wasn't simply forgetting to release this reference. I did have a call to release it, but that code was never running because it assumed that viewDidUnload would run and give me a chance to release everything, but it didn't. I put me deinit code inside dealloc instead, and it works now.

Removing a superview from a method call inside that class

I'm trying to switch between two views right now. The problem is how it is called.
Heres the easiest way to explain my situation:
I have a Parent View.
With a subclass ChildView, that contains a table.
Upon selecting an object in that table, I wish to switch to a different child view of that parent view.
Parent---------
|Child 1 |Child 2
Child 1 is a subclass of Parent to allow me to access a method in Parent that switches between Child Views 1 and 2, but for some reason it wont work when accessing it from Child 1.
Any clues on how to do this? Heres the basic code:
Child 1
- (void) changeViews
[super methodToSwitchChildViews];
Parent
- (void) methodToSwitchViews
[self.child1.view removeFromSuperView];
[self.view insertSubView:child2.view atindex:0];
Super is the class that precedes a (sub)class in inheritance. Here the children seem to be views on a superview (parent). So use superview, and not super.
Okay, I dug around quite a bit and finally figured out a solution. In case anybody ever has the same problem here's what you do:
In the .h file of the child view do
#class parentViewName
Then in the .m file add
#import "parentViewName.h"
...
- (void) functionToRemoveSelfFromView {
parentViewName *PARENT = [[parentViewName alloc] init];
// You must have a method in the parent view to toggle or remove the subview, the way
// you want it done, then call it with the new delegate. Make sure it doesn't set this
// view to nil or releases it because this method has yet to return. If animating do not
// hide this view either.
[PARENT methodToRemoveSelfFromView];
[PARENT release];
}

Accessing a class' instance variable (NSMutable Array) from another class

new to Obj C and programming in general - learned a lot from this site and really appreciate everyone's contributions.
My scenario is as follows (programming an iPhone game which explains the funny names)
In my main gameLoop (which is in my view controller) if a certain condition is met I create an enemy - the cherry bomb
if (bounceCounterGlobal % 2 == 0 && bounceCounterGlobal > 1 && cherryBombSwitch == 0){
[self addCherryBomb];
}
The addCherryBomb method is as follows:
-(void) addCherryBomb{
CherryBomb *myCherryBomb = [[CherryBomb alloc] init];
[cherryBombArray insertObject:myCherryBomb atIndex:0];
[myCherryBomb release];
[[cherryBombArray objectAtIndex:0] initializeCherryBomb];
[self.view addSubview:[[cherryBombArray objectAtIndex:0] cherryBombView]];
cherryBombSwitch = 1;
}
The CherryBomb header file is short:
#import <Foundation/Foundation.h>
#import "SimpleGameViewController.h"
#interface CherryBomb : NSObject {
UIImageView *cherryBombView;
NSTimer *cherryBombDetonateTimer;
NSTimer *cherryBombMoveTimer;
}
#property (nonatomic, retain) UIView *cherryBombView;
-(void) initializeCherryBomb;
-(void) detonateCherryBomb;
-(void) moveCherryBomb;
#end
What I would like to do is when the cherry bomb detonates (which is determined within the cherryBomb object), I would like the object to remove itself from the cherryBombArray which is an ivar of the view controller.
I tried calling a view controller class method to do this - but I am unable to access ivars of the view controller (because it is a class method). I do not know how to communicate back to the view controller class to tell it to remove the exploded object.
#implementation CherryBomb
...
-(void) detonateCherryBomb{
NSLog(#"KABOOM!");
cherryBombDetonateTimer = nil;
[cherryBombMoveTimer invalidate];
[cherryBombView removeFromSuperview];
//I would like to remove this object from the view controller's cherryBombArray
}
#end
Your help is greatly appreciated. Thank you in advance!
I recommend you to create some "Environment" object that will handle all the gaming logic.
The cherryBomb shouldn't deal with its explosion. The cherryBomb can store many information (size of the explosion, type of explosion, and so on), but the effects of the cherryBomb on the others "things" (characters, bombs, whatever) shouldn't be calculated by the cherryBomb itself.
I'm not very used to game programming, but this aspect of architecture/design is common: each object/class has its responsibilities.
The cherryBomb represents a bomb, no more (and not the "graphic" aspect either).
The Environnement represents a "world" at current instant, and modelizes actions/interactions between the elements of the world.
There is a lot to say about the best way to design a game...
Anyway, to give your question an answer, you can still use "events". The bomb can send a message to your controller telling him: "I've exploded, remove me".
In the bomb:
[[NSNotificationCenter defaultCenter] postNotificationName:#"kaBOOM"
object:self];
In the controller:
[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(methodToCallWhenKaBOOM)
name:#"kaBOOM"
object:nil];
And
- (void)methodToCallWhenKaBOOM:(NSNotification *)note
{
// do stuffs
}
http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSNotificationCenter_Class/Reference/Reference.html
There are a number of ways to do this, and you may want to think about the exact division of labour here, in terms of controllers and models. Things can get pretty spaghettified if you have too much calling back and forth.
However, without getting into all of that, the basic thing you need to do to allow one object to access another object is to give the first a reference to the second.
In this case, you're actually creating the CherryBomb in the view controller, so it's easy to just pass it a reference at that point. Give your CherryBomb class another ivar like this:
SimpleGameViewController* cherryBombViewController;
Modify CherryBomb so that either the init method or your initializeCherryBomb one (these probably should just be a single method, btw) takes such a pointer and assigns it to the ivar:
- (void) initializeCherryBomb:(SimpleGameViewController*)vc
{
// ... whatever other stuff you do in here, plus something like:
cherryBombViewController = vc;
}
When you call this, pass it self as the vc parameter. Then later on, when your bomb detonates it can invoke some method you add on the controller to remove itself:
[cherryBombViewController handleDetonationOfCherryBomb:self];
Note that you absolutely should not access the controller's array directly -- that's an implementation detail your bomb should have no knowledge of. You can get away with being a little sloppy in your control structures in simple cases, but never screw with your encapsulation.
Read up on the MVC design pattern. If you find ivars which you need to share among views, they should probably go in a higher level Model object (the M of MVC), instead of having some view's peaking into other view's ivars. A pointer to this Model object can then be passed down to all the view objects that need to access it.
I think this is a good application of key value observing. You need a property of the cherry bomb which represents its state e.g.
#property (assign) BOOL isExploded;
Any object that is interested in whether the cherry bomb has exploded registers itself for KVO on the isExploded property. For example the view controller might do:
[cherryBomb addObserver: self
forKeyPath: #"isExploded"
options: ....
context: ....];
and in -observeValueForKeyPath:ofObject:change:context: for the view controller remove the cherry bomb from the array.
Your detonate method does the following as well as everything else it is currently doing:
[self setExploded: YES];

objective-c delegates and events design (I don't get it)

I'm pretty new to the objective-c language (less than three months) but it is something that i really need to understand.
Suppose there is a controller (in a iOS environment) that manages a table view for input data from the user. The table must have editable cells and some features to make the value selection easier, for example a button that shows a popover with the possible values for a field.
Suppose there is a field to store country names. The popover first shows a list of continents; when the user selects a continent, the controller of the popover must show the countries of the previews selected continent.
Now, this popover appears in many places in the app so it will be nice if I can encapsulate it for later use. What i will expect for this popover is something like this:
...
#protocol MyPopoverDelegate<NSObject> {
-(void)didSelectCountry:(NSString *)countryName;
{
...
MyPopoverController *dataSelector = [[MyPopoverController] alloc] init];
dataSelector.dataType = CountryDataType;
dataSelector.delegate = self;
[dataSelector show];
[dataSelector release];
...
The problem here is the line [dataSelector release] because the code for managing the popover must stay alive until the country is selected. That's means the dataSelector variable must be a property of the caller class and that sucks.
The question then is:
How can i organize situations like this to have a reusable controller?
Thanks
Edited after vodkhang answer:
Ok, that's a good one, but dataSelector still is a property.
What if i do:
#implementation MyPopoverController
- (id)init {
...
[self retain];
...
}
- (void)popoverControllerDidDismissPopover: (UIPopoverController *)popoverController {
...
[delegate didFinishSelectingCountry:countryName];
[self release];
}
#end
I never see this behavior in objective-c, i feel that this is not the idea.
Why is it wrong?.
One of the way you can do for delegate method is to have:
MyPopOverDelegate
- (void)didFinishSelectingCountry:(NSString *)countryName popOver:(MyPopOver *)popOver;
Caller.m
// the caller
- (void)viewDidLoad {
MyPopoverController *dataSelector = [[MyPopoverController] alloc] init];
dataSelector.dataType = CountryDataType;
dataSelector.delegate = self;
[dataSelector show];
}
- (void)didFinishSelectingCountry:(NSString *)countryName popOver:(MyPopOver *)popOver {
// finish stuff
[popOver release];
}
This way is used a lot like NSUrlConnection, UIImagePickerController
If you want some unique object reusable across an entire app from anywhere in the view hierarchy, you can make it a property of the app delegate, and let the app delegate own it (retain it when live, release it during memory warnings, etc.).
A self retained object may eventually run into problems if you ever port your code to a garbage collected environment.

passing handles between a ViewController and a View

I have an array of booleans (soundArray) that is modified based on user input. Therefore it is owned by the main UIView that the user is interacting with. Meanwhile, in another separate controller (GestureController), I need access to this soundArray, so I can pass it as a parameter in a method call.
I'm pretty sure a ViewController shouldn't be digging out properties of a UIView it doesn't own (correct MVC), so I've created another pointer to the soundArray in the ViewController (MusicGridController) that owns the main UIView (MusicGridView).
In GestureController, I have the following code:
// instantiate the sound thread
NSArray *soundArrayPointers = [NSArray arrayWithObjects:self.musicGridViewController.soundArray, nil];
[NSThread detachNewThreadSelector:#selector(metronome:) toTarget:[MusicThread class] withObject:soundArrayPointers];
(eventually I'll have more soundArrays in that array).
Now, in MusicGridController, I need to set the pointer to the SoundArray in the UIView MusicGridView... so I did this:
- (void)viewDidLoad {
[super viewDidLoad];
self.view.frame = CGRectMake(20, 0, 480, 480);
MusicGridView *tempview = self.view; // this line gives me a warning that I am initializing from a distinct Objective-C type, which I don't understand.
self.soundArray = tempview.soundArray;
}
I tried self.soundArray = self.view.soundArray, but it threw an error, and I don't understand why. So my question is: Is this a correct implementation? How do I get from one viewController to a property of a given UIView?
I'm not sure I understand who the NSArray belongs to, or who it should belong to, but you can try utilizing the AppDelegate if it's supposed to be a global object (accessed by more than one view and/or controller).
Now, to solve your problem. Your implementation is not wrong, and the warning will go away if you add (MusicGridView *) before self.view. This is related to the error you got when you tried self.soundArray = self.view.soundArray, and the reason is simple: UIView doesn't have a soundArray property, and self.view is a pointer to a UIView. By casting self.view to a MusicGridView *, you get rid of the warning, and your code should work fine.