I've created a UITableView which I want to scroll underneath my semi-transparent black status bar. In my XIB, I just set the table view's y position to -20 and it all looks fine.
Now, I've just added a pull-to-refresh iOS6 UIRefreshControl which works however, because of the -20 y position, it drags from behind the status bar. I'd like it's "stretched to" position to be under the status bar rather than behind.
It makes sense why it's messing up but there doesn't seem to be any difference changing it's frame and the tableview's content insets etc don't make a difference.
The docs suggest that once the refreshControl has been set, the UITableViewController takes care of it's position from then on.
Any ideas?
You can subclass the UIRefreshControl and implement layoutSubviews like so:
#implementation RefreshControl {
CGFloat topContentInset;
BOOL topContentInsetSaved;
}
- (void)layoutSubviews {
[super layoutSubviews];
// getting containing scrollView
UIScrollView *scrollView = (UIScrollView *)self.superview;
// saving present top contentInset, because it can be changed by refresh control
if (!topContentInsetSaved) {
topContentInset = scrollView.contentInset.top;
topContentInsetSaved = YES;
}
// saving own frame, that will be modified
CGRect newFrame = self.frame;
// if refresh control is fully or partially behind UINavigationBar
if (scrollView.contentOffset.y + topContentInset > -newFrame.size.height) {
// moving it with the rest of the content
newFrame.origin.y = -newFrame.size.height;
// if refresh control fully appeared
} else {
// keeping it at the same place
newFrame.origin.y = scrollView.contentOffset.y + topContentInset;
}
// applying new frame to the refresh control
self.frame = newFrame;
}
It takes tableView's contentInset into account, but you can change topContentInset variable to whatever value you need and it will handle the rest.
I hope the code is documented enough to understand how it works.
Just subclass the UIRefreshControl and override layoutSubviews like this:
- (void)layoutSubviews
{
UIScrollView* parentScrollView = (UIScrollView*)[self superview];
CGSize viewSize = parentScrollView.frame.size;
if (parentScrollView.contentInset.top + parentScrollView.contentOffset.y == 0 && !self.refreshing) {
self.hidden = YES;
} else {
self.hidden = NO;
}
CGFloat y = parentScrollView.contentOffset.y + parentScrollView.scrollIndicatorInsets.top + 20;
self.frame = CGRectMake(0, y, viewSize.width, viewSize.height);
[super layoutSubviews];
}
The current upvoted answer does not play well with the fact you pull the component down (as Anthony Dmitriyev pointed out), the offset is incorrect. The last part is to fix it.
Either way: subclass the UIRefreshControl with the following method:
- (void)layoutSubviews
{
UIScrollView* parentScrollView = (UIScrollView*)[self superview];
CGFloat extraOffset = parentScrollView.contentInset.top;
CGSize viewSize = parentScrollView.frame.size;
if (parentScrollView.contentInset.top + parentScrollView.contentOffset.y == 0 && !self.refreshing) {
self.hidden = YES;
} else {
self.hidden = NO;
}
CGFloat y = parentScrollView.contentOffset.y + parentScrollView.scrollIndicatorInsets.top + extraOffset;
if(y > -60 && !self.isRefreshing){
y = -60;
}else if(self.isRefreshing && y <30)
{
y = y-60;
}
else if(self.isRefreshing && y >=30)
{
y = (y-30) -y;
}
self.frame = CGRectMake(0, y, viewSize.width, viewSize.height);
[super layoutSubviews];
}
UIRefreshControl always sits above the content in your UITableView. If you need to alter where the refreshControl is place, try altering the tableView's top contentInset. The UIRefreshControl takes that into account when determining where it should be positioned.
Try this:
CGFloat offset = 44;
for (UIView *subview in [self subviews]) {
if ([subview isKindOfClass:NSClassFromString(#"_UIRefreshControlDefaultContentView")]) {
NSLog(#"Setting offset!");
[subview setFrame:CGRectMake(subview.frame.origin.x, subview.frame.origin.y + offset, subview.frame.size.width, subview.frame.size.height)];
}
}
This will move UIRefreshControll down for 44 points;
I found the overriding of the layoutsubviews in the other answers did more than change the frame, they also change the behaviour (the control started sliding down with the content). To change the frame but not the behaviour I did this:
- (void)layoutSubviews {
[super layoutSubviews];
CGRect frame = self.frame;
CGFloat desiredYOffset = 50.0f;
self.frame = CGRectMake(frame.origin.x, frame.origin.y + desiredYOffset, frame.size.width, frame.size.height);
}
This works super-smoothly, but it's a super hacky solution:
class CustomUIRefreshControl: UIRefreshControl {
override func layoutSubviews() {
let offset = UIApplication.shared.windows[0].safeAreaInsets.top
frame = CGRect(
x: frame.origin.x,
y: frame.origin.y + offset/2.5,
width: frame.size.width,
height: frame.size.height
)
let attribute = [
NSAttributedString.Key.font: UIFont(name: "Chalkduster", size: offset/2)!,
NSAttributedString.Key.foregroundColor: UIColor.clear
]
attributedTitle = NSAttributedString(string: "Hack", attributes: attribute)
super.layoutSubviews()
}
}
Related
I want to center the text vertically inside a big UITextView that fills the whole screen - so that when there's little of text, say a couple of words, it is centered by height.
It's not a question about centering the text (a property that can be found in IB) but about putting the text vertically right in the middle of UITextView if the text is short, so there are no blank areas in the UITextView.
Can this be done?
First add an observer for the contentSize key value of the UITextView when the view is loaded:
- (void) viewDidLoad {
[textField addObserver:self forKeyPath:#"contentSize" options:(NSKeyValueObservingOptionNew) context:NULL];
[super viewDidLoad];
}
Then add this method to adjust the contentOffset every time the contentSize value changes:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
UITextView *tv = object;
CGFloat topCorrect = ([tv bounds].size.height - [tv contentSize].height * [tv zoomScale])/2.0;
topCorrect = ( topCorrect < 0.0 ? 0.0 : topCorrect );
tv.contentOffset = (CGPoint){.x = 0, .y = -topCorrect};
}
Because UIKit is not KVO compliant, I decided to implement this as a subclass of UITextView which updates whenever the contentSize changes.
It's a slightly modified version of Carlos's answer which sets the contentInset instead of the contentOffset. In addition to being compatible with iOS 9, it also seems to be less buggy on iOS 8.4.
class VerticallyCenteredTextView: UITextView {
override var contentSize: CGSize {
didSet {
var topCorrection = (bounds.size.height - contentSize.height * zoomScale) / 2.0
topCorrection = max(0, topCorrection)
contentInset = UIEdgeInsets(top: topCorrection, left: 0, bottom: 0, right: 0)
}
}
}
If you don't want to use KVO you can also manually adjust offset with exporting this code to a function like this :
-(void)adjustContentSize:(UITextView*)tv{
CGFloat deadSpace = ([tv bounds].size.height - [tv contentSize].height);
CGFloat inset = MAX(0, deadSpace/2.0);
tv.contentInset = UIEdgeInsetsMake(inset, tv.contentInset.left, inset, tv.contentInset.right);
}
and calling it in
-(void)textViewDidChange:(UITextView *)textView{
[self adjustContentSize:textView];
}
and every time you edit the text in the code. Don't forget to set the controller as the delegate
Swift 3 version:
func adjustContentSize(tv: UITextView){
let deadSpace = tv.bounds.size.height - tv.contentSize.height
let inset = max(0, deadSpace/2.0)
tv.contentInset = UIEdgeInsetsMake(inset, tv.contentInset.left, inset, tv.contentInset.right)
}
func textViewDidChange(_ textView: UITextView) {
self.adjustContentSize(tv: textView)
}
For iOS 9.0.2. we'll need to set the contentInset instead. If we KVO the contentOffset, iOS 9.0.2 sets it to 0 at the last moment, overriding the changes to contentOffset.
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
UITextView *tv = object;
CGFloat topCorrect = ([tv bounds].size.height - [tv contentSize].height * [tv zoomScale])/2.0;
topCorrect = ( topCorrect < 0.0 ? 0.0 : topCorrect );
[tv setContentInset:UIEdgeInsetsMake(topCorrect,0,0,0)];
}
- (void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:NO];
[questionTextView addObserver:self forKeyPath:#"contentSize" options:(NSKeyValueObservingOptionNew) context:NULL];
}
I used 0,0, and 0 for the left,bottom and right edge insets respectively. Make sure to calculate those as well for your use case.
It is the simple task using NSLayoutManager to get real text size of the NSTextContainer
class VerticallyCenteredTextView: UITextView {
override func layoutSubviews() {
super.layoutSubviews()
let rect = layoutManager.usedRect(for: textContainer)
let topInset = (bounds.size.height - rect.height) / 2.0
textContainerInset.top = max(0, topInset)
}
}
Don't use contentSize and contentInset in your final calculations.
Here's a UITextView extension that centers content vertically:
extension UITextView {
func centerVertically() {
let fittingSize = CGSize(width: bounds.width, height: CGFloat.max)
let size = sizeThatFits(fittingSize)
let topOffset = (bounds.size.height - size.height * zoomScale) / 2
let positiveTopOffset = max(0, topOffset)
contentOffset.y = -positiveTopOffset
}
}
You can set it up directly with only constraints:
There are 3 constraints i added to align text vertically and horizontally in constraints as below :
Make height 0 and add constraints greater than
Add vertically align to parent constraints
Add horizontally align to parent constraints
I just created a custom vertically centered text view in Swift 3:
class VerticallyCenteredTextView: UITextView {
override var contentSize: CGSize {
didSet {
var topCorrection = (bounds.size.height - contentSize.height * zoomScale) / 2.0
topCorrection = max(0, topCorrection)
contentInset = UIEdgeInsets(top: topCorrection, left: 0, bottom: 0, right: 0)
}
}
}
Ref: https://geek-is-stupid.github.io/2017-05-15-how-to-center-text-vertically-in-a-uitextview/
func alignTextVerticalInTextView(textView :UITextView) {
let size = textView.sizeThatFits(CGSizeMake(CGRectGetWidth(textView.bounds), CGFloat(MAXFLOAT)))
var topoffset = (textView.bounds.size.height - size.height * textView.zoomScale) / 2.0
topoffset = topoffset < 0.0 ? 0.0 : topoffset
textView.contentOffset = CGPointMake(0, -topoffset)
}
I have a textview that I'm using with autolayout and with setting the lineFragmentPadding and textContainerInset to zero. None of the solutions above worked in my situation. However, this works for me. Tested with iOS 9
#interface VerticallyCenteredTextView : UITextView
#end
#implementation VerticallyCenteredTextView
-(void)layoutSubviews{
[self recenter];
}
-(void)recenter{
// using self.contentSize doesn't work correctly, have to calculate content size
CGSize contentSize = [self sizeThatFits:CGSizeMake(self.bounds.size.width, CGFLOAT_MAX)];
CGFloat topCorrection = (self.bounds.size.height - contentSize.height * self.zoomScale) / 2.0;
self.contentOffset = CGPointMake(0, -topCorrection);
}
#end
I have also this problem and I solved it with a UITableViewCell with UITextView. I created method in a custom UITableViewCell subclass, property statusTextView:
- (void)centerTextInTextView
{
CGFloat topCorrect = ([self.statusTextView bounds].size.height - [self.statusTextView contentSize].height * [self.statusTextView zoomScale])/2.0;
topCorrect = ( topCorrect < 0.0 ? 0.0 : topCorrect );
self.statusTextView.contentOffset = (CGPoint){ .x = 0, .y = -topCorrect };
And call this method in methods:
- (void)textViewDidBeginEditing:(UITextView *)textView
- (void)textViewDidEndEditing:(UITextView *)textView
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
This solution worked for me with no issues, you can try it.
Swift 3:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
textField.frame = self.view.bounds
var topCorrect : CGFloat = (self.view.frame.height / 2) - (textField.contentSize.height / 2)
topCorrect = topCorrect < 0.0 ? 0.0 : topCorrect
textField.contentInset = UIEdgeInsetsMake(topCorrect,0,0,0)
}
Add to Carlos answer, just in case you have text in tv bigger then tv size you don't need to recenter text, so change this code:
tv.contentOffset = (CGPoint){.x = 0, .y = -topCorrect};
to this:
if ([tv contentSize].height < [tv bounds].size.height) {
tv.contentOffset = (CGPoint){.x = 0, .y = -topCorrect};
}
Auto-layout solution:
Create a UIView that acts as a container for the UITextView.
Add the following constraints:
TextView: Align leading space to: Container
TextView: Align trailing space to: Container
TextView: Align center Y to: Container
TextView: Equal Height to: Container, Relation: ≤
You can try below code, no observer mandatorily required. observer throws error sometimes when view deallocates.
You can keep this code in viewDidLoad, viewWillAppear or in viewDidAppear anywhere.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^(void) {
UITextView *tv = txtviewDesc;
CGFloat topCorrect = ([tv bounds].size.height - [tv contentSize].height * [tv zoomScale])/2.0;
topCorrect = ( topCorrect < 0.0 ? 0.0 : topCorrect );
tv.contentOffset = (CGPoint){.x = 0, .y = -topCorrect};
});
});
I did it like this: first of all, I embedded the UITextView in an UIView (this should work for mac OS too). Then I pinned all four sides of the external UIView to the sides of its container, giving it a shape and size similar or equal to that of the UITextView. Thus I had a proper container for the UITextView. Then I pinned the left and right borders of the UITextView to the sides of the UIView, and gave the UITextView a height. Finally, I centered the UITextView vertically in the UIView. Bingo :) now the UITextView is vertically centered in the UIView, hence text inside the UITextView is vertically centered too.
UITextView+VerticalAlignment.h
// UITextView+VerticalAlignment.h
// (c) The Internet 2015
#import <UIKit/UIKit.h>
#interface UITextView (VerticalAlignment)
- (void)alignToVerticalCenter;
- (void)disableAlignment;
#end
UITextView+VerticalAlignment.m
#import "UITextView+VerticalAlignment.h"
#implementation UITextView (VerticalAlignment)
- (void)alignToVerticalCenter {
[self addObserver:self forKeyPath:#"contentSize" options:NSKeyValueObservingOptionNew context:NULL];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
UITextView *tv = object;
CGFloat topCorrect = ([tv bounds].size.height - [tv contentSize].height * [tv zoomScale])/2.0;
topCorrect = ( topCorrect < 0.0 ? 0.0 : topCorrect );
tv.contentOffset = (CGPoint){.x = 0, .y = -topCorrect};
}
- (void)disableAlignment {
[self removeObserver:self forKeyPath:#"contentSize"];
}
#end
I fixed this problem by creating extension to center height vertically.
SWIFT 5:
extension UITextView {
func centerContentVertically() {
let fitSize = CGSize(width: bounds.width, height: CGFloat.greatestFiniteMagnitude)
let size = sizeThatFits(fitSize)
let heightOffset = (bounds.size.height - size.height * zoomScale) / 2
let positiveTopOffset = max(0, heightOffset)
contentOffset.y = -positiveTopOffset
}
}
Solution for iOS10 in RubyMotion:
class VerticallyCenteredTextView < UITextView
def init
super
end
def layoutSubviews
self.recenter
end
def recenter
contentSize = self.sizeThatFits(CGSizeMake(self.bounds.size.width, Float::MAX))
topCorrection = (self.bounds.size.height - contentSize.height * self.zoomScale) / 2.0;
topCorrection = 0 if topCorrection < 0
self.contentInset = UIEdgeInsetsMake(topCorrection, 0, 0, 0)
end
end
I want to create a subclass of UITableView or UIScrollView that will have some shading at the top when the content offset is > 0 to indicate that the content is scrollable. (See image attached)
The way I'm implementing it right now is using the UIViewController that is the delegate of the tableView. I simply have a GradientView on top of the tableView, and I intercept scrollViewDidScroll: to animate the visibility of that top gradient.
My problem with this implementation is that it's not "clean". I want my UIViewControllers to take care of logic, and not to deal with applying gradients and stuff. I wish I could just drop a subclass of UITableView that will do that for me.
The challenge for me is that I can't figure out how the tableView could add to itself a fixed content on top of the scrollable content.
Another question is what method/s of UIScrollView should I override to intercept the scrolling event. Obviously I don't want the tableView to be the delegate of itself...
Any ideas?
Thanks!
Ok, so I found the solution on Apple's WWDC 2011 Session 104 video - Advanced Scroll View Techniques.
There is a whole section in this video about "Stationary Views" inside a scroll view.
According to Apple, the way to go here is to override layoutSubviews and put there all the code to position whatever you want - wherever you want.
I tried it and it's actually pretty easy and it's working as expected.
So for example if I would like a shadowed header on top of the table when the content is being scrolled, this is the code I should write:
-(void) layoutSubviews
{
[super layoutSubviews];
[self positionTopShadow];
}
-(void) positionTopShadow
{
CGFloat yOffset = self.contentOffset.y;
// I'm doing some limiting so that the maximum height of the shadow view will be 40 pixels
yOffset = MIN(yOffset, 40);
yOffset = MAX(0, yOffset);
CGRect frame = self.topShadowView.frame;
// The origin should be exactly like the content offset so it would look like
// the shadow is at the top of the table (when it's actually just part of the content)
frame.origin = CGPointMake(0, self.contentOffset.y);
frame.size.height = yOffset;
frame.size.width = self.frame.size.width;
self.topShadowView.frame = frame;
if (self.topShadowView.superview == nil)
{
[self addSubview:self.topShadowView];
}
[self bringSubviewToFront:self.topShadowView];
}
I've managed to figure out a much simpler way of doing this then what Avraham did.
I use the fact that the UIScrollView calls scrollViewDidScroll: ever pixel the scrolling changes to set the object at the location of the offset. Below is my full code to keep a gray bar at the top of the scrollview as you move around:
- (void)viewDidLoad {
UIScrollView* scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(5.0, 50.0, self.bounds.size.width - 15.0, self.bounds.size.height - 60.0)];
[scrollView setBackgroundColor:[UIColor colorWithRed:251.0/255.0 green:251.0/255.0 blue:251.0/255.0 alpha:1.0]];
[scrollView setContentSize:CGSizeMake(scrollView.frame.size.width + 500, 1000.0)];
[scrollView setDelegate:self];
[self addSubview:scrollView];
UIView* header = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, scrollView.contentSize.width, 40.0)];
[header setTag:100];
[header setBackgroundColor:[UIColor darkGrayColor]];
[scrollView addSubview:header];
}
-(void)scrollViewDidScroll:(UIScrollView *)scrollView {
UIView* header = [self viewWithTag:100];
[header setFrame:CGRectMake(0.0, scrollView.contentOffset.y, header.bounds.size.width, header.bounds.size.height)];
}
You could try using viewForHeaderInSection method of tableView for the shaded view(and also heightForHeaderInSection)... Make the shaded portion as a header.That way there is a fixed content on top of the scrollable content.
#define kImageOriginHight 300
- (void)scrollViewDidScroll:(UIScrollView *)scrollView1{
CGFloat yOffset = scrollView1.contentOffset.y;
// NSLog(#" y offset := %f", yOffset);
//zoom images and hide upper view while scrooling to down position
if (yOffset < 0) {//-kImageOriginHight
CGRect f = imgV.frame;
f.origin.y = yOffset;
f.size.height = -yOffset + kImageOriginHight;
imgV.frame = f;
//viewTableUpperView.alpha = 1.5 - (yOffset/-kImageOriginHight);
//viewTableUpperView.userInteractionEnabled = NO;
if(yOffset+0.5 == -kImageOriginHight){
[UIView animateWithDuration:0.1 animations:^{
//viewTableUpperView.alpha = 1.0;
}];
//viewTableUpperView.userInteractionEnabled = YES;
}
}
}
how can i detect when a UIScrollViewframe intersect with the main screen (when scrolling) in both direction up and down? i was trying to do it like so :
- (void)detectScreenBoundsIntersect{
if (CGRectIntersectsRect([myScrollView frame], [[UIScreen mainScreen] bounds])) {
NSLog(#"COLLISION");
}
}
But this doesn't seem to work! Thanks.
Adopt the UIScrollViewDelegate protocol in the view controller and set the scroll view's delegate to the controller. Adopt the scrollViewDidScroll: method.
- (void) scrollViewDidScroll:(UIScrollView *)scrollView {
if ( [self hasReachedAVerticalEdge] ) {
NSLog(#"At World's End");
}
}
- (BOOL) hasReachedAVerticalEdge {
CGPoint offset = myScrollView.contentOffSet;
CGSize contentSize = myScrollView.contentSize;
CGFloat height = myScrollView.frame.size.height;
CGFloat width = myScrollView.frame.size.width;
if ( offset.y == 0 ||
(offset.y + height) == contentSize.height ) {
return YES;
}
return NO;
}
Is this what you are looking for?
Checking if an image view falls in the visible portion of the scroll view.
- (BOOL)isContentFrameVisible:(CGRect)aFrame {
CGRect visibleRect = CGRectZero;
visibleRect.origin = myScrollView.offset;
visibleRect.size = myScrollView.frame.size;
if ( CGRectIntersectsRect(visibleRect, aFrame) ) {
return YES;
}
}
I want to be align Left margin of UITextField.text to 10Px. please suggest me best way ?? same in roundedRect TesxtField where text start 10 px from left
have reached almost by overriding - (CGRect)textRectForBounds:(CGRect)bounds. now issue is when TextField goes in to edit mode their left margin reset to Zero .......
#implementation UITextField(UITextFieldCatagory)
- (CGRect)textRectForBounds:(CGRect)bounds {
CGRect theRect=CGRectMake(bounds.origin.x+10, bounds.origin.y, bounds.size.width-10, bounds.size.height);
return theRect;
}
Can you try UITextField's instance method drawTextInRect:?
I think you could use the leftView property for this.
You can add a leftView and rightView to a UITextfield. These views can be used to display an icon, but if it's an empty view it'll just take up space, which is what you want.
CGFloat leftInset = 5.0f;
UIView *leftView = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, leftInset, self.bounds.size.height)];
self.leftView = leftView;
self.leftViewMode = UITextFieldViewModeAlways;
[leftView release];
Refer SO question UITextField custom background view and shifting text
Have you tried
-(CGRect)editingRectForBounds(CGRect)bounds;
Override the super's implementation, and tune the results. This way you don't need to calculate with leftView and rightView (if you use them).
- (CGRect)textRectForBounds:(CGRect)bounds {
CGRect ret = [super textRectForBounds:bounds];
ret.origin.x = ret.origin.x + 10;
ret.size.width = ret.size.width - 20;
return ret;
}
- (CGRect)editingRectForBounds:(CGRect)bounds {
return [self textRectForBounds:bounds];
}
swift:
let leftView:UIView = UIView(frame: CGRectMake(0, 0, 30, 1))
leftView.backgroundColor = UIColor.clearColor()
my_text_field.leftView = leftView;
my_text_field.leftViewMode = UITextFieldViewMode.Always;
I want to make the top of the navigation view a bit smaller. How would you achieve this? This is what I've tried so far, but as you can see, even though I make the navigationbar smaller, the area which it used to occupy is still there (black).
[window addSubview:[navigationController view]];
navigationController.view.frame = CGRectMake(0, 100, 320, 280);
navigationController.navigationBar.frame = CGRectMake(0, 0, 320, 20);
navigationController.view.backgroundColor = [UIColor blackColor];
[window makeKeyAndVisible];
Create a UINavigationBar Category with a custom sizeThatFits.
#implementation UINavigationBar (customNav)
- (CGSize)sizeThatFits:(CGSize)size {
CGSize newSize = CGSizeMake(self.frame.size.width,70);
return newSize;
}
#end
Using this navigation bar subclass I've successfully created a larger navigation bar on iOS 5.x to iOS 6.x on the iPad. This gives me a larger navigation bar but doesn't break all the animations.
static CGFloat const CustomNavigationBarHeight = 62;
static CGFloat const NavigationBarHeight = 44;
static CGFloat const CustomNavigationBarHeightDelta = CustomNavigationBarHeight - NavigationBarHeight;
#implementation HINavigationBar
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// UIColor *titleColor = [[HITheme currentTheme] fontColorForLabelForLocation:HIThemeLabelNavigationTitle];
// UIFont *titleFont = [[HITheme currentTheme] fontForLabelForLocation:HIThemeLabelNavigationTitle];
// [self setTitleTextAttributes:#{ UITextAttributeFont : titleFont, UITextAttributeTextColor : titleColor }];
CGAffineTransform translate = CGAffineTransformMakeTranslation(0, -CustomNavigationBarHeightDelta / 2.0);
self.transform = translate;
[self resetBackgroundImageFrame];
}
return self;
}
- (void)resetBackgroundImageFrame
{
for (UIView *view in self.subviews) {
if ([NSStringFromClass([view class]) rangeOfString:#"BarBackground"].length != 0) {
view.frame = CGRectMake(0, CustomNavigationBarHeightDelta / 2.0, self.bounds.size.width, self.bounds.size.height);
}
}
}
- (void)setBackgroundImage:(UIImage *)backgroundImage forBarMetrics:(UIBarMetrics)barMetrics
{
[super setBackgroundImage:backgroundImage forBarMetrics:barMetrics];
[self resetBackgroundImageFrame];
}
- (CGSize)sizeThatFits:(CGSize)size
{
size.width = self.frame.size.width;
size.height = CustomNavigationBarHeight;
return size;
}
- (void)setFrame:(CGRect)frame
{
[super setFrame:frame];
[self resetBackgroundImageFrame];
}
#end
For swift
create a subclass of Uinavigation bar.
import UIKit
class higherNavBar: UINavigationBar {
override func sizeThatFits(size: CGSize) -> CGSize {
var newSize:CGSize = CGSizeMake(self.frame.size.width, 87)
return newSize
}
There will be two blank strips on both sides, I changed the width to the exact number to make it work.
However the title and back button are aligned to the bottom.
It's not necessary to subclass the UINavigationBar. In Objective-C you can use a category and in Swift you can use an extension.
extension UINavigationBar {
public override func sizeThatFits(size: CGSize) -> CGSize {
return CGSize(width: frame.width, height: 70)
}
}
I have found the following code to perform better on iPad (and iPhone):
- (CGSize)sizeThatFits:(CGSize)size
{
return CGSizeMake(self.superview.bounds.size.width, 62.0f);
}
If you want to use a custom height for your nav bar, I think you should probably, at the very least, use a custom nav bar (not one in your nav controller). Hide the navController's bar and add your own. Then you can set its height to be whatever you want.
I was able to use the following subclass code in Swift. It uses the existing height as a starting point and adds to it.
Unlike the other solutions on this page, it seems to still resize correctly when switching between landscape and portrait orientation.
class TallBar: UINavigationBar {
override func sizeThatFits(size: CGSize) -> CGSize {
var size = super.sizeThatFits(size)
size.height += 20
return size
}
}
Here's a pretty nice subclass in Swift that you can configure in Storyboard. It's based on the work done by mackross, which is great, but it was pre-iOS7 and will result in your nav bar not extending under the status bar.
class UINaviationBarCustomHeight: UINavigationBar {
// Note: this must be set before the navigation controller is drawn (before sizeThatFits is called),
// so set in IB or viewDidLoad of the navigation controller
#IBInspectable var barHeight: CGFloat = -1
#IBInspectable var barHeightPad: CGFloat = -1
override func sizeThatFits(size: CGSize) -> CGSize {
var customSize = super.sizeThatFits(size)
let stockHeight = customSize.height
if (UIDevice().userInterfaceIdiom == .Pad && barHeightPad > 0) {
customSize.height = barHeightPad
}
else if (barHeight > 0) {
customSize.height = barHeight
}
// re-center everything
transform = CGAffineTransformMakeTranslation(0, (stockHeight - customSize.height) / 2)
resetBackgroundImageFrame()
return customSize
}
override func setBackgroundImage(backgroundImage: UIImage?, forBarPosition barPosition: UIBarPosition, barMetrics: UIBarMetrics) {
super.setBackgroundImage(backgroundImage, forBarPosition: barPosition, barMetrics: barMetrics)
resetBackgroundImageFrame()
}
private func resetBackgroundImageFrame() {
if let bg = valueForKey("backgroundView") as? UIView {
var frame = bg.frame
frame.origin.y = -transform.ty
if (barPosition == .TopAttached) {
frame.origin.y -= UIApplication.sharedApplication().statusBarFrame.height
}
bg.frame = frame
}
}
}
I am a newbie in ios yet. I solved the problem in following way :
I have created a new class that inherits from UINavigationBar
I override the following method :
(void)setBounds:(CGRect)bounds {
[super setBounds:bounds];
self.frame = CGRectMake(0, 0, 320, 54);
}
3.To get a custom background of the navigation bar, I overrided the following method :
-(void)drawRect:(CGRect)rect {
[super drawRect:rect];
UIImage *img = [UIImage imageNamed:#"header.png"];
[img drawInRect:CGRectMake(0,0, self.frame.size.width, self.frame.size.height)];
}
In xib file, I have changed the default UINavigationBar class of the navigation bar to my class.