Saving iOS Application Scene State - ios

This may be impossible, but I'm trying to save the state of my application between scene transitions, but I can't figure out what to do. Currently I love the way that when you have an application running and hit the home button, you can go back to that application just where you left off, but if you transition between scenes (in a storyboard), once you get back to that scene the application state was not saved.
I only have two different scenes that need to be saved (you transition back and forth from one to the other). How can I go about saving a storyboard scenes state without taking up precise memory?
More Detailed: Here is my entire storyboard. You transition back and forth between scenes using the plus toolbar button. On the second scene the user can tap on the table view cells and a real image will fill the image view (See figure 1.2)
Figure 1.1
In figure 1.2 you see what happens when you tap inside one of the many table view cells (an image view pops up.)
Figure 1.2
THE PROBLEM: When you tap a table view cell, which fills an image view (shown in figure 1.2) it works fine if you stay on that scene or even hit the iPhone home button (if you hit the iPhone home button and then reopen the app the scene's state was saved and the image view filled with a simple image still shows just like we left it), but if I transition (using the plus button) back to the first scene, and then use the plus button on the first scene to get back to the second scene the image view that I created (shown in figure 1.2) disappears and the second scene loads without saving the state and image views we filled.
EDIT: I tried using the same view controller for both scenes, but it didn't solve the problem.
UPDATE: I just found the following code (that I think stores a views state). How could I use this and is this what I've been looking for?
MyViewController *myViewController=[MyViewController alloc] initWithNibName:#"myView" bundle:nil];
[[self navigationController] pushViewController:myViewController animated:YES];
[myViewController release];

I would suggest a combination of two things:
1. Take DBD's advice and make sure that you don't continuously create new views
2. Create a shared class that is the data controller (for the golfers, so that the data is independent of the scene)
The correct way to make the segues would be to have one leading from the view controller on the left to the one on the right. However, to dismiss the one on the right you can use
-(IBAction)buttonPushed:(id)sender
[self dismissModalViewControllerAnimated:YES];
}
This will take you back the the view controller on the left, with the view controller on the left in its original state. The problem now is how to save the data on the right.
To do this, you can create a singleton class. Singleton classes have only one instance, so no matter how many times you go to the view controller on the right, the data will always be the same.
Singleton Class Implementation (Of a class called DataManager) - Header
#interface DataManager : NSObject {
}
+(id)initializeData;
-(id)init;
#end
Singleton Class Implementation (Of a class called DataManager) - Main
static DataManager *sharedDataManager = nil;
#implementation DataManager
+(id)initializeData {
#synchronized(self) {
if (sharedDataManager == nil)
sharedDataManager = [[self alloc] init];
}
return sharedDataManager;
}
-(id)init {
if(self == [super init]) {
}
return self;
}
#end
Then, inside your view controller code you can grab this instance like this
DataManager *sharedDataManager = [DataManager initializeDataManager];
This way you will have the same data no matter how many times you switch views.
Also, you can better adhere to MVC programming by keeping you data and your view controllers separate. (http://en.wikipedia.org/wiki/Model–view–controller)

Figure 1.1 has a fundamental flaw which I believe the basis of your problem.
Segues (the arrows between controllers on the storyboard) create new versions of the UIViewControllers. You have circular segues. So when you go "back" to the original screen through the segue is really taking you forward by creating a new version.
This can create a major problem for memory usage, but it also means you can't maintain state because each newly created item is an empty slate.
Since your are using a UINavigationController and pushViewController:animated: you should "pop" your controller to get rid of it.
On your "second" scene, remove the segue from the + button and create an IBAction on a touchUpInside event. In the IBAction code add the "pop"
- (IBAction)plusButtonTapped {
[self.navigationController popViewControllerAnimated:YES];
}

I see what you mean. This should happen to every application, as when the last view controller in the navigation stack is transitioned away from, it is deallocated and freed. If you need to save values such as text or object positions, a plist may be the way to go. See this related question for how to use a plist.

Apple isn't going to do this for you. You should probably just save the state of each view using NSUserDefaults and each time your application launches re-load your saved data.
If you are storing everything in CoreData you would only need to save the active view and a few object ids, if not you would need to save any data you have.
Don't expect iOS to save anything that you have in memory between launches. Just store it in NSUserDefaults and load it each time.

Store the state of the scene in NSUserDefaults or inside a plist file then when loading up the scene just load it with the settings from there. If the images are loaded from the internet you might also want to save them locally on your iphones hard drive so it runs a bit smoother.

I don't think you should cycle the segues, just use one that connects viewcontroller 1 from viewcontroller 2 should be enough and that way you make sure that no additional viewcontrollers are being made (memory problems maybe?)
However for your particular problem, I believe that you should use core data to save the exact state of your table, view because ios doesn't save the exact state of view at all times. it will require work but you will achieve what you want. You will need to save the exact photo( using a code or enums that will be saved), the location in the table view, the score or well whatever data you need to save that state.
The best of all is that coredata is so efficient that reloading the data when the app is relaucnhed or into foreground it takes no time, and ive used core data to load more than 5k of records until now and works just fine and its not slow at all.
When i get back home ill provide a code you might use to get an idea of what i mean.

The key here is to:
Have some sort of storage for the data that your application needs. This is your application's data model.
Give each view controller access to the model, or at least to the part of the model that it needs to do its job. The view controller can then use the data from the model to configure itself when it's created, or when the view is about to appear.
Have each view controller update the model at appropriate times, such as when the view is about to disappear, or even every time the user makes a change.
There are a lot of ways that you can organize your data in memory, and there are a lot of ways that you can store it on disk (that is, in long term storage). Property lists, Core Data, plain old data files, and keyed archives are all possibilities for writing the data to a file. NSArray, NSDictionary, NSSet, and so on are all classes that you can use to help you organize your data in memory. None of that has anything to do with making your view controllers feel persistent, though. You'll use them, sure, but which one you choose really doesn't matter as far as updating your view controllers goes. The important thing, again, is that you have some sort of model, and that your view controllers have access to it.
Typically, the app delegate sets up the model and then passes it along to the view controllers as necessary.
Something else that may help is that you don't have to let your view controller(s) be deleted when they're popped off the navigation stack. You can set up both view controllers in your app delegate, if you want, so that they stick around. You can then use the ones you've got instead of creating new ones all the time, and in so doing you'll automatically get some degree of persistence.

Related

UIRefreshControl Inside of UITableView Causing App to Freeze on Rotation - iOS 6+

Ok StackOverflow People...I've got a very interesting problem that I've been trying to solve for days and can't figure out so I need some major help. This will most likely be a very lengthy description but please bear with me and thank you deeply in advance for reading all of this because the more words I have, the clearer I can describe the full picture to you all. I will do my absolute best to be as terse and coherent as I can possibly be. Please let me know wherever I fall short.
Here's the context of my problem: I'm using Storyboards for my iOS app and for a particular nav tab in my app, I had to create two separate scenes for both the Portrait and Landscape orientations. The reason for doing this (instead of say, using Autolayout), is because within this said tab, there are visual elements (table views, web views, etc.) that are laid out differently depending on the orientation and it was a lot easier to create a separate orientation scene to handle this change in the UI instead of doing it programmatically -- (it's also just a lot easier to understand and cleaner code-wise). So the take away to keep in mind from all of this is that these two separate Portrait and Landscape scenes represent the SAME TAB in my app. (Side Note: these scenes were made in the IB of course)
Now the visual elements that I mentioned in the UI earlier -- going deeper, they are all containers for different UIViewControllers. I sandboxed everything in the app and pretty much have a 1-to-1 relationship for all things so these containers will map to my subclassed UIViewControllers that I've created for their specific purposes -- but it's here that the first caveat of my problem arises. Here's a practical example for a clearer picture, I have one UIViewController that contains a UITableView called MXSAnnouncementsViewController and this same view controller exists in both the Landscape and Portrait scenes. I did not create an explicit Portrait or Landscape VERSION of that view controller but instead, have the controller keep track of two IBOutlet properties (tableViewLandscape and tableViewPortrait) that point to the orientation-specific UITableViews -- and this approach works perfectly fine. Moreover in my MXSAnnouncementsViewController, I have a local property called tableView that abstracts the orientation-specific table views. It gets set within viewDidLoad which you can see below:
- (void)viewDidLoad
{
[super viewDidLoad];
if (self.tableViewPortrait) {
self.tableView = self.tableViewPortrait;
} else {
self.tableView = self.tableViewLandscape;
}
[self.tableView setDelegate:self];
[self.tableView setDataSource:self];
if (![MXSAnnouncementManager sharedAnnouncementManager].latestAnnouncements) {
[MXSAnnouncementManager loadModel:#"MXSAnnouncementGroupAllAnnouncements" withBlock:^(id model, NSError *error) {
if (!error) {
self.arrayLatestAnnouncements = [MXSAnnouncementManager sharedAnnouncementManager].latestAnnouncements;
[self.tableView reloadData];
} else {
// show some error msg
}
}];
} else {
self.arrayLatestAnnouncements = [MXSAnnouncementManager sharedAnnouncementManager].latestAnnouncements;
}
[self setupPullToRefresh];
}
Whenever I'm in the tab, one of the two orientation-specific IBOutlets is always active and has an address in memory while the other is nil. Whenever I rotate, the roles reverse -- whatever had an address in memory previously is now nil and the other has been initialized and allocated which is why I do what I did with the tableView property in the snippet above. Here is where caveat #2 comes into the picture and it's a doozy -- it has to do with the view lifecycle. Here's a practical example for clarity sake: Say I load the app up in Landscape orientation. When I do, my tableViewLandscape outlet has an address in memory and my tableViewPortrait outlet is nil. That's the expected and desired behavior. Now, when I rotate the app, the crazy stuff begins. Here's one place where I need clarity from all of you with regards to instances of UIViewControllers and what's normal vs. what's not so read the following VERY slowly and carefully.
Rotating the app immediately causes the opposite orientation scene (another INSTANCE of MXSAnnouncementsViewController???) to call its viewDidLoad method (in this example, we're in Landscape so the Portrait scene invokes that method). In that method, my local tableView property gets set to the currently active table view for that orientation (see snippet above). When that method finishes, the previous LANDSCAPE instance of MXSAnnouncementsViewController invokes its viewWillDisappear method which is then followed by the PORTRAIT instance's invocation of its viewWillAppear method which then lastly ends with the LANDSCAPE instance calling its willRotateToInterfaceOrientation callback -- that's the order of operation that I'm seeing from the breakpoints. I really do hope you got all of that because my mind just blew up from it all.
If you're still with me at this point, thank you because we're finally at the home stretch. As the title of this post suggests, the problem I'm trying to solve is my app freezing on rotation. If you haven't noticed on the viewDidLoad snippet, the last instruction to get executed is the setupPullToRefresh method which is the following:
- (void)setupPullToRefresh
{
UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
[refreshControl addTarget:self action:#selector(refreshTableView:) forControlEvents:UIControlEventValueChanged];
[self.tableView addSubview:refreshControl];
}
Since I already explained the whole view lifecycle order of operations on rotation earlier, to make a very long story short, if I comment out that last setupPullToRefresh instruction at the end of viewDidLoad for MXSAnnouncementsViewController, my app works fine. If I include that instruction, my app becomes totally unresponsive on the first rotation and I cannot for the life of me figure out why. Not sure if I'm dealing with an edge case here or something. Any and all insights are welcome and THANK YOU SO MUCH for reading all of this!
Your best approach is probably to abandon your current design of having two separate controllers for portrait and landscape. On iOS, you should always relayout the views for the orientation you want to be in, not destroying and recreating everything. By trying to handle it by recreating everything, you're just going to get yourself in trouble I think.
You can use auto layout to do complex reorderings of views upon rotation if you know it well, but probably your best bet is to scrap your current code to do landscape, and write code to simply rearrange the views yourself upon rotating. You'll have far fewer issues down the road, and your code will be easier for others to understand and maintain as well.
When you remove that one bit of code, your app may appear to be working just fine, but there is probably something going on behind the scenes that isn't quite correct that could come back to bite you in the future. That's probably why adding the line of code breaks it.
Try to add it after rotation
-(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation{
[self setupPullToRefresh];
}
If that doesn't help, create UIRefreshControl only once and set it to the right table on rotation.
If that doesn't help too, follow the first given answer (#Gavin's answer) and create only 1 table on viewDidLoad and relayout things in -(void)viewWillLayoutSubviews

Creating new object in Core Data: create first or upon return?

So I'm struggling with Core Data. I'm finding there are many ways to do the same thing, and that if you try to build the app using Storyboards and UIManagedDocuments you need to read all the tutorials and examples older than last year with a translation sheet. Today's question is about finding the best practice when adding a new managed object. I've seen examples done both ways:
Create the new managed object in a table view controller (after clicking +) and giving that new shining managed object to the subordinate "Add" view controller to get user input for all the object attributes. This seems simple, and the returned object is simple to understand because it contains all the individual attributes. But I've seen example code in this "Add" view controller for a "cancel" button that deletes the managed object passed in and then calls Save Context before dismissing itself. Functional, but the MVC training gnome on my shoulder is screaming at me about having this subordinate Add View delete an object and horrors directly call Save Context. The Recipe example code from Apple appears to use this method.
Send nothing to the Add view controller, and have it send back a delegate call the table view controller that returns each of the attributes as a separate passed parameter. So the return method becomes really long: controller:(UIViewController *)controller didReturnFirstName:(NSString *)firstName andLastName:(NSString *)lastName andStreetAddress:(NSString *) and... and... and.. But this is SO consistent with MVC dogma because the managed object is created back in the table view controller when it receives all the individual attributes, and the "Add" view never touches the Model(Core Data), or throws away an unused managed object when the user changes their mind.
Even with chained delegated methods, I'm still debating with myself which is a better method. Comments and ideas from those who've lived with both forms would be a welcome addition.
Thanks.
It you look at the example in Apple's tutorial, they accomplish this task by doing a number of things outlined below, in this case they have a modal view appear to input the information that is to be added to the data model:
In the modal view that appears, they create a protocol to handle either dismissing the view or saving the data and a property of type id that implements that protocol to be the delagate, this insures whatever object that is implments the required methods
In the view controller that created the modal view, they implement the protocol, including saving the object to the data model and dismissing the modal view
In the view controller that created the modal, they set the view controller that created the modal view as the modal view delegate during the seque to the modal
So, to summarize, in the modal view to collect the new data you need to:
create a protocol and property in .h and synthesize it
#protocol yourProtocol <NSObject>;
//methods that determine what happens based on what user does, it would save your core data object
#end
#property(nonatomic, weak) id<yourProtocol> delegate;
Then, in the modal view .m file you call those methods on the delegate, likely when they pick save or done, so a method for each probably as IBAction connected to a button
[self.delegate myMethod];
In the view that presented the modal view, you implement the protocol in the .h file
#interface viewController() <yourProtocol>
and finally, add your methods to the view controller that presented the modal view's .m file, this should include removing the view and saving your core data. According to Apple and other sources, the view controller that caused the popup/modal, etc... should be the one that dismisses it. Then, in the seque using the seque indentfying, set the view controller that is presented int he modal view as the modal view's delegate.
You're right, there are many approaches to take on this one.
It sounds as if you are starting with a context that may not be saved, so in order to be able to get back to your starting point I would tackle it like this:
Start by creating a new NSManagedObject to be used as a temporary object which gets passed to your "Edit" view.
If you are editing an existing object, copy the attributes from the existing object into this new temporary object (You can use a quick for loop to copy them all generically). Otherwise proceed with the newly created object.
Pass the temporary object to the "Edit" view controller and have it treat both cases the same.
a. If the user presses cancel, have either a protocol or delegate method which notifies the tableview and the tableview then simply discards the temporary object.
b. If they press save, notify the tableview and then copy the attributes from the temporary copy back to the original and delete the temporary object (if it is an edit operation), or just leave it as the newly created object if it was an "Add" operation.
This is not really an answer so feel free to ignore, but neither is any of the others (so far) a complete answer, and I feel the need to add some points without creating a new question, because I really want to get to the bottom of this too. In particular the answer should deal with
what happens when you Cancel vs Save
what if you want to use the same controller for editing an existing entity rather than creating a new one
Nicks answer deals with apple's (current) convention for passing data back, but is not a complete answer because that tutorial does not deal with core-data managed objects, and apples own samples that do use managed objects do it differently.
But on this subject, I find that the delegate convention is unwieldy in some cases and was happy to read this from a more experienced developer:
http://robsprogramknowledge.blogspot.pt/2012/05/back-segues.html
Rob details different mechanisms for that which are all equally valid, including the protocol/delegate convention (Nick's), using a data object without a protocol (so-called "shared memory") - more like some of the others suggested, and - my favourite - using a block.
A block is a nice option because, while it works like a delegate, the code remains entirely in context in the "parent" viewcontrollers source
However, the "shared memory" makes more sense because, as you say, using anything other than the data (managed) object which you've already designed to pass all of these properties around just seems silly.
The issue for me then is, what's the convention for creating these managed objects, passing them around, then either
1. saving changes
2. canceling changes
3. canceling creation of a new entity altogether
Inafzigers answer above works something like this, but why all that copying of properties? Can't I just not save my changes?
Apple' CoreDataBooks seems to deal with this by creating a child ManagedObjectContext and a new entity in that context (insertNewObjectForEntityForName) in the prepareForSegue, and in the delegate method (when it comes back):
- if Save was clicked, it saves both this new ManagedObjectContext and the parent FetchedResultsContext to store the object
- if Cancel was clicked it does nothing, effectively discarding the new managed object context, and thus, presumably, the new object
So this might be the conventional approach, except that:
There is a comment in the code of that sample (CoreDataBooks RootViewController.m) which states that the two managed contexts are not necessary, implying that you might be able to do the same with just one. Here's the full comment:
IMPORTANT: It's not necessary to use a second context for this. You could just use the existing context, which would simplify some of the code -- you wouldn't need to perform two saves, for example. This implementation, though, illustrates a pattern that may sometimes be useful (where you want to maintain a separate set of edits).
CoreDataBooks does not use a UIManagedDocument - not sure if that makes a difference
What's not clear to me is, do we really to use insertNewObjectForEntityForName to create the managed object? What about just creating the object and then only inserting it later if the user hit Save?
Also what about if we do an Edit and a Cancel - can we use the undo manager to get rid of changes?
I think the right approach would actually be to create a new, separate NSManagedObjectContext for your AddController (if a new item is to be created) or EditController (if an existing item is to be modified).
Chose A) or B) depending on your SDK:
A) You then have the choice to either save changes in that context, or discard the context.
In case of a "save", you can merge the changes into the TableController's ManagedObjectContext through Notifications (NSManagedObjectDidSaveNotification).
In case of a 'cancel", you can just discard the separate context.
B) Alternatively, if you're on OSX 10.7(+), you could use nested NSManagedObjectContexts, creating an NSManagedObjectContext in AddController (the child context), and settings it's parentContext to the TableController's NSManagedObjectContext.
In case of a "save", you'd save the child context, and the parent context.
In case of a "cancel", you'd just discard the child context.
See a detailed example of this in Apple's CoreDataBooks example http://developer.apple.com/library/ios/#samplecode/CoreDataBooks/Introduction/Intro.html

