Using Storyboard sizeClasses i want to define the different TableView Row Height
Using UITableView Row Height Automatic based on screen height (Specially in iPad)
Using Static TableView i Also want to achieve the Dynamic Row so that we can utilize the screen Height
It might be possible that the Question is very old and may be its duplicate also but i have searched and not getting proper result so its better i should ask this again might be something new in Xcode 7 with iOS 9 so guys anyone have any good resource which fullfill my requirement please share it, Thanks is advanced
You can set the row height in heightForRowAtIndexPath: delegate method of UITalbeView.
You can get the trait-collection details and then use it to identify the current size class.
NSInteger horizontalClass = self.traitCollection.horizontalSizeClass;
NSInteger verticalCass = self.traitCollection.verticalSizeClass;
switch (horizontalClass) {
case UIUserInterfaceSizeClassCompact :
// horizontal is compact class.. do stuff...
break;
case UIUserInterfaceSizeClassRegular :
// horizontal is regular class.. do stuff...
break;
default :
// horizontal is unknown..
break;
}
Then you can calculate or set a predefined row height as per device height.
Related
I'm working on the iOS version of an app I already developed on Android. This app has the following 2 column grid of self-sizing (fixed width but variable height) cells:
Achieving this in the Android version was easy because Google provides a StaggeredGridLayoutManager for its RecyclerView. You specify the number of columns and the direction of the scroll and you are done.
The default UICollectionView layout UICollectionViewFlowLayout doesn't allow the staggered layout I'm looking for, so I have to implement a custom layout. I have watched 2 WWDC videos that talk about this topic (What's New in Table and Collection Views and Advanced User Interfaces with Collection Views) and I more or less have an idea of how it should be implemented.
Step 1. First an approximation of the layout is computed.
Step 2. Then the cells are created and sized with autolayout.
Step 3. Then the controller notifies the of the cell sizes so the layout is updated.
My doubts come when trying to code these steps. I found a tutorial that explains the creation of a custom layout with staggered columns, but it doesn't use autolayout to obtain the size of the cells. Which leaves me with the following questions:
In step 2, how and when can I obtain the cell size?
In step 3, how and when can I notify the layout of the changes?
I want to point out that, as you have mentioned, RayWenderlich PinInterest Layout is exactly the tutorial that'll help you achieve this layout.
To answer your questions - with regards to the tutorial:
In step 2, how and when can I obtain the cell size?
To get the cell height, a delegate method was implemented that was called in the prepareLayout method of the custom UICollectionViewLayout. This method is called once (or twice, I just attempted to run it with a print statement, and I got two calls). The point of prepareLayout is to initialize the cell's frame property, in other words, provide the exact size of each cell. We know that the width is constant, and only the height is changing, so in this line of prepareLayout:
let cellHeight = delegate.collectionView(collectionView!,
heightForItemAtIndexPath: indexPath, withWidth: width)
We obtain the height of the cell from the delegate method that was implemented in the UICollectionViewController. This happens for all the cells we want to show in the collectionView. After obtaining and modifying the height for each cell, we cache the result so we can inspect it later.
Afterwards, for the collectionView to obtain the size of each cell on screen, all it needs to do is query the cache for the information. This is done in layoutAttributesForElementsInRect method of your custom UICollectionViewLayout class.
This method is called automatically by the UICollectionViewController. When the UICollectionViewController needs layout information for cells that are coming onto the screen (as a result of scrolling, for instance, or upon first load), you return the attributes from the cache that you've populated in prepareLayout.
In conclusion to your question: In step 2, how and when can I obtain the cell size?
Answer: Each cell size is obtained within the prepareLayout method of your custom UICollectionViewFlowLayout, and is calculated early in the life cycle of your UICollectionView.
In step 3, how and when can I notify the layout of the changes?
Note that the tutorial does not account for new cells to be added at runtime:
Note: As prepareLayout() is called whenever the collection view’s layout is invalidated, there are many situations in a typical implementation where you might need to recalculate attributes here. For example, the bounds of the UICollectionView might change – such as when the orientation changes – or items may be added or removed from the collection. These cases are out of scope for this tutorial, but it’s important to be aware of them in a non-trivial implementation.
Like he wrote, it's a non trivial implementation that you might need. There is, however, a trivial (very inefficient) implementation that you might adopt if your data set is small (or for testing purposes). When you need to invalidate the layout because of screen rotation or adding/removing cells, you can purge the cache in the custom UICollectionViewFlowLayout to force prepareLayout to reinitialize the layout attributes.
For instance, when you have to call reloadData on the collectionView, also make a call to your custom layout class, to delete the cache:
cache.removeAll()
I realise this is not a complete answer, but some pointers regarding your steps 2 and 3 may be found in the subclassing notes for UICollectionViewLayout.
I presume you have subclassed UICollectionViewFlowLayout since off the top of my head I believe this is a good starting point for making adjustments to the layout to get the staggered appearance you want.
For step 2 layoutAttributesForElementsInRect(_:) should provide the layout attributes for the self sized cells.
For step 3 your layout will have shouldInvalidateLayoutForPreferredLayoutAttributes(_:withOriginalAttributes:) called with the changed cell sizes.
In step 2, how and when can I obtain the cell size?
You need to calculate height of each cell in prepareLayout() method. Result of calculation for each cell should be assigned to UICollectionViewLayoutAttributes variable, and than put it into collection NSDictionary, where key would be NSIndexPath(of each cell), and value would be UICollectionViewLayoutAttributes variable.
Example:
- (void)prepareLayout {
[_layoutMap removeAllObjects];
_totalItemsInSection = [self.collectionView numberOfItemsInSection:0];
_columnsYoffset = [self initialDataForColumnsOffsetY];
if (_totalItemsInSection > 0 && self.totalColumns > 0) {
[self calculateItemsSize];
NSInteger itemIndex = 0;
CGFloat contentSizeHeight = 0;
while (itemIndex < _totalItemsInSection) {
NSIndexPath *targetIndexPath = [NSIndexPath indexPathForItem:itemIndex inSection:0];
NSInteger columnIndex = [self columnIndexForItemAtIndexPath:targetIndexPath];
// you need to implement this method and perform your calculations
CGRect attributeRect = [self calculateItemFrameAtIndexPath:targetIndexPath];
UICollectionViewLayoutAttributes *targetLayoutAttributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:targetIndexPath];
targetLayoutAttributes.frame = attributeRect;
contentSizeHeight = MAX(CGRectGetMaxY(attributeRect), contentSizeHeight);
_columnsYoffset[columnIndex] = #(CGRectGetMaxY(attributeRect) + self.interItemsSpacing);
_layoutMap[targetIndexPath] = targetLayoutAttributes;
itemIndex += 1;
}
_contentSize = CGSizeMake(self.collectionView.bounds.size.width - self.contentInsets.left - self.contentInsets.right,
contentSizeHeight);
}
}
Don't forget to implement following methods:
- (NSArray <UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
NSMutableArray<UICollectionViewLayoutAttributes *> *layoutAttributesArray = [NSMutableArray new];
for (UICollectionViewLayoutAttributes *layoutAttributes in _layoutMap.allValues) {
if (CGRectIntersectsRect(layoutAttributes.frame, rect)) {
[layoutAttributesArray addObject:layoutAttributes];
}
}
return layoutAttributesArray;
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
return _layoutMap[indexPath];
}
These methods would be triggered once you call reloadData() mehtod or invalidateLayout().
In step 3, how and when can I notify the layout of the changes?
Just call self.collectionView.collectionViewLayout.invalidateLayout() and prepareLayout() method would be called once again, so you can recalculate all parameters you need.
You can find my full tutorial about custom UICollectionViewLayout here: https://octodev.net/custom-collectionviewlayout/
Tutorial contains implementation in both languages: Swift and Objective-C.
Would be more than glad to answer all your questions.
The "cell size" is defined by UICollectionViewLayoutAttribute in the layout subclass which mean you can modify it every time you have the chance to touch them. You can set every attributes' size to what you desire.
For example you can do it in layoutAttributesOfElementsInRect(:) , calculate the right size and config all attributes before pass them to collectionView. You can also do it in layoutAttributeOfItemAtIndexPath(:) ,make the calculation when every attribute is created.
Furthermore, consider to provide the desired size by a datasource so every attribute can easily get their size with their index.
For if you want to have the cell size to layout the subviews in a cell, do it in the collectionView delegate method: collectionView:ItemAtIndexPath:
Hope this help.
I want my UITableView to only be as tall as it needs to be, as it floats above the main UIViewController's view. If I hardcode a height of 200 and theres' only one cell in the table, it looks silly.
I'm aware in my view controller I could monitor the table view and define the height of it based on the number of cells it has, but the height is a property of the view, and for MVC it doesn't make much sense for the controller to be actively managing a view's height.
Is it possible to have a UITableView subclass, and have it define an intrinsic height based on the number of cells it holds? So with Auto Layout I could add the subclass to my view, specify its width, center it vertically, and perhaps define a "less than or equal" height constraint saying to keep it smaller than 200pts. But for the most part have the intrinsic content size of the view define the height of the view automatically?
This would be just like a UILabel being able to be centered horizontally and vertically with some distance from the left and right, and have it grow and shrink vertically automatically.
Could I feasibly do this with a UITableView subclass?
You can do this easily by having the table view use its contentSize property to "know" how tall it needs to be. This value could be somewhat inaccurate if you're using estimated row heights, but it should be good enough. In this example, I gave the table view a height constraint (as well as width and centerY), and made an IBOutlet to it (heightCon). The only code needed was this,
#interface RDTableView ()
#property (weak,nonatomic) IBOutlet NSLayoutConstraint *heightCon;
#end
#implementation RDTableView
-(void)reloadData {
[super reloadData];
self.heightCon.constant = MIN(200, self.contentSize.height);
}
You would also have to override reloadRowsAtIndexPaths:withRowAnimation: and
reloadSections:withRowAnimation: if you're updating your table with either of those as well.
This feels like a chicken/egg problem you have with designing your UI. Sounds like you want to set the height of the floating tableview based on how many rows it contains * height per row, but the tableview doesn't know any of this information until its been drawn, ie [tableView reloadData] and the corresponding height for row delegate methods are called.
I'd suggest rendering the tableview offscreen somewhere, draw all the rows, sum up all the heights for each row, then present the view to the user with the appropriate CGRect.
When you (re)load data in your TableView, you can do this. The TableView will take only the height it needs
CGRect frame = youTableView;
frame.size.height = CELL_HEIGHT*[yourArray count];
if(frame.size.height > MAX_TABLEVIEW_HEIGHT)
{
frame.size.height = MAX_TABLEVIEW_HEIGHT
}
youTableView.frame = frame;
i want to display multiple lines in a Table's cell view for an IOS app that i'm working on. To be precise, i have a table view which will be populated with the JSON data that is returned from server, in here if the status returned is 0 or 2 (Status is one of the JSON object that is being returned for a profile) i have to display an error message that is of 5-6 lines.
Table's cell view is customized to display 2 UILabel's and obviously the height of this cell is small enough to display 2 labels. I found one solution, but it doesn't actually solve the problem. Can anyone suggest a different solution. If possible, link to an example will be a great help. Thanks
What you're searching for is called "dynamic sized" cells. There is a tutorial on how to realize this : http://www.raywenderlich.com/73602/dynamic-table-view-cell-height-auto-layout
It does resize all cells height in function of the text length like in this example:
if you have to show this multi lines message to one of the UILabels you currently have in UITableViewCell, then set the property "No of Lines" to 0 (Zero) and set "Line Mode" to UILineBreakModeWordWrap, Now for height of the cell you have two options i.e with contraints or without constraints, this will be done using cellForRowAtIndexPath method.
1.if (error_message_shown) {
return 200.0;
else
return 60.0;
With constraints
Add top, bottom, left, right, width and height constraints, ensure to use >= for height and calculate the height of the text assigned to the cell using below method
CGSize size = [string sizeWithAttributes:
#{NSFontAttributeName:
[UIFont systemFontOfSize:17.0f]}];
When I have dynamic cell height I use something like
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
static MyTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"my_cell_id"];
NSDictionary *responseItem = [self itemFor:indexPath];
return (CGFloat)[cell heightFor:responseItem];
}
I am making app in which there is a chat window. In this window there is one image and a label on that image in custom cell.
I have take two custom cells, one for sender and other for receiver. Both cell are same with left and right alignment.
I want that when the length of comment is increased then whole comment shows in multiline within that image(increases the image size also) .
how can I handle this situation?
I am using setVariable method to set content on cell. I am trying comment code for framing like below comment code but it doesn't work
- ( void ) setComment : ( NSString* ) Comment
{
[ txtComment setText : Comment ] ;
/*CGRect frame1 = txtComment.frame;
frame1.size.height = txtComment.contentSize.height;
txtComment.frame=frame1;*/
}
I would suggest you use auto layout to define the custom cell height. It will help you create a dynamic cell height depending on the length of the comment. You can read about using dynamic height using auto layout in this link
To make this done you can do the following:
Subclass UITableViewCell and also create xib file.
Go to the xib file and add UIImageView and UILabel objects to your cell. Also create an outlet for the label.
As Pavan Kotesh mentioned the easiest way is to use auto layout.
Add top space and bottom space constraints to the cell content view for both image view and label. Then set constraints for x position for both subviews and finally set width and height constraints.
Height constraints must be "Greater of equal" type to let you change size of the views.
Having done that in Interface Builder all you need is to add one method to your subclass for setting a message.
- (void) setMessage: (NSString*) message
{
CGFloat oldLabelSize = _label.frame.size.height;
_label.text = message;
[_label sizeToFit];
CGFloat newLabelSize = _label.frame.size.height;
CGRect frame = self.frame;
frame.size.height += newLabelSize - oldLabelSize;
self.frame = frame;
}
After calling this method add the cell as subview to you chat view.
EDIT:
I think your -cellForRowAtIndexPath method implementation is wrong. What does [ChatViewCell send] perform?
I would have done it like this:
First declare two arrays in your table view controller class. The first is for storing the text of messages and the second for storing cell heights. Also you can create some structure to store those values. After user has finished inputing a message you should somehow estimate cell height and put both height and cell's message to the appropriate arrays. After that you should insert new row(section) in your tableview using insertRowsAtIndexPaths:withRowAnimation: method.
Then in your cellForRowAtIndexPath method:
If dequeueReusableCellWithIdentifier returns nil you should just initialise new ChatViewCell and after if (cell == nil) statement set its message from the appropriate array (due to reuse you should set cell's content every time it goes on screen).
In your heightForRowAtIndexPath: method return values from array that stores cell heights.
The Goal
I'm trying to create a dynamic message cell using auto-layout.
What I've Tried
The cell is positioning correctly, for the most part, with auto-layout given the following constraints:
The Problem
My first problem was the message label (Copyable Label) width was constrained. That seems to be resolved by using setPreferredMaxLayoutWidth: as described in this question.
Height is still a problem. As you can see, the message bubble is still cutting off. In addition, I'm not sure how to determine the message cell height for the table view.
I expected auto-layout to somehow just work. I've read the answer here, but it seems like a lot to of steps.
The Question
First, is a case where auto-layout is more complex than traditional frame arithmetic?
Second, using auto-layout, how can I determine the height of the resulting cell?
I fully use Auto Layout and what you speak about is kinda a problem.
I didn't want to modify the way intrinsic size is calculated for performance purpose of UITable.
So I used a very simple way that is correct in the end. It's ok if your cell is simple, can become such hard if your cell contains more than one variable text.
I defined my cells normally, where you can put a UILabel that fits the insets (no problem about it).
Then, in your table datasource, you define directly the height of the cell:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return [TEXTOFYOURCELL sizeWithFont:[UIFont systemFontOfSize:14] constrainedToSize:CGSizeMake(300, 1000)].height + 31; // Here it's defined for 15 of top and bottom insets, define +1 than the size of the cell is important.
}
EDIT :
Here some code about the UILabel in the cell (in init method).
__titleLabel = [UILabel new];
__titleLabel.numberOfLines = 0;
[self.contentView addSubview:__titleLabel]; // adding to contentView rather than self is very important !
[__titleLabel keepInsets:UIEdgeInsetsMake(0, 15, 0, 15)];
I use this API : https://github.com/iMartinKiss/KeepLayout to manage auto layout simpler.
This is possible on iOS 8 as can be read on AppCoda
Basically:
Set the label lines to 0.
Set the row height UITableViewAutomaticDimension