How to properly copy CAShapeLayer in swift? - swift

I'm trying to create copies of CAShapeLayer in swift but I'm getting a crash
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[CAShapeLayer copyWithZone:]: unrecognized selector sent to instance 0x282e87e60'
which extra steps should I take to allow CALayer .copy() work without crashing the app?
Even if I don't cast the result of .copy() it stills fails exactly at that copy() line...
private var drawLines: [CAShapeLayer]
func getCopiedLayers() -> [CAShapeLayer] {
return drawLines.compactMap { layer -> CAShapeLayer? in
return layer.copy() as? CAShapeLayer
}
}
what I'm doing wrong here?
Thanks in advance for the answers

CALayer does not conform to NSCopying from API, but it conforms to NSSecureCoding, so it is possible to add copying capability as below
Tested with Xcode 11.2 / iOS 13.2 (with CAShapeLayer &
CAGradientLayer)
extension CALayer : NSCopying {
public func copy(with zone: NSZone? = nil) -> Any {
if let data = try? NSKeyedArchiver.archivedData(withRootObject: self, requiringSecureCoding: false) {
if let newInstance = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) {
return newInstance
}
}
fatalError() // << should never got here
}
}
Now, it is possible to call layer.copy() to any layer (theoretically) without exception.

Related

Snapshotting a `UIView` crashes the app with `NSInvalidArgumentException` "[_UIReplicantView _isSymbolImage]: unrecognized selector sent to instance"

Hi guys i have just updated XCode to version 11.4 from the app store and when i try to snapshot a UIView on iOS 13.4 like this:
extension UIView {
func snapshot(at scale: CGFloat) -> UIImage? {
let renderer = UIGraphicsImageRenderer(size: bounds.size)
let image = renderer.image { [weak self] context in
self?.drawHierarchy(in: self?.bounds ?? .zero, afterScreenUpdates: true)
}
return image
}
}
or like that:
extension UIView {
func snapshotOld(at scale: CGFloat) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(bounds.size, false, scale)
guard let currentContext = UIGraphicsGetCurrentContext() else { return nil }
layer.render(in: currentContext)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
}
and set the resulting image to UIImageView like that:
class ViewController: UIViewController {
#IBOutlet private weak var imageView: UIImageView!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let image = view.snapshot
imageView.image = image
}
}
extension UIView {
#objc var snapshot: UIImage? {
snapshot(at: 3.0)
// snapshotOld(at: 3.0)
}
}
i get:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[_UIReplicantView _isSymbolImage]: unrecognized selector sent to instance 0x115907c00'
*** First throw call stack:
(0x1ad52c164 0x1ad240c1c 0x1ad42a7e0 0x1b16a5b6c 0x1ad53085c 0x1ad532b60 0x1b1af6cdc 0x1b1af714c 0x1b1af0b30 0x1025061a8 0x102506270 0x1b1010880 0x1b10112cc 0x1b0f25658 0x1b167fc10 0x1b166f13c 0x1b16a088c 0x1ad4a6c54 0x1ad4a18e4 0x1ad4a1d84 0x1ad4a1660 0x1b78b2604 0x1b167615c 0x102507f78 0x1ad31d1ec)
libc++abi.dylib: terminating with uncaught exception of type NSException
I get this both in simulator and on a real device.
Do you guys have any idea why is this happening? I suspect it is a XCode 11.4 bug because this code worked nicely on the older versions 🤔
Here is a sample project if you would like to try it out on your machine
You're not calling the method you think you are. You have two choices:
Change snapshot to snapshott everywhere (or some other nonambiguous alternative).
Or else, remove the #objc designation.
Do either of those things, and all will be well.

why do I get "Attempted to unregister unknown __weak variable" when copying an instance variable?

