How to translate this method from Objective-C to Swift - objective-c

I'm trying to translate this class from Objective-C to Swift. I'm almost done except for this method:
-(void) handleCookiesInRequest:(NSMutableURLRequest*) request
{
NSURL* url = request.URL;
NSArray* cookies = [self cookiesForURL:url];
NSDictionary* headers = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies];
NSUInteger count = [headers count];
__unsafe_unretained id keys[count], values[count];
[headers getObjects:values andKeys:keys];
for (NSUInteger i=0;i<count;i++) {
[request setValue:values[i] forHTTPHeaderField:keys[i]];
}
}
My attempt:
func handleCookiesInRequest(request: NSMutableURLRequest) {
var url = request.URL
var cookies = self.cookiesForURL(url!)
var headers = NSHTTPCookie.requestHeaderFieldsWithCookies(cookies)
var count = headers.count
for i in 0...count {
request.setValue(value: headers.values[i], forHTTPHeaderField: headers.keys[i])
}
}
I get an error in the line that set value saying
CustomHTTPCookieStorage.swift:88:21: Type '(value: $T8, forHTTPHeaderField: $T21)' does not conform to protocol '_SignedIntegerType'
Can you help me?

NSHTTPCookie.requestHeaderFieldsWithCookies() returns a dictionary, not an array. You meant this:
for (key, value) in headers {
request.setValue(value as? String, forHTTPHeaderField: key as! String)
}
You can make this a bit safer this way:
if let headers = NSHTTPCookie.requestHeaderFieldsWithCookies(cookies) as? [String:String] {
for (key, value) in headers {
request.setValue(value, forHTTPHeaderField: key)
}
}
Of course, if you haven't set any headers yet, this can all be replaced with:
request.allHTTPHeaderFields = NSHTTPCookie.requestHeaderFieldsWithCookies(cookies)
But this will blow away any preset headers, so the semantics are a little different.

Related

How to get ["error"]["type"] from error.userInfo in Swift?

I am wondering how do we do this in Swift?
I am trying to convert the code below to Swift
FBSDKGraphRequest *request = [[FBSDKGraphRequest alloc] initWithGraphPath:#"me" parameters:nil];
[request startWithCompletionHandler:^(FBSDKGraphRequestConnection *connection, id result, NSError *error) {
if (!error) {
// handle successful response
} else if ([[error userInfo][#"error"][#"type"] isEqualToString: #"OAuthException"]) { // Since the request failed, we can
NSLog(#"The facebook session error");
} else {
NSLog(#"Some other error: %#", error);
}
}];
Here is what I've done.
request.startWithCompletionHandler { (connection: FBSDKGraphRequestConnection!, result: AnyObject!, error: NSError!) -> Void in
if error == nil {
// handle successful response
}
else if error.userInfo["error"]["type"] == "OAuthException" {
//THIS LINE WONT COMPILE
}
else {
println("Some other error");
}
}
But I get a compile error that says could not find member 'subscript' on this line
error.userInfo["error"]["type"] == "OAuthException"
Any ideas please?
Try:
if (error.userInfo?["error"]?["type"] as? String) == "OAuthException" {
userInfo is an optional dictionary of type [NSObject: AnyObject]? so you'll need to unwrap it. Dictionary lookups always return an optional (because the key might not exist) so you have to unwrap it before accessing the key of the nested dictionary. Using a ? instead of ! is optional chaining and it will harmlessly result in nil if the "error" key doesn't exist (instead of crashing if you use a !). Finally, you'll need to cast the result to String (from AnyObject) in order to be able to compare it to "OAuthException".
How about breaking down the error and type as shown below?
else if let errorOAuthException: AnyObject = error.userInfo as? Dictionary<String, AnyObject>{
if errorOAuthException["error"] != nil {
if errorOAuthException["type"] as? String == "OAuthException" {
//Do something for me
}
}
}

Convert AVPlayer NSData to NSString (Or player format)

I need to know how to convert saved NSData from an AVPlayer back into a playable format. But I cannot figure out how to convert this NSData into a dataString, which would then allow me to create and NSUrl. Let's say I have the following code:
NSURL *videoUrl=(NSURL*)[info objectForKey:UIImagePickerControllerMediaURL];
self.data=[NSData dataWithContentsOfURL:videoUrl];
Now later on when I get this data back, I call:
NSString *dataString = [NSString stringWithUTF8String:[self.data bytes]];
But the dataString is always nil. Why? Am I decoding it in the wrong format or something?
A URL is a reference to data. The data is a (possibly complicated) encoding of something like a movie.
I am not sure exactly what you are asking, but I think you want to get the data (as an NSData object) and then save it somewhere. If this is correct, then what you need to do is
[self.data writeToFile:myFilePath atomically:YES]
where myFilePath is a path to somewhere where you can store files.
You could implement AVAssetResourceLoaderDelegate to provide data for AVPlayer.
func resourceLoader(resourceLoader: AVAssetResourceLoader!, shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest!) -> Bool {
if let data = videoData {
dispatch_async(dispatch_get_main_queue()) { () -> Void in
if let infoRequest = loadingRequest.contentInformationRequest {
infoRequest.contentType = "public.mpeg-4" // UTI
infoRequest.contentLength = Int64(data.length)
infoRequest.byteRangeAccessSupported = true
}
if let request = loadingRequest.dataRequest {
let part = data.subdataWithRange(NSRange(location: Int(request.requestedOffset), length: Int(request.requestedLength)))
request.respondWithData(part)
}
loadingRequest.finishLoading()
}
return true
}
return false
}
To create an AVPlayer:
let asset = AVURLAsset(URL: NSURL(scheme: "yourcustomscheme", host: nil, path: "/pathtovideo"), options: nil)
asset.resourceLoader.setDelegate(self, queue: dispatch_get_main_queue())
let item = AVPlayerItem(asset: asset)
let player = AVPlayer(playerItem: item)

What is wrong with the implementation of NSCoding protocol in Swift

I thought I'd be cautious and try out Swift on an existing Obj-C project by converting one class. And a small, simple one at that. Oh dear.
Transliterating the original obj-c into Swift should be straightforward and so it seemed. Unfortunately, whilst the encoder to persistent store seems to work, it crashes with an EXC_BREAKPOINT error at the first line of the init coder.
IF (and the caps are intentional) NSCoding/Swift gives the same persistent content as NSCoding/ObjC, then my all obj-c version should be able to read what is encoded by Swift and vice versa. This proves not to be the case - and my perfectly-functioning obj-c version crashes out when it tries to read the persistent store from the Swift version. Surely, if NSCoding is implemented correctly, it ought to generate something in one that is readable in t'other? Otherwise, there ought to be separate NSCodingSwift and NSCodingObjC protocols?
So, to summarise, I can read/write in obj-c. I can't write/obj-c and read/swift and I can write/swift read/obj-c and I can't read/write in swift.
Here are the two versions:
let keyBeaconItemNameKey = "name"
let keyBeaconItemUUIDKey = "uuid"
let keyBeaconItemMajorValueKey = "major"
let keyBeaconItemMinorValueKey = "minor"
import UIKit
import CoreLocation
class SMBeaconItem : NSObject, NSCoding
{
var name : String!
var uuid : NSUUID!
var major : NSNumber!
var minor : NSNumber!
init(newName : String, newUUID : NSUUID, newMajor : NSNumber, newMinor : NSNumber )
{
name = newName
uuid = newUUID
major = newMajor
minor = newMinor
}
init( coder decoder : NSCoder!)
{
name = decoder.decodeObjectForKey(keyBeaconItemNameKey) as String
uuid = decoder.decodeObjectForKey(keyBeaconItemUUIDKey) as NSUUID
major = decoder.decodeObjectForKey(keyBeaconItemMajorValueKey) as NSNumber
minor = decoder.decodeObjectForKey(keyBeaconItemMinorValueKey) as NSNumber
}
func encodeWithCoder( encoder: NSCoder!)
{
encoder.encodeObject(name, forKey:keyBeaconItemNameKey)
encoder.encodeObject(uuid, forKey:keyBeaconItemUUIDKey)
encoder.encodeObject(major, forKey:keyBeaconItemMajorValueKey)
encoder.encodeObject(minor, forKey:keyBeaconItemMinorValueKey)
}
}
And the working original:
#implementation SMBeaconItem
- (instancetype)initWithName:(NSString *)name uuid:(NSUUID *)uuid major:(CLBeaconMajorValue)major minor:(CLBeaconMinorValue)minor
{
self = [super init];
if (!self)
{
return nil;
}
_name = name;
_uuid = uuid;
_majorValue = major;
_minorValue = minor;
return self;
}
#pragma mark - Persistence
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (!self)
{
return nil;
}
_name = [aDecoder decodeObjectForKey:keyBeaconItemNameKey];
_uuid = [aDecoder decodeObjectForKey:keyBeaconItemUUIDKey];
_majorValue = [[aDecoder decodeObjectForKey:keyBeaconItemMajorValueKey] unsignedIntegerValue];
_minorValue = [[aDecoder decodeObjectForKey:keyBeaconItemMinorValueKey] unsignedIntegerValue];
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:self.name forKey:keyBeaconItemNameKey];
[aCoder encodeObject:self.uuid forKey:keyBeaconItemUUIDKey];
[aCoder encodeObject:[NSNumber numberWithUnsignedInteger:self.majorValue] forKey:keyBeaconItemMajorValueKey];
[aCoder encodeObject:[NSNumber numberWithUnsignedInteger:self.minorValue] forKey:keyBeaconItemMinorValueKey];
}
#end
Thanks for any help you can give.
The only thing that stands out to me is that you're using String instead of NSString as the name's type. In the betas Apple has been putting out, String (strangely) is not a one-for-one replacement of NSString. Namely, some methods are missing and require calling .bridgeToObjectiveC() to get the NSString version. Using that type instead will probably conform to what NSCoder is expecting, though that difference should not be so.
I didn't actually test this claim since I'm not on my dev machine. But that's my gut instinct. Try it and see what happens! If nothing changes, try switching the order of how you're setting the vars and see if it's a problem related to the name field or simply the first line of the init function.
Below code working is swift for save retrieve NSCoding value in UserDefaults
import UIKit
import Foundation
class ViewController: UIViewController {
var employees: Employees?
let static_key = "nscdeing_data_saved"
override func viewDidLoad() {
super.viewDidLoad()
var request = URLRequest(url: URL(string: "http://dummy.restapiexample.com/api/v1/employees")!, cachePolicy: .returnCacheDataElseLoad, timeoutInterval: 60)
request.httpMethod = "GET"
URLSession.shared.dataTask(with: request) { (data, response, error) in
if let status = (response as? HTTPURLResponse)?.statusCode, status == 200, let data = data{
do {
guard let dic = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String:Any] else { return }
self.employees = Employees.init(fromDictionary: dic)
let archiveData = try NSKeyedArchiver.archivedData(withRootObject: self.employees as Any, requiringSecureCoding: true)
UserDefaults.standard.set(archiveData, forKey: self.static_key)
} catch let error {
fatalError(error.localizedDescription)
}
}
}.resume()
}
#IBAction func printAction(_ sender: Any) {
if let data = UserDefaults.standard.data(forKey: static_key){
do {
let value = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data)
print(value as Any)
} catch let error {
fatalError(error.localizedDescription)
}
}
}
}
class Employees : NSObject, NSCoding, NSSecureCoding{
static var supportsSecureCoding: Bool{
return true
}
var data : [Datum]!
var status : String!
/**
* Instantiate the instance using the passed dictionary values to set the properties values
*/
init(fromDictionary dictionary: [String:Any]){
status = dictionary["status"] as? String
data = [Datum]()
if let dataArray = dictionary["data"] as? [[String:Any]]{
for dic in dataArray{
let value = Datum(fromDictionary: dic)
data.append(value)
}
}
}
/**
* Returns all the available property values in the form of [String:Any] object where the key is the approperiate json key and the value is the value of the corresponding property
*/
func toDictionary() -> [String:Any]{
var dictionary = [String:Any]()
if status != nil{
dictionary["status"] = status
}
if data != nil{
var dictionaryElements = [[String:Any]]()
for dataElement in data {
dictionaryElements.append(dataElement.toDictionary())
}
dictionary["data"] = dictionaryElements
}
return dictionary
}
/**
* NSCoding required initializer.
* Fills the data from the passed decoder
*/
#objc required init(coder aDecoder: NSCoder){
data = aDecoder.decodeObject(forKey: "data") as? [Datum]
status = aDecoder.decodeObject(forKey: "status") as? String
}
/**
* NSCoding required method.
* Encodes mode properties into the decoder
*/
#objc func encode(with aCoder: NSCoder){
if data != nil{
aCoder.encode(data, forKey: "data")
}
if status != nil{
aCoder.encode(status, forKey: "status")
}
}
}
class Datum : NSObject, NSCoding, NSSecureCoding{
static var supportsSecureCoding: Bool{
return true
}
var employeeAge : String!
var employeeName : String!
var employeeSalary : String!
var id : String!
var profileImage : String!
/**
* Instantiate the instance using the passed dictionary values to set the properties values
*/
init(fromDictionary dictionary: [String:Any]){
employeeAge = dictionary["employee_age"] as? String
employeeName = dictionary["employee_name"] as? String
employeeSalary = dictionary["employee_salary"] as? String
id = dictionary["id"] as? String
profileImage = dictionary["profile_image"] as? String
}
/**
* Returns all the available property values in the form of [String:Any] object where the key is the approperiate json key and the value is the value of the corresponding property
*/
func toDictionary() -> [String:Any]{
var dictionary = [String:Any]()
if employeeAge != nil{
dictionary["employee_age"] = employeeAge
}
if employeeName != nil{
dictionary["employee_name"] = employeeName
}
if employeeSalary != nil{
dictionary["employee_salary"] = employeeSalary
}
if id != nil{
dictionary["id"] = id
}
if profileImage != nil{
dictionary["profile_image"] = profileImage
}
return dictionary
}
/**
* NSCoding required initializer.
* Fills the data from the passed decoder
*/
#objc required init(coder aDecoder: NSCoder){
employeeAge = aDecoder.decodeObject(forKey: "employee_age") as? String
employeeName = aDecoder.decodeObject(forKey: "employee_name") as? String
employeeSalary = aDecoder.decodeObject(forKey: "employee_salary") as? String
id = aDecoder.decodeObject(forKey: "id") as? String
profileImage = aDecoder.decodeObject(forKey: "profile_image") as? String
}
/**
* NSCoding required method.
* Encodes mode properties into the decoder
*/
#objc func encode(with aCoder: NSCoder){
if employeeAge != nil{
aCoder.encode(employeeAge, forKey: "employee_age")
}
if employeeName != nil{
aCoder.encode(employeeName, forKey: "employee_name")
}
if employeeSalary != nil{
aCoder.encode(employeeSalary, forKey: "employee_salary")
}
if id != nil{
aCoder.encode(id, forKey: "id")
}
if profileImage != nil{
aCoder.encode(profileImage, forKey: "profile_image")
}
}
}

