Custom iOS UIView Class MvvmCross Constraints - Draw(CGRect rect) Method Never Called - ios

I have a situation that I cannot seem to work out Where the specific problem lies and I'm hoping for a little input.
I have a very simple view that at the moment just has a UIScrollView. In the ViewDidLoad method I register to listen for the view models PropertyChanged event like this:
public override void ViewDidLoad()
{
base.ViewDidLoad();
ViewModel.PropertyChanged += ViewModel_PropertyChanged;
}
private void ViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == "MyCollection")
{
BuildUi();
}
}
When that event is fired it kicks off building the view since the data is now available within the view model. In the BuildUi method I have this code:
View Code
private void BuildUi()
{
_mainScrollView = new UIScrollView(View.Frame)
{
ShowsHorizontalScrollIndicator = false,
AutoresizingMask = UIViewAutoresizing.All
};
Add(_mainScrollView);
View.SubviewsDoNotTranslateAutoresizingMaskIntoConstraints();
View.AddConstraints(
_mainScrollView.AtLeftOf(View),
_mainScrollView.AtTopOf(View),
_mainScrollView.WithSameWidth(View),
_mainScrollView.WithSameHeight(View));
foreach(var ctx in ViewModel.MyCollection)
{
var contextScoreControl = new ContextScoreView(new CGRect(100,100,100,100), ctx);
_mainScrollView.Add(contextScoreControl);
//var btn = ViewHelpers.CreateDefaultButton("test");
//_mainScrollView.Add(btn);
}
_mainScrollView.SubviewsDoNotTranslateAutoresizingMaskIntoConstraints();
var constraints = _mainScrollView.VerticalStackPanelConstraints(new Margins(20, 10, 20, 10, 5, 5), _mainScrollView.Subviews);
_mainScrollView.AddConstraints(constraints);
}
Control Code
[Register("MyControl")]
public class MyControl : UIView
{
private MyViewModel _vm;
private UILabel _ctxTitle;
private UITextView _explanationText;
private UISlider _scoreSlider;
public MyControl(MyViewModel vm)
{
_vm = vm;
BackgroundColor = UIColor.Cyan;
}
public MyControl(IntPtr handle) : base(handle) { }
public MyControl(CGRect rect, MyViewModel vm)
{
_vm = vm;
BackgroundColor = UIColor.Cyan;
}
public MyControl(CGRect rect) : base(rect)
{
BackgroundColor = UIColor.Cyan;
}
public override void Draw(CGRect rect)
{
base.Draw(rect);
_ctxTitle = ViewHelpers.CreateTitleLabel();
_ctxTitle.Text = _vm.Text;
_explanationText = ViewHelpers.CreateAutoExpandingReadOnlyTextView();
_explanationText.Text = _vm.ScoringHint;
_scoreSlider = ViewHelpers.CreateDefaultSlider();
_scoreSlider.Value = _vm.Score;
AddSubview(_ctxTitle);
AddSubview(_explanationText);
AddSubview(_scoreSlider);
this.AddConstraints(
_ctxTitle.AtTopOf(this, IosConstants.UIPadding),
_ctxTitle.AtLeftOf(this, IosConstants.UIPadding),
_ctxTitle.WithSameWidth(this).Minus(IosConstants.UIPadding * 2),
_explanationText.Below(_ctxTitle, IosConstants.UIPadding),
_explanationText.WithSameLeft(_ctxTitle),
_explanationText.WithSameRight(_ctxTitle),
_scoreSlider.Below(_explanationText, IosConstants.UIPadding),
_scoreSlider.WithSameLeft(_explanationText),
_scoreSlider.WithSameRight(_explanationText),
_scoreSlider.AtBottomOf(this)
);
}
}
The issue I'm seeing right now is that the Draw method is never called which means nothing appears in the view.
I've tried lots and lots of permutations of the constructors you can see in the control code. Using CGRect.Empty and as is in the code a manually generated CGRect but no matter what I try I cannot seem to get this flow of code to work.
As you can see from the code if I swap out my custom control for a simple UIButton everything works as expected so the main view code where the UIScrollView is added with constraints is working and laying itself out as expected and desired. The problem seem to lie either in the control code itself or the hierarchy of the constraints themselves. I just cannot figure out what needs to happen to make this work.
Under certain circumstances I was seeing the following error:
Unable to simultaneously satisfy constraints.
I've used this "pattern" of code many times in other views within the app and it works in those situations which is why I'm slightly confused about what is going on in this situation. The difference is the looping over a dynamic number of controls in this situation.
Without changing the current structure of the view (to say using a UITableView instead) how can I get this playing nicely and render the custom UIView controls?

