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
Related
I a receiving the following complier error in the View_Preview section of code: "Missing argument for parameter 'userInfo' in call".
Xcode suggests the following fix: "(userInfo: [Employee])", however this fix gives the following complier error: "Cannot convert value of type '[Employee].Type' to expected argument type '[Employee]'"
Here is my view code:
struct EmployeeView: View {
#EnvironmentObject var pilot: Pilot
let userInfo: [Employee]
var body: some View {
ZStack {
List {
ForEach(userInfo, id: \.age) {line in
EmployeeCardView(employee: line)
}
}
}
}
}
struct EmployeeView_Previews: PreviewProvider {
static var previews: some View {
EmployeeView() //ERROR here
.environmentObject(Pilot)
}
}
Here is my data model code:
struct Employee: Identifiable, Codable {
var id = UUID()
var age: Int
var yearGroup: Int
var category: String
init(age: Int, yearGroup: Int, category: String) {
self.age = age
self.yearGroup = yearGroup
self.category = category
}
}
#MainActor class Pilot: ObservableObject {
#Published var pilot: Employee
init(pilot: Employee) {
self.pilot = pilot
}
var pilotInfo: [Employee] = [
Employee(age: 35, yearGroup: 1, category: "B717")]
}
I also tried this:
Is there an apple tutorial that explains the logic of this aspect of View_Previews?
During testing my Graph Project I receive the following error:
nft-lottery
Player entity
𝖷 (assert.fieldEquals) Expected field 'lotteries' to equal '[0x1]', but was '[]' instead.
🛠 Mapping aborted at ~lib/matchstick-as/assembly/assert.ts, line 13, column 7, with message: Assertion Error
wasm backtrace:
0: 0x387c - <unknown>!~lib/matchstick-as/assembly/assert/assert.fieldEquals
1: 0x38fa - <unknown>!start:tests/nft-lottery.test~anonymous|0~anonymous|3
If I log the results after calling handleNftLotteryEnter() function I get (i.e., the lottery.id has not been stored even though it should have as it is derivedFrom the NftLottery - see below):
"Player": {
"0xdb...a7bf": {
"lotteries": {
"type": "List",
"data": []
}
}
},
...
If I try to mock firing three subsequent events, the first two players has got values stored but the last one has not, i.e.:
"Player": {
"0x5a...3ca1": {
"lotteries": {
"type": "List",
"data": [
{
"type": "String",
"data": "0x1"
}
]
}
},
"0xd1...6575": {
"lotteries": {
"type": "List",
"data": []
}
},
"0xdb...a7bf": {
"lotteries": {
"type": "List",
"data": [
{
"type": "String",
"data": "0x1"
}
]
}
}
},
What could be the reason thereof?
Thank you in advance!
Test file
import {
assert,
describe,
test,
clearStore,
beforeAll,
afterAll,
logStore,
} from "matchstick-as/assembly/index"
import { BigInt, Address } from "#graphprotocol/graph-ts"
import { handleNftLotteryEnter } from "../src/nft-lottery"
import { createNftLotteryEnterEvent } from "./nft-lottery-utils"
describe("Enter the NFT Lottery 1x", () => {
beforeAll(() => {
let lotteryCounter = BigInt.fromI32(1)
let player_one = Address.fromString("0xDB...a7bf")
let entranceFee = BigInt.fromI32(1)
let newNftLotteryEnterEvent = createNftLotteryEnterEvent(
lotteryCounter,
player_one,
entranceFee
)
handleNftLotteryEnter(newNftLotteryEnterEvent)
logStore()
})
afterAll(() => {
clearStore()
})
test("Player entity", () => {
assert.fieldEquals("Player", "0xdb...a7bf", "lotteries", "[0x1]")
})
})
describe("Enter the NFT Lottery 3x", () => {
beforeAll(() => {
let lotteryCounter = BigInt.fromI32(1)
let entranceFee = BigInt.fromI32(1)
let player_one = Address.fromString("0xDB...a7bf")
let player_two = Address.fromString("0x5a...3cA1")
let player_three = Address.fromString("0xd1...6575")
// the first event
let oneNftLotteryEnterEvent = createNftLotteryEnterEvent(
lotteryCounter,
player_one,
entranceFee
)
handleNftLotteryEnter(oneNftLotteryEnterEvent)
// the second event
let twoNftLotteryEnterEvent = createNftLotteryEnterEvent(
lotteryCounter,
player_two,
entranceFee
)
// the third event
handleNftLotteryEnter(twoNftLotteryEnterEvent)
let threeNftLotteryEnterEvent = createNftLotteryEnterEvent(
lotteryCounter,
player_three,
entranceFee
)
handleNftLotteryEnter(threeNftLotteryEnterEvent)
logStore()
})
afterAll(() => {
clearStore()
})
test("Counts", () => {
assert.entityCount("NftLottery", 1)
assert.entityCount("Player", 3)
})
test("Player Entity 3x", () => {
assert.fieldEquals("Player", "0xdb...a7bf", "lotteries", "[0x1]")
})
})
Schema.graphql
type NftLottery #entity {
id: ID!
openAtTimestamp: BigInt!
closeAtTimestamp: BigInt!
prize: BigInt!
players: [Player!]!
winners: [Player!]
requestId: BigInt
updatedAtTimestamp: BigInt
}
type Player #entity {
id: ID! # address
lotteries: [NftLottery!]! #derivedFrom(field: "players")
}
# events
type NftLotteryEnter #entity {
id: ID! # Set lotteryCounter + playerAddress
lotteryCounter: BigInt!
player: Bytes!
numberOfEntrances: [BigInt!]!
updatedAtTimestamp: BigInt
}
Handlers
import { BigInt, Address } from "#graphprotocol/graph-ts"
import {
NftLotteryEnter as NftLotteryEnterEvent
} from "../generated/NftLottery/NftLottery"
import { NftLottery, Player, NftLotteryEnter } from "../generated/schema"
export function handleNftLotteryEnter(event: NftLotteryEnterEvent): void {
/* Event params */
const playerId = event.params.player.toHexString()
const lotteryId = event.params.lotteryCounter.toHexString()
const entranceFee = event.params.entranceFee.toString()
const lotteryPlayerId = lotteryId + playerId
/* NftLottery: if a lottery does not exist, create it */
let nftLottery = NftLottery.load(lotteryId)
if (!nftLottery) {
nftLottery = new NftLottery(lotteryId)
nftLottery.openAtTimestamp = event.block.timestamp
nftLottery.closeAtTimestamp = BigInt.fromString("000000000000")
nftLottery.requestId = BigInt.fromString("0")
nftLottery.prize = BigInt.fromString("0")
nftLottery.players = new Array<string>()
nftLottery.winners = new Array<string>()
}
// update lottery data
let currentPrize = nftLottery.prize
nftLottery.prize = currentPrize.plus(BigInt.fromString(entranceFee))
// update players
let arrPlayers = nftLottery.players
arrPlayers.push(playerId)
nftLottery.players = arrPlayers
nftLottery.updatedAtTimestamp = event.block.timestamp
nftLottery.save()
/* Player: if a player does not exist, create them */
let player = Player.load(playerId)
if (!player) {
player = new Player(playerId)
player.save()
}
/* NftLotteryEnter */
let nftLotteryEnter = NftLotteryEnter.load((lotteryPlayerId))
if (!nftLotteryEnter) {
nftLotteryEnter = new NftLotteryEnter(lotteryPlayerId)
nftLotteryEnter.lotteryCounter = BigInt.fromString(event.params.lotteryCounter.toString())
nftLotteryEnter.player = Address.fromHexString(playerId)
nftLotteryEnter.numberOfEntrances = BigInt.fromString("0")
}
// update entrances
const entrances = nftLotteryEnter.numberOfEntrances
nftLotteryEnter.numberOfEntrances = entrances.plus(BigInt.fromString("1"))
nftLotteryEnter.save()
}
There might be a bug in Matchstick testing tool:
As PlayerEntity derives lotteries from NftLottery, the NftLottery should be saved AT THE END of all operations related to both entities.
Otherwise Player.lotteries does not get updated immediately and it stays empty, thus test fails.
...
// update when updated
nftLottery.updatedAtTimestamp = event.block.timestamp
/* Player: if a player does not exist, create them */
let player = Player.load(playerId)
if (!player) {
player = new Player(playerId)
player.save()
}
nftLottery.save()
...
Credit goes to Maks#3349 at unit testing of Graph discord.
I have two nested ForEach statements that together create a list with section headers. The section headers are collected from a property of the items in the list. (In my example below, the section headers are the categories.)
I'm looking to enable the user to delete an item from the array underlying the list. The complexity here is that .onDelete() returns the index of the row within the section, so to correctly identify the item to delete, I also need to use the section/category, as explained in this StackOverflow posting. The code in this posting isn't working for me, though - for some reason, the category/territorie variable is not available for use in the onDelete() command. I did try converting the category to an index when iterating through the categories for the first ForEach (i.e., ForEach(0..< appData.sortedByCategories.count) { i in ), and that didn't work either. An added wrinkle is that I'd ideally like to use the appData.deleteItem function to perform the deletion (see below), but I couldn't get the code from the StackOverflow post on this issue to work even when I wasn't doing that.
What am I overlooking? The example below illustrates the problem. Thank you so much for your time and any insight you can provide!
#main
struct NestedForEachDeleteApp: App {
var appData = AppData()
var body: some Scene {
WindowGroup {
ContentView().environmentObject(appData)
}
}
}
import Foundation
import SwiftUI
class AppData: ObservableObject {
static let appData = AppData()
#Published var items = [Item]()
// Create a dictionary based upon the category
var dictGroupedByCategory: [String: [Item]] {
Dictionary(grouping: items.sorted(), by: {$0.category})
}
// Sort the category-based dictionary alphabetically
var sortedByCategories: [String] {
dictGroupedByCategory.map({ $0.key }).sorted(by: {$0 < $1})
}
func deleteItem(index: Int) {
items.remove(at: index)
}
init() {
items = [
Item(id: UUID(), name: "Item 1", category: "Category A"),
Item(id: UUID(), name: "Item 2", category: "Category A"),
Item(id: UUID(), name: "Item 3", category: "Category B"),
Item(id: UUID(), name: "Item 4", category: "Category B"),
Item(id: UUID(), name: "Item 5", category: "Category C"),
]
} // End of init()
}
class Item: ObservableObject, Identifiable, Comparable, Equatable {
var id = UUID()
#Published var name: String
#Published var category: String
// Implement Comparable and Equable conformance
static func <(lhs: Item, rhs: Item) -> Bool {
return lhs.name < rhs.name
}
static func == (lhs: Item, rhs: Item) -> Bool {
return lhs.category < rhs.category
}
// MARK: - Initialize
init(id: UUID, name: String, category: String) {
// Initialize stored properties.
self.id = id
self.name = name
self.category = category
}
}
struct ContentView: View {
#EnvironmentObject var appData: AppData
var body: some View {
List {
ForEach(appData.sortedByCategories, id: \.self) { category in
Section(header: Text(category)) {
ForEach(appData.dictGroupedByCategory[category] ?? []) { item in
Text(item.name)
} // End of inner ForEach (items within category)
.onDelete(perform: self.deleteItem)
} // End of Section
} // End of outer ForEach (for categories themselves)
} // End of List
} // End of body View
func deleteItem(at offsets: IndexSet) {
for offset in offsets {
print(offset)
// print(category) // error = "Cannot find 'category' in scope
// let indexToDelete = appData.items.firstIndex(where: {$0.id == item.id }) // Error = "Cannot find 'item' in scope
appData.deleteItem(index: offset) // this code will run but it removes the wrong item because the offset value is the offset *within the category*
}
}
} // End of ContentView
I've figured out a solution to my question above, and am posting it here in case anyone else ever struggles with this same issue. The solution involved using .swipeActions() rather than .onDelete(). For reasons I don't understand, I could attach .swipeActions() (but not .onDelete()) to the Text(item.name) line of code. This made the "item" for each ForEach iteration available to the .swipeAction code, and everything else was very straightforward. The revised ContentView code now looks like this:
struct ContentView: View {
#EnvironmentObject var appData: AppData
var body: some View {
List {
ForEach(appData.sortedByCategories, id: \.self) { category in
Section(header: Text(category)) {
ForEach(appData.dictGroupedByCategory[category] ?? []) { item in
Text(item.name)
.swipeActions(allowsFullSwipe: false) {
Button(role: .destructive) {
print(category)
print(item.name)
if let indexToDelete = appData.items.firstIndex(where: {$0.id == item.id }) {
appData.deleteItem(index: indexToDelete)
} // End of action to perform if there is an indexToDelete
} label: {
Label("Delete", systemImage: "trash.fill")
}
} // End of .swipeActions()
} // End of inner ForEach (items within category)
} // End of Section
} // End of outer ForEach (for categories themselves)
} // End of List
} // End of body View
} // End of ContentView
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")
}
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
}
}