Same UIView subclass in several UIViewController subclasses? - iphone

I have several UIViewControllers. I would like to use the same UIView subclass (which goes on top of the existing UIViewController's view) in all of them. Is this possible using Interface Builder?
I mean, I would like to be able to drag a UIView onto each UIViewController's view, and rename the class of this dragged UIView to CustomView, and all the elements within the CustomView would show up... is this possible?

Based on your question and your response to highlycaffeinated — whose answer is correct but I think may be slightly askew from what you're asking — I think you want to be able to design a view graphically within Interface Builder (so, you haven't written a custom UIView subclass, you've just arranged some UIViews in a certain way and so that they're all children of another view), then embed it into several view controllers via some sort of indirect reference, so that you're not copying and pasting the same user interface elements and if you make a change in one place, that change then takes effect everywhere?
As far as I'm aware, there's no built-in facility in Interface Builder or Xcode 4 for achieving that. XIBs are pure data and UIViews don't have the smarts to handle an out-of-file reference.
What you can do is design the view you want to use in one XIB, called say ReusableView.xib, then write a custom UIView subclass that looks something like:
#implementation ReusableViewTemplate
- (id)initWithCoder:(NSCoder *)aDecoder
{
// initialise ourselves normally
self = [super initWithCoder:aDecoder];
if(self)
{
// load everything in the XIB we created
NSArray *objects = [[NSBundle mainBundle]
loadNibNamed:#"ReusableView" owner:self options:nil];
// actually, we know there's only one thing in it, which is the
// view we want to appear within this one
[self addSubview:[objects objectAtIndex:0]];
}
return self;
}
#end
Then, in your NIBs put in a UIView where you want the reusable view to go, and set the 'class' to 'ReusableViewTemplate' or whatever you called it.
If you open the ReusableView XIB and set the type of the parent view to ReusableViewTemplate, then you can wire up any UIControls (such as buttons or switches) to connect to there. You'll probably want to define some custom protocol for your reusable view template and catch viewDidLoad in any view controllers that use the reusable view in order to set an appropriate delegate.
EDIT: further thoughts on this. I've created an example project (currently at a generic file sharing site, so may not survive forever) with a class ReusableView that, for the purpose of example contains a segment view and a button, and looks like this:
#implementation ReusableView
/*
initWithCoder loads the relevant XIB and adds its
only object, which is a UIView, as a subview of this
one. If you don't like the double hierachy, you
could just have a list of views in the XIB and
addSubviews:, but then it'd much more difficult to
edit the thing graphically. You could strip the top
view programmatically, but this is just a simple
example, so...
*/
- (id)initWithCoder:(NSCoder *)aDecoder
{
// initialise ourselves normally
self = [super initWithCoder:aDecoder];
if(self)
{
// load everything in the XIB we created
NSArray *objects = [[NSBundle mainBundle]
loadNibNamed:#"ReusableView"
owner:self
options:nil];
// actually, we know there's only one thing in it, which is the
// view we want to appear within this one
[self addSubview:[objects objectAtIndex:0]];
}
return self;
}
#synthesize delegate;
#synthesize segmentedControl;
#synthesize button;
/*
NSObject contains machinery to deal with the possibility
that a class may be sent a selector to which it doesn't
respond.
As of iOS 4, forwardingTargetForSelector: can be used to
nominate an alternative target for the selector quickly.
In previous versions of iOS, or in iOS 4 if you don't
respond to forwardingTargetForSelector:, you may take
delivery of the problematic invocation and deal with it
yourself.
Dealing with the invocation costs more than providing
a forwarding target for the selector, so its worth having
both.
If you're only targeting iOS 4 or above, you needn't
keep the implementation of forwardInvocation: below.
What we're doing is declaring a bunch of IBActions so
that we can wire changes up to them in Interface Builder.
By failing to implement them and providing the delegate
as the thing to talk to for any selectors we don't know,
we're allowing those wirings to be as though assigned
to the delegate.
*/
- (id)forwardingTargetForSelector:(SEL)aSelector
{
return delegate;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
[anInvocation setTarget:delegate];
[anInvocation invoke];
}
#end
With interface:
#interface ReusableView : UIView
{
IBOutlet id delegate;
IBOutlet UISegmentedControl *segmentedControl;
IBOutlet UIButton *button;
}
#property (nonatomic, assign) id delegate;
#property (nonatomic, assign) UISegmentedControl *segmentedControl;
#property (nonatomic, assign) UIButton *button;
/*
NB: we're not actually going to implement these. We're
declaring them for the benefit of Interface Builder / Xcode 4.
What we'll actually do is, any time we receive a selector
we don't implement, hand it off to the delegate. So it's a
quick way of avoiding writing any glue code to pass messages
from this little reusable view to its delegate.
A better alternative could define a formal protocol that
forwards both the changed control and self from the
reusable view to its delegate. But that's obvious and
verbose, so has been omitted for the purposes of example.
The implementation as stands will generate compiler warnings,
but such is life. To get rid of the warnings, comment out
the two following lines, but only AFTER you've wired
everything up in Interface Builder / Xcode 4. They're left
uncommented here to help draw attention to the point about
selector/invocation forwarding that you'll see in the
#implementation.
!!!!!!!!!!!!!!!
HENCE:
delegates MUST implement the following methods.
!!!!!!!!!!!!!!!
We could work around that by checking at runtime whether
the actual delegate implements them and forwarding to
a dummy object that implements them to do nothing otherwise,
but that's slightly beyond the point of the example.
*/
- (IBAction)reusableViewSegmentControlDidChange:(id)sender;
- (IBAction)reusableViewButtonWasPressed:(id)sender;
#end
Net effect is that if a view controller has a UIView of type ReusableView within a XIB, it gets the contents of ReusableVew.xib inserted at runtime. If it wires itself up as the delegate of ReusableView within Interface Builder / Xcode 4 and implements:
- (IBAction)reusableViewSegmentControlDidChange:(id)sender;
- (IBAction)reusableViewButtonWasPressed:(id)sender;
Then it gets the messages from the embedded views.
This is achieved very simply and very neatly in Objective-C by using NSObject's inherent ability to forward selectors (as of iOS 4) or invocations (in earlier versions, at a greater cost) that it doesn't implement rather than allow an exception to occur.

Yes, it's possible. Just as you could have (for instance) multiple UIViewControllers in your project, each with a UIImageView as a view, you can do the same with your own subclasses of UIView.

Related

Subclass UIViewController or create a custom NSObject when the view is not fullscreen

I need to create a class controller to manage the behavior of a custom view I created.
The standard approach is to subclass UIViewController, but in my case I instead decided to
subclass the NSObject essentially for three reasons:
my view needs to be added as small subview of the main view controller (it will not be displayed using something like presentModalViewController or pushViewController...) and it does not require any kind of toolbar or navigation control inside of it
Most probably my controller will not need to be notified for device orientation because its view will be always used in portrait format, so I'm not interested to receive the usual rotation messages willRotateToInterfaceOrientation etc...
I need to keep this class as lightweight as possible minimizing memory consumption. Not subclassing UIViewController have the advantage to obtain a lighter class without a bunch of methods that I will never need to use
The interface of my controller is pretty simple, example:
#interface MyScrollTabBarController : NSObject <MyTabBarViewDelegate> { }
/**
* The view is created internally by the controller and the client class
* can access to it in readonly mode
*/
#property (nonatomic, readonly) UIView *view;
/**
* A Property to change the view appearance
*/
#property (nonatomic, assign) MyScrollTabBarViewState viewState;
/**
* Others properties used to construct the view's subviews
*/
#property (nonatomic, retain) Location *rootLocation;
#property (nonatomic, readonly, retain) Place *place;
/**
* Designated initializer
*/
- (id)initWithPlace:(Place *)aPlace;
- (void)setRootLocation:(Location *)location animated:(BOOL)animated;
#end
To display its internal view from the parent view controller, I will use something like this:
tabBarController = [[MyScrollTabBarController alloc] initWithPlace:aPlace];
tabBarController.viewState = MyScrollTabBarViewStateXX;
tabBarController.view.frame = CGRectMake(...);
[self.view addSubview:tabBarController.view];
I'd like to know what do you think about my choice, if you think that there could be drawbacks in it and what do you usually do when you need to write a controller for a view which is not fullscreen like mine.
Thanks
Yes, this is the correct approach.
UIViewControllers are specifically for controlling full-screen views, not for sub-screens. In iOS5 there is a mechanism for composing sub-screen viewcontrollers in this way, but that's not available in iOS4 without lots of hackery.
In cases where the view and controller are inherently coupled, you could also consider making a custom view subclass that is its own controller, so for example you could have a self-contained table view subclass that managed its own data and could just be dropped into a page.
I think this is an acceptable solution.
Another solution would be creating a "fat" view that does its controlling itself (like, for instance, MKMapView, UITextView etc.). This might make things a little more manageable, and if the view is very specialized, and its controller is intended to only work with this one class of view, you don't really lose any reusability (because there isn't much).
what do you usually do when you need to write a controller for a view which is not fullscreen like mine
It is not important that your view is not displayed full screen. It is possible (and usual) to have views consisting of subviews which each have their own controller.
I need to keep this class as lightweight as possible minimizing memory consumption. Not subclassing UIViewController have the advantage to obtain a lighter class without a bunch of methods that I will never need to use
Subclassing UIViewController does not consume an unreasonable amount of memory, so this should not be part of the consideration.
[...] if you think that there could be drawbacks in it [...]
With your solution you loose flexibility. It is likely that you will reuse your solution in a context where you need to respond to UILifecyle-Messages or use other UIViewController features.
If your views shall be lightweight you could consider using a UIView subclass and use a delegate for the logic behind your view.
Hi You are subclassing NSObject and declaring a UIView inside it
#interface MyScrollTabBarController : NSObject <MyTabBarViewDelegate> { }
#property (nonatomic, readonly) UIView *view;
I Suggest you should subclass UIView, so you will not have to declare an additional view object.
so instead of self.view you can simply refer as self
tabBarController = [[MyScrollTabBarController alloc] initWithPlace:aPlace];
tabBarController.viewState = MyScrollTabBarViewStateXX;
tabBarController.frame = CGRectMake(...);
[self.view addSubview:tabBarController];

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];

