zacwest / ZSWTappableLabel

UILabel subclass for links which are tappable, long-pressable, 3D Touchable, and VoiceOverable.
MIT License
169 stars 37 forks source link

Compatibiliy with kCTRubyAnnotationAttributeName #45

Closed alamodey closed 3 years ago

alamodey commented 3 years ago

I've got a UILabel with ruby text using kCTRubyAnnotationAttributeName, but once I wrap it in ZSWTappableLabel, the ruby text disappears. Since ZSWTappableLabel is a subclass of UILabel, is it meant to eliminate the ability to create ruby text with kCTRubyAnnotationAttributeName?

zacwest commented 3 years ago

Can you provide sample code of your configuring the label?

alamodey commented 3 years ago
import UIKit
import CoreText
import ZSWTappableLabel

class ViewController: UIViewController, ZSWTappableLabelTapDelegate, ZSWTappableLabelLongPressDelegate {

  @IBOutlet var textLabel: ZSWTappableLabel!

  override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view.
    textLabel.tapDelegate = self
    textLabel.longPressDelegate = self

    let word = "食べ物"
    let ruby = "たべもの"

    let attributes: [NSAttributedString.Key: Any] = [
      .tappableRegion: true,
      //.tappableHighlightedBackgroundColor: UIColor.lightGray,
      //.tappableHighlightedForegroundColor: UIColor.white,
      //kCTRubyAnnotationAttributeName as NSAttributedString.Key: annotation,
      .foregroundColor: UIColor.blue,
      //.font: UIFont.systemFont(ofSize: 32)
      //.underlineStyle: NSUnderlineStyle.single.rawValue
    ]

    //textLabel.attributedText = NSAttributedString(string: word, attributes: attributes)

    var text = furigana(main: word, furigana: ruby)
    text.addAttributes(attributes, range: NSMakeRange(0, text.length))

    textLabel.attributedText = text

  }

  func tappableLabel(_ tappableLabel: ZSWTappableLabel, tappedAt idx: Int, withAttributes attributes: [NSAttributedString.Key : Any] = [:]) {
    print("tapped at: ", idx)

  }

  func tappableLabel(_ tappableLabel: ZSWTappableLabel, longPressedAt idx: Int, withAttributes attributes: [NSAttributedString.Key : Any]) {
    print("long-pressed at: ", idx)
  }

  func furigana(main: String, furigana: String) -> NSMutableAttributedString {

    var ruby = furigana
    var unmanage = Unmanaged.passRetained(ruby as CFString)
    defer { unmanage.release() }
    var text: [Unmanaged<CFString>?] = [unmanage, .none, .none, .none]

    let rubyAttribute: [AnyHashable: Any] = [
        kCTRubyAnnotationSizeFactorAttributeName: 0.5,
        kCTForegroundColorAttributeName: UIColor.black
    ]

    let annotation = CTRubyAnnotationCreateWithAttributes(.auto, .auto, .before, ruby as CFString, rubyAttribute as CFDictionary)

    let attributed = NSMutableAttributedString(
      string: main,
      attributes: [kCTRubyAnnotationAttributeName as NSAttributedString.Key: annotation,
                   .foregroundColor: UIColor.blue,
                   ])

    let paragraphStyle = NSMutableParagraphStyle()
    paragraphStyle.lineHeightMultiple = 1.0
    paragraphStyle.lineSpacing = 12

    attributed.addAttribute(NSAttributedString.Key.paragraphStyle, value: paragraphStyle, range: NSMakeRange(0, attributed.length))
    attributed.addAttributes([NSAttributedString.Key.font: UIFont.systemFont(ofSize: 32)],range: NSMakeRange(0, (attributed.length)))

    return attributed
  }
}

The desired visual output is when you comment out .tappableRegion: true , which defeats the purpose of using this custom label as I want to detect taps and long presses on specific regions. Thanks!

alamodey commented 3 years ago

With tappable region disabled:

image

With tappable region enabled:

image
zacwest commented 3 years ago

I do not believe this is an issue with ZSWTappableLabel. If you instead use directly UILabel in your example, it still will not render the second layer of text; this happens even when using an attribute key outside of ZSWTappableLabel, such as the following:

.init("test"): true,

Nothing immediately jumps out at me as to why this is happening. I tried replacing keys/values with CF equivalents (e.g. kCFBooleanTrue) but it didn't work. It must be tripping some weird code path in TextKit. This also still occurs if you draw yourself:

// overriding text drawing in ZSWTappableLabel to see what it looks like here
- (void)drawTextInRect:(CGRect)rect
{
    [self performWithTouchHandling:^(ZSWTappableLabelTouchHandling *th) {
        [th.layoutManager drawGlyphsForGlyphRange:NSMakeRange(0, th.unmodifiedAttributedString.length)
                                          atPoint:th.pointOffset];
    }];
}

I suggest filing a Technical Support Incident with Apple to see if they can help you find a workaround, that's probably your best bet if you want to be able to use attributed string keys alongside these ruby values.

It might also be worth seeing if a straight-Objective-C (rather than Swift) implementation works.