Weekly activity summary help? SWIFTUI - arrays

I'm tryin' to obtain a list of activities ("dd/mm/YY: goal achieved/missed goal") which has to be setted every week. The problem is that I obtain a list of activities with the same date and the same result of the previous one. For example:
28/02/2022: goal achieved
28/02/2022: goal achieved
28/02/2022: goal achieved
and the next day:
01/03/2022: missed goal
01/03/2022: missed goal
01/03/2022: missed goal
01/03/2022: missed goal
I want to obtain, instead, a list like:
28/02/2022: goal achieved
01/03/2022: missed goal
02/03/2022: goal achieved...
These are useful structs:
struct Persistent {
#AppStorage("goalAchieved") static var goalAchieved : Bool = false
#AppStorage("activityList") static var activityList : [String] = []
}
struct obj {
static var currentDate = Date()
static var stringDate = ""
static var activity = Activity(date:Persistent.lastUpdatedDate)
}
This is the ActivityListView:
import SwiftUI
func activitystring(activity:Activity) -> String{
var output = ""
output = "\(activity.date): \(activity.reachedobj(goalAchieved: Persistent.goalAchieved))"
return output
}
struct Activity: Identifiable{
let id = UUID()
let date: String
func reachedobj(goalAchieved: Bool) -> String {
var output = ""
if Persistent.goalAchieved == false { output = "Missed goal" }
if Persistent.goalAchieved == true { output = "Goal Achieved!"}
return output
}
}
struct ActivityRow: View{
var activity: Activity
var body: some View{
Text(activitystring(activity: activity))
Divider()
}
}
struct ActivityListView: View {
var body: some View {
ScrollView{
Text("Week summary").font(.system(size: 15)).foregroundColor(Color.green)
Text("")
ForEach(Persistent.activityList, id: \.self) { activity in
let activity = Activity(date: Persistent.lastUpdatedDate)
ActivityRow(activity: activity)
}
}
}
}
Finally this is the useful code in the ApplicationApp file (main) where I update activity list:
MenuView().onAppear(){
if Persistent.activityList.count>7{
Persistent.activityList.removeAll()
}
obj.currentDate = Date()
let formatter = DateFormatter()
formatter.dateFormat = "dd/MM/YY"
obj.stringDate = formatter.string(from:obj.currentDate)
if Persistent.lastUpdatedDate != obj.stringDate{
Persistent.goalAchieved = false
let activity = Activity(date: Persistent.lastUpdatedDate)
Persistent.activityList.append(activitystring(activity: activity))
Persistent.lastUpdatedDate = obj.stringDate
}
}
What's wrong on this?

You're calling obj.activity in your ForEach and ActivityRow, that's why it repeats that same static property all over the place.
You better just drop your struct obj and try again without it

