Accessing and manipulating parent view controllers views from a container view IOS - ios

I am making an app where I have a container view that is half the screen of my view controller. From the container view controller's class I am trying to access and manipulate a view that sits out side of the container view. (picture below)
I am trying to access and add items to the scroll view from the container view class like so:
parent = (GFProfileViewController*)[self parentViewController];
UIScrollView *scroll = (UIScrollView *)[parent.view viewWithTag:222];
parent.titleHolders.contentSize = CGSizeMake(320 * 4,60);
UILabel *testLabel = [[UILabel alloc] init];
[testLabel setFrame:CGRectMake(0, 0, 100, 40)];
[testLabel setText:#"My Test label"];
[parent.titleHolders addSubview:testLabel];
scroll.backgroundColor = [UIColor blueColor];
how ever does not work. I tried even accessing the view from the parents "view with tag" method.
neither works.
I know the code is fine because when I move it to the parent vc all works as expected. I need to be able to manipulate the view from the container though. Can anyone help?

As nhgrif says, don't do that.
You should treat another view controller's views as private.
Again, as nhgrif says, create a public method in the parent view controller that takes the information needed and does the displaying itself.
If the view controllers are just being initialized then the parent view controller's view hierarchy may not exist yet. In that case you'd want to set properties to hold the value(s) you want to display, and then display them in your viewWillAppear method.
With storyboards and iOS >= 6, you can set up the child view controller using an embed segue, and then in your prepareForSegue method you can set the parent view controller up as the child view controller's delegate. That's a clean way to have the child communicate back to the parent. (I have a sample app on github that demonstrates this technique if you need a more detailed explanation.)

As far as programming ethics, correctness, good practices and whatnot, I think others have that covered. What you want to do surely isn't the clean way to go, but I often find myself going for such shortcuts, but you really need to know what you're doing, and if it isn't coming back to bite you later. The good thing is that it takes one line of code to achieve and has very low overhead. The problem is that such approach is highly dependent on the view structure, so if you change it, it will no longer work.
With that being said. What you tried doesn't work because the 'parentViewController' property isn't set on any arbitrary view. It should only be defined on the main view of the view controller, the one you can access from 'viewController.view'.
I will assume from your comment that your view structure is something like:
UIView
UIView
UIScrollView
Container
So basically starting from Container you need to go up one level and then down to UIScrollView like this:
UIScrollView = [((UIView*)[self.superview.subviews objectAtIndex:0]).subviews objectAtIndex:0];
I am unsure if you can search by tag from the upper view, since the one you're searching for is somewhere nested inside, farther than one level down. Assuming you can actually do that, this should also work and be more fail proof.
UIView *upperView = self;
while([upperView superview]){
upperView = [upperView superview];
}
UIScrollView *scroll = [upperView viewWithTag:222];

Related

Can I create a UIContainerView programmatically?

I'm trying to create a dynamic view flow that uses UIContainerViews to host UIViewControllers. The UIContainerViews (or UIViewControllers) need to be programmatically added to the UIView to allow multiple side by side.
I've been looking around, but can't find any constructors I can use to create my UIContainerView.
Is there a simple way to do this or is it against normal guidelines for creating reusable views?
To sum up, I want to achieve something like this:
var containerView = UIContainerView()
containerView.add(myViewController)
A UIContainerView is just a visual way to add a viewController as a child of another and setting its bounds.
You can do this programatically by adding the second viewController as a child of the first, taking the second's view and placing it somewhere on the first's view.
Something like this;
childVC = [[SomeViewController alloc] init];
[self addChildViewController:childVC];
[self.view addSubview:childVC.view];
[childVC didMoveToParentViewController:self];

MVC with view built in code

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.

How to add childview programmatically in iOS?

I have googled for many days but don't find out. I just know how to do by IB, but not by programmatic way. I think [addsubview:view] is just only add sibling view, not parent-childview. Thanks guy for reading.
It is easy:
//allocate a view (or take it from IB or from where you want)
UIView *newView = [[UIView alloc] initWithFrame:CGRectMake(10,10,100,100)];
//add the view on the actual view (or any other view)
[self.view addSubview:newView];
So this is a parent-child case and not a sibling view.
In fact by [newView superView] you get the parent view.
You might be getting confused by the difference between subviews and child view controllers, which are what's used when we have Container View Controllers that manage & display a number of child view controllers.
Take a look at the "Implementing a Container View Controller" section of Apple's UIViewController documentation, and you'll see how they describe how child view controllers work.
And, to answer your question specifically, here are the four methods that involve Child View Controllers (from the documentation):
Here are the essential methods you might need to call:
1) addChildViewController:
2) removeFromParentViewController
3) willMoveToParentViewController:
4) didMoveToParentViewController:
And, of course, if you're really talking about subviews, there are a bunch of other people excitedly providing all sorts of answers to your question along this front. Hopefully you'll have the correct answer for your problem really quick.
You can subclass a class to achieve this.
Example:
#interface MSCustomView:UIView
Here UIView is the parent class and MSCustomView is the child class
The [UIView addSubView:] function adds a view as a child view to the view you call it on i.e
[view1 addSubView:view2] will add view2 as a child to view1.
In the Apple docs:
http://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/UIView/UIView.html#//apple_ref/occ/instm/UIView/addSubview:
addsubview does just that. If you'll keep adding subviews to the same View you will basically create more siblings to that subview
From the iOS Developer Library:
Views can embed other views and create sophisticated visual
hierarchies. This creates a parent-child relationship between the view
being embedded (known as the subview) and the parent view doing the
embedding (known as the superview).
https://developer.apple.com/library/ios/#documentation/UIKit/Reference/UIView_Class/UIView/UIView.html
AddSubview add a view into parent hierarchy . IOS provide insertSubview too for ad child view. This
http://developer.apple.com/library/ios/#Documentation/WindowsViews/Conceptual/ViewPG_iPhoneOS/CreatingViews/CreatingViews.html
provide a better approach to understand about it.
You can add sub view by [parentview addSubView:childView] in the syntax both childView and parentview will be of UIView type.

