NSRange to Range <String.Index>


258

मैं कैसे परिवर्तित कर सकते हैं NSRangeकरने के लिए Range<String.Index>स्विफ्ट में?

मैं निम्नलिखित UITextFieldDelegateविधि का उपयोग करना चाहता हूं :

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

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

यहां छवि विवरण दर्ज करें


71
धन्यवाद, Apple, हमें नई रेंज क्लास देने के लिए, लेकिन फिर उनमें से किसी भी स्ट्रिंग उपयोगिता वर्ग, जैसे NSRegularExpression, का उपयोग करने के लिए अपडेट नहीं कर रहा है!
puzzl

जवाबों:


269

NSStringसंस्करण (के रूप में स्विफ्ट स्ट्रिंग के खिलाफ) की replacingCharacters(in: NSRange, with: NSString)एक स्वीकार करता है NSRange, तो एक सरल उपाय है परिवर्तित Stringकरने के लिए NSStringपहले । प्रतिनिधि और प्रतिस्थापन विधि के नाम स्विफ्ट 3 और 2 में थोड़े अलग हैं, इसलिए आप जिस स्विफ्ट का उपयोग कर रहे हैं, उसके आधार पर:

स्विफ्ट 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 मल्टी कोड यूनिट वर्णों के वर्ण जैसे कि झंडे (field)।
zaph 15

2
@Zaph: चूंकि रेंज UITextField से आती है, जो NSString के खिलाफ Obj-C में लिखा गया है, मुझे संदेह है कि स्ट्रिंग में यूनिकोड वर्णों के आधार पर केवल मान्य रेंज प्रतिनिधि कॉलबैक द्वारा प्रदान की जाएगी, और इसलिए यह उपयोग करने के लिए सुरक्षित है , लेकिन मैंने व्यक्तिगत रूप से इसका परीक्षण नहीं किया है।
एलेक्स प्रेट्ज़लाव

2
@Zaph, सच है कि, मेरी बात है कि है UITextFieldDelegateकी textField:shouldChangeCharactersInRange:replacementString:विधि, के बाद से कॉलबैक एक उपयोगकर्ता कुंजीपटल से पात्रों में प्रवेश पर आधारित है एक सीमा है जो प्रारंभ करने या यूनिकोड चरित्र के अंदर समाप्त हो जाती है प्रदान नहीं करेगा, और यह केवल हिस्सा टाइप करने के लिए संभव नहीं है इमोजी यूनिकोड चरित्र का। ध्यान दें कि यदि प्रतिनिधि ओब्ज-सी में लिखा गया था, तो यह एक ही समस्या होगी।
एलेक्स प्रेट्ज़लाव

4
सबसे अच्छा जवाब नहीं। कोड पढ़ने के लिए सबसे आसान लेकिन @ मार्टिन-आर का सही उत्तर है।
रोज

3
वास्तव में आप लोगों को ऐसा करना चाहिए(textField.text as NSString?)?.stringByReplacingCharactersInRange(range, withString: string)
वानबोक चोई

361

के रूप में स्विफ्ट 4 (Xcode 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)
        // ...
    }
    // ...
}

(स्विफ्ट 3 और उससे पहले के पुराने उत्तर :)

स्विफ्ट 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
    }
}

यह विधि एक वैकल्पिक स्ट्रिंग रेंज लौटाती है क्योंकि सभी 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 के लिए अपडेट:

स्विफ्ट का 2 संस्करण rangeFromNSRange()पहले ही सेरही याकोवेन्को ने इस उत्तर में दिया था , मैं इसे पूर्णता के लिए यहां शामिल कर रहा हूं:

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 कोड बिंदु, जैसे Emojis वाले वर्णों के लिए सही ढंग से काम नहीं करता है। - एक उदाहरण के रूप में, यदि पाठ फ़ील्ड में "and" पाठ है और आप उस वर्ण को हटाते हैं, तो rangeइसका (0,2)कारण NSRangeयह है कि UTF-16 वर्णों को संदर्भित करता है a NSString। लेकिन यह रूप में गिना जाता एक एक स्विफ्ट स्ट्रिंग में यूनिकोड वर्ण। - let end = advance(start, range.length)संदर्भित उत्तर से इस मामले में त्रुटि संदेश "घातक त्रुटि:" वेतन वृद्धि नहीं कर सकता है के साथ दुर्घटना होती है "एंडेक्स"।
मार्टिन आर

