MacOS: reading shared preferences in Swift and ObjectiveC - swift

I have a project that mixes Swift and objective c. The controller for my preference panes is written in Swift. I can’t seem to read those preferences in Objective C.
This is the code that writes the parameter into the preferences…
let userDefaults = Preferences.shared.getUserDefaults()
#IBAction func DefType(_ sender: NSPopUpButton) {
var parameter:NSNumber
parameter = DefType.indexOfSelectedItem + 1 as NSNumber
userDefaults.set(parameter, forKey: ConstantUtility.StorageKeys.SelectedType.rawValue)
}
func getUserDefaults() -> UserDefaults {
return UserDefaults.standard
}
The above code is working as the following code is in the function that sets up the preference pane And it initiates the popup correctly.
func setupUI() {
var temp1:Int
temp1 = userDefaults.parameter(forKey: ConstantUtility.StorageKeys.SelectedType.rawValue) ?? 0
temp1 -= 1
DefType.selectItem(at: temp1)
}
On the other side of the program is the following code intended to read the preferences. This code always produces an answer of zero.
- (void) tableViewDoubleTapAction {
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
long selectedType = [userDefaults integerForKey:#"SelectedType"];
I'm assuming the problem is in the objectiveC code but I'm open to changing either or both. Been spinning my wheels too long on this one.
Thanks in advance!

I set up a Preferences class as follows...
class Preferences : NSObject {
private override init() {}
#objc static let shared = Preferences()
#objc var selectedType: ConstantUtility.SelectedType {
get {
let type = UserDefaults.standard.integer(forKey: ConstantUtility.StorageKeys.SelectedType.rawValue)
guard type != 0, let selectedType = ConstantUtility.SelectedType(rawValue: type) else {
return ConstantUtility.SelectedType.One
}
return type
}
set {
UserDefaults.standard.set(newValue.rawValue, forKey: ConstantUtility.StorageKeys.SelectedType.rawValue)
}
}
the swift code to read the preference was changed to
temp1 = userDefaults.integer(forKey: ConstantUtility.StorageKeys.SelectedChart.rawValue)
And the Objective C code became
- (void) tableViewDoubleTapAction {
long selectedChartType = Preferences.shared.selectedType;
It is all working!

Related

How to stop variable from going back to default value when read in different file

I have a DiscoveredSerialNumbers class that I want to access from various swift files:
class DiscoveredSerialNumbers {
var snConnect: String = ""
}
In my ViewController I change the value of snConnect based on the selection from a Picker View.
class ViewController: UIViewController, UIPickerViewDataSource,UIPickerViewDelegate {
#IBOutlet weak var SerialNumbers: UIPickerView!
var serialNums: [String] = [String]()
...
override func viewDidLoad() {
...
SerialNumbers.dataSource = self
SerialNumbers.delegate = self
}
...
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
let global = DiscoveredSerialNumbers()
global.snConnect = serialNums[row]
print(serialNums[row])
print(global.snConnect)
}
}
When I print out the new value of snConnect set in the following line:
global.snConnect = serialNums[row]
Immediately afterward I get the new updated value of snConnect.
However, when I try to access the updated value of snConnect in a different swift file that controls a different ViewController in the following code:
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
let global = DiscoveredSerialNumbers()
var sn = global.snConnect
...
}
The value of snConnect reverts back to the original value which is "".
How do I stop the value from reverting back to the initial value? I think it has something to do with me initializing the class DiscoveredSerialNumbers but I do not know how to access the value of snConnect in a different swift file otherwise.
Edit: Thanks to Don's comments, I am trying to have the snConnect value persist between instances of the application launching. I want to set the value of snConnect in the main app and access it when I launch an extension to the main app, in this case a custom keyboard extension.
Update: Question was a bit misleading you actually need to save the variable. I'm not sure if UserDefaults for app and keyboard extension are the same, you can try this.
class DiscoveredSerialNumbers {
static var main = DiscoveredSerialNumbers()
var snConnect: String {
get {
// Read from UserDefaults
return UserDefaults.standard.string(forKey: "snConnect") ?? ""
}
set {
// Save to UserDefaults
UserDefaults.standard.set(newValue, forKey: "snConnect")
}
}
init() {
print("New instance of DiscoveredSerialNumbers initialized.")
}
}
You can do this with a number of different ways,
however easiest one is creating a singleton of DiscoveredSerialNumbers() object, so your object and values can be used globally through it.
(although this method should be used with caution, it can cause a number of problems)
class DiscoveredSerialNumbers {
static var main = DiscoveredSerialNumbers()
var snConnect: String = ""
init() {
print("New instance of DiscoveredSerialNumbers initialized.")
}
}
now whenever you call DiscoveredSerialNumbers.main.snConnect old value will be kept and can be used/changed from anywhere.
Edit: Here's a sample Playground code for you to test out how singletons work
class Singleton
{
var someVariable = ""
static var main = Singleton()
}
class ClassA
{
init() {
Singleton.main.someVariable = "Hey I was changed in Class A"
}
}
class ClassB
{
init() {
print(Singleton.main.someVariable)
Singleton.main.someVariable = "And now I'm changed in class B"
}
}
let _ = ClassA()
let _ = ClassB()
print(Singleton.main.someVariable)
For an app extension to access data stored through it's container app, both the application and extension need to be part of the same app group. App groups are set in Signing & Capabilities section of Xcode for your project.
Once your app and extension are part of the same app group, you can use the following code to set the value of a global variable:
let defaults = UserDefaults(suiteName:"group.dataShare")
defaults?.set(serialNums[row], forKey: "snConnect")
Where group.dataShare is the name of your App group.
To retrieve the value, you can use the following code in your extension:
let defaults = UserDefaults(suiteName:"group.dataShareImada")
var sn = defaults?.string(forKey: "snConnect")

trouble acsessing values from class

i am learning swift atm and the use of classes kept me busy the last two weeks.
thank you for any help
for a project in xcode i created a swift file containing a class that initializes empty strings/arrays of strings.
then a function in that class is retrieving data from google firebase to fill those strings.
than in a viewcontroller class is want to retrieve those two strings. how do it do it right?
so far i tried many different ideas but i either get empty strings (as initialized in the beginning of the class) or i get errors
ideally everytime the viewdidload() (in this case) of the viewcontroller class is called i want it to create an instance of the class with uptodate data.
class RetrieveDatabase {
//static let sharedInstance = RetrieveDatabase()
var allMessages = [[String]]()
var messages = [String]()
var categorieNames = [String]()
func loadGoogleValue() -> (allMessages: [[String]], categorieNames: [String]) {
//this function works, the arrays contain type [string] and string
return (self.allMessages, self.categorieNames)
}
/*
-> i tried initializers in so many variations...
init(allMessages: [[String]], categorieNames: [String]) {
self.allMessages = loadGoogleValue().allMessages
self.categorieNames = loadGoogleValue().categorieNames
messages = []
}
*/
}
here the code from the viewcontroller class:
class SettingsView: UIViewController {
let retrieveDatabase = RetrieveDatabase.//tried everything here, only errors
override func viewDidLoad() {
super.viewDidLoad()
label1.text = retrieveDatabase.categorieNames[0]
}
}
I've simplified your code to something that you can easily try out in a playground. If you start with a working version, perhaps you can see the difference between it and the one that's giving you trouble.
class RetrieveDatabase {
var allMessages = [[String]]()
var messages = [String]()
var categorieNames = [String]()
init() {
messages.append("Hi")
messages.append("Hello")
allMessages.append(messages)
categorieNames.append("SALUTATIONS")
}
func loadGoogleValue() -> (allMessages: [[String]], categorieNames: [String]) {
return (self.allMessages, self.categorieNames)
}
}
class SettingsView {
let retrieveDatabase = RetrieveDatabase()
func caller() {
print(retrieveDatabase.loadGoogleValue().allMessages)
print(retrieveDatabase.loadGoogleValue().categorieNames)
}
}
let test = SettingsView().caller()
Since there's very little difference between what I've done and the code you added in your comment above, I suspect it's something you've left out concerning how strings are added to the arrays.

Unsafe mutable addressor crash

I have a struct and sometimes, for some users, there will be a crash when trying to access a variable of that type.
struct AppSettings {
var mute:Bool {
didSet {
if mute != oldValue {
let savedSettings = NSUserDefaults.standardUserDefaults()
savedSettings.setBool(mute, forKey: KEY_SETTING_MUTE)
}
}
}
init() {
let savedSettings = NSUserDefaults.standardUserDefaults()
if let savedMute = savedSettings.objectForKey(KEY_SETTING_MUTE) as? Bool {
mute = savedMute
} else {
mute = false
}
}
}
var appSettings = AppSettings()
And someplace during startup of the app it sometimes crashes
if appSettings.mute { // This will sometimes cause a crash
} // in AppDelegate or the methods it calls
This is only for some users and I cannot seem to reproduce it. Not being to reproduce it is the worst because it leaves me with nothing to work with.
Searching for unsafe mutable addressor as an error doesn't help because there are almost no results.
I think you solution is over engineered and difficult to understand or debug. I've been using a similar solution in my apps for a few years now to manage session state and wish to share with you. It's something you could lift straight from here and implement and be trouble free.
I work on a Session principle meaning the values which I wish to store and read go through a solid Session class and leave no room for error.
Here is how it's used from anywhere in the application.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// how to set the value to be stored in userdefaults
Session.HasMuted = true
// two example of how to read the value
if Session.HasMuted == true {
print("has muted")
}
if Session.HasMuted == false {
print("has not muted")
}
}
Here is the Session class
class Session {
class var HasMuted: Bool {
get {
guard let HasMuted = UserDefaults.standard.object(forKey: SessionSpace.HasMuted) as? Bool else {
return false
}
return HasMuted
}
set {
UserDefaults.standard.set(newValue, forKey: SessionSpace.HasMuted)
}
}
}
and the accompanying Session Space struct
struct SessionSpace {
static let HasMuted = "HasMuted"
}
I would consider adjusting this to suit your style, I'm not a big fan of capital letters for key strings etc, this is a more elegant readable and implement and forget solution. You can extend this by adding a setter / getter block and key string in the session space block and again use from anywhere in the app in seconds knowing it's trouble free and robust. Let me know if you need any more help. Hope you adopt it and save it to snippets.
It is better to
use
init() {
let savedSettings = UserDefaults.standard
let savedMute = savedSettings.bool(forKey: KEY_SETTING_MUTE)
instead of
init() {
let savedSettings = NSUserDefaults.standardUserDefaults()
if let savedMute = savedSettings.objectForKey(KEY_SETTING_MUTE) as? Bool {

Call a func from another class in swift

I would like to call a function which is coded on another class.
So far I have made a struct on the file structs.swift for my data:
struct defValues {
let defCityName: String
let loadImages: Bool
init(defCity: String, loadImgs: Bool){
self.defCityName = defCity
self.loadImages = loadImgs
}
}
I have made the file Defaults.swift containing:
import Foundation
class DefaultsSet {
let cityKey: String = "default_city"
let loadKey: String = "load_imgs"
func read() -> defValues {
let defaults = NSUserDefaults.standardUserDefaults()
if let name = defaults.stringForKey(cityKey){
print(name)
let valuesToReturn = defValues(defCity: name, loadImgs: true)
return valuesToReturn
}
else {
let valuesToReturn = defValues(defCity: "No default city set", loadImgs: true)
return valuesToReturn
}
}
func write(city: String, load: Bool){
let def = NSUserDefaults.standardUserDefaults()
def.setObject(city, forKey: cityKey)
def.setBool(load, forKey: loadKey)
}
}
in which I have the two functions read, write to read and write data with NSUsersDefault respectively.
On my main ViewController I can read data with:
let loadeddata: defValues = DefaultsSet().read()
if loadeddata.defCityName == "No default city set" {
defaultCity = "London"
}
else {
defaultCity = loadeddata.defCityName
defaultLoad = loadeddata.loadImages
}
But when I try to write data it gives me error. I use this code:
#IBOutlet var settingsTable: UITableView!
#IBOutlet var defaultCityName: UITextField!
#IBOutlet var loadImgs: UISwitch!
var switchState: Bool = true
#IBAction func switchChanged(sender: UISwitch) {
if sender.on{
switchState = true
print(switchState)
}else {
switchState = false
print(switchState)
}
}
#IBAction func saveSettings(sender: UIButton) {
DefaultsSet.write(defaultCityName.text, switchState)
}
You need an instance of the DefaultsSet class
In the view controller add this line on the class level
var setOfDefaults = DefaultsSet()
Then read
let loadeddata = setOfDefaults.read()
and write
setOfDefaults.write(defaultCityName.text, switchState)
The variable name setOfDefaults is on purpose to see the difference.
Or make the functions class functions and the variables static variables and call the functions on the class (without parentheses)
From the code you posted, it seems you either need to make the write method a class method (just prefix it with class) or you need to call it on an instance of DefaultsSet: DefaultsSet().write(defaultCityName.text, switchState).
Another issue I found is that you also need to unwrapp the value of the textField. Your write method takes as parameters a String and a Bool, but the value of defaultCityName.text is an optional, so String?. This results in a compiler error.
You can try something like this:
#IBAction func saveSettings(sender: UIButton) {
guard let text = defaultCityName.text else {
// the text is empty - nothing to save
return
}
DefaultsSet.write(text, switchState)
}
This code should now compile and let you call your method.
Let me know if it helped you solve the problem

