Making an UILabel appear in another view once pressed in one view - objective-c

I have 2 views
SoundViewController
ShowViewController
The sound view has a sound on it (IBAction).
- (IBAction)oneSound:(id)sender; {
if (oneAudio && oneAudio.playing) {
[oneAudio stop];
[oneAudio release];
oneAudio = nil;
return;
}
NSString *path = [[NSBundle mainBundle] pathForResource:#"1k" ofType:#"mp3"];
if (oneAudio) [oneAudio release];
NSError *error = nil;
oneAudio = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:&error];
if (error)
NSLog(#"%#",[error localizedDescription]);
oneAudio.delegate = self;
[oneAudio play];
mainText.text =#"test";
}
And the ShowViewController needs to display the uilabel thats been pressed from the sound button
I want it so once the user has pressed the sound on SoundViewController, the uilabel appear on the showviewcontroller as it appear on the soundviewcontroller at the moment

Well, you can do this by
retain the UILabel
remove it from it's superview
add it to the other view
release it
You'll need access to the ShowViewController from the SoundViewController. So you'll have to define a connection between the two views (via IBOutlet or retained property, most likely).
I'm not sure what variable in the above code is your UILabel, so replace 'sender' with the correct ivar (mainLabel, maybe?):
[sender retain];
[sender removeFromSuperview];
[showViewController.view addSubview:sender];
[sender release];
Edit:
To clarify, the variable in the above code "sender" is the object that triggered this method. Whatever you have connected to the IBAction in the nib. In this case it would probably be a UIButton. You probably have to add an IBOutlet for your UILabel, and attach it to the correct UILabel in your nib file and use that IBOutlet in place of "sender".
You should probably read up on view hierarchy and view controllers. What you're trying to do is remarkably easy, and there are about a dozen ways to make it happen, but you have to understand how to structure your app correctly first. The most obvious issue is that the two view controllers need to have a reference to each-other in order to pass views back and forth. I can't send a view to another view if I don't know where that other view is. The views can be connected in IB, in code when they are created, etc.
view is a property of UIViewController. Assuming your ShowViewController is a subclass of UIViewController, it will have a view property. Perhaps your showViewController ivar isn't correctly typed? (if the type is id for example, it will give a warning when you try to access it's view property).

Related

Split View Popover Delegate Method Not Called

