swiftUI array.first(where: not working with struct/class array - arrays

I am trying to find a string within an array using this code;
var userArray = [UserItem]()
var foundUser: String {
guard let findUser = userArray.first(where: { $0 == item.name }) else { return "Not found" }
return findUser
}
But I am getting the following error message;
"Cannot convert value of type '(String) -> Bool' to expected argument type '(UserList) throws -> Bool'"
So I tried adding a standard array;
var userArray = ["1", "Gale Dyer", "3", "4"]
and got rid of the error and the result I intended.
I assume it is because my struct or class does not conform to String but I am not sure how I fix this as adding ", String" doesn't seem to be the answer.
For reference here is the other data;
struct UserItem: Codable, Identifiable {
var id: String
var isActive: Bool
var name: String
var age: Int
var company: String
var email: String
var address: String
var about: String
// var registered: Date
var tags: [String]
var friends: [Friend]
}
struct Friend: Codable {
var id: String
var name: String
}
class UserList: ObservableObject {
#Published var items = [UserItem]()
{
didSet {
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(items) {
UserDefaults.standard.set(encoded, forKey: "Items")
}
}
}
init() {
if let items = UserDefaults.standard.data(forKey: "Items") {
let decoder = JSONDecoder()
if let decoded = try? decoder.decode([UserItem].self, from: items) {
self.items = decoded
return
}
}
self.items = []
}
}
Thank you in advance

You have to check with same type in the closure of first(where:) method. Here's the fix.
var foundUser: String {
guard let findUser = userArray.first(where: { $0.name == item.name })?.name else { return "Not found" }
return findUser
}

Related

Creating an array from stuct data SwiftUI

First of all, i am very sorry for the noob question, but i just cant seem to figure this out.
I am very new to coding and just started to get my feet wet with SwiftUI, following a few courses and started to dabble in trying to create some basic apps.
I am currently working on an app that does an API call and displays the data.
My issue is, im trying to put the decoded data into an array, it sounds so simple and I think i am missing something very easy, but for the life of me I cant seem to figure it out.
Below is the codable struct I have
struct Drinks: Codable, Identifiable {
let id = UUID()
let strDrink : String
let strInstructions: String
let strDrinkThumb: String?
let strIngredient1: String?
let strIngredient2: String?
let strIngredient3: String?
let strIngredient4: String?
let strIngredient5: String?
}
I want to put the ingredients into an Array so I can go through them in lists etc
import SwiftUI
struct IngredientView: View {
let drink : Drinks
let ingredientArray : [String] = [] // I want to append the ingredients here
var body: some View {
GroupBox() {
DisclosureGroup("Drink Ingredience") {
ForEach(0..<3) { item in
Divider().padding(.vertical, 2)
HStack {
Group {
// To use the array here
}
.font(Font.system(.body).bold())
Spacer(minLength: 25)
}
}
}
}
}
}
Again, sorry for the noob question that probably has a simple answer, but worth a shot asking :D
Thanks!
you could use this approach to get all your ingredients into an array and use
it in Lists. The idea is to use a function to gather all your ingredients into an array of
Ingredient objects. You could also use a computed property.
It is best to use a Ingredient object and declare it Identifiable
so that when you use them in list and ForEach, each one will be
unique, even if the names are the same.
import SwiftUI
#main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
#State var drinkList = [Drink]()
var body: some View {
List {
ForEach(drinkList) { drink in
VStack {
Text(drink.strDrink).foregroundColor(.blue)
Text(drink.strInstructions)
ForEach(drink.allIngredients()) { ingr in
HStack {
Text(ingr.name).foregroundColor(.red)
Text(ingr.amount).foregroundColor(.black)
}
}
}
}
}
.task {
let theResponse: ApiResponse? = await getData(from: "https://www.thecocktaildb.com/api/json/v1/1/search.php?s=margarita")
if let response = theResponse {
drinkList = response.drinks
}
}
}
func getData<T: Decodable>(from urlString: String) async -> T? {
guard let url = URL(string: urlString) else {
print(URLError(.badURL))
return nil // <-- todo, deal with errors
}
do {
let (data, response) = try await URLSession.shared.data(for: URLRequest(url: url))
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
print(URLError(.badServerResponse))
return nil // <-- todo, deal with errors
}
return try JSONDecoder().decode(T.self, from: data)
}
catch {
print("---> error: \(error)")
return nil // <-- todo, deal with errors
}
}
}
struct ApiResponse: Decodable {
var drinks: [Drink]
}
struct Drink: Decodable, Identifiable {
let id = UUID()
let idDrink: String
let strDrink: String
let strDrinkThumb: String
let strAlcoholic: String
let strGlass: String
let strInstructions: String
let strIngredient1: String?
let strIngredient2: String?
let strIngredient3: String?
let strIngredient4: String?
let strIngredient5: String?
let strIngredient6: String?
let strIngredient7: String?
let strIngredient8: String?
let strIngredient9: String?
let strIngredient10: String?
var strMeasure1: String?
var strMeasure2: String?
var strMeasure3: String?
var strMeasure4: String?
var strMeasure5: String?
var strMeasure6: String?
var strMeasure7: String?
var strMeasure8: String?
var strMeasure9: String?
var strMeasure10: String?
// --- here adjust to your needs, could also use a computed property
func allIngredients() -> [Ingredient] {
return [
Ingredient(name: strIngredient1 ?? "", amount: strMeasure1 ?? ""),
Ingredient(name: strIngredient2 ?? "", amount: strMeasure2 ?? ""),
Ingredient(name: strIngredient3 ?? "", amount: strMeasure3 ?? ""),
Ingredient(name: strIngredient4 ?? "", amount: strMeasure4 ?? ""),
Ingredient(name: strIngredient5 ?? "", amount: strMeasure5 ?? ""),
Ingredient(name: strIngredient6 ?? "", amount: strMeasure6 ?? ""),
Ingredient(name: strIngredient7 ?? "", amount: strMeasure7 ?? ""),
Ingredient(name: strIngredient8 ?? "", amount: strMeasure8 ?? ""),
Ingredient(name: strIngredient9 ?? "", amount: strMeasure9 ?? ""),
Ingredient(name: strIngredient10 ?? "", amount: strMeasure10 ?? "")
].filter{!$0.name.isEmpty}
}
}
struct Ingredient: Identifiable {
let id = UUID()
var name: String
var amount: String
}
change you struct to
struct Drink: Codable, Identifiable {
let id = UUID()
let strDrink : String
let strInstructions: String
let strDrinkThumb: String?
let strIngredients: [String] = []
}
wherever you want to loop through ingredients you can use drink.strIngredients array
First of all your design cannot work because you are ignoring the root object, a struct with a key drinks
struct Root : Decodable {
let drinks : [Drink]
}
A possible solution is to write a custom init method. However this a bit tricky because you have to inject dynamic CodingKeys to be able to decode the ingredients in a loop
First create a custom CodingKey struct
struct AnyKey: CodingKey {
var stringValue: String
var intValue: Int?
init?(stringValue: String) { self.stringValue = stringValue }
init?(intValue: Int) { return nil } // will never be called
}
In the Drink struct – by the way it's supposed to be named in singular form – specify a second container, create the ingredient keys on the fly, decode the ingredients in a loop and append the results to an array until the value is nil. Somebody should tell the owners of the service that their JSON structure is very amateurish. As you have to specify the CodingKeys anyway I mapped the keys to more meaningful and less redundant struct member names.
struct Drink: Decodable, Identifiable {
private enum CodingKeys : String, CodingKey {
case name = "strDrink", instructions = "strInstructions", thumbnail = "strDrinkThumb"
}
let id = UUID()
let name: String
let instructions: String
let thumbnail: URL
let ingredients: [String]
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
self.instructions = try container.decode(String.self, forKey: .instructions)
self.thumbnail = try container.decode(URL.self, forKey: .thumbnail)
var counter = 1
var temp = [String]()
let anyContainer = try decoder.container(keyedBy: AnyKey.self)
while true {
let ingredientKey = AnyKey(stringValue: "strIngredient\(counter)")!
guard let ingredient = try anyContainer.decodeIfPresent(String.self, forKey: ingredientKey) else { break }
temp.append(ingredient)
counter += 1
}
ingredients = temp
}
}
In the view display the ingredients like this
struct IngredientView: View {
let drink : Drink
var body: some View {
GroupBox() {
DisclosureGroup("Drink Ingredience") {
ForEach(drink.ingredients) { ingredient in
Divider().padding(.vertical, 2)
HStack {
Group {
Text(ingredient)
}
.font(Font.system(.body).bold())
Spacer(minLength: 25)
}
}
}
}
}
}

