NSTableRowView "Empty Rows" - objective-c

I've overridden NSTableRowView, which so far is working a treat. The problem I'm facing is drawing the separator for the empty rows.
drawSeparatorInRect:dirtyRect
only seems to override the rows that have content, all the rest seems to be filled with system colours.
At the moment I have:
-(void)drawSeparatorInRect:(NSRect)dirtyRect
{
NSRect sepRect = self.bounds;
sepRect.origin.y = NSMaxY(sepRect) - 1;
sepRect.size.height = 1;
sepRect = NSIntersectionRect(sepRect, dirtyRect);
if (!NSIsEmptyRect(sepRect))
{
[[NSColor gridColor] set];
NSRectFill(sepRect);
}
}
Which, as I said works well. I originally picked this up from some apple sample.
When I change the separator color to "red", you can see what happens:
The bottom-half of the last populate row is filled, but none of the others there after are.
Has anyone got an idea what I'm missing?
Cheers,
A

There are only row views for rows which have content. Therefore, a custom row view can not affect the drawing of separators for rows past the end of your table's content.
To customize the drawing of those separators, you need to override -[NSTableView drawGridInClipRect:]. You compute the position of the separator for where rows past the end of the content would be and draw the same way as your custom row view does.
This is explained, with example code, in the video for WWDC 2011 Session 120 – View Based NSTableView Basic to Advanced. It's also demonstrated in Apple's HoverTableDemo sample project.

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).

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.

auto adjusting the positions and height width of UILabel in iOS

I have designed my help page for my application,it contains some FAQs..so I have used UILabels for question and answer,I have adjusted,x,y,width and height exactly in the xib,leaving a gap between a question and answer and answer and next question.so totally I have some 10 question and answers,I have put them inside a scroll view.
But now, suppose the text changes dynamically, how can I adjust the positions and width and height,is a problem which I am not able to figure out, by using the below code,it adjusts the height and width of a particular label,if the text increases
NSString * test=#" It may come as a rude shock, but Facebook users should not feel surprised if tomorrow they come across their photos existing in the web world despite having deleted them personally long ago.";
m_testLabel.text = test;
m_testLabel.numberOfLines = 0; //will wrap text in new line
[m_testLabel sizeToFit];
but then, my next label will clash with it..is their any way so that all the labels get adjusted according if the width of any label increases or decreases?
Hope you all have understood my question..
Did you look into the below function, pass the font you have used and the size to which u want to constraint the label into.
- (CGSize)sizeWithFont:(UIFont *)font constrainedToSize:(CGSize)size

Rotate UITableViewCell inside a rotated UITableView

Rather than implementing a clunky, potentially bug-ridden custom table, I went with the much simpler rotate table option. The problem comes in when I decide that, rather than initializing rotated contents, I want to rotate the cell itself and cut down on the amount of code in complex cells.
The following lines are immediately after cell configuration.
This causes every cell to rotate 90° on load, regardless of orientation:
cell.transform =
(CGAffineTransform)CGAffineTransformRotate(cell.transform, (M_PI / 2.0));
cellRotated = YES;
And this option only rotates the first cell once, but preserves the rotation:
if (!cellRotated) {
cell.transform =
(CGAffineTransform)CGAffineTransformRotate(cell.transform, (M_PI / 2.0));
cellRotated = YES;
}
Can cell orientation be tracked with an existing function (or set thereof)?
I can't find anything about this with Google. There are related questions, but mostly about tables and Portrait/Landscape UI orientations, so the counter-rotation implementation is quite a bit different.
EDIT
If I move cellRotated = YES to viewDidAppear: every cell except one gets rotated. (And then cell reuse makes it so that, in this case, every sixth cell is left alone.)
1-5 is good, 6 is bad, 7-11 is good, 12 is bad, etc (and then it changes in a perfectly logical but entirely unwanted pattern when I hit the end of the table)
Halfway there or a step back, I don't know, but that's what I have now.
If rotation is the only transform you do then why don't you just do this,
cell.transform = CGAffineTransformMakeRotation(M_PI/2.0);
and then,
if ( !CGAffineTransformIsIdentity (cell.transform) ) {
// Rotated.
}

Cursor position in a UITextView

I am looking for a non private way to find the position of the Cursor or Caret (blinking bar) in a UITextView preferably as a CGPoint.
There may be a question like this already but it does not provide a definitive way for doing it.
And,
I do not mean the NSRange of the selected area.
Just got it in another thread:
Requires iOS 3.2 or later.
CGPoint cursorPosition = [textview caretRectForPosition:textview.selectedTextRange.start].origin;
Remember to check that selectedTextRange is not nil before calling this method. You should also use selectedTextRange.empty to check that it is the cursor position and not the beginning of a text range. So:
if (textview.selectedTextRange.empty) {
// get cursor position and do stuff ...
}
Pixel-Position of Cursor in UITextView
SWIFT 2.1 version:
let cursorPosition = infoTextView.caretRectForPosition( (infoTextView.selectedTextRange?.start)! ).origin
print("cursorPosition:\(cursorPosition)")
There is no such way. Full stop.
The only thing you could do to come close is to in parallel lay out the text using CoreText, there calcualte the text position from a point and apply that on the UITextView. This, however, works with non-0 selections only. In addition CoreText has a different text layout engine, that e.g. supports kerning, which UITextView doesn't. This may result in deviations of the rendered and laid out text and thus give sub-optimal results.
There is absolutely no way to position the caret. This task is even very though if you do use private API. It's just one of the many "just don't" in iOS.
Swift 4 version:
// lets be safe, thus if-let
if let selectedTextRange = textView.selectedTextRange {
let caretPositionRect = textView.caretRect(for: selectedTextRange.start)
let caretOrigin = caretPositionRect.origin
print(">>>>> position rect: \(caretPositionRect), origin: \(caretOrigin)")
}
We simply use textView.selectedTextRange to get selected text range and than textView.caretRect(for:) to get CGRect defining position of the caret.