How to dynamically set the height of a UITableView? - ios

I have a tableview with different cell heights using the heightForRowAtIndexPath: method.
I would like to dynamically set the height of a UITableView. At the moment I'm setting the dimensions of the tableView in viewDidLoad method using:
self.tableView = [[UITableView alloc] initWithFrame:CGRectMake(10, 770, 310, 400)];
self.tableView.dataSource = self;
self.tableView.delegate = self;
I thought maybe I could add this: self.tableView.contentSize.height; but the problem of course is that the content size is only calculated after the table loads, so if I put it in
self.tableView = [[UITableView alloc] initWithFrame:CGRectMake(10, 770, 310, self.tableView.contentSize.height)];
it doesn't work.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *description = [photosFromCommentsArray objectAtIndex:indexPath.row];
// NSLog(#"description is %#",description);
if(description == (id)[NSNull null])
{
return 70;
}
else
{
return 200;
}
}

Add an observer for the contentSize property on the table view, and adjust the frame accordingly
[self.tableView addObserver:self forKeyPath:#"contentSize" options:0 context:NULL];
then in the callback:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
CGRect frame = self.tableView.frame;
frame.size = self.tableView.contentSize;
self.tableView.frame = frame;
}

Step 1 : add observer in viewDidLoad()
self.tableView.addObserver(self, forKeyPath: "contentSize", options: [], context: nil)
Step 2 : add method
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
self.tableViewHeight.constant = tableView.contentSize.height
}
Step 3 : Remove Observer
override func viewDidDisappear(_ animated: Bool) {
self.removeObserver(self, forKeyPath: "contentSize", context: nil)
}
Alternate Solution :
tableView.separatorStyle = .none
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = CGFloat(70.0)
self.tableViewHeight.constant = CGFloat.greatestFiniteMagnitude
self.tableView.layoutIfNeeded()
self.tableView.reloadData()
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
UIView.animate(withDuration: 0, animations: {
self.tableView.layoutIfNeeded()
}) { (complete) in
var heightOfTableView: CGFloat = 0.0
// Get visible cells and sum up their heights
let cells = self.tableView.visibleCells
for cell in cells {
heightOfTableView += cell.frame.height
}
// Edit heightOfTableViewConstraint's constant to update height of table view
self.tableViewHeight.constant = heightOfTableView
}
}
Another Solution :
self.tableViewHeight.constant = CGFloat(arrVideoList.count * 80)
self.view.updateConstraintsIfNeeded()

You can set the frame of the tableView at any moment. You can even animate that change in size.

Hello You can get height of text with below code and set height according to it.
CGFloat textViewWidth = CGRectGetWidth(textView.bounds);
CGSize inset = CGSizeMake(5.0,5.0);
CGSize stringSize = [myString sizeWithFont:textView.font constrainedToSize:CGSizeMake(textViewWidth-2*inset.width,1024) lineBreakMode:UILineBreakModeWordWrap];
BOOL textOutOfFit = stringSize.height+2*inset.height>CGRectGetHeight(textView.bounds);

Try this in viewDidLoad:
[tableView setFrame:CGRectMake(tableView.frame.origin.x, tableView.frame.origin.y + 4, tableView.frame.size.width, tableView.frame.size.height - 65)];

Related

Increase the Height of the Tableview when the Keyboard Disappears?

