Why isn't cell resizing when content is taller than the EstimatedSize (sizeHint) - ios

I ported a table view implementation from Swift with a variable height cell. But in my Xamarin/ReactiveUI implementation, the cell does not resize when the content grows (i.e. when an optional label is displayed). Please note that the cell does resize to fit in the native Swift implementation.
Most answers online focus on the setting of two properties on UITableView: RowHeight and EstimatedHeight as well as using AutoLayout. My row height is set to UITableView.AutomaticDimension and the EstimatedHeight is set to 44f. I am using autolayout constraints which I will show below. I am using the BindTo extensions in ReactiveTableViewSourceExtensions.
I also tried setting the Text property of the optional label right away to test the theory that setting the property up front would provoke a resize.
Here are the pertinent lines of code:
In a ReactiveViewController<T> ctor:
this.WhenActivated(disposables =>
{
//// ...
// This method automatically wires up the cell reuse key to be the nameof my cell class which is what I want.
this.WhenAnyValue(view => view._cells)
.BindTo<IImportedFileViewModel, ImportedFileCell>(TableView, 44f)
.DisposeWith(disposables);
//// ...
});
Below in ViewDidLoad:
//// ...
TableView = new UITableView
{
RowHeight = UITableView.AutomaticDimension,
EstimatedRowHeight = 44f, // Setting or not setting this doesn't matter
SeparatorStyle = UITableViewCellSeparatorStyle.None,
AllowsSelection = false,
TranslatesAutoresizingMaskIntoConstraints = false,
};
//// ...
In the cell implementation:
ClipsToBounds = true;
PreservesSuperviewLayoutMargins = true;
ContentView.ClipsToBounds = true;
ContentView.PreservesSuperviewLayoutMargins = true;
var stackView = new UIStackView
{
TranslatesAutoresizingMaskIntoConstraints = false,
Axis = UILayoutConstraintAxis.Vertical,
Distribution = UIStackViewDistribution.Fill,
Alignment = UIStackViewAlignment.Fill,
Spacing = 4,
};
var fileStackView = new UIStackView
{
TranslatesAutoresizingMaskIntoConstraints = false,
Axis = UILayoutConstraintAxis.Horizontal,
Distribution = UIStackViewDistribution.Fill,
Alignment = UIStackViewAlignment.Top,
};
FilenameLabel = new UILabel
{
TranslatesAutoresizingMaskIntoConstraints = false,
Font = UIFont.SystemFontOfSize(15f, UIFontWeight.Medium),
};
fileStackView.AddArrangedSubview(FilenameLabel);
StatusImage = new UIImageView()
{
TranslatesAutoresizingMaskIntoConstraints = false,
ContentMode = UIViewContentMode.Center,
};
fileStackView.AddArrangedSubview(StatusImage);
stackView.AddArrangedSubview(fileStackView);
var reasonStackView = new UIStackView
{
TranslatesAutoresizingMaskIntoConstraints = false,
Axis = UILayoutConstraintAxis.Horizontal,
Distribution = UIStackViewDistribution.Fill,
Alignment = UIStackViewAlignment.Top,
};
// This is the optional label that, when its Text property is set, should resize the cell.
FailureReasonLabel = new UILabel
{
TranslatesAutoresizingMaskIntoConstraints = false,
Font = UIFont.SystemFontOfSize(13f, UIFontWeight.Medium),
Lines = 0,
};
reasonStackView.AddArrangedSubview(FailureReasonLabel);
stackView.AddArrangedSubview(reasonStackView);
ContentView.AddSubview(stackView);
stackView.BottomAnchor.ConstraintEqualTo(ContentView.LayoutMarginsGuide.BottomAnchor).Active = true;
stackView.TopAnchor.ConstraintEqualTo(ContentView.LayoutMarginsGuide.TopAnchor).Active = true;
stackView.LeadingAnchor.ConstraintEqualTo(ContentView.LayoutMarginsGuide.LeadingAnchor).Active = true;
stackView.TrailingAnchor.ConstraintEqualTo(ContentView.LayoutMarginsGuide.TrailingAnchor).Active = true;
StatusImage.HeightAnchor.ConstraintEqualTo(16f).Active = true;
StatusImage.WidthAnchor.ConstraintEqualTo(16f).Active = true;
I would be happy to get a solution to the problem using the basic structure I have now but I would also accept an example of some other pattern that someone has used (based on ReactiveUI) to get this working. I would prefer that the solution not be based on old manual resizing patterns like in the pre-iOS 8 days or some sort of hack.

