Nscalendar in Swift - Init Can Return Nil, But Isn't Optional

NSCalendar in Swift - init can return nil, but isn't optional

Note: The following only applies to Swift 1.0. In Swift 1.1, there are failable initializers.


This works:

if let c4 = NSCalendar(calendarIdentifier: "rubbish") as NSCalendar? { 

}

In Swift 1.0, there is a known issue in the Xcode release notes about Swift not supporting Objective-C initializers that return nil. Basically, what is happening is that according to the Swift language, the expression NSCalendar(...) has type NSCalendar, which is a non-optional type (cannot be nil). However, this is really an imported initializer from Objective-C, where it can return nil.

So what currently happens is that when you call this and it returns nil, you have a value that at runtime is nil, but Swift thinks is a non-optional NSCalendar (which cannot be nil). This is a really bad situation in which you have a value that is not possible for the type. The Xcode release notes mention a "workaround" where you convert the result of the initializer to an optional type before using it. The reason that this works is that at runtime, both optional and non-optional object pointers are represented as simple pointers, where nil object pointer is the null pointer, and non-optional ones are assumed to not be null pointers. The operation of converting from a non-optional to optional object pointer is a simple assignment (in both cases it's a non-null pointer). But in the case where the value is nil, a simple assignment turns it into a (valid) nil value of optional type. So everything is happy.

But if you don't convert it to optional first, all hell breaks loose, as you have a value that is not supposed to be possible for that type.

Your attempt to downcast using as? from NSCalendar to NSCalendar isn't allowed, because a cast from NSCalendar to NSCalendar cannot fail (theoretically).

Class Init with Optional Parameters - not possible?

You don't need (or want) to unwrap the property when you are assigning to it. You just refer to it directly;

init(firstName: String, middleName: String, lastName: String)
{
self.firstName = firstName
self.middleName = middleName
self.lastName = lastName
}

When middleName is nil (as it is initially) the conditional unwrap self.middleName? fails, so the assignment isn't performed.

Conditionally unwrapping self.middleName is effectively a shorthand way of saying

if self.middleName != nil {
self.middleName = middleName
}

Since middleName is optional, then you may want to make the initialisation parameter optional too:

init(firstName: String, middleName: String?=nil, lastName: String)
{
self.firstName = firstName
self.middleName = middleName
self.lastName = lastName
}

Why am I getting fatal error: unexpectedly found nil while unwrapping an Optional value?

The documentation for the NSDateComponents date property:

Returns nil if the calendar property value of the receiver is nil or cannot convert the receiver into an NSDate object.

You have not set the date components' calendar, so .date is nil. Read the answers on this question for more about unwrapping optionals.

Confused about optional type in swift (Nil can compare to Int)

(Edit update: after additional question added by OP)

I'll add an answer to you question

"Does it make sense "The nil can compare to Int" in Swift?"

You can consider Optional a type just like Int or double, Double, with a (simplified) enum implementation

enum Optional<T> {
case None
case Some(T)

init(_ value: T) {
self = .Some(value)
}

init() {
self = .None
}
}

The generic T in the Optional type is never used for case .None, which, in this discussion, would be nil. Hence, any type that is also optional (e.g. Int?, String? and so on) can be compared to nil. For the sake of this discussion, you could almost think of nil as a single literal that can be used to initialize or give value to any type that is defined as optional. With this latter statement, it's obvious that we can also compare the values of our optional type to nil.

Hence: yes, we can compare the value of any optional variable to nil, but there are usually better options, e.g. optional chaining or the nil coalescing operator (??).


(From before edit, still relevant)

I'll add an example to help to show you what is going on in loops constructed as if a.b?.c != d { ....

Consider the following structures:

struct MyStruct {
var myRow : Int
init (row: Int) {
myRow = row
}
}

struct MyTopStruct {
var myStruct : MyStruct? = nil

mutating func setValueToMyStruct(row: Int) {
myStruct = MyStruct(row: row)
}
}

Let a be an instance of MyTopStruct, and lets have some integer and optional integer properties in our example:

var a = MyTopStruct()
let myInt = 10
var myOptionalInt : Int? = nil

Then, we have a few different outcomes for if a.b?.c != d { ... clauses, depending on whether optionals in this example are nil (as initialized) or not.

/* "a.myStruct?" returns nil, and this clause will hence
compare "nil != myInt"?, which will always be true, since
myInt is a non-optional integer */
if a.myStruct?.myRow != myInt {
print("Entered 1") // prints
}

/* "a.myStruct?" still nil, and myOptionalInt not yet initialised
so also nil. Hence, "nil != nil"? => false */
if a.myStruct?.myRow != myOptionalInt {
print("Entered 2") // doesnt print
}

/* now initialise a.myStruct */
a.setValueToMyStruct(myInt)

/* "a.myStruct?" now returs value of myInt (10), which != nil,
the latter being the current value of myOptionalInt
=> always enters */
if a.myStruct?.myRow != myOptionalInt {
print("Entered 3") // prints
}

/* finally initialize myOptionalInt */
myOptionalInt = 9

/* Now this is the first comparison that can actually behave
in "different ways" w.r.t. how we usually think of if clauses:
comparing _values_ of same-type objects.
Hence, comparison now depends on the two non-nil values of
a.myStruct?.myRow and myOptionalInt */
if a.myStruct?.myRow != myOptionalInt {
print("Entered 4") // prints, since 10 != 9
}

NSDate beginning of day and end of day

You are missing NSDayCalendarUnit in

NSDateComponents *components = [cal components:( NSMonthCalendarUnit | NSYearCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit ) fromDate:date];

What does the exclamation mark mean for a swift initializer?

It is failable initializer, introduced in Swift 1.1 (with Xcode 6.1)

From Apple Developer:

The init! Failable Initializer


You typically define a failable initializer that creates an optional
instance of the appropriate type by placing a question mark after the
init keyword (init?). Alternatively, you can define a failable
initializer that creates an implicitly unwrapped optional instance of
the appropriate type. Do this by placing an exclamation mark after the
init keyword (init!) instead of a question mark.

You can delegate from init? to init! and vice versa, and you can
override init? with init! and vice versa. You can also delegate from
init to init!, although doing so will trigger an assertion if the
init! initializer causes initialization to fail.

(emphasis mine)

My convenience init doesn't work in an extension

Sounds like you are trying to create a class method that returns an instance of an NSPredicate.

Your extension would need to be something like this:

import Foundation

extension NSPredicate {
static func forLiftLogFilter() -> NSPredicate? {
if let logFilter = UserDefaultsManager.sharedInstance.logFilter?.rawValue {
return NSPredicate(format: "lift.liftName = [c] %@", logFilter)
} else {
return nil
}
}
}

Now you can properly call this class method:

let filterPredicate = NSPredicate.forLiftLogFilter

How do I get text to display Coptic Calendar date SwiftUI

The interface you want for Swift is Calendar rather than NSCalendar:

let calendar = Calendar(calendarIdentifier: .coptic)

The NSCalendar interface is from ObjC and is documented to return nil if the name of the calendar is unknown (you can pass arbitrary strings in ObjC for this parameter).

The Swift Calendar interface cannot fail because it will not allow you to create an unknown identifier.

There are many types that begin "NS" that are bridged from Objective-C. It is very common for there to be a Swift version that drops the "NS" prefix. When you see this, you should generally use the Swift version. It will generally behave in a more Swift-like way.

If your goal is to display the current date on the Coptic calendar, however, you will need more than this. A calendar represents the whole calendar, not a particular date. You will need a DateFormatter in order to create a localized string.

For example:

let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .medium
dateFormatter.calendar = Calendar(identifier: .coptic)
dateFormatter.eraSymbols = ["BC", "AM"] // See below
print(dateFormatter.string(from: Date()))

// Baramouda 18, 1737 AM

(I believe there is a bug in Calendar such that the Coptic calendar has era symbols "ERA0" and "ERA1". I believe the correct symbols for this calendar are "BC" and "AM". You can force this by assigning them directly to the date formatter. If I'm correct that this is a bug and it impacts you, I recommend opening an Apple Feedback. See also the DateFormatter documentation for how to customize this string.)

To xTwisteDx's point, to put this in SwiftUI you want something along these lines:

struct ContentView: View {

let dateFormatter: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .medium
dateFormatter.eraSymbols = ["BC", "AM"] // Workaround for Foundation bug
dateFormatter.calendar = Calendar(identifier: .coptic)
return dateFormatter
}()

func today() -> String {
dateFormatter.string(from: Date())
}

var body: some View {
Text(today())
.padding()
}
}


Related Topics



Leave a reply



Submit