Swift: Does not conform to protocol NSCoding - ios

I am trying to use the NSCoding protocol on a class I have written in swift, but cannot seem to figure out why the compiler complains that it "does not conform to protocol NSCoding" when I do implement the required methods:
class ServerInfo: NSObject, NSCoding {
var username = ""
var password = ""
var domain = ""
var location = ""
var serverFQDN = ""
var serverID = ""
override init() {
}
init(coder aDecoder: NSCoder!) {
self.username = aDecoder.decodeObjectForKey("username") as NSString
self.password = aDecoder.decodeObjectForKey("password") as NSString
self.domain = aDecoder.decodeObjectForKey("domain") as NSString
self.location = aDecoder.decodeObjectForKey("location") as NSString
self.serverFQDN = aDecoder.decodeObjectForKey("serverFQDN") as NSString
self.serverID = aDecoder.decodeObjectForKey("serverID") as NSString
}
func encodeWithCoder(_aCoder: NSCoder!) {
_aCoder.encodeObject(self.username, forKey: "username")
_aCoder.encodeObject(self.password, forKey: "password")
_aCoder.encodeObject(self.domain, forKey: "domain")
_aCoder.encodeObject(self.location, forKey: "location")
_aCoder.encodeObject(self.serverFQDN, forKey: "serverFQDN")
_aCoder.encodeObject(self.serverID, forKey: "serverID")
}
}
Is this a bug or am I just missing something?

