iPad UIModalPresentationFormSheet with UITabBarController's moreNavigationController edit mode issue - ipad

This is seemingly a bug, but i'm wondering if anyone can think of a workaround.
On iPad, you present a view controller as a UIModalPresentationFormSheet. This view controller is extending UITabBarController and has enough controllers to automatically display the "more" tab bar button. Once you tap on the more button it will display the list correctly, but as soon as you tap on 'edit' it presents the edit view larger then the actual form sheet (cropped inside the form sheet), causing the content to be out of view, including the toolbar with the "done" button. The only way to dismiss is to force quit the app.
To verify that it's not something specific to my app I started a single view project, and presented a simple modal view. This modal view controller extends UITabBarController and has the following init method:
- (id)init {
self = [super init];
if (self) {
self.modalPresentationStyle = UIModalPresentationFormSheet;
NSMutableArray *controllers = [NSMutableArray array];
for (int i = 0; i< 15; i++) {
UIViewController *vc = [[UIViewController alloc] init];
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:vc];
vc.title = [NSString stringWithFormat:#"view %i", i];
[controllers addObject:nav];
}
self.viewControllers = controllers;
}
return self;
}
I also tried adding the modalPresentationStyle to moreNavigationController with no change.

Good day, dizy.
A nice challenge you've made. Here is a solution, maybe it's a bit hardcore, but it works.
I've done as you wrote – subclassed UITabBarController and presented it as a modal view controller. And run into the same problem. When tapping "edit" button in "More" screen UITabBarCustomizeView appears and it's frame is inadequate.
So I've done the following. I've made MyModalTabBarVC a delegate of itself and implemented tabBarController:willBeginCustomizingViewControllers: method:
- (void)tabBarController:(UITabBarController *)tabBarController
willBeginCustomizingViewControllers:(NSArray *)viewControllers
{
UIView *modalView = self.view;
CGRect bounds = modalView.bounds;
UIView *customizationView = [[modalView subviews] objectAtIndex:1];
UIView *customizationNavBar = [[customizationView subviews] objectAtIndex:0];
CGRect navBarFrame = [customizationNavBar frame];
navBarFrame.size.width = bounds.size.width;
customizationNavBar.frame = navBarFrame;
customizationView.frame = bounds;
}
So when this method is called UITabBarCustomizeView is already created. And a wrong frame can be changed manually. If you log po [self.view subviews] at the start you'll get:
(id) $1 = 0x06c6a940 <__NSArrayM 0x6c6a940>(
<UITransitionView: 0xd744ab0; frame = (0 0; 540 571); clipsToBounds = YES; autoresize = W+H; layer = <CALayer: 0xd744b50>>,
<UITabBarCustomizeView: 0x6c5e570; frame = (0 -384; 768 1004); animations = { position=<CABasicAnimation: 0x6c569a0>; }; layer = <CALayer: 0x6c618d0>>,
<UITabBar: 0xd744110; frame = (0 571; 540 49); autoresize = W+TM; layer = <CALayer: 0xd742b80>>,
)
PS. This solution doesn't fix animation. As you can see from log, corrupted animation is already created and charged. I hope that canceling it and adding a new one, appropriate, will not be a problem.

The modal view's viewController must be causing the glitch.
You could try to:
hide the tab bar while editing and un-hiding it when the done button
is pressed.
create a custom toolbar for the view controller, this could be done
with a UIView, so that it's set always be on top of the view.
resize your individual tabs. Best way to do this is to create your
own custom tab bar with a UIViewController and IBActions connected
to UIButtons with IBOutlets.
Why would you have so many tabs in the modalPresentationStyle? I personally would use push segue instead.
Try pushing to a new set of view controllers that are under their own navigation controller as well. There would be more room for the tab bar. To get back, put a back button in the toolbar that pops the push, or pushes back to the original.

Related

Unable to click "under" a hidden TabBar