I would like to increase the height of a tableview whenever the keyboard disappears. I have a tableview that is populated from the bottom upwards. The tableview is not filled entirely with data, meaning there is some empty space at the top of the tableview. There will be some cells in the tableview. Since the table view is populated from the bottom upwards, these cells should be towards the bottom of the tableview. When the keyboard is shown, the total height of the tableview is about half of the screen. When the keyboard disappears, the total height of the tableview should be almost the whole screen. This means that when the keyboard disappears, the cells should start from further down on the screen.
This is what it looks like when there are two cells in the tableview:
This is what it looks like when two more cells have been added to the tableview:
This is what it looks like when the keyboard disappears:
When the keyboard disappears, the four cells should start from the bottom of the screen, not from halfway up the screen. The four cells should move downwards so that they start from where the gray line at the bottom of the screen is. There should not be a big gap between where the first cell (which says "Apple") is and where the gray line at the bottom of the screen is. At the same time, the tableView itself should be bigger. This means that while the top of the tableview remains in the same place, the bottom of the tableview is now lower than it was before, because the keyboard is now not taking up approximately half of the screen. How can I do this? Thanks.
Here is my code right now:
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var textField: UITextField!
#IBOutlet weak var lowerGrayLine: UIView!
#IBOutlet weak var tableView: UITableView!
var fruits = [FruitModel]()
override func viewDidLoad() {
super.viewDidLoad()
//Adjust height of the lower Gray Line
lowerGrayLine.frame.origin.y = 411
//No dividing lines in the tableview initially
self.tableView.separatorStyle = .none
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 140
if (self.tableView(self.tableView, numberOfRowsInSection: 0) > 0)
{
self.updateTableContentInset()
}
// Allow for keyboard dismissal
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(ViewController.dismissKeyboard))
view.addGestureRecognizer(tap)
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.keyboardWillShow), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.keyboardWillHide), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
// Show keyboard
textField.becomeFirstResponder()
}
func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
//Adjust text box, and dividing line placement
textField.frame.origin.y = 416
lowerGrayLine.frame.origin.y = 411
}
}
func keyboardWillHide(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
textField.frame.origin.y = 597
lowerGrayLine.frame.origin.y = 557
}
}
//Calls this function when the tap is recognized.
func dismissKeyboard() {
//Causes the view (or one of its embedded text fields) to resign the first responder status.
view.endEditing(true)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.fruits.count;
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "fruitCell"
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? FruitTableViewCell else {
fatalError("The dequeued cell is not an instance of FruitTableViewCell.")
}
var fruitName = fruits[indexPath.row]
var fruitNamename = fruitName.name
var colon = ":"
cell.nameLabel.text = fruitNamename + colon
cell.fruitLabel.text = fruitName.fruit
//Make some adjustments to make a line appear when a fruitCell is actually being shown
cell.preservesSuperviewLayoutMargins = false
cell.separatorInset = UIEdgeInsets.zero
cell.layoutMargins = UIEdgeInsets.zero
// This causes the bottom-most cell to not have a cell separator
if (indexPath.row == fruits.count-1) {
cell.separatorInset = UIEdgeInsetsMake(0, cell.bounds.size.width, 0, 0);
}
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
// Adjust the tableView
func updateTableContentInset() {
let numRows = tableView(tableView, numberOfRowsInSection: 0)
var contentInsetTop = tableView.bounds.size.height
for i in 0..<numRows {
contentInsetTop -= tableView(tableView, heightForRowAt: IndexPath(item: i, section: 0))
if contentInsetTop <= 0 {
contentInsetTop = 0
}
}
self.tableView.contentInset = UIEdgeInsetsMake(contentInsetTop, 0, 0, 0)
self.tableView.scrollToRow(at: IndexPath(item: numRows-1, section: 0), at: .bottom, animated: false)
//Prevent scrolling
self.tableView.bounces = false;
//When we have actual fruit Cells to show, we want the line divisions
self.tableView.separatorStyle = .singleLine
}
}
I have tried adding the following code to my keyboardWillShow function:
var tableViewFrame: CGRect!
tableViewFrame = self.tableView.frame
tableViewFrame.size.height += 146.0
tableView.frame = tableViewFrame
tableView.contentInset = UIEdgeInsetsMake(146.0, 0, 0, 0);
However, when the keyboard disappears, there are horizontal gray lines that span the screen, as shown below:
EDIT:
These horizontal gray lines (six in total) should not be there. How do I prevent them from appearing when the keyboard has disappeared?
The simplest solution is IQKeyboardManager. This will manage the space below keyboard for you.
Otherwise, add a bottom constraint tableViewBottomConstraint to the table view. Set its initial value tableViewBottomConstraint.constant = 0
Now on set it equal to the keyboard height + padding in keyboardWillShow keyboardWillHide
func keyboardWillShow(notification: NSNotification) {
tableViewBottomConstraint.constant = keyboardHeight + padding
layoutTableView()
}
func keyboardWillHide(notification: NSNotification) {
tableViewBottomConstraint.constant = 0
layoutTableView()
}
private func layoutTableView() {
UIView.animate(withDuration: 0.3, animations: {
tableView.superview?.layoutIfNeeded()
}, completion: nil)
}
To do this you need to write your code in keyboardWillHide:
Resize the tableView increasing its frame's height (or the relative constraint's constant value) of the keyboard's height;
Key point: set tableView's top content inset equal to the keyboard's height.
So for instance if the keyboard's height is 216pt, you will do:
CGRect tableViewFrame = tableView.frame;
tableViewFrame.size.height += 216.0;
tableView.frame = tableViewFrame;
tableView.contentInset = UIEdgeInsetsMake(216.0, 0, 0, 0);
If you are using constraints instead, the only difference is that you'll change the height of the constraint's constant relative to the height of the tableView.
P.s. if you want a deeper explanation of what UIEdgeInsets is, take a look here: https://developer.apple.com/documentation/uikit/uiedgeinsets
They have been made exactly to create empty space with a specific offset.
func keyboardWillShow(notification:NSNotification) {
if let keyboardSize = notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? CGRect {
let contentInsets = UIEdgeInsets(top: 0, left: 0, bottom:
keyboardSize.height, right: 0)
print(contentInsets)
btnBottomConstrain.constant = contentInsets.bottom
UIView.animate(withDuration: 0.2, delay: 0, options: .transitionCurlDown, animations: {
self.view.layoutIfNeeded()
}, completion: nil)
}
}
func keyboardWillHide(notification:NSNotification) {
if let keyboardSize = notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? CGRect {
let contentInsets = UIEdgeInsets(top: 0, left: 0, bottom: keyboardSize.height, right: 0)
print(contentInsets)
self.btnBottomConstrain.constant = 0
UIView.animate(withDuration: 0.2, delay: 0, options: .transitionCurlDown, animations: {
self.view.layoutIfNeeded()
}, completion: nil)
}
}
replace these two methods with your keyboardshow and keybordhide methods accordingly. here btnBottomConstrain will be the outlet of your tableview's bottom constraint. I hope this will work for you.

