Reputation: 798
Update: this is regarding a Mac app created with the Document-based Application template in Xcode, and I’m overriding
override func readFromFileWrapper(fileWrapper: NSFileWrapper, ofType typeName: String, error outError: NSErrorPointer) -> Bool
I’m trying to read in a file from an NSFileWrapper and it seems like I can't get away from having at least one !
in there.
First, I tried
if let rtfData = files["textFile.rtf"]?.regularFileContents,
newString = NSMutableAttributedString(data: rtfData, options: [NSDocumentTypeDocumentAttribute : NSRTFTextDocumentType], documentAttributes: nil, error: nil) {
text = newString
return true
}
So I get this error
Value of optional type 'NSData?' not unwrapped; did you mean to use '!' or '?'?
And I have to either cast regularFileContents as NSData!
or I have to force unwrap it in the NSMutableAttributedString's initializer (data: rtfData!
).
So then I tried
let rtfData = files["textFile.rtf"]?.regularFileContents
if (rtfData != nil) {
if let newString = NSMutableAttributedString(data: rtfData, options: [NSDocumentTypeDocumentAttribute : NSRTFTextDocumentType], documentAttributes: nil, error: nil) {
text = newString
return true
}
}
Which results in
Cannot find an initializer for type 'NSMutableAttributedString' that accepts an argument list of type '(data: NSData??, options: [String : String], documentAttributes: nil, error: nil)'"
So I have to change the initializer to say data: rtfData!!
, which I'm not even sure what that means.
Or, I can cast regularFileContents as NSData?
, and then I can only use one !
in the initializer.
Update: Here's another thing I’ve tried since posting. I figured the double ?
in NSData??
could be due to me optionally unwrapping the NSFileWrapper (files["textFile.rtf"]?
) so I tried this:
if let rtfWrapper = files["textFile.rtf"],
rtfData = rtfWrapper.regularFileContents,
newString = NSMutableAttributedString(data: rtfData, options: [NSDocumentTypeDocumentAttribute : NSRTFTextDocumentType], documentAttributes: nil, error: nil) {
text = newString
return true
}
The compiler still wants me to force unwrap NSData.
I am pretty thoroughly confused.
What is NSData??
, why is it “doubly” optional, why is it the result of .regularFileContents
, and how can I safely, without !
-ing all the way, get the NSData and pass it to the NSMutableAttributedString’s intializer?
Another update
I figured maybe the files
constant could be the source of another optional wrapping:
let files = fileWrapper.fileWrappers
But the compiler won't let me if let
it. If I try, for example,
if let files = fileWrapper.fileWrappers {
if let rtfFile = files["textFile.rtf"] {
if let rtfData = rtfFile.regularFileContents {
if let newString = NSMutableAttributedString(data: rtfData, options: [NSDocumentTypeDocumentAttribute : NSRTFTextDocumentType], documentAttributes: nil, error: nil) {
text = newString
return true
}
}
}
}
The compiler gives this error:
Bound value in a conditional binding must be of Optional type
About fileWrapper.fileWrappers
The fileWrapper
variable is a parameter of the method, which is
override func readFromFileWrapper(fileWrapper: NSFileWrapper, ofType typeName: String, error outError: NSErrorPointer) -> Bool
Upvotes: 1
Views: 2894
Reputation: 438277
You can use optional binding for the file first, and then proceed from there:
if let file = files["textFile.rtf"],
contents = file.regularFileContents,
newString = NSMutableAttributedString(data: contents, options: [NSDocumentTypeDocumentAttribute : NSRTFTextDocumentType], documentAttributes: nil, error: nil) {
text = newString
return true
}
If you're using fileWrappers
dictionary (a [NSObject : AnyObject]
), you have to do some additional casting. This example is using a file I happen to have in that file wrapper, but hopefully it illustrates the idea:
var text: NSString!
func someMethod(fileWrapper: NSFileWrapper) {
let files = fileWrapper.fileWrappers
if let file = files["test.json"] as? NSFileWrapper,
contents = file.regularFileContents,
newString = NSString(data: contents, encoding: NSUTF8StringEncoding) {
text = newString
println(text)
}
}
Upvotes: 1
Reputation: 10091
Your line:
files["textFile.rtf"]?.regularFileContents
Returns a doubly-wrapped optional. One for the dictionary look up, one for the .regularFileContents
. When you did the if let
, you unwrapped it once: that's why your error was:
// Value of optional type 'NSData?' not unwrapped; did you mean to use '!' or '?'?
But then, on your second version, you just check if rtfData
is nil - you don't unwrap it:
if (rtfData != nil)
Which gives you the doubly-wrapped optional.
The easiest way to get around it is to use flatMap
:
if let rtfData = files["textFile.rtf"].flatMap({$0.regularFileContents}) {
text = NSMutableAttributedString(data: rtfData, options: [NSDocumentTypeDocumentAttribute : NSRTFTextDocumentType], documentAttributes: nil, error: nil)
return true
}
flatMap
takes an optional, and unwraps it to put it into a function that returns an optional. If either the original optional, or the value returned from the function, evaluates to nil, the whole thing evaluates to nil. But the value it returns is only singly wrapped.
What flatMap
is doing is basically:
if let lookup = files["textFile.rtf"] {
if let rtfData = lookup.regularFileContents {
let newString = NSMutableAttributedString(data: rtfData, options: [NSDocumentTypeDocumentAttribute : NSRTFTextDocumentType], documentAttributes: nil, error: nil)
text = newString
return true
}
}
This code would work as well. Both methods safely unwrap rtfData
twice.
Upvotes: 1