facebook / react-native

A framework for building native applications using React
https://reactnative.dev
MIT License
118.69k stars 24.29k forks source link

[Blur & Focus] Support relinquishing focus from multiple views via container #113

Closed ide closed 9 years ago

ide commented 9 years ago

If you have two text inputs in a container view, calling container.blur() could make both of the text inputs try to resign their first responder status. In UIKit this is built-in as -[UIView endEditing:animated]. This way you can just blur the root view if you want the keyboard to go away.

vjeux commented 9 years ago

I'm not sure I understand. You can do .blur() on Text ref right now. Do you want to do blur() on an arbitrary View?

ide commented 9 years ago

Yeah, it blurs any descendant that has focus. The descendant could be a TextInput but could also be any other kind of input element that can become the first responder.

vjeux commented 9 years ago

You can actually call blur() on a View today. See NativeMethodMixin::blur. Can you tell me if it's doing what you want?

brentvatne commented 9 years ago

ping @ide

ide commented 9 years ago

Confirmed that this.refs.parentView.blur does not blur its child views. Where this would be handy is in the navigator stack where a scene will lose focus without being unmounted, and you want to make sure to blur all of its text fields. So instead of

this.refs.textInput1.blur();
this.refs.textInput2.blur();
this.refs.textInput3.blur();

you want to write

this.refs.container.blur();
brentvatne commented 9 years ago

This does seem like it would be useful! Another issue came up (referenced above) where this would solve the problem. cc @ide @vjeux

dhrrgn commented 9 years ago

@brentvatne I came up with a simple solution for this internally where I wrote a component that essentially just extends UIView and blurs all of the subviews on touch. Here is the guts of it:


@implementation KBCloserView

- (void)didMoveToSuperview
{
    self.userInteractionEnabled = TRUE;
    [super didMoveToSuperview];
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    for (UIView *subview in self.subviews) {
        [subview endEditing:YES];
    }
}

@end

So I just convert my container View's to <KBCloserView> components, and :boom: tap anywhere in the view to close the keyboard.

I share this because I think adding a closeKeyboardOnPress type property to the RN View component, which just does the above, would be simple and solve this problem entirely without having to go through the effort of changing how blur works (I personally like that it only blurs the specific input).

brentvatne commented 9 years ago

Nice on @dhrrgn! Seems simple enough. Might be worth putting into a library on npm until a solution is integrated into core

dhrrgn commented 9 years ago

@brentvatne I will do that tomorrow...right after I figure out how to create an Xcode project people can just add to their's (bit of an Xcode "noob") haha. (Side note: it would be awesome if their was a "Creating Distributable Native Components" section of the docs for this sort of thing).

brentvatne commented 9 years ago

@dhrrgn - I have something about that on my blog: Packaging a React Native component, I hesitated to add it to the docs because it seemed like a temporary solution, but we haven't worked out a better one yet (aside from react-native new-library from within a React Native project, in order to create a skeleton bridge module component - you could do this and just change it up accordingly rather than create a new project from scratch as I mention in my blog post)

dhrrgn commented 9 years ago

@brentvatne Awesome, thanks.

grabbou commented 9 years ago

Any updates on that? Calling blur on a view should act like endEditing as per @ide suggestion. If not, maybe worth adding another method.

My use case: Using @brentvatne modal with click backdrop to dismiss feature. When the keyboard is opened, I'd prefer closing the keyboard instead of dismissing the modal so user has the ability to continue editing. Not all keyboard types are provided done button as of now (e.g. numpad) so it gets tricky.

brentvatne commented 9 years ago

@grabbou - do you have time to make the changes that @ide mentioned above and submit a PR?

grabbou commented 9 years ago

Yeah, can do. Just need to dig into the source code as I haven't contributed yet but would love to.

Another thing worth noticing is that the method signature is actually endEditing:force instead of mentioned :animated.

After doing a couple of more tests it looks like the this.refs.mainView.blur() blurs all the child TextViews (including TextInputs that are inside another view).

Sample structure returned from render:

<View ref='modal'>
    <View style={styles.container}>
          <TextInput .... />
          <TextInput .... />
    </View>
</View>

and the invocation:

this.refs.modal.blur();
brentvatne commented 9 years ago

@ide - what do you think about @grabbou's observation above?

ide commented 9 years ago

Not sure why that's happening. Maybe something changed. Feel free to close this if it just works now.

brentvatne commented 9 years ago

@ide - seems to be a false alarm, still not working for me with 0.5: https://rnplay.org/apps/SwEnTw - tried it against master as well, same thing: https://rnplay.org/apps/AxZnJw

@grabbou - can you reproduce that fix on rnplay.org?

Still an outstanding issue as far as I can tell

