Table Cell seems to lose binding when the table is pulled up - ios

I have an issue with a Xamarin IOS project using MVVMCross. There is a MvxTableViewController which displays a variety of MvxTableViewCells. When the user touches inside a text field in a cell and then pulls the table view controller up, the data in the focused cell disappears. As far as I can tell, there is no application code executing after this gesture.
I've attached screens showing the data before, during and after the screen is pulled up. There are no messages written to the console during this event.
Any idea where I should Look for this issue?
Update
Here is the code used to bind the values in the cell's constructor. There's a bit more than just binding going on, but I included it all in case it is impacting the issue.
Thanks!
public BaseComponentCell (IntPtr handle) : base (handle)
{
this.DelayBind (() => {
ComponentValueTextField.ShouldReturn += (textField) => {
textField.ResignFirstResponder ();
var component = (BaseComponent)DataContext;
if(component.DoneEditingCommand!=null){
component.DoneEditingCommand.Execute(null);
}
return true;
};
var myComponent = (BaseComponent)DataContext;
if (myComponent.DisplayNumberPadKeyboard)
{
ComponentValueTextField.KeyboardType = UIKeyboardType.NumberPad;
}
var set = this.CreateBindingSet<BaseComponentCell, BaseComponent> ();
set.Bind(ComponentNameLabel).To(c=>c.ComponentName);
set.Bind(ComponentValueTextField).To(c=>c.ComponentValue);
set.Bind(ComponentValueTextField).For(f => f.Enabled).To(c => c.IsEnabled);
set.Bind(this).For(bc=>bc.IsSecure).To(c=>c.IsSecure);
set.Apply();
});
}

Related

Vaadin Flow: Returning to a view, the view should not reload data from the backend

Split out from Vaadin Dataprovider: how to avoid "auto-fetch"?.
Given a Vaadin Flow 19 app with a MainView extends AppLayout, a GridView and an EmptyView And #PreserveOnRefresh annotation is used on MainView.
When returning to GridView, the GridView should be exactly in the same state as before:
open GridView using button in MainView for the first time -> Grid uses DataProvider to fetch data from backend
enter "Spiderman" in TextField with caption "stateCheck"
switch to EmptyView using button in MainView
in the real app: do something in EmptyView and potentially other views
return to GridView using button in MainView for the 2nd time
Then (1) the TextField with caption "stateCheck" should display the value "Spiderman"
And (2) the grid should still show the same data as before; it should not reload the data from the backend
Observed behaviour:
(1) is ok, but (2) not: the grid always calls fetch method to get data from the backend.
How do I achieve the desired behavior?
Here's the code of my GridView which also fakes the backend DataProvider:
#Route(value = "grid", layout = MainView.class)
public class GridView extends VerticalLayout {
public GridView() {
final Grid<Person> g = new Grid(Person.class);
g.setColumns("name");
g.setDataProvider(DataProvider.fromCallbacks(q -> fetch(q), q -> count(q)));
add(g);
add(new TextField("State check"));
}
// fake DataProvider
private int count(Query<Person, Void> q) { return 3; }
private Stream<Person> fetch(Query<Person, Void> q) {
q.getLimit(); //vaadin checks these have been called
q.getOffset(); //vaadin checks these have been called
System.out.println("fetching again");
return Arrays.asList(new Person("1"), new Person("2"), new Person("3")).stream();
}
}
MainView is used to switch between GridView and EmptyView
#PreserveOnRefresh
public class MainView extends AppLayout {
private Component emptyBView;
private Component gridBView;
public MainView() {
final Button emptyB = new Button("Btn empty");
emptyB.addClickListener(e -> {
if (emptyBView == null) { emptyBView = new EmptyView();}
setContent(emptyBView);
});
addToNavbar(emptyB);
final Button gridB = new Button("Btn grid");
gridB.addClickListener(e -> {
if (gridBView == null) gridBView = new GridView();
setContent(gridBView);
});
addToNavbar(gridB);
}
}
This is actually intentional behavior. The server side dataprovider listener needs to be removed when component is detached and rewired on attaching. The reason is that otherwise there would be listeners accumulating and producing a memory leakage. If you think your users would be using refresh page often, you should consider adding a cache to your application to optimize performance.
Now one could entertain with the idea of having this kind of caching of previous loaded data behavior via API in Grid also in Vaadin framework, as it may or may not be desirable. It is application specific.
If the use case of refreshing is really to get the fresh data of live and active database, it is actually desired that data is loaded when page is refreshed.
If the desire is to avoid extra bombarding of DB as data is known to be static, you want to have caching.

