Вопрос о коммуникации между элементами графики в NSView и текстом в NSTextView (macOS)


#1

Добрый день! Есть текст в NSTextView и для его отдельных фрагментов рисуем в отдельном NSView отрезки, используя для этого array of NSRange фрагментов. Рисуем в draw(_ dirtyRect: NSRect) с использованием NSBezierPath() . Теперь надо как то реализовать обратную задачу - подводим курсор мыши к нарисованному отрезку и соотвествующий фрагмент текста в NStextView должен быть выделен.

Вопрос, как решить эту обратную задачу ?


#2

Ждем от Вас решения :))


#3

Пробую, но пока не получается. В принципе все просто.

В кастомном NSView где рисовались отрезки, внутри draw(_ dirtyRect: NSRect), при их рисовании с использованием array of NSRange фрагментов текста, создаем дополнительный массив из (NSBezierPath, NSRange). Далее проверяем если позиция мыши NSPoint при нажатии мышкой на отрезок (mouseDown(with theEvent: NSEvent) ) содержится в том или ином NSBezierPath и используем NSRange соотвествующий этому NSBezierPath отрезка . Для этого прописываем в кастомном NSView.

 var arrayOfNSBezierPaths_NSRanges = [(NSBezierPath, NSRange)]()
 var locationNSRange = NSRange()  

 override func mouseDown(with theEvent: NSEvent)
 {
     mouseLocationNSPoint = self.convert(theEvent.locationInWindow, from: nil)
    
     if arrayOfNSRanges_NSBezierPaths.isEmpty != true
     {
         for each in arrayOfNSBezierPaths_NSRanges
         {
             if each.0.contains(mouseLocationNSPoint) == true
             {
                 locationNSRange = each.1
             }
         }
     }
 } 

Итак, получаем нужный NSRange чтобы пометить в другом view (в NSTextView) текст соотвествующий отрезку на который мы нажали мышкой. Нужно затем передать NSRange в контроллер. Проблема в том как его передать в контроллер при нажатии мыши и соотвественно при нажатии мышкой добавить attribute к тесту. Если бы был Notification для mouseDown, но пока ничего не нашел. В этом загвоздка. Коллеги подскажите, что делать !!!


#4

В итоге. Придумал костыль. Для NSView есть NSNotification.Name.NSViewBoundsDidChange. Соотсветсвенно прописал в NSView :

var locationNSRange = NSRange()

override func mouseDown(with event: NSEvent)
{
    locationNSRange = NSRange()
    
    let mouseLocationNSPoint = self.convert(event.locationInWindow, from: nil)
    
    if arrayOfNSRanges_NSBezierPaths.isEmpty != true
    {
        for each in arrayOfNSRanges_NSBezierPaths
        {
            if each.0.contains(mouseLocationNSPoint) == true
            {
                locationNSRange = each.1
            }
        }
    }
    self.bounds = NSRect(x: 0.0, y: 0.0, width: 870.1, height: 540.1)

}

override func mouseUp(with event: NSEvent)
{
    self.bounds = NSRect(x: 0.0, y: 0.0, width: 870.0, height: 540.0)
    locationNSRange = NSRange()
}


override func acceptsFirstMouse(for event: NSEvent?) -> Bool
{
    return true
}

Что означает, что при mouse down изменяем bounds для NSView и посылаем Notification. Далее при mouse up возвращаем bounds к исходным значениям и посылаем Notification. Изменение для bounds делаем маленьким, чтобы было незаметно. Соотвественно, при нажатии мышкой на отрезок в NSView, в итоге, в контроллере получаем NSRange и подсвечиваем текст ему соотвествующий.

func controlMouseDownUp(_ notification: Notification)
{
    let range = myNSView.locationNSRange
    myTextView.showFindIndicator(for: range)
}

#5

Теперь надо как то добавить прокрутку к тому месту где выделяется текст, т.е. чтобы при нажатии мышкой сразу появлялся подсвеченный текст, если весь текст большой и текст соотвествующий отрезку не в окне.


#6

Добавляем в controlMouseDownUp функцию следующую строчку для скролинга к подсвечиваемой части текста.

   myTextView.scrollRangeToVisible(range)