Problems with storing UISwitch state - ios

I have a setting in my app that I am having issues retaining the state of. Basically, the UISwitch is on by default, however when updating my app to a newer version, it seems to switch off. I figured out that if the user has opened the settings menu in version 1.0, then this isn't an issue, however if they have never changed a setting, or even opened the settings menu, then it's a problem. Therefore, it must be an issue somewhere in viewWillAppear() but I can't figure out what it is. The code I have for storing the switches state seems really overcomplicated.
This is the code I have in my settings menu:
#IBAction func dupOffOnSwitch(sender: AnyObject) {
if dupSwitch.on == true {
autoAdjust = true
println(autoAdjust)
} else {
autoAdjust = false
println(autoAdjust)
}
NSUserDefaults.standardUserDefaults().setBool(autoAdjust, forKey: "autoAdjustSettings")
}
override func viewWillAppear(animated: Bool) {
autoAdjust = NSUserDefaults.standardUserDefaults().boolForKey("autoAdjustSettings")
if autoAdjust == true {
dupSwitch.on = true
} else {
dupSwitch.on = false
}
}
if userReturnedAuto == false {
dupSwitch.on = true
themeSwitch.on = false
userReturnedAuto = true
NSUserDefaults.standardUserDefaults().setBool(userReturnedAuto, forKey: "userReturnedAuto")
NSUserDefaults.standardUserDefaults().setBool(userReturnedAuto, forKey: "autoAdjustSettings")
}
I am declaring the bool 'autoAdjust' in a different view controller as a global variable. In that same view controller in viewWillAppear() I have this code:
autoAdjust = NSUserDefaults.standardUserDefaults().boolForKey("autoAdjustSettings")
Can anyone suggest a better way of storing the switches state while also having it on by default when the app is first launched? Or a fix to my current solution.

NSUserDefaults.standardUserDefaults().boolForKey("key") defaults to false. Therefore, as in your case, if the user never sets the key, then it will be false and the switch will default to off.
Instead, rename the key and use it as the negative. So instead, call it autoAdjustSettingsOff which will default to false and the switch will default to on. Don't forget to switch around the true/false settings in your conditional blocks.

