Is NSUserDefaults for Today Extensions broken in iOS 8 beta 5? - ios

My Today Extensions works perfectly on beta 4, and I have implement a simple cache when Today Extensions first loaded.
let defaults = NSUserDefaults(suiteName: "group.ReadWidget") // app group
var feed = [String: String]()
override func viewDidLoad() {
getCache()
// The feed is empty
}
func parseRSS() {
...
// Parse in background
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
self.parser = NSXMLParser(contentsOfURL:NSURL(string:url))
self.parser.delegate = self
self.parser.parse() // populate the feed
self.saveCache() // Save the feed
...
})
})
}
func saveCache() {
defaults.setObject(feed, forKey:"feed")
defaults.synchronize()
}
func getCache() {
if defaults.objectForKey("feed") {
feed = defaults.dictionaryForKey("feed") as [String : String]
}
else {
feed = [:]
}
}
In beta 5, after saveCache(), and when getCache() back in Today Extension reloaded, I always got back an empty dictionary.
I am using NSUserDefaults(suiteName:...) to store cache. I have also tried NSUserDefaults.standardUserDefaults() and the result is the same.
btw, which NSUserDefaults is the preferred method for loading and saving cache? The cache is not shared with the main app.

This works:
var defaults = NSUserDefaults.standardUserDefaults()
var feed = [String:String]()
func getCache() {
if defaults.objectForKey("feed") {
feed = defaults.dictionaryForKey("feed") as [String : String]
}
else {
feed = [:]
}
println("feed: \(feed)")
}

Related

How to save data from two UITextViews to UserDefaults

For each UITextView using UserDefaults, I've made a function to save and a function to display.
Whatever text is added needs to be displayed at the time of adding, saved and then displayed again when opening the app again.
If I install the app with ONLY the function to save, quit the app and add the function to display then reinstall without deleting the installed app everything works perfectly.
If I install the app with both functions added it doesn't work.
There has to be a simple solution for this, I'm obviously doing something wrong.
The data from one textView is used to calculate results and then to display them on the other textView.
All data is added with other functions, none by the user.
numberHistoryView.isEditable = false
numberHistoryView.isSelectable = false
func saveHistoryTextView()
{
let defaults = UserDefaults.standard
let numberHistory = numberHistoryView.text
defaults.set(numberHistory, forKey: "combos")
}
func displaySavedHistory()
{
let defaults = UserDefaults.standard
let savedCombos = defaults.object(forKey: "combos") as? String ?? ""
numberHistoryView.text = savedCombos
}
func saveFrequencyTextView()
{
let defaults = UserDefaults.standard
let numberFrequency = numberFrequencyCount.text
defaults.set(numberFrequency, forKey: "frequency")
}
func displaySavedFrequency()
{
let defaults = UserDefaults.standard
let savedFrequency = defaults.object(forKey: "frequency") as? String ?? ""
numberFrequencyCount.text = savedFrequency
}
override func viewWillDisappear(_ animated: Bool)
{
saveHistoryTextView()
saveFrequencyTextView()
}
override func viewWillAppear(_ animated: Bool)
{
displaySavedHistory()
displaySavedFrequency()
}
This depends on the order and timing in which you are calling save and display methods.
When you're installing a fresh app, there will be no data in saved in UserDefaults. So when you call displaySavedHistory() and displaySavedFrequency() methods in viewWillAppear(_:), nothing will be fetched because nothing is saved yet.
Now, when you save the data using saveHistoryTextView() and saveFrequencyTextView() methods in viewWillDisappear(_:) and then you kill and run the app again, the saved data will be fetched and displayed.
Also, since you're saving the data in UserDefaults, and UserDefaults are saved within the sandbox, so the data won't persist when you delete the app. You've to save the data in iCloud or keychain etc. if you want to persist the data even after app deletion.
Once I put my brain into a theta state with the right frequency I managed to figure it out.
Thanks to #Naresh and all other contributors for trying to help as you may have assisted me a little.
The solution basically just required a simple if statement.
Everything now works perfectly.
func saveHistoryTextView()
{
if numberHistoryView.text?.count != nil
{
let defaults = UserDefaults.standard
defaults.set(numberHistoryView.text!, forKey: "combos")
}
}
func displaySavedHistory()
{
let defaults = UserDefaults.standard
if let savedCombos = defaults.string(forKey: "combos")
{
numberHistoryView.text = savedCombos
}
}
func saveFrequencyTextView()
{
if numberFrequencyCount.text?.count != nil
{
let defaults = UserDefaults.standard
defaults.set(numberFrequencyCount.text!, forKey: "frequency")
}
}
func displaySavedFrequency()
{
let defaults = UserDefaults.standard
if let savedFrequency = defaults.string(forKey: "frequency")
{
numberFrequencyCount.text = savedFrequency
}
}
}
override func viewWillDisappear(_ animated: Bool)
{
saveHistoryTextView()
saveFrequencyTextView()
}
override func viewWillAppear(_ animated: Bool)
{
displaySavedHistory()
displaySavedFrequency()
}
You can do it with property observer as:
private let DATA_KEY = "Saved Data"
//After initialising the outlet we can set the data
#IBOutlet weak var textView: UITextView! {
didSet {
textView.text = self.data
}
}
private var data: String {
set {
//Save data in user defaults
UserDefaults.standard.set("The value you will assign", forKey: DATA_KEY)
}
get {
//get the data from user defaults.
return UserDefaults.standard.value(forKey: DATA_KEY) as? String ?? ""
}
}
//UITextViewDelegate: set the text data on end of UITextView editing
func textViewDidEndEditing(_ textView: UITextView) {
self.data = textView.text
}

