NSUserDefaults are not being saved [duplicate] - ios

This question already has answers here:
NSUserDefaults not saving properly
(4 answers)
Closed 7 years ago.
I am having a bear of a time getting my NSUserDefaults to save when the user accepts the license. I'm guessing I am missing something simple but as I have been staring at this for two solid days now, I probably wouldn't see it if it bit me. This is my first project so please understand I am very new to this. Thank you for you patience and assistance.
In the main VC a function runs on ViewDidLoad to determine if the user has accepted the license.
override func viewDidLoad() {
super.viewDidLoad()
print("ViewController.swift View Did Load")
userLicense()
Here is that function:
func userLicense() {
if licenseStatus == nil {
print("nil status - The NSUserDefaults value for lisenceStatus is", licenseStatus)
segueIdentifier = "showEULA"
self.segue()
print("First Run or Not yet accepted or declined. License Status var = ",licenseStatus)
} else {
licenseStatus = NSUserDefaults.standardUserDefaults().objectForKey("licenseStatus")! as? String
print("userLicense func licenseStatus var is ",licenseStatus)
if licenseStatus == "Declined License" {
print("declined status - The NSUserDefaults value for lisenceStatus is", licenseStatus)
segueIdentifier = "showEULA"
segue()
print("Declined License. Send user back to EULA screen")
} else
if licenseStatus == "Accepted License" {
NSUserDefaults.standardUserDefaults().objectForKey("licenseStatus")
print("accepted status - The NSUserDefaults value for lisenceStatus is", licenseStatus,". The app should run.")
//segueIdentifier = "backToLoginScreen"
//segue()
//print("Accepted License")
}
}
}
If the user is not licensed, he is segued to the License Acceptance VC and this code runs:
There is a button to accept the license:
#IBAction func acceptEULA(sender: AnyObject) {
NSUserDefaults.standardUserDefaults().setObject("Accepted License", forKey: "licenseStatus")
licenseStatus = NSUserDefaults.standardUserDefaults().objectForKey("licenseStatus")! as! String
print("The Accepted func NSUserDefaults license Status is ", licenseStatus)
segueIdentifier = "backToLoginScreen"
self.passThroughError = "You have chosen to accept the user license. Click cancel to return, otherwise enjoy using MORE 2 Go."
self.passThroughErrorAlertController("Confirm Accept License", error: passThroughError)
}
And a button to Decline the license:
#IBAction func cancelApp(sender: AnyObject) {
NSUserDefaults.standardUserDefaults().setObject("Declined License", forKey: "licenseStatus")
licenseStatus = (NSUserDefaults.standardUserDefaults().objectForKey("licenseStatus")! as? String)!
print("The NSUserDefaults licenseStatus is ", self.licenseStatus)
segueIdentifier = "backToLoginScreen"
self.passThroughError = "You have chosen to decine the user license. Click cancel to return, otherwise click OK and the app will be inactive."
self.passThroughErrorAlertController("Confirm Decline License", error: passThroughError)
}
As you see, it is in these button IBActions that I set the licenseStatus var to the appropriate value. The print commands show in the logs that the values are set correctly but when the user is passed back to the login VC, the value has not stuck. Here is the function where the sequel is actually called, in case this is where I am missing a step:
func passThroughErrorAlertController(title:String, error:String) {
let passThroughAlert = UIAlertController(title: title, message: passThroughError, preferredStyle: UIAlertControllerStyle.Alert)
passThroughAlert.addAction((UIAlertAction(title: "Cancel", style: .Cancel, handler: nil)))
passThroughAlert.addAction((UIAlertAction(title: "OK", style: .Default, handler: {action in
print("The user clisked OK, the license status is ", self.licenseStatus)
self.performSegueWithIdentifier(self.segueIdentifier, sender: self)
//self.dismissViewControllerAnimated(true, completion: nil)
})))
self.presentViewController(passThroughAlert, animated: true, completion: nil)
}
I apologize for such a lengthy post but as I said, I have been trying to implement this for two days and none of the references I can find seem to be helping (or I am just missing the forrest for the trees now that I am so far in). Thanks again for your assistance.

