iPhone: Proper use of View and View Controller - iphone

I've recently been doing a lot of Objective-C programming, and just got back into doing more iPhone development. I've done a lot of programming using MVC in other languages/frameworks, but I just want to make sure I'm using MVC properly in my iPhone Development.
I created a new iPhone Utility Application, which creates two views: MainView and FlipsideView. Both have a controller (FlipsideViewController and MainViewController) and XIB file of their own.
What I've been doing is putting the IBOutlet UIControl myControl variables in my MainView.h or FlipsideView.h files and then tying the controls in Interface Builder to those variables. Then I put any IBAction SomeAction myAction methods in the MainViewController.h and FlipsideViewController.h files and tying the events to those methods in Interface Builder.
This seems to be conceptually correct, but seems to cause problems. Say I have a button that when clicked it changes a label's text. So the Controller doesn't have a clue of what the variable name of the label is in the OnTouchUp event handler for my button. So I make a #property for it. But since the MainViewController.view property isn't of type MyView, I get warnings or errors whenever I try to access those properties from the view controller.
I am doing this correctly? Is there a better way to do this? If this is correct, how do I properly work with those variables without getting warnings or errors?
Thanks
Here's some code showing what I'm doing:
MainView.h
#import ...
#interface MainView : UIView
{
IBOutlet UILabel* label;
IBOutlet UIButton* button;
}
#property UILabel* label;
#property UIButton* button;
#end
MainViewController.m
-(void) buttonTouchUp:(id) sender
{
self.view.label.text = #"The button was pressed!"; //This gives error because label is not in the view structure or union
[self.view label].text = #"The button was pressed!"; //This gives a warning
}
I know I can cast the view to be of the right type, but that seems like a hack job.
I know I can live with the warning, but a warning says to me that I'm doing something that I probably shouldn't be doing. I don't believe the SDK would set me up to do have to do something like that all the time.
I must just be misunderstanding what the View is supposed to be and I'm using it incorrectly.

Most code I've seen and written myself keeps all the outlets and the actions on the controller. The button sends a message to the controller (via IBAction), then the controller updates the label (via IBOutlet). In other words, putting outlets on your views is unusual.
Views shouldn't be connected to other views unless they have some special relationship, like maybe back/forward buttons that target a UIWebView... but even then, you find out you need a controller to enable/disable the buttons as appropriate.

Related

IBAction and IBOutlet Clarification

I am new to Xcode and I am wondering, what does IBAction and IBOutlet do? I've tried doing simple task like 'hello world' but it seems I stuff it up. I am making app that involves a questionnaire that links to a database.
IBAction is used for Methods which performs as a result of any Action for example Button Press.
-(IBAction) buttonPress : (id) sender;
IBOutlet is used for UI related objects like Button,label, View etc.
IBOutlet UILabel *nameLabel;
Note: If you are using XIB for development, you should make use of IBAction and IBOutlet. Otherwise you will not able to map objects and methods on XIB. If you are developing everything by coding then IBAction and IBOutlet are optional.
As mentioned in the answer linked above: "IBAction and IBOutlet are macros defined to denote variables and methods that can be referred to in Interface Builder."
However in layman's terms and a simple way to think of them -
IBActions mark the methods that will be called when an event (e.g. touch down) is triggered on one of your interface builder controls (e.g. button, switch etc).
IBOutlets mark the variable references for your interface builder controls.
Outlets allow you to programatically interact with controls you layout on interface builder.

My objects in storyboard don't link correctly with the code

