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

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!
}

Related

UIBarButtonItem custom view aspect fill swift 4

I'm developing an app that displays, in the main ViewController, the user's profile image inside a round UIBarButtonItem, so I'm using a custom Button with cornerRadius and clipsToBounds enabled, I am resizing the UIImage's width to 75% of NavigationBar's height to fit well in it, I also used button.imageView?.contentMode = .scaleAspectFill
When I use a squared image(width = height) it works perfect, but if I use a portrait or landscape image it looks like if BarButton was using .scaleAspectFit
I already tried to create first a squared UIImage cropping original profile image without any luck.
This is my Bar button code:
func setProfileButton() {
let width = self.navigationController!.navigationBar.frame.size.height * 0.75
if let image = ResizeImage(CFUser.current!.getProfileImage(), to: width) {
let button = UIButton(type: .custom)
button.setImage(image, for: .normal)
button.imageView?.contentMode = .scaleAspectFill
button.addTarget(self, action: #selector(goProfile), for: .touchUpInside)
button.frame = CGRect(x: 0, y: 0, width: width, height: width)
button.layer.cornerRadius = button.bounds.width / 2
button.clipsToBounds = true
let barButton = UIBarButtonItem(customView: button)
self.navigationItem.rightBarButtonItem = barButton
}
}
This is ResizeImage code:
func ResizeImage(_ image: UIImage, to width: CGFloat) -> UIImage? {
let size = image.size
let ratio = width / image.size.width
let height = image.size.height * ratio
let targetSize = CGSize(width: width, height: height)
let widthRatio = targetSize.width / image.size.width
let heightRatio = targetSize.height / image.size.height
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)
}
let rect = CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height)
UIGraphicsBeginImageContextWithOptions(newSize, false, 1.0)
image.draw(in: rect)
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage
}
Here is the app working with squared image
And this is with a portrait image
Thanks for your help! :)
PD: I'm using Xcode 10 and Swift 4
As the portrait image has different dimensions unlike square image, calculate the minimum dimension and modify the image size accordingly.
Replace method ResizeImage(_ image: UIImage, to width: CGFloat) -> UIImage? with below.
func ResizeImage(_ image: UIImage, to width: CGFloat) -> UIImage? {
let ratio = width / image.size.width
let height = image.size.height * ratio
let dimension = min(width, height)
let targetSize = CGSize(width: dimension, height: dimension)
let rect = CGRect(x: 0, y: 0, width: targetSize.width, height: targetSize.height)
UIGraphicsBeginImageContextWithOptions(targetSize, false, 1.0)
image.draw(in: rect)
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage
}
Square image:
Portrait image:

Why is custom navigation bar button image stretched?

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

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;

iOS - Replaced back button too close to edge

I want to globally change the navigation bar back button, so I have this:
UIBarButtonItem.appearance().setBackButtonTitlePositionAdjustment(UIOffsetMake(-100, -5000), forBarMetrics: .Default)
var backImage: UIImage = UIImage(named: "BackButton")!
backImage = backImage.imageWithAlignmentRectInsets(UIEdgeInsetsMake(0, 0, 0, 20))
UINavigationBar.appearance().backIndicatorImage = backImage
UINavigationBar.appearance().backIndicatorTransitionMaskImage = backImage
But the back image is too close to the left of the screen. How do I move it farther to the right?
Just adjust the actual image.
extension UIImage {
func imageWithInsets(insets: UIEdgeInsets) -> UIImage {
UIGraphicsBeginImageContextWithOptions(
CGSizeMake(self.size.width + insets.left + insets.right,
self.size.height + insets.top + insets.bottom), false, self.scale)
let context = UIGraphicsGetCurrentContext()
let origin = CGPoint(x: insets.left, y: insets.top)
self.drawAtPoint(origin)
let imageWithInsets = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return imageWithInsets
}
}
For Swift 4:
extension UIImage {
func withInsets(_ insets: UIEdgeInsets) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(
CGSize(width: size.width + insets.left + insets.right,
height: size.height + insets.top + insets.bottom),
false,
self.scale)
let origin = CGPoint(x: insets.left, y: insets.top)
self.draw(at: origin)
let imageWithInsets = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return imageWithInsets
}
}
You need to adjust the left position on UIEdgeInsetsMake.
You are already calling it here:
backImage = backImage.imageWithAlignmentRectInsets(UIEdgeInsetsMake(0, 0, 0, 20))
Like so:
backImage = backImage.imageWithAlignmentRectInsets(UIEdgeInsetsMake(0, -50, 0, 50))

How to merge two UIImages?