You need to synchronize your NSUserDefaults after setting value to it:
NSUserDefaults.standardUserDefaults().synchronize()
or in Objective-C:
[[NSUserDefaults standardUserDefaults] synchronize];
The NSUserDefaults is a file, and settings value to it is writing data to a file - a heavy IO code, so use it only if you need to save some data between app running, and synchronize it after you insert data to it.
If you inseting more than one value, call it at the end, and it will flash all the last inserted data to the file.

Related

Handling the answer from API in UI Testing Swift

I have weather app. It fetches the data from API. I enter needed city, then next screen opens and shows me the name of the city and temperature. I am writing UI test, which should open the app, handle an alert which asks to use location, then test should write the city name and check if this city exists in the screen. All works except checking the city name at the end. I thought maybe the problem is because it needs some time to get the answer from API, and tests doesn’t wait for it. Maybe I need to set timer to wait for answer. Or the problem is in smth else?
Here is my code and it fails at the last line.
func testExample() throws {
let app = XCUIApplication()
app.launchArguments = ["enable-testing"]
app.launch()
app/*#START_MENU_TOKEN#*/.staticTexts["My location"]/*[[".buttons[\"My location\"].staticTexts[\"My location\"]",".staticTexts[\"My location\"]"],[[[-1,1],[-1,0]]],[0]]#END_MENU_TOKEN#*/.tap()
addUIInterruptionMonitor(withDescription: "Allow “APP” to access your location?") { (alert) -> Bool in
let button = alert.buttons["Only While Using the App"]
if button.exists {
button.tap()
return true // The alert was handled
}
return false // The alert was not handled
}
app.textFields["Enter your city"].tap()
app.textFields["Enter your city"].typeText("Barcelona")
app.buttons["Check weather"].tap()
XCTAssertTrue(app.staticTexts["Barcelona"].exists)
}
XCTest comes with a built-in function you need
Documentation: https://developer.apple.com/documentation/xctest/xcuielement/2879412-waitforexistence/
Example:
XCTAssertTrue(myButton.waitForExistence(timeout: 3), "Button did not appear")
I found the function and used it to wait before the result.
Here is the function and its usage in my code.
func waitForElementToAppear(_ element: XCUIElement) -> Bool {
let predicate = NSPredicate(format: "exists == true")
let expectation = expectation(for: predicate, evaluatedWith: element,
handler: nil)
let result = XCTWaiter().wait(for: [expectation], timeout: 5)
return result == .completed
}
app.textFields["Enter your city"].tap()
app.textFields["Enter your city"].typeText("Barcelona")
app.buttons["Check weather"].tap()
let result = app.staticTexts["Barcelona"]
waitForElementToAppear(result)
XCTAssertTrue(result.exists)

Load video in share extension with Swift 4

