Creating a JSON String from a Swift object in Xcode 7+ - swift2

I have the following class that I need to convert into a JSON String using Xcode 7 and above. In the previous version of Xcode there was a JSONSerelization.toJson(AnyObject) function available, however does not appear in Xcode7 .
I need to convert the following class :
struct ContactInfo
{
var contactDeviceType: String
var contactNumber: String
}
class Tradesmen
{
var type:String = ""
var name: String = ""
var companyName: String = ""
var contacts: [ContactInfo] = []
init(type:String, name:String, companyName:String, contacts [ContactInfo])
{
self.type = type
self.name = name
self.companyName = companyName
self.contacts = contacts
}
I Have set up my test data as follows
contactType =
[
ContactInfo(contactDeviceType: "Home", contactNumber: "(604) 555-1111"),
ContactInfo(contactDeviceType: "Cell", contactNumber: "(604) 555-2222"),
ContactInfo(contactDeviceType: "Work", contactNumber: "(604) 555-3333")
]
var tradesmen = Tradesmen(type: "Plumber", name: "Jim Jones", companyName: "Jim Jones Plumbing", contacts: contactType)
Any help or direction would be appreciated.

I do not think that there is any direct way, even in previous Xcode. You will need to write your own implementation. Something like below:
protocol JSONRepresenatble {
static func jsonArray(array : [Self]) -> String
var jsonRepresentation : String {get}
}
extension JSONRepresenatble {
static func jsonArray(array : [Self]) -> String {
return "[" + array.map {$0.jsonRepresentation}.joinWithSeparator(",") + "]"
}
}
And then implement JSONRepresentable in your modals like below:
struct ContactInfo: JSONRepresenatble {
var contactDeviceType: String
var contactNumber: String
var jsonRepresentation: String {
return "{\"contactDeviceType\": \"\(contactDeviceType)\", \"contactNumber\": \"\(contactNumber)\"}"
}
}
struct Tradesmen: JSONRepresenatble {
var type:String = ""
var name: String = ""
var companyName: String = ""
var contacts: [ContactInfo] = []
var jsonRepresentation: String {
return "{\"type\": \"\(type)\", \"name\": \"\(name)\", \"companyName\": \"\(companyName)\", \"contacts\": \(ContactInfo.jsonArray(contacts))}"
}
init(type:String, name:String, companyName:String, contacts: [ContactInfo]) {
self.type = type
self.name = name
self.companyName = companyName
self.contacts = contacts
}
}

Related

How to sort List ForEach Swiftui

Hi there
I'm newbie for SwiftUI, and I want to sort the "expireDate" , then use forEach to display the view according to the expireDate, how to???
sorry for my messy code, coding is really not easy.
will be much appreciate if someone can help
Here is the data
import Foundation
struct CardData: Identifiable, Codable {
let id: UUID
var cardName: String
var cardNumber: String
var expireDate: Date
var theme: Theme
var history: [History] = []
init(id: UUID = UUID(), cardName: String, cardNumber: String, expireDate: Date, theme: Theme) {
self.id = id
self.cardName = cardName
self.cardNumber = cardNumber
self.expireDate = expireDate
self.theme = theme
}
}
extension CardData {
struct Data {
var cardName: String = ""
var cardNumber: String = ""
var expireDate: Date = Date.now
var theme: Theme = .orange
}
var data: Data {
Data(cardName: cardName, cardNumber: cardNumber, expireDate: expireDate, theme: theme)
}
mutating func update(from data: Data) {
cardName = data.cardName
cardNumber = data.cardNumber
expireDate = data.expireDate
theme = data.theme
}
init(data: Data) {
cardName = data.cardName
cardNumber = data.cardNumber
expireDate = data.expireDate
theme = data.theme
id = UUID()
}
}
And here is the view
import SwiftUI
struct CardView: View {
#Binding var datas: [CardData]
#Environment(\.scenePhase) private var scenePhase
#State private var isPresentingNewCardView = false
#State private var newCardData = CardData.Data()
let saveAction: () -> Void
#EnvironmentObject var launchScreenManager: LaunchScreenManager
#State private var confirmationShow = false
var body: some View {
List {
ForEach($datas) { $data in
NavigationLink(destination: DetailView(cardData: $data)){
CardDataView(cardData: data)
}
.listRowBackground(data.theme.mainColor)
}
.onDelete(perform: deleteItems)
}
.navigationTitle("Expiry Date")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
Button(action: {
isPresentingNewCardView = true
}) {
Image(systemName: "plus")
}
.accessibilityLabel("New data")
}
.sheet(isPresented: $isPresentingNewCardView) {
NavigationView {
DetailEditView(data: $newCardData)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Dismiss") {
isPresentingNewCardView = false
newCardData = CardData.Data()
}
}
ToolbarItem(placement: .confirmationAction) {
Button("Add") {
let newData = CardData(data: newCardData)
datas.append(newData)
isPresentingNewCardView = false
newCardData = CardData.Data()
}
}
}
}
}
.onChange(of: scenePhase) { phase in
if phase == .inactive { saveAction() }
}
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
launchScreenManager.dismiss()
}
}
}
func deleteItems(at offsets: IndexSet) {
datas.remove(atOffsets: offsets)
}
}
Hi there
I'm newbie for SwiftUI, and I want to sort the "expireDate" , then use forEach to display the view according to the expireDate, how to???
sorry for my messy code, coding is really not easy.
will be much appreciate if someone can help
You can sort the datas in place, before you use it in the ForEach,
when you create the datas for example. Like this:
datas.sort(by: { $0.expireDate > $1.expireDate}).
Or
you can sort the datas just in the ForEach,
like this, since you have bindings,
ForEach($datas.sorted(by: { $0.expireDate.wrappedValue > $1.expireDate.wrappedValue})) { $data ...}
Note with this ForEach($datas.sorted(by: ...), when you do your func deleteItems(at offsets: IndexSet),
you will have to get the index in the sorted array, and delete the equivalent in the original one.
EDIT-1:
updated func deleteItems:
func deleteItems(at offsets: IndexSet) {
let sortedArr = datas.sorted(by: { $0.expireDate > $1.expireDate})
for ndx in offsets {
if let cardIndex = datas.firstIndex(where: { $0.id == sortedArr[ndx].id }) {
datas.remove(at: cardIndex)
}
}
}
Note you may want to put let sortedArr = datas.sorted(by: { $0.expireDate > $1.expireDate}) somewhere else (eg. .onAppear) instead of evaluating this, every time you use deleteItems

Save String Array With #AppStorage

In the following example I am saving a String with #AppStorage:
struct ContentView: View {
#AppStorage("text") private var text = ""
var body: some View {
Button("Append text: \(text)") {
text.append("APPEND")
}
}
}
But I want to save a String array, something like this:
#AppStorage("text") private var text = [
"APPEND",
"APPEND"
]
class Storage: NSObject {
static func archiveStringArray(object : [String]) -> Data {
do {
let data = try NSKeyedArchiver.archivedData(withRootObject: object, requiringSecureCoding: false)
return data
} catch {
fatalError("Can't encode data: \(error)")
}
}
static func loadStringArray(data: Data) -> [String] {
do {
guard let array = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? [String] else {
return []
}
return array
} catch {
fatalError("loadWStringArray - Can't encode data: \(error)")
}
}}
VIEW_
struct ContentView: View {
#State var TempTextArray: [String] = []
#AppStorage("textArray", store: UserDefaults(suiteName: "group.de.domain.MyApp")) var items: Data = Data()
var body: some View {
VStack(spacing: 30){
Button("Append text:") {
TempTextArray.append("Append")
items = Storage.archiveStringArray(object: TempTextArray)
}
Button("GET String Array"){
print(Storage.loadStringArray(data: items))
}
}
}}

