Calling a single view in different UIViewControllers - iphone

I have declared a UIView inside a UIViewController class. Can I call this UIView in another UIViewController class?
If its possible how can I call it?

Yes, you can use a single instance of a view in a number of views/viewControllers. Typically I do the same with Views that carry advertisements.
You pass them along as you would do with any other object.
If you do not create it in Interface Bulder (I suggest creating it programmatically) then you may want to define it within your application delegate rather than a view controller and pass it to the individual view controllers that make use of it.
Within the view controller just add it as sub view accordingly, as you would do it with any other view too.
There is one thing though. When you add this view to another super view for the second time or more then it will be removed from its prior super view. That means that you will have to add it as super view again, when its prior super view becomes visible again. A view can only be part of one view hierarchy at a time.
Sample code, thanks to Gordon:
/* Untested and simplified */
AppDelegate.h:
#property ( strong, nonatomic) ReuseableView reuseableView
;
AppDelegate.m
#synthesize reuseableView;
/* in didFinishLaunchingWithOptions ...*/
reuseableView = [[alloc] init]; // or init from nib, initwithframe, etc.
viewController.m
/* In each view controller that uses the view */
- (void) viewWillAppear:(BOOL)animated
{
[self.view addSubview:((AppDelegate*)[UIApplication sharedApplication].delegate).reuseableView];
}
- (void) viewWillDisappear:(BOOL)animated
{
[((AppDelegate*)[UIApplication sharedApplication].delegate).ReuseableView removeFromSuperview];
}
I am not quite sure whether this removeFromSuperview is really required. The next addSubview will remove it from its existing superview anyway and if addSubview is called on the same super view twice in a row then it does not do any harm. However, it is save using removeFromSuperview at this point.
Well, summarized that is basically it. You define, create and store your shared view (the reusableView in Gordon's example) at a common place. The application delegate is a good place for that. In each view's controller that uses the shared view you fetch it from the delegate and add it as subview to the current view.

I would subclass UIView and import it on the ViewControllers where I wanna use it
NSArray *nibArray = [[NSBundle mainBundle] loadNibNamed:#"YourView" owner:self options:nil];
yourView = (YourView *)[nibArray objectAtIndex:0];
Then you set its frame and [self.view addSubview:yourView]

Related

Question about the mechanics of iPhone view controllers (i.e., explain why this crashes)

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

Reference the class that a subview was added from

I'm adding a subview to my primary iPad UIViewController, and within that subview I need to reference said view controller in order to play a video using that controller.
Can anyone help me out with the way that should be done, and possibly a code example?
Thank you in advance.
Regards,
Ed
EDIT (A LITTLE BIT MORE INFO):
This subview is a view from a uiviewcontroller class that is designed for the iPhone. It's a table that loads a video when a row is pressed. The movieplayer loads the video within the referenced viewcontroller (which is why I want to reference the iPad view controller from within the subview). The view is basically used within an iPad app in it's same form.
This sounds like an architecture problem. It's not up to your view to tell something to play a sound. That's a controller's job. It's up to your view to tell "someone" that it was touched, or slid, or whatever the user has done. "Someone" (who is watching) will then perform the correct response to that.
To do this, your view should generally take a target, and possibly and action. Look at how UIControl (for example, UIButton) informs other objects that it has been activated. The observer (controller) then reacts accordingly.
EDIT
The view should not load a video. A view controller should load the video and install it into the correct view. The only job the view has is to tell its view controller that it has been pressed. UITableView handles this automatically with the UITableViewDelegate method tableView:didSelectRowAtIndexPath:. If you're not using a UITableView, you should still follow this pattern. The view accepts has a delegate and tells the delegate (controller) that something was selected. Then the controller updates the views with the new data.
You probably already have the main view living in a property of your application delegate (it is commonly assigned via the application's .xib file, look in the app delegate's applicationDidFinishLaunching: method, where it adds the main view as a subview to the window, something like:
[window addSubview:primaryController.view];
[window makeKeyAndVisible];
).
So anywhere in your app where you need to access the main view, you can do:
[(MyAppDelegateType*)([UIApplication sharedApplication].delegate).primaryController somePrimaryControllerMethod];
Edit: while this will work, I agree with Rob Napier that this isn't the best way to do it, architecture-wise.
You could just use [yourSubview superview] to get the superview.
Can't you just add a property to the view like this:
#property (nonatomic, assign) UIViewController *parentViewController;
And on initialization of the view (from within the UIViewController), set the property:
UIView *customView = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 320.0f, 460.0f)];
customView.parentViewController = self;
And from then on you should be able to call the parentViewController from within the view.
Edit: Rob Napier has a good point, it's probably a better idea to set a target and selector from within the view instead of a view controller. This way you should be able to hook up methods directly with the view. The properties would look like this:
#property (nonatomic assign) id target;
#property (nonatmic, assign) SEL selector;
And perhaps add a designated initializer to your view:
- (id)initWithFrame:(CGRect)aFrame target:(id)aTarget selector:(SEL)aSelector
{
self = [super initWithFrame:aFrame];
if (self)
{
self.target = aTarget;
self.selector = aSelector;
}
return self;
}

Call rootViewController to switch views within content view (iOS)

I'm working on a pretty simple multiview app for the iOS and I've been following a great tutorial in an Apress book. I've basically got my rootViewController instantiated and displayed with the app delegate, and I've got a number of content viewControllers (6) which I'd like to swap in and out based on user input. However, in the book they perform their switches with a button on a toolbar placed in the rootView using Interface Builder. It fires a method in rootView that loads up the new content ViewController and displays it.
My problem is that I'd like to perform the content view switch (that lies in my rootViewController instance), but I'd like to trigger the switch action with a button that's in my content view (and is therefore unavailable as my File Owner is my contentViewController, whose reference is held inside my rootViewController).
Hopefully I've explained it well enough, please let me know if I should elaborate more. I appreciate any help!
You need to pass down a reference to your root view controller (RootViewController *rootViewController) when you create your content view either in a custom init method or by just assigning it after you created it: self.contentView.rootViewController = self;.
Now inside your content view you can then call the appropriate method in the root view controller to do the switch: [self.rootViewController switchView]. This call then can be triggered inside the method that is called when you press the button (IBAction method).
So this is what you need to do:
1) Create a property inside the your content view controller of type RootViewController
#class RootViewController;
#interface MyContentViewController : NSObject {
#private
RootViewController *rootViewController;
}
#property (retain) RootViewController *rootViewController;
and make sure it retains the reference.
2) Synthesis the property and add the callback to the root view controller that switches the view:
#implementation MyContentViewController
#synthesize rootViewController;
- (IBAction) switchView:(id) sender {
[rootViewController switchToNextView];
}
-(void) dealloc {
[rootViewController release];
[super dealloc];
}
Also release your retain reference at the end.
3) Assign the root view controller to the content view inside your RootViewController:
self.contentViewController = [[[MyContentViewController alloc]
initWithNibName:#"ContentView"
bundle:nil] autorelease];
self.contentViewController.rootViewController = self;
That should be all. I hope that helps you.
Well, you could simply create an IBAction in each of your child controllers that calls:
[self.parentViewController switchToDifferentController:(int) viewNumber]
and then implement the switchToDifferentController method in your root. Other than ignore the compiler warning that parentView might not implement that method, it might work.
However, that is a bit brittle, as you'd have to assume that it was the parent calling you and that nobody will forget to implement that method.
In general, you use the "delegate" concept for a child controller to ask its parent to do something. The general idea is that you declare a group of methods as a "protocol". Think of it as a contract between objects. One object can say "I promise to implement these methods," and another can then choose to send those messages to it. The contract allows the compiler/system to check for conformance. You'll see this in UITableView, where the OS provides a standard table, but it calls back to your code to provide the individual cells as needed.
To implement a protocol, you mustdo the following: (See code segments below
Declares a protocol for the conversation
Specify that the parent will follows that protocol
Create a delegate property in your child
When the parent is about to launch the child, it assigns itself as the delegate for that child.
When the child wants to switch, it calls the parent using that protocol
#protocol myVCDelegate
- (void)switchToDifferentController:(int) viewNumber ;
#end
#interface ParentViewController : UIViewController <VCDelegate>
#property(nonatomic, assign) id <VCDelegate> delegate
childController.delegate = self;
[self.delegate switchToDifferentController:kController5];

Using a custom UIView from multiple view controllers

I have created a custom UIView that I would like to use on multiple different view controllers in my iPhone application. Currently, I have copied the UIView that contains the controls from the nib file for the first view controller, pasted it into the other view controllers, and then wired up all of the actions to each view controller. This works fine, but is not the optimal way that I would like to use this custom view.
My custom view is reasonably simple, it consists of some UIButtons with labels, and tapping on these buttons fires actions that changes the contents of controls on my view controller's view.
What would be a strategy to consolidate the definition and usage of this UIView? Ideally, I would like to just reference this custom view from the nib of view controllers, and have my view controller respond to actions from this custom view.
EDIT: OK, based on J.Biard's suggestions, I have tried the following with not much luck.
I created another UIView based nib file with the contents (for now just some UIButton objects) of my reusable view and UIView subclass .m and .h files, and then set the nib File's Owner class to my newly created class name.
Then, I added most of the code from J.Biard (I changed the rect to 50,50,100,100, left out the setDelegate out for now, as I am just trying to get it working visually for now, and i found that [self.view addSubview:view] worked much better than [self addSubView:view]) to the end of the viewDidLoad method of the first view controller that is displayed when the app fires up.
And what I get now is my main view with a black square in it. Have I missed an outlet somewhere, or is there some initialization needed in initWithFrame or drawRect in the UIView subclass?
Create your MyCustomView class and nib file.
In the nib file, set Files Owner to MyCustomView - then design your view as normal, with a top level UIView. Create an IBOutlet in MyCustomView to link to your top level UIView in your nib file.
In MyCustomView add this method:
- (BOOL) loadMyNibFile {
if (![[NSBundle mainBundle] loadNibNamed:#"MyCustomView" owner:self options:nil]) {
return NO;
}
return YES;
}
In your view controllers you can use this custom view like so
- (void)viewDidLoad {
MyCustomView *customView = [[MyCustomView alloc] initWithFrame:CGRectMake(0, 0, 320, 44)];
[customView loadMyNibFile];
[self.view addSubview:customView.view]; //customView.view is the IBOutlet you created
[customView release];
}
You could also create a convenience class method on MyCustomView to do this if you liked.
If it is very simple I would recommend that you create a subclass of UIView in code and create instances of this class (or you can use Interface Builder to create the custom UIView that is then archived into the NIB file and restored later on also using code).
Using the code solution you could create instances of your custom UIView in your controller by calling something like:
#import "MyCustomView.h"
// let the superview decide how big it should be or set it as needed.
CGRect sizeRect = CGRectMake(0,0,0,0);
// create an instance of your view
MyCustomView *view = [MyCustomView alloc] initWithFrame:sizeRect];
// set a custom delegate on the view or set callback methods using #selector()...
[view setDelegate:self]; // self = view controller
// add the view to the controller somewhere... (eg: update CGRect above as needed)
[self addSubView:view];
// don't forget to release the view somewhere ;-)
This example assumes that you create a delegate protocol that your View Controller can respond to or you can wire up events dynamically using #selector. If you don't want to create instances of the view in code you could add a "UIView" to your NIB file and set it's class type in the inspector window (command -> 4 -> class dropdown).
If you want to do everything in interface builder you can create your custom UIView and use something like "- (NSArray *)loadNibNamed:(NSString *)name owner:(id)owner options:(NSDictionary *)options" (see NSBundle) to load the NIB file dynamically. This presents it's own challenges though it is also feasible.
The most involved option would be to create your own xcode custom UI library / plugin so that your custom control / view could be dragged into each NIB file from the Library window like any other control shipped by Apple.
Hope this clarifies or eliminates some options for re-using controls for you.
Cheers-

Nib objects (subviews) accessing properties in ViewController

Edited for brevity:
How does a subview access properties in its superview and its superview's view controller? Easy enough to go down the chain. How do we go back up?
Original (verbose) post:
The immediate problem I'm trying to solve is simplifying the "birth" of a somewhat complex view. The large picture has to do with Nibs and how subclasses (of UIView in particular) that have beyond trivial initializers are reconstituted when the view loads.
I have some custom UIViews - subviews of a subview of my viewcontroller's view. When I instantiate these views in particular they need a reference to some properties (NSNumberFormatter, & NSDictionary) of the View Controller.
Currenty I use a method to instantiate them:
- (id)initWithFrame:(CGRect)frame items:(NSDictionary *)dictionary forEditingMode:(EditingMode)mode
I'm experimenting with moving them into a Nib to let them reconstitute themselves and running into basic design issues. One being I think I'm limited to initWithFrame as the default initializer?!? If so how can these objects look into the parent view controller and get a reference to the dictionary and some other properties?
Are there methods I could call within initWithFrame (similar to the ones that retrieve the UIApplication delegate) but would instead allow the child view to send methods to it's parent views and/or controllers?
Send in the MVC police, I'm sure I'm breaking something...
It should work the other way round.
The views only contain controls (text fields, etc.). The data lives in a model, and the view/window controllers mediate, accessing and setting the view controls values, synchronizing with the model.
OK, sometimes you may need to have a dictionary shared between the controller and the view. Then create a dictionary property in the view and set it in the awakeFromNib method of the nib owner.
You can set up outlets that get connected to the view's superview or view controller and pull out the info in awakeFromNib. You need to use awakeFromNib instead of init* because the connections won't be created until then.
FYI, if you instantiate via a Nib, your designated initializer is initWithCoder:, not initWithFrame: (but don't use either for this).
You should probably be doing this through the view controller manually just after the nib is loaded. This means that you will have to set the shared dictionary AFTER the view has been initialized. You might do it like so:
#interface MyViewController : UIViewController {
IBOutlet MyView* view1;
IBOutlet MyView* view2;
IBOutlet MyView* view3;
}
#end
#implementation MyViewController
-(void) viewDidLoad;
{
NSDictionary* sharedDict = //make the shared dictionary here;
view1.sharedDict = sharedDict;
view2.sharedDict = sharedDict;
view3.sharedDict = sharedDict;
}
#end