8
@MattDiPasquale: ज़रूर। मेरा इरादा शब्दशः सवाल का जवाब देने था "मैं रेंज <String.Index> करने के लिए स्विफ्ट में NSRange परिवर्तित कर सकते हैं" एक यूनिकोड-सुरक्षित तरीका में (उम्मीद है कि किसी को जो अब तक :( मामला नहीं है इसे उपयोगी पाते सकता है,
मार्टिन

5
धन्यवाद, एक बार और, उनके लिए एप्पल के काम करने के लिए। आप जैसे लोगों और स्टैक ओवरफ्लो के बिना, कोई तरीका नहीं है कि मैं iOS / OS X का विकास करूं। ज़िंदगी बहुत छोटी है।
Womble

1
@ jiminybob99: StringAPI संदर्भ में कूदने के लिए कमांड-क्लिक करें , सभी विधियों और टिप्पणियों को पढ़ें, तब तक अलग-अलग चीजों को आज़माएं जब तक कि वह काम न करें :)
मार्टिन आर।

1
के लिए let from16 = utf16.index(utf16.startIndex, offsetBy: nsRange.location, limitedBy: utf16.endIndex), मुझे लगता है कि आप वास्तव में इसे सीमित करना चाहते हैं utf16.endIndex - 1। अन्यथा, आप स्ट्रिंग के अंत से शुरू कर सकते हैं।
माइकल Tsai

18

आपको Range<String.Index>क्लासिक के बजाय उपयोग करने की आवश्यकता है NSRange। जिस तरह से मैं यह करता हूं (शायद एक बेहतर तरीका है) स्ट्रिंग के String.Indexसाथ इसे ले जाने से है advance

मुझे नहीं पता कि आप किस रेंज को बदलने की कोशिश कर रहे हैं, लेकिन आप पहले 2 अक्षरों को बदलना चाहते हैं।

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

मार्टिन आर का यह उत्तर सही प्रतीत होता है क्योंकि इसमें यूनिकोड का वर्णन है।

हालाँकि पोस्ट के समय (स्विफ्ट 1) उसका कोड स्विफ्ट 2.0 (Xcode 7) में संकलित नहीं है, क्योंकि उन्होंने advance()फ़ंक्शन को हटा दिया है। अद्यतन संस्करण नीचे है:

स्विफ्ट 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

के बाद से आप विशेष रूप से पूछा कि कन्वर्ट करने के लिए यह एमिली के जवाब तथापि के समान है NSRangeकरने के लिए Range<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

@ ईमली द्वारा शानदार उत्तर पर एक रिप, एक प्रतिस्थापन / प्रतिस्पर्धा का जवाब नहीं।
(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

कई कोड इकाइयों के लिए अनुक्रमित खाते को नोटिस करें। ध्वज (क्षेत्रीय संकेतक प्रतीक पत्र ES) 8 बाइट्स और (चेहरे के साथ चेहरा) 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

स्विफ्ट 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.

परिणाम।

😂
🇪🇸
🇪🇸
🇪🇸

विवरण

NSRangeसे NSStringमायने रखता है UTF-16 कोड इकाई है। और Range<String.Index>स्विफ्ट Stringसे एक अपारदर्शी रिश्तेदार प्रकार है जो केवल समानता और नेविगेशन संचालन प्रदान करता है। यह जानबूझकर छिपा हुआ डिजाइन है।

हालाँकि, Range<String.Index>यूटीएफ -16 कोड-यूनिट ऑफ़सेट को मैप किया जा रहा है, यह सिर्फ एक कार्यान्वयन विवरण है, और मुझे किसी भी गारंटी के बारे में कोई उल्लेख नहीं मिला। इसका मतलब है कि कार्यान्वयन विवरण किसी भी समय बदला जा सकता है। स्विफ्ट Stringका आंतरिक प्रतिनिधित्व बहुत परिभाषित नहीं है, और मैं इस पर भरोसा नहीं कर सकता।

NSRangeमूल्यों को सीधे String.UTF16Viewअनुक्रमित करने के लिए मैप किया जा सकता है । लेकिन इसे बदलने की कोई विधि नहीं है String.Index

स्विफ्ट String.Index, इट्रिफ्ट स्विफ्ट का सूचकांक है Characterजो एक यूनिकोड ग्रेफेम क्लस्टर है । फिर, आपको उचित प्रदान करना चाहिए NSRangeजो सही अंगूर समूहों का चयन करता है। यदि आप उपर्युक्त उदाहरण की तरह गलत रेंज प्रदान करते हैं, तो यह गलत परिणाम देगा क्योंकि उचित ग्रैफेम क्लस्टर रेंज का पता नहीं लगाया जा सकता है।

एक गारंटी नहीं है कि अगर कोई String.Index है UTF-16 कोड इकाई ऑफसेट, तो समस्या आसान हो जाता है। लेकिन ऐसा होने की संभावना नहीं है।

उलटा रूपांतरण

वैसे भी उलटा रूपांतरण ठीक किया जा सकता है।

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

मैंने सबसे साफ स्विफ्ट 2 पाया है केवल समाधान 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 के साथ काम करने के लिए अपना जवाब अपडेट कर दिया है।
डैनी ब्रावो

0

स्विफ्ट 3.0 बीटा आधिकारिक दस्तावेज शीर्षक के अंतर्गत इस स्थिति के लिए अपने मानक समाधान प्रदान की गई है String.UTF16View में खंड UTF16View तत्वों मैच NSString वर्ण शीर्षक


यह उपयोगी होगा यदि आप अपने उत्तर में लिंक प्रदान करेंगे।
m5khan

0

स्वीकृत उत्तर में मुझे वैकल्पिक विकल्प बोझिल लगते हैं। यह स्विफ्ट 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)
    }
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.