Can't cast a value from a subclass Swift - swift

I made a lot of research but I didn't find an answer to my question. Others talk about basic issues with Swift classes. Still I have an issue with my own classes. I also read courses about classes but it didn't help me.
I have two classes; one of them inherit from the other.
Here is my classes code :
class GlobalUser {
var uid: String!
var publicName: String!
var pushID: String!
var firstName: String!
var lastName: String!
var example1: [String:String]!
var fullName: String! {
get {
return firstName + " " + lastName
}
}
init(document: DocumentSnapshot) {
guard let data = document.data() else {
print("Missing user information during initialization.")
return
}
self.uid = document.documentID
self.publicName = (data["publicName"] as? String)!
self.pushID = (data["pushID"] as? String)!
self.example1 = (data["example1"] as? [String : String])!
let name = data["name"] as? [String:String]
self.firstName = (name!["firstName"])!
self.lastName = (name!["lastName"])!
}
}
class InterestingUser: GlobalUser {
var code: Int?
var example: [String:String]?
var number: Int! {
get {
return example.count
}
}
override init(document: DocumentSnapshot) {
super.init(document: document)
}
}
And then I try to cast a GlobalUser to a InterestingUser like this :
if let interestingUser = user as? InterestingUser {
...
}
But this cast always fails...
Any idea? Thanks in advance for your help.

The error you're experiencing is due to this statement from your question: 'And then I try to cast a GlobalUser to a InterestingUser like this...' and is due to inheritance.
Your GlobalUser class is the superclass. Your InterestingUser is a subclass of your GlobalUser.
So your InterestingUser class 'knows' about the GlobalUser because it is it's parent and you can cast InterestingUser as? GlobalUser but not the other way around.
Example:
if let interstingUser = InterestingUser() as? GlobalUser {
// this will succeed because InterestingUser inherits from GlobalUser
}
if let globalUser = GlobalUser() as? InterestingUser {
// this will fail because GlobalUser is not a subclass of InterestingUser
}
Here's some playground code for you to test with:
class GlobalUser {
}
class InterestingUser: GlobalUser {
}
class Demo {
func comparison() {
let interesting = InterestingUser()
let global = GlobalUser()
if let intere = interesting as? GlobalUser {
print("Interesting is global as well")
}
if let global = global as? InterestingUser {
print("Global is interesting")
}
}
}
let demo = Demo()
demo.comparison()
// prints 'Interesting is global as well'

Related

Swift Firebase get Data to Class Object

