How to use 9-patch images in IOS?
Asked Answered
R

4

31

I want to use 9-patch images in my IOS app. Is it possible?

Reciprocity answered 12/7, 2012 at 7:36 Comment(1)
Google ban :) ? 1) SO #5571318 2) Tortuga maniacdev.com/2011/07/…Contestant
R
20

Look into UIImage's method resizableImageWithCapInsets:(UIEdgeInsets)capInsets.

Retrace answered 12/7, 2012 at 8:21 Comment(2)
you are right that resizableImageWithCapInsets: is great. but its not 9 patch. it will allow you to apply some rules regarding how the image will be resized. but it wont replicate 9 patch.Antebi
Yes the above answer is correct, however 9 patch is better option than itUnthoughtof
W
4

You can try this simple implementation: https://github.com/shiami/SWNinePatchImageFactory

The concept is easy and described as below:

The technique is only to transform the 9-patch PNG images to iOS compatible, resizable UIImage objects. See the UIImage's method resizableImageWithCapInsets:(UIEdgeInsets)insets for more info. So it only support to stretch one segment of patch markers in both horizontal and vertical sides.

+ (UIImage*)createResizableImageFromNinePatchImage:(UIImage*)ninePatchImage
{
    NSArray* rgbaImage = [self getRGBAsFromImage:ninePatchImage atX:0 andY:0 count:ninePatchImage.size.width * ninePatchImage.size.height];
    NSArray* topBarRgba = [rgbaImage subarrayWithRange:NSMakeRange(1, ninePatchImage.size.width - 2)];
    NSMutableArray* leftBarRgba = [NSMutableArray arrayWithCapacity:0];
    int count = [rgbaImage count];
    for (int i = 0; i < count; i += ninePatchImage.size.width) {
        [leftBarRgba addObject:rgbaImage[i]];
    }

    int top = -1, left = -1, bottom = -1, right = -1;
    count = [topBarRgba count];
    for (int i = 0; i <= count - 1; i++) {
        NSArray* aColor = topBarRgba[i];
        if ([aColor[3] floatValue] == 1) {
            left = i;
            break;
        }
    }
    NSAssert(left != -1, @"The 9-patch PNG format is not correct.");
    for (int i = count - 1; i >= 0; i--) {
        NSArray* aColor = topBarRgba[i];
        if ([aColor[3] floatValue] == 1) {
            right = i;
            break;
        }
    }
    NSAssert(right != -1, @"The 9-patch PNG format is not correct.");
    for (int i = left + 1; i <= right - 1; i++) {
        NSArray* aColor = topBarRgba[i];
        if ([aColor[3] floatValue] < 1) {
            NSAssert(NO, @"The 9-patch PNG format is not support.");
        }
    }
    count = [leftBarRgba count];
    for (int i = 0; i <= count - 1; i++) {
        NSArray* aColor = leftBarRgba[i];
        if ([aColor[3] floatValue] == 1) {
            top = i;
            break;
        }
    }
    NSAssert(top != -1, @"The 9-patch PNG format is not correct.");
    for (int i = count - 1; i >= 0; i--) {
        NSArray* aColor = leftBarRgba[i];
        if ([aColor[3] floatValue] == 1) {
            bottom = i;
            break;
        }
    }
    NSAssert(bottom != -1, @"The 9-patch PNG format is not correct.");
    for (int i = top + 1; i <= bottom - 1; i++) {
        NSArray* aColor = leftBarRgba[i];
        if ([aColor[3] floatValue] == 0) {
            NSAssert(NO, @"The 9-patch PNG format is not support.");
        }
    }

    UIImage* cropImage = [ninePatchImage crop:CGRectMake(1, 1, ninePatchImage.size.width - 2, ninePatchImage.size.height - 2)];

    return [cropImage resizableImageWithCapInsets:UIEdgeInsetsMake(top, left, bottom, right)];
}

SWNinePatchImageView also provides the Nib or Storyboard usage. You can check the example project in the repo.

Weisbart answered 10/7, 2014 at 15:59 Comment(0)
S
1

the most probable result is https://github.com/andylanddev/Tortuga22-NinePatch 9-patch library have already used bellow method resizableImageWithCapInsets:(UIEdgeInsets)insets

Schellens answered 19/3, 2014 at 2:18 Comment(0)
P
0

you can use image crop and image stretch.

image crop

       func crop(img: UIImage, with rect: CGRect) -> UIImage? {
            guard let cgImg = img.cgImage else { return nil }
            // Create bitmap image from context using the rect
            if let imageRef: CGImage = cgImg.cropping(to: rect){
                // Create a new image based on the imageRef and rotate back to the original orientation
                let image: UIImage = UIImage(cgImage: imageRef, scale: img.scale, orientation: img.imageOrientation)
                return image
            }
            else{
                return nil
            }
        }

image stretch

func stretch(image: UIImage, newWidth: CGFloat) -> UIImage? {
        guard image.size.width < newWidth else {
            return image
        }

        let originalWidth = image.size.width
        let offset: CGFloat = 1
        // crop left first
        let left = crop(img: image, with: CGRect(x: 0, y: 0, width: originalWidth/2, height: image.size.height))
        // crop right first
        let right = crop(img: image, with: CGRect(x: originalWidth/2, y: 0, width: originalWidth/2, height: image.size.height))

        UIGraphicsBeginImageContextWithOptions(CGSize(width: newWidth, height: image.size.height),
                                               false, image.scale)

        let lhsResizable = left?.resizableImage(withCapInsets: UIEdgeInsets(top: 0,
                                                                                  left: originalWidth/2 - offset,
                                                                                  bottom: 0,
                                                                                  right: 0),
                                                  resizingMode: .stretch)
        // draw left stretched first
        lhsResizable?.draw(in: CGRect(x: 0, y: 0, width: newWidth/2, height: image.size.height))
        
        let rhsResizable = right?.resizableImage(withCapInsets: UIEdgeInsets(top: 0,
                                                                                  left: 0,
                                                                                  bottom: 0,
                                                                                  right: originalWidth/2 - offset),
                                                  resizingMode: .stretch)
        // then , draw right stretched
        rhsResizable?.draw(in: CGRect(x: newWidth/2, y: 0, width: newWidth/2, height: image.size.height))

        let fullImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        return fullImage
    }

Here is an example:

  • raw image:

gift.9

0000

  • image after processing

22233

  • the example code:

          view.backgroundColor = UIColor.white
          let imgViewWidth: CGFloat = 300
          let imgView = UIImageView(frame: CGRect(origin: CGPoint(x: 50, y: 250), size: CGSize(width: imgViewWidth, height: 100)))
          view.addSubview(imgView)
          if let image = UIImage(named: "gift.9"), let img = stretch(image: image, newWidth: imgViewWidth){
              imgView.image = img
          }
    
Pry answered 18/1, 2022 at 19:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.