Disabled button in UITableViewCell Xamarin.iOs - xamarin

I have UITableView and each cell has button and text. Actual cell shouldn't be clickable, but button should. When I run in simulator I can hit button, but when deploy to device it is disabled. (edited)
Tried to follow many different steps to fix it from this post Button in UITableViewCell not responding under ios 7, such as ContentView.UserInteractionEnabled = False; for cell.
All of those for native iOS, maybe for Xamarin there are something else?
Any ideas what am I missing?
UITableViewCell code
this.DelayBind(() =>
{
var set = this.CreateBindingSet<CalendarCell, ActivityModel>();
set.Bind(CustomerNameLabel).To(a => a.Subject);
set.Bind(MarkAsMeetingButton).For(a => a.Hidden).WithConversion("ActivityTypeToVisibility");
set.Bind(MarkAsMeetingButton).To(a => a.SendMessageToViewModelCommand).CommandParameter(BindingContext);
set.Apply();
});
ContentView.UserInteractionEnabled = false;
UItableView code
public override void ViewDidLoad()
{
base.ViewDidLoad();
var source = new TableSource(CalendarList);
var set = this.CreateBindingSet<CalendarView, CalendarViewModel>();
set.Bind(source).To(vm => vm.CalendarList);
set.Apply();
CalendarList.Source = source;
CalendarList.ReloadData();
}
public class TableSource : MvxSimpleTableViewSource
{
private List<IGrouping<DateTime, ActivityModel>> GroupedCalendarList = new List<IGrouping<DateTime, ActivityModel>>();
public TableSource(UITableView calendarView) : base(calendarView, "CalendarCell", "CalendarCell")
{}
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
{
var currentSection = GroupedCalendarList[indexPath.Section].ToList();
var item = currentSection[indexPath.Row];
var cell = GetOrCreateCellFor(tableView, indexPath, item);
var bindable = cell as IMvxDataConsumer;
if (bindable != null)
bindable.DataContext = item;
return cell;
}
public override nint RowsInSection(UITableView tableview, nint section)
{
return (nint)GroupedCalendarList[(int)section].Count();
}
public override UIView GetViewForHeader(UITableView tableView, nint section)
{
var label = new UILabel();
var currentDate = DateTime.Now;
var titleText = GroupedCalendarList[(int)section].FirstOrDefault().ScheduledStart.Value.Date;
return label;
}
public override nint NumberOfSections(UITableView tableView)
{
return (nint)GroupedCalendarList.Count;
}
public override void ReloadTableData()
{
if (ItemsSource == null) return;
var groupedCalendarList = (ItemsSource as List<ActivityModel>).GroupBy(cl => cl.ScheduledStart.Value.Date).ToList();
GroupedCalendarList = new List<IGrouping<DateTime, ActivityModel>>(groupedCalendarList);
base.ReloadTableData();
}
}
This code works absolutely fine for simulator, but doesn't work on device, UIButton in each cell is disabled.

As #Luke said in the comments of your question said, do not use ContentView.UserInteractionEnabled = false;, since this disables any touch events on the whole view of your cell.
To achieve what you need, implement the UITableViewDelegate method ShouldHighlightRow and return false:
public override bool ShouldHighlightRow(UITableView tableView, NSIndexPath rowIndexPath) {
return false;
}
Then, the cells will not get highlighted on tap, and the RowSelected method will not get called, but your button will be clickable!

Related

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

How to raise event when UITextField inside the UIView in Xamarin.Forms?

I have tried to raise event for UITextfield when inside the UIView.
UIView view = new UIView();
UITextField textfield = new UITextField();
textField.TouchDown += textfield_TouchDown;
view.AddSubView(textfield);
// event not raised.
void textfield_TouchDown(object s, EventArgs args)
{
// your code here
}
I have tried to add the gesture, but it is not working.
Please refer the below code
UITextField textfield = new UITextField();
UITapGestureRecognizer gestureRecognizer = new UITapGestureRecognizer();
gestureRecognizer.NumberOfTapsRequired = 1;
if (textfield != null)
{
textfield.UserInteractionEnabled = true;
gestureRecognizer.AddTarget(() =>
{
// click event for lable/image
OnTapped(gestureRecognizer);
});
textfield.AddGestureRecognizer(gestureRecognizer);
}
private void OnTapped(UITapGestureRecognizer gestureRecognizer)
{
//your code here
}
Please suggest your idea on this this?
Regards,
Srinivasan

Centering a UIView within a UIScrollView Xamarin.iOS

