AutoLayout warning after update tableview cell frame with UIWebView inside - objective-c

I have a custom TableViewCell with image, label, and webview inside, after calculate webview size I update cell height only for cells which loaded web content.
(
"<NSLayoutConstraint:0x7ffa58c20680 UIImageView:0x7ffa58c1fe50.top == UILabel:0x7ffa588c9660'Nguy\U00ean Tuyen'.top>",
"<NSLayoutConstraint:0x7ffa58c20720 UILabel:0x7ffa588c9660'Nguy\U00ean Tuyen'.top == UITableViewCellContentView:0x7ffa588c94e0.topMargin>",
"<NSLayoutConstraint:0x7ffa58c20950 V:[UIImageView:0x7ffa58c1fe50]-(16)-[UIWebView:0x7ffa588c9ee0]>",
"<NSLayoutConstraint:0x7ffa58c20a90 V:[UIWebView:0x7ffa588c9ee0]-(NSSpace(8))-[UILabel:0x7ffa588c99d0'1 Tr\U1ea3 l\U1eddi']>",
"<NSLayoutConstraint:0x7ffa58c20ae0 UILabel:0x7ffa588c99d0'1 Tr\U1ea3 l\U1eddi'.bottom == UITableViewCellContentView:0x7ffa588c94e0.bottomMargin + 5>",
"<NSLayoutConstraint:0x7ffa58c295b0 'UIView-Encapsulated-Layout-Height' V:[UITableViewCellContentView:0x7ffa588c94e0(0.5)]>"
)
Please tell me how to fix this!
Thank you.

There are two cases when it comes to AutoLayout warnings:
1. Ambiguity
In this case, there are not enough constraints and so the Interface Builder can't resolve the position or the size of the subview.
2. Conflict
In this case, there are too many constraints. You can either resolve them manually or decrease the priority of the less necessary ones from 1000.
Constraints with a priority less than 1000 are considered optional.

Related

UICollectionViewLayout with dynamic heights - but NOT using a flow layout

Say you have a UICollectionView with a normal custom UICollectionViewLayout.
So that is >>> NOT <<< a flow layout - it's a normal custom layout.
Custom layouts are trivial, in the prepare call you simply walk down the data and lay out each rectangle. So say it's a vertical scrolling collection...
override func prepare() {
cache = []
var y: CGFloat = 0
let k = collectionView?.numberOfItems(inSection: 0) ?? 0
// or indeed, just get that direct from your data
for i in 0 ..< k {
// say you have three cell types ...
let h = ... depending on the cell type, say 100, 200 or 300
let f = CGRect(
origin: CGPoint(x: 0, y: y ),
size: CGSize(width: screen width, height: h)
)
y += thatHeight
y += your gap between cells
cache.append( .. that one)
}
}
In the example the cell height is just fixed for each of the say three cell types - all no problem.
Handling dynamic cell heights if you are using a flow layout is well-explored and indeed relatively simple. (Example, also see many explanations on the www.)
However, what if you want dynamic cell heights with a (NON-flow) completely normal everyday UICollectionViewLayout?
Where's the estimatedItemSize ?
As far as I can tell, there is NO estimatedItemSize concept in UICollectionViewLayout?
So what the heck do you do?
You could naively just - in the code above - simply calculate the final heights of each cell one way or the other (so for example calculating the height of any text blocks, etc). But that seems perfectly inefficient: nothing at all of the collection view, can be drawn, until the entire 100s of cell sizes are calculated. You would not at all be using any of iOS's dynamic heights power and nothing would be just-in-time.
I guess, you could program an entire just-in-time system from scratch. (So, something like .. make the table size actually only 1, calculate manually that height, send it along to the collection view; calculate item 2 height, send that along, and so on.) But that's pretty lame.
Is there any way to achieve dynamic height cells with a custom UICollectionViewLayout - NOT a flow layout?
(Again, of course obviously you could just do it manually, so in the code above calculate all at once all 1000 heights, and you're done, but that would be pretty lame.)
Like I say above the first puzzle is, where the hell is the "estimated size" concept in (normal, non-flow) UICollectionViewLayout?
Just a warning: custom layouts are FAR from trivial, they may deserve a research paper on their own ;)
You can implement size estimation and dynamic sizing in your own layouts. Actually, estimated sizes are nothing special; rather, dynamic sizes are. Because custom layouts give you a total control of everything, however, this involves many steps. You will need to implement three methods in your layout subclass and one method in your cells.
First, you need to implement preferredLayoutAttributesFitting(_:) in your cells (or, more generally, reusable views subclass). Here you can use whatever calculations you want. Chances are that you will use auto layout with your cells: if so, you will need to add all cell's subviews to its contentView, constrain them to the edges and then call systemLayoutSizeFitting(_:withHorizontalFittingPriority:verticalFittingPriority:) within this "preferred attributes" method. For example, if you want your cell to resize vertically, while being constrained horizontally, you would write:
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
// Ensures that cell expands horizontally while adjusting itself vertically.
let preferredSize = systemLayoutSizeFitting(layoutAttributes.size, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel)
layoutAttributes.size = preferredSize
return layoutAttributes
}
After the cell is asked for its preferred attributes, the shouldInvalidateLayout(forPreferredLayoutAttributes:withOriginalAttributes:) on the layout object will be called. What's important, you can't just simply type return true, since the system will reask the cell indefinitely. This is actually very clever, since many cells may react to each other's changes, so it's the layout who ultimately decides if it's done satisfying the cells' wishes. Usually, for resizing, you would write something like this:
override func shouldInvalidateLayout(forPreferredLayoutAttributes preferredAttributes: UICollectionViewLayoutAttributes, withOriginalAttributes originalAttributes: UICollectionViewLayoutAttributes) -> Bool {
if preferredAttributes.size.height.rounded() != originalAttributes.size.height.rounded() {
return true
}
return false
}
Just after that, invalidationContext(forPreferredLayoutAttributes:withOriginalAttributes:) will be called. You usually would want to customize the context class to store the information specific to your layout. One important, rather unintuitive, caveat though is that you should not call context.invalidateItems(at:) because this will cause the layout to invalidate only those items among the provided index paths that are actually visible. Just skip this method, so the layout will requery the visible rectangle.
However! You need to thoroughly think if you need to set contentOffsetAdjustment and contentSizeAdjustment: if something resizes, your collection view as a whole probably will shrink or expand. If you do not account for those, you will have jump-reloads when scrolling.
Lastly, invalidateLayout(with:) will be called. This is the step that's intended for you to actually adjust your sections/rows heights, move something that's been affected by the resizing cell etc. If you override, you will need to call super.
PS: This is really a hard topic, I just scratched the surface. You can look here how complicated it gets (but this repo is also a very rich learning tool).

UITableViewCell Image full-size with right aspect ratio

I have created a tableviewcell. In it is a Image. I want to display the image over the full width of the column.
For that I have added constraints all set to zero for:
- Leading space
- Trailing space
- Top space
- Bottom space
I have also defined an Aspect Ratio constraint 8:6 to get the correct aspect ratio for the image.
In Interface Builder I get the "red arrow" error that "Need constraints for: X position of width".
When I run the code (it is compilable) it is displayed nicely. However I get runtime constraints warnings:
Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want.
Try this:
(1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
(
"<NSLayoutConstraint:0x60800008cfd0 UILayoutGuide:0x6080001a5080'UIViewSafeAreaLayoutGuide'.bottom == UIImageView:0x7fc9fa90fa10.bottom + 8 (active)>",
"<NSLayoutConstraint:0x60800008d2a0 UIImageView:0x7fc9fa90fa10.centerY == UILayoutGuide:0x6080001a5080'UIViewSafeAreaLayoutGuide'.centerY (active)>",
"<NSLayoutConstraint:0x60800008d2f0 UIImageView:0x7fc9fa90fa10.top == UILayoutGuide:0x6080001a5080'UIViewSafeAreaLayoutGuide'.top + 7 (active)>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x60800008d2a0 UIImageView:0x7fc9fa90fa10.centerY == UILayoutGuide:0x6080001a5080'UIViewSafeAreaLayoutGuide'.centerY (active)>
Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.
I do not see what I'm doing wrong, can someone point me in the right direction?
The problem is that whenever your cell size does not have a ratio of 8:6 your constraints will conflict.
There are several ways to fix this, but I think the easiest is to replace the leading and trailing constraints with a center horizontally constraint. The image will now be centered, its height is set by the cell's height, and its width will be set by the aspect constraint. The only thing to watch out for is if the cell becomes narrower than 8:6, in which case the image will extend outside the left/right edges.

Two NSTextFields with interdependent widths in autolayout

I’m trying to put together what seems to be a simple case of two NSTextFields with dynamic width and fixed spacing in between. I cannot figure out an effective way to do so though.
I’m looking to get something like this:
The blue boxes are the NSTextFields. When more text is entered into one, it should grow and thus make the other one shrink, maintaining the lead space, trailing space and the spacing in between the fields. The first one should take the priority if both of the fields have too much text. Each field will also clearly have a maximum and a minimum possible width it can reach.
How would I go around handling this, preferably utilising IB autolayout as much as possible?
It seems to me that all of constraints you mentioned directly translate into interface builder --
First view has width >= something.
First view has width <= something
Same for Second view.
Space between views is fixed.
Second view wants to be as small as possible (have its width at 0) but this has lower lower priority than the previous constraints and lower priority than inner content size constraints.
The code I had to add to my view controller, after applying the constraints as per the ilya’s answer:
In controlTextDidChange (_controlWidthConstraint refers to the fixed width constraint of the input; it’s probably 0 by default for the second input):
// Get the new width that fits
float oldWidth = textControl.frame.size.width;
[input sizeToFit];
float controlWidth = textControl.frame.size.width;
// Don’t let the sizeToFit method modify the frame though
NSRect controlRect = textControl.frame;
controlRect.size.width = oldWidth;
textControl.frame = controlRect;
_controlWidthConstraint.constant = controlWidth;
The key lies in invalidating the intrinsicContentSize for the text field when text is input.
You can check a sample project here, to get you on the right track.

Set Fixed Height for NSSplitView subview

OK, so here's the deal :
I'm having a vertical NSSplitView (actually using DMSplitView sublass) with 2 subviews in it
I want the bottom one to be of fixed height
DMSplitView manages to "fix" its size under certain conditions (e.g. when the window is resized the bottom view stays as is)
However, if the user tries to drag-resize the view (although both min/max constraints have been set) he's able to, which means that the bottom view is not of fixed height after all.
And this all of my (related) code :
[_mainHorizontalSplitView setMinSize:25 ofSubviewAtIndex:1];
[_mainHorizontalSplitView setMaxSize:25 ofSubviewAtIndex:1];
So, any ideas of a possible workaround for this?
Just found it (actually a modification to DMSplitView delegate) :
- (CGFloat)splitView:(NSSplitView *)splitView
constrainSplitPosition:(CGFloat)proposedPosition
ofSubviewAt:(NSInteger)dividerIndex
{
return [self positionOfDividerAtIndex:dividerIndex];
}

Make the divider of an NSSplitView undraggable and don't show the dragging cursor

I have an NSSplitView (NO UISplitView(Controller)!!) with three subviews. Now, for the last divider (index 1), I want the divider to not show the dragging cursor (two arrows pointing out of eachother). I have this to stop the dragging, but the cursor is still showing up:
- (CGFloat)splitView:(NSSplitView *)splitView constrainSplitPosition:(CGFloat)proposedPosition ofSubviewAt:(NSInteger)dividerIndex {
if (dividerIndex == 1) {
return [splitView frame].size.width - 161;
}
}
Note that I only want to hide the cursor for the divider at index 1. Can anyone help me? Thanks. :)
No, I don't want to use BWToolkit.
I know this has been answered for a while, but the supplied answer did not suit my needs.
The delegate method splitView:effectiveRect:forDrawnRect:ofDividerAtIndex: allows you to set the effective rectangle for dragging the divider. If you return NSZeroRect no drag cursor will ever appear, regardless of your setup in splitView:constrainMaxCoordinate:ofSubviewAt: and splitView:constrainMinCoordinate:ofSubviewAt:.
Try using splitView:constrainMaxCoordinate:ofSubviewAt: and splitView:constrainMinCoordinate:ofSubviewAt: instead of splitView:constrainSplitPosition:ofSubviewAt:.
The former two methods are called once as the user drags the mouse and they give enough information for NSSplitView to know how to change the cursor during the drag.
The latter is called repeatedly as the user drags the splitter, so NSSplitView doesn't have enough information to know that you're returning a constant value each time and therefore can't change the cursor.