As you can see in the detailed compiler messages in the Report navigator,
your methods are not declared correctly:
error: type 'ServerInfo' does not conform to protocol 'NSCoding'
class ServerInfo: NSObject, NSCoding {
^
Foundation.NSCoding:2:32: note: protocol requires function 'encodeWithCoder' with type '(NSCoder) -> Void'
#objc(encodeWithCoder:) func encodeWithCoder(aCoder: NSCoder)
^
note: candidate has non-matching type '(NSCoder!) -> ()'
func encodeWithCoder(_aCoder: NSCoder!) {
^
Foundation.NSCoding:3:25: note: protocol requires initializer 'init(coder:)' with type '(coder: NSCoder)'
#objc(initWithCoder:) init(coder aDecoder: NSCoder)
^
note: candidate has non-matching type '(coder: NSCoder!)'
init(coder aDecoder: NSCoder!) {
(This may have changed between the beta releases.)
In addition, the initWithCoder method has to be marked as required:
required init(coder aDecoder: NSCoder) { }
func encodeWithCoder(_aCoder: NSCoder) { }
In Swift 3 the required methods are
required init(coder aDecoder: NSCoder) { }
func encode(with aCoder: NSCoder) { }

The parameters are not implicitly unwrapped (remove the !), and the initializer requires the required modifier:
required init(coder aDecoder: NSCoder) {
...
func encodeWithCoder(_aCoder: NSCoder) {
For Swift 3
A minor but important change has been commited . The init method is same but the encodeWithCoder method has been modified.
required init(coder aDecoder: NSCoder) {
...
}
func encode(with _aCoder: NSCoder) {
...
}

For Swift 3 (on Xcode 8.2 beta (8C23))
It appears to have changed again. This is the only variation that I could get to work...
func encodeWithCoder(_ _aCoder: NSCoder) {
...
}

Related

iOS settings page helper class

I am trying to build a settings page helper class in order to simplify the setup of a settings page.
The idea would be that the class handles saving the state to UserDefaults and setting the initial state of any UISwitch.
Setting up a switch would just be a matter of setting a new switch to a class of "UISettingsSwitch" and adding the name of it to the accessibility label (it's the only identifier available as far as i'm aware).
So far I have :
import Foundation
import UIKit
class SettingsUISwitch: UISwitch {
enum SettingsType {
case darkMode, sound
}
func ison(type: SettingsType ) -> Bool {
switch type {
case .darkMode:
return userDefaults.bool(forKey: "darkMode")
case .sound:
return userDefaults.bool(forKey: "sound")
}
}
let userDefaults = UserDefaults.standard
override init(frame: CGRect) {
super.init(frame: frame)
initSwitch()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initSwitch()
}
deinit {
}
func initSwitch() {
addTarget(self, action: #selector(toggle), for: .valueChanged)
}
#objc func toggle(){
userDefaults.setValue(self.isOn, forKey: self.accessibilityLabel!)
}
}
Not an awful lot I know.
I can currently do :
if settingsSwitch.ison(type: .darkMode) {
print (settingsSwitch.ison(type: .darkMode))
print ("ON")
} else {
print ("OFF")
}
The accessibility label doesn't seem to be available in the init setup at any point, so setting up the initial state doesn't seem to be a possibility.
Is it possible to set the initial state of the UISwitch this way ?
Ideally , I'd like to expose : settingsSwitch.darkMode.ison as a boolean ... but I can't figure that one out. Thanks for any help
I managed to use the restoration identifier to do the setup for the switch but I'd still love to remove the cases and the repeated calls to userDefaults
import Foundation
import UIKit
class UISwitchSettings: UISwitch {
enum SettingsType: String, CaseIterable {
case darkMode = "darkMode"
case sound = "sound"
}
func ison(type: SettingsType ) -> Bool {
switch type {
case .darkMode:
return userDefaults.bool(forKey: "darkMode")
case .sound:
return userDefaults.bool(forKey: "sound")
}
}
let userDefaults = UserDefaults.standard
override init(frame: CGRect) {
super.init(frame: frame)
initSwitch()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initSwitch()
}
deinit {
}
func initSwitch() {
if let key = self.restorationIdentifier {
// Logic needs changing if default switch is off
if userDefaults.bool(forKey: key) || userDefaults.object(forKey: key) == nil {
self.isOn = true
} else {
self.isOn = false
}
}
addTarget(self, action: #selector(toggle), for: .valueChanged)
}
#objc func toggle(){
userDefaults.setValue(self.isOn, forKey: self.restorationIdentifier!)
}
}

UIViewController with closure init

I try to create UIViewController:
class CategoriesVC: UIViewController {
let tableView = UITableView()
var completionHandler: (Category)->Void?
init(completionHandler: #escaping (Category)->Void) {
super.init()
self.completionHandler = completionHandler
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
and I get this error:
Must call a designated initializer of the superclass 'UIViewController'
On this line:
super.init()
The error states clearly that you must call the designate init for UIViewController, which in this case is super.init(nibName:,bundle:).
Also, the completionHandler syntax is wrong, here's the fix:
class CategoriesVC: UIViewController {
let tableView = UITableView()
var completionHandler: ((Category)->Void)?
init(completionHandler: #escaping ((Category)->Void)) {
super.init(nibName: nil, bundle: nil)
self.completionHandler = completionHandler
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

How does Codable fit in with iOS restoration process?

In AppDelegate.swift I have:
func application(_ application: UIApplication, shouldRestoreApplicationState coder: NSCoder) -> Bool {
return true
}
And iOS will call my encodeRestorableState() & decodeRestorableState() class methods during state restoration.
How does Codable work with respect to state restoration? What does iOS call and how do I tie in my Codable structs and classes?
encodeRestorableState(with:) passes you an instance of NSCoder. Any variables you require to restore your state must be encoded here using encode(_:forKey:) with this coder and must therefore conform to Codable.
decodeRestorableState(with:) passes you this same Coder into the function body. You can access the properties in the decoder with the key you used when they were encoded and then set them to instance variables or otherwise use them to configure your controller.
e.g.
import UIKit
struct RestorationModel: Codable {
static let codingKey = "restorationModel"
var someStringINeed: String?
var someFlagINeed: Bool?
var someCustomThingINeed: CustomThing?
}
struct CustomThing: Codable {
let someOtherStringINeed = "another string"
}
class ViewController: UIViewController {
var someStringIDoNotNeed: String?
var someStringINeed: String?
var someFlagINeed: Bool?
var someCustomThingINeed: CustomThing?
override func encodeRestorableState(with coder: NSCoder) {
super.encodeRestorableState(with: coder)
let restorationModel = RestorationModel(someStringINeed: someStringINeed,
someFlagINeed: someFlagINeed,
someCustomThingINeed: someCustomThingINeed)
coder.encode(restorationModel, forKey: RestorationModel.codingKey)
}
override func decodeRestorableState(with coder: NSCoder) {
super.decodeRestorableState(with: coder)
guard let restorationModel = coder.decodeObject(forKey: RestorationModel.codingKey) as? RestorationModel else {
return
}
someStringINeed = restorationModel.someStringINeed
someFlagINeed = restorationModel.someFlagINeed
someCustomThingINeed = restorationModel.someCustomThingINeed
}
}

Custom class archiving in Swift

I would like to archive and unarchive a custom object in swift, something like this:
class Line : NSObject, NSCoding {
var start : CGPoint
init(start _start: CGPoint) {
start = _start
}
required init(coder aDecoder: NSCoder) {
// 1 - compile time error
self.start = aDecoder.decodeObjectForKey("start") as CGPoint
}
override init() {
}
func encodeWithCoder(aCoder: NSCoder) {
// 2 - compile time error
aCoder.encodeObject(start, forKey: "start")
}
}
The 1. compile time error is:
Type 'CGPoint' does not conform to protocol 'AnyObject'
The 2. compile time error is:
Extra argument 'forKey' in call
How to archive and unarchive a CGPoint, I know CGPoint is a struck and this is the problem, but how to solve it?
thanks in advance.
You can archive/unarchive a CGPoint using :
encodeCGPoint:forKey:
and
decodeCGPointForKey:
More information here : https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSCoder_Class/index.html#//apple_ref/occ/instm/NSCoder/encodeCGPoint:forKey:
May be you can convert CGPoint to NSValue.
func encodeWithCoder(aCoder: NSCoder) {
var cgPointAsObject:NSValue = NSValue(CGPoint: start)
aCoder.encodeObject(cgPointAsObject, forKey: "start")
}
Similarly reverse process convert NSValue to CGpoint while decoding.
In swift3 there is some func changed, to do like this:
class UserModel: NSObject,NSCoding {
var username:String = ""
var password: Int = 0
required init(coder aDecoder: NSCoder) {
super.init()
}
override init() {
}
func encode(with aCoder: NSCoder) {
}
}

Cannot subclass WKWebView

I am trying to subclass WKWebView. When I implement my own initializer, I got this error:
'required' initializer 'init(coder:)' must be provided by subclass of 'WKWebView'
Ok, that is well known that we have to implement it for subclasses of UIView. For a direct subclass of UIView it works just implementing it, but with WKWebView it does not seem so simple. I followed the Fix-it hint, and this snippet is added to the code:
required #availability(*, unavailable) convenience init!(coder: NSCoder!) {
fatalError("init(coder:) has not been implemented")
}
So I get a class like the following:
import WebKit
class TSWebView : WKWebView {
let s: String
let i: Int
init(s: String, i: Int) {
self.s = s
self.i = i
super.init(frame: CGRectZero, configuration: WKWebViewConfiguration())
}
required #availability(*, unavailable) convenience init!(coder: NSCoder!) {
fatalError("init(coder:) has not been implemented")
}
}
However, when I do this I get these four other errors:
expected declaration
required #availability(*, unavailable) convenience init!(coder: NSCoder!) {
consecutive declarations on a line must be separated by ';'
required #availability(*, unavailable) convenience init!(coder: NSCoder!) {
cannot override 'init' which has been marked unavailable
required #availability(*, unavailable) convenience init!(coder: NSCoder!) {
'required' modifier must be present on all overrides of a required initializer
required #availability(*, unavailable) convenience init!(coder: NSCoder!) {
Any ideas? My Xcode Version is 6.1.1 (6A2008a). Thanks a lot.
Just override the regular initialization like this. This worked for me, Swift 5.
override init(frame: CGRect, configuration: WKWebViewConfiguration) {
super.init(frame: frame, configuration: configuration)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
This is totes possible. You must only use convenience initializers and properties with default values set:
import WebKit
class MPWebView : WKWebView {
var transparent: Bool = false
convenience init(config: WKWebViewConfiguration = WKWebViewConfiguration()) {
let prefs = WKPreferences()
prefs.plugInsEnabled = true // NPAPI for Flash, Java, Hangouts
prefs.minimumFontSize = 14
prefs.javaScriptCanOpenWindowsAutomatically = true;
config.preferences = prefs
config.suppressesIncrementalRendering = false
self.init(frame: CGRectZero, configuration: config)
}
convenience required init(url: NSURL) {
self.init(config: nil)
loadRequest(NSURLRequest(URL: url))
}
}
Try taking out the extra decorations:
import WebKit
class TSWebView : WKWebView {
let s: String
let i: Int
init(s: String, i: Int) {
self.s = s
self.i = i
super.init(frame: CGRectZero, configuration: WKWebViewConfiguration())
}
convenience init!(coder: NSCoder!) {
super.init(coder:coder)
}
}
Although I'm guessing the whole point of the "availablity(*, unavailable)" is to make it so that you can't invoke the initializer (and hence can't effectively subclass WKWebView.

Resources