Swift Array.append in init() not working in combination with Firestore (Google Cloud/FIrebase)

I have tried my first SwiftUI Project.
I only want to show some data stored in Firestore (Google Firebase).
Here is my code:
import SwiftUI
import FirebaseFirestore
import FirebaseFirestoreSwift
struct MonsterObj: Identifiable, Equatable, Hashable {
var id = UUID()
var name: String
var element: String
var immune: String
var size: String
var twoStarWeakness: String
var threeStarWeakness: String
#if DEBUG
static let exampleMonster = MonsterObj(id: UUID(), name: "Test Monster", element: "Test Element", immun: "Test immun", groesse: "Test groesse", twoStarWeakness: "Test 2 Weakness", threeStarWeakness: "Test3 Weakness")
#endif
}
class MonsterC: ObservableObject {
#Published var monsters = [MonsterObj]()
init() {
let db = Firestore.firestore()
var monsterNames: [String] = []
db.collection("Monster").getDocuments() { (querySnapshot, err) in
if let err = err {
print(err)
} else {
for document in querySnapshot!.documents {
monsterNames.append("\(document.documentID)")
print("document: \(document.documentID)")
}
}
}
for monsterName in monsterNames {
print(monsterName)
db.collection("Monster").document(monsterName).getDocument { (document, error) in
if let document = document, document.exists {
let elementGetter = document.get("element") as! String
let immuneGetter = document.get("immune") as! String
let sizeGetter = document.get("size") as! String
let twoStarWeaknessGetter = document.get("2 star weakness") as! String
let threeStarWeaknessGetter = document.get("3 star weakness")as! String
self.monsters.append(MonsterObj(name: monsterName, element: elementGetter, immune: immuneGetter, size: sizeGetter, twoStarWeakness: twoStarWeaknessGetter, threeStarWeakness: threeStarWeaknessGetter))
}
}
}
}
}
This is my View:
import SwiftUI
struct ContentView: View {
#EnvironmentObject var monsterT: MonsterC
var body: some View {
List(monsterT.monsters, id: \.self) { monster in
Text(monster.name)
}
}
}
And I did following to SceneDelegate.swift:
var window: UIWindow?
var monsterT = MonsterC()
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions {
// Create the SwiftUI view that provides the window contents.
let contentView = ContentView().environmentObject(monsterT)
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: contentView)
self.window = window
window.makeKeyAndVisible()
}
}
So my Problem is the list is empty.
I figured out in init of class MonsterC the line monsterNames.append("\document.documentID)") does not append anything to monsterNames.
But print("document: \(document.documentID)") is printing all monsterNames.
My google Firestore structure looks like this:
Collection -> Document -> Fields
-------------------------------------------
Monster -> Anjanath -> immune: fire,
element: fire
etc.
There's only one collection ("Monster").
Can anyone explain to a beginner why .append is not working here but print is doing everything right?
You need to understand calling asynchronous functions. My advice is to restructure your code, it is not a good idea to do these async calls in your init().
Your function "db.collection("Monster").getDocuments() { (querySnapshot, err) in ..."
is asynchronous. You must either wait till it is finished to use the results, or do what you need inside the function. Note you also have another async function "db.collection("Monster").document(monsterName).getDocument {"
So .append is not working because the results of your function "db.collection("Monster").getDocuments() { (querySnapshot, err) in ..." are not available when you do the .append.
So if you must use this dodgy code, try this to fix your array problem:
class MonsterC: ObservableObject {
#Published var monsters = [MonsterObj]()
init() {
let db = Firestore.firestore()
db.collection("Monster").getDocuments() { (querySnapshot, err) in
if let err = err {
print(err)
} else {
var monsterNames: [String] = []
for document in querySnapshot!.documents {
monsterNames.append("\(document.documentID)")
print("document: \(document.documentID)")
}
for monsterName in monsterNames {
print(monsterName)
db.collection("Monster").document(monsterName).getDocument { (document, error) in
if let document = document, document.exists {
let elementGetter = document.get("element") as! String
let immuneGetter = document.get("immune") as! String
let sizeGetter = document.get("size") as! String
let twoStarWeaknessGetter = document.get("2 star weakness") as! String
let threeStarWeaknessGetter = document.get("3 star weakness")as! String
self.monsters.append(MonsterObj(name: monsterName, element: elementGetter, immune: immuneGetter, size: sizeGetter, twoStarWeakness: twoStarWeaknessGetter, threeStarWeakness: threeStarWeaknessGetter))
}
}
}
}
}
}
}

