19

I have a problem that i can't fix.

Keyboard doesn't show on input.focus() on IOS

 searchMobileToggle.addEventListener('click', function() {
       setTimeout(function(){
          searchField.focus();
       }, 300);
    });

I've been looking for a solution with no result, i know this is a frequently unsolved question but i see NIKE (https://m.nike.com/fr/fr_fr/) and FOODSPRING (https://www.foodspring.fr/) doing it on mobile.

So i'm wondering how do they do ?

Mibuko
  • 217
  • 1
  • 2
  • 5

7 Answers7

21

None of the other answers worked for me. I ended up looking into the Nike javascript code and this is what I came up with as a reusable function:

function focusAndOpenKeyboard(el, timeout) {
  if(!timeout) {
    timeout = 100;
  }
  if(el) {
    // Align temp input element approximately where the input element is
    // so the cursor doesn't jump around
    var __tempEl__ = document.createElement('input');
    __tempEl__.style.position = 'absolute';
    __tempEl__.style.top = (el.offsetTop + 7) + 'px';
    __tempEl__.style.left = el.offsetLeft + 'px';
    __tempEl__.style.height = 0;
    __tempEl__.style.opacity = 0;
    // Put this temp element as a child of the page <body> and focus on it
    document.body.appendChild(__tempEl__);
    __tempEl__.focus();

    // The keyboard is open. Now do a delayed focus on the target element
    setTimeout(function() {
      el.focus();
      el.click();
      // Remove the temp element
      document.body.removeChild(__tempEl__);
    }, timeout);
  }
}

// Usage example
var myElement = document.getElementById('my-element');
var modalFadeInDuration = 300;
focusAndOpenKeyboard(myElement, modalFadeInDuration); // or without the second argument

Note that this is definitely a hacky solution, but the fact that Apple hasn't fixed this in so long justifies it.

n8jadams
  • 991
  • 1
  • 9
  • 21
  • @sandrina-p It worked for me on this code sandbox in ios v13: https://codesandbox.io/s/z33rnoy1nm – n8jadams Dec 29 '19 at 14:20
  • @n8jadams can we do this without clicking on show modal button? – Deepak Apr 01 '20 at 09:44
  • @Deepak of course. That code sandbox just uses an onclick callback that calls that function. Whenever you want that all to happen, (onclick or not,) call the `focusAndOpenKeyboard` function. – n8jadams Apr 01 '20 at 16:53
  • my use case is with a contenteditable div. i thought the behavior would be the same as a div contenteditable. seems it is not. – chitgoks Apr 24 '20 at 23:59
  • @chitgoks I never tried it with a contenteditable div, but you might want to try something from this other SO answer: https://stackoverflow.com/questions/2388164/set-focus-on-div-contenteditable-element/16863913#16863913 – n8jadams Apr 25 '20 at 19:06
  • 1
    Successfully used this hack on iOS 13.3 with React and MUI. Thanks, @n8jadams! – kenecaswell Jun 10 '20 at 17:22
  • @n8jadams I have tried this it is not working. Can you please look at it (https://stackoverflow.com/questions/63652448/ionic-ios-keyboard-show-issue) and respond. Thanks! – maverickosama92 Aug 29 '20 at 23:41
  • @maverickosama92 This hack depends on nuances of the web browser. It won't work in a native, mobile environment like Ionic framework, React Native, etc. – n8jadams Sep 01 '20 at 16:49
  • I have tried in mobile safari too. I think ion-content overflow=true has some issues which is causing not to get focus on the input and hence keyboard does not pop up. Anyways thank you for this code. Cheers! – maverickosama92 Sep 01 '20 at 16:58
  • `offsetTop` and `offsetLeft` are not guaranteed to be the distance from the top and left of the `body`. I believe a better approach would be to append `__tempEl__` to the parent container of `el` so you're not comparing apples and oranges. https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetTop – tylertrotter Feb 16 '21 at 16:30
  • @tylertrotter That's a good solution if you know that you can tinker with the surrounding DOM elements directly. I'm using this in React and can't. The approximate location is good enough for me though. – n8jadams Feb 16 '21 at 17:43
  • 1
    @n8jadams, if for some reason you can't append to the same element, you could append to body and use `getBoundingClientRect().top` which is measured from the viewport. The location is probably exact for you, however if you were to add the CSS `position: relative` to any ancestor between `el` and `body` the measurement could be way off. It may not matter to you, but it mattered in my case so I wanted to have that caveat for others in a similar spot to me. https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect – tylertrotter Feb 17 '21 at 13:11
  • I can't get the keyboard to come up. I'm on iOS 15 and the HTML doc is rendered inside a WKWebView. Does that make a difference? I can sort of get myself positioned near the text field in question using the above code. It's just that no matter how many times I issue click() or focus() on the input field, it doesn't gain focus nor does the keyboard appear. Any suggestions? – Michael Nguyen Mar 29 '22 at 19:43
  • @MichaelNguyen I would guess that your web app (in javascript) needs to send a message to your iOS app that the input was focused on, and then your iOS app needs to display the keyboard. Admittedly that's kind of annoying that it wouldn't do that automatically. – n8jadams Mar 29 '22 at 21:42
  • The issue then becomes that the field itself isn't activated. The DOM states that the text field is the current activeElement. However visually it doesn't look like anything is happening. The cursor doesn't appear which tells me that .focus() or .click() is not acting like a user tapped the text field. Sending a message back to the app to display the keyboard, it still wouldn't know which field to hook up the keyboard to right? displaying the keyboard should be triggered off some event. There wouldn't be an event if I posted a message back to the app that would indicate which input field – Michael Nguyen Mar 30 '22 at 01:57
  • 3
    Please remember, this function *must* be called from a user interaction (like a click-handler). It just wasn't working for me, and then I realised Safari iOS has this rule. – Qasim Aug 24 '22 at 13:09
1

I found a solution, click() didn't work, but i figured it out.

searchMobileToggle.addEventListener('click', function() {
         if(mobileSearchblock.classList.contains('active')) {
            searchField.setAttribute('autofocus', 'autofocus');
            searchField.focus();
        }
        else {
            searchField.removeAttribute('autofocus');
        }
    });

I was working with vue.js that was removing input autofocus attribute, when the component was loaded. So i had it on click, but there was another problem, the autofocus only worked once, but combined with focus(), it now work all the time :)

Thanks for your help !

Mibuko
  • 217
  • 1
  • 2
  • 5
  • 1
    Doesn't work for me ... i even combined it with triggering a click event after i set the focus , still doesn't work :/ Testing on Iphone11 iOS, v13 Safari – pashata Oct 15 '20 at 11:01
1

Worked in 2022 with ios 16! OMG, I searched for so long and the above solution won't work for me.

Here is how it worked for me. I wrapped the input in a React FocusLock component. Check this package out: https://www.npmjs.com/package/react-focus-lock

Here is a small example:

<FocusLock>
<Input />
</FocusLock>
0

There is no legitimate way to do this since iOS kind of wants to only open the keyboard on a user interaction, however you can still achieve this with either using prompt() or using focus() from within a click() event it and will show up.

oel
  • 77
  • 5
  • "From within a click() event" so, you're basically telling to remove timeout, right? Because author is already in click event – Gena Moroz Jan 29 '19 at 16:11
  • 1
    No, you can keep that. Basically trigger a click event into the input field after focusing it. – oel Jan 29 '19 at 16:18
  • Here's a detailed explanation https://stackoverflow.com/a/35768655/8741781 – bdoubleu Aug 23 '23 at 12:18
0

