Preload a modal view controller - objective-c

There's a way to preload a modal view controller without showing it?
I'm asking because when I'm displaying the modal view controller (allocating and initializing the view controller class), the view have a little delay to load and display.
After it, when I try to show it again, the delay stops.
I'm using a UITableView, and inside the method scrollViewDidEndDragging I check if the contentOffset.y is less than -90. If so, the user basically "pulled to refresh", then I'm loading the view.

You can use UINib to preload the XIB into memory.
It will pre-decode the object-tree of the XIB into memory, but won't instantiate the objects in the XIB. Then you can call the instantiate method of UINib to instanciate the view.
You can also force the view of a UIViewController to be loaded by calling its view getter method (as the view is lazy-loaded, so it will be loaded from the XIB the first time it is accessed).
But as you can only instantiate UI objects (from your XIB) on the main thread -- as every UI component and UI action has to be done in the main thread -- your app will still "freeze" (block the main thread) while you load the view. You can't for example load the root UIView of your XIB in a separate thread or queue (you will have an runtime exception if you try that).
Use Instruments and its "Time Profiler" tool to check where exactly the loading process of your XIB file takes time. (Maybe it is easy enough to resolve, or maybe you will have some component that you can lazy-load only after the view has been displayed, etc.)

you could call the viewWillAppear, viewDidLoad by hand.

You can load all the contents of the view on viewWillAppear and then show it.
Alternatively you can load all the properties of the modalview and then show it with a method. Making the view a property.

You could try accessing the view, this will make the viewDidLoad method to get called, although this is not the way things should be done...
//Preload
YourViewController * controller = [[YourViewController alloc] init];
controller.view.backgroundColor = [UIColor whiteColor];
.
.
.
.
//Load Controller after time
[self presentModalViewController:controller animated:YES];

Related

Correct method to present a different NSViewController in NSWindow

I am developing an app that is a single NSWindow and clicking a button inside the window will present a NSViewController, and a button exists in that controller that will present a different NSViewController. I know how to swap out views in the window, but I ran into an issue trying to do this with the multiple view controllers. I have resolved the issue, but I don't believe I am accomplishing this behavior in an appropriate way.
I originally defined a method in the AppDelegate:
- (void)displayViewcontroller:(NSViewController *)viewController {
BOOL ended = [self.window makeFirstResponder:self.window];
if (!ended) {
NSBeep();
return;
}
[self.box setContentView:viewController.view];
}
I set up a target/action for an NSButton to the AppDelegate, and here's where I call that method to show a new view controller:
- (IBAction)didTapContinue:(NSButton *)sender {
NewViewController *newVC = [[NewViewController alloc] init];
[self displayViewcontroller:newVC];
}
This does work - it presents the new view controller's view. However if I then click any button in that view that has a target/action set up that resides within its view controller class, the app instantly crashes.
To resolve this issue, I have to change didTapContinue: to the following:
- (IBAction)didTapContinue:(NSButton *)sender {
NewViewController *newVC = [[NewViewController alloc] init];
[self.viewControllers addObject:newVC];
[self displayViewcontroller:[self.viewControllers lastObject]];
}
First of all, can you explain why that resolves the issue? Seems to be related to the way the controller is "held onto" in memory but I'm not positive.
My question is, how do I set this up so that I can swap out views from within any view controller? I was planning on getting a reference to the AppDelegate and calling displayViewcontroller: with a new controller I just instantiated in that class, but this causes the crash. I need to first store it in the array then send that reference into the method. Is that a valid approach - make the viewControllers array public then call that method with the lastObject, or how should this be set up?
What is interesting in your code is that you alloc/init a new view controller every time that you call the IBAction. It can be that your view its totally new every time you call the IBAction method, but I would think that you only have a limited number of views you want to show. As far as my knowledge goes this makes your view only to live as long as your IBAction method is long. That the view still exists, is because you haven't refreshed it. However, calling a method inside a view controller that is not in the heap anymore (since you left the IBAction method and all local objects, such as your view controller are taken of the heap thans to ARC) makes the app crash, because you reference a memory space that is not in use or used by something else.
Why does the app work when you ad the view to the viewcontrollers array? I assume this array is an array that has been initiated in the AppDelegate and now you add the view controller with a strong reference count to the viewcontrollers array. When you leave the IBAction method, the view controller still has a strong reference and ARC will not deallocate the view controller.
Is this the proper way? Well, it works. I would not think it is considered very good programming, since you don't alloc/init an object in a method that needs to stay alive after leaving the method. It would be better practice to allocate and initialize your view controller(s) somewhere in an init, awakeFromNIB or a windowDidLoad method of your AppDelegate. The problem with your current solution is that you are creating an endless array of view controllers of which you only use the last. Somewhere your program will feel the burden of this enormously long array of pretty heavy objects (view controllers) and will run out of memory.
Hope this helps.
By the way, this is independent of whether you use Mavericks or Yosemite. I was thinking in a storyboard solution, but that wouldn't answer your question.
Kind regards,
MacUserT

Instantiating nib lazily in UIViewController's view property accessor

