save and get array of custom object in user default [duplicate] - ios

I try to store Array of objects in NSUserDefaults.
I have following snippets of code:
var accounts = MyAccounts()
var array:Array<MyAccounts.MyCalendar> = accounts.populateFromCalendars()
NSUserDefaults.standardUserDefaults().
setObject(array, forKey: "test_storeAccounts_array") // <- get error here
NSUserDefaults.standardUserDefaults().synchronize()
But I get Exception:
does not implement methodSignatureForSelector: -- trouble ahead
my class structure:
class MyAccounts {
/* ... */
class MyCalendar {
var title:String?
var identifier:String?
var email:String?
var calType:String?
var isActive:Bool?
var isMainAcount:Bool?
init(){}
}
}
Any ideas?

Make sure your class inherits from NSObject
class MyAccounts:NSObject {
/* ... */
class MyCalendar {
var title:String?
var identifier:String?
var email:String?
var calType:String?
var isActive:Bool?
var isMainAcount:Bool?
init(){}
}
}

I was getting this exception in Swift 3.0. In my case, my model class was not inherit from NSObject base class. just inherit my class from NSObject base class and implements NSCoding protocol (if your container array has custom objects)
class Stock: NSObject, NSCoding {
var stockName: String?
override init() {
}
//MARK: NSCoding protocol methods
func encode(with aCoder: NSCoder){
aCoder.encode(self.stockName, forKey: "name")
}
required init(coder decoder: NSCoder) {
if let name = decoder.decodeObject(forKey: "name") as? String{
self.stockName = name
}
}
func getStockDataFromDict(stockDict stockDict:[String:AnyObject]) -> Stock {
if let stockName = stockDict["name"] {
self.stockName = stockName as? String
}
return self
}
}

In Swift 2, I experienced similar error while using the Notification Pattern within a custom class. Note that when the same notification(Observe) is implemented in a ViewController class , it doesn't complain. Its only with the custom class, created from a Swift file without subclassing this error was thrown
class myClass : NSObject {
override init(){
super.init()
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("functionCall:"), name: "NotificationName", object: nil)
}
//Implement function
func functionCall(notification: NSNotification) {
//Extract the object and implement the function
}
}

You need to convert the class into NSData first. Something like this:
var data = NSKeyedArchiver.archivedDataWithRootObject(accounts.populateFromCalendars())
var userDefaults = NSUserDefaults.standardUserDefaults();
userDefaults.setObject(data, forKey: "test_storeAccounts_array");

Related

How to implement simple MVC design pattern in Swift?