I noticed this today when playing with NSOutlineView and NSTableHeaderCell, but when this specific configuration is made, an error/warning(?) is printed:
objc[2774]: Attempted to unregister unknown __weak variable at 0x1016070d0. This is probably incorrect use of objc_storeWeak() and objc_loadWeak(). Break on objc_weak_error to debug.
here's the snippet:
class Foo: NSCell {
weak var weak: NSView?
override func copy(with zone: NSZone? = nil) -> Any {
// according to NSCopying documentation:
// If a subclass inherits NSCopying from its superclass and declares
// additional instance variables, the subclass has to override copy(with:)
// to properly handle its own instance variables, invoking the superclass’s implementation first.
let copy = super.copy(with: zone) as! Foo
// this produces "Attempted to unregister unknown __weak variable"
copy.weak = self.weak
return copy
}
}
let view = NSView(frame: NSRect.zero)
let foo = Foo()
foo.weak = view
let copy = foo.copy() as! Foo
this also happens if I substitute NSCell with: NSEvent, NSImage, NSImageCell
but this doesn't happen to NSColor, NSDate, NSIndexPath
I started learning Swift without prior knowledge of Obj-C. could someone help me understand why this is? is it safe to ignore? who has the blame in this case?
This is a framework bug. It's easy to reproduce with the following crasher:
import Cocoa
class Cell: NSCell {
var contents: NSString?
override func copy(with zone: NSZone? = nil) -> Any {
let newObject = super.copy(with: zone) as! Cell
newObject.contents = contents
return newObject
}
}
func crash() {
let cell = Cell()
cell.contents = "hello world"
cell.copy() // crashes while releasing the copied object
}
crash()
When you use a weak var instead, you get the error message that you showed.
My gut feeling is that there is something in the copy implementation of NSCell (and possibly of NSEvent and NSImage) that does not handle subclassing for types that have non-trivial constructors. Accordingly, if you change let newObject = super.copy(...) with let newObject = Cell(), the crash is avoided. If your superclass's copy logic is simple enough, you should probably do that for now.
If you hit this problem, you should file a bug report separately of mine, but you can probably reuse my sample.

Error archiving CMTime with NSKeyedArchiver in swift4.0 in ios9.3

The following code archivedTimes() builds successfully in swift4.
And it runs fine on a device with ios10.3 installed.
typealias Time = CMTime
typealias Times = [Time]
static let times: Times = Array<Int64>.init(1...9).map({ CMTime.init(value: $0, timescale: 100) })
static func archivedTimes() -> Data {
return archived(times: times)
}
static func archived(times: Times) -> Data {
let values = times.map({ NSValue.init(time: $0) })
return NSKeyedArchiver.archivedData(withRootObject: values) // ERROR here
// -- ideally would instead be:
// return NSKeyedArchiver.archivedData(withRootObject: times)
// -- but probably not compatible with ios 9.3
}
However, while running it on a device with ios9.3 installed, it crashes saying:
Terminating app due to uncaught exception
'NSInvalidArgumentException', reason: '*** -[NSKeyedArchiver
encodeValueOfObjCType:at:]: this archiver cannot encode structs'
My guess is that it may have something to do with some conflict between the new Codable protocol and the old NSCoder protocol. But I don't know what!
Note that the issue has nothing to do with the array. As archiving a simple CMTime also leads to such error. However, I posted it like this, because archiving the array of CMTime is ultimately my objective.
I believe Codable protocol is only available in ios10, therefore on ios9, CMTime does not implement Codable.
So for ios9, I went with a wrapper class for a CMTime, which implements the NSCoding protocol.
This can be done by importing AVFoundation which declares both the extension to NSValue and to NSCoder so as to encode CMTime.
So then I went with an array of WrappedTime.init($0), instead of an array of NSValue.init(time: $0).
class WrappedTime: NSObject, NSCoding {
enum EncodeKey: String {
case time = "time"
}
let time: CMTime
// ...
func encode(with aCoder: NSCoder) {
aCoder.encode(time, forKey: EncodeKey.time.rawValue)
}
required init?(coder aDecoder: NSCoder) {
time = aDecoder.decodeTime(forKey: EncodeKey.time.rawValue)
}
init(_ time: Time) {
self.time = time
}
}

How to use the delegates with NSKeyedUnarchiver?