In the application I'm currently working on, there's a case where I have 2 screens for a set of data: one is a list and one is a map. Each screen has its own view controller. The default screen is the list view, so that view controller loads first. But the other map screen view controller is also loaded and set up (as it encapsulates some geographic map-related data that the list screen view controller uses), even though the map screen is not visible yet.
I don't want the map screen view controller's nib and views to be loaded and initialized until the user switches to that screen, however.
Is there anything wrong with overriding the view property accessor within the map screen view controller as in the code below, and lazily loading/instantiating the nib? (The view property is not accessed until the map screen is about to be displayed, right before the map screen view controller's viewDidLoad method gets called.) I've tested that this works well, but I've never seen it done this way.
- (UIView *)view {
if (!_view) {
UINib *nib = [UINib nibWithNibName:#"MyNibName" bundle:nil];
[nib instantiateWithOwner:self options:nil];
}
return _view;
}
Figured out a better answer to this.
The code I'm working with isn't actually my own, and I hadn't noticed that the map screen's view controller wasn't actually a subclass of UIViewController, it was just subclassing NSObject (and adding its own view property).
By changing the map screen's view controller to actually inherit from UIViewController, and then using the designated initializer of initWithNibName:bundle:, the nib is by default lazily loaded/instantiated when the view property is accessed -- just like I was doing.
So, the answer to my question would be this: use the system frameworks and you won't even run into these issues! :) But it does seem to be the case that my code aligned with the actual best practice pattern; see Apple's guidelines and recommendations here.

Reloading a view

I can't find the correct method on how to reload a modal view controller from within itself. I'm developing a very simple UI based app but I'm not sure how to do this.
I open the modal view with the following:
MainView *mainmview = [[MainView alloc] initWithNibName:#"submod" bundle:nil];
mainmview.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self mainmview animated:YES];
and then can dismiss it depending on iOS version. However this has no effect when used on what i need to be a simple restart button. Any help is appreciated.
You will have to implement your own "refresh" functionality - define a "refresh" method inside the modal view controller's class and do whatever needs to be done inside it - if for instance you have a tableview that requires a reload or to refresh your views depending on new data, etc...

View Controller behaves differently when set as 'initial view controller' vs. loading with presentModalViewController

My app has a map that tracks the user's location. This map will only appear under certain circumstances, and will dominate the user's attention until a particular task is complete, which is why the map isn't part of a navigation or tab bar UI.
If my map VC is set as the initial view controller in storyboard, it works fine. But if I try to load the map VC from elsewhere like this;
MapViewController *mapVC = [[MapViewController alloc] init];
[self presentModalViewController:mapVC animated:YES];
I just get a black screen.
I can confirm with NSLog that the VC is calling viewDidLoad and viewDidAppear, but the 'map' property of the VC is (null). I don't understand why (or how) I need to create the map property manually when using this technique, but it gets done for me when it is the initial VC.
The MapViewController instance in your storyboard is configured with a view hierarchy, including an MKMapView, and whatever else you did to configure that particular instance in the storyboard.
Now in this code which you show here, you are creating a completely new instance of MapViewController. It has no relationship to the instance in the storyboard other than they happen to be of the same class. So the one you create here with [[MapViewController alloc] init] has no view hierarchy (which is why you see a black screen), and none of the outlets or other configuration you may have made to the other MapViewController in your storyboard.
So what you want is to load that MapViewController that you've already set up from the storyboard. Assuming you are doing this from within a method in another view controller loaded from the same storyboard already, you can just do this:
// within some method on another vc from a scene in the same storyboard:
// given an identifier for the map view controller we want to load:
static NSString *mapVCIdentifier = #"SomeAppropriateIdentifier";
NSLog(#"Storyboard: %#",self.storyboard); // make sure this vc(self) was loaded from a storyboard
MapViewController *mapVC = [self.storyboard instantiateViewControllerWithIdentifier:mapVCIdentifier];
[self presentModalViewController:mapVC animated:YES];
And then back in the storyboard, just make sure you set the identifier for this map view controller to "SomeAppropriateIdentifier".
Hope that helps.

Loading a Custom ViewController with data

I have a UITableView which loads through it's navigationController a new viewcontroller.
This code goes in the tableView:didSelectRowAtIndexPath method:
ConcertDetailViewController *detailVC = [[ConcertDetailViewController alloc] initWithNibName:#"ConcertDetailViewController" bundle:nil];
The UITableView has a model, I want to sent an element of this model to the newly created ViewController.
detailVC.aProd = [_prod objectAtIndex:indexPath.row];
When the value is set I want the detailVC to draw the data on the screen. I thought a custom setter, overwriting the one generated by #synthesize would work.
-(void)setaProd:(NSMutableDictionary *)aProd {
_aProd = aProd;
[self displayAProd];
}
displayAProd just takes the values in aProd and put's them on the screen, or rather I'm setting some value of an outlet , created in my nib file.
self.prodNameLbl.text = [_aProd objectForKey:#"name"];
Nothing special about this. But it just doesn't work. I figured out why, I think.
It's because the setter executes way faster then, loading the whole view into memory.
If I put self.prodNameLbl.text = #"something"; in the viewDidLoad method it does display the correct value in the label.
A quick workaround would be the see if _concerts has been set and from there call displayAProd. Here I'm doubting myself if it's a good way to load a view. What if the custom setter takes longer to execute the loading the view. The test to see if _concerts has been set will be false and nothing will be displayed. Or is that just impossible to happen ?
Or maybe there's a better pattern for loading views and passing data to them to be displayed.
Thanks in advanced, Jonas.
The problem is that when you load the view controller from the NIB, the IBOutlets will not be connected to your UILabel and other similar properties during the initWithNibName call.
You need to wait for viewDidLoad to be called on detailVC and call [self displayAProd] from there. At this point, the connections will have been made.
Do a quick test. Put a break point in your didSelectRowAtIndexPath method and, after initialising detailVC, check to see if prodNameLbl is null or not.