Not sure if this is over kill(or just stupid). But why not use CoreData to save all the users settings. Then when the app becomes active(in your appDelegate) you can set the NSUserDefaults from these stored values. Just a thought :)
For shortness I will only make an example for one switch, you can then fill out and add the rest. I will also assume you will be updating the NSUserDefaults when the switch changes state. We will only update/save the core data when the app goes to in-active and load settings once when app becomes active.
Also if anyone sees something I missed or could be done better please let me know and I will update the answer.
First off you need to make sure you import the CodeData libraries into your view controller:
import CoreData
class fooBarViewController: UIViewController {
You need to create and entity in core data: lets assume it is called "Settings"
then once you have created the entity, you need to add some attributes. Press the "+" symbol.
Now add the attribute and set the name to "dupSwitch" and type to "Boolean". Add an attribute for each setting you need to store.
Next: You need to create a new swift file. Type NSObject. name it something like "dataObjects".
Inside you you will use the following code: (first remove all current code - make it blank)
import UIKit
import CoreData
#objc(settingObj)
class settingObj: NSManagedObject {
#NSManaged var dupSwitch:NSNumber //do this for each setting
}
Next you need to go back to your CoreData dataModel and do the following.
1. click on "Default" under the section "Configuration"
2. You will then get a list of all your "entities" select "Settings"
3. click on class and edit the field to "settingObj"
Now that you have you CoreData set up. You can now save/edit/delete data as need be.
For example when you need to load all saved user settings when app goes will become active.
in your AppDelegate.swift file: import the CoreData libraries.
import CoreData
then add this function if it is not already present
func applicationWillEnterForeground(application: UIApplication) {
}
inside "applicationWillEnterForeground" you will load any settings that have been stored in CoreData
var results:[settingObj] = []
let appDel:AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let context:NSManagedObjectContext = appDel.managedObjectContext!
let req = NSFetchRequest(entityName: "Settings")
let tapResults = context.executeFetchRequest(req, error: nil)
if let presentResults = tapResults {
if let castedResults = presentResults as? [settingObj] {
for item in castedResults {
let dupSwitch = item.dupSwitch
let settingSwitch = item.whateverElseYouNeedExample
//now based on this we can set the NSDefault
NSUserDefaults.standardUserDefaults().setBool(dupSwitch, forKey: "autoAdjustSettings")
NSUserDefaults.standardUserDefaults().setBool(settingSwitch, forKey: "whateverElseYouNeedExample")
}
}
}
Now when you want to save the users settings. I would would just it when app goes in-active.
First we will check if the settings have already been saved. If so, then we will just perform
an update. If not, we will save a new entry into yuor CoreData Entity.
add the following func to your appDelegate(if not already there):
func applicationDidEnterBackground(application: UIApplication) {
}
EDIT I HAVE TO GO TO WORK WILL COMPLETE REST OF ANWSER WHEN I GET BACK HOME. REALLY SORRY.

Related

Retreiving data with Core Data

I'm working on a project where I should save data locally with Core Data.
Here is my workflow :
To start, I ask the user to fill a form, let's say his firstname and lastname.
He clicks on the submit button, then data is saved on the device using Core Data
User is redirected to the "last filled form" view controller.
I have a bar button item that when clicked can show the latest filled form.
I should test if the array of filled forms is empty, then the button should be disabled.
Otherwise, the button should be enabled ...
I tried this piece of code, where I fetch data from the database and affected to an array but the button seams not working at all and it never gets disabled ...
class ViewController: UIViewController {
var userIdentity: UserIDentity = UserIDentity(context: PersistanceService.context)
var identityArray = [UserIDentity]()
override func viewDidLoad() {
super.viewDidLoad()
self.fetchIdentityHistoryArray()
}
func fetchIdentityHistoryArray(){
let fetchRequest: NSFetchRequest<UserIDentity> = UserIDentity.fetchRequest()
do {
let identityArray = try PersistanceService.context.fetch(fetchRequest)
if identityArray.isEmpty {
self.identityHistoryButton.isEnabled = false
}
else {
self.identityHistoryButton.isEnabled = true
}
}
catch {
print("Error fetching sworn statement history !")
}
}
}
So I have 2 questions :
What's wrong with my code ?
How can I manage that when the user clicks on the "back button" for the first form filled ever, the "history button" can refresh itself and turn from disabled to enabled button ?
Any help is much appreciated. Thank you
You are initialising a core data object directly in your code
var userIdentity: UserIDentity = UserIDentity(context: PersistanceService.context)
This new object will exist in Core Data and will be included everytime you execute the fetch request. You must understand that Core Data is not a database layer it is an object mapping layer so even if you haven't called save() yet the object exists in the Core Data context.
Either change the declaration to
var userIdentity: UserIDentity?
or remove it completely if it isn't used.

Create a favorite button that connects to a favorite tableview in swift

I was wondering how to create a favorite button in swift that when that button is pressed the object that is "favorited" becomes part of a tableview and stays that way. I hear that core data is a way to do this, but I am still struggling to have the button change when pressed.
When I was creating a button all I get is a button that only changes the first time it presses. An example is I have an empty star button, which means that it is not a favorite, and when i pressed it it changes to a filled heart, which means that it is favorited. When I press the button a second time, when it is currently a filled star, it doesn't change and still remains a filled star.
The other problem that I am having is sending the information from the object to a favorite tableview. I am not sure how to keep something in a tableview as long as the favorite button is switched to filled star.
What I am seeing a lot of is this topic but it is all about making a favorite button in table views. I am not looking for tableviews. An example of what I am trying to do is like a favorite book app. The app opens as a tableview full of books, and when a cell is pressed it opens a new view controller with that book's information, and at the top is a favorite button that is normally an empty star. If I like that book I would like to press that button and the empty star becomes a filled star button, indicating that I like it. The book's information is then sent to a new table view that holds all of the liked books i have done before. If I no longer like that book i would like to press the filled star button again and it is removed from the favorite tableview list.
I am some experience with mysql in regards to swift, and I know that someone people have made comments about using some kind of persistence to save the state of the button.
My biggest problem is that I don't even know where to start. Every attempt I make seems to end the same way, so i don't really have any source code for others to see. I have looked online and through github but the closest thing I could find was a cocoapod named DoButton, but it hasn't been updated in a long time and I am worried that it won't last with another swift update. If someone could lead me in the right path or know a good tutorial, I would greatly appreciate it. If there are any questions I can answer i will answer them to the best of my ability.
Another Update: I managed to get the button to work. It connects to core data and saves the state of the button even when it is quit. Now all that is left is creating a favorite tableView to store the favorites.
Here is the code:
class ViewController: UIViewController {
var buttonIsSelected = false
var favorites = [Favorite]()
#IBOutlet var onOffButton: UIButton!
let image1 = UIImage(named: "empty") as UIImage?
let image2 = UIImage(named: "filled") as UIImage?
override func viewDidLoad() {
super.viewDidLoad()
print("buttonIsSelected: \(buttonIsSelected)")
let fetchedRequest: NSFetchRequest<Favorite> = Favorite.fetchRequest()
do {
let favorite = try PersistenceService.context.fetch(fetchedRequest)
for fav in favorite {
resetAllRecord(entity: fav.favorite)
buttonIsSelected = fav.favorite
print("fav.favorite: \(fav.favorite)")
print("button: \(buttonIsSelected)")
if fav.favorite == true {
onOffButton.setImage(image2, for: .normal)
}
}
} catch {
}
}
//button Action
#IBAction func onOffButtonPressed(_ sender: Any) {
buttonIsSelected = !buttonIsSelected
if buttonIsSelected == true {
onOffButton.setImage(image2, for: .normal)
} else if buttonIsSelected == false {
onOffButton.setImage(image1, for: .normal)
}
saveBool(bool: buttonIsSelected)
}
//save to core data
func saveBool(bool: Bool) {
if bool == true {
print("favorite")
print("buttonIsSelected \(buttonIsSelected)")
let liked = Favorite(context: PersistenceService.context)
liked.favorite = bool
PersistenceService.saveContext()
favorites.append(liked)
} else if bool == false {
print("unfavorite")
print("buttonIsSelected \(buttonIsSelected)")
let liked = Favorite(context: PersistenceService.context)
liked.favorite = bool
PersistenceService.saveContext()
favorites.append(liked)
}
}
//clears core data so it doens't get full
func resetAllRecord(entity: Bool) {
let context = PersistenceService.persistentContainer.viewContext
let deleteFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "Favorite")
let deleteRequest = NSBatchDeleteRequest(fetchRequest: deleteFetch)
do
{
try context.execute(deleteRequest)
try context.save()
}
catch
{
print ("There was an error")
}
}
}
In the object class create a bool parameter called isFavorite and default set it to false. When the favorite button is pressed the bool should change from false to true. When you have to access only the "favorite" objects, with a for loop iterate through your array of objects and through an if statement check if the isFavorite parameter is true. If it is, the element must be appended to a new object array maybe called favoriteObjects, and there you have all your favorite objects. The rest is pretty straightforward.

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?

