How to insert new cell at first element in UICollectionView in Xamarin? - xamarin

I want to make horizontal collection and add new cell at the first position.
I make custom UICollectionViewFlowLayout and override
LayoutAttributesForElementsInRect like this (to see reverse order of items):
public override UICollectionViewLayoutAttributes[] LayoutAttributesForElementsInRect(CGRect rect)
{
var attributes = new UICollectionViewLayoutAttributes[cellCount];
NSIndexPath indexPath;
// Setting first position
int shift = 25, reverseXCenter;
for (int i = 0; i < cellCount; i++)
{
indexPath = NSIndexPath.FromItemSection(i, 0);
attributes[i] = LayoutAttributesForItem(indexPath);
// We have to make opposite order
reverseXCenter = (cellCount -1 - i) * 50;
attributes[i].Center = new CGPoint(reverseXCenter + shift, attributes[i].Center.Y);
}
return attributes;
}
I think is bad solution, because when I have more than 5 cells, the displayed collection is filled and I can't scroll.
I had debugger in GetOrCreateCellFor in CollectionViewSource
and indexPath.Item's had value for example (10,9,8,7,6...), where 10 is the last I added.
Maybe is another way to do this, or just I miss something?

Ok I am gonna help you out with a flow .
Suppose you are in a ViewController called "A" which will present a controller ViewController called "B"
Inside A use this function to push B
Public void PushNewHorizontalCollectionController(){
using (var vc =
new B(this.PageViewControllerLayout(), indexPath))
{
this.controller.PushViewController(pageViewController, true);
}
}
public UICollectionViewFlowLayout PageViewControllerLayout()
{
var flowLayout = new UICollectionViewFlowLayout();
var itemSize = this.controller.NavigationBarHidden ? new CGSize(Constants.ScreenWidth, Constants.ScreenHeight + 20) : new CGSize(Constants.ScreenWidth, Constants.ScreenHeight - Constants.NavigationHeaderAndStatusbarHeight);
flowLayout.ItemSize = itemSize;
flowLayout.MinimumLineSpacing = 0;
flowLayout.MinimumInteritemSpacing = 0;
flowLayout.ScrollDirection = UICollectionViewScrollDirection.Horizontal;
return flowLayout;
}
Here itemSize is CGSize which u can assign what size you can ignore my constants which are self explainable .
Inside B your constructor should look like this .
public HorizontalViewController(UICollectionViewLayout layout, NSIndexPath indexpath) : base(layout)
{
Do your initialisation like registering UICollectionViewCell class nibs for use with this Controller
}
Handling real requirement from comment .
You wanna add something always as 1st element in collectionView ,Go to this method which should look like below
public override UICollectionViewCell GetCell(UICollectionView collectionView, NSIndexPath indexPath)
{
Console.WriteLine("getcellHori");
var isQuoteType = Photo.PhotoCollection[(int)indexPath.Item].Type;
if (isQuoteType == 3 || isQuoteType == 4)
{
var item = Photo.PhotoCollection[(int)indexPath.Item];
var cell = (HorizontalviewForQuoteType)collectionView.DequeueReusableCell("HorizontalviewForQuoteType", indexPath);
cell.PopulateCell(item.Caption,item.TextLabel,item.Likes,item.TimeLabel,item.Id);
HorizontalviewForQuoteType.pullAction = (offset) =>
{
this.pullOffset = offset;
this.NavigationController.PopViewController(true);
};
cell.SetNeedsLayout();
return cell;
}
Here you this line
var item = Photo.PhotoCollection[(int)indexPath.Item];
this is the data source , so whenever you adding something at index first and everything will be solved .

Related

Can't Trigger MouseEntered on NSButton in Xamarin.Mac

I'm trying to programmatically add a MouseEntered event to a custom NSButton class, and I can't seem to get it to fire. I'm writing a Mac OS application in Visual Studio for Mac, using Xamarin.Mac. I need to add the event in code because I'm creating the buttons dynamically.
Here's my ViewController where these buttons are being created. They're instantiated in the DrawHexMap method near the bottom.
public partial class MapController : NSViewController
{
public MapController (IntPtr handle) : base (handle)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
DrawHexMap();
}
partial void GoToHex(Foundation.NSObject sender)
{
string[] coordinateStrings = ((NSButton)sender).Title.Split(',');
Hex chosenHex = HexRepo.GetHex(coordinateStrings[0], coordinateStrings[1]);
HexService.currentHex = chosenHex;
NSTabViewController tp = (NSTabViewController)ParentViewController;
tp.TabView.SelectAt(1);
}
private void DrawHexMap()
{
double height = 60;
for (int x = 0; x < 17; x++) {
for (int y = 0; y < 13; y++) {
HexButton button = new HexButton(x, y, height);
var handle = ObjCRuntime.Selector.GetHandle("GoToHex:");
button.Action = ObjCRuntime.Selector.FromHandle(handle);
button.Target = this;
View.AddSubview(button);
}
}
}
}
And here's the custom button class.
public class HexButton : NSButton
{
public NSTrackingArea _trackingArea;
public HexButton(int x, int y, double height)
{
double width = height/Math.Sqrt(3);
double doubleWidth = width * 2;
double halfHeight = height/2;
double columnNudge = decimal.Remainder(x, 2) == 0 ? 0 : halfHeight;
Title = x + "," + y;
//Bordered = false;
//ShowsBorderOnlyWhileMouseInside = true;
SetFrameSize(new CGSize(width, height));
SetFrameOrigin(new CGPoint(width + (x * doubleWidth), (y * height) + columnNudge));
_trackingArea = new NSTrackingArea(Frame, NSTrackingAreaOptions.ActiveInKeyWindow | NSTrackingAreaOptions.MouseEnteredAndExited, this, null);
AddTrackingArea(_trackingArea);
}
public override void MouseEntered(NSEvent theEvent)
{
base.MouseEntered(theEvent);
Console.WriteLine("mouse enter");
}
}
So as you can see, I'm creating a tracking area for the button and adding it in the constructor. Yet I can't seem to get a MouseEntered to fire. I know the MouseEntered override in this class works, because when I call button.MouseEntered() directly from my code, the method fires.
A few other things I've tried include: Commenting out the lines that set the Action and Target in the ViewController, in case those were overriding the MouseEntered handler somehow. Setting those values inside the HexButton constructor so that the Target was the button instead of the ViewController. Putting the MouseEntered override in the ViewController instead of the button class. Creating the tracking area after the button was added as a subview to the ViewController. None of these made a difference.
Any help would be much appreciated! It's quite difficult to find documentation for Xamarin.Mac...
Thanks!
You are adding the Frame as the region tracked, but you are attaching the tracking area to the view that you are creating the region from, thus you need to track the Bounds coords.
_trackingArea = new NSTrackingArea(Bounds, NSTrackingAreaOptions.ActiveInKeyWindow | NSTrackingAreaOptions.MouseEnteredAndExited, this, null);
Note: If you were tracking from the view that you are adding the buttons to, then you would need to track the "Frame" as it is the tracking region is relative to the view being tracked, not its children.