I am new to MVC design pattern. I created "DataModel" it will make an API call, create data, and return data to the ViewController using Delegation and "DataModelItem" that will hold all data. How to call a DataModel init function in "requestData" function. Here is my code:
protocol DataModelDelegate:class {
func didRecieveDataUpdata(data:[DataModelItem])
func didFailUpdateWithError(error:Error)
}
class DataModel: NSObject {
weak var delegate : DataModelDelegate?
func requestData() {
}
private func setDataWithResponse(response:[AnyObject]){
var data = [DataModelItem]()
for item in response{
if let tableViewModel = DataModelItem(data: item as? [String : String]){
data.append(tableViewModel)
}
}
delegate?.didRecieveDataUpdata(data: data)
}
}
And for DataModelItem:
class DataModelItem{
var name:String?
var id:String?
init?(data:[String:String]?) {
if let data = data, let serviceName = data["name"] , let serviceId = data["id"] {
self.name = serviceName
self.id = serviceId
}
else{
return nil
}
}
}
Controller:
class ViewController: UIViewController {
private let dataSource = DataModel()
override func viewDidLoad() {
super.viewDidLoad()
dataSource.delegate = self
}
override func viewWillAppear(_ animated: Bool) {
dataSource.requestData()
}
}
extension ViewController : DataModelDelegate{
func didRecieveDataUpdata(data: [DataModelItem]) {
print(data)
}
func didFailUpdateWithError(error: Error) {
print("error: \(error.localizedDescription)")
}
}
How to implement simple MVC design pattern in Swift?
As a generic answer, in iOS development you're already doing this implicitly! Dealing with storyboard(s) implies the view layer and controlling the logic of how they work and how they are connected to the model is done by creating view controller, that's the default flow.
For your case, let's clarify a point which is: according to the standard MVC, by default the responsible layer for calling an api should be -logically- the view controller. However for the purpose of modularity, reusability and avoiding to create massive view controllers we can follow the approach that you are imitate, that doesn't mean that its the model responsibility, we can consider it a secondary helper layer (MVC-N for instance), which means (based on your code) is DataModel is not a model, its a "networking" layer and DataModelItem is the actual model.
How to call a DataModel init function in "requestData" function
It seems to me that it doesn't make scene. What do you need instead is an instance from DataModel therefore you could call the desired method.
In the view controller:
let object = DataModel()
object.delegate = self // if you want to handle it in the view controller itself
object.requestData()
I am just sharing my answer here and I am using a codable. It will be useful for anyone:
Model:
import Foundation
struct DataModelItem: Codable{
struct Result : Codable {
let icon : String?
let name : String?
let rating : Float?
let userRatingsTotal : Int?
let vicinity : String?
enum CodingKeys: String, CodingKey {
case icon = "icon"
case name = "name"
case rating = "rating"
case userRatingsTotal = "user_ratings_total"
case vicinity = "vicinity"
}
}
let results : [Result]?
}
NetWork Layer :
import UIKit
protocol DataModelDelegate:class {
func didRecieveDataUpdata(data:[String])
func didFailUpdateWithError(error:Error)
}
class DataModel: NSObject {
weak var delegate : DataModelDelegate?
var theatreNameArray = [String]()
var theatreVicinityArray = [String]()
var theatreiconArray = [String]()
func requestData() {
Service.sharedInstance.getClassList { (response, error) in
if error != nil {
self.delegate?.didFailUpdateWithError(error: error!)
} else if let response = response{
self.setDataWithResponse(response: response as [DataModelItem])
}
}
}
private func setDataWithResponse(response:[DataModelItem]){
for i in response[0].results!{
self.theatreNameArray.append(i.name!)
self.theatreVicinityArray.append(i.vicinity!)
self.theatreiconArray.append(i.icon!)
}
delegate?.didRecieveDataUpdata(data: theatreNameArray)
print("TheatreName------------------------->\(self.theatreNameArray)")
print("TheatreVicinity------------------------->\(self.theatreVicinityArray)")
print("Theatreicon------------------------->\(self.theatreiconArray)")
}
}
Controller :
class ViewController: UIViewController {
private let dataSource = DataModel()
override func viewDidLoad() {
super.viewDidLoad()
dataSource.delegate = self
}
override func viewWillAppear(_ animated: Bool) {
dataSource.requestData()
}
}
extension ViewController : DataModelDelegate{
func didRecieveDataUpdata(data: [DataModelItem]) {
print(data)
}
func didFailUpdateWithError(error: Error) {
print("error: \(error.localizedDescription)")
}
}
APIManager :
class Service : NSObject{
static let sharedInstance = Service()
func getClassList(completion: (([DataModelItem]?, NSError?) -> Void)?) {
guard let gitUrl = URL(string: "") else { return }
URLSession.shared.dataTask(with: gitUrl) { (data, response
, error) in
guard let data = data else { return }
do {
let decoder = JSONDecoder()
let gitData = try decoder.decode(DataModelItem.self, from: data)
completion!([gitData],nil)
} catch let err {
print("Err", err)
completion!(nil,err as NSError)
}
}.resume()
}
}
I would recommend using a singleton instance for DataModel, since this would be a class you would be invoking from many points in your application.
You may refer its documentation at :
Managing Shared resources using singleton
With this you wont need to initialise this class instance every time you need to access data.

How do I save (persist) class objects that include other classes with Swift

I'm having a little trouble trying to wrap my head around saving data. I know at the very least that if I were to have a simple class with primitive types then I'd only need the NSCoding (encoder and decoder). But I have a main class that has 2 other objects of the same class and I'm not too sure how to persist it.
Example of the main class:
class MainClass {
var name: String
var description: String
var objectA: CustomClass
var objectB: CustomClass
...
}
Would I have to implement the same encoding as I do with the main class? Make it serializable? How would I go to save that?
You need to implement NSCoding in your contained classes and then you can do this
class MainClass {
var name: String
var description: String
var objectA: CustomClass
var objectB: CustomClass
required init?(coder decoder: NSCoder)
{
super.init()
if let decodedObjectA = decoder.decodeObject(forKey: "objectA") as? CustomClass{
self.objectA = decodedObjectA
}
if let decodedObjectB = decoder.decodeObject(forKey: "objectB") as? CustomClass{
self.objectB = decodedObjectB
}
}
}
//MARK: NSCoding
extension MainClass : NSCoding
{
func encode(with aCoder: NSCoder) {
aCoder.encode(self.objectA, forKey: "objectA")
aCoder.encode(self.objectB, forKey: "objectB")
}
}
I hope this helps you

How to store an array in NSUserDefaults? swift 3

I am trying to store an array in userDefaults but i am getting this error when i run my app:
'Attempt to insert non-property list object (
"Morning_Star_2.Event(title: Optional(\"test title\"), location: Optional(\"Test Location\"))"
) for key test'
Here is my code:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var txtTitle: UITextField!
#IBOutlet weak var txtLocation: UITextField!
#IBOutlet weak var txtDate: UITextField!
#IBOutlet weak var txtTime: UITextField!
var eventsArray = [Event]()
#IBAction func btnSave() {
let savedEvents = UserDefaults.standard.object(forKey: "test")
let event = Event(eventTitle: txtTitle.text!, eventLocation: txtLocation.text!)
if let tempEvents = savedEvents {
eventsArray = tempEvents as! [Event]
eventsArray.append(event)
}
else {
let event = Event(eventTitle: txtTitle.text!, eventLocation: txtLocation.text!)
eventsArray.append(event)
}
UserDefaults.standard.set(eventsArray, forKey: "test")
//print(eventsArray)
}
}
To store custom objects in UserDefaults, your objects must
Inherit from NSObject
Conform to the NSCoding protocol
An example of the class implementation could look like this:
class Event: NSObject, NSCoding {
private var eventTitle: String!
private var eventLocation: String!
init(eventTitle: String, eventLocation: String) {
self.eventTitle = eventTitle
self.eventLocation = eventLocation
}
override init() {
}
required convenience init?(coder aDecoder: NSCoder) {
self.init()
eventTitle = aDecoder.decodeObject(forKey: "eventTitle") as? String
eventLocation = aDecoder.decodeObject(forKey: "eventLocation") as? String
}
func encode(with aCoder: NSCoder) {
aCoder.encode(eventTitle, forKey: "eventTitle")
aCoder.encode(eventLocation, forKey: "eventLocation")
}
}
Now, you can only store certain objects in UserDefaults. Luckily the type Data can be stored in UserDefaults. You then need to convert your array to Data, and then store it.
let data = NSKeyedArchiver.archivedData(withRootObject: eventsArray)
// This calls the encode function of your Event class
UserDefaults.standard.set(data, forKey: "test")
/* This saves the object to a buffer, but you will need to call synchronize,
before it is actually saved to UserDefaults */
UserDefaults.standard.synchronize()
When you retrieve the data it comes back as Any?, which will have to be casted to your object:
if let data = UserDefaults.standard.object(forKey: "test") as? Data {
if let storedData = NSKeyedUnarchiver.unarchiveObject(with: data) as? [Event] {
// In here you can access your array
}
}

How to access elements from an NSObject in swift?