I hide my tab bar like so:
self.tabBarController.tabBar.hidden = YES;
And because now there is a black bar where it once stood I stretch the view which is a UIWebView on top(or is it under?) that empty space. The UIWebView is in a UIViewController. I do that with a constraint which by default is like so:
The code for the constraint:
if(self.tabBarController.tabBar.hidden){
self.webviewBottomConstrain.constant = -self.tabBarController.tabBar.frame.size.height;
}else{
self.webviewBottomConstrain.constant = 0;
}
However if I tap the device on the place where the TabBar was it will not execute. It is as if there is something invisible there with the size of the tab bar. I have also tried hiding it the way this thread sugests. Still the same result.
Update: It seems that when you tap on the invisible tab bar the tap is recognized by the tab bar and not by the view that is visible under the tab bar
self.extendedLayoutIncludesOpaqueBars = YES;
this will solve you problem
You hide your tabBar by setting its hidden property to NO? Try setting it to YES. Unless I am misunderstanding what you are trying to do, it seems like your tab bar is not hidden with that code.
Another thing I would check is to see if User Interaction Enabled is checked for the web view. If it is not, that can seem like there is something invisible blocking you from interacting with your view.
Well I am using quite ugly hack to fix this. I am hiding the tab bar in another way now:
if (shouldShow) {
self.hidesBottomBarWhenPushed = NO;
UIViewController *someView = [[UIViewController alloc] init];
[self.navigationController pushViewController:someView animated:NO];
[self.navigationController popToViewController:self animated:NO];
} else if (shouldHide) {
self.hidesBottomBarWhenPushed = YES;
self.tabBarController.hidesBottomBarWhenPushed = YES;
self.navigationController.hidesBottomBarWhenPushed = YES;
UIViewController *someView = [[UIViewController alloc] init];
[self.navigationController pushViewController:someView animated:NO];
[self.navigationController popToViewController:self animated:NO];
}
I do need that random view because I cannot push the view on itself.
I had the same issue when hiding the tab bar by moving it offscreen to the bottom. My custom UITabBarViewController was intercepting the touch events in the area vacated by the tab bar, so instead of changing the frame of the tab bar to move the tab bar offscreen, I extended the height of my tab bar view controller so that the tab bar still moved offscreen, but the child view above the tab bar now filled that space. This allowed the touches to be received by the child view.
As you may see with view hierarchy instrument, UITabBar is not directly blocking your tap, but your current view controller's view height is not full screen:
So, the tap doesn't response because your finger's y position is higher than view's maxY.
Code like this (inside your UITabBarController) will expand your view's height, according to tabbar visibility, and all tap events will work correctly.
func updateTabBarAppearanceWithDegree(_ degree: CGFloat) {
let screenHeight = UIScreen.main.bounds.size.height
let tabBarHeight = self.tabBar.frame.size.height
self.tabBar.frame.origin.y = screenHeight - tabBarHeight * degree
self.tabBar.alpha = degree
let currentNavigation = self.selectedViewController as? UINavigationController
if let currentTopView = currentNavigation?.viewControllers.last?.view {
currentTopView.frame.size.height = self.tabBar.frame.origin.y
}
}

Tableview frame issue when put in a scrollview

