Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

larswik

macrumors 68000
Original poster
Sep 8, 2006
1,552
11
I am getting an error message
Type '[AnyObject]?' does not conform to protocol 'SequenceType'

when I click on a button the NSOpenPanel comes up. I select a txt file and then I create a string with the contents of the txt file. I then proceed to remove all new lines and white spaces and the return type from this is an array. So far so good. But now I want to write the array out to a txt file again. I am trying to use each index of the array to be a new line in the text document. The problem looks like a type casting issue? The problem is the line highlighted in red. I am unsure how to resolve this issue?

When I set break points I can see everything is correct including the nameArray, each name is in it's own index. What am I doing wrong?

Code:
@IBAction func importButton(sender: NSButton){ // Opens up the window to select a file
        var openPanel = NSOpenPanel()
        openPanel.allowsMultipleSelection = false
        openPanel.canChooseDirectories = false
        openPanel.canCreateDirectories = false
        openPanel.canChooseFiles = true
        openPanel.beginWithCompletionHandler { (result) -> Void in
            if result == NSFileHandlingPanelOKButton {
                let URLString = openPanel.URL
                let pathStringFromURL = URLString?.absoluteString
                let checkExt = pathStringFromURL?.pathExtension
                
                if(checkExt == "txt"){
                    let documentDirectoryURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first! as NSURL
                    let fileDestinationUrl = documentDirectoryURL.URLByAppendingPathComponent("names2names.txt")
                    
                    
                    var inString = NSString(contentsOfURL: URLString!, encoding: NSUTF8StringEncoding, error: nil)
                    
    
                    let nameArray = inString?.componentsSeparatedByCharactersInSet(NSCharacterSet(charactersInString:" ,\n"))
                    
                    [B][COLOR="Red"]let str = "\n".join(nameArray)[/COLOR][/B]
                    
                    str.writeToURL(fileDestinationUrl, atomically: true, encoding: NSUTF8StringEncoding, error: nil)
                    
                    println(nameArray!)
                }
                else{
                    println("NO, not equal to")
                }
            }
        }
    }
 

ArtOfWarfare

macrumors G3
Nov 26, 2007
9,597
6,116
Shouldn't nameArray be a [String]
? (Line break added so it doesn't look like I'm suggesting it's an optional).
 

larswik

macrumors 68000
Original poster
Sep 8, 2006
1,552
11
Hey AOW, thanks for the reply. The return value from
Code:
componentsSeparatedByCharactersInSet
returns an array. So nameArray is an array of NSStrings. I included a photo at a break point to show what is in the array.

If I remove the question mark, also know as the optional, from the inString? I get an error message that
'NSString?' does not have a member named 'componentsSeparatedByCharactersInSet'
But in the line above that one where I create the var inString = , that method returns an NSString.

I am assuming that in the image the nameArray says [AnyObject] because I have the optional in behind the inString? If I remove it that line of code will not work. The NSString class can't see the method that is one of it's methinds componentsSeparatedByCharactersInSet

Confused? <-not an optional :)
 

Attachments

  • Screen Shot 2015-03-17 at 3.11.33 PM.png
    Screen Shot 2015-03-17 at 3.11.33 PM.png
    57.7 KB · Views: 259

lee1210

macrumors 68040
Jan 10, 2005
3,182
3
Dallas, TX
http://stackoverflow.com/questions/25581324/swift-how-can-string-join-work-custom-types

