Mibuko
Mibuko

Reputation: 285

IOS show keyboard on input focus

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 ?

Upvotes: 26

Views: 55380

Answers (10)

rolo.05
rolo.05

Reputation: 1

I needed to implement this feature in my code, and the solution from @n8jadams was very helpful. I have created two examples to illustrate the implementation.

The first example: Here, when the user lands on the page, the input field is automatically focused by default.

The second example: In this scenario, the user opens the dialog, and the input element is automatically focused when the dialog opens.

Both scenarios utilize the same function.

// setAutofocus function
const setAutofocus = (wrapper, autofocusInputElem) => {
  const tmpElement = document.createElement('input')
  tmpElement.classList.add('hidden-input')
  document.querySelector(`${wrapper}`).prepend(tmpElement)
  tmpElement.focus()
  setTimeout(() => {
    autofocusInputElem.focus()
    tmpElement.remove()
  }, 0)
}


// FIRST CASE
// The user arrives at the page and the input field has autofocus
const autofocusInputElemOne = document.querySelector('#default-autofocus [autofocus]')
if (autofocusInputElemOne) {
  setAutofocus('#default-autofocus', autofocusInputElemOne)
}


// SECOND CASE
// With a dialog, when the dialog opens and the input element has the autofocus attribute, it automatically receives focus
const autofocusInputElemTwo = document.querySelector('#dialog-autofocus [autofocus]')
const dialog = document.querySelector("dialog")
const showButton = document.querySelector("dialog + button")
const closeButton = document.querySelector("dialog button")

// "Show the dialog" button opens the dialog modally
showButton.addEventListener("click", () => {
  dialog.showModal()
  if (autofocusInputElemTwo) {
    setAutofocus('#dialog-autofocus', autofocusInputElemTwo)
  }
})

// "Close" button closes the dialog
closeButton.addEventListener("click", () => {
  dialog.close()
})
input.hidden-input {
  position: absolute;
  opacity: 0;
  z-index: -1;
}
<h2>Default autofocus FIRST CASE</h2>
<section id="default-autofocus">
  <form>
    <div>
      <label for="name">Name</label>
      <input autofocus for="name" autofocus type="text">
    </div>
    <div>
      <label for="company">Company</label>
      <input for="company" type="text">
    </div>
  </form>
</section>

<h2>Dialog autofocus SECOND CASE</h2>
<section id="dialog-autofocus">
  <dialog>
    <form>
      <div>
        <label for="name">Name</label>
        <input autofocus for="name" type="text">
      </div>
      <div>
        <label for="company">Company</label>
        <input for="company" type="text">
      </div>
    </form>
    <button>Close</button>
  </dialog>
  <button>Show the dialog</button>
</section>

Upvotes: 0

KingAzion1232
KingAzion1232

Reputation: 89

You can add focus by using useRef.

const inputRef = useRef(null)

// run this on load, click, or after a submission
inputRef.current?.focus()

<input ref={inputRef}/>

As an aside, my keyboard would close on mobile after I would send a message in my chat. To fix this, I changed my code to use onClick instead of onSubmit. I then set it to submit onKeyDown of "Enter".

This made it so my send button would keep the keyboard open as well as using "Enter"/"Return" to submit a message.

Upvotes: 0

Pretty Boy
Pretty Boy

Reputation: 1

import {FocusTrap} from 'vueuc';
<FocusTrap active>
<input/>
</FocusTrap>

Upvotes: 0

clamchoda
clamchoda

Reputation: 4951

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.
    },

Upvotes: 0

Charlie Liang
Charlie Liang

Reputation: 31

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>

Upvotes: 3

plusz
plusz

Reputation: 233

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/

Upvotes: 0

Adam Cai
Adam Cai

Reputation: 387

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)

Upvotes: 0

n8jadams
n8jadams

Reputation: 1164

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.

Upvotes: 25

Mibuko
Mibuko

Reputation: 285

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 !

Upvotes: 0

oel
oel

Reputation: 97

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.

Upvotes: 0

Related Questions