An UIViewController inside other UIviewController and IBActions - ipad

I'm designing an iPad app where little UIScrollViews have their own UIViewController . Those controllers have in their view a button that call an IBAction method. But it isnt working, in fact, it doesnt seem that they are being pressed in the simulator.
Here is some code to give you an idea of what im am doing .
// In UIViewController A (say the parent or root that have several UIScrollViews)
MiniViewController * mini = [[MiniViewController alloc]init];
[scrollView1 addSubview:mini.view];
//repeat the same process a couple of times with different uiscrollsviews and instances of miniviewcontrollers
Now The MiniController is very simple as you can guess, i only post the .h file
#interface MiniControlador : UIViewController {
IBOutlet UIButton * button;
}
#property (nonatomic, retain) IBOutlet UIButton * button;
- (IBAction)doSomething:(id)sender;
#end
You can see that i used Interface builder to connect an UIButton "button" to a method called doSomething. But as i already said, it isnt working.
One more thing. I also tried to add a button to the UIScrollView with the Mini Controller instance programmatically.And it worked! But I certainly believe that it's extremely hardcoded.
What do you think? I'll appreciate any suggestion(s).

Apple's View Controller Programming Guide is an important read, and explains a lot about Apple's philosophy of one-view-controller-per-screen.
Much of the behavior of view controllers is built on the assumption that there is only one view controller operating at a time. When that assumption is violated, the behavior is undefined (or at least undocumented). In this case, your description suggests that the normal view controller behavior of inserting the controller into the responder chain between its root view and that root's superview (usually the previous screen) isn't working.
While you may find methods of initialization that do work properly, they're not going to be guaranteed to work, and the behavior is liable to change with future OS updates.
Edit: A relevant quotes from the View Controller Programming Guide:
Each custom view controller object you create is responsible for managing all of the views in a single view hierarchy. In iPhone applications, the views in a view hierarchy traditionally cover the entire screen, but in iPad applications they may cover only a portion of the screen. The one-to-one correspondence between a view controller and the views in its view hierarchy is the key design consideration. You should not use multiple custom view controllers to manage different portions of the same view hierarchy. Similarly, you should not use a single custom view controller object to manage multiple screens worth of content.

Thanks guys, I finally solve this using objects of a class (that I called GenericViewController). It actually acts like a regular UIViewController, the IBActions responds well to any event (i.e. buttons being pressed).
I used an IBOutlet UIView in order to contain UILabels, buttons...and so on.
Here is some code if anyone is interested.
#interface GenericViewController : NSObject {
/* Some IBOutlets here*/
//like a regular UIView of an UIViewController, this holds the rest of the outlets
IBOutlet UIView * view;
}
//some IBActions here
}
Then the UIScrollView only add the view of each GenericViewController object
[scrollView addSubview:genericViewControllerObject.view];
If anyone has a better solution, please let me know :)

