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 high: MobileSafari ignores parts of its UI when calculating 100vh.


Image by Max Schmitt

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

As Apple basically gives us the finger on this – stating that it works as intended (which we all can disagree on) – we have to rely on workarounds. In the past I’ve used like 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 I guess the recommended workaround right now still remains Louis Hoebregts’ CSS Custom Properties Hack:

.my-element {
  height: 100vh;
  height: calc(var(--vh, 1vh) * 100);
}
const setVh = () => {
  const vh = window.innerHeight * 0.01;
  document.documentElement.style.setProperty('--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: https://app.bitrise.io/users/sign_up?referrer=09c28434a5e3e18d

~

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/strtod.cc'`
- `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/ios-install-third-party.sh ; cd ../../
  cd node_modules/react-native/third-party/glog-0.3.5/ ; ./../../scripts/ios-configure-glog.sh ; cd ../../../../
fi

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: android.support.v4.app.ActionBarDrawerToggle$DelegateProvider

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

Error:Program type already present: android.support.v4.app.ActionBarDrawerToggle$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 com.android.support to a specific version

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

android {

    …

    // Fix for "Program type already present: android.support.v4.app.ActionBarDrawerToggle$DelegateProvider"
    configurations.all {
        resolutionStrategy.eachDependency { DependencyResolveDetails details ->
            def requested = details.requested
            if (requested.group == 'com.android.support') {
                if (!requested.name.startsWith("multidex")
                        && !requested.name.startsWith("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 com.android.build.api.attributes.BuildTypeAttr 'preview' and found incompatible value 'debug'.
      - Found com.android.build.api.attributes.VariantAttr 'debug' but wasn't required.
      - Required com.android.build.gradle.internal.dependency.AndroidTypeAttr 'Aar' and found compatible value 'Aar'.
      - Required org.gradle.usage 'java-api' and found compatible value 'java-api'.
  - Configuration 'debugRuntimeElements':
      - Required com.android.build.api.attributes.BuildTypeAttr 'preview' and found incompatible value 'debug'.
      - Found com.android.build.api.attributes.VariantAttr 'debug' but wasn't required.
      - Required com.android.build.gradle.internal.dependency.AndroidTypeAttr 'Aar' and found compatible value 'Aar'.
      - Required org.gradle.usage 'java-api' and found incompatible value 'java-runtime'.
  - Configuration 'releaseApiElements':
      - Required com.android.build.api.attributes.BuildTypeAttr 'preview' and found incompatible value 'release'.
      - Found com.android.build.api.attributes.VariantAttr 'release' but wasn't required.
      - Required com.android.build.gradle.internal.dependency.AndroidTypeAttr 'Aar' and found compatible value 'Aar'.
      - Required org.gradle.usage 'java-api' and found compatible value 'java-api'.
  - Configuration 'releaseRuntimeElements':
      - Required com.android.build.api.attributes.BuildTypeAttr 'preview' and found incompatible value 'release'.
      - Found com.android.build.api.attributes.VariantAttr 'release' but wasn't required.
      - Required com.android.build.gradle.internal.dependency.AndroidTypeAttr '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(AbstractDependencyMetadata.java:46)
	at org.gradle.internal.component.model.LocalComponentDependencyMetadata.selectConfigurations(LocalComponentDependencyMetadata.java:90)
	at org.gradle.internal.component.local.model.DslOriginDependencyMetadataWrapper.selectConfigurations(DslOriginDependencyMetadataWrapper.java:60)
	at org.gradle.api.internal.artifacts.ivyservice.resolveengine.graph.builder.EdgeState.calculateTargetConfigurations(EdgeState.java:143)
	... 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.

How to reposition multiple iOS apps at the same time

TIL: You can move multiple iOS apps at the same time. Tap and hold to start wiggling one, move it a little, then tap other apps to add ‘m to the stack you’re moving.

Did this help you out? Like what you see?
Consider donating.

I don’t run ads on my blog nor do I do this for profit. A donation however would always put a smile on my face though. Thanks!

☕️ Buy me a Coffee ($3)

Carthage – A simple, decentralized dependency manager for Cocoa

Whilst checking out the aforementioned IMcD23/TabView and a few other iOS/macOS libraries I could not help by notice the lack of CocoaPods and the presense of Carthage. Apparently the community is now leaning more towards the latter.

Carthage is intended to be the simplest way to add frameworks to your Cocoa application.

Carthage builds your dependencies and provides you with binary frameworks, but you retain full control over your project structure and setup. Carthage does not automatically modify your project files or your build settings.

Dependencies are defined in a Cartfile:

# Require version 2.3.1 or later
github "ReactiveCocoa/ReactiveCocoa" >= 2.3.1

# Require version 1.x
github "Mantle/Mantle" ~> 1.0    # (1.0 or later, but less than 2.0)

# Require exactly version 0.4.1
github "jspahrsummers/libextobjc" == 0.4.1

# Use the latest version
github "jspahrsummers/xcconfigs"

# Use the branch
github "jspahrsummers/xcconfigs" "branch"

# Use a project from GitHub Enterprise
github "https://enterprise.local/ghe/desktop/git-error-translations"

# Use a project from any arbitrary server, on the "development" branch
git "https://enterprise.local/desktop/git-error-translations2.git" "development"

# Use a local project
git "file:///directory/to/project" "branch"

# A binary only framework
binary "https://my.domain.com/release/MyFramework.json" ~> 2.3

After running carthage the lockfile Cartfile.resolved (which you should commit into your version control system!) will be created. Dev dependencies can be stored in a Cartfile.private file.

So why is Carthage getting more popular? From their README, these two here caught my attention:

CocoaPods (by default) automatically creates and updates an Xcode workspace for your application and all dependencies. Carthage builds framework binaries using xcodebuild, but leaves the responsibility of integrating them up to the user. CocoaPods’ approach is easier to use, while Carthage’s is flexible and unintrusive.

Carthage has been created as a decentralized dependency manager. There is no central list of projects, which reduces maintenance work and avoids any central point of failure.

Installation possible using Homebrew:

brew install carthage

Carthage – A simple, decentralized dependency manager for Cocoa →

IMcD23/TabView – Easily add multiple tabs to your iOS app, styled similar to Safari for iPad.

There are two primary view controllers in this library: TabViewController and TabViewContainerViewController. A TabViewController contains an array of tabs, a visible tab, and some methods to add and remove tabs. A TabViewContainerViewController contains TabViewControllers.

IMcD23/TabView (GitHub) →

(via Louis D’hauwe)

Easily share Wifi Passwords with friends on iOS 11

When a nearby contact with an iOS device wants to connect to a hotspot you are familiar with (e.g. your device knows the password), you can easily share the password with their device:

It’s small additions like this that put a smile on my face 🙂