Using NSCopy to Copy a Custom Object Containing Pointers? - iphone

I am learning how to use NSCopy. I want to make a copy of a custom object I am using, which is an ImageView in a UIScrollView.
I am trying to implement NSCopying protocol as follows :
-(id)copyWithZone:(NSZone *)zone
{
ImageView *another = [[ImageView allocWithZone:zone]init];
another.imageView = self.imageView;
another.imageToShow = self.imageToShow;
another.currentOrientation = self.currentOrientation;
another.portraitEnlarge = self.portraitEnlarge;
another.landscapeEnlarge = self.landscapeEnlarge;
another.originalFrame = self.originalFrame;
another.isZoomed = self.isZoomed;
another.shouldEnlarge = self.shouldEnlarge;
another.shouldReduce = self.shouldReduce;
another.frame = self.frame;
//another.delegate = another;
another.isZoomed = NO;
[another addSubview:another.imageView];
return another;
}
Then to copy the object in another class :
ImageView * onePre = [pictureArray objectAtIndex:0];
ImageView * one = [onePre copy];
The copy is made however I have one odd problem. The copied object's ImageView (a UIImageView) and ImageToShow (a UIImage) properties seem to be the same as the original objects. This kind of makes sense as in the copy code I am re-pointing a pointer, rather than making a new version of ImageView and ImageToShow.
My question is how do I make a copy of an object that contains pointers to other objects ?
Thanks !

UIView does not conform to NSCopying, but it does conform to NSCoding:
another.imageView = [NSKeyedUnarchiver unarchiveObjectWithData:
[NSKeyedArchiver archivedDataWithRootObject:self.imageView]];
This serializes and then deserializes the object, which is the standard way to perform a deep copy in ObjC.
EDIT: See https://stackoverflow.com/a/13664732/97337 for an example of a common -clone category method that uses this.

Related

How to create a copy of uiview (not a pointer to original uiview)

I want to create a copy of a UIView, and I dont want to use NSKeyedArchiver because I am frequently creating a copy of many views, and using NSKeyedArchiver was slow.
I heard about copy or initWithZone:, but Googling it found it is not good for UIView. I don't want a copy which points to the original view because any changes made to it will also make changes to the copied UIView.
You can copy a view using:
TheView *copyOfView = (TheView *) [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:origionalTheView]];
Create a UIView Category which can help in copying the view. Something like this:
- (id) copyView {
UIView *a = [[UIView alloc]init];
a.frame = self.frame;
//set all other properties of the UIView
return a; //return [a autorelease] if not using ARC
}

CALayer Array with Multiple of Same Object

