I have a ContentPage with a WebView in my shared Xamarin project.
To enable Android's back button within the WebView, I used the following code:
protected override bool OnBackButtonPressed()
{
_myWebView.GoBack();
return true;
}
As we all know, iOS does not have a back button. I want to enable swiping left in order to navigate to the previous Webview page.
What is the easiest way or approach?
I would suggest implementing a custom renderer for the WebView and attach a gesture recognizer to the underlying UIWebView. This is how the WebViewRenderer for iOS is implemented in Xamarin Forms.
The Xamarin Forms WebView is actually a UIWebView on iOS. So you can implement the Swift code from the following answer in your renderer: https://stackoverflow.com/a/32012660/6192895. Converting this Swift code to C# is fairly trivial, but this is also a potential solution:
Basic solution
public class SwipeWebViewRenderer : WebViewRenderer
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
var view = NativeView as UIWebView;
var swipeBackRecognizer = new UISwipeGestureRecognizer(HandleSwipeBack);
swipeBackRecognizer.Direction = UISwipeGestureRecognizerDirection.Right;
view.AddGestureRecognizer(swipeBackRecognizer);
}
void HandleSwipeBack()
{
(Element as WebView).GoBack();
}
}
This solution is fairly basic, it just does the GoBack() action without actually showing the swipe of the page like the normal Safari browser does. So you might have to add some more code if that's what you need.
Transitioning
You can make use of CoreAnimation for this. Use this as your HandleSwipeBack method:
void HandleSwipeBack()
{
CATransition animation = new CATransition();
animation.Type = CAAnimation.TransitionPush;
animation.Subtype = CAAnimation.TransitionFromLeft;
animation.Duration = 0.50;
animation.TimingFunction = CAMediaTimingFunction.FromName(CAMediaTimingFunction.EaseInEaseOut);
(NativeView as UIWebView).Layer.AddAnimation(animation, CALayer.Transition);
(Element as WebView).GoBack();
}
This will not get you the exact same behaviour as the Safari browser, as it will not cancel the animation if you stop the swipe halfway for example. It is possible to do so, but it would require more code.
Extra information
You might also be able to draw some inspiration from this Pull Request for a SwipeGestureRecognizer, which was not included in Xamarin Forms because unit tests and UI tests were missing.
Unfortunately the default Xamarin Forms GestureRecognizers are limited to Tap, Pinch and Pan for the moment.
Can you try the following
NavigationPage.SetBackButtonTitle (this, "")
or alternatively you can add tool bar item and then try something like this
ToolbarItems.Add(new ToolbarItem("Back", "BackIcon.png", () => { _webview.GoBack(); }));
Related
Using Xamarin Forms 2.5.0.122203, I want to achieve to add an image that is half over navigation bar and another half in the page like this image
I tried Negative margin and it only worked on UWP. Unfortunately, iOS and Android cut image.
I was thinking of creating a custom navigation bar but the thing is UWP has this bug that doesn't hide native Navigation right.
Any solution?
Thank you
I ended up Copying SNavigation.Forms to my projects.
After following their documentation.
In the Page I override
protected override void OnBindingContextChanged()
{
if (BindingContext == null)
{
return;
}
base.OnBindingContextChanged();
SNavigationPage.SetNavContent(this, new LogoHeader()
{
BindingContext = BindingContext,
});
}
The LogoHeader is the intended layout to be postioned in the Navigation (Better use a Grid)
Visually I have one Navigation Header but technically it is cut in half.
It took a lot of time to get the perfect result because you have to remove all navigation shadows from Android and iOS but it is a reliable solution
I have a custom control inherited from Frame. (In a nutshell, it's kind of a custom alert box which I show over the whole app content).
I am using Custom Renderer to achieve this.
In xaml the control is located directly on the page (among other controls) (actually, I am creating it in the condebehind, but that makes no difference).
(Implementing it for iOS so far only).
The showing/hiding is initiated by IsVisible XF property. Then, I am adding the container view (native one) into the root of the app.
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "IsVisible")
{
// showing/hiding here
I am having two issues in this situation:
1. Right on this event raising the content positioning of the native view generated is not quite initialized: the iOS Frame values of the views don't have any widths/heights setup. That all probably done right after, so what I do is the following:
Task.Run(async () =>
{
await Task.Delay(10);
InvokeOnMainThread(() =>
{
SetupLayout();
Subviews[0].Alpha = 1;
SetupAnimationIn();
});
});
... which generally works, but still not quite slightly, and the approach is neither reliable nor nice.
On IsVisible = false it's even worse: I cannot handle the leaving animation as the element content got destroyed by XF engine (I suppose) right after (or even before) the notification raised, so the element disappears instantly, which doesn't look nice for the user experience.
So, is there any nice way to handle those things?
It's probably a little late, but I thought I would offer some guidance for anyone else trying to do something similar. For (1) I'm not quite sure what you mean that the native view is not quite initialized, but here is something you might find useful:
var measurement = Measure(screenWidth, screenHeight);
var contentHeight = measurement.Request.Height;
You can read more about what the 'Measure' method does in the docs, but basically it gives you the minimum dimensions of the view element based on its content. The arguments are constraints, so use the maximum size that the view might be. You can use it to initialize the dimensions manually by setting 'WidthRequest' and 'HeightRequest'.
(2) Basically you need to override the IsVisible property like this:
public static new readonly BindableProperty IsVisibleProperty = BindableProperty.Create(
nameof(IsVisible), typeof(bool), typeof(MyCustomView), default(bool), BindingMode.OneWay);
public new bool IsVisible
{
get => (bool)GetValue(IsVisibleProperty);
set => SetValue(IsVisibleProperty, value);
}
Note the use of the new keyword to override the inherited property. Of course, then you will need to handle the visibility yourself.
protected override void OnPropertyChanged(string propertyName = null)
{
base.OnPropertyChanged(propertyName);
if (propertyName == IsVisibleProperty.PropertyName)
{
if (IsVisible) AnimateIn();
else AnimateOut();
}
}
Now you can handle the visibility however you want. Hopefully this is of some help!
I am trying to make like my own music app. With my dependency service solution in iOS i can hear the mp3 just fine but once I navigate through other pages the file stops. So my question is, how can I make so the file keeps playing even if i navigate through different pages after i "play" it?
This is my code:
My button where I pick the "track".
async void PlayThisSongButton (object s, EventArgs e)
{
DependencyService.Get<IPlayerVoice>().PlayAudioFile("myfilename.mp3");
}
Interface:
public interface IPlayerVoice
{
void PlayAudioFile(string fileName);
}
Dependency service iOS:
[assembly: Xamarin.Forms.Dependency(typeof (VoicePlayer_iOS))]
namespace myProject.iOS
{
public class VoicePlayer_iOS : IPlayerVoice
{
AVAudioPlayer player;
public VoicePlayer_iOS()
{
}
public void PlayAudioFile(string fileName)
{
string sFilePath = NSBundle.MainBundle.PathForResource
(Path.GetFileNameWithoutExtension(fileName), Path.GetExtension(fileName));
var url = NSUrl.FromString(sFilePath);
var _player = AVAudioPlayer.FromUrl(url);
_player.FinishedPlaying += (object sender, AVStatusEventArgs e) =>
{
_player = null;
};
_player.Play();
}
}
}
So with this current code. I click on my button where i start the audiofile. I then navigate to a different page. The audiofile stops. Any idea how i can solve this?
I have never worked with audio in Xamarin Forms but looked into it once.
I think the comment from #yanyankelevich with a GitHub link would still stop the audio when the user navigates away from the page.
As #yanyankelevich suggested though, using a background service might be your best bet. Some links for that (there is a lot of code so I will not duplicate it in this post):
iOS AVAudioPlayer (specifically check out the PlayBackgroundMusic method and the other background related methods below that one)
Android Background Audio Streaming
For a super simple way of doing it (which I do not suggest and have not ever tried), have you tried just running your method within a Device.BeginInvokeOnMainThread() which will do a fire and forget?
Device.BeginInvokeOnMainThread(() => DependencyService.Get<IPlayerVoice>().PlayAudioFile("myfilename.mp3"));
I would say you need to Use MVVM or MVC and run the music on the main view.
this will allow you to browse the app, while your music still plays.
basically have the Main view and create a grid that you place your "browserView"
in, add all the normal code you would use for the app to the viewmodel of the browser.
then add the code for the music to the viewmodel of the mainview.
Just make sure to add the functionality to pause the music.
i'm developing an app in Xamarin Forms, but i've noticed a strange behavior, when i display an alert, in Android (version major than 4.2.2), if i press outside the alert modal, the alert disappear immediately. There is any way to prevent this? I want that the alert disappear only on user selection.
Many thanks
When a dialog is shown in Android, clicking just outside of it to cancel is pretty standard. To change this behavior you will need to create an interface in your PCL:
public interface ICustomAlert
{
void ShowAlert(string message);
}
In your Android project create the implementation (something like this):
[assembly: Xamarin.Forms.Dependency (typeof (AndroidCustomAlert))]
public class AndroidCustomAlert : ICustomAlert
{
void ShowAlert(string message)
{
var builder = new AlertDialog.Builder(Xamarin.Forms.Forms.Context);
builder.SetMessage(message);
builder.SetPositiveButton("OK", (sender, args) => { });
builder.SetCancelable(false);
builder.Show();
}
}
Notice the SetCancelable(false). That's what makes it so the user can't click outside of the alert and make it disappear.
To use it, get ICustomAlert from the dependency service and call ShowAlert:
DependencyService.Get<ICustomAlert>().ShowAlert("Hello!");
Implement your own DisplayAlert with a PopupLayout.
OR
Make use of ACR User Dialogs Plugin for Xamarin by Allan Ritchie
Recently i decided to give Xamarin Forms a try, so i started migrating my Windows Phone / Android Xamarin project, to the new "Forms" format.
After migrating two simple screens (views), the code runs just fine on my Windows Phone, but it crashes on the Android Platform. I am using two devices to test the code, with no emulator to avoid unecessary headaches.
The exception raised on the android platform is: System.Exception: Android only allows one navigation page on screen at a time.
The problem is not related to the "View" class itself, since it doesn`t happen once i set it as the "start screen" for the Android.
I'm afraid the problem is related to the fact that i am "Pushing" two screens via the "Navigation.PushAsync(...)" method provided by Xamarin.
Any ideas on how do actually fix it ?
EDIT:
Code Sample
Overview: The "MainView.cs" is just a content page with a label on the top, followed by two buttons alligned vertically.
How to reproduce the exception: Click on "Get All Users" button on the mainview. I have modified the code to push the same view once the button is clicked, instead of pushing another view (The view that has a listview of users, which i haven't included, just to make it simple). Once the button is clicked, it will navigate to a new instance of "MainView" by calling "Navigation.PushAsync(...)". This will trigger the exception after the screen is loaded.
You are nesting Navigation pages. This is not necessary. Try this instead:
public class ViewsHandler
{
public static Page GetMainPage()
{
return new MainView();
}
public static Page GetUsersListPage()
{
return new UsersListView ();
}
}
public class MainActivity : AndroidActivity
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
// Initializing Xamarin Form
Xamarin.Forms.Forms.Init (this, bundle);
// create a single NavigationPage wrapping your content
SetPage (new NavigationPage(ViewsHandler.GetMainPage()));
}
}