I am trying to use storyboards in my application. I began well, I have added two view controllers with a button to go from the first to the second.
The problem is in this second view controller: the objects I add on it and link with the code do not work. When I set a breakpoint in the view controller code, the 'self.property' is set to nil, whereas it should be instantiated by the storyboard.
I have been looking for an answer for hours, and I really don't understand the problem, since all the rest seems to be good.
I have tried to write the property in the code (strong/nonatomic, nonatomic/retain and even weak/nonatomic), to link the object directly to the code so that it creates the property automatically, but I never get anything else than "nil" with breakpoints.
viewController.h:
#interface NMLoadingViewController : UIViewController
{
__weak IBOutlet UIProgressView *imageProcessingProgressView;
}
#property (weak, nonatomic) IBOutlet UIProgressView *imageProcessingProgressView;
#end
.m:
#synthesize imageProcessingProgressView;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
// Custom initialization
NSLog(#"INIT.");
}
(amont other lines of irrelevant code)
If I set the breakpoint to the "INIT." line, my imageProcessingProgressView is nil. I can't see a reason for that, I guess I have missed a very little detail...
(Note that I have tried this with other objects, like a UILabel, but it did not work either. Stranger, when I had two objects, one of them had an adress which was not nil, but still it was corrupted and did not show the right object.)
Edit: It was a lot of trouble for nothing... The problem is about the Variable View in XCode, which show my variable to "nil", whereas a log shows me a correct object...
Remove this...
{
__weak IBOutlet UIProgressView *imageProcessingProgressView;
}
...and make the property strong.
Change your log message to...
NSLog(#"INIT: %#", self.imageProcessingProgressView);
...and see if you still have a problem. If you do, then take another look at your storyboard connections.
Make sure NMLoadingViewController is the class on your viewController
First try out the answer by #Eric because I do believe that is the real issue.
Also, make sure that you are actually using the interface builder to hook the controls back to the view controller. This is done by dragging a line from the control back to the property of the view controller.

Trying to update UILabel with contents of UITextField on different view controller

I have two view controllers, one is MainViewController, the other is SetupViewController. I want a UILabel on MainViewController to set the text to the contents of a UITextField from the SetupViewController when a button is pressed in the SetupViewController.
In SetupViewController, I have this in the IBAction:
- (IBAction)donePressed:(id)sender {
MainViewController *mvc = [[MainViewController alloc] init];
[mvc.testLabelOnMVC setText:testTextFieldOnSVC.text];
[release mvc];
}
testLabelOnMVC (and testTextFieldOnSCV, with respective terms) is
#property (nonatomic, retain) UILabel *testLabelOnMVC;
and is also synthesized.
Every time I try, it doesn't work. Nothing happens, nothing changes. I have no errors or warnings. Can anyone help me out?
The view of your MainViewController does not exist until you reference the MainViewController's view property (which forces viewDidLoad to execute). You must reference the view (or otherwise force the view to be constructed) before you attempt to modify any UI objects in the MainViewController.
You are allocating a new MainViewController when you press the button, then you are setting the text of the label on this new controller, not on the MainViewController that your app is showing.
To fix this, create either and IBOutlet or iVar that points to the original MainViewController and set the text on that instead.
Easiest way is to create a #property in the main view controller and write the text in there. Then just read it in the second MVC's viewDidLoad.
The only views that MainViewController should worry about are the ones that it owns; it shouldn't be trying to access the view hierarchy managed by SetupViewController. Likewise, SetupViewController should not directly modify views in MainViewController's view graph.
The right way to do what you're asking is for the two controllers to talk to each other, either directly or via the data model. For example, let's say that your MainViewController instantiates SetupViewController. If that's the case, it'd be natural for mvc to set itself as svc's delegate, so that svc sends it a messages like -setupController:didUpdateTestStringTo:. MainViewController's implementation of that method could then save the new test string somewhere and update it's testLabel field.
Another example: MainViewController instantiates SetupViewController. SetupViewController contains a field where the user can enter a new value for the test string. Before exiting, SetupViewController writes the contents of that field into NSUserDefaults or some other common data storage. When control returns to MainViewController, that object reads the shared data and updates itself as necessary, including setting the new value for testLabel.
There are other variations on the same theme, but the common thread here is that neither view controller has to directly access views that it doesn't own.
You can change the text of the label if the view is already loaded. Instead of initializing the viewcontroller, retrieve it from the view stack if you are using navigation controller.
I dont know if your viewController is already loaded or not.

Objective-C Novice. Change property in Controller from another Controller?

The context: I have three views. One Introductory view, an Upload view and the Main view. As classes (With their respective headers) I have the rootViewController (SwitchViewController), IntroViewController and UploadViewController. The first view to be shown is IntroView. The user presses a button (declared in SwitchViewController) that takes them to the UploadView, then in the UploadView they get to choose an image and press the button again to go back to IntroView.
The thing is that while the user gets to pick the image with UIImagePickerController the button to switch views won't hide nor a UIImageView I have with a logo on top of the view(screen). The UIImageView and the UIButton are both declared in SwitchViewController's header.
The code used:
UploadViewController.h
#import [...] //Imports
#class SwitchViewController;
#interface UploadViewController :
UIViewController <UIImagePickerControllerDelegate,
UINavigationControllerDelegate,UIActionSheetDelegate> {
UITextField *imageTextField;
UIImageView *uploadedImage;
SwitchViewController *switchViewController;
[...]
}
#property (nonatomic, retain) SwitchViewController *switchViewController;
#property (nonatomic, retain) IBOutlet UITextField *imageTextField;
#property (nonatomic, retain) IBOutlet UIImageView *uploadedImage;
[...]
#end
UploadViewController.m
[...]
- (IBAction) selectImageButtonPressed {
self.switchViewController.submitButton.hidden = YES;
self.switchViewController.imageLogo.hidden = YES;
[...] //continues
I just begun recently programming in objective-c so please forgive me if the question is very essential. I have looked and am following "Beginning iPhone 3 Development" of APRESS. But even if it helps to greatly understand the basics sometimes I get lost.
PS: If it is clearer to answer the question the SwitchViewController.h and .m snippet codes can be provided if asked. But I thought this text is big as it is.
#Joze i think I may have understood your problem switchViewController is a variable of the class UploadViewController so if you do anything with that variable it wont affect the switchViewController view. so when you are calling the switchViewController view at that time you have to do initWithNibName: bundle: and then hide the button and imageView and also you need to do something like switchViewController.delegate = self; and then call the view modally or what ever way you want it.
PS. i m not sure the that spelling is correct. i dont have xcode at my home.
I hope your problem solves with this.
I solved my problem after refactoring the whole code and changing the general structure of the program itself. Now I have 3 views and each with a viewController to control it. All the switching of views occurs in the Delegate since he has access to everyone. That way I can control every property with every controller, without much difficulty. Changing the property of one of the objects present in one view from another view is difficult and rather inconvenient if not sometimes impossible.
The approach I took when asking this question was short sighted for the application that had to be done. I thank all those who tried to help.

iPhone Views at Runtime?

I am new to the iPhone SDK and am trying to create 3 views and switch between them. Data will come from a server and I will basically be showing 1 view and caching the other two. So far I am just trying to create a view and display it at run-time. My code is listed below. It shows only a blank screen and I think I am missing a key concept. Any Help?
#import <UIKit/UIKit.h>
#import "ImageViewController.h"
#interface Test5ViewController : UIViewController
{
IBOutlet UIView *rootView;
ImageViewController *curImage;
ImageViewController *nextImage;
ImageViewController *prevImage;
}
#property(nonatomic,retain) IBOutlet UIView *rootView;
#property(nonatomic,retain) ImageViewController *curImage;
#property(nonatomic,retain) ImageViewController *nextImage;
#property(nonatomic,retain) ImageViewController *prevImage;
#end
and
- (void)loadView
{
self.curImage = [[ImageViewController alloc]initWithNibName:#"ImageView" bundle:[NSBundle mainBundle]];
UIImage *pic = [UIImage imageNamed:#"baby-gorilla.jpg"];
[self.curImage assignImage:pic];
self.rootView = self.curImage.view;
}
and
#import <UIKit/UIKit.h>
#interface ImageViewController : UIViewController
{
IBOutlet UIImageView *image;
}
-(void)assignImage:(UIImage *)screenShotToSet;
#property(nonatomic,retain) IBOutlet UIImageView *image;
#end
Welcome to the iPhone SDK!
In general, there are two ways to get any view displayed.
First, and most commonly, you use a NIB file created by the Interface Builder. This is usually the easiest way to get started and I would recommend it for what you're trying to do here. It's too lengthy to describe all the steps you need to do for what you have here, but basically start in xcode by creating a new file and selecting "user interfaces" and choose View XIB. This will create a basic NIB file (they're called NIBs rather than XIBs for historical reasons). The first step in interface builder is to change the class name of the "File's Owner" to your UIViewController subclass (Test5ViewController). You can then drop anything that IB will allow into the view window or even replace the pre-supplied view object with one of your own. And here's the trick: make sure the view outlet (supplied by the UIViewController superclass) is connected to a view. Once this is done, this view will be automatically loaded when your NIB is loaded. You can then just put your UIViewController subclass (Test5ViewController) in your MainWindow.xib NIB file to get it automatically loaded, and you're in business.
Now, the way you're doing it here is the second way. Some people like to code this way all the time and not user interface builder. And while it's definitely necessary sometimes and always more flexible, it makes you understand what is happening a bit better. There may be other things, but the main thing you're missing is that in your code above, you have nothing that is adding your view into the view hierarchy. You need to check first that you have an UIApplicationDelegate subclass and it needs to load your "root" UIViewController class. All initial project creation types in xcode do this (except Window-based application). It is code like:
[window addSubview:rootController.view];
Once this is done, if your view controller wasn't loaded by the NIB (described briefly above), your loadView method will be called, expecting you to build your own view hierarchy. Above, you created the view(s), but failed to put them in a hierarchy. You need something like:
[self.view addSubview:curImage.view];
No view will be rendered until added to the view hierarchy. Make sure to look up the UIView class in the documentation and understand the variety of ways to add and remove views to the view hierarchy.
A couple things I should warn you about:
* your code above is leaking. You need to review how objective-C properties work. There's lots on this site about it. More than I have time to write about here.
* don't create a rootView property in the case you have here. There already is one in the superclass (UIViewController). It's just 'view'. Use that for saving your root view.
I hope this helps you get started. It can be bewildering at first, but you'll soon get it going! I recommend building and rewriting and rebuilding a lot of sample code before you do your "real" application. The SDK has many great samples.