mutableArrayValueForKey: countOf<Key> not being called, countOfSongs

My exact question is here:
http://www.cocoabuilder.com/archive/cocoa/236041-kvc-array-proxy-objects.html#236058
I'm trying to understand how the proxy object which is returned from
mutableArrayValueForKey: works and I've hit a bit of a wall. I get
what the proxy is and why it exists. I have a test app which allows
me to do things to/with the collection it represents and it all works
fine. The problem is that when I try to implement some of the methods
mentioned in the developer docs under the "Key-Value Coding Accessor
Methods" section. There are some methods there which if implemented
in the hosting object (i.e. the original recipient of the
mutableArrayValueForKey: call) are to be called by the proxy when the
proxy is asked to do various things. The hitch is that in my test app
I can't get the -countOf<key> or -objectIn<key>AtIndex methods to be
invoked.
In reading through the docs, it seems that a number of methods need
to be implemented before any of these (dare I call them 'proxy
methods'?) are called. I implemented a whole slew of them - including
a number that shouldn't need to be - and ended up with this set: (The
test app is based on a Playlist->Songs->Song model where "songs" is
the NSMutableArray which lives in the Playlist class in which I'm
interested in getting a count of its member songs.)
- (unsigned int)countOfSongs;
- (Song *)objectInSongsAtIndex:(unsigned int)index;
- (NSArray *)songsAtIndexes:(NSIndexSet *)indexes;
- (void)getSongs:(Song **)buffer range:(NSRange)inRange;
- (void)insertObject:(Song *)newSong inSongsAtIndex:(unsigned int)idx;
- (void)removeObjectFromSongsAtIndex:(unsigned int)idx;
Even simple test code like this fails:
Playlist *myPlaylist = [[[Playlist alloc] init] autorelease];
id arrayProxy = [myPlaylist mutableArrayValueForKey:#"songs"];
[arrayProxy insertObject:[[[Song alloc] initWithName:#"test" andLength:
10] autorelease] atIndex:0];
unsigned int theCount = [arrayProxy count];
I've got all the properties defined, all the methods written, etc.
Yet when I call [arrayProxy count] my countOfSongs method in the
Playlist class isn't touched. The right answer is returned, but it's
apparently coming from the runtime going to the array directly and
getting the answer via NSArray's count method.
Oddly enough, when I do the insertObject call in line #3 the
insertObject:inSongsAtIndex: method IS called... so some of this stuff
works as I believe it is supposed to. Unfortunately it's the other
stuff that's driving me nuts. I've been working on this one for a
couple of days now and have tried everything I could come up with -
including some really silly, paranoid stuff - and have made no progress.
Can anybody help me with a suggestion as to what I might be doing
wrong or what I'm missing?
Thanks!
And the answer there--although it seemed to help the op--does nothing to shed any light on the problem for me.
Here is my playground code:
import Cocoa
"hello"
class Song {
dynamic var title = "Hello"
}
"hello"
class PlayList: NSObject {
/*dynamic*/ var songs = NSMutableArray()
//private var theSongs = NSMutableArray()
//var countOfSongs: Int = 100
func countOfSongs() -> Int {
println("count of songs")
return 100
//return theSongs.count + 100
}
func objectInSongsAtIndex(i: Int) -> AnyObject? {
println("getter")
return songs[i]
//return theSongs[i]
}
func insertObject(song:AnyObject, inSongsAtIndex index:Int) {
println("insert")
songs[index] = song as! Song
//theSongs[index] = song as! Song
}
func removeObjectFromSongsAtIndex(index:Int) {
println("remove")
songs.removeObjectAtIndex(index)
//theSongs.removeObjectAtIndex(index)
}
}
"hello"
var playlist = PlayList()
"hello"
let arrayProxy = playlist.mutableArrayValueForKey("songs")
"hello"
arrayProxy.addObject(Song()) //successfully calls proxy method => outputs "insert"
arrayProxy.removeObjectAtIndex(0) //successfully calls proxy method => outputs "remove"
arrayProxy.count //=> 0 ???
What changes do I need to make to my code so that the countOfSongs property or method is called when I write:
arrayProxy.count
From the referenced article
http://www.cocoabuilder.com/archive/cocoa/236041-kvc-array-proxy-objects.html#236058:
The documentation about accessor search order shows that it will
prefer a method named -<key> over the corresponding -countOf<Key> and
- objectIn<Key>AtIndex: methods.
which means that
let arrayProxy = playlist.mutableArrayValueForKey("songs")
let cnt = arrayProxy.count
accesses the "songs" property of Playlist directly, if there is such
a property.
If you rename the property then it works as you expected:
class Song {
dynamic var title = "Hello"
}
class PlayList: NSObject {
var thesongs = NSMutableArray()
func countOfSongs() -> Int {
println("count of songs")
return 100
}
func objectInSongsAtIndex(i: Int) -> AnyObject? {
println("getter")
return thesongs[i]
}
func insertObject(song:AnyObject, inSongsAtIndex index:Int) {
println("insert")
thesongs[index] = song as! Song
}
func removeObjectFromSongsAtIndex(index:Int) {
println("remove")
thesongs.removeObjectAtIndex(index)
}
}
var playlist = PlayList()
let arrayProxy = playlist.mutableArrayValueForKey("songs")
let cnt = arrayProxy.count
println(cnt)
Output:
count of songs
100
For future searchers, here is a full example exercising all the methods in an OSX>Application>Command Line Tool (I checked Swift for the language):
import Foundation
class Song {
var title = "Hello"
}
class PlayList: NSObject {
private var array: [Song] = []
func countOfSongs() -> Int {
println("countOfSongs() called")
return array.count
}
/*
//This also works:
var countOfSongs: Int {
println("countOfSongs property was accessed")
return array.count
}
*/
func objectInSongsAtIndex(index: Int) -> AnyObject? {
println("getter called")
return array[index]
}
func insertObject(song:AnyObject, inSongsAtIndex index:Int) {
println("inserting at index \(index)")
array.insert(song as! Song, atIndex: index)
}
func removeObjectFromSongsAtIndex(index:Int) {
println("removing at index \(index)")
array.removeAtIndex(index)
}
}
var playlist = PlayList()
let arrayProxy = playlist.mutableArrayValueForKey("songs")
arrayProxy.addObject(Song())
println(arrayProxy.count)
arrayProxy.objectAtIndex(0)
arrayProxy.removeObjectAtIndex(0)
println(arrayProxy.count)
var playlist = PlayList()
let arrayProxy = playlist.mutableArrayValueForKey("songs")
arrayProxy.addObject(Song())
println(arrayProxy.count)
arrayProxy.removeObjectAtIndex(0)
println(arrayProxy.count)
--output:--
countOfSongs() called
inserting at index 0
countOfSongs() called
1
getter called
removing at index 0
countOfSongs() called
0
No more playgrounds for me!