Filtering nested array of struct in swift 5

struct Objects {
var sectionName : String!
var sectionObjects : [CountryList]!
}
var objectArray = [Objects]()
Here objectArray is my tableView data source where sectionObjects is an array of CountryList struct.
struct CountryList: Codable {
let country_id: String?
let country_name: String?
let country_code: String?
let country_flag_url: String?
init(countryID: String, countryName: String, countryCode: String, countryFlagURL: String) {
self.country_id = countryID
self.country_name = countryName
self.country_code = countryCode
self.country_flag_url = countryFlagURL
}
}
I want to filter my objectArray according to country_name.
This is what I have done in UISearchResultsUpdating.
extension CountryListViewController: UISearchResultsUpdating {
public func updateSearchResults(for searchController: UISearchController) {
guard let searchText = searchController.searchBar.text else {return}
if searchText == "" {
objectArray += objectArray
} else {
objectArray += objectArray
objectArray = objectArray.filter {
let countryListArray = $0.sectionObjects!
for countryList in countryListArray {
print("cName \(String(describing: countryList.country_name))")
countryList.country_name!.contains(searchText)
}
}
}
self.countryListTableView.reloadData()
}
}
And getting two errors:
Result of call to 'contains' is unused
Missing return in a closure expected to return 'Bool'
What am I missing here? Any suggestion will be highly appreciated.
Thanks in advance.
filter expects a bool return inside it so You need
var objectArray = [Objects]()
var filtered = [Objects]()
filtered = objectArray.filter {
let countryListArray = $0.sectionObjects
for countryList in countryListArray {
print("cName \(String(describing: countryList.country_name))")
if countryList.country_name!.contains(searchText) {
return true
}
}
return false
}
Or better
filtered = objectArray.filter { $0.sectionObjects.filter { $0.country_name!.contains(searchText) }.count != 0 }
Tip : use another array filtered to hold filtered data as not to overwrite main content in objectArray

