Can't modify variable of a class from closure - swift

I want decodedResult variable assigns to exchangeInformations variable:
self.exchangeInformations = decodedResult
I created an instance of exchangeInfo() class. I try print the value of the exchangeInformations variable but I got nil. Why?
import Cocoa
class exchangeInfo {
var exchangeInformations: exchangeInformation? //Exchange information
let exchangeInformationURL: String = "https://api.binance.com/api/v3/exchangeInfo"
struct exchangeInformation: Codable {
var timezone: String //UTC
var serverTime: Int //1565246363776
var rateLimits: [rateLimits] = []//Rate limits
var exchangeFilters: [exchangeFilters] = [] //Exchange filters
var symbols: [symbols] //Symbols
}
struct rateLimits: Codable { //Rate limits
var rateLimitType: String //REQUEST_WEIGHT, ORDERS, RAW_REQUESTS
var interval: String //interval
var intervalNum: UInt16 //1
var limit: UInt32 //1200
}
struct exchangeFilters: Codable {
var filterType: String
var minPrice, maxPrice, tickSize, multiplierUp, multiplierDown, minQty, maxQty, stepSize, minNotional, maxPosition: String?
var avgPriceMins, limit, maxNumOrders, maxNumAlgoOrders, maxNumIcebergOrders: UInt16?
var applyToMarket: Bool?
}
struct symbols: Codable { //Symbols
var symbol: String //ETHBTC
var status: String //TRADING
var baseAsset: String //ETH
var baseAssetPrecision: UInt16 //8
var quoteAsset: String //BTC
var quotePrecision: UInt16 //8
var quoteAssetPrecision: UInt16 //8
var baseCommissionPrecision: UInt16 //8
var quoteCommissionPrecision: UInt16 //8
var orderTypes: [String] //orderTypes
var icebergAllowed: Bool //true
var ocoAllowed: Bool //true
var quoteOrderQtyMarketAllowed: Bool //true
var isSpotTradingAllowed: Bool //true
var isMarginTradingAllowed: Bool //true
var filters: [exchangeFilters] = [] //Filters
var permissions: [String] //Permissions
}
init() {
guard let url = URL(string: exchangeInformationURL) else {
print("URL is not valid")
return
}
let request = URLRequest(url: url)
let configuration = URLSessionConfiguration.ephemeral
let session = URLSession(configuration: configuration)
session.dataTask(with: request) { data, response, error in
do {
if let adat = data {
if let decodedResult = try? JSONDecoder().decode(exchangeInformation.self, from: adat) {
DispatchQueue.main.async {
self.exchangeInformations = decodedResult
}
}
}
} catch {
print("Error: \(error.localizedDescription)")
}
}.resume()
}
}
var informacio = exchangeInfo()
if let serverTime = informacio.exchangeInformations?.serverTime {
print (serverTime)
}
I got nothing.

Related

How To Remove and Add Double Nested Elements in Firestore Array

