DelayBind not binding MvxCommands within MvxTableViewCell on iOS - 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).

Related

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

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?

VaadinSession attribute and updating session-bound components

I have a Vaadin Navigator with multiple View elements. Each view has a different purpose however some also contain common traits that I have put inside custom components.
One of those custom components is the menu - it is positioned at the top and allows navigation between the different views. I create and add this component inside the constructor of each view (if you are interested in the menu's implementation see the end of this post). Here is a skeleton for each custom view:
class MyViewX implements View {
MenuViewComponent mvc;
public MyViewX() {
mvc = new MenuViewComponent();
addComponent(mvc);
}
#Override
public void enter(ViewChangeEvent event) {
}
}
So far, so good. In order to make things simple I will explain my problem using a simple label and not one of my other custom components but the dependency that I will describe here is the same for those components just like with the label.
Let's say I have a label which sole purpose is to display a greeting with the user's username. In order to do that I use VaadinSession where I store the attribute. This is done by my LoginController, which validates the user by looking into a database and if the user is present, the attribute is set and one of the views is opened automatically. The problem is that VaadinSession.getCurrent().getAttribute("username") returns null when called inside the constructor. This of course makes sense omho because a constructor should not be bound by a session-attribute.
So far I have managed to use the enter() method where there is no problem in retrieving session attributes:
class MyViewX implements View {
MenuViewComponent mvc;
public MyViewX() {
mvc = new MenuViewComponent();
addComponent(mvc);
}
#Override
public void enter(ViewChangeEvent event) {
String username = (String)VaadinSession.getCurrent().getAttribute("username");
Label greeting = new Label("Hello " + username);
addComponent(greeting);
}
}
The issue that comes from this is obvious - whenever I open the view where this label is present, a new label is added so if I re-visit the view 10 times, I will get 10 labels. Even if I move the label to be a class member variable the addComponent(...) is the one that screws things up. Some of my custom components really depend on the username attribute (in order to display user-specific content) hence I also have to place those in the enter(...) method. The addComponent(...) makes a mess out of it. I even tried the dirty way of removing a component and then re-adding it alas! in vain:
class MyViewX implements View {
MenuViewComponent mvc;
Label greeting;
public MyViewX() {
mvc = new MenuViewComponent();
addComponent(mvc);
}
#Override
public void enter(ViewChangeEvent event) {
String username = (String)VaadinSession.getCurrent().getAttribute("username");
greeting = new Label("Hello " + username);
// Remove if present
try { removeComponent(greeting); }
catch(Exception ex) { }
// Add again but with new content
addComponent(greeting);
}
}
but it's still not working. So my question is: what is the simplest way of updating a component that requires session-bound attributes?
The navigation via the menu custom component is omho not the issue here since all components of the menu are loaded in it's constructor. That's why it's also load that component in particular in a view's own constructor. Here is an example of a button in my menu that opens a view:
#SuppressWarnings("serial")
#PreserveOnRefresh
public class MenuViewComponent extends CustomComponent {
public MenuViewComponent(boolean adminMode) {
HorizontalLayout layout = new HorizontalLayout();
Label title = new Label("<h2><b>Vaadin Research Project</b></h2>");
title.setContentMode(ContentMode.HTML);
layout.addComponent(title);
layout.setComponentAlignment(title, Alignment.TOP_LEFT);
Button personalDashboardButton = new Button("Personal dashboard", new Button.ClickListener() {
#Override
public void buttonClick(ClickEvent event) {
getUI().getNavigator().navigateTo(MainController.PERSONALDASHBOARDVIEW);
}
});
personalDashboardButton.setStyleName(BaseTheme.BUTTON_LINK);
layout.addComponent(personalDashboardButton);
layout.setComponentAlignment(personalDashboardButton, Alignment.TOP_CENTER);
// Add other buttons for other views
layout.setSizeUndefined();
layout.setSpacing(true);
setSizeUndefined();
setCompositionRoot(layout);
}
}
PERSONALDASHBOARDVIEW is just one of the many views I have.
It may be worth considering how long should your view instances "live", just as long they're displayed, until the session ends or a mix of the two. With this in mind and depending on what needs to happen when you enter/re-enter a view, you have at least the following 3 options:
1) Recreate the whole view (allowing for early view garbage-collection)
first register a ClassBasedViewProvider (instead of a StaticViewProvider) which does not hold references to the created views:
navigator = new Navigator(this, viewDisplay);
navigator.addProvider(new Navigator.ClassBasedViewProvider(MyView.NAME, MyView.class));
simple view implementation
public class MyView extends VerticalLayout implements View {
public static final String NAME = "myViewName";
#Override
public void enter(ViewChangeListener.ViewChangeEvent event) {
// initialize tables, charts and all the other cool stuff
addComponent(new SweetComponentWithLotsOfStuff());
}
}
2) Keep some already created components and replace others
public class MyView extends VerticalLayout implements View {
private MySweetComponentWithLotsOfStuff mySweetComponentWithLotsOfStuff;
public MyView() {
// initialize only critical stuff here or things that don't change on enter
addComponent(new MyNavigationBar());
}
#Override
public void enter(ViewChangeListener.ViewChangeEvent event) {
// oh, so the user does indeed want to see stuff. great, let's do some cleanup first
removeComponent(mySweetComponentWithLotsOfStuff);
// initialize tables, charts and all the other cool stuff
mySweetComponentWithLotsOfStuff = new SweetComponentWithLotsOfStuff();
// show it
addComponent(mySweetComponentWithLotsOfStuff);
}
}
3) Lazy creating and updating (or not) the content when entering
public class MyView extends VerticalLayout implements View {
private boolean isFirstDisplay = true;
private MySweetComponentWithLotsOfStuff mySweetComponentWithLotsOfStuff;
public MyView() {
// initialize only critical stuff here, as the user may not even see this view
}
#Override
public void enter(ViewChangeListener.ViewChangeEvent event) {
// oh, so the user does indeed want to see stuff
if (isFirstDisplay) {
isFirstDisplay = false;
// lazily initialize tables, charts and all the other cool stuff
mySweetComponentWithLotsOfStuff = new SweetComponentWithLotsOfStuff();
addComponent(mySweetComponentWithLotsOfStuff);
} else {
// maybe trigger component updates, or simply don't do anything
mySweetComponentWithLotsOfStuff.updateWhateverIsRequired();
}
}
}
I'm sure (and curious) that there may be other options, but I've mainly used a variation of 1) using spring with prototype views and component tabs.

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!

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

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();
});
}

