Get UIViewController to reload different UIView programmatically - objective-c

I am overriding the loadView method within a UIViewController as follows:
-(void)loadView
{
NSLog(#"HPSFormController loadView starting");
HPSFormView* viewForThisController = [ [ HPSFormView alloc ] initWithFrame:_frame ] ;
self.view = viewForThisController;
}
When a certain button is pressed within the view then the same UIViewController gets control again and at this point I wish to completely change the view that the controller is showing. As follows:
-(void)buttonTapped
{
ABCFormView* newview = [ [ ABCFormView alloc ] initWithFrame:_frame ] ;
self.view = newview;
}
However, the buttonTapped method does not load the second view. A completely blank view is shown instead.
How do I get the view controller to reload a completely different view when the button is pressed?

However, the buttonTapped method does not load the second view. A completely blank view is shown instead.
Is it not possible that the problem is in the way you create ABCFormView? I mean, it seems that the original view is replaced by an empty view, so check how the latter is created...
EDIT AFTER YOUR COMMENT:
if you say that the view is "created within a viewDidLoad method within the view controller", then you should instantiate your view controller:
#property (...) ABCFormViewController* newviewController;
....
-(void)buttonTapped
{
self.newviewController = [ [ ABCFormViewController alloc ] init] ;
self.view = newviewController.view;
}
keep in mind that newviewController must be around as long as you are using its controlled view, otherwise you will get a crash. That is the reason why I store its reference in a property.

Obviously you can't get your new view visible by simply setting self.view = newView; because the newView has never been added as a subview to any other views yet - i.e. not in the window.
If you need to switch to a different view, you should probably add APSFormView as a subview to your viewcontroller.view, and when you need to switch, remove APSFormView from superview then add ABCFormView as a new subview to viewcontroller.view.

If your loadView implementation needn't do much else, it may be better to use the storyboard to set it up initially. It is easy to miss, but you can specify in the storyboard that the view should be of a custom type (in the "identity inspector" with the view selected). Further, it may be worth evaluating why a completely different class of view is necessary for the same view controller instance; to me this may be a red flag regarding the application design. You may be better served by a flow between two view controllers, or else write some state-changing logic in this custom UIView-extending class. The decision for me would be made based on the model being represented by the views, along with which behaviors each is designed to facilitate.
If the models are different (i.e. your first view shows a list of accounts, second shows one account detail), or if the behaviors are significantly different (i.e. the first is viewing an account and the second is creating a new one), then I would use two distinct view controllers.
If the models and behaviors are similar, and the style should change, then I would likely write state-changing code in the custom view class to rearrange things, etc.
If you are coming from a different platform, it can seem silly at first, but we really do throw around view controllers without much hesitation. They are elegantly handled by the framework, and are designed to manage "a screenful of content" and be easily swapped for another screenful.

It is hard to tell without knowing what is inside ABCFormViewController. I had some timing issues once on a view controller which I just needed to generate the view because I wanted to capture its content to create a pdf file (its a view that never gets displayed onscreen). In that case I needed to insert a code like this:
[newviewcontroller.view setneedsrefresh];
Before I do
otherVC.view = newviewcontroller.view;
Otherwise I get a blank page.
I believe I get this because by the time everything is sorted out ARC deallocates newviewcontroller so the view is nil. In your case this may not be the problem. Is there a reason why you need a 2nd and 3rd view controller to put into your view because a much simpler way of doing this is to just transfer control to the other view controllers via modal, pop view or a navigationController. Another more usual way is to create multiple views in your XIB and then just load it into a blank view instead of creating view controllers for each of them.

Related

Does presenting a view controller nullify all objects?

I have two view controllers, FirstViewController and FourthViewController. FirstViewController is my initial view controller. I have a UILabel, mainLab, in FirstViewController. I present FourthViewController with
[self presentViewController:[self.storyboard instantiateViewControllerWithIdentifier:#"FourthViewController"] animated:YES completion:nil];
Then, I use delegates to try changing the text of mainLab in FirstViewController, after FourthViewController is pushed. This, however, isn't changing the text of mainLab. I checked and mainLab does become null after the view controller is pushed. That being said, how would I change the label's text while FourthViewController is pushed even though mainLab gets nullified by FourthViewController's pushing?
You should show some of your code. However, this is my guess that I take from similar experiences that I had before I understood the view controller's life cycle (which is quite well documented BTW).
I assume that you set the UIControls (UILabel etc) directly in your delegate methods. If that is true: Don't do hat. Do not access any IBOutlet or view item directly from without a view controller.
Instead declare a property that is an NSNumber or NSString, just well suited to carry the data that you want to set. Provide a custom setter that sets the related UILabel etc. too but only, if the it is not nil. (You may as well declare private instance variables and just provde the quasi setter method.)
In your viewDidLoad method, too, set the UILables etc. according to the values of the properties.
What happens in your case is that you set, lets say the text of a UILable before or even just after presentViewController. In your single line of code you even instanciate the view controller and then present it. Take that literally. The view controller is instanciated. Meaning a new one is created and all values get their defaults (which might be nil in some cases or empty strings or whatever has been set in the storyboard).
But even if you acces the UILabel (etc.) from the calling view controller directly after that, there is no guarantee that the newly instanciated view controller has been fully loaded in the meantime. In the event that the new view controller is loaded a bit later then your settings will be overwritten again. Unless you follow my advice.
There are other "hacks" that aim to force loading of the new view controller before actually setting its view items values. I tried one or two of them and they worked fine. But there is not guarantee that they will work in the future too and that is why I suggest to do it properly.

Best alternative to overlapping subviews

I have a UIView containing a login form, however when the user is already logged in, I want to show a logout button instead of the form.
My current approach is creating a subview for both the login form and the login button, overlapping each other in Interface Builder.
I would then only show one of the subviews at a time.
Is there a better way of doing this so it is easier to design in Interface Builder, while still using the same UIViewController?
If it matters the view is a modal view.
You could tweak your UIViewController to actually be a UITabBarController, but tweak the tab bar so that it's not visible when the controller is pushed to the screen:
self.hidesBottomBarWhenPushed = YES;
This should allow you to:
Programmatically switch between views easily
Manage two (or more) separate UIViewController instances in IB easily, without overlapping
For optional / modal parts of a view controller, one approach I've used several times in the past is to create a view for each section as top level objects in the nib. You can arrange each view using interface builder more easily then, and then all you need to do in your code is conditionally add the appropriate view to the main view in your viewDidLoad method. Remember that, as top-level objects in the nib, they should have strong outlets, not weak outlets.

MVC and UIButtons

I have an iPad project I'm working on that dynamically creates a number of buttons that I need to add to the main view of my application. I was planning on firing a method that lives inside my View from my ViewController...say it's called add_buttons. It's my impression that in the MVC pattern, the view should handle the rendering of buttons and the general display, but the controller should handle the interaction. I'm also under the impression that it's bad design for the view to "know" about the view controller.
So I guess my question is, should I have my button tap logic contained in my controller? If so, how do I handle the separation? If I'm creating the buttons in my view, then it would have to have a reference to the view controller to use as the delegate for the event handler. If I create them in the controller, then I feel like I need to set certain UI elements in the controller which seems wrong to me. I know I'm missing something obvious but searching so far has proven fruitless.
Can anyone point me in the right direction?
The button tap logic most certainly belongs in the controller.
The code that creates the buttons probably belongs in the controller too. However, it would make sense to include a view method that returns a button with its appearance configured but not its behaviour.
Some general pointe:
View controllers and their view are very tightly coupled.
If in doubt it's best to put the code in the view controller.
A views interface should deal with primitive data types (e.g. strings, numbers, images). Views should not need to know the specifics of model objects.
Also, the naming convention for methods lowerCamelCase. Eg:add_button should be addButton.
when you add button to view,
[myButton addTarget:nil action:#selector(myButtonPressed) forControlEvents:UIControlEventTouchUpInside];
in view controller add
-(void)myButtonPressed {
}
with target:nil event will be handled by first controller containing this view
I rescently finished a project where I had to dynamically create UIViews which contained both UITextfields and UILabels.My approach to this problem was to call the "addViews" function from within the view controller. I figured this to be a better and easier approach rather than dealing with view controllers from inside the UIView. I have tested my approach and works completely fine (dynamically change UIView size and adding other UIViews with a UITextfield and a UILabel).

How to bind nib custom view to a NSVIew subclass

I have a simple requirement.
On Click of a + button, I am trying to add a custom view to a SplitView.
I have created a class MyCustomView which is a subclass of NSView
In the applications nib file, I have a custom view which contains the buttons etc.
Now How to allocate a new MyCustomView every time ?
Is there an example to do this?
I am hoping something like
MyCustomView *v1 = [[MyCustomView alloc] init];
..
..
[splitView addSubView:v1];
[splitView addSubView:v2];
...
Please help
It's hard to tell exactly what you're describing based on your description but let's see if I understand you. You want to add a "copy" of your custom view assembly into a split view each time "+" is clicked, right?
The absolute best way to do this is to put the custom view assembly that will be copied (the "prototype") in its own xib. For each object you want to represent, you will instantiate a new copy from the xib and give it to some owner then add it to some parent view (a split view in your case ... odd for an unlimited number of views, but I don't have enough detail to say otherwise).
So. In the modern Cocoa world, such a view assembly should likely have its own view controller (NSViewController). This makes things easier for you since the xib's File's Owner will be an instance of your MyCustomViewController, whose -view is connected to the main container view in the xib (your custom view with all its subviews) and whose -representedObject is set to whatever model object your custom view represents. Your app will then maintain a list (an array or a dictionary, perhaps) of all the view controllers for the model objects. See this SO question/answer for a run-down of how to load from nibs/xibs.
This is basically how an NSCollectionView works (though the views must all be the same size - might not work for you). The collection view corresponds to your split view in this case; NSCollectionViewItem corresponds to your MyCustomViewController (and in fact on 10.5 and above NSCollectionViewItem is a subclass of NSViewController); your custom view is the collection view item's main -view. For each model object in its collection, it instantiates an NSCollectionViewItem and loads the view prototype from a xib (ideally, but this is optional), and uses this to set the item's view, then it sets the item's represented object (the model object).
I hope this clarifies things a bit. You've got some reading to do in order to understand enough of the nuts and bolts, but if you're still stuck, you might try editing your question to clarify or opening a new, more specific question.

How do I create a UINavigationController with a default "back" state?

I have a UINavigationController, complete with table view and associated magic.
The data I'm populating that table view from may have items from multiple categories, but the default view for the user will be one in which they are viewing all of the items, and then they have the ability to move backwards to a different table view that would allow them to select a different category, which would then return to the original table view with the appropriate data populated.
What's the proper approach for this? I can't seem to wrap my head around how I would make the navigation controller give me a back button (with appropriately wired up actions) without having come from a previous view in the stack (which wouldn't really exist at launch time if I start the user off from what is essentially the detail view, in stack terms.)
Also, the back button should be titled "Groups", not "Back", but that's really just an implementation detail. :)
Update: This issue finally manifested itself in production code, and here’s how I fixed it:
My UINavigationController is created in a nib, with the root view set as the “groups” view. Then, in my app delegate, I push the second view onto the stack while the app is launching.
That works fine for achieving the proper stack, but that doesn’t help with the back button title, because the navigation controller didn’t seem to want to grab the title from the root view, and instead was showing a back button with “Item” as the title.
So, on the pushed view, in viewDidLoad, I set:
self.navigationController.navigationBar.backItem.title = #"Groups";
and that did the trick.
The only potential downside of doing it this way would be if the pushed view controller were ever used in a scenario where the view below it wasn’t the groups view, but since the design of this particular application ensures that never happens, I’m accepting that failure. ;)
Another update:
I’m an idiot. Just set the title property of the navigationItem provided by the navigationController in Interface Builder, and boom, no issue. Or do it in code. It doesn’t matter, just don’t do it by setting the backItem.title way I show you above. That’s just dumb.
In your application delegate's .m file in the application:didFinishLaunchingWithOptions: method just push your view controllers like you normally would with[self.navigationController pushViewController:your_view_controller animated:YES]; and it should push them on before the application's first view controller appears.
To change the text of the button to Groups just call this before pushing your controllers.:
UIBarButtonItem *newBackButton = [[UIBarButtonItem alloc] initWithTitle: #"Groups" style: UIBarButtonItemStyleBordered target: nil action: nil];
[[self navigationItem] setBackBarButtonItem: newBackButton];
[newBackButton release];