Monotouch.Dialog: ScopeBar hidden by SectionHeader - ios

Using MonoTouch.Dialog I add a simple table and add a ScopeBar:
this.Style = UITableViewStyle.Plain;
this.EnableSearch = true;
this.AutoHideSearch = false;
this.SearchPlaceholder = "Search".t();
UISearchBar sb = TableView.TableHeaderView as UISearchBar;
if (sb != null)
{
sb.ScopeButtonTitles = new string[] { "Girl".t(),"Boy".t(),"All".t() };
sb.ShowsScopeBar = true;
sb.SizeToFit();
}
Looks good:
When I set the Section and give it a Title, the Section appears on top of the scope bar:
Section secMain = new Section("Top 100".t());

To do this, you need to change MonoTouch.Dialog.DialogViewController and make void SetupSearch() protected virtual.
Then, in your controller override the SetupSearch method with the code that follows. The downside to this approach is that you have to use a custom search delegate. But from seeing the response to some of your other questions, it appears you are already doing that.
protected override void SetupSearch()
{
SearchBar = new UISearchBar(new RectangleF (0, 0, TableView.Bounds.Width, 90))
{
Delegate = new MySearchBarDelegate(this),
Placeholder = "Search".t(),
ShowsScopeBar = true,
ScopeButtonTitles = new [] { "Girl".t(),"Boy".t(),"All".t() },
SelectedScopeButtonIndex = 0,
};
TableView.TableHeaderView = SearchBar;
}
public class MySearchBarDelegate : UISearchBarDelegate
{
MyViewController _container;
public SearchDelegate (MyViewController container)
{
_container = container;
}
public override void SelectedScopeButtonIndexChanged (UISearchBar searchBar, int index)
{
_container.SearchScopeChanged(searchBar, index);
}
public override void OnEditingStarted (UISearchBar searchBar)
{
searchBar.ShowsCancelButton = true;
_container.StartSearch ();
}
public override void OnEditingStopped (UISearchBar searchBar)
{
searchBar.ShowsCancelButton = false;
_container.FinishSearch ();
}
public override void TextChanged (UISearchBar searchBar, string searchText)
{
_container.PerformFilter (searchText ?? "");
}
public override void CancelButtonClicked (UISearchBar searchBar)
{
searchBar.ShowsCancelButton = false;
_container.SearchBar.Text = "";
_container.FinishSearch ();
searchBar.ResignFirstResponder ();
}
public override void SearchButtonClicked (UISearchBar searchBar)
{
_container.SearchButtonClicked (searchBar.Text);
}
}

Related

UICollectionView with section layout issue in Xamarin Native