How to set textAlignment for UITableViewHeaderFooterView?

I make a UITableViewControl. I need to set textAlignment for UITableViewHeaderFooterView as shown below. But it's not working.
UITableViewHeaderFooterView *header = (UITableViewHeaderFooterView *)view;
[header.textLabel setTextColor:[UIColor colorWithPatternImage:[UIImage imageNamed:#"button.png"]]];
header.textLabel.textAlignment = NSTextAlignmentCenter;
Create a view,on this view add a label,and set the view as the header view on your table:
self.tableView.headerView = view
If you want to have a header filled by a stretched image, try to use contentView instead of textLabel
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
return HEADER_HEIGHT;
}
- (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section {
UITableViewHeaderFooterView *header = (UITableViewHeaderFooterView *)view;
UIImage *originImage = [UIImage imageNamed:#"yourImage"];
CGSize size = header.frame.size;
UIGraphicsBeginImageContext(size);
[originImage drawInRect:CGRectMake(0,0,size.width,size.height)];
UIImage* resizedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
header.contentView.backgroundColor = [UIColor colorWithPatternImage:resizedImage];
}
As suggested by #wg_hij, we can use custom UIView as header view for table view.
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let sectionInfo = fetchedResultsController!.sections! as [NSFetchedResultsSectionInfo]
let headerTitle = sectionInfo[section].name
let headerHeight:CGFloat = tableView.sectionHeaderHeight
let headerView = HeaderView(frame: CGRectMake(0, 0, tableView.width, headerHeight), withHeaderLable: headerTitle)
return headerView
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
return 40;//custom height
}
HeaderView will be something like:
class HeaderView: UIView {
var headerLbl: UILabel?
init(frame: CGRect, withHeaderLable headerText: String) {
super.init(frame: frame)
backgroundColor = UIColor.clearColor()
makeUpHeaderLabel(headerText)
}
func makeUpHeaderLabel(lblText: String) {
if headerLbl == nil {
headerLbl = UILabel(frame: CGRectMake(0, 0, 10, 10))
headerLbl!.makeRoundedRect()
headerLbl!.bgColorName = ColorPaletteCP1
headerLbl!.fontName = BHTypoMI14
headerLbl!.textAlignment = .Center
headerLbl!.baselineAdjustment = .AlignCenters
addSubview(headerLbl!)
}
headerLbl!.text = lblText
headerLbl!.sizeToFit()
let frame = headerLbl!.frame
headerLbl!.frame = CGRectMake(0, 0, frame.size.width + 32, frame.height + 8)
headerLbl!.center = CGPointMake(width / 2, height / 2)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Using this custom header view, we can do anything related to look-n-feel!

Hiding Tableview section headers on the fly

I am trying to hide and display section headers when there is or isn't data populating the tableview. The code I have now works as intended occasionally, but mostly, one or both of the section headers will remain displayed, (However, if I drag the header off screen when there is no data it will disappear as intended).
This is the code I am using to hide/unhide the sections.
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
//Create custom header titles for desired header spacing
if (section == 0) {
UIView *headerView;
if (self.searchUsers.count || self.searchTracks.count)
{
headerView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 320, 40)];
}
else{
headerView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 0, 0)];
}
HeaderTitleLabel *headerTitle = [[HeaderTitleLabel alloc] initWithFrame:CGRectMake(17, 10, 150, 20) headerText:#"USERS"];
[headerView addSubview:headerTitle];
return headerView;
}
else if (section == 1){
UIView *headerView;
if (self.searchUsers.count || self.searchTracks.count)
{
headerView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 320, 40)];
}
else{
headerView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 0, 0)];
}
HeaderTitleLabel *headerTitle = [[HeaderTitleLabel alloc] initWithFrame:CGRectMake(17, 0, 150, 20) headerText:#"SONGS"];
[headerView addSubview:headerTitle];
return headerView;
}
else
return nil;
}
-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
//custom header spacing
if (section == 0) {
if (self.searchUsers.count || self.searchTracks.count)
{
return 40;
}
else
return 0;
}
else if (section == 1) {
if (self.searchUsers.count || self.searchTracks.count)
{
return 50;
}
else
return 0;
}
else
return 0;
}
I check if my array has objects, if not then set the frame's height to 0. This doesn't seem to be working though. Any ideas how I should go about doing this? Thank you.
Your model should drive your table view. Don't just return a 'number of rows' value from your model, but also a 'number of sections' value.
Consider an array that contains two arrays as your model.
You are using wrong approach of table view. Can you clarify what do you exactly want to do. I could help you. Rather then UIView use UITableView Cell.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (self.searchUsers.count || self.searchTracks.count)
{
if (indexPath.section ==0) {
static NSString *simpleTableIdentifier = #"CategoryCell";
CategoryCell *cell = (CategoryCell *)[tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"CategoryCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
cell.lbl_CatName.text = [[self.CategoryArray objectAtIndex:indexPath.row] objectForKey:kNAME];
return cell;}
else if (indexPath.section ==1){
static NSString *EarnPointIdentifier = #"EarnPointTableCell";
EarnPointTableCell *EarnPointcell = (EarnPointTableCell *)[tableView dequeueReusableCellWithIdentifier:EarnPointIdentifier];
if (EarnPointcell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"EarnPointTableCell" owner:self options:nil];
EarnPointcell = [nib objectAtIndex:0];
}
return EarnPointcell ;
}
else{
//it will have transparent background and every thing will be transparent
static NSString *RewardIdentifier = #"RewardTableCell";
RewardTableCell *RewardCell = (RewardTableCell *)[tableView dequeueReusableCellWithIdentifier:RewardIdentifier];
if (RewardCell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"RewardTableCell" owner:self options:nil];
RewardCell = [nib objectAtIndex:0];
}
return RewardCell ;
}
}
}
It seems to be working fine now. What I did was create boolean values to keep track of my data when my data source is nil. Like so;
[SoundCloudAPI getTracksWithSearch:userInput userID:nil withCompletion:^(NSMutableArray *tracks) {
self.searchTracks = tracks;
[self.tableView beginUpdates];
if (tracks.count) {
self.songsHeaderIsHidden = NO;
}
else
self.songsHeaderIsHidden = YES;
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:1] withRowAnimation:UITableViewRowAnimationFade];
[[self.tableView headerViewForSection:1] setNeedsLayout];
[[self.tableView headerViewForSection:1] setNeedsDisplay];
[self.tableView endUpdates];
}];
Then set the header accordingly..
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
//Create custom header titles for desired header spacing
if (section == 0) {
if (self.userHeaderIsHidden) {
return nil;
}
else {
UIView *userHeaderView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 320, 40)];
HeaderTitleLabel *usersHeaderTitle = [[HeaderTitleLabel alloc] initWithFrame:CGRectMake(17, 10, 150, 20) headerText:#"USERS"];
[userHeaderView addSubview:usersHeaderTitle];
return userHeaderView;
}
}
else if (section == 1){
if (self.songsHeaderIsHidden) {
return nil;
}
else {
UIView *songsHeaderView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 320, 40)];
songsHeaderView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 320, 40)];
HeaderTitleLabel *songsHeaderTitle = [[HeaderTitleLabel alloc] initWithFrame:CGRectMake(17, 0, 150, 20) headerText:#"SONGS"];
[songsHeaderView addSubview:songsHeaderTitle];
return songsHeaderView;
}
}
else
return nil;
}
Personally, I would use UICollectionView and write your own layout. That way you can explicitly layout where the section headers will be at all times. The best approach is to probably subclass UICollectionViewFlowLayout because you get all the superclass properties for free (such as itemSize, headerReferenceSize, etc.)
Here's an implementation I wrote for having sticky headers, like the Instagram app.
Notice that in prepareLayout, I just calculate the normal positions of every element. Then in layoutAttributesForElementInRect I go through and figure out where to place the header. This is where you can go through and see if the header should be shown or not.
import UIKit
class BoardLayout: UICollectionViewFlowLayout {
private var sectionFrames: [CGRect] = []
private var headerFrames: [CGRect] = []
private var footerFrames: [CGRect] = []
private var layoutAttributes: [UICollectionViewLayoutAttributes] = []
private var contentSize: CGSize = CGSizeZero
override func prepareLayout() {
super.prepareLayout()
self.sectionFrames.removeAll(keepCapacity: false)
self.headerFrames.removeAll(keepCapacity: false)
self.footerFrames.removeAll(keepCapacity: false)
self.layoutAttributes.removeAll(keepCapacity: false)
let sections = self.collectionView?.numberOfSections()
var yOffset: CGFloat = 0.0
for var i: Int = 0; i < sections; i++ {
let indexPath = NSIndexPath(forItem: 0, inSection: i)
var itemSize: CGSize = self.itemSize
if let d = self.collectionView?.delegate as? UICollectionViewDelegateFlowLayout {
if let collection = self.collectionView {
if let size = d.collectionView?(collection, layout: self, sizeForItemAtIndexPath: NSIndexPath(forItem: 0, inSection: i)) {
itemSize = size
}
}
}
let headerFrame = CGRect(x: 0, y: yOffset, width: self.headerReferenceSize.width, height: self.headerReferenceSize.height)
self.headerFrames.append(headerFrame)
var headerAttribute = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withIndexPath: indexPath)
headerAttribute.frame = headerFrame
headerAttribute.zIndex = 1000
self.layoutAttributes.append(headerAttribute)
yOffset += self.headerReferenceSize.height - 1 // - 1 so that the bottom border of the header and top border of the cell line up
let sectionFrame = CGRect(x: 0, y: yOffset, width: itemSize.width, height: itemSize.height)
self.sectionFrames.append(sectionFrame)
var sectionAttribute = UICollectionViewLayoutAttributes(forCellWithIndexPath: indexPath)
sectionAttribute.frame = sectionFrame
self.layoutAttributes.append(sectionAttribute)
yOffset += itemSize.height
let footerFrame = CGRect(x: 0, y: yOffset, width: self.footerReferenceSize.width, height: self.footerReferenceSize.height)
self.footerFrames.append(footerFrame)
var footerAttribute = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: UICollectionElementKindSectionFooter, withIndexPath: indexPath)
footerAttribute.frame = footerFrame
self.layoutAttributes.append(footerAttribute)
yOffset += self.minimumLineSpacing + self.footerReferenceSize.height
}
self.contentSize = CGSize(width: self.collectionView!.width, height: yOffset)
}
override func layoutAttributesForElementsInRect(rect: CGRect) -> [AnyObject]? {
var newAttributes: [UICollectionViewLayoutAttributes] = []
for attributes in self.layoutAttributes {
let frame = attributes.frame
if !CGRectIntersectsRect(frame, CGRect(x: 0, y: rect.origin.y, width: rect.size.width, height: rect.size.height)) {
continue
}
let indexPath = attributes.indexPath
let section = indexPath.section
if let kind = attributes.representedElementKind {
if kind == UICollectionElementKindSectionHeader {
var headerFrame = attributes.frame
let footerFrame = self.footerFrames[section]
if CGRectGetMinY(headerFrame) <= self.collectionView!.contentOffset.y {
headerFrame.origin.y = self.collectionView!.contentOffset.y
}
if CGRectGetMinY(headerFrame) >= CGRectGetMinY(footerFrame) {
headerFrame.origin.y = footerFrame.origin.y
}
attributes.frame = headerFrame
self.headerFrames[section] = headerFrame
} else if kind == UICollectionElementKindSectionFooter {
attributes.frame = self.footerFrames[section]
}
} else {
attributes.frame = self.sectionFrames[section]
}
newAttributes.append(attributes)
}
return newAttributes
}
override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes! {
let frame = self.sectionFrames[indexPath.item]
let attributes = super.layoutAttributesForItemAtIndexPath(indexPath)
attributes.frame = frame
return attributes
}
override func layoutAttributesForSupplementaryViewOfKind(elementKind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes! {
let attributes = super.layoutAttributesForSupplementaryViewOfKind(elementKind, atIndexPath: indexPath)
if elementKind == UICollectionElementKindSectionHeader {
attributes.frame = self.headerFrames[indexPath.section]
} else if elementKind == UICollectionElementKindSectionFooter {
attributes.frame = self.footerFrames[indexPath.section]
}
return attributes
}
override func collectionViewContentSize() -> CGSize {
return self.contentSize
}
override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool {
return true
}
}
I'd recommend using the UICollectionViewDelegateFlowLayout methods so you can dynamically determine the reference size of the header.

