NSRange จาก Swift Range หรือไม่


175

ปัญหา: NSAttributedString รับ NSRange ขณะที่ฉันใช้ Swift String ที่ใช้ Range

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

สร้างข้อผิดพลาดต่อไปนี้:

ข้อผิดพลาด: 'ช่วง' ไม่สามารถแปลงได้เป็น 'NSRange' constitutString.addAttribute (NSForegroundColorAttributeName ค่า: NSColor.redColor (), ช่วง: substringRange)


4
ซ้ำซ้อนที่เป็นไปได้ของNSRange เป็น Range <String.Index>
Suhaib

2
@Shahaib ที่กำลังไปทางอื่น
geoff

คำตอบ:


262

Stringช่วงและช่วงSwift NSStringไม่ "เข้ากันได้" ตัวอย่างเช่นอีโมจิเช่น😄นับเป็นตัวละคร Swift หนึ่งตัว แต่เป็นสองตัว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)

เอาท์พุท:

par ยาว paragra {
} ph say {
    NSColor = "NSCalibratedRGBColorSpace 1 0 0 1";
ไอเอ็นจี}! {
}

ตามที่คุณเห็น "ph say" ถูกทำเครื่องหมายด้วยแอตทริบิวต์ไม่ใช่ "กำลังพูด"

เนื่องจากNS(Mutable)AttributedStringในที่สุดต้องใช้NSStringและเป็น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 1 0 0 1";
}! {
}

อัปเดตสำหรับ Swift 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)

อัปเดตสำหรับ Swift 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)

อัปเดตสำหรับ Swift 4:

ในฐานะของสวิฟท์ 4 (Xcode 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)

นี่substringRangeคือ a Range<String.Index>และนั่นถูกแปลงให้สอดคล้องNSRangeกับ

NSRange(substringRange, in: text)

74
สำหรับทุกคนที่ต้องการพิมพ์ตัวอักษรอิโมจิบน OSX - แถบควบคุมพื้นที่สั่งให้ตัวเลือกอักขระ
Jay

2
วิธีนี้ใช้ไม่ได้หากฉันจับคู่มากกว่าหนึ่งคำและฉันไม่แน่ใจว่าจะจับคู่สตริงทั้งหมดได้อย่างไร สมมติว่าฉันได้รับสตริงกลับจาก API และใช้ภายในสตริงอื่นและฉันต้องการให้สตริงจาก API ขีดเส้นใต้ฉันไม่สามารถรับประกันได้ว่าสตริงย่อยจะไม่อยู่ในทั้งสตริงจาก API และอีกอัน สตริง! ความคิดใด ๆ
simonthumper

NSMakeRange เปลี่ยน str.substringWithRange (ช่วง <String.Index> (เริ่มต้น: str.startIndex สิ้นสุด: str.endIndex)) // "สวัสดีสนามเด็กเล่น" นี่เป็นการเปลี่ยนแปลง
HariKrishnan.P

(หรือ) การคัดเลือกสตริง --- ให้ substring = (สตริงเป็น NSString) .substringWithRange (NSMakeRange (เริ่มต้น, ความยาว))
HariKrishnan.P

2
คุณพูดถึงมันRange<String.Index>และNSStringไม่เข้ากัน คู่ของพวกเขายังไม่เข้ากัน? เช่นNSRangeและStringเข้ากันไม่ได้? สาเหตุที่หนึ่งใน API ของ Apple ได้รวมการจับคู่
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)

11
56.Sted.addAttribute จะไม่ทำงานกับช่วงที่รวดเร็ว
Paludis

7
@Paludis คุณถูกต้อง แต่วิธีนี้ไม่ได้พยายามใช้ช่วง Swift NSRangeมันถูกใช้ strเป็นNSStringและดังนั้นจึงส่งกลับstr.RangeOfString() NSRange
tjpaul

3
คุณสามารถลบสตริงที่ซ้ำกันในบรรทัดที่ 2 โดยแทนที่บรรทัดที่ 2 และ 3 ด้วย: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:

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))) //  "🐶🦇🐱"
 }

แต่การแปลงสามารถทำได้โดยตรงจากช่วง <String.Index> เป็น NSRange โดยไม่มีอินสแตนซ์ของสตริงของ Swift

แทนที่จะใช้การเริ่มต้นทั่วไปซึ่งต้องการพารามิเตอร์เป้าหมายเป็นสตริงและหากคุณไม่มีสตริงเป้าหมายอยู่ในมือคุณสามารถสร้างการแปลงได้โดยตรง

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 โดยไม่มีตัวอย่างของ String เองเพราะในการคำนวณออฟเซ็ตเราต้องการสตริงแหล่งที่มาซึ่ง เข้ารหัสใน UTF-8 และควรแปลงเป็น UTF-16 ก่อนการคำนวณออฟเซ็ต ดังนั้นวิธีที่ดีที่สุดสำหรับตอนนี้คือการใช้ทั่วไปinit


การใช้encodedOffsetจะถือว่าเป็นอันตรายและจะเลิก
Martin R

3

สวิฟต์ 4

ฉันคิดว่ามีสองวิธี

1. NSRange (ช่วงใน:)

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 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 แต่การใช้NSAttributedStringกับ Swift 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.