I'm working on an iOS app that provides a share extension supposed to upload a video from Photo Library to a server.
I cannot find any example on how to handle the video.
Here is my code:
if itemProvider.hasItemConformingToTypeIdentifier("public.movie") {
print ("A movie has been chosen")
itemProvider.loadItem(forTypeIdentifier: "public.movie", options: nil, completionHandler: { (data, error) in
print ("Loaded the video")
if let video = data as? Data {
// Do stuff with the movie now.
print ("The movie should be loaded now")
}
self.extensionContext?.completeRequest(returningItems: [], completionHandler:nil)
})
}
The first print is printed, so I'm actually in the case of a public.movie item. But not the second and third print.
Can someone, please, tell me how the movie is passed and how I can handle it?
Thanks
With the help of app groups you can do work around : http://www.atomicbird.com/blog/sharing-with-app-extensions
Get video from library and save in Data in userdefaults.
Transfer the USerDefault key to via appschems and intercept it open URL method in app delgate of app.
With the same key load that video and forward it to server.
For references of app groups and Share extension : https://medium.com/#abhishekthaplithapliyal/ios-share-extension-930ba1ad3c3d
loadItem runs asynchronously, and when you're working with a video file, the file size is much larger than images, so loadItem does not have time to complete itself before self.extensionContext?.completeRequest runs and closes the share extension.
The solution I found involved the following:
(1) Create a boolean variable that measures whether the loadItem function is complete, or not.
var loadItemDone: Bool = false
(2) Write a recursive function that periodically checks to see whether the loadItem function has completed. If so, then run the completeRequest call. If not, continue recursion. In my case, I check for completion once per second, which is why it says "after: 1".
func waitForLoadItemToComplete(after seconds: Int) {
let deadline = DispatchTime.now() + .seconds(seconds)
DispatchQueue.main.asyncAfter(deadline: deadline) {
if self.loadItemDone == false{
self.waitForLoadItemToComplete(after: 1)
}else{
let alert = UIAlertController(title: "Success!", message: "Your file is uploaded!", preferredStyle: .alert)
let action1 = UIAlertAction(title: "Yeah!", style: .cancel) { (action) in
print("User acknowledged file uploaded.")
self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
}
alert.addAction(action1)
self.present(alert, animated: true, completion: nil)
}
}
}
(3) Set the loadItemDone variable to true, when loadItem completes:
if attachment.hasItemConformingToTypeIdentifier("com.apple.quicktime-movie") {
// Do stuff with the movie now.
self.loadItemDone = true
}
'public.movie' didn't work for me. I had to use 'com.apple.quicktime-movie'
Also, DispatchQueue did not work for me, which is why I had to hack a bit... I would love to hear a better solution from anyone more familiar with threads in share extensions...

Firebase Database saving data after approvement

I'm using firebase database on an iOS app! I'm writing in swift. I'm using a 'Send' button to write data (ex. textField and label values) on my firebaseDatabase. Is there any way to accept or decline data on my database? What I mean is if a user add something to textfield and press send (which means adding it to my database), I want to accept or decline it to my database, before adding it there!
My button action:
#IBAction func saveBtn(_ sender: Any) {
//Saving item to database
if commentTextField.text != "" && placeLabel.text != "Location"
{
let place = placeLabel.text
let key = dbRef!.child("placeLabel").childByAutoId().key
dbRef!.child(place!+"/placeLabel").child(key).setValue(place)
dbRef!.child(place!+"/comment").child(key).setValue(commentTextField.text)
dbRef!.child(place!+"/rating").child(key).setValue(ratingControl.rating)
commentTextField.text = ""
//alert
createAlert(title: "Thank you!", message: "Review submitted.")
self.navigationController?.popViewController(animated: true)
}else{
//alert
createAlert(title: "Why don't write a review?", message: "Please write a review.")
}
}
If I have understood you currectly, then First way:
1) Add extra field like isAccepted
2) Add new value to your node, but show only if isAccepted == true.
3) If false show some UIView for approving.
Second way:
1) You should create additional node with name like Suggested
actions
2) Let user add to this node
3) Check from your user this node and accept/decline.
4) If accepted - add to final node
Hope it helps
Another way to enable offline capability of firebase database. It will enhance the processing and you don't need to handle this.This will achieve with one line of code only.

AdReward from AdColony not working on Swift 2.0 for some reason