I have UIcollectionView with 8 sections. Each section has a dedicated array as the data source. I want to add an item to each section on a button press. UICollectionView scroll direction set to Horizontal because I want the sections to be vertical direction.
Everything working fine in Xcode project(Xib and Objective-C) as this screenshot(Red colored items are the items added on button press).
When I do the same implementation in Xamarin ios, the layout become a mess after press the button as bellow.
My code as bellow
public partial class GridViewController : UIViewController
{
CVSource source;
int MAX_NUMBER_OF_ROWS = 4;
public GridViewController(IntPtr handle) : base(handle)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
// Perform any additional setup after loading the view, typically from a nib.
//this.collectionView.SetNeedsDisplay();
this.collectionView.RegisterClassForCell(typeof(ItemCell), ItemCell.CellId);
source = new CVSource();
this.collectionView.DataSource = source;
}
partial void AddButtonPressed(UIButton sender)
{
Console.WriteLine("Add button pressed");
MAX_NUMBER_OF_ROWS = MAX_NUMBER_OF_ROWS + 1;
source.AddItem();
collectionView.Layer.RemoveAllAnimations();
collectionView.ReloadData();
ViewDidLayoutSubviews();
}
public override void ViewDidLayoutSubviews()
{
base.ViewDidLayoutSubviews();
collectionView.Frame = new CoreGraphics.CGRect(collectionView.Frame.Location.X, collectionView.Frame.Location.Y, collectionView.Frame.Size.Width, MAX_NUMBER_OF_ROWS * 40);
btnAdd.Frame = new CoreGraphics.CGRect(btnAdd.Frame.Location.X, collectionView.Frame.Location.Y + collectionView.Frame.Size.Height + 10, btnAdd.Frame.Size.Width, btnAdd.Frame.Size.Height);
}
public override void DidReceiveMemoryWarning()
{
base.DidReceiveMemoryWarning();
// Release any cached data, images, etc that aren't in use.
}
DataSource class
class CVSource : UICollectionViewSource
{
List<string> arr1 = new List<string> { "40", "30"};
List<string> arr2 = new List<string> { "25", "22" };
List<string> arr3 = new List<string> { "35", "67" };
List<string> arr4 = new List<string> { "26", "12" };
List<string> arr5 = new List<string> { "27", "21", };
List<string> arr6 = new List<string> { "12", "45" };
List<string> arr7 = new List<string> { "34", "67" };
List<string> arr8 = new List<string> { "21", "44", };
public override nint NumberOfSections(UICollectionView collectionView)
{
return 8;
}
public override nint GetItemsCount(UICollectionView collectionView, nint section)
{
if ((int)section == 0)
return arr1.Count;
else if ((int)section == 1)
return arr2.Count;
else if ((int)section == 2)
return arr3.Count;
else if ((int)section == 3)
return arr4.Count;
else if ((int)section == 4)
return arr5.Count;
else if ((int)section == 5)
return arr6.Count;
else if ((int)section == 6)
return arr7.Count;
else if ((int)section == 7)
return arr8.Count;
return 0;
}
public override UICollectionViewCell GetCell(UICollectionView collectionView, Foundation.NSIndexPath indexPath)
{
var itemCell = (ItemCell)collectionView.DequeueReusableCell(ItemCell.CellId, indexPath);
if (indexPath.Section == 0)
{
itemCell.SetText(arr1[indexPath.Row]);
}
else if (indexPath.Section == 1)
{
itemCell.SetText(arr2[indexPath.Row]);
}
else if (indexPath.Section == 2)
{
itemCell.SetText(arr3[indexPath.Row]);
}
else if (indexPath.Section == 3)
{
itemCell.SetText(arr4[indexPath.Row]);
}
else if (indexPath.Section == 4)
{
itemCell.SetText(arr5[indexPath.Row]);
}
else if (indexPath.Section == 5)
{
itemCell.SetText(arr6[indexPath.Row]);
}
else if (indexPath.Section == 6)
{
itemCell.SetText(arr7[indexPath.Row]);
}
else if (indexPath.Section == 7)
itemCell.SetText(arr8[(indexPath.Row)]);
return itemCell;
}
public override void ItemSelected(UICollectionView collectionView, NSIndexPath indexPath)
{
Console.WriteLine("Row selected "+ indexPath.Row);
}
public void AddItem()
{
arr1.Add("0");
arr2.Add("0");
arr3.Add("0");
arr4.Add("0");
arr5.Add("0");
arr6.Add("0");
arr7.Add("0");
arr8.Add("0");
}
}
ItemCell class
public partial class ItemCell : UICollectionViewCell
{
public static readonly NSString CellId = new NSString("ItemCell");
public static UITextField txtFld;
[Export("initWithFrame:")]
public ItemCell(CGRect frame) : base(frame)
{
BackgroundView = new UIView { BackgroundColor = UIColor.Orange };
SelectedBackgroundView = new UIView { BackgroundColor = UIColor.Green };
ContentView.Layer.BorderColor = UIColor.LightGray.CGColor;
ContentView.Layer.BorderWidth = 2.0f;
ContentView.BackgroundColor = UIColor.White;
ContentView.Transform = CGAffineTransform.MakeScale(0.8f, 0.8f);
txtFld = new UITextField() { Frame = new CGRect(5.0, 5.0, 60.0, 30.0), KeyboardType = UIKeyboardType.DecimalPad };
}
}
Im not a Xamarin developer, appreciate any comments/suggestions from Xamarin experts
Maybe you miss UICollectionViewLayout to set for collectionView , you can set the GridLayout for collectionView to have a try .
this.collectionView.SetCollectionViewLayout(new GridLayout(), true); //add CollectionViewLayout
this.collectionView.RegisterClassForCell(typeof(ItemCell), ItemCell.CellId);
GridLayout inherits from UICollectionViewFlowLayout in Xamrarin iOS :
public class GridLayout : UICollectionViewFlowLayout
{
public GridLayout ()
{
}
public override bool ShouldInvalidateLayoutForBoundsChange (CGRect newBounds)
{
return true;
}
public override UICollectionViewLayoutAttributes LayoutAttributesForItem (NSIndexPath path)
{
return base.LayoutAttributesForItem (path);
}
public override UICollectionViewLayoutAttributes[] LayoutAttributesForElementsInRect (CGRect rect)
{
return base.LayoutAttributesForElementsInRect (rect);
}
}
The effect :
Affter tried the shared code , the effect as follow :
The different with your codes only here :
public ViewController (IntPtr handle) : base (handle)
{
collectionView = new UICollectionView(new CGRect(0, 0, UIScreen.MainScreen.Bounds.Size.Width, 300), new GridLayout());
collectionView.RegisterClassForCell(typeof(ItemCell), ItemCell.CellId);
//collectionView.SetCollectionViewLayout(new GridLayout(), true);
collectionView.BackgroundColor = UIColor.Blue;
source = new CVSource();
collectionView.DataSource = source;
View.AddSubview(collectionView);
button = new UIButton(new CGRect(150, 400, 100, 50));
button.SetTitle("Hello", UIControlState.Normal);
button.BackgroundColor = UIColor.Red;
View.AddSubview(button);
button.TouchUpInside += Button_TouchUpInside;
}
Note : I use code to create Interface , and set GridLayout for CollectionView .Here is the sample link .