Scrolling UITableView to top doesn't scroll properly

My UIViewController splits to 2:
UIView
UITableView
I've added a footer view to table in order to hide the rest of the table.
Since I cannot use static cells and also hide all bottom view of the table, I did it a bit tricky.
But the table view didn't scroll to top when I tap on my textfields correctly.
The keyboard hides the UITextField and doesn't scroll to correct point.
How can I fix it?
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return 6;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *identifier;
if (indexPath.row == 0) identifier = #"cell_name";
else if (indexPath.row == 1) identifier = #"cell_password";
else if (indexPath.row == 2) identifier = #"cell_password_verify";
else if (indexPath.row == 3) identifier = #"cell_email";
else if (indexPath.row == 4) identifier = #"cell_cellphone";
else if (indexPath.row == 5) identifier = #"cell_social";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier forIndexPath:indexPath];
if (cell == nil) cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
return cell;
}
- (void)configureUI
{
UIView *tableFooterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth([UIScreen mainScreen].bounds), 1)];
tableFooterView.backgroundColor = [UIColor whiteColor];
self.tableView.tableFooterView = tableFooterView;
tableFooterView.backgroundColor = [UIColor whiteColor];
self.tableView.tableFooterView = tableFooterView;
self.edgesForExtendedLayout = UIRectEdgeNone;
self.extendedLayoutIncludesOpaqueBars = NO;
self.automaticallyAdjustsScrollViewInsets = NO;
}
UPDATE:
Problem was, scroll view cannot scroll because tableFooterView is too short, then I modified my code.
Basically,#Roger Nolan right, I also added the following code and now it works perfect:
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
UITableViewCell *cell = (UITableViewCell *)textField.superview.superview.superview;
[self.tableView scrollToRowAtIndexPath:[self.tableView indexPathForCell:cell] atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
- (void)registerObservers
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardDidShow:)
name:UIKeyboardDidShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardDidHide:)
name:UIKeyboardDidHideNotification
object:nil];
}
- (void)keyboardDidShow:(NSNotification *)notification
{
CGFloat keyboardHeight = [CoreGraphicsHandler keyboardFramFromNotification:notification].size.height;
UIView *tableFooterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth([UIScreen mainScreen].bounds), keyboardHeight)];
tableFooterView.backgroundColor = [UIColor whiteColor];
self.tableView.tableFooterView = tableFooterView;
}
- (void)keyboardDidHide:(NSNotification *)notification
{
UIView *tableFooterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth([UIScreen mainScreen].bounds), 1)];
tableFooterView.backgroundColor = [UIColor whiteColor];
self.tableView.tableFooterView = tableFooterView;
}
You need to make the controller a delegate of your text fields and then send the tableview scrollToRowAtIndexPath:atScrollPosition: animated:
See this exact dupe: Making a UITableView scroll when text field is selected
I had a similar problem and, as suggested by Idan Moshe in the update, I solved inserting a footerView in my tableView.
The solution in Swift 4:
func registerObservers(){
NotificationCenter.default.addObserver(self, selector: #selector(keyboardDidShow), name: NSNotification.Name.UIKeyboardDidShow, object: nil);
NotificationCenter.default.addObserver(self, selector: #selector(keyboardDidHide), name: NSNotification.Name.UIKeyboardDidHide, object: nil);
}
#objc func keyboardDidShow(_ notification: Notification) {
DispatchQueue.main.async {
if let keyboardFrame: NSValue = notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue {
let keyboardHeight = keyboardFrame.cgRectValue.height;
let tableFooterView = UIView.init(frame: CGRect.init(x: 0, y: 0, width: UIScreen.main.bounds.width, height: keyboardHeight))
self.tableView.tableFooterView = tableFooterView;
}
}
}
#objc func keyboardDidHide(_ notification: Notification) {
DispatchQueue.main.async {
let tableFooterView = UIView.init(frame: CGRect.init(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 1));
self.tableView.tableFooterView = tableFooterView;
}
}
func textFieldDidBeginEditing(_ textField: UITextField) {
let cell = self.selectedTExtField?.superview?.superview;
if let idxPath = self.tableView.indexPath(for: cell){
self.tableView.scrollToRow(at: idxPath, at: .top, animated: true);
}
else{
print("Error: index path not found");
}
}

