Changing app icon doesn't work when saving user defaults - ios

In a secondary view controller in my app, I have a table of icons to change to, like the Reddit app Apollo.
In my main view controller, I am using user defaults to save two labels text and the view background color. Without any user default saving at all, when tapping on one of the table rows, I am able to change the app icon freely with these lines:
let appIconService = AppIconService()
appIconService.changeAppIcon(to: .goldAppIcon)
For saving user defaults: When a button is pressed on screen it randomly selects a string from an array and sets one of the two label.text, then randomly selects a UIColor and sets the view.backgroundcolor. I then save those with the "saveLastQuote()" function, and then call the "checkForLastQuote()" function in viewDidLoad() to grab from user defaults:
let defaults = UserDefaults.standard
func saveLastQuote() {
defaults.set(randomText.text!, forKey: Keys.savedQuote)
defaults.set(nameText.text!, forKey: Keys.savedName)
defaults.set(self.view.backgroundColor, forKey: Keys.savedBackgroundColor)
}
func checkForLastQuote() {
let lastQuote = defaults.value(forKey: Keys.savedQuote) as? String ?? "\"Quote\""
let lastName = defaults.value(forKey: Keys.savedName) as? String ?? ""
let lastBackgroundColor = defaults.color(forKey: Keys.savedBackgroundColor)
randomText.text = lastQuote
nameText.text = lastName
self.view.backgroundColor = lastBackgroundColor ?? Colors.black
}
For some reason when I have the saving user defaults code in the main ViewController.swift, when I tap on a row to change the icon on the other screen, I get this dialog box (The screenshot below) indicating to me that it has changed, but when I close the app and reopen, it always stays at its original app icon (Black and white version of the gold one in the screenshot)
EDIT: When I build and run the app on my device (Which is where I do all my testing for the app), the app's icon DOES change to the new one I clicked in the table view, then continues to not change until I build and run again.
Do I need to save the icon I am changing now since I am using user defaults to append other items? Or possibly grab the changed icon it normally saves by itself from user defaults and set it? I don't know why I would be getting this behavior. Pretty confused about it.

Related

How do you access a textfield which does not have a label or static text in XCTest?

so I've recently started testing an iOS app with xctest. I'm on a time model view where I would like to change the number in a cell. The number in this cell is the number of days after which the selected time model repeats itself. But I'm unable to access this textfield and change the number as it does not have a label / name / static text. When I record a tap on this field, Xcode gives me a strange element hierarchy which I've defined as the parameter 'onDay' below
func testRepetitionTypeMonthsOnDay() throws {
let app = XCUIApplication()
let tablesQuery = app.tables
let cellsQuery = tablesQuery.cells
XCTAssertTrue(app.navigationBars["Zeitmodelle"].waitForExistence(timeout: standardTimeout)) //wait for the time model view to open
app.staticTexts["Wiederholend"].firstMatch.tap() //tapping on a cell
XCTAssertTrue(app.navigationBars["Wiederholend"].waitForExistence(timeout: standardTimeout)) // wait for the cell to open
let repetitionType = app.tables.cells["Am, Am Tag, Expand"] //cell 1 from screenshot
let onDay = tablesQuery.children(matching: .cell).element(boundBy: 7).children(matching: .other).element(boundBy: 1).children(matching: .other).element.children(matching: .textField).element //cell 2
let endDate = app.tables.cells["Endet, Endet nicht, Expand"] // cell 3 from screenshot
onDay.tap()
onDay.clearAndEnterText("5")
}
However, Xcode cannot find the parameter onDay that itself has generated in the previous step, tap on it and enter a new text. I've attached a screenshot of the app with this cell here. The cells above and below the marked cell can be identified easily and the assertions for their existence work. The marked cell, however, is a different matter as it does not have a label / static text. Do you have an idea how I can get around this? Thanks a lot!
So I've finally found a way to access this element. Instead of going into the cell view I've accessed the textfields array from tables view.
func testRepetitionTypeMonthsOnDay() throws {
let app = XCUIApplication()
let tablesQuery = app.tables
XCTAssertTrue(app.navigationBars["Zeitmodelle"].waitForExistence(timeout: standardTimeout)) //wait for the time model view to open
app.staticTexts["Wiederholend"].firstMatch.tap() //tapping on a cell
XCTAssertTrue(app.navigationBars["Wiederholend"].waitForExistence(timeout: standardTimeout)) // wait for the cell to open
let onDay = tablesQuery.textFields.element(boundBy: 3)
onDay.tap()
onDay.clearAndEnterText("5")
}
Because the app had a mixture of picker wheels and textfields in the same view, it was a bit problematic for Xcode to find the element I wanted from a general element index. But the use of the textfields array filtered the index list down to only textfields. Xcode was then able to identify the element without any conflict and perform the test. I hope this helps anyone having the same problem.