admmasters commented 9 years ago

Not working for me either - an outstanding issue I'm afraid.

brentvatne commented 9 years ago

Anyone up for fixing this? Would be a great way to learn about some of the React Native internals

josebalius commented 9 years ago

It is still outstanding, I made another rnplay: https://rnplay.org/apps/aAb8aA/ to showcase the problem with 0.6.0 and can reproduce with 0.8.0-rc, I'm going to give this a try with my limited Objective-C skills.

brentvatne commented 9 years ago

It looks like nobody on the team at Facebook has a need for this at the moment so I've tagged it as Community Responsibility :smile:

josebalius commented 9 years ago

@brentvatne So i went off and tried to do it, and I have something working but wanted to show you first as this is my first time messing with the internals so want to see if I should change my approach.

In NativeMethodsMixin.js I added the following under blur():

endEditing: function() {
    RCTUIManager.endEditing(findNodeHandle(this));
  }

In RCTUIManager.m I did the following:

- (void)endEditingForShadowView:(NSNumber *)reactTag manager:(RCTUIManager *)uiManager viewRegistry:(RCTSparseArray *)viewRegistry
{
    RCTShadowView *shadowView = uiManager.shadowViewRegistry[reactTag];

  for(RCTShadowView *subview in [shadowView reactSubviews]) {
      if([[subview reactSubviews] count] > 0) {
          [uiManager endEditingForShadowView:subview.reactTag manager:uiManager viewRegistry:viewRegistry];
      } else {
          UIView *view = viewRegistry[subview.reactTag];
          [view resignFirstResponder];
      }
  }
}

RCT_EXPORT_METHOD(endEditing:(NSNumber *)reactTag)
{
  if (!reactTag) return;
  [self addUIBlock:^(__unused RCTUIManager *uiManager, RCTSparseArray *viewRegistry){
      [uiManager endEditingForShadowView:reactTag manager:uiManager viewRegistry:viewRegistry];
  }];
}

My test case:

blurFields() {
        this.refs.xview.endEditing();
}

<View style={{flex: 1}} ref="xview">
                <ScrollView ref="scrollView">
                    <View testProp={true}>
                        <TextInput ref="field1" style={{backgroundColor: 'red', width: 100, height: 20}} />
                        <TextInput ref="field2" style={{backgroundColor: 'blue', width: 100, height: 20, marginTop: 20}} />
                        <View>
                            <TextInput ref="field3" style={{backgroundColor: 'green', width: 100, height: 20, marginTop: 20}} />
                        </View>
                        <TouchableHighlight onPress={() => this.blurFields()}>
                            <Text>Blur fields</Text>
                        </TouchableHighlight>
                    </View>

                </ScrollView>
            </View>

Everything seems to work okay and the functionality works as expected. What do you think? I'll submit a proper PR if you think it's good.

brentvatne commented 9 years ago

@josebalius - this looks very reasonable to me, @tadeuzagallo what do you think?

One comment about the API: it would be nice if this was just a modification of blur so that blur would affect all children as well.

josebalius commented 9 years ago

@brentvatne roger on the API, i thought about the same thing shouldn't be a problem. If @tadeuzagallo is fine with the code i'll submit the PR with an API like this.refs.parentView.blur(true) true for deep search, if not true then it will act the same as it does now which is tries to blur the active text field.

Actually maybe we don't need the extra param at all, if it's on a text field that you are calling it on the loop should have to search once so I think that works as well.....we can just replace the blur functionality altogether.

brentvatne commented 9 years ago

@josebalius - yeah I agree, if it's just a TextField then we don't have to be concerned about traversing any children

wluxion commented 9 years ago

This can be closed, no? https://github.com/facebook/react-native/blob/master/Libraries/Utilities/dismissKeyboard.js

var dismissKeyboard = require('dismissKeyboard')
...
onClose: function() {
    dismissKeyboard();
}
ryanmcdermott commented 9 years ago

@wluxion Looks interesting, how do you require the dismissKeyboard Utility in a project? I didn't see Utilities or that particular one in the docs. Will it work on iOS and Android?

brentvatne commented 9 years ago

We are exposing TextInputState in #3308 which will allow you to do this too. I'm not sure it's worth exposing this one line function as it will increase API surface area, if you believe it's important then feel free to chime in on that PR! Thanks @wluxion for the heads-up.

ryanmcdermott commented 9 years ago

@brentvatne Looks great, I look forward to it.

josebalius commented 8 years ago

@brentvatne Any update on this? I am upgrading a couple of projects to the latest RN and running into blur issues not hiding the keyboard, do we have docs on the proper way to do it?

brentvatne commented 8 years ago

@josebalius - hmmm I'm not sure about this, could you put together a small example and create a new issue?