Set height of delete button that appears on swipe in UITableViewCell

I have UITableViewCell as shown in figure below.
The cell occupy the height occupied by delete. The cell height is set so as to keep spacing between two cell.
Now, when i swipe and delete button appears (red in color), it occupies cell height as given in picture above. I simply want to set its height to height of white part only or say the height of gray button. Can anyone help me on how to set the height of delete button that appears after swipe in UITableViewCell?
The best way to solve this was overriding
-(void)layoutSubviews in YourCustomCell:UITableViewCell
then
if ([NSStringFromClass([subview class])isEqualToString:#"UITableViewCellDeleteConfirmationControl"]){
UIView *deleteButtonView = (UIView *)[subview.subviews objectAtIndex:0];
CGRect buttonFrame = deleteButtonView.frame;
buttonFrame.origin.x = Xvalue;
buttonFrame.origin.y = Yvalue;
buttonFrame.size.width = Width;
buttonFrame.size.height = Height;
deleteButtonView.frame = buttonFrame;
}
Use this code in your custom Cell class
-(void) layoutSubviews
{
NSMutableArray *subviews = [self.subviews mutableCopy];
UIView *subV = subviews[0];
if ([NSStringFromClass([subV class])isEqualToString:#"UITableViewCellDeleteConfirmationView"]){
[subviews removeObjectAtIndex:0];
CGRect f = subV.frame;
f.size.height = 106; // Here you set height of Delete button
subV.frame = f;
}
}
Swift 5, works for iOS12, iOS13 and iOS14
func tableView(_ tableView: UITableView, willBeginEditingRowAt indexPath: IndexPath) {
// for iOS13, iOS14
if let swipeContainerView = tableView.subviews.first(where: { String(describing: type(of: $0)) == "_UITableViewCellSwipeContainerView" }) {
if let swipeActionPullView = swipeContainerView.subviews.first, String(describing: type(of: swipeActionPullView)) == "UISwipeActionPullView" {
swipeActionPullView.frame.size.height -= 10
}
}
// for iOS12
tableView.subviews.forEach { subview in
if String(describing: type(of: subview)) == "UISwipeActionPullView" {
subview.frame.size.height -= 10
}
}
}
Add this method to your customCell.m file.
-(void) layoutSubviews
{
NSMutableArray *subviews = [self.subviews mutableCopy];
UIView *subview = subviews[0];
if ([NSStringFromClass([subview class])isEqualToString:#"UITableViewCellDeleteConfirmationView"]){
UIView *deleteButtonView = (UIView *)[subview.subviews objectAtIndex:0];
CGRect buttonFrame = deleteButtonView.frame;
buttonFrame.origin.x = deleteButtonView.frame.origin.x;
buttonFrame.origin.y = deleteButtonView.frame.origin.y;
buttonFrame.size.width = deleteButtonView.frame.size.width;
buttonFrame.size.height = 46;
deleteButtonView.frame = buttonFrame;
subview.frame=CGRectMake(subview.frame.origin.x, subview.frame.origin.y, subview.frame.size.width, 46);
deleteButtonView.clipsToBounds=YES;
subview.clipsToBounds=YES;
}
}
For IOS 13 , the Position has been yet again change , not inside table view it is once again in _UITableViewCellSwipeContainerView . Thus you should iterate through that as well.Take a look below
([NSStringFromClass([subview class])
isEqualToString:#"_UITableViewCellSwipeContainerView"]){
for (UIView *deleteButtonSubview in subview.subviews){
if ([NSStringFromClass([deleteButtonSubview class])
isEqualToString:#"UISwipeActionPullView"]) {
if ([NSStringFromClass([deleteButtonSubview.subviews[0] class]) isEqualToString:#"UISwipeActionStandardButton"]) {
//do what you want
}
}
}
}
Swift 5 - iOS 14
Change the way you are handling cell height to add spacing using the following:
override var frame: CGRect {
get {
return super.frame
}
set (newFrame) {
var frame = newFrame
frame.origin.y += 4
frame.size.height -= 10
super.frame = frame
}
}
Write below code in your custom cell hope it will work for you-
- (void)willTransitionToState:(UITableViewCellStateMask)state
{
[super willTransitionToState:state];
if(state == UITableViewCellStateShowingDeleteConfirmationMask)
{
[self performSelector:#selector(resetDeleteButtonSize) withObject:nil afterDelay:0];
}
}
- (void)resetDeleteButtonSize
{
NSMutableArray *subviews = [self.subviews mutableCopy];
while (subviews.count > 0)
{
UIView *subV = subviews[0];
[subviews removeObjectAtIndex:0];
if ([NSStringFromClass([subV class])isEqualToString:#"UITableViewCellDeleteConfirmationButton"])
{
CGRect f = subV.frame;
f.size.height = 74;
subV.frame = f;
break;
}
else
{
[subviews addObjectsFromArray:subV.subviews];
}
}
}
To see how it's work in IOS 11 please copy this Swift 4 code snippet in your UITableViewController:
override func tableView(_ tableView: UITableView, willBeginEditingRowAt indexPath: IndexPath) {
self.tableView.subviews.forEach { subview in
print("YourTableViewController: \(String(describing: type(of: subview)))")
if (String(describing: type(of: subview)) == "UISwipeActionPullView") {
if (String(describing: type(of: subview.subviews[0])) == "UISwipeActionStandardButton") {
var deleteBtnFrame = subview.subviews[0].frame
deleteBtnFrame.origin.y = 12
deleteBtnFrame.size.height = 155
// Subview in this case is the whole edit View
subview.frame.origin.y = subview.frame.origin.y + 12
subview.frame.size.height = 155
subview.subviews[0].frame = deleteBtnFrame
subview.backgroundColor = UIColor.yellow
}
}
}
}
This code working for IOS 11 and higher
SWIFT
override func layoutSubviews() {
super.layoutSubviews()
for subview in self.subviews {
if String(describing: type(of: subview.self)) == "UITableViewCellDeleteConfirmationView" {
let deleteButton = subview
let deleteButtonFrame = deleteButton.frame
let newFrame = CGRect(x: deleteButtonFrame.minX,
y: deleteButtonFrame.minY,
width: deleteButtonFrame.width,
height: yourHeight)
deleteButton.frame = newFrame
}
}
}

Resources