Open lincolnthree opened 4 years ago
Thanks for the issue. Can you please provide a GitHub repo I can use to reproduce the issue? I can't reproduce the issue using the code snippets provided.
Thanks for the issue! This issue has been labeled as needs reproduction
. This label is added to issues that need a code reproduction.
Please provide a reproduction with the minimum amount of code required to reproduce the issue. Without a reliable code reproduction, it is unlikely we will be able to resolve the issue, leading to it being closed.
For a guide on how to create a good reproduction, see our Contributing Guide.
I've adjusted the styles to make the issue more apparent.
Repo: https://github.com/TopDecked/ionic-swipe-gesture-bug
I realized it also has to do with the menu:
Video of issue: https://photos.google.com/photo/AF1QipMiQEB4oBy8sfMnk4Q__uRjKddDWi3GNZ62rkc
Note, this happens both as PWA and as traditional webapp, though my recording shows the latter.
The swipe gesture with split pane is similarly odd, though I think this is possibly an issue that the Safari swipe to go back gesture needs to be blocked so that ionic can keep doing its thing:
swipeGesture=false
https://photos.app.goo.gl/dDYNfknq88xBMeoa8
Again, I've tested this in web and PWA on iOS 14.
The swipeGesture
property on ion-router-outlet
does not disable Safari/WKWebView's swipe to go back gesture, it is only intended to disable Ionic Framework's swipe to go back gesture.
We are not aware of a method for disabling this, but I can ask some WebKit folks if there is a way.
One thing you could do for that menu is use replaceUrl
which should disable the Safari swipe to go back gesture:
this.router.navigate([url], { replaceUrl: true });
Hmmm... interesting. I guess that makes some sense. Though... is more worrying than if this were an Ionic bug. Ionic bugs are more easily fixed... I'll be interested to hear what the Safari folks say.
Is there any way to disable the Ionic menu animation/function if Safari has started navigating back?
I did some digging and found a 'hack' that seems to disable edge swiping... sometimes. I turned it into a directive to make it easier to apply/test, but it seems like there's some timing issue as it only sometimes prevents the default Safari back action.
Essentially listen for the touchstart event and prevent default if it's near the edge.
It seems like this hack needs to be applied directly on the top-level element from which the event was fired to have any type of success. Again, I'm guessing there's some kind of timing issue on Safari here, unless I've incorrectly registered the non-passive listener somehow.
const EDGE_THRESHOLD = 10;
@Directive({
selector: '[stopEdgeSwipe]'
})
export class StopEdgeSwipeDirective {
constructor(private _el: ElementRef) {
this._el?.nativeElement?.addEventListener('touchstart', (e: Event) => {
const event = (<any>e);
if (event.pageX === undefined || event.pageX === null
|| (event.pageX > EDGE_THRESHOLD
&& event.pageX < window.innerWidth - EDGE_THRESHOLD)
) {
return true;
}
e.preventDefault(); // This only sometimes works, and the event never appears as if the default has been prevented after calling this. (Internal property is never updated.)
return false; // I don't know if this does anything or not.
}, {
passive: false
});
}
}
Good call with the suggestion. I agree that replaceUrl: true
would likely solve the problem since the browser history would constantly be replaced with the next state. This might be possible.
One snag, however, It does not appear to be possible to apply replaceUrl: true
globally, unless I'm missing something. So this would need to be added to all links and calls to Router.navigate
and NavController.navigate
. Am I missing a hook somewhere?
@liamdebeasi Also, I did some testing and I think replaceUrl
also needs to be accompanied by skipLocationChange: true
for that workaround to really work, which has the effect of... actually breaking all back buttons.
Otherwise, the browser still seems to record the history state (not sure why, I may have done something wrong.)
Could be useful, but may only be reasonable on PWA/native. Even so, you'd have to do some manual state saving of the current route to return users to where they left off.
I'll do some more testing.
Working on an interceptor for the built in angular Router
:
https://stackoverflow.com/questions/45514970/angular-2-override-router-navigate-method
Update. { replaceUrl: true }
seems to be all that is required. But I still don't think it's a good idea to do this outside of PWA/native. That said, I think those are the only places where this really matters.
Honestly, you guys said you were trying to figure out how to deal with the back button for PWAs. I think turning it off entirely might be the simplest solution (and could certainly be done as an opt-in setting,) if you have any desire to include this. Code below:
import { Location } from '@angular/common';
import { Compiler, Injector, NgModuleFactoryLoader, Type } from '@angular/core';
import { ChildrenOutletContexts, NavigationExtras, Router, Routes, UrlSerializer, UrlTree } from '@angular/router';
export class GlobalRouterDecorator extends Router {
private static _historyEnabled = true;
public static enableHistory(enabled: boolean = true) {
GlobalRouterDecorator._historyEnabled = !!enabled;
}
constructor(rootComponentType: Type<any>, urlSerializer: UrlSerializer,
rootContexts: ChildrenOutletContexts, location: Location, injector: Injector,
loader: NgModuleFactoryLoader, compiler: Compiler, config: Routes) {
super(rootComponentType, urlSerializer, rootContexts, location, injector, loader, compiler, config);
}
navigate(commands: any[], extras?: NavigationExtras): Promise<boolean> {
return super.navigate(commands, this.enhanceExtras(extras));
}
navigateByUrl(url: string | UrlTree, extras?: NavigationExtras) {
return super.navigateByUrl(url, this.enhanceExtras(extras));
}
private enhanceExtras(extras: NavigationExtras) {
return !GlobalRouterDecorator._historyEnabled ? {
...extras,
replaceUrl: true
} : extras;
}
}
Then in your app.module.xml
@NgModule({
declarations: [AppComponent],
imports: [...],
providers: [
Location, { provide: LocationStrategy, useClass: PathLocationStrategy },
{
provide: Router,
useFactory: routerFactory,
deps: [ApplicationRef, UrlSerializer, ChildrenOutletContexts, Location, Injector,
NgModuleFactoryLoader, Compiler, ROUTES]
]},
bootstrap: [AppComponent]
})
export class AppModule { }
function flatten<T>(arr: T[][]): T[] {
return Array.prototype.concat.apply([], arr);
}
export function routerFactory(rootComponentType: Type<any>, urlSerializer: UrlSerializer,
rootContexts: ChildrenOutletContexts, location: Location, injector: Injector,
loader: NgModuleFactoryLoader, compiler: Compiler, config: Route[][]): Router {
return new GlobalRouterDecorator(
rootComponentType,
urlSerializer,
rootContexts,
location,
injector,
loader,
compiler,
flatten(config)
);
}
Then in app.component.ts (or whatever Ionic setting):
if (this._plt.is('pwa')) {
GlobalRouterDecorator.enableHistory(false);
}
Update #2. This solution does not work for Chrome based browsers/PWAs. But things appear sliiiightly less broken there since they'll give you a chance to swipe twice to go back if the PWA is in fullscreen mode. It also does nothing to override the back button, if one is displayed in standalone PWA mode.
Spoke with some WebKit team members, and this is the gist of what they said:
-[WKWebView setAllowsBackForwardNavigationGestures:]
.history.replaceState
instead of history.pushState
). This is similar to what I suggested with https://github.com/ionic-team/ionic-framework/issues/22299#issuecomment-707767466.On a related note -- Are you able to swipe to go back on a Chromium-based PWA?
Thanks for looking into this @liamdebeasi - I appreciate the feedback! Looks like your initial answer was correct, and it appears to be working.
Gestures are an unresolved problem with the Web platform. They mainly spoke about issues around being able to detect and resolve conflict between gestures.
Tell me about it...
On a related note -- Are you able to swipe to go back on a Chromium-based PWA?
In short, yes. You can use swipe to go back on Chromium-based PWAs as of Android Q (Android 10). Only some devices seem to support this interaction, newer Pixel phones have it, as of the Pixel 3, I believe (100% certain about the pixel 4. I tested it myself and can send a video to demonstrate if it would be helpful). However, my Pixel 2 XL does not. Newer pixels have been removing the android control-bar in favor of gesture-based controls like iOS.
Do you guys have a stack of test devices over at Ionic? :) Can I borrow it 😆
Here are a few articles that describe the new edge-press/swipe navigation, and how Google has been approaching the gesture, since they acknowledge it interferes with menus and such.
https://www.androidpolice.com/2019/07/11/android-q-beta-5-gesture-nav-side-menu/ https://www.androidpolice.com/2019/03/21/chrome-gets-swipe-gestures-to-navigate-back-or-forward/
There do appear to be two conflated types of back-navigation however. I cannot find any docs for the above.
That said:
Overscroll navigation
is a separate feature defined in the WC3, and newer versions of Chrome do support this as well. It can be activated using multi-touch gestures (two fingers swipe right, etc). The appears to be controllable using the css overscroll-action
property:
https://wicg.github.io/user-gesture-nav/
And can also be managed by cancelling mousewheel
events, apparently:
https://stackoverflow.com/questions/23560528/how-to-prevent-overscroll-history-navigation-without-preventing-touchstart
The other issue (edge gestures) is much more insidious, and I have not been able to find any docs about controlling or disabling it.
I hope this is helpful.
On an unrelated note: I'd really love to show you this app sometime. It's been a massive undertaking and Ionic (w/Angular) has made it possible. We've had to work around a lot of issues since we're going full PWA. (Most Safari related. Keyboard heights & detection. Device rotation causing improper scaling. And the list goes on...)
In short, yes. You can use swipe to go back on Chromium-based PWAs as of Android Q (Android 10). Only some devices seem to support this interaction, newer Pixel phones have it, as of the Pixel 3, I believe (100% certain about the pixel 4. I tested it myself and can send a video to demonstrate if it would be helpful). However, my Pixel 2 XL does not. Newer pixels have been removing the android control-bar in favor of gesture-based controls like iOS.
Yeah if you wouldn't mind sending a screen recording, that would be great. I don't have a physical Android device running Android 10 at the moment.
Overscroll navigation is a separate feature defined in the WC3, and newer versions of Chrome do support this as well. It can be activated using multi-touch gestures (two fingers swipe right, etc). The appears to be controllable using the css overscroll-action property:
On that note, we've been discussing this more with the WebKit team and they suggested that this kind of gesture control would be good as a standard, as it can affect more than just iOS devices. We are currently looking at proposing an extension of touch-action
since that seems the closest in terms of semantics.
On an unrelated note: I'd really love to show you this app sometime. It's been a massive undertaking and Ionic (w/Angular) has made it possible. We've had to work around a lot of issues since we're going full PWA. (Most Safari related. Keyboard heights & detection. Device rotation causing improper scaling. And the list goes on...)
Would love to see it! Feel free to shoot me an email liam [at] ionic.io.
Yeah if you wouldn't mind sending a screen recording, that would be great. I don't have a physical Android device running Android 10 at the moment.
Here are two recordings that show the basic issue in slightly different scenarios (same basic issue). Let me know if you have any issues accessing the Google Photos/Drive link. It should be public:
If you notice, this PWA is set to "fullscreen" in metadata.json, however, that's not necessary to reproduce the issue as this also occurs on the web, in "native" Chrome.
On that note, we've been discussing this more with the WebKit team and they suggested that this kind of gesture control would be good as a standard, as it can affect more than just iOS devices. We are currently looking at proposing an extension of touch-action since that seems the closest in terms of semantics.
I'm glad to hear this is being discussed. It obviously can't come soon enough :) This is one rare scenario when Safari was easier to tame than Chrome is. Have you had any discussions with the Chrome team about this? I'd be interested to hear their thoughts.
Would love to see it! Feel free to shoot me an email liam [at] ionic.io.
Ill shoot you an email :)
@liamdebeasi Just a quick heads up on the tags for this issue. Not sure "needs reproduction" is correct as the videos display the issue using a sample app. Not a big deal just don't want this to get lost in the mix. More later, things got busy this week.
Here are two recordings that show the basic issue in slightly different scenarios (same basic issue). Let me know if you have any issues accessing the Google Photos/Drive link. It should be public:
Thanks! This is really helpful.
I'm glad to hear this is being discussed. It obviously can't come soon enough :) This is one rare scenario when Safari was easier to tame than Chrome is. Have you had any discussions with the Chrome team about this? I'd be interested to hear their thoughts.
We have only had talks with the WebKit team. Once we put up the proposal I imagine we could ask some Chromium folks to take a look.
@lincolnthree regarding swipe gesture in safari - as you mentioned setting swipeGesture to false is not sufficient, but if you disable animations (set animated = false in ion-nav or ion-router-outlet) it will work as expected: safari native back gesture will work properly. Also you can disable swipeGesture in menu. In my case I check what platform is running the app and either enable/disable animations and swipe gesture.
@MarkChrisLevy The issue here is we want neither. The fact that both occur is just bad icing on a terrible cake of confusion :)
That's why such a complex workaround is currently necessary.
Hi everyone. Just developing right now an Ionic 5 + Angular 11 APP which has to be deployed in Android + iOS (Native) + Web APP + PWA.
Facing the same issue, just in case it's useful for anyone, I kind of solved this "double" gesture binding (browser + ionic) with the following, as Mark & Lincoln suggested above.
...
import { Platform } from '@ionic/angular';
import { IonRouterOutlet } from '@ionic/angular';
...
constructor(
...
private routerOutlet: IonRouterOutlet,
private platform: Platform
...
) { }
...
ngOnInit() {
if ( this.platform.is('mobileweb') || this.platform.is('pwa') )
{
// In these cases, you already get the browser swipe-back gesture, so disable ionic's.
this.routerOutlet.swipeGesture = false;
}
}
Of course, this is only a workaround, but since it's something that for me (as maybe for many people) is kind of a basic and expected behavior to be covered out of the box, I'd share it.
Hope the guys at the team got this to work soon!
Best,
Hi everyone,
After discussing with the WebKit team, I created a proposal for a way to disable the swipe back gesture using touch-action
in CSS: https://github.com/w3c/csswg-drafts/issues/6161
Any feedback on this proposal is appreciated!
@liamdebeasi Awesome! I'll check it out. Thanks for continuing to pursue this!
네 그럼 일단 오픈은 하고 다른이슈 한번 보시고 잡아볼래요?
Hi everyone,
I got a few questions about this issue this week and wanted to post a quick summary here:
When running an Ionic app in Safari or as a PWA on iOS the platform's swipe to go back gesture (STGB) is running at the same time as Ionic's swipe to go back gesture (STGB), leading to unexpected flickering/rendering on the page.
Safari/WebKit provides its own built in STGB that conflicts with Ionic Framework's STGB as they get triggered via the same touch interaction.
For developers using WKWebView, there is an API to disable the built in STGB that WebKit provides, but no such API exists for developers using Safari or WebKit in a PWA.
No. This is not a bug in WebKit either. Both functionalities are triggering when they were designed to be triggered. The problem is that they conflict and interfere with the functionality of Ionic Framework's STGB.
The long term solution is for browser vendors to expose an API that allow web developers to control this behavior. I created a proposal for the W3C Pointer Events group: https://github.com/w3c/pointerevents/issues/358
There is no great short term solution. If this is a blocker for your app, I recommend disabling Ionic Framework's STGB in the application config using the swipeBackEnabled
functionality. While this will not disable Safari's built in STGB, it will at least prevent the two gestures from interfering.
Ionic Angular config: https://ionicframework.com/docs/angular/config#config-options Ionic React config: https://ionicframework.com/docs/react/config#config-options Ionic Vue config: https://ionicframework.com/docs/vue/config#config-options
For hybrid apps of the iOS version, is it possible to disable the back gesture through settings? This can solve at least part of the problem https://developer.apple.com/documentation/webkit/wkwebview/1414995-allowsbackforwardnavigationgestu
Apps running as a PWA do not have access to that API. Cordova/Capacitor apps use that API so this issue does not impact them.
@liamdebeasi Thank you very much for your follow-up My program runs on Capacitor But this problem also exists on my iphone12, and the gesture back conflict will occasionally appear. And after fast switching (browse\back) several times, two pages will appear on the interface at the same time (each one occupies a part). In this case, the smart solution is solved by restarting the app.
If you are running into issues where the webview is allowing a swipe back then I recommend filing an issue on the Capacitor repo: https://github.com/ionic-team/capacitor
Ionic Framework does not have control over the webview.
Hello everyone who uses ionic vue, you can use my directive: https://github.com/Sitronik/v-disable-swipe-back
Hello everyone who uses ionic vue, you can use my directive: https://github.com/Sitronik/v-disable-swipe-back
Thank you man. I had dragging effects on my page and the going back by swiping was annoying on ios. This solved it. Thanks again!
Hello everyone who uses ionic vue, you can use my directive: https://github.com/Sitronik/v-disable-swipe-back
Thank you man. I had dragging effects on my page and the going back by swiping was annoying on ios. This solved it. Thanks again!
You are welcome, I'm glad I could help you
Quick update: I requested a feature for the Web App Manifest to disable built-in navigation gestures: https://github.com/w3c/manifest/issues/1041
(Previously I had proposed this as a CSS addition with touch-action
, but I got some feedback that made me think it was better for this feature to exist in the Web App Manifest)
i have a same problem, but change code at xcode like this. and disable swipe.. webview.allowsBackForwardNavigationGestures = false
because as a user of ios, i didn't use swipe at all. so, swipe make user complicated .
i have a same problem, but change code at xcode like this. and disable swipe.. webview.allowsBackForwardNavigationGestures = false
Good tip! That would work for Capacitor/iOS bundled apps, but if you host a website or PWA with Ionic (which is what this issue is about), this won't solve the problem.
One trick I've seen other devs do is to use an in-memory router (as opposed to a URL router) when running as a PWA. This avoids the OS-level swipe gesture since no new URLs are pushed. However, this prevents deep linking with PWAs on platforms that support it.
One trick I've seen other devs do is to use an in-memory router (as opposed to a URL router) when running as a PWA. This avoids the OS-level swipe gesture since no new URLs are pushed. However, this prevents deep linking with PWAs on platforms that support it.
Yeah, that would work, and I agree the side-effect is pretty consequential.
I think my old solution here for just never updating the URL would probably be preferred to that since you can still react to URLs being set/deeplinked, and the app still never creates a scenario where URL history state is pushed to. But if you don't care about deeplinks, then that would work. Though I don't know if there's a significant difference of development effort between the two approaches.
One trick I've seen other devs do is to use an in-memory router (as opposed to a URL router) when running as a PWA. This avoids the OS-level swipe gesture since no new URLs are pushed. However, this prevents deep linking with PWAs on platforms that support it.
yep. I think I will use vue-router createMemoryHistory mode try to fix the issue. Although it may be that this approach may not be appropriate👀
One trick I've seen other devs do is to use an in-memory router (as opposed to a URL router) when running as a PWA. This avoids the OS-level swipe gesture since no new URLs are pushed. However, this prevents deep linking with PWAs on platforms that support it.
yep. I think I will use vue-router createMemoryHistory mode try to fix the issue. Although it may be that this approach may not be appropriate👀
What are the drawbacks of using use vue-router createMemoryHisotry mode in PWA? @d1y @liamdebeasi
I've noted the primary drawback in https://github.com/ionic-team/ionic-framework/issues/22299#issuecomment-1633051728: Deep linking won't work.
I am encountering the same issue with Ionic 7 React, both when using as a PWA and on Safari and specifically when handling nested routes. As a temporary solution, I am opting to use modals instead of implementing a nested view.
I am so glad I found this thread and the createMemoryHistory
mode option as a solution, since deep nesting is not a big concern in my use case. In my opinion there should be a big fat warning in the Ionic docs for the WebHistory and WebHashHistory router history modes that default swipe back behavior is basically broken with these router modes for PWAs. The "almost fix" is to set swipeBackEnabled: false
in the global Ionic config, but then you still get the Ionic swipe back animation playing after the browser's native swipe transition. animated: false
is not a satisfying solution, because I don't want to disable all animations. Why not disable the animation automatically when swipeBackEnabled is false (keep it when tapping the back button)? Anyway, for now sticking to the memory history. Looking forward to see how this issue develops ✌️
Since there are a bunch of new folks in this thread now. If you want a real fix, I suggest voting for this W3C feature request that Liam submitted: https://github.com/w3c/csswg-drafts/issues/6161
FWIW, I have not had a problem with Deep Linking using my Router-based approach to prevent URL history changes.
Hello everyone, I wanted to share with you the workaround I have found to disable the swipe to go back on my app for iOS ("@ionic/angular": "6.6.1"). I hope it works for you on more versions.
app.component.html
`
`
app.component.ts
import { createGesture, Gesture } from '@ionic/core';
.
.
.
private gesture: Gesture;
.
.
.
private async initializeApp(): Promise<void> {
.
.
await this.platform.ready().then((platform: string) => {
if (this.platform.is('ios')) {
const el = document.querySelector('ion-router-outlet');
if (el) {
this.gesture = createGesture({
el,
gestureName: 'goback-swipe',
gesturePriority: 400,
threshold: 10,
onStart: () => false,
onMove: () => {},
onEnd: () => {}
});
this.gesture.enable(true);
}
}
}
.
.
}
Explanation If we take a look at the ionic swipe-back.ts, we can see the definition of the default swipe to go back Gesture.
What I understand we do with the above code is overriding this existing default Gesture. We create our own with the same name and we give it a higher priority.
I hope this helps. Regards!
swipeBackEnabled: false and the above workaround doesn't work at all for PWA test. Swipe action still enabled and it cause current view jump to parent page and then back to original page.
Bug Report
Ionic version:
[ ] 4.x [x] 5.x
Current behavior:
Swipe gesture cannot be disabled on native iOS or Android devices. When swiping back, if using
[swipeGesture]=true
the animation/back gesture will trigger in addition to browser navigation, resulting in what appears to be two simultaneous back navigation events, and sometimes, very inconsistent app state on completion.Navigate to any URL on an iPad or Android device with browser swipe-back functionality. Navigate to another ion-page/route.
Drag finger from left edge of screen to right. Swipe gesture still triggers back navigation.
Expected behavior: With
[swipeGesture]=false
, The swipe gesture should be prevented and browser back navigation should not occur. With[swipeGesture]=true
only one of the browser back navigation or ionic built in back-transitions should occur, not both.Steps to reproduce:
Generate a sample ionic application:
ionic start myApp sidemenu
ionic serve --external
set
[swipeGesture]='false'
on ion-router-outlet inapp.component.html
Navigate to the URL on an iPad or Android device with browser swipe-back functionality. Navigate to another page.
Drag finger from left edge of screen to right. Swipe gesture still triggers back navigation.
Related code:
Any brand new ionic generated app will demonstrate this issue.
Other information:
Ionic info: