Problem
After checking and double checking the usual solutions, instantiateViewControllerWithIdentifier: is crashing for a reason beyond my realm of experience.
Details
I'm trying to use a storyboard view controller for paging with a UIPageViewController, with the pagecontroller being a child controller of a root controller, similar to how Apple sets up a page-based project.
I have my storyboard ViewController all labelled up:
And I'm initializing it as so for paging use:
-(MemoImageViewController *)viewControllerAtIndex:(NSUInteger)index
{
if (index >= [pageMemories count] || [pageMemories count] ==0) return nil;
MemoImageViewController * viewController = [_mainBoard instantiateViewControllerWithIdentifier:#"MemoImageViewController"];
viewController.memory = [pageMemories objectAtIndex:index];
return viewController;
}
_mainboard = an UIStoryboard reference of the main and ONLY storyboard I use.
Yet, the app crashes with a SIGABRT with zero explanation as to why. When I turn on exception breakpoints it leads me to the instantiateViewControllerWithIdentifier method.
What I've tried
I have tried launching a different viewcontroller from the storyboard, and IT WORKED. Leading me to believe it is something to do with the viewcontroller I'm using itself.
Changing the identity, title, storyboard ID up didn't have any effect.
I deleted derived data, snapshot, cleaned my project, built several different ways, no use.
I hope this is enough information to evaluate my problem, its very well possible I will just switched the view controller to a xib but I'd like to see if this won't work first.
Thank you, Happy holidays.
Try to change from _mainboard to self.storyboard
MemoImageViewController * viewController = [self.storyboard instantiateViewControllerWithIdentifier:#"MemoImageViewController"];
Related
Here's what I'd like to do: use a SplitViewController but not have it be the first thing that shows up. I want an initialization view (which does some network stuff and then completes) which then punches over to the split view.
I've got this working. BUT not sure if what I'm doing makes sense and it has a weird behavior.
Here's what I think I know: to use a SplitViewController, it must be the rootViewController. Also, working with separate storyboards seemed the only way to get it to work. (I've tried having them all in one storyboard but couldn't get it to work).
My first storyboard's controller does it's init and then, to switch to the "SplitViewBoard" storyboard and launch the split view, does the following:
let mainStrbd = UIStoryboard(name: "SplitViewBoard", bundle: nil)
let splitController = mainStrbd.instantiateInitialViewController() as UIViewController
if splitController is UISplitViewController {
var mySplitController: UISplitViewController = splitController as UISplitViewController
mySplitController.delegate = appDel
appDel.window?.rootViewController = splitController
} else {
debugPrint("badness")
}
Now, this all works fine. With one exception. After setting the rootViewController to the splitController, there's an 8-10 second delay before the split view shows up on the screen! I'm imagining there's some way of telling the delegate (or whatever) to "refresh" or of just being more explicit but haven't found it yet. Is this way reasonable? Or it there a much better way?
I want to do something like UITabBarController. UITabBarController has property viewControllers into which I can add instances of UIViewController * in IB. I'm trying to do the same think with my own VC:
#property (nonatomic, copy) IBOutletCollection(UIViewController) NSArray *viewControllers;
but this doesn't work. Is it even possible for us?
EDIT 1.
Ramshad posted the proper sample, but using XIB. I would like to achieve it using storyboards.
EDIT 2. - at the end of bounty worth...
I question vaderkvarn's post because in case of UITabBarController it works. Also as Ramshad posted, it is possible in NIBs. So far, dasblinkenlight's post is the most correct, but not answers the question. I'm holding this question opened because we shall find out if it's restricted for us or what is the way.
PS: Why these downvotes?
Although it does not look like you can connect UIViewControllers to IBOutletCollections (or there are too many restrictions placed on using them), there is a simple solution that works when you use storyboards. This code goes into your viewDidLoad method:
_viewControllers = [NSArray arrayWithObjects:
, [self.storyboard instantiateViewControllerWithIdentifier:#"Controller1"]
, [self.storyboard instantiateViewControllerWithIdentifier:#"Controller2"]
, [self.storyboard instantiateViewControllerWithIdentifier:#"Controller3"]
, nil];
The reason why the outlet collection solution does not work is that a View Controller is not an outlet. From the documentation:
An outlet is a property that is annotated with the symbol IBOutlet and
whose value you can set graphically in a nib file or a storyboard.
A view controller is not a graphical object.
If you want to use an IBOutletCollection, you should only use one view controller, and put the views in the collection.
But if you want one controller for every view, you need to go for a more programmatic approach. An array with view controllers might be a good start, but I couldn't say as I don't know what you want to do with them.
Edit:
To be more clear as you don't seem to catch my point:
No, it does not has to be a way. A Storyboard is not an API, it is a graphical tool for drawing scenes and segues. It is specially designed for things like Tab Bar based apps.
If you right click on your Storyboard file and choose open as -> Source Code, you will see that a Tab Bar Controller have special elements that other View Controllers do not have. To mess around with the XML in a Storyboard file is beyond me.
If you want to go with Nib files, use Ramshads answer.
If you want to get as close as possible with storyboards, go with dasblinkenlights answer.
But the answer to your question (as far as I know) is NO, there is no way to accomplish this with storyboards. If it were, it would have been documented, which it is not.
I have done your requirement using UISegmentedControl + IBOutletCollection + presentViewController.
You can use any control instead of UISegmentedControl as your wish.
I have Added 3 different UIViewControllers to Xib and labelled as ViewController1,2,3.
I also added 2 extra methods. One for presenting the corresponding view and another one for dismissing the earlier populated view.
I have attached the Xib settings screen-shot below.
You can use only one UIViewController instead of 3 and reuse it with some logic :)
The methods are below:
#property (strong, nonatomic) IBOutletCollection(UIViewController) NSArray *ViewCollection;
//dismissing the earlier populated view
- (IBAction)dismiss:(id)sender {
[self dismissViewControllerAnimated:YES completion:nil];
}
//presenting the corresponding view.
- (IBAction)resetAction:(id)sender {
UISegmentedControl *mySegment = (UISegmentedControl *)sender;
int index = 0;
for (UIViewController *viewItem in _ViewCollection) {
if (index == mySegment.selectedSegmentIndex) {
[self presentViewController:viewItem animated:YES completion:nil];
}
index++;
}
}
You can download the my sample application from here
Xib Settings Screen-shot
i'm cracking my head on memory issues with my app, the app are working fine except that it will crash once it hit low memory warning and are very very very laggy when using it for 10 to 20 minutes.
EDIT: how to poptoviewcontroller?
introvideo-> welcomeview & tutorialview-> mainviewcontroller-> scannerviewcontoller-> questionview ->(if answer correct -> correctView) else ->wrongView
how do i pop back to mainView controller ?
the below code are to solve adding view controller to the navigationcontroller.viewcontroller stack. As i'm using storyboard pushing from viewcontroller to another view contoller with out poping.
the code will pop to the viewcontroller that are already in the viewcontroller stack.
the flow of the of my storyboard as attached:
http://dl.dropbox.com/u/418769/storyboard%20flow.png
intro video -> welcome view & tutorial view (if username !exist) -> main view controller
this is the main file that user will alway go to.
http://dl.dropbox.com/u/418769/scannerViewController.h
http://dl.dropbox.com/u/418769/scannerViewController.m
i'm using a custom segue to pop viewcontrollers, which solved part of the problem.
-(void)perform {
UIViewController *sourceVC = (UIViewController *) self.sourceViewController;
NSInteger index = -1;
NSArray* arr = [[NSArray alloc] initWithArray:sourceVC.navigationController.viewControllers];
for(int i=0 ; i<[arr count] ; i++)
{
if([[arr objectAtIndex:i] isKindOfClass:NSClassFromString(#"mainViewController")])
{
index = i;
}
}
[UIView transitionWithView:sourceVC.navigationController.view duration:0.5
options:UIViewAnimationOptionTransitionCrossDissolve
animations:^{
[sourceVC.navigationController popToViewController:[arr objectAtIndex:index] animated:NO];
}
completion:^(BOOL completed)
{
}
];
}
however, the app are still eating up the RAM and VRAM.
I really appreciate any friends here to help solving my question, does Strong value caused this problem ?
When you say that you're using a "custom segue to pop to the Main View Controller", I'm not sure if I quite understand that. Are you using performSegueWithIdentifier? If so, then you're not popping; you're pushing another copy of the main view controller!
In most storyboards, you don't see a segue out of the right side child view looping back to the left of the parent view (and your screen snapshot showed a dizzying number of segue's entering back into the main view controller, which is a bit of a red flag). This is a far more customary storyboard (taken from Beginning Storyboards in iOS 5) at Ray Wenderlich):
(source: cloudfront.net)
Usually you'll dismiss a child view with something like the follow, not use a segue.
[self.navigationController popViewControllerAnimated:YES];
Of, if you wanted to pop back multiple levels, you would use popToViewController or popToRootViewControllerAnimated. Or if you use modal segues, you would dismiss the modal with dismissViewControllerAnimated.
If I've misunderstood what you mean't by "custom segue to pop", can you provide what code you're using do to that?
Computer-aided analysis is the way to solve this. Do Build and Analyze and resolve all issues. Run your app under the Leaks and Allocations instruments. Use heap-shot analysis.
#ken-thomases analysis is spot on. Also see Finding leaks.
I presume you are using ARC?
Can you explain what is the purpose of the above code sample and what your app is doing to require something like this? It feels like you're addressing a symptom rather than solving a problem.
In answer to your question, the use of strong is unlikely to be the source of problems, unless you have strong reference cycles (see Transitioning to ARC). If you follow Ken's suggestions, you'll identify where your app is leaking memory (assuming it is), and if so, where it is. At that point, if you still don't understand the source of the leak, you can post the offending code here and I'm sure there will be plenty of people willing to help. Also, if you have some code that you wonder whether the strong reference is inappropriate, post that relevant code and, again, I'm sure we'll be happy to help if we can.
I have a UITabBarController, and one tab is a UINavigationController. I have a search bar that goes to a certain view within the UINavigationController. The problem is that if the first view is not pushed by the UINavigationController, than it crashes because my search doesn't recognize the visibleViewController from this call:
UINavigationController *navController = [self.MainTab.viewControllers objectAtIndex:1];
FirstViewController *fVC = [navController visibleViewController];
What I don't understand is, before this code, I do this:
self.MainTab.selectedIndex = 1;
This code on its own selects the viewController in that tab, where then the view gets loaded to my knowledge. So shouldn't this be enough for the [navController visibleViewController] to get the current viewController? Thanks.
Try topViewController instead of visibleViewController.
FirstViewController *fVC = [navController topViewController];
From what you explain in your question and comments, I understand that your code tries to access an object of type FirstViewController, supposedly the first view to be pushed on to your UINavigationController, when it has not yet been created.
On the other hand, if you first programmatically select the tab, the view is created and everything works fine. Indeed, that view is created in a viewDidLoad method that is run when the tab is selected.
The solution I would suggest is avoiding accessing the UINavigationController visibleViewController directly from your search tab; instead, let your search code access the model (as in Model-View-Controller) for your app and store there the result; then, from the mentioned viewDidLoad method again access the model to read the search result and update/show the UI.
This is the clean solution, IMO. If you want a sort of workaround to your current design, then check the fVC value you get back from visibleViewController and if it is not what expected, then instantiate the view properly.
I hope this helps.
I know this has been answered, but I found another solution that might be helpful. In my case I was handling rotation differently for some viewControllers within my NavigationController, I did the following:
Subclass UINavigationController, then where needed in your new subclass you can access the current visibleViewController's title like so:
- (BOOL)shouldAutorotate
{
if ([[self visibleViewController].title isEqualToString:#"Special Case"]) {
return NO;
}
return YES;
}
This is not specific to rotation, this is just what I used it for. The only thing you have to do is set your self.title for each of the viewControllers you are checking against in their viewDidLoad, if they are set in IB or are not set they will be nil.
I have a TabBarController with 7 Custom ViewControllers and what i am trying to do is have the TabBarController on startup load the first ViewController in its array as well as a ViewController in one of the other tabs.
As far as i understand it the viewDidLoad method for the ViewController's is only called for that tab when it is first selected. Is there a way to force the TabBarController to call a ViewController viewDidLoad method even if its not active?
thx
Just reference the ViewController view:
[myViewController view]
If myViewController's view is nil, then it will be loaded.
Note that even if this approach is working, your app will be affected by the view loading/unloading mechanism which is controlled by the internal run loop logic and not by your app, while the view controller "internal logic" should be initialized by the initWithNib: method which is completely under your control. But this is just a suggestion to avoid strange bugs, anyway the solution proposed is working.
This technique seems to work pretty good. I have an app with a UITabBarController at the bottom with 5 buttons in it. When the user clicks the 3rd button, the viewDidLoad for that view took 5 seconds to do stuff so I used this technique to cause its viewDidLoad to get called when the app starts. I don't want the 3rd view to be displayed; just to be initialized so it shows up instantly when clicked.
I had to subclass the UITabBarController to something like FoohBarController, then in it's viewDidLoad I made a background thread do this:
{
// get a pointer to the 3rd item in the tab bar (0 based)
UINavigationController *navcon = [self.viewControllers objectAtIndex:2];
// get a pointer to the viewController I want to init
CalendarViewController *calendar = [navcon.viewControllers objectAtIndex:0];
// Just ask for the view. This will force the view to load and to initialize
UIView *v = calendar.view;
v = nil; // to remove a compiler warning
}