iPhone - How set uinavigationbar height? - iphone

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.

Related

UITableView with UIRefreshControl under a semi-transparent status bar

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()
}
}

Custom UINavigationBar with custom height causes the UIBarButtonItem's to be positioned wrong

I created my own subclass of UINavigationBar in order to enable custom background that is taller than 44pxs.
I did it by overriding these two methods:
-(void) drawRect:(CGRect)rect
{
[self.backgroundImage drawInRect:CGRectMake(0, 0, self.backgroundImage.size.width, self.backgroundImage.size.height)];
}
- (CGSize)sizeThatFits:(CGSize)size
{
CGRect frame = [UIScreen mainScreen].applicationFrame;
CGSize newSize = CGSizeMake(frame.size.width , self.backgroundImage.size.height);
return newSize;
}
And this is the result:
Now, my problem as you can see is that all the UIBarButtonItem's (and the titleView) are placed at the bottom of the navigation bar.
I would like them to be pinned to the top of the bar (with some padding of course).
What to I need to override to achieve that?
Thanks!
EDIT:
This is the solution that I used:
-(void) layoutSubviews
{
[super layoutSubviews];
for (UIView *view in self.subviews)
{
CGRect frame = view.frame;
frame.origin.y = 5;
view.frame = frame;
}
}
Does the trick for idle state, still have some weird behavior on push and pop items.
Try to override layoutSubviews: call [super layoutSubviews] inside and then reposition the items.
To solve the push/pop issue use setTitleVerticalPositionAdjustment in sizeThatFits:(CGSize)size
- (CGSize)sizeThatFits:(CGSize)size {
UIImage *img = [UIImage imageNamed:#"navigation_background.png"];
[self setTitleVerticalPositionAdjustment:-7 forBarMetrics:UIBarMetricsDefault];
CGRect frame = [UIScreen mainScreen].applicationFrame;
CGSize newSize = CGSizeMake(frame.size.width , img.size.height);
return newSize;
}

Resetting UITextField Left margin

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;

How can I get a full-sized UINavigationBar titleView

I am trying to add a custom control as the titleView in a UINavigationBar. When I do so, despite setting the frame and the properties that would normally assume full width, I get this:
The bright blue can be ignored as it is where I am hiding my custom control. The issue is the narrow strips of navbar at the ends of the bar. How can I get rid of these so my customview will stretch 100%?
CGRect frame = CGRectMake(self.view.bounds.origin.x, self.view.bounds.origin.y, self.view.width, kDefaultBarHeight);
UANavBarControlView *control = [[[UANavBarControlView alloc] initWithFrame:frame] autorelease];
control.autoresizingMask = UIViewAutoresizingFlexibleWidth;
self.navigationItem.titleView = control;
PS - I know I can add the view by itself instead of being attached to a navigation bar and it would be very easy to position it myself. I have my reasons for needing it to be "on" the navigation bar, and those reasons are here
Just ran into the same problem and after a bit of thinking solved it. titleView's frame gets set by the navigationBar every time it appears.
All you have to do is subclass UIView and override setFrame.
Like this:
- (void)setFrame:(CGRect)frame {
[super setFrame:CGRectMake(0.0, 0.0, 320.0, 50.0)];
}
Now set your sneaky new UIView as the navigationItem.titleView and enjoy its newfound resistance to resizing by the superview.
You don't have to set super's frame every time your frame gets set. You can just set it once and be done. If you want to support orientation changes you could probably hack that together too.
Setting the titleView of your view's navigationItem will never do the trick. Instead, you can add a subView to the navigation controller's navigationBar :
UIView* ctrl = [[UIView alloc] initWithFrame:navController.navigationBar.bounds];
ctrl.backgroundColor = [UIColor yellowColor];
ctrl.autoresizingMask = UIViewAutoresizingFlexibleWidth;
[navController.navigationBar addSubview:ctrl];
The following code worded on iOS8/iOS9/iOS10/iOS11.
Code in swift 3
class TitleView: UIView {
override var frame: CGRect {
get {
return super.frame
}
set {
super.frame = newValue.insetBy(dx: -newValue.minX, dy: 0)
}
}
override func didMoveToSuperview() {
if let superview = superview {
frame = superview.bounds
translatesAutoresizingMaskIntoConstraints = true
autoresizingMask = [.flexibleWidth, .flexibleHeight]
}
}
override func updateConstraints() {
super.updateConstraints()
/// remove autolayout warning in iOS11
superview?.constraints.forEach { constraint in
if fabs(constraint.constant) == 8 {
constraint.isActive = false
}
}
}
}
Swift version of ksm's answer
let leftOffset: CGFloat = 60
let rightOffset: CGFloat = 60
#objc override var frame: CGRect {
didSet {
let width: CGFloat = UIScreen.main.bounds.width - leftOffset - rightOffset
let height: CGFloat = 44
super.frame = CGRect(
x: leftOffset,
y: 20,
width: width,
height: height
)
}
}
thanks #VdesmedT for the answers , I have been using this answer to achieve a full screen size titleView in navigationbar .
But, I have just upgraded to iOS11 recently and I found that this solution did not work. And so I figured it out by another way(may seem weird). The core of the idea is not to change the titleView's size, but to change the subViews of the bar to achive the fullscreen bar affects
make a barView (make sure the barView width is screenSize - 12*2, 12
is the system padding set to to titleView, you can use manual layout
or autolayout's constraint to achive this). and then set it as
navigation bar's titleView
add your components into this barView. you can align the components
from -12 to the barView's width + 12
overload your barView's pointInside , to make it respondable even
when the click happed outside the barView.
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
BOOL ret = [super pointInside:point withEvent:event];
if (ret == NO)
{
CGRect expandRect = UIEdgeInsetsInsetRect(self.bounds, UIEdgeInsetsMake(0, -12, 0, -12));
ret = CGRectContainsPoint(expandRect, point);
}
return ret;
}
CGRect frame = CGRectMake(0, 0, 320, 44);
UILabel *titlelabel = [[UILabel alloc]initWithFrame:frame];
titlelabel.textAlignment = UITextAlignmentCenter;
titlelabel.backgroundColor = [UIColor clearColor];
titlelabel.textColor = [UIColor whiteColor];
titlelabel.font = [UIFont systemFontOfSize:20];
titlelabel.text =#"Available Reports";
self.navigationItem.titleView = titlelabel;
if you want to set image then take uiimage view instead on uilable you can create any view of fully navigation bar just tell me how ur navigation look like i will send you code for that if you want i can put 6 button on navigation also

