Is it possible to override a default init without subclassing?
I want every instance of DateFormatter created in my app to have the same locale attached to it. Right now, upon each instance creation, I have to:
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX"
Is it possible to have this happen without the need to write out the locale every single time? I'd prefer to not create a DateFormatter subclass as I feel that's a bit of overkill
Thanks!
There is no way to override an existing method of a class without subclassing it or modifying the source code (which you obviously can't do in case of built-in types).
However, for your specific case, you could simply add a method/static variable that returns a DateFormatter specific to your needs.
extension DateFormatter {
static func usPosix() -> DateFormatter {
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
return dateFormatter
}
}
And then instead of calling let dateFormatter = DateFormatter(), you do let dateFormatter = DateFormatter.usPosix()
While I personally HIGHLY advise against swizzling, that is certainly an option here.
First, start by creating your own locale that you want to be used throughout your app:
extension DateFormatter {
// Note the "#objc" here, this is necessary for altering the runtime logic
#objc var swizzledLocale: Locale {
return Locale(identifier: "en_US_POSIX")
}
}
Next, you'll want to tell Swift that whenever a DateFormatter's locale is accessed, you want your new swizzledLocale to be used instead:
// get reference to the two getter methods for the properties you want to "switch"
let orig = #selector(getter: DateFormatter.locale)
let new = #selector(getter: DateFormatter.swizzledLocale)
let origMethod = class_getInstanceMethod(DateFormatter.self, orig)!
let newMethod = class_getInstanceMethod(DateFormatter.self, new)!
// performs the actual switch so that whenever "locale" is accessed in code, the returned value will be the value of "swizzledLocale"
method_exchangeImplementations(origMethod, newMethod)
And that's all there is to it! Now whenever you access any DateFormatter's locale, a Locale("en_US_POSIX") will be returned.
Some things to note: first, this will alter ALL instances of locale, not just the locales that you personally use in your own code. That is, if there's any internal code that relies on a formatter's locale, en_US_POSIX will always be used. Second, the second block of code (the one that performs the actual swizzling) should be called exactly one time; look into wrapping it in a dispatch_once block to ensure it only runs once.
Related
I am trying to override init in my DatabaseHelper but somehow. I am getting an error Property 'self.dateFormatter' not initialized at super.init call I am not getting what i have done wrong here.
public override init() {
super.init() //I am getting error here
dateFormatter.dateFormat = "yyyyMMdd HHmmss";
do {
db = try Connection(dbPath);
try db!.run(FormTbl.create(temporary: false, ifNotExists: true) { t in
t.column(self.ColumnId, primaryKey:true);
t.column(self.FileName);
t.column(self.ReceivedDate);
t.column(self.SentDate);
t.column(self.Status);
t.column(self.AwaitingStatus);
});
try db!.run(SettingTbl.create(temporary: false, ifNotExists:true) {t in
t.column(self.ColumnId, primaryKey:true);
t.column(self.SettingName, unique: true);
t.column(self.SettingValue);
});
try db!.run(PhotoTbl.create(temporary: false, ifNotExists:true) {t in
t.column(self.ColumnId, primaryKey:true);
t.column(self.PhotoFormId);
t.column(self.FileName);
t.column(self.PhotoSentFileName);
t.column(self.ReceivedDate);
t.column(self.SentDate);
t.column(self.Status);
});
try db!.run(GpsTbl.create(temporary: false, ifNotExists:true) {t in
t.column(self.ColumnId, primaryKey: true);
t.column(self.GpsFormId);
t.column(self.Longitude);
t.column(self.Latitude);
t.column(self.Altitude);
t.column(self.DateTime);
});
} catch {
}
}
NOTE: I was trying to migrate my project from swift3 to swift4.2
my full source code is here : full class url
I am really stuck here from a day! Can any body please help me what I am doing wrong here !! Any suggestion will be a great help! Thanks in advance
The code doesn't even compile in Swift 3.
As dateFormatter is a declared but not initialized property in the class you have to initialize it before calling super for example
public override init() {
dateFormatter = DateFormatter()
super.init()
dateFormatter.dateFormat = "yyyyMMdd HHmmss"
and remove the exclamation mark, dateFormatter is supposed to be non-optional
private let dateFormatter : DateFormatter
Apart from that there are three bad practices in your code.
This is Swift. No trailing semicolons
Please conform to the naming convention, this line is highly confusing
public var DateFormatter : DateFormatter {
All structs, classes and enums start with an uppercase letter
All variables, functions and enum cases start with a lowercase letter.
The creation of the shared instance is objective-c-ish and outdated since Swift 2
You're getting that error because your dateFormatter variable isn't initialized in your class. You have to initialize all your variables before you finish your init initializer, or otherwise the compiler will complain for security reasons.
In your code, you're declaring the following:
private let dateFormatter : DateFormatter!
But that is just a constant that holds no value. Isn't pointing to a DateFormatter class. You haven't initialized it. In order to initialize it, you have to assign a DateFormatter() to it, for example, in that exact same line:
private let dateFormatter = DateFormatter()
You should be able to initialize it in your init method too, by adding dateFormatter = DateFormatter() on it, but you'll need to replace your let with a var.
In Objective C, one can create a CFLocale as follows (taken from this post in 2012):
CFLocaleRef myLocale = CFLocaleCopyCurrent() for the current locale; or:
CFLocaleRef myLocale = CFLocaleCreate(kCFAllocatorDefault, CFSTR("ja")), for a target locale. The locale name comes from the rightmost column of the ISO 639-1/639-2 table, which Apple specifies as their standard for language codes here.*
*Note: very old code examples refer to long language codes like 'Japanese', as may be expected by versions of Mac OS X older than 10.4.
How does one create a CFLocale in Swift 3, as the API appears to have changed in several ways?
CFLocale is toll-free bridged to NSLocale, so you can simply call
let myLocale = NSLocale(localeIdentifier: "ja")
// or
let myLocale = NSLocale(localeIdentifier: NSLocale.canonicalLocaleIdentifier(from: "Japanese"))
depending on whether you have a ISO 639-1 language code or not.
The corresponding Swift 3 "overlay value type" Locale
(which is used by Calendar, DateFormatter, ...,
compare SE-0069 Mutability and Foundation Value Types)
can similarly be created with
let myLocale = Locale(identifier: "ja")
// or
let myLocale = Locale(identifier: Locale.canonicalIdentifier(from: "Japanese"))
Here are the API changes to Swift since the Objective-C example given from 2012:
CFLocaleRef has been replaced by CFLocale.
CFStringRef has been replaced by CFString, which may be created by a regular String cast to CFString type: "ja" as CFString!.
CFLocaleCreate() now expects a CFLocaleIdentifier rather than just a CFString, so we must provide one using CFLocaleCreateCanonicalLanguageIdentifierFromString().
This can be done with the following two lines:
let localeIdentifier: CFLocaleIdentifier = CFLocaleCreateCanonicalLanguageIdentifierFromString(kCFAllocatorDefault, "ja" as CFString!)
let locale: CFLocale = CFLocaleCreate(kCFAllocatorDefault, localeIdentifier)
I am trying to build a table of current locale properties in code, and have encountered issues with trying to pass the value of a variable to a function:
let currentLocale = Locale(identifier: "en_US")
let calendar1 = currentLocale.calendar // "gregorian (fixed)"
let propertyName = "calendar"
let calendar2 = currentLocale.propertyName // Error: Value of type 'Locale' has no member 'porpertyName'
In the last line of code above, the instance of Locale thinks I am passing it "propertyName" rather than the contents of the variable "calendar".
Is there any way to pass the value of propertyName ("calendar") to the instance of Locale? I know that in other languages, you can prepend the variable name like '$propertyName', and that tells it to read the value of the variable.
I want to keep this pure Swift if possible.
You are looking for some form of key-value coding.
It's a little tricky, in that this is a purely Objective-C feature of Cocoa, so it doesn't work with the Swift overlay class Locale; you will have to cast currentLocale to Objective-C NSLocale. Moreover, NSLocale exposes its attributes through special NSLocale.Key types. After a great deal of casting, I find that this works:
let calendar2 =
(currentLocale as NSLocale).object(forKey:NSLocale.Key(rawValue:propertyName))
calendar2 is typed as Any but you can cast it down to a String.
In several places here, it has been suggested that using a computed property within an extension of NSDate might a good way to obtain a string version of a date via a NSDateFormatter, like so:
extension NSDate {
public var UTC : String {
let formatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss ZZZ"
formatter.timeZone = NSTimeZone(abbreviation: "UTC")
return formatter.stringFromDate(self)
}
}
However, allocating a NSDateFormatter is expensive and it is suggested that they be created once and cached. The above code creates the NSDateFormatter every time a date is formatted, and I'm wondering if there is a way to create the NSDateFormatter once inside the extension for reuse?
Obviously, I could create it just once outside the extension, but that seems to defeat the encapsulation that characterizes classes.
I am reminded of: https://xkcd.com/1179/ !!
You can add static members to class extensions just the same as on classes. You need to prefix the class name to the static member name when you use it, e.g. NSDate.dateFormatterUTC, even if you’re using it in the same class.
This works:
extension NSDate {
private static let dateFormatterUTC: NSDateFormatter = {
let formatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss ZZZ"
formatter.timeZone = NSTimeZone(abbreviation: "UTC")
return formatter
}()
public var UTC : String {
return NSDate.dateFormatterUTC.stringFromDate(self)
}
}
It’s also not the worst thing in the world just to use a private constant:
private let dateFormatterUTC: NSDateFormatter = {
let formatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss ZZZ"
formatter.timeZone = NSTimeZone(abbreviation: "UTC")
return formatter
}()
extension NSDate {
public var UTC : String {
return dateFormatterUTC.stringFromDate(self)
}
}
This is not significantly worse than the static class member, because Swift’s private is file-private, not type-private. These two declarations of dateFormatterUTC have the same scope. Even in the first example, NSDate.dateFormatterUTC is accessible throughout the entire file it’s declared in.
I do agree that the static version is preferable, but for stylistic reasons only: I like the way it’s indented right next to the thing that uses it.
As Gwendal wisely notes above, this approach assumes UTC will only ever be called from one thread. Although static let and global let are both thread-safe in Swift, the NSDateFormatter class is not! Looks like it’s threadsafe starting in iOS 7. Phew.
Still, always good to keep a thread safety warning next to any mention of singletons. If you do want to use a non-threadsafe helper object from multiple threads, consider either creating a new helper on every call, or using NSThread.currentThread().threadDictionary to create a per-thread instance. Be sure to do a little profiling to make sure you’re actually solving a performance problem before opting for the more complex thread-local option.
I want to be able to get the current time as a decimal point number so it can be used. so for example if the time is 13:46 I want to get it as 13.46
It seems simple but I am struggling getting to it.
We can use an NSDateFormatter to do exactly this:
extension NSDate {
func currentTime() -> String {
let formatter = NSDateFormatter()
formatter.dateFormat = "HH.mm"
return formatter.stringFromDate(self)
}
}
And now we just use it by calling it on any instance of NSDate:
let now = NSDate()
print(now.currentTime())
you can stringify the time and serch lastindexOf(":") and substitue it with a "."
UPDATE
I don't really catch what program language are you using, but it's plenty of library for stringify object so if you have a 13:46 you can convert it to String and, in the same string library you could find the method lastIndexOf(char). But if you don't find it you can always write following this concepts:
String are an array of Char so you can cycle it and convert the char in that position in the char you need.