How to write init method in Swift? - objective-c

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

Related

How to return a custom object in a swift convenience initializer?

I'm trying to do something like this:
public extension UIImage {
public convenience init(whatever: Int) {
UIGraphicsBeginImageContextWithOptions(...)
//...
let image = UIGraphicsGetImageFromCurrentContext()
UIGraphicsEndImageContext()
return image // <- impossible
}
}
But this is not possible as "nil" is the only valid return for an initializer... How do i do this?
For example, the Objtive-C method [UIImage imageNamed:] is a class method (that can return whatever it wants in Objective-C) and it was mapped to the swift initializer UIImage(named:).
What you want is a class factory method, not an initializer. Most factory methods in Foundation/Cocoa are automatically bridged to initializers, but if what you want can't be done via init, you can add a new class method:
public extension UIImage {
class func imageWithWhatever(whatever: Int) -> UIImage {
UIGraphicsBeginImageContextWithOptions(...)
//...
let image = UIGraphicsGetImageFromCurrentContext()
UIGraphicsEndImageContext()
return image
}
}
This is because you are returning a new object, not self. The point of init is to create the structure of your object, not a new one, so if you want to do it as a convenience init, you need to do it like this:
public extension UIImage {
public convenience init?(whatever: Int) {
defer {
UIGraphicsEndImageContext()
}
UIGraphicsBeginImageContextWithOptions(...)
//...
guard let currentContext = UIGraphicsGetCurrentContext() else { return nil }
guard let image = currentContext.makeImage() else { return nil }
self.init(cgImage:image)
}
}
perhaps instead of a convenience init, you want to create a class function that is doing what you are asking:
public class func createImage(whatever: Int) -> UIImage? {
defer {
UIGraphicsEndImageContext()
}
UIGraphicsBeginImageContextWithOptions(...)
//...
guard let currentContext = UIGraphicsGetCurrentContext() else { return nil }
guard let cgImage = currentContext.makeImage() else { return nil }
let image = UIImage(cgImage: cgImage)
return image
}
I apologize that this is not 100% to code, but that is basically the gist of it

How can method in Swift with inout parameter be used in Objective-C?

I want
func foo(inout stop: Bool) -> Void {
// ...
}
use in my Objective-C part. But it is never generated in Module-Swift.h header. If I mark it with #objc, the
Method cannot be marked #objc because the type of the parameter cannot
be represented in Objective-C
error occurs.
You can't use an inout parameter when bridging with Objective-C, but you can do something similar if you use an UnsafeMutablePointer<T> (T would be Bool in your case). It would look something like this:
#objc func foo(stop: UnsafeMutablePointer<Bool>) -> Void {
if stop != nil {
// Use the .pointee property to get or set the actual value stop points to
stop.pointee = true
}
}
Example
TestClass.swift:
public class TestClass: NSObject {
#objc func foo(stop: UnsafeMutablePointer<Bool>) -> Void {
stop.pointee = true
}
}
Objective-C:
TestClass *test = [[TestClass alloc] init];
BOOL stop = false;
[test foo:&stop];
// stop is YES here
Similarly to what happening with generics, inout is not objc-compatible.
One possible workaround is to embed your parameter(s) in a class (which is a reference type, hence passed by pointer and not by value):
#objc class MyFuncParams {
var stop: Bool
init(stop: Bool) {
self.stop = stop
}
}
and define the function to accept an instance of that class:
func changeParam(params: MyFuncParams) {
params.stop = true
}
Not an elegant way to solve the problem, but what's important is that it should work (never tried myself though).

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")
}
}
}

property type or class using reflection