Pre-enter text in a textfield in SwiftUI

I was able to solve my problem i had earlier on how to update data in Firestore via UI. Now the next problem:
I would like to have my Firebase-Stored Data pre-entered in a text field so you can just !edit! the text in the fields you want instead of writing all data new in every single field. As you can see, the only thing i was able to do, is getting the stored data in the text field as placeholder, which means you have to re-write all data in case you want to update just 1 field.
CODE:
import SwiftUI
import Firebase
struct ContentViewVier: View {
#ObservedObject var dataDrei = getDataZwei()
var body: some View {
NavigationView{
ZStack(alignment: .top){
GeometryReader{_ in
// Home View....
Text("Bitte Seriennummer eingeben").foregroundColor(.black)
}.background(Color("FarbeSeriennummerStartbildschirm").edgesIgnoringSafeArea(.all))
CustomSearchBarEdit(dataZwei: self.$dataDrei.datasZwei).padding(.top)
}.navigationBarTitle("")
.navigationBarHidden(true)
}
}
}
struct CustomSearchBarEdit : View {
#State var txt = ""
#Binding var dataZwei : [dataTypeZwei]
var body : some View{
VStack(spacing: 0){
HStack{
TextField("Nach Seriennummer suchen", text: self.$txt).opacity(100).foregroundColor(.black)
if self.txt != ""{
Button(action: {
self.txt = ""
}) {
Text("Abbrechen")
}
.foregroundColor(.black)
}
}.padding()
if self.txt != ""{
if
self.dataZwei.filter({$0.sn.lowercased() .contains(self.txt.lowercased())}).count == 0 {
Text("Es wurde kein Gerät mit dieser Seriennummer gefunden").foregroundColor(Color.red.opacity(0.6)).padding()
}
else{
List(self.dataZwei.filter{$0.sn.contains(self.txt.lowercased())}){j in
NavigationLink(destination: DetailZwei(data: j)) {
Text(j.sn)
}
Text(j.typ)
}.frame(height: UIScreen.main.bounds.height / 5)
}
}
}.background(Color.white)
.padding()
}
}
class getDataZwei : ObservableObject{
#Published var datasZwei = [dataTypeZwei]()
init() {
let db = Firestore.firestore()
db.collection("Geräte").getDocuments { (snap, err) in
if err != nil{
print((err?.localizedDescription)!)
return
}
for i in snap!.documents{
let id = i.documentID
let sn = i.get("Seriennummer") as! String
let objekt = i.get("Objekt") as! String
let str = i.get("Strasse") as! String
let pos = i.get("Position") as! String
let typ = i.get("Gerätetyp") as! String
let ida = i.get("Installation")as! String
let lg = i.get("LeasingOderGekauft")as! String
let la = i.get("LeasingAblaufdatum")as! String
let ga = i.get("GarantieAblaufdatum")as! String
let nr = i.get("Hausnummer")as! String
let plz = i.get("Postleitzahl")as! String
let ort = i.get("Ort")as! String
let vp = i.get("Verantwortlich")as! String
let tel = i.get("Telefonnummer")as! String
let zusatz = i.get("Zusätzlich")as! String
let ed = i.get("EingetragenDurch")as! String
let ldvds = i.get("LieferungBeiVDS")as! String
self.datasZwei.append(dataTypeZwei(id: id, sn: sn, pos: pos, typ: typ, ida: ida, lg: lg, la: la, ga: ga, objekt: objekt, str: str, nr: nr, plz: plz, ort: ort, vp: vp, tel: tel, zusatz: zusatz, ed: ed, ldvds: ldvds))
}
}
}
}
struct dataTypeZwei : Identifiable, Codable {
var id: String? = UUID().uuidString
var sn : String
var pos : String
var typ : String
var ida : String
var lg : String
var la : String
var ga : String
var objekt : String
var str : String
var nr : String
var plz : String
var ort : String
var vp : String
var tel : String
var zusatz : String
var ed : String
var ldvds : String
enum CodingKeys: String, CodingKey {
case sn = "Seriennummer"
case objekt = "Objekt"
case str = "Strasse"
case nr = "Hausnummer"
case ort = "Ort"
case vp = "Verantwortlich"
case tel = "Telefonnummer"
case pos = "Position"
case typ = "Gerätetyp"
case ida = "Installation"
case lg = "LeasingOderGekauft"
case la = "LeasingAblaufdatum"
case ga = "GarantieAblaufdatum"
case zusatz = "Zusätzlich"
case plz = "Postleitzahl"
case ed = "EingetragenDurch"
case ldvds = "LieferungBeiVDS"
}
}
struct DetailZwei : View {
var data : dataTypeZwei
#State var viewModel = GerätEditieren()
#State var serie: String? = nil
#Environment(\.presentationMode) var presentationMode
var body : some View {
NavigationView {
ScrollView {
Group {
Section(header: Text("Gerät")) {
Text("Seriennummer")
TextField(data.sn, text: $viewModel.gerät.sn).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Objekt")
TextField(data.objekt, text: $viewModel.gerät.objekt).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Gerätetyp")
TextField(data.typ, text: $viewModel.gerät.typ).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Geräteposition")
TextField(data.pos, text: $viewModel.gerät.pos).textFieldStyle(RoundedBorderTextFieldStyle())
}
Group {
Text("Installationsdatum")
TextField(data.ida, text: $viewModel.gerät.ida).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Leasing oder Gekauft?")
TextField(data.lg, text: $viewModel.gerät.lg).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Ablaufdatum Leasing")
TextField(data.la, text: $viewModel.gerät.la).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Ablaufdatum Garantie")
TextField(data.ga, text: $viewModel.gerät.ga).textFieldStyle(RoundedBorderTextFieldStyle())
}
Section(header: Text("Adresse")) {
Text("Strasse")
TextField(data.str, text: $viewModel.gerät.str).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Hausnummer")
TextField(data.nr, text: $viewModel.gerät.nr).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Postleitzahl")
TextField(data.plz, text: $viewModel.gerät.plz).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Ort")
TextField(data.ort, text: $viewModel.gerät.ort).textFieldStyle(RoundedBorderTextFieldStyle())
}
Section(header: Text("Kontakt")) {
Text("Ansprechperson")
TextField(data.vp, text: $viewModel.gerät.vp).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Telefonnummer")
TextField(data.tel, text: $viewModel.gerät.tel).textFieldStyle(RoundedBorderTextFieldStyle())
}
Section(header: Text("VDS")) {
Text("Eingetragen durch")
TextField(data.ed, text: $viewModel.gerät.ed).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Lieferdatum VDS")
TextField(data.ldvds, text: $viewModel.gerät.ldvds).textFieldStyle(RoundedBorderTextFieldStyle())
}
Section(header: Text("Zusätzliche Informationen")) {
Text("Zusätzliche Informationen")
TextField(data.zusatz, text: $viewModel.gerät.zusatz).textFieldStyle(RoundedBorderTextFieldStyle())
}
}.padding()
.navigationBarTitle("Gerät bearbeiten", displayMode: .inline)
.navigationBarItems(leading: Button(action: { self.handleCancelTapped() }, label: {
Text("Abbrechen")
}),
trailing: Button(action: { self.handleDoneTapped() }, label: {
Text("Speichern")
})
// .disabled(!viewModel.modified)
)
}
}
}
func handleCancelTapped() {
dismiss()
}
func handleDoneTapped() {
let db = Firestore.firestore()
let docRef = db.collection("Geräte").document(data.id!)
print("setting data")
docRef.updateData(["Seriennummer": "\(viewModel.gerät.sn)", "Objekt": "\(viewModel.gerät.objekt)", "Strasse": "\(viewModel.gerät.str)", "Hausnummer": "\(viewModel.gerät.nr)", "Ort": "\(viewModel.gerät.ort)", "Verantwortlich": "\(viewModel.gerät.vp)", "Telefonnummer": "\(viewModel.gerät.tel)", "Position": "\(viewModel.gerät.pos)", "Gerätetyp": "\(viewModel.gerät.typ)", "Installation": "\(viewModel.gerät.ida)", "LeasingOderGekauft": "\(viewModel.gerät.lg)", "LeasingAblaufdatum": "\(viewModel.gerät.la)", "GarantieAblaufdatum": "\(viewModel.gerät.ga)", "Zusätzlich": "\(viewModel.gerät.zusatz)", "Postleitzahl": "\(viewModel.gerät.plz)", "EingetragenDurch": "\(viewModel.gerät.ed)", "LieferungBeiVDS": "\(viewModel.gerät.ldvds)"]){ (error) in
if let error = error {
print("error = \(error)")
} else if self.viewModel.gerät.sn == "" {
self.viewModel.gerät.sn = self.data.sn
}
else {
print("data uploaded successfully")
}
}
dismiss()
}
func dismiss() {
presentationMode.wrappedValue.dismiss()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentViewVier()
}
}
This belong to your viewModel to initialize the Published values with the exact value.
Another option is to set the State variables, which you pass to your TextField with the actual value.
#State var vp : String
init(data: Data) { 
self._vp = State(initialValue: data.vp) //<< here set the value from Firebase
}
TextField(data.vp, text: $vp).textFieldStyle(RoundedBorderTextFieldStyle())