I wanted to post a gif but apparently I don't have enough reputation. Oh well, whatever; I was using UIPageViewController, but for some reason I decided to go with a more manual solution by using UIScrollView and adding the views of UITableViewControlllers to the corresponding offsets (pages). I have 4 UItableViewControllers on each page (the views of table view controllers) and all of these are added to the container view controller (which has the UIScrollView) as child view controllers.
The actual problem is when I made the switch, table views began refusing to go all the way down and part of the final table view cell stays trimmed by the end of the screen when the scrolling ends.
So, wanted to ask if anyone came across something like this before of know how to get rid of this. I know I could always use a library, but I want to learn. Here is some code:
_containerScrollView = [[UIScrollView alloc] initWithFrame:self.view.frame];
_containerScrollView.contentSize = CGSizeMake(_containerScrollView.frame.size.width * 4, 0.0f);
_containerScrollView.pagingEnabled = YES;
[self.view addSubview:_containerScrollView];
UITableViewController *vc1 = [[UIViewController alloc] init];
UITableViewController *vc2 = [self.storyboard instantiateViewControllerWithIdentifier:#"trendingViewController"];
UITableViewController *vc3 = [self.storyboard instantiateViewControllerWithIdentifier:#"placesViewController"];
UITableViewController *vc4 = [self.storyboard instantiateViewControllerWithIdentifier:#"favoritesViewController"];
self.rallyViewControllers = [NSArray vc1, vc2, vc3, vc4, nil];
[self addViewControllers];
Other methods;
- (void)addViewControllers{
if (self.rallyViewControllers == nil) {
return;
}
for (int i = 0; i < [self.rallyViewControllers count]; i++) {
UIViewController* viewController = [self.rallyViewControllers objectAtIndex:i];
[self addChildViewController:viewController];
[viewController didMoveToParentViewController:self];
[_containerScrollView addSubview:viewController.view];
}
}
This is called in viewDidLayoutSubviews
- (void)configureFrames{
if (self.rallyViewControllers == nil) {
return;
}
CGFloat width = _containerScrollView.frame.size.width;
CGFloat height = _containerScrollView.frame.size.height;
for (int i = 0; i < [self.rallyViewControllers count]; i++) {
UIViewController *viewController = [self.rallyViewControllers objectAtIndex:i];
viewController.view.frame = CGRectMake(i * width, 0.0f, width, height);
}
}
I should state upfront that I didnt completely understand your description of
"table views began refusing to go all the way down and part of the
final table view cell stays trimmed by the end of the screen when the
scrolling ends."
My answer is based on an issue I faced before. My setup is a uitableview in the storyboard container view ( without any parent scrollview)
I faced this issue where part of the tableview was cut off and I could not see about 5 bottom rows.
Turns out I did not have any constraints setup between the parent container view and the tableview.
To determine if your tableviews are rendering fine, get your project running in XCode and then press on the below button
This button will then pause your app and give you a visual stack of the the different views that are currently rendered in your app. You can then see if any of the children ( in your case tableviews) are rendered outside the frame of the parent view in which case that portion will not be visible. This indicates that you either dont have constraints (or) the current constraints you have are incorrect.
I got it. The problem was when I added the view controllers views to the scroll view, they returned dimensions as if there was no navigation bar. But they were still positioned under the navigation bar, which caused total view to have 64 pt more height than the screen could show. when I manually subtracted 64 pt from views, and it was fixed. But since that is a very crude way of doing this, I then tried to fix it by fiddling with auto-layout, which ended up fine.

Why isn't the rootViewController of my modally presented (form sheet) navController aware of it's smaller size when presented modally?

I am working on an iPad app with a few different modal views, and this code is pretty common:
UIViewController *v1 = [[UIViewController alloc] init];
UINavigationController *nav1 = [[UINavigationController alloc] initWithRootViewController:v1];
nav1.modalPresentationStyle = UIModalPresentationFormSheet;
[self presentViewController:nav1 animated:YES completion:nil];
It could be that I am doing this wrong, but this is how I am presenting a navController-nested vc modally.
The problem is that within the v1 class, any reference to self.frame/bounds results in full screen dimensions:768x1024. Even though the navController clearly isn't being displayed with that size.
What should I be doing to make it so that the v1 vc knows how big it actually is? So that if I wanted to add, say, a tableView, it would know how big it should be?
Thanks!
EDIT:
I have tried a few more things, and still don't have a solution to this problem. I have made a simple sample project to illustrate the problem I am having. I just have one view and this is the core of the code:
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"Frame: %#", NSStringFromCGRect(self.view.frame));
NSLog(#"Bounds: %#", NSStringFromCGRect(self.view.bounds));
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = CGRectMake(self.view.frame.size.width - 400, 0, 400, 400);
button.backgroundColor = [UIColor redColor];
[button addTarget:self action:#selector(presentModal) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
}
- (void)presentModal {
SSViewController *view = [[SSViewController alloc] init];
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:view];
nav.modalPresentationStyle = UIModalPresentationFormSheet;
[self.navigationController presentViewController:nav animated:YES completion:nil];
}
When this view loads, I have a big red button that is up against the top right corner of my view. When I press the button, it loads the same VC in a modal view embedded in a navController. The button shows up nearly off screen because the frame hasn't changed. It still shows as full screen. Here is a link to the project.
Not sure why you're having the issue you're having. I'm using the following code:
- (void)presentNewView {
NewViewController *newVC = [[NewViewController alloc] initWithNibName:nil bundle:nil];
newVC.view.backgroundColor = [UIColor redColor];
UINavigationController *newNC = [[UINavigationController alloc] initWithRootViewController:newVC];
newNC.modalPresentationStyle = UIModalPresentationFormSheet;
[self.navigationController presentViewController:newNC animated:YES completion:NULL];
}
.. it results in the following in the simulator:
.. and when I print out the first ViewController's frame and bounds (I thought it might be an issue with the two) I get the following:
frame height: 1024.000000
frame width: 768.000000
bounds height: 1024.000000
bounds width: 768.000000
.. and when I print out the presented ViewController's frame/bounds I get the following:
frame height: 620.000000
frame width: 540.000000
bounds height: 620.000000
bounds width: 540.000000
How are you determining the size of the frame exactly? Any reference within the v1 class that was presented modally SHOULD know its actual size, like I showed above.
EDIT
The major difference I found with my code and yours, is that in my code I created a subclass of my view controller "NewViewController" and was printing out the frame from within that class. The class itself seems to be aware of its correct bounds, but the class the presented it seems not to be. This is demonstrated by printing the view property from the ViewController class that presented it:
NewViewController's View From Presenting Class: frame = (0 0; 768 1024)
..compared to printing out the self.view from within the ViewDidAppear method of NewViewController itself:
NewViewController's View Did Appear: frame = (0 0; 540 576)
Moral of the story, if you are going to be presenting a UIViewController in the way you've shown, you're likely going to want to subclass UIViewController anyway so you can customize it however you want, so within that file if you reference self.view or self.bounds you will be getting the ACTUAL view/bounds.
EDIT #2
Based on the project you provided, the reason why you are having that issue is because you are printing out the frame/bounds of the view in viewDidLoad as opposed to viewDid/viewWillAppear. Adding those NSLog statements to VWA or VDA provides you the correct frame, so as I said in my initial edit, you should be fine accessing the view of the modal correctly at that point.
It's a new feature of iOS7. If you embed a UIViewController in navigation bar, it won't get smaller, because by default navigation bar is translucent.
You will see it if you change the background color of a view controller, that the top part of it is actually behind the navigation bar.
To lay out the v1 view controller underneath the navigation bar, you can use following code:
if ([v1 respondsToSelector:#selector(edgesForExtendedLayout)]) {
v1.edgesForExtendedLayout = UIRectEdgeNone;
}
It will behave just as in iOS6.
when presenting view controllers modally from a child view controller (one that has lass than the full screen and is a child of another view controller..) it is important to do this so that the modal controller knows the size of the canvas its appearing in
childViewController.definesPresentationContext = YES;
modalViewControllerWhichIsAboutToBePushed.modalPresentationStyle = UIModalPresentationCurrentContext

Hide MasterView - UISplitViewController with UITabController and UITableView

I have a split view controller that contains a tab controller that contains a table view in each of its tab items - all on the "master side". The table view gives a list of items that the user can select from in order to control what is displayed on the "detail side".
I am trying to move the master table view off the screen after the user has made a selection - rather than the user needing to tap the screen to dismiss the master table view.
I am using the code below. As the code comments indicate it leaves an translucent "shadow" in place of the master side table view. What else do I need to be doing to completely move the master side table view off the screen.
- (void)hideSidebarNavPanel
{
/* This almost works - in that it moves the table view off the screen but leaves a "shadow" in its place.
This occurs whether we modify the frame for the tabBarView or the masterTableView or the masterView - or for all 3.
It seems that there is something else sitting below both of these that also needs to have its frame modified.
The question is what?
*/
UIView *masterView = self.view;
CGRect masterViewFrame = masterView.frame;
masterViewFrame.origin.x -= masterViewFrame.size.width;
NSArray *controllers = self.splitViewController.viewControllers;
UITabBarController *tabBarController = [controllers objectAtIndex:0];
UIView *tabBarView = tabBarController.view;
CGRect tabBarFrame = tabBarView.frame;
tabBarFrame.origin.x -= tabBarFrame.size.width;
// UIViewController *masterNavigationController = [tabBarController.viewControllers objectAtIndex:0];
// UIView *masterTableView = masterNavigationController.view;
// CGRect masterTableViewFrame = masterTableView.frame;
// masterTableViewFrame.origin.x -= masterTableViewFrame.size.width;
[UIView beginAnimations:#"showView" context:NULL];
masterView.frame = masterViewFrame;
tabBarView.frame = tabBarFrame;
//masterTableView.frame = masterTableViewFrame;
[UIView commitAnimations];
}

Adding viewController views to UIScrollView

Despite the number of similar posts on this, I still am having trouble making this work correctly.
I have a two UIViewControllers which I have designed in IB with UILabels and imageviews, etc.
I would like to add these to a scroll view so they can be paged between.
I have outlets defined connecting each of the scrollViewcontrollers with their elements in IB, however, the controllers themselves, I am programatically creating from within the master view controller in viewDidLoad. After creating them, I assign some values from the master view controller than add it as a subview to the scrollViewController. However nothing displays. From the debugging i have done, when I set a break point after assigning the values, I can see that the elements are nil. They should be loaded from the xib file, but it does not seem to be read. Even as a test, I tried initializing one of the elements (eg. UILabel) and setting it to that sub view controller, but it nothing would display. Here is some code to explain a bit better.
In the master view controller
detailControllerA *tempA = [[detailControllerA alloc] initWithNibName:#"detailOverviewA" bundle:nil];
self.overviewA = tempA;
[tempA release];
self.overviewA.someLabel.text = #"Some Text";
detailScrollView.contentSize = CGSizeMake(scrollWidth, scrollHeight);
detailScrollView.pagingEnabled = YES;
[detailScrollView addSubview:self.overviewA.view];
In the detailControllerA implementation I set the frame in loadView:
-(void)loadView {
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 90)];
self.view = view;
[view release];
}
I also have the labels, etc defined with IBOutlets in detailControllerA.h and implemented to the elements in the xib file.
Any ideas why the xib is not loading correctly when created programatically?
Try in this way:
NSUInteger scrollContentCount = 0;
for (NSUInteger arrayIndex = 0;
arrayIndex < [contents count];
arrayIndex++) {
// set scorllview properties
CGRect frame;
frame.origin.x = self.mScrollView.frame.size.width * arrayIndex;
frame.origin.y = 0;
frame.size = self.mScrollView.frame.size;
myScrollView.autoresizingMask = YES;
// alloc - init PODetailsView Controller
myController = [[MyController alloc] initWithNibName:#"MyController" bundle:[NSBundle mainBundle]];
[myController.view setFrame:frame];
// add view in scroll view
[self.myScrollView addSubview:myController.view];
scrollContentCount = scrollContentCount + 1;
}
// set scroll content size
self.myScrollView.contentSize =
CGSizeMake(self.myScrollView.frame.size.width * scrollContentCount,
self.myScrollView.frame.size.height);
}
Don't release the content controller object which is in for-loop.
Set your scrollView contentSize according to your requirement.
Hope this would be helpful to you.
Interestingly enough, the -(void)loadView was the problem.
Since the view is being generated in interface builder (with the frame settings), when loadView fires, it was creating a new view, blowing away the one created in interface builder.

Resources