Xamarin.iOS - Multiple XIBs in a single UITableView

I am trying to load different nibs into a single UITableView depending on a variable within a model. I seem to have something that looks logical and doesn't crash, however, only 1 of the xibs is being loaded and displayed.
Controller Method:
private void populateTableData()
{
liveTipsTableView.RegisterNibForCellReuse(UINib.FromName("LiveTipCell_", null), "LiveTipCell_");
liveTipsTableView.RegisterNibForCellReuse(UINib.FromName("NewsCell_", null), "NewsCell_");
setListViewSource();
Refresh();
AddRefreshControl();
Add(liveTipsTableView);
liveTipsTableView.Add(RefreshControl);
}
TableSource Method
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
{
var localTip = _tips[indexPath.Row];
if (localTip.NewsItem)
{
CellIdentifier = new NSString("NewsCell_");
NewsCell_ cell = new NewsCell_();
cell = tableView.DequeueReusableCell("NewsCell_") as NewsCell_;
var views = NSBundle.MainBundle.LoadNib("NewsCell_", cell, null);
cell = ObjCRuntime.Runtime.GetNSObject(views.ValueAt(0)) as NewsCell_;
cell.SelectionStyle = UITableViewCellSelectionStyle.None;
cell.BindDataToCell(localTip);
return cell;
}
else
{
CellIdentifier = new NSString("LiveTipCell_");
LiveTipCell_ cell = new LiveTipCell_();
cell = tableView.DequeueReusableCell("LiveTipCell_") as LiveTipCell_;
var views = NSBundle.MainBundle.LoadNib("LiveTipCell_", cell, null);
cell = ObjCRuntime.Runtime.GetNSObject(views.ValueAt(0)) as LiveTipCell_;
cell.SelectionStyle = UITableViewCellSelectionStyle.None;
cell.BindDataToCell(localTip);
return cell;
}
}
I have 2 separate Xib files and they have their own classes which work to populate the views. They all seem to getting used when debugging, it's just a case of the table view is only displaying 1 of these items.
Thanks in advance and please let me know if you need more information to see what's going on.
This is in objective C, but it shouldn't be too hard to convert.
SO Post on a different question