Showing user location when button on custom UITabBarController is pressed

I have a custom UITabBar controller, where I have buttons to switch between the view controllers. Here is an Image:
http://i.stack.imgur.com/UInLI.png
What I want to do it that when the main button is pressed the view will change to the mapViewController, and when it pressed again (on the same mapViewController) it will show to user Location.
I am using mapBox as my Map API, I have a function in my mapViewController,
findUserLocation()
and this show the user location by using:
mapView.userTrackingMode = MGLUserTrackingMode.Follow
So here is My code:
class CustomTabBarViewController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
self.tabBar.hidden = true
let tabBarView = CustomTabBarView()
tabBarView.viewdidload()
self.view.addSubview(tabBarView)
tabBarView.mapButton.addTarget(self, action: #selector(self.changeToMyMap), forControlEvents: UIControlEvents.TouchUpInside)
// Do any additional setup after loading the view.
}
func changeToMyMap(){
if (self.selectedIndex != 0){
self.selectedIndex = 0
}else{
let mapViewController = myMap() // the class Name
mapViewController.mapView.userTrackingMode = MGLUserTrackingMode.Follow
}
}
}
So when I pressed the button to show the user location I get an error. I think is because the Map is not loaded into the customTabViewController, but I have no Idea how to make it work.
when I try to call a function
from my customTabBarController, my application get a fatal error.
fatal error: unexpectedly found nil while unwrapping an Optional value
If you haven't used them already, these may be some helpful mapBox resources.
I'm not sure why it would be crashing with what you have there.
I would try putting some breakpoints in to find out where it's actually crashing.
Try putting one on the line if (self.selectedIndex != 0){
to see if it's actually going inside that method.
If it is, try typing po yourPropertyName i.e. po self.selectedIndex in the debug console to try and find out what property is returning nil.
Also, if you know what properties are optional, check where you're accessing them. That's where they could be nil.
In our Swift application, we made use of showsUserLocation to toggle the user location.
This would be worth trying, if you are simply trying to show the User Location.
The MGLUserTrackingMode in Mapbox iOS SDK 3.2.3 is for how to track the user location on the map (e.g., the experience when following a driving route).
How you could use showsUserLocation
func changeToMyMap(){
// not sure if the 'if/else' logic fit with what you intended, but
// left your code intact to better highlight how to use mapView.showsUserLocation
if (self.selectedIndex != 0){
self.selectedIndex = 0
mapViewController.mapView.showsUserLocation = false
}else{
let mapViewController = myMap()
// causes the map view to use the Core Location framework to find the current location
mapViewController.mapView.showsUserLocation = true
}
}
}
Be aware of this note in the docs for Mapbox iOS SDK 3.2.3. You will need to pay attention to set new keys in the .plist.
On iOS 8 and above, your app must specify a value for
NSLocationWhenInUseUsageDescription or NSLocationAlwaysUsageDescription in
its Info.plist to satisfy the requirements of the underlying Core Location
framework when enabling this property.
For comparison of how we use showsUserLocation in Mapbox (what produces the experience in the above GIF) —
#IBAction func handleSettings(sender: AnyObject) {
var trackingLocation = UIImage(named: "TrackingLocationOffMask.png")
switch (self.mapView.userLocationVisible) {
case true:
self.mapView.showsUserLocation = false
trackingLocation = UIImage(named: "TrackingLocationOffMask.png")
break
case false:
self.mapView.showsUserLocation = true
trackingLocation = UIImage(named: "TrackingLocationMask.png")
break
}
self.navigationItem.leftBarButtonItem?.image = trackingLocation
}

