Xamarin - adding swipe to a UIVIEW - xamarin

I'm new using Xamarin and I'm trying to learn it by converting an App I built using Titanium in the past. Actually I'm stucked trying to add swipe on my UIView. I checked the documentation and also I found a similar code here in Stack Overflow, but it's not working anyway. Can someone point me what's wrong ? ty !
public partial class ViewController : UIViewController
{
UISwipeGestureRecognizer swipe;
...
}
private void addSwipe()
{
nfloat vpHeight = View.Bounds.Height;
nfloat vpWidth = View.Bounds.Width;
var mainContainer = new UIView()
{
Frame = new CoreGraphics.CGRect(0, 0, vpWidth, vpHeight),
BackgroundColor = UIColor.Red
};
swipe = new UISwipeGestureRecognizer();
mainContainer.AddGestureRecognizer(swipe);
if (swipe.Direction == UISwipeGestureRecognizerDirection.Up)
{
Console.WriteLine("up");
}
}

You need to specify a handler when you create your recognizer:
swipe = new UISwipeGestureRecognizer( (s) =>
{
if (s.Direction == UISwipeGestureRecognizerDirection.Up)
{
Console.WriteLine("up");
}
});

I found the solution on my own...
public partial class ViewController : UIViewController
{
UISwipeGestureRecognizer swipe;
...
}
private void addSwipe()
{
nfloat vpHeight = View.Bounds.Height;
nfloat vpWidth = View.Bounds.Width;
var mainContainer = new UIView()
{
Frame = new CoreGraphics.CGRect(0, 0, vpWidth, vpHeight),
BackgroundColor = UIColor.Red
};
UISwipeGestureRecognizer swipeUp = new UISwipeGestureRecognizer(OnSwipeUp);
swipeUp.Direction = UISwipeGestureRecognizerDirection.Up;
mainContainer.AddGestureRecognizer(swipeUp);
}
private void OnSwipeUp() {
Console.Writeline("Up");
}

Related

Webview OnProgressChanged and progressbar

In a Xamarin application I have a CustomWebView renderer; I'm injecting in the view and displaying a progressbar in OnProgressChanged event of the WebChromeClient with the following code.
Init:
var progressBar = new Android.Widget.ProgressBar(_context, null, Android.Resource.Attribute.ProgressBarStyleHorizontal);
Control.SetWebViewClient(new CusWebViewClient($"javascript: {JavascriptFunction}"));
Control.SetWebChromeClient(new CusWebChromeClient(progressBar));
Control.AddView(progressBar);
CusWebChromeClient:
public class CusWebChromeClient : WebChromeClient
{
Android.Widget.ProgressBar progressBar;
public CusWebChromeClient(Android.Widget.ProgressBar progressBar)
{
this.progressBar = progressBar;
}
public override void OnProgressChanged(Android.Webkit.WebView view, int newProgress)
{
if (newProgress < 100 && progressBar.Visibility == ViewStates.Gone)
{
progressBar.Visibility = ViewStates.Visible;
}
progressBar.SetProgress(newProgress, true);
if (newProgress == 100)
{
//progressBar.Visibility = ViewStates.Gone;
}
}
}
The issue is that the progress bar is displayed really small like:
I need to display it with full display width and with a more heigth.
You forgot to set the width of progress bar.
You could use the screen width to set the progress bar in custom renderer. After that, it would be okay.
var width = (int)Application.Current.MainPage.Width;
Please note, 'Application' is an ambiguous reference between 'Android.App.Application' and 'Xamarin.Forms.Application'. You could add reference like below to fix it.
using Application = Xamarin.Forms.Application;
Change:
Control.AddView(progressBar);
To:
Control.AddView(progressBar, width, 30);

Pop the UIViewController in xamarin ios