UICollectionViews GetSizeForItem() crashes on call of collectionView.DequeueReusableCell ()

I have a strange behaviour on my Custom UICollectionView.
Everytime i call
KeyWordsFieldsCell _dummyCellForRendering = (KeyWordsFieldsCell)collectionView.DequeueReusableCell (KeyWordsFieldsCell.CellId, indexPath);
in
public override CGSize GetSizeForItem (UICollectionView collectionView, UICollectionViewLayout layout, NSIndexPath indexPath)
my Code crashes without Error or Stacktrace.
In
public override UICollectionViewCell GetCell (UICollectionView collectionView, NSIndexPath indexPath)
however the call of
KeyWordsFieldsCell _dummyCellForRendering = (KeyWordsFieldsCell)collectionView.DequeueReusableCell (KeyWordsFieldsCell.CellId, indexPath);
just works fine.
Here is the complete Code of my UICollectionViews DataSource and Delegate.
namespace KeyWordFieldsView
{
#region CollectionViewDataSource
public class KeyWordsFieldDataSource : UICollectionViewDataSource
{
private readonly UICollectionView keyWordsCollectionView;
public KeyWordsFieldDataSource (UICollectionView keyWordsCollectionView)
{
this.keyWordsCollectionView = keyWordsCollectionView;
}
public event EventHandler ContentChangedEvent;
private List<String> data = new List<String> ();
public List<String> Data
{
get
{
return data;
}
set
{
data = value;
}
}
public override nint GetItemsCount (UICollectionView collectionView, nint section)
{
return data.Count;
}
public override UICollectionViewCell GetCell (UICollectionView collectionView, NSIndexPath indexPath)
{
var textCell = (KeyWordsFieldsCell)collectionView.DequeueReusableCell (KeyWordsFieldsCell.CellId, indexPath);
textCell.initCell ();
textCell.Text = Data [indexPath.Row];
textCell.DeleteButtonPressedEvent += HandleDeleteButtonPressedEvent;
return textCell;
}
public void HandleDeleteButtonPressedEvent (object sender, EventArgs a)
{
if (sender.GetType () == typeof (KeyWordsFieldsCell))
{
var cell = sender as KeyWordsFieldsCell;
NSIndexPath [] pathsToDelete = { keyWordsCollectionView.IndexPathForCell (cell) };
if (pathsToDelete [0] != null)
{
cell.DeleteButtonPressedEvent -= HandleDeleteButtonPressedEvent;
Data.RemoveAt (pathsToDelete [0].Row);
keyWordsCollectionView.DeleteItems (pathsToDelete);
}
OnContentChanged (sender, a);
}
}
public void OnContentChanged (object sender, EventArgs ea)
{
if (ContentChangedEvent != null)
{
ContentChangedEvent (this, ea);
}
}
}
#endregion
#region CollectionViewDelegate
class KeyWordsFieldDelegate : UICollectionViewDelegateFlowLayout
{
public override CGSize GetSizeForItem (UICollectionView collectionView, UICollectionViewLayout layout, NSIndexPath indexPath)
{
List<String> data = ((KeyWordsFieldDataSource)collectionView.DataSource).Data;
KeyWordsFieldsCell _dummyCellForRendering = (KeyWordsFieldsCell)collectionView.DequeueReusableCell (KeyWordsFieldsCell.CellId, indexPath);
_dummyCellForRendering.Text = data [indexPath.Row];
_dummyCellForRendering.keyWordContainerView.SetNeedsLayout ();
_dummyCellForRendering.keyWordContainerView.LayoutIfNeeded ();
double height = Math.Max (_dummyCellForRendering.keyWordLabel.Frame.Height, _dummyCellForRendering.keyWordFieldDeleteButton.Frame.Height);
double width = Math.Min (_dummyCellForRendering.keyWordContainerView.Frame.Width, collectionView.Bounds.Width);
_dummyCellForRendering = null;
return new CGSize (width, height);;
}
public override void ItemSelected (UICollectionView collectionView, NSIndexPath indexPath)
{
}
public override bool ShouldSelectItem (UICollectionView collectionView, NSIndexPath indexPath)
{
return true;
}
public override void CellDisplayingEnded (UICollectionView collectionView, UICollectionViewCell cell, NSIndexPath indexPath)
{
var keyWordCell = cell as KeyWordsFieldsCell;
keyWordCell.DeleteButtonPressedEvent -= ((KeyWordsFieldDataSource)collectionView.DataSource).HandleDeleteButtonPressedEvent;
}
}
#endregion
#region left justified cells
class LeftAlignedCollectionViewFlowLayout : UICollectionViewFlowLayout
{
nfloat maxCellSpacing = 4;
public override UICollectionViewLayoutAttributes [] LayoutAttributesForElementsInRect (CGRect rect)
{
var attributesForElementsInRect = base.LayoutAttributesForElementsInRect (rect);
UICollectionViewLayoutAttributes [] newAttributesForElementsInRect = new UICollectionViewLayoutAttributes [attributesForElementsInRect.Count ()];
var leftMargin = this.SectionInset.Left;
for (int i = 0; i < attributesForElementsInRect.Count (); i++)
{
var attributes = attributesForElementsInRect [i];
//if Element is first in new Line and already leftaligned or if element is in new line
if (attributes.Frame.X == leftMargin || attributes.Frame.Y > attributesForElementsInRect[i > 0 ? i-1 : i].Frame.Y)
{
leftMargin = this.SectionInset.Left; //reset the leftMargin to left sectionInset.
}
CGRect newLeftAlignedFrame = attributes.Frame;
newLeftAlignedFrame.X = leftMargin;
attributes.Frame = newLeftAlignedFrame;
leftMargin += attributes.Size.Width + maxCellSpacing;
newAttributesForElementsInRect [i] = attributes;
}
return newAttributesForElementsInRect;
}
}
#endregion
}
and here is the code of my UICollectionViewCell
namespace KeyWordFieldsView
{
public partial class KeyWordsFieldsCell : UICollectionViewCell
{
protected KeyWordsFieldsCell (IntPtr handle) : base (handle)
{
// Note: this .ctor should not contain any initialization logic.
}
public string Text
{
get
{
return keyWordLabel.Text;
}
set
{
initCell ();
keyWordLabel.Text = value;
keyWordLabel.SizeToFit ();
SetNeedsDisplay ();
}
}
public UILabel keyWordLabel;
public UIButton keyWordFieldDeleteButton;
public UIView keyWordContainerView;
public static readonly NSString CellId = new NSString ("KeyWordsFieldsCell");
public event EventHandler DeleteButtonPressedEvent;
public void initCell () {
UIColor chipGrey = UIColor.FromRGBA (153, 153, 153, 51);
ContentView.BackgroundColor = chipGrey;
ContentView.Layer.CornerRadius = 16;
if (keyWordContainerView == null)
{
keyWordContainerView = new UIView (new CGRect (0, 0, 0, 32));
keyWordContainerView.TranslatesAutoresizingMaskIntoConstraints = false;
keyWordContainerView.BackgroundColor = UIColor.Clear;
ContentView.AddSubview (keyWordContainerView);
}
if (keyWordLabel == null)
{
keyWordLabel = new UILabel (new CGRect (0, 0, 0, 32));
keyWordLabel.BackgroundColor = UIColor.Clear;
UIFont labelFont = UIFont.SystemFontOfSize (14f);
keyWordLabel.Font = labelFont;
keyWordLabel.TranslatesAutoresizingMaskIntoConstraints = false;
keyWordLabel.LineBreakMode = UILineBreakMode.MiddleTruncation;
keyWordContainerView.AddSubview (keyWordLabel);
}
if (keyWordFieldDeleteButton == null)
{
keyWordFieldDeleteButton = UIButton.FromType (UIButtonType.Custom);
keyWordFieldDeleteButton.Frame = new CGRect (0, 0, 32, 32);
keyWordFieldDeleteButton.SetImage (UIImage.FromBundle ("remove-icon"), UIControlState.Normal);
keyWordFieldDeleteButton.BackgroundColor = UIColor.Clear;
keyWordFieldDeleteButton.TouchUpInside += DeleteButtonPressed;
keyWordFieldDeleteButton.TranslatesAutoresizingMaskIntoConstraints = false;
keyWordContainerView.AddSubview (keyWordFieldDeleteButton);
}
else {
//Add ButtonEvent in Case of Reuse
keyWordFieldDeleteButton.TouchUpInside -= DeleteButtonPressed;
keyWordFieldDeleteButton.TouchUpInside += DeleteButtonPressed;
}
var cvDictionary = NSDictionary.FromObjectsAndKeys (new NSObject [] { keyWordContainerView }, new NSObject [] { new NSString ("kwcv") });
ContentView.AddConstraints (NSLayoutConstraint.FromVisualFormat ("H:|[kwcv]|", 0, new NSDictionary (), cvDictionary));
ContentView.AddConstraints (NSLayoutConstraint.FromVisualFormat ("V:|[kwcv]|", 0, new NSDictionary (), cvDictionary));
keyWordContainerView.SetContentHuggingPriority (249, UILayoutConstraintAxis.Vertical);
keyWordContainerView.SetContentCompressionResistancePriority (749, UILayoutConstraintAxis.Vertical);
var viewsDictionary = NSDictionary.FromObjectsAndKeys (new NSObject [] { keyWordLabel, keyWordFieldDeleteButton }, new NSObject [] { new NSString ("kwlbl"), new NSString ("kwbtn") });
keyWordContainerView.AddConstraints (NSLayoutConstraint.FromVisualFormat ("H:|-[kwlbl][kwbtn(==32)]|", 0, new NSDictionary (), viewsDictionary));
keyWordContainerView.AddConstraints (NSLayoutConstraint.FromVisualFormat ("V:|[kwbtn(==32)]|", 0, new NSDictionary (), viewsDictionary));
keyWordContainerView.AddConstraints (NSLayoutConstraint.FromVisualFormat ("V:|[kwlbl]|", 0, new NSDictionary (), viewsDictionary));
keyWordFieldDeleteButton.SetContentHuggingPriority (249, UILayoutConstraintAxis.Vertical);
keyWordFieldDeleteButton.SetContentCompressionResistancePriority (751, UILayoutConstraintAxis.Vertical);
keyWordLabel.SetContentHuggingPriority (249, UILayoutConstraintAxis.Vertical);
keyWordLabel.SetContentCompressionResistancePriority (749, UILayoutConstraintAxis.Vertical);
}
//[Export ("initWithFrame:")]
//public KeyWordsFieldsCell (CGRect frame) : base (frame)
//{
// initCell ();
//}
public override void LayoutSubviews ()
{
base.LayoutSubviews ();
}
public void DeleteButtonPressed (object sender, EventArgs ea)
{
((UIButton)sender).TouchUpInside -= DeleteButtonPressed;
OnDeleteButtonPressed (sender, ea);
}
void OnDeleteButtonPressed (object sender, EventArgs ea)
{
if (DeleteButtonPressedEvent != null)
{
DeleteButtonPressedEvent (this, ea);
}
}
}
}
This is where the UICollectionView gets initialized:
if (CollectionView != null && CollectionView.DataSource == null)
{
CollectionView.RegisterClassForCell (typeof (KeyWordsFieldsCell), KeyWordsFieldsCell.CellId);
CollectionView.TranslatesAutoresizingMaskIntoConstraints = false;
CollectionView.SetCollectionViewLayout (new LeftAlignedCollectionViewFlowLayout (), false);
KeyWordsFieldDataSource Source = new KeyWordsFieldDataSource (CollectionView);
if (data != null)
{
Source.Data = data;
}
CollectionView.DataSource = Source;
KeyWordsFieldDelegate keyWordsDelegate = new KeyWordsFieldDelegate ();
CollectionView.Delegate = keyWordsDelegate;
(CollectionView.CollectionViewLayout as UICollectionViewFlowLayout).MinimumLineSpacing = 4;
(CollectionView.CollectionViewLayout as UICollectionViewFlowLayout).MinimumInteritemSpacing = 2;
//CollectionViewHeightConstraint.Constant = CollectionView.CollectionViewLayout.CollectionViewContentSize.Height;
}
Hope that someone can help be, because this is a rather frustrating problem.
Greets,
Mav
So for anyone who stumbles across the same Problem.
Frankly speaking there is just a different behaviour between UITableView and UICollectionView.
Where in UITableView it is totally ok to call dequeueReusableCellWithReuseIdentifier() in getHeightForRow() to get a cell for height calculation, calling it in sizeForItemAtIndexPath in a UICollectionView will cause an indefinite loop and thus crash the app.
Thx to #Markus Rautopuro for point me in the right direction with his Answer
I'm now calculating the height of the cell, by calculating the size of the components in the cell. That works quite well and needs less resources, since i don't need to build a complete cell, but only the items, that add up to the height.

