2010-09-27 80 views
9

我有一些代碼在視圖中使用CoreText繪製一些屬性文本。在這裏,我正在尋找網址並將它們變成藍色。這個想法是爲了獲得可點擊的鏈接而不是引入UIWebView的所有開銷。一旦用戶點擊該鏈接(而不是整個表格視圖單元格),我想要引發一個委託方法,然後用它來呈現一個模式視圖,其中包含一個到該URL的Web視圖。使用CoreText和觸摸創建一個可點擊的動作

我將路徑和字符串本身保存爲視圖的實例變量,並且繪圖代碼發生在-drawRect:(爲簡潔起見,我已將其省略)。

但是,我的觸摸處理程序不完整,沒有打印出我期望的結果。它下面是:

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

這不是目前最漂亮的代碼,我還在努力,只是使它工作,所以請原諒的代碼質量。

我遇到的問題是,當我點擊鏈接外,我期望會發生,會發生:沒有任何被解僱。

不過,我希望"Tapped line"如果我點擊同一行鏈路上,這不會發生在得到打印,並且我希望雙方"Tapped line""Tapped run"如果我的網址點擊來獲得打印。

我不確定在哪裏採取這一進一步,我已經看到解決這個問題的資源是可可特定的(這幾乎完全不適用),或缺乏關於此特定情況的信息。

我很樂意指出文檔,詳細說明如何正確地檢測觸摸是否發生在代碼的核心文本繪圖的邊界內,但此時,我只是想解決這個問題,所以任何幫助將不勝感激。

UPDATE:我已經將我的問題縮小到一個座標問題。我翻轉了座標(而不是如上所示),我得到的問題是觸摸按我的預期註冊,但座標空間翻轉,我似乎無法將其翻轉。

+0

你有沒有考慮到,CoreText使用翻轉座標系中的事實呢?現在看起來像你在比較兩個不同座標系中的東西。 – Jacques 2010-09-27 13:39:02

+0

此外,在每次觸摸時重新創建framesetter是一個壞主意。創建一個framesetter是非常昂貴的,所以你應該在第一次繪圖或設置文本時進行緩存。 – Jacques 2010-09-27 13:41:18

+0

我的開發方法很簡單:1)讓它工作; 2)做對; 3)快速/清潔。讓我們來處理#2當我們得到#1去:)也是關於座標系,是的,我意識到這一點,並且有一段時間我沒有處理它。在現在的代碼中,在與一位同事協商之後,他讓我直截了當地說,至少現在檢測線上的點擊,而不是跑步。仍試圖找出那一個。 – jer 2010-09-27 14:07:49

回答

8

我剛剛做了這個從觸摸位置獲取字符串字符索引。行號是我在這種情況下:

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

謝謝尼克你救了我! :) – stefanosn 2012-12-20 02:04:24

+0

這很好,我可以比較這一點,我已經爲可點選的單詞存儲的範圍。這比我的老辦法快得多 - 謝謝你:) – 2013-01-24 23:00:56

+0

我如何獲得ctFrame? – 2014-06-09 05:30:20

0

你可以嘗試添加你的文本繪圖到CALayer中,添加這個新圖層作爲你的視圖層的子圖層並在你的touchesEnded中對它進行測試?如果你這樣做,你應該能夠創建一個更大的可觸摸區域,使圖層大於繪製文本。

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

然後我會遇到另一個問題,就是不得不將我的文本分成兩個不同的層 - 我想觸發委託的那一層,然後是其他層。我遇到的問題是,如果URL位於一行的中間,並且前後有文本,會發生什麼情況?然後我不得不分裂成許多不同的層。如果我呈現的文字組中有幾個網址,它可能會變得毛茸茸的。我寧願只是獲取attrib字符串中所有項目的列表,我已將其着色爲藍色,並獲取它們的邊界框,查看用戶是否在那裏輕敲並用該URL調用委託。 – jer 2010-09-27 12:19:01

+0

沒錯,這可能是一個問題。也許我不明白你的問題是正確的。如何保持文本原樣並覆蓋可觸擊圖層來捕捉這些觸摸? – msg 2010-09-27 12:23:23

+0

我仍然需要知道確切位置 - 這意味着我仍然需要獲取URL的矩形。這讓我回到原點。 :) – jer 2010-09-27 12:45:30

0

斯威夫特3版尼克H247的回答:

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