I am transiting my project to iOS7. I am facing a strange problem related to the translucent navigation bar.
I have a view controller and it has a tableview as subview (let's call it ControllerA) . I init a new uinavigationcontroller with the controllerA and present it modally using presentviewcontroller. The presented view controller's table view is blocked by the navigation bar. I set the automaticallyAdjustsScrollViewInsets to YES but the result did not change.
I knew I can set the edgesForExtendedLayout to UIRectEdgeNone, but it will make the navigation bar no more translucent.
After that, I tried to create a new view controller for testing. It contains almost the same elements. But the result is much different. The table view content does not get blocked.
Conclusion
Two View Controllers' automaticallyAdjustsScrollViewInsets set to YES
The project is not using storyboard
The first one is created at Xcode 4.6, The second one is newly created on Xcode 5
I have compared two classes xib and code, not much different
I have found the answer on apple developer forum.
There are two different case.
The first one, the view controller added is a UITableViewController.
And the issue should not be appeared since apple will auto padding it.
The second one, the view controller is NOT a UITableViewController.
And in the view hierarchy, it contains a UITableView. In this case, if the UITableview(or ScrollView) is the viewController's mainview or the first subview of the mainview, it will work. Otherwise, the view controller doesn't know which scroll view to padding and it will happen the issue.
In my case, the view controller is the second one. And there is a background image view as the first subview of the main view. So, it fails.
Here is the Apple developer forum link (need developer account to access):
https://devforums.apple.com/message/900138#900138
If you want the view to underlap the navigation bar, but also want it positioned so the top of the scrollview's content is positioned below the navigation bar by default, you can add a top inset manually once the view is laid out. This is essentially what the view layout system does when the top-level view is a scroll view.
-(void)viewDidLayoutSubviews {
if ([self respondsToSelector:#selector(topLayoutGuide)]) {
UIEdgeInsets currentInsets = self.scrollView.contentInset;
self.scrollView.contentInset = (UIEdgeInsets){
.top = self.topLayoutGuide.length,
.bottom = currentInsets.bottom,
.left = currentInsets.left,
.right = currentInsets.right
};
}
}
Based on Tony's answer I was able to get around this problem programatically with temporarily sending the table view to the back, let the adjustments be made and then send the background view back to the back. In my case there is no flickering to this approach.
In the View Controller:
- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
[self.view sendSubviewToBack:self.tableView];
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
[self.view sendSubviewToBack:self.backgroundView];
}
Obviously if there are other subviews on self.view you may need to re-order those too.
There's probably too many answers on this already, but I had to take Christopher's solution and modify it slightly to support view resizing and allowing the content inset to be changed in a subclass of the UIViewController.
#interface MyViewController ()
#property (weak, nonatomic) IBOutlet UIScrollView *scrollView;
#property (assign, nonatomic) UIEdgeInsets scrollViewInitialContentInset;
#end
#implementation MyViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self setScrollViewInitialContentInset:UIEdgeInsetsZero];
}
- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
if (UIEdgeInsetsEqualToEdgeInsets([self scrollViewInitialContentInset], UIEdgeInsetsZero)) {
[self setScrollViewInitialContentInset:[self.scrollView contentInset]];
}
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
UIEdgeInsets scrollViewInset = [self scrollViewInitialContentInset];
if (UIEdgeInsetsEqualToEdgeInsets(scrollViewInset, UIEdgeInsetsZero) {
if ([self respondsToSelector:#selector(topLayoutGuide)]) {
scrollViewInset.top = [self.topLayoutGuide length];
}
if ([self respondsToSelector:#selector(bottomLayoutGuide)]) {
scrollViewInset.bottom = [self.bottomLayoutGuide length];
}
[self.scrollView setContentInset:scrollViewInset];
}
}
#end
To explain the point:
Any subclass of MyViewController can now modify the contentInset of scrollView in viewDidLoad and it will be respected. However, if the contentInset of scrollView is UIEdgeInsetsZero: it will be expanded to topLayoutGuide and bottomLayoutGuide.
#Christopher Pickslay solution in Swift 2:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let topInset = topLayoutGuide.length
inTableView.contentInset.top = topInset
inTableView.contentOffset.y = -topInset
inTableView.scrollIndicatorInsets.top = topInset
}
Yeah - a bit annoying.
I have a nib with a single tableview within the main view, not using autolayout. There is a tabbar, navigationbar and a statusbar and the app needs to work back to 5.0. In Interface builder that neat 'see it in iOS7 and iOS6.1 side-by-side' thing works, showing the tables neatly fitting (once the iOS6/7 deltas were set properly).
However running on a device or simulator there was a large gap at the top of the table, which was as a result of a content inset (which pretty much matched by iOS6/7 vertical delta) that was set to zero in the nib.
Only solution I got was in viewWillAppear to put in [_tableView setContentInset:UIEdgeInsetsZero].
Another ugly hack with a pretty on-screen result.....
[self.navigationController setNavigationBarHidden:YES animated:YES];
in:
- (void)viewDidLoad
Related
I load InfoViewController's view as a subview in MainViewController like this (declared in .h):
- (void)viewDidLoad
{
[super viewDidLoad];
infoViewController = [[InfoViewController alloc] initWithNibName:#"InfoViewController" bundle:nil];
mainInfoView = (MainInfoView *)[mainInfoViewController view];
}
(MainViewController is push-presented from a segue from a tableviewcontroller cell and has a navigation bar.)
This is in InfoViewController's viewDidLoad to set the size of view:
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.frame = CGRectMake(130, 0, 178, 299);
}
In the Size inspector for InfoViewController I've set the same size (with: 178, height: 299).
I disabled "Use Autolayout" both for InfoViewController in XIB and for the MainViewController in Storyboard.
I have some UILabels, UITextViews and UIImageViews in the InfoViewController. They are declared with properties in .h and synthesized in .m. and connected to the elements in the XIB. Their content is defined in viewWillAppear of MainViewController like this:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
infoViewController.kategoriLabel.text = [[objArr objectAtIndex:detailIndex] objectForKey:#"Type"];
}
When I run the app and this view is entered, some of the labels, images and textViews are suddenly placed in what appears to be random locations. This is only happening to some of the elements.
I have done this same thing in another app and it works fine on both simulator (iPhone 4 ver 6,0) and iPhone 5 device, but in this app it just gets messy on both.
I cannot seem to find anything different anywhere in the Utilities or anywhere else when I compare the apps.
If you have experienced something similar and solved it or if you might know what could be causing it, I will appreciate your answer. Thanks.
PS. I'm using Xcode 4.5.1
I had a different problem, but my brute-force resolution might work for you as well. In my case, I wanted to disable the nav bar on the final item in a series of view controllers. IB, though, won't let you remove the Nav bar so I had to do it manually in viewWillAppear. However, once the bar was hidden, IOS shifted up all my buttons and objects that had been defined in IB by the height of the (now hidden) nav bar.
I resorted to the following in the Controller's viewWillAppear. (objects tended to shift visibly when I tried this in viewWillAppear)
-(void) viewWillAppear:(BOOL)animated
{
//turn off nav bar
[[self navigationController]setNavigationBarHidden:YES animated:NO];
//correct positions of items that shift when the bar disappears
myThing.center = CGPointMake(desired_x_coord, desired_y_coord);
myOtherThing.center = CGPointMake(desired_x_coord2, desired_y_coord2);
...etc...
//don't forget to call the base class
[super viewWillAppear:animated];
}
Annoying, but worked. If you can't find the source of your objects moving, you could try forcing their positions using viewWillAppear.
I'm searching for a way to have a UITableViewController with a UITableView at the top and a UIPickerView bellow (with fix position).
I've found a solution for fixing the picker with the code bellow:
- (void)viewDidLoad {
[super viewDidLoad];
_picker = [[UIPickerView alloc] initWithFrame:CGRectZero];
_picker.showsSelectionIndicator = YES;
_picker.dataSource = self;
_picker.delegate = self;
// Add the picker to the superview so that it will be fixed
[self.navigationController.view addSubview:_picker];
CGRect pickerFrame = _picker.frame;
pickerFrame.origin.y = self.tableView.frame.size.height - 29 - pickerFrame.size.height;
_picker.frame = pickerFrame;
CGRect tableViewFrame = self.tableView.frame;
tableViewFrame.size.height = 215;
self.tableView.frame = tableViewFrame;
[_picker release];
}
The problem is with the tableview, it seems resizing doesn't work so I can't see all results .
Thanks for your advice.
You should use a UIViewController subclass instead of UITableViewController to manage a table view if the view to be managed is composed of multiple subviews, one of which is a table view. You can add a UITableView subview and make your controller implement UITableViewDelegate and UITableViewDataSource protocols.
The default behavior of the UITableViewController class is to make the table view fill the screen between the navigation bar and the tab bar (if either are present).
From Table View Programming Guide for iOS:
Note: You should use a
UIViewController subclass rather than
a subclass of UITableViewController to
manage a table view if the view to be
managed is composed of multiple
subviews, one of which is a table
view. The default behavior of the
UITableViewController class is to make
the table view fill the screen between
the navigation bar and the tab bar (if
either are present).
If you decide to
use a UIViewController subclass rather
than a subclass of
UITableViewController to manage a
table view, you should perform a
couple of the tasks mentioned above to
conform to the human-interface
guidelines. To clear any selection in
the table view before it’s displayed,
implement the viewWillAppear: method
to clear the selected row (if any) by
calling deselectRowAtIndexPath:animated:.
After the table view has been
displayed, you should flash the scroll
view’s scroll indicators by sending a
flashScrollIndicators message to the
table view; you can do this in an
override of the viewDidAppear: method
of UIViewController.
Here is a related question I found, but it does not answer my question in detail.
[http://stackoverflow.com/questions/3209993/cocoa-touch-can-i-have-multiple-views-per-view-controller-or-specify-bounds-of][1]
I have a UIView class, BallView, which is set to be the default view of the ballViewController. Now, this view has a ball bouncing around according to the accelerometer. I am calling a private function draw every time the accelerometer sends updates.
However, my main question is: I would like to have multiple such balls bouncing around.
Do I have to recreate the view for every class ? But then the File's Owner's IBOutlet view will also have to be connected. And an IBOutlet can point to just one address.
Any other way round this ?
Here is how I'm instantiating the Ball View class in the ballViewController:
[motionManager startAccelerometerUpdatesToQueue:queue withHandler:
^(CMAccelerometerData *accelerometerData, NSError *error){
[(BallView *)self.view setAcceleration:accelerometerData.acceleration];
[(BallView *)self.view performSelectorOnMainThread:#selector(draw) withObject:nil waitUntilDone:NO];
}];
Thus, it means, my question is a bit different from those multi-view tab-bar solutions. Because in those cases only 1 view is shown at a time. I want 4-5 views overlaid on top of each other.
Any help ?
You're right, your view controller can only have a single UIView in its view property. That view though can certainly be used to contain other subviews.
What I would do is have a plain old UIView as your controller's view, and have your BallViews be subviews of that view. Your controller can still control those views, they ust can't all be in its view property.
EDIT: If you're using nib files/Interface Builder, adding a BallView as a subview of your controller's view is pretty easy - just drag a UIView object onto the view, and in the identity inspector you can change the identity of the view to your BallView class.
If you're not using IB, you can also do the same programatically:
// BallViewController.h
#interface BallViewController
{
BallView* ballView;
}
#end
// BallViewController.m
#implementation BallViewController
- (void) loadView
{
...
CGRect frame1 = ...
CGRect frame2 = ...
self.view = [[UIView alloc] initWithFrame:frame1];
ballView = [[BallView alloc] initWithFrame:frame2] retain];
[self.view addSubview:ballView];
...
}
#end
Is there any way to show a tab bar after it has been hidden?
Got a tabbar-nav structure. For one of the tabs, I need to hide the tab bar for its 2nd and 3rd level view. But at the same time I will need to show its 1st and 4th view.
The sample code from Elements isn't really applicable here I think.
I've found quite a good pragmatic solution to this problem - make the UITabBarController's view larger than it needs to be, so that the actual UITabBar is clipped by the screen.
Assuming that the tab bar view normally fills its superview, this sort of thing should work:
CGRect frame = self.tabBarController.view.superview.frame;
if (isHidden)
{
CGFloat offset = self.tabBarController.tabBar.frame.size.height;
frame.size.height += offset;
}
self.tabBarController.view.frame = frame;
The tab bar is still showing, but it's off the bottom of the screen, so appears to have been hidden.
It might have performance implications if it causes extra clipping, but so far, it seems to work.
The UIViewControllers that are pushed onto the navigation stack can do the something like the following:
- (void)viewWillAppear:(BOOL)animated {
self.tabBarController.tabBar.hidden = NO; // Or YES as desired.
}
EDIT: Added additional code below to deal with the frame. Don't think I particular recommend this idea since it relies on the internal default view structure of a UITabBarController.
Define the following category on UITabBarController:
#interface UITabBarController (Extras)
- (void)showTabBar:(BOOL)show;
#end
#implementation UITabBarController (Extras)
- (void)showTabBar:(BOOL)show {
UITabBar* tabBar = self.tabBar;
if (show != tabBar.hidden)
return;
// This relies on the fact that the content view is the first subview
// in a UITabBarController's normal view, and so is fragile in the face
// of updates to UIKit.
UIView* subview = [self.view.subviews objectAtIndex:0];
CGRect frame = subview.frame;
if (show) {
frame.size.height -= tabBar.frame.size.height;
} else {
frame.size.height += tabBar.frame.size.height;
}
subview.frame = frame;
tabBar.hidden = !show;
}
#end
Then, instead of using the tabBar.hidden change I originally suggested, do the following:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.tabBarController showTabBar:NO];
}
Obviously making sure that the implementation has included the category definition so that 'showTabBar' is known.
You need to implement a delegate method
- (BOOL)tabBarController:(UITabBarController *)tabBarController2 shouldSelectViewController:(UIViewController *)viewController
Inside that you can check which index is selected and show the tab bar
if([[tabBarController.viewControllers objectAtIndex:0] isEqual:viewController])// it is first tab
{
tabBarController.tabBar.hidden = FALSE;
}
I know this is an old post but i think the below code would help to hide the tabbar on the viewcontroller you don't want it on and has the added benefit of automatically readding the tabbar when you come back from that view controller
UIViewController *hideTabbarViewController = [[UIViewController alloc] init];
hideTabbarViewController.hidesBottomBarWhenPushed = YES;
[[self navigationController] hideTabbarViewController animated:YES];
I have a UIPopoverController with a subclass UINavigationController. Both the parent and child views are UITableviews.
When i call parent view originally with contentSizeForViewInPopover = (320,480) it works great.
When i click into the child view i resize the popover to contentSizeForViewInPopover = (320,780)
When return back to the parent view i cannot get the popover to resize back to contentSizeForViewInPopover = (320,480). the popover stays at the (320,780) size.
Been trying everything but just missing something. Anyone know how resize the view with UIPopoverControllers in the above scenario?
Thanks in Advance!!
The contentSizeForViewInPopover property of the view controller only sets the default (initial) size of its containing UIPopoverController. To resize the UIPopoverController at an arbitrary time, you must set its popoverContentSize property. Note that popoverContentSize is a property of the UIPopoverController and not of the view controller (so you'll probably need a reference to the popover controller around).
To reset the popover's size every time a view controller becomes the top view controller of a UINavigationController, you can use the UINavigationControllerDelegate protocol methods:
navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
if (viewController == viewControllerToResize) {
referenceToUIPopoverController.popoverContentSize = CGSizeMake(320,480);
}
}
I had the same problem, but none of the above solutions worked for me. However, in trying to combine their approaches, I was inspired to try a slightly different way to attack the problem. This works for me:
-(void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
viewController.contentSizeForViewInPopover = navigationController.topViewController.view.frame.size;
}
I have three different popovers that each use navigation view controllers. This solution has the nice side effect of working for all of them because it doesn't make a specific reference to any of the popover controllers, but ends up using the popoverContentSize from the popover controller currently being used.
In my project I had to dynamically change the size of the popover.
I made the original controller delegate of my popover content controller, and implemented it's delegate method that is called each time the size is changed:
The code is bellow, hope it helps somebody:
-(void) popover:(UIViewController*)controller didChangeSize:(CGSize)size{
if ([controller class] == [AZViewController class]){
if (!_popoverController){
_popoverController = [[UIPopoverController alloc] initWithContentViewController:controller];
}
_popoverController.popoverContentSize = size;
}
}
I think I had the same problem and I solved it by setting the size for the view in the popover every time the view was about to appear. Like this:
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
self.contentSizeForViewInPopover = CGSizeMake(320, 444); //Set your own size
}
I hope this helps you.
I think I might have figured this out as I struggled with this issue for a while now. Might be a bit optimistic so please feel free to comment if this solution isn't working for you.
In each viewController displayed with the navigation hierarchy, set the contentSizeForViewInPopover property in the viewDidAppear: method to its appropriate size.
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self setContentSizeForViewInPopover:CGSizeMake(320, 320)];
}
Another thing I picked up is that when tapping back while editing a textField, the size stays small instead of the larger previous view. Call the resignFirstResponder method on your textField in the controller's viewWillDisappear.
I'm curious whether this solution works across sdks.
After trying many things, the answer by #chuck-k helped me decently resolve for my UINavigationController popover woes in iOS7.
Here's what I did:
for each UIViewController within the UINavigationController I calculate the content size I want displayed in method - (CGSize)contentSizeForViewInPopover plus navigationController.navigationBar.frame.size.height (which is always 44 I think). I don't use any other popover functionality in these UIViewControllers.
I declared my UIViewController that creates the UINavigationController as a UINavigationControllerDelegate
Then in the delegate....
.....
- (void)navigationController:(UINavigationController *)navigationController willShowViewController: (UIViewController *)viewController animated:(BOOL)animated {
BOOL popoverAnimation = NO;
if ( self.myPopoverController.popoverContentSize.height < viewController.contentSizeForViewInPopover.height ) popoverAnimation = YES;
[self.myPopoverController setPopoverContentSize:viewController.contentSizeForViewInPopover animated:popoverAnimation];
}
The height check compares the current view controller's popover content size to the "incoming" view controller popover content size. I use animation = NO when going from a larger --> smaller popover content size, because otherwise I get some jerky repositioning animation in iOS7. But peculiarly if animation = NO when going from a smaller --> larger popover content size, the popover size would increase to the size I was expecting but would not display content larger than the previously smaller content size... setting animation = YES resolved this issue for me. (I only check height because in my case the width is fixed.)
By using this technique almost everything finally works to my satisfaction and I hope this might help someone else.