How to drag and drop rows in NSTableView in Mac

I have struck with one point i.e now I have got a list of data using NSTableView but what my requirement is, able to drag and drop that rows from one row position to another row position. please give any suggestion for get out this problem. Thanks in advance.
My sample code
Within your NSTableViewDataSource subclass implement WriteRows, ValidateDrop and AcceptDrop and register which Drag/Drop targets your NSTableView accepts. In this case you are only accepting Drops from within your own NSTableView.
Assign a name that will be used for valid drag operations on this NSTableView:
// Any name can be registered, I find using the class name
// of the items in the datasource is cleaner than a const string
string DragDropType = typeof(Product).FullName;
Register the drag types for your NSTableView:
ProductTable.RegisterForDraggedTypes(new string[] { DragDropType });
Implement drag/drop methods on your NSTableViewDataSource:
public override bool WriteRows(NSTableView tableView, NSIndexSet rowIndexes, NSPasteboard pboard)
{
var data = NSKeyedArchiver.ArchivedDataWithRootObject(rowIndexes);
pboard.DeclareTypes(new string[] { DragDropType }, this);
pboard.SetDataForType(data, DragDropType);
return true;
}
public override NSDragOperation ValidateDrop(NSTableView tableView, NSDraggingInfo info, nint row, NSTableViewDropOperation dropOperation)
{
tableView.SetDropRowDropOperation(row, dropOperation);
return NSDragOperation.Move;
}
public override bool AcceptDrop(NSTableView tableView, NSDraggingInfo info, nint row, NSTableViewDropOperation dropOperation)
{
var rowData = info.DraggingPasteboard.GetDataForType(DragDropType);
if (rowData == null)
return false;
var dataArray = NSKeyedUnarchiver.UnarchiveObject(rowData) as NSIndexSet;
Console.WriteLine($"{dataArray}");
// Move hack for this example... you need to handle the complete NSIndexSet
tableView.BeginUpdates();
var tmpProduct = Products[(int)dataArray.FirstIndex];
Products.RemoveAt((int)dataArray.FirstIndex);
if (Products.Count == row - 1)
Products.Insert((int)row - 1 , tmpProduct);
else
Products.Insert((int)row, tmpProduct);
tableView.ReloadData();
tableView.EndUpdates();
return true;
}

Flex 4.6 Optimizing View appearance ContentCache VS override data for Mobile App

