passing handles between a ViewController and a View - iphone

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.

Related

using string variable from one class in another

Rookie question: I am writing a program that will generate a specific string and then display it in a text window in a different view controller. I have been testing to ensure that the code in fact generates the string using NSLog commands and I know the code is working as intended. For some reason it is not transferring across the view controller and I cant figure out why. Any help? Here is a snippet of the code:
CreateStoryViewController.m
- (IBAction)makeStory:(id)sender
{
StoryLine *myStory =[[StoryLine alloc] init];
[myStory setStory];
self.story = myStory.plot;
NSLog(#"story is %#", self.story);//this is generating the correct story string
self.displayStoryController = [[BIDDisplayStoryViewController alloc] initWithNibName:#"DisplayStoryView" bundle:nil];
[self.view insertSubview:self.displayStoryController.view atIndex:1];
}
DisplayStoryViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
BIDCreateStoryViewController *newStory = [[BIDCreateStoryViewController alloc] init];
NSLog(#"newStory.story is %#",newStory.story);//this generates null message
self.storyDisplay.text = newStory.story;
}
This is wrong. You're instantiating a new BIDCreateViewController object inside your second view controller. This is not the same as the original BIDCreateViewController object that pushed your second BIDDisplayStoryViewController.
You need to declare a string property in your BIDDisplayStoryViewController's header file.
Something like
#property (nonatomic, retain /*or strong, if using ARC*/) NSString *storyToDisplay;
Be sure to synthesize this in your implementation file as well.
When you create BIDDisplayStoryViewController inside your first view controller, you need to do it as follows:
self.displayStoryController = [[BIDDisplayStoryViewController alloc] initWithNibName:#"DisplayStoryView" bundle:nil];
self.displayStoryViewController.storyToDisplay = self.story;
Now inside your second view controller you can access this using self.myStory.
While this will solve your problem (and please do understand that it's not my intention to be rude here), I feel that there's a lack of understanding of how iOS (and OOP in general) works.
In your viewDidLoad method you are making a whole new story. This story is totally different from the one you made in the makeStory: method. You should add a StoryLine Property to DisplayStoryViewController.h, and set that after you init your displayStoryController.
make the intended variable a property type at .h file, so the other file can access it

objective-c: Delegate object argument getting overwritten when i create multiple instances of custom class

EDIT: I apologize for wasting time, the erorr had nothing to do with what I'm taking about but rather some logic in my code that made me believe this was the cause. I'm awarding Kevin with the correct answer since using his idea to pass the whole AuthorSelectionView, and his note on correcting the NSNumer mistake. Sorry about that.
I've been trying to figure this out for hours, and even left it alone for a day, and still can not figure it out...
My situation is as follows:
I've created a custom class that implements 'UIView' and made this class into a protocol as follows:
custom UIView h file
#protocol AuthorSelectionViewDelegate <NSObject>
-(void)AuthorSelected:(NSNumber *)sender;
#end
#import <UIKit/UIKit.h>
#interface AuthorSelectionView : UIView
#property (nonatomic,assign) id<AuthorSelectionViewDelegate> delegate;
#property (strong,retain) NSNumber *authorID;
- (id)initWithFrame:(CGRect)frame withImage:(UIImage *)img withLabel:(NSString *)lbl withID:(int)authorID ;
#end
the implementation...
- (id)initWithFrame:(CGRect)frame withImage:(UIImage *)img withLabel:(NSString *)lbl withID:(int)authorID
{
self = [super initWithFrame:frame];
if (self) {
self.authorID = [[NSNumber alloc] initWithInt:authorID]; //used to distinguish multiple instances of this class in a view.
...
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, FRAMEWIDTH, FRAMEHEIGHT)];
[button addTarget:self action:#selector(CUSTOMBUTTONCLICK) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:button];
}
return self;
}
- (void) CUSTOMBUTTONCLICK
{
[self.delegate performSelector:#selector(AuthorSelected:) withObject:self.authorID];
}
Now the method in my delegate object gets called just fine, but my major problem here is that something is going on with the object being pass through when i have multiple instances of the AuthorSelected class alloc'd.. (the NSNumber authorID). I'm getting some weird behavior with it. It seems almost random with the value being passed, but i'm detecting some pattern where the value passed through is coming up late..
thats confusing so ill try to explain:
I create two instances of the AuthorSelected view, one with authorID=1 and the other with authorID=2.
On the first press, lets say i press the first button, i'll get 1 as expected.
On the second press, if I press the 1st custom button, i'll get '1', but if i press the second i'll still get 1.
On the third go, either button will give me back '2'
I feel like this is some issue with pointers since that has always been a weak point for me, but any help would be greatly appreciated as I can not seem to figure this one out.
Thank you!
EDIT:
as requested here is how I create the AuthorSelectionView Objects...
AuthorSelectionView * asView01 = [[AuthorSelectionView alloc]
initWithFrame:CGRectMake(0, 0, FRAMEWIDTH, FRAMEHEIGHT)
withImage:userPic1
withLabel:randomUserName
withID:1];
asView01.delegate = self;
AuthorSelectionView * asView02 = [[AuthorSelectionView alloc]
initWithFrame:CGRectMake(0, 0, FRAMEWIDTH, FRAMEHEIGHT)
withImage:userPic2
withLabel:randomUserName2
withID:2];
asView02.delegate = self;
A detail that may be important:
As soon as i click on one of these custom views, my code is set to (for now) call the method that runs the above AuthorSelectionView alloc code, so that i can refresh the screen with the same layout, but with different userpic/userName. This is poor design, I know, but for now I just want the basic features to work, and will then worry about redrawing. I metion this tidbit, becuase I understand that objective-c 'layers' veiws on top of eachother like paint on a canvas, and had a thought that maybe when I click what I think may be my 2nd button, its really 'clicking' the layer beneath and pulling incorrect info.
Your description of the problem is a bit confusing, but this line in your init is very clearly wrong:
self.authorID = [self.authorID initWithInt:authorID];
In -init, your property self.authorID defaults to nil, so the expression [self.authorID initWithInt:authorID] is equivalent to [nil initWithInt:authorID], which evaluates back to nil. So you should actually be seeing nil in your action. You probably meant to say self.authorID = [NSNumber numberWithInt:authorID]
You're missing the alloc message, so this message:
self.authorID = [self.authorID initWithInt:authorID];
Is sent to a nil target, because self.authorID hasn't been allocated yet.
So first allocate it, then use the init method, or mix these two messages. A faster syntax allows to do it this way:
self.authorID= #(authorID);
EDIT
I don't see where you initialize the delegate, that method shouldn't even be called if you haven't initialized it. Show the code where you create the AuthorSelectionView objects and set the delegates.
instead of :
self.authorID = [self.authorID initWithInt:authorID];
put :
self.authorID = [NSNumber numberWithInt:authorID];
or
self.authorID = [[NSNumber alloc] initWithInt:authorID];
EDIT :
Don't you have errors or warnings in your code ? I can't see you returning self object in the init method ("return self;")

UIPickerView on Separate Page

I am working on an iPhone app. Initially, I had my pickerview in the same screen so this was just a one page app. After skinning it i realized that i want the pickerview on it's own separate page. So i did that. However, my pickerview originally would update uilabels and other objects on that same page. How can I have my pickerview access those objects from it's new view?
- (IBAction)ShowPickerAction:(id)sender {
if (self.theView == nil) {
theView = [containerView initWithNibName:#"containerView" bundle:nil];
theView.parentView = self;
}
self.theView.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentModalViewController:self.theView animated:YES];
}
That is the method I am using to call my new view. But line 3 of the above code gives me the error "No known class name for selector initWithNibName:bundle". I think that error is related to something that i did wrong in my header file. My new class is called containerView. So i did this in my header:
#interface ViewController : UIViewController {
containerView *theView;
But that gives me the error "Unknown type name containerView" even though i do have a class named containerView!!
Look into uiappdelegate protocol or try passing values to through a static function to the previous page.
Use a delegate to pass information back and forth to the view object that instantiatrd the picker view. You want to keep your code coupling as loose as possible, especially if you might like to drop it into your next project. Using a delegate and/or blocks are some of the best ways.

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.

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

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.