15

My class contains an UIImage property which I want to enforce as a 'copy' property by any external clients accessing it. But, when I try to do a copy in my custom setter, I get the runtime error about UIImage not supporting copyWithZone. So what's a good way to ensure that the correct ownership policy is followed?

// declared in the interface as:
@property (nonatomic, readonly, copy) UIImage *personImage;

// class implementation
- (void)setPersonImage:(UIImage *)newImage
{
    if (newImage != personImage) 
    {
        [personImage release];

        // UIImage doesn't support copyWithZone
        personImage = [newImage copy];
    }
}
Justin Galzic
  • 3,951
  • 6
  • 28
  • 39

6 Answers6

16

Here is a way to do it:

UIImage *imageToCopy = <# Your image here #>;
UIGraphicsBeginImageContext(imageToCopy.size);
[imageToCopy drawInRect:CGRectMake(0, 0, imageToCopy.size.width, imageToCopy.size.height)];
UIImage *copiedImage = [UIGraphicsGetImageFromCurrentImageContext() retain];
UIGraphicsEndImageContext();   

The reason I needed this is that I had an image variable initialized with imageWithContentsOfFile:, and I wanted to be able to delete the file from disk but keep the image variable. I was surprised to find that when the file is deleted the image variable goes crazy. It was displaying wacky random data even though it was initialized before the file was deleted. When I deep copy first it works fine.

Stan James
  • 2,535
  • 1
  • 28
  • 35
user1139733
  • 986
  • 9
  • 14
2

A deep copy

When talking about deep copies we must first understand that UIImage is a container. It doesn't actually contain the image data. The underlying data can be a CIImage or a CGImage. In most cases the backing data is a CGImage which is in turn a wrapping struct and copying the CGImage is just copying the metadata and not the underlying data. If you want to copy the underlying data you can draw the image into a context or grab a copy of the data as a PNG.

UIImagePNGRepresentation

Creating a playground with the following demonstrates the method.

let zebra = UIImage(named: "an_image_of_a_zebra")
print(zebra?.CGImage) // check the address
let shallowZebra = UIImage(CGImage: zebra!.CGImage!) 
print(shallowZebra.CGImage!) // same address

let zebraData = UIImagePNGRepresentation(zebra!)
let newZebra = UIImage(data: zebraData!)
print(newZebra?.CGImage) // new address
Cameron Lowell Palmer
  • 21,528
  • 7
  • 125
  • 126
1

Why do you need copy semantics? The UIImage is immutable, so there's no benefit to setting a copy policy. You just need a copy policy if there's the risk that someone else could modify the image on you. Since the UIImage is immutable, there's no risk of that happening, so a retain property is fine.

If you explain a bit more what you're trying to do, it might be that there's some other way of achieving it.

Jacques
  • 6,050
  • 1
  • 31
  • 24
  • 3
    I'm not sure if the OP had a good reason, but one reason to copy a UIImage is if it comes from a compressed file format. It won't decompress until it is needed and if you apply it to a UIImageView, the decompression takes place on the UI thread, which can be janky. Copying forces the decompression into memory. – Rupert Rawnsley Sep 21 '13 at 11:59
0

Because the image itself is a pointer, you need to create an image context, draw the image into the context, and then get the original image from the context. Maybe you need to use the UIImage.CGImage.

Alpha Law
  • 13
  • 3
0
 CGImageRef newCgIm = CGImageCreateCopy(oldImage.CGImage);
 UIImage *newImage = [UIImage imageWithCGImage:newCgIm scale:oldImage.scale 
                     orientation:oldImage.imageOrientation];
Hemang
  • 26,840
  • 19
  • 119
  • 186
0

Code for Swift 5.1:

    let imageToCopy = <# your_image #>
    UIGraphicsBeginImageContext(imageToCopy.size)
    imageToCopy.draw(in: CGRect(x: 0, y: 0, width: imageToCopy.size.width, height: imageToCopy.size.height))
    let copiedImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
Wanderer
  • 1
  • 1