Fix TextKit/UILabel sync: use CoreText for hit testing#74
Open
pannous wants to merge 3 commits intolixiang1994:TextKit=UILabelfrom
Open
Fix TextKit/UILabel sync: use CoreText for hit testing#74pannous wants to merge 3 commits intolixiang1994:TextKit=UILabelfrom
pannous wants to merge 3 commits intolixiang1994:TextKit=UILabelfrom
Conversation
TextKit's NSLayoutManager calculates line heights differently from UILabel, causing misalignment with mixed Chinese/English fonts, numberOfLines truncation, and byCharWrapping mode. Since UILabel internally uses CoreText for text layout, switching to CoreText (CTFramesetter/CTFrame/CTLine) directly for hit testing and debug overlay eliminates these discrepancies. Key changes: - Use CTFramesetter/CTFrame for text layout instead of NSLayoutManager - Use UILabel.textRect(forBounds:limitedToNumberOfLines:) for accurate positioning - Handle line truncation with CTLineCreateTruncatedLine for visual matching - Remove lineBreakMode adaptation hack (CoreText handles all modes natively) - Debug overlay now uses CTLineDraw for closer visual match with UILabel Fixes lixiang1994#12
Use vertical midpoints between adjacent lines for tap detection instead of strict ascent/descent bounds. This ensures taps in inter-line spacing are correctly assigned to the nearest line.
UILabel uses CoreText internally, not TextKit. The previous approach used NSLayoutManager which has known line height discrepancies with UILabel, especially for mixed Chinese/English fonts and numberOfLines truncation (e.g. abc\n\n\ndefg with numberOfLines=2). This replaces the TextKit-based matching() with CoreText (CTFramesetter/ CTFrame/CTLine) which matches UILabel's actual rendering engine: - Uses textRect(forBounds:limitedToNumberOfLines:) for positioning - Midpoint-based vertical line boundary splitting (no inter-line gaps) - Accounts for text alignment offset in horizontal hit testing - CoreText handles lineBreakMode natively (no adaptation() workaround) Fixes lixiang1994#12
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Resolves #12 — the $100 bounty for fixing TextKit and UILabel content synchronization.
Problem
TextKit's
NSLayoutManagercalculates line heights differently fromUILabel, causing misalignment when:numberOfLinestruncation (e.g.,"abc\n\n\ndefg"withnumberOfLines = 2)lineBreakMode = .byCharWrappingRoot Cause
UILabelinternally uses CoreText for text layout, while the hit testing code was building a separate TextKit layout. These two engines have different line height calculation strategies, making pixel-perfect matching extremely difficult.Solution
Replace TextKit (
NSLayoutManager/NSTextContainer/NSTextStorage) with CoreText (CTFramesetter/CTFrame/CTLine) for both hit testing and the debug overlay. Since UILabel itself uses CoreText, the line heights and glyph positions now match inherently.Key changes:
matching(_ point:)usesCTFramesetterCreateFrame+CTLineGetStringIndexForPositionfor hit testingUILabel.textRect(forBounds:limitedToNumberOfLines:)for accurate text positioningorigins[i].x) in horizontal hit testingadaptation()hack — CoreText handles alllineBreakModevalues nativelyCTLineDrawfor visual verificationprint(scaledMetrics)fromUILabelLayoutManagerDelegateWhat this fixes
numberOfLinestruncation calculation errorslineBreakMode = .byCharWrappinggarbled renderingabc\n\n\ndefg)Tests (all pass)
testCoreTextHeightMatchesUILabelForMixedText— CoreText height within 2pt of UILabel textRecttestCoreTextLineCountMatchesUILabelWithNumberOfLines— correct line limitingtestCoreTextHandlesNewlinesWithNumberOfLines— theabc\n\n\ndefgbug verified fixedtestCoreTextHitTestingMixedFonts— accurate character index for mixed font textBackward compatibility
UILabelLayoutManagerDelegatepreserved but no longer used bymatching()— safe to remove later