Design pattern for sharing a network data model between ViewControllers

I am currently developing an iPad application. For business reasons there wont be any data persistence on the device. The data will be accessed from a back-end server as needed using NSURLConnection. I have developed a 'model' object which does all the network access. The UI has a split view controller with a table view controller inside a UINavigationControlller as the root controller. User will drill-down on the table view controller to eventually load the detail view controller. The table Viewcontrollers are passing a reference to the model object when they are being loaded into the UINavigationController so that they can dynamically generate parts of the Table View Cell from the model. In order to be responsive, each Table View controller sets itself as the delegate of the Model object in the view will appear and when the cell is selected, queries the model object, which in turn updates the UI via a delegate method.
My question is where is the best place to set and unset the delegate of the data model?. Currently I am setting the delegate in the ViewWillAppear and setting it to nil immediately after navigation Controller:pushViewController:Animated.
// Setting the delegate
- (void)viewWillAppear:(BOOL)animated {
// set ourself as the delegate
[[self dataModel] setDelegate:self];
// Get the count of studies
[[self dataModel]GetListOfDiagnosticStudyResultsForID:[[self currentPatient]patientID]];
}
// setting delegate to nil
DiagnosticStudiesViewController *selectedVC = [[DiagnosticStudiesViewController alloc] init];
selectedVC.dataModel = self.dataModel;
[[self dataModel]setDelegate:nil];
[[self navigationController]pushViewController:selectedVC animated:YES];
Is this appropriate? Could you think of any issues with this pattern. The program is very responsive and I do not see any issues in the instruments. Is there a better way to do this?.
Sorry that this question is long winded.
I think this is an okay approach but there are a couple of considerations to be made:
You're sharing the dataModel with 2 views so you may have to update the view when you return to the DiagnosticsStudiesViewController's parent (self in your code) depending on how dataModel data is displayed.
This might get hairy in the future if you need to thread your code. In that case you might have to make a copy of the dataModel to pass to DiagnosticsStudiesViewController or handle edits to dataModel in a thread-safe manner.
You'll obviously require a network connection for both view controllers to work so you've made a workflow decision with your two view controllers by pulling dataModel from the server. In the future it may be hard to uncouple these view controllers.
If it works for your case and the decision has been made to not persist I think you'll be fine.

