NSRange to Range <문자열. 색인>


258

어떻게 변환 할 수 있습니다 NSRangeRange<String.Index>스위프트에?

다음 UITextFieldDelegate방법 을 사용하고 싶습니다 .

    func textField(textField: UITextField!,
        shouldChangeCharactersInRange range: NSRange,
        replacementString string: String!) -> Bool {

textField.text.stringByReplacingCharactersInRange(???, withString: string)

여기에 이미지 설명을 입력하십시오


71
Apple에 새로운 Range 클래스를 제공 한 후 NSRegularExpression과 같은 문자열 유틸리티 클래스를 업데이트하지 않고 사용하도록 감사합니다.
puzzl

답변:


269

NSString버전 (Swift String과 반대)은을 replacingCharacters(in: NSRange, with: NSString)허용 NSRange하므로 간단한 해결책은 first 로 변환 String하는 것NSString 입니다. Swift 3과 2에서는 델리게이트 및 대체 메소드 이름이 약간 다르므로 사용중인 Swift에 따라 다릅니다.

스위프트 3.0

func textField(_ textField: UITextField,
               shouldChangeCharactersIn range: NSRange,
               replacementString string: String) -> Bool {

  let nsString = textField.text as NSString?
  let newString = nsString?.replacingCharacters(in: range, with: string)
}

스위프트 2.x

func textField(textField: UITextField,
               shouldChangeCharactersInRange range: NSRange,
               replacementString string: String) -> Bool {

    let nsString = textField.text as NSString?
    let newString = nsString?.stringByReplacingCharactersInRange(range, withString: string)
}

14
textField이모티콘과 같은 다중 코드 단위 유니 코드 문자 가 포함 된 경우 에는 작동하지 않습니다 . 입력 필드이므로 사용자는 이모티콘 (😂), 다른 페이지 1 문자 (예 : 플래그 (🇪🇸))를 입력 할 수 있습니다.
zaph December

2
@ Zaph : 범위는 NSString에 대해 Obj-C로 작성된 UITextField에서 가져 오기 때문에 문자열의 유니 코드 문자를 기반으로 한 유효한 범위 만 대리자 콜백에서 제공 될 것으로 의심되므로 사용하는 것이 안전합니다. 하지만 개인적으로 테스트하지는 않았습니다.
Alex Pretzlav

2
@ Zaph, 사실은, 요점은 UITextFieldDelegate' textField:shouldChangeCharactersInRange:replacementString:콜백은 키보드에서 문자를 입력하는 사용자를 기반으로하기 때문에 유니 코드 문자 내에서 시작하거나 끝나는 범위를 제공하지 않으며 일부만 입력 할 수 없기 때문에 이모티콘 유니 코드 문자 대리자가 Obj-C로 작성된 경우에도 동일한 문제가 발생합니다.
Alex Pretzlav

4
가장 좋은 대답은 아닙니다. 코드를 읽는 것이 가장 쉽지만 @ martin-r은 정답입니다.
Rog

3
실제로 당신은 이것을해야합니다(textField.text as NSString?)?.stringByReplacingCharactersInRange(range, withString: string)
Wanbok Choi

361

현재 스위프트 4 (엑스 코드 9), 스위프트 표준 라이브러리는 스위프트 문자열 범위 (사이의 변환 방법 제공 Range<String.Index>) 및 NSString범위 ( NSRange). 예:

let str = "a👿b🇩🇪c"
let r1 = str.range(of: "🇩🇪")!

// String range to NSRange:
let n1 = NSRange(r1, in: str)
print((str as NSString).substring(with: n1)) // 🇩🇪

// NSRange back to String range:
let r2 = Range(n1, in: str)!
print(str[r2]) // 🇩🇪

따라서 텍스트 필드 위임 방법에서 텍스트 교체는 이제 다음과 같이 수행 할 수 있습니다

func textField(_ textField: UITextField,
               shouldChangeCharactersIn range: NSRange,
               replacementString string: String) -> Bool {

    if let oldString = textField.text {
        let newString = oldString.replacingCharacters(in: Range(range, in: oldString)!,
                                                      with: string)
        // ...
    }
    // ...
}

(Swift 3 및 이전 버전에 대한 이전 답변 :)

Swift 1.2부터는 String.Index초기화 프로그램이 있습니다

init?(_ utf16Index: UTF16Index, within characters: String)

중간 변환없이 다음 NSRange으로 Range<String.Index>올바르게 변환 하는 데 사용할 수 있습니다 (이모 지, 지역 지표 또는 기타 확장 된 그래 핀 군집 포함) NSString.

extension String {
    func rangeFromNSRange(nsRange : NSRange) -> Range<String.Index>? {
        let from16 = advance(utf16.startIndex, nsRange.location, utf16.endIndex)
        let to16 = advance(from16, nsRange.length, utf16.endIndex)
        if let from = String.Index(from16, within: self),
            let to = String.Index(to16, within: self) {
                return from ..< to
        }
        return nil
    }
}

이 메소드는 주어진 Swift 문자열에 대해 모든 s가 유효 하지 않기 때문에 선택적 문자열 범위를 리턴 NSRange합니다.

UITextFieldDelegate위임 방법은 다음과 같이 쓸 수있다

func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {

    if let swRange = textField.text.rangeFromNSRange(range) {
        let newString = textField.text.stringByReplacingCharactersInRange(swRange, withString: string)
        // ...
    }
    return true
}

역변환은

extension String {
    func NSRangeFromRange(range : Range<String.Index>) -> NSRange {
        let utf16view = self.utf16
        let from = String.UTF16View.Index(range.startIndex, within: utf16view) 
        let to = String.UTF16View.Index(range.endIndex, within: utf16view)
        return NSMakeRange(from - utf16view.startIndex, to - from)
    }
}

간단한 테스트 :

let str = "a👿b🇩🇪c"
let r1 = str.rangeOfString("🇩🇪")!

// String range to NSRange:
let n1 = str.NSRangeFromRange(r1)
println((str as NSString).substringWithRange(n1)) // 🇩🇪

// NSRange back to String range:
let r2 = str.rangeFromNSRange(n1)!
println(str.substringWithRange(r2)) // 🇩🇪

스위프트 2 업데이트 :

Swift 2 버전은 rangeFromNSRange()Serhii Yakovenko가 이미이 답변 에서 제공했습니다. 완전성을 위해 여기에 포함 시킵니다 .

extension String {
    func rangeFromNSRange(nsRange : NSRange) -> Range<String.Index>? {
        let from16 = utf16.startIndex.advancedBy(nsRange.location, limit: utf16.endIndex)
        let to16 = from16.advancedBy(nsRange.length, limit: utf16.endIndex)
        if let from = String.Index(from16, within: self),
            let to = String.Index(to16, within: self) {
                return from ..< to
        }
        return nil
    }
}

스위프트 2 버전 NSRangeFromRange()

extension String {
    func NSRangeFromRange(range : Range<String.Index>) -> NSRange {
        let utf16view = self.utf16
        let from = String.UTF16View.Index(range.startIndex, within: utf16view)
        let to = String.UTF16View.Index(range.endIndex, within: utf16view)
        return NSMakeRange(utf16view.startIndex.distanceTo(from), from.distanceTo(to))
    }
}

스위프트 3 업데이트 (Xcode 8) :

extension String {
    func nsRange(from range: Range<String.Index>) -> NSRange {
        let from = range.lowerBound.samePosition(in: utf16)
        let to = range.upperBound.samePosition(in: utf16)
        return NSRange(location: utf16.distance(from: utf16.startIndex, to: from),
                       length: utf16.distance(from: from, to: to))
    }
}

extension String {
    func range(from nsRange: NSRange) -> Range<String.Index>? {
        guard
            let from16 = utf16.index(utf16.startIndex, offsetBy: nsRange.location, limitedBy: utf16.endIndex),
            let to16 = utf16.index(utf16.startIndex, offsetBy: nsRange.location + nsRange.length, limitedBy: utf16.endIndex),
            let from = from16.samePosition(in: self),
            let to = to16.samePosition(in: self)
            else { return nil }
        return from ..< to
    }
}

예:

let str = "a👿b🇩🇪c"
let r1 = str.range(of: "🇩🇪")!

// String range to NSRange:
let n1 = str.nsRange(from: r1)
print((str as NSString).substring(with: n1)) // 🇩🇪

// NSRange back to String range:
let r2 = str.range(from: n1)!
print(str.substring(with: r2)) // 🇩🇪

7
이 코드는 하나 이상의 UTF-16 코드 포인트 (예 : 이모티콘)로 구성된 문자에 대해 올바르게 작동하지 않습니다. - 텍스트 필드에 텍스트를 "👿"를 포함하고 해당 문자를 삭제하는 경우 예를 들어, 다음 range입니다 (0,2)때문에 NSRange에서 UTF-16 문자를 말합니다 NSString. 그러나 Swift 문자열에서 하나의 유니 코드 문자 로 계산됩니다 . – let end = advance(start, range.length)이 경우에는 "치명적 오류 : endIndex를 증가시킬 수 없습니다"라는 오류 메시지와 함께 참조 답변에서 충돌이 발생합니다.
Martin R

8
@MattDiPasquale : 물론입니다. 내 의도는 그대로 질문에 대답했다 "나는 스위프트의 범위 <String.Index>에 NSRange을 변환 할 수있는 방법" 그 사람이 :( 지금까지의 경우하지 않은, 유용 찾을 수있는 희망 (유니 코드 안전한 방법을
마틴 R

5
애플의 노력에 다시 한번 감사드립니다. 당신과 같은 사람들과 스택 오버플로가 없다면 iOS / OS X 개발을 할 방법이 없습니다. 인생이 너무 짧아
Womble

1
@ jiminybob99 : 명령을 클릭 String하여 API 참조로 이동하고 모든 메소드와 주석을 읽은 후 작동 할 때까지 다른 작업을 시도하십시오. :)
Martin R

1
에 대해 let from16 = utf16.index(utf16.startIndex, offsetBy: nsRange.location, limitedBy: utf16.endIndex), 나는 당신이 실제로 그것을 제한하고 싶다고 생각합니다 utf16.endIndex - 1. 그렇지 않으면 문자열의 끝에서 시작할 수 있습니다.
Michael Tsai

18

Range<String.Index>클래식 대신 사용해야 합니다 NSRange. 내가하는 방법 (아마도 더 좋은 방법이있을 수 있음)은 문자열을 String.Index이동시키는 것입니다 advance.

대체하려는 범위를 모르지만 처음 두 문자를 바꾸려는 척하십시오.

var start = textField.text.startIndex // Start at the string's start index
var end = advance(textField.text.startIndex, 2) // Take start index and advance 2 characters forward
var range: Range<String.Index> = Range<String.Index>(start: start,end: end)

textField.text.stringByReplacingCharactersInRange(range, withString: string)

18

Martin R의 대답 은 유니 코드를 설명하기 때문에 올바른 것 같습니다.

그러나 게시물 (Swift 1) 당시 그의 코드는 advance()기능 을 제거했기 때문에 Swift 2.0 (Xcode 7)에서 컴파일되지 않습니다 . 업데이트 된 버전은 다음과 같습니다.

스위프트 2

extension String {
    func rangeFromNSRange(nsRange : NSRange) -> Range<String.Index>? {
        let from16 = utf16.startIndex.advancedBy(nsRange.location, limit: utf16.endIndex)
        let to16 = from16.advancedBy(nsRange.length, limit: utf16.endIndex)
        if let from = String.Index(from16, within: self),
            let to = String.Index(to16, within: self) {
                return from ..< to
        }
        return nil
    }
}

스위프트 3

extension String {
    func rangeFromNSRange(nsRange : NSRange) -> Range<String.Index>? {
        if let from16 = utf16.index(utf16.startIndex, offsetBy: nsRange.location, limitedBy: utf16.endIndex),
            let to16 = utf16.index(from16, offsetBy: nsRange.length, limitedBy: utf16.endIndex),
            let from = String.Index(from16, within: self),
            let to = String.Index(to16, within: self) {
                return from ..< to
        }
        return nil
    }
}

스위프트 4

extension String {
    func rangeFromNSRange(nsRange : NSRange) -> Range<String.Index>? {
        return Range(nsRange, in: self)
    }
}

7

당신은 변환하는 방법을 구체적으로 요청 때문에 그러나 에밀리의 대답과 유사 NSRangeRange<String.Index>당신이 이런 짓을 할 것이다 :

func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {

     let start = advance(textField.text.startIndex, range.location) 
     let end = advance(start, range.length) 
     let swiftRange = Range<String.Index>(start: start, end: end) 
     ...

}

2
문자열의 문자보기와 UTF16보기는 길이가 다를 수 있습니다. 이 함수는 NSRange문자열의 문자보기에 대해 UTF16 색인 ( 구성 요소는 정수임)을 사용하고 있습니다. 이는 하나 이상의 UTF16 단위를 사용하여 표현하는 "문자"가있는 문자열에서 사용될 때 실패 할 수 있습니다.
네이트 쿡

6

@Emilie의 훌륭한 답변에 대한 리프로 대체 / 경쟁 답변이 아닙니다.
(Xcode6-Beta5)

var original    = "🇪🇸😂This is a test"
var replacement = "!"

var startIndex = advance(original.startIndex, 1) // Start at the second character
var endIndex   = advance(startIndex, 2) // point ahead two characters
var range      = Range(start:startIndex, end:endIndex)
var final = original.stringByReplacingCharactersInRange(range, withString:replacement)

println("start index: \(startIndex)")
println("end index:   \(endIndex)")
println("range:       \(range)")
println("original:    \(original)")
println("final:       \(final)")

산출:

start index: 4
end index:   7
range:       4..<7
original:    🇪🇸😂This is a test
final:       🇪🇸!his is a test

색인은 여러 코드 단위를 설명합니다. 플래그 (Regional INDICATOR SYMBOL LETTERS ES)는 8 바이트이고 (FACE WITH TOYS OF JOY)는 4 바이트입니다. (이 특별한 경우 바이트 수는 UTF-8, UTF-16 및 UTF-32 표현에서 동일하다는 것이 밝혀졌습니다.)

기능으로 감싸기 :

func replaceString(#string:String, #with:String, #start:Int, #length:Int) ->String {
    var startIndex = advance(original.startIndex, start) // Start at the second character
    var endIndex   = advance(startIndex, length) // point ahead two characters
    var range      = Range(start:startIndex, end:endIndex)
    var final = original.stringByReplacingCharactersInRange(range, withString: replacement)
    return final
}

var newString = replaceString(string:original, with:replacement, start:1, length:2)
println("newString:\(newString)")

산출:

newString: !his is a test

4
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {

       let strString = ((textField.text)! as NSString).stringByReplacingCharactersInRange(range, withString: string)

 }

2

Swift 2.0에서 다음을 가정합니다 func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {.

var oldString = textfield.text!
let newRange = oldString.startIndex.advancedBy(range.location)..<oldString.startIndex.advancedBy(range.location + range.length)
let newString = oldString.stringByReplacingCharactersInRange(newRange, withString: string)

1

최선을 다하겠습니다. 그러나 이것은 잘못된 입력 인수를 확인하거나 감지 할 수 없습니다.

extension String {
    /// :r: Must correctly select proper UTF-16 code-unit range. Wrong range will produce wrong result.
    public func convertRangeFromNSRange(r:NSRange) -> Range<String.Index> {
        let a   =   (self as NSString).substringToIndex(r.location)
        let b   =   (self as NSString).substringWithRange(r)

        let n1  =   distance(a.startIndex, a.endIndex)
        let n2  =   distance(b.startIndex, b.endIndex)

        let i1  =   advance(startIndex, n1)
        let i2  =   advance(i1, n2)

        return  Range<String.Index>(start: i1, end: i2)
    }
}

let s   =   "🇪🇸😂"
println(s[s.convertRangeFromNSRange(NSRange(location: 4, length: 2))])      //  Proper range. Produces correct result.
println(s[s.convertRangeFromNSRange(NSRange(location: 0, length: 4))])      //  Proper range. Produces correct result.
println(s[s.convertRangeFromNSRange(NSRange(location: 0, length: 2))])      //  Improper range. Produces wrong result.
println(s[s.convertRangeFromNSRange(NSRange(location: 0, length: 1))])      //  Improper range. Produces wrong result.

결과.

😂
🇪🇸
🇪🇸
🇪🇸

세부

NSRangeNSStringUTF-16 코드 단위 를 계산합니다 . 그리고 Range<String.Index>Swift String는 평등 및 탐색 작업 만 제공 하는 불투명 한 상대 유형입니다. 이것은 의도적으로 숨겨진 디자인입니다.

(가) 비록 Range<String.Index>오프셋 UTF-16 코드 단위로 매핑 할 것, 그건 그냥 구현 세부이며, 나는 어떤 보장에 대한 어떤 언급을 찾을 수 없습니다. 즉, 구현 세부 사항을 언제든지 변경할 수 있습니다. Swift의 내부 표현은 String명확하게 정의되어 있지 않으므로 신뢰할 수 없습니다.

NSRange값을 String.UTF16View인덱스에 직접 매핑 할 수 있습니다 . 그러나로 변환하는 방법은 없습니다 String.Index.

스위프트 String.Index스위프트 반복하는 인덱스 Character입니다 유니 코드 그래 핀 클러스터를 . 그런 다음 올바른 그래 NSRange핀 군집을 선택 하는 적절한 항목 을 제공해야 합니다. 위의 예제와 같이 잘못된 범위를 제공하면 적절한 grapheme 클러스터 범위를 알 수 없으므로 잘못된 결과가 생성됩니다.

(가) 있다는 보장이 있다면 String.Index 입니다 UTF-16 코드 단위의 오프셋 (offset)는, 다음 문제는 간단해진다. 그러나 일어날 것 같지 않습니다.

역변환

어쨌든 역변환은 정확하게 수행 될 수있다.

extension String {
    /// O(1) if `self` is optimised to use UTF-16.
    /// O(n) otherwise.
    public func convertRangeToNSRange(r:Range<String.Index>) -> NSRange {
        let a   =   substringToIndex(r.startIndex)
        let b   =   substringWithRange(r)

        return  NSRange(location: a.utf16Count, length: b.utf16Count)
    }
}
println(convertRangeToNSRange(s.startIndex..<s.endIndex))
println(convertRangeToNSRange(s.startIndex.successor()..<s.endIndex))

결과.

(0,6)
(4,2)

스위프트 2 년 return NSRange(location: a.utf16Count, length: b.utf16Count)으로 변경해야합니다return NSRange(location: a.utf16.count, length: b.utf16.count)
엘리엇

1

가장 깨끗한 swift2 전용 솔루션은 NSRange에서 카테고리를 만드는 것입니다.

extension NSRange {
    func stringRangeForText(string: String) -> Range<String.Index> {
        let start = string.startIndex.advancedBy(self.location)
        let end = start.advancedBy(self.length)
        return Range<String.Index>(start: start, end: end)
    }
}

그런 다음 텍스트 필드 대리자 함수에서 호출하십시오.

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

    // your code goes here....

    return true
}

최신 Swift2 업데이트 후에 내 코드가 더 이상 작동하지 않는 것으로 나타났습니다. Swift2와 함께 작동하도록 답변을 업데이트했습니다.
Danny Bravo

0

스위프트 3.0 베타 공식 문서는 제목 아래이 상황에 대한 표준 솔루션을 제공하고있다 String.UTF16View 섹션에 UTF16View 요소 일치는 NSString 문자 제목


답변에 링크를 제공하면 도움이 될 것입니다.
m5khan 2016 년

0

허용 된 답변에서 선택 사항이 번거 롭습니다. 이것은 Swift 3에서 작동하며 이모티콘에는 문제가없는 것 같습니다.

func textField(_ textField: UITextField, 
      shouldChangeCharactersIn range: NSRange, 
      replacementString string: String) -> Bool {

  guard let value = textField.text else {return false} // there may be a reason for returning true in this case but I can't think of it
  // now value is a String, not an optional String

  let valueAfterChange = (value as NSString).replacingCharacters(in: range, with: string)
  // valueAfterChange is a String, not an optional String

  // now do whatever processing is required

  return true  // or false, as required
}

0
extension StringProtocol where Index == String.Index {

    func nsRange(of string: String) -> NSRange? {
        guard let range = self.range(of: string) else {  return nil }
        return NSRange(range, in: self)
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.