How to add UITextView to bottom of UITableView - swift

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.

Related

Cell loaded in cellForItemAt, but not in visibleCells

I have a custom UICollectionView and the cells are loaded in cellForItemAt but when I try to get all the visible cells by using visibleCells I'm not getting all the cells.
For example, in cellForItemAt, I'm setting the alpha of the labels in the cells to 0. When panned, I want the alpha of those labels change to 1:
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
handleLabel(scrollView, active: true)
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
if pickerIsActive { handleLabel(scrollView, active: false) }
}
private func handleLabel(_ scrollView: UIScrollView, active: Bool) {
guard let pickerView = scrollView as? UICollectionView else { return }
let cells = pickerView.visibleCells.flatMap { $0 as? CustomCell }
panningIsActive = active
UIView.animate(duration: 0.3) {
cells.forEach { $0.label.alpha = $0.isSelected || active ? 1 : 0 }
}
}
And cellForItemAt:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CustomCell
cell.label.alpha = 0
return cell
}
What can I do to change all the "loaded" cells instead of just the "visible" cells?
The visibleCells are only the on screen cells. This used to be everything initialized in cellForItem:at: but as of iOS 10 UICollectionView now prefetches to improve scrolling performance (see WWD 2016 video) which maybe why you are having this problem. Anyways it sounds like all you want to do is animate the cells to fade in when they come on screen. You can either move your animation logic to willDisplayCell or subclass UICollectionViewCell. UIColectionViewCell inherits from UIView, so you can override didMoveToSuperView in your UICollectionViewCell and call your animation method there, which will cause the cell to animate as it appears.
I am using Xcode 11.4 and Swift 5, and I had the exactly the same issue: .visibleCells is not giving me all the loaded cells.
By reading #Josh Homann's answer and the comments below, I figured out 2 solutions.
The first solution is same as the solution you reached at: customize cell appearance in collectionView(_:willDisplay:_:) after it's loaded but before it's displayed on the screen.
Another quick and dirty solution is to simply uncheck UICollectionView's 'Prefetch' option in attributes inspector.
This fixes the issue because by disabling prefetching, UICollectionView will stop pre-loading cells that are not displayed on the screen, so .visibleCells are now all the loaded cells. This solution will work fine if you're simply loading static or small local data in the cells. If you're prefetching large data (e.g. images) from network for upcoming cells, you probably need Prefetching Enabled, then solution 1 is your go-to option.
It sounds like you might want to try using layoutAttributesForElements(in:).
You'll need to implement your own collection view layout subclass (rather than using the delegate methods) but I think it will be worth it in the long term.
Rather than manually managing the animations (via UIView.animateWithDuration) you use this method to tell the collection view what properties cells should have at different positions, and as people pan the collection view, the correct properties are automatically applied.
I tried to find a good Swift reference for this, but I could't, but here's a post in Objective-C that you can follow if you want to try this approach:
https://bradbambara.wordpress.com/2014/05/24/getting-started-with-custom-uicollectionview-layouts/

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.

How to set the table view cell to adjust to label?

I am using Xcode to make an app. Using auto layout, I have a tableview contained in a container view which is part of a view controller embedded un a navigation controller.
This table view is my content page, so the individual tableview cells leads to another tableview which contains bulk of the information I would like to display.
I added UIlabel which contains a paragraph of words to each tableview cells and set the constraints to the respective tableview cells.
In the storyboard, it looks alright having to be able to see all the paragraphs. I have set the line to be 0, and word wrap.
When I run the programme with lets say 4s simulator, the table view cells do not display the whole paragraph but rather, just indicate "..." at the end of each paragraph.
How do I set the settings such that the tableview cells will adjust itself according to the UIlabel after being word wrapped giving a certain screen sizes of phone?
PS: I am new to SWIFT programming and thanks for taking your time to help me.
Have you done this?:
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
This is required for self-sizing table view cells. Also make sure of the following things:
a) The number of lines of UILabel is set to 0
b) The autolayout system in the table view cell is proper -- all the views are pinned on all sides and the autolayout can accurately determine the height of the cell.
c) Also do a tableView.estimatedRowHeight = 75 (or your estimated height) for efficiency.
add this two line in viewDidLoad Method and label number of lines 0 and dont give any height.
func viewDidLoad() {
super.viewDidLoad()
tableView.estimatedRowHeight = 44.0
tableView.rowHeight = UITableViewAutomaticDimension
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! CustomCell
//cell.customTexLabel Number 0f lines 0 and dont give height contrains just Leading,Trailing,TOP and Bottom.
cell.customTextLable?.text = "Here index starts from 0 which means first element can be accessed using index as 0, second element can be accessed using index as 1 and so on. Let's check following example to create, initialize and access arrays:Here index starts from 0 which means first element can be accessed using index as 0, second element can be accessed using index as 1 and so on. Let's check following example to create, initialize and access arrays:Here index starts from 0 which means first element can be accessed using index as 0, second element can be accessed using index as 1 and so on. Let's check following example to create, initialize and access arrays:"
return cell
}
Hi #Daniel you can learn how to create self sizing cell here is the demo tutorial for custom dynamic cell
http://developerclouds.com/2016/03/23/table-view-custom-cell-with-self-sizing/

