问题 从Swift中的UITextView中获取单词


我知道这个问题已经在Objective-C中解决了,但我在Swift中没有看到任何解决方案。我试图转换解决方案代码 这个 发布,但我收到错误:

func textTapped(recognizer: UITapGestureRecognizer){

    var textView: UITextView = recognizer.view as UITextView
    var layoutManager: NSLayoutManager = textView.layoutManager
    var location: CGPoint = recognizer.locationInView(textView)
    location.x -= textView.textContainerInset.left
    location.y -= textView.textContainerInset.top

    var charIndex: Int
    charIndex = layoutManager.characterIndexForPoint(location, inTextContainer: textView.textContainer, fractionOfDistanceBetweenInsertionPoints: nil)

    if charIndex < textView.textStorage.length {
        // do the stuff
        println(charIndex)
    }
}

我认为问题出在这一行(见错误 这里):

 var textView: UITextView = recognizer.view as UITextView

...我已根据此行从Objective-C转换而来:

 UITextView *textView = (UITextView *)recognizer.view;

最后,我也对如何调用此函数表示怀疑。据我了解,该函数应该传递给viewDidLoad()中的Selector,如下所示:

 let aSelector: Selector = "textTapped:"   

 let tapGesture = UITapGestureRecognizer(target: self, action: aSelector)
 tapGesture.numberOfTapsRequired = 1
 view.addGestureRecognizer(tapGesture)

因为我得到了前面提到的错误,我不确定它是否会起作用。但我想我还需要将textTapped函数(识别器)中的参数传递给Selector。但是,我已经读过你只能传递函数而不是任何参数。


4933
2017-08-23 18:47


起源



答案:


你需要添加 UITapGestureRecognizer 到了 UITextView 你希望能够点击。你现在正在添加 UITapGestureRecognizer 到你的 ViewControllerview。这就是演员让你陷入困境的原因。你正试图施展一个 UIView 到了 UITextView

