How to prevent scrolling the page on iOS Safari 15

Rik Schennink:

If we show a modal on iOS we need to prevent events inside the modal from interacting with the page behind the modal. On a previous episode of “Fun with Safari” we could use preventDefault() on the touchmove event but on iOS 15 that no longer works. Here we go.

The solution lies in preventing pointermove while the modal is shown.

Using Rik’s code, here’s a demo on CodePen:

See the Pen
How to prevent scrolling the page on iOS Safari 15
by Bramus (@bramus)
on CodePen.

In the near future we should be able to drop Rik’s code, and simply use the overscroll-behavior CSS property (which I’ve covered here before back in 2017) for this. And by “near future” I really do mean soon: there’s been some active movement in the relevant WebKit bug since early December. Perhaps Safari 16 will include it? 🤞

How to prevent scrolling the page on iOS Safari 15 →

Achoo — iOS Safari Extension to view HTML Source

Quickly view the HTML for a given page in Safari on iOS/iPadOS 15. Customizable, beautiful, easy to use, and you can tweak the page too!

When tapping the “Edit” button it activates contenteditable on the page. Costs $0.99.

Achoo HTML Viewer →

Talking to the Competition and Markets Authority about Browser Choice on Apple’s iOS

I you want to get the summary about browser choice on iOS, Stuart Langridge has got you covered:

Last week I sat down with the UK Competition and Markets Authority, the regulator, to talk about browser choice on Apple devices, and whether the claims that limiting choice is good for security and privacy actually hold up.

Talking to the Competition and Markets Authority about Apple (Slides)
Talking to the Competition and Markets Authority about Apple (Blogpost)

“For developers, Apple’s Safari is crap and outdated”

Perry Sun:

Safari is very good web browser, delivering fast performance and solid privacy features.

But at the same time, the lack of support for key web technologies and APIs has been both perplexing and annoying at the same time.

The enormous popularity of iOS makes it all the more annoying that Apple continues to hold back developers from being able to create great experiences over the web that work across all platforms.

Well, can’t say I disagree there …

Thankfully there’s people like Jen Simmons working at Apple, actively asking what should change (you can find my response here). Let’s hope she and the other bright folks who work on Webkit/Safari can have an impact there …

For developers, Apple’s Safari is crap and outdated →

8 Tips to Make Your Website Feel Like an iOS App

Very nice video by Sam Selikoff in which he sets up a web manifest to make his site feel more app-like. Great production quality.

