Xamarin IOS Load View form Gesture Event - c#

I am trying to capture the PanGesture event, so when the user swipe with his/her finger anywhere in the view to the right, for example, the RightView shows up and when to left, the LeftView shows up.
I am using the code below. It is from Xamarin docs. I modified it a bit. My 'currView' is my outlet for the current View. When I swipe at the simulator with my mouse, the event gets captured correctly, but when stepping through (after break pointing) at the curly bracket of
if (recognizer.State != (UIGestureRecognizerState.Cancelled | UIGestureRecognizerState.Failed
{
The code loops between it and this one.
HandleDrag(gesture))
once for every pixel (I think).
I am trying to trigger the load of another view, but didn't find a spot to place it at. I tried at the
Console.WriteLine ("Here")
and at:
currView.AddGestureRecognizer (gesture);
but every time the code finishes the loop, it just ignores it and execution ends. How can I make the loading of next view work?
The line of the comment:
// NEED TO LOAD ANOTHER VIEW HERE
shows my desires spot to load another View.
Here is my code
namespace Example_Touch.Screens.iPhone.GestureRecognizers
{
using System;
using System.Drawing;
using MonoTouch.Foundation;
using MonoTouch.UIKit;
public partial class GestureRecognizers_iPhone : UIViewController
{
private RectangleF originalImageFrame = RectangleF.Empty;
// The IntPtr and initWithCoder constructors are required for items that need
// to be able to be created from a xib rather than from managed code
public GestureRecognizers_iPhone(IntPtr handle)
: base(handle)
{
Initialize();
}
[Export("initWithCoder:")]
public GestureRecognizers_iPhone(NSCoder coder)
: base(coder)
{
Initialize();
}
public GestureRecognizers_iPhone()
: base("GestureRecognizers_iPhone", null)
{
Initialize();
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
WireUpDragGestureRecognizer();
}
protected void WireUpDragGestureRecognizer()
{
// create a new tap gesture
UIPanGestureRecognizer gesture = new UIPanGestureRecognizer();
// wire up the event handler (have to use a selector)
gesture.AddTarget(() => HandleDrag(gesture));
currView.AddGestureRecognizer (gesture);
}
protected void HandleDrag(UIPanGestureRecognizer recognizer)
{
PointF offset2 = recognizer.TranslationInView(currView);
// if it's just began, cache the location of the image
if (recognizer.State != (UIGestureRecognizerState.Cancelled | UIGestureRecognizerState.Failed
| UIGestureRecognizerState.Possible))
{
Console.WriteLine ("Here");
// NEED TO LOAD ANOTHER VIEW HERE
}
}
private void Initialize()
{
}
}
}

From Apple documentation:
A panning gesture is continuous. It begins (UIGestureRecognizerStateBegan) when the minimum number of fingers allowed (minimumNumberOfTouches) has moved enough to be considered a pan. It changes (UIGestureRecognizerStateChanged) when a finger moves while at least the minimum number of fingers are pressed down. It ends (UIGestureRecognizerStateEnded) when all fingers are lifted.
Check for UIGestureRecognizerState.Ended state and if it occurs, you can load another view.
From your description it seems that you could use UISwipeGestureRecognizer instead of UIPanGestureRecognizer. The UISwipeGestureRecognizer is a discrete gesture, so you don't have to check for states, you just implement it this way:
Somewhere in your code implement the handler:
private void HandleSwipeRight()
{
// load another view
Console.WriteLine("Swipe right.");
}
And initialize the gesture recognzier in ViewDidLoad():
UISwipeGestureRecognizer swipeGestureRecognizerRight = new UISwipeGestureRecognizer(HandleSwipeRight);
swipeGestureRecognizerRight.Direction = UISwipeGestureRecognizerDirection.Right;
this.View.AddGestureRecognizer(swipeGestureRecognizerRight);
You can add another swipe gesture recognizer for a different direction.

Related

Multiple binding to RelayCommand in WPF MVVM Light

I have started working with WPF MVVM Light and now I'am trying to navigate between pages.
In the MainWindow I have added a "BackButton"
<Button Command='{Binding Main.GoBack, Mode=OneWay}' />
which is binding to MainViewModel method "RelayCommand GoBack".
private RelayCommand _goBack;
public RelayCommand GoBack
{
get
{
return _goBack
?? (_goBack = new RelayCommand(
() =>
_navigationService.GoBack();
}));
}
}
Why is this button changing view only once? If I want to click it secound time
it doesn't work (nothing happend). If I change page for another by another button its starting work again and againg only for once.
Part of implementation of FrameNavigationService:
public FrameNavigationService()
{
_pagesByKey = new Dictionary<string, Uri>();
_historic = new List<string>();
}
public void GoBack()
{
if (_historic.Count > 1)
{
_historic.RemoveAt(_historic.Count - 1);
NavigateTo(_historic.Last(), null);
}
}
public void NavigateTo(string pageKey)
{
NavigateTo(pageKey, null);
}
public virtual void NavigateTo(string pageKey, object parameter)
{
lock (_pagesByKey)
{
if (!_pagesByKey.ContainsKey(pageKey))
{
throw new ArgumentException(string.Format("No such page: {0} ", pageKey), "pageKey");
}
var frame = GetDescendantFromName(Application.Current.MainWindow, "MainFrame") as Frame;
if (frame != null)
{
frame.Source = _pagesByKey[pageKey];
}
Parameter = parameter;
_historic.Add(pageKey);
CurrentPageKey = pageKey;
}
}
What can I do to handle this? May be I should do it tottaly differently?
You should possibly not be doing goback at all.
Unless you really want to use the journal, using a frame and pages is a bad idea. It's a rare requirement to go back to the last view in desktop apps. What with them not being a web browser.
Maybe you have that requirement though.
If you have a frame then you have it's journal and you can just call goback on the frame's navigationservice.
https://learn.microsoft.com/en-us/dotnet/api/system.windows.navigation.navigationservice.goback?view=netframework-4.8
You set keepalive on pages.
https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.page.keepalive?view=netframework-4.8
You wrote that code and it seems to be largely reproducing navigationservice functionality. From what you've shown us.
As it is.
Use type rather than a magic string as the key. A type is checked at compile time, a magic string is not and you can make mistakes.
Have you explored this issue at all? I think maybe this is one of those times that telling someone what they did wrong isn't really helping as much as telling them how they ought to diagnose.
Debugging is a key skill for any developer.
You have the code running in front of you.
Put break points in, step through and examine what is happening.
When you navigate, what ends up in _historic?
When you goback, what happens exactly?
When you click the goback that second time what path does it go down and what state is causing that.
Make sure you are using RelayCommand in GalaSoft.MvvmLight.CommandWpf,not at GalaSoft.MvvmLight.Command.RelayCommand

Loading screen for GoogleMaps activity

I'm trying to implement a loading screen since GoogleMaps sometimes takes ~2 seconds to load. I tried using a RelativeLayout and putting the ImageView above the map fragment and making it ViewStates.Gone once the map loads. However, I noticed that the loading is happening on base.OnCreateBundle and not on SetUpMap() and now I'm clueless how to implement it.
private GoogleMap mMap;
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.Map);
mSplash = FindViewById<ImageView>(Resource.Id.splash);
SetUpMap();
}
private void SetUpMap()
{
if (mMap == null)
{
FragmentManager.FindFragmentById<MapFragment(Resource.Id.map).GetMapAsync(this);
}
}
public void OnMapReady(GoogleMap googleMap)
{
mSplash.Visibility = ViewStates.Gone;
mMap = googleMap;
mMap.MapType = GoogleMap.MapTypeSatellite;
(...)
}
If I'm not mistaken, OnMapReady is meant to be used to verify that Google Play Services is installed and active on the device. As the documentation puts it:
If Google Play services is not installed on the device, the user will
be prompted to install it, and the onMapReady(GoogleMap) method will
only be triggered when the user has installed it and returned to the
app.
OnMapReady Documentation
Alternatively, the OnMapLoaded function is meant to be called after the map has finished loading its tiles.
Called when the map has finished rendering. This will only be called
once. You must request another callback if you want to be notified
again.
OnMapLoaded Documentation
You can implement the OnMapLoaded function by first setting the callback once you have a reference to the map
mMap.setOnMapLoadedCallback(this);
Finish by simply overriding the OnMapLoaded function and handling it accordingly
#Override
public void onMapLoaded() {
if (mMap != null) {
mSplash.Visibility = ViewStates.Gone;
}
}
Remember that if you need to load the map again and need to display another splash screen, you will first need to set the callback again, as onMapLoaded is only called once per callback.

