Jump to content

Featured Replies

  • Management
20 hours ago, KT Walrus said:

I won’t bring this up ever again.

Frustrated World Cup GIF

Yes, we know the PWA needs work. We will make it better, but we really need to get 5.0.0 out. I know it’s very important to you, and I understand why but there are around 200 very important things that 200 people want adding in before final, and we just can’t do it all.

Lets get 5.0.0 out and then we can really get into building on existing features.

  • Author

I consider this navigation issue on the iOS PWA a bug and not a feature.

I guess you don’t agree and are not willing to fix this before GA.

I really will shut up about this now, but I am extremely frustrated by not getting the UI issues on iPhone addressed for non-technical users before v5 release which is very very important to me for my new v5 based site.

I’ll probably have to fix this myself, not use the PWA for users that Add To Home Screen or delay opening the site until this fix is in a point release. Probably I will just fix myself.

9 minutes ago, KT Walrus said:

I consider this navigation issue on the iOS PWA a bug and not a feature.

I guess you don’t agree and are not willing to fix this before GA.

I really will shut up about this now, but I am extremely frustrated by not getting the UI issues on iPhone addressed for non-technical users before v5 release which is very very important to me for my new v5 based site.

I’ll probably have to fix this myself, not use the PWA for users that Add To Home Screen or delay opening the site until this fix is in a point release. Probably I will just fix myself.

We fully understand you are frustrated. We hear you.

  • Management

It’s not a bug in the sense that there is a refresh function that doesn’t work.

I agree that there needs to be a refresh, I just disagree that it’s a few minutes work to do properly (there’s obviously the JS to allow the pull down, then fire a reload() and then the CSS to style it up) but I’d also like to add swipe gestures to allow you to navigate backwards and forwards when on a page as you would on an app.

  • Author
6 hours ago, Matt said:

It’s not a bug in the sense that there is a refresh function that doesn’t work.

I agree that there needs to be a refresh, I just disagree that it’s a few minutes work to do properly (there’s obviously the JS to allow the pull down, then fire a reload() and then the CSS to style it up) but I’d also like to add swipe gestures to allow you to navigate backwards and forwards when on a page as you would on an app.

Swipe back and forward already work in the PWA, even from the beginning of PWA support. The only thing missing is the pull to refresh and there are a number of JS code snippets you could use to add pull to refresh to the JS. I don’t think you have to actually code anything.

I’ll do a quick search to find you some links.

  • Author

Here is Claude 3.5 Sonnet’s answer to adding “pull to refresh” to a PWA on iOS.

Claude on adding pull to refresh

Yes! Here's a JavaScript solution to implement pull-to-refresh functionality in Progressive Web Apps on iOS. This code uses the touchstart, touchmove, and touchend events to create a native-feeling pull-to-refresh experience:

```javascript

class PullToRefresh {

constructor(options) {

this.options = {

target: document.querySelector("body"),

threshold: 60, // Required pull distance in pixels

onRefresh: () => location.reload(),

...options,

};

this.startY = 0;

this.currentY = 0;

this.dragging = false;

this.refreshing = false;

this.init();

}

init() {

// Create refresh indicator element

this.indicator = document.createElement("div");

this.indicator.style.cssText = `

position: fixed;

top: 0;

left: 0;

right: 0;

height: 50px;

display: flex;

align-items: center;

justify-content: center;

transform: translateY(-100%);

transition: transform 0.2s;