UIImageView erroneously moves up in parent when clicked on

I have an ImageViewController that presents an ImageZoomView (which currently doesn't zoom yet, but it will soon). When I tap on the image, the entire image will shift upwards slightly. This should not happen, the image should stay in it's place until a user swipes or pinches.
The ImageViewController lives inside a UIPageViewController that allows swiping between views. However, the only issue I have is with the ImageViewController (i.e. the VideoViewController and PdfViewController both work fine and do not shift upwards on a tap)
ImageZoomView is a UIScrollView with a UIImageView as it's child control.
ImageZoomView:
public class ImageZoomView : UIScrollView
{
private UIImage _image;
private UIImageView _imageView;
public ImageZoomView()
{
_imageView = new UIImageView
{
ContentMode = UIViewContentMode.ScaleAspectFill
};
AutoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight;
ClipsToBounds = true;
AddSubview(_imageView);
}
public int Index { get; set; }
public override void LayoutSubviews()
{
base.LayoutSubviews();
var imageWidth = Bounds.Width;
_imageView.Frame = new CGRect(
Bounds.Left, Bounds.Top,
imageWidth, imageWidth / _image.Size.Width * _image.Size.Height);
ContentSize = _imageView.Bounds.Size;
}
public void DisplayImage(UIImage image)
{
_image = image;
_imageView.Image = image;
}
}
ImageViewController:
public class ImageViewController : AssetViewController
{
private ImageZoomView _imageView;
public ImageViewController(int index, Media media) : base(index, media) { }
public override void ViewDidLayoutSubviews()
{
base.ViewDidLayoutSubviews();
_imageView.Frame = View.Bounds;
}
public override void LoadView()
{
_imageView = new ImageZoomView()
{
Index = Index,
BackgroundColor = UIColor.White,
};
_imageView.DisplayImage((_media as LocalMedia<UIImage>).Resource);
View = _imageView;
}
}
AssetViewController:
public abstract class AssetViewController : UIPageViewController
{
protected Media _media;
public AssetViewController(int index, Media media)
{
_media = media;
Index = index;
View.BackgroundColor = Globals.ColorDark;
}
public int Index { get; set; }
public Media CurrentMedia { get { return _media; } }
}
Few things causing problems
All your controllers are derived from UIPageViewController. The content controllers should not be derived from UIPageViewController
In ImageViewController the code View = _imageView; substitutes main View of controller to ScrollView. I suggest to use AddSubview(_imageView); What I saw that this erroneous movement shifts not the scroll view but somehow view of the whole controller.
LayoutSubviews is called pretty often and calculates the same thing all the time as neither image size nor scroll bounds are changing.
Below suggested improvements.
ImageViewController is content controller inside PageViewController and ImageZoomView is the scroll view inside ImageViewController
PageViewController
public partial class PageViewController : UIPageViewController
{
public PageViewController(IntPtr handle) : base(handle)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
List<ImageViewController> pages = new List<ImageViewController>();
UIStoryboard board = UIStoryboard.FromName("Main", null);
for (int i = 0; i < 3; i++)
{
ImageViewController ctrl = (ImageViewController)board.InstantiateViewController("imageControllerStoryboardID");
ctrl.Index = i;
pages.Add(ctrl);
}
DataSource = new PageDataSource(pages);
View.Frame = View.Bounds;
//Set the initial content (first page)
SetViewControllers(new UIViewController[] { pages[0] }, UIPageViewControllerNavigationDirection.Forward, false, s => { });
}
}
public class PageDataSource : UIPageViewControllerDataSource
{
List<ImageViewController> pages;
public PageDataSource(List<ImageViewController> pages)
{
this.pages = pages;
}
override public UIViewController GetPreviousViewController(UIPageViewController pageViewController, UIViewController referenceViewController)
{
var currentPage = referenceViewController as ImageViewController;
if (currentPage.Index == 0)
{
return pages[pages.Count - 1];
}
else
{
return pages[currentPage.Index - 1];
}
}
override public UIViewController GetNextViewController(UIPageViewController pageViewController, UIViewController referenceViewController)
{
var currentPage = referenceViewController as ImageViewController;
return pages[(currentPage.Index + 1) % pages.Count];
}
}
ImageViewController
public partial class ImageViewController : UIViewController
{
public int Index;
private ImageZoomView _imageView;
public ImageViewController (IntPtr handle) : base (handle)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
_imageView = new ImageZoomView(UIImage.FromFile("MergedImage.png"), View.Frame)
{
Index = Index,
BackgroundColor = UIColor.Blue,
};
View.AddSubview(_imageView);
}
}
ImageZoomView
public class ImageZoomView : UIScrollView
{
private UIImage _image;
private UIImageView _imageView;
public ImageZoomView(UIImage image, CGRect frame):base()
{
_imageView = new UIImageView
{
ContentMode = UIViewContentMode.ScaleAspectFill,
};
_image = image;
_imageView.Image = image;
Frame=_imageView.Frame = frame;
AutoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight;
ClipsToBounds = true;
AddSubview(_imageView);
ContentSize = new CGSize(Frame.Width, Frame.Width / _image.Size.Width * _image.Size.Height);
_imageView.Center = new CGPoint(ContentSize.Width / 2, ContentSize.Height / 2);
}
public int Index { get; set; }
}
There are still some code left from original sample as unused Index field and private _image variables. May be useful in the future, so I kept them around

Xamarin iOS 8, UICollectionView - Error in dequeue reusable UICollectionViewCell

I'm having some trouble implementing a UICollectionView in my UIViewController. It gives me an error in GetCell Failed to marshal the Objective-C object 0x137622db0 (type: ParkXApp_iOS_FamilyCollectionCell). Could not find an existing managed instance for this object, nor was it possible to create a new managed instance (because the type 'ParkXApp_iOS.FamilyCollectionCell' does not have a constructor that takes one IntPtr argument)..
I have implemented next to the monotouch sample code: monotouch sample simple UICollectionView
this is my code:
using System;
using UIKit;
using CoreGraphics;
using System.Collections.Generic;
using ObjCRuntime;
using Foundation;
namespace ParkXApp_iOS
{
public class FamilyView : UIViewController
{
nfloat navBarHeight;
nfloat viewWidth;
nfloat viewHeight;
public FamilyView ()
{
}
public override void ViewWillAppear (bool animated)
{
base.ViewWillAppear (animated);
Draw ();
}
public void Draw () {
// clean the screen before (re)drawing it
foreach (var subView in this.View.Subviews) {
subView.RemoveFromSuperview ();
}
this.NavigationItem.SetRightBarButtonItem(null, true);
// set background
this.View.BackgroundColor = new UIColor((nfloat)0.99, (nfloat)0.99, (nfloat)0.99, 1);
// get viewWidth and viewHeight for drawing relative to the screen
navBarHeight = this.NavigationController.NavigationBar.Frame.GetMaxY ();
viewWidth = UIScreen.MainScreen.Bounds.Width;
viewHeight = UIScreen.MainScreen.Bounds.Height - navBarHeight;
UICollectionViewFlowLayout flowLayout = new UICollectionViewFlowLayout ();
flowLayout.ItemSize = new CGSize (100, 100);
flowLayout.ScrollDirection = UICollectionViewScrollDirection.Vertical;
flowLayout.SectionInset = new UIEdgeInsets (20, 20, 20, 20);
UICollectionView collectionView = new FamilyCollectionView (flowLayout).CollectionView;
collectionView.Frame = new CGRect (0, navBarHeight, viewWidth, viewHeight);
collectionView.ContentInset = new UIEdgeInsets (50, 0, 0, 0);
this.View.AddSubview (collectionView);
}
}
public class FamilyCollectionView : UICollectionViewController
{
List<string> familyMembers;
public FamilyCollectionView (UICollectionViewLayout layout) : base (layout) {
familyMembers = new List<string> ();
for (int i = 0; i < 5; i++) {
familyMembers.Add ("tom");
}
CollectionView.RegisterClassForCell (typeof(FamilyCollectionCell), "FamilyCollectionCell");
UIMenuController.SharedMenuController.MenuItems = new UIMenuItem[] {
new UIMenuItem ("Custom", new Selector ("custom"))
};
}
public override nint NumberOfSections (UICollectionView collectionView)
{
return 1;
}
public override nint GetItemsCount (UICollectionView collectionView, nint section)
{
return familyMembers.Count;
}
public override UICollectionViewCell GetCell (UICollectionView collectionView, Foundation.NSIndexPath indexPath)
{
try {
var cell = (FamilyCollectionCell)collectionView.DequeueReusableCell ("FamilyCollectionCell", indexPath);
cell.nameLabel.Text = familyMembers [indexPath.Row];
return cell;
} catch (Exception ex) {
Console.WriteLine (ex.Message);
}
return null;
}
public override void ItemHighlighted (UICollectionView collectionView, NSIndexPath indexPath)
{
var cell = collectionView.CellForItem (indexPath);
cell.ContentView.BackgroundColor = UIColor.Yellow;
}
public override void ItemUnhighlighted (UICollectionView collectionView, NSIndexPath indexPath)
{
var cell = collectionView.CellForItem (indexPath);
cell.ContentView.BackgroundColor = UIColor.White;
}
public override bool ShouldHighlightItem (UICollectionView collectionView, NSIndexPath indexPath)
{
return true;
}
}
public class FamilyCollectionCell : UICollectionViewCell
{
public UILabel nameLabel;
public FamilyCollectionCell (CGRect frame) : base (frame)
{
BackgroundColor = UIColor.Orange;
ContentView.Layer.BorderColor = UIColor.LightGray.CGColor;
ContentView.Layer.BorderWidth = 2.0f;
ContentView.BackgroundColor = UIColor.White;
ContentView.Transform = CGAffineTransform.MakeScale (0.8f, 0.8f);
nameLabel = new UILabel ();
nameLabel.Text = "name";
nameLabel.SizeToFit ();
nameLabel.Center = ContentView.Center;
ContentView.AddSubview (nameLabel);
}
[Export("custom")]
void Custom()
{
Console.WriteLine ("some code in the cell");
}
public override bool CanPerform (Selector action, NSObject withSender)
{
if (action == new Selector ("custom"))
return true;
else
return false;
}
}
}
To test it you should be able to copy-paste the code and run it in a solution. It is completely independent.
Thanks in advance!
You need to add this:
[Export ("initWithFrame:")]
in you cell like so
public class FamilyCollectionCell : UICollectionViewCell
{
public UILabel nameLabel;
[Export ("initWithFrame:")] // ADDED HERE
public FamilyCollectionCell (CGRect frame) : base (frame)
{
BackgroundColor = UIColor.Orange;
ContentView.Layer.BorderColor = UIColor.LightGray.CGColor;
ContentView.Layer.BorderWidth = 2.0f;
ContentView.BackgroundColor = UIColor.White;
ContentView.Transform = CGAffineTransform.MakeScale (0.8f, 0.8f);
nameLabel = new UILabel ();
nameLabel.Text = "name";
nameLabel.SizeToFit ();
nameLabel.Center = ContentView.Center;
ContentView.AddSubview (nameLabel);
}
[Export("custom")]
void Custom()
{
Console.WriteLine ("some code in the cell");
}
public override bool CanPerform (Selector action, NSObject withSender)
{
if (action == new Selector ("custom"))
return true;
else
return false;
}
}
Also that's an interesting way of adding in the collectionView into a viewcontroller. Is there any need for FamilyView? It might be better to add the navbar to the collectionview controller?