Better way to determine if a button is clicked

I am developing a game in MonoGame and decided to create my own button class. In order to determine if these buttons were clicked, I determined if they were in the bounds of the button, and if the left mouse button was down. This has lead to two problems.
The mouse click doesn't need to start inside the button, and it registers immediately rather than after the mouse button is released (Inside the bounds of the button.)
When multiple buttons on different screens are in the same area, it results in clicking them both as the mouse button did not release fast enough.
How can I make the clicking behave more like the WinForm buttons?
To make mouseclicking work effectively you should do the following :
First thing we do is create a MouseInput class, this should track stuff like, mouseState, lastMouseState, MouseX, MouseY. The mouseState and lastMouseState work together to handle 1 single click. But for now you can just add this class to your project :
class MouseInput
{
private static MouseState mouseState;
private static MouseState lastMouseState;
public static MouseState MouseState
{
get { return mouseState; }
set { mouseState = value; }
}
public static MouseState LastMouseState
{
get
{
return lastMouseState;
}
set
{
lastMouseState = value;
}
}
public MouseInput()
{
}
public static int getMouseX()
{
return Mouse.GetState().X;
}
public static int getMouseY()
{
return Mouse.GetState().Y;
}
}
After you've done that you want to start loooking for a mouseclick everytime your update method gets executed :
MouseInput.LastMouseState = MouseInput.MouseState;
// Get the mouse state relevant for this frame
MouseInput.MouseState = Mouse.GetState();
After you've completed these 2 crucial steps you can continue and use your code anywhere you want like this :
if (MouseInput.LastMouseState.LeftButton == ButtonState.Released && MouseInput.MouseState.LeftButton == ButtonState.Pressed) {
//Execute code here
}
Ofcourse as the above if statement only checks if the player pressed the left mouse button, you'll still have to check if the mouse positions are inside the button.
If you have any questions, feel free to ask
You can use RoutedEvent that can invoke handlers on multiple listeners in an element tree, rather than just on the object that raised the event. you can read more about RoutedEvents because The concept of an attached event enables you to add a handler for a particular event to an arbitrary element rather than to an element that actually defines or inherits the event. When those events are not related to the element which, like in case of MouseDown, both raises and listens to the event.
So RoutedEvents might help you to solve your problem
Here is a sample code
public static readonly RoutedEvent SelectedEvent =
EventManager.RegisterRoutedEvent( "Selected", RoutingStrategy.Bubble,
typeof(RoutedEventHandler), typeof(MyCustomControl));
// .NET wrapper
public event RoutedEventHandler Selected
{
add { AddHandler(SelectedEvent, value); }
remove { RemoveHandler(SelectedEvent, value); }
}
// Raise the routed event "selected"
RaiseEvent(new RoutedEventArgs(MyCustomControl.SelectedEvent));
UI for XNA 4.0 like WinForms, written from scratch.
Create form and, button to it, call Form.Update() from Game.Update() method.
In Game.Draw() first call Form.Refresh(), then Form.Draw().
For processing mouse and keyboard input used managers from GameHelper.Input library.

