Manually Trigger `automaticallyAdjustsScrollViewInsets` at a Certain Time - uitableview

I have a UINavigationController, containing a UIViewController that is parent to two UITableViewController controllers.
When the user taps on a segmented control in the UIToolbar of the navigation controller, the current child table controller is swapped out with the new one. This includes removing the old controller from the parent hierarchy and removing its view as a subview of the parent view controller.
The first view controller that is displayed when the navigation view controller first presents it has its contentInset correctly configured by automaticallyAdjustsScrollViewInsets, however, when I pull that one out and insert the view from the second table view controller, that does not.
Furthermore, if I rotate the device (Which shrinks the UINavigationBar) and then swap back to the first view controller, its contentInset is now incorrect and it doesn't scroll properly. The second controller, however, does have its contentInset property properly set as a result of the device rotation.
Is there a way to manually force a UIViewController to redo its automaticallyAdjustsScrollViewInsets operation when I need it?

It's not an absolutely amazing one, but I found a solution that works.
Inserting a new child view controller isn't enough to trigger UINavigationController to automatically work out the appropriate contentInset values for any scroll views in the new child. BUT! You can force it to perform that calculation by doing something that would have required it anyway. For example, hiding and showing the navigation bar or toolbar.
- (void)insertViewController:(UIViewController *)viewController
{
// Add the view to our view
viewController.view.frame = self.view.bounds;
[self.view addSubview:viewController.view];
// Add the new controller as a child
[self addChildViewController:viewController];
[viewController didMoveToParentViewController:self];
// Show and hide the toolbar to force the content inset calculation
self.navigationController.toolbarHidden = YES;
self.navigationController.toolbarHidden = NO;
}
I've tested it, and there appear to be no visual glitches by rapidly hiding either the navigation bar or toolbar, so this solution seems to be acceptable.

Related

Overriding view controller based status bar appearance