I would like to implement a reward interstitial in my game, but i'm getting a lot of AdColony errors such as: "No fill for ad request" or that my Zone ID is invalid.
To start of, this would be how I configured my AdColony Zone:
Zone is active? Yes
Zone Type: Preroll/Interstitial (Gives me "No fill for ad request error")
Value Exchange/V4VC (Gives me "Zone ID invalid, please check config error")
House Ads: Back Fill
Options: 0 0 1
Development: Show Test Ads Only (Although my app is currently Live)
The example they give you with the SDK download, is for Apps not for Games so I tried to kinda translate it for Games, although it wasn't that different, but there might be a problem with my current code.
So this is how I have it in my GameViewController.swift.
// Outside I declare a struct
struct Constants
{
static let adColonyAppID = "app5aab6bb6aaf3xxxxxx"
static let adColonyZoneID = "vz19959f95bd62xxxxxx"
static let currencyBalance = "coinAmount"
}
// Inside GameViewController
var ad: AdColonyInterstitial?!
var spinner: UIActivityIndicatorView!
override func viewDidLoad() {
super.viewDidLoad()
self.setupAdRewardBanner()
}
func setupAdRewardBanner() {
AdColony.configureWithAppID(Constants.adColonyAppID, zoneIDs: [Constants.adColonyZoneID], options: nil,
completion: {(zones) in
let zone = zones.first
zone?.setReward({ (success, name, amount) in
if (success) {
let storage = NSUserDefaults.standardUserDefaults()
let wrappedBalance = storage.objectForKey(Constants.currencyBalance)
var balance = 0
if let nonNilNumWrappedBalance = wrappedBalance as? NSNumber {
balance = Int(nonNilNumWrappedBalance.integerValue)
}
balance = balance + Int(amount)
let newBalance: NSNumber = NSNumber(integerLiteral: balance)
storage.setValue(newBalance, forKey: Constants.currencyBalance)
storage.synchronize()
self.updateCurrencyBalance()
}
})
//If the application has been inactive for a while, our ad might have expired so let's add a check for a nil ad object
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(GameViewController.onBecameActive), name: "onBecameActive", object: nil)
//AdColony has finished configuring, so let's request an interstitial ad
self.requestInterstitial()
})
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(GameViewController.triggerAdReward), name: "triggerAdReward", object: nil)
self.updateCurrencyBalance()
}
func requestInterstitial()
{
//Request an interstitial ad from AdColony
AdColony.requestInterstitialInZone(Constants.adColonyZoneID, options:nil,
//Handler for successful ad requests
success:{(newAd) in
//Once the ad has finished, set the loading state and request a new interstitial
newAd.setClose({
self.requestInterstitial()
})
//Interstitials can expire, so we need to handle that event also
newAd.setExpire( {
self.ad = nil
self.requestInterstitial()
})
//Store a reference to the returned interstitial object
self.ad = newAd
},
//Handler for failed ad requests
failure:{(error) in
NSLog("SAMPLE_APP: Request failed with error: " + error.localizedDescription + " and suggestion: " + error.localizedRecoverySuggestion!)
}
)
}
func triggerAdReward(sender: AnyObject)
{
if let ad = self.ad {
if (!ad!.expired) {
ad?.showWithPresentingViewController(self)
}
}
}
func updateCurrencyBalance()
{
//Get currency balance from persistent storage and display it
let storage = NSUserDefaults.standardUserDefaults()
let wrappedBalance = storage.objectForKey(Constants.currencyBalance)
var balance: Int = 0
if let nonNilNumWrappedBalance = wrappedBalance as? NSNumber {
balance = Int(nonNilNumWrappedBalance.integerValue)
}
print("current balance ", balance)
//XXX Run animation of giving user coins and update view
}
func onBecameActive()
{
//If our ad has expired, request a new interstitial
if (self.ad == nil) {
self.requestInterstitial()
}
}
And then after all that, I call this notification to request the ad interstitial when pressing a button after the user loses in GameScene.
NSNotificationCenter.defaultCenter().postNotificationName("triggerAdReward", object: nil)
I tried debugging, I can't seem to see the code getting inside the if (success) block. So there might be an issue there.
Does anyone know what I'm doing wrong?
After debugging more, i noticed that it's not advancing with this method
self.requestInterstitial()
So there might be an issue with my account maybe? Why is not passing through the success and goes through the error block?
The error in the console is :
SAMPLE_APP: Request failed with error: No fill for ad request and
suggestion: Make sure you have configured your zone properly in the
control panel: http://clients.adcolony.com.
Thanks in advance.
It seems your code should be working.
Since you want to implement a reward interstitial, you should set the zone type to V4VC.
In case it said "Zone ID invalid, please check config error", you should check twice the App id and zone id in the source code and the Adcolony client panel page.
After you changed the zone type, wait for some time(10 min?) to test, the server should need time to sync the status.
Test on device instead of simulator if possible.
Here is the document for v4vc: https://github.com/AdColony/AdColony-iOS-SDK-3/wiki/Rewarded-Interstitial-Ads-Quick-Start-Guide

