When my app is in the background and goes to foreground, the OnAppearing() method doesnt work in IOS or when the phone is locked and then unlocked and the app is in the foreground the method OnAppearing() isnt called, on Android everything works fine.
I found this guide below, but still doesnt work, i have the last version of xamarin forms.
Guide:
https://kent-boogaart.com/blog/hacking-xamarin.forms-page.appearing-for-ios
Can anyone help me?
As you have seen, the "lifecycles" within iOS are different. One way that helps is to use the Application lifecycles and tie those into Page events (or Commands if needed).
In your Application subclass add a couple of public EventHandlers and tie those into the OnResume (and OnSleep if needed)
public partial class App : Application
{
public EventHandler OnResumeHandler;
public EventHandler OnSleepHandler;
public App()
{
InitializeComponent();
MainPage = new MyPage();
}
protected override void OnSleep()
{
OnSleepHandler?.Invoke(null, new EventArgs());
}
protected override void OnResume()
{
OnResumeHandler?.Invoke(null, new EventArgs());
}
}
Now in your ContentPage subclass, add a handler that tracks when that page comes back from being in the background, kind-of an "OnAppearing after OnPause" handler...
void Handle_OnResumeHandler(object sender, EventArgs e)
{
Console.WriteLine("OnPauseResumeWithPage");
}
protected override void OnAppearing()
{
(App.Current as App).OnResumeHandler += Handle_OnResumeHandler;
base.OnAppearing();
}
protected override void OnDisappearing()
{
(App.Current as App).OnResumeHandler -= Handle_OnResumeHandler;
base.OnDisappearing();
}
Related
I want to hide a button when the user is scrolling through the page so a specific area of the page is not blocked by the button, after the user finish scrolling I will show the button again after a delay of 1000-1500ms. My idea so far is that I will subscribe to the scroll changing event, hide the button, wait for specific delay, show the button again. I managed to implement it for Android platform using effect like this:
public class AndroidScrollingEffect : Xamarin.Forms.Platform.Android.PlatformEffect
{
private bool _isAttached;
public static void Initialize() { }
protected override void OnAttached()
{
if (!_isAttached)
{
Control.ScrollChange += Control_ScrollChange;
_isAttached = true;
}
}
private void Control_ScrollChange(object sender, global::Android.Views.View.ScrollChangeEventArgs e)
{
var command = ScrollingEffect.GetCommand(Element);
command?.Execute(null);
}
protected override void OnDetached()
{
if (_isAttached)
{
Control.ScrollChange -= Control_ScrollChange;
_isAttached = false;
}
}
}
The implementation is working just fine, for android platform. Now, I want to do the same thing for iOS. So, is there an equivalent event for ScrollChange on iOS?
P.S. I want to use effect so I don't write code in the code behind of the page and still able to bind to a specific command in the view model.
Forms ScrollView is rendered as UIScrollView on iOS. So you can try the code below to construct your custom effect:
public class iOSScrollingEffect : PlatformEffect
{
UIScrollView nativeControl;
private bool _isAttached;
protected override void OnAttached()
{
nativeControl = Control as UIScrollView;
if (nativeControl != null && !_isAttached)
{
nativeControl.Scrolled += NativeControl_Scrolled;
_isAttached = true;
}
}
private void NativeControl_Scrolled(object sender, EventArgs e)
{
...
}
protected override void OnDetached()
{
if (_isAttached)
{
nativeControl.Scrolled -= NativeControl_Scrolled;
_isAttached = false;
}
}
}
I'm attempting to fake the act of clicking a button, by programmatically calling it within my ViewWillAppear() function.
The onclick function is defined in my ViewDidLoad(), and you can see I am trying to use a Perform Selector to manually call the button.
The button does not appear to be running. Any ideas?
public override void ViewDidLoad()
{
base.ViewDidLoad();
idButtonScanLoad.TouchUpInside += async (sender, ea) =>
{
System.Console.WriteLine("Scan button pressed");
};
}
[Export("TouchUpInsideEvent:")]
private void TouchUpInsideEvent(NSObject sender)
{
Console.WriteLine("yay!");
}
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
this.PerformSelector(new ObjCRuntime.Selector("TouchUpInsideEvent:"), this as NSObject, 10f);
}
Here is the answer, you should call it from your ViewDidLoad method.
myButton.SendActionForControlEvents (UIControlEvent.TouchUpInside);
If you want to create a button's click event, you can use the this
public override void ViewDidLoad()
{
Mybutton.AddTarget(ButtonEventHandler, UIControlEvent.TouchUpInside);
}
public void ButtonEventHandler(object sender, EventArgs e)
{
if(sender==Mybutton)
{
//do stuff here
}
}
In my application I was using SystemEvents to add objects to an ObservableCollection (code shortened for this example)
public partial class App : Application
{
private ObservableCollection<StateChanged> _messages = new ObservableCollection<StateChanged>();
public ObservableCollection<StateChanged> messages { get { return _messages; } }
protected override void OnStartup(StartupEventArgs e)
{
SystemEvents.SessionSwitch += SystemEvents_SessionSwitch;
}
private void SystemEvents_SessionSwitch(object sender, SessionSwitchEventArgs e)
{
messages.Insert(0, new StateChanged(DateTime.Now, State.Logoff));
}
}
Above code works without a problem.
Because I do not only have to handle SessionSwitch events, but also SessionEnding etc. I wrote a small class that should raise a 'unified' event for some of the SystemEvents (again shortened)
public class SystemEventArgs : EventArgs
{
public State newState { get; set; }
}
public delegate void SystemEventHandler(object sender, SystemEventArgs e);
class SystemEventCollector
{
public event SystemEventHandler SessionEvent;
protected virtual void RaiseSystemEvent(SystemEventArgs e)
{
SystemEventHandler handler = this.SessionEvent;
if (handler != null)
handler(this, e);
}
public SystemEventCollector()
{
SystemEvents.SessionSwitch += SystemEvents_SessionSwitch;
}
protected void SystemEvents_SessionSwitch(object sender, SessionSwitchEventArgs e)
{
SystemEventArgs ea = new SystemEventArgs();
ea.newState = State.Unknown;
RaiseSystemEvent(ea);
}
}
When I instanciate this class in my Application and subscribe to the SessionEvent, doing the same stuff, like this
public partial class App : Application
{
private ObservableCollection<StateChanged> _messages = new ObservableCollection<StateChanged>();
public ObservableCollection<StateChanged> messages { get { return _messages; } }
private SystemEventCollector _sysEventCollector = new SystemEventCollector();
protected override void OnStartup(StartupEventArgs e)
{
_sysEventCollector.SessionEvent += OnSessionEvent;
}
private void OnSessionEvent(object sender, SystemEventArgs e)
{
messages.Insert(0, new StateChanged(DateTime.Now, e.newState));
}
}
The messages.Insert() call raises an exception
This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread.
I do understand that I can not update a GUI element from another thread than the one it was created on and have worked around this problem by using the extension method mentioned in this SO answer.
My question is to why this happens? My assumptions are that events are handled on the same thread as they are raised, so why is there a difference between handling the SessionSwitch event directly and my approach of raising an event when handling the SessionSwitch event? How are the SystemEvents different from my event? Do I have the correct solution to the problem? Is there a simpler solution?
From some testing it seems that the error lies in the non-working code is the instanciation of SystemEventCollector object.
MS does all the necessary marshalling in their SessionEvents.*** handlers, this is why the first example works without problems. In the non-working code SystemEventCollector is no instanciated in the OnStartup function (which is called from the UI thread) but basically with the constructor. When marshalling from the SessionEvents is done, it goes to the wrong thread, leading to the problem.
Apart from my original solution, the problem can also be solved by instanciating the SystemEventCollector in the OnStartup function.
protected override void OnStartup(StartupEventArgs e)
{
_sysEventCollector = new SystemEventCollector();
_sysEventCollector.SessionEvent += OnSessionEvent;
}
I have a problem with my behavior using Microsoft Expression Blend. I don't know how to get the OnAttached event in Expression Blend or Visual Studio. It doesn't fire. Here's an example:
public class MyBehavior : Behavior<Path>
{
public PathNavigation()
{
}
protected override void OnAttached()
{
// Only firing in runtime
base.OnAttached();
AssociatedObject.Loaded += AssociatedObject_Loaded;
}
private void AssociatedObject_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
// Only firing in runtime
}
}
Is there a possibility to get that events while designing in Blend?
Thanks in advance.
Chris
Is it possible that at design time, AssociatedObject.IsLoaded == true? You could check that before attaching your event handler, and call a common OnLoaded method.
Is it OnNavigatedTo or Loaded Event? I've been able to use both interchangeably but I would like to know explicitly which comes first.
OnNavigatedTo fires first -- source - A simple experiment
See code below
public partial class MainPage : PhoneApplicationPage
{
// Constructor
public MainPage()
{
InitializeComponent();
Loaded += new RoutedEventHandler(MainPage_Loaded);
}
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
throw new NotImplementedException();
}
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
base.OnNavigatedTo(e);
}
// Simple button Click event handler to take us to the second page
private void Button_Click(object sender, RoutedEventArgs e)
{
NavigationService.Navigate(new Uri("/GamePage.xaml", UriKind.Relative));
}
}
in OnNavigatedTo:
System.Diagnostics.Debug.WriteLine("in OnNavigatedTo");
in Loaded:
System.Diagnostics.Debug.WriteLine("in Loaded");
Run in Debug mode, see which one writes first.