I am working on creating an iOS app for iPhone and I am running into a problem with the UITabBarController I am using. Whenever I click a tab that is in the tab bar, the program crashes. I am not using storyboard or anything of the sort and have opted to do everything through code. That being said, here is the code that sets up the tabs.
- (void) loadView
{
// create main view
UIView *contentView = [[UIView alloc] initWithFrame: [[UIScreen mainScreen] applicationFrame]];
contentView.backgroundColor = [UIColor grayColor];
self.view = contentView;
// now create the tab pages
StatusViewController *status = [[StatusViewController alloc] initWithNibName: nil bundle: nil];
OperationsViewController *operations = [[OperationsViewController alloc] initWithNibName: nil bundle: nil];
self.statusTab = status;
self.operationsTab = operations;
status.title = #"Status";
operations.title = #"Operations";
// create the tab bar controller and add all tabs
UITabBarController *tabbar = [[UITabBarController alloc] init];
tabbar.view.frame = CGRectMake(0, 0, 320, 460);
NSMutableArray *tabs = #[status, operations];
[tabbar setViewControllers:tabs];
[self.view addSubview: tabbar.view];
}
The tabs do show up correctly along the bottom of the app, and the content for the first tab is loaded correctly by default, by clicking any tab causes it to crash. When I do crash I am not getting a stack trace and XCode just higlights...
#autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([MyAppDelegate class]));
}
Which is not very helpful (if somebody can comment how to get a stack tract in Xcode 5.1.1 I will be more than happy to update this post with additional information).
The classes that I have created, StatusViewController and OperationsViewController are both just subclasses of UIViewController where I added custom logic in loadView.
I saw other questions that seemed similar here on StackOverflow, but they were either using interfacebuilder/storyboard, or something else was different enough I felt the need to ask. Look forward to hearing any help anybody can offer me.
Thanks!
This is due to memory issues (often called a zombie). You aren't retaining the UITabViewController, so the views that should be managed by a UITabViewController are causing the crash.
When you use a view managed by another UIViewController, you can add them to the childViewControllers array of the parent UIViewController.
[self addChildViewController:tabBar];
[self.view addSubview: tabbar.view];
[tabBar didMoveToParentViewController:self];
Apple refers to this feature as "Custom Container View Controllers" https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/CreatingCustomContainerViewControllers/CreatingCustomContainerViewControllers.html
Related
I am currently having an issue with my login flow. The logic is all there but the animation from the login view to the main view is weird.
Heres a side note: The main view is a UITabBarController with 5 UINavigationControllers and the login view is just a UIViewController
So first I determine in - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions , whether the current user is already logged in (I am using Parse to handle that), if their not then set the UIWindows rootViewController to be the Login controller. Else, set the UIWindows rootViewController to be the main view.
This works fine... Assume that the user is logged in on app launch. Okay cool, it shows the main View. I then sign out and everything is working as expected. I switch the UIWindows rootViewController by running this method:
- (void)showLogin {
self.activity = nil;
self.chat = nil;
self.create = nil;
self.notification = nil;
self.more = nil;
self.loginViewController = nil;
if (!self.loginViewController) { // this check is redundant i feel lol
self.loginViewController = [[PHLoginRegisterViewController alloc] init];
}
[UIView transitionWithView:self.window
duration:0.5
options:UIViewAnimationOptionTransitionCrossDissolve
animations:^{
self.window.rootViewController = self.loginViewController;
} completion:^(BOOL finished) {
self.activity = [[PHActivityViewController alloc] init];
self.chat = [[PHGlobalChatViewController alloc] init];
self.notification = [[PHNotificationsViewController alloc] init];
self.more = [[PHMoreViewController alloc] init];
[self.tabBarController setViewControllers:#[[[UINavigationController alloc] initWithRootViewController:self.activity],
[[UINavigationController alloc] initWithRootViewController:self.chat],
[[UINavigationController alloc] init],
[[UINavigationController alloc] initWithRootViewController:self.notification],
[[UINavigationController alloc] initWithRootViewController:self.more]]];
[self.tabBarController setSelectedIndex:0];
}];
}
Okay, so the login view is now shown with a nice transition... Then when I attempt to login, everything works fine, but when the transition is happening login->main, the entire main view (except for the position of the UITabBar of the UITabBarController) is basically set -20px for a brief moment, then gets reset to its expected position... Heres two screenshots of when the login is transitioning to the main view, then when it is done transitioning...
I've tested this exact implementation without a UITabBarController and it doesn't do this. This sorta GLITCH issue. Any ideas that anyone can pass along?
I want to know if its possible to do this login->main and main->login transition without having to implement the main's viewDidAppear or viewWillAppear methods.
For example, in the main views viewDidAppear,
if (user is logged in)
[self presentViewController:login animated:NO]
This causes the main screen to be seen for a brief moment before the loginview is shown, which I don't want.
I think the problem is that before your transition is completed, the auto layout process is not triggered, so your view is the position it is in your xib design.
I have an idea to work around this, whenever the app is launching, you use you main view controller as the root view controller, and if you need login, in main view controller's viewDidLoad, you load the login view controller and add it's view as the top subview of main view controller, this will solve problem that presentation has.
The only drawback is that your login view need a opaque background.
I have recently removed the tab bar from my app in favor of a "slide out" styled menu which I have written myself. It appears to be working perfectly, except that the one split-view controller I use in the app does not work correctly on the iPad in portrait orientation (landscape is just fine). The problem I'm seeing is the detail VC ONLY is loaded into the portrait view, where both VCs load correctly in landscape view. This is on iOS7.
So, here is the code I'm using. This code has been moved from the AppDelegate (previously) to the root VC. There were some changes but relatively minor. It did work fine with the tab bar, but using this new slide out menu (i.e., a table view) I have this one problem. I'm only including the portions of the code that I think would be relevant.
AdminMasterViewController *adminMasterVC = [[AdminMasterViewController alloc] init];
UINavigationController *adminMasterNav = [[UINavigationController alloc] init];
adminMasterNav.viewControllers = [NSArray arrayWithObjects:adminMasterVC, nil];
adminMasterNav.view.frame = CGRectMake(0,0,[Utility screenWidth],[Utility screenHeight]);
AdminDetailViewController *adminDetailVC = [[AdminDetailViewController alloc] init];
UINavigationController *adminDetailNav = [[UINavigationController alloc] init];
adminDetailNav.viewControllers = [NSArray arrayWithObjects:adminDetailVC, nil];
adminDetailNav.view.frame = CGRectMake(0,0,[Utility screenWidth],[Utility screenHeight]);
UISplitViewController *adminSplitVC = [[UISplitViewController alloc] init];
adminSplitVC.viewControllers = [NSArray arrayWithObjects: adminMasterNav, adminDetailNav, nil];
adminSplitVC.delegate = self;
adminSplitVC.title = #"Admin";
adminSplitVC.view.frame = CGRectMake(0,0,[Utility screenWidth],[Utility screenHeight]);
vcArray = [NSArray arrayWithObjects:homeVC, adminSplitVC, expressiveNav, receptiveNav, typerNav, nil];
(The last line builds the array of all the VCs in the project; the code which creates these VCs has been omitted).
After the VC has been selected it is presented as a child VC as follows:
UIViewController *vc;
if ([selected isEqualToString:#"Home"])
{
vc = [vcArray objectAtIndex:VCHome];
}
else if ([selected isEqualToString:#"Administrator"])
{
vc = [vcArray objectAtIndex:VCAdmin];
}
... (others listed here)
[self.view addSubview:vc.view];
[self addChildViewController:vc];
[vc didMoveToParentViewController:self];
From what I have read there may be some issue with NOT using the tab bar, but the articles are confusing because a lot of them are dated. I will appreciate any input or suggestions on this problem as I've read everything I can find and don't really know where to go from here. TIA.
After a couple of days of reading and trying different things, an hours after posting the question I solved this. Just in case anyone else hits this same problem, here's the solution:
I subclassed the UISplitViewController and made it its own delegate.
Then, this delegate method solved it:
- (BOOL)splitViewController:(UISplitViewController *)svc shouldHideViewController:(UIViewController *)vc inOrientation:(UIInterfaceOrientation)orientation
{
return NO;
}
When I have a "loading" root view controller when the app starts which will determine to load either the slide menu controller or another controller, the menu does not work. It is visible, and the animations work just fine, only all touches are disabled.
I've distilled my setup to this:
In the app delegate
self.window.rootViewController = [[TestViewController alloc] init];
And in the testViewController viewDidAppear:
SlideNavigationController *slideNavigationController = [[SlideNavigationController alloc] initWithRootViewController:[[ARootViewController alloc] init]];
slideNavigationController.leftMenu = [[UINavigationController alloc] initWithRootViewController:[[MenuViewController alloc] init]];
slideNavigationController.menuRevealAnimator = [[SlideNavigationContorllerAnimatorScaleAndFade alloc] initWithMaximumFadeAlpha:0.6f fadeColor:[UIColor darkGrayColor] andMinimumScale:.8];
slideNavigationController.enableSwipeGesture = NO;
slideNavigationController.view.layer.shouldRasterize = NO;
[self presentViewController:slideNavigationController: animated:YES callback:nil];
But then the menu does not respond to touches. If the slide navigation controller is the root view controller in the app delegate the menu does work correctly. It is really a matter of that the slide navigation controller must be the apps root controller. Is there a workaround or fix for this?
For others viewing this thread, implement SlideNavigationControllerDelegate and add below function in every controller that you do not wish to show side menu icon.
And most importantly it should be the root view controller in app delegate, otherwise it won't work.
Refer to documentation of developer here.
- (BOOL)slideNavigationControllerShouldDisplayLeftMenu
{
//or no based on your preference
return YES;
}
In my iPad application, i have a main menu screen.. with various icons on it.
On tapping on an icon, it navigates me to Custom UISplitViewController. I did it with this code. Everything is working fine in SplitView.
Problem: I am facing issue to get back to my Main Menu screen on tapping a button in MASTER Viewcontroller's Navigation bar.
code for Custom UIsplitview:-
self.navigationController.navigationBarHidden = NO;
self.navigationController.navigationBar.barStyle = UIBarStyleBlack;
left = [[LeftViewController alloc] initWithStyle:UITableViewStylePlain];
UINavigationController *leftNav = [[UINavigationController alloc] initWithRootViewController:left];
right = [[RightViewController alloc] initWithNibName:#"RightViewController" bundle:nil];
UINavigationController *rightNav = [[UINavigationController alloc] initWithRootViewController:right];
left.right = right;
splitViewController = [[UISplitViewController alloc] init];
splitViewController.viewControllers = [NSArray arrayWithObjects:leftNav,rightNav, nil];
splitViewController.delegate = right;
appDelegate = (AppDelegate *)[[UIApplication sharedApplication]delegate];
UISplitViewController *cvc = (UISplitViewController *) splitViewController;
[appDelegate.window setRootViewController:cvc];
EDIT: Custom button code
UIButton *a1 = [UIButton buttonWithType:UIButtonTypeCustom];
[a1 setFrame:CGRectMake(0.0f, 0.0f, 32.0f, 32.0f)];
[a1 addTarget:self action:#selector(menu:) forControlEvents:UIControlEventTouchUpInside];
[a1 setImage:[UIImage imageNamed:#"icon.png"] forState:UIControlStateNormal];
UIBarButtonItem *random = [[UIBarButtonItem alloc] initWithCustomView:a1];
left.navigationItem.leftBarButtonItem = random;
- (void)menu {
[self.view removeFromSuperview];
ViewController *vc = [[ViewController alloc] initWithNibName:#"ViewController" bundle:nil];
[appDelegate.window setRootViewController:vc];
[vc release];
}
This causes CRASH, with EXC_BAD_ACCESS message.
Pls guide me..
I assume you can see which line of your code the crash happens on by stepping through from a breakpoint?
I'm not a fan of your sw design here to be honest as I wouldn't use a UISplitViewController subclass. I'd be more likely to create a custom SplitViewController using UIViewController containment APIs and then use that in conjunction with a UINavigationController.
That said, if you must use a UISplitViewController subclass then I would suggest having all your code to add or remove the viewControllers from the window in the main appDelegate and then use notifications to tell the appDelegate when to add or remove the relevant viewControllers (switch the root). This way you reduce the potential to have multiple instances of the same view controllers hanging around leading memory issues. It's always tricky to try to get a viewController to remove itself from it's parent, and is best managed by parent or higher level entity (in this case the main app delegate).
I'm not sure this is a good design. What else do you do from your main menu screen bedsides going to the split view controller?
If you want to keep this design, you would do the same thing you did at the end of your posted code -- in the button's action method, create an instance of your main menu controller and set it as the window's root view controller.
After Edit:
I can see one problem that's sure to create a crash -- when you create your button, you have the action set as "menu:", but your method implementation is just "menu" with no colon or arguments. Remove the colon, and everything should work.
I don't think these 3 lines are causing trouble, but there's no need to do what you're doing:
appDelegate = (AppDelegate *)[[UIApplication sharedApplication]delegate];
UISplitViewController *cvc = (UISplitViewController *) splitViewController;
[appDelegate.window setRootViewController:cvc];
You already have a property or ivar, splitViewController, there's no need to reassign it to cvc. Also there's no need to get the app delegate, you can get the window with self.view.window. So those 3 line can be changed to just this:
self.view.window.rootViewController = splitViewController;
So I have a popover with a button in it. When that button is pushed, I want the popover to go away. It seems easy enough, but I can't figure it out for the life of me. The code I use to show the popover is below:
AddCategoryViewController* content = [[AddCategoryViewController alloc] init];
UIPopoverController* aPopover = [[UIPopoverController alloc]
initWithContentViewController:content];
aPopover.delegate = self;
[content release];
// Store the popover in a custom property for later use.
self.addCategoryPopover = aPopover;
[aPopover release];
[addCategoryPopover presentPopoverFromBarButtonItem:sender permittedArrowDirections:UIPopoverArrowDirectionUp animated:YES];
Within the addcategoryviewcontroller, I have:
-(IBAction)saveAddCategory:(id)sender {
if (rootViewController == nil)
rootViewController = [[RootViewController alloc] init];
[rootViewController.addCategoryPopover dismissPopoverAnimated:YES];
[rootViewController dismissPopover];
}
Rootviewcontroller is where the popover is being created from. Unfortunately, neither of those methods work to dismiss it. any help?
You would be seeing a warning at this line.
aPopover.delegate = self;
and if you would execute your code. The app would crash. Instead you need to do it like this.
I have
- (void)viewWillDisappear:(BOOL)anAnimated
{
[self.dPopover dismissPopoverAnimated: NO];
self.dPopover = nil;
[super viewWillDisappear: anAnimated];
}
and don't see why this wouldn't work in your case.
Your if is a bit troubling, so my guess is you aren't talking to the view you think you are. rootViewController.addCategoryPopover is probably nil, because you made a new controller.
I think I answered just a similar question with the solution I used to dismiss a popover with a UIView loaded from a MKMapView.
The use of my solution is basically the same as for any other view loading a popover.
Have a look at:
How to dismissPopoverAnimated on iPad with UIPopoverController in MKMapView (SDK3.2). I hope that solved your problem.
use NSNotificationCenter To DissmissPoperController Fro Father viewControll