Get full height of content of tableView - swift

I'm facing the following issue:
I have container view which has tableView, tableView contains cells and footer
in the footer, I have UIView, which contains another tableView with cells (this is opaqued into PageBoy because I've to change tabs due to date of tableView content)
What I want to achieve?
I want to resize tableView footer to fit content of it's tableView.
What I'm doing to achieve this?
I've prepared some research, I've developed ResizableTableView which code is the following:
class ResizableTableView: UITableView {
override var intrinsicContentSize: CGSize {
layoutIfNeeded()
return contentSize
}
func reloadData(with completion: #escaping () -> ()) {
animator.animate(withDuration: 0, options: [], animations: {
self.reloadData()
self.invalidateIntrinsicContentSize()
}, completion: { _ in
completion()
})
}
}
And the issue is that on first load view is not resizing correctly - tableView contentSize is not giving me full height, content is cut.
Maybe, anyone has a similar issue with matching height of tableView to its content. I've read a lot of similar questions, but none of them were matching my problem.
I was trying to reload tableView data and then by delegate tell my viewController to resize tableView footer, but this also failed on the first load. Cells are resizing by autolayout - this is probably a crucial problem in this implementation.

It's not good to contain a new TableView in the footer of your tableView.
Instead of adding footer view, it'd be better to add new section and use customized cells.
In this way, even you don't have to take care of the cells with dynamic heights of the second section which was the footer of the table view.

Related

How to add UITextView to bottom of UITableView

I have a project where I have to show a lot of information on one screen. It is not too much information, but it is complicated to achieve. For the purpose of this question I suggest to look at this screenshot that will illustrate what I want to achieve.
I currently set up this screen with a UIViewController containing a UITableView that is pinned to the top, bottom, leading and trailing anchor of the view using AutoLayout for the middle section. This works great. I then added a tableHeaderView which contains the information about the user on top. Now I have just added a second UITableView in the tableFooterView, and surprisingly, this works like a charm too. I had a bit of trouble with the height, but I managed to get it done in the viewDidLayoutSubviews() method. But that's bit off topic.
I am now at the final stage where I want to add a UITextView together with a UIButton (in a UIView container) to allow users to add comments. At first, I added this in the tableFooterView and it worked, but as soon as the content was too short (e.g. only one row in the middle and no comments yet), the UITextView would appear in the middle of the screen (directly under the contents of the UITableView). I read up on this and figured it is the expected behavior of the tableFooterView, so I am now trying to figure out a way on how to add this custom view to add comments that will always be on the bottom if the content is not filling the entire screen, but will also scroll with the contents if the content is larger than the screen size. (Ideally, I would want to be able to grow or shrink the UITextView when a user enters text - might be relevant in case someone suggests contentInsets).
Any suggestions? Should I add a subview directly to the UITableView (which is, as far as I read, not recommended)? Should I work with contentInsets on the UITableView and add the UIView container as a subview of my main UIViewController? I'm a bit lost after searching multiple solutions without finding the right one, so I hope you guys can help me out. Cheers!
Adding a UITextView is tricky because it's subclass of UIScrollView, just like UITableView, and adding a scroll view to another scroll view is not only difficult for the gesture recognisers to handle, but also for auto-layout.
What would make your life easier is to have just 1 table view in your view controller with different sections, and each section has a different type of cell.
What I like to do for this is declare an enum in my view controller:
class MyViewController: UIViewController {
private enum Section: Int, CaseIterable {
case foo
case bar
}
}
extension MyViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return Section.allCases.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard let section = Section(rawValue: section) else { preconditionFailure() }
switch section {
case .foo:
return 1
case .bar:
return 4
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let section = Section(rawValue: indexPath.section) else { preconditionFailure() }
let cell: UITableViewCell
switch section {
case .foo:
cell = ...
case .bar:
cell = ...
return cell
}
}
For the cells with long text you can just use a default cell style (in your XIB/storyboard) and set
cell.textLabel?.numberOfLines = 0
The table view should be able to automatically size itself to fit the entire text.
The only reason you should need to use a UITextView in your scenario is if you want the user to be able to edit the text. Otherwise save yourself a lot of pain and go with a UILabel.
You should add separate UIView at the bottom of main screen and add UITextView as a subview in it.
Add TableView above this container view so that it scrolls and textview is always visible at the bottom.

Set UITableView header view height depends on its childviews

I have a headerview in my UITableView but I can't find a solution to make it dynamically height. In my headerview I have a UITextView which height depends on its content.
What I have tried so far.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
sizeHeaderToFit()
}
func sizeHeaderToFit() {
let headerView = mTabView.tableHeaderView!
mTxtViewDescription.sizeToFit()
mConDescriptionHeight.constant = mTxtViewDescription.frame.height
print(mContainerView.frame.maxY)
headerView.frame.size.height += mTxtViewDescription.frame.height
}
The green area is my headerview
Changing the height of the frame of a UITableView header that's already been set won't recalculate the height, meaning you can't just change the height of a tableHeaderView as its layout is managed by the tableView. You need to set the tableHeaderView again, setting the property triggers the new calculation.
There are a number of questions going into detail about this, several linked from this one.