iPhone: Switching Views From Outside Root Controller

I am using a UINavigationController to switch between views. What I would like is for each view to have the ability to control when it is swapped out for another view by having buttons within the view. All of the samples I've seen thus far have placed buttons on a toolbar, which is located on the root view containing the Switch View Controller rather than the views, them self. Is it possible to do what I want? I can't figure how to wire up the connection back to the UINavigationController.
I'm having a difficult time wording this, so please feel free to let me know if you need additional clarification.
Read about delegates. Delegates are a common method to signal stuff from objects to their "parents" or any other objects.
You should have a "delegate" property (can really be called anything, this is just a convention) on your child views. You can have buttons in your child views.
You declare the delegate like this:
interface ChildView : UIViewController {
id delegate;
}
#property (assign) id delegate;
implementation ChildView
#synthesize delegate;
Then, when you set up your child views inside your UINavigationController, you do:
ChildView *childView = [[ChildView alloc] init...]
childView.delegate = self;
Inside your child view, you have a button method:
- (IBAction) didPressButton:(id)sender {
[self.delegate didPressButtonToSwapView];
}
Inside your UINavigationController, you have a method:
- (void) didPressButtonToSwapView {
[self popViewController]; // use the right names, I made these up :)
[self pushAnotherViewController];
}
You should also read about protocols which would make the above code more robust and would help you make sure you only call the right methods on delegate, but I did not want to complicate this example.
EDIT: yes, the cleanest way to get rid of the warning is to use a protocol. Just put this in a separate .h file:
#protocol SwitchingDelegate
- (void) didPressButtonToSwapView;
#end
Include this .h in the UINavController header, and say the UINavController implements the protocol:
#interface MyNav: UINavController <SwitchingDelegate> { ...
Implement the method in the implementation (you don't need anything more in the interface).
In your ChildView, say that the delegate must implement the protocol: change all the declarations to:
id<SwitchingDelegate> delegate;
The compiler then helps you by checking whether the delegate objects really implement the protocol. You should not get any warnings when you have completed all of this correctly.

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

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.