Am I trying to create a game where an obstacle is constantly fired at the user. I have an NSMutableArray so I can access all the obstacles as a group. Here is my code:
CALayer *obstacle = [[[CALayer alloc] init] autorelease];
UIImage *obstacleImage = [UIImage imageNamed:#"Obstacle.png"];
obstacle.contents = (id)obstacleImage.CGImage;
obstacle.bounds = CGRectMake(0, 0, starImage.size.width/2, starImage.size.width/2);
int xPosition = (arc4random()%(360-0))+0;
obstacle.position = CGPointMake(xPosition, 20);
[self.view.layer addSublayer:obstacle];
[self.obstacleArray addObject:obstacle];
My questions is: How would I access the objects in this array? I want to be able to access the latest object so I can animate it. I have looked through the NSMutableArray Class Reference , but still can't find anything. I have tried this:
NSLog(#"%d",[obstacleArray indexOfObject:obstacle]);
But all it returns is: 0. Is there an easy solution to this problem that I'm just not seeing? Thanks in advance for any responses.
Use [obstacleArray lastObject] or [obstacleArray objectAtIndex:[obstacleArray count]-1] to get the last Object. You can find that in the NSArray Class Reference. (Since it's the parent class of NSMutableArray)

Dealing with objects from different classes

I have 3 classes of objects. All 3 classes share some properties in common, as color, text, etc.
For example, I can have this
Class1 *objectA = [[Class1 alloc] init];
objectA.myColor = [UIColor redColor];
Class2 *objectB = [[Class2 alloc] init];
objectA.myColor = [UIColor redColor];
Class3 *objectC = [[Class3 alloc] init];
objectA.myColor = [UIColor redColor];
... etc.
Now I need, for example, to create a method that can change the color of a given object, whatever class it represents.
A typical method would be
- (void) changeColor:(Class1*) myOBJ toColor:(UIColor*)myColor {
myOBJ.color = myColor;
}
when in fact I need this
- (void) changeColor:(???) myOBJ toColor:(UIColor*)myColor {
myOBJ.color = myColor;
}
// what to put on ??? to make it generic? Is this a "whatever" kind?
thanks
EDIT
the problem of using this approach
- (void) changeColor:(id)myOBJ toColor:(UIColor*)myColor {
if ([myOBJ respondsToSelector:#selector(setColor:)]) {
myOBJ.color = myColor;
}
}
is this. Imagine I want to set the frame of the object.
Then I will have to have this:
- (void) changeColor:(id)myOBJ newFrame:(CGRect)myFrame {
if ([umID isKindOfClass:[Class1 class]]) {
Class1 *oneObj = (Class1 *)myObj;
oneObj.frame = myFrame;
}
if ([umID isKindOfClass:[Class2 class]])
Class2 *oneObj = (Class2 *)myObj;
oneObj.frame = myFrame;
}
if ([umID isKindOfClass:[Class3 class]])
Class3 *oneObj = (Class3 *)myObj;
oneObj.frame = myFrame;
}
}
in other words, I will have to repeat the same stuff 3 times... right?
in other words, the problem is not solved as this is the same of having 3 methods, one for each class.
Maybe you can use protocols? Make Class1, Class2 and Class3 conform to a protocol with a property myColor. Then you could have a method like this (assuming your classes are of type UIView and your protocol is called ColorProtocol):
- (void) changeColor:(UIView<ColorProtocol>*) myOBJ toColor:(UIColor*)myColor {
myOBJ.color = myColor;
myOBJ.frame = ...;
}
Here is what your protocol definition could look like:
#protocol ColorProtocol
#property (nonatomic, retain) UIColor *myColor;
#end
Change your class definition files (.h) as follows to specify that you will conform to the protocol:
interface Class1 : UIView <ColorProtocol> {...}
In the implementation files (.m) you must simply synthesize the myColor property to conform to the ColorProtocol:
#synthesize myColor;
If your classes are very similar, using inheritance might be even simpler though. Check out Philip Regan's answer.
You have a couple options. The simplest, and "most dangerous" approach is to use a type id. This will let you pass in any object, but you'll want to test that it actually has a color property before you try and set it.
- (void) changeColor:(id)myOBJ toColor:(UIColor*)myColor {
if ([myOBJ respondsToSelector:#selector(setColor:)]) {
myOBJ.color = myColor;
}
}
(That said, with the responds to selector check, this approach isn't all that dangerous, and it's much more flexible than the next idea.)
Another approach is to have all your objects inherit from a shared base class that has a color property. Then your parameter type would be the base class. This approach could be considered "safer" as the compiler would check that you're passing in the correct type of object. This approach also requires considerably more code.
If you want to use the first approach, but set something other than color, adjust the respondsToSelector: call appropriately.
- (void) changeFrame:(id)myOBJ newFrame:(CGRect)myFrame {
if ([myOBJ respondsToSelector:#selector(setFrame:)]) {
myOBJ.frame = myFrame;
}
}
In general, if you want to know if an object supports propertyX, use [myOBJ respondsToSelector:#selector(setPropertyX:)]. If the passed in object is declared as id, you can then call [myOBJ setPropertyX:newPropertyValue] or myObj.propertyX = newPropertyValue.
If you have multiple classes that share characteristics, then, if at all possible, I suggest refactoring the class structure so that those characteristics are contained in an umbrella parent class, we'll call it ClassZ. ClassZ's subclasses can override things as needed. Otherwise, let the method in the parent class handle it for you. Then, your method turns back into this...
- (void) changeColor:(ClassZ *) myOBJ toColor:(UIColor*)myColor {
myOBJ.color = myColor; // note, myObj is ClassZ, not the subclasses.
}
Otherwise, you are stuck with id and testing the individual classes.
use [object setFrame:newFrame]; instead of object.frame = newFrame;
and instead of oldFrame = object.frame; use oldFrame = [object frame];
??? will be 'id'.

Using a variable in a statement to reference the image stored in a UIImageView

UPDATED Scroll down to see the question re-asked more clearly....
If I had the name of a particular UIImageView (IBOutlet) stored in a variable, how can I use it to change the image that is displayed. I tried this, but it does not work.
I'm still new to iphone programming, so any help would be appreciated.
NSString *TmpImage = #"0.png";
NSString *Tst = #"si1_1_2";
TmpImage = #"1.png";
UIImage *sampleimage = [[UIImage imageNamed:TmpImage] retain];
((UIImageView *) (Tst)).image = sampleimage; // This is the line in question
[sampleimage release];
RESTATED:
I have a bunch of images on the screen.... UIImageView *s1, *s2 ,*s3 etc up to *s10
Now suppose I want to update the image each displays to the same image.
Rather than doing
s1.image = sampleimage;
s2.image = sampleimage;
:
s10.image = sampleimage;
How could i write a for loop to go from 1 to 10 and then use
the loop var as part of the line that updates the image.
Something like this.
for ( i = 1; i <- 10; ++i )
s(i).image = sample; // I know that does not work
Basic question is how do I incorporate the variable as part of the statement to access the image? Don't get hung up on my example. The main question is how to use a variable as part of the access to some element/object.
Bottom Line... If I can build the name of a UIImageView into a NSString object, How can I then use that NSString object to manipulate the UIImageView.
Thanks!
Ugh! Your line in question:
((UIImageView *) (Tst)).image = sampleimage;
is casting a string pointer as a UIImageView pointer - you're basically saying that your pointer to a string is actually a pointer to a UIImageView! It will compile (because the compiler will accept your assertion happily) but will of course crash on running.
You need to declare a variable of type UIImageView. This can then hold whichever view you want to set the image of. So your code could look like the following:
NSString *TmpImage = #"0.png";
UIImageView *myImageView;
If (someCondition == YES) {
myImageView = si1_1_2; //Assuming this is the name of your UIImageView
} else {
myImageView = si1_1_3; //etc
}
UIImage *sampleimage = [UIImage imageNamed:TmpImage]; //no need to retain it
myImageView.image = sampleImage;
Hopefully this makes sense!
Edit: I should add, why are you trying to have multiple UIImageViews? Because a UIImageView's image can be changed at any time (and in fact can hold many), would it not be better to have merely one UIImageView and just change the image in it?

How do you copy a CALayer?

How can you make an NSArray full of multiple instances of a CALayer (all with the same frame, contents etc)?
Background: CALayer takes a bit of overhead to create, so I would like to create a number of CALayers (all sharing the same properties) in the init method of a class (to be used later on in that class.)
I haven't tried this with CALayer specifically, but I know you can perform a deep-copy by taking advantage of NSCoding:
CALayer *layer = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:layer]];
I'm not sure how copying them would really help with performance, though.
CALayer doesn't have a built in -(id)copy method. I'm not sure why. It's not difficult to gin up your own however. Create a CALayer category and write your own copy method. All you have to do is instantiate and manually get the public ivars/properties from the original and set to the new copy. Don't forget to call [super copy]
BTW, CALayer is an object. You can add it to an NSArray.
Try to use CAReplicatorLayer. It can duplicate your layers.
reference: https://developer.apple.com/documentation/quartzcore/careplicatorlayer
sample code: http://www.knowstack.com/swift-careplicatorlayer-sample-code/
https://developer.apple.com/documentation/quartzcore/careplicatorlayer/1522391-instancedelay
An updated link to MaxGabriel's top rated answer.
Objective-C
CALayer *layer1;
CALayer *layer2;
// Set up layer1's specifics.
layer2 = [NSKeyedUnarchiver unarchivedObjectOfClass:[CALayer class]
fromData:[NSKeyedArchiver archivedDataWithRootObject:layer1 requiringSecureCoding:NO error:nil] error:nil];
And in Swift.
let layer1: CALayer?
var layer2: CALayer? = nil
// Set up layer1's specifics
do {
layer2 = try NSKeyedUnarchiver.unarchivedObject(
ofClass: CALayer.self,
from: try NSKeyedArchiver.archivedData(withRootObject: layer1, requiringSecureCoding: false))
} catch {
// It failed. Do something.
}
I do exactly the same thing in my program.
In init:
self.turrets = [NSMutableArray array];
for (count = 0; count < kMaxTurrets; count++)
[self spawnTurret];
spawnTurret:
evTurret* aTurret = [[[evTurret alloc] init] autorelease];
CGImageRef theImage = [self turretContents];
aTurret.contents = theImage;
double imageHeight = CGImageGetHeight(theImage);
double imageWidth = CGImageGetWidth(theImage);
double turretSize = 0.06*(mapLayer.bounds.size.width + mapLayer.bounds.size.height)/2.0;
aTurret.bounds = CGRectMake(-turretSize*0.5, turretSize*0.5, turretSize*(imageWidth/imageHeight), turretSize);
aTurret.hidden = YES;
[mapLayer addSublayer:aTurret];
[self.turrets addObject:aTurret];
Basically, just I just repeatedly create CALayer objects. It's going to be faster than copying them, as this method only requires 1 CALayer call per property, as opposed to copying it which requires you to read the property and then additionally set it. I spawn about 500 objects using this method in about 0.02 seconds, so it's definitely fast. If you really needed more speed you could even cache the image file.
NSProxy is used for that reason. What you're describing is a common scenario, and one from which any number of design patterns are derived.
Pro Objective-C Design Patterns for iOS provides the solution to the very problem you describe; read Chapter 3: The Protoype Pattern. Here's a summary definition:
The Prototype pattern specifies the kind of objects to create using a prototypical instance, whereby a new object is created by copying this instan
Although it's best to use CAReplicatorLayer for your use case,
others may really need to copy CALayer, so I'm here writing another answer.
(I had to copy because of CARenderer)
I tried NSKeyedArchiver as well, it works mostly, but some functionality get broken for complex CALayer
(for me, it was the mask with filters)
implementation
Implementation is pretty straightforward.
Make new CALayer.
Copy properties that you need/use.
Do it recursively for sublayers and mask.
Here is an example for CAShapeLayer:
extension CALayer {
func deepCopy() -> CAShapeLayer {
let layer = CAShapeLayer()
layer.frame = frame
layer.bounds = bounds
layer.filters = filters
layer.contents = contents
layer.contentsScale = contentsScale
layer.masksToBounds = masksToBounds
//add or remove lines for your need
if let self = self as? CAShapeLayer {
layer.path = self.path?.copy()
layer.lineCap = self.lineCap
layer.lineJoin = self.lineJoin
layer.lineWidth = self.lineWidth
layer.fillColor = self.fillColor
layer.strokeColor = self.strokeColor
//add or remove lines for your need
}
layer.mask = mask?.deepCopy()
layer.sublayers = sublayers?.map { $0.deepCopy() }
return layer
}
}