swift Xcode reading application settings - ios

I am new to swift/ios programming. I created a small app with settings but I can't seem to read value.
UserDefaults.standard.register(defaults: [String : Any]())
let settings = UserDefaults.standard;
let k = Bundle.main.infoDictionary!;
let b = settings.dictionaryRepresentation()
let val3 = Bundle.main.object(forInfoDictionaryKey: "digit_preference")
let value2 = settings.string(forKey: "digit_preference")
I can't find my settings "digit_preference" in Bundle.main.InfoDictionary or UserDefaults.standard.dictionaryRepresentation()
I can view my application settings properly in my phone. (Digits set to 2)

You need to cast the file data to a dictionary at first since the root structure is a Dictionary. This of course, if the file you're showing us is actually a .plist file; the following is how to access a .plist file, but the first step is also common for almost every other file type in your app bundle. What differs (say from .txt, .json, etc) is the second step depending on what you want to do..
Like so:
guard let idForResource = Bundle.main.path(forResource: "pListFilename", ofType: ".plist"),
let dict = NSDictionary(contentsOfFile: idForResource) as? [String: Any] else {
return }
Then you access the variable through dict like you would from any other dictionary.

Related

how to unarchive plists in bundle using a path in Swift 5

Short version:
How to access an archived file in the bundle for unarchiving with unarchivedObject(ofClass:from:). The core issue is going from a path (string or URL) to Data which is required by the method.
Long version:
I have many plists containing SCNNode hierarchies stored as plists in bundle resources.
I was able to access them in Swift 3 with:
let fileName = MoleName.DNA_ideal_comps // retrieves String
let filePath = getFilePath(fileName)
guard let components = NSKeyedUnarchiver.unarchiveObject(withFile: filePath) as? [SCNNode] , components.count >= 4
else { // no value for this key, or not enough nodes
print("Couldn't find molecule (DNA_ideal_comps)")
return
} // [ atoms_A, bonds_A, atoms_B, bonds_B ]
scaleNode_2 = components[0] // DNA A
fixedNode_2 = components[1] // bonds
scaleNode_3 = components[2] // DNA B
fixedNode_3 = components[3] // bonds
// where filePath is:
func getFilePath(_ fileName: String) -> String {
if let path = Bundle.main.path(forResource: fileName, ofType: "plist") {
return path
}
return "path could not be found"
}
Here fileName is the name of the plist to retrieve, "DNA_ideal_comps" in this example. I created these independently of the active program due to the sheer volume of data; some of these plists contain over 30,000 items and there are about 90 total.
The above attempt to unarchive is deprecated and I've struggled to replace it. My best try so far:
guard let components = NSKeyedUnarchiver.unarchivedObject(ofClass: SCNNode.self, from: moleData), components.count >= 4
But this fails with Cannot convert value of type 'String' to expected argument type 'Data'. Unlike the method in Swift 3, there appears no way here to use the path of the object to retrieve it.
Is there a way to access these plists using this method? Is there another method more appropriate?
The core issue is going from a path (string or URL) to Data which is required by the method
Well, that's trivial enough; just call the Data initializer that reads from a file on disk. You would then decode the Data by calling NSKeyedUnarchiver.unarchivedObject(ofClass: SCNNode.self, from: theData).
https://developer.apple.com/documentation/foundation/data/3126626-init
https://developer.apple.com/documentation/foundation/nskeyedunarchiver/2983380-unarchivedobject
However, I'm confused as to why you're using NSKeyedUnarchiver to read a plist file. A plist file would be read using a PropertyListSerialization object.
https://developer.apple.com/documentation/foundation/propertylistserialization

Localizable strings from JSON runtime on iOS

