Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create a copy of a UIView in Swift

Tags:

ios

swift

uiview

Because objects are reference types, not value types, if you set a UIView equal to another UIView, the views are the same object. If you modify one you'll modifying the other as well.

I have an interesting situation where I would like to add a UIView as a subview in another view, then I make some modifications, and those modifications should not affect the original UIView. How can I make a copy of the UIView so I can ensure I add that copy as a subview instead of a reference to the original UIView?

Note that I can't recreate the view in the same way the original was created, I need some way to create a copy given any UIView object.

like image 227
Jordan H Avatar asked Nov 25 '14 08:11

Jordan H


People also ask

What is the use of makeui in Swift?

The makeUI functions essentially creates the views, adds them to UIController 's view (as a subview) and set constraints on them (AutoLayout). But the ViewController become bulky if more UIView s are added. Should I move the UI code to another file (say LoginView.swift )?

How do I display all tweets in a UIView?

At the bottom of the screen is the table with all the tweets and a tab bar. All the elements marked with purple are subclasses of UIView (UIButtons, UILabels, UIImageViews etc). We are going to focus on the header view, framed in orange in the image below.

How to avoid copy-paste problems with uiviews?

We avoid the copy-paste problems that might appear and if this view changes we only have to change it once. This example is not only helpful with UIViews, it can be used with all types of elements like UITextfields, UIButtons, etc. Use reusable elements, don’t copy-paste.

How to avoid duplicate constraints in a UIView subclass?

Since we are adding constraints in a UIView subclass, we should override the updateConstraints method. This method may be called more than once during the lifetime of the object. To avoid duplicate constraints you should keep a flag ( shouldSetupContraints) that lets you know once you have done your initial constraint setup.


4 Answers

You can make an UIView extension. In example snippet below, function copyView returns an AnyObject so you could copy any subclass of an UIView, ie UIImageView. If you want to copy only UIView 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: UIView = sourceView.copyView()
like image 161
Ivan Porkolab Avatar answered Oct 19 '22 03:10

Ivan Porkolab


You can't arbitrarily copy an object. Only objects that implement the NSCopying protocol can be copied.

However, there is a workaround: Since UIViews can be serialized to disk (e.g. to load from a XIB), you could use NSKeyedArchiver and NSKeyedUnarchiver to create a serialized NSData describing your view, then de-serialize that again to get an independent but identical object.

like image 27
uliwitness Avatar answered Oct 19 '22 02:10

uliwitness


Update for iOS >= 12.0

Methods archivedData(withRootObject:) and unarchivedObject(with:) are deprecated as of iOS 12.0.

Here is an update to @Ivan Porcolab's answer using the newer API (since 11.0), also made more general to support other types.

extension NSObject {
    func copyObject<T:NSObject>() throws -> T? {
        let data = try NSKeyedArchiver.archivedData(withRootObject:self, requiringSecureCoding:false)
        return try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? T
    }
}
like image 17
David James Avatar answered Oct 19 '22 02:10

David James


This answer shows how to do what @uliwitness suggested. That is, get an identical object by archiving it and then unarchiving it. (It is also basically what Ivan Porkolab did in his answer, but in a more readable format, I think.)

let myView = UIView()

// create an NSData object from myView
let archive = NSKeyedArchiver.archivedData(withRootObject: myView)

// create a clone by unarchiving the NSData
let myViewCopy = NSKeyedUnarchiver.unarchiveObject(with: archive) as! UIView

Notes

  • Unarchiving the data creates an object of type AnyObject. We used as! UIView to type cast it back to a UIView since we know that's what it is. If our view were a UITextView then we could type cast it as! UITextView.
  • The myViewCopy no longer has a parent view.
  • Some people mention some problems when working with UIImage. However, see this and this answer.

Updated to Swift 3.0

like image 6
Suragch Avatar answered Oct 19 '22 01:10

Suragch