Removing Custom Objects from List in UserDefaults

I am trying to remove an element from a list which is stored in NSUserDefaults. The getAll function is implemented below:
func getAllOrders() -> [Order] {
var orders = [Order]()
if let userDefaults = UserDefaults(suiteName: "group.com.johndoe.SoupChef.Shared") {
if let ordersData = userDefaults.data(forKey: "Orders") {
orders = try! JSONDecoder().decode([Order].self, from: ordersData)
}
}
return orders
}
And here is the code for deleting the order.
func delete(order :Order) {
var persistedOrders = getAllOrders()
persistedOrders.removeAll { persistedOrder in
persistedOrder.identifier.uuidString == order.identifier.uuidString
}
}
After deleting the order in the code above when I call getAllOrders I still see all the elements, meaning I don't see the order being deleted.
That's because you don't save your changes. Once you've performed the removal you need to turn persistedOrders back into JSON and then:
userDefaults.set(json, forKey:"Orders")
You need to use jsonEncoder and encode the edited array then store it again the user defaults
func delete(order :Order) {
var persistedOrders = getAllOrders()
persistedOrders.removeAll { persistedOrder in
persistedOrder.identifier.uuidString == order.identifier.uuidString
}
do {
let data = try JSONEncoder().encode(persistedOrders)
userDefaults.set(data, forKey:"Orders")
}
catch {
print(error)
}
}
You have to store correctly your UserDefaults
UserDefaults.standard.set(json, forKey:"Orders")
Now, you can remove them using:
UserDefaults.standard.removeObject(forKey: "Orders")

How to send MSMessage in Messages Extension?

