UIRefreshControl hidden / obscured by my UINavigationController's UINavigationBar - ios

I'm attempting to use a UIRefreshControl inside my UITableViewController which itself is inside a UINavigationController, which has its hidesNavigationBar property set to NO (so the navigation bar is visible).
The UIRefreshControl works, but is obscured by the UINavigationBar. I'm surprised I can't find anyone else who has run into this problem.
Possible relevant points:
I set the rootViewController of my UIWindow to be my UINavigationController.
I set the initial view controller of the UINavigationController by setting the viewControllers property of the UINavigationController.
My UITableViewController subclass is instantiated with a nib.
I instantiate my UIRefreshControl in the viewDidLoad method of my UITableViewController subclass. I set the refreshControl property of the UITableViewController subclass in this method.
The UIRefreshControl works perfectly fine, and I can see a portion of it, but it is obscured by my UINavigationBar. It looks completely normal if I set hidesNavigationBar to YES (but I don't want to hide it).
Edit:
The code used to create and position my UIRefreshControl is:
UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
[refreshControl addTarget:self
action:#selector(toggleRefresh:)
forControlEvents:UIControlEventValueChanged];
self.refreshControl = refreshControl;
This code snippet is in the viewDidLoad method of my UITableViewController subclass, which is a child view controller of a UINavigationViewController.

For those targeting iOS 7, there seems to be a new issue present where the UIRefreshControl is drawn behind the UITableView's backgroundView. I experienced this both when initializing the UIRefreshControl programatically and from a storyboard. A simple workaround is to update the zPosition of the UIRefreshControl in viewDidLoad of your UITableViewController:
self.refreshControl.layer.zPosition = self.tableView.backgroundView.layer.zPosition + 1;

I've find a real solution, here it is:
I've a UIViewController inside a UINavigationController with a translucent NavigationBar. Inside the UIViewController there is the UITableView.
I want to add a UIRefreshControl but when I do it, it's hidden by the NavigationBar, like you explain.
Here is my code to make it work:
// Add a UITableViewController
self.tblViewController = [[UITableViewController alloc] init];
// Set the UITableView to it
self.tblViewController.tableView = self.tblView;
// Initialize the UIRefreshControl and set it's method
self.refreshControl = [[UIRefreshControl alloc] init];
[self.refreshControl addTarget:self action:#selector(refreshTable) forControlEvents:UIControlEventValueChanged];
// Set the RefreshControl to the UITableViewController
self.tblViewController.refreshControl = self.refreshControl;
// Here is the thing ! Just change the contentInset to push down the UITableView content to 64 pixels (StatusBar + NavigationBar)
self.tblView.contentInset = UIEdgeInsetsMake(64.f, 0.f, 0.f, 0.f);
With this code, your UITableViewController will show the RefreshControl perfectly and keep the translucent NavigationBar effect when you scroll down the cells.

It looks like a bug to me, because it only occures when the contentOffset property of the tableView is 0
see this question
UIRefreshControl not showing spiny when calling beginRefreshing and contentOffset is 0
I fixed that with the following code (method for the UITableViewController) :
- (void)beginRefreshingTableView {
[self.refreshControl beginRefreshing];
if (self.tableView.contentOffset.y == 0) {
[UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^(void){
self.tableView.contentOffset = CGPointMake(0, -self.refreshControl.frame.size.height);
} completion:^(BOOL finished){
}];
}
}

In iOS 7, self.view is under the navigationBar, except that you write something as follows,
self.edgesForExtendedLayout = UIRectEdgeNone; // or UIRectEdgeAll & ~UIRectEdgeTop
or
self.navigationViewController.navigationbar.translucent = NO;

Using #Jonathan's answer I got this working well. But since I am using storyboard I set the content inset there like so: Which in case someones needs:
(xcode 6.4)

Do not call -setTranslucent: on your UINavigationBar. Then, your refresh control will be positioned properly, below the navigation bar.

Related

iOS hidesBarsOnSwipe status bar background color

When I swipe and hide the navigation bar with the hidesBarsOnSwipe property the status bar has a clear background. How can I set the background of the status bar to the same color as the navigation bar? Here are a few pictures showing my problem, this is all contained in a UITableViewController.
Separate
Separate picture, looks like one big one.
I've come across the same issue, and was able to solve it. I'm fairly new to iOS dev, and I don't imagine this solution to be foolproof. I couldn't find any good answers elsewhere, so here's how I overcame it:
I converted from a UITableViewController over to UIViewController with a nested UITableView. Note, double check that the delegate to the child tableview is set to the UIViewController.
I Added a view with a height of 20px and a background colour that you want to set as the "background" to the status bar. Set the constraints on that view as follows:
On your table view, set the constrains to be basically full screen. One important note here, the top constraint is to "Top Layout Guide.Top" and not to "Top Layout Guide.Bottom". By default I believe this constraint ties to the bottom. Double clicking on the constraint allows you to adjust it to the top. Without this, any table header cells weren't positioned properly for me
Hope that helps.
Adding to George Huber's answer. I solved this issue programmatically by adding a 20pt height UIView as a subview of the navigationController's view property -- in viewDidLoad method.
- (void)viewDidLoad
{
[super viewDidLoad];
UIView *statusBarBG = [[UIView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.view.bounds), 20)];
statusBarBG.backgroundColor = [UIColor navBar];
[self.navigationController.view addSubview:statusBarBG];
// REST OF CODE
}
Per skg's answer, I add a relative height for status bar according to iOS version.
self.navigationController.hidesBarsOnSwipe = true;
// add a UIView as subView to navigationController
CGFloat statusBarHeight;
if (#available(iOS 13, *)) {
NSArray *windows = UIApplication.sharedApplication.windows;
UIWindow *keyWindow = nil;
for (UIWindow *window in windows) {
if (window.isKeyWindow) {
keyWindow = window;
break;
}
}
statusBarHeight = keyWindow.windowScene.statusBarManager.statusBarFrame.size.height;
NSLog(#"statusBarHeight: %f", statusBarHeight);
} else {
statusBarHeight = UIApplication.sharedApplication.statusBarFrame.size.height;
}
UIView *statusBarBG = [[UIView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.view.bounds), statusBarHeight)];
statusBarBG.backgroundColor = [UIColor systemBackgroundColor];
[self.navigationController.view addSubview:statusBarBG];

Adding childContainerView disables UITableView

I have a chat that's setup as a UIViewController with a tableView added to it's view. This was working fine. I've since subclassed this viewController and added a childViewController as a header of the table:
- (void) createClassVideoView {
CGRect rect = CGRectMake(0.0f,
0.0f,
self.view.frame.size.width,
kWSClassVideoHeight);
WSClassVideoController *classVideoController = [[WSClassVideoController alloc] initWithFrame:rect mainUser:[[WSConversationManager shared] teacherObject] secondaryUsers:[[WSConversationManager shared] students]];
[self addChildViewController:classVideoController];
[self.view addSubview:classVideoController.view];
[classVideoController didMoveToParentViewController:self];
self.classVideoController = classVideoController;
}
It's not set as the actual tableHeaderView. It's just added as a childViewController to the subclass of the chat view controller, and I've just reset the frame of the tableView to accomodate it. This "header view" works fine and receives touches.
However, it's since disabled the superclass tableView's touches. The tableView displays, but I can't scroll it and tapping the UITextField does nothing.

Fade UIBarButtonItem When Swapped

When I change my UIBarButtonItems they change abruptly, unlike the default which gives a nice but speedy fade animation. You can see this when segueing between view controllers, for instance, the back button will fade in and out. How can I simulate the same effect?
Update - Based on this answer - https://stackoverflow.com/a/10939684/2649021
It looks like you would have to do something like this to make the button itself fade out.
[self.navigationItem setRightBarButtonItem:nil animated:YES];
And do something like this to make it fade in
[self.navigationItem setRightBarButtonItem:myButton animated:YES];
Otherwise if you want more control over animation properties you would have to create a custom view I believe.
EDIT: I just confirmed that you can fade a UIBarButtonItem custom view using this.
As a test I created a simple project and dropped a UIBarButtonItem onto the navigation bar. I created an outlet to the view controller. In viewDidLoad on the view controller I setup a custom view
-(void)viewDidLoad{
[super viewDidLoad];
UILabel *lbl = [[UILabel alloc]initWithFrame:CGRectMake(0,0,40,40)];
lbl.text = #"test";
UIView *customView = [[UIView alloc]initWithFrame:CGRectMake(0,0,40,40)];
[customView addSubview:lbl];
self.barButtonItem.customView = customView;
}
As a test in viewDidAppear I animated it
-(void)viewDidAppear:(BOOL)animated
{
[UIView animateWithDuration:3.0
delay:3.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
self.barButtonItem.customView.alpha = 0;
}
completion:^(BOOL finished) {
NSLog(#"animation complete");
}];
EDIT: Here's a link to Apples Documentation for a full explanation of UIView animations.
https://developer.apple.com/library/ios/documentation/windowsviews/conceptual/viewpg_iphoneos/animatingviews/animatingviews.html
You might have to create a custom bar button item using a view or an image, and then animate the properties of the view as digitalHound shows.

iOS 7 UISearchBar right spacing

Don't know why, my search bar in iOS 7 is leaving a right space. It's ok in iOS 6.
I know it has something to do with the section index, because if I remove it the space disappears, but I don't know how to fix it. Any thoughts?
Embed your UISearchBar in a UIView and then add that as the tableHeaderView. Structuring it that way in a storyboard worked for me. I'm guessing iOS resizes the UISearchBar in the tableHeaderView, but leaves a basic UIView alone (and doesn't bother to look inside it).
You might also want to make the section index transparent, which I did with:
[[UITableView appearance] setSectionIndexBackgroundColor:[UIColor clearColor]];
[[UITableView appearance] setSectionIndexTrackingBackgroundColor:[UIColor clearColor]];
Until a better answer appears, I just manually changed the frame of the search bar like this:
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
CGRect barFrame = self.searchBar.frame;
barFrame.size.width = self.view.bounds.size.width;
self.searchBar.frame = barFrame;
}
I had this same issue with the iPhone 6/ 6Plus when using a SearchDisplayController. (Using Swift)
I tried setting the frame of the search bar but with no luck but i noticed that if i tapped on the textField of the UISearchBar and then cancelled it then it would take on the proper size of the view. I therefore managed to fix the issue by calling the code below in ViewDidLoad of the viewController using the search.
self.searchController.setActive(true, animated: false)
self.searchController.setActive(false, animated: false)
self.contactsTableView.sectionIndexBackgroundColor = [UIColor clearColor];
The reason for that white edge is because your index layer has a white background and is on top of the search bar. This should be sufficient.
Add the search bar inside a UIView put as tableView's header view. Set the tableview's sectionIndexBackgroundColor to clear color because it covers the header.
Tested with iOS 7, 7.1;
Because the table view always leaves 15px on the right for section Indexes View, so you should resize the Seach bar after reloading the table view
First:
self.tblData.sectionIndexBackgroundColor = [UIColor clearColor]; //(iOS >= 7 only)
Cheating time:
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
[self performSelector:#selector(resizeSearchBar) withObject:nil afterDelay:0.01];
}
- (void) resizeSearchBar
{
CGRect frame = self.searchBar.frame;
if (frame.size.width < self.tblData.frame.size.width) {
frame.size.width = self.tblData.frame.size.width;
}
self.searchBar.frame = frame;
}
- (void) reloadTableData // call it anytime you want to reload table view
{
[self.tblData reloadData];
[self performSelector:#selector(resizeSearchBar) withObject:nil afterDelay:0.01];
}
Suggest
Dont cheat like me, just do the simpler way:
self.searchBar.searchBarStyle = UISearchBarStyleMinimal; // iOS >= 7 only
I also attached a UISearcBar in my application, and nothing is wrong there even my application supports rotation also.
Could you try removing and re creating UISearchBar in storyboard/xib
I added the search bar as a subview of the top-level view instead of the table view. Used autolayout to pin the searchbar to the top guide, and a vertical space constraint of 0 between the search bar and the table view.
The accepted solution with the method viewDidLayoutSubviews makes the screen flicker.
Instead what I did was create a subclass of UISearchBar that simply does this:
FullWidthSearchBar.h:
#interface FullWidthSearchBar : UISearchBar
#end
FullWidthSearchBar.m:
#import "FullWidthSearchBar.h"
#implementation FullWidthSearchBar
- (void)setFrame:(CGRect)frame {
frame.size.width = self.superview.bounds.size.width;
[super setFrame:frame];
}
#end
And then I assigned that class to the search bar on my xib:
The problem is the right white block, so if we change the block color the same as the search bar background, it looks normal.
just
if (IOS7) {
self.tableview.backgroundColor = [UIColor colorWithPatternImage:self.searchBar.backgroundImage];
}

Why does UIViewController extend under UINavigationBar, while UITableViewController doesn't?

I have UITabbarController with UINavigationController in it. I have a subclass of UIView that I assign as the view of UIViewController in the navController. This is pretty standard stuff, right? This is how I do it
_productCategoryView = [[ProductCategoryView alloc] initWithFrame:self.view.frame];
self.view = _productCategoryView;
This view has a UITableView as subView
_productCategoryTableView = [[UITableView alloc] initWithFrame:self.frame style:UITableViewStylePlain];
_productCategoryTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
_productCategoryTableView.backgroundColor = [UIColor clearColor];
[self addSubview:_productCategoryTableView];
For the sake of debugging I am setting self.backgroundColor = [UIColor blueColor] on the view.
From the above initialization of tableView one might think that the view's and table's frame is same. However when I run in iOS 7, the view's origin is set behind the UINavigationBar. This is understandable because I am setting self.navigationBar.translucent = YES; in my subclass of UINavigationController. But what I don't understand is how come the table is sitting just below the navBar? Shouldn't it also start from (0, 0) which is behind the navBar? See screenshot Scenario 1 below. Notice the blue hue behind navBar
Now, I push another viewController on the navigation stack, simply by using [self.navigationController pushViewController.....]. Again I have a custom UIView with a tableView in it. However I also have a UILabel above this table, and again for debugging, I gave it a redColor. This time I am setting the label's origin to be almost same as the view's
CGRect boundsInset = UIEdgeInsetsInsetRect(self.bounds, UIEdgeInsetsMake(10, 10, 10, 10));
CGSize textSize = [_titleLabel.text sizeWithFont:_titleLabel.font
constrainedToSize:CGSizeMake(boundsInset.size.width, MAXFLOAT)
lineBreakMode:NSLineBreakByWordWrapping];
printSize(textSize);
_titleLabel.frame = CGRectMake(boundsInset.origin.x,
boundsInset.origin.y,
boundsInset.size.width,
textSize.height);
So, going by the logic above, the label should be visible, right? But this time it's not. This time the label is behind the navBar.
Notice, the red hue behind navBar.
I would really like to align the subView below the navBar consistently. My questions are
1. How is the tableView offset by 64pixels (height of nav + status bar in iOS 7) automatically, even though it's frame is same as the view's?
2. Why does that not happen in the second view?
By default, UITableViewController's views are automatically inset in iOS7 so that they don't start below the navigation bar/status bar. This is controller by the "Adjust scroll view insets" setting on the Attributes Inspector tab of the UITableViewController in Interface Builder, or by the setAutomaticallyAdjustsScrollViewInsets: method of UIViewController.
For a UIViewController's contents, if you don't want its view's contents to extend under the top/bottom bars, you can use the Extend Edges Under Top Bars/Under Bottom Bars settings in Interface Builder. This is accessible via the edgesForExtendedLayout property.
Objective-C:
- (void)viewDidLoad {
[super viewDidLoad];
self.edgesForExtendedLayout = UIRectEdgeNone;
}
Swift 2:
self.edgesForExtendedLayout = UIRectEdge.None
Swift 3+:
self.edgesForExtendedLayout = []
#Gank's answer is correct, but the best place to do this is on the UINavigationControllerDelegate (if you have one):
func navigationController(navigationController: UINavigationController, willShowViewController viewController: UIViewController, animated: Bool) {
viewController.edgesForExtendedLayout = UIRectEdge.None
}

Resources