iOS - Today extension widget cleared over time

Situation as it should be
We have a today widget that shows a maximum of 6 buttons depending on data set in the corresponding app. This data is shared using app-groups. If at least one button is configured it will show up as shown in the image above. If the user is not logged in, or if no buttons are configured, it will show a message as shown in the image below.
Problem
After several hours (somewhere between 4 and 7) of not having opened the app, the widget reverts to the 'No buttons configured' view.
Analysis so far
The way the data is loaded from the app-group is done using the code as shown below. (gist for full code) In the way I had written it, the only way the 'No buttons configured' view can be shown is if the buttons array actually exists but has a length of zero.
I expected something like a cache clearing or a background service stopping, but as far as I can see, exceptions should be caught earlier:
If no connection could be made to the app-group data, userDefaults should be nil, so it should show the 'Not logged in view'.
In case the buttons were never defined, buttons should be nil and so again it should show the 'Not logged in view'
Considering the app does nothing in the background, the app itself could not be changing the buttons.
I tried reproducing this while having the debugger connected, but the problem will not reproduce.
Does anyone even have the slightest idea on how to fix this issue or how to start debugging this?
Relevant files:
TodayViewController
Cordova Plugin
Relevant code:
private struct sharedData {
static var baseUrl: String?
static var token: String?
static var phoneDeviceId: String?
static var buttons: Array<Button>?
}
func loadData() {
let groupIdentifier = "group." + NSBundle.mainBundle().bundleIdentifier!
var groupIdArray = groupIdentifier.componentsSeparatedByString(".")
groupIdArray.removeAtIndex(groupIdArray.count - 1)
let appGroupIdentifier = groupIdArray.joinWithSeparator(".");
let userDefaults = NSUserDefaults.init(suiteName: appGroupIdentifier)
if (userDefaults == nil) {
print("Error in user defaults")
setButtonTitle("Not logged in. Open Triggi to continue.")
return false
}
sharedData.baseUrl = userDefaults?.valueForKey("baseUrl") as? String
sharedData.token = userDefaults?.valueForKey("token") as? String
sharedData.phoneDeviceId = userDefaults?.valueForKey("phoneDeviceId") as? String
let buttons = userDefaults?.valueForKey("buttons") as? NSArray
if (sharedData.baseUrl == nil || sharedData.token == nil || sharedData.phoneDeviceId == nil || buttons == nil) {
print("Missing data")
setButtonTitle("Not logged in. Open Triggi to continue.")
return false
}
if (buttons?.count == 0) {
print("No buttons configured")
setButtonTitle("No buttons configured. Open Triggi to continue.")
return false;
}
// More things are done with the data here
}
Today Extension Controller is a UIViewController and thus follows the same lifecycle as that of a UIViewController. So, the lifecycle method viewDidLoad() is called everytime whenever the widget is loaded.
Also, widgetPerformUpdateWithCompletionHandler is called:
when widget is updated in background
before widget snapshot is taken
So, instead of just calling loadData() in widgetPerformUpdateWithCompletionHandler, also call it from viewDidLoad().
Also, where have you written the code to add/remove UIButtons from superview and to show "No buttons configured" in your code?

Adding two of the same values to array instead of one - swift