I am using NSKeyedUnarchiver to unarchive an object and would like to use the delegates (NSKeyedUnarchiverDelegate), but my delegates are not called. Archiving and Unarchiving is working fine, but the Delegates (unarchiver & unarchiverDidFinish) are not called. Can someone help?
I have the following implementation:
class BlobHandler: NSObject , NSKeyedUnarchiverDelegate{
func load() -> MYOBJECTCLASS{
let data:NSData? = getBlob();
var mykeyedunarchiver:NSKeyedUnarchiver=NSKeyedUnarchiver(forReadingWithData: data!);
mykeyedunarchiver.delegate = self;
let temp=mykeyedunarchiver.decodeObjectForKey("rootobject")
// No delegates are called
if temp==nil {
blobsexists=false;
}else{
objectreturn = temp! as! MYOBJECTCLASS;
return objectreturn;
}
}
func save1(myobject:MYOBJECTCLASS){
let data = NSMutableData()
var keyedarchiver:NSKeyedArchiver=NSKeyedArchiver(forWritingWithMutableData: data);
keyedarchiver.encodeObject(maptheme, forKey: "rootobject");
let bytes = data.bytes;
let len=data.length;
saveblob(bytes);
}
The following delegates, which are also implemented in my Blobhandler, are never called:
func unarchiver(unarchiver: NSKeyedUnarchiver, cannotDecodeObjectOfClassName name: String, originalClasses classNames: [String]) -> AnyClass? {
print("I am in unarchiver !");
return nil;
}
func unarchiverDidFinish(_ unarchiver: NSKeyedUnarchiver){
print("I am in unarchiverDidFinish ! ");
}
I don't know what it was, but its working after a clean and rebuild of the project.
I notice with different cases, that the builds are not in sync sometimes. There is sometimes code, which is in XCode but it is not executed. Sounds unbelievable, but I guess its true.
XCode 7.2
I think the first function is never called since you didn't actually feed a "cannotDecodeObjectOfClassName" at all, since you only did try to unarchive previously archived data. You can try this method(or something requires a class name) to validate your solution(feed a class doesn't conform NSCoding):
unarchiver.decodeObjectOfClass(cls: NSCoding.Protocol, forKey: String)
The second one is a little bit tricky. I've tried this method in a similar situation and it turned out that unarchiverDidFinish only get called when a complete unarchiving job is done and probably before it's destroyed. For example, I had a NSCoding class and the convenience initiator is like
required convenience init?(coder aDecoder: NSCoder) {
let unarchiver = aDecoder as! NSKeyedUnarchiver
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
unarchiver.delegate = appDelegate.uad
let name = unarchiver.decodeObjectForKey(PropertyKey.nameKey) as! String
print(321)
self.init(name: name, photo: photo, rating: rating)
}
uad is an instance of class:
class UAD:NSObject, NSKeyedUnarchiverDelegate {
func unarchiverDidFinish(unarchiver: NSKeyedUnarchiver) {
print(123)
}
}
And in the view controller the loading process is like
func load() -> [User]? {
print(1)
let ret = NSKeyedUnarchiver.unarchiveObjectWithFile(ArchiveURL.path!) as? [User]
print(2)
return ret
}
And the output is like:
1
321
321
321
321
321
123
2
After finishing loading a group of users, the unarchiverDidFinish finally got called once. Notice that this is a class function and an anonymous instance is created to finish this sentence:
NSKeyedUnarchiver.unarchiveObjectWithFile(ArchiveURL.path!) as? [User]
So I really believe that this function only get called before it is destroyed or a group of call back functions is finished.
I am not quite sure if this is the case for you. You may try to make your unarchiver object global and destroy it after your loading is done to see whether this function is called.
Correct me if anything not right.
To make either unarchiverWillFinish: and unarchiverDidFinish: be called properly, we have to invoke finishDecoding when finished decoding.
Once you have the configured decoder object, to decode an object or data item, use the decodeObjectForKey: method. When finished decoding a keyed archive, you should invoke finishDecoding before releasing the unarchiver.
We notify the delegate of the instance of NSKeyedUnarchiver and perform any final operations on the archive through invoking this method. And once this method is invoked, according to Apple's official documentation, our unarchiver cannot decode any further values. We would get following message if we continue to perform any decoding operation after invoked finishDecoding:
*** -[NSKeyedUnarchiver decodeObjectForKey:]: unarchive already finished, cannot decode anything more
It also makes sense for encoding counterparts.

WatchOS2 connectivity framework doesn't work

I want to pass data from Iphone to Apple Watch. I tried everything but when I am using the didReceiveUserInfo function, nothing happens I check if WCSession is compatible and it is.
Code on my Iphone:
if(ipField.text != ""){
do {
try watchSession?.transferUserInfo(["name" : "test"])
print("context update")
} catch let error as NSError {
NSLog("Updating the context failed: " + error.localizedDescription)
print("failed")
}
Code on my Apple Watch:
func session(session: WCSession, didReceiveUserInfo userInfo: [String : AnyObject]){
let Value = userInfo["name"] as? String
self.currentIpLabel.setText(Value)
print("done1")
}
WCSESSION check Iphone:
if (WCSession.isSupported()) {
let session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
print("SUPPORT OK")
}
WCSESSION check AppleWatch
if(WCSession.isSupported()){
watchSession = WCSession.defaultSession()
// Add self as a delegate of the session so we can handle messages
watchSession!.delegate = self
watchSession!.activateSession()
}
I have created an issue on github with a suggested patch attached. I tested this version of the app on my own devices and the watch received the userInfo just fine. The main change I made was to move the declaration of the WCSessionDelegate methods from being "nested functions" to top level functions in the file. Nested functions are only available from within the scope of the function they are defined in, which would mean that the delegate object wouldn't have implementations for those methods.