스위프트 범위에서 NSRange?


175

문제 : Range를 사용하는 Swift String을 사용하는 동안 NSAttributedString이 NSRange를 사용합니다.

let text = "Long paragraph saying something goes here!"
let textRange = text.startIndex..<text.endIndex
let attributedString = NSMutableAttributedString(string: text)

text.enumerateSubstringsInRange(textRange, options: NSStringEnumerationOptions.ByWords, { (substring, substringRange, enclosingRange, stop) -> () in

    if (substring == "saying") {
        attributedString.addAttribute(NSForegroundColorAttributeName, value: NSColor.redColor(), range: substringRange)
    }
})

다음과 같은 오류가 발생합니다.

오류 : 'Range'는 'NSRange'로 변환 할 수 없습니다 trustedString.addAttribute (NSForegroundColorAttributeName, 값 : NSColor.redColor (), 범위 : substringRange)



2
@Suhaib 그 반대 방향으로 가고 있습니다.
geoff

답변:


262

스위프트 String범위와 NSString범위는 "호환되지 않습니다". 예를 들어, 😄와 같은 이모지는 하나의 스위프트 문자로 간주되지만 두 NSString 문자 (소위 UTF-16 서로 게이트 쌍)로 계산됩니다.

따라서 문자열에 이러한 문자가 포함되어 있으면 제안 된 솔루션에서 예기치 않은 결과가 발생합니다. 예:

let text = "😄😄😄Long paragraph saying!"
let textRange = text.startIndex..<text.endIndex
let attributedString = NSMutableAttributedString(string: text)

text.enumerateSubstringsInRange(textRange, options: NSStringEnumerationOptions.ByWords, { (substring, substringRange, enclosingRange, stop) -> () in
    let start = distance(text.startIndex, substringRange.startIndex)
    let length = distance(substringRange.startIndex, substringRange.endIndex)
    let range = NSMakeRange(start, length)

    if (substring == "saying") {
        attributedString.addAttribute(NSForegroundColorAttributeName, value: NSColor.redColor(), range: range)
    }
})
println(attributedString)

산출:

😄😄😄Long paragra {
} ph 말 {
    NSColor = "NSCalibratedRGBColorSpace 10010 1";
} ing! {
}

보시다시피 "ph say"는 "saying"이 아닌 속성으로 표시되었습니다.

NS(Mutable)AttributedString궁극적으로 NSStringand가 필요하기 때문에 NSRange실제로 주어진 문자열을 NSString먼저 변환하는 것이 좋습니다 . 그런 다음은 substringRange 이며 NSRange더 이상 범위를 변환 할 필요가 없습니다.

let text = "😄😄😄Long paragraph saying!"
let nsText = text as NSString
let textRange = NSMakeRange(0, nsText.length)
let attributedString = NSMutableAttributedString(string: nsText)

nsText.enumerateSubstringsInRange(textRange, options: NSStringEnumerationOptions.ByWords, { (substring, substringRange, enclosingRange, stop) -> () in

    if (substring == "saying") {
        attributedString.addAttribute(NSForegroundColorAttributeName, value: NSColor.redColor(), range: substringRange)
    }
})
println(attributedString)

산출:

😄😄😄 긴 단락 {
}속담{
    NSColor = "NSCalibratedRGBColorSpace 10010 1";
}! {
}

스위프트 2 업데이트 :

let text = "😄😄😄Long paragraph saying!"
let nsText = text as NSString
let textRange = NSMakeRange(0, nsText.length)
let attributedString = NSMutableAttributedString(string: text)

nsText.enumerateSubstringsInRange(textRange, options: .ByWords, usingBlock: {
    (substring, substringRange, _, _) in

    if (substring == "saying") {
        attributedString.addAttribute(NSForegroundColorAttributeName, value: NSColor.redColor(), range: substringRange)
    }
})
print(attributedString)

스위프트 3 업데이트 :

let text = "😄😄😄Long paragraph saying!"
let nsText = text as NSString
let textRange = NSMakeRange(0, nsText.length)
let attributedString = NSMutableAttributedString(string: text)