I was wondering if it's possible to determine the class or primitive type of an Objects properties. Getting all properties names and values is pretty easy. SO answer
So is there any way to get the properties class type while the property hast no value or nil value?
Example Code
#interface MyObject : NSObject
#property (nonatomic, copy) NSString *aString;
#property (nonatomic, copy) NSDate *aDate;
#property NSInteger aPrimitive;
#end
#implementation MyObject
#synthesize aString;
#synthesize aDate;
#synthesize aPrimitive;
- (void)getTheTypesOfMyProperties {
unsigned int count;
objc_property_t* props = class_copyPropertyList([self class], &count);
for (int i = 0; i < count; i++) {
objc_property_t property = props[i];
// Here I can easy get the name or value
const char * name = property_getName(property);
// But is there any magic function that can tell me the type?
// the property can be nil at this time
Class cls = magicFunction(property);
}
free(props);
}
#end
After searching through Apples Documentation about objc Runtime and according to this SO answer I finally got it working. I just want to share my results.
unsigned int count;
objc_property_t* props = class_copyPropertyList([MyObject class], &count);
for (int i = 0; i < count; i++) {
objc_property_t property = props[i];
const char * name = property_getName(property);
NSString *propertyName = [NSString stringWithCString:name encoding:NSUTF8StringEncoding];
const char * type = property_getAttributes(property);
NSString *attr = [NSString stringWithCString:type encoding:NSUTF8StringEncoding];
NSString * typeString = [NSString stringWithUTF8String:type];
NSArray * attributes = [typeString componentsSeparatedByString:#","];
NSString * typeAttribute = [attributes objectAtIndex:0];
NSString * propertyType = [typeAttribute substringFromIndex:1];
const char * rawPropertyType = [propertyType UTF8String];
if (strcmp(rawPropertyType, #encode(float)) == 0) {
//it's a float
} else if (strcmp(rawPropertyType, #encode(int)) == 0) {
//it's an int
} else if (strcmp(rawPropertyType, #encode(id)) == 0) {
//it's some sort of object
} else {
// According to Apples Documentation you can determine the corresponding encoding values
}
if ([typeAttribute hasPrefix:#"T#"]) {
NSString * typeClassName = [typeAttribute substringWithRange:NSMakeRange(3, [typeAttribute length]-4)]; //turns #"NSDate" into NSDate
Class typeClass = NSClassFromString(typeClassName);
if (typeClass != nil) {
// Here is the corresponding class even for nil values
}
}
}
free(props);
Inspired by the ObjC answer by #arndt-bieberstein I have written a solution in Swift 3 (probably very similar - if not same - in earlier versions of Swift). You can find it on Github I am trying to make a pod of it but I am having issues getting pob lib lintto work with the Swift 3 code (CLI xcodebuild or Xcode 8 related problem probably.) Anyhow, the class method func getTypesOfProperties(inClass clazz: NSObject.Type) -> Dictionary<String, Any>? can extract the name and types of any Swift class that inherits from NSObject.
The work horse of the project are these methods, but checkout the full code on Github:
func getTypesOfProperties(in clazz: NSObject.Type) -> Dictionary<String, Any>? {
var count = UInt32()
guard let properties = class_copyPropertyList(clazz, &count) else { return nil }
var types: Dictionary<String, Any> = [:]
for i in 0..<Int(count) {
guard let property: objc_property_t = properties[i], let name = getNameOf(property: property) else { continue }
let type = getTypeOf(property: property)
types[name] = type
}
free(properties)
return types
}
func getTypeOf(property: objc_property_t) -> Any {
guard let attributesAsNSString: NSString = NSString(utf8String: property_getAttributes(property)) else { return Any.self }
let attributes = attributesAsNSString as String
let slices = attributes.components(separatedBy: "\"")
guard slices.count > 1 else { return getPrimitiveDataType(withAttributes: attributes) }
let objectClassName = slices[1]
let objectClass = NSClassFromString(objectClassName) as! NSObject.Type
return objectClass
}
func getPrimitiveDataType(withAttributes attributes: String) -> Any {
guard let letter = attributes.substring(from: 1, to: 2), let type = primitiveDataTypes[letter] else { return Any.self }
return type
}
func getNameOf(property: objc_property_t) -> String? {
guard let name: NSString = NSString(utf8String: property_getName(property)) else { return nil }
return name as String
}
It can extract the NSObject.Type of all properties which class type inherits from NSObject such as NSDate (Swift3: Date), NSString(Swift3: String?) and NSNumber, however it is store in the type Any (as you can see as the type of the value of the Dictionary returned by the method). This is due to the limitations of value types such as Int, Int32, Bool. Since those types do not inherit from NSObject, calling .self on e.g. an Int - Int.self does not return NSObject.Type, but rather the type Any. Thus the method returns Dictionary<String, Any>? and not Dictionary<String, NSObject.Type>?.
You can use this method like this:
class Book: NSObject {
let title: String
let author: String?
let numberOfPages: Int
let released: Date
let isPocket: Bool
init(title: String, author: String?, numberOfPages: Int, released: Date, isPocket: Bool) {
self.title = title
self.author = author
self.numberOfPages = numberOfPages
self.released = released
self.isPocket = isPocket
}
}
guard let types = getTypesOfProperties(inClass: Book.self) else { return }
for (name, type) in types {
print("'\(name)' has type '\(type)'")
}
// Prints:
// 'title' has type 'NSString'
// 'numberOfPages' has type 'Int'
// 'author' has type 'NSString'
// 'released' has type 'NSDate'
// 'isPocket' has type 'Bool'
You can also try to cast the Any to NSObject.Type, which will succeed for all properties inheriting from NSObject, then you can check the type using standard == operator:
func checkPropertiesOfBook() {
guard let types = getTypesOfProperties(inClass: Book.self) else { return }
for (name, type) in types {
if let objectType = type as? NSObject.Type {
if objectType == NSDate.self {
print("Property named '\(name)' has type 'NSDate'")
} else if objectType == NSString.self {
print("Property named '\(name)' has type 'NSString'")
}
}
}
}
If you declare this custom == operator:
func ==(rhs: Any, lhs: Any) -> Bool {
let rhsType: String = "\(rhs)"
let lhsType: String = "\(lhs)"
let same = rhsType == lhsType
return same
}
You can then even check the type of value types like this:
func checkPropertiesOfBook() {
guard let types = getTypesOfProperties(inClass: Book.self) else { return }
for (name, type) in types {
if type == Int.self {
print("Property named '\(name)' has type 'Int'")
} else if type == Bool.self {
print("Property named '\(name)' has type 'Bool'")
}
}
}
LIMITATIONS
I have not yet been able to give this project support for when the value types are optionals. If you have declared a property in you NSObject subclass like this: var myOptionalInt: Int? my solution will not work, because the method class_copyPropertyList can't find those properties.
Does anyone have a solution for this?

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]