Navigation Bar Items not left aligned - ios

I have a navigationBar where I would like to have a button on the left, a activityIndicator as title and a doneButton on the right. This is what I tried:
let activityIndicator = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 25, height: 25))
let googleImage = UIImage(named: "google")!
let googleButton = UIBarButtonItem(image: googleImage, style: .plain, target: self, action: #selector(googleButtonTapped))
googleButton.image = UIImage(named: "google")?.withRenderingMode(UIImage.RenderingMode.alwaysOriginal)
navigationController?.navigationBar.barTintColor = UIColor.blueCustom
navigationController?.navigationBar.tintColor = UIColor.white
activityIndicator.hidesWhenStopped = true
activityIndicator.color = .white
self.navigationItem.titleView = self.activityIndicator
self.navigationItem.leftBarButtonItem = googleButton
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Fertig", style: .done, target: self, action: #selector(doneTapped))
The problem is that googleButton and activityIndicator are not on the left/center:
What am I missing here?
Update:
I tried setting the leftBarButtonItem to a simple item with title and it works as expexted.. Why is it not working with the image?

I think it has to do with the intrinsicContentSize of the googleButton. Since you haven't explicitly set it, the size of the google logo image is expanding the width of the button. The intrinsicContentSize property of a UIButton has a set height by default, but none for the width:
let googleImage = UIImage(named: "google")?.withRenderingMode(UIImage.RenderingMode.alwaysOriginal).resizeTo(size: CGSize(width: 25, height: 25))
let googleButton = UIButton()
googleButton.setBackgroundImage(googleImage, for: .normal)
googleButton.addTarget(self, action: #selector(googleButtonTapped), for: .touchUpInside)
let googleBarButton = UIBarButtonItem(customView: googleButton)
You can create a UIImage extension to redraw the size of the google logo:
extension UIImage {
func resizeTo(size: CGSize) -> UIImage {
let renderer = UIGraphicsImageRenderer(size: size)
let image = renderer.image { _ in
self.draw(in: CGRect.init(origin: CGPoint.zero, size: size))
}
return image.withRenderingMode(self.renderingMode)
}
}
Change the button name to:
self.navigationItem.leftBarButtonItem = googleBarButton
Of course, the 25 points for the width and the height is arbitrary. Adjust it to fit your need.

Related

How to customize navigation back button in swift?

I want to change the back button icon and the label and the color of the back button label on the navigation bar. I have written the following code but it's not working. if anybody knows the solution please help me out.
override func viewDidLoad() {
super.viewDidLoad()
let yourBackImage = UIImage(named: "back_arrow")
self.navigationController?.navigationBar.backIndicatorImage = yourBackImage
self.navigationController?.navigationBar.tintColor = .white
self.navigationController?.navigationBar.backIndicatorTransitionMaskImage = yourBackImage
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: "Find or Invite", style: UIBarButtonItem.Style.plain, target: nil, action: nil)
}
Expected out put
Looks like you have manually added navigationBar without an embedded navigation controller.
For this just connect the outlet like this
#IBOutlet weak var navigationBar: UINavigationBar!
and remove the navigation controller part from your code as follows
let yourBackImage = UIImage(named: "back_arrow")
self.navigationBar.backIndicatorImage = yourBackImage
self.navigationBar.tintColor = .white
self.navigationBar.backIndicatorTransitionMaskImage = yourBackImage
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: "Find or Invite", style: UIBarButtonItem.Style.plain, target: nil, action: nil)
Try this:
in viewDidLoad declare your button, image and title
let button = UIButton(type: .custom)
//Set the image
button.setImage(UIImage(systemName: "chevron.backward"), for: .normal)
//Set the title
button.setTitle("Yourtitle", for: .normal)
//Add target
button.addTarget(self, action: #selector(callMethod), for: .touchUpInside)
button.frame = CGRect(x: 0, y: 0, width: 100, height: 30)
button.sizeToFit()
now set spacing between image and title:
let spacing:CGFloat = 10.0; // the amount of spacing to appear between image and title
button.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: spacing)
button.titleEdgeInsets = UIEdgeInsets(top: 0, left: spacing, bottom: 0, right: 0)
after that create your bar button and add to your nav bar:
let barButton = UIBarButtonItem(customView: button)
navigationItem.leftBarButtonItem = barButton
this is the result:
Bonus:
if you want to set navigation bar correctly and in one line, take a look to my extension here https://stackoverflow.com/a/58361273/5575955
I have add both back button and title in Navigation bar button item. I have made dynamic function for using both.
this code for back button, it will return UIBarButtonItem item.
func getBackButton(selector: Selector, target: Any) -> UIBarButtonItem {
let button = UIButton(type: .custom)
button.setImage(UIImage(named: "img_back_white"), for: .normal)
button.setImage(UIImage(named: "img_back_white"), for: .highlighted)
button.setImage(UIImage(named: "img_back_white"), for: .selected)
button.imageView?.contentMode = .scaleAspectFit
button.imageEdgeInsets = UIEdgeInsets(top: 0, left: -15, bottom: 0, right: 0)
button.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
//button.imageEdgeInsets = UIEdgeInsets(top: 0.0, left: 0, bottom: 0, right: 0)
button.backgroundColor = .clear
button.addTarget(target, action: selector, for: .touchUpInside)
return UIBarButtonItem(customView: button)
}
Now for add title beside back button
func getNavigationTitleButton(title: String, font: UIFont = UIFont.SFProDisplayRegular(size: 19)) -> UIBarButtonItem {
let navTitle = UILabel()
navTitle.text = title
navTitle.textColor = .AppWhite
navTitle.isEnabled = true
navTitle.font = font
//navTitle.backgroundColor = .red
//navTitle.frame.origin.x = -40
//navTitle.sizeToFit()
return UIBarButtonItem(customView: navTitle)
}
You can customize according your need. I want to add one more method if you want to show both in some screen directly so use this method with title and selector parameter.
func addBackWithTitle(title: String, selector: Selector) {
self.navigationItem.leftBarButtonItems = [self.getBackButton(selector: selector, target: self), self.getNavigationTitleButton(title: title)]
}

