UIButton height not being set correctly on UITableView - ios

I have an UITableView which holds a list of items. For each UITableViewcell in that table, depending on a property from the associated object to that cell, I toggle the visibility of an UIButton by changing the height constraint constant to 0, or to a value defined to make it visible. I've checked the Clip to Bounds option on the Xcode designer for that button.
If I feed the table view a list of items that set some of the buttons visible, and others hidden and scroll, the cells that had the button visible may have it hidden, and vice-versa. This is more noticeable when there's few cells with the button, and the rest without it.
The method that contains the logic to show or hide the UIButton is from within the UITableViewCell custom class for the cells, as it follows:
public partial class UITableViewCellCustom : UITableViewCell
{
public Object obj;
public void SetObject(Object obj)
{
// Do something with obj...
// Do something with the obj that determines if the buttons should be collapsed or not
Boolean collapseButton = ...;
ToggleButtonVisibility(collapseButton);
}
private void ToggleButtonVisibility(Boolean collapse)
{
NSLayoutConstraint uiButtonCancelHeightConstraint = UIButtonCancel.Constraints
.FirstOrDefault(query => query.FirstItem == UIButtonCancel
&& query.FirstAttribute == NSLayoutAttribute.Height);
NSLayoutConstraint uiButtonCancelTopConstraint = this.ContentView.Constraints
.FirstOrDefault(query => query.FirstItem == UIButtonCancel
&& query.FirstAttribute == NSLayoutAttribute.Top);
if (collapse)
{
uiButtonCancelHeightConstraint.Constant = 0;
uiButtonCancelTopConstraint.Constant = 0;
}
else
{
uiButtonCancelHeightConstraint.Constant = 30;
uiButtonCancelTopConstraint.Constant = 10;
}
}
}
The SetObject method is called from the UITableViewSource class that gets the object from the correct index and sets it to the cell ( No problem here ). Then, while some UILabels texts are changed with the values from the object, I check if the button is required or not ( No problem here ). When I call the ToggleButtonVisibility method, and attempt to change the two constraints -- height and top -- the values are applied, the top constraint is visibly changed, but the height constraint seems to be ignored when the cell is reused.
I've tried to force the ClipToBounds to true, force the method in the main thread, but none of them worked. What am I missing here?
Forgot to mention: When the button is pressed, the table view is cleared ( I feed the source an empty list, and reload the data ), an long task is performed, and then a new list is applied to the table, but the cell in question remains with the button bugged.
Notes:
Hiding the button by changing the Alpha to 0 or by set the Hidden to true is not an option, since it will leave an hole within the tableview.

Fixed this problem by wrapping the UIButton on a UIView and then resizing the view depending on my needs.

Related

How to add unique insets to specific UITableViewCells?

I have a UITableView whose insetsContentViewsToSafeArea property is set to false - this makes the tableView span the width of the screen.
The amount and different types of cells are driven by the server, so there's no way of knowing the content of the tableView.
What I'd like to to:
Assign unique insets to certain cells only.
I can't post a screenshot, so I'll try to make a quick doodle:
These three cells are all in the same UITableView:
|[This is one cell that goes edge-to-edge]|
|[Here's another one]|
|-----This cell needs its own insets-----|
Question:
What's the best way to achieve this?
What I've tried:
Overriding layoutMarginsDidChange
Trying to add layoutMargins directly in cellForRowAtIndexPath...
Create a UITableViewCell subclass for the cell with custom insets.
Then you have 2 options --
Add a subview to cell's contentView with constraints -- these will correspond to your desired insets. This view will act as your " pseudo contentView" (add cell content to it)
Play around with contentView's insets. (not sure if it'll work)
I hope this will get you started.
You can use the cell subclass for all cell cases too if you want -- with an enum style which you can set in cell for row.
enum Style {
case edgeToEdge
case withInset(inset: UIEdgeInset)
}
inside cell subclass
var insetStyle: Style = .edgeToEdge {
didSet {
//update the constraints of the pseudo content view
}
}

Stop Keyboard From Hiding UITextField When Open