I am trying to merge two different images and create a new one. This is the way I would like to do:
I have this image (A):
It's a PNG image and I would like to merge this one with another image (B) which I took from the phone to create something like this:
I need a function who merge A with B creating C. The size must remain from the A image and the image B should auto adapt the size to fit into the polaroid (A). Is it possible to do that? Thank for your help!
UPDATE
Just one thing, the image (A) is a square and the image i took is a 16:9, how can i fix that?? If i use your function the image (B) that i took become stretched!
Hope this may help you,
var bottomImage = UIImage(named: "bottom.png")
var topImage = UIImage(named: "top.png")
var size = CGSize(width: 300, height: 300)
UIGraphicsBeginImageContext(size)
let areaSize = CGRect(x: 0, y: 0, width: size.width, height: size.height)
bottomImage!.draw(in: areaSize)
topImage!.draw(in: areaSize, blendMode: .normal, alpha: 0.8)
var newImage:UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
All the Best :)
Swift 5: Extension for UIImage
extension UIImage {
func mergeWith(topImage: UIImage) -> UIImage {
let bottomImage = self
UIGraphicsBeginImageContext(size)
let areaSize = CGRect(x: 0, y: 0, width: bottomImage.size.width, height: bottomImage.size.height)
bottomImage.draw(in: areaSize)
topImage.draw(in: areaSize, blendMode: .normal, alpha: 1.0)
let mergedImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return mergedImage
}
}
Swift 4 UIImage extension that enables easy image merging / overlaying.
extension UIImage {
func overlayWith(image: UIImage, posX: CGFloat, posY: CGFloat) -> UIImage {
let newWidth = size.width < posX + image.size.width ? posX + image.size.width : size.width
let newHeight = size.height < posY + image.size.height ? posY + image.size.height : size.height
let newSize = CGSize(width: newWidth, height: newHeight)
UIGraphicsBeginImageContextWithOptions(newSize, false, 0.0)
draw(in: CGRect(origin: CGPoint.zero, size: size))
image.draw(in: CGRect(origin: CGPoint(x: posX, y: posY), size: image.size))
let newImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return newImage
}
}
This way the overlay picture will be much cleaner:
class func mergeImages(imageView: UIImageView) -> UIImage {
UIGraphicsBeginImageContextWithOptions(imageView.frame.size, false, 0.0)
imageView.superview!.layer.renderInContext(UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
Objective C version of this solution with top image re-centered logic :
-(UIImage *)getImageInclosedWithinAnotherImage
{
float innerImageSize = 20;
UIImage *finalImage;
UIImage *outerImage = [UIImage imageNamed:#"OuterImage.png"];
UIImage *innerImage = [UIImage imageNamed:#"InnerImage.png"];
CGSize outerImageSize = CGSizeMake(40, 40); // Provide custom size or size of your actual image
UIGraphicsBeginImageContext(outerImageSize);
//calculate areaSize for re-centered inner image
CGRect areSize = CGRectMake(((outerImageSize.width/2) - (innerImageSize/2)), ((outerImageSize.width/2) - (innerImageSize/2)), innerImageSize, innerImageSize);
[outerImage drawInRect:CGRectMake(0, 0, outerImageSize.width, outerImageSize.height)];
[innerImage drawInRect:areSize blendMode:kCGBlendModeNormal alpha:1.0];
finalImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return finalImage;
}
The upvoted answer stretches the background image changing its ratio. The solution below fixes that by rendering the image from a UIView that contains the two image views as subviews.
ANSWER YOU ARE LOOKING FOR (Swift 4):
func blendImages(_ img: UIImage,_ imgTwo: UIImage) -> Data? {
let bottomImage = img
let topImage = imgTwo
let imgView = UIImageView(frame: CGRect(x: 0, y: 0, width: 306, height: 306))
let imgView2 = UIImageView(frame: CGRect(x: 0, y: 0, width: 306, height: 306))
// - Set Content mode to what you desire
imgView.contentMode = .scaleAspectFill
imgView2.contentMode = .scaleAspectFit
// - Set Images
imgView.image = bottomImage
imgView2.image = topImage
// - Create UIView
let contentView = UIView(frame: CGRect(x: 0, y: 0, width: 306, height: 306))
contentView.addSubview(imgView)
contentView.addSubview(imgView2)
// - Set Size
let size = CGSize(width: 306, height: 306)
// - Where the magic happens
UIGraphicsBeginImageContextWithOptions(size, true, 0)
contentView.drawHierarchy(in: contentView.bounds, afterScreenUpdates: true)
guard let i = UIGraphicsGetImageFromCurrentImageContext(),
let data = UIImageJPEGRepresentation(i, 1.0)
else {return nil}
UIGraphicsEndImageContext()
return data
}
The returned image data doubles the size of the image, so set the size of the views at half the desired size.
EXAMPLE: I wanted the width and height of the image to be 612, so I set the view frames width and height to 306)
// Enjoy :)
Slightly modified version of answer by budidino. This implementation also handles negative posX and posY correctly.
extension UIImage {
func overlayWith(image: UIImage, posX: CGFloat, posY: CGFloat) -> UIImage {
let newWidth = posX < 0 ? abs(posX) + max(self.size.width, image.size.width) :
size.width < posX + image.size.width ? posX + image.size.width : size.width
let newHeight = posY < 0 ? abs(posY) + max(size.height, image.size.height) :
size.height < posY + image.size.height ? posY + image.size.height : size.height
let newSize = CGSize(width: newWidth, height: newHeight)
UIGraphicsBeginImageContextWithOptions(newSize, false, 0.0)
let originalPoint = CGPoint(x: posX < 0 ? abs(posX) : 0, y: posY < 0 ? abs(posY) : 0)
self.draw(in: CGRect(origin: originalPoint, size: self.size))
let overLayPoint = CGPoint(x: posX < 0 ? 0 : posX, y: posY < 0 ? 0 : posY)
image.draw(in: CGRect(origin: overLayPoint, size: image.size))
let newImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return newImage
}
}

Resources