Why is custom navigation bar button image stretched? - ios

I tried to add a navigation bar button with custom image. However, whatever method I use, the image always appears stretched.
Method 1:
let barbuttonitem = UIBarButtonItem(image: UIImage(named: "apps_small"), style: .plain, target: self, action: nil)
navigationItem.leftBarButtonItem = barbuttonitem
It appears like this:
Method 2:
let button = UIButton(type: .custom)
button.setImage(UIImage(named: "apps_small"), for: .normal)
button.addTarget(self, action: #selector(TeachingRootViewController.appsTouched), for: .touchUpInside)
button.frame = CGRect(x: 0, y: 0, width: 10, height: 10)
button.bounds = CGRect(x: 0, y: 0, width: 10, height: 10)
navigationItem.leftBarButtonItem = UIBarButtonItem(customView: button)
It appears like this:
Method 3:
let button = UIButton(type: .custom)
button.setImage(UIImage(named: "apps_small"), for: .normal)
button.setTitle("Button", for: .normal)
button.frame = CGRect(x: 0, y: 0, width: 10, height: 10)
button.bounds = CGRect(x: 0, y: 0, width: 10, height: 10)
button.imageEdgeInsets = .init(top: 5, left: 5, bottom: 5, right: 300)
navigationItem.leftBarButtonItem = UIBarButtonItem(customView: button)
navigationItem.leftBarButtonItem?.imageInsets = .init(top: 5, left: 5, bottom: 5, right: 300)
It looks like this:
As you can see the title is gone.
In the UI Hierarchy, it looks like this:
It appears that the button has taken up all spaces in the navigation bar.
However, there is no problem for button with a system item:
Is there anybody who knows the reason for this problem? I think I have run out of ideas.

The above answer is a workaround and not a proper fix.
The correct answer is you should have generate your photos with the correct sizes
asset#1x: 22x22px
asset#2x: 44x44px
asset#3x: 66x66px

Try adding it as a subView instead of setting it as the leftBarButtonItem
var leftButton = UIButton(frame:CGRect(x: 0, y: 0, width: 10, height: 10))
var background = UIImageView(image: UIImage(named: "apps_small"))
background.frame = CGRect(x: 0, y: 0, width: 10, height: 10)
leftButton.addSubview(background)
self.navigationController.navigationBar.addSubview(leftButton)

As per my experience, You have faced this issue because you're added large size images for all resolution 1X, 2X and 3X. You need
to scale it down to a size more appropriate for the navigation bar.
Solution: 1
You need to scale it down to a size more appropriate for the navigation bar. Please have look image sizes for UINavigationBar
asset#1X Size: 24X24
asset#2X Size: 48X48
asset#3X Size: 72X72
Solution: 2
If you want to go with the same images then you need to do below changes in your code.
You just need to add UIImageView inside the UIView then everything
works as expected.
Code:
let containerView = UIControl(frame: CGRect.init(x: 0, y: 0, width: 30, height: 30))
containerView.addTarget(self, action: #selector(handleSearch), for: .touchUpInside)
let imageSearch = UIImageView(frame: CGRect.init(x: 0, y: 0, width: 30, height: 30))
imageSearch.image = UIImage(named: "search")
containerView.addSubview(imageSearch)
let searchBarButtonItem = UIBarButtonItem(customView: containerView)
navigationItem.rightBarButtonItem = searchBarButtonItem

It's all about the Image size that you put inside your UIButton.
If you are using UIButton as a custom view for
UIBarButtonItem(customView: button)
and inside that custom button, you are setting an image. It is better to set the image size to something you need.
let button = UIButton(type: .custom)
button.setImage(UIImage(named: "image")!.resizeImage(newWidth: 25), for: .normal)
And this is a good extension for resizing images in your app. it is using CoreGraphic to resize.
extension UIImage{
//Resize image
func resizeImage(newWidth: CGFloat) -> UIImage {
let originalImage = self
let scale = newWidth / self.size.width
let newHeight = self.size.height * scale
let newImage = self.resizeWithCoreGraphics(to: CGSize(width: newWidth, height: newHeight)) ?? originalImage
return newImage
}
private func resizeWithCoreGraphics(to newSize: CGSize) -> UIImage? {
guard let cgImage = cgImage, let colorSpace = cgImage.colorSpace else { return nil }
let width = Int(newSize.width)
let height = Int(newSize.height)
let bitsPerComponent = cgImage.bitsPerComponent
let bytesPerRow = cgImage.bytesPerRow
let bitmapInfo = cgImage.bitmapInfo
guard let context = CGContext(data: nil, width: width, height: height,
bitsPerComponent: bitsPerComponent,
bytesPerRow: bytesPerRow, space: colorSpace,
bitmapInfo: bitmapInfo.rawValue) else { return nil }
context.interpolationQuality = .high
let rect = CGRect(origin: CGPoint.zero, size: newSize)
context.draw(cgImage, in: rect)
return context.makeImage().flatMap { UIImage(cgImage: $0) }
}
}

Related

how to remove padding left UIBarButtonItem?

I would like to use a custom View as UIBarButtonItem left in an UINavigationController, is it possible to remove the left padding here?
Ill alrady tried:
let btn = UIButton()
btn.frame = CGRect(x: -50, y: -50, width: 44, height: 50)
btn.setImage(UIImage(name:"img").withRenderingMode(.alwaysTemplate), for: .normal)
btn.addTarget(self, action: #selector(openSetting), for: .touchUpInside)
let leftButtonBar = UIBarButtonItem(customView: btn)
self.navigationItem.leftBarButtonItems = ( [leftButtonBar , otherBtn ])
how to remove space left setting icon
update. tired this code:
let view = UIView(frame: CGRect(x: 0, y: 0, width: 50, height:
view.backgroundColor = .gray
let btn = UIButton()
btn.frame = CGRect(x: -15, y: 0, width: 44, height: 50)
btn.setImage(UIImage(name:"img").withRenderingMode(.alwaysTemplate), for: .normal)
btn.addTarget(self, action: #selector(DashboardTabBarController.openSetting), for: .touchUpInside)
view.addSubview(btn)
let leftButtonBar = UIBarButtonItem(customView: view)
problem this user when clicked btn setting user
and other problem title navigation not center align
You should have noticed that whatever x value you provide to your custom view i.e, btn, it will always start from the same position because internally navigation bar doesn't count for position provided for that item and it always set the internally defined position as in below image, I set the background color to the custom button for better visuals.
So, the trick here is to put your custom button into another container view as below,
let view = UIView(frame: CGRect(origin: .zero, size: CGSize(width: 200, height: 50)))
view.backgroundColor = .green
let btn = UIButton(frame: CGRect(x: -10, y: 10 , width: 30, height: 30))
btn.setImage(UIImage(named: "cancelled")?.withRenderingMode(.alwaysTemplate), for: .normal)
btn.addTarget(self, action: #selector(openSetting), for: .touchUpInside)
btn.backgroundColor = .yellow
btn.tintColor = .red
view.addSubview(btn)
let btn2 = UIButton(frame: CGRect(x: 20, y: 10, width: 30, height: 30))
btn2.setImage(UIImage(named: "cancelled")?.withRenderingMode(.alwaysTemplate), for: .normal)
btn2.contentMode = .scaleAspectFit
btn2.tintColor = .yellow
btn2.addTarget(self, action: #selector(openSetting), for: .touchUpInside)
view.addSubview(btn2)
let itemsContainer = UIBarButtonItem(customView: view)
self.navigationItem.leftBarButtonItems = [itemsContainer]
This will result into this,
Now, if you change the x, y, width, height of btn, it will adjust accordingly.
Please try this. It works fine for me. If this code not working properly then I will give you another solution.
let view = UIView(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
let btn = UIButton()
btn.frame = CGRect(x: -15, y: 0, width: 44, height: 50)
btn.setImage(UIImage(name:"img").withRenderingMode(.alwaysTemplate), for: .normal)
btn.addTarget(self, action: #selector(openSetting), for: .touchUpInside)
view.addSubview(btn)
let leftButtonBar = UIBarButtonItem(customView: view)
self.navigationItem.leftBarButtonItems = ( [leftButtonBar])
Please add other button in View and set the width of view accordingy.It may hepls to you.Thank you
You can set imageInsets and set image this is example of left margin to set back image on position of default back button
let rightBtn = UIBarButtonItem(image: UIImage(named: "BackArrow"), style: .done, target: self, action: #selector(actnClose))
rightBtn.imageInsets = UIEdgeInsets(top: 0, left: -12, bottom: 0, right: 0)
self.navigationItem.setLeftBarButton(rightBtn, animated: true)
Set the x value of cRect to zero:
btn.frame = CGRect(x: 0, y: -50, width: 44, height: 50)
The less painful and actually cleanest solution was to hide the left button and just add a subview directly to the navigationBar and customise it's position and size with the good old constraints. Worked like a charm. Haven't tested it in every situation but seems to work fine.

How do I create a Navigation Bar that has a left and right button on a ViewController that is not the root Viewcontroller?

This is how I want it to look. I've tried to use this code:
let button = UIButton.init(type: .custom)
button.setImage(UIImage.init(named: "yourImageName.png"), for: UIControlState.normal)
button.addTarget(self, action:#selector(ViewController.callMethod), for:.touchUpInside)
button.frame = CGRect.init(x: 0, y: 0, width: 30, height: 30)
//CGRectMake(0, 0, 30, 30)
let barButton = UIBarButtonItem.init(customView: button)
self.navigationItem.leftBarButtonItem = barButton
However, when I create the Navigation Controller, I make my map view(the main screen) the root viewcontroller for the Nav Controller. Thus, the view will not have a default back button, since there are no views on the stack before it. Is there a way to override this so my image shows up for the left button? The code above does not produce an image for the left button successfully. It's blank.
Figured out the answer:
var image = UIImage(named: "ic-menu-48px.png")
let navCon = self.navigationController
image = self.resizeImage(image: image!, targetSize: CGSize(width: 25.0,height: 25.0))
image = image?.withRenderingMode(.alwaysOriginal)
self.navigationItem.leftBarButtonItem = UIBarButtonItem(image: image, style:.plain, target: nil, action: nil)
func resizeImage(image: UIImage, targetSize: CGSize) -> UIImage {
let size = image.size
let widthRatio = targetSize.width / size.width
let heightRatio = targetSize.height / size.height
// Figure out what our orientation is, and use that to form the rectangle
var newSize: CGSize
if(widthRatio > heightRatio) {
newSize = CGSize(width: size.width * heightRatio, height: size.height * heightRatio)
} else {
newSize = CGSize(width: size.width * widthRatio, height: size.height * widthRatio)
}
// This is the rect that we've calculated out and this is what is actually used below
let rect = CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height)
// Actually do the resizing to the rect using the ImageContext stuff
UIGraphicsBeginImageContextWithOptions(newSize, false, 1.0)
image.draw(in: rect)
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage!
}

Set background color for top safe area only in iOS 11

I'm new to Swift. I have set my top bar programmatically.It works fine for all versions except for iOS version 11.
I want to change the background color of the safe area in iPhone X. Presently I have just added the following code to hide the status bar.
override var prefersStatusBarHidden: Bool {
return true
}
I have created top bar using code:
//Top Bar
let topBar = UIView(frame:CGRect(x: 0,y: 0, width: width, height: 60))
topBar.backgroundColor = UIColor.white
topBar.layer.shadowColor = UIColor.gray.cgColor
topBar.layer.shadowOffset = CGSize(width: 0, height: 3)
topBar.layer.shadowOpacity = 1
topBar.layer.masksToBounds = false
topBar.layer.shadowRadius = 8.0;
//ImageView - Back Button
let backBtn = UIButton(frame:CGRect(x: 25, y: 18, width: 18, height: 34))
let backBtnImage = UIImage(named: "back_button") as UIImage?
backBtn.setImage(backBtnImage, for: .normal)
backBtn.layer.masksToBounds = true
backBtn.addTarget(self,action:#selector(backButtonClicked),
for:.touchUpInside)
//Label - Title
let titleLabel = UILabel(frame:CGRect(x: width * 0.3, y: 13, width: width * 0.55, height: 40))
titleLabel.text = "Favorites"
titleLabel.contentMode = UIViewContentMode.center
//include all in view
topBar.addSubview(titleLabel)
topBar.addSubview(backBtn)
containerView.addSubview(topBar)
Is there a way in which i can do it without using UINavigationBar or setting status bar.
Hi you can also try this one ,
need to add a view with same background which you set in your header view and add to container parent view .
please refer below code
//Top Bar
let safeAreaView = UIView(frame:CGrect(x: 0,y: 0, width: width, height: 40))
safeAreaView.backgroundColor = UIColor.white
self.view.addSubview(safeAreaView)
let topBar = UIView(frame:CGRect(x: 0,y: 0, width: width, height: 60))
topBar.backgroundColor = UIColor.white
topBar.layer.shadowColor = UIColor.gray.cgColor
topBar.layer.shadowOffset = CGSize(width: 0, height: 3)
topBar.layer.shadowOpacity = 1
topBar.layer.masksToBounds = false
topBar.layer.shadowRadius = 8.0;
//ImageView - Back Button
let backBtn = UIButton(frame:CGRect(x: 25, y: 18, width: 18, height: 34))
let backBtnImage = UIImage(named: "back_button") as UIImage?
backBtn.setImage(backBtnImage, for: .normal)
backBtn.layer.masksToBounds = true
backBtn.addTarget(self,action:#selector(backButtonClicked),
for:.touchUpInside)
//Label - Title
let titleLabel = UILabel(frame:CGRect(x: width * 0.3, y: 13, width: width * 0.55, height: 40))
titleLabel.text = "Favorites"
titleLabel.contentMode = UIViewContentMode.center
//include all in view
topBar.addSubview(titleLabel)
topBar.addSubview(backBtn)
containerView.addSubview(topBar)

UINavigationBar set background image center swift..?

I'm placing the background image this:
self.navigationController.navigationBar.setBackgroundImage(image, forBarMetrics: .Default)
but, repeat the image. No I want it to be repeated to me and Put it center.. ?
Here's how to fix it using UIAppearance and UIImage API:
// disable image stretching by defining left and top caps.
let navbarImage = image.stretchableImage(withLeftCapWidth: 1, topCapHeight: 1)
UINavigationBar.appearance().setBackgroundImage(navbarImage, for: .default)
Example before:
and after the fix:
Try with below method which worked for me.
var headerview = UIView(frame: CGRect(x: 0, y: 0, width: 320, height: 44))
let imgview = UIImageView(frame: CGRect(x: 75, y: 0, width: 150, height: 44))
imgview.image = UIImage(named: "ImageName")
imgview.contentMode = UIViewContentMode.scaleAspectFit
headerview.addSubview(imgview)
self.navigationController?.navigationBar.topItem?.titleView = headerview

How to set circle button with background image on right navigation bar item

I have tried to put circle button on right navigation bar of iOS but unfortunately When I use button background it doesn't round the image it shows square shape background image but When I remove image and put background colour it round the button with background colour.
Code that I tried:
let button = UIButton()
button.frame = CGRectMake(0, 0, 40, 40)
button.layer.cornerRadius = 0.5 * button.bounds.size.width
button.setImage(self.myPic, forState: .Normal)
let barButton = UIBarButtonItem()
barButton.customView = button
self.navigationItem.rightBarButtonItem = barButton
Try to use this code..
for rounded button with image -
let button = UIButton()
button.frame = CGRectMake(0, 0, 40, 40)
let color = UIColor(patternImage: UIImage(named: "btnImage")!)
button.backgroundColor = color
button.layer.cornerRadius = 0.5 * button.bounds.size.width
let barButton = UIBarButtonItem()
barButton.customView = button
self.navigationItem.rightBarButtonItem = barButton
With Actual image---
let button = UIButton()
button.frame = CGRectMake(0, 0, 40, 40)
let image = UIImage(named: "btnImage")!
UIGraphicsBeginImageContextWithOptions(button.frame.size, false, image.scale)
let rect = CGRectMake(0, 0, button.frame.size.width, button.frame.size.height)
UIBezierPath(roundedRect: rect, cornerRadius: rect.width/2).addClip()
image.drawInRect(rect)
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
let color = UIColor(patternImage: newImage)
button.backgroundColor = color
button.layer.cornerRadius = 0.5 * button.bounds.size.width
let barButton = UIBarButtonItem()
barButton.customView = button
self.navigationItem.rightBarButtonItem = barButton
I made a solution for Swift 4, you need to resize the image and the frame too
let avatarSize: CGFloat = 30
let button = UIButton()
button.frame = CGRect(x: 0, y: 0, width: avatarSize, height: avatarSize)
button.setImage(UIImage(named: "avatar")?.resizeImage(avatarSize, opaque: false), for: .normal)
if let buttonImageView = button.imageView {
button.imageView?.layer.cornerRadius = buttonImageView.frame.size.width / 2
button.imageView?.clipsToBounds = true
button.imageView?.contentMode = .scaleAspectFit
}
Extension you need:
extension UIImage {
func resizeImage(_ dimension: CGFloat, opaque: Bool, contentMode:
UIViewContentMode = .scaleAspectFit) -> UIImage {
var width: CGFloat
var height: CGFloat
var newImage: UIImage
let size = self.size
let aspectRatio = size.width/size.height
switch contentMode {
case .scaleAspectFit:
if aspectRatio > 1 { // Landscape image
width = dimension
height = dimension / aspectRatio
} else { // Portrait image
height = dimension
width = dimension * aspectRatio
}
default:
fatalError("UIIMage.resizeToFit(): FATAL: Unimplemented ContentMode")
}
if #available(iOS 10.0, *) {
let renderFormat = UIGraphicsImageRendererFormat.default()
renderFormat.opaque = opaque
let renderer = UIGraphicsImageRenderer(size: CGSize(width: width, height: height), format: renderFormat)
newImage = renderer.image {
(context) in
self.draw(in: CGRect(x: 0, y: 0, width: width, height: height))
}
} else {
UIGraphicsBeginImageContextWithOptions(CGSize(width: width, height: height), opaque, 0)
self.draw(in: CGRect(x: 0, y: 0, width: width, height: height))
newImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
}
return newImage
}
}
swift 2
set bar button in circle
func setProfileImageOnBarButton() {
let button = UIButton()
button.setImage(profileImage, forState: UIControlState.Normal)
button.addTarget(self, action:#selector(self.openUserProfile), forControlEvents: UIControlEvents.TouchUpInside)
button.frame = CGRectMake(0, 0, 36, 36)
button.layer.cornerRadius = CGRectGetWidth(button.frame) / 2
button.layer.masksToBounds = true
let barButton = UIBarButtonItem(customView: button)
self.navigationItem.rightBarButtonItem = barButton
}
Swift 5 - You can simply do that, and make sure you make clipstobounds to true
teacherImage.setImage(UIImage(named: "icon_profile"), for: .normal)
teacherImage.frame = CGRect(x: 0, y: 0, width: 40, height: 40)
teacherImage.addTarget(self, action: #selector(addPressed), for: .touchUpInside)
teacherImage.layer.cornerRadius = 0.5 * teacherImage.bounds.size.width
teacherImage.clipsToBounds = true
let rightNavBarButton = UIBarButtonItem(customView: teacherImage)
let currWidth = rightNavBarButton.customView?.widthAnchor.constraint(equalToConstant: 40)
currWidth?.isActive = true
let currHeight = rightNavBarButton.customView?.heightAnchor.constraint(equalToConstant: 40)
currHeight?.isActive = true
self.navigationItem.rightBarButtonItem = rightNavBarButton
It works for Objective-C. Have a try!
UIButton *avatarButton = [[UIButton alloc]initWithFrame:CGRectMake(0, 0, 40, 40)];
// redraw the image to fit new size
UIGraphicsBeginImageContextWithOptions(avatarButton.frame.size, NO, 0);
[[UIImage imageNamed:#"pikachu"] drawInRect:CGRectMake(0, 0, avatarButton.frame.size.width, avatarButton.frame.size.height)];
UIImage *resultImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIColor *color = [UIColor colorWithPatternImage: resultImage];
avatarButton.backgroundColor = color;
avatarButton.layer.cornerRadius = 0.5 * avatarButton.bounds.size.width;
UIBarButtonItem *barButton = UIBarButtonItem.new;
barButton.customView = avatarButton;
self.navigationItem.rightBarButtonItem = barButton;

Resources