Related

Xamarin.iOS Popovers on Rotate

I've seen lots and lots of posts about handling rotation for iOS popovers, but the majority of them target < iOS8 and/or Swift/ObjC. How can I do this with UIPopoverPresentationController and Xamarin.iOS? I need to keep it in the center of the screen.
public override void ViewDidLoad()
{
...
_menuController.PopoverPresentationController.PermittedArrowDirections = 0;
_menuController.PopoverPresentationController.WillReposition += OnWillReposition;
}
private void OnMenuSelected(object sender, EventArgs eventArgs)
{
InitializePopover();
PresentViewController(_menuController, true, null);
}
private void OnWillReposition(object sender, UIPopoverPresentationControllerRepositionEventArgs args)
{
InitializePopover();
}
private void InitializePopover()
{
_menuController.PopoverPresentationController.SourceRect = GetCenterRect();
_menuController.PopoverPresentationController.SourceView = View;
}
private CGRect GetCenterRect()
{
var midX = View.Bounds.GetMidX();
var midY = View.Bounds.GetMidY();
return new CGRect(midX, midY, 0, 0);
}
OnWillReposition is called on rotation, but setting the SourceRect and SourceView doesn't appear to take effect. When I rotate again, I can see during debug that those properties have the old values.
Popular links I've seen so far:
seems like it should work but doesn't (am I listening to the
wrong event?)
got the initial display working! but doesn't recenter
potential solution but what would I constrain?
Other thoughts:
Can I reposition in ViewDidLayoutSubviews? How?
Using a UIBarButtonItem isn't really an option, but maybe an invisible one?
Should I dismiss and present again? Seems hacky and I was getting exceptions...
Should I give up and simply set another view over everything else?
I figured it out just before posting. My problem was that I wasn't setting PermittedArrowDirections each time. Vent: Who thought that was a good idea? Anyway, after making that part of initialization, I simply call that in ViewDidLayoutSubviews, as below:
public override void ViewDidLayoutSubviews()
{
base.ViewDidLayoutSubviews();
InitializePopover();
}
private void InitializePopover()
{
_menuController.PopoverPresentationController.PermittedArrowDirections = 0;
_menuController.PopoverPresentationController.SourceRect = GetCenterRect();
_menuController.PopoverPresentationController.SourceView = View;
}
So I don't use the UIPopoverPresentationController.WillReposition event at all. I hope this helps somebody -- I wouldn't want my worst enemy to go through all that frustration.

How to make my RefreshControl appear during ViewDidAppear