Parse signup going to next view controller if fields are missing

So I am trying to create a signup page, and I am running into some trouble. I want the user to HAVE to fill in all the information before they hit signup. After they hit the signup button, if they do not have all of the fields filled in they get an UIAlertView error for many different things. When I hit signup, it takes the user to the next page and then displays the UIAlertView, I do not want this to happen. I can't figure out what is happening. If the UIAlertView comes up if they do not have a username or email filled in I need the UIAlertView to display after they click the button and not go to the next page. I have a performSegue method in my code but it is performing first, and not last. Can anybody help me? I will include the screenshot of my storyboard as well.
user.signUpInBackgroundWithBlock {
(succeeded: Bool, error: NSError?) -> Void in
if let error = error {
let errorString = error.userInfo?["error"] as? NSString
if fullname.isEmpty || email.isEmpty || username.isEmpty || password.isEmpty {
var emptyFields:UIAlertView = UIAlertView(title: "Plese try again", message: "It looks like you forgot to fill out all the fields. Please make sure all the fields are filled out so we can create an account for you.", delegate: self, cancelButtonTitle: "Try again")
emptyFields.show()
} else if error.code == 203 {
var takenEmail:UIAlertView = UIAlertView(title: "Please try again", message: "It looks like that email has already been taken, please try again.", delegate: self, cancelButtonTitle: "Try again")
} else if error.code == 202 {
var usernameTaken:UIAlertView = UIAlertView(title: "Please try again", message: "It looks like that username is already in use, please try again.", delegate: self, cancelButtonTitle: "Try again")
}
}
}
There are a few ways to do this. The way I would do it is as follows.
Step 1.
Ok you want to select your Sign Up View Controller.
Step 2.
You are going to control click and drag from the Yellow View Controller Icon to the Second View Controller. In the Menu that appears choose the Show Or Present Modally Option.
Step 3.
Choose the Segue you just created and change its Identifier, In the right side panel to "signupSuccessful".
Step 4.
You can set the code up like this.
if usernameTF.text.isEmpty || passwordTF.text.isEmpty {
var emptyFields = UIAlertView()
emptyFields.title = "Plese try again"
emptyFields.message = "It looks like you forgot to fill out all the fields. Please make sure all the fields are filled out so we can create an account for you."
emptyFields.addButtonWithTitle("Try Again!")
emptyFields.show()
}else if usernameTF.text == "ERROR CODE 203" {
//do same thing with the alert view!
}else if usernameTF.text == "ERROR CODE 202" {
//Do another alert view here
//You can keep adding the "else ifs" for any other error codes
}
//Here is where it will send to the second view when everything above is false!
else {
//Here we present the second view.
self.performSegueWithIdentifier("signupSuccessful", sender: self)
}
There you go.
Here is a download of the project if you need to see the segue again, or any of the above code.
https://github.com/bobm77/ParseSegueExample
Implement the method func shouldPerformSegueWithIdentifier(identifier: String?, sender: AnyObject?) -> Bool in your view controller and return false if you don't want the segue to happen.

Resources