Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Set stretching parameters for images programmatically in swift for iOS

Tags:

xcode

ios

swift

So if we want to stretch only parts of an image, be it a regular image or a background image, we use the following settings in layout editor:

enter image description here

How do you set those programmatically?

I'm using Xcode 7.2.1

like image 749
Milad.Nozari Avatar asked Feb 24 '16 16:02

Milad.Nozari


1 Answers

Specifying the cap insets of your image

You can set the stretch specifics by making use of the UIImage method .resizableImageWithCapInsets(_:UIEdgeInsets, resizingMode: UIImageResizingMode).

Declaration

func resizableImageWithCapInsets(capInsets: UIEdgeInsets, resizingMode: UIImageResizingMode) -> UIImage

Description

Creates and returns a new image object with the specified cap insets and options.

A new image object with the specified cap insets and resizing mode.

Parameters

capInsets: The values to use for the cap insets.

resizingMode: The mode with which the interior of the image is resized.


Example: custom stretching using the specified cap insets

As an example, let's try to---programmatically---stretch my (current) profile picture along its width, precisely at my right leg (left side from viewing point of view), and leave the rest of the image with its original proportions. This could be comparable to stretching the width of some button texture to the size of its content.

First, let's load our original image foo.png as an UIImage object:

let foo = UIImage(named: "foo.png") // 328 x 328

enter image description here

Now, using .resizableImageWithCapInsets(_:UIEdgeInsets, resizingMode: UIImageResizingMode), we'll define another UIImage instance, with specified cap insets (to the middle of my right leg), and set resizing mode to .Stretch:

/* middle of right leg at ~ |-> 0.48: LEG :0.52 <-| along 
   image width (for width normalized to 1.0)                */
let fooWidth = foo?.size.width ?? 0
let leftCapInset = 0.48*fooWidth
let rightCapInset = fooWidth-leftCapInset // = 0.52*fooWidth

let bar = UIEdgeInsets(top: 0, left: leftCapInset, bottom: 0, right: rightCapInset)
let fooWithInsets = foo?.resizableImageWithCapInsets(bar, resizingMode: .Stretch) ?? UIImage()

Note that 0.48 literal above corresponds to the value you enter for X in the interface builder, as shown in the image in your question above (or as described in detail in the link provided by matt).

Moving on, we finally place the image with cap insets in an UIImageView, and let the width of this image view be larger than the width of the image

/* put 'fooWithInsets' in an imageView.
   as per default, frame will cover 'foo.png' size */
let imageView = UIImageView(image: fooWithInsets)

/* expand frame width, 328 -> 600 */
imageView.frame = CGRect(x: 0, y: 0, width: 600, height: 328)

The resulting view stretches the original image as specified, yielding an unproportionally long leg.

enter image description here

Now, as long as the frame of the image has 1:1 width:height proportions (328:328), stretching will be uniform, as if only fitting any image to a smaller/larger frame. For any frame with width values larger than the height (a:1, ratio, a>1), the leg will begin to stretch unproportionally.


Extension to match the X, width, Y and height stretching properties in the Interface Builder

Finally, to thoroughly actually answer your question (which we've really only done implicitly above), we can make use of the detailed explanation of the X, width, Y and height Interface Builder stretching properties in the link provided by matt, to construct our own UIImage extension using (apparently) the same properties, translated to cap insets in the extension:

extension UIImage {
    func resizableImageWithStretchingProperties(
    X X: CGFloat, width widthProportion: CGFloat,
    Y: CGFloat, height heightProportion: CGFloat) -> UIImage {

        let selfWidth = self.size.width
        let selfHeight = self.size.height

        // insets along width
        let leftCapInset = X*selfWidth*(1-widthProportion)
        let rightCapInset = (1-X)*selfWidth*(1-widthProportion)

        // insets along height
        let topCapInset = Y*selfHeight*(1-heightProportion)
        let bottomCapInset = (1-Y)*selfHeight*(1-heightProportion)

        return self.resizableImageWithCapInsets(
            UIEdgeInsets(top: topCapInset, left: leftCapInset,
                bottom: bottomCapInset, right: rightCapInset),
            resizingMode: .Stretch)
    }
}

Using this extension, we can achieve the same horizontal stretching of foo.png as above, as follows:

let foo = UIImage(named: "foo.png") // 328 x 328
let fooWithInsets = foo?.resizableImageWithStretchingProperties(
    X: 0.48, width: 0, Y: 0, height: 0) ?? UIImage()

let imageView = UIImageView(image: fooWithInsets)
imageView.frame = CGRect(x: 0, y: 0, width: 600, height: 328)

Extending our example: stretching width as well as height

Now, say we want to stretch my right leg as above (along width), but in addition also my hands and left leg along the height of the image. We control this by using the Y property in the extension above:

let foo = UIImage(named: "foo.png") // 328 x 328
let fooWithInsets = foo?.resizableImageWithStretchingProperties(
    X: 0.48, width: 0, Y: 0.45, height: 0) ?? UIImage()

let imageView = UIImageView(image: fooWithInsets)
imageView.frame = CGRect(x: 0, y: 0, width: 500, height: 500)

Yielding the following stretched image:

enter image description here


The extension obviously allows for a more versatile use of the cap inset stretching (comparable versatility as using the Interface Builder), but note that the extension, in its current form, does not include any user input validation, so it's up to the caller to use arguments in the correct ranges.

Finally, a relevant note for any operations covering images and their coordinates:

Note: Image coordinate axes x (width) and y (height) run as

x (width):  left to right (as expected)
y (height): top to bottom (don't miss this!)
like image 140
dfrib Avatar answered Nov 02 '22 13:11

dfrib