I want to get the following structure (screenshot of Firebase Database):
In chats I have the id of the chat. There are the users with the child userid and the values of id and name.
At first I look for the chats which a user have and want to get then the details of the chatId (users with their id and name)
I have the following class in Swift:
class Chat {
var chatId: String!
var userIds: [String]!
var userNames: [String]!
}
I have the following code to get the details, but I get not the userIds or userNames from the chatId:
func getChatsFromFirebase() {
self.ref = Database.database().reference()
self.ref?.child("users").child(userdefaults.getUserId()).child("chats").observe(.childAdded, with: { (snapshot) in
let chat = Chat()
chat.chatId = snapshot.key
chat.userIds = []
chat.userNames = []
//print(chat.chatId)
for i in 0..<self.chats.count {
let usersRef = self.ref.child("chats").child(self.chats[i].chatId).child("users").observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
for userid in value!.allKeys as! [String] {
let usersdetailsRef = self.ref.child("chats").child(self.chats[i].chatId).child("users").child(userid).queryOrdered(byChild: "name").observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
//print(value)
let id = value?["id"] as? String ?? ""
let name = value?["name"] as? String ?? ""
//print( id + ": " + name)
chat.userIds.append(id)
chat.userNames.append(name)
})
}
})
}
self.chats.append(chat)
self.tableView.reloadData()
})
}
I am very new to the Firebase topic. Can someone help me here?
Thanks.
Well You need to change your datamodel first. You dont need to store id value in , 12345 in this case. you can already fetch the key. Also, in /users/chats, you just can just save the chat id as either chat1 : IBDrbfku887BLIY or IBDrbfku887BLIY : true. You can always fetch them through value or the key respectively.
And in your chat document, you just need to reference the user id, i.e just get them and store them as user1 and user2. You can add more users if your usecase requires more.
Reconfigure your Data Model as follows.
Now You need 2 Objects Users and Chats as follows :
Users.swift
class User : NSObject {
private var _name: String!
private var _username: String!
private var _userid: String!
private var _userRef: DatabaseReference!
var name: String! {
get {
return _name
} set {
_name = newValue
}
}
var username : String! {
get {
return _username
} set {
_username = newValue
}
}
var userid: String! {
get {
return _userid
} set {
_userid = newValue
}
}
var userRef: DatabaseReference! {
get {
return _userRef
} set {
_userRef = newValue
}
}
init(userid: String, userData: Dictionary<String, Any>){
self._userid = userid
_userRef = Database.database().reference().child(_userid)
if let username = userData["username"] as? String {
self._username = username
}
if let name = userData["name"] as? String {
self._name = name
}
}
}
Chats.swift
class Chat : NSObject {
private var _chatid: String!
private var _user1: String!
private var _user2: String!
private var _chatRef: DatabaseReference!
var user1: String! {
get {
return _user1
} set {
_user1 = newValue
}
}
var user2 : String! {
get {
return _user2
} set {
_user2 = newValue
}
}
var chatid: String! {
get {
return _chatid
} set {
_chatid = newValue
}
}
var chatRef: DatabaseReference! {
get {
return _chatRef
} set {
_chatRef = newValue
}
}
init(chatid: String, chatData: Dictionary<String, Any>){
self._chatid = chatid
_chatRef = Database.database().reference().child(_chatid)
if let user = chatData["users"] as? Dictionary<String, Any> {
if let user1 = user["user1"] as? String {
self._user1 = user1
}
if let user2 = user["user2"] as? String {
self._user2 = user2
}
}
}
}
The major issue/or an overlooked issue here is the type of the data. In the /users, you id 12345 will be of type String. But when you fetch the same from /chats, it returns as Int. This downloads the value but never converts it. Always take care while seeding/testing your data.
To fetch the user's credentials just reference that through another query. This is what you can do :
var allUsers = [User]()
var allChats = [Chat]()
func viewDidLoad() {
super.viewDidLoad()
fetchAllChats()
}
func getUser(from userId: String, completion: #escaping (User) -> Void) {
Database.database().reference().child("users").child(userId).observeSingleEvent(of: .value, with: { snapshot in
if let datasnap = snapshot.value as? Dictionary<String, Any> {
let user = User(userid: userId, userData: datasnap)
completion(user)
}
})
}
func fetchAllChats() {
Database.database().reference().child("chats").observeSingleEvent(of: .value, with: { snapshot in
allChat.removeAll()
if let snapshot = snapshot.value as? Dictionary<String, Any> {
for snap in snapshot {
if let chatd = snap.value as? Dictionary<String, Any> {
let chat = Chat(chatid: snap.key, chatData: chatd)
self.allChats.append(chat)
}
}
}
// collectionview.reloadData() <--------- only if required.
})
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let chatData = allChats[indexPath.row]
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CellId, for: indexPath) as! Cell
getUser(from: chatData.user1) { user in
cell.label.text = user.usernme
}
return cell
}

Instance Member Cannot Be Used On Type - Firebase

I am new to Swift and I am following a tutorial on how to create a social media app with Xcode and Firebase. However, I got this error:
Instance member 'database' cannot be used on type 'DatabaseReference'
Here is my code:
import Foundation
import Firebase
class Post {
private var _username: String!
private var _userImg: String!
private var _postImg: String!
private var _likes: Int!
private var _postKey: String!
private var _postRef: DatabaseReference
var username: String {
return _userImg
}
var postImg: String {
get {
return _postImg
}set {
_postImg = newValue
}
}
var likes: Int {
return _likes
}
var postKey: String {
return _postKey
}
init(imgUrl: String, likes: Int, username: String, userImg: String) {
_likes = likes
_postImg = postImg
_username = username
_userImg = userImg
}
init(postKey: String, postData: Dictionary<String, AnyObject>) {
_postKey = postKey
if let username = postData["username"] as? String {
_username = username
}
if let userImg = postData["userImg"] as? String{
_userImg = userImg
}
if let postImage = postData["imageUrl"] as? String {
_postImg = postImage
}
if let likes = postData["likes"] as? Int {
_likes = likes
}
_postRef = DatabaseReference.database().reference().child("posts")
}
}
I get my error on the third to last line that says:
_postRef = DatabaseReference.database().reference().child("posts")
The database property is an instance type, meaning it must be referenced by an instance of DatabaseReference. Your call to DatabaseReference.database is accessing for a class, or static, type. You need to change your call to an instance of DatabaseReference.
Presumably, you need to initialize an instance of DatabaseReference. I don't know Firebase to know what is required for that, but that will take care of your issue.
Essentially:
let databaseReference = DatabaseReference() // Likely won't work, but some init method here will
_postRef = databaseReference.database()... // Whatever you need here
It sounds like you're looking for either:
_postRef = Database.database().reference("posts")
Or
_postRef = DatabaseReference.root.child("posts")

Swift 2.2 singleton

I am new in Swift. I am trying to parse some JSON data from web service and want a singleton class of user.But I got stuck to create the singleton. Here is my code:
import Foundation
class User {
private var success: String
private var userId: String
private var name: String
private var gender: String
private var email: String
private var userObject = [User]()
class var sharedInstane:User {
struct Singleton {
static var onceToken: dispatch_once_t = 0
static var instance:User? = nil
}
dispatch_once(&Singleton.onceToken){
Singleton.instance = User()
}
return Singleton.instance!
}
private init(success: String, userId: String, name: String, gender: String, email: String)
{
self.success = success
self.userId = userId
self.name = name
self.gender = gender
self.email = email
}
convenience init(dictionary: [String:AnyObject]) {
let success = dictionary["success"] as? String
let userId = dictionary["userId"] as? String
let name = dictionary["name"] as? String
let gender = dictionary["gender"] as? String
let email = dictionary["email"] as? String
self.init(success: success!, userId: userId!, name: name!, gender: gender!, email: email!, )
}
func callWebserviceToLoadUserInfo (url:String, param:[String:AnyObject],completeHandler:(Bool?,String) -> ())
{
let connection = ServerConnection()
connection.getJSONDataForWebService(url, params: param) { (response, error) in
// code goes here
var responseDict = response as! [String : AnyObject]
responseDict = responseDict["responseDict"] as! [String : AnyObject]
if responseDict["success"] as! String == "1" {
for dict in responseDict {
let user = User(dictionary: (dict as! [String:AnyObject]))
self.userObject.append(user)
}
print("user : \(self.userObject[0].name)")
}else{
// error goes here
}
}
}
}
Can any one please help me how should I do this code?
The singleton in the single line sample code.
class TheOneAndOnlyKraken {
static let sharedInstance = TheOneAndOnlyKraken()
private init() {} //This prevents others from using the default '()' initializer for this class.
}
For more details.
Using Krakendev's single-line singleton code, cited by Maheshwar, and turning your convenience init into an instance function to be called with User.sharedInstance.initialize(dictionary):
import Foundation
class User {
// Here you declare all your properties
// "private var" and all that jazz
static let sharedInstance = User()
private init() {
// If you have something to do at the initialization stage
// you can add it here, as long as it does not involve
// arbitrary values that you would pass as parameters.
}
func initialize(dictionary: [String:AnyObject]) {
// Transfer the values of the dictionary to each `self.property`.
// Be careful while using `as?` as you may have to deal with
// optionals. No need to call `self.init` at the end, because
// this is now a regular `func`.
}
// Add the rest of your stuff here
}
One note about how you were working inside of that convenience initializer: if you do property = SomeClass.someMethod().someProperty as? SomeType, then property will be of type SomeType?, or Optional(SomeType). According to The Swift Programming Language,
The conditional form, as?, returns an optional value of the type you are trying to downcast to.
While User was not instantiated at least one time sharedInstance will return nil. After the first successful instantiation of the User, sharedInstance starts return it and that's became impossible to instantiate another one User as singleton pattern requires it. Consider this:
class User {
private static var sharedUser: User?
class var sharedInstance: User? {
return sharedUser
}
private init(success: String, userId: String, name: String, gender: String, email: String)
{
//User initialization code here
User.sharedUser = self
}
convenience init?(dictionary: [String:AnyObject]) {
guard User.sharedUser == nil else {
return nil
}
//dictionary parsing code is here
self.init(success: success!, userId: userId!, name: name!, gender: gender!, email: email!)
}
}
Client's code:
User.sharedUser
//return nil
let dict: [String:AnyObject] = ["success": "success", "userId":"userId", "name":"name", "gender":"gender","email":"email"]
User(dictionary: dict)
//creates User
User.sharedUser
//returns just created user
User(dictionary: dict)
//return nil
You should think about making this two classes, so that User is your model class and then create a manager to handle all the users (which seems to be your goal).
So in User remove the sharedInstane part and create a second singleton class, e.g. called UserManager, with the standard way to create a singleton in Swift. Then you can keep the way you're creating your user and in the end just assign it to the singleton:
class UserManager {
static let sharedInstance = UserManager()
var users = [User]()
}
// in your code:
...
for dict in responseDict {
let user = User(dictionary: (dict as! [String:AnyObject]))
UserManager.sharedInstance.users.append(user)
}
...

PFUser to custom model

I have a model I built for my PFUser from Parse:
import Foundation
import Parse
class TradeUser : PFUser {
override class func initialize() {
self.registerSubclass()
}
var userID : String {
get {return objectForKey("objectId") as! String}
set { setObject(newValue, forKey: "objectId") }
}
var emailAddress : String {
get {return objectForKey("email") as! String}
set { setObject(newValue, forKey: "email") }
}
var firstName : String {
get {return objectForKey("firstName") as! String}
set {setObject(newValue, forKey: "firstName")}
}
var lastName : String {
get {return objectForKey("lastName") as! String}
set {setObject(newValue, forKey: "lastName")}
}
var primaryQueue : String {
get {return objectForKey("primaryQueue") as! String}
set {setObject(newValue, forKey: "primaryQueue")}
}
var image : PFFile {
get { return self["profileImage"] as! PFFile }
set { self["profileImage"] = newValue }
}
}
But when I query to get a user, and try to cast it as that, I get a "Could not cast value of type 'PFUser' (0x107555928) to 'ShiftSwap.TradeUser' (0x10754e910)."
I thought that TradeUser would be the same value type as PFUser, since it is a PFUser cast? I'm a little confused, and any clarification would be appreciated!
EDIT:
#IBAction func chatBarButton(sender: AnyObject) {
let postingUser = self.object?.objectForKey("userID") as! String
let tradeUserQuery = PFUser.query()
tradeUserQuery?.whereKey("objectId", equalTo: postingUser)
let trader = tradeUserQuery?.getFirstObject() as! TradeUser
var chatVC = MessagesViewController()
chatVC.currentUser = TradeUser.currentUser()
print(TradeUser.currentUser())
chatVC.otherUser = trader
self.navigationController?.pushViewController(chatVC, animated: true)
}
You are forcing a downcast which is impossible. You cannot downcast an instance of a base class PFUser to a derived one TradeUser.

How to create objects from SwiftyJSON