Alignment UIImageView with Aspect Fit

for my UIImageView I choose Aspect Fit (InterfaceBuilder) but how can I change the vertical alignment?
[EDIT - this code is a bit moldy being from 2011 and all but I incorporated #ArtOfWarefare's mods]
You can't do this w/ UIImageView. I created a simple UIView subclass MyImageView that contains a UIImageView. Code below.
// MyImageView.h
#import <UIKit/UIKit.h>
#interface MyImageView : UIView {
UIImageView *_imageView;
}
#property (nonatomic, assign) UIImage *image;
#end
and
// MyImageView.m
#import "MyImageView.h"
#implementation MyImageView
#dynamic image;
- (id)initWithCoder:(NSCoder*)coder
{
self = [super initWithCoder:coder];
if (self) {
self.clipsToBounds = YES;
_imageView = [[UIImageView alloc] initWithFrame:self.bounds];
_imageView.contentMode = UIViewContentModeScaleAspectFill;
[self addSubview:_imageView];
}
return self;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.clipsToBounds = YES;
_imageView = [[UIImageView alloc] initWithFrame:self.bounds];
_imageView.contentMode = UIViewContentModeScaleAspectFill;
[self addSubview:_imageView];
}
return self;
}
- (id)initWithImage:(UIImage *)anImage
{
self = [self initWithFrame:CGRectZero];
if (self) {
_imageView.image = anImage;
[_imageView sizeToFit];
// initialize frame to be same size as imageView
self.frame = _imageView.bounds;
}
return self;
}
// Delete this function if you're using ARC
- (void)dealloc
{
[_imageView release];
[super dealloc];
}
- (UIImage *)image
{
return _imageView.image;
}
- (void)setImage:(UIImage *)anImage
{
_imageView.image = anImage;
[self setNeedsLayout];
}
- (void)layoutSubviews
{
if (!self.image) return;
// compute scale factor for imageView
CGFloat widthScaleFactor = CGRectGetWidth(self.bounds) / self.image.size.width;
CGFloat heightScaleFactor = CGRectGetHeight(self.bounds) / self.image.size.height;
CGFloat imageViewXOrigin = 0;
CGFloat imageViewYOrigin = 0;
CGFloat imageViewWidth;
CGFloat imageViewHeight;
// if image is narrow and tall, scale to width and align vertically to the top
if (widthScaleFactor > heightScaleFactor) {
imageViewWidth = self.image.size.width * widthScaleFactor;
imageViewHeight = self.image.size.height * widthScaleFactor;
}
// else if image is wide and short, scale to height and align horizontally centered
else {
imageViewWidth = self.image.size.width * heightScaleFactor;
imageViewHeight = self.image.size.height * heightScaleFactor;
imageViewXOrigin = - (imageViewWidth - CGRectGetWidth(self.bounds))/2;
}
_imageView.frame = CGRectMake(imageViewXOrigin,
imageViewYOrigin,
imageViewWidth,
imageViewHeight);
}
- (void)setFrame:(CGRect)frame
{
[super setFrame:frame];
[self setNeedsLayout];
}
#end
If using Storyboards this can be achieved with constraints...
Firstly a UIView with the desired final frame / constraints. Add a UIImageView to the UIView. Set the contentMode to Aspect Fill. Make the UIImageView frame be the same ratio as the image (this avoids any Storyboard warnings later). Pin the sides to the UIView using standard constraints. Pin the top OR bottom (depending where you want it aligned) to the UIView using standard constraints. Finally add an aspect ratio constraint to the UIImageView (making sure ratio as the image).
This is a bit tricky one since there is no option to set further alignment rules if you already selected a content mode (.scaleAspectFit).
But here's a workaround to this:
First need to resize your source image explicitly by calculating dimensions (if it'd be in a UIImageView with contentMode = .scaleAspectFit).
extension UIImage {
func aspectFitImage(inRect rect: CGRect) -> UIImage? {
let width = self.size.width
let height = self.size.height
let aspectWidth = rect.width / width
let aspectHeight = rect.height / height
let scaleFactor = aspectWidth > aspectHeight ? rect.size.height / height : rect.size.width / width
UIGraphicsBeginImageContextWithOptions(CGSize(width: width * scaleFactor, height: height * scaleFactor), false, 0.0)
self.draw(in: CGRect(x: 0.0, y: 0.0, width: width * scaleFactor, height: height * scaleFactor))
defer {
UIGraphicsEndImageContext()
}
return UIGraphicsGetImageFromCurrentImageContext()
}
}
Then you simply need to call this function on your original image by passing your imageView's frame and assign the result to your UIImageView.image property. Also, make sure you set your imageView's desired contentMode here (or even in the Interface Builder)!
let image = UIImage(named: "MySourceImage")
imageView.image = image?.aspectFitImage(inRect: imageView.frame)
imageView.contentMode = .left
Try setting:
imageView.contentMode = UIViewContentModeScaleAspectFit;
imageView.clipsToBounds = YES;
This worked for me.
I used UIImageViewAligned for changing the alignment of image thanks to the developer
UIImageViewAligned
I know this is an old thread, but I thought I'd share what I did to easily change the clipped region from the top to the bottom of the image view in Interface Builder, in case anyone had the same problem I did. I had a UIImageView that filled the View of my ViewController, and was trying to make the top stay the same, independent of the size of the device's screen.
I applied the retina 4 form factor (Editor->Apply Retina 4 Form Factor).
I pinned the height and width.
Now, when the screen changes size, the UIImageView is actually the same size, and the view controller just clips what is off the screen. The frame origin stays at 0,0, so the bottom and right of the image are clipped, not the top.
Hope this helps.
You can do it by first scaling and then resizing.
The thing to mention here is that I was conditioned by height. I mean , I had to have the image of 34px high and no matter how width.
So , get the ratio between the actual content height and the height of the view ( 34 px ) and then scale the width too.
Here's how I did it:
CGSize size = [imageView sizeThatFits:imageView.frame.size];
CGSize actualSize;
actualSize.height = imageView.frame.size.height;
actualSize.width = size.width / (1.0 * (size.height / imageView.frame.size.height));
CGRect frame = imageView.frame;
frame.size = actualSize;
[imageView setFrame:frame];
Hope this helps.
I came up to the following solution:
Set UIImageView content mode to top: imageView.contentMode = .top
Resize image to fit UIImageView bounds
To load and resize image I use Kingfisher:
let size = imageView.bounds.size
let processor = ResizingImageProcessor(referenceSize: size, mode: .aspectFit)
imageView.kf.setImage(with: URL(string: imageUrl), options: [.processor(processor), .scaleFactor(UIScreen.main.scale)])
If you need to achieve aspectFit and get rid empty spaces
Dont forget to remove width constraint of your imageview from storyboard and enjoy
class SelfSizedImageView :UIImageView {
override func layoutSubviews() {
super.layoutSubviews()
guard let imageSize = image?.size else {
return
}
let viewBounds = bounds
let imageFactor = imageSize.width / imageSize.height
let newWidth = viewBounds.height * imageFactor
let myWidthConstraint = self.constraints.first(where: { $0.firstAttribute == .width })
myWidthConstraint?.constant = min(newWidth, UIScreen.main.bounds.width / 3)
layoutIfNeeded()
}}
I solved this by subclassing UIImageView and overriding the setImage: method. The subclass would first store it's original values for origin and size so it could use the original set size as a bounding box.
I set the content mode to UIViewContentModeAspectFit. Inside of setImage: I grabbed the image width to height ratio and then resized the image view to fit the same ratio as the image. After the resize, I adjusted my frame properties to set the image view on the same spot it was before, and then I called the super setImage:.
This results in an image view who's frame is adjusted to fit the image exactly, so aspect fit works and the image view frame properties are doing the heavy lifting in putting the image view where it should be to get the same effect.
Here's some code that I used:
First up, and I find it pretty useful in general, is a category on UIView that makes it easy to set frame properties on a view via properties like left, right, top, bottom, width, height, etc.
UIImageView+FrameAdditions
#interface UIView (FrameAdditions)
#property CGFloat left, right, top, bottom, width, height;
#property CGPoint origin;
#end
#implementation UIView (FrameAdditions)
- (CGFloat)left {
return self.frame.origin.x;
}
- (void)setLeft:(CGFloat)left {
self.frame = CGRectMake(left, self.frame.origin.y, self.frame.size.width, self.frame.size.height);
}
- (CGFloat)right {
return self.frame.origin.x + self.frame.size.width;
}
- (void)setRight:(CGFloat)right {
self.frame = CGRectMake(right - self.frame.size.width, self.frame.origin.y, self.frame.size.width, self.frame.size.height);
}
- (CGFloat)top {
return self.frame.origin.y;
}
- (void)setTop:(CGFloat)top {
self.frame = CGRectMake(self.frame.origin.x, top, self.frame.size.width, self.frame.size.height);
}
- (CGFloat)bottom {
return self.frame.origin.y + self.frame.size.height;
}
- (void)setBottom:(CGFloat)bottom {
self.frame = CGRectMake(self.frame.origin.x, bottom - self.frame.size.height, self.frame.size.width, self.frame.size.height);
}
- (CGFloat)width {
return self.frame.size.width;
}
- (void)setWidth:(CGFloat)width {
self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, width, self.frame.size.height);
}
- (CGFloat)height {
return self.frame.size.height;
}
- (void)setHeight:(CGFloat)height {
self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, self.frame.size.width, height);
}
- (CGPoint)origin {
return self.frame.origin;
}
- (void)setOrigin:(CGPoint)origin {
self.frame = CGRectMake(origin.x, origin.y, self.frame.size.width, self.frame.size.height);
}
#end
This is the subclass of UIImageView. It is not fully tested, but should get the idea across. This could be expanded to set your own new modes for alignment.
BottomCenteredImageView
#interface BottomCenteredImageView : UIImageView
#end
#interface BottomCenteredImageView() {
CGFloat originalLeft;
CGFloat originalBottom;
CGFloat originalHeight;
CGFloat originalWidth;
}
#end
#implementation BottomCenteredImageView
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if(self) {
[self initialize];
}
return self;
}
- (void)awakeFromNib {
[self initialize];
}
- (void)initialize {
originalLeft = self.frame.origin.x;
originalHeight = CGRectGetHeight(self.frame);
originalWidth = CGRectGetWidth(self.frame);
originalBottom = self.frame.origin.y + originalHeight;
}
- (void)setImage:(UIImage *)image {
if(image) {
self.width = originalWidth;
self.height = originalHeight;
self.left = originalLeft;
self.bottom = originalBottom;
float myWidthToHeightRatio = originalWidth/originalHeight;
float imageWidthToHeightRatio = image.size.width/image.size.height;
if(myWidthToHeightRatio >= imageWidthToHeightRatio) {
// Calculate my new width
CGFloat newWidth = self.height * imageWidthToHeightRatio;
self.width = newWidth;
self.left = originalLeft + (originalWidth - self.width)/2;
self.bottom = originalBottom;
} else {
// Calculate my new height
CGFloat newHeight = self.width / imageWidthToHeightRatio;
self.height = newHeight;
self.bottom = originalBottom;
}
self.contentMode = UIViewContentModeScaleAspectFit;
[super setImage:image];
} else {
[super setImage:image];
}
}
#end
Credit to #michael-platt
Key Points
imageView.contentMode = .scaleAspectFit
let width = Layout.height * image.size.width / image.size.height
imageView.widthAnchor.constraint(equalToConstant: width).isActive = true
Code:
func setupImageView(for image: UIImage) {
let imageView = UIImageView()
imageView.backgroundColor = .orange
//Content mode
imageView.contentMode = .scaleAspectFill
view.addSubview(imageView)
//Constraints
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
imageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
//Feel free to set the height to any value
imageView.heightAnchor.constraint(equalToConstant: 150).isActive = true
//ImageView width calculation
let width = Layout.height * image.size.width / image.size.height
imageView.widthAnchor.constraint(equalToConstant: width).isActive = true
}
To align a scale-to-fit image use auto layout. Example right aligned image after scale-to-fit in UIImageView:
Create UIImageView that holds the image
Add auto constraints for right, top, bottom, width
Set the image:
myUIImageView.contentMode = .ScaleAspectFit
myUIImageView.translatesAutoresizingMaskIntoConstraints = false
myUIImageView.image = UIImage(named:"pizza.png")
Your aspect fit scaled image is now right aligned in the UIImageView
+----------------------------------+
| [IMAGE]|
+----------------------------------+
Change the constraints to align differently within the imageview.