I've got the following code that i've adapted from the Xamarin MobileCRM example:
[assembly: ExportCell(typeof(ListTextCell), typeof(ListTextCellRenderer))]
namespace Manager.iOS
{
class ListTextCellRenderer : TextCellRenderer
{
public override UITableViewCell GetCell(Cell item, UITableView tv)
{
var cellView = base.GetCell(item, tv);
cellView.TextLabel.Lines = 0;
return cellView;
}
}
}
Im wanting to have it that the TextLabel will stetch/wrap when it contains more text than the width of the device and therefore the rows will grow in size with it. When I set the Lines = 0; it wraps the label but the row height stays the same so every row just overlaps each other. How do I grow the row height as well?
Thanks
In your tableView source you will want to override the following method
public override float GetHeightForRow(UITableView tableView, NSIndexPath indexPath)
{
var text = new NSString(yourTextHere);
var size = text.StringSize(UIFont.FromName("FontName", fontSize), new SizeF(tableView.Frame.Width, 70)); // The 70 can be any number, the width is the important part
return size.Height + 16; // 16 is for padding
}
You may have to set HasUnevenRows or the RowHeight on your TableView
Related
For performance reasons I decided to write a custom ViewCell for my ListView in Xamarin Forms. For this ViewCell I've extended the ViewCellRenderer for iOS. In the GetCell-Method I create a new instance of an extended UITableViewCell which creates some labels and images and arranges them by some constraints on the anchors.
The ListView in Xamarin Forms has HasUnevenRows="True", but the rows are all cut-off at 44px. How can I get automatic height to the rows by the created content from the custom UITableViewCell?
Cause:
Actually, The auto height with HasUnevenRows=true works fine on iOS if not using a custom renderer.
If using a custom renderer, then it is up to the renderer to set the height of the cell, but in reality you have to do it in the GetHeightForRow override of the UITableViewSource.
This means you have to calculate the row height in UITableViewSource that you make that sets the height for a row in the GetHeightForRows override if you want to use custom viewcell render in the listView with uneven rows.
Solution:
Basically you have to make a custom ListView renderer. Then you have to get the UITableView's Source property (Control.Source in the list view renderer) and pass that to a new instance of a subclass of UITableVIewSource that you create, and then override all of the UITableViewSource methods so you can call into the original source methods when you don't want to change anything, but for the GetRowForHeight method, you would return the height you want for that row. E.G.:
[assembly: ExportRenderer(typeof(ListView), typeof(MyLVRenderer))]
public class MyLVRenderer : ListViewRenderer
{
//UITableViewSource originalSource;
protected override void OnElementChanged(ElementChangedEventArgs<ListView> e)
{
base.OnElementChanged(e);
UITableViewSource originalSource = (UIKit.UITableViewSource)Control.Source;
Control.Source = new MyLVSource(originalSource);
}
}
public class MyLVSource : UITableViewSource
{
UITableViewSource originalSource;
public MyLVSource(UITableViewSource origSource)
{
originalSource = origSource;
}
public override nint RowsInSection(UITableView tableview, nint section)
{
return originalSource.RowsInSection(tableview, section);
}
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
{
return originalSource.GetCell(tableView, indexPath);
}
public override nfloat GetHeightForFooter(UITableView tableView, nint section)
{
return originalSource.GetHeightForFooter(tableView, section);
}
public override nfloat GetHeightForRow(UITableView tableView, NSIndexPath indexPath)
{
nfloat origHeight = originalSource.GetHeightForRow(tableView, indexPath);
// calculate your own row height here
return origHeight + (indexPath.Row +1) * 10;
}
}
This is a example of increasing the row height by ten for every row. You have to calculate your own row height by yourself. I can share you the demo if you need.
Refer: viewcell
You should override LayoutSubviews. You should set renderer.NativeView.Frame to proper values.
I understand that this question has been asked here, but it didn't solve my problem as the link in the accepted answer is down and the minimal example didn't help.
Here is a picture of my custom UITableViewCell & all its constraints:
Each post contains these UI elements. The only element that could make each cell's height different is messageView, because its height depends on the string being displayed. Question is, how do I dynamically set each cell's height? Here's what I have now (Does NOT work, messageView is not shown at all):
func cellForRowAt(indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(...) as! PostCell
let message = ...
cell.messageView.text = message
return cell
}
func heightForRowAt(indexPath: IndexPath) -> CGFloat {
var cellMinimumHeight: CGFloat = 120
if let cell = tableView.cellForRow(at: indexPath) as? PostCell {
let size = cell.messageView.sizeThatFits(cell.messageView.frame.size)
cellMinimumHeight += size.height
}
return cellMinimumHeight
}
in heightForRowAt function, the if is not being executed, therefore, all cells' heights are cellMinimumHeight = 120.
How could I make each cell's height = 120 + messageView's height?
---------------------------------EDIT 1---------------------------------
Made a mistake in the picture, messageView's height is not set
When using Auto-Layout for dynamically sizing cells, you don't really need to implement sizeThatFits(...). If the constraints are setup correctly, then you only need to disable the scrolling of the UITextView.
From code:
yourTextView.scrollEnabled = false
From IB:
Select your Text View and open Attributes inspector, then
In Attributes Inspector select your UITextView(messageView) and Uncheck "Scrolling Enabled".
And then change your UITextView(messageView)'s content compression resistence priority as follows:
Horizontal = 750
Vertical = 1000
I hope this will help you.
Just disable UITextview scroll...
But here is no use of UITextview, you can use label also.
In HeightForRow tableview delegate method remove that stuff and use
return UITableViewAutomaticDimension
You have used constrain to make cell hight dynamic
form apple documentation
To define the cell’s height, you need an unbroken chain of constraints
and views (with defined heights) to fill the area between the content
view’s top edge and its bottom edge.
So for your case you need
cell's height = 120 + messageView's height?
So start from Profile Image to measure unbroken chain of constraints from Top to Bottom
Profile Image top = 10 + ImageHeight = 60 ----> 70
MessageView top = 10 + set minimum height to message say 20 one line if every cell should have message even if one word and set this height Greater than or equal to 20 make sure that you set Scroll enable = false
so message Height minimum = 10 top + 20 + 10 bottom ---> 40
Menu Stack view Height ---> 30
So all Total = 70 + 40 + 30 = 140 this default hight no cell will be less than this
Also you must set the table view’s rowHeight property to UITableViewAutomaticDimension. You must also assign a value to the estimatedRowHeight property
tableView.estimatedRowHeight = 130.0
tableView.rowHeight = UITableViewAutomaticDimension
Here is apple documentation Here
So currently, I have a tableviewcell that looks like this
What I want to happen, is that if an expense of the date already exists, the top label should disappear, the tableviewcell height should be reduced from 95 to 64 and everything should be centrally aligned. Sort of like this
I tried doing this many ways.
Use 2 different cells and switch, but that didn't work as only one expense was returned at a time and my tableviewcontroller didn't populate correctly.
Try using a stack view, but in that, I can't get the constraints to match as they are currently.
I have all the correct row height being returned in the heightForRowAtIndexPath method, but it centrally reduces the height and some of the data is cut.
How is it possible to achieve what I want to do (have the label not visible, the row height reduced and everything vertically center)?
Here is the code for switching of the cells.
func tableView(_ tableView: UITableView,
heightForRowAt indexPath: IndexPath) -> CGFloat
{
if newMode==true {
return 95
}
else if newMode==false {
return 64
}
else {
return 0
}
}
This works, however it reduces the height from the top and the bottom and I only want the height from the top to be reduced.
You should not use the delegate method heightForRowAt instead you should use:
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 80
then give the (date of expense) label a hight constraint and connect an outlet to it.
in cellForRowAtIndex delegate method should have :
if expense != nil
{
lblExpenseConstraintHeight.constant = 0
}
else
{
lblExpenseConstraintHeight.constant = 34
}
cell.layoutIfNeeded()
Edit:
More info about about dynamic tableViewHeight:
Using Auto Layout in UITableView for dynamic cell layouts & variable row heights
Another possible solution is remove the height constraint of the (date of expense) label and set it's text to empty string.
I have a UICollectionView with
a custom layout that displays the items in a horizontal row
a custom UICollectionViewCell which contains a number of subviews - an image
and some labels.
Multiple instances of the UICollectionView are displayed vertically in a UITableView.
On iOS 8.1, all works well. Testing on iOS 7.1 though I can not drag the collection unless my finger is in the top third of the row (indicated by the yellow rectangle in the picture).
Note that the UITableView scrolls correctly vertically.
Any suggestions?
Thanks in advance, and apologies for the C#.
// The Layout
public sealed class SingleRowLayout : UICollectionViewFlowLayout
{
public SingleRowLayout()
{
this.ItemSize = new CGSize(50, 120); // 120 is the full cell height
this.ScrollDirection = UICollectionViewScrollDirection.Horizontal;
}
}
// The UICollectionViewCell
public sealed class StickerViewCell : UICollectionViewCell
{
[Export ("initWithFrame:")]
public StickerViewCell (CGRect frame) : base (frame)
{
var top = 0f;
this.imageView = new UIImageView();
this.imageView.ContentMode = UIViewContentMode.ScaleAspectFit;
this.imageView.Frame = new CGRect(
new CGPoint(GridDimensions.ImageInternalSidePadding, top),
new CGSize(GridDimensions.StickerWidth - (GridDimensions.ImageInternalSidePadding * 2), GridDimensions.StickerHeight)
);
top = (float)this.imageView.Frame.Height
this.sequenceTextView = this.createTextView(ref top);
this.dateTextView = this.createTextView(ref top);
this.sensationTextView = this.createTextView(ref top);
ContentView.AddSubview(this.imageView);
ContentView.AddSubview(this.sequenceTextView);
ContentView.AddSubview(this.sensationTextView);
ContentView.AddSubview(this.dateTextView);
}
private UILabel createTextView(ref float top)
{
var result = new UILabel();
result.AdjustsFontSizeToFitWidth = true;
result.Frame = new CGRect(
new CGPoint(2, top),
new CGSize(GridDimensions.StickerWidth - (2 * 2), GridDimensions.TextHeight)
);
top += GridDimensions.TextHeight + GridDimensions.TextFieldSpacing;
return result;
}
}
Each UICollectionViewCell has its own button hooked up to the following action:
#IBAction func dropDown(sender:UIButton){
var pt = sender.bounds.origin
var ptCoords : CGPoint = sender.convertPoint(pt, toView:sender.superview);
var ptCoords2 : CGPoint = sender.convertPoint( ptCoords, toView: collectionView!);
var cellIndex: NSIndexPath = self.collectionView!.indexPathForItemAtPoint(ptCoords2)!
//var i : NSInteger = cellIndex.row;
//var i2 : NSInteger = cellIndex.section;
var selectedCell = collectionView?.cellForItemAtIndexPath(cellIndex) as CollectionViewCell!
selectedCell.button.backgroundColor = UIColor.blackColor()
for (var i = 0; i < 3; i++){
var textView : UITextView! = UITextView(frame: CGRectMake(self.view.frame.size.width - self.view.frame.size.width/1.3, CGFloat(50 + (30*(i+1))), CGRectGetWidth(self.view.frame), CGFloat(25)))
textView.backgroundColor = UIColor.whiteColor()
selectedCell.contentView.addSubview(textView)
}
}
What I want to do is add 3 subviews to only the cell that's been tapped. The subviews are added successfully, but as soon as I scroll, cells that come into view & correspond to the previously set indexPath are loaded with 3 subviews. I figure this is due to the dequeueReusableCellWithReuseIdentifier method, but I can't figure out a way around it. I considered removing the subviews on scrollViewDidScroll, but ideally I would like to keep the views present on their parent cell until the button is tapped again.
EDIT:
Okay, I ditched the whole convertPoint approach and now get the cell index based on button tags:
var selectedCellIndex : NSIndexPath = NSIndexPath(forRow: cell.button.tag, inSection: 0)
var selectedCell = collectionView?.cellForItemAtIndexPath(selectedCellIndex) as CollectionViewCell!
Regardless, when I try to add subviews to only the cell at the selected index, the subviews are duplicated.
EDIT:
I've created a dictionary with key values to track the state of each cell like so:
var cellStates = [NSIndexPath: Bool]()
for(var i = 0; i < cellImages.count; i++){
cellStates[NSIndexPath(forRow: i, inSection: 0)] = false
}
which are set by cellStates[selectedCellIndex] = true within the dropDown function. Then in the cellForItemAtIndexPath function, I do the following check:
if(selectedIndex == indexPath && cellStates[indexPath] == true){
for (var i = 0; i < 3; i++){
var textView : UITextView! = UITextView(frame: CGRectMake(cell.frame.size.width - cell.frame.size.width/1.3, CGFloat(50 + (30 * (i+1))), CGRectGetWidth(cell.frame), CGFloat(25)))
textView.backgroundColor = UIColor.whiteColor()
cell.contentView.addSubview(textView)
println("display subviews")
println(indexPath)
}
} else {
println("do not display subviews")
println(indexPath)
}
return cell
where selectedIndex, the NSIndexPath of the active cell set via the dropDown function, is compared to the indexPath of the cell being created & the cellState is checked for true.
Still no luck - the subviews are still displayed on the recycled cell. I should mention that "display subviews" and "do not display subviews" are being logged correctly while scrolling, so the conditional statement is being evaluated successfully.
MY (...hack of a...) SOLUTION!
Probably breaking a bunch of best coding practices, but I assigned tags to all the created subviews, remove them at the beginning of the cellForItemAtIndexPath method, and create them again if the cellState condition returns true.
No problem. Basically, you need to store program state OUTSIDE your UI components in what is commonly called a "model". Not sure what your app is so I am going to make up an example. Assume you want to show a grid where each cell is initially green and they toggle to red when the user taps it. You would need to store the state (I.e., whether a cell has been tapped or not) in some two dimensional array, which is going to contain a Boolean for ALL cells, and not just the ones that are currently showing (assuming you have enough cells to make the grid scroll). When the user taps a cell you set the flag in corresponding array element. Then, when the iOS calls you back to provide a cell (in the dequeue method) you check the state in the array, apply the appropriate color to the UIView of the cell, then return it. That way, iOS can reuse the cell view objects for efficiency, while at the same time you apply your model state to corresponding cells dynamically. Let me know if this clear.
One of two things:
- Disallow pooling of cells.
- Maintain sufficient info in your mode to be able to draw cells depending on the model rather than on their location on screen. That is, store a bit in your model that determines whether or not to show the three views for each "logical" cell. Then, when asked to dequeue a cell, check its model and add/remove the backgrounds dynamically.