Swift Array to String problem

Discussion in 'Mac Programming' started by larswik, Mar 16, 2015.

  1. larswik macrumors 68000

    Joined:
    Sep 8, 2006
    #1
    I am getting an error message
    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")
                    }
                }
            }
        }
    
    
     
  2. ArtOfWarfare macrumors 604

    ArtOfWarfare

    Joined:
    Nov 26, 2007
    #2
    Shouldn't nameArray be a [String]
    ? (Line break added so it doesn't look like I'm suggesting it's an optional).
     
  3. larswik thread starter macrumors 68000

    Joined:
    Sep 8, 2006
    #3
    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
    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 :)
     

    Attached Files:

  4. lee1210 macrumors 68040

    lee1210

    Joined:
    Jan 10, 2005
    Location:
    Dallas, TX
    #4
    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
     
  5. larswik thread starter macrumors 68000

    Joined:
    Sep 8, 2006
    #5
    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")
                    }
                }
            }
        }
    
    
     

    Attached Files:

  6. chown33 macrumors 604

    Joined:
    Aug 9, 2009
    #6
    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?
     
  7. larswik thread starter macrumors 68000

    Joined:
    Sep 8, 2006
    #7
    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")
                    }
                }
            }
        }
    
    
     
  8. snookums, Mar 26, 2015
    Last edited: Mar 26, 2015

    snookums macrumors newbie

    Joined:
    Mar 26, 2015
    #8
    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
     
  9. larswik thread starter macrumors 68000

    Joined:
    Sep 8, 2006
    #9
    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.
     
  10. snookums macrumors newbie

    Joined:
    Mar 26, 2015
    #10
    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.

    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:

     

Share This Page