I have user localizable strings for the strings used in storyboard or source files which are fixed. These are defined in files like Localizable.string (Spanish), Localizable.string (German) etc. But I have a requirement where these strings can keep changing. These strings are received as a response to REST API call. My question is how can I use it.
Current code is let text = NSLocalizedString("Some string", comment: "")
Where NSLocalizedString looks for Localizable.string file. How can I make NSLocalizedString look for localized words from my custom dictionary/Json?
Try this
First you need to copy that files in document directory .
Get Localised Label
let localisedString = self.getLocalizatioString(key: "your key", stringFileName: "test.strings") //
Function
func getLocalizatioString(key : String?, stringFileName : String ) -> String {
let doumentDirectoryPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString
let destinationPath = doumentDirectoryPath.appendingPathComponent(stringFileName)
let dict = NSDictionary.init(contentsOfFile: destinationPath)
return (dict?.object(forKey: key!) as? String)!
}
Output
In String file

How to read a multidimensional String Array from a plist in Swift?

I'm using the following code to save a 2D String array to a plist:
func saveFavourites(favouriteStops: [[String]]) {
let directories = NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true)
if let library = directories.first {
if let libraryUrl = URL(string: library) {
let favouritesUrl = libraryUrl.appendingPathComponent("favourites.plist")
// Write favourites to disk
let favsArray = favouriteStops as NSArray
print(favsArray)
favsArray.write(toFile: favouritesUrl.path, atomically: true)
}
}
}
The above snippet properly creates the .plist file (confirmed by looking at the simulator's filesystem in ~/Library/Developer/CoreServices). However, when I try reading it back to a NSArray with the following snippet, it results in nil:
let directories = NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true)
if let library = directories.first {
if let libraryUrl = URL(string: library) {
let favouritesUrl = libraryUrl.appendingPathComponent("favourites.plist")
// favsToLoad is nil
let favsToLoad = NSArray(contentsOf: favouritesUrl)
// Do stuff with favsToLoad, if it would load properly
}
}
You're doing two very basic things wrong.
First, never make a URL from a file path by saying URL(string); this is a file on disk, so you must use URL.fileURL.
Second, don't start with a file path at all! Obtain the directory as a URL right from the start.
(Also, though I do not know whether this is the source of the issue, do not read and write directly in the Library directory. Use the Documents directory, the Application Support directory, or similar.)
So, for example, I would write:
let fm = FileManager.default
let docsurl = try fm.url(for:.documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
let favouritesurl = docsurl.appendingPathComponent("favourites.plist")
I see your problem. You misspelled "favorites". :)
But seriously...
Plists can only contain a very small set of "property list objects": (dictionaries, arrays, strings, numbers (integer and float), dates, binary data, and Boolean values).
If your array's "object graph" (the objects the array contains and any container objects inside the array recursively contain) contain anything other than the above types, the save will fail.
I don't honestly know what gets saved when it fails. Have you tried opening the plist file in a text editor and looking at it?
My guess is that something other than a string has snuck into your array, it's not one of the above types, and THAT'S why it's failing.

What is causing items from UIPasteboard to be converted to NSConcreteMutableData?

I'm running the following in a playground (I've tested in Xcode 7.3.1 as well as Xcode 8.1 and see the same behavior):
import UIKit
let key: String = "some_key"
let value: String = "some_value"
UIPasteboard.general.items = [[key: value]]
let item = UIPasteboard.general.items.first
if let item = UIPasteboard.general.items.first {
switch item[key] {
case let x as String:
print("This is expected")
case let x as Any:
type(of: x)
print("This is unexpected")
default:
print("This is unexpected")
}
} else {
print("This is unexpected")
}
And I notice that the String that I put into the pasteboard actually gets bridged back out as NSConcreteMutableData.
My questions are the following:
Is this caused by an internal UIPasteboard implementation (i.e. explicitly converting from NSString to NSConcreteMutableData), or is this standard ObjC-Swift bridging behavior?
How can I work around this to store custom key/value pairs in UIPasteboard?
Here's what this looks like in a playground, for easy reference:
The Data you get for item[key] is simply the UTF-8 encoded value for the string.
If you add the following case, you will see that:
case let x as Data:
let str = String(data: x, encoding: .utf8)
print("str = \(str)")
The cause of the confusion is that the keys used by the items property are UTIs, not random keys. If you change your key to public.text then your code will work as expected.
Normally, you would not put a string on the pasteboard using the items property. You would use the string property to read and write the value.
UIPasteboard.general.string = "Hello"
let aStr = UIPasteboard.general.string
print("aStr = \(aStr)")
Doing this avoids the need to specify a UTI and it avoids replacing all existing items on the pasteboard.

