My question is as simple as the title, but here's a little more:
I have a UITextField, on which I've set the background image. The problem is that the text hugs so closely to it's edge, which is also the edge of the image. I want my text field to look like Apple's, with a bit of horizontal space between the background image and where the text starts.
This is the quickest way I've found without doing any subclasses:
UIView *spacerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
[textfield setLeftViewMode:UITextFieldViewModeAlways];
[textfield setLeftView:spacerView];
In Swift:
let spacerView = UIView(frame:CGRect(x:0, y:0, width:10, height:10))
textField.leftViewMode = UITextFieldViewMode.Always
textField.leftView = spacerView
Swift 5:
let spacerView = UIView(frame:CGRect(x:0, y:0, width:10, height:10))
textField.leftViewMode = .always
textField.leftView = spacerView
You have to subclass and override textRectForBounds: and editingRectForBounds:. Here is a UITextfield subclass with custom background and vertical and horizontal padding:
#interface MyUITextField : UITextField
#property (nonatomic, assign) float verticalPadding;
#property (nonatomic, assign) float horizontalPadding;
#end
#import "MyUITextField.h"
#implementation MyUITextField
#synthesize horizontalPadding, verticalPadding;
- (CGRect)textRectForBounds:(CGRect)bounds {
return CGRectMake(bounds.origin.x + horizontalPadding, bounds.origin.y + verticalPadding, bounds.size.width - horizontalPadding*2, bounds.size.height - verticalPadding*2);
}
- (CGRect)editingRectForBounds:(CGRect)bounds {
return [self textRectForBounds:bounds];
}
#end
Usage:
UIImage *bg = [UIImage imageNamed:#"textfield.png"];
CGRect rect = CGRectMake(0, 0, bg.size.width, bg.size.height);
MyUITextField *textfield = [[[MyUITextField alloc] initWithFrame:rect] autorelease];
textfield.verticalPadding = 10;
textfield.horizontalPadding = 10;
[textfield setBackground:bg];
[textfield setBorderStyle:UITextBorderStyleNone];
[self.view addSubview:textfield];
A good approach to add padding to UITextField is to subclass UITextField , overriding the rectangle methods and adding an edgeInsets property. You can then set the edgeInsets and the UITextField will be drawn accordingly. This will also function correctly with a custom leftView or rightView set.
OSTextField.h
#import <UIKit/UIKit.h>
#interface OSTextField : UITextField
#property (nonatomic, assign) UIEdgeInsets edgeInsets;
#end
OSTextField.m
#import "OSTextField.h"
#implementation OSTextField
- (id)initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
if (self) {
self.edgeInsets = UIEdgeInsetsMake(0, 0, 0, 0);
}
return self;
}
-(id)initWithCoder:(NSCoder *)aDecoder{
self = [super initWithCoder:aDecoder];
if(self){
self.edgeInsets = UIEdgeInsetsMake(0, 0, 0, 0);
}
return self;
}
- (CGRect)textRectForBounds:(CGRect)bounds {
return [super textRectForBounds:UIEdgeInsetsInsetRect(bounds, self.edgeInsets)];
}
- (CGRect)editingRectForBounds:(CGRect)bounds {
return [super editingRectForBounds:UIEdgeInsetsInsetRect(bounds, self.edgeInsets)];
}
#end
I turned this into a nice little extension for use inside Storyboards:
public extension UITextField {
#IBInspectable public var leftSpacer:CGFloat {
get {
return leftView?.frame.size.width ?? 0
} set {
leftViewMode = .Always
leftView = UIView(frame: CGRect(x: 0, y: 0, width: newValue, height: frame.size.height))
}
}
}
My 2 cents:
class PaddedTextField : UITextField {
var insets = UIEdgeInsets.zero
var verticalPadding:CGFloat = 0
var horizontalPadding:CGFloat = 0
override func textRect(forBounds bounds: CGRect) -> CGRect {
return CGRect(x: bounds.origin.x + insets.left, y: bounds.origin.y + insets.top, width: bounds.size.width - (insets.left + insets.right), height: bounds.size.height - (insets.top + insets.bottom));
}
override func editingRect(forBounds bounds: CGRect) -> CGRect {
return textRect(forBounds: bounds)
}
}
I suggest converting your UITextField to a UITextView and setting the contentInset. For example to indent by 10 spaces:
textview.contentInset=UIEdgeInsetsMake(0, 10, 0, 0);
Sometimes the leftView of the textField is a Apple's magnifying glass image.
If so, you can set an indent like this:
UIView *leftView = textField.leftView;
if (leftView && [leftView isKindOfClass:[UIImageView class]]) {
leftView.contentMode = UIViewContentModeCenter;
leftView.width = 20.0f; //the space you want indented.
}
If you don't want to create #IBOutlet's could simply subclass (answer in Swift):
class PaddedTextField: UITextField {
override func awakeFromNib() {
super.awakeFromNib()
let spacerView = UIView(frame:CGRect(x: 0, y: 0, width: 10, height: 10))
leftViewMode = .always
leftView = spacerView
}
}
Related
How would I go about drawing a custom UIView that is literally just a ball (a 2D circle)? Would I just override the drawRect method? And can someone show me the code for drawing a blue circle?
Also, would it be okay to change the frame of that view within the class itself? Or do I need to change the frame from a different class?
(just trying to set up a ball bouncing around)
You could use QuartzCore and do something this --
self.circleView = [[UIView alloc] initWithFrame:CGRectMake(10,20,100,100)];
self.circleView.alpha = 0.5;
self.circleView.layer.cornerRadius = 50; // half the width/height
self.circleView.backgroundColor = [UIColor blueColor];
Would I just override the drawRect
method?
Yes:
- (void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextAddEllipseInRect(ctx, rect);
CGContextSetFillColor(ctx, CGColorGetComponents([[UIColor blueColor] CGColor]));
CGContextFillPath(ctx);
}
Also, would it be okay to change the frame of that view within the class itself?
Ideally not, but you could.
Or do I need to change the frame from a different class?
I'd let the parent control that.
Here is another way by using UIBezierPath (maybe it's too late ^^)
Create a circle and mask UIView with it, as follows:
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
view.backgroundColor = [UIColor blueColor];
CAShapeLayer *shape = [CAShapeLayer layer];
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:view.center radius:(view.bounds.size.width / 2) startAngle:0 endAngle:(2 * M_PI) clockwise:YES];
shape.path = path.CGPath;
view.layer.mask = shape;
My contribution with a Swift extension:
extension UIView {
func asCircle() {
self.layer.cornerRadius = self.frame.width / 2;
self.layer.masksToBounds = true
}
}
Just call myView.asCircle()
Swift 3 - custom class, easy to reuse. It uses backgroundColor set in UI builder
import UIKit
#IBDesignable
class CircleBackgroundView: UIView {
override func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = bounds.size.width / 2
layer.masksToBounds = true
}
}
Swift 3 class:
import UIKit
class CircleView: UIView {
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else {return}
context.addEllipse(in: rect)
context.setFillColor(UIColor.blue.cgColor)
context.fillPath()
}
}
Another way of approaching circle (and other shapes) drawing is by using masks.
You draw circles or other shapes by, first, making masks of the shapes you need, second, provide squares of your color and, third, apply masks to those squares of color. You can change either mask or color to get a new custom circle or other shape.
#import <QuartzCore/QuartzCore.h>
#interface ViewController ()
#property (weak, nonatomic) IBOutlet UIView *area1;
#property (weak, nonatomic) IBOutlet UIView *area2;
#property (weak, nonatomic) IBOutlet UIView *area3;
#property (weak, nonatomic) IBOutlet UIView *area4;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.area1.backgroundColor = [UIColor blueColor];
[self useMaskFor: self.area1];
self.area2.backgroundColor = [UIColor orangeColor];
[self useMaskFor: self.area2];
self.area3.backgroundColor = [UIColor colorWithRed: 1.0 green: 0.0 blue: 0.5 alpha:1.0];
[self useMaskFor: self.area3];
self.area4.backgroundColor = [UIColor colorWithRed: 1.0 green: 0.0 blue: 0.5 alpha:0.5];
[self useMaskFor: self.area4];
}
- (void)useMaskFor: (UIView *)colorArea {
CALayer *maskLayer = [CALayer layer];
maskLayer.frame = colorArea.bounds;
UIImage *maskImage = [UIImage imageNamed:#"cirMask.png"];
maskLayer.contents = (__bridge id)maskImage.CGImage;
colorArea.layer.mask = maskLayer;
}
#end
Here is the output of the code above:
There's another alternative for lazy people. You can set the layer.cornerRadius key path for your view in the Interface Builder. For example, if your view has a width = height of 48, set layer.cornerRadius = 24:
However, this only works if you have a static size of the view (width/height is fixed) and it's not showing the circle in the interface builder.
Swift 3 - Xcode 8.1
#IBOutlet weak var myView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
let size:CGFloat = 35.0
myView.bounds = CGRect(x: 0, y: 0, width: size, height: size)
myView.layer.cornerRadius = size / 2
myView.layer.borderWidth = 1
myView.layer.borderColor = UIColor.Gray.cgColor
}
How can one draw a progressbar inside a UITextField ? I have tested two ways so far.
1. Add a UIProgressView object as a subview of the UITextField object.
UIProgressView* progressView = [[UIProgressView alloc] init];
[aUITextField addSubview:progressView];
progressView.progress = 0.5;
[progressView release];
2. Subclass UITextfield and override drawRect:.
- (id)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
// Initialization code
[self setBackgroundColor:[UIColor clearColor]];
}
return self;
}
- (void)drawRect:(CGRect)rect {
// Drawing code
[[UIColor orangeColor] setFill];
[[UIBezierPath bezierPathWithOvalInRect:rect] fill];
}
Both approaches didn't work. Do you see any problem with these approaches? And how can I make this work?
I am not sure adding the UIProgressView as a subview of a UITextField object will be useful as you can't change the frame of the progress view.
Subclassing seems to be the right approach. Here is what I could come up with. Check if it is useful to you.
ProgressField.h
#interface ProgressField : UITextField {
}
#property (nonatomic, assign) CGFloat progress;
#property (nonatomic, retain) UIColor * progressColor;
#end
ProgressField.m
#implementation ProgressField
#synthesize progress;
#synthesize progressColor;
- (void)setProgress:(CGFloat)aProgress {
if ( aProgress < 0.0 || aProgress > 1.0 ) {
return;
}
progress = aProgress;
CGRect progressRect = CGRectZero;
CGSize progressSize = CGSizeMake(progress * CGRectGetWidth(self.bounds), CGRectGetHeight(self.bounds));
progressRect.size = progressSize;
// Create the background image
UIGraphicsBeginImageContext(self.bounds.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [UIColor clearColor].CGColor);
CGContextFillRect(context, self.bounds);
CGContextSetFillColorWithColor(context, [self progressColor].CGColor);
CGContextFillRect(context, progressRect);
UIImage * image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[super setBackground:image];
}
- (void)setBackground:(UIImage *)background {
// NO-OP
}
- (UIImage *)background {
return nil;
}
- (id)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
[self setBorderStyle:UITextBorderStyleBezel];
}
return self;
}
This doesn't seem to work with UITextFields with borderStyle set to UITextBorderStyleRoundedRect.
UIProgressView* progressView = [[UIProgressView alloc] init];
progressView.frame = aUITextField.frame;// you can give even set the frame of your own using CGRectMake();
[aUITextField addSubview:progressView];
progressView.progress = 0.5;
[progressView release];
Set the progressview's frame.
Here I think you have to add progressView as subview to self.view , just set progressView's frame according to size that will fit in to UITextField , and make set center of progressview to center of UITextField .
hope it will help you.
how to set rounded corner for a UITextView ?
fist import the file
#import <QuartzCore/QuartzCore.h>
and then set the property of your text view
yourTextViewName.layer.cornerRadius = kCornerRadius;
where kCornerRadius is a constant you set as a radius for corner
Try this it will work for sure
you have to import
QuartzCore/QuartzCore.h
UITextView* txtView = [[UITextView alloc] initWithFrame:CGRectMake(50, 50, 300, 100)];
txtView.layer.cornerRadius = 5.0;
txtView.clipsToBounds = YES;
I define an category class for UITextView in .h:
#interface UITextView (RoundedCorner)
-(void) roundedCornerDefault;
-(void) roundedCornerWithRadius:(CGFloat) radius
borderColor:(CGColorRef) color
borderWidth:(CGFloat) width;
#end
and the implementation class:
#import <QuartzCore/QuartzCore.h>
#import "UITextView+RoundedCorner.h"
#implementation UITextView (RoundedCorner)
-(void) roundedCornerDefault {
[self roundedCornerWithRadius:10
borderColor:[[UIColor grayColor] CGColor]
borderWidth:1];
}
-(void) roundedCornerWithRadius:(CGFloat) radius
borderColor:(CGColorRef) color
borderWidth:(CGFloat) width {
self.layer.cornerRadius = radius;
self.layer.borderColor = color;
self.layer.borderWidth = width;
self.clipsToBounds = YES;
}
#end
Example to use it:
#import "UITextView+RoundedCorner.h"
...
[self.myTextView roundedCornerDefault];
Work for me as below code and step
create textView var
#IBOutlet weak var currentAddressOutlet: KMPlaceholderTextView!
create this function
private func setBorderForTextView() {
self.currentAddressOutlet.layer.borderColor = UIColor.lightGray.cgColor
self.currentAddressOutlet.layer.borderWidth = 0.5
self.currentAddressOutlet.layer.cornerRadius = 5
}
call above function in your viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
setupTable()
setBorderForTextView()
}
Result is here
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.
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.