I've answered this question myself - just leaving it here now for people with the same issue.
My code is fairly simple - A custom table with a datasource to provide data asynchronously from a web service.
In order to get the nicest user experience I would like to have the UIRefreshControl animate the process of loading whenever this controller appears, instead of just when the list has been pulled down.
Unfortunately the UIRefreshControl does not appear at all if I call my method during ViewDidAppear.
I've tried suggestions from answers these questions, but none of them seemed to work for me:
public partial class ControlCenterSelectionController : UITableViewController
{
public ControlCenterSelectionController (IntPtr handle) : base (handle)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
TableView.RefreshControl = new UIRefreshControl();
TableView.RefreshControl.ValueChanged += RefreshControlOnValueChanged;
}
private async void RefreshControlOnValueChanged(object sender, EventArgs eventArgs)
{
await UpdateDataSourceAsync();
}
public override async void ViewDidAppear(bool animated)
{
base.ViewDidAppear(animated);
TableView.SetContentOffset(new CGPoint(0, -TableView.RefreshControl.Frame.Size.Height), true);
await UpdateDataSourceAsync();
}
private async Task UpdateDataSourceAsync()
{
TableView.RefreshControl.BeginRefreshing();
var ccClient = DI.Instance.Get<IControlCenterRestClient>();
var controlCenters = await ccClient.GetAllAsync();
var source = new GenericViewSource<ControlCenter>(controlCenters, item => item.Name, ControlCenterSelected);
TableView.Source = source;
TableView.RefreshControl.EndRefreshing();
}
private void ControlCenterSelected(ControlCenter controlCenter)
{
var controller = this.InstantiateController(Constants.ViewControllerIds.ControlCenter) as ControlCenterController;
controller.ControlCenter = controlCenter;
this.NavigateTo(controller);
}
}
UITableView UIRefreshControl Does Not Show Its View The First Time
UIRefreshControl on viewDidLoad
UIRefreshControl - beginRefreshing not working when UITableViewController is inside UINavigationController
All I currently get is a screen which looks empty, loads data without indication and then updates the screen once it's done.
Can someone spot an error? I can't really find one, since this code is rather simple.
The desperation of posting it on SO + desperate coding attempts were key:
public override async void ViewDidAppear(bool animated)
{
base.ViewDidAppear(animated);
TableView.SetContentOffset(new CGPoint(0, -(TableView.RefreshControl.Frame.Size.Height + 20)), true);
TableView.RefreshControl.BeginRefreshing();
await UpdateDataSourceAsync();
}
private async Task UpdateDataSourceAsync()
{
var ccClient = DI.Instance.Get<IControlCenterRestClient>();
var controlCenters = await ccClient.GetAllAsync();
var source = new GenericViewSource<ControlCenter>(controlCenters, item => item.Name, ControlCenterSelected);
TableView.Source = source;
TableView.RefreshControl.EndRefreshing();
}
This change in particular made the difference:
-(TableView.RefreshControl.Frame.Size.Height + 20)
Turns out that, while in the other questions the issue was fixed by simply applying a scroll based on RefreshControl would fire the animation, in my case I had to add some extra y delta to make the animation fire.
Correction
this extra offset seems to be required only if extended edges is set to extend under top bars.

Why do I get a "Must translate autoresizing mask into constraints to have _setHostsLayoutEngine:YES" error in Xamarin iOS?

I created a TableView with a TableViewCell and always have the View opening. I have this error:
Must translate autoresizing mask into constraints to have _setHostsLayoutEngine:YES
I'm using XIB file to design my TableViewCell and the TableView.
This is my TableView:
public partial class FavoriteProjectsView : MvxTableViewController<FavoriteProjectsViewModel>
{
public FavoriteProjectsView() : base("FavoriteProjectsView", null)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
// ios7 layout
if (RespondsToSelector(new Selector("edgesForExtendedLayout")))
{
EdgesForExtendedLayout = UIRectEdge.None;
}
var source = new MvxSimpleTableViewSource(TableView, FavoriteProjectsItem.Key, FavoriteProjectsItem.Key);
TableView.Source = source;
this.CreateBinding(source).To<FavoriteProjectsViewModel>(viewModel => viewModel.Projetos).Apply();
this.CreateBinding(BtnSave).To<FavoriteProjectsViewModel>(viewModel => viewModel.SaveCommand).Apply();
var bounds = UIScreen.MainScreen.Bounds;
var carregamento = new CarregamentoIOS(bounds);
ViewModel.Carregamento = carregamento;
ViewModel.PreenchePagina();
}
}
This is my TableViewCell:
public partial class FavoriteProjectsItem : MvxTableViewCell
{
public static readonly NSString Key = new NSString("FavoriteProjectsItem");
public static readonly UINib Nib = UINib.FromName("FavoriteProjectsItem", NSBundle.MainBundle);
protected FavoriteProjectsItem(IntPtr handle) : base(handle)
{
this.DelayBind(() =>
{
this.CreateBinding(LblName).To<Project>(project => project.Name).Apply();
this.CreateBinding(SwitchFavorite).To<Project>(project => project.IsFavorite).Apply();
});
}
public static FavoriteProjectsItem Create()
{
return (FavoriteProjectsItem)Nib.Instantiate(null, null)[0];
}
}
It seems you are using MvvmCross or some other Mvvm library?
It's only a guess at this point but it would seem that something, somewhere is setting a property _setHostsLayoutEngine:YES but that this setting requires Auto Layout constraints, which may have been disabled with a call on the view to view.TranslatesAutoresizingMaskIntoConstraints = false.
The old system of laying out views in iOS was to use something called AutoResizing. The AutoResizing flags can either be set in code or in the XIB/Storyboard. If you are using AutoLayout, the new and improved layout system that uses constraints and offers much more flexibility (and complexity), for the XIB/Storyboard then any AutoResizing flags should be converted automatically into constraints that can be used with the AutoLayout system. This is the default behavior, but can be disabled with the call noted above, view.TranslatesAutoresizingMaskIntoConstraints = false.
For more information about using AutoLayout with Xamarin iOS Designer, please see:
https://developer.xamarin.com/guides/ios/user_interface/designer/designer_auto_layout/

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!

