Swift Range'den NSRange?


177

Sorun: Range kullanan bir Swift String kullanırken NSAttributedString bir NSRange alıyor

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)
    }
})

Aşağıdaki hatayı üretir:

hata: 'Aralık', 'NSRange' biçimine dönüştürülemiyor attributedString.addAttribute (NSForegroundColorAttributeName, değer: NSColor.redColor (), aralık: substringRange)



2
@Suhaib bu tam tersi.
geoff

Yanıtlar:


262

Swift Stringaralıkları ve NSStringaralıkları "uyumlu" değildir. Örneğin, 😄 benzeri bir emoji bir Swift karakteri olarak, ancak iki NSString karakter (UTF-16 vekil çifti olarak adlandırılır) olarak sayılır .

Bu nedenle, dize bu tür karakterler içeriyorsa, önerilen çözümünüz beklenmedik sonuçlar üretir. Misal:

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)

Çıktı:

ParaUzun paragra {
} ph demek {
    NSColor = "NSCalibratedRGBColorSpace 1 0 0 1";
} İng! {
}

Gördüğünüz gibi, "ph say", "say" ile değil, niteliğiyle işaretlenmiştir.

Yana NS(Mutable)AttributedStringsonuçta bir gerektiriyor NSStringve bir NSRange, o verilen dize dönüştürmek için aslında iyidir NSStringilk. Sonra substringRange bir NSRangeve artık aralıkları dönüştürmek zorunda değilsiniz:

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)

Çıktı:

ParagraphUzun paragraf {
} Diyerek {
    NSColor = "NSCalibratedRGBColorSpace 1 0 0 1";
}! {
}

Swift 2 için güncelleme:

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)

Swift 3 için güncelleme:

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)

Swift 4 için güncelleme:

Swift 4'ten (Xcode 9) itibaren, Swift standart kütüphanesi Range<String.Index>ve arasında dönüştürme yöntemi sağlar NSRange. Dönüştürmek NSStringartık gerekli değil:

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)

İşte substringRangebir olduğunu Range<String.Index>ve bu karşılık gelen dönüştürülür NSRangeile

NSRange(substringRange, in: text)

74
OSX'te emoji karakterleri yazmak isteyen herkes için - Kontrol-Komut-boşluk çubuğu bir karakter seçici getirir
Jay

2
Birden fazla kelimeyi eşleştiriyorsam bu işe yaramaz ve eşleşecek dizenin tamamının ne olduğundan emin değilim. Diyelim ki bir dize geri bir API alıyorum ve başka bir dize içinde kullanarak ve API dize altı çizili istiyorum, alt dize API ve diğer dize olmayacak garanti edemez dize! Herhangi bir fikir?
simonthumper

NSMakeRange Değişti str.substringWithRange (Aralık <String.Index> (başlangıç: str.startIndex, end: str.endIndex)) // "Merhaba, oyun alanı" bu değişiklikler
HariKrishnan.P

(veya) dizeyi döküm --- let substring = (NSString olarak dize) .substringWithRange (NSMakeRange (başlangıç, uzunluk))
HariKrishnan.P

2
Bundan bahsediyorsunuz Range<String.Index>ve NSStringuyumlu değilsiniz. Muadilleri de uyumsuz mu? Yani NSRangeve Stringuyumsuz muyuz? Çünkü Apple'ın API'lerinden biri özellikle ikisini birleştiriyor: eşleşmeler (içinde: seçenekler: aralık :)
Senseful

57

Açıkladığınız gibi, bunun işe yaradığını gördüm. Nispeten kısa ve tatlı:

 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)

11
attributedString.addAttribute hızlı bir
aralıkla çalışmaz

7
@Paludis, haklısın ama bu çözüm bir Swift aralığı kullanmaya çalışmıyor. Bir kullanıyor NSRange. strbir NSStringve bu nedenle str.RangeOfString()bir NSRange.
tjpaul

3
Ayrıca 2. ve 3. satırları aşağıdaki satırla değiştirerek 2. satırdaki yinelenen dizeyi kaldırabilirsiniz:let str = attributedString.string as NSString
Jason Moore

2
Bu bir yerelleştirme kabusu.
Sulthan

29

Cevaplar iyi, ancak Swift 4 ile kodunuzu biraz basitleştirebilirsiniz:

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

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

Dikkatli olun, çünkü rangefonksiyonun sonucu açılmalıdır.


10

Olası çözüm

Swift, bir NSRange oluşturmak için kullanılabilen başlangıç ​​ve bitiş arasındaki mesafeyi ölçen distance () sağlar:

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
Not: Dizede emoji gibi karakterler kullanılıyorsa bu kırılabilir - Bkz. Martin yanıtı.
Jay

7

Benim için bu mükemmel çalışıyor:

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

Hızlı 4:

Elbette, Swift 4'ün zaten NSRange için bir uzantısı olduğunu biliyorum

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

Çoğu durumda bu initin yeterli olduğunu biliyorum. Kullanımına bakın:

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

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

Ancak Swift dizesi örneği olmadan doğrudan Range <String.Index> 'den NSRange' a dönüşüm yapılabilir.

Bunun yerine jenerik bir init sizden gerektirir kullanımı hedef olarak parametreyi dize ve yoksa hedef el altında dize doğrudan dönüşüm oluşturabilir

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

veya Range'in kendisi için özel uzantı oluşturabilirsiniz

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

Kullanımı:

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

veya

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

Hızlı 5:

Swift dizelerinin varsayılan olarak UTF-8 kodlamasına geçirilmesi nedeniyle, kullanımının kullanımdan encodedOffsetkaldırıldığı kabul edilir ve Range, String'in bir örneği olmadan NSRange'a dönüştürülemez, çünkü ofseti hesaplamak için kaynak dizeye ihtiyacımız vardır. UTF-8 olarak kodlanmıştır ve ofset hesaplanmadan önce UTF-16'ya dönüştürülmelidir. Şimdilik en iyi yaklaşım jenerik init kullanmaktır .


Kullanımı zararlıencodedOffset olarak kabul edilir ve kullanımdan kaldırılacaktır .
Martin R

3

Hızlı 4

Bence iki yol var.

1. NSRange (aralık, içinde:)

2. NSRange (konum :, uzunluk:)

Basit kod:

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))
}

Ekran Görüntüsü: resim açıklamasını buraya girin


Kullanımı zararlıencodedOffset olarak kabul edilir ve kullanımdan kaldırılacaktır .
Martin R

1

Mevcut nitelikleri koruyan Swift 3 Uzantı Varyantı .

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 dilini seviyorum, ancak uyumlu olmayan NSAttributedStringbir Swift ile kullanmak başımı çok uzun süre incitti. Tüm bu çöpleri aşmak için aşağıdaki yöntemleri tasarladım.RangeNSRangeNSMutableAttributedString için renklerle vurgulanmış kelimelerle .

Bu mu değil emoji'yi için çalışıyorum. Gerekirse değiştirin.

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
    }
}

Kullanımı:

// 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
Cevabınızı biraz açıklamaya ve tercihen kodu düzgün bir şekilde biçimlendirmeye ne dersiniz?
SamB
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.