I have a nested Codable Object In another object array in another object. I don't see how I can use FieldValue.arrayRemove[element]. Anyone know to do this? Thanks. I am trying to make it so that I can remove a cardField element in the LevelCard element in the job array.
Here is my code
struct Job: Identifiable, Codable {
var id: String? = UUID().uuidString
var uid: String = ""
var title: String = ""
var description: String = ""
var images: [ImagesForJob] = []
var levelCards: [LevelCard] = []
var tags: [Tag] = []}
struct LevelCard: Identifiable, Codable {
var id = UUID().uuidString
var name: String = ""
var color: String = "A7D0FF"
var fields: [CardField] = []}
struct CardField: Identifiable, Codable {
var id = UUID().uuidString
var name: String = ""
var value: String = ""
var type: FieldType = .Text}
func removeExistingCard(id: String, card: LevelCard) {
var data: [String: Any] = ["":""]
do {
let encoder = JSONEncoder()
let jsonData = try! encoder.encode(card)
data = try JSONSerialization.jsonObject(with: jsonData, options: []) as! [String : Any]
} catch {
print("Error encoding account info\(error.localizedDescription)")
}
db
.collection("listings")
.document(id)
.updateData(["levelCards": FieldValue.arrayRemove([data])]) {err in
if let err = err {
withAnimation {
self.errMsg = "Failed to delete card: \(err.localizedDescription)"
self.showErrMsg = true
}
return
}
self.getUsrLstngs()
}
}
func removeExistingField(id: String, field: CardField) {
var data: [String: Any] = ["":""]
do {
let encoder = JSONEncoder()
let jsonData = try! encoder.encode(field)
data = try JSONSerialization.jsonObject(with: jsonData, options: []) as! [String : Any]
} catch {
print("Error encoding account info\(error.localizedDescription)")
}
db
.collection("listings")
.document(id)
.updateData(["levelCards": FieldValue.arrayRemove([data])]) {err in
if let err = err {
withAnimation {
self.errMsg = "Failed to delete card: \(err.localizedDescription)"
self.showErrMsg = true
}
return
}
self.getUsrLstngs()
}
}
Also, Bonus, Does anyone know how to ignore the "id" variable when encoding all of my objects to Firestore? Thanks again.

Values of variable not saving Swiftui

I am working on a swiftui app where I am getting the sign in data from the user when they login. But for some reason the data that I save to the variable is just not saving. The data get pulled from the api and when the Post function is running the data is there but as soon as the function finishes the data gets turned to nil.
#Published var userData : SignInData?
#Published var signInSuccess: Bool = false
func signIn(bodyData:Any, completion: #escaping((Error?, SignInData?) -> Void)) { /
if let url = URL(string: signInURL) {
let session = URLSession.shared
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = try! JSONSerialization.data(withJSONObject: bodyData)
let task = session.dataTask(with: request) { (data, response, error) in
if error == nil {
let decoder = JSONDecoder()
if let safeData = data {
do {
let results = try decoder.decode(SignInData.self, from: safeData)
DispatchQueue.main.sync {
//print(results)
self.userData = results
completion(nil,results)
self.signInSuccess = true
}
} catch {
print(error)
completion(error,nil)
}
}
}
}
task.resume()
}
}
struct SignInData: Codable{
var Message : String?
var UID : String?
var Dob : String?
var FirstName : String?
var LastName : String?
var UserName : String?
var Status:String?
var Rank : String?
var Cell : String?
var Insider : String?
var Interests : String?
var Gender : String?
var Location : String?
var ParentsStatus : String?
var School : String?
var FB_username: String?
var FB_score : String?
var IG_username : String?
var IG_score : String?
var TW_username : String?
var Tw_score : String?
var Email: String?
var Balance : String?
}
This is where I call it in another Class
#ObservedObject var networkManager = NetworkManager()
if self.networkManager.userData?.Status == "0"{
print("the account is not activated yet")
}
else{
print("successful")
}

can not convert json to struct object

