Looking for assistance since I am missing something! I have defined a new object called "User".
class User: NSObject {
var userID: String!
var fullname: String!
var imagePath: String!
}
Now I want to iterate through each object one at a time checking the values of some of the fields.
func retrieveUsers() {
let ref = Database.database().reference()
ref.child("users").queryOrderedByKey().observeSingleEvent(of: .value, with: { (snapshot: DataSnapshot) in
let users = snapshot.value as! [String: AnyObject]
self.user.removeAll()
for (key,value) in users {
// HOW DO I REFERENCE A FIELD IN THE USER OBJECT
}
}
Keep in mind that in your code, your users object is of type [String:AnyObject] since you're grabbing it from a database (Firebase?).
So, you have an untyped Dictionary to deal with.
You can reference fields on the dictionary by doing users[key] where key is a String.
Judging by your naming of variables, it looks like you're expecting users to be an array rather than a dictionary, but then you're casting it as a dictionary. Without knowing your database schema it's hard to say what's actually happening.
But, you most likely want to actually turn your [String:AnyObject] into your actual data type. There are a number of approaches to this, but you may have to write your own decoder.
You may want to add more information about what database you're actually using.
Update: Including an example method for turning your dictionary into your object:
class User: NSObject {
var userID: String
var fullname: String
var imagePath: String
required init(withDictionary dict : [String: AnyObject]) {
userID = (dict["userID"] as? String) ?? ""
fullname = (dict["fullname"] as? String) ?? ""
imagePath = (dict["imagePath"] as? String) ?? ""
}
}
Note that I'm not handling failures -- just putting in empty strings.
You can also look into storing making your model Codable compliant and try to convert in and out of JSON. See: How can I use Swift’s Codable to encode into a dictionary?
Related
let current_data: [String : Any] = ["table": "tasks", "data": ["title": title_of_task, "completed_by": date_of_task]]
if (!description_of_task.isEmpty){
current_data["data"]["description"] = description_of_task
}
I get the following error:
Value of type 'Any?' has no subscripts
As matt notes, you need to redesign current_data. It is possible to do what you're describing, but the code is extremely ugly, complicated, and fragile because all interactions with Any are ugly, complicated, and fragile. It seems unlikely that you mean "Any" here. Would it be reasonable for the value to be a UIViewController, or a CBPeripheral, or an array of NSTimers? If it would be a problem to pass any of those types, you don't mean "literally any type." You almost never mean that, so you should almost never use Any.
To answer the question as asked, the code would be:
if (!description_of_task.isEmpty) {
if var data = current_data["data"] as? [String: Any] {
data["description"] = description_of_task
current_data["data"] = data
}
}
Yes, that's horrible, and if there are any mistakes, it will quietly do nothing without giving you any errors.
You could, however, redesign this data type using structs:
struct Task {
var title: String
var completedBy: String
var description: String
}
struct Row {
var table: String
var data: Task
}
With that, the code is trivial:
var row = Row(table: "tasks",
data: Task(title: "title_of_task",
completedBy: "date_of_task",
description: ""))
// ...
if !descriptionOfTask.isEmpty {
row.data.description = descriptionOfTask
}
import FirebaseFirestoreSwift
import Firebase
// I created this object so that i can map the users data and access it threw this object. example I could say thigs like user.username
// The decodable protocall will read the data dictonary and looks for the exact name for the keys/property names I have listed in the data dictonary, this makes life easier when working with objects and downloading information from an api
struct User: Identifiable, Decodable {
// Im able to delete the uid field out of firebase because this will read the documentID from firebase and store it in this id property, so that I dont have to dupicate that data in the actual body of the object
#DocumentID var id: String?
let username: String
let fullname: String
let profileImageUrl: String
let email: String
let stats: UserStats
// This is a computed property saying if the currently logged in user's id is equal to the id on my object (#DocumentID)
var isCurrentUser: Bool { return Auth.auth().currentUser?.uid == id }
}
struct UserStats: Decodable {
let followers: Int
let following: Int
}
Add ? at the end of each variable.
#FirestoreQuery does little error handling when it comes to decoding.
Also, if you are not using #FirestoreQuery use do try catch instead of try?
I am new to iOS and trying to store User object in UserDefaults. So that when the app is launched again, I can check user type and based on it, I need to navigate to relevant screen.
For that, I have created a User class as below (Codable) and it has one userType enum property!
enum UserType: Int, Codable {
case userType1 = 0
case userType2 = 1
case notDetermined = 2
init(from decoder: Decoder) throws {
let label = try decoder.singleValueContainer().decode(Int.self)
self = UserType(rawValue: label) ?? .notDetermined
}
}
class User: Codable {
public var userFullName: String? = ""
public var userType: UserType? //= .notDetermined
enum CodingKeys: String, CodingKey {
case userFullName
}
}
In my view Controller class, I am creating a new instance for User object and trying to store in user defaults as below:
let newUser = User()
newUser.userFullName = "Test"
newUser.userType = userTypeBtn.isSelected ? .userType1 : .userType2
when I print the newUser's userType, I can see proper value whichever is selected. But after that, when I am trying to store it in userDefaults as below, it returns nil for userType property.
do {
let encoded = try JSONEncoder().encode(newValue)
UserDefaults.standard.set(encoded, forKey: UserDefaultKey.currentUser)
UserDefaults.standard.sync()
} catch {
print("Unable to Encode User Object: (\(error))")
}
when I tried to print this encoded variable, and decoded it in console
JSONDecoder().decode(User.self, from: encoded).userType
it prints nil.
Please help me how can I store optional enum property in UserDefaults and retrieve it when needed using Codable
You should include userType in your CodingKeys enum:
enum CodingKeys: String, CodingKey {
case userFullName
case userType
}
Or just delete the CodingKeys enum entirely, since by default, all the properties are included as coding keys. The keys in the CodingKeys enum determines what the synthesised Codable implementation will encode and decode. If you don't include userType, userType will not be encoded, so it will not be stored into UserDefaults.
I am not getting it from Server and userType is an external property outside the JSON response
This is fine, because userType is optional. If the JSON does not have the key, it will be assigned nil. This might be a problem if you are also encoding User and sending it to the server, and that the server can't handle extra keys in the request, in which case you need two structs - one for storing to/loading from UserDefaults, one for parsing/encoding server response/request.
Remember to encode a new User to UserDefaults when you try this out, since the old one still doesn't have the userType encoded with it.
Observations
Having a custom implementation for Decodable part of enum UserType: Int, Codable is probably not the best idea. Swift compiler supports encoding/decoding enum X: Int out of the box without having you to write custom implementation for it. (In fact, starting with Swift 5.5, Swift compiler can now do this for enums that have cases with associated values as well.)
You should try to avoid having cases like .notDetermined. Either user has a type that's well defined or user.type is nil. You can easily define convenience getters on user itself to know about it's type.
Swift allows nesting of types, so having User.Kind instead of UserType is more natural in Swift.
Following implementation takes care of all of these points.
import Foundation
class User: Codable {
enum Kind: Int, Codable {
case free = 1
case pro = 2
}
public var fullName: String?
public var kind: Kind?
}
let newUser = User()
newUser.fullName = "Test"
newUser.kind = .free
do {
let encoded = try JSONEncoder().encode(newUser)
UserDefaults.standard.set(encoded, forKey: "appUser")
if let fetched = UserDefaults.standard.value(forKey: "appUser") as? Data {
let decoded = try JSONDecoder().decode(User.self, from: fetched)
print(decoded)
}
}
Above code includes definition, construction, encodeAndStore, fetchAndDecode and it does everything you need without any custom implementation.
Bonus
Above code does not print a nice description for the User. For that, you can add CustomStringConvertible conformance like this.
extension User: CustomStringConvertible {
var description: String {
"""
fullName: \(fullName ?? "")
kind: \(kind?.description ?? "")
"""
}
}
extension User.Kind: CustomStringConvertible {
var description: String {
switch self {
case .free: return "free"
case .pro: return "pro"
}
}
}
If you try print(decoded) after implementing this, you will clearly see what you want to see for User instance.
User.kind can be nil and I don't want to handle it with if let every time I need to check this from different screens in the app.
No worries, it can be simplified to this.
extension User {
var isFreeUser: Bool { kind == .free }
var isProUser: Bool { kind == .pro }
}
I was toying in the playground in xcode 7.3.1 with swift. I am a bit confused about the type casting in swift.
So, here is a bit of code that I tried.
class MediaItem {
var name: String
init(name: String) {
self.name = name
}
}
class Movie: MediaItem {
var director: String
init(name: String, director: String) {
self.director = director
super.init(name: name)
}
}
class Song: MediaItem {
var artist: String
init(name: String, artist: String) {
self.artist = artist
super.init(name: name)
}
}
var movieItem = Movie(name: "GOT", director: "RRMartin")
movieItem.dynamicType //Movie.Type
(movieItem as? MediaItem).dynamicType //Optional<MediaItem>.Type
var someItm = movieItem as! MediaItem //Movie
someItm.dynamicType //Movie.Type
I've shown the output from the playground in the comment. Here you can see the type in each line.
Now according the docs of apple, The conditional form, as?, returns an optional value of the type you are trying to downcast to. As per the docs, I am trying to downcast to MediaItem, and I am getting the MediaItem as optional type.
But when I use force unwrap(that is as!) the returned type is Movie. But I wanted it to be MediaItem.
Also, another thing to notice is that, the type is actually changed. Some data are actually truncated. Because when I tried to access the director property which is present in the Movie, I cannot access it. As I've downcast it.
So, if the type is downcast, why the returned type is Movie? Shouldn't it be MediaType?
So, my question is this, when I type cast some derived class(Movie) to base class(MediaType), shouldn't the converted type be base class(MediaType)?
dynamicType tells you what the underlying type of the object is. It doesn't tell you what the type of var currently referencing that object is.
For instance:
let a: Any = 3
a.dynamicType // Int.Type
Swift, of course, keeps track of these underlying types which is what allows you to later downcast a MediaItem to a Movie (if that is what it really is).
The confusion for you came when you did:
(movieItem as? MediaItem).dynamicType //Optional<MediaItem>.Type
An Optional is it's own type. It is an enumeration with two values: .None and .Some(T). The .Some value has an associated value that has its own dynamic type. In your example, when you asked for the dynamicType, it returned the underlying type of the Optional which is Optional<MediaItem>.Type. It didn't tell you what the dynamic type of the value associated with that Optional is.
Consider this:
let x = (movieItem as? MediaItem)
x.dynamicType // Optional<MediaItem>.Type
x!.dynamicType // Movie.Type
I have a number of structs which implement a Resource protocol. This defines that they must have a variable extendedInfo which conforms to ExtendedInfo protocol to provide a way to initialise them with json via init(json: [String: AnyObject]. I'm trying to provide a way to dynamically instantiate these, with JSON, providing the right type of ExtendedInfo and assign it to the struct's extendedInfo variable. However, I'm getting a Argument labels '(json:)' do not match any available overloads error when trying to instantiate them via their dynamicType
protocol Resource {
associatedtype ExtendedInfoTypeAlias: ExtendedInfo
var extendedInfo: ExtendedInfoTypeAlias? { get set }
}
protocol ExtendedInfo {
init(json: [String: AnyObject])
}
struct User: Resource {
typealias ExtendedInfoTypeAlias = UserExtendedInfo
let name: String = "Name"
var extendedInfo: UserExtendedInfo?
}
struct UserExtendedInfo: ExtendedInfo {
let age: Int?
init(json: [String: AnyObject]) {
age = json["age"] as? Int
}
}
let user = User()
let sampleJSON = ["age": 50]
let userExtendedInfo = user.extendedInfo.dynamicType.init(json: sampleJSON) // Argument labels '(json:)' do not match any available overloads
user.extendedInfo = userExtendedInfo
Any ideas guys? Thanks
First of all, you don't need to explicitly define the type of ExtendedInfoTypeAlias in your struct implementation – you can just let it be inferred by the type you provide for extendedInfo.
struct User: Resource {
let name: String = "Name"
var extendedInfo: UserExtendedInfo?
}
Second of all, you can just use the protocol's associated type of your given struct's dynamicType in order to use your given initialiser. For example:
user.extendedInfo = user.dynamicType.ExtendedInfoTypeAlias.init(json: sampleJSON)
print(user.extendedInfo) // Optional(Dynamic_Protocols.UserExtendedInfo(age: Optional(50)))
As for why your current code doesn't work, I suspect it's due to the fact that you're getting the dynamicType from an optional – which is preventing you from calling your initialiser on it.
I did find that the following works, even when extendedInfo is nil. (This is a bug).
user.extendedInfo = user.extendedInfo!.dynamicType.init(json: sampleJSON)
Change:
let user = User()
To:
var user = User()
and try this:
user.extendedInfo = UserExtendedInfo(json: sampleJSON)