1

I have a viewController where I can enter some text into a textField and tap a done button to save it. I only want the done button to be visible if there is text in the textField. In order to do this, I used the delegate method for the UITexfield which fires when it is about to be edited as shown below. As it passes in an NSRange, I can't put that into stringByReplacingCharactersInRange as swift only allows a Range. Therefor I bridged it which allowed me to use the NSRange given. If you know a way to cast an NSRange as a Range, or even better, if you know a more concise and neater way to check if the text field is empty, please let me know. Thanks a lot.

func textField(textField: UITextField!, shouldChangeCharactersInRange range: NSRange, replacementString string: String!) -> Bool {
    let newString = textField.text.bridgeToObjectiveC().stringByReplacingCharactersInRange(range, withString: string)

    if (newString == "" ) {
        self.doneButton.enabled = false
    } else {
        self.doneButton.enabled = true
    }
    return true
}
Kanan Vora
  • 2,124
  • 1
  • 16
  • 26
Jack Chorley
  • 2,309
  • 26
  • 28
  • 1
    possible duplicate of [NSRange to Range](http://stackoverflow.com/questions/25138339/nsrange-to-rangestring-index) – Emilie Aug 05 '14 at 12:52
  • quite possibly, Im just updating the rest of my project to Beta 5 and will see if that link has the answer, thanks for a fast response. – Jack Chorley Aug 05 '14 at 13:06
  • It did indeed answer my question once i had edited the code given by the top answer. – Jack Chorley Aug 05 '14 at 13:14
  • Best test with emoji if there is any possibility the user will enter emoji characters. – zaph Aug 05 '14 at 14:16
  • possible duplicate of [bridgeToObjectiveC and makeObjectsPerformSelector in Swift beta 5](http://stackoverflow.com/questions/25126188/bridgetoobjectivec-and-makeobjectsperformselector-in-swift-beta-5) – rickster Aug 05 '14 at 15:13

3 Answers3

1

Here is a func that will take an NSRange and replace a portion of a String:

func replaceRange(nsRange:NSRange, #ofString:String, #withString:String) ->String {
    let start  = nsRange.location
    let length = nsRange.length
    let endLocation = start + length
    let ofStringLength = countElements(ofString)
    if start < 0 || endLocation < start || endLocation > ofStringLength {
        return ofString
    }
    var startIndex = advance(ofString.startIndex, start)
    var endIndex = advance(startIndex, length)
    var range = Range(start:startIndex, end:endIndex)
    var final = ofString.stringByReplacingCharactersInRange(range, withString:withString)
    return final
}
var original = "This is a test"
var replacement = "!"
var nsRange:NSRange = NSMakeRange(1, 2)
var newString = replaceRange(nsRange, ofString:original, withString:replacement)
println("newString:\(newString)")

Output:

newString:!his is a test
zaph
  • 111,848
  • 21
  • 189
  • 228
  • You probably are assuming that indexes into String and NSString are interchangeable. That is NOT the case. Indexes into a String refer to user-perceived characters while indexes into an NSString refer to UTF-16 code units! – JanX2 Aug 20 '14 at 11:22
  • No I am not assuming the indexes are the same. The NSRange I pass into the method replaceRange is only used as a carrier to pass in integer values location and length of the desired Character positions and it is not used as range. Note that the flag is 4 UTF-16 code units and the "FACE WITH TEARS OF JOY" is 2 UTF-16 code units, I picked these values to demonstrate that the correct indexing is being done. I see where I should have named the two range variables with different names, I have corrected that to remove confusion. – zaph Aug 20 '14 at 11:56
  • Why not use a Range then? This might confuse people otherwise. – JanX2 Aug 20 '14 at 12:15
  • The point of the question was to use `NSRange` with Swift `Strings`. IMO Apple really needs to add some methods to handle the basic string operations that require indexing and correctly handle multi-unit UTF-16 characters in both Swift and Objective-C. As for confusion: multi-unit UTF-16 confuses most developers, few know that unicode is a 21-bit system. Prior to the addition of emoji (plane 1 characters) it was mostly a non-issue for most (not all) developers. – zaph Aug 20 '14 at 12:30
  • Your comments are absolutely correct, but that doesn’t change the fact that using an NSRange you get from Cocoa Touch in the way you describe will not work correctly. – JanX2 Aug 20 '14 at 12:44
  • In what way will it not work correctly? Can you provide an example? – zaph Aug 20 '14 at 13:36
  • The NSRange you get in the textField(…) delegate method is based on UTF-16 code units. So if you got an NSRange(1, 2) in textField(…) it would refer to the middle two code units of the flag instead of both the FACE and the “T”. – JanX2 Aug 22 '14 at 00:00
  • This answer has nothing to do with how you get a `NSRange`, my answer makes a range: `NSMakeRange(1, 2)` which refers to `Character` position. If you get a `NSRange` that refers to the middle of a character that is the problem, Swift or Objective-C, At least here it will return whole `Characters`. – zaph Aug 22 '14 at 02:28
1

Instead of using bridgeToObjectiveC() simply cast your string to an NSString:

let newString = (textField.text as NSString).stringByReplacingCharactersInRange(range, withString: string)
vacawama
  • 150,663
  • 30
  • 266
  • 294
0

Here's what I like to use:

if ([textField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]].length > 0)
{
    // do something with the text that is there ...
}
BonanzaDriver
  • 6,411
  • 5
  • 32
  • 35