NSRange từ Swift Range?


175

Vấn đề: NSAttributionString mất NSRange trong khi tôi đang sử dụng Chuỗi Swift sử dụng Phạm vi

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

Tạo ra lỗi sau:

lỗi: 'Phạm vi' không thể chuyển đổi thành 'NSRange' AttributionString.addAttribution (NSForegroundColorAttributionName, value: NSColor.redColor (), phạm vi: subopesRange)


4
Bản sao có thể có của NSRange thành Phạm vi <
String. Index

2
@Suhaib đó là cách khác.
geoff

Câu trả lời:


262

StringPhạm vi và phạm vi Swift NSStringkhông "tương thích". Ví dụ: một biểu tượng cảm xúc như 😄 được tính là một ký tự Swift, nhưng là hai NSString ký tự (một cặp được gọi là cặp thay thế UTF-16).

Do đó, giải pháp được đề xuất của bạn sẽ tạo ra kết quả không mong muốn nếu chuỗi chứa các ký tự đó. Thí dụ:

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)

Đầu ra:

ParLong paragra {
} ph nói {
    NSColor = "NSCalibratedRGBColorSpace 1 0 0 1";
} ing! {
}

Như bạn thấy, "ph say" đã được đánh dấu bằng thuộc tính, không phải "nói".

NS(Mutable)AttributedStringcuối cùng đòi hỏi một NSStringvà một NSRange, nên thực sự tốt hơn để chuyển đổi chuỗi đã cho thành NSStringđầu tiên. Sau đó, substringRange là một NSRangevà bạn không phải chuyển đổi phạm vi nữa:

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)

Đầu ra:

Đoạn văn dài {
} nói {
    NSColor = "NSCalibratedRGBColorSpace 1 0 0 1";
}! {
}

Cập nhật cho 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)

Cập nhật cho 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)

Cập nhật cho Swift 4:

Kể từ Swift 4 (Xcode 9), thư viện chuẩn Swift cung cấp phương thức để chuyển đổi giữa Range<String.Index>NSRange. Chuyển đổi sang NSStringkhông còn cần thiết:

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)

Đây substringRangelà một Range<String.Index>, và được chuyển đổi thành tương ứng NSRangevới

NSRange(substringRange, in: text)

74
Đối với bất kỳ ai muốn nhập các ký tự biểu tượng cảm xúc trên OSX - Thanh điều khiển-lệnh-không gian sẽ hiển thị trình chọn ký tự
Jay

2
Điều này không hoạt động nếu tôi kết hợp nhiều hơn một từ và tôi không chắc toàn bộ chuỗi phù hợp là gì. Giả sử tôi nhận được chuỗi từ API và sử dụng chuỗi đó trong chuỗi khác và tôi muốn chuỗi từ API được gạch chân, tôi không thể đảm bảo rằng các chuỗi con sẽ không nằm trong cả chuỗi từ API và chuỗi khác chuỗi! Có ý kiến ​​gì không?
simonthumper

NSMakeRange đã thay đổi str.subopesWithRange (Phạm vi <String.Index> (start: str.startIndex, end: str.endIndex)) // "Xin chào, sân chơi" đây là những thay đổi
HariKrishnan.P

(hoặc) truyền chuỗi --- hãy để chuỗi con = (chuỗi dưới dạng NSString) .subopesWithRange (NSMakeRange (bắt đầu, độ dài))
HariKrishnan.P

2
Bạn đề cập đến điều đó Range<String.Index>NSStringkhông tương thích. Là đối tác của họ cũng không tương thích? Tức là NSRangeStringkhông tương thích? Một nguyên nhân của API của Apple đặc biệt kết hợp hai: các trận đấu (trong: tùy chọn: dải :)
Senseful

56

Đối với các trường hợp như trường hợp bạn mô tả, tôi thấy điều này có hiệu quả. Nó tương đối ngắn và ngọt ngào:

 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