Swift - json nested array not returning value

I am trying to grab the array from "imports" from the JSON file but the array is returning encoded.
[ServerName.JSONNodeValue, ServerName.JSONNodeValue, ServerName.JSONNodeValue, ServerName.JSONNodeValue, ServerName.JSONNodeValue, ServerName.JSONNodeValue]
I know the data can be seen as if I print the value of the data in the JSONNode file, it comes up as
https://servername.com/storage/sessions/00167/imports
(
"movie1.mov",
"movie3.mov",
"movie2.mov",
"._movie1.mov",
"._movie2.mov",
"._movie3.mov"
)
This isn't my code and the reason why I am struggling as I am still pretty new at swift.
My JSON file looks similar to this.
{
"id": 135
"name": Test
"angles" [
{
"id": 35,
"name": "test",
"mp4": "http:/0.0.0:3000/storage/seesion/00138/url.mp4"
}
]
"imports" [
movie1.mp4,
movie2.mp4,
movie3.mp4
]
}
Swift Code - Session File
struct Session {
var id: Int
var name: String
var angles: [Angle] = []
var imports: [Any]
extension Session {
init(fromDict dict: [String: AnyObject]){
let node = JSONNodeValue(dict)
let sessionId = node["id"].int ?? 0
self.id = sessionId
self.name = node["name"].string ?? "???"
print(name)
self.imports = node["imports"].arrayOrEmpty
self.angles = node["angles"].arrayOrEmpty.map { angleDict in
Angle(fromDict: angleDict.object!)
}
JSONnode file that handles the JSON
protocol JSONNode {
subscript(key: String) -> JSONNode { get }
var int: Int? { get }
var double: Double? { get }
var string: String? { get }
var bool: Bool? { get }
var date: Date? { get }
var array: [JSONNode]? { get }
var arrayOrEmpty: [JSONNode] { get }
var object: [String: Any]? { get }
var objectOrEmpty: [String: Any] { get }
}
class JSONNodeValue : JSONNode {
static func parse (_ data: Data) -> JSONNode {
if let root = try? JSONSerialization.jsonObject(with: data) {
return JSONNodeValue(root)
} else {
return JSONNodeNone.instance
}
}
var value: Any
init(_ value: Any) {
self.value = value
print(value) // SHOWS THE DATA I NEED
}
subscript(key: String) -> JSONNode {
if let object = value as? [String: Any], let subvalue = object[key] {
return JSONNodeValue(subvalue)
} else {
return JSONNodeNone.instance
}
}
var int: Int? {
return value as? Int
}
var double: Double? {
return value as? Double
}
var string: String? {
return value as? String
}
var bool: Bool? {
return value as? Bool
}
var date: Date? {
if let formatted = string {
return Date.fromIso8601(formatted)
} else {
return nil
}
}
var array: [JSONNode]? {
if let array = value as? [Any] {
return array.map { JSONNodeValue($0) }
} else {
return nil
}
}
var arrayOrEmpty: [JSONNode] {
return array ?? []
}
var object: [String: Any]? {
return value as? [String: Any]
}
var objectOrEmpty: [String : Any] {
return object ?? [:]
}
}
Could someone point me in the right direction or to other answered questions that could help me solve this? Thanks
The struct looks good, but remove the = [] after [Angle], but you should use Codable protocol to parse the JSON like so:
struct Session: Codable {
var id: Int
var name: String
var angles: [Angle]
var imports_dir_contents: [Any]
then create another struct to get the Angle
struct Angle: Codable
let id: Int
let name: String
let mp4: String
Then you want to parse using the Codable protocol method by passing in the data retrieved from the networking call.
do {
let newJSONDecoder = JSONDecoder()
let session = try newJSONDecoder.decode(Session.self, from:data)
let anglesArray = session.angles // or use whatever array property you want
} catch {
print("error while parsing:\(error)")
}
I solved what I was trying to achieve by adding a new a variable in the JSNODE file which then gave me ["movie1.mov", "movie3.mov", "movie2.mov", "._movie1.mov", "._movie2.mov", "._movie3.mov"]
var arrayList: [String]? {
return value as? [String]
}
Thanks for the help though.

struggling with JSON parsing in swift

I am trying to load data in JSON format from my server into IOS application.
Here is my JSON:
[
{
"BankName": "bank1",
"CurrencyName": "cur1",
"SellRate": "0.65",
"BuyRate": "0.55",
"OfficialRate": "0.6"
},
{
"BankName": "bank1",
"CurrencyName": "cur2",
"SellRate": "1.65",
"BuyRate": "1.55",
"OfficialRate": "1.6"
}
]
There are 2 files in my project:
1:
import Foundation
class Shot {
var bankName: String!
var currencyName: String!
var sellRate: String!
var buyRate: String!
var offRate: String!
init (data: NSDictionary) {
self.bankName = getStringFromJSON(data, key:"BankName")
self.currencyName = getStringFromJSON(data, key:"CurrencyName")
self.sellRate = getStringFromJSON(data, key:"SellRate")
self.buyRate = getStringFromJSON(data, key:"BuyRate")
self.offRate = getStringFromJSON(data, key: "OfficialRate")
}
func getStringFromJSON(data: NSDictionary, key: String) -> String {
if let info = data[key] as? String{
return info
}
return ""
}
}
2:
import Foundation
class JsonTest {
func loadJson(completion: ((AnyObject) -> Void)!) {
var urlString = "http://a.com/g.php"
let session = NSURLSession.sharedSession()
let sourceUrl = NSURL(string: urlString)
var task = session.dataTaskWithURL(sourceUrl!){
(data, response, error) -> Void in
if error != nil {
println(error.localizedDescription)
} else {
var error: NSError?
var jsonData = NSJSONSerialization.JSONObjectWithData(data, options: .MutableContainers, error: &error) as NSArray
var shots = [Shot]()
println(jsonData)
for shot in jsonData{
let shot = Shot(data: shot as NSDictionary)
shots.append(shot)
}
println(shots) //[jsontest.Shot, jsontest.Shot]
}
}
task.resume()
}
}
I am trying to populate array automatically when my app starts. To do it I have a code in my mainViewController class.
override func viewDidLoad() {
super.viewDidLoad()
let api = JsonTest()
api.loadJson(nil)
}
The problem occurs when I try to print shots variable in the second file.
it returns [jsontest.Shot, jsontest.Shot] when I was expecting the array of dictionaries.
println(jsonData) works fine and shows JSON data from URL.
Can anybody advise what is wrong in my program?
"shots" is an array of instances of Shot, not a dictionary:
class Shot {
var bankName: String!
var currencyName: String!
var sellRate: String!
var buyRate: String!
var offRate: String!
init (data: NSDictionary) {
self.bankName = getStringFromJSON(data, key:"BankName")
self.currencyName = getStringFromJSON(data, key:"CurrencyName")
self.sellRate = getStringFromJSON(data, key:"SellRate")
self.buyRate = getStringFromJSON(data, key:"BuyRate")
self.offRate = getStringFromJSON(data, key: "OfficialRate")
}
func getStringFromJSON(data: NSDictionary, key: String) -> String {
if let info = data[key] as? String{
return info
}
return ""
}
}
var shots = [Shot]()
let urlString = "http://almazini.lt/getrates.php"
let sourceUrl = NSURL(string: urlString)
// Using NSData instead of NSURLSession for experimenting in Playground
let data = NSData(contentsOfURL: sourceUrl!)
var error: NSError?
// As I'm using Swift 1.2 I had to change "as" with "as!"
let jsonData = NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers, error: &error) as! NSArray
for shot in jsonData{
let shot = Shot(data: shot as! NSDictionary)
shots.append(shot)
}
println(shots[0].bankName)
Update for Swift 2
var shots = [Shot]()
let urlString = "http://almazini.lt/getrates.php"
// Using NSData instead of NSURLSession for experimenting in Playground
if let sourceUrl = NSURL(string: urlString) {
NSURLSession.sharedSession().dataTaskWithURL(sourceUrl, completionHandler: { (data, response, error) in
if error == nil {
if let data = data, jsonData = try? NSJSONSerialization.JSONObjectWithData(data, options: []), jsonArray = jsonData as? [NSDictionary] {
for item in jsonArray {
let shot = Shot(data: item)
shots.append(shot)
}
print(shots[0].bankName)
} else {
print("no JSON data")
}
} else {
print(error!.localizedDescription)
}
}).resume()
}
Seems like there are two problems:
You're trying to use println to debug instead of setting a breakpoint and checking your objects values.
You have not created a description or debugDescription property for your object, so println on your object is just using some default implementation.
shots is an array of your custom object, so when you call println, it's using the description for Array, which prints out the objects in the array, comma separated, and within square brackets.
The default description property for classes in Swift just prints the class name.
Ideally, you should just use a break point to check the values of your object to be certain it initialized correctly, but if it's actually important to get them to print right, it's only a matter of implementing the description property:
override var description: String {
get {
// build and return some string that represents your Shot object
}
}

Resources