In your Persistent object you have an array of many activities, called activitylist , but one single boolean that tells if the goal is achieved - goalachieved indeed.
Your view is iterating through the array of Persistent.activitylist, so you will have many lines for one single result - achieved or not achieved. You might actually want to iterate over an array of Persistent objects - meaning that somewhere you should probably store [Persistent] in some variable. In this way, you will see one line only for each result.
If I also may suggest: use the conventions for naming variables, Swift uses "camelCaseConventionForVariables", easier to read than "thewholevariableislowercase"
Edit:
Let me try to change a little bit your code (I would personally change it more radically, but that's not the scope of the answer).
Instead of having only one goalAchieved for all elements on the array activityList, make it a dictionary:
struct Persistent {
// Drop this variable
// #AppStorage("goalAchieved") static var goalAchieved : Bool = false
// Make this a dictionary, the date will be the key and the goalAchieved will be the value
#AppStorage("activityList") static var activityList : [String: Bool] = [:]
}
Add values to the dictionary (#meomeomeo is right, you don't need obj):
MenuView().onAppear() {
if Persistent.activityList.count > 7 {
Persistent.activityList.removeAll()
}
let currentDate = Date()
let formatter = DateFormatter()
formatter.dateFormat = "dd/MM/YY"
let stringDate = formatter.string(from: currentDate)
if Persistent.lastUpdatedDate != stringDate {
let activity = Activity(date: Persistent.lastUpdatedDate)
Persistent.activityList[activitystring(activity: activity))] = false // Will create something like ["01/03/2022": false]
Persistent.lastUpdatedDate = stringDate
}
}
Iterate on the dictionary in your ForEach; for more info: read here.

Related

SwiftUI - Return values from array of structs are all in one row

I am calling a function in order to do a select statement in a bundled SQLite database. The function returns an array of structs. The database is being read correctly as I have put some print commands in the code. However the final array only has 1 row in it, which contains all the data, instead of 16 rows of structs.
The struct code, which is in databaseHelper.swift, is...
struct ButtonData: Hashable {
let english: String
let categoryID: Int
let indonesian: String
}
The database code, in databaseHelper, is
class DatabaseHelper {
var buttonVars = [ButtonData]()
var database: Connection!
let buttonsTable = Table("Button")
let english = Expression<String>("english")
let category = Expression<String>("category")
let categoryID = Expression<Int>("ID")
let filename = Expression<String>("filename")
let indonesian = Expression<String>("indonesian")
init() {
do {
let path = Bundle.main.path(forResource: "sga", ofType: "db")!
let database = try Connection(path, readonly: true)
self.database = database
print("Database initialized at path \(path)")
} catch {
print("error")
}
}
func queryDatabase(passedCategory: String) -> [ButtonData] {
do {
let buttons = try self.database.prepare(self.buttonsTable.filter(self.category==passedCategory))
for row in buttons {
print("English: \(row[self.english]), ID: \(row[self.categoryID]), Indonesian: \(row[self.indonesian])")
// buttonVars.append(ButtonData(english: row[english], categoryID: row[categoryID], indonesian: row[indonesian]))
buttonVars.append(ButtonData(english: row[english], categoryID: row[categoryID], indonesian: row[indonesian]))
}
} catch {
print(error)
}
print(buttonVars[0])
print(buttonVars[1])
print(buttonVars[2])
print(buttonVars[3])
print(buttonVars[4])
print(buttonVars[5])
print(buttonVars[6])
print(buttonVars[7])
print(buttonVars[8])
print(buttonVars[9])
print(buttonVars[10])
print(buttonVars[11])
print(buttonVars[12])
print(buttonVars[13])
print(buttonVars[14])
print(buttonVars[15])
return buttonVars
}
}
The function code, which is in SoundPageView.swift (this page calls the database function), is...
func getArrayValues() {
let buttonRows = [DatabaseHelper().queryDatabase(passedCategory: category)]
let btnCount: Int = buttonRows.count
print(btnCount)
print(buttonRows[0])
}
The print values in the console show me that btnCount = 1 but before the array is returned, it is made of 16 rows. It is only after it is returned that it is reduced to 1 row.
Can anyone tell me what I'm doing wrong? I don't know how to access the data. Thanks.
I'm not sure why you are putting the brackets in this call:
let buttonRows = [DatabaseHelper().queryDatabase(passedCategory: category)]
The func queryDatabase returns an array on its own. I think it should be this:
let buttonRows = DatabaseHelper().queryDatabase(passedCategory: category)
Otherwise your result will be an array with one entry, which is the result of the call to queryDatabase.

Speed Up a list

I'm try to speed up my APP that for now unfortunately is very slow on performing some search and list of data with Swift Ui.
first of all I have a data model that describe my object airport, called AirportModel
import Foundation
class AirportModel : Identifiable , Codable{
var aptICAO : String
var aptName : String
var aptCity : String
var aptCountry :String
var aptIATA : String
init(aptICAO: String, aptName: String, aptCity: String, aptCountry: String, aptIATA: String) {
self.aptICAO = aptICAO
self.aptName = aptName
self.aptCity = aptCity
self.aptCountry = aptCountry
self.aptIATA = aptIATA
}
}
I have a local file apt.json, that contain all the data information for my airport (I downloaded from internet this json, inside there are around 29000 airport)
so when I run the first time the app, with the following function , i create and save the airportVector of type AirportModel.
func openfilejson (fileName : String) {
if let path = Bundle.main.path(forResource: fileName, ofType: "json") {
do {
let fileUrl = URL(fileURLWithPath: path)
let datafile = try Data(contentsOf: fileUrl,options: .mappedIfSafe)
let json = JSON(data:datafile)
objectWillChange.send()
for (key,_) in json {
let airport = AirportModel(aptICAO: "", aptName: "", aptCity: "", aptCountry: "", aptIATA: "")
airport.aptName = json[key]["name"].stringValue
airport.aptCity = json[key]["city"].stringValue
airport.aptCountry = json[key]["country"].stringValue
airport.aptIATA = json[key]["iata"].stringValue
airport.aptICAO = json[key].stringValue
airportVector.append(airport)
}
debugPrint("nel vettore airport Vector ci sono \(airportVector.count) aeroporti")
save2() // to save airport vector in the plistfile
debugPrint("SALVATO IN MEMORIA ")
} catch {
print("ERRORE OPEN FILE AEROPORTI")
}
}
}
all, this work fine , the vector with 29000 airport inside is created once the app is ru the first time.
NOW THE BIG ISSUE.
in one view, I try to list this airport using SwiftUI and a searchBar to search on it.
the problem is, due to the big amount of data to be opened, when I load the View with the list; the data take very long to be listed and moreover when I lunch the search everything is stuck!
any idea how can I process or speed up this huge amount of data to avoid the app stuck on the loading of the following view!
import SwiftUI
struct ContentView: View {
#ObservedObject var dm: DataManager
#State private var searchTerm : String = ""
var body: some View {
VStack {
Text("List")
SearchBar(text: $searchTerm).shadow(radius: 10)
List(dm.airportVector.filter{
$0.aptCity.localizedCaseInsensitiveContains(searchTerm)
|| $0.aptName.localizedCaseInsensitiveContains(searchTerm)
}){ item in
HStack {
Text(item.aptICAO).bold().font(Font.system(size:20)).frame(width: 90, height: 10)
//
Text(item.aptName).font(Font.system(size: 15))
Spacer()
VStack(alignment: .leading) {
Text(item.aptCity).font(Font.system(size: 10)).font(.subheadline)
Spacer()
Text(item.aptCountry).font(Font.system(size: 10))
}
}
}
}
}
}
Thanks in advance.
Damiano
Make your data conform to Identifiable and use ForEach inside of your List with a separate View to render the item:
List {
ForEach(items) {
MyItem(item)
}
}
Only render items when they change by making MyItem conform to Equatable, and define the == function. Make sure the == function is being executed by adding a breakpoint or print statement. If it's not being executed see this article for a workaround:
https://swiftui-lab.com/equatableview/
If the above suggestion works, but you find yourself making wholesale modifications to the list after initial render, try using id() during these modifications. See https://swiftui-lab.com/swiftui-id/
If the above suggestions don't improve things enough, you probably need a more custom solution. Use a TableView or CollectionView, see https://github.com/apptekstudios/ASCollectionView

Swift - Update Value of a specific item in a nested dictionary

I have a tableview and each cells are meant to be linked to an array inside a dictionary.
var buttonAction : [String: [ButtonAction]] = [:]
below is the struct of the buttonAction
struct ButtonAction: Codable {
var action: String
var array_linked_of_buttons: [[String:String]]
init(action: String, array_linked_of_buttons: [[String:String]]) {
self.action = action
self.array_linked_of_buttons = array_linked_of_buttons
}
}
It gets a bit complicated to explain the whole code but when I connect two buttons together, I can get the data for the variable "singleAction" which then can be added to the button action dictionary "array_linked_of_buttons".
let singleAction = [linkedButtonUUID: connectorMarker.UUIDpic.uuidString, linkedButtonCategory: "", linkedButtonName: connectorMarker.name]
let mainMarkerAction = mainMarker.buttonAction["button actions array"]!
for existingMarker in mainMarkerAction {
actionArray.append(existingMarker)
}
var actionSub = actionArray[indexRowTag].array_linked_of_buttons
if let addAction = actionSub.filter({$0[linkedButtonUUID] == connectorMarker.UUIDpic.uuidString}).first {
print("MARKER Exists", addAction)
} else {
actionSub.append(singleAction)
print("UPDATED MARKER", actionSub)
}
let action = ButtonAction(action: actionArray[indexRowTag].action, array_linked_of_buttons: actionSub)
//ISSUE--?? mainMarker.buttonAction.updateValue(VALUE forKey: "button actions array")
saveData()
I can workout which item of the dictionary needs to be edited but how do I update that specific value? I am really confused has it just creates a new item but I want to update a previous one.
So for example, I want to append the "array_linked_buttons" of the item 0 and have 4 items instead of 3.
This is how my dictionary looks like if that helps too
I have searched other questioned but I still work it out.
Any help pointing me in the right direction is much appreciated!
Copying instead of changing value happened because you're using struct for ButtonAction which has value semantics and swift create copy on assigning to any variable.
You need to use class instead. Classes have reference semantics, so it won't create new instance on assigning to variable so you'll be able to update property of needed instance.
I worked out i was updating the main dictionary array and not the nested array.
The code below is what I used in the end.
let singleAction = [linkedButtonUUID: connectorMarker.UUIDpic.uuidString, linkedButtonCategory: "", linkedButtonName: connectorMarker.name]
var mainMarkerAction = mainMarker.buttonAction["button actions array"]!
for existingMarker in mainMarkerAction {
actionArray.append(existingMarker)
}
var actionSub = actionArray[indexRowTag].array_linked_of_buttons
if let addAction = actionSub.filter({$0[linkedButtonUUID] == connectorMarker.UUIDpic.uuidString}).first {
print("MARKER HERE", addAction)
} else {
actionArray[indexRowTag].array_linked_of_buttons.append(singleAction)
}
mainMarkerAction[indexRowTag] = actionArray[indexRowTag]

Filter multiple arrays with one condition

I have a tableView with different kinds of infos, each coming from a different array.
I could not work with dictionaries because then the list would have been unordered and I could not work with classes, because I have different lists with all kinds of dynamic entries (properties are always different etc.)
Here my problem:
I want to implement a search function. But when I use the filter function for one array, it changes of course based on the implemented condition but the other 5 stay the same => I can't reload the tableView because the array information does not match anymore ...
Here the arrays:
var categoryItemUIDs = [String]()
var categoryItemDescriptions = [String]()
var categoryItemLfdNrs = [Int]()
var categoryGivenOuts = [Bool]()
var categoryGivenTos = [String]()
var categoryGivenAts = [String]()
var categoryStorageLocations = [String]()
In the tableView(cellForRowAtIndexPath method):
cell.customTextLabel?.text = categoryItemLfdNrs[indexPath.row]
cell.customDetailTextLabel.text = categoryItemDescriptions[indexPath.row]
Here the searchBar(textDidChange) method:
self.categoryItemDescriptions.filter { $0.lowercased().contains(searchText.lowercased()) }
Now I get an array back with reduced size, but all the other arrays stay the same... Is there maybe another way to avoid this problem? I already tried type aliases but it did not work out.
I would appreciate any help!
Kind regards,
When it goes to such a big count of arrays, the time for your specific type comes.
The simple solution is to create something like
struct Category {
var uid: String
var description: String
// ...
var storageLocation: String
}
The you have simply something like
var items: [Category]
And you can still do simple things in cellForRowAtIndexPath
cell.customTextLabel?.text = items[indexPath.row].lfdnrs
cell.customDetailTextLabel.text = items[indexPath.row].description
And only 1 array to filter
items.filter { $0.description.lowercased().contains(searchText.lowercased()) }
So overall advice is to solve different problem (here I suggested the solution of the having your data in the app problem instead of filtering multiple arrays with one condition)
try
var categoryItemUIDs = ["aaa","bbb","ccc"]
var categoryItemDescriptions = ["ddd","eee","fff"]
var categoryItemLfdNrs = [0,1,2]
struct data {
var id = ""
var desc = ""
var item = 0
init(id :String, desc:String, item:Int)
{
self.id = id
self.desc = desc
self.item = item
}
}
//var cat = [data]()
//for i in 0..<categoryItemUIDs.count {
// cat.append(data(id:categoryItemUIDs[i], desc:categoryItemDescriptions[i],item:categoryItemLfdNrs[i] ))
//}
//more swift
let cat = (0..<categoryItemUIDs.count).map { (i) -> data in
return data(id:categoryItemUIDs[i], desc:categoryItemDescriptions[i],item:categoryItemLfdNrs[i] )
}
print (cat)
let catFilter = cat.filter { $0.id == "aaa" }
print (catFilter)

Filtering arrays for use with UISearchBar

I have a table view which displays a user's Name, Company Name and Photo (PFFile). Each tableView row I have has all of this information in it.
I am using UISearchBarDelegate and IB to implement a search function to filter by the user's Name. It is finding the correct user but I have not been able to also update the company photo.
How do I filter the other arrays? The items I need from the arrays will be at the same index as the ones taken from the user's Name array.
EDIT: I am trying a different data structure and am receiving array index out of range, updated code below:
var filterArray = [User]() //<-- globally declared
var userArray = [User]() //< Global
class User {
var name: String?
var company: String?
init (name: String?, company: String?) {
self.name = name
self.company = company
}
}
//In a class which populates the search arrays
for object in unwrappedSucceeded {
let username = object.valueForKey("username") as! String
let companyName = object.valueForKey("companyName") as! String
let user = User(name: username, company: companyName)
userArray.append(user)
}
//tableViewController
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
filterArray.removeAll(keepCapacity: false)
if searchText.characters.count != 0 {
isSearch = true
self.search(searchText)
} else {
isSearch = false
}
}
func search(text: String) -> Void {
filterArray = userArray.filter({$0.name == text})
}
//In cellForRowAtIndexPath
cell.usernameCell.text = filterArray[indexPath.row].name //ARRAY INDEX OUT OF RANGE
Like I said you strongly recommend to group each user's info into one big container, therefore we could use array of struct or class, then it comes easier to filter.
schematic for the container:
struct Container
{
var username:String?
var companyName:String?
var photo:UIImage?
}
your main array will be : var arrayofData = [Container]()
Now when you are query your objects from parse, inside of your query function
// after you called the findObjectsWithBackgroundBlock()
// let's assume you check for error and if the [PFObject] is empty or not
for one in objectsFromParse
{
let photoToget = one["Photo"] as! PFFile
// next step should be to get the image data right :)
{
// let's assume that is the block when get the image data right:)
// check your data and assign it to some UIImage
// then
let userRepresentation = Container() //<-- we are creating a single object representation for each user
let username = one["username"] as! String //<--data we got from Parse
let companyName = one["companyName"] as! String
let userImage = //the UIImage which contains the data
userRepresentation.username = username
userRepresentation.companyName = companyName
userRepresentation.photo = userImage
// then we append
arrayOfData.append(userRepresentation)
}
}
Now we have all data into our array, so let's filter by username and also I hope you configure your tableView so when you have data from filter or regular array.
var filterArray = [Container]() //<-- globally declared
func search(text: String) -> Void
{
filterArray = arrayOfData.filter(){ (Container) -> Bool in
let range = Container.name!.rangeOfString(text, options:NSStringCompareOptions.CaseInsensitiveSearch) return range != nil }
// then you are good to go
}
let arr1 = [10,20,40]
let e1 = arr1.enumerate()
let arr2 = ["a","b","c"]
let f1 = e1.filter { $0.element % 20 == 0 }
let f2 = arr2.enumerate().filter { j, _ in
f1.contains { i, _ in
i == j
}
}
print(f1.map{$0.element}, f2.map{$0.element})
// [20, 40] ["b", "c"]
now you have both arrays "filtered". the best, what you can do is redesigning your data model!

Resources