Swift 4 - Struct static variables not showing values - swift

I am new to swift but learning as I go. I have a function to get data from AWS DynamoDB table, using a for..loop. I am capturing field values of table items in struct variables within the loop, but when I call the struct variables in a tableview, it is showing blank array like [] in the log screen in Xcode. Here is the code:
import Foundation
import SQLite3
import UIKit
import AWSDynamoDB
struct MyVariables {
static var empDict: [String: String] = [:]
static var arrayEmployees = Array<Dictionary<String, String>>()
}
// This is a class defined to hold various methods to be used throughout the app.
class DatabaseClass: UIViewController, UITextFieldDelegate
{
// other methods within DatabaseClass
// ........
//
//
// Scan DynamoDB table for employees
func scanEmployees () {
let dynamoDBObjectMapper = AWSDynamoDBObjectMapper.default()
let scanExpression = AWSDynamoDBScanExpression()
scanExpression.limit = 20
dynamoDBObjectMapper.scan(Employees.self, expression: scanExpression).continueWith(block: { (task:AWSTask<AWSDynamoDBPaginatedOutput>!) -> Any? in
if let error = task.error as NSError? {
print("The request failed. Error: \(error)")
}
let paginatedOutput = task.result!
for emp in paginatedOutput.items as! [Employees] {
if emp._empid != nil {
MyVariables.empDict["Empid"] = emp._empid
} else {
MyVariables.empDict["Empid"] = nil
}
if emp._email != nil {
MyVariables.empDict["email"] = emp._email
} else {
MyVariables.empDict["email"] = nil
}
if emp._firstname != nil {
MyVariables.empDict["firstname"] = emp._firstname
} else {
MyVariables.empDict["firstname"] = nil
}
if emp._lastname != nil {
MyVariables.empDict["lastname"] = emp._lastname
} else {
MyVariables.empDict["lastname"] = nil
}
if emp._location != nil {
MyVariables.empDict["location"] = emp._location
} else {
MyVariables.empDict["location"] = nil
}
if emp._mobile != nil {
MyVariables.empDict["mobile"] = emp._mobile
} else {
MyVariables.empDict["mobile"] = nil
}
if emp._work != nil {
MyVariables.empDict["work"] = emp._work
} else {
MyVariables.empDict["work"] = nil
}
if emp._site != nil {
MyVariables.empDict["site"] = emp._site
} else {
MyVariables.empDict["site"] = nil
}
MyVariables.arrayEmployees.append(MyVariables.empDict)
//print(MyVariables.arrayEmployees)
} // for loop
print ("printing array employees just after for loop")
print(MyVariables.arrayEmployees)
// The above print works
return nil
})
// print(MyVariables.arrayEmployees)
// Above doesn’t print
}
}
Later, I call this DatabaseClass in a tableview as follows:
class JsonParseViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let createEmp = DatabaseClass()
createEmp.scanEmployees()
print("in JsonParseViewController, calling scan function")
print(MyVariables.arrayEmployees)
When running in simulator, the log file shows the following:
in JsonParseViewController, calling scan function
[]
printing array employees just after for loop
[["location": "XXXXXX", "email": "johnSmith1#email.com", "firstname": "John1", "lastname": "Smith1", "mobile": "123-456-7890", "work": "123-456-7890", "Empid": "12345", "site": "KKKKKK"], ["location": "YYYYY", "email": "johnSmith8#email.com", "firstname": "John8", "lastname": "Smith8", "mobile": "123-456-7890", "work": "123-456-7890", "Empid": "12415", "site": "ZZZZZ"], ......etc]]
While executing the function scanEmployees, it seems to skip the DynamoDB scan command and returns [] in tableview, but goes to the function somehow and executes the data. It fails to capture the data in struct variables. I have bee trying to fix this for many days.

