As I said in other questions I am very newbie in .Net technologies. Now I am starting with an exercise using the Xamarin Crossplatform (Xamarin.IOS for the moment). But I have a problem with this code, it's working, but the method ForceUpdateSize() crashes the application if I have more than 6 elements in the table?. When I remove the method the app works fine, but then the text is overlapped.
This is the method that is filling my table:
public sealed class AutoViewCellRenderer : ViewCellRenderer
{
public override UITableViewCell GetCell(Cell item, UITableViewCell reusableCell, UITableView tv)
{
ViewCell vc = item as ViewCell;
if (vc != null)
{
var sr = vc.View.Measure(tv.Frame.Width, double.PositiveInfinity, MeasureFlags.IncludeMargins);
if (vc.Height != sr.Request.Height)
{
sr = vc.View.Measure(tv.Frame.Width, double.PositiveInfinity, MeasureFlags.IncludeMargins);
vc.Height = sr.Request.Height;
//vc.ForceUpdateSize(); --> Text ovelpping effect if I remove this method, but the app doesn't crash
}
}
return GetCell(item, reusableCell, tv);
}
}
Can someone help me to understand what I am doing wrong?
Thanks in advance to everybody!
Try this:
//vc.ForceUpdateSize(); --> Text ovelpping effect if I remove this method, but the app doesn't crash
tv.BeginUpdates();
tv.EndUpdates();
I have found this in an older post, but sadly I can't find it anymore.
Also take a look at this: https://github.com/xamarin/Xamarin.Forms/issues/4012
I think the Xamarin Community knows well about this and is addressing it in this Thread.
Related
I'm writing a custom render for Xamarins UITableView that allows drag and drop.
I implemented the UITableViewDragDelegate and DropDelegate.
When I use UIDropOperation.Copy in the DropSessionDidUpdate function all is working fine as PerformDrop is called afterwards. The problem is Copy adds this nasty + icon to the cell while dragging which I don't want to have.
If I return UIDropOperation.Move then the + icon is gone. However PerformDrop is not called then anymore.
I found several hints to what could be wrong, for example do a clean:
UIDropInteractionDelegate performDrop not called?
In the same post one answer explains that Move can only be used if session.allowsMove is true.
So I implemented the CanMoveRow in my DataSource:
public override bool CanMoveRow(UITableView tableView, NSIndexPath indexPath)
{
return true;
}
but that didn't fix the issue. At least I can confirm that session.allowsMove is always true now.
In another thread (where I lost the link) for MacOS it was said that perform drop doesn't work if NSItemProvider of the UIDragItem doesn't have an object associated to. So I Implemented a wrapper for my Forms object and assigned it to the NSItemProvider:
public override UIDragItem[] GetItemsForBeginningDragSession(UITableView tableView, IUIDragSession session, NSIndexPath indexPath)
{
if (list.ItemsSource is IList source) //List is the instance of the Xamarin Forms ListView
{ //Check is always fulfilled
var item = NSObjectWrapper.Wrap(source[indexPath.Row]);
var itemProvider = new NSItemProvider(item, item.GetType().ToString());
var dragItem = new UIDragItem(itemProvider);
dragItem.LocalObject = item;
return new UIDragItem[] { dragItem };
}
return null;
}
but, still no change.
What am I doing wrong, how can I get move to perform a drop or how can I get rid of the + icon in the copy view?
Thank you!
We have updated out Swift 2.3 project to Swift 3 recently using Xcode 8.2.1 (8C1002), and now most of our UI Tests related with tableViews and the isSelected property aren't working. It's always returning false, even when the object is selected (we can see it in the iOS Simulator).
Has anyone experienced similar issues? Our code used to work normally in Swift 2.3 before the conversion. Here is how we retrieve a tableView cell:
let cell = app.tables.cells.element(at: 4)
Note: app is a XCUIApplication.
And here is how we check if it's selected or not:
XCTAssert(cell.isSelected)
Another observation is that we are sure that the object exists because waitForExpectations is returning true:
let existsPredicate = NSPredicate(format: "exists = 1")
expectation(for: existsPredicate, evaluatedWith: cell, handler: nil)
waitForExpectations(timeout: 20, handler: nil)
EDIT: In order to replace isSelected, I've tried to use NSPredicate with selected = 1 and with isSelected = 1. None worked. I also tried to use acessibilityValue based in other question's answer, however it wasn't that simple since sometimes the items in my table view are selected/unselected programatically. Also, that method involved adding test code to the app, which isn't a good practice.
EDIT AFTER BOUNTY END: Since no one could find a solution for that problem and that's obviously a bug in Xcode, I've submitted a bug report to Apple. I will comment here when they release an Xcode version with the fix.
EXTRA EDIT: One day after my last edit, dzoanb came with a functional answer.
I made a few tests and a little research. You can check out the app created for this purpose >>here<<. It would be great if you could check it out (it required a little bit of work). There are also UI tests to prove it works. Also, two options are available, one is vanilla XCTest and one library with a lot of helpers I'm creating with my colleagues AutoMate. But that's not the point.
Here is what I found out:
1) isSelected property of XCUIElement depends on accessibilityTrait. Element to be selected in XCTest has to have UIAccessibilityTraitSelected set.
2) I couldn't reproduce Your problem but I was able to control isSelected property.
3) Yes, it requires a little bit of code, but should work well with VoiceOver if it is important for You.
All necessary code is in Your custom UITableViewCell subclass. And uses overriding UIAccessibilityElement accessibilityTraits property.
private var traits: UIAccessibilityTraits = UIAccessibilityTraitNone
// MARK: UITableViewCell life cycle
override func awakeFromNib() {
super.awakeFromNib()
traits = super.accessibilityTraits
}
// MARK: UIAccessibilityElement
override var accessibilityTraits: UIAccessibilityTraits {
get {
if isSelected {
return traits | UIAccessibilityTraitSelected
}
return traits
}
set {
traits = newValue
}
}
Hope it helps.
Couldn't get that code to compile under Swift 4.
This worked for me.
public override var accessibilityTraits: UIAccessibilityTraits {
get {
if isSelected {
return super.accessibilityTraits.union(.selected)
}
return super.accessibilityTraits
}
set {
super.accessibilityTraits = newValue
}
}
Have you tried making a break point before and after the tap, and check the value of the cell? Like the WWDC video here: https://youtu.be/7zMGf-0OnoU
(See from 10 minutes in)
isSelected only works on views which inherit from UIControl. UIControl.isSelected informs XCUIElement.isSelected.
Since UITableViewCell does not inherit from UIControl, you aren't seeing the value you want in your tests when you observe cell.isSelected.
I suggest that if you want this to be testable via UI tests that you file a feature request with Apple to make UIControl a protocol, which you could then extend your cells to conform to, or add UITableViewCell.isSelected to the properties that inform XCUIElement.isSelected.
#dzoanb solution can work without adding a private var:
override var accessibilityTraits: UIAccessibilityTraits {
get {
if isSelected {
return super.accessibilityTraits | UIAccessibilityTraitSelected
}
return super.accessibilityTraits
}
set {
super.accessibilityTraits = newValue
}
}
I'm using a ViewModel first approach to Xamarin.Forms and have begun the process of writing my own Bindable TableView (I imagine plenty of people have). The project is going well and I'm already rendering cells in the UI based on my on CellViewModel types and wanted to move to the next phase of adding the 'effects' of things like 'Disclosure' and 'Checkbox' accessories to cells. It transpires that these things only really make sense in iOS projects so I found myself looking into ViewCellRenderers specifically in iOS.
In order to apply the appropriate accessory on the cell, I needed to create a class to do so:
public class AccessoryItemCellRenderer : ViewCellRenderer
which itself is relatively simple. It takes the BindingContext of the Xamarin Cell and then applies the accessory as appropriate:
var viewModel = item.BindingContext as TableCellViewModel;
if (viewModel != null )
{
UITableViewCell cell = base.GetCell(item, reusableCell, tv);
if (viewModel.Accessories == CellIndicators.Disclosure)
cell.Accessory = UIKit.UITableViewCellAccessory.DisclosureIndicator;
else if (viewModel.Accessories == CellIndicators.DisclosureDetail)
cell.Accessory = UIKit.UITableViewCellAccessory.DetailDisclosureButton;
else if (viewModel.Accessories == CellIndicators.Detail)
cell.Accessory = UIKit.UITableViewCellAccessory.DetailButton;
else if (viewModel.Accessories == CellIndicators.CheckMark)
cell.Accessory = UIKit.UITableViewCellAccessory.Checkmark;
}
At least I thought it was straightforward, as when the base.GetCell call gets made, it turns out my reusableCell property is null and this I am assuming is causing a System.InvalidCastException which then blow up. It's not very obvious to me what is causing it, the only real stack trace I get is this:
at Xamarin.Forms.Platform.iOS.ViewCellRenderer.GetCell (Xamarin.Forms.Cell item, UIKit.UITableViewCell reusableCell, UIKit.UITableView tv) [0x00000] in C:\BuildAgent2\work\aad494dc9bc9783\Xamarin.Forms.Platform.iOS\Cells\ViewCellRenderer.cs:28
Is it because somehow my cell doesn't have a reusable id? How do I provide one if this is the case? Any help would be greatly appreciated.
In the comments you mention that you're registering this AccessoryItemCellRenderer for TextCell. In the AccessoryItemCellRenderer you are inheriting from ViewCellRenderer which is for ViewCell. TextCell does not inherit from ViewCell and cannot be cast as a ViewCell and that's most likely where the exception is coming from.
I'm using MvvmCross with UICollectionView.
Bindings work perfectly, I have all my data properly displayed, and even if I select an item in CollectionView it gets properly set in my ViewModel.
For SelectedItem I use the following binding:
set.Bind(_collectionViewSource).For(x => x.SelectedItem).To(vm => vm.SelectedMachine);
The only problem I have is that I want a first CollectionViewItem to be selected initially.
As the sources of MvvmCross say that's not supported currently (in the setter for SelectedItem):
// note that we only expect this to be called from the control/Table
// we don't have any multi-select or any scroll into view functionality here
So, what's the best way to perform initial pre-selection of an item? What's the place I can call _collectionView.SelectItem from?
I tried calling it when collection changes, but that doesn't seem to work.
If you need this functionality, you should be able to inherit from MvxCollectionViewSource and to add a property something like
public event EventHandler SelectedItemExChanged;
public object SelectedItemEx
{
get { return base.SelectedItem; }
set
{
base.SelectedItem = value;
var index = FindIndexPath(value); // find the NSIndexPath of value in the collection
if (index != null)
_collectionView.SelectItem(index, true, UICollectionViewScrollPosition.CenteredHorizontally);
var handler = SelectedItemExChanged;
if (handler != null)
handler(this, EventArgs.Empty);
}
}
That can then be bound instead of SelectedItem
What's the place I can call _collectionView.SelectItem from? I tried calling it when collection changes, but that doesn't seem to work.
If that doesn't work, then I'm not sure - you are probably heading into animation timing problems - see questions like uicollectionview select an item immediately after reloaddata? - maybe try editing your question to post a bit more of your code - something that people can more easily hope with debugging.
In my Monotouch application for the iPhone, I'm trying to add a segmented controller to a UITableViewCell to act as a toggle for a value in each row. Everything displays correctly, and the first rows will initially work correctly when clicked. However, after the row has left the screen and is reloaded, if I clicked the segmented controller again I receive an exception:
Terminating runtime due to unhandled exception
[ERROR] FATAL UNHANDLED EXCEPTION: System.Exception: Selector invoked from objective-c on a managed object of type MonoTouch.UIKit.UIControlEventProxy (0x16D793C0) that has been GC'ed ---> System.MissingMethodException: No constructor found for MonoTouch.UIKit.UIControlEventProxy::.ctor(System.IntPtr)
I have heard of similar issues with people using Monotouch try to put buttons in table cells. The general recommendation has been to add each table cell to a List to prevent it from being garbage collected, but this has not worked for me.
Is there a solution to get around this problem?
Here is a somewhat simplified version of my GetCell code:
public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath)
{
Hashtable rowData = dataList [indexPath.Row];
FilterListToggleCell cell;
cell = (FilterListToggleCell)tableView.DequeueReusableCell ("FilterTogglePlusCell");
cacheList.Add(cell.Toggle);//cache only the segcontrol
//cacheList.Add(cell);//also previously tried caching the whole cell
cell.Title.Text = (string)rowData["title"];
if(additionalFields.Contains((string)rowData["key"])){
cell.Toggle.SelectedSegment = 0;
} else {
cell.Toggle.SelectedSegment = 1;
}
cell.Toggle.ValueChanged += (sender, e) => {
UISegmentedControl toggle = (UISegmentedControl)sender;
if(toggle.SelectedSegment == 0){
Console.WriteLine("Toggle YES");
} else {
Console.WriteLine("Toggle NO");
}
};
cell.Toggle.Tag = indexPath.Row;
return cell;
}
You're right about the basic condition. This condition happens because there's no managed reference to the UITableViewCell that you return from the GetCell method. As such the GC can collect and free it (and that will cause issues for things like events that wants to call back to the managed instance). See this answer for more details.
Now I'm not sure why this approach did not work for you. However your code above seems incomplete, e.g.
cell = (FilterListToggleCell)tableView.DequeueReusableCell ("FilterTogglePlusCell");
can return null which is when you should be creating more cells (in fact there's no condition where you actually create any new cell, so there's nothing that can be re-used).
Your next line will throw a NullReferenceException when cell is null (because of cell.Toggle).
Note that this does not explain your crash - I suspect there's some missing code that make it works (create cells) and that might not cache them correctly.
I was able to get this working without needing the extra cache based on information from this question: How do I remove/unregister event handler from an event?
The ValueChanged line was replaced with:
cell.Toggle.AddTarget(this, new Selector("ToggleChange"),UIControlEvent.ValueChanged);
and another method was added to my UITableViewDataSource
[Export ("ToggleChange")]
void OnChange(UISementedControl toggle){
if(toggle.SelectedSegment == 0){
Console.WriteLine("Toggle YES");
} else {
Console.WriteLine("Toggle NO");
}
}