let tapGesture = UITapGestureRecognizer(target: self, action: #selector(textTapped))
tapGesture.numberOfTapsRequired = 1
myTextView.addGestureRecognizer(tapGesture)

技术上 recognizer.view 是一种可选类型(UIView!)可能是 nil,但你似乎不太可能 textTapped() 会被称为未设置的。同样地, layoutManager 是类型 NSLayoutManager!。为了安全起见,Swift的方法是:

guard let textView = recognizer.view as? UITextView, let layoutManager = textView.layoutManager else {
    return
}
// code using textView and layoutManager goes here

事实上,如果你用这种方式编写它,你就不会因为条件转换而崩溃 UIView 至 UITextView 不会成功的。

要使这一切工作,请将属性添加到您将在textTapped例程中提取的属性字符串中:

var beginning = NSMutableAttributedString(string: "To the north you see a ")
var attrs = [NSFontAttributeName: UIFont.systemFontOfSize(19.0), "idnum": "1", "desc": "old building"]
var condemned = NSMutableAttributedString(string: "condemned building", attributes: attrs)
beginning.appendAttributedString(condemned)
attrs = [NSFontAttributeName: UIFont.systemFontOfSize(19.0), "idnum": "2", "desc": "lake"]
var lake = NSMutableAttributedString(string: " on a small lake", attributes: attrs)
beginning.appendAttributedString(lake)
myTextView.attributedText = beginning

这是完整的 textTapped

@objc func textTapped(recognizer: UITapGestureRecognizer) {
    guard let textView = recognizer.view as? UITextView, let layoutManager = textView.layoutManager else {
        return
    }
    var location: CGPoint = recognizer.locationInView(textView)
    location.x -= textView.textContainerInset.left
    location.y -= textView.textContainerInset.top

    /* 
    Here is what the Documentation looks like :

    Returns the index of the character falling under the given point,    
    expressed in the given container's coordinate system.  
    If no character is under the point, the nearest character is returned, 
    where nearest is defined according to the requirements of selection by touch or mouse.  
    This is not simply equivalent to taking the result of the corresponding 
    glyph index method and converting it to a character index, because in some 
    cases a single glyph represents more than one selectable character, for example an fi ligature glyph.
    In that case, there will be an insertion point within the glyph, 
    and this method will return one character or the other, depending on whether the specified 
    point lies to the left or the right of that insertion point.  
    In general, this method will return only character indexes for which there 
    is an insertion point (see next method).  The partial fraction is a fraction of the distance 
    from the insertion point logically before the given character to the next one, 
    which may be either to the right or to the left depending on directionality.
    */
    var charIndex = layoutManager.characterIndexForPoint(location, inTextContainer: textView.textContainer, fractionOfDistanceBetweenInsertionPoints: nil)

    guard charIndex < textView.textStorage.length else {
        return
    }

    var range = NSRange(location: 0, length: 0)
    if let idval = textView.attributedText?.attribute("idnum", atIndex: charIndex, effectiveRange: &range) as? NSString {
        print("id value: \(idval)")
        print("charIndex: \(charIndex)")
        print("range.location = \(range.location)")
        print("range.length = \(range.length)")
        let tappedPhrase = (textView.attributedText.string as NSString).substringWithRange(range)
        print("tapped phrase: \(tappedPhrase)")
        var mutableText = textView.attributedText.mutableCopy() as NSMutableAttributedString
        mutableText.addAttributes([NSForegroundColorAttributeName: UIColor.redColor()], range: range)
        textView.attributedText = mutableText
    }
    if let desc = textView.attributedText?.attribute("desc", atIndex: charIndex, effectiveRange: &range) as? NSString {
        print("desc: \(desc)")
    }
}

7
2017-08-23 20:37



如果你看一下Objective-C中的代码(我的初始帖子中的链接),他正在管理NS Range和id值,显然是为了收集被点击的字符串。你是否能够解释如何在Swift中解决这个问题,因为我不熟悉id值概念。我的目的是获取字符串,在点击时突出显示它(使用NSAttributedString)并根据字符串的值加载另一个ViewController。 - Benjamin Hviid
我已经尝试了一段时间,但我仍然无法掌握它(评论中的格式代码很麻烦,链接到代码是 这里)。它给出了“致命的错误:在解开一个Optional值时意外地发现了nil”,尽管我认为我写的是可选的正确的。 - Benjamin Hviid
你是如何创建textView中的属性字符串的? - vacawama
属性字符串是我的Location类中的变量(点击此处查看)。然后我在ViewController中创建一个Location实例,如下所示: var someLocation = Location(locationName:"Some place", id: 1) 并将属性字符串添加到viewDidLoad()中的UITextView,如下所示: someTextView.attributedText = someLocation.description - Benjamin Hviid
@wacawama,它的作品!但是,如果我的TextView大于输入的文本,我点击文本下方(在TextView中空的“空白”),它将打印出最后一个字符串。它似乎正在获取最后一个charIndex。如果它没有直接在文本的顶部,有没有办法忽略点击?我试过设置 textView.textContainer.size = textView.contentSize,这不起作用。最好的选择似乎是设置if语句 if charIndex < textView.textStorage.length - 1,但是我也无法点击最后一个字符。 - Benjamin Hviid


答案:


你需要添加 UITapGestureRecognizer 到了 UITextView 你希望能够点击。你现在正在添加 UITapGestureRecognizer 到你的 ViewControllerview。这就是演员让你陷入困境的原因。你正试图施展一个 UIView 到了 UITextView

let tapGesture = UITapGestureRecognizer(target: self, action: #selector(textTapped))
tapGesture.numberOfTapsRequired = 1
myTextView.addGestureRecognizer(tapGesture)

技术上 recognizer.view 是一种可选类型(UIView!)可能是 nil,但你似乎不太可能 textTapped() 会被称为未设置的。同样地, layoutManager 是类型 NSLayoutManager!。为了安全起见,Swift的方法是:

guard let textView = recognizer.view as? UITextView, let layoutManager = textView.layoutManager else {
    return
}
// code using textView and layoutManager goes here

事实上,如果你用这种方式编写它,你就不会因为条件转换而崩溃 UIView 至 UITextView 不会成功的。

要使这一切工作,请将属性添加到您将在textTapped例程中提取的属性字符串中:

var beginning = NSMutableAttributedString(string: "To the north you see a ")
var attrs = [NSFontAttributeName: UIFont.systemFontOfSize(19.0), "idnum": "1", "desc": "old building"]
var condemned = NSMutableAttributedString(string: "condemned building", attributes: attrs)
beginning.appendAttributedString(condemned)
attrs = [NSFontAttributeName: UIFont.systemFontOfSize(19.0), "idnum": "2", "desc": "lake"]
var lake = NSMutableAttributedString(string: " on a small lake", attributes: attrs)
beginning.appendAttributedString(lake)
myTextView.attributedText = beginning

这是完整的 textTapped

@objc func textTapped(recognizer: UITapGestureRecognizer) {
    guard let textView = recognizer.view as? UITextView, let layoutManager = textView.layoutManager else {
        return
    }
    var location: CGPoint = recognizer.locationInView(textView)
    location.x -= textView.textContainerInset.left
    location.y -= textView.textContainerInset.top

    /* 
    Here is what the Documentation looks like :

    Returns the index of the character falling under the given point,    
    expressed in the given container's coordinate system.  
    If no character is under the point, the nearest character is returned, 
    where nearest is defined according to the requirements of selection by touch or mouse.  
    This is not simply equivalent to taking the result of the corresponding 
    glyph index method and converting it to a character index, because in some 
    cases a single glyph represents more than one selectable character, for example an fi ligature glyph.
    In that case, there will be an insertion point within the glyph, 
    and this method will return one character or the other, depending on whether the specified 
    point lies to the left or the right of that insertion point.  
    In general, this method will return only character indexes for which there 
    is an insertion point (see next method).  The partial fraction is a fraction of the distance 
    from the insertion point logically before the given character to the next one, 
    which may be either to the right or to the left depending on directionality.
    */
    var charIndex = layoutManager.characterIndexForPoint(location, inTextContainer: textView.textContainer, fractionOfDistanceBetweenInsertionPoints: nil)

    guard charIndex < textView.textStorage.length else {
        return
    }

    var range = NSRange(location: 0, length: 0)
    if let idval = textView.attributedText?.attribute("idnum", atIndex: charIndex, effectiveRange: &range) as? NSString {
        print("id value: \(idval)")
        print("charIndex: \(charIndex)")
        print("range.location = \(range.location)")
        print("range.length = \(range.length)")
        let tappedPhrase = (textView.attributedText.string as NSString).substringWithRange(range)
        print("tapped phrase: \(tappedPhrase)")
        var mutableText = textView.attributedText.mutableCopy() as NSMutableAttributedString
        mutableText.addAttributes([NSForegroundColorAttributeName: UIColor.redColor()], range: range)
        textView.attributedText = mutableText
    }
    if let desc = textView.attributedText?.attribute("desc", atIndex: charIndex, effectiveRange: &range) as? NSString {
        print("desc: \(desc)")
    }
}

7
2017-08-23 20:37



如果你看一下Objective-C中的代码(我的初始帖子中的链接),他正在管理NS Range和id值,显然是为了收集被点击的字符串。你是否能够解释如何在Swift中解决这个问题,因为我不熟悉id值概念。我的目的是获取字符串,在点击时突出显示它(使用NSAttributedString)并根据字符串的值加载另一个ViewController。 - Benjamin Hviid
我已经尝试了一段时间,但我仍然无法掌握它(评论中的格式代码很麻烦,链接到代码是 这里)。它给出了“致命的错误:在解开一个Optional值时意外地发现了nil”,尽管我认为我写的是可选的正确的。 - Benjamin Hviid
你是如何创建textView中的属性字符串的? - vacawama
属性字符串是我的Location类中的变量(点击此处查看)。然后我在ViewController中创建一个Location实例,如下所示: var someLocation = Location(locationName:"Some place", id: 1) 并将属性字符串添加到viewDidLoad()中的UITextView,如下所示: someTextView.attributedText = someLocation.description - Benjamin Hviid
@wacawama,它的作品!但是,如果我的TextView大于输入的文本,我点击文本下方(在TextView中空的“空白”),它将打印出最后一个字符串。它似乎正在获取最后一个charIndex。如果它没有直接在文本的顶部,有没有办法忽略点击?我试过设置 textView.textContainer.size = textView.contentSize,这不起作用。最好的选择似乎是设置if语句 if charIndex < textView.textStorage.length - 1,但是我也无法点击最后一个字符。 - Benjamin Hviid


适用于Swift 3.0或以上版本

将点击手势添加到UITextView

let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapOnTextView(_:)))
textView.addGestureRecognizer(tapGesture)

添加tap处理程序方法

@objc private final func tapOnTextView(_ tapGesture: UITapGestureRecognizer){

  let point = tapGesture.location(in: textView)
  if let detectedWord = getWordAtPosition(point)
  {

  }
}

从点上得到消息

private final func getWordAtPosition(_ point: CGPoint) -> String?{
if let textPosition = textView.closestPosition(to: point)
{
  if let range = textView.tokenizer.rangeEnclosingPosition(textPosition, with: .word, inDirection: 1)
  {
    return textView.text(in: range)
  }
}
return nil}

5
2017-07-08 09:28



这个答案对我帮助很大。我需要获得被挖掘的单词范围 UITextView。 - derpoliuk
非常感谢,Hiren!这是一个简短而美观的解决方案! - Serzhas
@Serzhas谢谢!! !! - Hiren Panchal
当文本是英文时,这是完美的,我正在使用日语(hurigana / furigana),但这并没有找到'范围'。 + 1为你的工作。 - Vats