You have a problem with static memory. I would suggest not to use static values, its a very bad practice.
The solution could be to create a structure inside The class DatabaseClass. For example var myVariables = MyVariables() and fill it in the same way. Now You have the option for return it or use it from the DatabaseClass object. I will show the example now how it should look.
struct MyVariables {
var empDict: [String: String] = [:]
var arrayEmployees = Array<Dictionary<String, String>>()
}
// This is a class defined to hold various methods to be used throughout the app.
class DatabaseClass: UIViewController, UITextFieldDelegate
{
// other methods within DatabaseClass
// ........
//
//
// Scan DynamoDB table for employees
public var myVariables = MyVariables()
func scanEmployees () {
let dynamoDBObjectMapper = AWSDynamoDBObjectMapper.default()
let scanExpression = AWSDynamoDBScanExpression()
scanExpression.limit = 20
dynamoDBObjectMapper.scan(Employees.self, expression: scanExpression).continueWith(block: { (task:AWSTask<AWSDynamoDBPaginatedOutput>!) -> Any? in
if let error = task.error as NSError? {
print("The request failed. Error: \(error)")
}
let paginatedOutput = task.result!
for emp in paginatedOutput.items as! [Employees] {
myVariables.empDict["Empid"] = emp._empid
myVariables.empDict["email"] = emp._email
myVariables.empDict["firstname"] = emp._firstname
myVariables.empDict["lastname"] = emp._lastname
myVariables.empDict["location"] = emp._location
myVariables.empDict["mobile"] = emp._mobile
myVariables.empDict["work"] = emp._work
myVariables.empDict["site"] = emp._site
myVariables.arrayEmployees.append(myVariables.empDict)
//print(myVariables.arrayEmployees)
} // for loop
print ("printing array employees just after for loop")
print(myVariables.arrayEmployees)
// The above print works
return nil
})
// print(myVariables.arrayEmployees)
// Above doesn’t print
}
}
//then use it in this way
class JsonParseViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let createEmp = DatabaseClass()
createEmp.scanEmployees()
print("in JsonParseViewController, calling scan function")
print(createEmp.myVariables.arrayEmployees)
}
}

You miss asynchronous call
func scanEmployees (completion:#escaping ()-> Void) {
let dynamoDBObjectMapper = AWSDynamoDBObjectMapper.default()
let scanExpression = AWSDynamoDBScanExpression()
scanExpression.limit = 20
dynamoDBObjectMapper.scan(Employees.self, expression: scanExpression).continueWith(block: { (task:AWSTask<AWSDynamoDBPaginatedOutput>!) -> Any? in
if let error = task.error as NSError? {
print("The request failed. Error: \(error)")
}
let paginatedOutput = task.result!
for emp in paginatedOutput.items as! [Employees] {
myVariables.empDict["Empid"] = emp._empid
myVariables.empDict["email"] = emp._email
myVariables.empDict["firstname"] = emp._firstname
myVariables.empDict["lastname"] = emp._lastname
myVariables.empDict["location"] = emp._location
myVariables.empDict["mobile"] = emp._mobile
myVariables.empDict["work"] = emp._work
myVariables.empDict["site"] = emp._site
MyVariables.arrayEmployees.append(MyVariables.empDict)
//print(MyVariables.arrayEmployees)
} // for loop
print ("printing array employees just after for loop")
print(MyVariables.arrayEmployees)
// The above print works
completion()
return nil
}
)
// print(MyVariables.arrayEmployees)
// Above doesn’t print
}
//
when calling
createEmp.scanEmployees {
print(MyVariables.arrayEmployees)
}
Note : I recommend using SwiftyJson or JSONDecoder intstead of manually parsing the keys

Related

Get callback when ADMOB reward ad is closed without seeing whole ad in ios swift

