xCode Dynamically create ViewControllers - objective-c

I want to be able to dynamically create ViewControllers based on a JSON file. What I mean is, there will be a json that will dictate how many ViewControllers the user needs. I.e say I have a json file that lays out 5 ViewControllers, I want to be able to dynamically create these ViewControllers and be able to transition between them.
So what I am going to have is JSON file, that sets out the ViewControllers, say 3 for this example. This JSON file has info on the text, buttons etc and how to navigate between them.
So I want to be able to loop through this JSON, and create the necessary view controllers and add the required text, buttons etc. The JSON will also dictate how the view controllers link up together.
I know how to create one VC and add info like this (This is just quick example, just created vc and added label.
UIViewController *vc = [[UIViewController alloc] init];
vc.view.backgroundColor = [UIColor whiteColor];
UILabel *testLabel =[[UILabel alloc] initWithFrame:CGRectMake(220, 50, 130, 80)];
testLabel.backgroundColor = [UIColor clearColor];
testLabel.textColor = [UIColor blackColor];
testLabel.text = #"Hello";;
[vc.view addSubview:testLabel ];
[self.navigationController pushViewController:vc animated:YES];
I don't know how to create several differently named ViewControllers in a loop using JSON. Anyone have any ideas on how to do this? Or is something like this even possible?
Any assistance would be greatly appreciated.
EDIT:
Very basic example of what JSON will look like
{
"ViewControllers":[
{
"name":"FirstVC",
"id":1
},
{
"name":"SecondVC",
"id":2
},
{
"name":"ThirdVC",
"id":3
}
]
}
So first VC links to secondVC and second to thirdVC

Just create an array and hold them there. Something like this:
NSMutableArray *viewControllers = [[NSMutableArray alloc] initWithCapacity:0];
// ...
// Inside a loop
UIViewController *vc = [[UIViewController alloc] init];
vc.view.backgroundColor = [UIColor whiteColor];
UILabel *testLabel =[[UILabel alloc] initWithFrame:CGRectMake(220, 50, 130, 80)];
testLabel.backgroundColor = [UIColor clearColor];
testLabel.textColor = [UIColor blackColor];
testLabel.text = #"Hello";
[viewControllers addObject:vc];
// Release vc and label if you're not using ARC
Now, if you want to name your controllers, one idea would be to create a subclass of UIViewController and add a name (or something like that) property. Then you just set this property also inside your loop and you can refer/filter based on that property.

You won't explicitly name them as separate variables, but rather you may have an NSArray of different UIViewController instances. As you read through your JSON file, you can loop over the information presented, creating a view controller and adding it to the array each time the JSON tells you to. When you're done, you can pull view controllers out of the array as your user navigates back and forth.
What does your JSON look like? Post an example and we may be able to provide more info.

Why your ViewControllers needs different names? You should simply create instance of one viewController class.
For example if you have a 3 "screens" from JSON:
home
second
about
And all of them have different buttons, text etc. And you have a class name MyViewController. This class may have a #property name, #property buttons (probably NSArray with object from your button management class) etc if you want to distinguish your screens.
Next you should store your objects (MyViewController class objects) in NSArray.
So what you want to do when app starts:
You created an object of MyViewController class and display it. If user want to go to second screen you should simply create another instance of the same class.
So if you want to display name of the screen you have a self.name value in your ViewController class.

Related

How to addsubview to a view on class1 from class2?

I'm trying to do a really simple thing - I've got a main Xib file for the whole app and another Xib file for a small view.
I want the small view (Xib called "additionalView.xib") to appear in the first Xib ("ViewController.xib").
I have succeeded to do so in the "ViewController.m" but I want more - I want to do it from "additionalView.m"
There is a method I created called "openView:" in "additionalView.m" and it looks like this:
-(IBAction)openView:(id)sender
{
ViewController *vc = [[ViewController alloc] init];
NSArray *nibObjects = [[NSBundle mainBundle] loadNibNamed:#"additionalView" owner:self options:nil];
UIView *nibView = [nibObjects objectAtIndex:0];
[vc.view addSubview:nibView];
}
The method is being called and the lines are being read by the debugger - but nothing happens.
No crash - No error - No small view in bigger view.
Why is that?
I know that the last line is probably what's
screwing everything up but i don't know how to put it correctly.
Your problem is that ViewController *vc = [[ViewController alloc] init]; creates a new view controller. Because it's new, it's not the one that already exists in the view controller hierarchy that's managing the display.
Your method needs to access the existing view controller. How it does that depends on your app's structure and which object has a reference to the original controller object.
Try
[self.view addView:view.vc];
However, I'm not sure what is you view structure here. You say your -(IBAction)openView:(id)sender is in your "additionalView.m", but it is not the main view controller, correct? You need to do this in the main controller, so basically move the openView: method to your ViewController.m
And you normally need a separate view controller for each view to keep things neat and separate, so the additionalView.m should be an instance of UIViewController, which you can then create from your main view as follows:
-(IBAction)openView:(id)sender
{
AdditionalView *vc = [[AdditionalView alloc] initWithNibName:#"additionalView"];
[self.view vc.view];
}
You have options ... First you don't need to create a view controller vc if you just need the view . Create a uiview and add it .
Option 1: pass a ref to the app vc as suggested above and then :
[appVC.view addsubview:additionalView]
This will add it to main.
Use a view controller manager / ref in the app delegate that you can refer to as delegate and add your view to the current showing view.
Hope this helps

How to load one view from a selection of 3 all contained in one XIB file?

In short, how can i manage to load a SPECIFIC uiview from a set of UIViews all contained in one XIB file? iboutlets? how?
In some cases i would like the first view displayed instead of the 2nd, sometimes i want the 3rd displayed instead of the 1st. they are all similar UIViews however only one can be displayed MODALLY presented.. I know which UIView to display depending on the user interaction with buttons being clicked.. however the question is how can i specifically select a certain view to be displayed and then attach it modally to present it.
In detail this is what i have done so far:
Hi,
I have three view objects inside my TestViewController.xib file like so:
This xib's 'File's Owner' is connected to a TestViewController class.
On run time i am programatically instantiating the TeamViewController class like so:
TestViewController *tVController = [[TestViewController alloc] initWithNibName:#"TestViewController" bundle:nil];
I then present the view modally like so:
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:ls];
[nav setModalTransitionStyle:UIModalTransitionStyleFlipHorizontal];
[nav setModalPresentationStyle:UIModalPresentationFormSheet];
nav.navigationBar.tintColor = [UIColor blackColor];
[nav setNavigationBarHidden:YES];
[rootViewController.navigationController presentModalViewController:nav animated:YES];
nav.view.backgroundColor = [UIColor blackColor];
self.modalNavController = nav;
[nav release];
[tVController release];
This all works and my view is loaded, however it only loads one of the views by default - automatically.
What i would like to do is to be able to know how to load only a SPECIFIC UIView when instantiating the TestViewController. One way i thought of achieving this was to create IBOutlets for them and managing which view is displayed like that? so I have created three IBOutlets within the class and then connected them from the Xib's fileown to each UIView. This connected all fine.
IBOutlet *view1;
IBOutlet *view2;
IBOutlet *view3;
I am able to do something like this:
[self.view addSubview:view2];
and this will display view2 properly, however not in the modal view as i would ideally like it when instantiating TestViewController.
Can anyone guide me in how to achieve this goal of mine?
Thanks
You should give each of the views a unique tag, then use viewWithTag:
-EDIT-
In order for this to work, you might need to do NSNib *n = [[NSNib alloc] initWithNibNamed:bundle:]

When is it safe to manipulate screen controls through code in iOS/Objective-C

I have a question regarding iOS (or perhaps more accurately Objective-C) and properties. I have a UIView with a UISegmentedControl, by default it has 3 segments. I have a message which accepts a parameter and based on this parameter I may want to remove one of the segments. In UIView A I do this:
MyViewController *myview = [[[MyViewController alloc] initWithNibName:#"MyViewController" nib:nil] autorelease];
[[self navigationController] pushViewController:myview animate:YES];
[myview showItem:item];
In UIView B this happens in showItem:
-(void) showItem:(Item*)item{
if (item.removeSegment){
[segmentControl removeSegmentAtIndex:0 animate:NO];
}
}
I have noticed that the segment only gets removed when I call showItem after I have pushed it on the navigation controller. When I swap those two line, so I first call showItem and then push the view, the UISegmentedControl still has three segments instead of two.
This just feels wrong, it seems like bad practice that my code will break if someone doesn't call two messages in the right order. Is there a better way to do this? I've been looking at some sort of a property lifecyle that I can use, I am very familiar with this from ActionScript 3, but I have been unable to find anything on the subject.
(as an aside: in AS3 I would make a property, in the setter I don't manipulate any screen controls but call InvalideProperties. My overriden methode CommitProperties will be called once the entire object and child controls have been created. In CommitProperties I check if my property value has changed and this is where I would remove the segment.)
A common way of doing something like this is to create an Item *item property in MyViewController and set that when myview is created. So, your code becomes:
MyViewController *myview = [[[MyViewController alloc] initWithNibName:#"MyViewController" nib:nil] autorelease];
myview.item = item;
[[self navigationController] pushViewController:myview animate:YES];
MyViewController would then use that property in its viewWillAppear: method to configure its own segment control.
I think what you are falling prey to is myview->segmentControl doesn't exist until myview.view is referenced because of the lazy load of the view.
-(void) showItem:(Item*)item{
[self view]; // NO OP TO FORCE LOAD!!
if (item.removeSegment){
[segmentControl removeSegmentAtIndex:0 animate:NO];
}
}
Should work for you. Hope this helps!

UIViewcontroller with multiple views

The project currently has a UIviewController called "Dashboard" that acts as the main view of all the application. This main view consists of two subviews on top of it, kind of like a splitview. The left side of the main (left view) has multiple buttons. The right side (right view) will display the content of the selected button of the left.
When a button is pressed it will create a new instance of the view that is going to display like this :
vcMySchedule_iPad *vcSchedule = [[vcMySchedule_iPad alloc] initWithNibName:#"vcMySchedule_iPad" bundle:nil];
ncDashboard = [[UINavigationController alloc] initWithRootViewController:vcSchedule];
ncDashboard.navigationBar.barStyle = UIBarStyleBlackOpaque;
ncDashboard.view.frame = self.vwRightPanel.bounds;
[self.vwRightPanel addSubview:ncDashboard.view];
The thing is that when pressing another button it will display another view, but the memory of the previous one called still remains, and the dealloc of the previous view never gets called.
I'm not using a split view cause the left side has a button that when pressend it will move the left side to the left and the right side will move the the left to view completely.
Is there any approach to this?
Updated with some images...
Main (MainViewController):
Pressed Course Catalog:
vcCourseCatalog_iPad *vcCourse = [[vcCourseCatalog_iPad alloc] initWithNibName:#"vcCourseCatalog_iPad" bundle:nil];
ncDashboard = [[UINavigationController alloc] initWithRootViewController:vcCourse];
ncDashboard.navigationBar.barStyle = UIBarStyleBlackOpaque;
ncDashboard.view.frame = self.vwRightPanel.bounds;
[self.vwRightPanel addSubview:ncDashboard.view];
When selecting a row form the table it displays the detail and if the user press the button the view is displayed max.
I think I may have been calling the new views wrong perhaps. Where are the objects released?
Without more information, I can't give solid advice, but check the following:
Are you using ARC? If not, remember that you must explicitly release all references before something is dealloc'd.
Do you keep ahold of a reference to the subview anywhere else? If you are still referencing it somewhere (especially in ARC), it will stick around. Circular references are evil here.
Are you removing the subview from it's superview before you replace it with the new one? You'd be surprised how often it is something as simple as this.
EDIT:
In response to below, about you not using ARC, its plainly obvious that 1) is your problem. You are not releasing references. In this case, it seems quite obvious here:
vcCourseCatalog_iPad *vcCourse = [[vcCourseCatalog_iPad alloc] initWithNibName:#"vcCourseCatalog_iPad" bundle:nil]; ncDashboard = [[UINavigationController alloc] initWithRootViewController:vcCourse];
ncDashboard.navigationBar.barStyle = UIBarStyleBlackOpaque;
ncDashboard.view.frame = self.vwRightPanel.bounds;
[self.vwRightPanel addSubview:ncDashboard.view];
that you are allocating a vcCourseCatalog_iPad and a UINavigationController, without ever releasing them. Optimally, you'd autorelease the vcCourseCatalog_iPad, and release the navigation controller when you swap it out.
Your code ought to look something like this:
vcCourseCatalog_iPad *vcCourse = [[[vcCourseCatalog_iPad alloc] initWithNibName:#"vcCourseCatalog_iPad" bundle:nil] autorelease];
if(ncDashboard)
{
//do any sort of removal from views here
//[ncDashboard.view removeFromSuperview];
[ncDashboard release];
}
ncDashboard = [[UINavigationController alloc] initWithRootViewController:vcCourse];
ncDashboard.navigationBar.barStyle = UIBarStyleBlackOpaque;
ncDashboard.view.frame = self.vwRightPanel.bounds;
[self.vwRightPanel addSubview:ncDashboard.view];
Additionally to CrimsonDiego's answer, I'd suggest that you use followings lines in your files:
In the .h file:
#property (nonatomic, retain) UIView *ncDashBoard;
In the .m file:
#synthesize ncDashBoard = _ncDashBoard;
and then use _ncDashBoard only from then on. This is to make sure that the retain count is set properly.

How to get data into textfields for editing?

I have this code (XCode 4, using Storyboards with ARC) which takes data from an array (rArray) and places it in the textfields from which it originally came from (I want to edit the data). The array (rArray) has valid data in it, but nothing is in the textfields. What am I doing wrong?
SingletonListOfReadings *rShareInstance = [SingletonListOfReadings sharedInstance];
rArray *ra = [rShareInstance.listOfReadings objectAtIndex: indexPath.row]; // get an rArray object out of listOfReadings
// place data back into textfields
EnterDataViewController *edvc = [[EnterDataViewController alloc] init];
edvc.txtSTA1.text = ra.rSTA;
edvc.txtBS.text = ra.rBS;
edvc.txtFS.text = ra.rFS;
edvc.txtDesc.text = ra.rDesc;
[self.navigationController pushViewController:edvc animated:YES];
the UI text elements in VC are not yet set when you try to populate it (it will be set only after [self.navigationController pushViewController:edvc animated:YES]; (and only when it gets viewDidLoad).
easy way to go is to change txtSTA1, txtBS, txtFS etc to strings (make sure they are in edvc .h file), pupulate them as u do now
so, txtSTA1STR is a string in the .H file of edvc
and txtSTA is a UI text element in the xib (or programmatically) in edvc
EnterDataViewController *edvc = [[EnterDataViewController alloc] init];
edvc.txtSTA1STR = ra.rSTA;
edvc.txtBSSTR= ra.rBS;
edvc.txtFSSTR= ra.rFS;
edvc.txtDescSTR= ra.rDesc;
[self.navigationController pushViewController:edvc animated:YES];
and inside edvc
- (void)viewDidLoad
{
txtSTA1.text = txtSTA1STR;
//... etc
}
I fixed it... I took the original code and moved it to the View controller (edvc) which handles the objects. Then I called it from the view controller where I needed the data.
Works fine... I think the problem was in the addressing of the receiving textfields.
Thank you for your help... I appreciate it.