MVVMCross iOS: how to bind a command with Custom transition when switching to different View model

For MVVMCross ios, how can I use different TransitionalStyle such as FlipHorizontal style instead of the default sliding effect with "ShowViewModel"?
[Register("SearchResults")]
public class SearchResultsView : MvxTableViewController
{
public override void ViewDidLoad()
{
Title = "List";
base.ViewDidLoad();
var mapButton = new UIButton(new RectangleF(0, 0, 65, 30));
mapButton.SetBackgroundImage(UIImage.FromBundle("images/map_btn.png"), UIControlState.Normal);
mapButton.TouchUpInside += MapButtonClicked();
var rightButton = new UIBarButtonItem(mapButton);
NavigationItem.RightBarButtonItem = rightButton;
var bindings = this.CreateBindingSet<SearchResultsView, SearchResultsViewModel>();
//bindings.Bind(mapButton).To(x => x.ShowMapCommand); //how to do with binding command?
bindings.Apply();
}
private EventHandler MapButtonClicked()
{
return (sender, args) =>
{
var mapView = new SearchResultMapView {ModalTransitionStyle = UIModalTransitionStyle.FlipHorizontal};
var navigationController = new UINavigationController(mapView);
PresentViewController(navigationController, true, null);
};
}
}
ViewModels/Views are presented using a Presenter.
For Modal displays of navigation controllers, some people use the MvxModalNavSupportTouchViewPresenter.cs
You can use this presenter by overriding CreatePresenter in Setup:
protected override IMvxTouchViewPresenter CreatePresenter()
{
return new MvxModalNavSupportTouchViewPresenter(ApplicationDelegate, Window);
}
With this done then you should be able to achieve your transition effect by adding IMvxModalTouchView inheritance and by setting ModalTransitionStyle = UIModalTransitionStyle.FlipHorizontal; in the constructor for your SearchResultMapView.
public class SearchResultMapView
: MvxViewController, IMvxModalTouchView
{
public SearchResultMapView()
{
ModalTransitionStyle = UIModalTransitionStyle.FlipHorizontal;
}
// more code here....
}
Alternatively, if you wanted to completely customise the presentation of the view/viewcontroller, then implementing a custom presenter is fairly straight-forward to do - for more information, see some of the articles and tutorials on custom presenters in http://slodge.blogspot.co.uk/2013/06/presenter-roundup.html
#wing
All you need to do is add a close button to the new view of the pushed view controller and override it's touchupinside as follows:
private void CloseBtnTouchUpInside(object sender, EventArgs eventArgs)
{
DismissModalViewController(false);
}

Resources