I have a code, that parses JSON's list of questions and I can get every property. How can I iterate through the whole file and for each question create an object ?
class ViewController: UIViewController {
var hoge: JSON?
override func viewDidLoad() {
super.viewDidLoad()
let number = arc4random_uniform(1000)
let url = NSURL(string: "http://www.wirehead.ru/try-en.json?\(number)")
var request = NSURLRequest(URL: url!)
var data = NSURLConnection.sendSynchronousRequest(request, returningResponse: nil, error: nil)
if data != nil {
hoge = JSON(data: data!)
let level = hoge!["pack1"][0]["level"].intValue
let questionText = hoge!["pack1"][0]["questionText"].stringValue
let answer1 = hoge!["pack1"][0]["answer1"].stringValue
let answer2 = hoge!["pack1"][0]["answer2"].stringValue
let answer3 = hoge!["pack1"][0]["answer3"].stringValue
let answer4 = hoge!["pack1"][0]["answer4"].stringValue
let correctAnswer = hoge!["pack1"][0]["correctAnswer"].stringValue
let haveAnswered = hoge!["pack1"][0]["haveAnswered"].boolValue
}
}
}
my model of Question which objects I want to create below
class Question {
var level : Int?
var questionText : String?
var answer1 : String?
var answer2 : String?
var answer3 : String?
var answer4 : String?
var correctAnswer : String?
var haveAnswered : Bool = false
init(level: Int, questionText:String, answer1:String, answer2:String, answer3:String, answer4:String, correctAnswer: String, haveAnswered:Bool) {
self.level = level
self.questionText = questionText
self.answer1 = answer1
self.answer2 = answer2
self.answer3 = answer3
self.answer4 = answer4
self.correctAnswer = correctAnswer
self.haveAnswered = false
}
}
This is how I would approach the problem.
Step 1
Since your init inside Question does receive non optional objects, I had the feeling that the properties of Questions should be non optional too. I also converted the properties from var to let (tell me if I am wrong).
Step 2
This is the refactored Question class. As you can see I added a class method build that receive a JSON (a SwiftyJSON) and returns a Question (if the json contains correct data), nil otherwise.
Right now I cannot do this with a failable initializer.
extension String {
func toBool() -> Bool? {
switch self.lowercaseString {
case "true", "1", "yes" : return true
case "false", "0", "no" : return false
default: return nil
}
}
}
class Question {
let level: Int
let questionText: String
let answer1: String
let answer2: String
let answer3: String
let answer4: String
let correctAnswer: String
let haveAnswered: Bool
init(level: Int, questionText:String, answer1:String, answer2:String, answer3:String, answer4:String, correctAnswer: String, haveAnswered:Bool) {
self.level = level
self.questionText = questionText
self.answer1 = answer1
self.answer2 = answer2
self.answer3 = answer3
self.answer4 = answer4
self.correctAnswer = correctAnswer
self.haveAnswered = false
}
class func build(json:JSON) -> Question? {
if let
level = json["level"].string?.toInt(),
questionText = json["questionText"].string,
answer1 = json["answer1"].string,
answer2 = json["answer2"].string,
answer3 = json["answer3"].string,
answer4 = json["answer4"].string,
correctAnswer = json["correctAnswer"].string,
haveAnswered = json["haveAnswered"].string?.toBool() {
return Question(
level: level,
questionText: questionText,
answer1: answer1,
answer2: answer2,
answer3: answer3,
answer4: answer4,
correctAnswer: correctAnswer,
haveAnswered: haveAnswered)
} else {
debugPrintln("bad json \(json)")
return nil
}
}
}
Step 3
Now let's look at viewDidLoad.
func viewDidLoad() {
super.viewDidLoad()
let number = arc4random_uniform(1000)
if let
url = NSURL(string: "http://www.wirehead.ru/try-en.json?\(number)"),
data = NSURLConnection.sendSynchronousRequest(NSURLRequest(URL: url), returningResponse: nil, error: nil) {
// line #a
let rootJSON = JSON(data: data)
// line #b
if let questions = (rootJSON["pack1"].array?.map { return Question.build($0) }) {
// now you have an array of optional questions [Question?]...
}
}
}
At line #a I put inside rootJSON the whole data received from the connection (converted into JSON).
What happen at line #b?
Well I try to access the array located inside "pack1".
rootJSON["pack1"].array?
If the array exists I run the map method. This will extract each cell of the array and I will be able to refer to it with the $0 parameter name inside the closure.
Inside the closure I use this json block (that should represent a question) to build a Question instance.
The result will be an array of Question?. There could be ill values if some son data was not valid. If you want I can show you how to remove the nil values from this array
I could not try the code with real data, hope this helps.
Step 1. We will create one protocol with one constructor method in it and Model class
protocol JSONable {
init?(parameter: JSON)
}
class Style: JSONable {
let ID :String!
let name :String!
required init(parameter: JSON) {
ID = parameter["id"].stringValue
name = parameter["name"].stringValue
}
/* JSON response format
{
"status": true,
"message": "",
"data": [
{
"id": 1,
"name": "Style 1"
},
{
"id": 2,
"name": "Style 2"
},
{
"id": 3,
"name": "Style 3"
}
]
}
*/
}
Step 2. We will create extension of JSON which will convert JSON to model class type object
extension JSON {
func to<T>(type: T?) -> Any? {
if let baseObj = type as? JSONable.Type {
if self.type == .array {
var arrObject: [Any] = []
for obj in self.arrayValue {
let object = baseObj.init(parameter: obj)
arrObject.append(object!)
}
return arrObject
} else {
let object = baseObj.init(parameter: self)
return object!
}
}
return nil
}
}
Step 3. Use code with Alamofire or other code.
Alamofire.request(.GET, url).validate().responseJSON { response in
switch response.result {
case .success(let value):
let json = JSON(value)
var styles: [Style] = []
if let styleArr = json["data"].to(type: Style.self) {
styles = styleArr as! [Style]
}
print("styles: \(styles)")
case .failure(let error):
print(error)
}
}
I hope this will be useful.
Please refer to this link for more information on this.
https://github.com/SwiftyJSON/SwiftyJSON/issues/714
You can use SwiftyJSONModel which was specifically designed for this purpose. So in your case the model would be like this:
class Question: JSONObjectInitializable {
enum PropertyKey: String {
case level, questionText
case answer1, answer2, answer3, answer4
case correctAnswer, haveAnswered
}
var level : Int?
var questionText : String?
var answer1 : String?
var answer2 : String?
var answer3 : String?
var answer4 : String?
var correctAnswer : String?
var haveAnswered : Bool = false
required init(object: JSONObject<PropertyKey>) throws {
level = object.value(for: .level)
questionText = object.value(for: .questionText)
answer1 = object.value(for: .answer1)
answer2 = object.value(for: .answer2)
answer3 = object.value(for: .answer3)
answer4 = object.value(for: .answer4)
correctAnswer = object.value(for: .correctAnswer)
haveAnswered = object.value(for: .haveAnswered) ?? false
}
}
And then do like this:
let rootJSON = JSON(data: data)
let questions = rootJSON.arrayValue.flatMap { try? Question(json: $0) }
The framework gives you several nice features:
All the keys are stored in separated enum PropertyKey
No boilerplate as stringValue, intValue etc.
If JSON will be invalid, framework will give a verbose error and you will immediately see what exactly went wrong
for (item, content) in hoge {
let level = content["level"].intValue
}
that should work