Does not conform to protocol 'NSCoding' - Swift 3 - ios

I have seen several questions similar to mine; however, those are pertaining to swift 2/1 and I am currently using swift 3. I believe Apple has changed it slightly.
class Person: NSObject, NSCoding {
var signature: UIImage
init(signature: UIImage) {
self.signature = signature
}
required convenience init(coder aDecoder: NSCoder) {
let signature = aDecoder.decodeObject(forKey: "signature") as! UIImage
self.init(signature: signature)
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encode(signature, forKey: "signature")
}
}
You will notice how Swift 3 now forces me to use required convenience init( instead of required init(. Perhaps that has something to do with it.
How can I resolve this issue? Thanks!

The encode method in Swift 3 has been renamed to
func encode(with aCoder: NSCoder)
When you get the do not conform error you can easily find out which required methods are missing
Press ⌘B to build the code.
Press ⌘4 to show the issue navigator.
Click on the disclosure triangle in front of the issue line.

Related

Swift error from required convenience for viewcontroller

I'm playing around with having a factory that sets up a login in a view controller to satisfy myself what is going on (this is old code I'm updating) I'm trying to make this as simple an example as possible.
It crashes!
View Controller
init(loginServerClass: String){
super.init(nibName: nil, bundle: nil)
}
//called to initialize the login server class
public init?(coder aDecoder: NSCoder, loginServerClass: String){
super.init(coder: aDecoder)
}
//called from storyboard
required convenience init?(coder aDecoder: NSCoder) {
self.init(coder: aDecoder)
}
using a LoginFactory
public protocol LoginFactoryProtocol{
static func createLogin () -> String
}
class LoginFactory : LoginFactoryProtocol {
static public func createLogin () -> String {
return "testlogintype"
}
}
So
1). It crashes with the minimal example above (bad access)
2). It crashes when I add my prefered convenience int as below:
required convenience init?(coder aDecoder: NSCoder) {
self.init(coder: aDecoder, loginServerClass: LoginFactory.createLogin() )
}
(This coder requires that replaced objects be returned from initWithCoder:)
How can I compile this minimum code?
Github link https://github.com/stevencurtis/initissue
If downvoters could explain why, that would be appreciated as I can't improve without knowing what needs to be added. I already added the error messages, a Github link with the problem. I've listed what I've tried, and I'm going from existing previously working code to a small example (therefore I HAVE tried things, the documentation is https://docs.swift.org/swift-book/LanguageGuide/Initialization.html#//apple_ref/doc/uid/TP40014097-CH18-XID_324 and similar questions on stack overflow have not helped (they seem to have the same issues i.e. (NSGenericException: This coder requires that replaced objects be returned from initWithCoder))). By definition this is a small example of the problem. I believe this is how a question should be structured (from https://stackoverflow.com/help/how-to-ask), so please do tell me: what else do I need to do to write a good question, please?
One solution (and I don't know if it is good practice) seems to be to use a single initializer that calls super.init
public required init?(coder aDecoder: NSCoder) {
loginServerClass = LoginFactory.createLogin()
super.init(coder: aDecoder)
}
and then have a single intializer for testing and mocking (or to create the viewcontroller otherwise from code)
init(loginServerClass: String){
self.loginServerClass = loginServerClass
super.init(nibName: nil, bundle: nil)
}

Swift NSCoder Not Working For Double Variable

I'm restoring the state of my app and am storing a few variables using the NSCoder in Xcode 10.1. The NSCoder is fine at storing strings and other variable types, but won't allow me to store Doubles. I've tried numerous scenarios but it just doesn't seem to like Doubles!
If I convert the double into a string it will work. I've tried 'as? Double' and this still returns nil. Any ideas why I just can't use a Double?
override func encodeRestorableState(with coder: NSCoder) {
...
// Both don't work:
coder.encode(myDoubleVariable, forKey: "Length")
coder.encode(1.2345252, forKey: "Length")
}
override func decodeRestorableState(with coder: NSCoder) {
...
// Both don't work:
myDoubleVariable = coder.decodeObject(forKey: "Length")
myDoubleVariable = coder.decodeDouble(forKey: "Length")
}

ios swift method parameters

I'm reading the swift getting started apple's guide. And they give the following code :
required init?(coder aDecoder: NSCoder)
Why are they two parameters name ? coder and aDecoder.
I don't understand why we don't use this syntax that works to :
required init?(coder: NSCoder)
Thank you.
coder is external parameter name. It is used when calling method:
SomeClass(coder: someCoder)
aDecoderis local parameter name. It is used inside method:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
You may specify just local parameter name:
required init?(coder: NSCoder)
In this case external parameter name automatically takes the same value coder.
The main use is readability. Your example is not very obvious. Lets take a better one from the link posted by Eric D:
func sayHello(to person: String, and anotherPerson: String) -> String {
return "Hello \(person) and \(anotherPerson)!"
}
print(sayHello(to: "Bill", and: "Ted"))
This is more readable (better explains what method is doing) when calling sayHello method than
func sayHello(person: String, anotherPerson: String) -> String {
return "Hello \(person) and \(anotherPerson)!"
}
print(sayHello(person: "Bill", anotherPerson: "Ted"))
Of course you can write
func sayHello(to: String, and: String) -> String {
return "Hello \(to) and \(and)!"
}
print(sayHello(to: "Bill", and: "Ted"))
But in this case local variables are using bad naming style

NSCoding required initializer in inherited classes in Swift

I have class Foo which conforms to NSObject and NSCoding which I want to be able to persist with NSKeyedArchiver I want to create class Bar, a subclass of Foo that will also conform to NSObject and NSCoding. I am having a problem understanding how to create the required convenience init?(coder aDecoder: NSCoder) in the subclass.
so class Foo...
class Foo: NSObject, NSCoding {
let identifier:String
init(identifier:String) {
self.identifier = identifier
}
override var description:String {
return "Foo: \(identifier)"
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(identifier, forKey: "identifier")
}
required convenience init?(coder aDecoder: NSCoder) {
guard let identifier = aDecoder.decodeObjectForKey("identifier") as? String
else {
return nil
}
self.init(identifier:identifier)
}
}
Then class Bar ...
class Bar:Foo {
let tag:String
init(identifier:String, tag:String) {
self.tag = tag
super.init(identifier: identifier)
}
override var description:String {
return "Bar: \(identifier) is \(tag)"
}
}
I can get this to compile by adding the following methods on to make this NSCoding compliant
override func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(tag, forKey: "tag")
super.encodeWithCoder(aCoder)
}
this makes sense because I call super.encodeWithCoder(...) reusing the super makes this DRY. The problem I am having is creating the required convenience init?(...) the only way I can seem to get it to compile is by doing this...
required convenience init?(coder aDecoder:NSCoder) {
guard let identifier = aDecoder.decodeObjectForKey("identifier") as? String,
let tag = aDecoder.decodeObjectForKey("tag") as? String
else {
return nil
}
self.init(identifier:identifier, tag:tag)
}
I basically have copied the superclass required initializer and then added the additional decode method for the subclass property. This approach does not seem correct...
Is there a better way to implement this??
Right after you decode and assign all the subclass properties in the required init method, call:
super.init(coder: aDecoder)
Have thought about this for a while and believe that this is the correct way to implement this.
The reason is the way Swift enforces object initialization. Convenience initializers can only call the required initializers on self. Only the required initializer can call the init on super.
Therefore the only way to initialize the subclass object is to decode all of the required initialization parameters before you call the subclass required initializer...which then calls the super class initializer
Here is code you can copy to a playground https://gist.github.com/vorlando/dc29ba98c93eaadbc7b1
I have try your code in playground, it just auto to add the code when I tick the red circle of the Error.
The coding is like your function required convenience init.
sample code:
required convenience init?(coder aDecoder: NSCoder)
{
fatalError("init(coder:) has not been implemented")
}

Twitter Fabric "Show Timelines" Swift example failing with "Cannot override 'init' which has been marked unavailable"

I'm implementing the Twitter Fabric iOS Show Timelines example exactly as described in the Twitter documentation:
https://dev.twitter.com/twitter-kit/ios/show-timelines
However, I'm getting a build error:
Cannot override 'init' which has been marked unavailable
...on the "required init" line below.
I appreciate any help you can provide this newbie. Thanks in advance.
import UIKit
import TwitterKit
class ViewController: TWTRTimelineViewController {
convenience init() {
let client = Twitter.sharedInstance().APIClient
let dataSource = TWTRUserTimelineDataSource(screenName: "fabric", APIClient: client)
self.init(dataSource: dataSource)
}
override required init(dataSource: TWTRTimelineDataSource) {
super.init(dataSource: dataSource)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Twitter dev support confirms that this is a bug and they'll ship a fix ASAP. Here is the link to the issue on the Twitter dev forums:
https://twittercommunity.com/t/show-timelines-swift-example-failing-with-cannot-override-init-which-has-been-marked-unavailable/37853
Also add Following initializer
required convenience init(coder aDecoder: NSCoder) {
self.init()
}
Update the method declaration from TWTRTimelineViewController.h of TwitterKit header files as follows:
- (id)initWithCoder:(NSCoder *)aDecoder;// __attribute__((unavailable("Use -initWithDataSource: instead")));
Here I have commented __attribute__((unavailable as we are using this initWithDataSource: in our convenience init.
This is temporary workaround and anyone can update this answer if needed. I will also post update if any.

Resources