Xamarin UITableViewCell Memory Leak

I'm using Xamarin and classes that inherit UITableViewController, UITableViewSource, and UITableViewCell to provide a simple list of items.
I find that once garbage collection has run, the UITableViewController and UITableViewSource get collected, but the UITableViewCell's never get disposed and collected properly.
Here's my example code.
public class Test1ButtonCell : UITableViewCell
{
public static readonly NSString Key = new NSString("Test1ButtonCell");
public Test1ButtonCell () : base (UITableViewCellStyle.Default, Key)
{
TextLabel.TextColor = UIColor.Black;
TextLabel.TextAlignment = UITextAlignment.Left;
}
protected override void Dispose (bool disposing)
{
base.Dispose (disposing);
}
}
public class Test1Controller : UITableViewController
{
private WeakReference<Test1Source> source = null;
public Test1Controller () : base (UITableViewStyle.Grouped)
{
}
public override void DidReceiveMemoryWarning ()
{
base.DidReceiveMemoryWarning ();
}
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
List<String> testdata = new List<String>( );
for( int i = 0; i < 30; i++ )
{
testdata.Add( i.ToString( ) );
}
source = new WeakReference<Test1Source>(new Test1Source( this,testdata ));
testdata = null;
this.NavigationItem.LeftBarButtonItem = new UIBarButtonItem ("Back", UIBarButtonItemStyle.Plain, null);
}
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear (animated);
this.NavigationItem.Title="Test1";
TableView.Source = source.target;
this.NavigationItem.LeftBarButtonItem.Clicked += HandleBackButtonTouch;
}
public override void ViewWillDisappear (bool animated)
{
this.NavigationItem.LeftBarButtonItem.Clicked -= HandleBackButtonTouch;
base.ViewWillDisappear (animated);
TableView.Source = null;
}
private void HandleBackButtonTouch(object sender, EventArgs e)
{
NavigationController.PopToRootViewController( true );
}
protected override void Dispose (bool disposing)
{
base.Dispose (disposing);
}
}
public class Test1Source : UITableViewSource
{
private WeakReference<Test1Controller> controller;
private List<String> testdata = new List<string>();
public Test1Source (Test1Controller controller, List<String> testData)
{
testdata = testData;
this.controller = new WeakReference<Test1Controller>( controller );
}
public override nint NumberOfSections (UITableView tableView)
{
return 1;
}
public override nint RowsInSection (UITableView tableview, nint section)
{
if( section == 0 )
{
return testdata.Count;
}
else
{
return 0;
}
}
public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath)
{
var cell = tableView.DequeueReusableCell (Test1ButtonCell.Key) as Test1ButtonCell;
if( cell == null )
{
cell = new Test1ButtonCell( );
}
if( indexPath.Section == 0 )
{
cell.TextLabel.Text = testdata[ indexPath.Row ];
}
return cell;
}
}
The UITableView caches (and reuses) UITableViewCells.
They'll go away once the UITableView is freed.
I can confirm the problem. It works properly on the simulator and not on the device.
The only thing I can think of is to keep somewhere list of all created custom cells (by adding them to the list in the constructor of the cell) and dispose them when the table is disposed. Kind of ugly, but it works.

Resources