EDIT 2: I tried pointing to the view controller using the following code:
UIStoryboard *iPadStoryboard = [UIStoryboard storyboardWithName:#"iPadStoryboard" bundle: nil];
GTGiftsIPadViewController *giftsIVC = (GTGiftsIPadViewController *)[iPadStoryboard instantiateViewControllerWithIdentifier: #"giftsTableViewControllerId"];
[giftsIVC setGiftsDelegate:self];
NSLog(#"%#", giftsIVC);
NSLog(#"%#", [[[[[[self parentViewController] parentViewController] childViewControllers] objectAtIndex:0] childViewControllers] objectAtIndex:0]);
However, this outputs:
2012-06-02 14:28:47.148 Gift Manager[3958:707] <GTGiftsIPadViewController: 0xc6b9980>
2012-06-02 14:28:47.152 Gift Manager[3958:707] <GTGiftsIPadViewController: 0xc6a16d0>
Meaning that giftsIVC is not pointing to the proper view controller. It has to point to the view controller given by [[[[[[self parentViewController] parentViewController] childViewControllers] objectAtIndex:0] childViewControllers] objectAtIndex:0]. However, I cannot use this code because it is does not work for both the portrait and landscape view.
This seems like something that should be very straight forward to do, but I'm pulling my hair out trying to figure this out! It's driving me crazy!
EDIT: I figured out the problem and it has to do with how I'm setting the delegate. This is my (poor) way of setting the delegate:
[[[[[[[self parentViewController] parentViewController] childViewControllers] objectAtIndex:0] childViewControllers] objectAtIndex:0] setGiftsDelegate:self];
Basically [[[[[[[self parentViewController] parentViewController] childViewControllers] objectAtIndex:0] childViewControllers] objectAtIndex:0] is just referencing my table view controller. Is there an alternative way I could write this bit of code to exclude the [[[[[[[self parentViewController] parentViewController] childViewControllers] objectAtIndex:0] childViewControllers] objectAtIndex:0] portion?
I have been trying to avoid creating my own delegate protocols, mainly because I'm not very comfortable with the idea of a delegate yet. Anyway, I went ahead and created one yesterday because I really needed it, and to it worked very well! I have a split view controller that displays two controllers when the iPad is in landscape mode (one table view and a detail view for the objects in the table view). The purpose of the delegate protocol is to allow the table view to update the detail view when an object in the table view is selected. This works in landscape mode; however, in portrait mode I only have one view controller and a button to display a popover that presents the table view (this is almost identical to the Notes app on the iPad).
My problem is that when in portrait mode and a object in the popover view table is selected the didSelectGift method is not called; however, in landscape mode it is. See capitalized comments below.
This is my code for the delegate protocol:
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
#import "GTAddIPadViewController.h"
#class GTGift, GTGiftCell;
#protocol GTGiftsIPadViewControllerDelegate;
#interface GTGiftsIPadViewController : UITableViewController
#property (nonatomic, assign) id <GTGiftsIPadViewControllerDelegate>giftsDelegate;
#property (nonatomic, strong) IBOutlet UITableView *giftsTable;
#end
#protocol GTGiftsIPadViewControllerDelegate <NSObject>
- (void)didSelectGift:(GTGift *)selectedGift;
#end
didSelectGift is called in the GTGiftsIPadViewController
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
GTGift *gift = [[[GTGiftStore sharedStore] allGifts] objectAtIndex:[indexPath row]];
[giftsDelegate didSelectGift:gift]; //THIS FUNCTION IS NOT CALLED (TESTED BY PUTTING AN NSLOG INSIDE IT)
NSLog(#"%#", [gift name]); //THIS OUTPUTS PROPERLY THEREFORE TABLEVIEW:DIDSELECTROWATINDEXPATH: IS CALLED
}
The delegate is set the viewDidLoad method in a view controller called GTDetailIPadViewController
The didSelectGift method is implemented in the GTDetailIPadViewController:
- (void)didSelectGift:(GTGift *)selectedGift
{
[self setDetailGift:selectedGift];
[self populateTable];
}
(populateTable is a method that uses the instance variable of detailGift and populates the detail view with these values)
I really cannot understand why it works perfectly when the table is presented in the split view but does not work when it is presented in the popover view. Any help would be really appreciated and I hope I have made my problem clear! Thank you!
So, this code:
[[[[[[self parentViewController] parentViewController] childViewControllers] objectAtIndex:0] childViewControllers] objectAtIndex:0]
to reference to your table view controller... that's a bit crazy :-)
Also, I don't think delegate is the way to go here..
If you created a standard master/detail split view project in XCode, the master controller (the table view controller), should have a pointer to the DetailViewController (If I understand correctly, that would be the piece you call the delegate in your code).
So instead of declaring any delegate, you would just point directly to that detailViewController.
If for some reason, that pointer does not exist, you would want to create it, and whenever the master and detail are initialized, have something like this:
MasterViewController *masterViewController = [[MasterViewController alloc] initWithNibName:#"MasterViewController" bundle:nil];
DetailViewController *detailViewController = [[DetailViewController alloc] initWithNibName:#"DetailViewController" bundle:nil];
masterViewController.detailViewController = detailViewController;
(if your master and detail are not allocated in code like here, they should still both be accessible from a common place, let's say the app delegate, as IBOUtlets, you would then just use the third line of code, after making sure you have a property 'detailViewController' in your master's header)
That way, you should have the detailViewController referenced from the master view controller at all time, so you can call any method on it from the table view controller(master)
[detailViewController didSelectGift:selectedGift];

NSFetchedResultsController keep reference to deallocated delegate controller in storyboard, causing crash

I have a simple UIViewTable, with a drill-down detail realized with a UINavigationController push segue in a storyboard.
It happen from time to time, that the table view controller seems to gets deallocated, while I am in the detail view, therefore I get the famous:
[MyViewController controllerWillChangeContent:]: message sent to deallocated instance
I explain better, I have an NSOperation queue which load my data asynchronously, and fill the table as soon as it just finished. The data are correctly retrieved and the table filled.
For the detail view, I am clicking on a cell and passing the NSManagedObjectID to the destination controller in prepareForSegue method. Very randomly when I made a change to the detail view the fetched controller loose its delegate, or as it seems, the delegate itself which is the controller gets deallocated. Causing a crash.
The fetched results controller is declared as a property:
#property(nonatomic,strong) NSFetchedResultsController *fetchedResultsController;
Then this is how everything is working starting from viewDidLoad.
- (void)viewDidLoad {
[super viewDidLoad];
[self loadDataAsynchronously];
}
-(void)loadDataAsynchronously {
NSOperationQueue *queue = [NSOperationQueue new];
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
selector:#selector(loadData)
object:nil];
[queue addOperation:operation];
}
-(void)loadData {
NSFetchRequest *findAllEntities = [[NSFetchRequest alloc] init];
[findAllEntities setEntity:ENTITY_DESC];
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:#"created" ascending:YES];
[findAllEntities setSortDescriptors:[NSArray arrayWithObject:sort]];
[findAllEntities setFetchBatchSize:20];
[NSFetchedResultsController deleteCacheWithName:#"MyCache"];
if(self.fetchedResultsController==nil) {
self.fetchedResultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:findAllPlants
managedObjectContext:MOC
sectionNameKeyPath:nil
cacheName:#"MyCache"];
self.fetchedResultsController.delegate=self;
}
NSError *error=nil;
if (![FRC performFetch:&error]) {
exit(EXIT_FAILURE);
}
[self.dataTableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:YES];
}
this code works, and most of the time also works within the detail view which is called as a segue like this:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
IP2SegueIdentifier segueIdentifier = [IP2Factory segueSolver:[segue identifier]];
MyDestinationViewController *dvc = [segue destinationViewController];
NSIndexPath *indexPath = [TV indexPathForSelectedRow];
dvc.entityID=[[self.fetchedResultsController objectAtIndexPath: indexPath] objectID];
}
and the destination controller correctly get the entity id and reconstruct the object by asking the context.
Then, when I am in the detail view controller, I can make change to the entity, and when I go back to the navigation hierarchy I do a context save.
It is at this point that the application crashes, just right at context save. Not so often, but from time to time.
Because the fetched results controller recognize the change and submitted to its delegate which is already deallocated.
I have few doubts at this point, I am using iOS 5 and ARC so the compiler is supposed to have (almost) full control over release and dealloc methods. And I am also using a storyboard with a simple navigation hierarchy, which should guarantee that the whole previous view controllers chain gets retained.
I also run the profiler for memory leak/zombies analysis, but wasn't able to spot anything wrong, on the contrary I was happy the all the objects management were fine.
I have not many guess at this point, so please feel free to point out something I could have forgotten to check, or something you see wrong in my code.
thanks
First, a note about ARC. While ARC provides auto-zeroing weak pointers, it does not make assign pointers auto-zeroing. NSFetchResultsController uses an assign property for its delegate (see NSFetchedResultsController.h):
#property(nonatomic, assign) id< NSFetchedResultsControllerDelegate > delegate;
It is still your responsibility to clear yourself as the delegate before you deallocate. You typically do this in dealloc:
- (void)dealloc {
_fetchedResultsController.delegate = nil;
}
You may also want to get rid of your fetchedResultsController in viewWillDisappear: (including removing yourself as the delegate). Typically you do not want fetch requests to stay around when you are offscreen. (If you do, you probably should manage the fetch in a model object rather than in a view controller, since a view controller can go away anytime its view is offscreen.)
Your loadData is strange in that it creates a findAllEntities, but actually uses findAllPlants. Was this a typo or a bug? If there is a separate findAllPlants fetch request in an ivar, this could also be a cause of your problems.

UIViewController and UITableViewController, how to change self.view then back again?

I dont want to add a sub view, but instead change the "self.view" to another view eg (A warning view) then after the user suppresses the warning I would like to switch back. When ever i try to switch back to the original view i just get a blank screen for reasons i cant understand.
Here is what i currently have in one of my UITableViewControllers
//Show warning view controller
self.warningViewControler = [[[WarningViewController alloc] init] autorelease];
self.view = self.warningViewController.view;
//Then later
self.view = self.tableView; //<< Dosnt work
If you want to change your view, and if the original view is defined/linked into XCode, you must retain it before changing self.view to another view. If not, the original view is released and using it back can cause bad things to happen.
Warning :
self.warningViewControler = [[[WarningViewController alloc] init] autorelease];
self.view = self.warningViewController.view
is a bad bad call. Because you autorelease the controller but you use its view. So you get a view retained with a released controller after some time. Retain the controller and release it yourself when its view is not needed anymore.
Here's the better way to do what I think you're trying to do:
WarningViewController *warningViewController = [[WarningViewController alloc] initWithNibName:#"theRightNiborNil" bundle:nil];
[self presentModalViewController:warningViewController animated:YES];
// or if you don't need to support iOS4 any more:
[self presentViewController:warningViewController animated:YES completion:nil]
// and if you aren't using ARC yet, then [warningViewController release];
Then in your WarningViewController you want some action that calls:
[self dismissModalViewControllerAnimated:YES];
// or again if this is iOS5..
[self dismissModalViewControllerAnimated:YES completion:nil];
Hope that helps.

Can't access UIView in external method

I'm new to Objective-C, so the way I'm going about this might be ludicrous, but here goes:
I have a login form in my iPhone application. When the user has entered their credentials, they hit Done in the top right corner, which triggers an IBAction and a custom progress indicator pops up. I've created this indicator by using a class containing an instance method named showProgressIndicator. showProgressIndicator creates and returns a UIView, which I then add to my view like so:
ProgressIndicatorElement *ProgressIndicator = [[ProgressIndicatorElement alloc] init];
box = [ProgressIndicator showProgressIndicator];
[self.view addSubview:box];
I have of course declared box as a UIView in my header file. The progress indicator pops up beautifully and in the meantime I'm doing a behind-the-scenes URL request that, when finished, calls another method in my view controller named receivedServerResponse. Now, what I want to do is to remove the progress indicator, which is why I'm doing this:
- (void)receivedServerResponse {
[box removeFromSuperview];
}
But nothing happens at all. I'm not getting any errors or warnings, and the code is being highlighted just as if everything was running smoothly. I've tried retaining the indicator in my IBAction, but that doesn't help either.
Hope you can help out!
Updated:
Here is the showProgressIndicator method:
- (UIView *)showProgressIndicator {
box = [[UIView alloc] initWithFrame:CGRectMake(85, 190, 210, 140)];
box.backgroundColor = [UIColor colorWithRed:0.0 / 255 green:0.0 / 255 blue:0.0 / 255 alpha:.6];
box.layer.cornerRadius = 8;
UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
spinner.layer.frame = CGRectMake((box.layer.bounds.size.width - spinner.layer.bounds.size.width)/2, 20, spinner.layer.bounds.size.width, spinner.layer.bounds.size.height);
[spinner startAnimating];
[box addSubview:spinner];
UILabel *titleInBox = [[UILabel alloc] initWithFrame:CGRectMake(0, 65, 150, 20)];
titleInBox.font = [UIFont boldSystemFontOfSize:16];
titleInBox.textColor = [UIColor whiteColor];
titleInBox.textAlignment = UITextAlignmentCenter;
titleInBox.backgroundColor = [UIColor clearColor];
titleInBox.text = #"Authorizing...";
[box addSubview:titleInBox];
return box;
}
Second update:
#Deepak just pointed out in the comments that I might be running two different instances of my view controller, which actually seems to be the case. In the external class that handles the aforementioned URL request, I get back to the view controller's receivedServerResponse method by doing this:
- (void)requestFinished:(ASIHTTPRequest *)request {
NSString *responseString = [request responseString];
SignInViewController *viewController = [[SignInViewController alloc] init];
[viewController receivedServerResponse];
}
Without spreading myself too thin (probably too late ;)), ASIHTTPRequest is set up so that if you call one method that performs an asynchronous URL request, a predefined method called requestFinished (above) is called, which is why I've had to call my view controller this way, because I can't access the returned value in an easier way (that I know of).
Creating a new instance of SignInViewController is not the correct way. It only seems correct to maintain a weak reference (assigned property) of the SignInViewController object. Say your class is RequestHandler.
#interface RequestHandler: [..] {
}
#property (nonatomic, assign) SignInViewController * signInViewController;
#end
#implementation RequestHandler
#synthesize signInViewController;
[..]
- (void)requestFinished:(ASIHTTPRequest *)request {
NSString *responseString = [request responseString];
[signInViewController receivedServerResponse];
}
#end
So when you're creating a RequestHandler object within the SignInViewController instance, you do,
RequestHandler * requestHandler = [[RequestHandler alloc] init];
requestHandler.signInViewController = self;
[..]
Note, you can also look at delegation and notifications.
I think part of the problem may be with memory management. If showProgressIndicator does not return an autoreleased object, try releasing box after adding it as a subview, like so:
[self.view addSubview:box];
[box release];
box may not disappear if box is not deallocated when removed from the superview.
My other recommendation is that instead of doing it the way you are doing, creating a view, adding it, and then trying to removing it, you might want to try adding box as a subview when the login view is created and setting its hidden property to YES then unhiding it later when necessary.
Based on your update: You have some memory management issues in showProgressIndicator. Whenever you alloc an object, you should release it. In this case, release all of your variables after adding them as subviews as I mentioned above. box however should be returned as an autoreleased object since showProgressIndicator does not know when it will need to be released. For that you should replace return box; with return [box autorelease];
You need to send the activity indicator a stopAnimating message when you want the animation to stop. There's no need to remove it from its superview; instead, simply make sure that its hidesWhenStopped property is set to YES.
How about adding box view on window in appDelgate? Give a tag to your boxView and in the remove method get the boxView back by using tag. For example if you give tag 99
- (void)receivedServerResponse {
UIView *box = [window viewWithTag:99];
[box removeFromSuperview];
}
also you don't need to declare an instance variable in header file. and you can access progress indicator anywhere in the application.
Without spreading myself too thin (probably too late ;)), ASIHTTPRequest is set up so that if you call one method that performs an asynchronous URL request, a predefined method called requestFinished (above) is called, which is why I've had to call my view controller this way, because I can't access the returned value in an easier way (that I know of).
ASIHTTPRequest calls -requestFinished: on the object you set as the request's delegate. You should design your classes such that this delegate object either has a reference to the view controller you want it to act on or has some means of notifying that view controller to take action.
The easiest solution might be to make the controller the request's delegate.

Setting a ViewController's properties after instantiation

I'm creating an instance of a viewController, and then trying to set the text on of it's properties, a UILabel.
BoyController *boyViewController = [[BoyController alloc] initWithNibName:#"BoyView" bundle:nil];
NSString *newText = [astrology getSignWithMonth:month withDay:day];
boyViewController.sign.text = newText;
NSLog(#" the boyviewcontroller.sign.text is now set to: %#", boyViewController.sign.text);
[newText release];
I tried this, but it didn't work...
So I tried the following:
BoyController *boyViewController = [[BoyController alloc] initWithNibName:#"BoyView" bundle:nil];
UILabel *newUILabel = [[UILabel alloc] init];
newUILabel.text = [astrology getSignWithMonth:month withDay:day];
boyViewController.sign = newUILabel;
NSLog(#" the boyviewcontroller.sign.text is now set to: %#", newUILabel.text);
[newUILabel release];
But no avail..
I'm not sure why I can't set the text property of the UILabel "sign" in boyViewController..
The problem here is that the initializer does not actually load the nib file into memory. Instead, loading the nib is delayed until your application requests the view controller's view property. As such, your controller's sign property is null when you access it.
Manually requesting the controller's view property would make your example work...
BoyController *boyViewController = [[BoyController alloc] initWithNibName:#"BoyView" bundle:nil];
[boyViewController view]; // !!!: Calling [... view] here forces the nib to load.
NSString *newText = [astrology getSignWithMonth:month withDay:day];
boyViewController.sign.text = newText;
// and so on...
However, I'd guess that what you're really trying to do is create and configure your view controller before setting it free to do it's own thing. (Perhaps to display it modally, say.) Calling [... view] manually is not going to be a long-term solution.
Better is to set a separate property on your view controller for the label text and then implement viewDidLoad to assign it to the label:
#interface BoyViewController : UIViewController {
IBOutlet UILabel *label;
NSString *labelText;
}
#property(nonatomic, copy)NSString *labelText;
#end
#implementation
#synthesize labelText;
- (void)viewDidLoad
{
[label setText:[self labelText]];
}
// and so on...
#end
This has the added benefit of your label text being reset in case the view is purged during a low memory event.
Did you bind your outlets at Interface Builder?
It seems that you need to bind sign outlet of the first example into Interface Builder in order to actually set that text to whatever you want.
Once you bind your outlet to the actual UI component at Interface Builder, then you should be able to do something like:
NSString *newText = [astrology getSignWithMonth:month withDay:day];
[[boyViewController sign] setText:newText];
This is what you need to know about binding.
Your second example does not make sense at all to me.