I'm trying to learn Objective-C and iPhone programming, but I'm stuck with a problem. I have a utility application, and I have a text on my MainView and a button that change the text when I click it. Easy, and workes great. But what if I wan't to place the button on the "backside" in the FlipSideView, and still make it change the text on the frontside (MainView)? How do I get the views to talk together? I have tried a lot of different things, and searched for an answear, but can't seem to figure it out.
Would be great if someone had a answear, or maybe a link to a tutorial/example.
I suppose you've used the template which implements the following method in the MainViewController:
- (IBAction)showInfo:(id)sender {
FlipsideViewController *controller = [[FlipsideViewController alloc] initWithNibName:#"FlipsideView" bundle:nil];
controller.delegate = self;
...
}
As you can see it sets the delegate of the FlipSideController to the instance of the MainViewController.
A way would be to put an action into your FlipSideViewController, something like this:
- (IBAction)changeTextInMainView
{
[(MainViewController *)self.delegate changeText];
}
which is triggered when touching your button on the backside. You've got to wire it in IB as well as add the method to the header.
Then implement something like this in your MainViewController
- (void)changeText
{
self.myLabel.text = #"text changed to this";
}
Add this method to the header as well.
Another more elegant approach would be to save the text of your label in a property(maybe in it's own model class) which can be accessed from any view, by passing it by reference down the controllers. Then add a Key Value Observer from each viewController to the property which should show the text and update the view.
Related
I am pretty new to iPhone programming, and was playing around with an app yesterday trying different scenarios with view controllers and nib files. So, I started a new app with a FirstViewController (FVC for short) and an FVC.xib.
I layed out a quick view in FVC.xib and ran the app - view displays, great.
I now wanted to have a second view I could add on top of the main view. So I went ahead and created SecondViewController.xib (SVC) but did not create the .m and .h files. I went about trying to load both these views from the same view controller, and here is where my question lies:
I created a button in FVC.xib and created an IBAction like this:
- (IBAction)loadSVC {
FirstViewController *viewController = [[FirstViewController alloc] initWithNibName:#"SecondViewController" bundle:[NSBundle mainBundle]];
secondView = viewcontroller.view;
[viewController release];
[self.view addSubView:secondView];
}
So this works great and adds the contents of SVC.xib, but when I try and remove that view from the superview, the app crashes:
[secondView removeFromSuperview];
If I actually create a view controller for SVC, use that to instantiate my view in FVC, and move the remove code to the SVC:
[self.view removeFromSuperview];
Everything works. My question - I kind of get why my first method crashes, but I was hoping someone could explain why and what goes on behind the scenes. I'm still a noob with object oriented programming, so what is actually happening in my first case where I create a new instance of FirstViewController and add its view to self.view? Why can't I release it (I assume because the original view is associated with FirstViewController, and when I create a new instance with the second xib it messes everything up) - I'd love a more technical explanation as to what is happening...
Thanks much!!
EDIT to add more info in response to Nick's reply below
Nick - so your answer did clear my thinking a bit in regards to the retain count, etc... I did another test app trying to get this working from a single view controller - think, for example, that I wanted to display an Alert or Welcome message to the user (I know in a real app there are different methods to accomplish this, but this is more of a learning experience) -- so I have my main view # MainViewController and layout my alert message in a xib called alert.xib -- so there is no logic behind the alert message, no reason for it to have a view controller that I can see, my end goal being loading/unloading this on top of my main view from the main view's view controller (or understanding why it is impossible)
I tried this using instance variables as you recommended:
In MainViewController.h:
#import <UIKit/UIKit.h>
UIViewController *secondController;
UIView *secondView;
#interface MainViewController : UIViewController {
}
#property(nonatomic, retain) UIViewController *secondController;
#property(nonatomic, retain) UIView *secondView;
- (IBAction)loadSecond;
- (IBAction)removeSecond;
#end
In MainViewController.m:
#import "MainViewController.h"
#implementation MainViewController
#synthesize secondController, secondView;
- (IBAction)loadSecond {
secondController = [[MainViewController alloc] initWithNibName:#"alert" bundle:[NSBundle mainBundle]];
secondView = secondController.view;
[self.view addSubview:secondView];
}
- (IBAction)removeSecond {
//I've tried a number of things here, like [secondView removeFromSuperview];, [self.secondView removeFromSuperview];, [secondController.view removeFromSuperview];
}
- (void)dealloc {
[secondController release];
[secondView release];
[super dealloc];
}
So - this works to load the alert view, but the removeSecond button does nothing (I did use NSLog to verify the removeSecond method is fired) - why?
Second, and most importantly - is this even possible, or is it horrible practice? Should every nib/view I am manipulating have their own view controller? Am I wrong to think I could just make a new instance of MainViewController and use it to display and remove this no-functionality, very temporary view? (And yes, I realize I could easily create this view programatically or accomplish the end goal in many different ways which would be easier, but I'm trying to really learn this stuff and I think figuring this out will help...
Thanks for the help!
You created a view controller
You accessed its view which caused controller to create the view and call the delegates (i.e. viewDidLoad)
Controller returns the view that you asked for
Now you add the view as a subview which increases its retain count
Controller is released and it releases the view, BUT since view's retain count was increased the view is still there
You try to remove the view, it is unloaded and delegates are to be called (e.g. viewDidUnload), however that messes up since the controller who created the view is released and that piece of memory is... smth else :)
That's why the first method doesn't work.
The second method is NOT correct either but it works because:
You remove controller's view from superview but since controller itself is not released (you didn't call [self release] or anything like that, not saying that you should :), just an example), then the view didn't reach 0 (zero) retain count and is still there - which means its subviews aren't removed
The proper way to do it is to save the reference to the controller as an instance variable (usually declare a synthesized property), and release it only when you are done with the view, making sure that the view is removed from superview before hand. The default templete for a View Based App shows how view controller should be managed
Hope this helps to understand why both methods behave differently
Based on your clarifications, you don't need secondView property or iVar. Also in your loadSecond instead of secontController = bla you need self.secondController = bla, otherwise you simply assign reference to the iVar instead of going through the setter.
Yes, it's possible to load subviews/other resources from a nib without having a dedicated controller
This is how you do it (one of the approaches):
UIView *result = nil;
NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:#"MyNibName" owner:owner options:nil];
for ( id o in bundle ) {
if ( [o isKindOfClass:[UIView class]] ) {
result = (UIView *)o;
break;
}
}
Here the result will contain the first UIView in MyNibName. You can use other criteria to find out whether you got the view you wanted (tags, types...)
What I want to do is a navigation bar with a image on it. I have a tab controller on my main view, and inside each tab I have a UINavigationController. From inside the UIViewController that my tab/navigationController calls, I could set the titleView without much problem, doing this inside the viewDidLoad method:
self.navigationItem.titleView = [[[UIImageView alloc] initWithImage:[UIImage imageNamed:#"mylogo.png"]] autorelease];
But, I want to replace all titles in my navigationBar for this view, and it seems ugly to repeat this everywhere. So I did this on the delegate (after linking all the Outlet stuff)
self.tabOneNavController.navigationBar.topItem.titleView = [[[UIImageView alloc] initWithImage:[UIImage imageNamed:#"mylogo.png"]] autorelease];
Again, it worked! ok, I'm almost getting there.
But the point is, I've 5 tabs and all of them have navigationControllers inside. I reduced the code repetition from every internal view to only 5 times, but it still. It requires that I do that for the NavController of each tab.
Then I tried to extend the UINavigationBar to create my own, where I could set this in the initializer, and use it in the interface builder as the object class. But it doesn't seem to work. Here is what I did:
#implementation MyNavigationBar
- (id)init {
self = [super self];
self.tintColor = [UIColor greenColor];
self.topItem.title = #"testing please work";
return self;
}
#end
in the interface file MyNavigationBar inherits from UINavigationBar. But this didn't work. Should I overwrite other method? which one? is this a good practice?
I'm not even sure if I should add one navigationBar for each tab, as I said, I have tabs and I want to have a navigation bar / navigate inside them. By now, after a near death experience trying to figure out how the interface builder / outlets and classes work, the code is working, I just would like to make unglify it.
Thank you!
The problem of repeating code which you describe has an elegant solution. Objective-C supports something called a "category", which allows you to add methods to a class. A common use for this is to customize navigation and tab bars. In Xcode 4, you would do something like this to add a category on UINavigationBar:
Hit Command+N or open the "New File" dialog. Next, choose "Objective-C category" from the Cocoa Touch menu:
Click Next and you will be prompted to enter the name of the class that you would like to add methods to as a category. It should look something like this:
Then, you should end up with a save file dialog. A quick note about convention here. Convention is to name a category after the original class, the plus sign, and then a description of what you're adding. Here's what yours might look like:
Once you save your file, you will need get something like this:
Look at that beauty. You can now override the default drawing/init methods as well as extend the functionality of the navbar.
I'd suggest looking into the init and drawRect methods, although I don't remember which ones people use. Also, please note that while under NDA, this may change in iOS 5, so just be prepared for that possibility.
Why not define a UIViewController subclass which sets the title view via self.navigationItem.titleView and have your other view controllers extend from that class? Then you're sharing that behavior across all of your controllers without repeating the implementation.
I have a design question/technical question about my iPhone app.
I have a pretty simple (read really really simple) single view application. And it does everything that I need it to do. However I find myself in need of a help view. And I really don't quite know what to do!
I have a simple helpButton() method in my main view controller, and I really just want to display a scrollview with a bunch of images that show what to do during the use of my app. However, should I make a new viewcontroller class? How do I call it from my method?
Really I was thinking of an unfortunately simple method, just putting a scrollview behind everything and hiding it. Then showing it when the IBAction is called. Horrible...
Sorry if this is elementary, I haven't needed to do anything more yet!
You can push a modalViewController. To do that just make a new viewController with the scrollview and associated data in it, then
MyViewController *myViewController = [[MyViewController alloc] init];
[self presentModalViewController:myViewController animated:YES];
Create an IBAction in your new viewController and a hooked up button to that action to dismiss the modalView (something like this:
IBAction done {
[self dismissModalViewControllerAnimated:YES];
}
A couple options:
1) Create a new UIView object, either programmatically, or even in your existing XIB file. Use the [self.view addSubview:view] method to display it.
2) Create a new UIViewController with its own XIB file. Use [self presentModalViewController:anaimated:] to display it.
Either way, you'll need to add something to the new view to dismiss it when you're done.
I'm very new to xCode and objective-C so I wanted to make a simple textRPG.
I'm currently making a character creation process consisting of 4 xib-files. The only way I got switching views to work was to look at the utility-template. Problem is, now I have the first screen being the delegate for the second screen, being the delegate for the third screen etc. So by the end of the character creation process I can't dismiss the views because that just "steps back" through the views.
When I've searched around for a solution I've found a addSubview-method but it seems like that makes a new view, like, empty to arrange programmatically.
All I need is a simple way to switch from one loaded xib to another xib. Have I misunderstood addSubview or do I need something completely different?
(If it helps: I've worked with VB for several years, in case you notice that I missed some kind of concept concerning views and such)
Thanks in advance! :)
Use this code. It is really simple and works well.
View *view = [[View alloc] initWithNibName:#"xibNameGoesHere" bundle:nil];
view.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentViewController:view animated:YES completion:nil];
This will switch to another xib file and the two views won't own one another. I am using it in my own game right now.
#Joakim Ok, this is the way I do it. I have a class called RootViewController : UIViewContoller, whose view I add directly to the window. Then I make a subclass of UIView i.e. MyView. All of my views then subclass MyView. I also make an enum for all my views. In RootViewController you should have a switchView:(int)view method that looks something like this:
-(void) switchView:(myView) view
{
[_currentView removeFromSuperview];
switch(view)
{
case titleView:
_currentView = [[TitleView alloc] initWithRoot:self];
break;
case homeView:
_currentView = [[HomeView alloc] initWithRoot:self];
break;
default: break;
}
[self.view addSubview:_currentView];
[_currentView release];
}
in #interface RootViewContoller define MyView *_currentView;
TitleView and HomeView both subclass MyView and have a common method -(id)initWithRoot:(RootViewController*) rvc.
To switch between views use [_rvc switchView:homeView];
Hope this helps :)
It is called UINavigationController. Idea is
1) You push corresponding 'next' controller into navigation controller each time user submits current screen. Bonus - you'll get 'back' button for free on each step of character creation.
2) After character is created you pop all character creation controllers from stack.
Please read View Controller Programming Guide for iOS before trying to 'switch views' and such. You'll save tons of time and nerves.
another idea is not to use interface builder at all. i have been working with iphone apps for two years now and found that interface builder really prolongs the time to actually make something. make your own root controller and think about the logic you need to navigate through the views.
Going through many tutorials and with the help of everyone here, I am trying to wrap my head around using multi view controllers with their own xib files.
I have one example where there is a : multiViewController and two others: aboutViewController, rulesViewController.
In both aboutViewController.m and rulesViewController.m files, I have placed the following code:
- (void)viewDidLoad {
NSLog(#"rules View did load"); // (Or About View did load, depending on the .m file)
[super viewDidLoad];
}
The mainViewController.m file contains:
-(IBAction) loadRules:(id) sender {
[self clearView];
[self.view insertSubview:rulesViewController.view atIndex:0];
}
-(IBAction) loadAbout:(id) sender {
[self clearView];
[self.view insertSubview:aboutViewController.view atIndex:0];
}
My question is, why is it when I run the application does the ViewDidLoad for both About and Rules fire? Meaning, I get the NSLog messages. Does this mean that regardless of the separate xib files, all views are loaded on start up?
My point of all this is: I want the multiViewController to be my main menu which will have separate buttons for displaying the other xib files when clicked. I had been placing my "initalize" code in the viewDidLoad but this seems wrong now as I don't want to hit until the users presses the button to display that view.
An example was to have a view that is: playGameViewController. In the viewDidLoad I was checking to see if a prior game was in progress and if so, prompt the user if they would like to resume. In the above case, when the app starts, it prompts right away (because the viewDidLoad is firing) even though I only wanted to display the main menu first.
Any explanation is greatly appreciated. Thanks in advance.
Geo...
My question is, why is it when I run
the application does the ViewDidLoad
for both About and Rules fire?
Meaning, I get the NSLog messages.
Does this mean that regardless of the
separate xib files, all views are
loaded on start up?
When you call
[self.view insertSubview:rulesViewController.view atIndex:0];
it's going to first call viewDidLoad for the initial view and then viewDidLoad once again for RulesViewController.
When your MainViewController, or any view for that matter loads, viewDidLoad is called automatically. ViewDidLoad is there in order for you to override or modify any objects in the nib, or you can create objects yourself. Views are only loaded on an as needed basis. If you were to load all your views initially when the app boots up, the user would just see a black screen until all the views are processed.
You say you are going through some tutorials, I don't know your area of expertise yet, but have you looked into navigation based apps using UINavigationController?
Just an example as your request if you want to have a button load a view you can do something like.
- (IBAction)pushSecondView:(id)sender {
SecondViewController *secondView = [[SecondViewController alloc] initWithNibName:#"SecondViewController" bundle:nil];
if (secondView != nil) {
secondView.title = #"Second View";
[self.navigationController pushViewController: secondView animated: YES];
}
}