Specify Key Path in a List Swift UI

I have below structure , where GroceryData has details about the section as [GrocerySection] , this in turn has items to be displayed in the section as [Grocery].
struct GroceryData {
var showFavorites:Bool = false
var sections:[GrocerySection] = [GrocerySection(sectionName: "Common Items")]
}
struct GrocerySection {
var sectionName:String
var items:[Grocery] = [Grocery(id:1, name: "Milk", isFavorite: true, price: 1.99)]
}
struct Grocery: Identifiable,Hashable, Codable {
var id:Int
var name:String
var isFavorite:Bool
var price:Float
}
What should be the key path for the identifiable property.
struct ContentView: View {
var data:GroceryData
var body: some View {
List(data.sections, id: \GrocerySection.items.id) { (item) -> Text in
Text("Hello")
}
}
}
since you are dealing with sections, this may work:
List(data.sections, id: \.self.sectionName) { section in
Text("hello section \(section.sectionName)")
}
as long as the sectionName is unique, otherwise you can always add and id field.
If you want to loop over items you can try this:
List(data.sections, id: \.self.sectionName) { section in
ForEach(section.items) { item in
Text("\(item.name)")
}
}
You iterate list of sections so GrocerySection must be identifiable, like
struct GrocerySection: Identifiable {
var id = UUID() // << this
// var id: String { sectionName } // << or even this
var sectionName:String
var items:[Grocery] = [Grocery(id:1, name: "Milk", isFavorite: true, price: 1.99)]
}
then you can write
List(data.sections) { (section) -> Text in
Text("Hello")
}
or using keypath if every section name is unique, as
List(data.sections, id: \.sectionName) { (section) -> Text in
Text("Hello")
}