AttributionString.addAttribution sẽ không hoạt động với Phạm vi nhanh chóng
Paludis

7
@Paludis, bạn đã đúng nhưng giải pháp này không cố sử dụng phạm vi Swift. Nó đang sử dụng một NSRange. strlà một NSStringvà do đó str.RangeOfString()trả về một NSRange.
tjpaul

3
Bạn cũng có thể xóa chuỗi trùng lặp trong dòng 2 bằng cách thay thế dòng 2 và 3 bằng:let str = attributedString.string as NSString
Jason Moore

2
Đây là một cơn ác mộng địa phương hóa.
Sulthan

29

Câu trả lời là tốt, nhưng với Swift 4, bạn có thể đơn giản hóa mã của mình một chút:

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

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

Hãy thận trọng, vì kết quả của rangechức năng phải được mở ra.


10

Giải pháp có thể

Swift cung cấp khoảng cách () để đo khoảng cách giữa bắt đầu và kết thúc có thể được sử dụng để tạo 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
Lưu ý: Điều này có thể bị hỏng nếu sử dụng các ký tự như biểu tượng cảm xúc trong chuỗi - Xem phản hồi của Martin.
Jay

7

Đối với tôi điều này hoạt động hoàn hảo:

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

Swift 4:

Chắc chắn, tôi biết rằng Swift 4 đã có phần mở rộng cho NSRange rồi

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

Tôi biết trong hầu hết các trường hợp init này là đủ. Xem cách sử dụng của nó:

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

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

Nhưng việc chuyển đổi có thể được thực hiện trực tiếp từ Phạm vi <String.Index> sang NSRange mà không cần phiên bản Chuỗi của Swift.

Thay vì sử dụng init chung yêu cầu từ bạn tham số đíchChuỗi và nếu bạn không có chuỗi mục tiêu trong tay, bạn có thể tạo chuyển đổi trực tiếp

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

hoặc bạn có thể tạo tiện ích mở rộng chuyên biệt cho chính Range

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

Sử dụng:

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

hoặc là

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

Swift 5:

Do việc di chuyển các chuỗi Swift sang mã hóa UTF-8 theo mặc định, việc sử dụng encodedOffsetđược coi là không dùng nữa và Phạm vi không thể được chuyển đổi thành NSRange mà không có phiên bản của Chuỗi, vì để tính toán bù, chúng ta cần có chuỗi nguồn. được mã hóa theo UTF-8 và cần được chuyển đổi thành UTF-16 trước khi tính toán bù. Vì vậy, cách tiếp cận tốt nhất, bây giờ, là sử dụng init chung .


Việc sử dụng encodedOffsetđược coi là có hại và sẽ không được chấp nhận .
Martin R

3

Swift 4

Tôi nghĩ rằng, có hai cách.

1. NSRange (phạm vi, trong :)

2. NSRange (vị trí:, chiều dài :)

Mã mẫu:

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

Ảnh chụp màn hình: nhập mô tả hình ảnh ở đây


Việc sử dụng encodedOffsetđược coi là có hại và sẽ không được chấp nhận .
Martin R

1

Biến thể mở rộng Swift 3 bảo tồn các thuộc tính hiện có.

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

Tôi yêu ngôn ngữ Swift, nhưng sử dụng NSAttributedStringvới Swift Rangekhông tương thích với nó NSRangeđã khiến đầu tôi đau quá lâu. Vì vậy, để giải quyết tất cả những thứ rác rưởi đó, tôi đã nghĩ ra các phương pháp sau để trả về một NSMutableAttributedStringtừ được tô sáng bằng màu của bạn.

Điều này không làm việc cho biểu tượng cảm xúc. Sửa đổi nếu bạn phải.

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

Sử dụng:

// 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
Làm thế nào về việc giải thích câu trả lời của bạn một chút, và tốt nhất là định dạng mã đúng?
SamB
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.