MVC with view built in code - ios

I've been trying to interpret the lessons from CS193P, and have a few questions.
I'm building views in code, the way I do it is I have a UIView subclass where I put all the views in place in the init method. This class is initialized by the ViewController.
The question is then, what is the right approach from here - say i want to animate a button I placed at 0,0 to 100,100. I'd like to animate it from the ViewController, but i don't like the fact that i set the 0,0 position in the UIView class (in the initializer) and now i am setting a new position in the ViewController. I'd prefer there would be just one place knowing about the actual (x,y) positions of my views.
How am i supposed to go about this?
Move the positions in the initializer to the ViewController
Put a method in my UIView "-(void)AnimateToSecondPosition" where the actual "second position" is then up to the view?
Just let it go. It seems like this would be the right approach if i had placed the button in interface builder - i consider interface builder to be the view then...
Or maybe even a fourth option?
Please help me understand it better and not just give me the right answer ;)
I'd like to be able to compare my approach in some way to how you would do it using interface builder, so each of my views are public and accessable from the controller - this way i believe i could easily start using interface builder instead if i wanted, without changing the controller code, just hooking up the outlets.
I'm guessing the case would be the same for disabling, hiding and doing other things with the views.
Thanks in advance.

If you want to create a new View programmatically you should generally instantiate it in your View Controller using its designated initialiser:
UIView *testView = [[UIView alloc]initWithFrame:myFrame];
If you create a custom view it's totally fine and correct to put some configuration code in the init method, but it's your ViewController that should be in charge of deciding what to do with this view - it is his job! Using the MVC the View and the Model should never communicate directly (as you definitely learned in the first lesson of CS193P).
Therefore the same apply to the animations. You should animate the Views within your ViewController and not implement the animation in the View itself.
Therefore in my opinion you "second position" should be setup by the VC - if this has to be done when something happens to the view (e.g. someone pressing a UIButton) you should set a target/action to your VC and handle this within your VC.
ADDED:
Regarding building UIViews in the Interface Builder I don't know what you mean by "and let them go". Interface builder will create the views and add them to the specific superviews at runtime - as you can see in the example below you control the view hierarchy graphically on the left. For instance in this case there is a UIView (which I coloured green for clarity) and two buttons. One is a subview of the main view while the other is a subview of the green UIView.
Once your ViewController is loaded the view hierarchy is automatically loaded to self.view - in fact if you run the following code in your VC when it is loaded you will see the list of self.views subviews in your console.
for (UIView *view in self.view.subviews){
NSLog("%#", [view description]);
}
If you know already that you need to change some attributes of a specific UIView you setup via Interface Builder (e.g. we know we want to change programmatically the color of the green UIView in the example above) you should create an outlet which allows you to have a reference to that view in your code. You do it by crtl-drag from the storyboard to your ViewController code - see the example below.
When you have done that you can refer in code to this as any other property, with the difference that it has been created by Interface Builder.
Hope this helps.

You can add an -setButtonFrameToSecondPosition to the view subclass, which simply updates the frame of the button, and then call that from the view controller via one of the +[UIView animate:...] methods.

Related

Difference in view hierarchy when adding a child VC in code or IB

Iv'e noticed something quite weird, when I add a child VC to a view hierarchy in IB the hierarchy looks like this
parent vc view
- -container view
- - - -child vc view
when adding it by hand
parent vc view
- - child vc view
Following the instructions from apple, in their guide, they never talk about the container view as part of the hierarchy, however in the code in practice, for example when I am trying to call
- (void) hideContentController: (UIViewController*) content {
[content willMoveToParentViewController:nil];
[content.view removeFromSuperview];
[content removeFromParentViewController];
}
the container view is still "polluting" my view hierarchy. I don't understand the relationship between that container and my child VC.
A practical example in my code, is that I put these viewControllers in a UIStackView and when trying to remove the UIViewController that was inserted with an Embed Segue, I remain with a phantom view. The only way I could find to get access to that view, is an IBOutlet from storyboard.
Anyone have experience in handling, replacing or removing ChildViewControllers added with IB? Or can explain where the difference is coming from and how to get rid of it?
I would say that the "container view" you can use in IB is a bit, well... misleading?
If you select it in the Object Library pane, you get an info popup that lists it as UIContainerView. However, if you search Apple's documentation, that is nowhere to be found.
And, as you have probably noticed, if you connect it with an IBOutlet, it gets set as a UIView.... and if you try to do something like:
UIContainerView *vc;
or
let cv: UIContainerView?
you'll get a "Use of undeclared type" error.
What makes it "different" is only how it behaves in IB, and the automatic "load-and-display" of its embedded Child View Controller. This is much like Segues... you can create and "see" them in IB; you can assign properties to them; at run-time they server a purpose / have actions; but... you cannot create a UISegue in code.
So... if you want to use the visual design-and-embed automation of "container views" in IB, you need to be aware that it is added as a UIView to your view hierarchy, and the .view of its embedded VC is added as a subview to itself... and you'll need to use IBOutlet to have a reference to it at runtime if you want to remove it (or size it or change any other property of it).
When you see "code only" implementations of Child View Controllers, you generally also see code for adding the Child VC's view as a subview of the "main" view (or some other view). It just doesn't seem related to the UIContainerView you can use in IB, because it's never referenced that way.
Does that make sense?

