84

Simply using this way:

UIView* view2 = [view1 copy]; // view1 existed

This will cause simulator can not launch this app.

Try retain,

UIView* view2 = [view1 retain]; // view1 existed
// modify view2 frame etc

Any modifications to view2 will apply to view1, I understand that view2 share same memory with view1.

Why can't UIView be copied? What is the reason?

Paulo Mattos
  • 18,845
  • 10
  • 77
  • 85
Forrest
  • 122,703
  • 20
  • 73
  • 107

6 Answers6

163

this might work for you ... archive the view and then unarchive it right after. This should give you a deep copy of a view:

id copyOfView = 
[NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:originalView]];
j2emanue
  • 60,549
  • 65
  • 286
  • 456
  • 1
    That seems like a bit of a hack but works like a dream. ssj's answer below is basically a 'copy constructor' which is fine for small classes. You can maybe use the obj-c runtime to copy all the properties at once.. This is still easier ;) – Patrick Borkowicz Dec 16 '12 at 01:31
  • This sounds to good to be true. In my case it throws an NSInvalidArgumentException (`NSConcreteAttributedString initWithString:: nil value`) for a UILabel in the subview tree. – Ortwin Gentz Nov 08 '13 at 18:28
  • 1
    This works, but seems to be very slow for even trivial view trees. – Ian Newson Feb 26 '14 at 15:39
  • 6
    It works, but beware! It does not set values for @IBOutlets, all of them are nil after "copying" – nalexn Oct 16 '14 at 07:23
  • 1
    This seems to ignore `UIImageView`s :( – Iulian Onofrei Jul 28 '15 at 14:15
  • 1
    Thanks, works great! ALSO please note that the copied view has no superview (parent), no constraints and no gesture recognizers (if the parent had any). – Sajjon Mar 01 '16 at 12:08
  • agree with @IanNewson, just tried 2 approaches. A method, which: 1. instantiates new view and configure it 2. instantiates new view, configures it and archive. If archivation happened already, just call unarchiveObjectsWithData on saved data. 1. result of instantiation of 200 views, where this one is a subview ~ 0.5 sec. 2. result of instantiation of 200 views, where this one is a subview ~ 0.8 sec. The view is only UIImageView with couple autoresizing masks. I would not recommend archiving/unarchiving for duplication of views... – Nick Entin Jul 26 '17 at 10:30
  • This throws a `value for key (UIHighlighted) is not a boolean` exception when using `UIImageView`s. You can use `let copyView = aView.snapshotView(afterScreenUpdates: true)` instead. – paulvs Nov 22 '17 at 12:35
34

Your app probably crashes with something like:

 [UIView copyWithZone:]: unrecognized selector sent to instance 0x1c6280

The reason is that UIView does not implement the copying protocol, and therefore there is no copyWithZone selector in UIView.

Engin Kurutepe
  • 6,719
  • 3
  • 34
  • 64
27

You can make an UIView extension. In example swift snippet below, function copyView returns an AnyObject so you could copy any subclass of an UIView, ie UIImageView. If you want to copy only UIViews you can change the return type to UIView.

//MARK: - UIView Extensions

    extension UIView
    {
       func copyView<T: UIView>() -> T {
            return NSKeyedUnarchiver.unarchiveObject(with: NSKeyedArchiver.archivedData(withRootObject: self)) as! T
       }
    }

Example usage:

let sourceView = UIView()
let copiedView = sourceView.copyView()
Ivan Porkolab
  • 2,009
  • 3
  • 14
  • 17
  • 1
    Thanks, works great! ALSO please note that the copied view has no superview (parent), no constraints and no gesture recognizers (if the parent had any). – Sajjon Mar 01 '16 at 12:08
7

for swift3.0.1:

extension UIView{
 func copyView() -> AnyObject{
    return NSKeyedUnarchiver.unarchiveObject(with: NSKeyedArchiver.archivedData(withRootObject: self))! as AnyObject
 }
}
Sour LeangChhean
  • 7,089
  • 6
  • 37
  • 39
1

UIView doesn't implement the NSCoping protocol, see the declaration in UIView.h:

@interface UIView : UIResponder <NSCoding, UIAppearance, UIAppearanceContainer, UIDynamicItem, UITraitEnvironment, UICoordinateSpace, UIFocusEnvironment>

So, if we want to have a copy like method, we need to implement the NSCoping protocol in a category or so.

HongchaoZhang
  • 3,494
  • 1
  • 17
  • 8
-7

You can make method something like this:

-(UILabel*)copyLabelFrom:(UILabel*)label{
//add whatever needs to be copied
UILabel *newLabel = [[UILabel alloc]initWithFrame:label.frame];
newLabel.backgroundColor = label.backgroundColor;
newLabel.textColor = label.textColor;
newLabel.textAlignment = label.textAlignment;
newLabel.text = label.text;
newLabel.font = label.font;

return [newLabel autorelease];

}

Then you can set your ivar to the return value and retain it like so:

myLabel = [[self copyLabelFrom:myOtherLabel] retain];
ssj
  • 881
  • 1
  • 10
  • 21