Are you sure you are loading the view from the xib you made in InterfaceBuilder?
I'm doing something similar in my app, and it's working for me.
I'm implementing the init method like this:
- (id)init
{
if (self = [super initWithNibName:#"__your_xib_name__" bundle:[NSBundle mainBundle]])
{
// TODO: Add additional initializing here
// ...
}
return self;
}
If you are not loading the view from the xib, then there will be no connections made (no IBOutlets initialized and no IBActions triggered).

Related

providing common functionality for different viewcontrollers in objective c

I have many viewcontrollers which needs to have some common functionality related to navigation.
Earlier I made a base class BaseViewController(extending UIViewController) which have all common functionality (like doing some tasks on viewDidLoad etc) and all my viewcontrollers extends BaseViewController.
The problem is that some of my viewcontroller should be subclass of UIViewController and some of UITableViewController, so I can not use above approach.
One way could be to write base class for both and duplicating code. Is there any better way without duplicating code.
While you can get around this by using delegation or helper objects, I would make the case for just not using UITableViewController. It is only a very light subclass on top of UIViewController, providing a table view, conforming to the delegate & data source protocols, and adding a property or two for selection & refresh.
While I wouldn't normally suggest recreating something that the framework has already done for you, it may (in your case) make your code more easy to understand if you just keep everything inheriting from a common base class and add a table view to one of the subclasses.
If you do think this would be a reasonable approach, the UITableViewController documentation overview gives a detailed description of exactly what & where these behaviours are implemented, so mimicking its exact setup is trivial.
Adding a table view to UIViewController
#interface ViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>
#property (nonatomic, strong) IBOutlet UITableView *tableView;
#end
In your storyboard, drag a "Table View" from the object library and drop it on top of your View Controller scene's "View" in the Document Outline - this will replace the root view with a UITableView.
Then just hook it up:
ctrl-drag from the view controller to the table view to hook up the view and tableView outlets
ctrl-drag from the table view to the view controller to set the delegate and dataSource outlets.
Done - no magic required.

Can multiple container scenes use a common view controller?

I've got four scenes in my storyboard. One scene is acting as the parent to all the others via container views. Everything is arranged like so:
If you squint, you'll notice that all four are subclassed from the same view controller. I did this so that I could connect each scene's elements to a single, common view controller and avoid subclassing UIViewController four times. The ProductDetailViewController implementation looks like this:
#implementation ProductDetailViewController {
// Scene 1
__weak IBOutlet UINavigationBar *_navigationBar;
// Scene 2
__weak IBOutlet UILabel *_productName;
// Scene 3
__weak IBOutlet UILabel *_typeNameLabel;
__weak IBOutlet UILabel *_categoryNameLabel;
__weak IBOutlet UIImageView *_richImage;
// Scene 4
__weak IBOutlet UIImageView *_productImageView;
}
The problem is that viewDidLoad fires four times (obviously) and things are showing up blank. When I step through the debugger, the product object I'm passing around is nil for three cycles and then initializes on the fourth. Maybe the view controllers are loading out of order?
In any case, is this setup even ok to do? I'm thinking there's got to be a better way to avoid a subclassed view controller for every storyboard scene.
Setting multiple .xibs to be the same class is common. Not wanting to subclass UIViewController four times is also common.
One problem is that by doing this with Child View Controllers embedded in a Parent View Container and all the same View Controller class, you are creating four instances of the class that are all different and when writing you can't assume anything about which one you are. No wonder the product object is getting lost.
This structure can be salvaged, and it has a lot to recommend it, but it will take some arrangement. A good way is for the parent-delegate to make all the decisions.
Give the class a delegate property, of its own kind, of course weak (weak because the child can't keep the parent alive). Throughout, this you can check to see if it's nil, because if it has a delegate it's a child, but if it has no delegate it's a parent. You usually don't need to know, though.
Use Segue Identifiers. In IB, give each of those embed segues an identifier. Then, in your View Controller class, implement prepareForSegue:sender: with the body
if ([segue.identifier isEqualToString:#"ProductNameIdentiferInStoryboard"]) {
/*...*/ }
else if ([segue.identifier isEqualToString:#"typeBarIdentiferInStoryboard"]) {
/*...*/ }
else if ([segue.identifier isEqualToString:#"productImageIdentifierInStoryboard"]) { /*...*/ }
Implement three properties, again of the same kind, each representing a childViewController,
#property ProductDetailViewController *productNameViewController;
#property ProductDetailViewController *productTypeBarViewController;
#property ProductDetailViewController *productImageBarViewController;`
In prepareForSegue from step 2, fill in with the pattern:
self.productNameViewController = segue.destinationViewController;
self.productNameViewController.delegate = self;
Note prepareForSeque:sender: fires before any viewDidLoad, so you have the references: do all the setup here from the parent's point of view. self.productNameViewController.titleLabel.text = #"Product Name"; The children will fire first, regardless sending a message to nil does nothing, so it's fine they play with their imaginary children. They have a delegate if they need to send information up or ask something important.
This may seem cumbersome, and the identifier stuff definitely is. However, the benefits of keeping centralized control in the code while working with spread out layouts in IB are obvious as a way to handle view controllers.
You definitely can have a single view controller class shared by these four scenes, but there will be four instances of that view controller class, not one.
Having said that, I would advise against this approach and I would suggest either:
Keep the storyboard layout you have, but use a unique view controller class for each child scene, each with its own unique IBOutlet references.
If you'd like the parent view controller to have access to data entered in the child view controllers, you can obviously have the child view controllers update the parent view controller (e.g., using delegate-protocol pattern). But I wouldn't personally expose the child's UIKit properties to the parent view controller (a view controller has no business accessing the UIKit objects of another controller's view), but rather pass back the model data.
If you don't want separate controllers, just don't put these child views in separate scenes. Your example does not appear to be a very compelling use of view controller containment, anyway. In my mind, those child scenes would have to have some reasonable degree of complexity to justify the use of a separate scene (and thus justify separate view controllers). If they're not that complicated, you'd just add subviews to the parent view controller, which is much easier, rather than using container views and the view controller containment that entails.

Cannot remove a uiscrollview

I am working on an iPad app and i utilise a UISplitview for my program.
Now on the main detail view of my program i have a uiscrollview on which i add two labels.
UIScrollView *scroll=[[UIScrollView alloc]initWithFrame:CGRectMake(0,0, self.view.frame.size.width,self.view.frame.size.height)];
scroll.contentSize=CGSizeMake(320, 1600);
scroll.showsHorizontalScrollIndicator=YES;
scroll.backgroundColor=[UIColor clearColor];
[self.view addSubview:scroll];
This is the code i create on the first main page. Now imagine we push the second view, from that second view i can access everything by saying
[self.detailViewController.view addSubview:detailViewController.Image];
but when i try to add labels to the subview saying
[self.detailViewController.view.scoll...
but i cannot find the scroll object, BUT the background of the scroll set in the first view comes over in the second view. AND i cannot change the background of the first view.
I decided to make a second scrollview untop of the first (which works) but i much rather know how i can access the first view i created throughout the whole program since it would negate me having to waste space creating scroll views. but if i have to create all the views i need i want to be able to somehow delete or release them so the picture from scrollview one doesn't get transferred all the way too scrollview 3
Thank You
You have to create properties for all the variables that you want to access from other classes . So in your case
DetailsViewController.h
#interface DetailsViewController : UIViewController {
// class member variables here that can be accessed
// anywhere in your class (only in your class)
}
#property(nonatomic, strong)
//retain instead of strong if you are not using ARC or other types (weak, copy, readonly)
SomeClassThatYouHave *propertyThatCanBeAccessed
//declare here public instance methods
#end
In your DetailsViewController.m you will have:
#interface DetailsViewController (Private)
//declare private methods here or private properties
#end
#implementation DetailsViewController
#synthesize propertyThatCanBeAccessed;
//methods implementation here
#end
Now you can access the property of your DetailsViewController like detailsViewControllerInstance.propertyThatCanBeAccessed but you must alloc/init the instance.
Hope that this will give you an idea of class structure in the future.

Should UIViews have properties?

Using proper MCV with Objective-C can a UIView subclass have #propertys?
i.e. in the .h file
#class MyViewSubclass;
#interface MyViewSubclass : UIView
#property (strong, nonatomic) UILabel *labelLabel;
#property (strong, nonatomic) UILabel *valueLabel;
#end
or should this be done in a UIViewController subclass?
Thanks in advance.
It is most common to subclass UIViewController to manage the labels, fields, images, and other views within a view hierarchy. However, if you are creating a reusable component view that will be used throughout your application, then it's perfectly appropriate to subclass UIView and add properties to your subclass.
From Apple's iOS App Programming Guide:
View controller objects manage the presentation of your app’s content on screen. A view controller manages a single view and its collection of subviews. When presented, the view controller makes its views visible by installing them in the app’s window.
The UIViewController class is the base class for all view controller objects. It provides default functionality for loading views, presenting them, rotating them in response to device rotations, and several other standard system behaviors. UIKit and other frameworks define additional view controller classes to implement standard system interfaces such as the image picker, tab bar interface, and navigation interface.
http://developer.apple.com/library/ios/#documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/AppArchitecture/AppArchitecture.html#//apple_ref/doc/uid/TP40007072-CH3-SW1
It's good for views to have properties, but don't mix model logic into a view. The properties in a view should describe how the property looks, not what the property holds. I would avoid having a property named valueLabel in a view.
An example of view property names is UITableViewCell. It has properties imageView, textLabel, and detailTextLabel.
It's perfectly reasonable for a UIView subclass to have properties. You might need them to implement layoutSubviews, for example.
It is perfectly reasonable, also if you want to create a reusable component that allows for interaction and better flexibility, take a look at UIControl (which is a subclass of UIView)

Custom views with Storyboard

In complex screens (View Controllers) I used to separate the whole thing in smaller pieces (I call them widgets). These widgets consist basically of a MyWidget.h and a MyWidget.m file as well as a MyWidget.xib file, where the root element is a UIView and the MyWidget class is the File Owner of the UIView. In the init of this widget I do a loadNibNamed.
In my View Controller I then do a [[MyWidget alloc] init], which I add to View's Controller main view as a sub view. This, so far, works perfectly.
I'm now wondering, how to do the same with storyboard, because I cannot really start to drag in a UIView somewhere, I always have to start with an UIViewController, which I don't want to.
If there is no possible way doing this with a Storyboard, can I simply do it the old way, by using the Storyboard for my main screens and segues, and use a separate .xib file to define custom views?
Putting the widget/view in a separate .xib file works, and is appropriate especially if you might want to reference that same view from multiple View Controllers.
However, sometimes you do want to see the additional view/widget within the same storyboard, and it is possible. Here's how you do it:
Select your view controller in IB (click on the black bar below the view), then drag a UIView from the Object Library into the black bar:
When a view is in the black bar, it's instantiated like any other view in IB but just isn't added to your view hierarchy until you do so in code. Change the view's class to match your own subclass if necessary:
You can hook it up to your view controller like you would hook up any other view:
The added view shows up in your Document Outline and you can hook up actions and references there too:
Now, the problem that remains is that you can't actually see the view no matter how many times you try to click or double click, which would defeat the whole purpose of putting it in the same storyboard. Fortunately there are two workarounds that I know of.
The first workaround is to drag the view from the black bar back into your view controller's view, edit it, then drag it back into the black bar once you're done. This is troublesome but reliable.
The other workaround is more finicky, but I prefer it because it lets me see all my views at the same time:
Drag a UITableView from the Object Library into your newly added view.
Then drag a UITableViewCell into that UITableView.
Once you do that, your view pops out magically by the side, but you have a UITableView that you don't want. You can either resize that to 0x0, or you can delete it and your UIView will (usually) still stay visible.
Occasionally the secondary view will become hidden again in IB. You can repeat the above steps if you deleted the UITableView, or if the UITableView is still in the hierarchy you just need to click on the UITableViewCell and the view will appear again.
The second method works for UIViews but not so well for UIToolbars and is impossible for UIButtons, so the cleanest solution I've found when you need to include lots of different subviews is to attach a single secondary UIView to your view controller as a container that never gets shown, put all your secondary views in there, and use the UITableViewCell trick to make everything visible. I resize my dummy UITableView to 0x0 to make that invisible. Here's a screenshot of how it all looks like together:
If you're just looking to make your view controllers else-where(and not in your story-board), then there's a pretty simple way to accomplish this:
1) Create your CustomViewControllers(abcdController in the code I tried) with their individual xibs as usual.
2) Add a UIViewController(or whatever was the superclass of your CustomViewController) to the story-board.
3) Set the CustomClass to CustomViewController instead of UIViewController as shown here:
4) Finally, in your viewDidLoad, load the custom xib and you're done.
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSBundle mainBundle] loadNibNamed:#"abcdController" owner:self options:nil];
// Do any additional setup after loading the view from its nib.
}
I think you can do something like this to get instance of specific viewcontroller from Storyboard and use view on top of it.
ex:
MyViewController* myViewController = [[UIStoryboard storyboardWithName:#"Main" bundle:nil] instantiateViewControllerWithIdentifier:#"myViewController"];
UIView* view = myViewController.view; //Get the view from your StoryBoard.
Hope this helps
Thanks
Vijay

Resources