currently i am working on a class which initializes an multidimensional array:
class Manager {
var data: Dictionary<String,NSDictionary>
init(data: Dictionary<String,NSDictionary>) {
func getApiData() {
getApiDataResource() { responseObject, error in
let resourceA = responseObject!
self.data["resources"] = resourceA
}
}
}
}
The responseObject which is returned asyncronously has this structure:
{
"data": [
{
id: 1
name: "Name1"
},
{
id: 2
name: "Name2"
}
],
"count": 2,
"success": true
}
The structure i want to get in my "data"-variable:
{
"resources": [
{
"resourceA":
{
"data": [
{
id: 1
name: "Name1"
},
{
id: 2
name: "Name2"
}
],
"count": 2,
"success": true
}
},
{
"resourceB": // and so on ...
}
]
}
But when saving my responseObject into my "data"-variable:
self.data["resources"] = resourceAData // this line fails
It prints:
Cannot assign to the result of this expression
Anybody could help me with this problem??
Thanks and Greetings!
I assume there is a function defined as follow:
func getApiDataResource(callback: (responseObject:NSDictionary?, error:NSError?) -> ()) {
// data retrieved from server at warp 8 here and other futuristic things
}
Step 1 (required)
I moved the function getApiData outside the init.
class Manager {
var data: Dictionary<String,NSDictionary>
init(data: Dictionary<String,NSDictionary>) {
getApiData()
}
func getApiData() {
getApiDataResource() { responseObject, error in
let resourceA = responseObject!
self.data["resources"] = resourceA
}
}
}
Now the compiler is complaining about this:
Use of self in method call getApiData before all store properties are initialized
and this
Return from initializer without initializing all stored properties
(As stated by you in a comment)
Fine. Let's fix it.
Step 2 (required)
class Manager {
var data: Dictionary<String,NSDictionary>
init(data: Dictionary<String,NSDictionary>) {
self.data = data
getApiData()
}
func getApiData() {
getApiDataResource() { responseObject, error in
let resourceA = responseObject!
self.data["resources"] = resourceA
}
}
}
Now it does compiles properly.
However I would adjust a couple of other things.
Step 3 (suggested)
Inside the trailing closure you are using the !. You should avoid this when possible. And you are not checking the error parameter that could be not nil.
You should use conditional unwrapping that is a safe alternative to the force unwrap (you did with !).
class Manager {
var data: Dictionary<String,NSDictionary>
init(data: Dictionary<String,NSDictionary>) {
self.data = data
getApiData()
}
func getApiData() {
getApiDataResource() { responseObject, error in
if let resourceA = responseObject where error == nil {
self.data["resources"] = resourceA
}
}
}
}
Now the code does not crash if responseObject from the closure is nil.
Step 4 (just a preference)
This is just a style preference.
Swift allows you to define your dictionary this way (as you did).
Dictionary<String,NSDictionary>
or this way:
[String:NSDictionary]
The 2 statement are equivalent. I just find more essential the second one. So...
class Manager {
var data: [String:NSDictionary]
init(data: [String:NSDictionary]) {
self.data = data
getApiData()
}
private func getApiData() {
getApiDataResource() { responseObject, error in
if let resourceA = responseObject where error == nil {
self.data["resources"] = resourceA
}
}
}
}
Let me know is this is what you need!
You have let data: Dictionary<String, Array<String>>, should be var data: Dictionary<String, Array<String>>
Related
Model:
enum TaskType: Int, Codable {
case upcoming = 0
case inProgress
case testing
case completed
var title: String {
switch self {
case .upcoming:
return "Upcoming"
case .inProgress:
return "In Progress"
case .testing:
return "Testing"
case .completed:
return "Completed"
}
}
}
struct TasksModel: Encodable, Decodable {
var upcomingArray: [TaskInfo]
var inProgressArray: [TaskInfo]
var testingArray: [TaskInfo]
var completedArray: [TaskInfo]
}
struct TaskInfo: Codable, Equatable, Identifiable {
var id: String
var title: String
var description: String
var taskStatus: TaskType
var taskDate = Date()
}
VM:
class HomeVM: ObservableObject {
#Published var tasksArray: TasksModel
self.tasksArray = TasksModel.init(upcomingArray: [], inProgressArray: [], testingArray: [], completedArray: [])
}
So now that I could locate the record with received taskID and change the taskStatus, I need also to move the record from upcomingArray to inProgressArray. This is what I’m trying:
func inProgressSetTask(taskID: String) {
#StateObject var viewModel = HomeVM()
if let index = viewModel.tasksArray.upcomingArray.firstIndex(where: {$0.id == taskID}) {
// Update task status
viewModel.tasksArray.upcomingArray[index].taskStatus = .inProgress
// Need to remove from upcomingArray and append into inProgressArray
viewModel.tasksArray.upcomingArray.remove(at: index)
var lastIndex = viewModel.tasksArray.inProgressArray.last
viewModel.tasksArray.inProgressArray[lastIndex].append()
viewModel.save()
// End
} else {
…
Updating taskStatus above working fine but remove from one array into another is not.
This code above will repeat for each array after else. Appreciate any help.
you could try the following example code to achieve what you want:
(note, you should have #StateObject var viewModel = HomeVM() outside of the func inProgressSetTask(taskID: String) {...}
or pass it in as a parameter)
EDIT-1: moving the function with all arrays into HomeVM and assuming id are unique.
func inProgressSetTask(taskID: String) {
print("InProgress Set ID# \(taskID)")
// with index, using `firstIndex`
if let index = viewModel.tasksArray.inProgressArray.firstIndex(where: {$0.id == taskID}) {
// do something with the index
viewModel.tasksArray.inProgressArray[index].title = "xxx"
}
// with TaskInfo, using `first`
if var taskInfo = viewModel.tasksArray.inProgressArray.first(where: {$0.id == taskID}) {
// do something with the taskInfo
taskInfo.title = "xxx"
}
}
With all arrays of TaskInfo, use the function setTaskFromAll(...) in HomeVM. For example: viewModel.setTaskFromAll(taskID: "1")
class HomeVM: ObservableObject {
#Published var tasksArray: TasksModel = TasksModel.init(upcomingArray: [], inProgressArray: [], testingArray: [], completedArray: [])
func setTaskFromAll(taskID: String) {
if let index = tasksArray.inProgressArray.firstIndex(where: {$0.id == taskID}) {
tasksArray.inProgressArray[index].title = "inProgress"
} else {
if let index = tasksArray.completedArray.firstIndex(where: {$0.id == taskID}) {
tasksArray.completedArray[index].title = "completed"
} else {
if let index = tasksArray.testingArray.firstIndex(where: {$0.id == taskID}) {
tasksArray.testingArray[index].title = "testing"
} else {
if let index = tasksArray.upcomingArray.firstIndex(where: {$0.id == taskID}) {
tasksArray.upcomingArray[index].title = "upcoming"
}
}
}
}
}
}
EDIT-2:
However, since you already have the "TaskType" of each array in the TaskInfo struct, why not remove TasksModel
and use a single array of TaskInfo in your HomeVM. Like this:
class HomeVM: ObservableObject {
#Published var tasksArray: [TaskInfo] = [
TaskInfo(id: "1", title: "title1", description: "description1", taskStatus: .upcoming),
TaskInfo(id: "2", title: "title2", description: "description2", taskStatus: .inProgress)
// ....
]
func setTask(taskID: String, to taskType: TaskType) {
if let index = tasksArray.firstIndex(where: {$0.id == taskID}) {
tasksArray[index].taskStatus = taskType
}
}
func getAllTaskInfo(_ oftype: TaskType) -> [TaskInfo] {
return tasksArray.filter{$0.taskStatus == oftype}
}
}
and use it like this: viewModel.setTask(taskID: "1", to: .testing) and viewModel.getAllTaskInfo(.inProgress)
EDIT-3: to remove from one array and append to another, using your TasksModel scheme, use this:
class HomeVM: ObservableObject {
#Published var tasksArray: TasksModel = TasksModel(upcomingArray: [
TaskInfo(id: "1", title: "title1", description: "description1", taskStatus: .upcoming),
TaskInfo(id: "2", title: "title2", description: "description2", taskStatus: .upcoming)
], inProgressArray: [
TaskInfo(id: "3", title: "title3", description: "description3", taskStatus: .inProgress),
TaskInfo(id: "4", title: "title4", description: "description4", taskStatus: .inProgress)
], testingArray: [], completedArray: [])
func inProgressSetTask(taskID: String) {
if let index = tasksArray.upcomingArray.firstIndex(where: {$0.id == taskID}) {
// update task status
tasksArray.upcomingArray[index].taskStatus = .inProgress
// get the upcomingArray taskInfo
let taskInfo = tasksArray.upcomingArray[index]
// remove from upcomingArray
tasksArray.upcomingArray.remove(at: index)
// append to inProgressArray
tasksArray.inProgressArray.append(taskInfo)
} else {
// ...
}
}
}
Use it like this: viewModel.inProgressSetTask(taskID: "1")
As you can plainly see, you are much better-off with the EDIT-2, you are repeating/duplicating things in EDIT-3 for no reason. There is no need for separate arrays for the different TaskType, you already have this info in the TaskInfo var taskStatus: TaskType. With EDIT-2, use viewModel.getAllTaskInfo(.inProgress) to get all TaskInfo of a particular type, just like it would be if you used a separate array.
You are attempting to compare a String to a TaskInfo, because the elements of an inProgressArray are of type TaskInfo. What you need to do is drill into the array and get to the .id. That is simple to do. In the .first(where:), you simply pass a closure of $0.id == taskID.
if let index = viewModel.tasksArray.inProgressArray.first(where: { $0.id == taskID } ) {
How can the following JSON array be filtered to exclude values type = quiet to only initiate viewcontrollers where test.testid type everything else.
Currently doing the following:
do {
let nav = try JSONDecoder().decode([TestStruct].self, from: data)
self.viewControllers = nav.map { test -> UIViewController in
let selected = UIImage(named: "Tab4_Large")!
let normal = UIImage(named: "Tab4_Large")!
let controller = storyboard!.instantiateViewController(withIdentifier: String(test.testid))
controller.view.backgroundColor = UIColor.white
controller.floatingTabItem = FloatingTabItem(selectedImage: selected, normalImage: normal)
return controller
}
JSON Decode Structure:
struct TestStruct: Decodable {
let testname: String
let type: String
let testid: Int
}
Need to exclude type = "quiet", but include the rest in the nav.map
Example JSON:
[
{
"testname": "Teller",
"type": "load",
"testid": 2
},
{
"testname": "Manager",
"type": "load",
"testid": 8
},
{
"testname": "Tester",
"type": "quiet",
"testid": 8
}
]
For the result that you want to achieve. First need to filter the array with the values not having type = quiet.
let filteredArray = nav.filter { $0.type != "quiet" }
then you can use compactMap to get your desired result. I am not sure about the instantiateViewController part will work the way you want.
But I am adding a simple example which I tried in Playground:
var stringData = "[{\"testname\":\"Teller\",\"type\":\"load\",\"testid\":2},{\"testname\":\"Manager\",\"type\":\"load\",\"testid\":8},{\"testname\":\"Tester\",\"type\":\"quiet\",\"testid\":8}]"
let data = stringData.data(using: .utf8)!
struct TestStruct: Decodable {
let testname: String
let type: String
let testid: Int
}
struct ViewControl {
let id: Int
}
do {
let nav = try JSONDecoder().decode([TestStruct].self, from: data)
let filteredArray = nav.filter { $0.type != "quiet" }
print(filteredArray)
let controllers = filteredArray.compactMap({ test -> ViewControl in
let viewControl = ViewControl(id: test.testid)
return viewControl
})
print(controllers)
} catch let error as NSError {
print(error)
}
Hello can you help me get array of all the data including global_rank, brawlhalla_id, etc.? I am only able to get array of legend like legend_id, legend_key_name. This is the json
{
"name": "bmg | dan",
"brawlhalla_id": 2,
"rating": 1745,
"peak_rating": 1792,
"tier": "Platinum 2",
"wins": 207,
"games": 391,
"region": "US-E",
"global_rank": 5698,
"region_rank": 1644,
"legends": [
{
"legend_id": 4,
"legend_name_key": "cassidy",
"rating": 1736,
"peak_rating": 1792,
"tier": "Platinum 1",
"wins": 161,
"games": 300
},
{
"legend_id": 21,
"legend_name_key": "barraza",
"rating": 1640,
"peak_rating": 1640,
"tier": "Gold 5",
"wins": 41,
"games": 77
}
],
...
My code looks like this:
import SwiftUI
class BrawlhallaSevice {
let url = URL(string: "http://sion.red/brawlhalla/ranked.json")!
func getBrawlhalla(completion: #escaping (Result<[Brawlhalla], Error>) -> Void) {
URLSession.shared.dataTask(with: url) { data, response, error in
guard error == nil else {
completion(.failure(error!))
return
}
completion(.success(self.getBrawlhallaResponse(fromData: data!)))
}.resume()
}
private func getBrawlhallaResponse(fromData data: Data) -> [Brawlhalla] {
let brawlhallaData = try? JSONDecoder().decode(BrawlhallaResponse.self, from: data)
if let brawlhallaD = brawlhallaData {
print(brawlhallaD)
//return brawlhallaD.legends works and returns array of data about a legend
return brawlhallaD
}
return [Brawlhalla(legend_name_key: "Error", rating: 0, peak_rating: 0, tier: "Error")]
}
}
But it throws an error: "Cannot convert return expression of type 'BrawlhallaResponse' to return type '[Brawlhalla]'".
This is how my models look
import Foundation
struct BrawlhallaResponse: Codable {
let legends: [Brawlhalla]
let name: String
let rating: Int
let peak_rating: Int
let tier: String
}
import Foundation
struct Brawlhalla: Codable {
let name: String
let rating: Int
let peak_rating: Int
let tier: String
}
I have tried many changes to the code but nothing seem to work.
I think you got the right code, but it seems like you're trying to return brawhallaD in that if let statement. You're getting the error because your return type is [Brawlhalla], but you're returning a decodedResponse of type BrawlhallaResponse.
So what you can do is this:
class BrawlhallaSevice {
let url = URL(string: "http://sion.red/brawlhalla/ranked.json")!
func getBrawlhalla(completion: #escaping (Result<BrawlhallaResponse?, Error>) -> Void) {
URLSession.shared.dataTask(with: url) { data, response, error in
guard error == nil else {
completion(.failure(error!))
return
}
guard let safeData = data else { return }
completion(.success(self.getBrawlhallaResponse(fromData: safeData)))
}.resume()
}
private func getBrawlhallaResponse(fromData data: Data) -> BrawlhallaResponse?{
let brawlhallaData = try? JSONDecoder().decode(BrawlhallaResponse.self, from: data)
if let brawlhallaD = brawlhallaData {
return brawlhallaD // Will return BrawlhallaResponse.peak_rating and also legends
}
return nil
}
}
I am trying to create a basic to-do application for command-line in Swift. Below is my function to add a new item to the to-do array, but the new entry keeps overwriting the old one, instead of creating a new one. In the end, there is only one entry in the todo.json file.
When I multiply the entries and .append statements manually it works, but probably my brain is too dead to figure it out at the moment.
struct TodoItem: Codable {
let name: String
}
var todoList = [TodoItem]()
func addToList(_ item: String) -> String {
let todoItem = TodoItem(name: item)
todoList.append(todoItem)
do {
let fileURL = try FileManager.default
.url(for: .applicationSupportDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: true)
.appendingPathComponent("example")
.appendingPathExtension("json")
let encoder = JSONEncoder()
try encoder.encode(todoList).write(to: fileURL)
} catch {
return "Error: \(error.localizedDescription)"
}
return "Item added: \(todoItem.name)"
}
Your code works fine. I think the problem rests in the fact that todoList is empty when you run this. But you can write code to retrieve the contents of the JSON:
var todoList: [TodoItem] = []
func retrieveToDoList() {
guard let data = try? Data(contentsOf: fileURL) else { return }
todoList = (try? JSONDecoder().decode([TodoItem].self, from: data)) ?? []
}
So, for example, consider:
retrieveToDoList()
addToList("foo")
addToList("bar")
addToList("baz")
print(todoList)
// if you want to check the json
let data = try! Data(contentsOf: fileURL)
let json = String(data: data, encoding: .utf8)!
print(json)
That results in:
[MyApp.TodoItem(name: "foo"), MyApp.TodoItem(name: "bar"), MyApp.TodoItem(name: "baz")]
[
{
"name" : "foo"
},
{
"name" : "bar"
},
{
"name" : "baz"
}
]
And if I later do:
addToList("abc")
addToList("def")
addToList("hij")
I then get:
[
{
"name" : "foo"
},
{
"name" : "bar"
},
{
"name" : "baz"
},
{
"name" : "abc"
},
{
"name" : "def"
},
{
"name" : "hij"
}
]
So, every time the app starts up, just make sure to call retrieveToDoList before trying to append items or else the toDoList will be empty.
FYI, this is the code I used to generate the above. Hopefully it illustrates the idea.
struct TodoItem: Codable {
let name: String
}
class ViewController: UIViewController {
private var todoList: [TodoItem] = []
private let fileURL = try! FileManager.default
.url(for: .applicationSupportDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: true)
.appendingPathComponent("example.json")
override func viewDidLoad() {
super.viewDidLoad()
retrieveToDoList()
addToList("foo")
addToList("bar")
addToList("baz")
print(todoList)
// if you want to check the json
let data = try! Data(contentsOf: fileURL)
let json = String(data: data, encoding: .utf8)!
print(json)
}
}
private extension ViewController {
func retrieveToDoList() {
guard let data = try? Data(contentsOf: fileURL) else { return }
todoList = (try? JSONDecoder().decode([TodoItem].self, from: data)) ?? []
}
#discardableResult
func addToList(_ item: String) -> String {
let todoItem = TodoItem(name: item)
todoList.append(todoItem)
do {
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
try encoder.encode(todoList).write(to: fileURL)
} catch {
return "Error: \(error.localizedDescription)"
}
return "Item added: \(todoItem.name)"
}
}
I am working on a way to add new JSON data to my existing JSON array:
var resources: JSON = [
"resources": []
]
override func viewDidLoad() {
super.viewDidLoad()
getApiResourceA() { responseObject, error in
var resourceA: JSON = [
"resourceA": []
]
let resourceAResponseObject = JSON(responseObject!)
resourceA["resourceA"] = resourceAResponseObject
self.resources["resources"] = resourceA
}
getApiResourceB() { responseObject, error in
var resourceB: JSON = [
"resourceB": []
]
let resourceBResponseObject = JSON(responseObject!)
resourceB["resourceB"] = resourceBResponseObject
self.resources["resources"] = resourceB
}
}
The structure I am trying to get is:
{
"resources": {
"resourceA": {
"id": 1
"name": "Name1"
}
"resourceB": {
"id": 2
"name": "Name2"
}
}
}
But in my code there are two different "resources"-array created...
Anyone know how to deal with this?
First it is important to understand that JSON is Struct means it is duplicated every time you pass it or use it.
Another issue, you declared resources as Array and not as Dictionary means you can use resource as key.
Declare extensions:
extension JSON{
mutating func appendIfArray(json:JSON){
if var arr = self.array{
arr.append(json)
self = JSON(arr);
}
}
mutating func appendIfDictionary(key:String,json:JSON){
if var dict = self.dictionary{
dict[key] = json;
self = JSON(dict);
}
}
}
use:
//notice the change [String:AnyObject]
var resources: JSON = [
"resources": [String:AnyObject](),
]
resources["resources"].appendIfDictionary("resourceA", json: JSON(["key1":"value1"]))
resources["resources"].appendIfDictionary("resourceB", json: JSON(["key2":"value2"]))
result:
{
"resources" : {
"resourceB" : {
"key2" : "value2"
},
"resourceA" : {
"key1" : "value1"
}
}
}
#daniel-krom has right, but is a little confusing implement the extension, so, we need only add at the end of the Swift controller (or class) that code that adds the "append" methods, nothing else.
Using the appendIfArray method, I could pass from this
[
{
"id_usuario" : 2
}
]
...to this
[
{
"id_usuario" : 2
},
{
"id_usuario" : 111
},
{
"id_usuario" : 112
},
{
"id_usuario" : 113
}
]
The complete code is below:
do{
try json2!["usuarios"][indice]["fotos"][0]["me_gusta"].appendIfArray(json: JSON( ["id_usuario": 111] ))
try json2!["usuarios"][indice]["fotos"][0]["me_gusta"].appendIfArray(json: JSON( ["id_usuario": 112] ))
try json2!["usuarios"][indice]["fotos"][0]["me_gusta"].appendIfArray(json: JSON( ["id_usuario": 113] ))
}catch {
print("Error")
}
The complete JSON structure can find in
http://jsoneditoronline.org/?id=56988c404dcd3c8b3065a583f9a41bba
I hope this can be useful