I am currently trying to center a UIIView inside of a UIScrollView and am having some difficulty in doing so.
Here is the image of my current view:
Here is the code snippet I'm working with:
public void AddView(UIViewController viewCont)
{
this.AddChildViewController(viewCont);
this.mainScrollView.AddSubview(viewCont.View);
viewCont.DidMoveToParentViewController(this);
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
// Perform any additional setup after loading the view, typically from a nib.
var m = new Menu();
//var c = new Camera();
AddView(m);
AddView(c);
CGRect cFrame = c.View.Frame;
cFrame.X = this.View.Frame.Width;
c.View.Frame = cFrame;
this.mainScrollView.ContentSize = new CGSize(this.View.Frame.Width * 2, 1.0);
}
I want to fill this whole view with Green but as you can see, the bottom quarter of the View does not stretch all the way to the bottom.
For the time being, I have removed all constraints because every attempt in adding them results in no successes. I was hoping to get a concrete answer here as to how I could go about centering this view within this UIScrollView.
Thanks
UPDATE: 3-21-2017
My main goal is to have 2 ViewControllers side by side within my UIScrollView that I can navigate to and from using a swipe gesture, like SnapChat. Following, #Digitalsa1nt suggestions, I unfortunately come up with the same issue.
Here are some more pictures:
This first one shows what happens when I only add the 1 view:
This next one shows what happens when I try to add both views to my UIScrollView, only the camera shows:
Finally, here is the code that I am using to back my Camera view:
using Foundation;
using UIKit;
using AVFoundation;
namespace BRB.iOS
{
public partial class Camera : UIViewController
{
AVCaptureSession captureSession;
AVCaptureStillImageOutput stillImageOutput;
AVCaptureVideoPreviewLayer previewLayer;
public Camera() : base("Camera", null)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
// Perform any additional setup after loading the view, typically from a nib.
}
public override void DidReceiveMemoryWarning()
{
base.DidReceiveMemoryWarning();
// Release any cached data, images, etc that aren't in use
}
public override void ViewDidAppear(bool animated)
{
base.ViewDidAppear(animated);
previewLayer.Frame = cameraView.Bounds;
}
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
captureSession = new AVCaptureSession();
captureSession.SessionPreset = AVCaptureSession.Preset1920x1080;
var backCamera = AVCaptureDevice.GetDefaultDevice(AVMediaType.Video);
NSError error;
var input = AVCaptureDeviceInput.FromDevice(backCamera, out error);
if (error == null && captureSession.CanAddInput(input))
{
captureSession.AddInput(input);
stillImageOutput = new AVCaptureStillImageOutput();
stillImageOutput.OutputSettings = new NSDictionary(AVVideo.CodecKey, AVVideo.CodecJPEG);
if (captureSession.CanAddOutput(stillImageOutput))
{
captureSession.AddOutput(stillImageOutput);
previewLayer = new AVCaptureVideoPreviewLayer(captureSession);
previewLayer.VideoGravity = AVLayerVideoGravity.ResizeAspect;
previewLayer.Connection.VideoOrientation = AVCaptureVideoOrientation.Portrait;
cameraView.Layer.AddSublayer(previewLayer);
captureSession.StartRunning();
}
}
}
}
}
I usually override 'didlayoutsubviews' for making changes to the views within my UIScrollViews. The below worked for me.
public void AddView(UIViewController viewCont)
{
AddChildViewController(viewCont);
mainScrollView.AddSubview(viewCont.View);
viewCont.DidMoveToParentViewController(this);
}
public override void ViewDidLayoutSubviews()
{
base.ViewDidLayoutSubviews();
// Ensure our contentinsets are 0 so we don't have any blank space
mainScrollView.ContentInset = new UIEdgeInsets(0, 0, 0, 0);
// set the contentsize to the bounds of the container view within.
mainScrollView.ContentSize = View.Bounds.Size;
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
// Perform any additional setup after loading the view, typically from a nib.
var m = new Menu();
//var c = new Camera();
AddView(m);
AddView(c);
}

Xamarin forms block user back key press

In my Xamarin forms application I want to show a confirmation message when user clicks the back button from Main-page. Is there any way to achieve this?
I overrided the OnBackButtonPressed method in my MainPage. But still the app is closing while back key press. Here is my code
protected override bool OnBackButtonPressed ()
{
//return base.OnBackButtonPressed ();
return false;
}
You can override OnBackButtonPressed for any Xamarin.Form Page. But it only will work for the physical button in Android and Windows Phone devices.
protected override bool OnBackButtonPressed () {
DisplayAlert("title","message","ok");
return true;
}
For the virtual one, you will need to create CustomRenderers and to intercept the click handler. In iOS it can be tricky because the user can go back doing other actions (e.g. the swipe gesture). Once you intercept it you just need to create your Confirmation Message (which I assume that you know how to do it).
For iOS you can do something like this:
[assembly: ExportRenderer (typeof (YourPage), typeof (YourPageRenderer))]
namespace YourNamespace {
public class YourPageRenderer : PageRenderer {
public override void ViewWillAppear (bool animated) {
base.ViewWillAppear (animated);
Action goBack = () => page.DisplayAlert("title","message","ok");
var backButton = new NavBackButton (goBack);
navigationItem.LeftBarButtonItem = new UIBarButtonItem (backButton);
}
}
public class NavBackButton : UIView {
public NavBackButton (Action onButtonPressed) {
SetButton (onButtonPressed);
}
UILabel text;
UIImageView arrow;
void SetButton(Action onButtonPressed){
arrow = new UIImageView(new CGRect(-25,0, 50, 50)) {
Image = new UIImage("Images/back").ImageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate)
};
arrow.TintColor = Colors.DarkGreen.ToUIColor ();
text = new UILabel(new CGRect(arrow.Frame.Width + arrow.Frame.X -15, arrow.Frame.Height /2 - 10, 40, 20)) { Text = "Back" };
text.TextColor = Colors.DarkGreen.ToUIColor ();
Frame = new CGRect(0,0,text.Frame.Size.Width + arrow.Frame.Width, arrow.Frame.Height);
AddSubviews (new UIView[] { arrow, text });
var tapGesture = new UITapGestureRecognizer (onButtonPressed);
AddGestureRecognizer (tapGesture);
}
public override void TouchesBegan (Foundation.NSSet touches, UIEvent evt) {
base.TouchesBegan (touches, evt);
text.TextColor = UIColor.YourColor;
arrow.TintColor = UIColor.YourColor;
}
public override void TouchesEnded (Foundation.NSSet touches, UIEvent evt){
base.TouchesEnded (touches, evt);
arrow.TintColor = UIColor.YourColor;
text.TextColor = UIColor.YourColor;
}
}
}
PS You will need to provide an arrow image ("Images/back")

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