I read in this article http://www.adobe.com/devnet/flex/articles/flex-mobile-performance-checklist.html that I should not initialize a View's appearance in a creationComplete handler. Instead, I should change view's appearance in an overridden data setter.
The section in the article is:
Override the data setter instead of using bindings or initializing a View's appearance in a creationComplete handler
1-First, I would like to know if I got this right by doing the following:
//My code is loading a set of images and adding them in a View.
//On creationComplete of the View I am adding the images in case this is the first time
//the view is shown. In case the view has been already accessed I use the data:
protected function view1_creationCompleteHandler(event:FlexEvent):void
{
if(!data) //On first creation of the view I create the data object
{
data = new Object();
data.imageArray = new Array(); //set an array that will cache my images.
for(var i:int = 0; i<36;i++)
{
var img:Image = new Image();
img.source = 'assets/0'+i.toString()+'.png';
container.addElement(img);
(data.imageArray as Array).push(img);//Override the data for next time!
}
}
else//Next time use the save images
{
for(var ix:int = 0; ix<(data.imageArray as Array).length;ix++)
{
container.addElement((data.imageArray as Array)[ix]);
}
}
}
If I am doing this correctly, I would like to know which approach is best. The above one, or the next one I am going to show which uses the images contentLoader with caching and queuing enabled with a ContentCache:
protected function view1_creationCompleteHandler(event:FlexEvent):void
{
{
for(var i:int = 0; i<36;i++)
{
var img:Image = new Image();
img.contentLoader = ldr;
img.contentLoaderGrouping = 'gr1';
img.source = 'assets/0'+i.toString()+'.png';
container.addElement(img);
}
}
<fx:Declarations>
<s:ContentCache id="ldr" enableQueueing="true"
maxActiveRequests="1" maxCacheEntries="36"/>
</fx:Declarations>
Also if someone could tell me what is the contentLoaderGrouping for. I would be very grateful.
Thanks a lot!!!
PS:By the way both approaches work. The first approach is instant while the second approach shows the images beeing added in a very smooth way which actually gives a cool effect.
Neither. The point of the suggestion was to NOT alter the displaylist after creationComplete, which requires an additional update cycle. Instead you should inject the data property when you push your view on the stack, and initiate your changes in the setter. Using the ContentCache has nothing to do with it (and can sometimes cause additional overhead if not used correctly).
override public function set data(value:Object):void
{
super.data = value;
//this was poorly optimized, so I made it
//a little better...
var imageArray:Array = (value == null || value.imageArray == null)?
null : value.imageArray as Array;
if(imageArray == null) //On first creation of the view I create the data object
{
imageArray = new Array(36); //set an array that will cache my images.
for(var i:int = 0; i<36;i++)
{
var img:Image = new Image();
img.source = 'assets/0'+i.toString()+'.png';
container.addElement(img);
imageArray[i] = img;
}
super.data = {imageArray:imageArray}
}
else//Next time use the save images
{
var n:int = imageArray.length;
for (var j:int = 0; j < n; j++)
{
container.addElement(IVisualElement(imageArray[j]));
}
}
}
EDIT
I was mistaken about when the data property is set during the view life-cycle.
Here is how it works:
So you are correct that container would be null at that point. I was going to write up an example for you, but I'm having trouble figuring out what your end goal is here. Is there a specific reason you are storing the images on the data property? I think what you might actually want to do is this:
private var _data:Object = {cache: new ContentCache()};
protected function show_clickHandler(event:MouseEvent):void
{
this.navigator.pushView(views.MyView, _data);
}
And in the view...
<s:View xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" title="MyView">
<fx:Script>
<![CDATA[
import spark.components.Image;
import spark.core.ContentCache;
override protected function createChildren():void
{
super.createChildren();
//you might want to do a sanity first check to make sure the
//data was passed in correctly...
var cache:ContentCache = ContentCache(this.data.cache);
for(var i:int = 0; i < 36; i++)
{
var img:Image = new Image();
img.contentLoader = cache;
img.source = 'assets/0' + i.toString() + '.png';
container.addElement(img);
}
}
]]>
</fx:Script>
<s:VGroup id="container" />
</s:View>

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