DelayBind not binding MvxCommands within MvxTableViewCell on iOS

I have an ItemCell which inherits from MvxTableViewCell. Below is simplified:
Constructor:
public ItemCell(IntPtr handle) : base(handle)
{
CreateLayout();
InitializeBindings();
}
CreateLayout() creates and constrains, among other elements:
A UILabel _label, and
A UIButton _button
InitializeBindings:
private void InitializeBindings()
{
this.DelayBind(() =>
{
var set = this.CreateBindingSet<ItemCell, ItemViewModel>();
set.Bind(_label).For(x => x.Text).SourceDescribed("'Label: ' + ItemNumber");
set.Bind(_button).To(vm => vm.ItemCommand);
set.Apply();
});
}
ViewModel contains the following property and command:
private string _itemNumber;
public string ItemNumber
{
get { return _itemNumber; }
set { SetProperty(ref _itemNumber, value); }
}
private IMvxCommand _itemCommand;
public IMvxCommand ItemCommand
{
get
{
return _itemCommand?? (_itemCommand= new MvxCommand(() => {
//Logic
}));
}
}
When the TableView is bound to a collection and the cells are repeated, _label's text renders the correct value ("Label: {ItemNumber}"), but clicking the button doesn't hit the ItemCommand's get. I have also tried adding .For("TouchUpInside") to the button's binding, but that didn't change anything.
I'm confused as to why the label binds correctly, but the button does not.
Unfortunately I do not have enough points to add a comment so I'll add my suggestion as an answer.
What possibly might be happening is that your touch event is handled for the whole cell and therefore the event is not passed down to the children of that cell. Your button might be bound correctly but since your button within your cell will never receive the touch event the command which you bound to will never get fired.
EDIT
You can have a look at this link which seems to address your problem.
https://forums.xamarin.com/discussion/15560/how-to-add-a-custom-button-in-a-table-cell
Debug output (Mvx.Trace()) the Bounds of your UIButton. Even if it is displayed, its "bounds" may be 0. In this case, no interaction can occur on it, and you have to fix your layout (constraints or frame).

UIScrollView doesn't register events until ViewController Segue

