2010-09-27 6 views
9

Mam pewien kod w widoku, który rysuje jakiś przypisany tekst za pomocą CoreText. W tym szukam adresów URL i sprawię, że będą niebieskie. Pomysł polega na tym, aby nie obciążać wszystkich kosztami, aby uzyskać klikalne linki. Gdy użytkownik kliknie na ten link (nie całą komórkę widoku tabeli), chcę odpalić metodę delegata, która będzie następnie używana do przedstawienia widoku modalnego zawierającego widok internetowy przechodzący do tego adresu URL.Używanie CoreText i dotknięć, aby utworzyć klikalną akcję

Zapisuję ścieżkę i łańcuch jako zmienne instancji widoku, a kod rysunku występuje w -drawRect: (pominięto to dla zwięzłości).

Mój touch machine jednak, mimo że niekompletny, nie drukuje tego, czego oczekiwałbym. To jest poniżej:

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event 
{ 
    UITouch *touch = [touches anyObject]; 
    CGPoint point = [touch locationInView:self]; 
    CGContextRef context = UIGraphicsGetCurrentContext(); 

    NSLog(@"attribString = %@", self.attribString); 
    CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)self.attribString); 
    CTFrameRef ctframe = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, self.attribString.length), attribPath, NULL); 

    CFArrayRef lines = CTFrameGetLines(ctframe); 
    for(CFIndex i = 0; i < CFArrayGetCount(lines); i++) 
    { 
     CTLineRef line = CFArrayGetValueAtIndex(lines, i); 
     CGRect lineBounds = CTLineGetImageBounds(line, context); 

     // Check if tap was on our line 
     if(CGRectContainsPoint(lineBounds, point)) 
     { 
      NSLog(@"Tapped line"); 
      CFArrayRef runs = CTLineGetGlyphRuns(line); 
      for(CFIndex j = 0; j < CFArrayGetCount(runs); j++) 
      { 
       CTRunRef run = CFArrayGetValueAtIndex(runs, j); 
       CFRange urlStringRange = CTRunGetStringRange(run); 
       CGRect runBounds = CTRunGetImageBounds(run, context, urlStringRange); 

       if(CGRectContainsPoint(runBounds, point)) 
       { 
        NSLog(@"Tapped run"); 
        CFIndex* buffer = malloc(sizeof(CFIndex) * urlStringRange.length); 
        CTRunGetStringIndices(run, urlStringRange, buffer); 
        // TODO: Map the glyph indices to indexes in the string & Fire the delegate 
       } 
      } 
     } 
    } 
} 

To nie jest najładniejszy kod w tej chwili, ja wciąż staram się po prostu sprawiają, że działa, więc wybacz jakość kodu.

Problem polega na tym, że gdy klikam poza linkiem, to, czego się spodziewam, stanie się: Nic się nie zwalnia.

Jednak Spodziewam się "Tapped line" aby wydrukowane gdybym dotknij tej samej linii link jest włączony, który się nie dzieje, a ja się spodziewać zarówno "Tapped line" i "Tapped run" aby wydrukowane gdybym dotknij URL.

Nie jestem pewien, gdzie to zrobić dalej, zasoby, na które patrzyłem, aby rozwiązać tę kwestię, są specyficzne dla kakao (co prawie zupełnie nie ma zastosowania) lub brakuje informacji na temat tego konkretnego przypadku.

Z chęcią zabiorę wskaźniki do dokumentacji, która szczegółowo wyjaśni, jak właściwie przejść do wykrycia, czy dotknięcie wystąpiło w obrębie podstawowego tekstu rysującego kod, ale w tym momencie chcę tylko rozwiązać ten problem, więc wszelkie pomoc byłaby bardzo ceniona.

AKTUALIZACJA: Zawęziłem mój problem do kwestii współrzędnych. Odwróciłem współrzędne (a nie jak pokazano powyżej), a problem, który dostaję, dotyka rejestru tak, jak się spodziewałem, ale przestrzeń współrzędnych jest odwrócona i nie mogę jej odwrócić.

+0

Brałeś pod uwagę fakt, że CoreText wykorzystuje obróconą układ współrzędnych? Teraz wydaje mi się, że porównujesz rzeczy w dwóch różnych układach współrzędnych. – Jacques

+0

Nie jest również dobrym pomysłem odtworzenie oprawki przy każdym takim dotknięciu. Tworzenie framesettera jest bardzo kosztowne, więc powinieneś go buforować przy pierwszym rysowaniu lub ustawianiu tekstu. – Jacques

+0