nsText.enumerateSubstrings(in: textRange, options: .byWords, using: {
    (substring, substringRange, _, _) in

    if (substring == "saying") {
        attributedString.addAttribute(NSForegroundColorAttributeName, value: NSColor.red, range: substringRange)
    }
})
print(attributedString)

스위프트 4 업데이트 :

스위프트 4 (엑스 코드 9)로, 스위프트 표준 라이브러리 사이의 변환 방법을 제공 Range<String.Index>하고이 NSRange. NSString더 이상으로 변환 할 필요가 없습니다.

let text = "😄😄😄Long paragraph saying!"
let attributedString = NSMutableAttributedString(string: text)

text.enumerateSubstrings(in: text.startIndex..<text.endIndex, options: .byWords) {
    (substring, substringRange, _, _) in
    if substring == "saying" {
        attributedString.addAttribute(.foregroundColor, value: NSColor.red,
                                      range: NSRange(substringRange, in: text))
    }
}
print(attributedString)

여기서 substringRangeA는 Range<String.Index>, 그리고 그 대응으로 변환된다 NSRange으로

NSRange(substringRange, in: text)

74
OSX에 이모티콘 문자를 입력하고 싶은 누군가를 위해 - 제어 - 명령 스페이스 바를 문자 선택기를 제공합니다
제이

2
둘 이상의 단어를 일치시키는 경우 작동하지 않으며 전체 문자열이 일치하는지 확실하지 않습니다. API에서 문자열을 가져 와서 다른 문자열 내에서 사용하고 API의 문자열에 밑줄을 표시하려고하면 하위 문자열이 API의 문자열과 다른 문자열 모두에 있지 않을 것이라고 보장 할 수 없습니다 끈! 어떤 아이디어?
simonthumper

NSMakeRange 변경됨 str.substringWithRange (Range <String.Index> (시작 : str.startIndex, 끝 : str.endIndex)) // "안녕하세요, 놀이터"변경 사항
HariKrishnan.P

(또는) 문자열 캐스팅 --- let substring = (문자열을 NSString으로) .substringWithRange (NSMakeRange (start, length))
HariKrishnan.P 2016 년

2
당신은 그 언급 Range<String.Index>NSString호환되지 않습니다. 상대방도 호환되지 않습니까? 즉은 NSRangeString호환되지 않는? Apple의 API 중 하나가 구체적으로 다음 두 가지를 결합합니다 : matches (in : options : range :)
Senseful

56

당신이 묘사 한 것과 같은 경우에는 이것이 효과가 있음을 알았습니다. 비교적 짧고 달콤합니다.

 let attributedString = NSMutableAttributedString(string: "follow the yellow brick road") //can essentially come from a textField.text as well (will need to unwrap though)
 let text = "follow the yellow brick road"
 let str = NSString(string: text) 
 let theRange = str.rangeOfString("yellow")
 attributedString.addAttribute(NSForegroundColorAttributeName, value: UIColor.yellowColor(), range: theRange)


7
@Paludis, 당신은 맞지만이 솔루션은 Swift 범위를 사용하려고하지 않습니다. 를 사용하고 NSRange있습니다. strNSString이므로를 str.RangeOfString()반환합니다 NSRange.
tjpaul

3
라인 2와 3을 다음과 같이 바꾸면 라인 2에서 중복 문자열을 제거 할 수 있습니다.let str = attributedString.string as NSString
Jason Moore

2
이것은 현지화의 악몽입니다.
Sulthan

29

대답은 괜찮지 만 Swift 4를 사용하면 코드를 약간 단순화 할 수 있습니다.

let text = "Test string"
let substring = "string"

let substringRange = text.range(of: substring)!
let nsRange = NSRange(substringRange, in: text)

range기능 의 결과는 풀어야하므로주의하십시오 .


10

가능한 해결책

Swift는 NSRange를 만드는 데 사용할 수있는 시작과 끝 사이의 거리를 측정하는 distance ()를 제공합니다.

