Can't access UIView in external method - objective-c

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.

Related

ViewDidLoad not being called with my custom UIViewController

I have created a custom UIViewController class that creates a ScrollView at runtime that it loads into the view. See code here in the constructor of my custom UIViewController.
initControl(id, canEdit);
_controllers = new NSMutableArray(0); //required to keep view controllers around
_scrollView = new UIScrollView();
_scrollView.BackgroundColor = UIColor.Green;
this.View = _scrollView;
ViewDidAppear and ViewWillAppear are called normally.
ViewDidLoad is not called which I am not sure why as the view is showing up on the screen just fine.
Any ideas?
The viewDidLoad method is being called when accessing self.view
Examples:
1)
- (id) init {
self = [super init];
if (self)
{
...
[self.view addSubview: self.toolbar];
}
}
2)
viewContrl = [[MyViewController alloc] init];
viewContrl.view = webTopView;
3)
viewContrl = [[MyViewController alloc] init];
[viewContrl.view addSubview: webTopView];
ViewDidLoad is called when you are allocating the view. So if you are allocating the view once & only adding every time using addSubview then it called first time only. If you want to called it every time when you are adding it, then you needs to allocate it every time. Also handle memory management by releasing the view before allocating it, if it is already allocated.
Another way is to create a method which contains the operations which you wants to perform & called it after addSubview.
It may solves your problem, if you have any doubt then feel free to ask me.

Implement UITableView from different ControllerClass as Subview

I'm trying to implement a TableView from a different (Table)ViewController into a Scrollview. I'm literally trying for hours now and I can't figure out the problem. The error I get is:
-[UISectionRowData numberOfSectionsInTableView:]:
unrecognized selector sent to instance 0x687fdb0 2012-05-07
16:47:18.966 Test2[12212:f803] * Terminating app due to uncaught
exception 'NSInvalidArgumentException', reason: '-[UISectionRowData
numberOfSectionsInTableView:]: unrecognized selector sent to instance
0x687fdb0'
This is the code Segment I'm working with (Implemented in the viewDidLoad() method):
DateSubviewController *dateSubviewController = [[DateSubviewController alloc] init];
//This is the tableViewController which handles the code from the tableView I want to implement.
NSArray *views = [NSArray arrayWithObjects: [dateSubviewController view], nil];
self.scrollView.delegate = self;
self.pageControlBeingUsed = YES;
for (int i = 0; i < views.count; i++) {
CGRect frame;
frame.origin.x = self.scrollView.frame.size.width * i;
frame.origin.y = 0;
frame.size = self.scrollView.frame.size;
UITableView *subview = [views objectAtIndex:i];
[self.scrollView addSubview:subview];
subview = nil;
}
self.scrollView.contentSize = CGSizeMake(self.scrollView.frame.size.width * views.count, self.scrollView.frame.size.height);
The scrollview is working on it's own (Tested it with different colored frames).
The problem seems to be, that [dateSubviewController view] doesn't call the methods required for the tableView to Work. The view outlet is hooked up with the tableView but "greyed out" in the storyboards. I thought it might be pointing to a wrong view, so I already tried deleting the tableView and hook it up again. This had no effect. The crash appears right after the whole viewDidLoad() method is done.
When I try to use
NSArray *views = [NSArray arrayWithObjects: [dateSubviewController dateTable], nil];
(dateTable is the tableView I try to implement) to access the tableView directly the array views contains 0 elements in debugging. The TableView delegate methods aren't called either. But it doesn't crash.
I don't know what to do anymore. I'm working on this problem for about 6 hours now, debugged several times, did Internet research but didn't find anything. Thanks for your time!
After hours of work I now figured out this was a problem with the autoreleasepool. When I initialised the viewController...
DateSubviewController *dateSubviewController = [[DateSubviewController alloc] init];
...it was a local variable and got released as soon as the viewDidLoad() method finished. I had to use it as an instancevariable! I hope this fixes the error for a future reader too!
In headerfile of the class where you want to initialise the ViewController:
#property (strong, nonatomic) DateSubviewController * dateSubviewController;
Then initialise it in this way:
self.dateSubviewController = [[DateSubviewController alloc] init];
According to the error, it should be clear to you that your tableView's dataSource does not respond to the method the tableView needs. Since I guess that you already have an object that should be this dataSource (one that implements tableView:numberOfRowsInSection:), it's likely that you did not set the tableView's dataSource property to this object, but to another one. Check your code or your Xibs, and fix your tableView's dataSource link.
Do this first.
You may have other problems after. You'll see. But fix this first, and learn to read and understand your errors. Document yourself on words that you do not understand, such as "instance", "selector", "exception". Understanding errors is the key to successful debugging.

Making an UILabel appear in another view once pressed in one view

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).

Declaring UITouchGestures?

I'm working on using a UISwipeGestureRecognizerDirectionRight method to change views within an application and i have used the following code in the main file of the View Controller but it seems as though i need to define the gesture after the asterisk and declare it as this build error states:
"swipeGesture undeclared"
-(void)createGestureRecognizers {
UISwipeGestureRecognizerDirectionRight * swipeGesture = [[UISwipeGestureRecognizerDirectionRight alloc]
initWithTarget:self
action:#selector (handleSwipeGestureRight:)];
[self.theView addGestureRecognizer:swipeGesture];
[swipeGesture release];
}
-(IBAction)handleSwipeGestureRight {
NC2ViewController *second2 =[[NC2ViewController alloc] initWithNibName:#"NC2ViewController" bundle:nil];
second2.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentModalViewController:second2 animated:YES];
[second2 release];
}
So my question is how do i declare the "swipeGesture" after the asterisk in the header file or have i done something wrong?
Thank You
UISwipeGestureRecognizerDirectionRight is an enum value for the four possible directions. It is not a class you instantiate to recognize gestures. Use UISwipeGestureRecognizer instead:
UISwipeGestureRecognizer *swipeGesture = [[UISwipeGestureRecognizer alloc]
initWithTarget:self
action:#selector (handleSwipeGestureRight:)];
//Set the direction you want to detect by setting
//the recognizer's direction property...
//(the default is Right so don't really need it in this case)
swipeGesture.direction = UISwipeGestureRecognizerDirectionRight;
[self.view addGestureRecognizer:swipeGesture];
[swipeGesture release];
Also, the handler method should be:
-(IBAction)handleSwipeGestureRight:(UISwipeGestureRecognizer *)swipeGesture {
because in the selector for the action, you put a colon in the method name meaning you want it to pass the sender object as the first parameter. (You could also remove the colon from the selector instead if you don't need the sender in the handler.)
Finally, void is more appropriate than IBAction in the handler since it won't be called from an object in a xib. However, since IBAction and void are the same thing, it won't cause a problem.

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.