It looks like join needs something a little more explicit than the array you've got there. It doesn't know there are NSString's in there. I don't know Swift at all despite having read about half the book, but the consensus seems to be that this:
Code:
let str = “, “.join(x.map({ String($0) });

Which i would call nonsense, generally, is the easiest way to do what you want to do. I honestly think that this is going to create new strings from the strings already in the array, which seems super wasteful. There are other suggestions to create protocols on NSString/String to handle this, which seems a little nutty, too. Maybe:
Code:
let nameArray = inString?.componentsSeparatedByCharactersInSet(NSCharacterSet(charactersInString:" ,\n"))
could be:
Code:
let nameArray : NSArray? = inString?.componentsSeparatedByCharactersInSet(NSCharacterSet(charactersInString:" ,\n"))

I have to imagine that NSArray is a SequenceType and will work with join, but maybe I'm wrong. Don't have an environment to play in, so this is just speculation. Good luck.

-Lee
 

larswik

macrumors 68000
Original poster
Sep 8, 2006
1,552
11
http://stackoverflow.com/questions/25581324/swift-how-can-string-join-work-custom-types

It looks like join needs something a little more explicit than the array you've got there. It doesn't know there are NSString's in there. I don't know Swift at all despite having read about half the book, but the consensus seems to be that this:
Code:
let str = “, “.join(x.map({ String($0) });

Which i would call nonsense, generally, is the easiest way to do what you want to do. I honestly think that this is going to create new strings from the strings already in the array, which seems super wasteful. There are other suggestions to create protocols on NSString/String to handle this, which seems a little nutty, too. Maybe:
Code:
let nameArray = inString?.componentsSeparatedByCharactersInSet(NSCharacterSet(charactersInString:" ,\n"))
could be:
Code:
let nameArray : NSArray? = inString?.componentsSeparatedByCharactersInSet(NSCharacterSet(charactersInString:" ,\n"))

I have to imagine that NSArray is a SequenceType and will work with join, but maybe I'm wrong. Don't have an environment to play in, so this is just speculation. Good luck.

-Lee

Thanks Lee. I That worked for that line of code but the join function was still a problem. I waited to reply to see if I could search the web to find out more. String and NSString are interchangeable from what I read. I tried to re write the code to concatenate but got a bad access error when running the code.

Here is the full code for the IBAction if anyone would like to help me discover the problem, Thanks.

Code:
@IBAction func importButton(sender: NSButton){ // Opens up the window to select a file
        var openPanel = NSOpenPanel()
        openPanel.allowsMultipleSelection = false
        openPanel.canChooseDirectories = false
        openPanel.canCreateDirectories = false
        openPanel.canChooseFiles = true
        openPanel.beginWithCompletionHandler { (result) -> Void in
            
            if result == NSFileHandlingPanelOKButton {
                let URLString = openPanel.URL
                let pathStringFromURL = URLString?.absoluteString
                let checkExt = pathStringFromURL?.pathExtension
                var writeString = ""
                
                if(checkExt == "txt"){
                    let documentDirectoryURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first! as NSURL
                    let fileDestinationUrl = documentDirectoryURL.URLByAppendingPathComponent("names2names.txt")
                    
                    var inString : NSString = NSString(contentsOfURL: URLString!, encoding: NSUTF8StringEncoding, error: nil)!
                
                    let nameArray : NSArray = inString.componentsSeparatedByCharactersInSet(NSCharacterSet(charactersInString:" ,\n"))
                    
                    for str in nameArray{
                        if writeString.isEmpty{
                            writeString = str.name
                        }
                        else{
                           writeString += str.name
                        }
                        
                    }
                    
                    //let str = "\n".join(nameArray)
                    
                    //str.writeToURL(fileDestinationUrl, atomically: true, encoding: NSUTF8StringEncoding, error: nil)
                    
                    println(writeString)
                }
                else{
                    println("NO, not equal to")
                }
            }
        }
    }
 

Attachments

  • Screen Shot 2015-03-20 at 1.36.39 PM.png
    Screen Shot 2015-03-20 at 1.36.39 PM.png
    106.7 KB · Views: 277

chown33

Moderator
Staff member
Aug 9, 2009
10,933
8,789
A sea of green
Does the Swift String type have a name property?

If not, then this code is wrong:
Code:
writeString = str.name

Does the Swift type NSString have a name property?
 

larswik

macrumors 68000
Original poster
Sep 8, 2006
1,552
11
Does the Swift String type have a name property?

If not, then this code is wrong:
Code:
writeString = str.name

Does the Swift type NSString have a name property?

Thanks for the reply Chown33. I spent a few days to do some more reading and trying to wrap my head around this. I finally got this code working and although Swift seems simple because it is less wordy It's been a pain for me. What I am still grasping with in Swift are the Optionals like the question mark ? and then needing to use the Exclamation point ! that unwraps the optionals to get to the value. knowing when to use these has been tough to understand yet. But here is the functioning code.

Code:
@IBAction func importButton(sender: NSButton){ // Opens up the window to select a file
        var openPanel = NSOpenPanel()
        openPanel.allowsMultipleSelection = false
        openPanel.canChooseDirectories = false
        openPanel.canCreateDirectories = false
        openPanel.canChooseFiles = true
        openPanel.beginWithCompletionHandler { (result) -> Void in
            
            if result == NSFileHandlingPanelOKButton {
                let URLString = openPanel.URL
                let pathStringFromURL = URLString?.absoluteString
                let checkExt = pathStringFromURL?.pathExtension
                var writeString = ""
                var error:NSError?
                let array = [1,2,3,4]
                
                
                if(checkExt == "txt"){
                    let documentDirectoryURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first! as NSURL
                    let fileDestinationUrl = documentDirectoryURL.URLByAppendingPathComponent("names2names.txt")
                    
                    var inString = String(contentsOfURL: URLString!, encoding: NSUTF8StringEncoding, error: &error)
                    let nameArray = inString?.componentsSeparatedByCharactersInSet(NSCharacterSet(charactersInString:" ,\n"))
                    
                    var name1 = nameArray?[0]
                    
                    for str in nameArray!{
                        if writeString.isEmpty{
                            writeString = str as NSString
                        }
                        else{
                            writeString += "\n" // add new line to string
                           writeString += str as NSString
                        }
                        
                    }
                    
                    writeString.writeToURL(fileDestinationUrl, atomically: true, encoding: NSUTF8StringEncoding, error: nil)
                    
                    println(writeString)
                }
                else{
                    println("File type not equal to txt")
                }
            }
        }
    }
 

snookums

macrumors newbie
Mar 26, 2015
2
0
Hello. I'm just learning swift myself, but I think I can answer your original question (based on the code in your initial post):

NSString's componentsSeparatedByCharactersInSet has a return type of [AnyObject] (that is, an array of AnyObjects). While this is not an optional type, you are using optional chaining to get at it with the "inString?." part of the code. Since inString might be nil, swift will infer the type of nameArray to be an optional as well. So you'll have to unwrap it before you can use it. Also, the swift String's join method wants an array of swift Strings, not an array of AnyObjects, so you'll have to cast them. Since you can safely assume that componentsSeparatedByCharactersInSet is giving you an array of strings, you can force cast them with the as! operator. So your red line of code could simply be written as:

Code:
let str = "\n".join(nameArray! as! [String])

I tried this in a playground and it worked fine. No need for using map or anything like that. "nameArray!" unwraps the optional, and "as! [String]" force casts the whole [AnyObject] array to [String].


You could also use the if-let pattern on nameArray in which case you would not have to explicitly unwrap it in the join:

Code:
if let nameArray = inString?.componentsSeparatedByCharactersInSet(NSCharacterSet(charactersInString:" ,\n")) {
    let str = "\n".join(nameArray as! [String])
    str.writeToURL(fileDestinationUrl, atomically: true, encoding: NSUTF8StringEncoding, error: nil)
    println(nameArray)
}
else {
    println("uh oh, we apparently failed to load our string from the url")
}


Also, notice that you are having to deal with these AnyObject and NSString types because the cocoa frameworks are written in obj-c. The native swift String class also has methods like componentsSeparatedByCharactersInSet, and although it still takes an obj-c NSCharacterSet object as an argument, it only returns native swift types. So the same code (minus the optional) with native swift types and without the casting:

Code:
let myNativeString = "This is a test" // returns swift String
let myNativeStringArray = myNativeString.componentsSeparatedByCharactersInSet(NSCharacterSet(charactersInString: " ,")) // returns [String] 
let str = "\n".join(myNativeStringArray) // no casting required, because swift's String join method wants an input argument of type [String], which it already is

hth
 
Last edited:

larswik

macrumors 68000
Original poster
Sep 8, 2006
1,552
11
Hello. I'm just learning swift myself, but I think I can answer your original question (based on the code in your initial post):
hth
snookums, thanks for that great explanation. The code that Lee posted worked and fixed that line of code but then it broke at a different point after that.

I was looking for a native way to do this in swift but had to reach out to the internet for help. So the code
Code:
componentsSeparatedByCharactersInSet
is a method that is found in the NSString class that returns an NSArray. Since it returns an object from Objective C Cocoa or Foundations it's return type in Swift is then an [AnyObject] that needs to be 'force cast' to comply with the Swift Language?

So for syntax, the C term for '?' is the Ternary operator and is called an Optional in Swift?
Also the 'Not' operator, '!' is called what, the 'unwrapper' or 'as'?

Thanks again for your great explanation on this. It was driving me nuts for days.
 

snookums

macrumors newbie
Mar 26, 2015
2
0
So the code
Code:
componentsSeparatedByCharactersInSet
is a method that is found in the NSString class that returns an NSArray. Since it returns an object from Objective C Cocoa or Foundations it's return type in Swift is then an [AnyObject] that needs to be 'force cast' to comply with the Swift Language?

I wouldn't really say it's "to comply with the swift language" so much as the method signature of join simply requires String types, so in that particular case you have to proceed accordingly.

So for syntax, the C term for '?' is the Ternary operator and is called an Optional in Swift?
Also the 'Not' operator, '!' is called what, the 'unwrapper' or 'as'?

An optional is actually defined as a special kind of enumerated type in swift. It more or less has this form:

Code:
enum Optional<T> {
    case None
    case Some(T)
}

The "!" and "?" operators are just "syntactic sugar" for convenience to save you some keystrokes. There is a pretty good explanation of it here. The part that discusses casting with the "as" operator is somewhat out of date, since swift keeps changing so quickly. You can read an up-to-date explanation of "as", "as?" and "as!" on apple's swift blog here.

There is a nice summary statement at the end of the apple blog post:

It may be easiest to remember the pattern for these operators in Swift as: ! implies “this might trap [i.e., might cause a runtime error-- use with caution],” while ? indicates “this might be nil [i.e., allows you to safely test and fail gracefully].”
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.