Nektarios
Nektarios

Reputation: 10371

Copy NSView in cocoa / objective-c

I can't see any way to copy an NSView and create an identical NSView object. I see google hits about "use an NSData" but I don't understand that.

Upvotes: 14

Views: 4722

Answers (2)

Noah Nuebling
Noah Nuebling

Reputation: 309

Since around iOS 12 / macOS 14, the accepted answer by @Dave DeLong doesn't work anymore in all cases.

The problem is, that some of the methods which were used in @Dave DeLongs solution don't work anymore for objects that only adhere to the NSCoding protocol but not the NSSecureCoding protocol.


Solution

Luckily, there's still a solution that works for copying any Object that adheres to NSCoding (and not only ones that adhere to NSSecureCoding) even on the newest operating systems!

There are a few changes in this solution compared to the old one. Most importantly, you'll have to create an instance of NSKeyedUnarchiver instead of using the more convenient class methods. The convenient class methods only support NSSecureCoding but not NSCoding in the newer operating systems.

My implementation looks like this:

public func insecureCopy<T: NSCoding>(of original: T) throws -> T {
    
    /// See https://developer.apple.com/forums/thread/107533
    
    if #available(macOS 10.13, *) {
    
        let data = try NSKeyedArchiver.archivedData(withRootObject: original, requiringSecureCoding: false)
        
        let unarchiver = try NSKeyedUnarchiver(forReadingFrom: data)
        unarchiver.requiresSecureCoding = false
        
        let copy = unarchiver.decodeObject(forKey: NSKeyedArchiveRootObjectKey) as! T
        
        return copy
        
    } else { /// Fallback (untested)
        
        let data = NSKeyedArchiver.archivedData(withRootObject: original)
        
        let unarchiver = NSKeyedUnarchiver(forReadingWith: data)
        unarchiver.requiresSecureCoding = false
        
        let copy = unarchiver.decodeObject(forKey: NSKeyedArchiveRootObjectKey) as! T
        
        return copy
        
    }
}

Notes

  • The source for this solution was from @eskimos great answer on this Apple Dev Forum Post: https://developer.apple.com/forums/thread/107533
    as well as the WWDC session video that they linked to.
  • The method is called insecureCopy() because it works on non-secure coding objects. But, as far as I understand, this method is not in fact insecure. Working with non-secure NSCoding objects is only potentially insecure if you load an insecure archive from an untrusted source (Where a hacker might have modified it). If you just use the archives for deep-copying like in this case, it's perfectly safe.
  • The fallback code for macOS 10.13 and earlier is not tested.

Upvotes: 5

Dave DeLong
Dave DeLong

Reputation: 243146

To straight up "copy" an NSView, the view has to implement the NSCopying protocol. Unfortunately, NSView does not.

Fortunately, it does implement the NSCoding protocol, which means we can still duplicate a view like:

NSData * archivedView = [NSKeyedArchiver archivedDataWithRootObject:myView];
NSView * myViewCopy = [NSKeyedUnarchiver unarchiveObjectWithData:archivedView];

And voilá! You now have a duplicate of myView.


Edit: (Swift version)

let archivedView = NSKeyedArchiver.archivedData(withRootObject: myView)
let myViewCopy = NSKeyedUnarchiver.unarchiveObject(with: archivedView)

(archivedView is of type Data, not NSData)

Upvotes: 26

Related Questions