MonoTouch memory issue with Threading / Timers

I am having an issue with MonoTouch where UIViewControllers are remaining in-memory forever, even after they have been popped from the navigation stack.
I have a UINavigationController which contains a UIViewController with a button. Clicking the button pushes a custom UIViewController called ThreadingViewController onto the navigation stack.
The ThreadingViewController uses a NSTimer.CreateRepeatingScheduledTimer and a Thread to update the text of a label every one second.
When the user clicks "back" to pop back to the root view, the Mono Profiler says that my ThreadingViewController still exists in memory. The Profiler tells me it has something to do with NSAction and/or ThreadStart which has a reference to the ThreadingViewController, keeping it alive. I can see this by checking the "inverse references" checkbox in the profiler.
This means that if the user clicks backwards and forwards 100 times between the root ViewController and the custom ThreadingViewController, there will be 100 instances of this ViewController in memory. It isn't being garbage collected.
In ViewDidDisappear I have tried aborting the thread, setting it to null, to no avail.
What do I need to do to get this ThreadingViewController to be properly cleaned up / GC'ed by MonoTouch?
Here is the full (C# only) source code to reproduce the problem:
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using MonoTouch.Foundation;
using MonoTouch.UIKit;
using System.Threading;
namespace MemoryTests
{
[Register("AppDelegate")]
public partial class AppDelegate : UIApplicationDelegate
{
private UIWindow window;
private UINavigationController rootNavigationController;
private RootScreen rootScreen;
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
window = new UIWindow(UIScreen.MainScreen.Bounds);
rootScreen = new RootScreen();
rootNavigationController = new UINavigationController(rootScreen);
window.RootViewController = rootNavigationController;
window.MakeKeyAndVisible();
return true;
}
}
public class RootScreen : UIViewController
{
private UIButton button;
public override void ViewDidLoad()
{
base.ViewDidLoad();
this.Title = "Root Screen";
// Add a button
button = new UIButton(UIButtonType.RoundedRect);
button.SetTitle("Click me", UIControlState.Normal);
button.Frame = new RectangleF(100, 100, 120, 44);
this.View.Add(button);
}
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
button.TouchUpInside += PushThreadingViewController;
}
public override void ViewDidDisappear(bool animated)
{
base.ViewDidDisappear(animated);
button.TouchUpInside -= PushThreadingViewController;
}
private void PushThreadingViewController(object sender, EventArgs e)
{
var threadingViewController = new ThreadingViewController();
NavigationController.PushViewController(threadingViewController, true);
}
}
public class ThreadingViewController : UIViewController
{
private UILabel label;
private NSTimer timer;
private int counter;
public override void ViewDidLoad()
{
base.ViewDidLoad();
this.Title = "Threading Screen";
// Add a label
label = new UILabel();
label.Frame = new RectangleF(0f, 200f, 320f, 44f);
label.Text = "Count: 0";
this.View.Add(label);
// Start a timer
var timerThread = new Thread(StartTimer as ThreadStart);
timerThread.Start();
}
public override void ViewDidDisappear (bool animated)
{
base.ViewDidDisappear(animated);
timer.Dispose();
timer = null;
// Do I need to clean up more Threading things here?
}
[Export("StartTimer")]
private void StartTimer()
{
using (var pool = new NSAutoreleasePool())
{
timer = NSTimer.CreateRepeatingScheduledTimer(1d, TimerTicked);
NSRunLoop.Current.Run();
}
}
private void TimerTicked()
{
InvokeOnMainThread(() => {
label.Text = "Count: " + counter;
counter++;
});
}
}
}
Here is a screenshot of the profiler telling me that we have 3 instances of ThreadingViewController in memory:
Cheers.
I think the problem is here:
timer.Dispose();
Try changing it to:
timer.Invalidate();
NSTimer is the only thing here that uses an NSAction.
Also, I don't think this is very helpful:
var timerThread = new Thread(StartTimer as ThreadStart);
timerThread.Start();
Reasons:
It's not a background thread. Not sure how foreground .NET threads behave on iOS. My guess is, it never finishes.
You don't need to have a separate thread live all the time, within from you are starting the NSTimer. NSTimers are light, bulletproof and provide all the options you need. They work great even if you are starting them on the UI thread.
(Please ignore #2 above if you have another reason you are doing it this way, which is not directly visible from your code. But, nothing else comes to mind).
PS: I haven't tested your code above. But I'm 100% sure that you must Invalidate() NSTimers so that they stop running, when you no longer need them.
Anytime you connect a click handler such as:
button.TouchUpInside += PushThreadingViewController;
You must also disconnect it. I would suggest moving the line of code above to the ViewDidAppear override, and adding the line of code below to the ViewDidDisappear override:
button.TouchUpInside -= PushThreadingViewController;
I would also recommend that you check to see if threadingViewController is null and only creating a new instance if it is, before pushing it onto the navigation stack. That way it will use the same one each time if it hasn't been garbage collected yet.
Be careful with ViewDidDisappear. This method may called at any time your View-Controller yields control to another View-Controller (calling PresentViewController, invoking the sharing UI, display an iAd etc). If you only want to react when your View-Controller is removed from the navigation stack, I'd recommend this:
public override void DidMoveToParentViewController(UIViewController parent)
{
base.DidMoveToParentViewController(parent);
if(parent == null)
Cleanup();
}
Try invoking ReleaseOutlets() (generated from the Xamarin Studio XIB processor) from within your Cleanup() method in addition to unwiring the touch event handler improves the situation.

Windows 8: How to undo & redo ink using built in Inking functionality?

I've implemented inking code in my app based on the simplified inking sample by microsoft : http://code.msdn.microsoft.com/windowsapps/Input-simplified-ink-sample-11614bbf/view/SourceCode
First I made a class that saves data of an operation (draw/delete/clear) like this:
public enum eInkOperation
{
Draw,
Delete,
None
}
public class InkOperation
{
public InkStroke Stroke { get; set; } //requred for drawing from undo
public eInkOperation Operation { get; set; }
public InkOperation(InkStroke stroke, eInkOperation inkOperation)
{
Stroke = stroke.Clone(); //needs to be cloned for AddStroke to work
Operation = inkOperation;
}
}
Then I made one stack for undo ink operations and one for redo operations
//stack of normal operations
Stack<InkOperation> _undoStack = new Stack<InkOperation>();
//Undo action will pop them off of the undo stack and push them onto the redo stack
Stack<InkOperation> _redoStack = new Stack<InkOperation>();
When a user undoes a stroke I push it on the redo stack and delete it from the inkmanager with these methods:
private void RedoStackPush(InkOperation inkOperation)
{
inkOperation.Stroke = inkOperation.Stroke.Clone();
_redoStack.Push(inkOperation);
}
private void DeleteStroke(InkStroke stroke)
{
stroke = inkManager.GetStrokes().Last();
stroke.Selected = true;
inkManager.DeleteSelected();
}
Then when the user clicks on redo, the stroke is popped off the redo stack and drawn using this method:
private void DrawStroke(InkStroke stroke)
{
if (stroke!=null)
{
inkManager.Mode = InkManipulationMode.Inking;
inkManager.AddStroke(stroke);
}
renderer.Clear(); //this renderer object smooths the strokes
//and adds them as Path objects to the desired control (Grid, etc)
renderer.AddInk(inkManager.GetStrokes());
}
This all works, and the stroke is displayed back on the grid.
However, when I try to erase the newly redrawn stroke I get this exception:
AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
This happens in:
public void PointerMoved(PointerRoutedEventArgs e)
{
try
{
var pointerPoint = e.GetCurrentPoint(_inkingArea);
var pointerEventType = InkHelpers.GetPointerEventType(e);
if (pointerId == (int)pointerPoint.PointerId)
{
switch (inkManager.Mode)
{
case InkManipulationMode.Inking:
case InkManipulationMode.Selecting:
//process intermediate points
var intermediatePoints = e.GetIntermediatePoints(_inkingArea);
for (int i = intermediatePoints.Count - 1; i >= 0; i--)
{
inkManager.ProcessPointerUpdate(intermediatePoints[i]);
}
//live rendering
renderer.UpdateLiveRender(pointerPoint);
break;
case InkManipulationMode.Erasing:
//check if something has been erased
//in erase mode InkManager.ProcessPointerUpdate returns an invalidate rectangle:
//if it is not degenerate, something has been erased
//in erase mode don't bother processing intermediate points
//If inkManager.ProcessPointerUpdate throws an exception, it crashes the app regardless of any catches
Rect invalidateRect = (Rect)inkManager.ProcessPointerUpdate(e.GetCurrentPoint(_inkingArea));
if (invalidateRect.Height != 0 && invalidateRect.Width != 0)
{
//we don't know what has been erased so we clear the render
//and add back all the ink saved in the ink manager
renderer.Clear();
var remainingStrokes = inkManager.GetStrokes();
renderer.AddInk(remainingStrokes);
}
break;
default:
break;
}
}
}
catch (Exception) { }
}
At this line:
Rect invalidateRect = (Rect)inkManager.ProcessPointerUpdate(e.GetCurrentPoint(_inkingArea));
I think the problem lies in the process of adding strokes to the ink manager. I tried making a new stroke and even inheriting from the InkStroke to make it customizable but the InkStroke class is sealed and it doesn't have a constructor. The only was I found to copy it was to do inkStroke.Clone(). But even that has its problems when trying to redraw deleted ink (undo a deleted stroke).
I tried to make this question as clear as possible using the least amount of code to avoid confusion, so let me know if it's insufficient.
Also in this question I'm focusing on undoing a draw action. Undoing an erasing action (or even "clear all" action) has its own set of problems because I can't make a copy of the InkStroke object.
Thanks in advance for your time and consideration.
See this thread from MSDN, it might be helpful.

Categories