!! UPDATE: Firebase might not be the problem (data gotten in half a second it seems on ViewController main screen although connection may still be a hold up [?]; also, on AddUniversal screen, adding a new entity to Firebase database triggers the same delay, suggesting Firebase is the problem [?] as there's no sizing or collection view or much of anything on that screen) - on ViewController screen, I thought problem might lie somewhere between obtaining of data and plugging it into collectionView cells (cardviews) as per Brian Voong model of vertical sizing. I'll leave the original code below, and some of the Brian Voong stuff at the end. !!
I have tried removing encryption/decryption process, removing invocation of GoogleAppEngine (encryption key request), and removal of image (only text requested; probably under 200kB). I've also tried Frank van Puffelen's suggestion to add a "on" listener in addition to my "once" listeners. Nothing works. This app will be unusable for customers if I don't get it fixed.
Example code:
//
// MIProcessor.swift
// Bizzy Books
//
// Created by Brad Caldwell on 12/19/17.
// Copyright © 2017 Caldwell Contracting LLC. All rights reserved.
//
import UIKit
import Firebase
final class MIProcessor {
static let sharedMIP = MIProcessor()
private init() {}
public var firstTime: Bool = false
public var mIP: [MultiversalItem] = [MultiversalItem]()
public var sIP: [MultiversalItem] = [MultiversalItem]() // the search mip!
public var mipORsip: Int = Int()
public var mIPUniversals: [UniversalItem] = [UniversalItem]()
public var mIPProjects: [ProjectItem] = [ProjectItem]()
public var mIPEntities: [EntityItem] = [EntityItem]()
public var mIPAccounts: [AccountItem] = [AccountItem]()
public var mIPVehicles: [VehicleItem] = [VehicleItem]()
public var trueYou: String = String()
public var isUserCurrentlySubscribed: Bool = Bool()
private var tHeKeY: Data!
var theUser: User!
var universalsRef: DatabaseReference!
var entitiesRef: DatabaseReference!
var projectsRef: DatabaseReference!
var vehiclesRef: DatabaseReference!
var accountsRef: DatabaseReference!
var keyRef: DatabaseReference!
var obtainBalanceAfter = ObtainBalanceAfter()
var obtainProjectStatus = ObtainProjectStatus()
var balOneAfter: Int = 0
var balTwoAfter: Int = 0
var balsAfter: [Int?] = [Int?]()
var masterSearchArray: [SearchItem] = [SearchItem]()
var authorized: Bool!
var theKeyIsHere: String!
func loadTheMip(completion: @escaping () -> ()) {
mipORsip = 0 // MIP!
//obtainTheKey {
self.universalsRef = Database.database().reference().child("users").child(userUID).child("universals")
self.projectsRef = Database.database().reference().child("users").child(userUID).child("projects")
self.entitiesRef = Database.database().reference().child("users").child(userUID).child("entities")
self.accountsRef = Database.database().reference().child("users").child(userUID).child("accounts")
self.vehiclesRef = Database.database().reference().child("users").child(userUID).child("vehicles")
self.mIPUniversals.removeAll()
self.mIPProjects.removeAll()
self.mIPEntities.removeAll()
self.mIPAccounts.removeAll()
self.mIPVehicles.removeAll()
self.universalsRef.observeSingleEvent(of: .value, with: { (snapshot) in
for item in snapshot.children {
self.mIPUniversals.append(UniversalItem(snapshot: item as! DataSnapshot))
}
self.projectsRef.observeSingleEvent(of: .value, with: { (snapshot) in
for item in snapshot.children {
self.mIPProjects.append(ProjectItem(snapshot: item as! DataSnapshot))
}
self.entitiesRef.observeSingleEvent(of: .value, with: { (snapshot) in
for item in snapshot.children {
self.mIPEntities.append(EntityItem(snapshot: item as! DataSnapshot))
}
self.accountsRef.observeSingleEvent(of: .value, with: { (snapshot) in
for item in snapshot.children {
self.mIPAccounts.append(AccountItem(snapshot: item as! DataSnapshot))
}
self.vehiclesRef.observeSingleEvent(of: .value, with: { (snapshot) in
for item in snapshot.children {
self.mIPVehicles.append(VehicleItem(snapshot: item as! DataSnapshot))
}
let youRef = Database.database().reference().child("users").child(userUID).child("youEntity")
youRef.observeSingleEvent(of: .value, with: { (snapshot) in
if let youKey = snapshot.value as? String {
self.trueYou = youKey
completion()
}
})
})
})
})
})
})
//}
}
This code gets called from ViewController.swift during the log-in procedure, as shown below:
func checkLoggedIn() {
Auth.auth().addStateDidChangeListener { auth, user in
if user != nil {
// User is signed in.
userUID = (user?.uid)!
self.theUser = user
if user?.photoURL == nil {
}else{
if let imageUrl = NSData(contentsOf: (user?.photoURL)!){
self.profilePic.image = UIImage(data: imageUrl as Data)
} else {
self.profilePic.image = UIImage(named: "bizzybooksbee")
}
}
self.masterRef = Database.database().reference().child("users").child(userUID)
self.projectsRef = Database.database().reference().child("users").child(userUID).child("projects")
self.entitiesRef = Database.database().reference().child("users").child(userUID).child("entities")
self.accountsRef = Database.database().reference().child("users").child(userUID).child("accounts")
self.vehiclesRef = Database.database().reference().child("users").child(userUID).child("vehicles")
self.youEntityRef = Database.database().reference().child("users").child(userUID).child("youEntity")
self.firstTimeRef = Database.database().reference().child("users").child(userUID).child("firstTime")
self.masterRef.observe(.value, with: { (snapshot) in
print("Do NOTTTT ANY thingggggg")
})
self.firstTimeRef.observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.exists() {
print("Do NOT any THING")
if self.shouldEnterLoop {
self.shouldEnterLoop = false
self.loadTheMIP()
}
} else {
self.initializeIfFirstAppUse()
}
})
print("ONE!")
self.masterRef.observe(.childChanged, with: { (snapshot) in // GENIUS!!!!! This line loads MIP only when an item gets added/changed/deleted (and exactly WHEN an item gets added/changed/deleted) in Firebase database IN REALTIME!!!!
print("TWO!")
if self.shouldEnterLoop {
self.shouldEnterLoop = false
print("THREE!")
if MIProcessor.sharedMIP.mipORsip == 0 {
self.loadTheMIP()
} else {
DispatchQueue.main.async {
MIProcessor.sharedMIP.loadTheMip {
MIProcessor.sharedMIP.obtainTheBalancesAfter()
MIProcessor.sharedMIP.loadTheStatuses()
MIProcessor.sharedMIP.updateTheMIP()
MIProcessor.sharedMIP.updateTheSIP(i: self.thingToBeSearchedInt, name: self.thingToBeSearchedName)
self.cardViewCollectionView.reloadData()//Critical line - this makes or breaks the app :/
}
}
}
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(5), execute: {
self.shouldEnterLoop = true
})
}
})
if MIProcessor.sharedMIP.mIP.count == 0 {
print("FOUR!")
if self.shouldEnterLoop {
self.shouldEnterLoop = false
print("FIVE!")
self.loadTheMIP()
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(5), execute: {
self.shouldEnterLoop = true
})
}
}
} else {
// No user is signed in.
self.login()
}
}
}
Brian Voong collectionView vertical sizing stuff...
//Brian Voong inspiration... see if we can get vertical sizing of collectionview cells ie cardviews
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let i = indexPath.item
var baseHeight: CGFloat = 150
var sentenceOneHeight: CGFloat = 0
var sentenceTwoHeight: CGFloat = 0
var phoneHeight: CGFloat = 0
var emailHeight: CGFloat = 0
var geoHeight: CGFloat = 0
var ssnHeight: CGFloat = 0
var einHeight: CGFloat = 0
var imageHeight: CGFloat = 1
switch MIProcessor.sharedMIP.mipORsip {
case 1: // SIP!
switch MIProcessor.sharedMIP.sIP[i].multiversalType {
case 1: // Project
baseHeight = 130
if let projectItem = MIProcessor.sharedMIP.sIP[i] as? ProjectItem {
if projectItem.projectNotes != "" {
if projectItem.projectNotes.count < 30 {
phoneHeight = 25
} else if projectItem.projectNotes.count < 60 {
phoneHeight = 45
} else {
phoneHeight = 80
}
}
if projectItem.projectTags != "" {
if projectItem.projectTags.count < 30 {
emailHeight = 30
} else if projectItem.projectTags.count < 60 {
emailHeight = 60
} else {
emailHeight = 90
}
}
if projectItem.projectAddressStreet != "" {
sentenceOneHeight = 140
} else {
sentenceOneHeight = 100
}
}
sentenceTwoHeight = 180
case 2: // Entity
baseHeight = 160 //92
if let entityItem = MIProcessor.sharedMIP.sIP[i] as? EntityItem {
if (entityItem.phoneNumber != "") || (entityItem.email != "") || (entityItem.city != "") {
phoneHeight = 118
}
if entityItem.ssn != "" {
ssnHeight = 30
}
if entityItem.ein != "" {
einHeight = 30
}
}
case 3: // Account
baseHeight = 140 //92
if let accountItem = MIProcessor.sharedMIP.sIP[i] as? AccountItem {
if accountItem.phoneNumber != "" {
phoneHeight = 30
}
if accountItem.email != "" {
emailHeight = 38
}
if accountItem.street != "" {
if accountItem.city != "" {
if accountItem.state != "" {
geoHeight = 50
}
}
}
}
case 4: // Vehicle
baseHeight = 140 //92
if let vehicleItem = MIProcessor.sharedMIP.sIP[i] as? VehicleItem {
if vehicleItem.licensePlateNumber != "" {
phoneHeight = 25
}
if vehicleItem.vehicleIdentificationNumber != "" {
emailHeight = 25
}
if vehicleItem.placedInCommissionDate != "" {
geoHeight = 25
}
}
default: // Universal - Ie case 0 the most frequent
var longString = ""
if let universalItem = MIProcessor.sharedMIP.sIP[i] as? UniversalItem {
if universalItem.notes != "" {
if universalItem.notes.count < 30 {
phoneHeight = 25
} else if universalItem.notes.count < 60 {
phoneHeight = 45
} else {
phoneHeight = 80
}
}
imageHeight = (CGFloat(universalItem.picAspectRatio) / 1000) * widthConstraintConstant
switch universalItem.universalItemType {
case 1: // Personal
baseHeight = 100
longString = universalItem.whoName + universalItem.whomName + universalItem.taxReasonName
case 2: // Mixed
baseHeight = 160
switch universalItem.taxReasonId {
case 2: // Labor ie wc
longString = universalItem.whoName + universalItem.whomName + universalItem.personalReasonName + universalItem.taxReasonName + universalItem.workersCompName
case 5: // Vehicle
longString = universalItem.whoName + universalItem.whomName + universalItem.personalReasonName + universalItem.taxReasonName + universalItem.vehicleName
case 6: // AdMeans
longString = universalItem.whoName + universalItem.whomName + universalItem.personalReasonName + universalItem.taxReasonName + universalItem.advertisingMeansName
default: // Nothing important
longString = universalItem.whoName + universalItem.whomName + universalItem.personalReasonName + universalItem.taxReasonName
}
case 3: // Fuel
baseHeight = 70
longString = "At 234566 miles you paid $00.00 to " + universalItem.whomName + " for 00.000 gallons of 87 gas in your " + universalItem.vehicleName
case 4: // Transfer
baseHeight = 150
case 6: // Project Media
baseHeight = 80
default:
baseHeight = 120
switch universalItem.taxReasonId {
case 2: // Labor ie wc
longString = universalItem.whoName + universalItem.whomName + universalItem.taxReasonName + universalItem.workersCompName
case 5: // Vehicle
longString = universalItem.whoName + universalItem.whomName + universalItem.taxReasonName + universalItem.vehicleName
case 6: // AdMeans
longString = universalItem.whoName + universalItem.whomName + universalItem.taxReasonName + universalItem.advertisingMeansName
default: // Nothing important
longString = universalItem.whoName + universalItem.whomName + universalItem.taxReasonName
}
}
}
if longString.count < 15 {
sentenceOneHeight = 80
} else if longString.count < 40 {
sentenceOneHeight = 110
} else if longString.count < 65 {
sentenceOneHeight = 140
} else if longString.count < 80 {
sentenceOneHeight = 170
} else {
sentenceOneHeight = 200
}
}
let totalHeight = baseHeight + sentenceOneHeight + sentenceTwoHeight + phoneHeight + emailHeight + geoHeight + ssnHeight + einHeight + imageHeight
var theWidth: CGFloat = 0
if view.frame.width < 370 { //Protection for tiny iPhones
theWidth = view.frame.width - 20
} else { //Good for most iphones and safe for iPads
theWidth = 350
}
return CGSize(width: theWidth, height: totalHeight)
default: // I.e. case 0 MIP!
switch MIProcessor.sharedMIP.mIP[i].multiversalType {
case 1: // Project
baseHeight = 130
if let projectItem = MIProcessor.sharedMIP.mIP[i] as? ProjectItem {
if projectItem.projectNotes != "" {
if projectItem.projectNotes.count < 30 {
phoneHeight = 25
} else if projectItem.projectNotes.count < 60 {
phoneHeight = 45
} else {
phoneHeight = 80
}
}
if projectItem.projectTags != "" {
if projectItem.projectTags.count < 30 {
emailHeight = 30
} else if projectItem.projectTags.count < 60 {
emailHeight = 60
} else {
emailHeight = 90
}
}
if projectItem.projectAddressStreet != "" {
sentenceOneHeight = 140
} else {
sentenceOneHeight = 100
}
}
sentenceTwoHeight = 180
case 2: // Entity
baseHeight = 160 //92
if let entityItem = MIProcessor.sharedMIP.mIP[i] as? EntityItem {
if (entityItem.phoneNumber != "") || (entityItem.email != "") || (entityItem.city != "") {
phoneHeight = 118
}
if entityItem.ssn != "" {
ssnHeight = 30
}
if entityItem.ein != "" {
einHeight = 30
}
}
case 3: // Account
baseHeight = 140 //92
if let accountItem = MIProcessor.sharedMIP.mIP[i] as? AccountItem {
if accountItem.phoneNumber != "" {
phoneHeight = 30
}
if accountItem.email != "" {
emailHeight = 38
}
if accountItem.street != "" {
if accountItem.city != "" {
if accountItem.state != "" {
geoHeight = 50
}
}
}
}
case 4: // Vehicle
baseHeight = 140 //92
if let vehicleItem = MIProcessor.sharedMIP.mIP[i] as? VehicleItem {
if vehicleItem.licensePlateNumber != "" {
phoneHeight = 25
}
if vehicleItem.vehicleIdentificationNumber != "" {
emailHeight = 25
}
if vehicleItem.placedInCommissionDate != "" {
geoHeight = 25
}
}
default: // Universal - Ie case 0 the most frequent
var longString = ""
if let universalItem = MIProcessor.sharedMIP.mIP[i] as? UniversalItem {
if universalItem.notes != "" {
if universalItem.notes.count < 30 {
phoneHeight = 25
} else if universalItem.notes.count < 60 {
phoneHeight = 45
} else {
phoneHeight = 80
}
}
imageHeight = (CGFloat(universalItem.picAspectRatio) / 1000) * widthConstraintConstant
switch universalItem.universalItemType {
case 1: // Personal
baseHeight = 100
longString = universalItem.whoName + universalItem.whomName + universalItem.taxReasonName
case 2: // Mixed
baseHeight = 160
switch universalItem.taxReasonId {
case 2: // Labor ie wc
longString = universalItem.whoName + universalItem.whomName + universalItem.personalReasonName + universalItem.taxReasonName + universalItem.workersCompName
case 5: // Vehicle
longString = universalItem.whoName + universalItem.whomName + universalItem.personalReasonName + universalItem.taxReasonName + universalItem.vehicleName
case 6: // AdMeans
longString = universalItem.whoName + universalItem.whomName + universalItem.personalReasonName + universalItem.taxReasonName + universalItem.advertisingMeansName
default: // Nothing important
longString = universalItem.whoName + universalItem.whomName + universalItem.personalReasonName + universalItem.taxReasonName
}
case 3: // Fuel
baseHeight = 70
longString = "At 234566 miles you paid $00.00 to " + universalItem.whomName + " for 00.000 gallons of 87 gas in your " + universalItem.vehicleName
case 4: // Transfer
baseHeight = 150
case 6: // Project Media
baseHeight = 80
default:
baseHeight = 120
switch universalItem.taxReasonId {
case 2: // Labor ie wc
longString = universalItem.whoName + universalItem.whomName + universalItem.taxReasonName + universalItem.workersCompName
case 5: // Vehicle
longString = universalItem.whoName + universalItem.whomName + universalItem.taxReasonName + universalItem.vehicleName
case 6: // AdMeans
longString = universalItem.whoName + universalItem.whomName + universalItem.taxReasonName + universalItem.advertisingMeansName
default: // Nothing important
longString = universalItem.whoName + universalItem.whomName + universalItem.taxReasonName
}
}
}
if longString.count < 15 {
sentenceOneHeight = 80
} else if longString.count < 40 {
sentenceOneHeight = 110
} else if longString.count < 65 {
sentenceOneHeight = 140
} else if longString.count < 80 {
sentenceOneHeight = 170
} else {
sentenceOneHeight = 200
}
}
let totalHeight = baseHeight + sentenceOneHeight + sentenceTwoHeight + phoneHeight + emailHeight + geoHeight + ssnHeight + einHeight + imageHeight
var theWidth: CGFloat = 0
if view.frame.width < 370 { //Protection for tiny iPhones
theWidth = view.frame.width - 20
} else { //Good for most iphones and safe for iPads
theWidth = 350
}
return CGSize(width: theWidth, height: totalHeight)
}
}
}
I figured it out!!!
Getting financial transaction data from Firebase was NOT the slow point.
The problem was, AFTER getting the data, I ran through all the user's transactions starting with the starting bank balance and adjusted so that each transaction showed then-current bank balance. This apparently takes a heck of a lot of time to do for a couple thousand transactions.
By setting all bank balances to $0.00 and placing the calculations in a background thread, and then updating UI once complete, it now only takes about a second to load (still wait 19 seconds to get current balances to update):
DispatchQueue.global(qos: .background).async {
// do your job here
MIProcessor.sharedMIP.obtainTheBalancesAfter()
MIProcessor.sharedMIP.updateTheMIP()
DispatchQueue.main.async {
// update ui here
self.cardViewCollectionView.reloadData()
}
}