I want to parse JSON data into a struct object but i can't do it.
Actually the code is in different files but here i'm posting it as a whole.
Here is my code :
import Foundation
struct dataResponse: Decodable {
var results: [userData]
init(from decoder: Decoder) throws {
var results = [userData] ()
var container = try decoder.unkeyedContainer()
while !container.isAtEnd {
if let route = try? container.decode(userData.self) {
results.append(route)
}
else {
_ = try? container.decode(dummyData.self)
}
}
self.results = results
}
}
private struct dummyData: Decodable { }
enum dataError: Error {
case dataUnavailable
case cannotProcessData
}
struct userData: Codable {
var avatar: String
var city: String
var contribution: Int
var country: String
var friendOfCount: Int
var handle: String
var lastOnlineTimeSeconds: Int
var maxRank: String
var maxRating: Int
var organization: String
var rank: String
var rating: Int
var registrationTimeSeconds: Int
var titlePhoto: String
}
struct dataRequest {
let requestUrl: URL
init(){
self.requestUrl = URL(string: "https://codeforces.com/api/user.info?handles=abhijeet_ar")!
}
func getData(completionHandler: #escaping(Result<[userData], dataError>) -> Void) {
URLSession.shared.dataTask(with: self.requestUrl) { (data,response, error) in
guard let data = data else {
completionHandler(.failure(.dataUnavailable))
print("-------bye-bye--------")
return
}
do {
print("-------entered--------")
// let dataresponse = try JSONDecoder().decode([userData].self, from: data)
// print(type(of: dataresponse))
// completionHandler(.success(dataresponse))
let jsonResult = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers) as AnyObject
print(jsonResult)
completionHandler(.success(jsonResult as! [userData]))
}
catch {
completionHandler(.failure(.cannotProcessData))
}
}.resume()
}
}
here userData is my struct
the error says : Could not cast value of type '__NSDictionaryM' (0x7fff8fe2dab0) to 'NSArray' (0x7fff8fe2dd30).
I would appreciate if anyone helps, thanks.
You are making a very common mistake.
You are ignoring the root object which is a dictionary and causes the error.
struct Root: Decodable {
let status : String
let result: [UserData]
}
struct UserData: Decodable {
let avatar: String
let city: String
let contribution: Int
let country: String
let friendOfCount: Int
let handle: String
let lastOnlineTimeSeconds: Int
let maxRank: String
let maxRating: Int
let organization: String
let rank: String
let rating: Int
let registrationTimeSeconds: Int
let titlePhoto: String
}
Forget JSONSerialization and use only JSONDecoder
And it's not a good idea to return meaningless enumerated errors. Use Error and return the real error.
You get the array with dataresponse.result
struct DataRequest { // name structs always with starting capital letter
let requestUrl: URL
init(){
self.requestUrl = URL(string: "https://codeforces.com/api/user.info?handles=abhijeet_ar")!
}
func getData(completionHandler: #escaping(Result<Root, Error>) -> Void) {
URLSession.shared.dataTask(with: self.requestUrl) { (data,response, error) in
guard let data = data else {
completionHandler(.failure(error!))
print("-------bye-bye--------")
return
}
do {
print("-------entered--------")
let dataresponse = try JSONDecoder().decode(Root.self, from: data)
completionHandler(.success(dataresponse))
}
catch {
completionHandler(.failure(error))
}
}.resume()
}
}
And consider that if status is not "OK" the JSON response could be different.
Your problem is you are using the wrong struct. Create and use a Response struct for decoding. You need to keep your Types to have the first letter in capital to avoid confusion. You could use the JSONDecoder() maybe you are using something that doesn't have the correct format. Try the below code.
struct Response: Codable {
var result: [UserData]
}
enum DataError: Error {
case dataUnavailable, cannotProcessData
}
struct DataRequest {
let requestUrl: URL
init(){
self.requestUrl = URL(string: "https://codeforces.com/api/user.info?handles=abhijeet_ar")!
}
func getData(completionHandler: #escaping(Result<Response, DataError>) -> Void) {
URLSession.shared.dataTask(with: self.requestUrl) { (data,response, error) in
guard let data = data else {
completionHandler(.failure(.dataUnavailable))
return
}
do {
let dataresponse = try JSONDecoder().decode(Response.self, from: data)
completionHandler(.success(dataresponse))
} catch {
completionHandler(.failure(.cannotProcessData))
}
}.resume()
}
}
Note: Parameters in UserData always needs to right. Try with and Empty struct to check if everything else is working then proceed to adding the variable one-by-one.

Typecasting causing struct values to change (Swift)

