I'd like to get this collection view layout:
The collection view class is also its delegate/data source and the layout delegate. The scrolling direction is horizontal. There are two horizontal sections. The first one with header view (orange). The cells with simple border lines contain labels.
The issue here is the collection orientation is wrong 2 (top to bottom instead of left to right). Is there any explicit property that can control this orientation/cell composition?
Another question is related to the cell borders. Is there any elegant way how the border color/width can be set up using auto layout (subclassing)?
Thank you.
UPDATE:
The key value to play with might be ((UICollectionViewFlowLayout *)self.collectionViewLayout).minimumLineSpacing and assigning a big number to it. But it results in one long row of cells so that the sections are together in one row. It is weird component design when a navigation mode (the scrolling direction) dictates the collection layout.
If you subclass UICollectionViewFlowLayout you will be able to layout the cells any way you like. I use a custom flow layout to display a horizontal scrolling set of cells based on an event start time and duration:
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
dataSource = self.collectionView.dataSource;
event = [dataSource eventAtIndexPath:indexPath];
UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
attributes.frame = [Calculate a frame for the event from its data];
return attributes;
}
Similarly use layoutAttributesForSupplementaryViewOfKind to define your header position.
I found this reference useful Custom Collection View Layouts.
Related
TLDR: I want to implement the method of this question (or any other relevant method) on a NSOutlineView.
A NSOutlineView is more complicated than a NSTableView because some cells may be indented with respect to others, due to parent/child relationships.
I am open to using any method, including AutoLayout if that is possible. My views are simple NSTableCellView instances with only a NSTextField.
I need to be able to set the height of a row in a view-based NSOutlineView depending on the text in that row's NSTextField.
I am currently using the following (simplified) code, which makes use of NSString+Geometics to calculate the approximate height of the view given its width:
- (CGFloat)outlineView:(NSOutlineView *)outlineView
heightOfRowByItem:(id)item
{
static NSTableCellView *cell;
if (!cell) {
cell = [self.outlineView makeViewWithIdentifier:#"Cell"
owner:self];
}
NSString *text = [item text];
float width = cell.textField.bounds.size.width;
#warning this is completely wrong due to different levels of indentation
float height = [text heightForWidth:width];
return height;
}
The idea behind this code is to keep a scratch cell around in order to make use of its default bounds property. This works correctly for the top level row.
However, any 'child' (nested) rows in the NSOutlineView are indented towards the right. To calculate the required height of the NSTextField correctly, I need to know the width of the NSTextField after this indentation.
How can I obtain this information?
I should note that it is not possible to call
[outlineView viewAtColumn:0
row:[outlineView rowForItem:item]
makeIfNecessary:YES]
from within outlineView:heightOfRowByItem:, which means that you can't get a reference to the current cell.
Use -[NSOutlineView indentationPerLevel] to calculate reduced child width.
May I know is there any delegate method of collection view can be use to set the left, right margin of particular cell in collection view?
What I've found is as follows,
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section
{ // my code here }
This is to control the left right, top, bottom margin of all the cells within the collection view. May I know is there any delegate function of collection view can be done like
UITableView heightForRowAtIndexPath?
There isn't. But since you're going to supply the cells (ie UIView) yourself, why not adjust the cell's internal layout yourself?
My UICollectionView cells contain UILabels with multiline text. I don't know the height of the cells until the text has been set on the label.
-(CGSize)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout*)collectionViewLayout
sizeForItemAtIndexPath:(NSIndexPath *)indexPath;
This was the initial method I looked at to size the cells. However, this is called BEFORE the cells are created out of the storyboard.
Is there a way to layout the collection and size the cells AFTER they have been rendered, and I know the actual size of the cell?
I think your are looking for the invalidateLayout method you can call on the .collectionViewLayout property of your UICollectionView. This method regenerates your layout, which in your case means also calling -collectionView: layout: sizeForItemAtIndexPath:, which is the right place to reflect your desired item size. Jirune points the right direction on how to calculate them.
An example for the usage of invalidateLayout can be found here. Also consult the UICollectionViewLayout documentation on that method:
Invalidates the current layout and triggers a layout update.
Discussion:
You can call this method at any time to update the layout information. This method invalidates the layout of the collection view itself and returns right away. Thus, you can call this method multiple times from the same block of code without triggering multiple layout updates. The actual layout update occurs during the next view layout update cycle.
Edit:
For storyboard collection view which contains auto layout constraints, you need to override viewDidLayoutSubviews method of UIViewController and call invalidateLayout collection view layout in this method.
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
[yourCollectionView.collectionViewLayout invalidateLayout];
}
subclass UICollectionViewCell and override layoutSubviews like this
hereby you will anchor cell leading and trailing edge to collectionView
override func layoutSubviews() {
super.layoutSubviews()
self.frame = CGRectMake(0, self.frame.origin.y, self.superview!.frame.size.width, self.frame.size.height)
}
Hey in the above delegate method itself, you can calculate the UILabel size using the below tricky way and return the UICollectionViewCell size based on that calculation.
// Calculate the expected size based on the font and
// linebreak mode of your label
CGSize maximumLabelSize = CGSizeMake(9999,9999);
CGSize expectedLabelSize =
[[self.dataSource objectAtIndex:indexPath.item]
sizeWithFont:[UIFont fontWithName:#"Arial" size:18.0f]
constrainedToSize:maximumLabelSize
lineBreakMode:NSLineBreakByWordWrapping];
- (void)viewDidLoad {
self.collectionView.prefetchingEnabled = NO;
}
In iOS 10, prefetchingEnabled is YES by default. When YES, the collection view requests cells in advance of when they will be displayed. It leads to crash in iOS 10
I acknowledge that UITableview load dynamically a cell when user scrolls. I wonder if there is a way to preload all cells in order not to load each one while scrolling. I need to preload 10 cells. Is this possible?
You can initialize table view cells, put them into an array precomputedCells and in the data source delegate method tableView:cellForRowAtIndexPath: return the precomputed cells from the array instead of calling dequeueReusableCellWithIdentifier:. (Similar to what you would do in a static table view.)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
return [self.precomputedCells objectAtIndex:indexPath.row];
}
It might also be useful to have a look at the
WWDC 2012 Session 211 "Building Concurrent User Interfaces on iOS"
where it is shown how to fill the contents of table view cells in background threads while keeping the user interface responsive.
I was looking for a solution to the original question and I thought I'd share my solution.
In my case, I only needed to preload the next cell (I won't go into the reason why, but there was a good reason).
It seems the UITableView renders as many cells as will fit into the UITableView frame assigned to it.
Therefore, I oversized the UITableView frame by the height of 1 extra cell, pushing the oversized region offscreen (or it could be into a clipped UIView if needed). Of course, this would now mean that when I scrolled the table view, the last cell wouldn't be visible (because the UITableView frame is bigger than it's superview). Therefore I added an additional UIView to the tableFooterView of the height of a cell. This means that when the table is scrolled to the bottom, the last cells sits nicely at the bottom of it's superview, while the added tableFooterView remains offscreen.
This can of course be applied to any number of cells. It should even be possible to apply it to preload ALL cells if needed by oversizing the UITableView frame to the contentSize iOS originally calculates, then adding a tableFooterView of the same size.
Hopefully this helps someone else with the same problem.
As suggested by Mark I also changed the height of my UITableView temporarily so that the table view creates enough reusable cells. Then I reset the height of my table view so that it stops creating reusable cells while scrolling.
To accomplish that I create a helper bool which is set to false by default:
var didPreloadCells = false
It is set to true when my table view first reloaded data and therefore created the first reusable cells.
resultsHandler.doSearch { (resultDict, error) -> Void in
[...]
self.tableView.reloadData()
self.didPreloadCells = true
[...]
}
The real trick happens in my viewDidLayoutSubviews Method. Here I set the frame of my table view depending on my boolean. If the reusable cells were not created yet I increase the frame of the table view. In the other case I set the normal frame
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.tableView.frame = self.view.bounds
if !didPreloadCells
{
self.tableView.frame.size.height += ResultCellHeight
}
}
With the help of that the table view creates more initial reusable cells than normal and the scrolling is smooth and fluent because no additional cells need to be created.
Change the dequeueReusableCellWithIdentifier when you are reusing the table view otherwise it will load the old data
Solution for autoresizing cells. Change 'estimatedRowHeight' to lower value
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 32; //Actual is 64
}
Actual estimated height is 64. 32 is used to add more cells for reuse to avoid lagging when scrolling begins
I'm trying to figure out a clean way to make a UIToolbar only as wide as it needs to be to fit the items that it contains. Is there a way to either:
Configure the UIToolbar to adjust its width automatically as its items are changed?
Programatically determine the minimum width required by the items, which can then be used to set the UIToolbar's frame?
I haven't been able to figure this out due to the spacing between the items and the fact that UIBarButtonItems are not UIView subclasses.
After trying some suggestions from other answers that did not work unless I used custom views, or unless everything was loaded, I finally arrived at a way to set the toolbar width based on its items:
//Add bar items to toolbar first.
UIView* v = toolbar.subviews.lastObject;
float newWidth = v.frame.origin.x + v.frame.size.width;
//Set toolbar width
You'll need to override UIToolbar -setItems: or otherwise detect changed buttons to autoresize.
I have included this feature in my refactoring library, es_ios_utils, to set a navigation item's right item with multiple buttons. In the preceding link, see UIToolbar +toolbarWithItems: and UINavigationItem -setRightBarButtonItems.
I just checked the documentation and seems like UIBarButtonItems, even though they are not UIView subclasses, they have an attribute width. I didn't try it myself, but I think you could sum each item's width, including the flexible item, and get the width for your toolbar.
Hope it helps! ;)
Swift 3, 4 update
I have a toolbar with barButtonItem initiated with image. All I need to do is to calculate the total width of the images plus the intervals (margin.)
Margin is by default 11 in width. The code will be:
let width: CGFloat = YourBarButtonItemList.reduce(0) {sofar, new in
if let image = new.image {
return sofar + image.size.width + 11
} else {
return sofar + ADefaultValueIfThisDoesntWork + 11
}
}