Skip to content

fix(ios): make lineHeight work for both normal lines and headings; sync measurements - #12

Closed
hsource wants to merge 2 commits into
mainfrom
fix/ios-input-line-height-measurement
Closed

hsource wants to merge 2 commits into
mainfrom
fix/ios-input-line-height-measurement

Conversation

@hsource

@hsource hsource commented Sep 23, 2026 •

Copy link
Copy Markdown

Note: this PR has been manually reviewed and is a version of code that's been part of the Wanderlog (wanderlog.com) mobile apps for the past 3 weeks

What/Why?

Fixes software-mansion#615 on iOS. This is the iOS counterpart of the Android fix in #11.

lineHeight was ignored. style.lineHeight reached the native props, but nothing in the editor applied it.

  • It's now stored as baseLineHeight on ENRMInputFormatterStyle, read in applyInputStyleProps.
  • It's applied as a paragraph minimumLineHeight/maximumLineHeight, with a baseline offset so glyphs sit centred in the line like React Native Text.
  • It covers every non-heading paragraph, including list items, so toggling a list can't drop the line height. It's also added to the typing attributes (resetBaseTypingAttributes and the per-block typing attributes), so new text picks it up right away.
  • Headings use their font size plus the body's extra leading (lineHeight - fontSize), the same rule as the Android PR. A large heading doesn't get clipped to the body line height.

Measurement and drawing now agree, and match RN.

  • Side fix (for consistency): The input's NSLayoutManager had usesFontLeading at its default of YES, so each font's built-in leading was added on top of the line height. React Native turns it off, and fix(ios): use same font leading setting for measurement and drawing software-mansion/enriched-markdown#784 did the same for the read-only markdown view. It's now off for the input too. All six layout managers that draw or measure markdown (read-only view, view-free measurement, code blocks, and the input) now read one ENRMLayoutManagerUsesFontLeading constant in ENRMUIKit.h, which also carries the explanation, so they can't drift apart.
  • measureSize: used boundingRectWithSize with NSStringDrawingUsesFontLeading. It now uses ENRMMeasureAttributedTextViewFree, the same TextKit path as the read-only view, so the reported height matches what's drawn.
  • Fonts with nonzero leading (for example Geeza Pro for Arabic) no longer add extra space below the text.
  • ENRMMeasureAttributedTextViewFree gets a subtractExtraLineFragment flag. The read-only view keeps passing YES. The input passes NO, so a blank last line after a trailing newline still counts toward the height, like RN TextInput. That replaces the old workaround that appended an "I" for trailing newlines.

Runtime style changes apply to plain text. updateProps used to reformat existing text only when it had inline formatting ranges. Changing lineHeight, fontSize or color on plain text only affected newly typed text. It now reformats whenever the style changes and the text isn't empty.

Compared with software-mansion#664: on iOS that PR adds a similar baseLineHeight, but headings keep the body line height, and measurement still uses boundingRectWithSize with font leading on while the view draws with a different layout manager. This PR makes the input follow React Native Text/TextInput layout for both drawing and measuring.

The playground gets the same LH toggle as #11 (the code is identical, so the two PRs merge cleanly in either order), plus an iOS Maestro flow.

Testing

Checks run: clang-format on all changed iOS files, yarn typecheck, eslint, prettier, and an example app build for the iOS simulator (iPhone 17, iOS 26.4).

Test results

line_height_input_ios_test.yaml passes on the iPhone 17 simulator. Its screenshot step was run in capture mode, because the golden has to come from the CI iOS 26.5 simulator.

Left: default line height. Right: after tapping LH 36. The heading, body and list lines are evenly spaced and vertically centred, the heading keeps its larger derived height, and the editor grows to fit the content with no extra space at the bottom.

Full flow recording Final state

Manual testing

Setup:

yarn && yarn prepare
yarn react-native-example start
yarn react-native-example ios

In the example app, open Playground:

  1. Tap Set markdown and enter:
    # Heading
    Line one of body text
    - List item alpha
    Line two of body text
    
  2. Tap LH auto so it reads LH 36. Expected: lines are evenly spaced and vertically centred. The heading is taller but keeps the same spacing rhythm and isn't clipped. The editor grows to fit all lines, with no internal scrolling and no extra blank space at the bottom.
  3. Tap it again to go back to LH auto. Expected: spacing returns to the default right away, including on lines with no formatting. Before this change, plain text didn't update until you typed.
  4. With LH 36 on, type a few lines, then press Enter at the end. Expected: the editor grows by one line for the new blank line, and new text uses the tall line height straight away.
  5. With LH 36 on, toggle a bullet list on and off, and a heading (H1) on and off, on a body line. Expected: body and list lines stay at 36, and the heading uses its derived height.
  6. Tap Set markdown again with text that has list items followed by plain lines. Expected: the plain lines aren't indented. This is a regression check for fix(ios): reset typing attributes before importing markdown software-mansion/enriched-markdown#646.
  7. Leading check: temporarily set the input's fontFamily to Geeza Pro (or paste Arabic text into the input). Expected: no extra blank space under the last line.
  8. Placeholder: clear the input. Expected: the empty editor is one line tall.
  9. Regression check for the read-only view: open a few EnrichedMarkdownText screens (Commonmark, GFM, and a screen with numberOfLines). Expected: heights are unchanged.

Automated flow (screenshot-based; capture the golden first):

yarn test:e2e:ios:update-screenshots .maestro/enrichedMarkdownInput/flows/basic/block_elements/line_height_input_ios_test.yaml
yarn test:e2e:ios .maestro/enrichedMarkdownInput/flows/basic/block_elements/line_height_input_ios_test.yaml

PR Checklist

  • Code compiles and runs on iOS
  • Code compiles and runs on Android
  • Updated documentation/README if applicable
  • Ran example app to verify changes
  • E2E tests are passing
  • Required E2E tests have been added (if applicable)

@hsource
hsource force-pushed the fix/ios-input-line-height-measurement branch from f3007f6 to 5c9ce1d Compare September 23, 2026 21:09
hsource and others added 2 commits September 23, 2026 14:13
The input ignored style.lineHeight: it reached native props but nothing
applied it. Store it as baseLineHeight on ENRMInputFormatterStyle and
apply it as a paragraph min/max line height (with a baseline offset) to
every non-heading paragraph, list items included, and to the typing
attributes. Headings use their font size plus the body's extra leading.

Measure the input with the same TextKit path as the read-only view and
turn off usesFontLeading on the input's layout manager, like React
Native's RCTTextLayoutManager. Measurement and drawing now agree, and
fonts with built-in leading no longer add extra space. The trailing
newline's extra line fragment is kept so a blank last line still counts.

A style change now reformats existing plain text too, not only text with
inline formatting, so changing lineHeight at runtime applies immediately.

Co-authored-by: Cursor <cursoragent@cursor.com>
The playground toggle switches the input between the default line height
and 36, to check rendering and auto-grow with headings and lists.

Co-authored-by: Cursor <cursoragent@cursor.com>
@hsource
hsource force-pushed the fix/ios-input-line-height-measurement branch from 5c9ce1d to be3555f Compare September 23, 2026 21:13
@hsource hsource changed the title fix(ios): lineHeight and measurement parity with RN TextInput fix(ios): make lineHeight work for both normal lines and headings; sync measurements Sep 24, 2026
@hsource

hsource commented Sep 24, 2026

Copy link
Copy Markdown
Author

Moved upstream: software-mansion#886

@hsource hsource closed this Sep 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Android: editor lineHeight broken — render mixes dp/px AND auto-grow measurement ignores it

1 participant