I want to open a UIViewController from a content page and this code helps me in doing this
UIWindow Window = new UIWindow(UIScreen.MainScreen.Bounds);
var cvc = new ScanningPage();
var navController = new UINavigationController(cvc);
Window.RootViewController = navController;
Window.MakeKeyAndVisible();
but I also want a Back button on that Controller Page which navigate me back to that content Page.
this.NavigationController.PopViewController(true);
or
this.DismissViewController(true,null);
not working in this case.
I would recommend not creating a new Window and a UINavigationController...
Xamarin.Forms is contained in a single VC in the first Window, so you can obtain that view controller via:
UIApplication.SharedApplication.Windows[0].RootViewController;
So as an example Dependency service that presents and dismisses a VC (either from Forms or the view controller), you can do something like this.
Dependency interface:
public interface IDynamicVC
{
void Show();
void Dismiss();
}
iOS Dependency implementation
public class DynamicVC : IDynamicVC
{
UIViewController vc;
public void Show()
{
if (vc != null) throw new Exception("DynamicVC already showing");
vc = new UIViewController();
var button = new UIButton(new CGRect(100, 100, 200, 200));
button.SetTitle("Back", UIControlState.Normal);
button.BackgroundColor = UIColor.Red;
button.TouchUpInside += (object sender, EventArgs e) =>
{
Dismiss();
};
vc.Add(button);
var rootVC = UIApplication.SharedApplication.Windows[0].RootViewController;
rootVC.PresentViewController(vc, true, () => { });
}
public void Dismiss()
{
vc?.DismissViewController(true, () =>
{
vc.Dispose();
vc = null;
});
}
}

Adding a bottom border to an Entry in Xamarin Forms iOS with an image at the end