So two things had to be adjusted to fix my issue.
The sizeHint parameter in the ReactiveUI BindTo method does not behave like TableView.EstimatedRowHeight, which is what I was assuming. So I ended up setting it to the auto dimension constant like this:
.BindTo<IImportedFileViewModel, ImportedFileCell>(TableView, (float)UITableView.AutomaticDimension)
I was thinking that I could just update the properties of the view models within the IObservableCollection implementation that is passed in to the BindTo method but it wasn't until I started provoking changed events on the collection itself (the collection that the view is bound to) that the cells started resizing themselves. So since I am using DynamicData as part of ReactiveUI, that meant calling SourceCache.AddOrUpdate(updatedViewModel) whenever I knew that the FailureReasonLabel had been set.
I am also going to try and restore the ReactiveUI tag that was removed from this post because I believe it to be a relevant part of this question and its answer.

Related

How to render a UIButton in Xamarin.iOS

How do I render a UIButton in Xamarin.iOS? See the current Code for the full list.
This is the code I'm using to create and add the button to the Xamarin.Forms.Platform.iOS.CellTableViewCell cell. I cannot get the button to display anything.
With the use of a Foundation.NSMutableAttributedString, it shows a cut-off section of text in the top left corner, regardless of anything I try (alignments, insets, bounds, various constraints, etc). I'm currently trying things from Xamarin.Forms.Platform.iOS.Renderers.ButtonRenderer, but still can't get anything to display at all, no text, no button, or its outline.
If you could fork the repo and fix it or post the solution here, I would be very grateful.
protected override void SetUpContentView()
{
var insets = new UIEdgeInsets(SVConstants.Cell.PADDING.Top.ToNFloat(), SVConstants.Cell.PADDING.Left.ToNFloat(), SVConstants.Cell.PADDING.Bottom.ToNFloat(), SVConstants.Cell.PADDING.Right.ToNFloat());
_Button = new UIButton(UIButtonType.RoundedRect)
{
AutoresizingMask = UIViewAutoresizing.All,
HorizontalAlignment = UIControlContentHorizontalAlignment.Center,
VerticalAlignment = UIControlContentVerticalAlignment.Center,
ContentEdgeInsets = insets,
// TitleEdgeInsets = insets
};
DefaultFontSize = _Button.TitleLabel.ContentScaleFactor;
DefaultTextColor = _Button.TitleLabel.TextColor;
_Recognizer = new UILongPressGestureRecognizer(RunLong);
_Button.TouchUpInside += OnClick; // https://stackoverflow.com/a/51593238/9530917
_Button.AddGestureRecognizer(_Recognizer); // https://stackoverflow.com/a/6179591/9530917
ContentView.AddSubview(_Button);
_Button.CenterXAnchor.ConstraintEqualTo(ContentView.CenterXAnchor).Active = true;
_Button.CenterYAnchor.ConstraintEqualTo(ContentView.CenterYAnchor).Active = true;
_Button.WidthAnchor.ConstraintEqualTo(ContentView.WidthAnchor).Active = true;
_Button.HeightAnchor.ConstraintEqualTo(ContentView.HeightAnchor).Active = true;
UpdateConstraintsIfNeeded();
LayoutIfNeeded();
}
Found out that you can't subclass it. Any button added to the view must be native (UIButton) or custom rendered, such as Xamarin.Forms.Platform.iOS.ButtonRenderer; It doesn't show up otherwise.

How do I limit the size of a UIView in a storyboard designer and the runtime view overall?

I have a custom UIView that's my little component lets call it PlaceholderView.
My UITableViewCell prototype has a Label and a PlaceholderView that sits inside a UIStackView that's vertically axis.
The PlaceholderView, is supposed to call some custom code that goes to a cache to retrieve a specific view then it adds it to the SubView of the PlaceholderView.
I want this subview to take up the whole surface of the entire PlaceholderView. How do I go about doing that? I tried this but not sure if it does the job
if (view != null)
{
AddSubview(view);
view.SizeToFit();
}
Second question. These view's that I am adding, when I create them during design time, I make a new storyboard, drag and drop a ViewController then proceed to place other controls like Labels and Button's on it.
How do I restrict this ViewController's overall height so it's completely fixed size? I know I can set the simulated metrics, and I am also setting the View. Frame's size to restrict the height.
Are there better ways to make these views constrained easier?
Currently, when I am setting these fixed height values, it does cause some weird issues with overlaps if I set UITableView.RowHeigh to AutomaticDimension.
// attaches all sides of the child to its parent view
extension UIView {
func layoutToSuperview() {
guard let view = self.superview else {return}
self.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
self.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
self.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
self.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
}
}
Usage:
Instead of view.SizeToFit() use view.layoutToSuperview()
I want this subview to take up the whole surface of the entire
PlaceholderView
You can try autolayout:
if (view != null)
{
view.TranslatesAutoresizingMaskIntoConstraints = false;
AddSubview(view);
view.LeadingAnchor.ConstraintEqualTo(this.LeadingAnchor).Active = true;
view.TopAnchor.ConstraintEqualTo(this.TopAnchor).Active = true;
view.TrailingAnchor.ConstraintEqualTo(this.TrailingAnchor).Active = true;
view.BottomAnchor.ConstraintEqualTo(this.BottomAnchor).Active = true;
}
Are there better ways to make these views constrained easier?
The answer is also auto layout. Set a control's height constraint to claim how much space it wants to take. See this thread for more details: Using Auto Layout in UITableView for dynamic cell layouts & variable row heights.
Though it is native oc, you can see its concept.

UISegmentedControl with rounded corner and non proportional segments bug

i recently updated a rounded corner segment control to have different width for each segment.
the issue i have is that the last segment doesn't align properly with the end of the segmented control
i just used this code for this sample (seg being my segmented control) :
seg.layer.borderWidth = 1
seg.layer.cornerRadius = seg.bounds.height / 2
seg.layer.masksToBounds = true
seg.apportionsSegmentWidthsByContent = true
if i remove the masksToBounds line i can see that the right segment doesn't reach the edge of the segmented control.
is there any way to fix this issue?
It appears that this is caused by a rendering bug in UISegmentedControl when apportionsSegmentWidthsByContent = true. I couldn't find a simple workaround by manipulating the CALayers of the control. You can create a custom control using UIStackView to mimic the UISegmentedControl.
You should also file a radar with Apple.
I don't know if this still affects iOS, but a workaround in Xamarin.iOS is: Override UISegmentView, calculate text width of the last segment element and update it's width.
Sample code in C#:
public class MySegmentedControl : UISegmentedControl
{
public override void MovedToSuperview ()
{
// Fix truncation of last element
// when ApportionsSegmentWidthsByContent = true
if (ApportionsSegmentWidthsByContent) {
RecalculateLastSegmentWidth ();
}
}
private void RecalculateLastSegmentWidth()
{
var font = UIFont.FromName("Helvetica", 12);
var lastSegment = NumberOfSegments - 1;
var segmentTitle = TitleAt(lastSegment);
var segmentWidth = segmentTitle.StringSize(font).Width + 20;
SetWidth(segmentWidth, lastSegment);
}
}

Xamarin iOS Center TableViewCell Subview

I´m using the NuGet-Package iCarousel as a Subview in UITableViewCell.
Here is my code where I add the iCarousel object (this code is located in the ViewDidLoad method of my parent ViewController):
InvokeOnMainThread(async() =>
{
items = await ppCtrl.GetAllPerpetrationPartIds(masterProject.Id).ConfigureAwait(false);
if (items != null && items.Any())
{
InvokeOnMainThread(() =>
{
var carousel = new iCarousel
{
Bounds = CarouselViewCell.ContentView.Bounds,
ContentMode = UIViewContentMode.Center,
Type = iCarouselType.CoverFlow2,
Frame = CarouselViewCell.ContentView.Frame,
CenterItemWhenSelected = true,
DataSource = new SimpleDataSource(items, CarouselViewCell.ContentView.Bounds.Width, CarouselViewCell.ContentView.Bounds.Height),
Delegate = new SimpleDelegate(this)
};
NSLayoutConstraint centerX = carousel.CenterXAnchor.ConstraintEqualTo(this.CarouselViewCell.ContentView.CenterXAnchor);
NSLayoutConstraint centerY = carousel.CenterYAnchor.ConstraintEqualTo(this.CarouselViewCell.ContentView.CenterYAnchor);
centerX.SetIdentifier("centerXCostraint");
centerY.SetIdentifier("centerYConstraint");
var centerConstraints = new[] {centerX, centerY };
CarouselViewCell.ContentView.TranslatesAutoresizingMaskIntoConstraints = false;
CarouselViewCell.ContentView.AddConstraints(centerConstraints);
//NSLayoutConstraint.ActivateConstraints(centerConstraints);
CarouselViewCell.ContentView.ContentMode = UIViewContentMode.Center;
CarouselViewCell.ContentView.AddSubview(carousel);
ViewDidLayoutSubviews();
});
}
});
Now I want to vertically and horizontally center the iCarousel. So I added this code:
NSLayoutConstraint centerX = carousel.CenterXAnchor.ConstraintEqualTo(this.CarouselViewCell.ContentView.CenterXAnchor);
NSLayoutConstraint centerY = carousel.CenterYAnchor.ConstraintEqualTo(this.CarouselViewCell.ContentView.CenterYAnchor);
centerX.SetIdentifier("centerXCostraint");
centerY.SetIdentifier("centerYConstraint");
var centerConstraints = new[] {centerX, centerY };
CarouselViewCell.ContentView.TranslatesAutoresizingMaskIntoConstraints = false;
CarouselViewCell.ContentView.AddConstraints(centerConstraints);
My problem is now that the app Crashes at this line:
CarouselViewCell.ContentView.AddConstraints(centerConstraints);
I found in a similar post that you need to add the constraints to the assiciated UIView object. I tried several objects but with no success.
CarouselViewCell.ContentView.AddConstraints(centerConstraints);
This code will crash because we need to add the control first, then set its constraints. Or it will crash. Try to adjust the code's order:
//Let this code just lie after the carousel's construction.
CarouselViewCell.ContentView.AddSubview(carousel);
Also if you want to add the carousel's constraints, you should set the carousel's TranslatesAutoresizingMaskIntoConstraints to false not CarouselViewCell.ContentView.
Moreover depending on your description, I think you want to put an iCarousel on UITableViewCell. This configuration should be set in the Cell class not in the parent ViewController class.

AutoLayout in programmatically created UIControl doesn't appear to work

I've created a subclass of UIControl called 'TestButton' with a label and an imageview subview. That object is created with a frame, and as part of the init process I create the subview elements.
These 'TestButtons' are created programmatically, I never use them in the StoryBoard.
Code snippet:
class TestButton: UIControl {
var iconImageView: UIImageView?
var labelView: UILabel?
required init(size: CGSize, icon: UIImage? text: String?) {
super.init(frame: CGRect(x: 0, y: 0, width: size.width, height: size.height) )
if ( (icon != nil) && (text != nil) ) {
self.iconImageView = UIImageView()
self.iconImageView?.translatesAutoresizingMaskIntoConstraints = false
self.iconImageView?.contentMode = .center
self.iconImageView?.image = icon
self.iconImageView?.backgroundColor = UIColor.yellow // test: show bounds
self.labelView = UILabel()
self.labelView?.translatesAutoresizingMaskIntoConstraints = false
self.labelView?.text = "Test"
self.labelView?.backgroundColor = UIColor.blue
self.addSubview(self.iconImageView!)
//self.addSubview(self.labelView!)
// Setup constraints on created subview(s)
self.iconImageView?.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
self.iconImageView?.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
self.iconImageView?.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
self.iconImageView?.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
}
}
override func layoutSubviews() {
super.layoutSubviews()
print("iconframe: \(self.iconImageView!.frame)")
}
In the sample above, I've removed the label from the mix. I'm only trying to get the imageView constraints to work and effectively size the imageView to the view. This does not work, the image appears full size and the constraints appear to have been ignored. I've tried moving the constraints code into updateConstraints and calling that - all appears to work but again the constraints are not applied.
layoutSubviews does get called when you would expect it to be but the imageView frame is unmodified. There are no messages in the output window, it just silently doesn't work.
My question is; have I somehow disabled autoLayout by specifying the parent's frame? I would have expected autoLayout to still work within the bounds of the parent's frame?
Sorry if this has been answered once or many times before. I'm not actually sure what I'm searching for or the correct question to ask, only posted after a day of trawling SO. Thanks
The behaviour of TestButton view will depend on how it is constrained within its superview.
In NSLayoutConstraints both participating attributes (or anchors) are equal "partners": with just those four constraints you have, imageView will take full frame of it's parent (TestButton), but at the same time TestButton will be expanded to be big enough for a full-size image.
You can apply other constraints to TestButton view to prevent the latter.
To understand why standard views behave like that, look at intrinsicContentSize property (docs). It is implemented by standard controls, and tells the auto layout system how big the view should be, purely based on it's content (UIButton or UISwitch are auto-sized like that, for example). UIImageView's intrinsicContentSize is the size of its image, that's why it expands full-size if nothing is preventing it.

Resources