Embedded Xamarin.Forms View empty on IOS - ios

I have an Xamarin Application with a Android and an IOS version. Since Xamarin Forms now Supports the embedding of Form Pages into native pages I started to build a shared UI. Now I have a Page who shows some informations and intregrated it as fragment on Android which works without issues.
When I try to add it as a Subview on IOS though, it doesn't have any content. I created a new ViewController which inherits from MvxViewController and override the ViewDidLoad and ViewDidLayoutSubviews.
UIViewController earlyLabelViewController;
public override void ViewDidLoad()
{
base.ViewDidLoad();
earlyLabelViewController = new Business.Views.EarlyLabelView { BindingContext = ViewModel }.CreateViewController();
View.AddSubview(earlyLabelViewController.View);
}
public override void ViewDidLayoutSubviews()
{
earlyLabelViewController.View.Frame = View.Bounds;
}
When I add a new Label that works. Also if I just display the view controller with the following code it has the content as well.
PresentViewController(earlyLabelViewController, true, null);
What am I doing wrong that this doesn't show anything?

I found the solution. I replace the code in the ViewDidAppear Method with the following:
earlyLabelViewController = new Business.Views.EarlyLabelView { BindingContext = ViewModel }.CreateViewController();
earlyLabelViewController.WillMoveToParentViewController(this);
View.Add(earlyLabelViewController.View);
AddChildViewController(earlyLabelViewController);
earlyLabelViewController.DidMoveToParentViewController(this);

Related

Run method when Change Tab Controller tab (iOS)

I have xamarin ios app, with Tab Bar.
When I run main tab, I call methods
Here is code of main tab
public partial class ExperienceTableViewController : UIViewController
{
public ExperienceTableViewController (IntPtr handle) : base (handle)
{
}
public override async void ViewDidLoad()
{
base.ViewDidLoad();
List<GettingExperiences.Experience> tableItems = new List<GettingExperiences.Experience>();
tableItems.Clear();
tableItems = await ExperienceMethods.GetExperiencesAsync();
ExperienceTableView.Source = new ExperienceSource(tableItems, this, this.NavigationController);
ExperienceTableView.RowHeight = UITableView.AutomaticDimension;
ExperienceTableView.EstimatedRowHeight = 80f;
ExperienceTableView.ReloadData();
}
}
When I change filters in another tab, I need to get back and rerun this method - tableItems = await ExperienceMethods.GetExperiencesAsync();
I make redirect like this
this.TabBarController.SelectedIndex = 0; from another tab, but my method not reruns.
How I can solve this problem?
I found solution for this problem
just run method in ViewDidAppear() or using events
ViewDidLoad is only run once, First time view is loaded. After next time ViewWillAppear(), ViewDidAppear() will be executed. As View is already there in tab bar controller.
So If your ViewController is part of Tab bar you should put your always executing code in ViewWillAppear() and ViewDidAppear(). And code which only is needed to be run once should e placed in ViewDidLoad().

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

MvvmCross iOS: how to dismiss a view controller when use IMvxModelTouchView

I have used IMvXModelTouchView for a custom popup screen animation. And, I have a close button on this popup view. What is the proper way to switch back to a previous view?
Here is my code look like:
public class PopupView
: MvxViewController, IMvxModalTouchView
{
public PopupView()
{
ModalPresentationStyle = UIModalPresentationStyle.PageSheet;
}
public override void ViewDidLoad()
{
Title = "Map";
base.ViewDidLoad();
var closeButton = new UIButton(new RectangleF(0, 0, 50, 30));
closeButton.TouchUpInside += CloseButtonClicked();
Add(closeButton);
}
private EventHandler CloseButtonClicked()
{
return (sender, args) => NavigationController.DismissViewController(true, null);
}
}
It worked, the first time when I click this close button, but it crash when I tried to pop up this view again.
I suspect you are probably using the standard MvxModalSupportTouchViewPresenter which only expects one modal view/viewModel to be displayed at a time, and which expects that view/viewModel to be cleared using Close(this) from the ViewModel.
See: MvxModalSupportTouchViewPresenter.cs#L29
If you have strong ideas about your UI, then (in my opinion) your best bet is to write your own custom presenter - then you can open/close/hide/show whatever you want. For more on writing custom presenters, see some of the links and videos from: http://slodge.blogspot.com/2013/06/presenter-roundup.html

MonoTouch Instantiating a ViewController programmatically for ContainerView

I am trying to work with a container view in MonoTouch and I am following some tutorials online. They talk about adding and removing view controller programmatically from the container. I created a viewcontroller and view in the storyboard of my project and attached a few outlets and one action (for labels and buttons respectively). I created an overloaded construc
Here is the code in the view controller that I am trying to add viewControllers into the container view.
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
ContainerView.AutoresizingMask = UIViewAutoresizing.FlexibleHeight;
_controllerOne = new IngredientsController("Perishables");
_controllerTwo = new IngredientsController("Spices");
AddChildViewController(_controllerOne);
ContainerView.AddSubview(_controllerOne.View);
_controllerOne.DidMoveToParentViewController(this)
}
When I add the subview for _controllerOne I get an error because the elements on my controller are marked null. Is MonoTouch incapable of having view controllers being programmatically created if the controller was made in Interface Builder? Below are the two constructors for the Ingredient Controller. When the segue is used then all of the UI controls are initialized properly. Do I need to create the controller programmatically and then instantiate it that way? Any help would be appreciated.
//This ctor does not work
public IngredientsController (string title) : base(NSObjectFlag.Empty)
{
_ingredientTitle = title;
}
//This ctor works
public IngredientsController (IntPtr handle) : base (handle)
{
}
Try to swap the AddSubView() and DidMoveToParentViewController() methods like below:
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
ContainerView.AutoresizingMask = UIViewAutoresizing.FlexibleHeight;
_controllerOne = new IngredientsController("Perishables");
_controllerTwo = new IngredientsController("Spices");
this.AddChildViewController(_controllerOne); // Root child controller.
_controllerOne.DidMoveToParentViewController(this); // Confirm the rooting.
ContainerView.AddSubview(_controllerOne.View); // Access the view.
}
Try instantiating the view controller like this:
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
this.ContainerView.AutoresizingMask = UIViewAutoresizing.FlexibleHeight;
var newController = this.Storyboard.InstantiateViewController("IngredientsController");
this.AddChildViewController (newController);
this.ContainerView.AddSubview (mapController.View);
}
Make sure you set the Storyboard Id in the properties panel for the ViewController

Resources