I'm developing an app for blind or visually impaired people using Xamarin.Forms. I have a NavigationPage with the title X, and when the app starts talkback reads the title X. I have 4 pages that can be navigated to with A, B, C and D titles. I would like that when navigating through different pages, the screen reader would read the title of the page. Is it possible?
For example, if I switch on talkback on Android and I navigate through settings pages, talkback reads all page titles. I want to do something similar.
Read current page title with screen reader when push/pop a page
You could use DependencyService to achieve the feature on each platform and call the function code in the OnAppearing method of each page.
Check the code:
Create an interface in the shared project.
public interface IAccessibilityManager
{
void sendAccessibility(string speakText);
}
Implement the interface in the required platform projects.
[assembly: Dependency(typeof(DroidAccessibilityImplement))]
namespace App19F_9.Droid
{
public class DroidAccessibilityImplement : IAccessibilityManager
{
public void sendAccessibility(string speakContent)
{
AccessibilityManager manager = (AccessibilityManager)Android.App.Application.Context.GetSystemService(Context.AccessibilityService);
if (manager.IsEnabled)
{
AccessibilityEvent _event = AccessibilityEvent.Obtain();
_event.EventType = EventTypes.Announcement;
_event.Text.Add(new Java.Lang.String(speakContent));
manager.SendAccessibilityEvent(_event);
}
}
}
}
Resolve the platform implementations from shared code.
public partial class Page5 : ContentPage
{
...
protected override void OnAppearing()
{
base.OnAppearing();
DependencyService.Get<IAccessibilityManager>().sendAccessibility(this.Title);
}
}
Related
For the past 2 month I have been searching tirelessly for a way to implement a proper Page.Loaded event when using Xamarin.Forms but I couldn't implement or find a way to do it.
Most people suggest overriding Page.OnAppearing or adding an event handler for Page.Appearing both of which are not the answers or the proper way to achieve the desired effect and don't event behave as a real Page.Loaded event would.
I would like to know the following:
Why doesn't Xamarin.Forms have a built-in Page.Loaded event?
Is there's a work around?
Can I implement it from the native side?
Edit:
What I mean by "proper Page.Loaded" event is:
It must be called ONCE AND ONLY ONCE the page has loaded all of it's controls, laid them out, initialized them and rendered them for the first time.
It must NOT be called when returning from modal pages.
1.Why not load the data/controls in the constructor of the ContentPage? The constructor method is call only once and it is also called before Page.OnAppearing.
Can I implement it from the native side?
Yes, I think you can.
In iOS, override the ViewDidLoad method in custom renderer:
[assembly:ExportRenderer (typeof(ContentPage), typeof(MyPageRenderer))]
namespace App487.iOS
{
public class MyPageRenderer : PageRenderer
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
//call before ViewWillAppear and only called once
}
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
}
}
}
In Android, try to override the OnAttachedToWindow method:
[assembly: ExportRenderer(typeof(ContentPage), typeof(MyPageRenderer))]
namespace App487.Droid
{
public class MyPageRenderer : PageRenderer
{
public MyPageRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
{
base.OnElementChanged(e);
}
protected override void OnAttachedToWindow()
{
base.OnAttachedToWindow();
}
}
}
Currently Xamarin.Forms doesn't not provide a proper/complete life cycle events to fulfill all specific requirements, but things are improving, the Dev team is currently working on to address this issue, below mentioned issues and recent pull request on the official GitHub Repos (you may follow, get ideas and maybe implement it yourself before they even ship it), they will for sure provide that in the future, although it is not clear when it will be ready.
Specification: Enhancement Add better life cycle events #2210.
Issue: LifeCycle events for controls #556.
Pull request: Life cycle events for controls
GitHub Branch where currently working on.
MAUI repo (Next evolution of Xamarin) Cross-Platform LifeCycle.
Specification Add Loaded/Unloaded to VisualElement.
I have an issue with navigation. When I click home screen button of device and get back to app I get app homescreen instead of pin page. Ideally it should show pin page and its working fine with back button of device.
OnStart() method has navigationasync but the same is not working with OnResume() method.
Do I have to go to each of the Platform project cs file and add the navigation there like for Android OnRestart()/OnResume() method?
If anyone knows the solution please let me know
Most commonly when writing your Xamarin Application with Prism you will have something like:
protected override void OnInitialized()
{
NavigationService.NavigateAsync("SomePage");
}
OnInitialized is called each time the App's ctor is invoked. This is an important consideration here because this means that any time that the native platform tombstones the app in the background or otherwise refreshes the app by calling OnCreate in your MainActivity or FinishedLaunching in your AppDelegate, then OnInitialized will be invoked resetting your App's Navigation stack to SomePage.
You can however override the OnStart/OnResume in PrismApplication and use whatever business logic you need to determine where to navigate and how you might want to restore your application.
public override void OnStart()
{
NavigationService.NavigateAsync("MainPage");
}
public override void OnResume()
{
if(someCondition)
{
NavigationService.NavigateAsync("SomePage");
}
else
{
NavigationService.NavigateAsync("AnotherPage");
}
}
I have the following AppDelegate which takes quite some time to load:
Syncfusion.ListView.XForms.iOS.SfListViewRenderer.Init();
new Syncfusion.SfNumericUpDown.XForms.iOS.SfNumericUpDownRenderer();
Syncfusion.SfCarousel.XForms.iOS.SfCarouselRenderer.Init();
Syncfusion.XForms.iOS.Buttons.SfSegmentedControlRenderer.Init();
Syncfusion.XForms.iOS.Buttons.SfCheckBoxRenderer.Init();
new Syncfusion.XForms.iOS.ComboBox.SfComboBoxRenderer();
//Syncfusion.XForms.iOS.TabView.SfTabViewRenderer.Init();
new Syncfusion.SfRotator.XForms.iOS.SfRotatorRenderer();
new Syncfusion.SfRating.XForms.iOS.SfRatingRenderer();
new Syncfusion.SfBusyIndicator.XForms.iOS.SfBusyIndicatorRenderer();
What options should I consider when I know some of these components aren't needed for the main screen, but for subscreens?
I am using PRISM, and it appears that every tab is pre-loaded immediately before allowing display or interaction with the end user. What can I do to delay the pre-rendering that the Prism TabView does prior to showing the interface?
Should I use Lazy<T>? What is the right approach?
Should I move these components to another initialization section?
There are a number of ways you could ultimately achieve this, and it all depends on what your real goals are.
If your goal is to ensure that you get to a Xamarin.Forms Page as fast as possible so that you have some sort of activity indicator, that in essence says to the user, "it's ok I haven't frozen, we're just doing some stuff to get ready for you", then you might try creating a "SpashScreen" page where you do additional loading. The setup might look something like the following:
public partial class AppDelegate : FormsApplicationDelegate
{
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
global::Xamarin.Forms.Forms.Init();
LoadApplication(new App(new iOSInitializer()));
return base.FinishedLaunching(app, options);
}
}
}
public class iOSInitializer : IPlatformInitializer, IPlatformFinalizer
{
public void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterInstance<IPlatformFinalizer>(this);
}
public void Finalize()
{
new Syncfusion.SfNumericUpDown.XForms.iOS.SfNumericUpDownRenderer();
Syncfusion.SfCarousel.XForms.iOS.SfCarouselRenderer.Init();
Syncfusion.XForms.iOS.Buttons.SfSegmentedControlRenderer.Init();
Syncfusion.XForms.iOS.Buttons.SfCheckBoxRenderer.Init();
}
}
public class App : PrismApplication
{
protected override async void OnInitialized()
{
await NavigationService.NavigateAsync("SplashScreen");
}
}
public class SplashScreenViewModel : INavigationAware
{
private IPlatformFinalizer _platformFinalizer { get; }
private INavigationService _navigationService { get; }
public SplashScreenViewModel(INavigationService navigationService, IPlatformFinalizer platformFinalizer)
{
_navigationService = navigationService;
_platformFinalizer = platformFinalizer;
}
public async void OnNavigatedTo(INavigationParameters navigationParameters)
{
_platformFinalizer.Finalize();
await _navigationService.NavigateAsync("/MainPage");
}
}
If you're working with Modules you could take a similar approach though any Modules that would initialize at Startup would still be making that call to Init the renderers before you've set a Page to navigate to. That said, working with Modules does give you a number of benefits here as you only ever would have to initialize things that the app actually requires at that point.
All of that said I'd be surprised if you see much in the way of gain as these Init calls are typically empty methods only designed to prevent the Linker from linking them out... if you aren't linking or have a linker file you could simply instruct the Linker to leave your Syncfusion and other libraries alone.
I have a cross-platform mobile app (Android/iOS) which implements the generic WebView control. This works well for most circumstances, but some iOS users complain that, when attempting to load a certain resource-intensive web page, the app "goes black" and then focus returns to the main menu view. My suspicion is that the app is choking due to the amount of content and processing overhead of the web page, but frankly this is a blind guess and I don't have the resources (such as an iPhone at my disposal) in order to verify this. Using an iPhone simulator on a Mac does not reproduce the "black screen" issue.
Therefore, I am attempting to implement in parallel WkWebView for iOS devices at version 8.0 and above as this is presumably more performant and might alleviate the problem. It is just about working, but there seems to be a disconnect between the ViewController and ContentPage which is supposed to host the WkWebView control which I have been unable to rectify.
Below is the general implementation:
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class WkWebViewPage : ContentPage
{
public WkWebViewPage (string url, string title)
{
InitializeComponent();
Title = title;
App.GetWkWebView(this, url);
}
}
The markup for WkWebPageView has no inner content.
The method App.GetWkWebView is a delegate of type Action implemented as a static property in the main App class. This is assigned in the FinishedLaunching method of the AppDelegate class (iOS project) to a static method in the static class I am using to manage invoking the WkWebView. This class is implemented as such:
public static class WkWebViewController
{
private static WKWebView _wkWebView;
public static void GetWkWebView(Page parentView, string url)
{
if(_wkWebView == null)
{
// INSERT ATTEMPTED APPROACHES BELOW HERE
var frame = view.Frame;
var cgRect = new CoreGraphics.CGRect(frame.X, frame.Y, frame.Width, frame.Height);
_wkWebView = new WKWebView(cgRect, new WKWebViewConfiguration());
view.AddSubview(_wkWebView);
// NavigationDelegate is a custom class; not germane to the issue
_wkWebView.NavigationDelegate = new NavigationDelegate();
}
var nsUrl = new NSUrl(url);
var request = new NSUrlRequest(nsUrl);
_wkWebView.LoadRequest(request);
}
}
Here is where the trouble begins. I have tried two approaches to obtaining the appropriate ViewController -- and more pertinently, the UIView object:
1)
var renderer = Platform.GetRenderer(parentView);
if (renderer == null)
{
renderer = Platform.CreateRenderer(parentView);
Platform.SetRenderer(parentView, renderer);
}
var view = renderer.ViewController.View;
This results in the following:
(source: aquaspy.com)
The content area is white/blank. The http request is submitted successfully as a 200 response is received. Note that the navigation bar above the content area properly displays.
2)
var window = UIApplication.SharedApplication.KeyWindow;
var vc = window.RootViewController;
while (vc.PresentedViewController != null)
{
vc = vc.PresentedViewController;
}
var view = vc.View;
Which results in:
(source: aquaspy.com)
In this case, the web page displays; however, the WkWebView control takes up the entire screen, obscuring the navigation bar (and seemingly the Status Bar).
Any suggestions would be greatly appreciated!
I am using a portable project so do not have direct access to native code.
I have an interface in my project that allows me to access native objects in the Android/iOS projects. We use this primarily for playing audio.
Android, for example, has things like
Window w = new Window();
w.SetFlags(WindowManagerFlags.Fullscreen, WindowManagerFlags.KeepScreenOn);
However the main issue would be accessing a Window object. I could pass a Xamarin.Forms.Page object to the native code, but there would be no way (I don't think) to cast it to a native Android Window object to access the flags.
Is there a way to do this with a portable project?
You can't do this without platform specific services or renderers. A portable project will have to call platform specific code in order to achieve this.
From that platform specific code, either as a DependencyService or Renderer, you can access the Window object through the Forms.Context. The Forms.Context is your Android Activity, through which you can reach the Window object.
On Android it works like this:
Android.Views.Window window = (Forms.Context as Activity).Window;
window.SetFlags(WindowManagerFlags.KeepScreenOn);
On iOS you can try this (Apple docs):
UIApplication.SharedApplication.IdleTimerDisabled = true;
Now there is a plugin doing exactly what Tim wrote
https://learn.microsoft.com/en-us/xamarin/essentials/screen-lock
simple source code is here
https://github.com/xamarin/Essentials/blob/main/Samples/Samples/ViewModel/KeepScreenOnViewModel.cs
using System.Windows.Input;
using Xamarin.Essentials;
using Xamarin.Forms;
namespace Samples.ViewModel
{
public class KeepScreenOnViewModel : BaseViewModel
{
public KeepScreenOnViewModel()
{
RequestActiveCommand = new Command(OnRequestActive);
RequestReleaseCommand = new Command(OnRequestRelease);
}
public bool IsActive => DeviceDisplay.KeepScreenOn;
public ICommand RequestActiveCommand { get; }
public ICommand RequestReleaseCommand { get; }
void OnRequestActive()
{
DeviceDisplay.KeepScreenOn = true;
OnPropertyChanged(nameof(IsActive));
}
void OnRequestRelease()
{
DeviceDisplay.KeepScreenOn = false;
OnPropertyChanged(nameof(IsActive));
}
}
}
For Xamarin Forms Android.
Renders file I included below code
Window window = (Forms.Context as Activity).Window;
window.AddFlags(WindowManagerFlags.KeepScreenOn);