Looking for a solution that stops a UITextField from being hidden by the keyboard when it opens regardless of where the UITextField is in the view hierarchy and regardless of other UI elements.
There are plenty of solutions online for adjusting your view so that a UITextField isn't hidden behind the keyboard when it appears however, I couldn't find a "one size fits all" kind of solution so had to adapt a lot of answers and make my own. This is the solution I came up with. Hopefully it helps someone :)
First of all, this solution assumes that your "primary parent" or "top level" view is a UIScrollView or descendant (e.g. UITableView or UICollectionView). This will not work if you don't have a UIScrollView in your hierarchy as this is what is used to scroll the UITextField into position. Other solutions show you how to scroll any UIView but when you think about it, if you need to scroll your UITextField into position, chances are your view should be scrollable anyway to stop it from going off the screen.
Create the extension method below. This searches for subviews of subviews (etc.) that match the given query and returns them as an array. We'll use this later.
public static UIView[] Find(this UIView view, Func<UIView, bool> query)
{
if (view == null)
return null;
var views = new List<UIView>();
if (query.Invoke(view))
views.Add(view);
foreach (var subview in view.Subviews)
{
var foundViews = subview.Find(query);
if (foundViews != null)
views.AddRange(foundViews);
}
return views.ToArray();
}
Add the below method to your UIViewController. This finds the top level UIScrollView from the view hierarchy. There should only be one result.
private UIScrollView FindAdjustmentScrollView()
{
var scrollViews = View.Find(v => v is UIScrollView && v.FindSuperviewOfType(View, typeof(UIScrollView)) == null);
return scrollViews.Length > 0 ? scrollViews[0] as UIScrollView : null;
}
Add the below event handlers. We'll register these with observers next.
private void Keyboard_Appear(NSNotification notification)
{
var firstResponder = View.Find(v => v.IsFirstResponder).FirstOrDefault();
var scrollView = FindAdjustmentScrollView();
if (firstResponder == null || scrollView == null || !(notification.UserInfo[UIKeyboard.FrameEndUserInfoKey] is NSValue value))
return;
var keyboardBounds = value.CGRectValue;
var firstResponderAbsoluteFrame = firstResponder.Superview.ConvertRectToView(firstResponder.Frame, View);
// This is how much of a gap you would like there to be between the bottom of the UITextField
// and the top of the keyboard. Not mandatory but a nice touch in my experience.
var offset = 8;
var bottom = firstResponderAbsoluteFrame.Y + firstResponderAbsoluteFrame.Height + offset;
var scrollAmount = keyboardBounds.Height - (scrollView.Frame.Size.Height - bottom);
if (scrollAmount > 0)
scrollView.SetContentOffset(0, scrollView.ContentOffset.Y + scrollAmount);
}
private void Keyboard_Disappear(NSNotification notification)
{
var firstResponder = View.Find(v => v.IsFirstResponder).FirstOrDefault();
var scrollView = FindAdjustmentScrollView();
if (firstResponder == null || scrollView == null)
return;
scrollView.SetContentOffset(0, 0);
}
Add these 2 fields to your UIViewController. By keeping an object reference to the observers we can unregister them later when the UIViewController is no longer visible.
private NSObject _keyboardWillShowObserver, _keyboardWillHideObserver;
In ViewWillAppear (or wherever you register your event handlers) add these 2 lines. These register the observers and keep an object reference so we can unregister later.
_keyboardWillShowObserver = NSNotificationCenter.DefaultCenter.AddObserver(UIKeyboard.WillShowNotification, Keyboard_Appear);
_keyboardWillHideObserver = NSNotificationCenter.DefaultCenter.AddObserver(UIKeyboard.WillHideNotification, Keyboard_Disappear);
In ViewWillDisappear (or wherever you unregister your event handlers) add these 2 lines. These unregister the observers from the UIViewController so they no longer respond to events.
NSNotificationCenter.DefaultCenter.RemoveObserver(_keyboardWillShowObserver);
NSNotificationCenter.DefaultCenter.RemoveObserver(_keyboardWillHideObserver);
To clarify a few things:
This solution works when your UITextField is a subview many layers down (even
a UITableViewCell or UICollectionViewCell).
This solution does take into account any modifications made to the keyboard view. For example: if you add a "done" button to the top of your keyboard this will be calculated.
The scroll adjustment is animated.
This solution does work in different orientations.

Show entire table view inside cell

I need to display a dynamic table view inside a cell (of a static table). Using sections instead will not be enough for me. But I don't want this table to be scrollable, so the entire table must appear at once. The problem is that this table size [and rows count, and each row size] varies according to the content being shown. How can I associate the cell (which holds the table) autoresizing property with a table inside that must show all content at once?
Currently I have the tableView inside the cell, and constraints bonds it to all the 4 sides. The first table (not the one inside the cell) rowHeight property is set to UITableViewAutomaticDimension, but the table inside the cell doesn't appear entirely.
If I set the static cell height to a value greater than the tableView(inside cell) height, the whole table appears, and also an extra space beneath it (as the table is bounded to 4 sides of the cell)
So, any ideas on how to show this entire table inside a cell that dynamically has the perfect size for it ?
ps: I tried using collection view inside the cell. Unfortunately it doesn't serve my purpose.
Thanks!
Update
I tried to create a class for the inner table and use (as pointed by iamirzhan) contentSize didSet, like so:
override var contentSize:CGSize {
didSet {
self.invalidateIntrinsicContentSize()
}
}
I then used this method to call a function that resizes the cell that holds the table: self.frame.size.height = table.contentSize.height. The function is on this cell's own class.
This worked, the table now appears entirely. The problem is that it overlaps the cell underneath it, so i'm still looking for a solution.
I don't see why this requires 2 tableviews. The static portion should be uitableviewheaderfooters or just a UIStackView. But to answer your question simply query your child tableView for its size and return this in tableView:heightForRowAtIndexPath: for the outer tableview. The height of the child tableView is simply the sum of the hieght of all of its children (including any headers/footers). This is usually not difficult to calculate unless you are using something like a webview where you need to actually load the content and get the size asynchronously. You can calculate the size of elements that are based on their intrinsic content size with UIView.sizeThatFits(_:). The other elements should have fixed sizes or constants in the storyboard that you can sum up.
For inner tableView you should write such implementation.
Firstly, disable the scrollEnable flag
Then you should override the intrinsicContentSize method of tableView
Your inner tableView custom class:
class IntrinsicTableView: UITableView {
override var contentSize:CGSize {
didSet {
self.invalidateIntrinsicContentSize()
}
}
override var intrinsicContentSize: CGSize {
self.layoutIfNeeded()
return self.contentSize
}
}
Now, delete the height constraint, and the height will be calculating by its content.

Xamarin iOS Autolayout: Resize width and vertical scroll automatically for various devices while keeping the horizontal scroll disabled

I want to create a page which has a vertical but no horizontal scroll. It must adjust width of the content and vertical scroll automatically as per screen size.
Something similar to this:
I can not use UITableView since, my page may not have necessarily homogenous elements. It could have a combination of textfields , dropdown etc.
The previous answer was quite right, but not right at all. Indeed I tried to solve this problem using the method described before, but to make it work, I made some adjustments.
Your view's hierarchy has to be as follow :
UIScrollview :
View1
View2
View3
You don't need a container inside the UIScrollview, because apart the fact that it will be an extraview that you don't need, there is the problem that if you use this container-view you will get problem getting touch events in the views added.
So, let's make a step-by-step process:
Add scrollview to your viewController
The first step is to add the scrollview to your viewController, and we can simply do this programmatically in the following way:
UIScrollView scrollView = new UIScrollView();
scrollView.TranslatesAutoresizingMaskIntoConstraints = false;
View.AddSubview(scrollView);
View is the main-view of the viewController you are working in (aka Self.View).
Put attention to set TranslateAutoResizionMaskIntoConstrains property of the scrollview to false, otherwise autoresizing will mess your constraints.
Add constraint (autolayout) to your scrollView
You need to ensure that you layout will adjust for every different iPhone-screen, so simply use auotlayout to pin your scrollView to the viewController main-view (is the View used in the next code sample):
scrollView.TopAnchor.ConstraintEqualTo(View.TopAnchor, 0).Active = true;
scrollView.BottomAnchor.ConstraintEqualTo(View.BottomAnchor, 0).Active = true;
scrollView.LeadingAnchor.ConstraintEqualTo(View.LeadingAnchor, 0).Active = true;
scrollView.TrailingAnchor.ConstraintEqualTo(View.TrailingAnchor, 0).Active = true;
In this way your scrollView is pinned to the bound of the main-view.
Create the view to be added
You need to create the view that you will add to the scrollView:
UIView viewToBeAdded = new UIView();
viewToBeAdded.TranslatesAutoresizingMaskIntoConstraints = false;
viewToBeAdded.Frame = new CGRect(0, 0, UIScreen.MainScreen.Bounds.Width, 200);
We have created a new UIView that setting its frame large as the screen (UIScreen.MainScreen.Bounds.Width) so it won't scroll horizontally, and with an arbitrary height (200 in the sample).
NOTE : even in this case you have to set TranslateAutoResizingMaskProperty to false, otherwise you will get a mess.
Add the view to the scrollView
Next step is to add our new view to the scrollView as follow:
scrollView.AddSubview(view);
Nothing more.
Set constraint for the view added in relation to the scrollView
Once you have added your view you have to said which will her behavior related to the scrollView. We assume that we will add several view to the scrollView, so we have to made a distinction, to the behavior of the FIRST view, the IN-BETWEEN views, and the LAST view.
So to be clear we assume that we are adding only 3 views, so we will have the three different cases.
FIRST VIEW
The important thing is that the first view has to be pinned to the top of the scrollView, we do this as follow :
firstView.TopAnchor.ConstraintEqualTo(scrollView.TopAnchor, 0).Active = true;
and then we set the others constraints:
firstView.WidthAnchor.ConstraintEqualTo(firstView.Bounds.Width).Active = true;
firstView.HeightAnchor.ConstraintEqualTo(firstView.Bounds.Height).Active = true;
IN-BETWEEN VIEW
The in between views (in our sample the secondView) need to be pinned to the previous view added (in our case the first view). So we do as follow:
secondView.TopAnchor.ConstraintEqualTo(firstView.BottomAnchor).Active = true;
So the top of the secondView is pinned to the bottom of the firstView.
And then we add the others constraints:
secondView.WidthAnchor.ConstraintEqualTo(secondView.Bounds.Width).Active = true;
secondView.HeightAnchor.ConstraintEqualTo(secondView.Bounds.Height).Active = true;
LAST VIEW
The last view (in our case the third view) instead needs to be pinned to the bottom of the previousView (in our case the secondView) and to the bottom of the scrollView.
thirdView.TopAnchor.ConstraintEqualTo(secondView.BottomAnchor).Active = true;
thirdView.BottomAnchor.ConstraintEqualTo(scrollView.BottomAnchor).Active = true;
And the usual other constraints for width and eight:
thirdView.WidthAnchor.ConstraintEqualTo(thirdView.Bounds.Width).Active = true;
thirdView.HeightAnchor.ConstraintEqualTo(thirdView.Bounds.Height).Active = true;
In this way the eight of the scrollView will adapt to the eight of the views added, due to the fact that the views inside are pinned to the top and the bottom of the scrollView.
CONCLUSIONS
If you follow these simple instruction you will get everything work. Remember to disable autoResizingMask, as this is on of the common mistake.
Hope it was helpful.
Cheers
In a custom renderer for Xamarin.Forms i've written my UITableViewController like this:
_controller = new InfoFieldItemsTableViewController();
_controller.TableView.SeparatorStyle = UITableViewCellSeparatorStyle.None;
_controller.TableView.SeparatorColor = UIColor.Clear;
_controller.TableView.AllowsSelection = false;
// http://useyourloaf.com/blog/self-sizing-table-view-cells/
_controller.TableView.RowHeight = UITableView.AutomaticDimension;
In my controller i am doing this to register all potential cell candidates:
private void RegisterCells()
{
foreach (var tuple in InfoFieldCellMapping.Map)
{
this.TableView.RegisterNibForCellReuse(tuple.Value.Item1, tuple.Value.Item2);
}
}
public override void ViewDidLoad()
{
RegisterCells();
base.ViewDidLoad();
}
I am doing this in my controller so cells resize themselves depending on how much height they need:
public override nfloat EstimatedHeight(UITableView tableView, NSIndexPath indexPath)
{
return 100;
}
Now all you need to do is create cell files from within your IDE which should be .xib files and design them in the editor using autolayout (so they can adapt to orientation changes automatically).
Within your TableViews datasource all that's left to do is mapping between your data item and it's corresponding cell similar to:
public override UICollectionViewCell GetCell(UICollectionView collectionView, NSIndexPath indexPath)
{
var dataItem = Items[indexPath.Row];
var key = ""; // use info from data item to figure out which key identifies your table cell to dequeue the correct kind of cell
var cell = collectionView.DequeueReusableCell(key, indexPath) as UICollectionViewCell;
}
That's all you need really. In my scenario i am mapping fields which may contain different controls for date entries, number entries, long texts, short texts etc etc.
I hope that helps
1.Add Leading,Trailing,Top,Bottom Constraints on scrollView to it'superview.
2.Add UIView as containerView of scrollview and add 6 Constraints from containerView to scrollview as below.
a)Leading b)trailing c)top d)bottom e)Center Horizontally.
3.Make sure top elements in container view must bind to top by adding top constraints and also bind bottom most element to bottom of container view by adding bottom constraints.And also all the items between topmost and bottommost in the container view must be vertically connected to each other so it will define the content size of container view.
it will define the actual content height for scrollview.
and finally define content size for scrollview in code.
As I mentioned here .
Refer to Step 1 and Step 2 ,set constraints on Scrollview and containerView.
I remove the margin between Scrollview and View , and I add some controls on the containerView , so it looks like as below:
Notice
Since we set the containerView's width equal to scrollview's width, the width is fixed, so we can scroll vertically not horizontally.
Height of controls and spaces between them should be set clearly, because the contentSize is auto calculated by adding them. (If contentSize is greater than the height of screen ,the scrollview can be scrolled)
I saw you set those constrains on controls , but you can't scroll down to see the controls out of screen, I think you are missing to set bottom margin on the last control(the downmost one).
Let us do a test.
1. We set the margin (between button and textfield ) to 1000 and don't set bottom margin between the textfield and containerView.
Result : can't scroll down to see the textfield out of screen.
2. Set the margin 1000 and add a bottom margin(10) between textfiled and containerView.
Result: can scroll
Demo Link

How to modify view controller after pressing a button?

Let's say that if you press the button from the bottom of the screen after typing something in the account text field, you would be required to also enter the password like in the second image.
How should I do this? I don't think that creating a new view controller would be good. So, should I somehow modify the same view controller?
How could I add the new password text field under the account text field?
Keep in mind that they are still centered. Hiding and unhiding wouldn't work in this case and I also need to modify more things than only adding that text field.
First, create a UIView with everything you need on them. in this example I will have only two text fields and they are all color coded.
The view needs to be centered both horizontally and vertically, with width and height. Set identifiler for the height constraint to be updated later. Set the clip to board to true so that when we redeuce the height of the view, text fields below will hide. The settings for the view will be like this
For your text fields, they must have a constant padding to top. In my example, they are set to be in center horizontally, having a constant height, width and padding to opp.
Now, all you need to do is to get the height of the view from code and set the height to show or hide the text fields.
var flag = true
#IBAction func click(_ sender: Any) {
if flag {
flag = false
let filteredConstraints = theView.constraints.filter { $0.identifier == "viewHeight" }
if let heightConstraint = filteredConstraints.first {
heightConstraint.constant = 60
}
} else {
flag = true
let filteredConstraints = theView.constraints.filter { $0.identifier == "viewHeight" }
if let heightConstraint = filteredConstraints.first {
heightConstraint.constant = 128
}
}
}
Here is the code running in simulater.
another option is you can make tableView at Center, you need to create tableview height constraint outlet,
then you can maintain a counter how many time you want to add View, the counter should be return in tableView numberOfRowsInSection,
and you can make this view in Prototype Cell or using NIB, then just adjust header label text, textField placeholder and text on specific cell index,
when you increase or decrease counter update tableView Constraint, example you have a cell height as 50, in first case when you have on cell in tableView, you can set your tableView height constraint's constant as 50, when cells are two the 100 and so on.....
I think it's a simple logic

Resources