dkcas11
dkcas11

Reputation: 103

UIImage in UIButton pixelated with Vector Image

i am trying to put in an image in a UIButton in XCode with Swift. The button is in a keyboard extension and i am trying to put in "Globe" icon like the one iOS already has in the built-in keyboard. I used to use the globe emoji and the unicode (which automatically get picked up as an emoji), but that didn't look good.

So now, my image is all blurry, no matter which file i use. So far, i've used images from 22pt, 200pt, 600pt, svg and pdf. With no luck.

This is what it looks like: Blurry image

This is how the Apple one looks like, all sharp: Sharp image

My code for the button is this:

//Set an image to fit inside a button
    let btn = nextKeyboardButton
    let image = UIImage(named: "globe")!
    var scaledImage = image
    var targetWidth : CGFloat = 0
    var targetHeight : CGFloat = 0
    let spacing : CGFloat = 10

    if(btn.frame.width < btn.frame.height){
        targetWidth = round(btn.frame.width - (spacing * 2))
        targetHeight = targetWidth
    } else {
        targetHeight = round(btn.frame.height - (spacing * 2))
        targetWidth = targetHeight
    }

    UIGraphicsBeginImageContext(CGSizeMake(targetWidth, targetHeight))
    image.drawInRect(CGRectMake(0, 0, targetWidth, targetHeight))
    scaledImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()

    btn.setImage(scaledImage, forState: .Normal)
    btn.setTitle("", forState: .Normal)
    btn.tintColor = UIColor.blackColor()

As the icon is 1:1, i use the same width and height. But i will need the same (working) approach for another icon/image too. Any other icons/images i have used in 25, 50 and 75pts for my tab bar looks good and sharp. It also doesn't matter which simulator i use, if its iPhone 4s or 6 Plus.

Also, might there be any other solutions to this? I have tried to use another font which is all globes, but XCode wouldn't list it in the Editor. I have also tried to paint over the globe emoji, use the unicode character, but that gets picked up as an emoji.... So.. Any help? :)

Upvotes: 4

Views: 2295

Answers (3)

shoe
shoe

Reputation: 1070

I had an issue where a vector image was becoming blurry when scaled as well. In my case I was using it as the image for a button. Then I was using the same image — but somewhat transparent — for the .highlighted state of the button. That image was created using UIGraphicsImageRenderer. The problem seems to be that UIGraphicsImageRenderer produces a non-vector image when using it to create a new image from an original. And therefore changing the button's size with the images given from UIGraphicsImageRenderer resulted in blurry images. The solution I went with was to use the vector image as is for the button, and perform any transparency changes I wanted using UIView.alpha instead of trying to create new images by adding transparency to non-transparent images.

Upvotes: 0

dkcas11
dkcas11

Reputation: 103

To anyone interested who might be looking for a solution to a similar problem, or to put in image for a button, i have made a complete function that does this, with the suggested fix/help from @Brian Nickel :)

The function just takes a button, image name, color, and spacing for the indents around the image.

func setImageForButton(_button : UIButton, _color : UIColor, _imageName : String, _spacing : CGFloat) {
    let btn = _button

    let image = UIImage(named: _imageName)!
    var scaledImage : UIImage

    var targetWidth : CGFloat
    var targetHeight : CGFloat
    let spacing : CGFloat = _spacing

    if(btn.frame.width < btn.frame.height){
        targetWidth = btn.frame.width - (spacing * 2)
        let scale = targetWidth / image.size.width
        targetHeight = round(image.size.height * scale)
        targetWidth = round(targetWidth)
    } else {
        targetHeight = btn.frame.height - (spacing * 2)
        let scale = targetHeight / image.size.height
        targetWidth = round(image.size.width * scale)
        targetHeight = round(targetHeight)
    }

    UIGraphicsBeginImageContextWithOptions(CGSizeMake(targetWidth, targetHeight), false, 0)
    image.drawInRect(CGRectMake(0, 0, targetWidth, targetHeight))
    scaledImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()

    btn.setImage(scaledImage, forState: .Normal)
    btn.setTitle("", forState: .Normal)
    btn.tintColor = _color
}

Upvotes: 0

Brian Nickel
Brian Nickel

Reputation: 27550

Your main issue is that UIGraphicsBeginImageContext does not perform device specific scaling. On @2x screen, each point is represented by a single pixel rather than a 2x2 pixel square; it looks pixilated because it is.

Switching to UIGraphicsBeginImageContextWithOptions(size, NO, 0) and providing a sufficiently large should cause it to render crisply.

Another issue to be aware of is that PDF assets get rendered to multiple resolutions of PNG files at build time rather than in the app, so if you rendering a 22x22 image to 20x20 (for example) will cause some level of blurring that you wouldn't expect had the vector image been rendered on device.

Upvotes: 3

Related Questions