How to fit an image in rightbarbuttonitem

I've been trying to put a hamburger button (the three parallel lines) to the right of the titleView in the nav bar, but every time I do, the image I put in covers the entire nav bar and gets rid of the image I have in titleView.
If I select a default image in the storyboard editor it will appear on the right side of the nav bar without any problems, but as soon as I select the hamburger button in the storyboard editor I get the same problem as before. I've tried with multiple different images and I've changed up the code a little bit with no success. Is there a way to resize the image I'm using so it will fit in the nav bar properly or is there just something wrong with my code?
Here is my code from viewController.swift below:
override func viewDidAppear(_ animated: Bool) {
let nav = self.navigationController?.navigationBar
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 40, height: 40))
imageView.contentMode = .scaleAspectFit
let titleImage = UIImage(named: "logowhitecircle")
imageView.image = titleImage
navigationItem.titleView = imageView
let menuButton = UIButton(frame: CGRect(x: 0, y: 0, width: 40, height: 40))
menuButton.contentMode = .scaleAspectFit
let menuImage = UIImage(named: "hamburgericon")
menuButton.setImage(menuImage, for: .normal)
self.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: menuButton)
You need to set image size in the image assets like 3x = 84px, 2x=56px, 1x = 28px,
see the apple document for more info: https://developer.apple.com/design/human-interface-guidelines/ios/icons-and-images/custom-icons/
let menuButton = UIBarButtonItem(image: UIImage(named: "logowhitecircle"), style: .plain, target: self, action: #selector(menuButtonTapped(_:)))
self.navigationItem.rightBarButtonItem = menuBu
tton
Try Using below code:
let imageBurger = UIImage(named: "hamburgericon")!
let btnLeftMenu = UIButton(type: .system)
btnLeftMenu.bounds = CGRect(x: 10, y: 0, width: imageBurger.size.width, height: imageBurger.size.height)
btnLeftMenu.contentEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
btnLeftMenu.setImage(imageBurger, for: UIControl.State())
btnLeftMenu.setTitle(title, for: .normal)
let leftButton = UIBarButtonItem(customView: btnLeftMenu)
self.navigationItem.leftBarButtonItem = leftButton
Try this module FFBadgedBarButtonItem it's easy to use module, here is the documenataion Link
Below is my code how to implement it!
let image = UIImage(named: "yourImage")
let finalImage = resizeImage(image: image!, newWidth: 30)
navigationItem.rightBarButtonItem = FFBadgedBarButtonItem(image: finalImage, target: self, action: #selector(rightButtonTouched))
And here is the calling function
#objc func rightButtonTouched() {
// what event you need to perfom by clicking on this button
}
You need to create Bridging Header to work with this Obj-C module.
: D
Try this:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//create a new button
let button: UIButton = UIButton.buttonWithType(UIButtonType.Custom) as! UIButton
//set image for button
button.setImage(UIImage(named: "hamburgericon"), forState: UIControlState.Normal)
//add function for button
button.addTarget(self, action: "customButtonPressed", forControlEvents: UIControlEvents.TouchUpInside)
//set frame
button.frame = CGRectMake(0, 0, 40, 40)
let barButton = UIBarButtonItem(customView: button)
//assign button to navigationbar
self.navigationItem.rightBarButtonItem = barButton
}
//This method will call when you press button.
func customButtonPressed() {
println("button pressed")
}
}