Retain view state upon reloading

I am developing an iPad application that is essentially a sequence of user instructions to mimic a real life system test, with the ability to make modifications on each view if components were to fail (indicating issues that will need to be resolved).
The problem I am having is that the default behaviour of the views seems to be that as I progress forward through the hierarchy, it retains the state of each view, but if I progress back and then move forward again it will have reset the screen.
What I would like to do is have each view save its state, regardless of how the user leaves that screen, so that they can be confident that their work is preserved even if they need to return to a previous step.
Is there any way of doing this? Or do I need to fundamentally reconsider my design?
You need model objects for your views. These could be as simple as dictionaries or as involved as a custom class for each view.
Each view's controller must update its associated model with the changes made via its interface before the view goes off-screen. When it reappears, the VC will update the display with the information from the model.
This follows the dominant Cocoa paradigm of Model-View-Controller (see also: Cocoa Design Patterns); your views display information, your models store information, and the controllers mediate and translate between the two of them.
How to update a model from the view depends heavily on the design of your model. Here's a mockup that may or may not be helpful. All the things named xField are outlets to UITextFields.
// When the view is taken off screen
- (void) viewWillDisappear {
// Assume that when created, view controller is given a pointer
// to the relevant model object (probably by the previous view
// controller)
[model setNameOfHorse:[[self horseNameField] text]];
NSUInteger newBetValue;
newBetValue = [[dollarValueFormatter
numberFromString:[[self betField] text]]
unsignedIntegerValue];
[model setBet:newBetValue];
[model setNote:[[self noteField] text];
}

Managing View Controllers in iPad port

I've got an app that I've developed for the iPhone, but now want to port to the iPad. The iPhone app is navigation style and based on discrete table view controllers managed by a nav controller. The larger screen real estate of the iPad means that I can comfortably fit a couple of these table view controllers on to the screen at the same time.
The question is how? Should I
a) have the main view load two table view controllers from separate NIBs and then position them on screen (I'm not sure how I set they x and y of subviews loaded from nibs).
b) create sub-views in my main nib and populate these with data from my existing classes (if so how do I hook up the IBOutlets)?
c) do something completely different
One thing I should point out is that I don't want to use the split screen option.
Alert! This QA is now of historic value only.
It is now trivial to do this sort of thing with container views in iOS, which is why Apple edited them:
https://stackoverflow.com/a/25910881/294884
How to add a subview that has its own UIViewController in Objective-C?
Historic answer...
".. how I set they x and y of subviews loaded from nibs?"
I'm not sure if I fully understand your question Phil, but here's an easy and clear way:
Fire up interface builder and in the new larger iPad view, simply add new smaller views (UIViews)... Put them exactly where and how you want them. We are going to call these "basket" views.
Let's say one of your complicated views from the other app is your fatDogs view. Call the new basket view fatDogsBasket. Then in the code, in viewDidLoad, just do the following with all these "baskets"...
[fatDogsBasket addSubview:fatDogs.view];
[clientsBasket addSubview:clients.view];
[namesBasket addSubview:names.view];
[flashingLightsBasket addSubview:flashingLights.view];
// etc
You're done! (Obviously, make sure that the relevant view controllers, fatDogs, flashingLights and so on, are all ready to go and instantiated.)
The "basket" system is handy since each one will hold your previous work in one place; usefully you can (say) set overall invisibility or whatever just by touching the baskets. Obviously, if you want to set, or maybe move, the position of a basket in the code, just go
happyBasket.frame = CGRectMake(509,413,
happyBasket.frame.size.width,
happyBasket.frame.size.height);
UIViews in iOS are very lightweight, so it's no problem at all adding another layer of UIViews.
I hope this is what you were getting at!
------Later...
You went on to ask: "Just to make sure I'm clear on the right way to implement this. The main view controller has IBOutlets for each of the 'baskets' and its this IBOutlet connection to the subview that I'm calling. Each of the view controllers that I'm going to show in each basket has it's own nib and associated IBOutlets. Right? –"
So, "The main view controller has IBOutlets for each of the 'baskets'"...
Right, the main view in the new app, would have lines like this in the .h file:
IBOutlet UIView *fatDogsBasket;
Note that you are simply declaring "fastDogsBasket" to be a UIView. You shouldn't worry too much about the "IBOutlet" word. All that means is "I need to be able to look this item up, over in the interface controller." It's important to realise IT DOES NOTHING.
So yes all the "baskets" will be UIViews and hence of course you must delare them as such in the .h file of your main view controller. Personally is would not use the phrase "a view controller has IBOutlets." It sort of confuses things and gives the wrong idea. Just say "don't forget to mark the UIViews as iboutlets in the header file."
So anyway yes that's exactly what you do, declare all the "basket" UIViews in the .h file of the main controller, and indeed mark them all as IBOutlets so that interface builder will work more easily. Next ..
"its this IBOutlet connection to the subview that I'm calling" -- that's wrong.
The basket such as fatDogsBasket IS SIMPLY A UIVIEW and that's that. It's just a UIView.
Now, as you know you can put UIViews inside other UIViews. (Obviously, this is commonplace, every UIView has scores of UIViews inside it and so on and on - it's the most basic part of building up an interface.)
So, what are you going to put inside your fatDogsBasket uiview? You're going to put in ALL YOUR PREVIOUS WORK on fatDogs! Previously (for the iFone) you wrote a wonderful class - a view controller - called fatDogs. (It may well have even had many subclasses and so on.)
We're now going to take the view from fatDogs (of course, that is fatDogs.view) and literally put it inside fatDogsBasket. (Recall that fatDogsBasket is a UIView.)
So firstly you would have to completely include your amazing class fatDogs (from the old project) in your new project. Click "add existing flies/classes" or something like that...you'll figure it out. Yes, add all the class files, xibs, any subclasses and so on.
Then, simply do this .. in your new super-powerful uber-controller, in viewDidLoad, just do the following with all the "baskets"...
[fatDogsBasket addSubview:fatDogs.view];
[clientsBasket addSubview:clients.view];
[namesBasket addSubview:names.view];
[flashingLightsBasket addSubview:flashingLights.view];
// etc
You're done! Note that the view from fatDogs (ie, fatDogs.view) is now displaying inside of the UIView fatDogsBasket. The class fatDogs will now work completely normally, just as it did in the old days! And incredibly, you can easily (here in your new controller) do things like simply move fatDogsBasket, and it will move the fatDogs view easily all at once, without worrying about the details of fatDogs and it's views.
Finally you ask..
"Each of the view controllers that I'm going to show in each basket has it's own nib and associated IBOutlets."
Exactly correct. When you add your old system "fatDogs" to the new project, you will be adding all of it's xib files and so on. Anyting that happens or doesn't happen inside those classes, to do with perhaps buttons or anything else marked as iboutlets, or anything else, will just still be the same within those classes. I'm pretty sure absolutely NOTHING will change when you use those old classes in your new project.
Just for the record .. "Each of the view controllers that I'm going to show in each basket.." Just to be accurate, you don't really show as such a viewcontroller, you show the view of the viewcontroller (!!). In other words, for fatDogs (a view controller) you will be showing it's view, which is, simply enough, referred to as fatDogs.view. ("view" is, of course, a property of any UIViewController, so you can just say vcName.view and you're done.)
Hope it helps!
And finally you ask .................................
"I've got it compiling OK, but my baskets are showing up empty, i.e. they're not showing the views of the view controllers that I've imported."
Tell is the name of one of your UIViewController classes from the old project, so we can be specific
Let's say you have an old UIViewController called HappyThing. So you will very likely have a file HappyThing.h and a file HappyThing.m and a file HappyThing.xib.
put all those in the new project, you must do so using Add->Existing Files. (Control on one of your current filenames in the list on the left in XCode.)
You will have to do this #import "HappyThing.h" somewhere or other in your new project - either in your Prefix.pch file or at the top of your new UIControllerView
To be clear in HappyThing.h you will have a line of code
#interface HappyThing : UIViewController
In your new UIViewController.h file, you will have to add a new variable, we'll call it xxx,
HappyThing *xxx;
Note that the type of xxx is HappyThing. (Note that as a rule, you would use the naming convention "happyThing" (note the lowercase "h") rather than "xxx", but it's just a variable and I want it to be clear to you that it's just a variable.)
Next! At the moment it's just a variable that is not pointing to anything, it's nothing. (Just as if you said "int x", but then did not actually say "x = 3" or whatever.) So! In your code you have to actually instantiate xxx.
xxx = [[HappyThing alloc] init];
[xxxBasket addSubview:xxx.view];
Note that the first line is what makes an instance of HappyThing come in to existence. And of course, you want to use "xxx" to point to that instance.
The second line puts the view in to the relevant basket! Note that of course what you want is the view associated with xxx (ie, xxx.view) ... remember that xxx is a UIViewController, it is not itself a UIView. The associated UIView is "xxx.view". (The view is literally just a property of xxx.)
Memory management! Note that you used "alloc" to bring xxx in to existence. This means you DO own it, and of course that means YOU DO NOT need to send a retain there. Furthermore, since you do own it, that means You eventually have to RELEASE it. (easy ... [xxx release];)
So simply add the line [xxx release]; to the dealloc routine in your new UIViewController. (Really it won't cause any harm if you forget to do this, but do it anyway.) Conceivably you may want to release it earlier for some reason once you are more comfortable with the process.
(I was just working on a project with a huge number of huge tables, popovers and the like, so I only made them on the fly and got rid of them as soon as possible, to use less memory. But all of that is irrelevant to you at this stage.)
So now you should SEE IT ON THE SCREEN!
Don't forget if you previously had some routine in HappyThing, which you had to call to start it working (perhaps "beginProcessing" or something), you'll have to call that yourself from the new UIViewController. Hence perhaps something like:
xxx = [[HappyThing alloc] init];
[xxxBasket addSubview:xxx.view];
[xxx beginProcessing];
[xxx showAmazingRedFlashingLights]; // or whatever
Finally you asked ...
"When you've use this technique, do you simply include the headers of the imported files in your main view controller, or do you forward class them in some way?"
That was not your problem, your problem was that you were not instantiating it with the line xxx = [[HappyThing alloc] init];. So, good luck!
Regarding the line of code "#class HapyyThing", if you want to simply put it just above the start of the definition of your new UIControllerView. Generally you don't have to if you have your include line in the best place. Anyway it is an unrelated issue. It simply won't compile if your new UIViewController, can't find HappyThing. Enjoy!

Resources