very first SO question after long-time lurking. I've been teaching myself iOS development using C# under Xamarin with Visual Studio.
My problem statement is: Why does my scrollview only work, i.e. register events such as dragging, AFTER having used the ViewController segue to another view and back? While at the same time using scrolling through the PageControl works just fine at any time?
I've successfully used a wide range of sample code found both here, on the Xamarin site, and elsewhere (including some samples written in Swift which I translated to C# - Objective-C might as well be Minoan Linear B).
This also worked without issues in all variations until I started adding AutoLayout into the equation. Using FluentLayout made this a lot easier and I now overall have a well-behaved UI exactly the way I want it on all iPhone models.
The essential boilerplate code worked fine when it was all still all jammed into the root viewcontroller's ViewDidLoad, but now that I've broken it all up into "clean" classes it's showing this strange behaviour.
One problem I had to tackle was the problem of getting images to properly resize inside the UIImageView frame, when the size isn't available until the layout constraints are complete. As I now understand the concept, that's what ViewDidLayoutSubViews() is for.
Thus my root VS code:
namespace ScrollTest.iOS
{
public partial class RootViewController : UIViewController
{
...
public override void ViewDidLoad()
{
base.ViewDidLoad();
View = new MainView();
}
public override void ViewDidLayoutSubviews()
{
base.ViewDidLayoutSubviews();
CGRect pageFrame = SharedStatic.ScrollView.Frame;
SharedStatic.ScrollView.ContentSize = new CGSize(pageFrame.Width * 2, pageFrame.Height);
SharedStatic.ImageView.Frame = pageFrame;
SharedStatic.ImageView.Image = UIImage.FromFile("toothless.jpg").Scale(pageFrame.Size);
SharedStatic.ImageView.ContentMode = UIViewContentMode.ScaleAspectFit;
pageFrame.X += SharedStatic.ScrollView.Frame.Width;
SharedStatic.TableView.Frame = pageFrame;
SharedStatic.TableView.ContentMode = UIViewContentMode.ScaleAspectFit;
}
...
}
From the MainView() class on downward I have a hierarchy of Views, which are instantiated and then set layout constraints, such as like this:
namespace ScrollTest.iOS
{
[Register("MainView")]
internal class MainView : UIView
{
public MainView()
{
Initialise();
}
public MainView(CGRect frame) : base(frame)
{
Initialise();
}
private void Initialise()
{
SharedStatic.MainView = this;
var navView = new NavView();
Add(navView);
var contentView = new ContentView();
Add(contentView);
var pagerView = new PagerView();
Add(pagerView);
var toolView = new ToolView();
Add(toolView);
var buttonView = new ButtonView();
Add(buttonView);
const int padding = 1;
var navHeight = 32;
var pageControlHeight = 25;
var toolHeight = 25;
var buttonHeight = 20;
this.SubviewsDoNotTranslateAutoresizingMaskIntoConstraints();
this.AddConstraints
(
... other view constraints...
contentView.Below(navView, padding),
contentView.AtLeftOf(this),
contentView.WithSameWidth(this),
contentView.Bottom().EqualTo().TopOf(pagerView),
pagerView.Above(toolView, padding),
pagerView.AtLeftOf(this),
pagerView.WithSameWidth(this),
pagerView.Height().EqualTo(pageControlHeight),
...
);
}
}
}
The actual ScrollView class looks like this, with event handlers just hooked up to debug output for this test.
namespace ScrollTest.iOS
{
[Register("ContentView")]
internal class ContentView : UIScrollView
{
private ContainerView _containerView;
public ContentView()
{
Initialise();
}
public ContentView(CGRect frame) : base(frame)
{
Initialise();
}
private void Initialise()
{
SharedStatic.ScrollView = this;
_containerView = ContainerView.Instance;
Add(_containerView);
PagingEnabled = true;
ScrollEnabled = true;
Bounces = false;
DirectionalLockEnabled = true;
DecelerationEnded += scrollView_DecelerationEnded;
Scrolled += delegate { Console.WriteLine("scrolled"); };
DecelerationStarted += delegate { Console.WriteLine("deceleration started"); };
DidZoom += delegate { Console.WriteLine("did zoon"); };
DraggingEnded += delegate { Console.WriteLine("dragging ended"); };
DraggingStarted += delegate { Console.WriteLine("dragging started"); };
ScrollAnimationEnded += delegate { Console.WriteLine("Scroll animation ended"); };
ScrolledToTop += delegate { Console.WriteLine("Scrolled to top"); };
WillEndDragging += delegate { Console.WriteLine("will end dragging"); };
ZoomingEnded += delegate { Console.WriteLine("zooming ended"); };
ZoomingStarted += delegate { Console.WriteLine("zooming started"); };
this.SubviewsDoNotTranslateAutoresizingMaskIntoConstraints();
this.AddConstraints
(
_containerView.AtLeftOf(this),
_containerView.AtRightOf(this),
_containerView.AtTopOf(this),
_containerView.AtBottomOf(this)
);
}
private void scrollView_DecelerationEnded(object sender, EventArgs e)
{
Console.WriteLine("Done changing page");
nfloat x1 = SharedStatic.ImageView.Frame.X;
nfloat x2 = SharedStatic.TableView.Frame.X;
nfloat x = this.ContentOffset.X;
if (x == x1)
{
Console.WriteLine("flip");
SharedStatic.PageControl.CurrentPage = 0;
}
else
{
Console.WriteLine("flop");
SharedStatic.PageControl.CurrentPage = 1;
}
}
}
}
And "following" it a container view which holds my image and table, which from my understanding of iOS coding practises is the way to do this (apart from the singleton class, which was the result of testing something else).
namespace ScrollTest.iOS
{
[Register("ContainerView")]
internal sealed class ContainerView : UIView
{
private UIImageView _imageView;
private UITableView _tableView;
private static readonly Lazy<ContainerView> lazy = new Lazy<ContainerView>(() => new ContainerView());
public static ContainerView Instance { get { return lazy.Value; } }
private ContainerView()
{
Initialise();
}
private ContainerView(CGRect frame) : base(frame)
{
Initialise();
}
private void Initialise()
{
SharedStatic.ContainerView = this;
Console.WriteLine("setting up scrolling stuff");
_imageView = new UIImageView();
Add(_imageView);
SharedStatic.ImageView = _imageView;
_tableView = new UITableView();
Add(_tableView);
SharedStatic.TableView = _tableView;
this.SubviewsDoNotTranslateAutoresizingMaskIntoConstraints();
this.AddConstraints
(
_imageView.AtLeftOf(this),
_imageView.AtTopOf(this),
_imageView.AtBottomOf(this),
_imageView.WithSameWidth(this),
_tableView.Left().EqualTo().RightOf(_imageView),
_tableView.WithSameTop(_imageView),
_tableView.WithSameBottom(_imageView),
_tableView.WithSameWidth(_imageView)
);
}
}
}
On top of my screen is a navigation bar with a hamburger button which will trigger a segue to another viewcontroller, which down the road is in charge of popping up an Options screen. It holds another hamburger button to dismiss itself and return to the main screen. This works without issues:
_navBar.SetItems(new UINavigationItem[] { new UINavigationItem { LeftBarButtonItem = new UIBarButtonItem(UIImage.FromFile("hamburger32x32.png"), UIBarButtonItemStyle.Plain, BringUpOptions) } }, false);
And the event handler:
internal async void BringUpOptions(object s, EventArgs e)
{
Console.WriteLine("Options clicked");
var board = UIStoryboard.FromName("MainStoryboard", null);
var optionController = board.InstantiateViewController("OptionsViewController");
optionController.ModalTransitionStyle = UIModalTransitionStyle.FlipHorizontal;
await this.Window.RootViewController.PresentViewControllerAsync(optionController, true);
}
I use the storyboard editor only for my two view controllers and the segue. All other controls/views are programmatically generated, as are the layouts.
Putting this all together, results in a correctly laid out user interface, a correctly working segue to the options screen, a correct dismissal/return.
Also working is clicking on the page control: flipping back and forth between the image and the table view.
However, this paging does not work when trying to drag the image view over to the table view! Not at app startup that is! Debugging shows that no events are caught.
Until I go to Options screen at least once and then back! Then everything works as expected: page control flip/flops and scrollview scrolls back and forth.
I'm at a total loss why this would be the case! What does that initial segue initialise that is apparently necessary for the scrollview to work? I've plastered my code with debug output and single stepped to distraction. I just can't find it. Could it be a bug in Xamarin?
Is there some setting or call I need to make from within the view classes that I'm simply not aware of because it's normally hidden when using the storyboard editors? I've been over Xamarin's UIScrollView class doc but nothing jumps out at me.
I thought that my somewhat awkward use of this shared static class could be an issue, but I don't otherwise know how to get layout information across the various classes (therefore sizing of images fails). My intention was to dive into this and remove the kludge once I had resolved this showstopper of mine:
namespace ScrollTest.iOS
{
internal static class SharedStatic
{
internal static MainView MainView { get; set; }
internal static UIPageControl PageControl { get; set; }
internal static ContentView ScrollView { get; set; }
internal static ContentView ContentView => ScrollView;
internal static ContainerView ContainerView { get; set; }
internal static UIImageView ImageView { get; set; }
internal static UITableView TableView { get; set; }
}
}
With the code being so close to working perfectly, I'm really thinking that I'm missing something fairly straight-forward. Some newbie error that one doesn't run into when using storyboards, but which is somehow biting me.
Thanks!
EDIT 20150828: After some further research I've added overrides for SendEvent for the UIApplication:
[Register("TestApp")]
class TestApp : UIApplication
{
public override void SendEvent(UIEvent uievent)
{
base.SendEvent(uievent);
Console.WriteLine("event hit");
}
}
And then changed Main.cs to:
public class Application
{
// This is the main entry point of the application.
static void Main(string[] args)
{
UIApplication.Main(args, "TestApp", "AppDelegate");
}
}
As a result I can indeed see that touches/drags/swipes are causing event hits, but that they aren't identified/categorised properly. Drilling into the debugger, I was able to see that the UIScrollView is receiving the "hits" but is simply doing nothing with them! Essentially it's ignoring them totally - until I've done that very first view controller segue, then all events fire correctly.
This is starting to smack of a bug, but I simply don't know enough to be certain that it's not somewhere in my code or actually in Xamarin.
It's taken me a lot of cursing and screaming but I have been able to resolve the issue, get newly cropped up follow up problems fixed and get the entire target UI design to work. I hope my solution is of help to others.
First, in the end it was absolutely mandatory that I got my head around every single detail of the oft-referred to Apple technotes on UIScrollView and Autolayout.
Apple Technical Note TN2154
It was hard because I don't know Objective-C, but by whittling away at my ignorance through going back and forth with my Xamarin C# code and the Apple sample code I was able to drill down and learn and really understand the interaction between a scrolling view and the auto-layout constraints. My reluctance to dive into this sine qua non may have contributed to the time I needed to solve this issue.
While there's a gazillion links out there showing samples, many use the storyboard, which actually made my life harder. YMMV. As before I decided to take the programmatic route and use the tremendously useful
Cirrious FluentLayout on Github
The core of my solution, in the end, was the use of overriding LayoutSubviews() for my scrollview and the sub-view container class to contain the setting of constraints (apart from some other refactoring to bring everything into a single class file, with use of "partial" for break up the code optically).
For the UIScrollView I use one embedded UIView for the container. The ctor only declares the class, but the layout of that container is dealt with in the LayoutSubviews() method!
[Register("ScrollView")]
internal sealed class ScrollView : UIScrollView
{
internal ContainerView Container { get; }
internal ScrollView()
{
Container = new ContainerView();
Add(Container);
}
public override void LayoutSubviews()
{
base.LayoutSubviews();
this.SubviewsDoNotTranslateAutoresizingMaskIntoConstraints();
this.AddConstraints
(
Container.AtTopOf(this),
Container.AtLeftOf(this),
Container.WithSameWidth(this),
Container.WithSameHeight(this)
);
}
}
I repeated this for the views contained in my 2 scrolling pages:
[Register("ContainerView")]
internal sealed class ContainerView : UIView
{
private UIImageView _arenaView;
internal UIImageView Arena => _arenaView;
private UIView _gridView;
internal UIView Grid => _gridView;
internal ContainerView()
{
_arenaView = new UIImageView();
Add(_arenaView);
_gridView = new UIView()
Add(_gridView);
}
public override void LayoutSubviews()
{
base.LayoutSubviews();
// determine the size of the basic frame in the scrolling area
CGRect pageFrame = SharedStatic.ScrollView.Frame;
// set it's overall size to n times the width, where is number pages, in this case 2
SharedStatic.ScrollView.ContentSize = new CGSize(pageFrame.Width * 2, pageFrame.Height);
_arenaView.Frame = pageFrame;
_arenaView.Image = UIImage.FromFile("someimage.jpg");
// offset by the width to get to second page
pageFrame.X += SharedStatic.ScrollView.Frame.Width;
_gridView.Frame = pageFrame;
this.SubviewsDoNotTranslateAutoresizingMaskIntoConstraints();
this.AddConstraints
(
_arenaView.WithSameTop(this),
_arenaView.WithSameBottom(this),
_arenaView.WithSameHeight(this),
_arenaView.WithSameWidth(this),
_gridView.Left().EqualTo().RightOf(_arenaView),
_gridView.WithSameTop(_arenaView),
_gridView.WithSameBottom(_arenaView),
_gridView.WithSameWidth(_arenaView)
);
}
The reason for this approach as I understand it, is because not until the layout of the various subviews happens have the various frames and sizes been determined. Which means that I have to layout the "dynamic" subviews for my image and data grid themselves within the layout of the superviews for the constraints to properly apply, while at the same time still receiving the eventsl.
My original approach to put it all into the root view controller's ViewDidLayoutSubViews() led to the wrong view receiving all touch input, or worse not properly registering the event delegates at all. To a certain extent this is still a case of "no idea why it works now, but who am I to complain?"
An issue that remains is the constant and repeated calls to LayoutSubviews(). Every (even partial) scroll, touch action, segue will call this method over and over, whether a change in layout is needed or not. I found one helpful tutorial, which I promptly can no longer find, which explained to keep state information and minimise the number of calls in a smart way and I'll experiment with that next.
Information of when LayoutSubviews is called can be found here:
When is the layoutSubviews method called?
and links within that thread.
The horrid bit of tight coupling through the kludgy SharedStatic class will also be replaced, but you may find it useful to use such a construct during the testing and debugging stage.
Hope this helps someone out there!

Update - MvvmCross iOS with storyboards - TableViewCell not being displayed

I am trying to display a table view whose table view cells have a label in it. I followed MvvmCross N+1 days with N=3 to create this sample.
The table is being displayed, but the label is not being displayed
I have a MvxTableViewController
partial class View : MvxTableViewController
{
public View (IntPtr handle) : base (handle)
{
}
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
var source = new MvxSimpleTableViewSource (TableView, typeof(CellView), "CellView");
TableView.Source = source;
var set = this.CreateBindingSet<View,ViewModel> ();
set.Bind (source).To (vm => vm.CellViewModels);
set.Bind (source).For (s => s.SelectionChangedCommand).To (vm => vm.ViewCommand);
set.Apply ();
TableView.ReloadData ();
}
}
and an MvxTableViewCell with a label in it
partial class CellView : MvxTableViewCell
{
public CellView (IntPtr handle) : base (handle)
{
this.DelayBind (() => {
var set = this.CreateBindingSet<CellView, CellViewModel>();
set.Bind(cellLabel).To(vm => vm.TextToDisplay);
set.Apply();
});
}
}
As I said the table view is displayed but the label is not displayed in the cell. I can click on the table view and navigate to another view by binding to a command on my view model.
In the log I am getting the following warning
MvxBind:Warning: 13.35 Failed to create target binding for binding Text for TextToDisplay
I don't understand this, as the binding code looks OK to me!
There is one difference in the code from the N+1 N=3 video in that I am using the following code to construct the view source as I do not have a nib/xib
var source = new MvxSimpleTableViewSource (TableView, typeof(CellView), "CellView");
where "CellView" is the identifier entered in the properties for the prototype cell
Could this be the problem. I should also say I am using Xamarin Studio on a Mac.
Please help I have been banging my head against this for hours.
Update - I have had to abandon the use of Storyboards due to rapidly approaching deadlines. Have reverted to using xibs for views and cells. If anyone solves this problem please post your answer

How to open custom dialog box / popup using Xamarin.Forms?

I am newbie to Xamarin.Forms and stuck with a situation where I want to open up a popup box with my control details [e.g. View Employee Details] on click of parent page.
How can I open custom dialog box / popup using Xamarin.Forms?
Any example code will be appreciated?
Thanks in advance!
If you still want to have your popup's code in its own Page you can set up some custom renderers along the following logic.
1. A ModalPage & corresponding renderer
public class ModalPage : ContentPage { }
public class ModalPageRenderer : PageRenderer {
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
this.View.BackgroundColor = UIColor.Clear;
this.ModalPresentationStyle = UIModalPresentationStyle.OverCurrentContext;
}
public override void ViewDidLayoutSubviews()
{
base.ViewDidLayoutSubviews();
SetElementSize (new Size (View.Bounds.Width, View.Bounds.Height));
}
}
2. HostPage
public class ModalHostPage : ContentPage, IModalHost
{
#region IModalHost implementation
public Task DisplayPageModal(Page page)
{
var displayEvent = DisplayPageModalRequested;
Task completion = null;
if (displayEvent != null)
{
var eventArgs = new DisplayPageModalRequestedEventArgs(page);
displayEvent(this, eventArgs);
completion = eventArgs.DisplayingPageTask;
}
// If there is no task, just create a new completed one
return completion ?? Task.FromResult<object>(null);
}
#endregion
public event EventHandler<DisplayPageModalRequestedEventArgs> DisplayPageModalRequested;
public sealed class DisplayPageModalRequestedEventArgs : EventArgs
{
public Task DisplayingPageTask { get; set;}
public Page PageToDisplay { get; }
public DisplayPageModalRequestedEventArgs(Page modalPage)
{
PageToDisplay = modalPage;
}
}
}
3. HostPage renderer
public class ModalHostPageRenderer: PageRenderer
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
if(e.OldElement as ModalHostPage != null)
{
var hostPage = (ModalHostPage)e.OldElement;
hostPage.DisplayPageModalRequested -= OnDisplayPageModalRequested;
}
if (e.NewElement as ModalHostPage != null)
{
var hostPage = (ModalHostPage)e.NewElement;
hostPage.DisplayPageModalRequested += OnDisplayPageModalRequested;
}
}
void OnDisplayPageModalRequested(object sender, ModalHostPage.DisplayPageModalRequestedEventArgs e)
{
e.PageToDisplay.Parent = this.Element;
var renderer = RendererFactory.GetRenderer (e.PageToDisplay);
e.DisplayingPageTask = this.PresentViewControllerAsync(renderer.ViewController, true);
}
}
Then it is as simple as calling
await ModalHost.DisplayPageModal(new PopUpPage());
from your host page or in this particular case from the ViewModel behind.
What Pete said about PushModalAsync / PopModalAsync still remains valid for this solution too (which in my opinion is not a disadvantage), but your popup would appear with transparent background.
The main advantage of this approach, in my opinion, is that you can have your popup XAML/code definition separate from the host page and reuse it on any other page where you wish to show that popup.
The general purpose of what you are trying to achieve can be accomplished by using the PushModalAsync and PopModalAsync methods of Xamarin.Forms Navigation object.
The chances are that this is good enough for what you are needing - However - this isn't truely modal. I will explain after a small code snippet:-
StackLayout objStackLayout = new StackLayout()
{
};
//
Button cmdButton_LaunchModalPage = new Button();
cmdButton_LaunchModalPage.Text = "Launch Modal Window";
objStackLayout.Children.Add(cmdButton_LaunchModalPage);
//
cmdButton_LaunchModalPage.Clicked += (async (o2, e2) =>
{
ContentPage objModalPage = new ContentPage();
objModalPage.Content = await CreatePageContent_Page2();
//
await Navigation.PushModalAsync(objModalPage);
//
// Code will get executed immediately here before the page is dismissed above.
});
//
return objStackLayout;
private async Task<StackLayout> CreatePageContent_Page2()
{
StackLayout objStackLayout = new StackLayout()
{
};
//
Button cmdButton_CloseModalPage = new Button();
cmdButton_CloseModalPage.Text = "Close";
objStackLayout.Children.Add(cmdButton_CloseModalPage);
//
cmdButton_CloseModalPage.Clicked += ((o2, e2) =>
{
this.Navigation.PopModalAsync();
});
//
return objStackLayout;
}
The problem with the above is that the
await Navigation.PushModalAsync(objModalPage);
will immediately return after the animation.
Although you can't interact with the previous page, as we are displaying a new NavigationPage with a Close button shown - the parent Navigation Page is still executing behind the scenes in parallel.
So if you had any timers or anything executing these still would get called unless you stopped those.
You could also use the TaskCompletionSource approach as outlined in the following post also How can I await modal form dismissal using Xamarin.Forms?.
Note - that although you can now await the 2nd page displaying and then when that page is dismissed allowing code execution to continue on the next line - this is still not truely a modal form. Again timers or anything executing still will get called on the parent page.
Update 1:-
To have the content appear over the top of existing content then simply include it on the current page, however make this section of content invisible until you need it.
If you use an outer container such like a Grid that supports multiple child controls in the same cell, then you will be able to achieve what you want.
You will also want to use something like a filled Box with transparency that will cover the entire page also, to control the visible, see through section, that surrounds your inner content section.
I followed above approach and found it impossible to run on iOS 7.
I found this library BTProgressHUD which you can modify and use.
I Use its methods by Dependency service.
Actual library for popups.
https://github.com/nicwise/BTProgressHUD
Following example uses BTProgressHUD library internally.
https://github.com/xximjasonxx/ScorePredictForms

Resources