Skip to content

Locate text break (feature request) #3

Description

@Ario99

I needed to measure text to prevent a TextInput from determining the location of the text break itself, then resizing. I wanted the TextInput to fit it's content into a maximum width, so I used a patch to this package to calculate what needed to be the minimum width of the TextInput. I thought I would share since I never found any solutions to this problem online and it being a likely valuable feature to this package.

To see how what this solution solves, center a TextInput with a maxWidth. (I used a percentage amount.) The TextInput will change it's width depending on the first optimal text break found for that maxWidth of the TextInput. I never found a way to disable this behavior, so my solution to this was a work around.

My patch:

Function("findTextBreak") { (text: String, style: StyleOptions) -> Int in
 if (text.count == 0) {
   return 0
 }

 let fontSize = style.fontSize != nil ? RCTConvert.cgFloat(style.fontSize) : CGFloat(0)
 let letterSpacing = style.letterSpacing != nil ? RCTConvert.cgFloat(style.letterSpacing) : CGFloat(0)
 let usedFontWeight = style.fontWeight ?? "Normal"
 let maxWidth = style.maxWidth ?? nil

 if let font = style.fontFamily != nil ?
   RCTConvert.uiFont(["fontWeight": usedFontWeight, "fontFamily": style.fontFamily!, "fontSize": fontSize, NSAttributedString.Key.kern: letterSpacing ]) : RCTConvert.uiFont(["fontWeight": usedFontWeight, "fontSize": fontSize])
 {
   var minima: Int = 0, maxima : Int = text.count - 1, median: Int? = nil
   var currWidthCalc: Double? = nil
   var maxWidthSurpassed: Bool? = nil
   repeat {
    if (maxima - minima > 1) { // if an index lies between the minima and maxima
        median = ((maxima + minima) / 2)
     } else {
       median = maxima
     }
     let medianStrIndex = text.index(text.startIndex, offsetBy:(median ?? 0) + 1)
     currWidthCalc = String(text[..<medianStrIndex]).size(withAttributes: [.font: font]).width
     maxWidthSurpassed = currWidthCalc ?? Double(0) > maxWidth ?? Double(0)
     if (maxima - minima == 1) {
        return 1 + ((maxWidthSurpassed ?? false) ? minima : median ?? 0)
      }
      if (maxWidthSurpassed ?? false) { // if median point of string results in passing the maxWidth
        maxima = median ?? 0 // set maximum searchable index
      } else {
        minima = median ?? 0 // set minimum searchable index
      }
   } while (true)
   return 1 + ((maxWidthSurpassed ?? false) ? minima : median ?? 0)
 } else {
   return text.count
 }
}
export function findTextBreak(
  text: string,
  style: { fontSize?: number; fontFamily?: string; fontWeight?: string, letterSpacing?: number, allowFontScaling?: boolean, maxWidth?: number } = {}
): number {
  return ReactNativeMeasureText.findTextBreak(text, style);
}

Usage:

import React, { useState } from "react";
import { StyleSheet, TextInput, Text, View, Dimensions } from "react-native"
import * as MeasureText from "@domir/react-native-measure-text";

const textMaxWidth = Dimensions.get("window").width * .8

export default function TextMeasuring() {
   const [value, setValue] = useState("")
   
   const displayCharCnt = MeasureText.findTextBreak(value, {
      ...styles.nameInput,
      allowFontScaling: false,
      maxWidth: textMaxWidth,
   })

   const textMinWidth = clamp(MeasureText.measureWidth(value.slice(0, displayCharCnt).trimEnd(), {
      ...styles.nameInput,
      allowFontScaling: false,
   }), textMaxWidth)

   return (
      <View style={{ width: "100%", alignItems: "center", backgroundColor: "#444", }}>
         <TextInput
            style={[styles.nameInput, { minWidth: textMinWidth, maxWidth: textMaxWidth, }]}
            value={value}
            onChangeText={setValue}
            placeholder="?"
            placeholderTextColor="white"
            numberOfLines={1}
         />
         <Text style={styles.nameInput} numberOfLines={1}>{displayCharCnt}</Text>
      </View>
   )
}

const styles = StyleSheet.create({
   nameInput: {
		// maxWidth: "75%",
		color: "white",
		textAlign: "center",
		fontSize: 30,
		minWidth: 20,
	},
	label: {
		color: "white",
		textAlign: "center",
		fontSize: 30,
      maxWidth: textMaxWidth
	},
})

function clamp(value, max) {
	return value >= max ? max : value
}

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions