React-native-keyboard-aware-scroll-view: Force scroll update

Created on 7 May 2018  路  6Comments  路  Source: APSL/react-native-keyboard-aware-scroll-view

I am trying to leverage this component with custom components, events, and even a multiline TextInput (custom growing component). I think moving forward what I need is to be able to fire an event to force an update. This would be recalculating scroll position based on a view or component changing it's size.

What I've tried thus far is to add an update function to the render method.

return (
    <ScrollableComponent
        ...
        onScroll={this._onScroll}
        update={this.update}
    />
)

Now, some help moving forward to force this update would be much appreciated. So far I've tried the following methods with no success.

scrollIntoView(el) // Weird behavior, see below
scrollToFocusedInput(el) // Weird JSON stringify errors
this._resetKeyboardSpace() // this throws due to it being a Keyboard listener event.
this._updateKeyboardSpace // this throws due to it being a Keyboard listener event.

scrollIntoView(el) does something weird. When calling this, the screen bounces around. What I'm looking for is just to nudge the keyboard up to the bottom of this component when it's starting to scroll off screen.

Perhaps getting the coords (in conjunction with _measureElement) of the component and then firing resetScrollToCoords would work.

The code I ended up with, and similar results to scrollIntoView (with no surprise since the code was borrowed from around there).

update = async (element) => {

  if (!this._rnkasv_keyboardView || !element) {
    return
  }

  const [
    parentLayout,
    childLayout
  ] = await Promise.all([
    this._measureElement(this._rnkasv_keyboardView),
    this._measureElement(element)
  ])

  const getScrollPosition = this._defaultGetScrollPosition
  const { x, y, animated } = getScrollPosition(parentLayout,
  childLayout, this.position)

  this.scrollToPosition(x, y, animated)

}

Any and all help is much appreciated.

Most helpful comment

Finally have a solution! Here's the working code. I'll try to upload a video later today.

In component file:

render() {
    return (
        <KeyboardAwareScrollView extraScrollHeight={ 100 } ref='KeyboardAwareScrollView'>
            <TextInput ... />
        </KeyboardAwareScrollView>
     )
}

componentDidUpdate() {
  let keyboardScrollView = this.refs.KeyboardAwareScrollView

  if (keyboardScrollView)
    keyboardScrollView.update()
}

In KeyboardAwareHOC.js

update = () => {
  const currentlyFocusedField = TextInput.State.currentlyFocusedField()
  const responder = this.getScrollResponder()

  if (!currentlyFocusedField || !responder) {
    return
  }

  this._scrollToFocusedInputWithNodeHandle(
    currentlyFocusedField
  )
}

I'm not sure why this.getScrollResponder() is required, there's nothing else in the code that implies this changes any variables. However without this code, the ScrollView component will continue to bounce aimlessly while adding new lines to a multiline TextInput.

Another oddity is why bypassing _scrollToFocusedInputWithNodeHandle with scrollToFocusedInput altogether makes this behave so weirdly; the code for that is pretty simple. Might have something to do with ReactNative.findNodeHandle(nodeID)? I see nothing in here really worth value on x,y.

_scrollToFocusedInputWithNodeHandle = (
  nodeID: number,
  extraHeight?: number,
  keyboardOpeningTime?: number
) => {
  if (extraHeight === undefined) {
    extraHeight = this.props.extraHeight
  }
  const reactNode = ReactNative.findNodeHandle(nodeID)
  this.scrollToFocusedInput(
    reactNode,
    extraHeight + this.props.extraScrollHeight,
    keyboardOpeningTime !== undefined
      ? keyboardOpeningTime
      : this.props.keyboardOpeningTime || 0
  )
}

All 6 comments

Further experimentation has lead me to try to calculate the position by hand without helper functions. The result is still unsightly.

 update = async (element) => {

  ...

  let x = 0
  let y = Math.max(0, childLayout.y - parentLayout.y)
  const animated = false

  this.scrollToPosition(x, y, animated)

}

I think a new approach of trying to determine the offset of textInputBottomPosition might be a solution. Some code I found could hint at further experimentation.

UIManager.measureInWindow(
  currentlyFocusedField,
  (x: number, y: number, width: number, height: number) => {
    const textInputBottomPosition = y + height
    const keyboardPosition = frames.endCoordinates.screenY
    const totalExtraHeight =
      this.props.extraScrollHeight + this.props.extraHeight
    if (Platform.OS === 'ios') {
      if (
        textInputBottomPosition >
        keyboardPosition - totalExtraHeight
      ) {
        this._scrollToFocusedInputWithNodeHandle(
          currentlyFocusedField
        )
      }
    }
  }

Finally have a solution! Here's the working code. I'll try to upload a video later today.

In component file:

render() {
    return (
        <KeyboardAwareScrollView extraScrollHeight={ 100 } ref='KeyboardAwareScrollView'>
            <TextInput ... />
        </KeyboardAwareScrollView>
     )
}

componentDidUpdate() {
  let keyboardScrollView = this.refs.KeyboardAwareScrollView

  if (keyboardScrollView)
    keyboardScrollView.update()
}

In KeyboardAwareHOC.js

update = () => {
  const currentlyFocusedField = TextInput.State.currentlyFocusedField()
  const responder = this.getScrollResponder()

  if (!currentlyFocusedField || !responder) {
    return
  }

  this._scrollToFocusedInputWithNodeHandle(
    currentlyFocusedField
  )
}

I'm not sure why this.getScrollResponder() is required, there's nothing else in the code that implies this changes any variables. However without this code, the ScrollView component will continue to bounce aimlessly while adding new lines to a multiline TextInput.

Another oddity is why bypassing _scrollToFocusedInputWithNodeHandle with scrollToFocusedInput altogether makes this behave so weirdly; the code for that is pretty simple. Might have something to do with ReactNative.findNodeHandle(nodeID)? I see nothing in here really worth value on x,y.

_scrollToFocusedInputWithNodeHandle = (
  nodeID: number,
  extraHeight?: number,
  keyboardOpeningTime?: number
) => {
  if (extraHeight === undefined) {
    extraHeight = this.props.extraHeight
  }
  const reactNode = ReactNative.findNodeHandle(nodeID)
  this.scrollToFocusedInput(
    reactNode,
    extraHeight + this.props.extraScrollHeight,
    keyboardOpeningTime !== undefined
      ? keyboardOpeningTime
      : this.props.keyboardOpeningTime || 0
  )
}

Hi @bugs181!

Would you like to send us a PR for this? Thanks!

Sure thing, please check #251

This worked for me. Had to update the KeyboardAwareHOC.js manually. When will this be available as a standard part of the package?

@alexcittadini this was merged in
https://github.com/APSL/react-native-keyboard-aware-scroll-view/releases/tag/v0.6.0, please check that you're using the latest version.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

shimil2017 picture shimil2017  路  3Comments

EdwardDrapkin picture EdwardDrapkin  路  4Comments

brunolemos picture brunolemos  路  3Comments

diegorodriguesvieira picture diegorodriguesvieira  路  4Comments

snksergio picture snksergio  路  3Comments