I've set some variables as an Object
import UIKit
class SpeedTestResult: NSObject {
var testTime: NSDate?
}
Then in the controller I set this object and pass it to a class to store it:
testResult.testTime = NSDate()
SpeedTestManager().addTestResult(testResult)
I need to store this object and then access the elements within in a view later, This is what I have:
import Foundation
class SpeedTestManager : NSObject {
var testResultArray = [NSObject]()
func addTestResult(testResult: NSObject) {
testResultArray.append(testResult)
print("Printing testResultArray: \(testResultArray)")
}
}
But when I try to print the the object I just get
Printing testResultArray: [<ProjectName.SpeedTestResult: 0x127b85e50>]
How do I access elements within the object and store this object and retrieve it for later use in a view?
class TestResult : NSObject, NSSecureCoding {
var testTime: NSDate?
override init() {
super.init()
}
#objc required init?(coder decoder: NSCoder) {
self.testTime = decoder.decodeObjectForKey("testTime") as? NSDate
}
#objc func encodeWithCoder(encoder: NSCoder) {
encoder.encodeObject(self.testTime, forKey: "testTime")
}
#objc static func supportsSecureCoding() -> Bool {
return true
}
override var description: String {
return String.init(format: "TestResult: %#", self.testTime ?? "null")
}
}
class SpeedTestManager : NSObject, NSSecureCoding {
var testResultArray = [NSObject]()
func addTestResult(testResult: NSObject) {
testResultArray.append(testResult)
print("Printing testResultArray: \(testResultArray)")
}
override init() {
super.init()
}
#objc func encodeWithCoder(encoder: NSCoder) {
encoder.encodeObject(self.testResultArray, forKey: "testResultArray")
}
#objc required init?(coder decoder: NSCoder) {
self.testResultArray = decoder.decodeObjectForKey("testResultArray") as! [NSObject]
}
#objc static func supportsSecureCoding() -> Bool {
return true
}
override var description: String {
return String.init(format: "SpeedManager: [%#]", self.testResultArray.map({"\($0)"}).joinWithSeparator(","))
}
}
class TestViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let testResult = TestResult()
testResult.testTime = NSDate()
let speedManager = SpeedTestManager()
speedManager.addTestResult(testResult)
NSUserDefaults.standardUserDefaults().setObject(NSKeyedArchiver.archivedDataWithRootObject(speedManager), forKey: "speedManager")
NSUserDefaults.standardUserDefaults().synchronize()
if let archivedSpeedManager = NSUserDefaults.standardUserDefaults().objectForKey("speedManager") as? NSData {
let unarchivedSpeedManager = NSKeyedUnarchiver.unarchiveObjectWithData(archivedSpeedManager)
print("SpeedManager: \(unarchivedSpeedManager ?? "null")")
}
else {
print("Failed to unarchive speed manager")
}
}
}
Here is one way you can do it:
import Foundation
class SpeedTestResult: NSObject {
var testTime: NSDate?
}
class SpeedTestManager : NSObject {
var testResultArray = [NSObject]()
func addTestResult(testResult: NSObject) {
testResultArray.append(testResult)
for result in testResultArray {
// This will crash if result is not a SpeedTestResult.
// print((result as! SpeedTestResult).testTime)
// This is better:
if let timeResult = result as? SpeedTestResult
{
print(timeResult.testTime)
}
else
{
print("Not time type...")
}
}
}
}
var testResult = SpeedTestResult()
testResult.testTime = NSDate()
SpeedTestManager().addTestResult(testResult)
This addresses your specific question, but there are some other problems here:
If you are going to store only SpeedTestResult instances in
testResultArray, then why not make it of type
[SpeedTestResutl]()?
If you will store different types of tests in the array, then how do
you find out which type of test an NSObject element represents?
There are ways... In the above code we at least make sure we are not treating a wrong type of object as a SpeedTestResult.
When you do SpeedTestManager().addTestResult(testResult), you don't
keep a reference to the SpeedTestManager instance. The next time
you make the same call, you will be creating a different
SpeedTestManager instance.
This is not really a problem, but SpeedTestManager does not have to
be a sub-class of NSObject, unless you want to use it in
Objective-C.
You probably don't want to print the content of testResultArray in
the addTestResult() method. You could have other methods for
accessing the array.
To add your test results to the same test manager, you could do:
let myTestManager = SpeedTestManager()
myTestManager.addTestResult(testResult)
// create other test results and add them ...

Creating singleton in Swift