I am trying to make a search bar, where you can search for users names. The cell is showing two things - and image and a label.
The label is showing the users name and the image is showing the profile picture. The image needs the user userID to display his/her picture.
The countryAndCode contains the users userID called country, and the users name called code.
My problem is that it is adding the first user twice and I do not know why or how to change it.
Here is the relevant code, let me know if you would like to see more:
func startObersvingDB() {
FIRDatabase.database().reference().child("UserInformation").observeEventType(.ChildAdded, withBlock: { snapshot in
let title = snapshot.value!["usersUID"] as? String
let name = snapshot.value!["userName"] as? String
self.tableData.append(title!)
self.tableDataNames.append(name!)
for i in 0..<self.tableData.count {
print(self.tableDataNames.count)
print(self.tableData.count)
self.countryAndCode.append((self.tableData[i], code: self.tableDataNames[i]))//this shows error in XCode some times
//if the above code doesn't compile then use the below code
print("DONE")
print(self.countryAndCode)
}
self.tableView.reloadData()
})
}

Where to place code to populate a UITextfield (title field) after a segue screen returns to main screen?

I am building a Swift - Single View app. I’ve added a “segue" view to allow user to store a title line preference using NSUserDefaults. When I return to the primary view the title line isn’t filled with the new title info from NSUserDefaults. I’ve placed the code to populate the main screen title line in the viewDidLoad section noted below. Strangely enough, if I go to the “segue" screen and back to main screen again without making changes then it shows up. Where can I place code to get the title line stored in my preferences segue screen to show up when I go back to the main view? Here is my code that works, but only after going back and forth from the seague twice. Thanks for any help.
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let defaults = NSUserDefaults.standardUserDefaults()
let titlePref = defaults.objectForKey("usertitleLineKey")!
titleLine.text = titlePref as! String
}
You should use viewWillAppear or viewDidAppear callback methods to refresh any content in your view when or just before it shows up. viewDidLoad will only be called once when the view is initialised on to memory.
Try this code:
override func viewWillAppear() {
let defaults = NSUserDefaults.standardUserDefaults()
let titlePref = defaults.objectForKey("usertitleLineKey")!
titleLine.text = titlePref as! String
}

How to display an image only the first time an app is opened?

I'm writing a very simple app and I would like to display a message the first time an app is opened. And not display after that at all.
The message will be on an image.
Is there any way to do this? I'm using swift code and would really appreciate some help.
Use NSUserDefaults to store a value that you can check against to see if your application has been launched once before. Put this in your viewDidLoad if you'd like to present your image when the application launches.
let defaults = NSUserDefaults.standardUserDefaults()
if !defaults.boolForKey("haveRanOnce") {
// First run of app
// Present your message
// Update defaults
defaults.setBool(true, forKey: "haveRanOnce")
}
NSUserDefaults Class Reference
Store a flag in NSUserDefaults which shows if the image has been shown. If it hasn't, show the image. It's probably easiest to add this logic into your root view controller when its view is shown, and then add the image as a subview or present a view controller to show the image. Alternatively, depending on what your app does, you could have a splash screen which shows loading progress (etc) and then have it check and show the image if required before proceeding to the 'true' root view controller.
You can save NSUserDefault naming NotFirstTime to do this. When the application is launched for the first time there won't be any FirstTimeLaunch user default, therefore, it will return false as a default value. In this case you can display the image and set NotFirstTime to true.
On the second launch NotFirstTime will return true and in this case you should not display the image.
Here is the code to do so:
let defaults = NSUserDefaults.standardUserDefaults()
if let name = defaults.boolForKey("NotFirstTime")
{
/* don't display image here */
}
else
{
/* display image here */
defaults.setBool(true, forKey: "NotFirstTime");
}
Use NSUserDefaults to store a value of 1 in a variable. I the variable has a value of 0 then it is the first time the app has loaded. If it has a value 1 then it has been loaded before.
let defaults = NSUserDefaults.standardUserDefaults()
var isFirstTime = defaults.integerForKey("firstTime");
if isFirstTime == 0 {
//Show image / message
defaults.setInteger(value: 1, forKey "firstTime") //Set value to 1
}

Resources