SwiftyJSON error - ios

I have a class with init a build method that i want to use to create instances of class.
Code is the following
class Article {
let id:Int
let title:String
let subtitle:String
let editor1:String
let mainImage:NSData
init(id:Int, title:String, subtitle:String, editor1:String, mainImage:NSData) {
self.id = id
self.title = title
self.subtitle = subtitle
self.editor1 = editor1
self.mainImage = mainImage
}
class func build(json:JSON) -> Article {
id = Int(json["id"].string),
title = json["title"].string,
subtitle = json["subtitle"].string,
editor1 = json["editor1"].string,
mainImage = json["images"]["main"].rawData() {
return Article(
id: id,
title: title,
subtitle: subtitle,
editor1: editor1,
mainImage: mainImage)
}
}
}
But i have errors
What am I doing wrong ?

SwiftyJSON's .rawData() is an Optional getter.
So I guess what you wanted to do is use if let:
class func build(json:JSON) -> Article? {
id = Int(json["id"].string)
title = json["title"].string
subtitle = json["subtitle"].string
editor1 = json["editor1"].string
if let mainImage = json["images"]["main"].rawData() {
return Article(
id: id,
title: title,
subtitle: subtitle,
editor1: editor1,
mainImage: mainImage)
} else {
// ...
return nil
}
}
Also it looks like that you copied/pasted the parameters from your Article initializer to declare them earlier in the function but you forgot to get rid of the commas at the end of the lines.
Update
Your problem is that your class properties are immutable (declared with let) but inside this function you are trying to change their values:
id = Int(json["id"].string)
This is interpreted as
self.id = Int(json["id"].string)
And you can't change the value of self.id because it is immutable.
Solutions:
1- Make the properties mutable by using var instead of let. Example:
var id:Int
var title:String
var subtitle:String
var editor1:String
var mainImage:NSData
or
2- Do not replace the properties in the function since you're going to init with your new object anyway. Example:
class func build(json:JSON) -> Article? {
if let img = json["images"]["main"].rawData() {
return Article(
id: Int(json["id"].string),
title: json["title"].string,
subtitle: json["subtitle"].string,
editor1: json["editor1"].string,
mainImage: img)
} else {
// ...
return nil
}
}
Update 2
If the compiler complains about "not marked with try", do the "if let" with "try?":
if let img = try? json["images"]["main"].rawData() {
Explanation: SwiftyJSON may have changed this method without updating the documentation yet (or I didn't find it). It previously returned an Optional and now seems to "throw" instead. Using "try?" lets you make it an Optional again.

Couple things look off.
You should be returning Article? since there is a chance you're going to be returning nil.
You've got a number of extraneous commas after things like id = Int(json["id.... Get rid of them.
That closure seems unnecessary. Simply check for the valid JSON first, if its bad return nil, otherwise build it up and return the article.

You have ,s where they shouldn't be. Try using this:
class func build(json:JSON) -> Article {
id = Int(json["id"].string)
title = json["title"].string
subtitle = json["subtitle"].string
editor1 = json["editor1"].string
mainImage = json["images"]["main"].rawData() {
return Article(
id: id,
title: title,
subtitle: subtitle,
editor1: editor1,
mainImage: mainImage)
}
}

Related

Issue with adding Data to an AnyObject Var so that I could make native ads work

for postdata in postdata {
if index < tableViewItems.count {
tableViewItems.insert(postdata, at: index)
index += adInterval
} else {
break
}
}
I'll need to add both PostData ads and Native Ads on the same AnyObject Var for me to get it to work and I can't find a way to add the Post Data because it says an error appears saying "Argument type 'PostData' expected to be an instance of a class or class-constrained type." Assistance would be very much appreciated, thank you.
edit 1
class Ad {
var postimage: String!
var publisher: String!
var timestring: String!
var timestamp = Date().timeIntervalSince1970
}
class PostDataAd: Ad {
// Declare here your custom properties
struct PostData1
{
var postimage: String
var publisher: String
var timestring : String
var timestamp = Date().timeIntervalSince1970
}
}
class NativeAd: Ad {
// Declare here your custom properties
struct NativeAd
{
var nativeAds: [GADUnifiedNativeAd] = [GADUnifiedNativeAd]()
}
}
My model class to merge both Data into one AnyObject Var
and then trying to append the Data from Firebase by doing this
var ads: [Ad] = [PostDataAd(), NativeAd()]
let postList = PostDataAd.PostData1(postimage: postimage, publisher:
postpublisher, timestring: postid, timestamp: timestamp)
self.ads.insert(postList, at:0)
an error occurs saying Cannot convert value of type 'PostDataAd.PostData1' to expected argument type 'Ad'
I hope I got what you want correctly. So basically you have two objects which you want to store in one array, under AnyObject. If that is correct, I recommend you to go in a bit of different direction. It is a nice example where you can use subclassing. You can declare a class called Ad, where you define the common properties which will be true for both PostDataAds and NativeAds.
class Ad {
// Add here the common elements you want to retrieve from both classes
var name: String = ""
}
After you define your PostDataAds and NativeAds inheriting from Ad:
class PostDataAd: Ad {
// Declare here your custom properties
}
class NativeAd: Ad {
// Declare here your custom properties
}
And if you want to define an array with two types of objects you can go:
let ads: [Ad] = [PostDataAd(), NativeAd()]
When retrieving you can check their type:
if let item = ads.first as? PostDataAd {
// The Ad is a PostDataAd
} else if let item = ad as? NativeAd {
// The Ad is a NativeAd
}
Or at some cases you don't even how to know the exact type, as you can access the properties defined in Ad without checking.
Update:
First of all your PostData1 and Ad objects are the same, you don't need to duplicate them. If you really want to have two classes you can inherit PostData1 from Ad.
class Ad {
var postimage: String
var publisher: String
var timestring: String
var timestamp = Date().timeIntervalSince1970
// You can add timestamp here also if you wish
init(postimage: String, publisher: String, timestring: String) {
self.postimage = postimage
self.publisher = publisher
self.timestring = timestring
}
}
class PostDataAd: Ad {
// Define some custom properties
}
And if you want to append PostData to the [Ad] array, you would do the following:
var ads: [Ad] = []
// Replace with your values
let postList = PostDataAd(postimage: "", publisher: "", timestring: "")
ads.insert(postList, at: 0)
// Appending NativeAd works also
let nativeAdd = NativeAd(postimage: "", publisher: "", timestring: "")
ads.append(nativeAdd)

Return a single String from a dictionary using uniqueKeysWithValues

Goal of the code:
To assign a struct dictionary with Strings as Keys and String Arrays as values to a variable and then pull one (can be at random) specific String key value in the String Array and return that one String element in the underlying String Array so that it can be used elsewhere (potentially assigned to a label.text)
Essentially (please reference code below), I want to access one value at random in myDictionary using a specific key ("keyOne"), and pull, let's say, "Value2" then return only the string "Value2" from the underlying String Array associated with "keyOne" using indexing.
Errors are in the code below.
The issue I'm thinking is that I haven't figured out how to turn my final var Testing = dict["keyOne"] into an Int compatible index... if it was an index, the code would pull an Int value and the corresponding String from the three Strings in the underlying value array (due to the three String values associated with "keyOne").
Also, variableView() just inherits the datasource from several other containers, but the var dataSource : Structure? is the main reference, so that is what I included.
Code so far:
let myDictionary = [Structure(name: "keyOne", text: ["Value1", "Value2", "Value3"]), Structure(name: "keyTwo", text: ["Value4", "Value5", "Value6"])]
lazy var dict = Dictionary(uniqueKeysWithValues: myDictionary.lazy.map { ($0.name, $0.text) })
struct Structure: Hashable {
var name: String
var text: [String]
init(name: String, text: [String]){
self.name = name
self.text = text
}
}
func variable(at index: Int) -> variableView {
let variable = variableView()
var Testing = dict["keyOne"]
variable.dataSource = Testing![index] <- Cannot assign value of type 'String' to type 'structure'
return variable
var dataSource : Structure? {
didSet {
label.text = "This is a test"
} else {
// n/a
}
}
Please note that the error message is above in the code for variable.dataSource = Testing![index].
I am also suspecting that my issue lies in the "looping" logic of how I am assigning a variable with a struct, to a datasource which references that same struct.
Any help is appreciated as I have been stuck on this for legitimately a week (I truly have exhausted every single StackOverflow answer/question pair I could find).
THANK YOU!
EDIT:
I found this documentation to assist me greatly with this, and I recommend anyone with a similar question as mine to reference this: https://swift.org/blog/dictionary-and-set-improvements/
Given the question and the discussion in the comments I would add a mutating func to the struct that removes and returns a random string
mutating func pullText() -> String? {
guard let index = text.indices.randomElement() else {
return nil
}
return text.remove(at: index)
}
Example
if let index = myDictionary.firstIndex(where: { $0.name == "keyOne" }),
let text = myDictionary[index].pullText() {
someLabel.text = text
}
Here is another example based on the code in the question
Assuming VariableView looks something like this
struct VariableView: View {
var dataSource : Structure?
var word: String?
var body: some View {
Text(word ?? "")
}
}
Then the func variable can be changed to
func variable() -> VariableView {
var variable = VariableView()
if let index = dict.firstIndex(where: { $0.name == "keyOne" }) {
variable.dataSource = dict[index]
variable.word = dict[index].pullText()
}
return variable
}

Turn simple variable into an array (Swift)

I had this variable in a my viewController var category : QCategoryy?
and I decided to turn it into an array so I created this: var categories: [QCategoryy?]? = []
but after that i get all errors like this
"Value of type '[QCategoryy?]' has no member 'name'"
in this line self.title = categories?.nameand other lines like that.
Why i get these errors and how can i solve it?
First to turn the var into an array all worked well.
This is the class of QCategoryy
struct QCategoryy {
var name:String
var isSelected = false
init(name:String) {
self.name = name
}
}
extension QCategoryy: ExpressibleByStringLiteral {
init(stringLiteral value: String) {
self.name = value
}
init(unicodeScalarLiteral value: String) {
self.init(name: value)
}
init(extendedGraphemeClusterLiteral value: String) {
self.init(name: value)
}
}
Very simple, you have converted that variable to array. So, first of all you have to give index of the array to invoke the value of object at specific index.
like given below
for index in 0..<10 {
if let category = categories[index] {
self.title = category.name
}
}
conditional binding will prevent from crashing app if categories[0] is nil.
UPDATE
To prevent array index out of bound in loop. You can use below example.
for category in categories {
title = category.name
}
Here categories is an array. So do something like this
self.title = categories?[0]?.name
categories in now an array. So you'll have to refer to some object of it whose name you name you're referring to.
More like, categories?[index]?.name
Or you can use forEach:
categories?.forEach{ value in
self.title = value?.name
}

iOS Swift - SharkORM won't commit

I'm using SharkORM on iOS Swift project and I'm having problem with a specific object. I have other objects in the project that works, but this one.
My class is like this:
import Foundation
import SharkORM
class Exam: SRKObject {
dynamic var serverId: NSNumber?
dynamic var type: String?
dynamic var when: String?
dynamic var file: String?
dynamic var filename: String?
dynamic var url: String?
func toJson() -> [String:Any?] {
return [
"name" : type,
"date" : when,
"serverId" : serverId,
"file" : file,
"filename" : filename,
"url" : url,
"id" : id
]
}
static func fromJson(_ json: [String:Any?]) -> Exam {
let exam = Exam()
exam.id = json["id"] as? NSNumber ?? NSNumber(value: 0)
exam.type = json["name"] as? String ?? ""
exam.file = json["file"] as? String ?? ""
exam.filename = json["filename"] as? String ?? ""
exam.url = json["url"] as? String ?? ""
exam.serverId = json["serverId"] as? NSNumber ?? NSNumber(value: 0)
exam.when = json["date"] as? String ?? ""
return exam
}
}
I add to an array objects that needs to be saved and after user press save button, the app starts committing it.
// save exams
for exam in self.examsToSave {
if !exam.commit() {
print("Error commiting exam.")
}
}
if let rs = Exam.query().fetch() {
print("exams: \(rs.count)")
}
The commit method returns true and I added a print right after it finishes committing and result is zero.
Any idea?
I found out the problem right after post it. In my text here, my variable "when" was colored like a keyword. I just changed the name to "whenDate" and it started committing. Weird it didn't show up any error or a crash. Anyway, a variable named "when" is not allowed inside a SRKObject.
Given same Commit problem, figured best to keep to topic here. And I've spent number of hours trying to debug this so thought I'd try this:
I have a simple class (and overly simplified but tested as provided here):
class user: SRKObject {
#objc dynamic var name: String = ""
}
(No, no odd syntax coloring on the object property names.)
And I do the following (simplified test case), first defining
public var currUser = user()
Then in a function:
let users = user.query().fetch() as! [user]
if users.count > 0 {
currUser = users[0]
NSLog("Num users \(users.count) name \(currUser.name)")
} else {
self.currUser.name = "T1 User"
if !self.currUser.commit() {
print ("Failed to commit")
}
else {
let u = user.query().fetch()
print("Num users \(u.count)")
}
}
The commit() call succeeds -- at least I don't get the "Failed to commit" message. However, I do get zero count in the last fetch().
Viewing the DB file (in Simulator) from a "DB Browser for SQLite" shows the DB is created fine but the "user" record is not in there, and neither is the "committed" data.
BTW when I had this code in SRKTransaction.transaction, it DID fall into the failure (rollback) block, so yes, did get a transaction error, but tracking that down will be next.
In the meantime, appreciate in advance any help given this case as presented should work.
#retd111, I copied and pasted your code and got the same error.
Then, I moved the currUser to a local var, like this:
override func viewDidLoad() {
super.viewDidLoad()
var currUser: user? = nil
let users = user.query().fetch() as! [user]
if users.count > 0 {
currUser = users[0]
NSLog("Num users \(users.count) name \(currUser!.name)")
} else {
currUser = user()
currUser!.name = "T1 User"
if !currUser!.commit() {
print ("Failed to commit")
}
else {
let u = user.query().fetch()
print("Num users \(u?.count ?? 0)")
}
}
}
It works without problems.
For some reason, if you instantiate the currUser as a class member variable, as your example:
public var currUser = user()
it won't work.

What is the best way to connect Realm and SwiftBond

I love Realm and I love Bond. Both of them makes app creation a joy. So I was wondering what is the best way to connect Realm and Bond? In Realm we can store basic types such as Int, String, e.g. But in Bond we work with Dynamics and Bonds. The only way that I found to connect Realm and Bond is following:
class TestObject: RLMObject {
dynamic var rlmTitle: String = ""
dynamic var rlmSubtitle: String = ""
var title: Dynamic<String>
var subtitle: Dynamic<String>
private let titleBond: Bond<String>!
private let subtitleBond: Bond<String>!
init(title: String, subtitle: String) {
self.title = Dynamic<String>(title)
self.subtitle = Dynamic<String>(subtitle)
super.init()
self.titleBond = Bond<String>() { [unowned self] title in self.rlmTitle = title }
self.subtitleBond = Bond<String>() { [unowned self] subtitle in self.rlmSubtitle = subtitle }
self.title ->> titleBond
self.subtitle ->> subtitleBond
}
}
But it surely lacks simplicity and elegance and produces a lot of boiler code. Is there any way to do this better?
With Realm supporting KVO and Bond 4, you can extend Realm objects to provide Observable variants. There is some boilerplate to it, but it's clean and without hacks.
class Dog: Object {
dynamic var name = ""
dynamic var birthdate = NSDate(timeIntervalSince1970: 1)
}
extension Dog {
class ObservableDog {
let name: Observable<String>
let birthdate: Observable<NSDate>
init(dog: Dog) {
name = Observable(object: dog, keyPath: "name")
birthdate = Observable(object: dog, keyPath: "birthdate")
}
}
func observableVariant() -> Dog.ObservableDog {
return ObservableDog(dog: self)
}
}
Than you'll be able to do:
let myDog = Dog().observableVariant()
myDog.name.observe { newName in
print(newName)
}
myDog.name.bindTo(nameLabel.bnd_text)
realm.write {
myDog.name.value = "Jim"
}
You could likely simplify the pattern you're using somewhat if you used
default property values:
class TestObject: RLMObject {
dynamic var rlmTitle = ""
dynamic var rlmSubtitle = ""
var title: Dynamic<String>
var subtitle: Dynamic<String>
private let titleBond = Bond<String>() { [unowned self] title in self.rlmTitle = title }
private let subtitleBond = Bond<String>() { [unowned self] subtitle in self.rlmSubtitle = subtitle }
init(title: String, subtitle: String) {
self.title = Dynamic<String>(title)
self.subtitle = Dynamic<String>(subtitle)
self.title ->> titleBond
self.subtitle ->> subtitleBond
super.init()
}
}
You could remove another two lines of code if Bond's ->> operator returned the
left value so you could do self.title = Dynamic<String>(title) ->> titleBond.
But ultimately, until Swift has native language support for KVO or an equivalent observation mechanism, you're sadly going to have to write some amount of boilerplate.
I've been thinking about this for three days and came up with nearly perfect solution, which does not employ any boilerplate code. First of all I have created a super class for a realm model's wrapper:
class BondRealmBaseClass {
private var realmModel: RLMObject!
private let realm = RLMRealm.defaultRealm()
private var bonds = NSMutableArray()
init(){
realmModel = createRealmModel()
realm.beginWriteTransaction()
realm.addObject(realmModel)
realm.commitWriteTransaction()
createBonds()
}
init(realmModel: RLMObject){
self.realmModel = realmModel
createBonds()
}
func createBondFrom<T>(from: Dynamic<T>, toModelKeyPath keyPath: String){
from.value = realmModel.valueForKeyPath(keyPath) as T
let bond = Bond<T>() { [unowned self] value in
self.realm.beginWriteTransaction()
self.realmModel.setValue(value as NSObject, forKey: keyPath)
self.realm.commitWriteTransaction()
}
from ->| bond
bonds.addObject(bond)
}
//MARK: - Should be overriden by super classes
func createBonds(){ fatalError("should be implemented in supreclass") }
func createRealmModel() -> RLMObject{ fatalError("should be implemented in supreclass") }
}
After that for each realm model I create two classes, first is the actual realm model, which stores all properties:
class RealmTodoModel: RLMObject {
dynamic var title = ""
dynamic var date = NSDate()
}
and a second one is the wrapper around realm model:
class TodoModel : BondRealmBaseClass{
let title = Dynamic("")
let date = Dynamic(NSDate())
override func createBonds(){
createBondFrom(title, toModelKeyPath: "title")
createBondFrom(date, toModelKeyPath: "date")
}
override func createRealmModel() -> RLMObject { return RealmTodoModel() }
}
And this two classes is actually all is needed to link Realm and Bond: creating new TodoModel will actually add to Realm new RealmTodoModel and all changes made with TodoModel's title and date will be automatically saved to corresponding Realm model!
EDIT
I added some functionality and posted this as a framework on GitHub. Here is the link.

Resources