Moja metodologia rozwoju to po prostu: 1) Spraw, aby działała; 2) Zrób to dobrze; 3) Spraw, by była szybka/czysta. Zajmijmy się # 2, gdy otrzymamy # 1 wracając :) Także w odniesieniu do układu współrzędnych, tak, zdaję sobie z tego sprawę, i przez jakiś czas go nie obsługiwałem.W kodeksie teraz, po konsultacji z kolegą, postawił mnie prosto na to, i to przynajmniej wykrywa krany na liniach, po prostu nie przebiega. Wciąż próbuję to rozgryźć. – jer

Odpowiedz

8

Właśnie zrobiłem to, aby uzyskać indeks znaków ciągów z pozycji dotykowej. Numer linii byłoby I w tym przypadku:

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { 
    NSLog(@"touch ended"); 

    UITouch* touch = [touches anyObject]; 

    CGPoint pnt = [touch locationInView:self]; 

    CGPoint reversePoint = CGPointMake(pnt.x, self.frame.size.height-pnt.y); 

    CFArrayRef lines = CTFrameGetLines(ctFrame); 

    CGPoint* lineOrigins = malloc(sizeof(CGPoint)*CFArrayGetCount(lines)); 

    CTFrameGetLineOrigins(ctFrame, CFRangeMake(0,0), lineOrigins); 

    for(CFIndex i = 0; i < CFArrayGetCount(lines); i++) 
    { 
     CTLineRef line = CFArrayGetValueAtIndex(lines, i); 

     CGPoint origin = lineOrigins[i]; 
     if (reversePoint.y > origin.y) { 
      NSInteger index = CTLineGetStringIndexForPosition(line, reversePoint); 
      NSLog(@"index %d", index); 
      break; 
     } 
    } 
    free(lineOrigins); 
} 
+0

Dzięki Nick uratowałeś mnie! :) – stefanosn

+0

Działa to świetnie, mogę to porównać ze zbiorem zakresów, które zapisałem dla słów, które można wstawiać. Jest to znacznie szybsze niż moje stare podejście - dziękuję :) –

+0

Jak mogę uzyskać ctFrame? –

0

Możesz spróbować dodać swój rysunek tekstowy do CALayera, dodać tę nową warstwę jako podwarstwę swojej warstwy widoków i najlepiej do niej dopasować w swoich dotknięciach. Jeśli to zrobisz, powinieneś być w stanie stworzyć większy dotykowy obszar, dzięki czemu warstwa będzie większa niż narysowany tekst.

// hittesting 
UITouch *touch = [[event allTouches] anyObject]; 
touchedLayer = (CALayer *)[self.layer hitTest:[touch locationInView:self]]; 
+0

Wtedy miałbym inny problem z dzieleniem mojego tekstu na dwie różne warstwy - tą, którą chcę wywołać delegata, a potem resztę. Problem, na który się tam natknąłem, brzmi: co się dzieje, gdy adres URL znajduje się pośrodku linii, a tekst jest przed i po nim? Wtedy musiałbym podzielić się na wiele różnych warstw. Może się włochaty, jeśli w grupie tekstu renderowanego jest kilka adresów URL. Wolałbym po prostu pobrać listę wszystkich elementów w łańcuchu znaków attrib, które zabarwiłem na niebiesko, i uzyskać ich ramkę ograniczającą, sprawdzić, czy użytkownik stuknął w nią i zadzwonić do delegata z tym adresem URL. – jer

+0

To prawda, może to być problem. Może nie zrozumiałem twojego problemu. Co powiesz na zachowanie tekstu takim, jaki jest, i nałożenie warstwy, którą można oddzielić, by złapać te dotknięcia? – msg

+0

Nadal muszę dokładnie wiedzieć, gdzie go umieścić - co oznacza, że ​​nadal muszę uzyskać adres URL. To przywraca mnie do pierwszego. :) – jer

0

Swift 3 wersja odpowiedź Nick H247 za:

var ctFrame: CTFrame? 

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { 
    guard let touch = touches.first, let ctFrame = self.ctFrame else { return } 

    let pnt = touch.location(in: self.view) 
    let reversePoint = CGPoint(x: pnt.x, y: self.frame.height - pnt.y) 
    let lines = CTFrameGetLines(ctFrame) as [AnyObject] as! [CTLine] 

    var lineOrigins = [CGPoint](repeating: .zero, count: lines.count) 
    CTFrameGetLineOrigins(ctFrame, CFRange(location: 0, length: 0), &lineOrigins) 

    for (i, line) in lines.enumerated() { 
     let origin = lineOrigins[i] 

     if reversePoint.y > origin.y { 
      let index = CTLineGetStringIndexForPosition(line, reversePoint) 
      print("index \(index)") 
      break 
     } 
    } 
} 
Powiązane problemy