NSBrowser setRowHeight, not supported for browsers with matrix delegates - objective-c

I have an NSBrowser and try to use setRowHeight, but I get the error:
"setRowHeight: is not supported for browsers with matrix delegates."
I really don't understand what this means, and if someone could help me out by either telling me how to fix it or even just what a matrix delegate is, it would be much appreciated.

A delegate is a helper object that you tell the NSBrowser instance about either by using -setDelegate: in code or hooking the delegate outlet up in IB (the NIB editor). It is commonly used to fill the browser's data, determine programmatically the layout options, etc.
If you have a delegate assigned in your NSBrowser instance, you are expected (required) to give the row height using the delegate method:
- (CGFloat)browser:(NSBrowser *)browser heightOfRow:(NSInteger)row inColumn:(NSInteger)columnIndex
Which will allow you to optionally set the row height on a per-row basis, but in your case, you can safely return a constant.

A NSBrowser creates one instance of NSMatrix per column. The browser itself only manages the columns and leaves row management entirely to the matrices. The matrices display cell objects (NSCell or subclasses of it). A cell is a simple displayable object that knows nothing but how to draw itself. Unlike a view (NSView and subclasses), it never manages an own drawing context, it also doesn't belong to a window (and thus won't ever directly access the window's drawing context) and it knows nothing about the "view hierarchy" (superviews, subviews, view order, constraints, etc.), it just manages some properties, some state and knows how to draw itself to a drawing context provided.
If your delegate works with items (the docs speak about "the item delegate methods"), the developers of NSBrowser thought it's unlikely that you ever want to deal with matrices directly, thus the browser will control all the display aspects for you. You are only supposed to hand out items (basically arbitrary objects) in your delegate and answer questions about them by implementing various delegate methods. E.g.: What is the root item? Is item x a leaf item or has children? How many children does it have? What's child number n of item x? What display object (string, image, etc.) shall I use to display that item?
If you don't work with items, you have to work directly with the cells (NSBrowserCell, a subclass of NSCell) that the matrix owns and is going to display. In that case you are said to be a "browser matrix delegate". That means the browser will only ask you how many rows a column has, setup a matrix with cells for you for that column and finally pass every cell once to your delegate, so you can do something meaningful with it, e.g. fill it with displayable content, otherwise the cell would just be empty, and teach it all the stuff the browser has to know (e.g. setting the leaf property).
As a matrix based delegate has to deal with cells directly, it can as well also deal with the matrices directly and in fact that his exactly what you have to do here. E.g. if you want that all rows of your browser have a height of 50 points, implement the following NSBrowser delegate method:
- (void)browser:(NSBrowser *)browser
didChangeLastColumn:(NSInteger)oldLastColumn
toColumn:(NSInteger)column
{
NSMatrix * matrix = [browser matrixInColumn:column];
CGSize cellSize = [matrix cellSize];
if (cellSize.height != 50) {
cellSize.height = 50;
matrix.cellSize = cellSize;
[matrix sizeToCells];
}
}
Every matrix created by the browser is passed to that method at least once before it gets displayed on screen. In case that matrix is not using a cell height of 50 already, we change that and finally tell the matrix to re-layout; that is important as otherwise it won't recalculate its total height. The if is only to avoid that we call sizeToCells more often than necessary as this call may be rather expensive. In this example all columns get equal row height but of course you can set a different row heights per column.

Related

prepareLayout method of NSCollcectionViewLayout

I have hard time understanding the purpose of prepareLayout method of NSCollectionViewLayout.
According to the official apple documentation it is written
During the layout process, the collection view calls several methods of your layout object to gather information. In particular, it calls three very important methods, whose implementations drive the core layout behavior.
Use the prepareLayout method to perform your initial layout calculations. These calculations provide the basis for everything the layout object does later.
Use the collectionViewContentSize method to return the smallest rectangle that completely encloses all of the elements in the collection view. Use the calculations from your prepareLayout method to specify this rectangle.
Use the layoutAttributesForElementsInRect: method to return the layout attributes for all elements in the specified rectangle. The collection view typically requests only the subset of visible elements, but may include elements that are just offscreen.
The prepareLayout method is your chance to perform the main calculations associated with the layout process. Use this method to generate an initial list of layout attributes for your content. For example, use this method to calculate the frame rectangles of all elements in the collection view. Performing all of these calculations up front and caching the resulting data is often simpler than trying to compute attributes for individual items later.
In addition to the layoutAttributesForElementsInRect: method, the collection view may call other methods to retrieve layout attributes for specific items. By performing your calculations in advance, your implementations of those methods should be able to return cached information without having to recompute that information first. The only time your layout object needs to recompute its layout information is when your app invalidates the layout. For example, you might invalidate the layout when the user inserts or deletes items.
So I naively used this as a guide and rewrote my implementation of custom layout. I computed collectionViewContentSize and precomputed the array used in this method
- (NSArray<__kindof NSCollectionViewLayoutAttributes *>*)layoutAttributesForElementsInRect:(NSRect)rect;
such that in all 3 required methods I just return the cached values. And after this suddenly my collectionView became extremely laggy.
Apparently the method prepareLayout is called on every scroll.
Can anyone clarify what does it mean. Or maybe I do not understand anything?
The usual thing, if you don't want prepare called every time there is a scroll event, is to keep a CGSize instance property and implement shouldInvalidateLayout so that it returns YES only if the new bounds size is different from the stored CGSize property value.
Here's a Swift example, but I'm sure you can translate it into Objective-C:
var oldBoundsSize = CGSize.zero
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
let ok = newBounds.size != self.oldBoundsSize
if ok {
self.oldBoundsSize = newBounds.size
}
return ok
}
So this was my bad. Apparently if
- (BOOL)shouldInvalidateLayoutForBoundsChange:(NSRect)newBounds;
returns YES. Then this method is called. I just changed that this method returns NO and the method prepareLayout has been stopped called all the time.

get all tableview cells, even the non visible

I need to loop through tableview cells in different sections to calculate something in each one; however when I try to do this:
CustomCell * c = (CustomCell *)[self.table cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:i]
if the section "i" is not visible I can't get the content inside.
I must say that the content I want to calculate in each cell will vary before I call the calculate method, so I can't populate the tableview then calculate.
Any ideas?
You should not be performing calculations based on the content of UITableViewCell objects, because they fundamentally belong to the presentation layer; there should be no calculations going on in the presentation layer, only hiding and displaying things.
For calculations, you should go directly to the layer from which your UITableViewDataSource gets its data (presumably, that would be your model classes) and do the calculations there. Better yet, move the calculation to the model layer, and put out a method for getting it on demand.
If you are modifying the existing code written by someone else, start by examining the tableView:cellForRowAtIndexPath: method in the UITableViewDataSource to see from where it gets its data. Presumably, that place has all the data available, regardless of whether it is showing on the screen or not.

How do we implement selectionRectsForRange: from UITextInput Protocol?

How do we implement selectionRectsForRange: from UITextInput Protocol ?
Has anybody figured out this one?
Is it just very dependent upon specific use-case needs? Or is there something in the frameworks that will call this method?
To silence the compiler it is of course appropriate to stub out the method, but will returning nil or an empty NSArray cause any harm?
According to session 220 at WWDC12 this method was added to support subclassing of UITextView where the implementation renders its own text. Sadly their sample code from that session isn't available, would love to peek at it to see if I've missed anything in my implementation.
It's fairly similar to how you'd implement -firstRectForRange: except you'd return all rects which covers the current selection.
Furthermore you'd have to subclass UITextSelectionRect (it's an abstract class like UITextPosition/UITextRange) which you'd return an array of from this method. Make sure to calculate the containsStart and containsEnd properties correctly and only return YES for one of each once across all the selection rects you return. These properties are used by UITextView to decide on where to place the selection resize "paddles".
Returning an empty array (or nil I suppose) would indicate that UITextView shouldn't draw any selection rects for the current selection.

Objective-C / Cocoa Touch: Is it good design to set label texts in the setter of the UILabel object?

Let's say I have a two subclasses of UIViewController called MasterViewController and DetailViewController.
DetailViewController has a property of type NSNumber called level and a UILabel called levelLabel.
MasterViewController has a segue to DetailViewController called ToDetail. MasterViewController's prepareForSegue is like so
- (void)prepareForSegue:(UIStoryboardSegue)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"ToDetail"]) {
DetailViewController *detailVC = (DetailViewController *)segue.destinationViewController;
detailVC.level = [NSNumber numberWithInt:10]; // never mind the literal...pretend there was some algorithm for it
}
}
So then, in DetailViewController we implement the setter for levelLabel like so:
- (void)setLevelLabel:(UILabel *)levelLabel
{
if (levelLabel) {
_levelLabel = levelLabel;
_levelLabel.text = level.stringValue;
}
}
Is this good code design? Also, could you critique my code writing style? I pretty much wrote all this code on the fly so this is pretty much how I write code for the most part.
I thought of this question while showering because this is how I implement the setting of almost all the label texts that depend on a segue.
What follows is my own way of thinking about such relationships. Italics applies to your question.
You have the thing being controlled (the label) the controller (destination view controller) and the context it is being controlled within (the source view controller). This can also be expressed as model-view-controller, but I think thinking about a context can apply to much more specific and localised situations.
You should generally try to keep information flow going in one direction, from the context downwards. Objects should not have to be aware of the context in which they exist, ie they shouldn't have to ask for any information, they should be told everything they need to operate. So the source view controller should push the level to the destination view controller, the destination view controller should push this information to the label. This is what you already have, sort-of.
To build upon the above, not only should information flow in one direction, but I also try to ensure the relationships are causal, ie pushing information from one object to another should cause it to subsequently be pushed to the next object. Your code is not doing this which is probably why you have a bad feeling about it.
A more appropriate thing to do is set the text property of the label within the level setter, so that when you set or change the level, the label will update subsequently. The label may or may not be loaded so you will have to check whether it is using -isViewLoaded; -viewDidLoad is the appropriate place to set the text property upon first load.
(When I say 'push' that's just my way of thinking about setting properties or passing arguments because it implies directionality. It is really dependency injection. An example of pulling information would be delegates and data sources. But note here still the object isn't aware of any context, delegates and data sources are clearly defined as protocols, not classes, and usually within the same header file, and are themselves pushed onto the object from a surrounding context. So yes the object is asking for information, but on its own terms and from a system it has no knowledge of.)
Re coding style:
That's exactly how I write code but note Apple reserves the use of underscore prefixes

iOS storyboard: how to handle dynamic, floating content?

My App contains a news-section. I'm trying to create a View in my storyboard which will show an news item. A news contains of a title, a date, an image, and some text.
Content like this would be easy to display in HTML since it's content is floating, but as far as I understand iOS elements have a fixed size. I think a UILabel could be fine for title and date, UIImage for the image and a UITextView for the text. I think all elements should be inside a scroll view. The problem is, that the title for some news will fill one line but for other news multiple lines.
How can I handle a setup like this in a storyboard? A link to a guide, tutorial would be fine.
There are a lot of different ways you could approach this. Ultimately you have control of the size of any of these elements at runtime. From a UX point of view you'd probably want to keep at least some of the elements fixed in size (e.g., the image and the scrollview representing the total size of the news item). That would leave playing with the relative sizes of the title and the detail. If you want to show the entire title you can use the sizeWithFont method (or one of its variations) of the NSString class to calculate how much space to use for the title, and allocate space from the remaining amount to the detail as desired.
UPDATED: I might add that I intended this as a general strategy, not specifically aimed at the new iOS 5 Storyboard functionality.
UPDATED: Well, no tutorial but here are some general guidelines (at least about the way I might tackle it).
I'd set up a NewsItem class that encapsulates the elements that you describe here, similar to what I've laid out in the example image. How you expose NewsItem is something of a matter of taste; you might want to start with a UITableView as a container as it would make managing a NewsItem collection simpler. I'd subclass UITableViewCell as NewsItemTableViewCell, and generate a nominal layout for a NewsItem using Interface Builder. So in the example image, a news item would correspond to one table row.
The main part that you are asking about is identified by the red and blue frames in the image. Using the above approach, I would add logic in the NewsItemTableViewCell to calculate the extent of the title -- this assumes that the View (in this case, the NewsItemTableViewCell) has explicit knowledge of the Model (the underlying data for the news item) in the form of a reference (i.e, an instance of a NewsItem class
that contains the items you've described retrieved from a web service). If the width of the title is greater than the default width of the red frame, I'd adjust the height of the red frame (increasing it) at the same time decreasing the height of the blue frame. This kind of approach avoids having to include extra logic for differing cell heights within the table.
An alternative I often use is to have a Controller class mediate between the View and the Model. This can be useful when you have a requirement that your table contain different kinds of cells; it also makes tableview cell reuse much simpler. In this case, declare a base class. This might look something like:
#protocol GenericCellController
- (UITableViewCell*) tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath;
#optional
- (void) tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath;
#end
and then:
#interface NewsItemTableCellController : NSObject<GenericCellController> {
NewsItem* newsitem;
}
You then set up a collection of table cell controller objects. This has the advantage that when you implement tableView:cellForRowAtIndexPath: in your tableview controller code, you only have to delegate to the cell controller object at the appropriate row. What's more, you can refer to it using a superclass designation; e.g., in a hypothetical NewsItemTableViewController class you might have:
- (UITableViewCell*) tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*) path {
NSObject<GenericCellController>* cellController = [cellControllerArray objectAtIndexPath:indexPath.row];
UITableViewCell* cell = [cellController tableView:tableView cellForRowAtIndexPath:indexPath];
return cell;
}
This approach allows you to have any class derived from NSObject in the collection and polymorphically use delegation to have the derived class handle the rendering logic. The derived Controller class has its own implementation of tableView:cellForRowAtIndexPath: with a corresponding UITableViewCell-derived subclass as described above.
If you decide to go this way, there are numerous examples of the basics of tableviews and their controllers that you can use as a preface to implementing these ideas.