Access a plist in Swift (error: "Expressions are not allowed at the top level")

I have a plist that I would like to access as a Swift Dictionary in Xcode. I'm able to define the contents of the file as an NSDictionary but every time I try to access a specific value/object from it, it shows the error "Expressions are not allowed at the top level". I've tried bracket notation, type casting and class methods (with dot notation) using autocompletion.
My plist contains dictionaries, which contain dictionaries that contain dictionaries with CGFloats and Strings.
I would like to access the instance's values.
Here's the code I use to define it, which is at the beginning of my GameScene.swift (it's a SpriteKit game):
let levelBlocks = NSDictionary(contentsOfFile: NSBundle.mainBundle().pathForResource("LevelBlocks", ofType: "plist"))
I would like to be able to do:
levelBlocks["Level1"]
EDIT: Added more information.
I've also tried putting it in the ViewDidLoad(), init() and using a generic that conforms to Hashable inside of a function instead of AnyObject/Any. I still can't access the String.
EDIT 2: I've tried this in the ViewDidLoad(), it returns the error 254:
let levelBlocks = NSDictionary(contentsOfFile: NSBundle.mainBundle().pathForResource("LevelBlocks", ofType: "plist")) as Dictionary<String, Dictionary<String, Dictionary<String, String>>>
let test = levelBlocks["Level1"] as Dictionary<String, Dictionary<String, String>>
let test1 = test["Block0"] as Dictionary<String, String>
let test2 = test1["color"] as String
println(test2)
Here's the solution I found:
let levelBlocks = NSDictionary(contentsOfFile: NSBundle.mainBundle().pathForResource("LevelBlocks", ofType: "plist"))
let test: AnyObject = levelBlocks.objectForKey("Level1")
println(test) // Prints the value of test
I set the type of test to AnyObject to silence a warning about an unexpected inference that could occur.
Also, it has to be done in a class method.
To access and save a specific value of a known type:
let value = levelBlocks.objectForKey("Level1").objectForKey("amount") as Int
println(toString(value)) // Converts value to String and prints it
Xcode gives that error when you have an expression outside of a class or instance method. Declaring a global variable like levelBlocks is okay, but trying to call methods on a variable (which is what subscripting does) is an expression and not allowed there. If you're just testing things out, you can create another global:
let levelBlocks = NSDictionary(contentsOfFile: NSBundle.mainBundle().pathForResource("LevelBlocks", ofType: "plist"))
let level1 = levelBlocks["level1"]
but really any kind of manipulation should happen inside your class.
Note: This applies to ".swift" files -- playgrounds are built just for this sort of informal experimentation. Does your attempted code work in a playground?
Here's a breakdown of a Swift file that contains a class with the different levels scope shown in comments:
// MyViewController.swift
let globalNumber = 10 // global scope: can be accessed from
// anywhere in this module.
class MyViewController : ViewController {
var instanceNumber = 5 // instance scope: instances of this class each
// maintain their own value. should be
// accessed within methods using self.varname
override func viewDidLoad() {
super.viewDidLoad()
var localNumber = self.instanceNumber * globalNumber
// local scope: will disappear after this method
// finishes executing (in most cases)
}
}
In your case, you'll want to load the plist and access its values inside one of your class, probably the init or a ...DidLoad method.
As for pulling items out of the dictionary, keep in mind that an NSDictionary is bridged directly to Swift as a Dictionary, so you can load and parse it like this:
let path = NSBundle.mainBundle().pathForResource("LevelBlocks", ofType: "plist")
if let levelBlocks = NSDictionary(contentsOfFile: path) as? Dictionary<String, AnyObject> {
let level1 = levelBlocks["level1"] // level1 is of type AnyObject?
let level2 = levelBlocks["level2"] as NSString // type NSString
let level3: String = levelBlocks["level3"] as NSString // type String
}

Resources