`;

document.body.prepend(this.indicator);

// Add event listeners

this.options.target.addEventListener("touchstart", (e) =>

this.onTouchStart(e)

);

this.options.target.addEventListener("touchmove", (e) => this.onTouchMove(e));

this.options.target.addEventListener("touchend", (e) => this.onTouchEnd(e));

}

onTouchStart(e) {

if (window.scrollY === 0 && !this.refreshing) {

this.dragging = true;

this.startY = e.touches[0].pageY;

}

}

onTouchMove(e) {

if (!this.dragging) return;

this.currentY = e.touches[0].pageY;

const distance = this.currentY - this.startY;

if (distance > 0) {

e.preventDefault();

const pullDistance = Math.min(distance * 0.5, this.options.threshold);

this.indicator.style.transform = translateY(${pullDistance}px);

this.indicator.textContent = pullDistance >= this.options.threshold

? "Release to refresh"

: "Pull to refresh";

}

}

onTouchEnd() {

if (!this.dragging) return;

this.dragging = false;

const distance = this.currentY - this.startY;

if (distance >= this.options.threshold) {

this.refreshing = true;

this.indicator.textContent = "Refreshing...";

this.options.onRefresh().finally(() => {

this.refreshing = false;

this.indicator.style.transform = "translateY(-100%)";

});

} else {

this.indicator.style.transform = "translateY(-100%)";

}

}

}

// Usage example

const ptr = new PullToRefresh({

target: document.querySelector("#content"),

onRefresh: async () => {

// Your refresh logic here

await new Promise((resolve) => setTimeout(resolve, 1500));

location.reload();

},

});

```

To use this in your PWA:

1. Add the code to your project

2. Initialize it with your desired target element and refresh callback

3. Add the following to your manifest.json to enable pull-to-refresh:

```json

{

"display": "standalone",

"apple-mobile-web-app-capable": "yes"

}

```

Key features of this implementation:

- Works on iOS Safari and when installed as a PWA

- Smooth animation with native feel

- Customizable threshold and refresh callback

- Visual indicator with status messages

- Prevents default scroll behavior when pulling

- Handles edge cases and cleanup

You can customize the appearance of the refresh indicator by modifying the CSS in the init() method. You might also want to add a loading spinner or other visual feedback during the refresh operation.

Note that this is a basic implementation - you might want to add:

1. Better error handling

2. Animation for the refresh indicator

3. Custom styling to match your app's design

4. Support for different pull distances

5. Better accessibility features

Remember that while this works well for iOS, Android typically has built-in pull-to-refresh functionality that you might want to preserve, so you may want to add platform detection and only enable this for iOS devices.

  • Author

Here is Claude’s response to adding pull to refresh in PWAs on iOS. Don’t know if this all works but Claude usually writes good JS for me.

Here's the updated version of the PullToRefresh class that only enables the functionality for iOS devices:

