1

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
magiclantern
  • 768
  • 5
  • 19

2 Answers2

1

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.

oisdk
  • 9,763
  • 4
  • 18
  • 36
  • Hmm. Stil getting **Value of optional type 'NSData?' not unwrapped; did you mean to use '!' or '?'?** at the `data: rtfData` part of the initializer – magiclantern May 31 '15 at 17:44
  • Changed it - the newString shouldn't have been inside the if let statement. – oisdk May 31 '15 at 17:51
  • Same thing; still getting the same compiler error. Could it be the `files` constant itself? Check out the latest update to my question – magiclantern May 31 '15 at 17:54
  • Thanks. I marked Rob's answer as the correct answer as it resolved my particular issue but I appreciate your insight regarding `flatMap`. – magiclantern May 31 '15 at 18:13
  • Yeah, I completely missed the problem with the casting. @Rob 's answer is on the money. – oisdk May 31 '15 at 18:19
1

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)
    }
}
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Tried that, it didn't work either. In your snippet, the compiler would complain about `data: contents` and would only accept `data: contents!` – magiclantern May 31 '15 at 17:39
  • @kamera No, it wouldn't complain about `contents` being an optional, because it's not (we're doing optional binding here; this is why I broke it into two assignments, so each optional binding could take place in succession). I just tested it and it worked fine. – Rob May 31 '15 at 17:49
  • Could it be that my `files` constant is the issue? Where are you getting it in your snippet? Check out the update to my question. – magiclantern May 31 '15 at 17:52
  • Thanks. Is that iOS code? I wonder if my issue is specific to OS X. – magiclantern May 31 '15 at 18:10
  • Yes, but I updated my answer with more generic example and using `fileWrappers`. If nothing else, and additional issue is that `fileWrappers` is a `[NSObject : AnyObject]` dictionary so some additional casting is needed. – Rob May 31 '15 at 18:10
  • 1
    Yes! `if let file = files["test.json"] as? NSFileWrapper` was the ticket. Thank you! – magiclantern May 31 '15 at 18:12