The object inherits from NSObject.
Is there a method to create a copy of it as a new object?
UIButton does not conform to NSCopying, so you cannot make a copy via -copy.
However, it does conform to NSCoding, so you can archive the current instance, then unarchive a 'copy'.
NSData *archivedData = [NSKeyedArchiver archivedDataWithRootObject: button];
UIButton *buttonCopy = [NSKeyedUnarchiver unarchiveObjectWithData: archivedData];
Afterwards, you'll have to assign any additional properties that weren't carried over in the archive (e.g. the delegate) as necessary.
UIButton doesn't conform to the NSCopying protocol, so you have copy it by hand. On the other hand, it is not a bad thing, since it is not exactly clear what does it mean to copy a button. For example, should it add the button copy to the same view the original is in? Should it fire the same methods when tapped?
To add to Jim's answer above using a category
#implementation UIButton (NSCopying)
- (id)copyWithZone:(NSZone *)zone {
NSData *archivedData = [NSKeyedArchiver archivedDataWithRootObject:self];
UIButton *buttonCopy = [NSKeyedUnarchiver unarchiveObjectWithData: archivedData];
return buttonCopy;
}
#end
if you wanted to copy all of the actions from one button to another, add something like this:
for (id target in button.allTargets) {
NSArray *actions = [button actionsForTarget:target forControlEvent:UIControlEventTouchUpInside];
for (NSString *action in actions) {
[newButton addTarget:target action:NSSelectorFromString(action) forControlEvents:UIControlEventTouchUpInside];
}
}
If it implements the NSCopying protocol, then the -copy method should do the trick.
You can get more information about the -copy method and how it works with sub-objects on the ADC reference site. As Stephen Darlington mentions, you need to implement the NSCopying protocol in your object.
documentation
Swift 3/4 version would be:
let archivedData = NSKeyedArchiver.archivedData(withRootObject: button as Any)
let buttonCopy = NSKeyedUnarchiver.unarchiveObject(with: archivedData) as? UIButton
Related
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.
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
}
i am storing my "custom button" in NSUserdefaults using the following code.But i am getting an error "[UIImage encodeWithCoder:]: unrecognized selector sent to instance" while converting the object to NSdata..here "custom button" is UIButton class. Anyone know why...? please help me.
Custom_button *lock11 = (Custom_button*)[menu1 viewWithTag:100];
NSLog(#"opened lock1 ========= %#",lock11);
lock11.is_menu_lock_opened = YES;
NSData *myEncodedObject = [NSKeyedArchiver archivedDataWithRootObject:lock11]; //[NSKeyedArchiver archivedDataWithRootObject:lock11];
[prefs setObject:myEncodedObject forKey:#"set1lock"];
The NSUserDefaults class provides convenience methods for accessing
common types such as floats, doubles, integers, Booleans, and URLs. A
default object must be a property list, that is, an instance of (or
for collections a combination of instances of): NSData, NSString,
NSNumber, NSDate, NSArray, or NSDictionary. If you want to store any
other type of object, you should typically archive it to create an
instance of NSData.
Objects that don't map directly to property list objects are turned into NSData by being sent a coder and encoding their contents. [UIImage encodeWithCoder:]. They need to conform to the NSCoding protocol for this to work. You will find that UIImage does not confirm to the NSCoding protocol before iOS 5. If you want to deploy before iOS 5 you will have to work something out yourself, by implementing NSCoding in your Custom Button class and storing that image in a different way.
Implement this method into your Custom_button Class
initWithCoder and encodeWithCoder for all object
-(id) initWithCoder: (NSCoder *)coder {
self = [[CastInnerListData alloc] init];
if (self != nil) {
self.object1 = [coder decodeObjectForKey:#"object1"];
self.object2 = [coder decodeObjectForKey:#"object2"];
}
return self;
}
-(void) encodeWithCoder: (NSCoder *)coder{
[coder encodeObject:object1 forKey:#"object1"];
[coder encodeObject:object2 forKey:#"object2"];
}
For more detail click here
Well a little searching would have given you the answer fast:
UIImage encodeWithCoder urecognised selector?
If core data has an object that has a specific member variable value I draw a button to a view; there could be any number of these buttons on screen at the same time.
If the user clicks that button I want to get that associated Core data object.
What would be the best way allow this to happen in terms of allowing the button to reference/call the core Data object?
I have a few ideas of my own, but want to see if there is any quicker/more efficient methods.
Edit:
Button creation will always follow the creation of a managed object.
Buttons creation can also be triggered by a Core data read so Not always will the Managed object creation proceed button creation.
When I create the button, I save it to make sure it was a non-temperory UID, read its UID and store it in a varible (Subclassing UIButton). (The creation Process is optional, note the 2 bullets above). This idea is what ccjensen is getting at.
When I create the button I store 4-5 variables (Subclassing UIButton) that will allow a predicate to find the associated object in Core data.
Storing active button pointers in a dictionary with the CoreData ID
I would favour option 1, any thoughts or alternatives?
Have you considered using KVO with view controller (or whatever is responsible for creating/deleting buttons) observing the variable of interest?
For that matter, what approaches have you already considered? This might make your question more inviting to feedback from others.
Although you don't provide much details, my immediate suggestion would be to find a unique property in the core data objects that you can use for the buttons 'identifier' property. I would probably use the managed objects id or URI representation.
Then in your button handler method you can pull out the identifier of the (id)sender and that should give you the means to find the specific managed object that "belongs" to that button.
Ok, I went with Option 1...
Creation:
SomeManagedObject * managedObj = (SomeManagedObject *)[NSEntityDescription insertNewObjectForEntityForName:#"SomeManagedObject" inManagedObjectContext:myManagedContext];
NSError * er = nil;
if(![myManagedContext save:&er])NSLog(#"ERROR:SAVE Error -%#",er);
NSManagedObjectID *identifier = [managedObj objectID];
CGPoint myPoint;//set point data
if(![identifier isTemporaryID]){
CoreDataAwareButton * button = [CoreDataAwareButton buttonWithPosition:myPoint CoreDataId:identifier AndDelegate:self];
[self.documentImage button];
}
else NSLog(#"Error-save error");
On Press:
-(void)pressCoreDataAwareButton:(id)sendr
{
CoreDataAwareButton * note = (CoreDataAwareButton *)sendr;
SomeManagedObject * obj = (SomeManagedObject*)[fetchObjectFromCoreDataWithID:note.coreDataIDentifier];
}
CoreDataAwareButton.h
#import <Foundation/Foundation.h>
#interface CoreDataAwareButton : UIButton {
NSManagedObjectID * _coreDataIDentifier;
}
#property(nonatomic,retain) NSManagedObjectID * coreDataIDentifier;
+(AnnotationButton*)buttonWithPosition:(CGPoint)point CoreDataId:(NSManagedObjectID*)identifier AndDelegate:(id)del;
#end
CoreDataAwareButton.m
#import "CoreDataAwareButton.h"
#import <objc/runtime.h>
#implementation CoreDataAwareButton
#synthesize coreDataIDentifier=_coreDataIDentifier;
+(CoreDataAwareButton*)buttonWithPosition:(CGPoint)point CoreDataId:(NSManagedObjectID*)identifier AndDelegate:(id)del{
CoreDataAwareButton* button = [self buttonWithType:UIButtonTypeCustom];
if (button && (class_getInstanceSize([button class]) == class_getInstanceSize([CoreDataAwareButton class]))) {
button->isa = [CoreDataAwareButton class];//This looks dangerous, but its fine; credit: http://blog.jayway.com/2008/12/12/uibutton-troubles-and-obj-c-magic/
[button addTarget:del action:#selector(pressCoreDataAwareButton:) forControlEvents:UIControlEventTouchUpInside];
button.frame = CGRectMake(point.x, point.y, 30, 30);
//button.backgroundColor = [UIColor clearColor];
UIImage *img = [UIImage imageNamed:#"annotation_icon_large.png"];
[button setImage:img forState:UIControlStateNormal];
button.coreDataIDentifier = identifier;
}
return button;
}
-(void)dealloc{
[_coreDataIDentifier release];_coreDataIDentifier=nil;
[super dealloc];
}
#end
I have a NSXMLParser that parses YT's API. It then turns all the videos into a class I wrote called video. Then the class is put into an array. Then back at my rootviewcontroller I access xmlParser.allVideos (xmlParser is a class I wrote that is the delegate for the xml parser.
Here is what I do with it in the viewDidLoad:
arrayFromXML = xmlParser.allVideos;
Then in the drawing of the tableView I do this:
tempVideo = [arrayFromXML objectAtIndex:indexPath.row];
cell.textLabel.text = tempVideo.videoTitle; //crashes here and says NSString has been deallocated.
How can I fix this?
If arrayFromXML is an instance variable you have to retain or copy (to be safe from later manipulation) the array as xmlParser simply might not be alive anymore when other methods are called later:
arrayFromXML = [xmlParser.allVideos copy];
Or better yet using a copy or retain property:
self.arrayFromXML = xmlParser.allVideos;