I want to implement an imessage app, however being new to the messages framework and iMessage apps being such a new thing there aren't many resources. So I am following the WWDC video and using Apples providing sample app for a guide.
I have three views, the MessageViewController which handles pretty much all the functionality and then a CreateViewController and a DetailsViewController.
I am simply trying to create an MSMessage from the CreateViewController and display in the DetailsViewController.. then add to the data.
However I get a crash when trying to create the data.
#IBAction func createAction(_ sender: AnyObject) {
//present full screen for create list
self.delegate?.createViewControllerDidSelectAdd(self as! CreateViewControllerDelegate)
}
The data type I am trying to pass is the dictionary from a struct:
struct data {
var title: String!
var date: Date!
var dictionary = ["title" : String(), "Array1" : [String](), "Array2" : [String]() ] as [String : Any]
}
So here's how things are set up;
MessagesViewController
class MessagesViewController: MSMessagesAppViewController, {
// MARK: Responsible for create list button
func composeMessage(for data: dataItem) {
let messageCaption = NSLocalizedString("Let's make", comment: "")
let dictionary = data.dictionary
func queryItems(dictionary: [String:String]) -> [URLQueryItem] {
return dictionary.map {
URLQueryItem(name: $0, value: $1)
}
}
var components = URLComponents()
components.queryItems = queryItems(dictionary: dictionary as! [String : String])
let layout = MSMessageTemplateLayout()
layout.image = UIImage(named: "messages-layout-1.png")!
layout.caption = messageCaption
let message = MSMessage()
message.url = components.url!
message.layout = layout
message.accessibilityLabel = messageCaption
guard let conversation = activeConversation else { fatalError("Expected Convo") }
conversation.insert(message) { error in
if let error = error {
print(error)
}
}
}
}
extension MessagesViewController: CreateViewControllerDelegate {
func createViewControllerDidSelectAdd(_ controller: CreateViewControllerDelegate) {
//CreatesNewDataItem
composeMessage(for: dataItem())
}
}
CreateViewController
/**
A delegate protocol for the `CreateViewController` class.
*/
protocol CreateViewControllerDelegate : class {
func createViewControllerDidSelectAdd(_ controller: CreateViewControllerDelegate)
}
class CreateViewController: UIViewController {
static let storyboardIdentifier = "CreateViewController"
weak var delegate: CreateViewControllerDelegate?
#IBAction func create(_ sender: AnyObject) {
//present full screen for create list
self.delegate?.createViewControllerDidSelectAdd(self as! CreateListViewControllerDelegate)
}
}
Would someone show where I am going wrong and how I can send a MSMessage? If I am able to send the message I should then be able to receive and resend.
One issue I see, without being able to debug this myself:
you are setting your components.queryItems to your dictionary var cast as [String:String], but the dictionary returned from data.dictionary is not a [String:String], but a [String:Any]
In particular, dictionary["Array1"] is an array of Strings, not a single string. Same for dictionary["Array2"]. URLQueryItem expects to be given two strings in its init(), but you're trying to put a string and an array of strings in (though I'm not sure that you're actually getting to that line in your queryItems(dictionary:) method.
Of course, your dataItem.dictionary is returning a dictionary with 4 empty values. I'm not sure that's what you want.

Refreshing data in Watch App from App Group cached on iPhone

To pass data from my IOS App to my Watch app, I am caching the data into NSUserDefaults (in an App Group) in my iOS AppDelegate. (I call this method both in didFinishLaunching and willTerminate.)
I then pass this data to my Watch app via the App Group and all works well.
The problem is that when I change the data on my iPhone app I want to update the data on my Watch App as well, but it is not updated unless I uninstall and reinstall the Watch App.
I have tried my fetch from App Groups method in both awakeWithContext and willActivate but the data is still stale. How can I refresh the data on the Watch short of uninstalling/reinstalling?
*edit: In other words, the Apple Watch app apparently keeps running on the watch... whereas I would prefer that it was killed, so that when the user changes data on the iPhone app, then taps on the watch app it opens with the new data, instead of just awaking with the stale data.
class InterfaceController: WKInterfaceController {
var nameList: [String]?
var currentUserName: String?
var peopleDict: [String : [String : [String : String]]]?
#IBOutlet weak var personTable: WKInterfaceTable!
private func loadTableData() {
if let list = nameList {
personTable.setNumberOfRows(list.count, withRowType: "PersonTableRowController")
for (index, personName) in enumerate(list) {
if let row = personTable.rowControllerAtIndex(index) as? PersonTableRowController {
row.personLabel.setText(personName)
} else { println("Could not cast as PersonTableRowController") }
}
}
}
func fetchFromAppGroup() {
let defaults = NSUserDefaults(suiteName: "group.com.myndarc.thredz2")
defaults?.synchronize()
if let fetchedPeopleDict: AnyObject = defaults?.valueForKey("peopleDict") {
//save peopleDict locally
peopleDict = fetchedPeopleDict as? [String : [String : [String : String]]]
if let dict = fetchedPeopleDict as? NSDictionary {
nameList = dict.allKeys as? [String]
}
}
if let user: AnyObject = defaults?.valueForKey("currentUser") {
currentUserName = user as? String
}
}
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
}
override func willActivate() {
super.willActivate()
fetchFromAppGroup()
loadTableData()
}