YPDrawSignatureView - Table scrolling when drawing signature

So I want a signature view within a table cell. Obviously whenever somebody tries to draw in the cell, the table scrolls.
How would I stop the scrolling but ONLY when the user is writing in the signature box?
I found better solution for this issue rather than putting button. Implement the delegate methods in viewController,
class mainVC: UIViewController,YPSignatureDelegate {
Than set delegate of signature view to this view controller
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SignatureCell", for: indexPath) as! SignatureCell
cell.signatureView.delegate = self
return cell
}
And then add these code. This are two delegates of YPSignature. Add in Main view controller
func didStart() {
tableView.isScrollEnabled = false
}
// didFinish() is called rigth after the last touch of a gesture is registered in the view.
// Can be used to enabe scrolling in a scroll view if it has previous been disabled.
func didFinish() {
tableView.isScrollEnabled = true
}
I would solve this with a button covering the cell, and when the user taps it, the cell displays the YPDrawSignatureView. Just before the signature view is shown, disable the scrolling:
tableView.scrollEnabled = false
Later when you save the signature, enable scrolling again by setting scrollEnabled to true.
I added a uitableview and custom cells. In one of the custom cells contain a button(ex. addSignatureButton) on the top of signatureView.
I used delegate method to communicate between uitableviewcell and uiviewcontroller. A delegate is added to UITableViewCell to notify whether the addSignatureButton is tapped. Once it is tapped, addSignatureButton is hidden, signatureView is visible and the tableview's scroll is disabled. When user finishes adding signature, signatureView is hidden, addSignatureButton is visible and tableview scroll is enabled.
https://github.com/alvinvgeorge/DrawSignatureOnTableViewCell

UITableView - How to keep table rows fixed as user scrolls

I'd like to be able to fix the position of certain rows in a UITableView as the user scrolls.
Specifically, I have a table whereby certain rows are "headers" for the rows that follow, and I'd like the header to stay at the top of the screen as the user scrolls up. It would then move out of the way when the user scrolls far enough that the next header row would take its place.
A similar example would be the Any.DO app. The "Today", "Tommorrow" and "Later" table rows are always visible on the screen.
Does anyone have any suggestions about how this could be implemented?
I'm currently thinking of follow the TableDidScroll delegate and positioning my own cell in the appropriate place in front of the table view. The problem is that at other times I'd really like these cells to be real table cells so that they can be, for example, reordered by the user.
Thanks,
Tim
I've been playing about with this and I've come up with a simple solution.
First, we add a single UITableViewCell property to the controller. This should be initialize such that looks exactly like the row cells that we'll use to create the false section headers.
Next, we intercept scrolling of the table view
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
// Add some logic here to determine the section header. For example, use
// indexPathsForVisibleRows to get the visible index paths, from which you
// should be able to get the table view row that corresponds to the current
// section header. How this works will be implementation dependent.
//
// If the current section header has changed since the pervious scroll request
// (because a new one should now be at the top of the screen) then you should
// update the contents.
IndexPath *indexPathOfCurrentHeaderCell = ... // Depends on implementation
UITableViewCell *headerCell = [self.tableView cellForRowAtIndexPath:indexPathOfCurrentHeaderCell];
// If it exists then it's on screen. Hide our false header
if (headerCell)
self.cellHeader.hidden = true;
// If it doesn't exist (not on screen) or if it's partially scrolled off the top,
// position our false header at the top of the screen
if (!headerCell || headerCell.frame.origin.y < self.tableView.contentOffset.y )
{
self.cellHeader.hidden = NO;
self.cellHeader.frame = CGRectMake(0, self.tableView.contentOffset.y, self.cellHeader.frame.size.width, self.cellHeader.frame.size.height);
}
// Make sure it's on top of all other cells
[self.tableView bringSubviewToFront:self.cellHeader];
}
Finally, we need to intercept actions on that cell and do the right thing...
That's the default behavior for section headers in plain UITableView instances.
If you want to create a custom header, implement the tableView:viewForHeaderInSection: method in your table view delegate and return the view for your header.
Although you will have to manage sections and rows instead of just rows.
Swift 5 solution
var header: UIView?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(indexPath: indexPath) as UITableViewCell
header = cell.contentView
return cell
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let headerCell = tableView.cellForRow(at: IndexPath(row: 0, section: 0))
guard headerCell == nil || (headerCell!.frame.origin.y < self.tableView.contentOffset.y + headerCell!.frame.height/2) else {
header?.isHidden = true
return
}
guard let hdr = header else { return }
hdr.isHidden = false
hdr.frame = CGRect(x: 0, y: tableView.contentOffset.y, width: hdr.frame.size.width, height: hdr.frame.size.height)
if !tableView.subviews.contains(hdr) {
tableView.addSubview(hdr)
}
tableView.bringSubviewToFront(hdr)
}