How to write init method in Swift?

I want to write an init method in Swift. Here I initialize an NSObject class in Objective-C:
-(id)initWithNewsDictionary:(NSDictionary *)dictionary
{
self = [super init];
if (self) {
self.title = dictionary[#"title"];
self.shortDescription = dictionary[#"description"];
self.newsDescription = dictionary[#"content:encoded"];
self.link = dictionary[#"link"];
self.pubDate = [self getDate:dictionary[#"pubDate"]];
}
return self;
}
How can I write this method in Swift ?
that could be good bases for your class, I guess:
class MyClass {
// you may need to set the proper types in accordance with your dictionarty's content
var title: String?
var shortDescription: String?
var newsDescription: String?
var link: NSURL?
var pubDate: NSDate?
//
init () {
// uncomment this line if your class has been inherited from any other class
//super.init()
}
//
convenience init(_ dictionary: Dictionary<String, AnyObject>) {
self.init()
title = dictionary["title"] as? NSString
shortDescription = dictionary["shortDescription"] as? NSString
newsDescription = dictionary["newsDescription"] as? NSString
link = dictionary["link"] as? NSURL
pubDate = self.getDate(dictionary["pubDate"])
}
//
func getDate(object: AnyObject?) -> NSDate? {
// parse the object as a date here and replace the next line for your wish...
return object as? NSDate
}
}
advanced-mode
I would like to avoid to copy-pand-paste the keys in a project, so I'd put the possible keys into e.g. an enum like this:
enum MyKeys : Int {
case KeyTitle, KeyShortDescription, KeyNewsDescription, KeyLink, KeyPubDate
func toKey() -> String! {
switch self {
case .KeyLink:
return "title"
case .KeyNewsDescription:
return "newsDescription"
case .KeyPubDate:
return "pubDate"
case .KeyShortDescription:
return "shortDescription"
case .KeyTitle:
return "title"
default:
return ""
}
}
}
and you can improve your convenience init(...) method like e.g. this, and in the future you can avoid any possible mistyping of the keys in your code:
convenience init(_ dictionary: Dictionary<String, AnyObject>) {
self.init()
title = dictionary[MyKeys.KeyTitle.toKey()] as? NSString
shortDescription = dictionary[MyKeys.KeyShortDescription.toKey()] as? NSString
newsDescription = dictionary[MyKeys.KeyNewsDescription.toKey()] as? NSString
link = dictionary[MyKeys.KeyLink.toKey()] as? NSURL
pubDate = self.getDate(dictionary[MyKeys.KeyPubDate.toKey()])
}
NOTE: that is just a raw idea of how you could do it, it is not necessary to use conveniece initializer at all, but it looked obvious choice regarding I don't know anything about your final class – you have shared one method only.
class myClass {
var text: String
var response: String?
init(text: String) {
self.text = text
}
}
See Swift: Initialization
Do not need for call this method from other class it will get called automatically
override init()
{
super.init()
// synthesize.delegate = self
// println("my array elements are \(readingData)")
}
try:
initWithDictionary(dictionary : NSDictionary) {
init()
self.title = "... etc"
}
Source:
https://docs.swift.org/swift-book/LanguageGuide/Initialization.html

How to retrieve values from settings.bundle in Objective-c/Swift?

I have created a project that set and retrieve values from settings.bundle. I have also set some defaults values in settings.bundle file. Now the problem is when I retrieve values as
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
loginName.text = [defaults objectForKey:#"login_name"];
for the first time it shows null, but the values get set in iPhone application settings.
If I change the values or set it manually, then values are retrieved properly.
Help me out
Although you define the defaults settings, they are not really stored as a value. They are stored as default. If you try to read it, the value is null. Default setting is another property as value is. But it doesnt mean that will write the default value as a default.
What I do is, first, check if some setting,(that I'm sure that should have a value) has anything stored on it. If it doesn't have anything then I write all the defaults.
Here is an example.
on AppDelegate.m I check if email_notificaciones_preference has a value, if not, I write ALL default settings to each setting.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
NSUserDefaults * standardUserDefaults = [NSUserDefaults standardUserDefaults];
NSString * email_notificaciones_preference = [standardUserDefaults objectForKey:#"email_notificaciones_preference"];
if (!email_notificaciones_preference) {
[self registerDefaultsFromSettingsBundle];
}
}
This function is what I use to write defaults to each element.
#pragma NSUserDefaults
- (void)registerDefaultsFromSettingsBundle {
// this function writes default settings as settings
NSString *settingsBundle = [[NSBundle mainBundle] pathForResource:#"Settings" ofType:#"bundle"];
if(!settingsBundle) {
NSLog(#"Could not find Settings.bundle");
return;
}
NSDictionary *settings = [NSDictionary dictionaryWithContentsOfFile:[settingsBundle stringByAppendingPathComponent:#"Root.plist"]];
NSArray *preferences = [settings objectForKey:#"PreferenceSpecifiers"];
NSMutableDictionary *defaultsToRegister = [[NSMutableDictionary alloc] initWithCapacity:[preferences count]];
for(NSDictionary *prefSpecification in preferences) {
NSString *key = [prefSpecification objectForKey:#"Key"];
if(key) {
[defaultsToRegister setObject:[prefSpecification objectForKey:#"DefaultValue"] forKey:key];
NSLog(#"writing as default %# to the key %#",[prefSpecification objectForKey:#"DefaultValue"],key);
}
}
[[NSUserDefaults standardUserDefaults] registerDefaults:defaultsToRegister];
}
Hope that helps.
If anyone needs it - I translated the answer from MIQUEL to Swift (as good as I could as I'm still learning) :
var standardUserDefaults = NSUserDefaults.standardUserDefaults()
var us: AnyObject? = standardUserDefaults.objectForKey("your_preference")
if us==nil {
self.registerDefaultsFromSettingsBundle();
}
And the func registerDefaultsFromSettingsBundle:
func registerDefaultsFromSettingsBundle() {
// this function writes default settings as settings
var settingsBundle = NSBundle.mainBundle().pathForResource("Settings", ofType: "bundle")
if settingsBundle == nil {
NSLog("Could not find Settings.bundle");
return
}
var settings = NSDictionary(contentsOfFile:settingsBundle!.stringByAppendingPathComponent("Root.plist"))!
var preferences: [NSDictionary] = settings.objectForKey("PreferenceSpecifiers") as [NSDictionary];
var defaultsToRegister = NSMutableDictionary(capacity:(preferences.count));
for prefSpecification:NSDictionary in preferences {
var key: NSCopying? = prefSpecification.objectForKey("Key") as NSCopying?
if key != nil {
defaultsToRegister.setObject(prefSpecification.objectForKey("DefaultValue")!, forKey: key!)
}
}
NSUserDefaults.standardUserDefaults().registerDefaults(defaultsToRegister);
}
Updated for Swift 3:
func registerDefaultsFromSettingsBundle() {
let userDefaults = UserDefaults.standard
if let settingsURL = Bundle.main.url(forResource: "Root", withExtension: "plist", subdirectory: "Settings.bundle"),
let settings = NSDictionary(contentsOf: settingsURL),
let preferences = settings["PreferenceSpecifiers"] as? [NSDictionary] {
var defaultsToRegister = [String: AnyObject]()
for prefSpecification in preferences {
if let key = prefSpecification["Key"] as? String,
let value = prefSpecification["DefaultValue"] {
defaultsToRegister[key] = value as AnyObject
debugPrint("registerDefaultsFromSettingsBundle: (\(key), \(value)) \(type(of: value))")
}
}
userDefaults.register(defaults: defaultsToRegister)
} else {
debugPrint("registerDefaultsFromSettingsBundle: Could not find Settings.bundle")
}
}
Updated version for swift 2.1:
func registerDefaultsFromSettingsBundle() {
let userDefaults = NSUserDefaults.standardUserDefaults()
if let settingsURL = NSBundle.mainBundle().URLForResource("Root", withExtension: "plist", subdirectory: "Settings.bundle"),
settings = NSDictionary(contentsOfURL: settingsURL),
preferences = settings["PreferenceSpecifiers"] as? [NSDictionary] {
var defaultsToRegister = [String: AnyObject]()
for prefSpecification in preferences {
if let key = prefSpecification["Key"] as? String,
value = prefSpecification["DefaultValue"] {
defaultsToRegister[key] = value
NSLog("registerDefaultsFromSettingsBundle: (\(key), \(value)) \(value.dynamicType)")
}
}
userDefaults.registerDefaults(defaultsToRegister);
} else {
NSLog("registerDefaultsFromSettingsBundle: Could not find Settings.bundle");
}
}
You can use a simple property wrapper like this:
Usage
#SettingsBundleStorage(key: "storageUsage_preference")
var storageUsage: Double
Note that this is 100% objective-c compatible by just adding #objc before the variable.
Implementation of the code behind this:
Settings bundle values are live in the UserDefaults so you can use a custom PropertyWrapper for it. The following wrapper will work for any UserDefault value, including all values of the SettingsBundle.
Property wrapper
#propertyWrapper
public struct SettingsBundleStorage<T> {
private let key: String
public init(key: String) {
self.key = key
setBundleDefaults(plist: .root) // This is the main plist
setBundleDefaults(plist: .child(name: "DeveloperOptions")) // This is an example child.
}
public var wrappedValue: T {
get { UserDefaults.standard.value(forKey: key) as! T }
set { UserDefaults.standard.set(newValue, forKey: key) }
}
}
The root and the children
You should pass the following enum for the root and the child plists:
extension SettingsBundleStorage {
enum PList {
case root
case child(name: String)
var name: String {
var file: String
switch self {
case .root: file = "Root"
case .child(let name): file = name.replacingOccurrences(of: ".plist", with: "")
}
file.append(".plist")
return file
}
}
}
Find and set defaults if needed.
This wrapper finds the default value of the bundle keys with this function:
extension SettingsBundleStorage {
func setBundleDefaults(plist: PList = .root) {
let settingsName = "Settings"
let settingsExtension = "bundle"
let settingsPreferencesItems = "PreferenceSpecifiers"
let settingsPreferenceKey = "Key"
let settingsPreferenceDefaultValue = "DefaultValue"
guard let settingsBundleURL = Bundle.main.url(forResource: settingsName, withExtension: settingsExtension),
let settingsData = try? Data(contentsOf: settingsBundleURL.appendingPathComponent(plist.name)),
let settingsPlist = try? PropertyListSerialization.propertyList(
from: settingsData,
options: [],
format: nil) as? [String: Any],
let settingsPreferences = settingsPlist?[settingsPreferencesItems] as? [[String: Any]] else {
return assertionFailure("Can not get the \(plist.name) from the bundle: \(settingsName)")
}
var defaultsToRegister = [String: Any]()
settingsPreferences.forEach { preference in
if let key = preference[settingsPreferenceKey] as? String {
defaultsToRegister[key] = preference[settingsPreferenceDefaultValue]
}
}
UserDefaults.standard.register(defaults: defaultsToRegister)
}
}
This wrapper can store/restore any kind of codable into/from the user defaults including all Swift standard data types that are already conformed to the codable.
Also, you can find a similar but with way less code version for accessing any key-value from any user default, you can take a look at this answer here
try this
To register Default Values of Setting bundles
NSDictionary *appDefaults = [NSDictionary dictionaryWithObject:defaultValue forKey:#"key"];
[[NSUserDefaults standardUserDefaults] registerDefaults:appDefaults];
Before retrieving setting bundles values synchronize data
[[NSUserDefaults standardUserDefaults] synchronize]