Is there a way to set the BackgroundColor for a RadioGroup?

I am using MonoTouch.Dialog to create a settings-like page. The linq below creates a set of RootElements, each with one section that has a set of RadioEventElements (a subclass of RadioElement that I created in order to add an OnSelected event).
// initialize other phone settings by creating a radio element list for each phone setting
var elements = (from ps in PhoneSettings.Settings.Keys select (Element) new RootElement(ps, new RadioGroup(null, 0))).ToList();
// loop through the root elements we just created and create their substructure
foreach (RootElement rootElement in elements)
{
rootElement.Add(new Section()
{
(from val in PhoneSettings.Settings[rootElement.Caption].Values select (Element) new RadioEventElement(val.Name)).ToList()
});
// ...
}
One of the settings I implement is a "Theme" - which currently is simply a background color for the various screens in the app. I can style every one of the pages correctly by setting the TableView.BackgroundColor property to the desired color... Except for new DialogViewControllers that are automatically created and pushed by the parent DialogViewController when it navigates into a radio group.
Is there any way to style (or at least set the background color) of this child DialogViewController?
I need to use the assembly browser more before asking easy questions :-)
Fortunately the RootElement has a virtual method called PrepareDialogViewController for what appears to be exactly this purpose. All I had to do is create a simple subclass of RootElement and override this method to get my desired behavior.
public class ThemedRootElement : RootElement
{
public ThemedRootElement(string caption) : base (caption)
{
}
public ThemedRootElement(string caption, Func<RootElement, UIViewController> createOnSelected) : base (caption, createOnSelected)
{
}
public ThemedRootElement(string caption, int section, int element) : base (caption, section, element)
{
}
public ThemedRootElement(string caption, Group group) : base (caption, group)
{
}
protected override void PrepareDialogViewController(UIViewController dvc)
{
dvc.View.BackgroundColor = UIColorHelper.FromString(App.ViewModel.Theme.PageBackground);
base.PrepareDialogViewController(dvc);
}
}
Hopefully this helps save someone out there a litte time...
In order to get this to work, I had to override the MakeViewController method and cast the UIViewController that it normally returns to a UITableViewController, then make my edits.
protected override UIViewController MakeViewController()
{
var vc = (UITableViewController) base.MakeViewController();
vc.TableView.BackgroundView = null;
vc.View.BackgroundColor = UIColor.Red; //or whatever color you like
return vc;
}

Resources