First leftBarButtonItem is hiding

I used 2 bar button items. One is for back button and second is for the title. It is working fine. But when the title string is large, the title shifts to left and back button does not appear, but it is working.
I am also using a rightBarButtonItem, that is attached with a badge button. But that is not affecting this as I have tried the same code after removing that button. This is my code -
let backBtnImg: UIImage = UIImage(named: "Back Image")!
let Back: UIBarButtonItem = UIBarButtonItem(image: backBtnImg, style: .plain, target: self, action: #selector(backButtonAction))
let titleStr = ("titleString")
let titleItem: UIBarButtonItem = UIBarButtonItem(title: titleStr, style: .plain, target: nil, action: nil)
self.navigationItem.leftBarButtonItems = [Back, titleItem]
I have attached both the images.
Please try this code if you want to set the title in left side. It may helps to you.
In viewDidLoad method
let navView = UIView(frame: CGRect(x: 0, y: 0, width: (self.navigationController?.navigationBar.frame.size.width)! - 50, height: 40))
lblTitle = UILabel(frame: CGRect(x: 0, y: 0, width: navView.frame.size.width - 40, height: 40))
lblTitle?.text = strTitle
lblTitle?.backgroundColor = UIColor.clear
lblTitle?.textColor = UIColor.white
lblTitle?.textAlignment = .left
navView.addSubview(lblTitle!)
self.navigationItem.titleView = navView
Use this One It help you
-> take only one left button
-> For Title use: self.navigationItem.title = "Navigation Title"
let backBtnImg: UIImage = UIImage(named: "back")!
let Back: UIBarButtonItem = UIBarButtonItem(image: backBtnImg, style: .plain, target: self, action: #selector(backButtonAction))
self.navigationItem.title = "Navigation Title"
self.navigationItem.leftBarButtonItems = [Back]
func setupNevigationBar(){
let btnBack = UIButton(type: .custom)
btnBack.setImage(#imageLiteral(resourceName: "back"), for: .normal)
btnBack.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
btnBack.addTarget(self, action: #selector(btnBackPressed), for: .touchUpInside)
let itemBack = UIBarButtonItem(customView: btnBack)
let titleStr = ("Regular Title")
let titleItem: UIBarButtonItem = UIBarButtonItem(title: titleStr, style: .plain, target: nil, action: nil)
self.navigationItem.leftBarButtonItems = [itemBack, titleItem]
self.navigationItem.setLeftBarButton(itemBack, animated: true)
}

Change size of UIBarButtonItem (image) in Swift 3

I am trying to change the size of some icons in my navBar, but I am a little confused as to how to do this? My code so far is:
func setUpNavBarButtons() {
let moreButton = UIBarButtonItem (image: UIImage(named:"ic_more_vert_3")?.withRenderingMode(.alwaysOriginal), style: .plain, target: self, action: #selector(handleMore))
navigationItem.rightBarButtonItems = [moreButton]
let refreshButton = UIBarButtonItem (image: UIImage(named:"ic_refresh")?.withRenderingMode(.alwaysOriginal), style: .plain, target: self, action: #selector(refreshDataButton))
navigationItem.leftBarButtonItems = [refreshButton]
}
any help?
This is how I did it
iOS 10 and below
func setUpMenuButton(){
let menuBtn = UIButton(type: .custom)
menuBtn.frame = CGRect(x: 0.0, y: 0.0, width: 20, height: 20)
menuBtn.setImage(UIImage(named:"menuIcon"), for: .normal)
menuBtn.addTarget(self, action: #selector(vc.onMenuButtonPressed(_:)), for: UIControlEvents.touchUpInside)
let menuBarItem = UIBarButtonItem(customView: menuBtn)
self.navigationItem.leftBarButtonItem = menuBarItem
}
iOS 11 - Navigation bar come up with Autolayout so frame setting may not work
func setUpMenuButton(){
let menuBtn = UIButton(type: .custom)
menuBtn.frame = CGRect(x: 0.0, y: 0.0, width: 20, height: 20)
menuBtn.setImage(UIImage(named:"menuIcon"), for: .normal)
menuBtn.addTarget(self, action: #selector(vc.onMenuButtonPressed(_:)), for: UIControlEvents.touchUpInside)
let menuBarItem = UIBarButtonItem(customView: menuBtn)
let currWidth = menuBarItem.customView?.widthAnchor.constraint(equalToConstant: 24)
currWidth?.isActive = true
let currHeight = menuBarItem.customView?.heightAnchor.constraint(equalToConstant: 24)
currHeight?.isActive = true
self.navigationItem.leftBarButtonItem = menuBarItem
}
Extension for Swift 4.2
Just a different way of implementation the answer provided by anoop4real
However using button type .system allows the global tint to be applied to your icon
Usage:
navigationItem.leftBarButtonItem = UIBarButtonItem.menuButton(self, action: #selector(presentSettings), imageName: "settings")
Implementation:
extension UIBarButtonItem {
static func menuButton(_ target: Any?, action: Selector, imageName: String) -> UIBarButtonItem {
let button = UIButton(type: .system)
button.setImage(UIImage(named: imageName), for: .normal)
button.addTarget(target, action: action, for: .touchUpInside)
let menuBarItem = UIBarButtonItem(customView: button)
menuBarItem.customView?.translatesAutoresizingMaskIntoConstraints = false
menuBarItem.customView?.heightAnchor.constraint(equalToConstant: 24).isActive = true
menuBarItem.customView?.widthAnchor.constraint(equalToConstant: 24).isActive = true
return menuBarItem
}
}
You can configure the frame of you button like below:
let icon = UIImage(named: "imageName")
let iconSize = CGRect(origin: CGPoint.zero, size: CGSize(width: 50, height: 50))
let iconButton = UIButton(frame: iconSize)
iconButton.setBackgroundImage(icon, for: .normal)
let barButton = UIBarButtonItem(customView: iconButton)
iconButton.addTarget(self, action: #selector(foo), for: .touchUpInside)
navigationItem.leftBarButtonItem = barButton
#DogCoffee answer is a great and creative way to solve the problem. May I suggest some slightly mods in order to take into account size and tintColor
extension UIBarButtonItem {
static func menuButton(_ target: Any?,
action: Selector,
imageName: String,
size:CGSize = CGSize(width: 32, height: 32),
tintColor:UIColor?) -> UIBarButtonItem
{
let button = UIButton(type: .system)
button.tintColor = tintColor
button.setImage(UIImage(named: imageName), for: .normal)
button.addTarget(target, action: action, for: .touchUpInside)
let menuBarItem = UIBarButtonItem(customView: button)
menuBarItem.customView?.translatesAutoresizingMaskIntoConstraints = false
menuBarItem.customView?.heightAnchor.constraint(equalToConstant: size.height).isActive = true
menuBarItem.customView?.widthAnchor.constraint(equalToConstant: size.width).isActive = true
return menuBarItem
}
}
In the end I did it like this and it worked:
let moreButton = UIButton(frame: CGRect(x: 0, y: 0, width: 35, height: 35))
moreButton.setBackgroundImage(UIImage(named: "ic_more_vert_3"), for: .normal)
moreButton.addTarget(self, action: #selector(TableViewController.handleMore), for: .touchUpInside)
self.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: moreButton)
Answer from: Change width of a UIBarButtonItem in a UINavigationBar in swift
If you are using SF symbol image, which is available from iOS 13, you could set the SF symbol image configuration.
let config = UIImage.SymbolConfiguration(pointSize: 30, weight: .light, scale: .default)
let image = UIImage(systemName: "plus.circle", withConfiguration: config)
Swift 5 extension using button closure, expanding on #valvoline 's answer.
As you can appreciate instead of using target and selector Button allows you to pass a closure.
extension UIBarButtonItem {
static func imageButton(
image: UIImage,
size: CGFloat = 24,
color: UIColor = .systemBackground,
action: #escaping () -> Void
) -> UIBarButtonItem {
// create ui button
let button = UIButton(type: .system)
// assign image
button.setImage(image, for: .normal)
button.tintColor = color
// assign action
button.addAction(.init(handler: { _ in action() }), for: .touchUpInside)
// create menu bar item using custom view
let menuBarItem = UIBarButtonItem(customView: button)
//use auto layout to assign bar button's size
menuBarItem.customView?.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate(
[
menuBarItem.customView?.heightAnchor.constraint(equalToConstant: size),
menuBarItem.customView?.widthAnchor.constraint(equalToConstant: size)
].compactMap { $0 }
)
return menuBarItem
}
}
Usage Example:
let item = UIBarButtonItem.imageButton(image: image, color: .systemBlue) {
print("You pushed it!") }
You can configure the bar buttons using this function:
public convenience init(customView: UIView)
And You can init the custom view as You desire.
After that You can access the view if needed via UIBarButtonItem's:
open var customView: UIView?
Hint: Because UIButton is a child class of UIView, You can directly use it too.

image for nav bar button item swift

I want to display an image in the left hand side of my nav bar in swift.
I have tried adding a nav bar button item and setting an image there.
The problem is that I have to use a really small image for it to fit in the nav bar nicely. But making such a small image leads to pixelation especially on the bigger phone iPhone 6 and 6 Plus.
Is there a way to use a good quality image and then set the frame to fit within the bounds of the nav bar?
My attempt:
var image = UIImage(named: "Harp.png")
image = image?.imageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal)
self.navigationItem.leftBarButtonItem = UIBarButtonItem(image: image, style: UIBarButtonItemStyle.Plain, target: nil, action: nil)
self.navigationItem.leftBarButtonItem.frame = CGRectMake(0, 0, 53, 31)
//image.frame = CGRectMake(0, 0, 53, 31)
I tried putting the frame on the image first and then on the bar button item.
But this is throwing up an error:
Type of expression is ambiguous without more context.
Try This
let button = UIButton(type: UIButtonType.Custom)
button.setImage(UIImage(named: "yourImageName.png"), forState: UIControlState.Normal)
button.addTarget(self, action:Selector("callMethod"), forControlEvents: UIControlEvents.TouchDragInside)
button.frame=CGRectMake(0, 0, 30, 30)
let barButton = UIBarButtonItem(customView: button)
self.navigationItem.leftBarButtonItems = [newBackButton,barButton]
For Swift 3
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
Swift 4
let button = UIButton(type: UIButton.ButtonType.custom)
button.setImage(UIImage(named: "getstarted"), for: .normal)
button.addTarget(self, action:#selector(callMethod), for: .touchDragInside)
button.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
let barButton = UIBarButtonItem(customView: button)
self.navigationItem.leftBarButtonItems = [barButton]
Here is action
#objc func callMethod() {
//do stuff here
}
Use this code:
self.navigationItem.leftBarButtonItem = nil
let button = UIButton(type: .custom)
button.setImage(UIImage (named: "ChatTab"), for: .normal)
button.frame = CGRect(x: 0.0, y: 0.0, width: 35.0, height: 35.0)
//button.addTarget(target, action: nil, for: .touchUpInside)
let barButtonItem = UIBarButtonItem(customView: button)
let button2 = UIButton(type: .custom)
button2.setImage(UIImage (named: "ActivityTab"), for: .normal)
button2.frame = CGRect(x: 0.0, y: 0.0, width: 35.0, height: 35.0)
//button.addTarget(target, action: nil, for: .touchUpInside)
let barButtonItem2 = UIBarButtonItem(customView: button2)
self.navigationItem.rightBarButtonItems = [barButtonItem, barButtonItem2]
Output:
Swift 5, XCode 11 to make Navigation Bar Item with rounded image, image from assets or to download from URL.
1) New file: UIBarButtonItem+RoundedView.swift
import Foundation
class ImageBarButton : UIView {
var imageView: UIImageView!
var button: UIButton!
convenience init(withUrl imageURL: URL? = nil, withImage image: UIImage? = nil, frame: CGRect = CGRect(x: 0, y: 0, width: 40, height: 40)) {
self.init(frame: frame)
imageView = UIImageView(frame: frame)
imageView.backgroundColor = .white
imageView.layer.cornerRadius = frame.height/2
imageView.clipsToBounds = true
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
addSubview(imageView)
button = UIButton(frame: frame)
button.backgroundColor = .clear
button.setTitle("", for: .normal)
addSubview(button)
if let url = imageURL { // you can use pods like Nuke or Kingfisher
URLSession(configuration: .default).dataTask(with: URL(string: imageUrl)!) {[weak self] (data, response, error) in
if let data = data , let image = UIImage(data: data) {
DispatchQueue.main.async {
self?.imgView.image = image
}
}
}.resume()
} else if let image = image {
self.imageView.image = image
}
}
func load()-> UIBarButtonItem {
return UIBarButtonItem(customView: self)
}
}
2) Add navigation bar items, you can use navigationItem.rightBarButtonItems, navigationItem.leftBarButtonItems:
private func initalizeNavigationBarItems() {
let searchBarButtonView = ImageBarButton(withImage: #imageLiteral(resourceName: "greenSearchIcon")) // Assets
searchBarButtonView.button.addTarget(self, action: #selector(presentSearchViewController), for: .touchUpInside)
if let user = AccountManager.currentUser, let userProfilePictureURL = user.imageUrl { // API Url
let profileBarButtonView = ImageBarButton(withUrl: userProfilePictureURL)
profileBarButtonView.button.addTarget(self, action: #selector(presentMoreViewController), for: .touchUpInside)
navigationItem.rightBarButtonItems = [searchBarButtonView.load(), profileBarButtonView.load()]
} else {
let profileBarButtonView = ImageBarButton(withImage: #imageLiteral(resourceName: "profileIcon"))
profileBarButtonView.button.addTarget(self, action: #selector(presentMoreViewController), for: .touchUpInside)
navigationItem.rightBarButtonItems = [searchBarButtonView.load(), profileBarButtonView.load()]
}
}
#objc func presentMoreViewController(_ sender: Any) {
// present MoreViewController
}
#objc func presentSearchViewController(_ sender: Any) {
// present SearchViewController
}
Preview
Swift 4 and 5:
let imageView = UIImageView(image: UIImage(named: "Harp"))
let buttonItem = UIBarButtonItem(customView: imageView)
self.navigationItem.leftBarButtonItem = buttonItem
There is a way to use images of different sizes, depending on the device. It's called an Asset Catalog. You'll probably already have one in your project, or if not, you can add one with File > New > File > Resource > Asset Catalogue.
Within your Asset Catalog, you can have multiple 'Image Sets' (these will be shown down the left-hand side). Add a new Image Set with the '+' at the bottom. For each Image Set, you can supply different images (e.g. of different sizes) for each of #1x, #2x, and #3x.
Then, to use one of these images in code, you simply use UIImage(named: "name_of_image_set") - note no extension. The correct image will be loaded, depending on the device.
Hope this helps!
Swift 5
You need to use constraints to properly set up nav bar button with image.
let button = UIButton(type: .custom)
button.setImage(UIImage(named: yourImageName), for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
button.widthAnchor.constraint(equalToConstant: 40).isActive = true
button.heightAnchor.constraint(equalToConstant: 40).isActive = true
let barButton = UIBarButtonItem(customView: button)
navigationItem.rightBarButtonItem = barButton

Resources