Convert to string an Any value - swift

This fails (Non-nominal type 'Any' cannot be extended)
extension Any {
func literal() -> String {
if let booleanValue = (self as? Bool) {
return String(format: (booleanValue ? "true" : "false"))
}
else
if let intValue = (self as? Int) {
return String(format: "%d", intValue)
}
else
if let floatValue = (self as? Float) {
return String(format: "%f", floatValue)
}
else
if let doubleValue = (self as? Double) {
return String(format: "%f", doubleValue)
}
else
{
return String(format: "<%#>", self)
}
}
}
as I would like to use it in a dictionary (self) to xml string factory like
extension Dictionary {
// Return an XML string from the dictionary
func xmlString(withElement element: String, isFirstElement: Bool) -> String {
var xml = String.init()
if isFirstElement { xml.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n") }
xml.append(String(format: "<%#>\n", element))
for node in self.keys {
let value = self[node]
if let array: Array<Any> = (value as? Array<Any>) {
xml.append(array.xmlString(withElement: node as! String, isFirstElemenet: false))
}
else
if let dict: Dictionary<AnyHashable,Any> = (value as? Dictionary<AnyHashable,Any>) {
xml.append(dict.xmlString(withElement: node as! String, isFirstElement: false))
}
else
{
xml.append(String(format: "<%#>", node as! CVarArg))
xml.append((value as Any).literal
xml.append(String(format: "</%#>\n", node as! CVarArg))
}
}
xml.append(String(format: "</%#>\n", element))
return xml.replacingOccurrences(of: "&", with: "&amp", options: .literal, range: nil)
}
}
I was trying to reduce the code somehow, as the above snippet is repeated a few times in a prototype I'm building but this is not the way to do it (a working copy with the snippet replicated works but ugly?).
Basically I want to generate a literal for an Any value - previously fetched from a dictionary.

It seems like you can't add extensions to Any. You do have some other options though - either make it a function toLiteral(value: Any) -> String, or what is probably a neater solution; use the description: String attribute which is present on all types that conform to CustomStringConvertible, which includes String, Int, Bool, and Float - your code would be simplified down to just xml.append(value.description). You then just have make a simple implementation for any other types that you might get.

Ok, finally got this working. First the preliminaries: each of your objects needs to have a dictionary() method to marshal itself. Note: "k.###" are struct static constants - i.e., k.name is "name", etc. I have two objects, a PlayItem and a PlayList:
class PlayItem : NSObject {
var name : String = k.item
var link : URL = URL.init(string: "http://")!
var time : TimeInterval
var rank : Int
var rect : NSRect
var label: Bool
var hover: Bool
var alpha: Float
var trans: Int
var temp : String {
get {
return link.absoluteString
}
set (value) {
link = URL.init(string: value)!
}
}
func dictionary() -> Dictionary<String,Any> {
var dict = Dictionary<String,Any>()
dict[k.name] = name
dict[k.link] = link.absoluteString
dict[k.time] = time
dict[k.rank] = rank
dict[k.rect] = NSStringFromRect(rect)
dict[k.label] = label ? 1 : 0
dict[k.hover] = hover ? 1 : 0
dict[k.alpha] = alpha
dict[k.trans] = trans
return dict
}
}
class PlayList : NSObject {
var name : String = k.list
var list : Array <PlayItem> = Array()
func dictionary() -> Dictionary<String,Any> {
var dict = Dictionary<String,Any>()
var items: [Any] = Array()
for item in list {
items.append(item.dictionary())
}
dict[k.name] = name
dict[k.list] = items
return dict
}
}
Note any value so marshal has to be those legal types for a dictionary; it helps to have aliases so in the PlayItem a "temp" is the string version for the link url, and its getter/setter would translate.
When needed, like the writeRowsWith drag-n-drop tableview handler, I do this:
func tableView(_ tableView: NSTableView, writeRowsWith rowIndexes: IndexSet, to pboard: NSPasteboard) -> Bool {
if tableView == playlistTableView {
let objects: [PlayList] = playlistArrayController.arrangedObjects as! [PlayList]
var items: [PlayList] = [PlayList]()
var promises = [String]()
for index in rowIndexes {
let item = objects[index]
let dict = item.dictionary()
let promise = dict.xmlString(withElement: item.className, isFirstElement: true)
promises.append(promise)
items.append(item)
}
let data = NSKeyedArchiver.archivedData(withRootObject: items)
pboard.setPropertyList(data, forType: PlayList.className())
pboard.setPropertyList(promises, forType:NSFilesPromisePboardType)
pboard.writeObjects(promises as [NSPasteboardWriting])
}
else
{
let objects: [PlayItem] = playitemArrayController.arrangedObjects as! [PlayItem]
var items: [PlayItem] = [PlayItem]()
var promises = [String]()
for index in rowIndexes {
let item = objects[index]
let dict = item.dictionary()
let promise = dict.xmlString(withElement: item.className, isFirstElement: true)
promises.append(promise)
items.append(item)
}
let data = NSKeyedArchiver.archivedData(withRootObject: items)
pboard.setPropertyList(data, forType: PlayList.className())
pboard.setPropertyList(promises, forType:NSFilesPromisePboardType)
pboard.writeObjects(promises as [NSPasteboardWriting])
}
return true
}
What makes this happen are these xmlString extensions and the toLiteral function - as you cannot extend "Any":
func toLiteral(_ value: Any) -> String {
if let booleanValue = (value as? Bool) {
return String(format: (booleanValue ? "1" : "0"))
}
else
if let intValue = (value as? Int) {
return String(format: "%d", intValue)
}
else
if let floatValue = (value as? Float) {
return String(format: "%f", floatValue)
}
else
if let doubleValue = (value as? Double) {
return String(format: "%f", doubleValue)
}
else
if let stringValue = (value as? String) {
return stringValue
}
else
if let dictValue: Dictionary<AnyHashable,Any> = (value as? Dictionary<AnyHashable,Any>)
{
return dictValue.xmlString(withElement: "Dictionary", isFirstElement: false)
}
else
{
return ((value as AnyObject).description)
}
}
extension Array {
func xmlString(withElement element: String, isFirstElemenet: Bool) -> String {
var xml = String.init()
xml.append(String(format: "<%#>\n", element))
self.forEach { (value) in
if let array: Array<Any> = (value as? Array<Any>) {
xml.append(array.xmlString(withElement: "Array", isFirstElemenet: false))
}
else
if let dict: Dictionary<AnyHashable,Any> = (value as? Dictionary<AnyHashable,Any>) {
xml.append(dict.xmlString(withElement: "Dictionary", isFirstElement: false))
}
else
{
xml.append(toLiteral(value))
}
}
xml.append(String(format: "<%#>\n", element))
return xml
}
}
extension Dictionary {
// Return an XML string from the dictionary
func xmlString(withElement element: String, isFirstElement: Bool) -> String {
var xml = String.init()
if isFirstElement { xml.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n") }
xml.append(String(format: "<%#>\n", element))
for node in self.keys {
let value = self[node]
if let array: Array<Any> = (value as? Array<Any>) {
xml.append(array.xmlString(withElement: node as! String, isFirstElemenet: false))
}
else
if let dict: Dictionary<AnyHashable,Any> = (value as? Dictionary<AnyHashable,Any>) {
xml.append(dict.xmlString(withElement: node as! String, isFirstElement: false))
}
else
{
xml.append(String(format: "<%#>", node as! CVarArg))
xml.append(toLiteral(value as Any))
xml.append(String(format: "</%#>\n", node as! CVarArg))
}
}
xml.append(String(format: "</%#>\n", element))
return xml
}
func xmlHTMLString(withElement element: String, isFirstElement: Bool) -> String {
let xml = self.xmlString(withElement: element, isFirstElement: isFirstElement)
return xml.replacingOccurrences(of: "&", with: "&amp", options: .literal, range: nil)
}
}
This continues another's solution, the toLiteral() suggestion above, in hopes it helps others.
Enjoy.

Related

Realm accessed from incorrect thread occasional

I have this function
class func addCVals(_ criteres: [[AnyHashable: Any]], _ type: String) {
DispatchQueue.global(qos: .background).async {
autoreleasepool {
if criteres.count > 0 {
if let realm = DBTools.getRealm() {
do {
try realm.transaction {
let oldValues = CriteresVal.objects(in: realm, where: "type = '\(type)'")
if oldValues.count > 0 {
realm.deleteObjects(oldValues)
}
for critere in criteres {
let cval = CriteresVal(critere, type)
if let c = cval {
realm.addOrUpdate(c)
}
}
}
} catch {
DebugTools.record(error: error)
}
realm.invalidate()
}
}
}
}
}
The request that get oldValues occasionally cause an error
Realm accessed from incorrect thread
I don't understand why as I get a new Realm before with this lines:
if let realm = DBTools.getRealm()
My function getRealm:
class func getRealm() -> RLMRealm? {
if !AppPreference.lastAccount.elementsEqual("") {
let config = RLMRealmConfiguration.default()
do {
return try RLMRealm(configuration: config)
} catch {
DebugTools.record(error: error)
DispatchQueue.main.async {
Notifier.showNotification("", NSLocalizedString("UNKNOWN_ERROR_DB", comment: ""), .warning)
}
}
}
return nil
}
CriteresVal is an RLMObject that is composed of this:
#objcMembers
public class CriteresVal: RLMObject {
dynamic var cvalId: String?
dynamic var type: String?
dynamic var text: String?
dynamic var compositeKey: String?
override public class func primaryKey() -> String {
return "compositeKey"
}
private func updatePrimaryKey() {
self.compositeKey = "\(self.cvalId ?? "")/\(self.type ?? "")"
}
required init(_ cvalue: [AnyHashable: Any]?, _ type: String) {
super.init()
if let values = cvalue {
if let cvalId = values["id"] as? String {
self.cvalId = cvalId
} else if let cvalId = values["id"] as? Int {
self.cvalId = "\(cvalId)"
}
self.type = type
if let text = values["text"] as? String {
self.text = text
}
}
updatePrimaryKey()
}
func generateDico() -> [String: Any] {
var dicoSortie = [String: Any]()
if let realm = self.realm {
realm.refresh()
}
if let value = cvalId {
dicoSortie["id"] = value
}
if let value = type {
dicoSortie["type"] = value
}
if let value = text {
dicoSortie["text"] = value
}
return dicoSortie
}
}
compositeKey is the primary key which included cvalId and type
Thanks for help.

Swift. Data is loaded after then TableView

I have 2 files. First - TransactionsViewController. Second - GetTransactions. When I open TransactionsViewController the table from that view loads faster than the date from GetTransactions. Therefore, it is displayed blank. How to fix it?
Here is the code of TransactionsViewController viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
GetTransactions().getTransactions()
clientsCount = GetTransactions.transactions.count
setupNavBar()
createTable()
refreshSetup()
}
Here is the code of GetTransactions:
class GetTransactions {
static var transactionsArr = [[String : Any]]()
static var transactions = [TransactionInfo]()
let URL_GET_TRANSACTIONS = "https://mashkov.dev/sites/default/BankApp/Transactions/Transactions.php"
func getTransactions(){
GetTransactions.transactionsArr.removeAll()
AF.request(URL_GET_TRANSACTIONS).responseJSON{ (response) in
if (response.value as? [[String : Any]]) != nil {
GetTransactions.transactionsArr = response.value as! [[String : Any]]
}
self.convertData()
print(GetTransactions.transactions)
}
}
func convertData() {
GetTransactions.transactions.removeAll()
for transaction in GetTransactions.transactionsArr {
let cl = TransactionInfo(id: transaction["id"] as! String,
payee: transaction["payee_account_id"] as! String,
sender: transaction["sender_account_id"] as! String,
transDate: transaction["trans_date"] as! String,
amount: transaction["amount"] as! String,
isSuccessfully: Bool((transaction["isSuccessfully"] as! String)) ?? true)
GetTransactions.transactions.append(cl)
}
}
}
What you need is a closure / block to be passed as an argument
class GetTransactions {
static var transactionsArr = [[String : Any]]()
static var transactions = [TransactionInfo]()
let URL_GET_TRANSACTIONS = "https://mashkov.dev/sites/default/BankApp/Transactions/Transactions.php"
func getTransactions(completion block: () -> ()){
GetTransactions.transactionsArr.removeAll()
AF.request(URL_GET_TRANSACTIONS).responseJSON{ (response) in
if (response.value as? [[String : Any]]) != nil {
GetTransactions.transactionsArr = response.value as! [[String : Any]]
}
self.convertData()
print(GetTransactions.transactions)
block()
}
}
func convertData() {
GetTransactions.transactions.removeAll()
for transaction in GetTransactions.transactionsArr {
let cl = TransactionInfo(id: transaction["id"] as! String,
payee: transaction["payee_account_id"] as! String,
sender: transaction["sender_account_id"] as! String,
transDate: transaction["trans_date"] as! String,
amount: transaction["amount"] as! String,
isSuccessfully: Bool((transaction["isSuccessfully"] as! String)) ?? true)
GetTransactions.transactions.append(cl)
}
}
}
And you call it using,
GetTransactions().getTransactions {
clientsCount = GetTransactions.transactions.count
//reload your tableView here
}

SWIFT4 Contextual type 'FPChat!.Type' cannot be used with dictionary literal

I need to initialize an object, and pass it through a prepareforsegue to another class.
Last line of the code below throws "Contextual type 'FPChat!.Type' cannot be used with dictionary literal"
if (segue.identifier == "chatmessages") {
let vc = segue.destination as! FPChatMessageViewController
//vc.currentChat = fPChat
}
}
fPchat = FPChat?
// Start the Chat
#IBAction func Chat(_ sender: UIButton) {
// Create a new entry in chats. This variable is passed with prepareforsegue
let chatRef = ref.child("chats").childByAutoId()
let chatId = chatRef.key
//fPchat = FPChat?
let fPchat = FPChat.currentChat(currentChatID: chatId)
Below chat class:
import Firebase
class FPChat {
var chatID = ""
var chatDate: Date!
var text = ""
var messages: [FPChatMessage]!
var author: FPUser!
var mine = true
// Calling FPChat.currentChat(id) I have back the FPChat object
static func currentChat(currentChatID: String) -> FPChat {
return FPChat(chatID: currentChatID)
}
private init(chatID: String) {
self.chatID = chatID
}
init(snapshot: DataSnapshot, andMessages messages: [FPChatMessage]) {
guard let value = snapshot.value as? [String: Any] else { return }
self.chatID = snapshot.key
if let text = value["text"] as? String {
self.text = text
}
guard let timestamp = value["timestamp"] as? Double else { return }
self.chatDate = Date(timeIntervalSince1970: (timestamp / 1_000.0))
guard let author = value["author"] as? [String: String] else { return }
self.author = FPUser(dictionary: author)
self.messages = messages
self.mine = self.author.userID == Auth.auth().currentUser?.uid
}
}
What I am doing wrong?

Getting error while migrating to Swift 3.0

I am getting error while migrating to Swift 3. Below is the code in which error comes.
func getProfileFieldValue(_ formFields:NSMutableArray,keyValue:String) -> String {
for key in formFields{
if keyValue == key["name"] as! String{
return key["value"] as! String
}
}
return ""
}
Please help and thanks in advance.
NSMutableArray does not provide any type information, use native Swift array
func getProfileFieldValue(_ formFields:[[String:Any]], keyValue: String) -> String {
for key in formFields {
if let value = key["name"] as? String, value == keyValue {
return key["value"] as! String
}
}
return ""
}
or if the dictionaries contain only String values
func getProfileFieldValue(_ formFields:[[String:String]], keyValue: String) -> String {
for key in formFields {
if let value = key["name"], value == keyValue {
return key["value"]!
}
}
return ""
}
Or still swiftier
func getProfileFieldValue(_ formFields:[[String:Any]], keyValue: String) -> String {
if let profileField = formFields.first(where { $0["name"] as? String == keyValue }) {
return profileField["value"] as! String
}
return ""
}
Finally the waterproof-will-never-crash version:
func getProfileFieldValue(_ formFields:[[String:Any]], keyValue: String) -> String {
guard let profileField = formFields.first(where: { $0["name"] as? String == keyValue }),
let value = profileField["value"] as? String else { return "" }
return value
}
NSMutableArray does not provide type information, so you'll need to cast the array prior to the for loop
let array = NSMutableArray(array: [1, 2, 3, 4, 5])
let keyValue = 3
for item in array as! [Int]
{
if keyValue == item
{}
}

Casting AnyObject to Specific Class

I'm using socket.io Swift Library. With the following line of code,
socket.on("info") { (dataArray, socketAck) -> Void in
let user = dataArray[0] as? User
print(user._id)
}
dataArray[0] is a valid object but user appears to be nil after casting.
Since dataArray[0] returns as an AnyObject,
how can i cast AnyObject to User Object?. Or somehow manage to do what i want with a different approach?
Since after this line
let user = dataArray[0] as? User
you have a nil value inside user it means that you don't have a User value at the first position of dataArray.
Since dataArray comes from a server (as I guess) it probably contains a serialized version of User.
Now we really need to know what really dataArray[0] is. However...
if dataArray[0] contains NSData
In this case try this
let json = JSON(dataArray[0] as! NSData)
let user = User(json:json)
You need to create a constructor that accept AnyObject and read data in it.
I guess in this case dataArray[0] is an JSON Object.
class User {
init(data: [String: AnyObject]) {
username = data["username"] as? String ?? ""
}
}
This is how i manage mine:
// Structure used as parameter
struct InfoStruct {
var nome: String = ""
var sobrenome:String = ""
var nascimentoTimestamp: NSNumber = 0
init() {
}
// Struct to AnyObject
func toAnyObject() -> Any {
var dic = [String:AnyObject?]()
if (nome != "") { dic["nome"] = nome as AnyObject }
if (sobrenome != "") { dic["sobrenome"] = sobrenome as AnyObject }
if (nascimentoTimestamp != 0) { dic["nascimentoTimestamp"] = nascimentoTimestamp as AnyObject }
return dic
}
// AnyObject to Struct
func fromAnyObject(dic:[String:AnyObject]) -> InfoStruct {
var retorno = InfoStruct()
if (dic["nome"] != nil) { retorno.nome = dic["nome"] as? String ?? "" }
if (dic["sobrenome"] != nil) { retorno.sobrenome = dic["sobrenome"] as? String ?? "" }
if (dic["nascimentoTimestamp"] != nil) { retorno.nascimentoTimestamp = dic["nascimentoTimestamp"] as? NSNumber ?? 0 }
return retorno
} }
// User class
class Usuario: NSObject {
var key: String
var admin: Bool
var info: InfoStruct // Struct parameter
init(_ key: String?) {
self.key = key ?? ""
admin = false
info = InfoStruct() // Initializing struct
}
// From Class to AnyObject
func toAnyObject() -> Any {
var dic = [String:AnyObject?]()
if (key != "") { dic["key"] = key as AnyObject }
if (admin != false) { dic["admin"] = admin as AnyObject }
dic["info"] = info.toAnyObject() as AnyObject // Struct
return dic
}
// From AnyObject to Class
func fromAnyObject(dic:[String:AnyObject]) -> Usuario {
let retorno = Usuario(dic["key"] as? String)
if (dic["key"] != nil) { retorno.key = dic["key"] as? String ?? "" }
if (dic["admin"] != nil) { retorno.admin = dic["admin"] as? Bool ?? false }
if (dic["info"] != nil) { retorno.info = InfoStruct.init().fromAnyObject(dic: dic["info"] as! [String : AnyObject]) } // Struct
return retorno
} }
// Using
let dao = FirebaseDAO.init(_type: FirebaseDAOType.firebaseDAOTypeUser)
dao.loadValue(key: uid) { (error, values:[String : AnyObject]) in
if error == nil {
let user = Usuario(values["key"] as? String).fromAnyObject(dic: values)
}
}
I hope it helps!