After downcasting an array of structs, my Variables View window shows that all of the values in my struct have shifted "down" (will explain in a second). But when I print(structName), the values are fine. However, when I run an equality check on the struct, it once again behaves as though my values have shifted.
For example, I am trying to downcast Model A to ModelProtocol. var m = Model A and has the values {id: "1234", name: "Cal"}. When I downcast, m now has the values { id:"\0\0", name:"1234" }.
Actual Example Below:
Models that I want to downcast:
struct PrivateSchoolModel: Decodable, SchoolProtocol {
var id: String
var name: String
var city: String
var state: String
}
struct PublicSchoolModel: Decodable, SchoolProtocol {
var id: String
var name: String
var city: String
var state: String
var latitude: String
var longitude: String
}
Protocol I want to downcast to:
protocol SchoolProtocol {
var id: String { get set }
var name: String { get set }
var city: String { get set }
var state: String { get set }
var longitude: Float { get set }
var latitude: Float { get set }
}
extension SchoolProtocol {
var longitude: Float {
get { return -1.0 }
set {}
}
var latitude: Float {
get { return -1.0 }
set {}
}
}
Downcasting:
guard let downcastedArr = privateSchoolArray as? [SchoolProtocol] else { return [] }
Result (item at index 0) or originalArr:
id = "1234"
name = "Leo High School"
city = "Bellview"
state = "WA"
Result (item at index 0) of downcastedArr:
id = "\0\0"
name = "1234"
city = "Leo High School"
state = "Bellview"
But if I print(downcastArr[0]), it will show:
id = "1234"
name = "Leo High School"
city = "Bellview"
state = "WA"
But if I try originalArray[0].id == downcastArr[0].id, it returns false
My Code with the problem:
class SchoolJSONHandler {
private enum JSONFile: String {
case publicSchool = "publicSchool"
case privateSchool = "privateSchool"
}
private lazy var privateSchoolArray = getPrivateSchools()
private lazy var publicSchoolArray = getPublicSchools()
func getSchoolArray(sorted: Bool, filtered: Bool, by stateAbbreviation: String?) -> [SchoolProtocol] {
var schools = combineArrays()
if sorted {
schools.sort(by: { $0.name < $1.name })
}
if filtered {
guard let abbr = stateAbbreviation else { return [] }
schools = schools.filter {
return $0.state == abbr
}
}
return schools
}
private func combineArrays() -> [SchoolProtocol] {
// EVERYTHING IS FINE IN NEXT LINE
let a = privateSchoolArray
// PROBLEM OCCURS IN NEXT 2 LINES WHEN DOWNCASTING
let b = privateSchoolArray as [SchoolProtocol]
let c = publicSchoolArray as [SchoolProtocol]
return b + c
}
private func getPublicSchools() -> [PublicSchoolModel] {
guard let jsonData = getJSONData(from: .publicSchool) else { return [] }
guard let schools = decode(jsonData: jsonData, using: [PublicSchoolModel].self) else { return [] }
return schools
}
private func getPrivateSchools() -> [PrivateSchoolModel] {
guard let jsonData = getJSONData(from: .privateSchool) else { return [] }
guard let schools = decode(jsonData: jsonData, using: [PrivateSchoolModel].self) else { return [] }
return schools
}
private func getJSONData(from resource: JSONFile) -> Data? {
let url = Bundle.main.url(forResource: resource.rawValue, withExtension: "json")!
do {
let jsonData = try Data(contentsOf: url)
return jsonData
}
catch {
print(error)
}
return nil
}
private func decode<M: Decodable>(jsonData: Data, using modelType: M.Type) -> M? {
do {
//here dataResponse received from a network request
let decoder = JSONDecoder()
let model = try decoder.decode(modelType, from:
jsonData) //Decode JSON Response Data
return model
} catch let parsingError {
print("Error", parsingError)
}
return nil
}
}
And then it is just called in another class by schoolJSONHandler.getSchoolArray(sorted: true, filtered: true, by: "WA")

How to reduce code duplication for class properties (Swift 3)

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
}