Site icon May's Notes

[React Native] Text onTextLayout 在 Android, iOS 上回傳不一樣的行數

React Native logo

若 Text 設置了 numberOfLines,onTextLayout 在 Android 上可以獲取總文字行數,iOS 則不行,最多只能拿到 numberOfLines 的行數。

比如下方這段文字:

「有人嗎?有…」一條來自高皓軍(劉德華 飾)的神祕訊息從萬呎高空傳來國際安保專家高皓軍和失明的女兒小軍(張子楓 飾)搭乘有「空中巨無霸」之稱的五星級超豪華客機 A380 的國際首航,途中遭遇暴徒劫機。無差別射殺的恐怖手段,讓豪華機艙瞬間變成密閉煉獄,800 多名乘客危在旦夕,高皓軍挺身而出,在數千米的高空上與一眾暴徒周旋,女兒卻受困於機艙中,劫匪頭目 Mike(屈楚蕭 飾)以全機 800 餘人的生命作為籌碼威脅,小軍的媽媽傅源(劉濤飾)也身陷危險之中,飛機能否平安降落,這場失控的危機該如何化解?

在 Android 輸出的行數為 11,但在 iOS 上輸出的行數為 5:

const onTextLayout = (e: NativeSyntheticEvent<TextLayoutEventData>) => { 
  console.log(e.nativeEvent.lines.length) // iOS: 5, Android: 11
}

<Text numberOfLines={maxLines} onTextLayout={onTextLayout}>
  {children}
</Text>

在 iOS 上 e.nativeEvent.lines 的格式如下:

[
  {
    "ascender": 15.234375,
    "capHeight": 13.76,
    "descender": 3.859375,
    "height": 24,
    "text": "「有人嗎?有...」一條來自高皓軍(劉德華 飾)的",
    "width": 330.744375,
    "x": 0,
    "xHeight": 9.6,
    "y": 0
  },
  {
    "ascender": 15.234375,
    "capHeight": 13.76,
    "descender": 3.859375,
    "height": 24,
    "text": "神祕訊息從萬呎高空傳來國際安保專家高皓軍",
    "width": 326.4,
    "x": 0,
    "xHeight": 9.6,
    "y": 24
  },
  {
    "ascender": 15.234375,
    "capHeight": 13.76,
    "descender": 3.859375,
    "height": 24,
    "text": "和失明的女兒小軍(張子楓 飾)搭乘有「空中巨",
    "width": 325.86125,
    "x": 0,
    "xHeight": 9.6,
    "y": 48
  },
  {
    "ascender": 15.234375,
    "capHeight": 13.76,
    "descender": 3.859375,
    "height": 24,
    "text": "無霸」之稱的五星級超豪華客機 A380 的國際",
    "width": 325.674375,
    "x": 0,
    "xHeight": 9.6,
    "y": 72
  },
  {
    "ascender": 15.234375,
    "capHeight": 13.76,
    "descender": 3.859375,
    "height": 24,
    "text": "首航,途中遭遇暴徒劫機。無差別射殺的恐怖手段,讓豪華機艙瞬間變成密閉煉獄,800 多名乘客危在旦夕,高皓軍挺身而出,在數千米的高空上與一眾暴徒周旋,女兒卻受困於機艙中,劫匪頭目 Mike(屈楚蕭 飾)以全機 800 餘人的生命作為籌碼威脅,小軍的媽媽傅源(劉濤飾)也身陷危險之中,飛機能否平安降落,這場失控的危機該如何化解?",
    "width": 326.4,
    "x": 0,
    "xHeight": 9.6,
    "y": 96
  }
]

根據上方的格式可以確定前四行的寬度一定是拉滿的才會換行(但不一定都一樣長),所以只要先找到前四行的最短寬度,再和第五行的寬度做比較,若第五行寬度超過(或相等)前四行的最短寬度,就代表總行數超過五行。

const onTextLayout = (e: NativeSyntheticEvent<TextLayoutEventData>) => {
  const { lines } = e.nativeEvent
  if (lines.length < maxLines) return

  const shortestWidth = lines.slice(0, maxLines - 1)
    .reduce((acc, cur) => cur.width < acc ? cur.width : acc, lines[0].width)

  if (lines[maxLines - 1].width >= shortestWidth) {
    setIsLongText(true)
  }
}
Exit mobile version