I am trying to get data from firebase and use it as a model that I created.
Here is my model;
class UserData{
var nickname : String = ""
var onesignal_player_id : String = ""
var step_count : Int = 0
var total_point : Int = 0
var competitions : [String:Competition] = [String:Competition]()
}
class Competition{
var end_date : String = ""
var gift : String = ""
var id: String = ""
var name: String = ""
var users : [String:Int] = [:]
}
and this is my function;
func getFirebaseData() {
ref = Database.database().reference()
ref.child("users").child("HXXNCXf6RRS4WVO12shZ3j15BnG3").observe(.value) { (snapshot) in
if let snapshotValue = snapshot.value as? Dictionary<String,Any> {
//change userData with the snapshotValue
self.userData.nickname = snapshotValue["nickname"] as! String
self.userData.step_count = snapshotValue["step_count"] as! Int
self.userData.total_point = snapshotValue["total_point"] as! Int
self.userData.onesignal_player_id = snapshotValue["onesignal_player_id"] as! String
self.userData.competitions = snapshotValue["competitions"] as! [String:Competition]
//reload UI with userData
print(self.userData.competitions)
} else {
print("An error occured while assigning snapshotValue to userData")
}
}
}
This code gave me error like this;
Could not cast value of type '__NSDictionaryM' (0x1f47ada20) to 'StepCounterApp.Competition' (0x1004c06f0).
2021-01-02 23:05:49.985711+0300 StepCounterApp[32511:3685645] Could not cast value of type '__NSDictionaryM' (0x1f47ada20) to 'StepCounterApp.Competition' (0x1004c06f0).
but when i comment out the line included self.userData.competitions from getFirebaseData function, everything works fine.
What should I do? How can I use firebase data as a model?
Finally here is my firebase data;
The problem is in your data model. Declare your model data like this
class UserData {
var nickname : String = ""
var onesignal_player_id : String = ""
var step_count : Int = 0
var total_point : Int = 0
var competitions : Competition = Competition()
}
class Competition{
var end_date : String = ""
var gift : String = ""
var id: String = ""
var name: String = ""
var users : [String:Int] = [:]
init() {
}
init(with dictionary: [String: Any]) {
self.end_date = dictionary["end_date"] as! String
self.gift = dictionary["gift"] as! String
self.id = dictionary["id"] as! String
self.name = dictionary["name"] as! String
self.users = dictionary["users"] as! [String:Int]
}
}
And inside the getFirebaseData funcation
self.userData.competitions = Competition(with: snapshotValue["competitions"] as! [String: Any])
The problem was in my data model and with the help of Raja Kishan's data model sugestion I fixed the problem.
First I changed the model little bit;
class UserData{
var nickname : String = ""
var onesignal_player_id : String = ""
var step_count : Int = 0
var total_point : Int = 0
var competitions : [String:Competition] = [String:Competition]()
}
class Competition{
var end_date : String = ""
var gift : String = ""
var id: Int = 0
var name: String = ""
var users : [String:Int] = [:]
init() {
}
init(with dictionary: [String: Any]) {
self.end_date = dictionary["end_date"] as! String
self.gift = dictionary["gift"] as! String
self.id = dictionary["id"] as! Int
self.name = dictionary["name"] as! String
self.users = dictionary["users"] as! [String:Int]
}
}
Than I add a childSnapshot to my method so I can work directly the "competitions";
func getFirebaseData() {
ref = Database.database().reference()
ref.child("users").child("HXXNCXf6RRS4WVO12shZ3j15BnG3").observe(.value) { [self] (snapshot) in
if let snapshotValue = snapshot.value as? [String:Any] {
//change userData with the snapshotValue
self.userData.nickname = snapshotValue["nickname"] as! String
self.userData.step_count = snapshotValue["step_count"] as! Int
self.userData.total_point = snapshotValue["total_point"] as! Int
self.userData.onesignal_player_id = snapshotValue["onesignal_player_id"] as! String
//******
//This part of the coded added for to solve the problem starting from here
let childSnap = snapshot.childSnapshot(forPath: "competitions")
if let childSnapValue = childSnap.value as? [String:Any] {
childSnapValue.forEach { (element) in
self.userData.competitions.updateValue(Competition(with: element.value as! [String:Any]), forKey: element.key)
}
} else {
print("something wrong with the childSnap")
}
//to here
//******
} else {
print("An error occured while assigning snapshotValue to userData")
}
}
}
How can I clear the shared dictionary on logout in which I am saving login response?
Here is the code I am doing on getting status 1.
if(status == 1)
{
DispatchQueue.main.async {
GAReusableClass.sharedInstance.hideActivityIndicator()
UserDefaults.standard.set(self.DataDict, forKey:MaindataKey)
let Dict = self.mainDict[KData] as! [String: AnyObject]
print("self.DataDict", self.DataDict)
let User_ID = Dict[KUuid]as! String
print(User_ID)
let HMACSECRETKEY = self.deviceToken + "+" + User_ID
kHMACKey = HMACSECRETKEY
let cipher:String = CryptoHelper.encrypt(input:HMACSECRETKEY)!;
print(HMACSECRETKEY)
UserDefaults.standard.setValue(cipher, forKey:HmacKey)
UserDefaults.standard.set(true, forKey: "isLogin")
GAloginUserInfo.shared.saveUserInfo(dict: Dict )
let tabar = self.storyboard?.instantiateViewController(withIdentifier: "GAtHomeTabbarViewController") as! GAtHomeTabbarViewController
self.navigationController?.pushViewController(tabar, animated: true)
}
Here is the shared dictionary which I am using to save the values of login response.
import UIKit
import Firebase
class GAloginUserInfo: NSObject {
var loginUserMobileNo : String?
var loginUserId : String?
var loginUserUuid : String?
var loginUserCountry : String?
var loginUserCountryCode : String?
var loginUserEmail : String?
var loginUserlatitude : String?
var loginUserLongitude : String?
var loginUserName : String?
var loginUserQrcode : String?
var loginUserProfilePic : String?
var isverify : String?
var loginPassword : String?
var dateOfBirth: String?
var earnedPoints:String?
var loginUserGender:String?
var loginUserFollowers:Int = 0
static let shared = GAloginUserInfo()
func saveUserInfo (dict : [String : AnyObject?] ) {
if let loginUserMobileNo = dict["mobile"] as? String {
self.loginUserMobileNo = loginUserMobileNo
}
if let loginUserId = dict["id"] as? String {
self.loginUserId = loginUserId
}
if let loginUserUuid = dict["uuid"] as? String {
self.loginUserUuid = loginUserUuid
print(loginUserUuid)
}
if let loginUserCountry = dict["country"] as? String {
self.loginUserCountry = loginUserCountry
}
if let loginUserCountryCode = dict["country_code"] as? String {
self.loginUserCountryCode = loginUserCountryCode
}
if let loginUserEmail = dict["email"] as? String {
self.loginUserEmail = loginUserEmail
}
if let loginUserProfilePic = dict["profile_pic"] as? String {
self.loginUserProfilePic = loginUserProfilePic
}
if let loginUserLongitude = dict["logitude"] as? String {
self.loginUserLongitude = loginUserLongitude
}
if let loginUserName = dict["name"] as? String {
self.loginUserName = loginUserName
}
if let loginUserQrcode = dict["qr_code"] as? String {
self.loginUserQrcode = loginUserQrcode
}
if let Password = dict["password"] as? String{
self.loginPassword = Password
}
if let dateOfBirth = dict["dob"] as? String{
self.dateOfBirth = dateOfBirth
}
if let earnedPoints = dict["points"] as? String{
let myDouble = Double(earnedPoints)
let doubleStr = String(format: "%.2f", myDouble!)
self.earnedPoints = doubleStr
}
if let loginUserGender = dict["gender"] as? String{
self.loginUserGender = loginUserGender
}
if let loginUserFollowers = dict["followersCount"] as? Int{
self.loginUserFollowers = loginUserFollowers
}
}
}
Actually, the problem is when I log out and log in again with some other user it still shows some values of the previous user. I am clearing the userdefaults on the logout function. but I don't know how to clear this type of shared dictionary.
Use removeObject(forKey:)
to remove the values stored from user defaults in Logout method
UserDefaults.standard.removeObject(forKey: MaindataKey)
UserDefaults.standard.removeObject(forKey: HmacKey)
UserDefaults.standard.set(false, forKey: "isLogin")
Create a method to remove the values from the singleton class like this
extension GAloginUserInfo {
func removeUserInfo() {
self.loginUserMobileNo = nil
self.loginUserId = nil
self.loginUserUuid = nil
self.loginUserCountry = nil
self.loginUserCountryCode = nil
self.loginUserEmail = nil
self.loginUserlatitude = nil
self.loginUserLongitude = nil
self.loginUserName = nil
self.loginUserQrcode = nil
self.loginUserProfilePic = nil
self.isverify = nil
self.loginPassword = nil
self.dateOfBirth = nil
self.earnedPoints = nil
self.loginUserGender = nil
self.loginUserFollowers = 0
}
}
and call this method in logout
GAloginUserInfo.shared.removeUserInfo()
I have a list of JSON data downloaded from server:
(DataModal.swift)
class DataModal {
var orderAutoid: Int?
var orderId: String?
var orderName: String?
var orderQty: String?
var orderStatus: String?
init(bOrder_autoid: Int, bOrder_id: String, bOrder_name: String, bOrder_qty: String, bOrder_status: String){
self.orderAutoid = bOrder_autoid
self.orderId = bOrder_id
self.orderName = bOrder_name
self.orderQty = bOrder_qty
self.orderStatus = bOrder_status
}
(OrderStructureDownloadProtocol.swift)
protocol OrderStructureDownloadProtocol: class {
func newItemDownload(items: Array<Any>)
}
....
var jsonElement = Dictionary<String, Any>()
var newOrders = Array<Any>()
for i in 0..<jsonResult.count {
jsonElement = jsonResult[i] as! Dictionary
let newOrder_autoid = jsonElement["orderAutoid"] as? Int ?? 0
let newOrder_id = jsonElement["orderId"] as? String ?? ""
let newOrder_name = jsonElement["orderName"] as? String ?? ""
let newOrder_qty = jsonElement["orderQty"] as? String ?? ""
let newOrder_status = jsonElement["orderStatus"] as? String ?? ""
let newOrder = BMSDataModal(bOrder_autoid: newOrder_autoid, bOrder_id: newOrder_id, bOrder_name: newOrder_name, bOrder_qty: newOrder_qty, bOrder_status: newOrder_status)
newOrders.append(newOrder)
}
DispatchQueue.main.async (
execute: { () -> Void in
self.delegate.newItemDownload(items: newOrders as! Array<Any>)
})
(tableview.swift)
var newOrdersArray = [BMSDataModal]()
func newItemDownload(items: Array<Any>) {
newOrdersArray = items as! [BMSDataModal]
newOrderLookupTableView.reloadData()
}
(tableview.swift another part)
let cell = tableView.dequeueReusableCell(withIdentifier: "orderLookupCell", for: indexPath) as! NewOrderTableViewCell
let item = newOrdersArray[indexPath.row]
cell.newHMNumber?.text = item.orderId ?? "-"
cell.newMP?.text = item.orderName ?? "-"
cell.newQTY?.text = item.orderQty ?? "-"
return cell
}
having all the old NS-style changed. The app is running okay, there are some items that need to reset. As my data-source always contain Double, but I declared it as a String, as I won't deal with calculation so I treated it as 'String'.
This is the var var types: [String]? and here i get this error myLabel3.text = place.types how can i adjust it? I looked to other similar questions but not find something that is the same to my problem.
import UIKit
import CoreLocation
private let geometryKey = "geometry"
private let locationKey = "location"
private let latitudeKey = "lat"
private let longitudeKey = "lng"
private let nameKey = "name"
private let openingHoursKey = "opening_hours"
private let openNowKey = "open_now"
private let vicinityKey = "vicinity"
private let typesKey = "types"
private let photosKey = "photos"
class QPlace: NSObject {
var location: CLLocationCoordinate2D?
var name: String?
var photos: [QPhoto]?
var vicinity: String?
var isOpen: Bool?
var types: [String]?
init(placeInfo:[String: Any]) {
// coordinates
if let g = placeInfo[geometryKey] as? [String:Any] {
if let l = g[locationKey] as? [String:Double] {
if let lat = l[latitudeKey], let lng = l[longitudeKey] {
location = CLLocationCoordinate2D.init(latitude: lat, longitude: lng)
}
}
}
// name
name = placeInfo[nameKey] as? String
// opening hours
if let oh = placeInfo[openingHoursKey] as? [String:Any] {
if let on = oh[openNowKey] as? Bool {
isOpen = on
}
}
// vicinity
vicinity = placeInfo[vicinityKey] as? String
// types
types = placeInfo[typesKey] as? [String]
// photos
photos = [QPhoto]()
if let ps = placeInfo[photosKey] as? [[String:Any]] {
for p in ps {
photos?.append(QPhoto.init(photoInfo: p))
}
}
}
this is place class and this is the func of my customTableViewCell where i want to add it
func update(place:QPlace) {
myLabel.text = place.getDescription()
myImage.image = nil
myLabel2.text = place.vicinity
myLabel3.text = place.types
var types: [String]? is an optional array of String; the value of myLabel3.text is an optional String, or String?.
You need to get a value out from the array or join the values in order to set your label text, eg:
myLabel3.text = place.types?.joined()
In my application I want to implement separate class to keep all the temporary variables of Now Playing item for Music Player.
It has lots of properties with different types, but they should be handled in the same way. They should be handled in the class method "updateData" (see the end of code)
This is my code:
struct DataDefaults {
//MARK: Default properties
let albumTitle: String? = "Unknown Album"
let albumArtist: String? = "Unknown Artist"
let title: String? = "Unknown Title"
let artist: String? = "Unknown Artist"
let artwork: UIImage? = UIImage(named: "noartwork")!
let genre: String? = ""
let lyrics: String? = "No Lyrics"
let releaseDate: Date? = nil
let playbackDuration: TimeInterval? = 0
let rating: Int? = 0
let assetURL: URL? = nil
let isExplicitItem: Bool? = false
let isCloudItem: Bool? = false
let hasProtectedAsset: Bool? = false
}
class SongInfo: NSObject {
static let sharedData = SongInfo()
let defaults = DataDefaults()
//MARK: Properties
var albumTitle: String
var albumArtist: String
var title: String
var artist: String
var artwork: UIImage
var genre: String
var lyrics: String
var releaseDate: Date?
var playbackDuration: TimeInterval
var rating: Int
var assetURL: URL?
var isExplicitItem: Bool
var isCloudItem: Bool
var hasProtectedAsset: Bool
//MARK: Init
private override init () {
self.albumTitle = defaults.albumTitle!
self.albumArtist = defaults.albumArtist!
self.title = defaults.title!
self.artist = defaults.artist!
self.artwork = defaults.artwork!
self.genre = defaults.genre!
self.lyrics = defaults.lyrics!
self.releaseDate = defaults.releaseDate
self.playbackDuration = defaults.playbackDuration!
self.rating = defaults.rating!
self.assetURL = defaults.assetURL
self.isExplicitItem = defaults.isExplicitItem!
self.isCloudItem = defaults.isCloudItem!
self.hasProtectedAsset = defaults.hasProtectedAsset!
}
//MARK: Set properties
func updateData(allData: DataDefaults) {
var wasUpdated: Bool = false
if allData.albumTitle == self.albumTitle {
//pass
} else if allData.albumTitle == nil || allData.albumTitle == "" {
self.albumTitle = defaults.albumTitle!
wasUpdated = true
} else {
self.albumTitle = allData.albumTitle!
wasUpdated = true
}
//Need to repeat same IF for all properties
}
}
Is there any way I can use property name to make some reusage of the same code instead of duplicating it?
Rather than trying to find a solution to a weird design, I re-designed for what you're trying to accomplish 🙂
struct SongData: Equatable {
static let defaultData = SongData(albumTitle: "Unknown Album",
albumArtist: "Unknown Artist",
title: "Unknown Title",
artist: "Unknown Artist",
artwork: UIImage(named: "noartwork"),
genre:"",
lyrics: "No Lyrics",
releaseDate: nil,
playbackDuration: 0,
rating: 0,
assetURL: nil,
isExplicitItem: false,
isCloudItem: false,
hasProtectedAsset: false)
//MARK: Default properties
var albumTitle: String?
var albumArtist: String?
var title: String?
var artist: String?
var artwork: UIImage?
var genre: String?
var lyrics: String?
var releaseDate: Date?
var playbackDuration: TimeInterval?
var rating: Int?
var assetURL: URL?
var isExplicitItem: Bool?
var isCloudItem: Bool?
var hasProtectedAsset: Bool?
/// This initializer will set the properties to the defaultData properties if a passed value is nil
init(albumTitle: String?, albumArtist: String?, title: String?, artist: String?, artwork: UIImage?, genre: String?, lyrics: String?, releaseDate: Date?, playbackDuration: TimeInterval?, rating: Int?, assetURL: URL?, isExplicitItem: Bool?, isCloudItem: Bool?, hasProtectedAsset: Bool?) {
// initialize properties where the default is nil
self.releaseDate = releaseDate
self.assetURL = assetURL
//initialize other properties with the passed values, or use the default value if nil
self.albumTitle = SongData.valueOrDefault(albumTitle, SongData.defaultData.albumTitle)
self.albumArtist = SongData.valueOrDefault(albumArtist, SongData.defaultData.albumArtist)
self.title = SongData.valueOrDefault(title, SongData.defaultData.title)
self.artist = SongData.valueOrDefault(artist, SongData.defaultData.artist)
self.artwork = artwork ?? SongData.defaultData.artwork
self.genre = SongData.valueOrDefault(genre, SongData.defaultData.genre)
self.lyrics = SongData.valueOrDefault(lyrics, SongData.defaultData.lyrics)
self.playbackDuration = playbackDuration ?? SongData.defaultData.playbackDuration
self.rating = rating ?? SongData.defaultData.rating
self.isExplicitItem = isExplicitItem ?? SongData.defaultData.isExplicitItem
self.isCloudItem = isCloudItem ?? SongData.defaultData.isCloudItem
self.hasProtectedAsset = hasProtectedAsset ?? SongData.defaultData.hasProtectedAsset
}
static func ==(leftItem: SongData, rightItem: SongData) -> Bool {
return (leftItem.albumTitle == rightItem.albumTitle) &&
(leftItem.albumArtist == rightItem.albumArtist) &&
(leftItem.title == rightItem.title) &&
// Comparing a reference type here. may need to be handled differently if that's a problem
(leftItem.artwork === rightItem.artwork) &&
(leftItem.genre == rightItem.genre) &&
(leftItem.lyrics == rightItem.lyrics) &&
(leftItem.releaseDate == rightItem.releaseDate) &&
(leftItem.playbackDuration == rightItem.playbackDuration) &&
(leftItem.rating == rightItem.rating) &&
(leftItem.assetURL == rightItem.assetURL) &&
(leftItem.isExplicitItem == rightItem.isExplicitItem) &&
(leftItem.isCloudItem == rightItem.isCloudItem) &&
(leftItem.hasProtectedAsset == rightItem.hasProtectedAsset)
}
//simple helper function to avoid long turneries in the init
static func valueOrDefault(_ value: String?, _ defaultValue: String?) -> String? {
guard let value = value, !value.isEmpty else {
return defaultValue
}
return value
}
}
class SongInfo {
static let sharedData = SongInfo()
var data: SongData
//MARK: Init
private init ()
{
self.data = SongData.defaultData
}
//MARK: Set properties
func updateData(newData: SongData) {
if(newData != self.data) {
self.data = newData
}
}
}
I changed your struct to act more like it appears you're wanting it to be used, and the struct's init will fall back to using the default values if the init values are nil. My design also contains no force unwraps, which are almost always bad.
You could set the defaults directly in your class definition without using a separate struct and have a static unaltered instance with the default values.
For example:
class SongInfo: NSObject {
static let sharedData = SongInfo()
static let defaults = SongInfo()
//MARK: Properties
var albumTitle: String? = "Unknown Album"
var albumArtist: String? = "Unknown Artist"
var title: String? = "Unknown Title"
var artist: String? = "Unknown Artist"
var artwork: UIImage? = UIImage(named: "noartwork")!
var genre: String? = ""
var lyrics: String? = "No Lyrics"
var releaseDate: Date? = nil
var playbackDuration: TimeInterval? = 0
var rating: Int? = 0
var assetURL: URL? = nil
var isExplicitItem: Bool? = false
var isCloudItem: Bool? = false
var hasProtectedAsset: Bool? = false
//MARK: Init
private override init ()
{
// nothing to do here
}
//MARK: Set properties
func updateData(allData: DataDefaults) {
var wasUpdated: Bool = false
if allData.albumTitle == self.albumTitle {
//pass
} else if allData.albumTitle == nil || allData.albumTitle == "" {
self.albumTitle = SongInfo.defaults.albumTitle!
wasUpdated = true
} else {
self.albumTitle = allData.albumTitle!
wasUpdated = true
}
//Need to repeat same IF for all properties
}
}
If you also need to manipulate the basic data without the whole class functionality, you could define a SongInfoData class with only the properties and make SingInfo inherit from that class. Then the static variable for defaults could be in the SongInfoData class and the SingInfo subclass wouldn't need any property declarations.
[EDIT] avoiding code repetition in update function ...
You can generalize the property update process by adding a generic function to your class:
For example:
func assign<T:Equatable>(_ variable:inout T?, _ getValue:(SongInfo)->T?) -> Int
{
let newValue = getValue(self)
if variable == newValue
{ return 0 }
var valueIsEmpty = false
if let stringValue = newValue as? String, stringValue == ""
{ valueIsEmpty = true }
if newValue == nil || valueIsEmpty
{
variable = getValue(SongInfo.defaults)
return 1
}
variable = newValue
return 1
}
func update(with newInfo:SongInfo)
{
let updates = newInfo.assign(&albumTitle) {$0.albumTitle}
+ newInfo.assign(&albumArtist) {$0.albumArtist}
+ newInfo.assign(&title) {$0.title}
+ newInfo.assign(&artist) {$0.artist}
+ newInfo.assign(&artwork) {$0.artwork}
+ newInfo.assign(&genre) {$0.genre}
+ newInfo.assign(&lyrics) {$0.lyrics}
// ...
if updates > 0
{
// react to update
}
}
It seems to me, that you're using MPMedia item.
If so, you don't have to store all these properties at all.
You just need to store persistent ID of the item (convert from UInt64 to string), and later fetch MPMediaItem by using MPMediaQuery with predicate, something like this:
func findSong(persistentIDString: String) -> MPMediaItem? {
let predicate = MPMediaPropertyPredicate(value: persistentIDString, forProperty: MPMediaItemPropertyPersistentID)
let songQuery = MPMediaQuery()
songQuery.addFilterPredicate(predicate)
return songQuery.items.first
}