iOS Equivalent For Android Shared Preferences

I am porting an Android app to iOS, one thing I used was the Shared Preferences in Android to save each time a level was complete.
That way when the user gets back into the app, they can see they are up to level 3 or whatever.
Is there a similar mechanism in iOS? or do I have to manually write out to an application specific file?
If so, how do I write out to files only visible to my application?
Thanks.
Use NSUserDefaults: - note that this is for small bits of data, such as the current level like you mentioned. Don't abuse this and use it as a large database, because it is loaded into memory every time you open your app, whether you need something from it or not (other parts of your app will also use this).
Objective-C:
Reading:
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
NSString *currentLevelKey = #"currentlevel";
if ([preferences objectForKey:currentLevelKey] == nil)
{
// Doesn't exist.
}
else
{
// Get current level
const NSInteger currentLevel = [preferences integerForKey:currentLevelKey];
}
Writing:
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
NSString *currentLevelKey = #"currentlevel";
const NSInteger currentLevel = ...;
[preferences setInteger:currentLevel forKey:currentLevelKey];
// Save to disk
const BOOL didSave = [preferences synchronize];
if (!didSave)
{
// Couldn't save (I've never seen this happen in real world testing)
}
.
Swift:
Reading:
let preferences = NSUserDefaults.standardUserDefaults()
let currentLevelKey = "currentLevel"
if preferences.objectForKey(currentLevelKey) == nil {
// Doesn't exist
} else {
let currentLevel = preferences.integerForKey(currentLevelKey)
}
Writing:
let preferences = NSUserDefaults.standardUserDefaults()
let currentLevelKey = "currentLevel"
let currentLevel = ...
preferences.setInteger(currentLevel, forKey: currentLevelKey)
// Save to disk
let didSave = preferences.synchronize()
if !didSave {
// Couldn't save (I've never seen this happen in real world testing)
}
Here is an update for Swift 3
Reading
let preferences = UserDefaults.standard
let currentLevelKey = "currentLevel"
if preferences.object(forKey: currentLevelKey) == nil {
// Doesn't exist
} else {
let currentLevel = preferences.integer(forKey: currentLevelKey)
}
Writing
let preferences = UserDefaults.standard
let currentLevel = ...
let currentLevelKey = "currentLevel"
preferences.set(currentLevel, forKey: currentLevelKey)
Update
From UserDefaults documentation
synchronize() waits for any pending asynchronous updates to the defaults database and returns; this method is now unnecessary and shouldn't be used.
class Configuration {
static func value<T>(defaultValue: T, forKey key: String) -> T{
let preferences = UserDefaults.standard
return preferences.object(forKey: key) == nil ? defaultValue : preferences.object(forKey: key) as! T
}
static func value(value: Any, forKey key: String){
UserDefaults.standard.set(value, forKey: key)
}
}
Example
//set
Configuration.value(value: "my_value", forKey: "key_1")
//get
let myValue = Configuration.value(defaultValue: "default_value", forKey: "key_1")
As per the previous answer, you already know that UserDefaults is the equivalent to shared preferences in ios. You can create a common write function and for read create function based on data type. And call your required method from anywhere.
ViewController.swift
// write data
writeAnyData(key: "MY_KEY", value: "MyData")
// read string data
readStringData(key: "MY_KEY"), animated: true)
Utils.swift
// read and write user default
let userDefault = UserDefaults.standard
// write
func writeAnyData(key: String, value: Any){
userDefault.set(value, forKey: key)
userDefault.synchronize()
}
// read int values
func readIntData(key: String) -> Int{
if userDefault.object(forKey: key) == nil {
return 0
} else {
return userDefault.integer(forKey: key)
}
}
// read string values
func readStringData(key: String) -> String{
if userDefault.object(forKey: key) == nil {
return ""
} else {
return userDefault.string(forKey: key)!
}
}
// read bool value
func readBoolData(key: String) -> Bool{
if userDefault.object(forKey: key) == nil {
return false
} else {
return userDefault.bool(forKey: key)
}
}

Resources