let text = "Long paragraph saying something goes here!"
let textRange = text.startIndex..<text.endIndex
let attributedString = NSMutableAttributedString(string: text)

text.enumerateSubstringsInRange(textRange, options: NSStringEnumerationOptions.ByWords, { (substring, substringRange, enclosingRange, stop) -> () in
    let start = distance(text.startIndex, substringRange.startIndex)
    let length = distance(substringRange.startIndex, substringRange.endIndex)
    let range = NSMakeRange(start, length)

//    println("word: \(substring) - \(d1) to \(d2)")

        if (substring == "saying") {
            attributedString.addAttribute(NSForegroundColorAttributeName, value: NSColor.redColor(), range: range)
        }
})

2
참고 : 문자열에 이모 지와 같은 문자를 사용하면이 오류가 발생할 수 있습니다. Martin의 응답을 참조하십시오.
Jay

7

나를 위해 이것은 완벽하게 작동합니다.

let font = UIFont.systemFont(ofSize: 12, weight: .medium)
let text = "text"
let attString = NSMutableAttributedString(string: "exemple text :)")

attString.addAttributes([.font: font], range:(attString.string as NSString).range(of: text))

label.attributedText = attString

5

스위프트 4 :

물론 Swift 4에 NSRange의 확장 기능이 이미 있다는 것을 알고 있습니다.

public init<R, S>(_ region: R, in target: S) where R : RangeExpression,
    S : StringProtocol, 
    R.Bound == String.Index, S.Index == String.Index

나는 대부분의 경우이 init로 충분하다는 것을 알고있다. 사용법을보십시오 :

let string = "Many animals here: 🐶🦇🐱 !!!"

if let range = string.range(of: "🐶🦇🐱"){
     print((string as NSString).substring(with: NSRange(range, in: string))) //  "🐶🦇🐱"
 }

그러나 Swift의 String 인스턴스없이 Range <String.Index>에서 NSRange로 직접 변환 할 수 있습니다.

대상 매개 변수를 String 으로 요구하는 일반적인 초기화 사용법 대신 대상 문자열 이없는 경우 직접 변환을 만들 수 있습니다

extension NSRange {
    public init(_ range:Range<String.Index>) {
        self.init(location: range.lowerBound.encodedOffset,
              length: range.upperBound.encodedOffset -
                      range.lowerBound.encodedOffset) }
    }

또는 Range 자체에 대한 특수 확장을 만들 수 있습니다

extension Range where Bound == String.Index {
    var nsRange:NSRange {
    return NSRange(location: self.lowerBound.encodedOffset,
                     length: self.upperBound.encodedOffset -
                             self.lowerBound.encodedOffset)
    }
}

용법:

let string = "Many animals here: 🐶🦇🐱 !!!"
if let range = string.range(of: "🐶🦇🐱"){
    print((string as NSString).substring(with: NSRange(range))) //  "🐶🦇🐱"
}

또는

if let nsrange = string.range(of: "🐶🦇🐱")?.nsRange{
    print((string as NSString).substring(with: nsrange)) //  "🐶🦇🐱"
}

스위프트 5 :

Swift 문자열을 기본적으로 UTF-8 인코딩으로 마이그레이션하기 때문에 사용법은 encodedOffset더 이상 사용되지 않는 것으로 간주되며 범위를 문자열 자체가 없으면 NSRange로 변환 할 수 없습니다. 오프셋을 계산하려면 소스 문자열이 필요하기 때문에 UTF-8로 인코딩되며 오프셋을 계산하기 전에 UTF-16으로 변환해야합니다. 현재로서는 가장 좋은 방법은 generic init 을 사용하는 것 입니다.


의 사용은 encodedOffset되어 유해한 것으로 간주 될 것이다 되지 .
Martin R

3

스위프트 4

두 가지 방법이 있다고 생각합니다.

1. NSRange (범위 : in)

2. NSRange (위치 :, 길이 :)

샘플 코드 :

let attributedString = NSMutableAttributedString(string: "Sample Text 12345", attributes: [.font : UIFont.systemFont(ofSize: 15.0)])