I have successfully been able to create a singleton object in Swift, but I feel that the implementation is rather verbose. Is there a way to shorten this code up? And combine multiple formatters into one class where each formatter is its own singleton?
import Foundation
class sharedNumberFormatterWithOneDecimalPlace : NSNumberFormatter {
class var sharedInstance: sharedNumberFormatterWithOneDecimalPlace {
struct Singleton {
static let instance = sharedNumberFormatterWithOneDecimalPlace()
}
return Singleton.instance
}
override init () {
super.init()
self.minimumIntegerDigits = 1
self.maximumFractionDigits = 1
self.minimumFractionDigits = 1
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
In my other class I can call it by:
NSNumberFormatter *formatter = sharedNumberFormatterWithOneDecimalPlace.sharedInstance;
NSLog(#"%#", [formatter stringFromNumber:aNumber]);
I would like to be able to have 1 class of "MultipleFormatters" where I set up many formatters that get used all over the place, and then call something like "MultipleFormatters.OneDecimalPlace" for example.
PS. I have already read post: Using a dispatch_once singleton model in Swift
Thanks.
Best way in my opinion is :
private let _MultipleFormattersSharedInstance = MultipleFormatters()
class MultipleFormatters {
class var sharedInstance: MultipleFormatters {
return _MultipleFormattersSharedInstance
}
}
and you can use
MultipleFormatters.sharedInstance
Then you can create all the "formatters" that you need as methods from your class MultipleFormatters and call them all over the place.
Thanks to that thing in swift
Edit for example:
Let's say you want to have a formatter called "increment". I don't know what you mean by formatters but here's a stupid example.
private let _MultipleFormattersSharedInstance = MultipleFormatters()
class MultipleFormatters {
class var sharedInstance: MultipleFormatters {
return _MultipleFormattersSharedInstance
}
func increment(number: Int, amount: Int) -> Int{
//Maybe you need something that was initialized with
//the singleton here.
return number + amount
}
}
and you would use
let singleton : MultipleFormatters = MultipleFormatters.sharedInstance
let result = singleton.increment(1, amount: 25)
import Cocoa
import Foundation
class MultipleFormatters : NSNumberFormatter {
class var oneDecimalPlace: MultipleFormatters {
struct Singleton0 {
static let instance = MultipleFormatters(numberOfDigits: 1)
}
return Singleton0.instance
}
class var twoDecimalPlace: MultipleFormatters {
struct Singleton {
static let instance = MultipleFormatters(numberOfDigits: 2)
}
return Singleton.instance
}
convenience init(numberOfDigits:Int){
self.init()
self.maximumFractionDigits = numberOfDigits
}
override init () {
super.init()
self.minimumIntegerDigits = 1
self.maximumFractionDigits = 1
self.minimumFractionDigits = 1
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
var res = MultipleFormatters.oneDecimalPlace.stringFromNumber(1.234)
var asdf = MultipleFormatters.twoDecimalPlace.stringFromNumber(1.234)
My coworker helped out on this, this is what we think is the best way to create some formatters from one class and also be able to set options for different number formatter styles.
private let _oneDecimalPlaceInstance = MultipleFormatters(minFracDigits: 1, maxFracDigits: 1)
private let _twoDecimalPlaceInstance = MultipleFormatters(minFracDigits: 2, maxFracDigits: 2)
class MultipleFormatters : NSNumberFormatter {
class var oneDecimalPlace: MultipleFormatters {
return _oneDecimalPlaceInstance
}
class var twoDecimalPlace: MultipleFormatters {
return _twoDecimalPlaceInstance
}
convenience init(minFracDigits:Int, maxFracDigits:Int) {
self.init()
self.minimumIntegerDigits = 1
self.minimumFractionDigits = minFracDigits
self.maximumFractionDigits = maxFracDigits
}
}
class TestClass{
private static var instance = TestClass()
public static func shareInstance() ->TestClass{
return TestClass.instance
}
}
let a1 = TestClass.shareInstance()
let a2 = TestClass.shareInstance()
let a3 = TestClass()
print(a1===a2)
print(a1===a3)

Resources