I am using reward admob ad in my project with latest sdk. How can i get proper callback that the user has closed the ad in between. I know there is a delegate method of fullscreencontentdelegate which has a function adDidDismiss but in that function i am doing some code block which i perform when i complete watching the ad and it just works fine but what if i closed the ad in between, because what happens is that whether i see the whole ad or not this delegate function gets called and there is no way to differentiate how would i proceed with complete and incomplete ad. Please help me with this.
video link
as in the video first time i am not watching the whole ad and i just close it then also the scratch card popup comes because of the delegate method being called, which i dont want to open and then i just watch the whole ad and get my reward which is working fine
My code snippet:
enum RewardAdType {
case avatar, freeChips, scratchCard, chips250
}
typealias AD_COMPLETION_BLOCK = (_ success: Bool) -> ()
class RewardAdManager : NSObject
{
//MARK: - PROPERTIES
var rewardBasedVideoAd : GADRewardedAd? = nil
var rewardValue = ""
var type: RewardAdType? = nil
}
//MARK: - HELPERS
extension RewardAdManager
{
func loadRewardedAd(vc: UIViewController, userId: String, type: RewardAdType, imageName: String? = nil, chipsCoin: String? = nil, completion: #escaping AD_COMPLETION_BLOCK)
{
self.type = type
let adUnit = self.type == .avatar ? Constants.REWARD_AD_AVATAR_LIVE_ID : Constants.REWARD_AD_WINCHIPS_LIVE_ID
let request = GADRequest()
GADRewardedAd.load(withAdUnitID: adUnit, request: request) { [weak self] ad, error in
guard let self = self, error == nil else {
Helpers.hidehud()
self?.type = nil
self?.rewardBasedVideoAd = nil
return
}
let serverSideVerificationOptions = GADServerSideVerificationOptions()
serverSideVerificationOptions.userIdentifier = userId
if type == .scratchCard {
self.rewardValue = self.generateRandomRewardValue()
serverSideVerificationOptions.customRewardString = self.rewardValue
} else if type == .avatar {
serverSideVerificationOptions.customRewardString = imageName
} else if type == .freeChips {
serverSideVerificationOptions.customRewardString = chipsCoin
} else if type == .chips250 {
serverSideVerificationOptions.customRewardString = "250"
}
self.rewardBasedVideoAd = ad
self.rewardBasedVideoAd?.serverSideVerificationOptions = serverSideVerificationOptions
self.rewardBasedVideoAd?.fullScreenContentDelegate = self
self.showRewardedAd(viewController: vc, type: type, completion: completion)
}
}
func showRewardedAd(viewController: UIViewController, type: RewardAdType? = nil, completion: #escaping AD_COMPLETION_BLOCK)
{
Helpers.hidehud()
if let ad = self.rewardBasedVideoAd {
self.type = type
DispatchQueueHelper.delay {
ad.present(fromRootViewController: viewController) {}
}
completion(true)
} else {
self.type = nil
self.checkForSavedLanguage(viewController: viewController)
}
}
func checkForSavedLanguage(viewController: UIViewController)
{
let lang = LanguageCode(rawValue: Defaults[.LangCode]) ?? .english
viewController.showToast(msg: Constants.NO_ADS_MESSAGE.localizeString(string: lang))
}
func generateRandomRewardValue() -> String
{
var val = 0
let random = Double.random(in: 0.1...1.0)
if random < 0.20 {
val = 150
} else if random < 0.50 {
val = 200
} else if random < 0.70 {
val = 250
} else {
val = 350
}
return val.toString()
}
}
//MARK: - GADFullScreenContentDelegate
extension RewardAdManager : GADFullScreenContentDelegate {
func ad(_ ad: GADFullScreenPresentingAd, didFailToPresentFullScreenContentWithError error: Error)
{
self.type = nil
Helpers.hidehud()
let lang = LanguageCode(rawValue: Defaults[.LangCode]) ?? .english
let userInfo = ["msg":Constants.NO_ADS_MESSAGE.localizeString(string: lang)]
NotificationCaller.shared.showLeaveMsg(userInfo: userInfo)
}
func adDidDismissFullScreenContent(_ ad: GADFullScreenPresentingAd)
{
guard let type = self.type else { return }
self.rewardBasedVideoAd = nil
let userInfo: [String:RewardAdType] = ["type":type]
NotificationCaller.shared.showRewardTypePopup(userInfo: userInfo)
}
}

Get Proper callback when admob reward ad is closed without seeing whole ad in ios swift

i am using reward admob ad in my project with latest sdk. how can i get proper callback that the user has closed the ad in between. I know there is a delegate method of fullscreencontent delegate which will be called but in that delegate method i am calling some code block which i perform when i complete watching the ad and it justs work fine but what if i closed the ad in between then the same delegate is being called. so how do i decide that the user has watched the ad or closed in between.
my code snippet:
enum RewardAdType {
case avatar, freeChips, scratchCard, chips250
}
typealias AD_COMPLETION_BLOCK = (_ success: Bool) -> ()
class RewardAdManager : NSObject
{
//MARK: - PROPERTIES
var rewardBasedVideoAd : GADRewardedAd? = nil
var rewardValue = ""
var type: RewardAdType? = nil
}
//MARK: - HELPERS
extension RewardAdManager
{
func loadRewardedAd(vc: UIViewController, userId: String, type: RewardAdType, imageName: String? = nil, chipsCoin: String? = nil, completion: #escaping AD_COMPLETION_BLOCK)
{
self.type = type
let adUnit = self.type == .avatar ? Constants.REWARD_AD_AVATAR_LIVE_ID : Constants.REWARD_AD_WINCHIPS_LIVE_ID
let request = GADRequest()
GADRewardedAd.load(withAdUnitID: adUnit, request: request) { [weak self] ad, error in
guard let self = self, error == nil else {
Helpers.hidehud()
self?.type = nil
self?.rewardBasedVideoAd = nil
return
}
let serverSideVerificationOptions = GADServerSideVerificationOptions()
serverSideVerificationOptions.userIdentifier = userId
if type == .scratchCard {
self.rewardValue = self.generateRandomRewardValue()
serverSideVerificationOptions.customRewardString = self.rewardValue
} else if type == .avatar {
serverSideVerificationOptions.customRewardString = imageName
} else if type == .freeChips {
serverSideVerificationOptions.customRewardString = chipsCoin
} else if type == .chips250 {
serverSideVerificationOptions.customRewardString = "250"
}
self.rewardBasedVideoAd = ad
self.rewardBasedVideoAd?.serverSideVerificationOptions = serverSideVerificationOptions
self.rewardBasedVideoAd?.fullScreenContentDelegate = self
self.showRewardedAd(viewController: vc, type: type, completion: completion)
}
}
func showRewardedAd(viewController: UIViewController, type: RewardAdType? = nil, completion: #escaping AD_COMPLETION_BLOCK)
{
Helpers.hidehud()
if let ad = self.rewardBasedVideoAd {
self.type = type
DispatchQueueHelper.delay {
ad.present(fromRootViewController: viewController) {}
}
completion(true)
} else {
self.type = nil
self.checkForSavedLanguage(viewController: viewController)
}
}
func checkForSavedLanguage(viewController: UIViewController)
{
let lang = LanguageCode(rawValue: Defaults[.LangCode]) ?? .english
viewController.showToast(msg: Constants.NO_ADS_MESSAGE.localizeString(string: lang))
}
func generateRandomRewardValue() -> String
{
var val = 0
let random = Double.random(in: 0.1...1.0)
if random < 0.20 {
val = 150
} else if random < 0.50 {
val = 200
} else if random < 0.70 {
val = 250
} else {
val = 350
}
return val.toString()
}
}
//MARK: - GADFullScreenContentDelegate
extension RewardAdManager : GADFullScreenContentDelegate {
func ad(_ ad: GADFullScreenPresentingAd, didFailToPresentFullScreenContentWithError error: Error)
{
self.type = nil
Helpers.hidehud()
let lang = LanguageCode(rawValue: Defaults[.LangCode]) ?? .english
let userInfo = ["msg":Constants.NO_ADS_MESSAGE.localizeString(string: lang)]
NotificationCaller.shared.showLeaveMsg(userInfo: userInfo)
}
func adDidDismissFullScreenContent(_ ad: GADFullScreenPresentingAd)
{
guard let type = self.type else { return }
self.rewardBasedVideoAd = nil
let userInfo: [String:RewardAdType] = ["type":type]
NotificationCaller.shared.showRewardTypePopup(userInfo: userInfo)
}
}

Swift app crashing when attempting to fetch contacts

I am creating an onboarding portion of an app which gets the user's contacts to check which already have the app to add as friends. I'm using the CNContact framework. I have created several methods I'm using to get a full list of the users' contacts, check if they have the app, and enumerate them in a UITableView. However, when the view loads, the app crashes with the error "A property was not requested when contact was fetched." I already make a fetch request with the keys CNContactGivenNameKey, CNContactFamilyNameKey, CNContactPhoneNumbersKey, ad CNContactImageDataKey. I've included all my code here:
import Foundation
import Contacts
import PhoneNumberKit
struct ContactService {
static func createContactArray() -> [CNContact] {
var tempContacts = [CNContact]()
let store = CNContactStore()
store.requestAccess(for: .contacts) { (granted, error) in
if let _ = error {
print("failed to request access to contacts")
return
}
if granted {
let keys = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactPhoneNumbersKey, CNContactImageDataKey]
let request = CNContactFetchRequest(keysToFetch: keys as [CNKeyDescriptor])
request.sortOrder = CNContactSortOrder.familyName
do {
try store.enumerateContacts(with: request, usingBlock: { (contact, stop) in
tempContacts.append(contact)
})
} catch {
print("unable to fetch contacts")
}
print("created contact list")
}
}
return tempContacts
}
static func createFetchedContactArray(contactArray: [CNContact], completion: #escaping ([FetchedContact]?) -> Void) -> Void {
var temp = [FetchedContact]()
getNumsInFirestore { (nums) in
if let nums = nums {
for c in contactArray {
let f = FetchedContact(cnContact: c, numsInFirebase: nums)
temp.append(f)
}
return completion(temp)
} else {
print("Error retrieving numbers")
}
}
return completion(nil)
}
static func getNumsInFirestore(_ completion: #escaping (_ nums : [String]?) -> Void ) -> Void {
var numsInFirebase = [String]()
let db = FirestoreService.db
db.collection(Constants.Firestore.Collections.users).getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting user documents: \(err)")
completion(nil)
} else {
for doc in querySnapshot!.documents {
let dict = doc.data()
numsInFirebase.append(dict[Constants.Firestore.Keys.phone] as! String)
}
completion(numsInFirebase)
}
}
}
static func getPhoneStrings(contact: CNContact) -> [String] {
var temp = [String]()
let cnPhoneNums = contact.phoneNumbers
for n in cnPhoneNums {
temp.append(n.value.stringValue)
}
return temp
}
static func hasBump(contact: CNContact, completion: #escaping (_ h: Bool) -> Void ) -> Void {
let contactNums = ContactService.getPhoneStrings(contact: contact)
ContactService.getNumsInFirestore { (nums) in
if let nums = nums {
return completion(contactNums.contains(where: nums.contains))
} else {
print("Error retrieving numbers from firestore")
return completion(false)
}
}
}
static func anyContactsWithBump(completion: #escaping (_ yes: Bool) -> Void) {
let contacts = createContactArray()
var tempBool = false
let workgroup = DispatchGroup()
for c in contacts {
workgroup.enter()
hasBump(contact: c) { (has) in
if has {
tempBool = true
}
workgroup.leave()
}
}
workgroup.notify(queue: .main) {
completion(tempBool)
}
}
}
Then I call the methods in the view controller class:
import UIKit
import Contacts
import FirebaseDatabase
import Firebase
import FirebaseFirestore
class AddContactsVC: UIViewController {
var fetchedContactsWithBump: [FetchedContact] = []
override func viewDidLoad() {
let cnContacts = ContactService.createContactArray()
var contactsWithBump = [CNContact]()
let workgroup = DispatchGroup()
for contact in cnContacts {
workgroup.enter()
ContactService.hasBump(contact: contact) { (has) in
if has {
contactsWithBump.append(contact)
}
workgroup.leave()
}
}
workgroup.notify(queue: .main) {
print("Number of contacts with Bump: \(contactsWithBump.count)")
ContactService.createFetchedContactArray(contactArray: contactsWithBump) { (fetchedContacts) in
if let fetchedContacts = fetchedContacts {
self.fetchedContactsWithBump = fetchedContacts
} else {
print("Error creating fetchedContacts array.")
}
}
}
I also get the message "Error creating fetchedContacts array" in the console, so I know something is going wrong with that method, I'm just not sure what. Any help is appreciated!
Edit: The exception is thrown at 3 points: 1 at the first line of my FetchedContact init method, which looks like this:
init(cnContact: CNContact, numsInFirebase: [String]) {
if cnContact.imageDataAvailable { self.image = UIImage(data: cnContact.imageData!) }
It also points to the line let f = FetchedContact(cnContact: c, numsInFirebase: nums) in createFetched contact array, and finally at my completion(numsInFirebase) call in getNumsInFirestore.
To start with
let contacts = createContactArray()
will always return an empty array.
This function has a return statement outside the closure so will immediately return an empty array.
Change createContactArray to use a completion handler like the other functions you have to populate contacts from inside the closure.

How to save nested data to the Firestore?

I'm new here. An error occurs while writing the nested data to the Firestore. This is my data structure:
struct CartArray: Codable {
var num:Int
var name:String
var price:Double
init (num: Int,name: String,price: Double)
{ self.num = num
self.name = name
self.price = price
}
this is the data recording function:
let db = Firestore.firestore()
var arrayCart: [CartArray] = []
#IBAction func buttonCheckout(_ sender: Any) {
saveData()
}
...
func saveData () {
let menuItems = [arrayCart]
var list_menuItem = [Any]()
for item in menuItems {
do {
let jsonData = try JSONEncoder().encode(item)
let jsonObject = try JSONSerialization.jsonObject(with: jsonData, options: [])
list_menuItem.append(jsonObject)
}
catch {
// handle error
}
}
print(list_menuItem)
let parameters = [
"address": userAddress as Any,
"datetime": Timestamp(date: Date()),
"status": "Заказано",
"user_phone": userPhone as Any,
"username": userName as Any,
"total":cart.total,
"order":list_menuItem
]
db.collection("orders").document().setData(parameters)
{ err in
if let e = err {
print("$-- error save data \(e)")
} else {
print("success!")
}
}
}
this is the converting json of array:
[<__NSArrayI 0x600001f97880>( { name = "\U041f\U0438\U0440\U043e\U0436\U043a\U0438 \U0441 \U043c\U044f\U0441\U043e\U043c"; num = 3; price = 40; },
{ name = "\U041f\U0438\U0446\U0446\U0430 \U0421\U0442\U0430\U043d\U0434\U0430\U0440\U0442"; num = 1; price = 500; } ) ]
When saving occurs in the 'order' field, an 'Nested arrays are not supported' error occurs. Why?
The problem is that in the saveData() you are creating an array of arrays of CartArray.
Replace: let menuItems = [arrayCart]
with: let menuItems = arrayCart

NSUserDefaults not loading for Today Extension (Swift)

I'm trying to make a Today Extension, and in the main app favorite is being correctly written and read from both ar and ar2. favorite is a property of class Book. However, when I try to access the favorite property of an array of all Book objects from the Today Extension, both ar and ar2 are empty.
main app:
var favorite: Bool {
get {
let ar = NSUserDefaults.standardUserDefaults().stringArrayForKey("favorites") ?? []
let ar2 = NSUserDefaults(suiteName: "group.com.TodayExtension")
ar2?.synchronize()
let tempVar = ar2?.stringArrayForKey("favorites") ?? []
print("ar1: \(ar)")
print("tempVar: \(tempVar)")
return ar.contains {
$0 == slug
}
}
set {
var ar = NSUserDefaults.standardUserDefaults().stringArrayForKey("favorites") ?? []
let ar2 = NSUserDefaults(suiteName: "group.com.TodayExtension")
let contains = self.favorite
if (newValue && !contains) {
ar.append(self.slug)
ar2?.setObject(ar, forKey: "favorites")
ar2?.synchronize()
} else if (!newValue && contains) {
let idx = ar.indexOf {
$0 == slug
}
if let idx = idx {
ar.removeAtIndex(idx)
}
}
NSUserDefaults.standardUserDefaults().setObject(ar, forKey: "favorites");
}
}
Output:
ar1: ["Harry Potter", "Compound", "Nefarious"]
tempVar: ["Harry Potter", "Compound", "Nefarious"]
Today Extension:
func loadData(force: Bool, completion:(() -> Void)?) {
DATA.fetchEateries(force) { (error) -> (Void) in
dispatch_async(dispatch_get_main_queue(), {() -> Void in
if let completionBlock = completion {
completionBlock()
}
self.books = self.DATA.books
if self.books != [] {
for book in self.books {
if book.favorite {
print("I like it!")
} else {
print("I don't like \(book.name)")
}
}
}
else {
print("there are no books")
}
})
}
}
Output:
ar1: []
tempVar: []
I don't like Harry Potter
ar1: []
tempVar: []
I don't like Compound
ar1: []
tempVar: []
I don't like Nefarious
standardUserDefaults doesn't return the same user defaults for both target. You must create app group and then use NSUserDefaults(suiteName: "group")