```javascript
class PullToRefresh {
  constructor(options) {
    // First check if we should initialize on this platform
    if (!this.shouldInitialize()) {
      return;
    }

    this.options = {
      target: document.querySelector("body"),
      threshold: 60,
      onRefresh: () => location.reload(),
      ...options,
    };

    this.startY = 0;
    this.currentY = 0;
    this.dragging = false;
    this.refreshing = false;

    this.init();
  }

  shouldInitialize() {
    // Check for iOS devices
    const isIOS =
      /iPad|iPhone|iPod/.test(navigator.userAgent) ||
      (navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1);

    // Check if it's running as a PWA
    const isStandalone =
      window.navigator.standalone ||
      window.matchMedia("(display-mode: standalone)").matches;

    // Only initialize for iOS devices running as PWA
    return isIOS && isStandalone;
  }

  init() {
    // Create refresh indicator element
    this.indicator = document.createElement("div");
    this.indicator.setAttribute("aria-label", "Pull to refresh indicator");
    this.indicator.style.cssText = `
      position: fixed;
      top: 0;
      left: 0;
      right: 0;
      height: 50px;
      display: flex;
      align-items: center;
      justify-content: center;
      transform: translateY(-100%);
      transition: transform 0.2s;
      background-color: #f0f0f0;
      z-index: 9999;
      font-family: -apple-system, system-ui, sans-serif;
    `;
    document.body.prepend(this.indicator);

    // Create loading spinner
    this.spinner = document.createElement("div");
    this.spinner.style.cssText = `
      display: none;
      width: 20px;
      height: 20px;
      margin-right: 10px;
      border: 2px solid #007AFF;
      border-top-color: transparent;
      border-radius: 50%;
      animation: spin 1s linear infinite;
    `;

    // Add spinner animation
    const style = document.createElement("style");
    style.textContent = `
      @keyframes spin {
        to { transform: rotate(360deg); }
      }
    `;
    document.head.appendChild(style);

    this.indicator.appendChild(this.spinner);

    // Add text element
    this.textElement = document.createElement("span");
    this.indicator.appendChild(this.textElement);

    // Add event listeners with passive: false for better performance
    this.options.target.addEventListener(
      "touchstart",
      (e) => this.onTouchStart(e),
      { passive: false }
    );
    this.options.target.addEventListener(
      "touchmove",
      (e) => this.onTouchMove(e),
      { passive: false }
    );
    this.options.target.addEventListener("touchend", (e) => this.onTouchEnd(e), {
      passive: false,
    });
  }

  updateUI(distance) {
    const progress = (distance / this.options.threshold) * 100;
    this.spinner.style.transform = `rotate(${progress * 3.6}deg)`;
    this.spinner.style.display = "block";
    this.textElement.textContent =
      distance >= this.options.threshold ? "Release to refresh" : "Pull to refresh";
  }

  onTouchStart(e) {
    if (window.scrollY === 0 && !this.refreshing) {
      this.dragging = true;
      this.startY = e.touches[0].pageY;
    }
  }

  onTouchMove(e) {
    if (!this.dragging) return;

    this.currentY = e.touches[0].pageY;
    const distance = this.currentY - this.startY;

    if (distance > 0) {
      e.preventDefault();
      const pullDistance = Math.min(distance * 0.5, this.options.threshold);
      this.indicator.style.transform = `translateY(${pullDistance}px)`;
      this.updateUI(pullDistance);
    }
  }

  onTouchEnd() {
    if (!this.dragging) return;
    this.dragging = false;

    const distance = this.currentY - this.startY;
    if (distance >= this.options.threshold) {
      this.refreshing = true;
      this.spinner.style.display = "block";
      this.textElement.textContent = "Refreshing...";
      this.spinner.style.animation = "spin 1s linear infinite";

      this.options.onRefresh()
        .finally(() => {
          this.refreshing = false;
          this.indicator.style.transform = "translateY(-100%)";
          this.spinner.style.animation = "none";
        })
        .catch((error) => {
          console.error("Refresh failed:", error);
          this.textElement.textContent = "Refresh failed";
          setTimeout(() => {
            this.indicator.style.transform = "translateY(-100%)";
          }, 1000);
        });
    } else {
      this.indicator.style.transform = "translateY(-100%)";
    }
  }

  // Method to manually destroy the instance
  destroy() {
    if (this.indicator) {
      this.indicator.remove();
    }
    if (this.options.target) {
      this.options.target.removeEventListener("touchstart", this.onTouchStart);
      this.options.target.removeEventListener("touchmove", this.onTouchMove);
      this.options.target.removeEventListener("touchend", this.onTouchEnd);
    }
  }
}

// Usage example
const ptr = new PullToRefresh({
  target: document.querySelector("#content"),
  onRefresh: async () => {
    try {
      // Your refresh logic here
      await new Promise((resolve) => setTimeout(resolve, 1500));
      location.reload();
    } catch (error) {
      console.error("Refresh failed:", error);
      throw error; // Rethrow to trigger error handling in the class
    }
  },
});
```

This updated version includes:

1. Platform detection through `shouldInitialize()` method
2. Better iOS detection including newer iPads
3. PWA (standalone mode) detection
4. Improved visual feedback with a spinning loader
5. Better error handling
6. Improved accessibility
7. Performance optimizations with passive event listeners
8. Cleanup method through `destroy()`
9. More iOS-native feeling styling

To use it:

```javascript
// Initialize
const ptr = new PullToRefresh({
  target: document.querySelector("#content"),
  threshold: 60, // Optional: customize pull distance
  onRefresh: async () => {
    // Your custom refresh logic
    await fetchNewData();
  },
});

// Later, if needed, cleanup
ptr.destroy();
```

The pull-to-refresh will now only initialize on iOS devices running as a PWA. On other platforms, it will silently do nothing, allowing the native pull-to-refresh behavior (if any) to work as expected.

Remember to still include the appropriate manifest.json settings:

```json
{
  "display": "standalone",
  "apple-mobile-web-app-capable": "yes"
}
```

This implementation provides a more native-feeling experience for iOS PWA users while not interfering with other platforms' built-in behaviors.

Recently Browsing 0

  • No registered users viewing this page.