iOS: Embed Table View in other View (but table view has a complex table view controller..)

I'm struggling with this problem, so I need your help. Basically I've programmed a complex table view controller (with NSFetchedResults protocol etc) and already used it in my app and it worked great. Since I want now exactly this table view inside another view (so I can add a small subview at the bottom of the screen), I'm really struggling how to do this! I know by know how to embed a simple table view inside another view and link it to it's view controller.
But how can I do this in my case with as little effort as possible? I mean can I somehow use this table view controller I already have even though the superview must have its own view controller?! And how would I do that? Sorry I'm still a beginner :)
Since you already have a TableViewController. To add it as an subview to another ViewController's (self) view, do this:
TVC = <your tableViewController instance>;
[self addChildViewController:TVC];
TVC.view.frame = <your desired frame>;
[self.view addSubview:TVC.view];
adding the TVC as childViewController will forward UI methods of 'self' like willAppear and all to TVC.

Subview management within master/detail view in iOS (with ARC)

I have a master-detail controller for my app. The master controller is a UITabBarController and each tab is a UITableViewController that contains different types of data.
I plan on having a main header / image on the main detail view but then need to add different subviews to the main detail view to detail specific information depending on which tab I am using.
I am currently adding the relevant subview in my
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
Function like so:
UIViewController *subview = [[UIViewController alloc] initWithNibName:#"ItemNotFoundViewController" bundle:nil];
subview.view.frame = CGRectMake(20, 160, subview.view.frame.size.width, subview.view.frame.size.height);
[self.detailViewController.view addSubview:subview.view];
However, I believe that this is a poor way of doing things - every time someone clicks on a table cell another subview will be thrown on top of the stack of previously added subviews, creating memory issues.
What I am wondering is, does ARC take care of this for me? Is my approach passable? and even if it is passable, is there a better way of doing this?
First of all, no. ARC does not take care of this for you. It's not it's purpose to do that and even if it was, how could it know, that you don't want the previously added subviews anymore?
You have to remove those subviews yourself and then ARC will take care of deallocating them (if there are no other references to them).
Anyway that's not the way you're supposed to use a UISplitViewController (the master-detail view controller). As you noticed the split view controller handles two other view controllers. The master- and the detailViewController. In most cases the master view controller isn't changing while the app runs (it's content changes, but usually that's handled by a container view controller like UINavigationController which is assigned as the masterViewController), but the detail view controller does.
Instead of adding subviews to your existing detailViewController you should replace it by a new one. So you should create separate XIBs (what you've apparently done already) for all the view controllers that you want to present in the detail-section. And modify your code to
self.detailViewController = newDetailViewController; //newDetailViewController would be the vc you called subview in your code
instead of
[self.detailViewController.view addSubview:subview.view];
Edit: Notice that this assumes that your detailViewController property does 'the right things' when you set it's value. By default the UISplitViewController only has a property called viewControllers which is an NSArray in which the first object is the masterVC and the second is the detailVC.
Take a look at MultipleDetailViews for an example of how to manage that.
Since you want to have a header view in all your detail view controllers you have various choice of achieving that (which may or may not be applicable in your case, depending on your design):
add the header view to every details vc's XIB
instead of creating many XIBs for all detailVCs, create a new custom UIViewController subclass that modifies it's content based on some parameters you give it, i.e. which tableViewCell was tapped by the user
create a custom container view controller that manages two child view controllers, one for the headline and one for the content above it.
For more information about UISplitViewController and custom container view controller, please refer to:
View Controller Basics
Creating Custom Container View Controllers
No, ARC will not take of this for you, because detailViewController.view will keep a reference to its subviews. It's hard to say what approach is best without knowing more about what you're doing with these views. It would probably be better to just present the new view controller -- it will be deallocated after it's dismissed if you don't have a property pointing to it.

Resources