Now before anyone ignores this as a duplicate please read till the end. What I want to achieve is this
I've been doing some googling and looking at objective c and swift responses on stackoverflow as well. And this response StackOverFlowPost seemed to point me in the right direction. The author even told me to use ClipsToBounds to clip the subview and ensure it's within the parents bounds. Now here's my problem, if I want to show an image on the right side of the entry(Gender field), I can't because I'm clipping the subview.
For clipping, I'm setting the property IsClippedToBounds="True" in the parent stacklayout for all textboxes.
This is the code I'm using to add the bottom border
Control.BorderStyle = UITextBorderStyle.None;
var myBox = new UIView(new CGRect(0, 40, 1000, 1))
{
BackgroundColor = view.BorderColor.ToUIColor(),
};
Control.AddSubview(myBox);
This is the code I'm using to add an image at the beginning or end of an entry
private void SetImage(ExtendedEntry view)
{
if (!string.IsNullOrEmpty(view.ImageWithin))
{
UIImageView icon = new UIImageView
{
Image = UIImage.FromFile(view.ImageWithin),
Frame = new CGRect(0, -12, view.ImageWidth, view.ImageHeight),
ClipsToBounds = true
};
switch (view.ImagePos)
{
case ImagePosition.Left:
Control.LeftView.AddSubview(icon);
Control.LeftViewMode = UITextFieldViewMode.Always;
break;
case ImagePosition.Right:
Control.RightView.AddSubview(icon);
Control.RightViewMode = UITextFieldViewMode.Always;
break;
}
}
}
After analysing and debugging, I figured out that when OnElementChanged function of the Custom Renderer is called, the control is still not drawn so it doesn't have a size. So I subclassed UITextField like this
public class ExtendedUITextField : UITextField
{
public UIColor BorderColor;
public bool HasBottomBorder;
public override void Draw(CGRect rect)
{
base.Draw(rect);
if (HasBottomBorder)
{
BorderStyle = UITextBorderStyle.None;
var myBox = new UIView(new CGRect(0, 40, Frame.Size.Width, 1))
{
BackgroundColor = BorderColor
};
AddSubview(myBox);
}
}
public void InitInhertedProperties(UITextField baseClassInstance)
{
TextColor = baseClassInstance.TextColor;
}
}
And passed the hasbottomborder and bordercolor parameters like this
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
var view = e.NewElement as ExtendedEntry;
if (view != null && Control != null)
{
if (view.HasBottomBorder)
{
var native = new ExtendedUITextField
{
BorderColor = view.BorderColor.ToUIColor(),
HasBottomBorder = view.HasBottomBorder
};
native.InitInhertedProperties(Control);
SetNativeControl(native);
}
}
But after doing this, now no events fire :(
Can someone please point me in the right direction. I've already built this for Android, but iOS seems to be giving me a problem.
I figured out that when OnElementChanged function of the Custom Renderer is called, the control is still not drawn so it doesn't have a size.
In older versions of Xamarin.Forms and iOS 9, obtaining the control's size within OnElementChanged worked....
You do not need the ExtendedUITextField, to obtain the size of the control, override the Frame in your original renderer:
public override CGRect Frame
{
get
{
return base.Frame;
}
set
{
if (value.Width > 0 && value.Height > 0)
{
// Use the frame size now to update any of your subview/layer sizes, etc...
}
base.Frame = value;
}
}

Custom pin on xamarin forms map

I have a Xamarin Forms project which requires a custom map pin. I have the stub defined in the PCL with a couple of additional properties (for lat and long) defined within the class that inherits the map.
The map shows up fine, but the custom image pin only shows as the standard red pin.
Below is my custom renderer for the map (iOS version). From what I can see on various forums, this should work fine.
public class CustomMapRenderer : ViewRenderer<CustomMap, MKMapView>
{
MKMapView mkMapView;
protected override void OnElementChanged(ElementChangedEventArgs<CustomMap> e)
{
base.OnElementChanged(e);
var map = e.NewElement;
SetNativeControl(new MKMapView(CGRect.Empty));
mkMapView = Control;
MyMapDelegate myMapDelegate = new MyMapDelegate();
mkMapView.Delegate = myMapDelegate;
mkMapView.AddAnnotation(new MKPointAnnotation()
{
Coordinate = new CLLocationCoordinate2D(map.MapPinLatitude, map.MapPinLongitude)
});
mkMapView.MapType = MKMapType.Hybrid;
mkMapView.ZoomEnabled = true;
}
}
public class MyMapDelegate : MKMapViewDelegate
{
protected string annotationIdentifier = "PinAnnotation";
public override MKAnnotationView GetViewForAnnotation(MKMapView mapView, IMKAnnotation annotation)
{
MKAnnotationView anView;
if (annotation is MKUserLocation)
return null;
// create pin annotation view
anView = (MKPinAnnotationView)mapView.DequeueReusableAnnotation(annotationIdentifier);
if (anView == null)
anView = new MKPinAnnotationView(annotation, annotationIdentifier);
anView.Image = GetImage("pinned_location.png");
anView.CanShowCallout = true;
return anView;
}
public UIImage GetImage(string imageName)
{
var image = UIImage.FromFile(imageName).Scale(new SizeF() { Height = 20, Width = 30 });
return image;
}
Can anyone suggest why I'm not seeing the custom image for the pin?
You should use MKAnnotationView instead of MKPinAnnotationView.
// create annotation view
anView = (MKAnnotationView)mapView.DequeueReusableAnnotation(annotationIdentifier);
if (anView == null)
anView = new MKAnnotationView(annotation, annotationIdentifier);
MKPinAnnotationView is only for default icon.

Monotouch UITableviewCells never destroyed

I have a controller which has a UITableView named WaitTableView. It has only one cell, here is the code of the UITableViewCell class :
public class TableViewWaitCell : UITableViewCell
{
public UIActivityIndicatorView activityIndicator = new UIActivityIndicatorView(UIActivityIndicatorViewStyle.Gray);
public UILabel lblLoading = new UILabel();
public TableViewWaitCell(UITableViewCellStyle style, string reuseIdentifier) : base (style, reuseIdentifier)
{
this.SelectionStyle = UITableViewCellSelectionStyle.None;
}
~TableViewWaitCell(){
System.Console.WriteLine("TableViewWaitCell.~TableViewWaitCell");
lblLoading = null;
activityIndicator = null;
System.GC.Collect();
}
protected override void Dispose (bool disposing){
System.Console.WriteLine("TableViewWaitCell.Dispose");
lblLoading = null;
activityIndicator = null;
base.Dispose (disposing);
GC.Collect();
}
public override void Draw (System.Drawing.RectangleF rect)
{
base.Draw (rect);
var context = UIGraphics.GetCurrentContext();
var gradient = new CGGradient(
CGColorSpace.CreateDeviceRGB(),
new float[] { 1f, 1f, 1f, 1f,
0.68f, 0.68f, 0.72f, 1f },
new float[] { 0f, 1f } );
context.DrawLinearGradient(gradient,
new PointF(rect.X+rect.Width/2, rect.Y),
new PointF(rect.X+rect.Width/2, rect.Y+rect.Height),
CGGradientDrawingOptions.DrawsAfterEndLocation);
var activityIndicatorViewFrame = new RectangleF(rect.X + rect.Width/2-10, rect.Y+10, 20, 20);
this.activityIndicator .Frame = activityIndicatorViewFrame;
this.activityIndicator.AutoresizingMask = UIViewAutoresizing.FlexibleDimensions;
this.activityIndicator.StartAnimating();
this.AddSubview(this.activityIndicator);
var labelFrame = new RectangleF(rect.X, rect.Y+10+activityIndicatorViewFrame.Height, rect.Width, 35);
this.lblLoading.Frame = labelFrame;
this.lblLoading.AutoresizingMask = UIViewAutoresizing.FlexibleDimensions;
this.lblLoading.TextColor = UIColor.Black;
this.lblLoading.BackgroundColor = UIColor.Clear;
this.lblLoading.TextAlignment = UITextAlignment.Center;
this.lblLoading.Text = Dictionary.GetValue("Loading");
this.AddSubview(this.lblLoading);
}
}
here is the ViewWillDisappear method of the main UIViewController :
public override void ViewWillDisappear (bool animated)
{
Console.WriteLine("SpotlightView.ViewWillDisappear");
if(this.PopoverController!=null)
this.PopoverController.Dismiss(true);
this.PopoverController = null;
this.tableView.RemoveFromSuperview();
this.WaitTableView.RemoveFromSuperview();
this.searchBar.RemoveFromSuperview();
this.tableView.Source = null;
this.tableView.Dispose();
this.tableView = null;
this.WaitTableView.Source = null;
this.WaitTableView.Dispose();
this.WaitTableView = null;
this.searchBar.Delegate = null;
this.searchBar.Dispose();
this.searchBar = null;
base.ViewWillDisappear (animated);
}
My problem is that neither the destructor nor the Dispose of my cells got called. When I run heapshot the number of instances of the TableViewWaitCell class grow up has I navigate through my app. I don't understand how the cells life-cycle is managed in Monotouch, what could have I done wrong ?
I do not see anything that would cause this problem in the code you shared. However you do not show how the cell is constructed and stored. Your objects may be kept alive by a root you are not showing in your sample code. I have created a sample below that shows Dispose and the finalizer being called.
Using the simulator the table view and the cell are collected quickly. Usually after a press the of the 'Show Table' button the second time.
Running on the device shows a different behavior. The table and cell are not collected right away. The simplest explanation is that the GC is 'tuned' to only run when it needs to. So if your app is not using much memory, all the objects will continue to live.
There are two things you can do to force the GC to run as shown in the sample.
showTable.TouchUpInside += delegate {
navController.PushViewController (new MyViewController (), true);
// Not a great idea to call Collect but you could to force it.
// GC.Collect ();
};
allocate.TouchUpInside += delegate {
// Trigger the GC by creating a bunch of objects
System.Collections.Generic.List<object> list = new System.Collections.Generic.List <object> ();
for (int i=0; i<2048; i++)
{
list.Add (new object ());
}
};
First you can call GC.Collect. However I do not recommend that. The GC will run best when you let it run when it wants to. (In most cases.) When is it acceptable to call GC.Collect?
Second just keep writing code and let the GC decide what is best. In the sample I added another button that allocates a bunch of objects and adds them to a list. So if you toggle between the table view and the main view a few times then press the allocate button a couple of times you should see the finalizers run.
using System;
using MonoTouch.Foundation;
using MonoTouch.UIKit;
using MonoTouch.CoreGraphics;
using System.Drawing;
namespace delete20130320
{
[Register ("AppDelegate")]
public partial class AppDelegate : UIApplicationDelegate
{
UIWindow window;
public override bool FinishedLaunching (UIApplication app, NSDictionary options)
{
window = new UIWindow (UIScreen.MainScreen.Bounds);
var mainView = new UIViewController ();
var showTable = UIButton.FromType (UIButtonType.RoundedRect);
showTable.Frame = new System.Drawing.RectangleF (10, 10, 150, 35);
showTable.SetTitle ("Show Table", UIControlState.Normal);
var allocate = UIButton.FromType (UIButtonType.RoundedRect);
allocate.Frame = new System.Drawing.RectangleF (10, 55, 150, 35);
allocate.SetTitle ("Allocate", UIControlState.Normal);
mainView.View.BackgroundColor = UIColor.White;
mainView.View.Add (showTable);
mainView.View.Add (allocate);
var navController = new UINavigationController (mainView);
showTable.TouchUpInside += delegate {
navController.PushViewController (new MyViewController (), true);
// Not a great idea to call Collect but you could to force it.
// GC.Collect ();
};
allocate.TouchUpInside += delegate {
// Trigger the GC by creating a bunch of objects
System.Collections.Generic.List<object> list = new System.Collections.Generic.List <object> ();
for (int i=0; i<2048; i++)
{
list.Add (new object ());
}
};
window.RootViewController = navController;
window.MakeKeyAndVisible ();
return true;
}
}
public class MyViewController : UIViewController
{
UITableView _tableView;
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
_tableView = new UITableView (this.View.Bounds);
View.Add (_tableView);
_tableView.DataSource = new MyDataSource ();
}
~MyViewController ()
{
// Bad practice to call other managed objects in finalizer
// But for sample purposes it will be ok
Console.WriteLine ("~MyViewController");
}
protected override void Dispose (bool disposing)
{
// Bad practice to call other managed objects in Dispose
// But for sample purposes it will be ok
Console.WriteLine ("MyViewController.Dispose");
base.Dispose (disposing);
}
class MyDataSource : UITableViewDataSource
{
public override int RowsInSection (UITableView tableView, int section)
{
return 1;
}
public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath)
{
var cell = tableView.DequeueReusableCell ("SomeUniqueString");
if (cell != null)
return cell;
return new TableViewWaitCell (UITableViewCellStyle.Default, "SomeUniqueString");
}
}
}
public class TableViewWaitCell : UITableViewCell
{
public TableViewWaitCell(UITableViewCellStyle style, string reuseIdentifier) : base (style, reuseIdentifier)
{
this.SelectionStyle = UITableViewCellSelectionStyle.None;
this.TextLabel.Text = "Something";
}
~TableViewWaitCell()
{
// Bad practice to call other managed objects in finalizer
// But for sample purposes it will be ok
System.Console.WriteLine("TableViewWaitCell.~TableViewWaitCell");
// Avoid forcing the GC
//System.GC.Collect();
}
protected override void Dispose (bool disposing)
{
// Bad practice to call other managed objects in Dispose
// But for sample purposes it will be ok
System.Console.WriteLine("TableViewWaitCell.Dispose");
base.Dispose (disposing);
//GC.Collect();
}
}
}
The problem came from an EventHandler which referenced a method of my view controller thus prenventing the collection of my cells and my controller.

Resources