Alamofire parameter with array in class

This is a recurrent problem for me - can I use SwiftyJSON to do this or how?
How can you get the following JSON via .POST with Alamofire :
{
"class":"Class of 1969",
"students":
[
{
"name":"A name",
"age":25
},
{
"name": "B name",
"age": 25
}
]
}
I have the following class:
import UIKit
import SwiftyJSON
class Student{
var Name:String = ""
var Age: Int = 0
}
class StudentsOf1969{
var Teacher: String = ""
var Students = [Student]()
}
Alamofire
Alamofire.request(.POST,
strUrl,
parameters: parameters, // How?????????
encoding: .JSON,
headers: headersWish).responseObject {...
I have tried something like this - adding a var dictionary: [String: AnyObject] to the class:
class Student{
var Name:String = ""
var Age: Int = 0
}
class StudentsOf1969{
var Teacher: String = ""
var Students = [Student]()
var dictionary: [String: AnyObject]{
get {
return [
"teacher": self.Teacher,
"students": self.Students,
// or
"students": [self.Students],
]
}
}
}
In addition to giving StudentsOf1969 a dictionary property, you should do the same with Student. And then when you call the description method of StudentsOf1969, it will call the dictionary property for each Student in the array (which can be done quite gracefully with Swift's map function).
For example:
struct Student {
var name: String
var age: Int
var dictionary: [String: AnyObject] {
get {
return ["name": name, "age": age]
}
}
}
struct ClassOfStudents {
var className: String
var students: [Student]
var dictionary: [String: AnyObject] {
get {
return [
"class": className,
"students": students.map { $0.dictionary }
]
}
}
}
Note, I renamed StudentsOf1969 to ClassOfStudents to make it more clear that it's, more generally, used for any class of students, not those in 1969. Also, your JSON referenced class name, not teacher name, so I modified that accordingly.
Anyway, you can now get the parameters dictionary by calling dictionary method of classOf1969:
let classOf1969 = ClassOfStudents(className: "Class of 1969", students: [
Student(name: "A name", age: 25),
Student(name: "B name", age: 25)
])
let parameters = classOf1969.dictionary

Resources