Make UIView subview as default UIView for UIViewController with Storyboard

I have a multi-viewcontroller app.
The main view of the main VC is subclassed so I can use Core Graphics animations. It's called "animations" class.
On the storyboard, I add a UIView called "MainArea" as a subview of the "animations". I have a VC called "MainAreaVC". As you know it has a property of "view". How can I make the "MainArea" subview the default UIView of my "MainAreaVC"?
The other option is to just make the "MainArea" view a property of my "MainAreaVC" and use this. I just don't want the "MainAreaVC"'s default "view" property just hanging out doing nothing.
The view in a UIViewController is the bottom view: it is always the one at the root of the tree hierarchy, and appears behind everything else, possibly clipping subviews.
You can change the view Class, but no matter what you do, by design it must be at the root. If, somehow, you managed to point to one of your subviews, that subview would in essence become the root.
Comment:
You do not have to add a view of type animations containing another view of type mainArea. You can have 'MainAreainherit fromanimations, and drag suchMainArea` in the storyboard.

How to create a separate view in Storyboard to be included programmatically in UITableView?

I have a UIViewController with a UITableView that is fed with data from the local database. When the user first launches the app (after installing) the table view is empty and I display a UIView in the middle of the table view that contains a UIImage, a UILabel and a UIButton (a call to action).
The first version of this view I built programmatically, which was no good to me because every time I tweaked something I had to build the app again. Then I switched to the storyboard but had to drag a UIView to the middle of my tableView. It is working now but I don't like the way it is, I can't edit my table view cells without having to move the UIView out of the table view.
I'd like to have a way to build this view entirely separated from my tableView (or even from my view controller in question) and then reference it in the viewDidLoad call of my view controller.
Unfortunately Xcode does not allow us to drag views directly to the storyboard so I'm pretty lost here.
Please tell me if I haven't been clear enough. I appreciate any help you could give me.
UPDATE: It'd be particularly awesome to me if I could also create a custom Swift class for this view of mine and reference it in the storyboard. And then in my viewDidLoad I could simply instantiate my custom view.
Thanks in advance.
Create a XIB file in which you can drag a view (without a view controller).
In your code you can load the XIB using NSBundle.mainBundle().loadNibNamed("MyXibName", owner:self, options:nil).
In the XIB file you can give the UIView a custom class (like you can do with view controllers in storyboard).
You then of course have to retrieve the view from the array returned by loadNibNamed and cast it to your custom class.

Why can't I assign a UIView to self.view?

This might be a stupid question, but I'll shoot.
I made a little test project to test out a concept I had for a sliding view controller type of thing. I naively assumed I could create a UIView (let's call it peekView) with an outlet in a controller, and call something like [slidingControllerSlideFrom:self.view] from any visible view controller, the implementation of such being:
- (void)slidingControllerSlideFrom(UIView*)controllersMainView
{
// push side controller to top of navigation stack
self.peekView = controllersMainView;
// sliding animation
}
But there is no effect. No crash, no warning, no change of view in the pushed controller.
Of course, the pushed controller crashes when trying to add self's view as a subview, but assigning it to a predefined UIView just results in nothing.
So, why? And if a mere 'why' is not enough of a question- what happens when I try to assign one controller's view another controller's subview, and what was the reason for designing UIKit where you cannot set views from self.view?
To do that you have two options:
1 - If the controller in the peekView is always the same one in a given scene, use a "Container View". Those are explained here. Basically, they allow you to add a view in your scene that is managed by another controller.
2 - If the controller in the peekView depends on different conditions, you will have to create something similar to a custom tabbarcontroller. That means that you instantiate the controller that you need, add it's view as a subview of peekView (not assign the controller's view to the peekView itself) and then use didmovetoparentviewcontroller to notify the child controller. This question might help.
UPDATE:
Just saw your comment, so let me answer what you actually asked: The peekview property is actually just a reference to the real UIView you placed in the screen. When you do this:
self.peekView = controllersMainView;
You are changing the reference, but no the view object itself. That's why you are not seeing any changes. There are ways of adding a new view to the controller from code, but it is much simpler to simply use addSubview to add your controllers view to a UIView that is already in the controller.
Check out the discussion here: subView Slide up from bottom of the screen
and here: SubView slide in animation, iphone
Hopefully that gives you a bit of framework on how to approach this task!

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