I am attempting to programmatically deselect a UISegmentedControl from within Xamarin whenever the user re-taps the selected segment. I have created an inherited class that will determine if this is required, but whenever I try to set SelectedSegment to -1, the application bombs out, throwing an out of bounds exception.
public class UIDeselectableSegmentedControl : UISegmentedControl
{
private int previouslySelectedIndex = -1;
private bool isIOS7
{
get
{
return UIDevice.CurrentDevice.CheckSystemVersion(7, 0);
}
}
public UIDeselectableSegmentedControl()
{
this.ValueChanged += UIDeselectableSegmentedControl_ValueChanged;
}
void UIDeselectableSegmentedControl_ValueChanged(object sender, EventArgs e)
{
if (previouslySelectedIndex == this.SelectedSegment)
{
this.SelectedSegment = -1;
}
previouslySelectedIndex = this.SelectedSegment;
}
public override void TouchesBegan(NSSet touches, UIEvent evt)
{
int initialIndex = this.SelectedSegment;
base.TouchesBegan(touches, evt);
//Pre-iOS7 Segment Changes are present in Touches Began
if (!isIOS7) CheckSelectedSegment(initialIndex, this.SelectedSegment);
}
public override void TouchesEnded(NSSet touches, UIEvent evt)
{
int initialIndex = this.SelectedSegment;
base.TouchesEnded(touches, evt);
//Post-iOS7 Segment Changes are present in Touches Ended
if (isIOS7) CheckSelectedSegment(initialIndex, this.SelectedSegment);
}
/// <summary>
/// Compare the SelectedSegment with the Current Index value. If it is the same, raise the Value Changed event.
/// </summary>
/// <param name="index">Currently Selected Segment</param>
private void CheckSelectedSegment(int oldIndex, int newIndex)
{
if (oldIndex == newIndex)
{
this.SendActionForControlEvents(UIControlEvent.ValueChanged);
}
}
}
If I change it to this.SelectedSegment = 0 then the control will successfully force the SegementedControl to the first item whenever you re-tap another selected item, but if I have it to set it to -1, then it throws the following exception:
* Terminating app due to uncaught exception 'NSRangeException', reason: '* -[__NSArrayM objectAtIndex:]: index 4294967295 beyond bounds [0 .. 1]'
Is there a different way I should be deselecting the segment? According to this answer this is the correct way in Objective-C, and the Metadata comment on SelectedSegment indicates this is it correct:
//
// Summary:
// The index of the selected segment.
//
// Remarks:
// Set to -1 to turn off the currently selected segment. If MonoTouch.UIKit.UISegmentedControl.Momentary
// == true the SelectedSegment property is ignored.
In typical form, I think I've solved my own issue. I'm not sure why (perhaps someone can clarify this), but it seems to be something to do with trying to change the SelectedSegment within the ValueChanged event. Possibly some sort of mutation exception essentially.
I've reworked my code, so that the method called within TouchesEnded/Began just makes the change itself directly, rather than forcing ValueChanged to be called and changing it itself. In a way, that makes more sense, since ValueChanged is reacting to a change - it probably shouldn't be responsible for making the change:
public class UIDeselectableSegmentedControl : UISegmentedControl
{
private bool isIOS7
{
get
{
return UIDevice.CurrentDevice.CheckSystemVersion(7, 0);
}
}
public override void TouchesBegan(NSSet touches, UIEvent evt)
{
int initialIndex = this.SelectedSegment;
base.TouchesBegan(touches, evt);
//Pre-iOS7 Segment Changes are present in Touches Began
if (!isIOS7) DeselectSegmentIfApplicable(initialIndex, this.SelectedSegment);
}
public override void TouchesEnded(NSSet touches, UIEvent evt)
{
int initialIndex = this.SelectedSegment;
base.TouchesEnded(touches, evt);
//Post-iOS7 Segment Changes are present in Touches Ended
if (isIOS7) DeselectSegmentIfApplicable(initialIndex, this.SelectedSegment);
}
/// <summary>
/// Compare the SelectedSegment with the Current Index value. If it is the same, raise the Deselect the Segment.
/// </summary>
/// <param name="oldIndex">the previously selected segment</param>
/// <param name="newIndex">The newly selected segment</param>
private void DeselectSegmentIfApplicable(int oldIndex, int newIndex)
{
if (oldIndex == newIndex)
{
this.SelectedSegment = -1;
}
}
}
Related
I would like to know the scroll position of ViewCell inside the ListView.
Tried with various ways but always that gives me 0 value.
My intension is to get ViewCell's position in screen. In order to resolve this problem trying to get it's scroll position and then i will add this value to the Y value of ListView object.
Can anybody please help me in this case?
you have to make custom renderer of the ViewCell its kinda tricky to send the positions to pcl then we subscribe to the event in the view here's my code
PCL
public class SAChatViewCell : ViewCell
{
public delegate int[] IntEventHandler(object sender, float[] postion);
public event IntEventHandler OnCellItemLongClicked;
public event EventHandler OnCellItemTouched;
public void InvokeOnCellItemLongClicked(object sender, float[] e)
{
//send the current grid
OnCellItemLongClicked?.Invoke(sender, e);
}
public void InvokeOnCellItemTouched(object sender, EventArgs e)
{
//send the current grid
OnCellItemTouched?.Invoke(sender, e);
}
}
Android Renderer
class SAChatViewCellRenderer : ViewCellRenderer
{
private bool selected;
ClickListener handler = new ClickListener();
static Android.Widget.ListView listView;
Xamarin.Forms.ListView listviewforms;
static SAChatViewCell cellElement;
Android.Views.View cellControl;
protected override Android.Views.View GetCellCore(Cell item, Android.Views.View convertView, Android.Views.ViewGroup parent, Android.Content.Context context)
{
try
{
if (cellControl == null)
{
cellControl = base.GetCellCore(item, convertView, parent, context);
}
cellElement = item as SAChatViewCell;
selected = false;
listviewforms = cellElement.View.Parent.Parent as Xamarin.Forms.ListView;
if (listviewforms == null)
{
return null;
}
if (listviewforms.BackgroundColor.ToAndroid() == Color.Transparent.ToAndroid())
{
cellControl.SetBackgroundColor(Color.White.ToAndroid());
}
else
{
cellControl.SetBackgroundColor(listviewforms.BackgroundColor.ToAndroid());
}
cellControl.SetOnLongClickListener(handler);
cellControl.SetOnTouchListener(handler);
return cellControl;
}
catch (Exception ex)
{
return null;
}
}
protected override void OnCellPropertyChanged(object sender, PropertyChangedEventArgs args)
{
base.OnCellPropertyChanged(sender, args);
if (args.PropertyName == "IsSelected")
{
// I had to create a property to track the selection because cellCore.Selected is always false.
// Toggle selection
selected = !selected;
var selectedBackground = cellElement.SelectedBackgroundColor.ToAndroid();
if (selected)
{
if (selectedBackground == Color.Transparent.ToAndroid())
{
cellControl.SetBackgroundColor(Color.White.ToAndroid());
return;
}
cellControl.SetBackgroundColor(selectedBackground);
}
else
{
if (listviewforms.BackgroundColor.ToAndroid() == Color.Transparent.ToAndroid())
{
cellControl.SetBackgroundColor(Color.White.ToAndroid());
}
else
{
cellControl.SetBackgroundColor(listviewforms.BackgroundColor.ToAndroid());
}
}
}
}
internal class ClickListener : Java.Lang.Object, IOnLongClickListener, IOnTouchListener
{
//event priority Touch - LongClick - Click
//NOTE: return true to indicate that we have handled the event and it should stop here;
public bool OnLongClick(Android.Views.View sender)
{
var cellItem = sender as INativeElementView;
var viewCell = sender as Android.Views.View;
float[] location = new float[] { 0, 0 };
Android.Views.View parentRow = (Android.Views.View)viewCell.Parent;
listView = (Android.Widget.ListView)parentRow.Parent;
int position = listView.GetPositionForView(parentRow);
var x = parentRow.Right;
var y = (parentRow.Top - listView.DividerHeight) <= 0 ? parentRow.Bottom : parentRow.Top;
int view_height = parentRow.Height;
location[0] = (x / MainActivity.Current.Resources.DisplayMetrics.Density);
location[1] = y / MainActivity.Current.Resources.DisplayMetrics.Density;
//send current cell
cellElement.InvokeOnCellItemLongClicked((cellItem.Element as ViewCell).View, location);
listView.Scroll += ListView_Scroll;
return true;
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (listView != null)
{
listView.Scroll -= ListView_Scroll;
}
}
private void ListView_Scroll(object sender, Android.Widget.AbsListView.ScrollEventArgs e)
{
cellElement.InvokeOnCellItemTouched(cellElement.View, EventArgs.Empty);
}
//return false if you have not handled it and/or the event should continue to any other on-click listeners.
public bool OnTouch(Android.Views.View v, MotionEvent e)
{
if (e.Action == MotionEventActions.Down)
{
cellElement.InvokeOnCellItemTouched(cellElement.View, EventArgs.Empty);
//cellCore.SetOnTouchListener(this);
}
return false;
}
}
}
}
iOS Renderer
class SAUITableViewCell : UITableViewCell
{
public override void TouchesBegan(NSSet touches, UIEvent evt)
{
base.TouchesBegan(touches, evt);
}
}
//When you scroll, your cells are created in real time. cells aren't created from scratch, instead iOS just takes a cell that has just left the screen and sends it through
class SAChatViewCellRenderer : ViewCellRenderer, IUIGestureRecognizerDelegate
{
UITableView TV;
SAChatViewCell cellElement;
public IntPtr Handle => new IntPtr();
public override UITableViewCell GetCell(Cell item, UITableViewCell reusableCell, UITableView tv)
{
try
{
UITableViewCell cell = base.GetCell(item, reusableCell, tv);
TV = tv;
var uiTapGestureRecognize = new UITapGestureRecognizer(OnClick);
var uiLongPressGestureRecognizer = new UILongPressGestureRecognizer(OnLongClick);
uiLongPressGestureRecognizer.MinimumPressDuration = 0.5;
cell.AddGestureRecognizer(uiTapGestureRecognize);
cell.AddGestureRecognizer(uiLongPressGestureRecognizer);
cellElement = item as SAChatViewCell;
cell.BackgroundColor = UIColor.Clear;
if (cellElement.SelectedBackgroundColor == Color.Transparent)
{
cell.SelectionStyle = UITableViewCellSelectionStyle.None;
}
else
{
cell.SelectedBackgroundView = new UIView
{
BackgroundColor = cellElement.SelectedBackgroundColor.ToUIColor() ?? default(UIColor)
};
}
return cell;
}
catch (Exception ex)
{
throw ex;
}
}
private void OnLongClick(UILongPressGestureRecognizer arg)
{
//get the current touch coords based on listview
CGPoint coords = arg.LocationInView(TV);
//current cell
if (arg.State != UIGestureRecognizerState.Began)
{
var indexPath = TV.IndexPathForRowAtPoint(coords);
CGRect Rect = TV.RectForRowAtIndexPath(indexPath);
//delete the listview offset
Rect.Offset(-TV.ContentOffset.X, -TV.ContentOffset.Y);
var CurrentViewCell = (arg.View as UIKit.UITableViewCell).Superview;
//Note : xamarin forms cell element MonoTouch creates it's own internal delegate type for UIScrollView so we either override the uiviewtable or trigger the ondisappear event
var cellItem = arg.View as INativeElementView;
(((cellItem.Element as ViewCell).Parent) as ListView).ItemDisappearing += (s, o) =>
{
cellElement.InvokeOnCellItemTouched(cellElement.View, EventArgs.Empty);
};
float[] location = new float[] { 0, 0 };
location[0] = (float)Rect.X;
var Y = Rect.Top <= 0 ? Rect.Bottom : Rect.Top;
location[1] = (float)Y;
cellElement.InvokeOnCellItemLongClicked((cellItem.Element as ViewCell).View, location);
}
}
private void OnClick()
{
cellElement.InvokeOnCellItemTouched(cellElement.View, EventArgs.Empty);
}
public void Dispose()
{
throw new NotImplementedException();
}
}
}
I have found a solution,
Problem :
My intension is to get ViewCell's position in screen
Solution :
Step 1 : Keep scrollview inside the Relative layout.
Step 2 : When user click on scroll view's ViewCell, save touch point (X, Y) of relative layout. In Y co-ordinate, add top position of relative layout so you will get touch point relative to whole screen.
Step 3 : When user click on scroll view's ViewCell, call XYZ() method.
Step 4 : Inside XYZ() method, do whatever functionality which required on (X, Y) co-ordinate. (Note : put 300ms delay in doing functionality in XYZ() method, as step-2 required some time in saving
touch points.)
Trying to do some tests with a ViewPager.
I want to swipe between tabs, and I don't want to continue until the swipe is complete. But there doesn't appear to be a way to turn off the animation for the view pager (all animations under the developer options are disabled).
So this always results in a test failure, because the view pager hasn't completed it's animation, and so the view is not completely displayed yet:
// swipe left
onView(withId(R.id.viewpager)).check(matches(isDisplayed())).perform(swipeLeft());
// check to ensure that the next tab is completely visible.
onView(withId(R.id.next_tab)).check(matches(isCompletelyDisplayed()));
Is there an elegant or maybe even recommended way to do this, or am I stuck putting some kind of timed wait in there?
The IdlingResource #Simas suggests is actually pretty simple to implement:
public class ViewPagerIdlingResource implements IdlingResource {
private final String mName;
private boolean mIdle = true; // Default to idle since we can't query the scroll state.
private ResourceCallback mResourceCallback;
public ViewPagerIdlingResource(ViewPager viewPager, String name) {
viewPager.addOnPageChangeListener(new ViewPagerListener());
mName = name;
}
#Override
public String getName() {
return mName;
}
#Override
public boolean isIdleNow() {
return mIdle;
}
#Override
public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
mResourceCallback = resourceCallback;
}
private class ViewPagerListener extends ViewPager.SimpleOnPageChangeListener {
#Override
public void onPageScrollStateChanged(int state) {
mIdle = (state == ViewPager.SCROLL_STATE_IDLE
// Treat dragging as idle, or Espresso will block itself when swiping.
|| state == ViewPager.SCROLL_STATE_DRAGGING);
if (mIdle && mResourceCallback != null) {
mResourceCallback.onTransitionToIdle();
}
}
}
}
Since I've done this at least twice now, here is the accepted answer in Kotlin and with androidx ViewPager2:
class ViewPager2IdlingResource(viewPager: ViewPager2, name: String) : IdlingResource {
private val name: String
private var isIdle = true // Default to idle since we can't query the scroll state.
private var resourceCallback: IdlingResource.ResourceCallback? = null
init {
viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageScrollStateChanged(state: Int) {
isIdle = (state == ViewPager.SCROLL_STATE_IDLE // Treat dragging as idle, or Espresso will block itself when swiping.
|| state == ViewPager.SCROLL_STATE_DRAGGING)
if (isIdle && resourceCallback != null) {
resourceCallback!!.onTransitionToIdle()
}
}
})
this.name = name
}
override fun getName(): String {
return name
}
override fun isIdleNow(): Boolean {
return isIdle
}
override fun registerIdleTransitionCallback(resourceCallback: IdlingResource.ResourceCallback) {
this.resourceCallback = resourceCallback
}
}
And here is how you use it from a UI test using ActivityScenarioRule:
#get:Rule
val testRule = ActivityScenarioRule(OnboardingActivity::class.java)
private lateinit var viewPager2IdlingResource: ViewPager2IdlingResource
....
#Before
fun setUp() {
testRule.scenario.onActivity {
viewPager2IdlingResource =
ViewPager2IdlingResource(it.findViewById(R.id.onboarding_view_pager), "viewPagerIdlingResource")
IdlingRegistry.getInstance().register(viewPager2IdlingResource)
}
}
#After
fun tearDown() {
IdlingRegistry.getInstance().unregister(viewPager2IdlingResource)
}
The androidx.test.espresso:espresso-core library offers a ViewPagerActions class which contains a number of methods for scrolling between the pages of a ViewPager. It takes care of waiting until the scroll is complete so you don't need to add any explicit waits or sleeps in your test methods.
If you need to perform similar scrolling on a ViewPager2 instance, you can take the source code of the ViewPagerActions class and make some minor tweaks to it to get it to work for ViewPager2. Here is an example which you are welcome to take and use.
Try this,
onView(withId(R.id.pager)).perform(pagerSwipeRight()).perform(pagerSwipeLeft());
private GeneralSwipeAction pagerSwipeRight(){
return new GeneralSwipeAction(Swipe.SLOW, GeneralLocation.CENTER_LEFT,
GeneralLocation.CENTER_RIGHT, Press.FINGER);
}
private GeneralSwipeAction pagerSwipeLeft(){
return new GeneralSwipeAction(Swipe.SLOW, GeneralLocation.CENTER_RIGHT,
GeneralLocation.CENTER_LEFT, Press.FINGER);
}
I was having issues with #vaughandroid approach, so I did some changes to his approach. This approach will set idle to false as soon as it detects a scrolling is happening and "force" the ViewPager to finish scrolling by using setCurrentItem().
public class ViewPagerIdlingResource implements IdlingResource {
private volatile boolean mIdle = true; // Default to idle since we can't query the scroll state.
private ResourceCallback mResourceCallback;
private ViewPager mViewPager;
public static ViewPagerIdlingResource waitViewPagerSwipe(ViewPager viewPager) {
return new ViewPagerIdlingResource(viewPager);
}
private ViewPagerIdlingResource(ViewPager viewPager) {
mViewPager = viewPager;
mViewPager.addOnPageChangeListener(new ViewPagerListener());
}
#Override
public String getName() {
return ViewPagerIdlingResource.class.getSimpleName();
}
#Override
public boolean isIdleNow() {
return mIdle;
}
#Override
public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
mResourceCallback = resourceCallback;
}
private class ViewPagerListener extends ViewPager.SimpleOnPageChangeListener {
float mPositionOffset = 0.0f;
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
if (isSwipingToRight(positionOffset)) {
mIdle = false;
mViewPager.setCurrentItem(position + 1);
} else if (isSwipingToLeft(positionOffset)) {
mIdle = false;
mViewPager.setCurrentItem(position - 1);
}
mPositionOffset = positionOffset;
if (positionOffset == 0 && !mIdle && mResourceCallback != null) {
mResourceCallback.onTransitionToIdle();
mIdle = true;
mPositionOffset = 0.0f;
}
}
private boolean isSwipingToRight(float positionOffset) {
return mPositionOffset != 0.0f && positionOffset > mPositionOffset && mIdle;
}
private boolean isSwipingToLeft(float positionOffset) {
return mPositionOffset != 0.0f && positionOffset < mPositionOffset && mIdle;
}
}
}
My goal was to make a screenshot of the screen with ViewPager2 using Facebook screenshot test library. The easiest approach for me was to check almost every frame whether animation completed, if yes then it's time to make a screenshot:
fun waitForViewPagerAnimation(parentView: View) {
if (parentView is ViewGroup) {
parentView.childrenViews<ViewPager2>().forEach {
while (it.scrollState != ViewPager2.SCROLL_STATE_IDLE) {
Thread.sleep(16)
}
}
}
}
childrenViews function can be found here
You can either do a lot of work and use an IdlingResource to implement an OnPageChangeListener
or simply:
SystemClock.sleep(500);
I am working with an attached behavior for logging user actions on a ScrollBar.
my code:
class ScrollBarLogBehavior : Behavior<ScrollBar>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Loaded += new RoutedEventHandler(AssociatedObject_Loaded);
}
void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
{
...
var track = (Track)AssociatedObject.Template.FindName("PART_Track", AssociatedObject);
// ** HERE is the problem: track is null ! **
...
}
How can I detect that the template has loaded and I can find the Track?
(when I call AssociatedObject.Template.LoadContent() the result containt the requested Track, so it i a matter of timing and not a matter of wrong template or naming)
Override the method OnApplyTemplate
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
var textBox = Template.FindName("PART_Textbox", this) as TextBox;
}
I did not find any good way to detect when the template was loaded. However, I did find a way to find the Track:
in OnAttached() - register to Scroll event fo the ScrollBar (this can only happen after the entire template is loaded, of course):
protected override void OnAttached()
{
base.OnAttached();
_scrollHandler = new ScrollEventHandler(AssociatedObject_Scroll);
AssociatedObject.AddHandler(ScrollBar.ScrollEvent, _scrollHandler, true);
}
When handling the Scroll event, remove registration and find the Thumb:
void AssociatedObject_Scroll(object sender, ScrollEventArgs e)
{
var track = (Track)AssociatedObject.Template.FindName("PART_Track", Associated
if (track == null)
return;
AssociatedObject.RemoveHandler(ScrollBar.ScrollEvent, _scrollHandler);
// do my work with Track
...
}
If I understand correctly, you wish to create an attached behavior that will reference a template part after the ScrollBar has been loaded.
The following should work:
internal static class ScrollBarLogBehavior
{
public static readonly DependencyProperty LogUserActionProperty = DependencyProperty.RegisterAttached(
"LogUserAction",
typeof(bool),
typeof(ScrollBarLogBehavior),
new UIPropertyMetadata(default(bool), LogUserActionChanged));
public static bool GetLogUserAction(DependencyObject obj)
{
return (bool)obj.GetValue(LogUserActionProperty);
}
public static void SetLogUserAction(DependencyObject obj, bool value)
{
obj.SetValue(LogUserActionProperty, value);
}
public static void LogUserActionChanged(DependencyObject s, DependencyPropertyChangedEventArgs e)
{
if (s is ScrollBar scrollBar)
{
scrollBar.Loaded += OnScrollBarLoaded;
}
}
private static void OnScrollBarLoaded(object sender, RoutedEventArgs e)
{
if (sender is ScrollBar scrollBar)
{
if (scrollBar.Template != null)
{
// I'm not sure, but the `name` in the following method call might be case sensitive.
if (scrollBar.Template.FindName("PART_Track", scrollBar) is Track track)
{
// do work with `track` here
}
}
}
}
}
where you would "attach" the behavior in your XAML with:
<ScrollBar guiControls:ScrollBarLogBehavior.LogUserAction="True">
<!-- more here -->
</ScrollBar>
BE ADVISED: this implementation completely ignores the bool value that is being set for LogUserAction
I am developing an app for iOS using MvvmCross. On one of my Views I have some basic report data that is displayed in a tableview.
When the table row is touched a new view containing a detail report is displayed by making the call to ShowViewModel passing some parameters in a Dictionary. This works fine.
When the user swipes left or right the app needs to show the detail report for the next or previous item in the original list. I am doing this by updating some parameters and calling ShowViewModel again. The logic behind this is all working fine.
My problem; ShowViewModel animates the new view coming in from the right. This is perfect when the user has swiped left. However when swiping right it seems counter intuitive. How can I make ShowViewModel animate or transition in from the left side?
if you look to the MvvmCross source code here you see how the default behavior is showing the ViewControllers
You need to change that by doing something like the following:
How to change the Push and Pop animations in a navigation based app
for that, one idea is to have a custom view presenter and catch navigation to that particular view-model (override Show(IMvxTouchView view) )
or, maybe derive from UINavigationController, set it to MvvmCross to use it (look to the MvxSetup), and on some events change transition to that particular view
similar to this question
How to specify view transitions on iPhone
This is the solution I was able to come up with following the helpful pointers in the answer from Andrei N. In the end I opted for a TransitionFlipFromRight and TransitionFlipFromLeft when scrolling between detail reports. Hopefully it is useful to somebody else.
I already had a presenter class that was inherited from MvxModalSupportTouchViewPresenter
public class BedfordViewPresenter : MvxModalSupportTouchViewPresenter
Within this class I added a property of MvxPresentationHint.
private MvxPresentationHint _presentationHint;
In the override of method ChangePresentation the above property is used to store the passed in parameter
public override void ChangePresentation (MvxPresentationHint hint)
{
_presentationHint = hint;
base.ChangePresentation (hint);
}
Two new MvxPresentationHint class were declared (see later)
In the presenter class the Show method was overridden
public override void Show(IMvxTouchView view)
{
if (_presentationHint is FlipFromRightPresentationHint) {
var viewController = view as UIViewController;
MasterNavigationController.PushControllerWithTransition (viewController, UIViewAnimationOptions.TransitionFlipFromRight);
}
else
if (_presentationHint is FlipFromLeftPresentationHint) {
var viewController = view as UIViewController;
MasterNavigationController.PushControllerWithTransition (viewController, UIViewAnimationOptions.TransitionFlipFromLeft);
}
else {
base.Show (view);
}
_presentationHint = null;
}
A new class that provides extensions to a UINavigationController was created with the method PushControllerWithTransition
public static class UINavigationControllerExtensions
{
public static void PushControllerWithTransition(this UINavigationController
target, UIViewController controllerToPush,
UIViewAnimationOptions transition)
{
UIView.Transition(target.View, 0.75d, transition, delegate() {
target.PushViewController(controllerToPush, false);
}, null);
}
}
All that needs to be defined now are the two new MvxPresentationHint class derivations. These belong in your Core class library project rather than the iOS application project.
public class FlipFromLeftPresentationHint : MvxPresentationHint
{
public FlipFromLeftPresentationHint ()
{
}
}
and
public class FlipFromRightPresentationHint: MvxPresentationHint
{
public FlipFromRightPresentationHint ()
{
}
}
I hope this is a help to someone else trying to do something similar
Share my solution for android:
On view:
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
var view = base.OnCreateView(inflater, container, savedInstanceState);
var layout = view.FindViewById<LinearLayout>(Resource.Id.swippeable);
var swipeListener = new SwipeListener(this.Activity);
swipeListener.OnSwipeLeft += (sender, e) => this.ViewModel.LeftCommand?.Execute(); //Here use command into view model
swipeListener.OnSwipeRight += (sender, e) => this.ViewModel.RightCommand?.Execute();
layout.SetOnTouchListener(swipeListener);
return view;
}
Gesture listener:
public class SwipeListener : SimpleOnGestureListener, View.IOnTouchListener
{
private const int SWIPE_THRESHOLD = 100;
private const int SWIPE_VELOCITY_THRESHOLD = 100;
private readonly GestureDetector gestureDetector;
public SwipeListener(Context ctx)
{
this.gestureDetector = new GestureDetector(ctx, this);
}
public Boolean OnTouch(View v, MotionEvent e)
{
return this.gestureDetector.OnTouchEvent(e);
}
public event EventHandler OnSwipeRight;
public event EventHandler OnSwipeLeft;
public event EventHandler OnSwipeTop;
public event EventHandler OnSwipeBottom;
public override Boolean OnDown(MotionEvent e)
{
return true;
}
public override Boolean OnFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)
{
Boolean result = false;
float diffY = e2.GetY() - e1.GetY();
float diffX = e2.GetX() - e1.GetX();
if (Math.Abs(diffX) > Math.Abs(diffY))
{
if (Math.Abs(diffX) > SWIPE_THRESHOLD && Math.Abs(velocityX) > SWIPE_VELOCITY_THRESHOLD)
{
if (diffX > 0)
{
SwipeRight();
}
else
{
SwipeLeft();
}
result = true;
}
}
else if (Math.Abs(diffY) > SWIPE_THRESHOLD && Math.Abs(velocityY) > SWIPE_VELOCITY_THRESHOLD)
{
if (diffY > 0)
{
SwipeBottom();
}
else
{
SwipeTop();
}
result = true;
}
return result;
}
public void SwipeRight()
{
this.OnSwipeRight?.Invoke(this, EventArgs.Empty);
}
public void SwipeLeft()
{
this.OnSwipeLeft?.Invoke(this, EventArgs.Empty);
}
public void SwipeTop()
{
this.OnSwipeTop?.Invoke(this, EventArgs.Empty);
}
public void SwipeBottom()
{
this.OnSwipeBottom?.Invoke(this, EventArgs.Empty);
}
}
I am trying to implement a custom binding on a subclass of UITextField so that the bound value is set when the user is done editing instead of with each keystroke because some interim values are invalid in the viewmodel (for example, while setting 'Age' to '26', a value of '2' is invalid so I'd like to wait to set the value until both digits are there). Something similar to setting UpdateSourceTrigger in xaml. I looked at several examples here:
MvvmCross UITextField custom binding is similar, as is MvvmCross: change update source trigger property of binding on MonoDroid (but for Android). I've also watch N=28 custom binding and looked at the source for MvxUITextFieldTextTargetBinding.
I think I'm close, but my custom binding never gets created and the UITextFields in my app still FireValueChanged with every keystroke.
I created the following Custom Binding:
public class UITextFieldFocusChangedBinding : MvxTargetBinding
{
private bool _subscribed;
private UITextField _view;
public UITextFieldFocusChangedBinding(UITextField target) : base(target)
{
_view = target;
}
public override void SetValue(object value)
{
if (_view == null) return;
_view.Text = (string)value;
}
public override void SubscribeToEvents()
{
var view = _view;
if (view == null)
return;
view.Ended += TextFieldOnEnded;
}
private void TextFieldOnEnded(object sender, EventArgs eventArgs)
{
var view = _view;
if (view == null)
return;
if (!view.IsFirstResponder)
FireValueChanged(view.Text);
_subscribed = true;
}
public override Type TargetType
{
get { return typeof(string); }
}
public override MvxBindingMode DefaultMode
{
get { return MvxBindingMode.TwoWay; }
}
protected override void Dispose(bool isDisposing)
{
if (isDisposing)
{
var view = _view;
if (view != null && _subscribed)
{
view.Ended -= TextFieldOnEnded;
_subscribed = false;
}
}
base.Dispose(isDisposing);
}
}
My setup.cs contains the following:
protected override void FillTargetFactories(IMvxTargetBindingFactoryRegistry registry)
{
base.FillTargetFactories(registry);
registry.RegisterPropertyInfoBindingFactory(typeof(Bindings.UITextFieldFocusChangedBinding),typeof(UITextField), "Text");
}
and in my MvxViewController I have:
var set = this.CreateBindingSet<LifeNeedsView, LifeNeedsViewModel>();
set.Bind(_txtFinMedExpenses).To(vm => vm.FinalMedicalExpenses);
set.Apply();
The bindings work (values are passing correctly) but with every keystroke. Any suggestions on what I might be missing?