I have an app with view controller based status bar appearance set to YES. Some of my views have dark, some of my views have light content, and the app has a pretty complex view controller hierarchy, but it works perfectly with subclassing and overriding the appropriate methods combined with modal views capturing presentation styles etc).
However, I need a global way to view a specific item at top (behind status bar, inside my app bounds), just like the bar like personal hotspot/ GarageBand recording/in call etc bar at the top. Because of the bar's background color, I want to override the status bar appearance while displaying the bar (which can be displayed anywhere in the app so I subclassed UIWindow and put its presentation code and view directly there). The bar displays exactly as I wanted on screens with light content status bar (as my bar's text is white and background is dark) but looks terrible on dark content status bar (and no, I can't change the colors of the bar).
How can I override the "whatever the currently presented view controller is"'s preferred status bar style globally (of course, without traversing all instances of the status bar methods in all view controllers), while still using view controller based status bar appearance? My app targets iOS 8.0+.
I've ended up in a very hacky (but working) way. It might not work in every scenario, but it worked in mine. I've kept the view as it is, and haven't touched a single view or controller.
First, I've got the topmost view controller currently being displayed. I've used the code from iPhone -- How to find topmost view controller and modified it a little to handle navigation controller and tab bar controller cases too:
+ (UIViewController*) topmostControllerForViewController:(__kindof UIViewController*)topController
{
while (topController.presentedViewController) {
topController = topController.presentedViewController;
}
if([topController isKindOfClass:[UINavigationController class]]){
UINavigationController *navController = topController;
return [self topmostControllerForViewController:navController.visibleViewController];
}
if([topController isKindOfClass:[UITabBarController class]]){
UITabBarController *tabController = topController;
return [self topmostControllerForViewController:tabController.selectedViewController];
}
return topController;
}
+ (UIViewController*) topmostController
{
__kindof UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController;
return [self topmostControllerForViewController:topController];
}
Then I've created a view controller without a view (view is nil). In it's init method (does not work in first call if put in viewDidLoad: as it's called inside the transition process and it's too late), I've added the following:
self.modalPresentationCapturesStatusBarAppearance = YES;
self.modalPresentationStyle = UIModalPresentationOverCurrentContext;
That code allowed my "dummy" view(less) controller to handle all the presentation context, including status bar appaearance and what happens to the other views controllers when it's presented. When presented over current context, the view controller at the back is not removed from view hierarchy. If I don't do that, it will be removed and screen will be black (as I don't have any view and I want the previous view controller to be shown).
So far so good. Then, I've displayed my bar normally, but simultaneously, presented that view controller modally without any view. Because the view controller didn't have any view and was presented over the current context, it visually didn't appear in any way, but since it was a modal presentation and the dummy view controller was set to capture presentation style, it triggered iOS to ask my app for status bar style. I've simply set up my status bar style as I've wanted in the view controller methods.
There was a little problem. When I've presented the new view controller, system added a UITransitionView on top of my previous view controller. If there was an actual view, it would be on top of the transition view. The transition view is completely transparent, but it has user interaction enabled and captured all the touch events, making my app unresponsive until I've dismissed the controller. I needed my previous view controller to receive touch events. I've dug deeper and found where modal presentation adds the transition view, and removed it when presenting the view controller after transition animation is complete:
for (UIView *view in self.subviews) {
NSString *className = NSStringFromClass([view class]);
if([className hasPrefix:#"UIT"] && className.length == 16){
//this must be UITransitionView, but I'm not using it directly since it may interfere with private API usage and get app rejected by Apple.
//now, we need to find another transition view inside this and remove it
for (UIView *innerView in view.subviews) {
className = NSStringFromClass([innerView class]);
if([className hasPrefix:#"UIT"] && className.length == 16){
//this is the transition view that we need to remove
[innerView removeFromSuperview];
}
}
}
}
Since UITransitionView is a private view type and I'm not sure if it causes a problem with App Store, I've done a heuristic check of UITransitionView by checking the first letters UIT and checking the length of the class name. It's not bulletproof, but it seems to work and unlikely to return false positive.
Everything now works as expected. It's hacky, and may break in the future, especially if modal presentation changes under the hood. But rest assured, it works.

Popping UIViewController causes previous UIViewControllers View to change position

I have a UINavigationController with a UIViewController set as it's rootController, it contains a background on its UIView using an image set just under the navBar. I then push onto the navigation controller a new UIViewController and when the back button is pushed, the previous controller looks different. Using the visual debugger I can see that the self.view has moved entirely down below the navBar where previously it was at the top. I have no idea and been racking my brains as to why this might be happening
-(void)pushIPhoneMessagingContactsController:(MessageContactsViewController *)contactsController{
self.selectorView.hidden = YES;
[self.navigationController pushViewController:contactsController animated:YES];
}
On the RootViewController (iPhoneMessagingNotificationsController)
-(void)viewWillAppear:(BOOL)animated{
self.selectorView.hidden = NO;
[[[self navigationItem] leftBarButtonItem] setTintColor:[UIColor blackColor]];
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleDefault];
if ([_displayType intValue] == MESSAGES_SHOWING) {
[self.notificationsViewController.view removeFromSuperview];
[self.contentView addSubview:_messagesViewController.view];
} else {
[self.messagesViewController.view removeFromSuperview];
[self.contentView addSubview:_notificationsViewController.view];
}
}
It seems the offending line was in the viewWillAppear method of the pushed UIViewController
self.navigationController.navigationBar.translucent = YES;
Somewhere else this navigationBar gets set as translucent:
[self.navigationController.navigationBar setBackgroundImage:[UIImage new]
forBarMetrics:UIBarMetricsDefault];
self.navigationController.navigationBar.shadowImage = [UIImage new];
self.navigationController.navigationBar.translucent = YES;
and to make it solid colour again:
self.navigationController.navigationBar.shadowImage = nil;
self.navigationController.navigationBar.translucent = NO;
but this code seems to mess with the layout so perhaps there is another way to change the opacity of the navBar and statusBar without affecting the layout?
What you're currently trying to do is hide or show a selectorView which really only should appear for one specific view controller.
Here's an encapsulated way to solve this that makes your selectorView a part of the root view controller, removing the connection from other view controllers. They no longer have to know about it or hide it.
Add your selectorView to your rootViewController's navigation bar titleView. (You can do this in code, or drop it in Storyboard and add an IBOutlet for it.)
self.navigationItem.titleView = selectorView;
Now when you push another view controller, its title will replace your rootViewController's selectorView title (view). Your other view controllers don't need to know anything about that view.
This is a good design approach in general. Anytime you have a control that should only appear on one view controller's navigation bar, you want to make it a part of that view controller's navigationItem (titleView, or left/right bar button items.) iOS will display the control when it presents that view controller, and hide the control when that view controller is no longer the top view controller in the navigation controller stack.
As for the 64-pixel height issue, it's likely related to some complexity in the rootViewController hierarchy that shouldn't be there.
In iOS 7/8, a view's content, by default, appears under a translucent navigation bar. Apple freely managed this for you, by insetting the first view of the view hierarchy.
From your code, it appears that you're trying to "hide" or "show" the (un)selected viewController's view.
Each view controller should have a view it controls. A view controller shouldn't be trying to control other view controller's views, or adding other view controller's views to its own view hierarchy.
Here's Apple's recommended way to approach this. Use a containerView in your rootViewController. The whole purpose of a container view is to encapsulate a view controller within a view. As your selectorView changes which view to show, you have your container view transition from one view controller to the other. (If you're not familiar with how to do that, check out this answer.)
Pin the containerView to the rootViewController's view, so Auto Layout can size it for you.
Your view hierarchy now looks like view -> containerView, instead of view -> hidden view of unselected view controller, shown view of selected view controller. Apple can adjust the first view's inset, and nothing gets incorrectly offset (by the height of the navigation control).
Update:
This question talks about scrollViewInsets and how they can be set on a view-controller-by-view-controller basis. If you do have a view controller, and you don't want its content to appear under a bar, uncheck that box.
But the best way to handle this is to "standardize" your UI, so it isn't varying from view to view. Either make the bar always be translucent, or not always be translucent. This makes transitions less "jarring" for the users.

UIViewControllers in uiscrollview calling viewdidappear

I'm adding 5 viewcontrollers to a scrollview with a page control so I can swipe from one viewcontroller to another.
At initialization I'm loading 3 viewcontrollers (left, middle, right)
My problem is that my three viewcontrollers are firing a viewDidAppear but only the center viewController is visible...
Is there a way to avoid the view controllers that aren't visible to call viewdidappear?
I'm adding my viewcontrollers to my scrollview like so :
if (controller != nil){
[self addChildViewController:controller];
[controller didMoveToParentViewController:self];
}
[scrollView addSubview:controller.view];
No, the viewDidAppear method is not related to the visibility of the view, if you get the documentation you will see
Notifies the view controller that its view was added to a view hierarchy.
So this method will be called when the view is added to the hierarchy. So the view was loaded, and added to the hierarchy, even in a non visible space of your mainView, it will call viewDidAppear.
To achieve what you want, you should implement the delegate of the scrollView, check the offset, and see in which page you are, then you can call a method on your viewController to do the job you want.

popover content view doesn't display while viewcontroller has a child VC present

I have a container view controller that consists of a navigation view at top, and a content view for the remainder of the screen. The navigation menu consists of several buttons, some of which present a popover with UITableView for secondary navigation. This all worked until I assigned a child view controller and set it's view as subview of the content view. Now, the popover appears, but has nothing inside it (no tableview, just black).
Why is this?
Here's the code I added for the child vc in container view:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
ContentWebViewController *initialVC = [[ContentWebViewController alloc] init];
[self addChildViewController:initialVC];
initialVC.view.frame = self.view.bounds;
[self.containerView addSubview:initialVC.view];
self.currentController = initial;
}
See the screenshot below. I added a vc with a simple webview showing google (just as a placeholder for now). The popover was working fine before I assigned the child VC.
Maybe it will help other in other cases -
If you are using size classes (probably you are since you are developing this to iPad) -
Design your popover view controller in Any-Any size and it should be OK - after that you can return to your wanted size.
(You can also uninstall the size classes of any object in that view controller instead of redesign the VC)
I somehow (don't ask me how) changed the class that my table view controller was inheriting from. It should have been (obviously) UITableViewController, but was UITableViewController, so initWithStyle was not being called....

How can I add a transparent view?

I want to push a view controller onto the navigation stack but I don't want its view to initially appear - in other words I want the view that was visible when the view controller is push to still be visible.
I tried setting the view controller's view's alpha value to 0.0 which I thought would make it transparent. But instead what is happening is that when I push the view controller on the the stack the screen is white. If I set the alpha to 1.0 then the view controller's view appears as expected.
Why is it white and not transparent?
you will have to add the view to the viewcontrollers manually
Not pushing it
For example do the following
YourViewController *vc = [[YourViewController alloc] init];
[self.view addSubview:vc.view];
vc.view.alpha = 0.0;
//Animate Here
vc.view.alpha = 1.0;
//Commit Animate Here
Please not that you will have to do some additional coding to implement the release of the vc, since now you have retained vc.view you will not be able to release vc easily,
Another solution is instead of implementing the second view as a viewcontoller implement it as uiview, and the xib class will be view and not uiviewcontroller
Maybe make sure that the opaque property is set to NO?
Or perhaps the view you're pushing on was built in interface builder, and you have a background color of white with another view you put on top of it and you only changed the opacity of the subview?

Resources