This really drives me/us crazy. It works fine on the Android phone, but something is disabled by the Apple developer. (I understand it's annoying to pop the keyboard when not necessary though).

I accidentally found out that the "popup" module from Semantic-UI fixes this magically.

Note that the solution works for SemanticUI (@semantic-ui team may tell what event makes this work)

Here are how I did:

const [search, setSearch] = useState(false);
const inputRef = useRef(null);

React.useEffect(() => {
  if (search) {
     inputRef.current.focus();
   } else {
     inputRef.current.blur();
   }
}, [search]);

<div onClick={() => setSearch(true)}> 
   <Popup
     content="Search for Swimmers and Time Standards."
     offset={[-500, -1000]}
     trigger={<Icon name="search" />}
      />
</div>

{search && <Input ref={inputRef} />}

As you see, I wrapped the trigger Icon with the Popup module, and hide the Popup content by setting the crazy offset. And then it magically works.

See the demo here: https://swimstandards.com/ (check it out on your iPhone)

Adam Cai
  • 380
  • 1
  • 10
0

Angular solution:

on button click we need to create temporary input, append to existing container (close to our input) and focus on it.

  btnClicked() {
      this.showModal = true; 
      
      this.searchBar = this.renderer2.selectRootElement('#searchBar', true);
     // 2nd argument preserves existing content

      // setting helper field and focusing on it
      this.inputHelper = this.renderer2.createElement('input');
      this.renderer2.appendChild(this.searchBar, this.inputHelper);
      this.inputHelper.focus();

      let event = new KeyboardEvent('touchstart',{'bubbles':true});            
      this.searchBarButton.nativeElement.dispatchEvent(event);
  }

after modal/target input is shown, we move focus and remove temporary one:

  initiateKeyboard() {       
    setTimeout(()=> {      
      this.searchBarInput.nativeElement.focus();     
      this.renderer2.removeChild(this.searchBar, this.inputHelper);
    },180);
  }

and template:

<div id="searchBar"> 
  <input type="button" class="button is-link is-light" value="Search" (click)="btnClicked()" (touchstart)="initiateKeyboard()" #searchBarButton>
</div>

You just need to remember that iPhone may zoom screen, so you need to adjust parameters of temporary input.

working solution: https://inputfocus.vercel.app/

plusz
  • 224
  • 2
  • 16
0

n8jadams seemed to be the only thing that actually solved this. In my application sometimes the modal response time was unknown (server side) and in these cases a predictable timeout could not be made.

I adjusted their answer to use an observer to detect when the element becomes visible instead of using a timeout, thus removing the timeout parameter.

I also put a check to see if the element is visible before applying the "hack" as it is not needed.

   focusAndOpenKeyboard: function (elementId) {
        var el = document.getElementById(elementId);
        var __tempEl__ = document.createElement('input');

        if (el) {
            // Function to focus on the target element and remove the observer
            function focusOnElementAndCleanup() {
                el.focus();
                el.click();
                // Conditionally check these two as we only set them up when the target input is invisible.
                if (document.body.contains(__tempEl__)) { document.body.removeChild(__tempEl__); } // Remove the temporary element
                if (observer) { observer.disconnect(); }// Cleanup the observer
            }

            // Check if the target element is already visible
            if (isVisible(el)) {
                focusOnElementAndCleanup();
            } else {
                focusOnDummyElementToOpenIOSKeyboard();
                // Create a MutationObserver to watch for changes in the DOM
                var observer = new MutationObserver(function (mutationsList) {
                    for (var mutation of mutationsList) {
                        if (mutation.type === 'childList' && isVisible(el)) {
                            focusOnElementAndCleanup();
                            break;
                        }
                    }
                });

                // Start observing changes in the parent node (you can change this to a more appropriate parent)
                observer.observe(document.body, { childList: true, subtree: true });
            }

            // Create a temporary input element to focus on and open the keyboard
            function focusOnDummyElementToOpenIOSKeyboard() {
                __tempEl__.style.position = 'absolute';
                __tempEl__.style.top = (el.offsetTop + 7) + 'px';
                __tempEl__.style.left = el.offsetLeft + 'px';
                __tempEl__.style.height = 0;
                __tempEl__.style.opacity = 0; // Set opacity to 0 to make it invisible
                document.body.appendChild(__tempEl__);
                __tempEl__.focus();
            }

        }


        // Function to check if the element is visible in the DOM
        function isVisible(element) {
            return element && element.offsetParent !== null;
        }

        // Carry on with opening modal, and showing elementId to be focused.
    },
clamchoda
  • 4,411
  • 2
  • 36
  • 74