Swift tableView bottom loading indicator

I am currently struggling with implementing a bottom loading indicator for my app, exactly like Instagram and Facebook has. Simply I want to show a loading indicator at the bottom (on reverse drag) just like a normal table view loading.
Here is the code that I have for the regular table view update:
var refreshControl: UIRefreshControl!
//In viewDidLoad
refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action: "Refresh:", forControlEvents: UIControlEvents.ValueChanged)
tableView.addSubview(refreshControl)
In my func Refresh() I simply just fetch the data, and controls the activity indicator from there. However, how would I approach this, if I wanted to enable this in the bottom of my tableView?
Help is much appreciated.
Add it to the table footer view
tableView.tableFooterView = footerView
Adding a refreshControl would be difficult.
Add a UIIndicatorView to the footerview.
Implement scrollViewDidScroll:
func scrollViewDidScroll(scrollView: UIScrollView) {
if (scrollView.contentOffset.y + scrollView.frame.size.height) >= scrollView.contentSize.height {
tableView.tableFooterView!.hidden = true
// call method to add data to tableView
}
}
Refresh Controller is for pulltorefresh which usually use to reload data.
Spinner at the bottom of the screen is used for pagination.
What you need to do in numberOfRows method return arraysize+1.
in cellforRowAtIndexPath method check if indexpath.row > arraySize than return a cell having uiactivitycenter in the center.
Hope this will help you.
If you are getting the data from API call with pagination, which has to show in table view Then Kuntal's answer is right, in addition you can make 0 hight for cell containing indicator view when there is no more data, and after completion of reload. It is easy manage than check and return arraysize+1.

Accessibility (VO) does not scroll past after certain row

I've implemented a collectionView in a tableView cell in one my (Swift) projects where each UITableViewCell contains a UICollectionView and each collectionView then has UICollectionViewCell's. And my collectionView cell is again customized in a Xib.
Now when I tried to implement accessibility on this, this is going through some weird behavior.
What I did so far -
(1) Since each UITableViewCell contains UICollectionView, so I set tableView cell's accessibility to false.
e.g.
dummyTableViewCell.isAccessibilityElement = false
(2) Then I override UIAccessibilityContainer methods on my UICollectionView class.
e.g.
override func accessibilityElementCount() -> Int
{ return subViews.count }
and so on...
While this reads through each element of collectionView inside tableView cell, but then VO stops after 4th or 5th row and does not scroll past any more. I did try again with implementing UIScrollView delegate methods, but nothing is working out so far.

UIImageViews in collectionview are not recycled properly

I have a UICollectionview with 4 UIImageviews inside. These are there for showing specific icons on the cells. Unfortunately when I have a cell that populates all 4 image views with images it works correctly. But when a cell should only display less than 4 items, it sometimes ( so not always ) shows additional icons. It seems to be from other (previous) cells. Like its not recycled properly.
Here is my code. I have an outlet that stores all imageviews inside.
#IBOutlet var savings: [UIImageView]!
Next I have a method inside my Cell Viewcontroller that populates according to the given array of icon names.
func setPropositions(propositions: [SupplierProposition])
{
for (index, item) in propositions.enumerate() {
if index < 4 {
savings[index].image = nil
savings[index].setNeedsDisplay()
if let image = UIImage(named: item.iconName!) {
savings[index].image = image
} else {
savings[index].image = nil
}
}
}
}
as you see I tried everything to make sure the imageviews are empty before rendered, but it doesn't work. Anyone that know what I might be doing wrong?
Like its not recycled properly
No. It's like you are not recycling it properly. It is your job, in cellForItemAtIndexPath:, to completely configure this cell. This cell may have been used already, so it may already have image views in it that you placed there earlier; and if the cell for this index path is supposed to have fewer image views, it is your job to remove the ones you don't want.