There are some tweaks I’d suggest though:

  1. Fixate the header using position: sticky; instead of position: fixed;. No need for that extra margin on the content then. Update: See note below

  2. Don’t set the header height to a fixed number, as not all devices have (equally sized) notches. Use the User Agent Variable safe-area-inset-top instead, and apply it as the top padding:

    header {
      padding-top: env(safe-area-inset-top);
  3. Don’t disable scaling in case your app does not follow the system font sizing — which it does not by default. To make it do follow the system font sizing, use this snippet:

    @supports (font: -apple-system-body) {
      html {
        font: -apple-system-body;

    💡 You can still override the font-family after that. That way you can have your custom font and also follow the preferred system Text Size

As an extra: to prevent that long-press behavior when the app is not added to the home screen, set -webkit-touch-callout: none; on the links.

On Twitter Sam pointed me out that when using position: sticky; there’s an issue with overscroll/bounce: The header will shift down along with the body as you do so.

In theory one could easily fix this by applying some overscroll-behavior on the body. However, as Safari does not support overscroll-behavior you’ll have to resort to a hack to prevent overscrolling in Safari. But, as that hack relies on 100vh — which Safari also doesn’t understand properly — you’ll have to use another hack to make Safari use the correct value for 100vh.

(* mumbles something about Safari being the new IE6 *)

Did this help you out? Like what you see?
Thank me with a coffee.

I don't do this for profit but a small one-time donation would surely put a smile on my face. Thanks!

☕️ Buy me a Coffee (€3)

To stay in the loop you can follow @bramus or follow @bramusblog on Twitter.

100vh in Safari on iOS

When working with Viewport Units there’s this longstanding and extremely annoying bug in Safari on iOS where it does not play nice with the vh unit. Setting a container to 100vh for example will actually result in an element that’s a wee bit too tall: MobileSafari ignores parts of its UI when calculating 100vh.

Image by Max Schmitt

🤔 New to Viewport Units? Ahmad Shadeed has got you covered.

Apple/WebKit’s stance is that it works as intended, although it’s not what I (and many other developers) expect. As a result we have to rely on workarounds. In the past I’ve used Viewport Units Buggyfill or Louis Hoebregts’ CSS Custom Properties Hack to fix this behavior. I was glad to see that Matt Smith recently found a way to have MobileSafari render an element at 100vh using CSS:

As I replied on Twitter: Nice, but I’d rather have MobileSafari fix the vh unit, as using -webkit-fill-available for this will only work to achieving 100vh.

If you want to achieve a perfect 50vh for example, using -webkit-fill-available won’t work as you can’t use -webkit-fill-available in calc(). Above that it won’t work when the targeted element is nested somewhere deep in your DOM tree with one its parents already having a height set.

Come ‘on Safari, stop being the new IE6 …

UPDATE 2020.05.16 Apparently this -webkit-fill-available workaround can negatively impact the Chrome browser:

Given this it’s recommended to selectively ship -webkit-fill-available to only Safari using a @supports rule that tests for -webkit-touch-callout support:

body {
  height: 100vh;

@supports (-webkit-touch-callout: none) {
  body {
    height: -webkit-fill-available;

Alternatively you can still use Louis Hoebregts’ CSS Custom Properties Hack, which uses JavaScript:

.my-element {
  height: 100vh;
  height: calc(var(--vh, 1vh) * 100);
const setVh = () => {
  const vh = window.innerHeight * 0.01;'--vh', `${vh}px`);

window.addEventListener('load', setVh);
window.addEventListener('resize', setVh);

React Native 0.62 and Flipper

In this post Andréas Hanss gives us a tour on Flipper.

Flipper is a platform for debugging iOS, Android and React Native apps. Visualize, inspect, and control your apps from a simple desktop interface.

To add flipper support to your React Native project, I recommend the “Add Flipper Support” section from the Upgrade to React Native 0.62 guide.

Flipper – Extensible mobile app debugger →
React Native 0.62 and Flipper Will Change Your Mobile Development Experience and make it easier! →

💵 This linked article is stuck behind Medium’s metered paywall, which may prevent you from reading it. Open the link in an incognito window to bypass Medium’s ridiculous reading limit.

Take charge of the iOS/tvOS/watchOS simulators with Control Room

Nice tool by Paul Hudson:

Control Room is a macOS app that lets you control the simulators for iOS, tvOS, and watchOS – their UI appearance, status bar configuration, and more. It wraps Apple’s own simctl command-line tool, so you’ll need Xcode installed.

Some features, such as sending example push notifications or move between light and dark mode, require Xcode 11.4 or later.

Control Room →

Create beautiful menus on iOS with Codea’s iOS “Menu” pod

The folks who created Codea — an app for creative coding — recently adjusted it to make it also run on the iPhone. While Autolayout tackles most of the screen adapations, they were in need of menus.

I realised six months ago as I was using my Mac, using the menus, that I need these things — menus — in Codea. I was trying to solve a problem that has been solved for decades.

So I set out to make the best menus I could make for iOS.

The resulting code has been published as a separate pod.

pod 'Menu'

In to blogpost author Simeon Saëns focuses on the details that made it into this nice library. be sure to check out the videos too!

Menu Source (GitHub) →
The iOS Menu →
Detailing the iOS Menu →

Sidenote: Also see PanelKit and TabView, also two nice community-created UI components that found their birth in bigger projects that needed them (just like my own ansi-php package for example).

Upgrading from React Native 0.53.x to 0.57.4 (and to Xcode 10 and Gradle 4 along with that), a journey

Back in November, after 6 months of working on other projects, I picked up development for the EV-Point Mobile App again. The app is built using React Native and is available on both iOS and Android.

As Xcode got a new major release (from Xcode 9 to Xcode 10) since my last time working on the project I figured I’d trigger a new build on Bitrise, then see which errors would came at me, and continue from thereon. What I didn’t know however is that it would eventually take me 3 days, 17 failed builds, lots of Google Fu, and some guesswork to get everything working again. Here’s an overview of the biggest hurdles that I had to take, along with their solutions. I hope they might help you too.

There’s always a green light at the end of the build-pipeline tunnel …

My initial plan was to first get the current version of the app – still running React Native 0.53.3 – to build again in Xcode 10, and only then upgrade the React Native version to the latest one (0.57.4 at the time of writing). During the course of my journey I eventually flipped that around, as React Native 0.57.x already contained some fixes that I was in need of.

👋 I’m using Bitrise for automatically building all my apps. If you sign up through this referral link, we both get extra build-time:


Problem #1: iOS: React Native project won’t build on Xcode 10

Of course my first Bitrise trigger using Xcode 10 would fail (but one can always try, right?). What I didn’t expect is that quite a lot of things were going wrong (REF):

  • Xcode Signing Issues
  • Failures indicating that libfishook.a could not be linked
  • Third party things such as glog that were not installed
  • etc.

As I stumbled from one build error into the other (locally, of course) and tried fixing these one by one, I eventually found that the most easy solution would be to upgrade to the latest RN version (0.57.4 at the time of writing) as it already contained most of the fixes.

Solution #1 (part1): Upgrading to the latest RN version

To upgrade to the latest RN version I used react-native-git-upgrade. I didn’t immediately jump from RN 0.53 to RN 0.57 though, but gradually went through all intermediate releases.

# install react-native-git-upgrade
yarn global add react-native-git-upgrade

# Go to project folder
cd ~/repos/evpoint/mobileapp/

# Upgrade to RN 0.54.4
react-native-git-upgrade 0.54.4

# Upgrade to RN 0.55.4
react-native-git-upgrade 0.55.4

# Upgrade to RN 0.56.1
react-native-git-upgrade 0.56.1

# Upgrade to RN 0.57.4
react-native-git-upgrade 0.57.4

Using react-native-git-upgrade to upgrade RN versions works like a charm, but it might will require you to resolve a few merge conflicts …

Upgrading from one React Native version to the other …

Once I performed all upgrades I tried building my app locally and … yes, great succes!

💁‍♂️ Note that when upgrading from RN 0.53 to 0.57, the Android build system will have migrated from Gradle 3 to Gradle 4. As we’re now focussing on getting iOS to build, we’ll skip this for now and tackle it later (see Problem #4 below)

Solution #1 (part2): Manually installing the third party scripts

UPDATE: This is fixed with the release of React Native 0.57.5 (#277c19c). You can skip this step if you’re running 0.57.5 or newer.

After successfully building locally I pushed my changes online in order for Bitrise to kick into action. Bitrise however failed. Hmmz …

- Build input file cannot be found: `./node_modules/react-native/Libraries/WebSocket/libfishook.a`
- `configure: error: in `(..._/node_modules/react-native/third-party/glog-0.3.4'`
- `✗ Build input file cannot be found: 'node_modules/react-native/third-party/double-conversion-1.1.6/src/'`
- `No member named '__rip' in '__darwin_arm_thread_state64'`

Turns out that there was an issue with third party scripts not getting installed correctly. Locally they were, however.

FYI: Locally it worked fine because – as it turns out – a second build would correctly struggle through it. As Bitrise always starts from scratch (e.g. with a non-existing node_modules folder) it couldn’t make it through.

To fix this I added an extra step in the build-pipeline to Manually install Third Party Scripts + Configure Glog, (REF):

#!/usr/bin/env bash -e

if [ ! -d "node_modules/react-native/third-party" ]; then
  cd node_modules/react-native ; ./scripts/ ; cd ../../
  cd node_modules/react-native/third-party/glog-0.3.5/ ; ./../../scripts/ ; cd ../../../../

I’ve added this step right before Bitrise’s Certificate and Profile Installer step.

💡You’ll also need to execute this step locally, especially when starting from scratch. You can safely add this script to your local build-script as it contains a built-in check to not double install the Third Party Scripts in case they are already present.


Problem #2: Android: Program type already present:$DelegateProvider

On Android I was still getting build errors though. My first one was something like this:

Error:Program type already present:$DelegateProvider

java.lang.NoClassDefFoundError: Failed resolution of: android/support/v4/animation/AnimatorCompatHelper;
java.lang.NoSuchFieldError: No field notification_template_lines of type

Solution #2: Fixate the SDK version for to a specific version

Thankfully someone else had already stumbled onto this problem and provided a fix: fixate the SDK version for to version 24.2.1. You can do this by adjusting your app/android/build.gradle file:

android {


    // Fix for "Program type already present:$DelegateProvider"
    configurations.all {
        resolutionStrategy.eachDependency { DependencyResolveDetails details ->
            def requested = details.requested
            if ( == '') {
                if (!"multidex")
                        && !"exifinterface")) {
                    details.useVersion '24.2.1'



Problem #3: Android: Unable to find a matching configuration in project

When running ./gradlew assembleDebug I could successfully build for Android. Running ./gradlew assembleRelease however, yielded this error:

Unable to find a matching configuration of project :react-native-devsettings-android:
  - Configuration 'debugApiElements':
      - Required 'preview' and found incompatible value 'debug'.
      - Found 'debug' but wasn't required.
      - Required 'Aar' and found compatible value 'Aar'.
      - Required org.gradle.usage 'java-api' and found compatible value 'java-api'.
  - Configuration 'debugRuntimeElements':
      - Required 'preview' and found incompatible value 'debug'.
      - Found 'debug' but wasn't required.
      - Required 'Aar' and found compatible value 'Aar'.
      - Required org.gradle.usage 'java-api' and found incompatible value 'java-runtime'.
  - Configuration 'releaseApiElements':
      - Required 'preview' and found incompatible value 'release'.
      - Found 'release' but wasn't required.
      - Required 'Aar' and found compatible value 'Aar'.
      - Required org.gradle.usage 'java-api' and found compatible value 'java-api'.
  - Configuration 'releaseRuntimeElements':
      - Required 'preview' and found incompatible value 'release'.
      - Found 'release' but wasn't required.
      - Required 'Aar' and found compatible value 'Aar'.
      - Required org.gradle.usage 'java-api' and found incompatible value 'java-runtime'.
	at org.gradle.internal.component.model.AbstractDependencyMetadata.selectConfigurationUsingAttributeMatching(
	at org.gradle.internal.component.model.LocalComponentDependencyMetadata.selectConfigurations(
	at org.gradle.internal.component.local.model.DslOriginDependencyMetadataWrapper.selectConfigurations(
	at org.gradle.api.internal.artifacts.ivyservice.resolveengine.graph.builder.EdgeState.calculateTargetConfigurations(
	... 89 more

Solution #3: Use matchingFallbacks

The fix was to adjust the defined buildType of debug so that it falls back to the release‘s buildType values in case one is missing. This can be done by using the matchingFallbacks instruction inside your app/android/build.gradle file (REF, REF):

android {

    buildTypes {
        release {
        preview {
            matchingFallbacks = ['release', 'debug']



Problem #4 Android: Execution failed for task ':react-native-config:verifyReleaseResources'

The final Android problem I encountered was that dependencies would not properly compile anymore with the change to Gradle 4. Packaged like react-native-config and react-native-devsettings-android would fail on their verifyReleaseResources step.

Solution #4: Upgrade packages and/or force subprojects to use same compileSdk/BuildTools as main project

Thankfully packages like react-native-config have already landed fixes for this issue. Merely upgrading your affected dependencies using yarn will fix it.

If you’re using packages do not yet have fixes – like react-native-devsettings-android (ISSUE) – you can thankfully force them to use the correct compileSdk/BuildTools version from within your own android/build.gradle file (REF, REF):

subprojects {

    // Force subprojects to use same compileSdk/BuildTools as main project
    afterEvaluate {project ->
        if (project.hasProperty("android")) {
            android {
                compileSdkVersion rootProject.ext.compileSdkVersion
                buildToolsVersion rootProject.ext.buildToolsVersion



Furthermore I have a few classic issues – such as signing certificates that were expired – which I had to tackle. Supposedly the release of RN 0.58 – which I’m looking forward to – should no longer require the adjustment mentioned above.

Did this help you out? Like what you see?
Thank me with a coffee.

I don't do this for profit but a small one-time donation would surely put a smile on my face. Thanks!

☕️ Buy me a Coffee (€3)

To stay in the loop you can follow @bramus or follow @bramusblog on Twitter.