How to make an view controller, which displays only when app opens first time?

I use Xcode 6.4
I want to make an viewController which displays a settings and which opens only when the app launch at first time, or when a setting contains nil. What can i do, to do such thing?
Just set a flag to NSUserDefaults...
if (!NSUserDefaults.standardUserDefaults().boolForKey("firstTimeKey")) {
NSUserDefaults.standardUserDefaults().setBool(true, forKey: "firstTimeKey")
NSUserDefaults.standardUserDefaults().synchronize()
showViewController()
}
showViewController() is where you call your ViewController that should be showed once...
I typically solve this problem by creating a CoreData Entity called AppConfig and I give it any important properties to do with Application state, time open, user settings ect...
Upon App Launch if the CoreData entity does not exist, than I create it, and know that its the first launch and that I should display the "FirstLaunch" or "Help" ViewController.
Here is an example of what I'd do:
override func viewDidLoad() {
super.viewDidLoad()
var fetch = NSFetchRequest(entityName: "AppConfig")
var appConfigs = managedObjectContext.executeFetchRequest(fetch, error: error) as! [AppConfig]
if let appConfig = appConfigs.first{
//App has already launched before
}else{
//Create a new AppConfig Object and save it in CoreData
//Present your FirstLaunch Controller
}
return nil
}
In the above code I'm assuming you know the basic concepts of CoreData, and presenting ViewControllers. Both are relatively basic concepts so I'll omit them for now, but if someone does need help with them I can provide more info.

Resources