// NSRange(range, in: )
if let range = attributedString.string.range(of: "Sample")  {
    attributedString.addAttribute(.foregroundColor, value: UIColor.orange, range: NSRange(range, in: attributedString.string))
}

// NSRange(location: , length: )
if let range = attributedString.string.range(of: "12345") {
    attributedString.addAttribute(.foregroundColor, value: UIColor.green, range: NSRange(location: range.lowerBound.encodedOffset, length: range.upperBound.encodedOffset - range.lowerBound.encodedOffset))
}

스크린 샷 : 여기에 이미지 설명을 입력하십시오


의 사용은 encodedOffset되어 유해한 것으로 간주 될 것이다 되지 .
Martin R

1

기존 속성을 유지하는 Swift 3 Extension 변형 .

extension UILabel {
  func setLineHeight(lineHeight: CGFloat) {
    guard self.text != nil && self.attributedText != nil else { return }
    var attributedString = NSMutableAttributedString()

    if let attributedText = self.attributedText {
      attributedString = NSMutableAttributedString(attributedString: attributedText)
    } else if let text = self.text {
      attributedString = NSMutableAttributedString(string: text)
    }

    let style = NSMutableParagraphStyle()
    style.lineSpacing = lineHeight
    style.alignment = self.textAlignment
    let str = NSString(string: attributedString.string)

    attributedString.addAttribute(NSParagraphStyleAttributeName,
                                  value: style,
                                  range: str.range(of: str as String))
    self.attributedText = attributedString
  }
}

0
func formatAttributedStringWithHighlights(text: String, highlightedSubString: String?, formattingAttributes: [String: AnyObject]) -> NSAttributedString {
    let mutableString = NSMutableAttributedString(string: text)

    let text = text as NSString         // convert to NSString be we need NSRange
    if let highlightedSubString = highlightedSubString {
        let highlightedSubStringRange = text.rangeOfString(highlightedSubString) // find first occurence
        if highlightedSubStringRange.length > 0 {       // check for not found
            mutableString.setAttributes(formattingAttributes, range: highlightedSubStringRange)
        }
    }

    return mutableString
}

0

나는 Swift 언어를 좋아하지만 NSAttributedStringSwift Range와 호환되지 않는 언어를 사용 하면 NSRange너무 오랫동안 머리가 아프다. 그래서 모든 쓰레기를 피하기 위해 다음과 같은 방법을 고안 NSMutableAttributedString하여 강조 표시된 단어를 색상으로 설정했습니다.

이모티콘 에서는 작동 하지 않습니다 . 필요한 경우 수정하십시오.

extension String {
    func getRanges(of string: String) -> [NSRange] {
        var ranges:[NSRange] = []
        if contains(string) {
            let words = self.components(separatedBy: " ")
            var position:Int = 0
            for word in words {
                if word.lowercased() == string.lowercased() {
                    let startIndex = position
                    let endIndex = word.characters.count
                    let range = NSMakeRange(startIndex, endIndex)
                    ranges.append(range)
                }
                position += (word.characters.count + 1) // +1 for space
            }
        }
        return ranges
    }
    func highlight(_ words: [String], this color: UIColor) -> NSMutableAttributedString {
        let attributedString = NSMutableAttributedString(string: self)
        for word in words {
            let ranges = getRanges(of: word)
            for range in ranges {
                attributedString.addAttributes([NSForegroundColorAttributeName: color], range: range)
            }
        }
        return attributedString
    }
}

용법:

// The strings you're interested in
let string = "The dog ran after the cat"
let words = ["the", "ran"]

// Highlight words and get back attributed string
let attributedString = string.highlight(words, this: .yellow)

// Set attributed string
label.attributedText = attributedString

-3
let text:String = "Hello Friend"

let searchRange:NSRange = NSRange(location:0,length: text.characters.count)

let range:Range`<Int`> = Range`<Int`>.init(start: searchRange.location, end: searchRange.length)

6
대답을 조금 설명하고 코드를 올바르게 형식화하는 것이 어떻습니까?
SamB
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.