Check table view cell fully visible - swift

how are you
I need to know how can i determine table view cell fully visible for playing auto play movie in cell and also detect hiding the table view cell.
I have apply this code below in table view but not give me the correct solution.
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let cellRect = tableView.rectForRow(at: indexPath)
let isFullyVisible = tableView.bounds.contains(rectInFull)
if isFullyVisible {
// Play video
}
}
So please can you tell me how can i get the correct table view cell visible

cell.frame.origin.y >= tableview.contentOffset.y && cell.frame.origin.y + cell.frame.size.height <= tableview.contentOffset.y + tableview.bounds.size.height

You might want to implement the UIScrollViewDelegate method scrollViewDidScroll(_:) and in there you could do the check for visible cells in your table view:
func scrollViewDidScroll(_ scrollView: UIScollView) {
guard let tv = scrollView as? UITableView else { return }
tv.visibleCells.forEach { $0.playVideo() }
}
Of course assuming that method on the cell (playVideo()) exists and it takes care of being called multiple times when the video is already playing (i.e. must ignore the call if is already playing).
Otherwise if you need the indexPaths of visible cells or any fine tuning use either the property indexPathsForVisibleRows or the method indexPathForRows(in:) on the table view, then those index paths you'll optionally obtain inside an array will point to the model's objects (which might implement the play video logic).

Related

Checking If every row in table view have image hidden or not

Is there any way to check inside a function : func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) If rest of the rows ( instead of the first one ) have image hidden or not.
If images of rows which are greater then indexPath.row - 0 are all hidden or not.
So basically I would like to get the first row ( image ) of the table view hidden when the rest one are hidden as well. The simple check boxes.
This how I'm hiding them :
let row = indexPath.row
if row > 0 {
UIView.animate(withDuration: 0.5, animations: {
let currentCell = self.engineStatusTableView.cellForRow(at: indexPath) as! DropDownViewCell
if !currentCell.checkMark.isHidden {
currentCell.checkMark.isHidden = true
} else {
currentCell.checkMark.isHidden = false
}
})
}
Thanks in advance!
It's generally not a good idea to keep your state inside of cells. Cells are reused and somewhat expensive to create, so you want to let UITableView control the creation and manage their reuse.
But, hopefully, your datasource knows if the checkmark is supposed to be hidden or not and you can ask it without having to create a cell to do that.

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/

TableView only loading visible cells (top 4 sections out of 12 section table view)

I'm trying to figure out why my tableView is only rendering sections that are visible to the user when the viewDidAppear method is called.
When I first launch the application, sections 1, 2, and 3 render correctly as seen below. However, sections 4 and later don't render correctly. When I press the refresh button (manually calling the reload() method), the visible sections during the time of clicking the refresh button refresh and render correctly.
I have linked my viewDidAppear method, as well as a temporary "Refresh" button, to the reload() method below:
textViewArray = [m1DF,m2DF,m3DF,m4DF,m5DF,m6DF,m7DF,m8DF,m9DF,m10DF,m11DF,m12DF,m13DF,m14DF,m15DF,m16DF]
for (index, tv) in textViewArray!.enumerate() {
let fixedWidth = tv.frame.size.width
tv.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.max))
let newSize = tv.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.max))
var newFrame = tv.frame
newFrame.size = CGSize(width: max(newSize.width, fixedWidth), height: newSize.height)
tv.frame = newFrame;
heightArray[index] = newSize.height
}
for (index, _) in (heightArray.enumerate()) {
heightArray[index] = textViewArray![index].frame.height
}
self.tableView.reloadData()
When I first launch the application, sections 1, 2, and 3 render correctly as seen below:
However, sections 4 and later don't render correctly:
When I press the refresh button (manually calling the reload() method), the visible sections during the time of clicking the refresh button (in this case, sections titled "M05" and "M06") refresh and render correctly:
EDIT
Full code excluding outlets:
import UIKit
import AdSupport
import iAd
class CalculatorTableViewController: UITableViewController {
var heightArray: [CGFloat] = [44.0,44.0,44.0,44.0,44.0,44.0,44.0,44.0,44.0,44.0,44.0,44.0,44.0,44.0,44.0,44.0]
var textViewArray = [UITextView]?()
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
reload()
}
func reload() {
textViewArray = [m1DF,m2DF,m3DF,m4DF,m5DF,m6DF,m7DF,m8DF,m9DF,m10DF,m11DF,m12DF,m13DF,m14DF,m15DF,m16DF]
print("*****1*****\n\n\n\n\n \(m1DF.bounds.height), \(m1DF.frame.height)")
for (index, tv) in textViewArray!.enumerate() {
let fixedWidth = tv.frame.size.width
tv.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.max))
let newSize = tv.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.max))
var newFrame = tv.frame
newFrame.size = CGSize(width: max(newSize.width, fixedWidth), height: newSize.height)
tv.frame = newFrame;
heightArray[index] = newSize.height
print("Row \(index) has height \(newSize.height)")
}
for (index, _) in (heightArray.enumerate()) {
heightArray[index] = textViewArray![index].frame.height
}
self.tableView.reloadData()
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
}
override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
//sizeCell(indexPath.section)
}
#IBAction func refreshButton(sender: AnyObject) {
reload()
}
override func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 300.0
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if indexPath.row == 0 {
return heightArray[indexPath.section]
} else {
return UITableViewAutomaticDimension
}
}
/*override func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 200.0
}*/
}
looking through your code, I think you are getting confused over the key principles of implementing a table view in UIKit. It can be intimidating and I certainly found it confusing at first! You should look through the reference at:
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/index.html#//apple_ref/occ/cl/UITableView
This outlines in more detail a number of things I'm about to talk about.
Essentially, a UiTableViewController needs to do two things: first, it needs to know where to get the data from to populate the tables; and second, it needs to know how to put the table together in the way you want and present it to the user.
For the first of these tasks - call it the data task, it's actually quite easy. You just need to ensure the table view controller has access to a data object which encapsulates all the data you need. There is a dedicated protocol for this too - UITableViewDataSource - but you don't need to be this sophisticated. I've implemented tables with anything from arrays of strings to using dedicated data manager classes working in much higher dimensions.
The second of these tasks - call it the presentation task - can be the more confusing. The way in which you configure your table view is via delegate methods which come ready packaged with the UITableViewController object (as it is the table view's delegate by default).
Perhaps the most important of these is: cellForRowAtIndexPath:. This function tells the view what to put at each index path in the table. You use the index path provided in the delegate function to locate the data you need (for instance, cell.text = stringData[indexPath.row] in a simple case) and you then tailor the view and any subviews to present that data as you would want to. I'm assuming you know how to do this. One thing that can be problematic with implementing cellForRowAtIndexPath: is that you need to re-use a dequeued cell rather than creating a new cell every time you need one. The way you do this is: first, tell the table view controller which class you are using for your reusable cells with a call to registerClass:forCellReuseIdentifier. In the standard case the class you would register would be UiTableViewCell, but you can use your own too. You can then grab one of these cells (which will be a cell that is no longer being used, I.e. It has scrolled off the screen) with a call to dequeueReusableCellWithIdentifier.
The other key function that you will need is: numberOfRowsInSection(_ section: Int) which tells the controller how many rows in each section of the table (if there's only one section it is of course the number of rows in the table). You use this with the UITableView variable numberOfSections and UITableView rowHeight when initializing the table to set these basic parameters.
Putting this all together, there is no need to set the height for each row in the table (as the array you have used apears to be trying to do), nor to have an explicit reload() function - there is a function "built in" to do this which simply calls the methods I have already alluded to.
For the sake of not making this one of the longest posts I've written I'll leave it there! However, if you'd like me to send over some sample code I'd be happy to do so, although it's probably better for you to play around with what I've briefly outlined above - hope that helps!

How does changing the intrinsicContentSize of a UITableViewCell work?

I made a UITableView with cells that expand when you tap on them. It is modeled off the following project: https://github.com/rushisangani/TableViewCellExpand
Basically, the cell's container view has two subviews which act as containers for the expanded/contracted states - "front" and "back", each of which is constrained to the top, bottom, leading, and trailing edges of the cell's main content view. To make the cell expand or contract, I just toggle the isActive property on the bottom constraint of the front and back views. This works, but only if I reload the cell when it is tapped. If I just change the constraints and then try to call invalidateIntrinsicContentSize(), nothing happens.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
// Expand/contract the cell and invalidate size (doesn't work)
// let cell = tableView.cellForRow(at: indexPath) as! ExpandingCell
// cell.tap()
// cell.invalidateIntrinsicContentSize()
// Keep track of the selected index and configure the expand/contract state when the cell is remade
tableView.deselectRow(at: indexPath, animated: false)
expandedIndexPath = indexPath
if(!expandedIndexPathArray.contains(indexPath)){
expandedIndexPathArray.append(indexPath)
}
else{
expandedIndexPathArray = expandedIndexPathArray.filter({$0 != indexPath})
}
// Whenever a cell's intrinsicContentSize changes, it must be reloaded
tableView.reloadRows(at: [indexPath], with: .none)
}
What's going on behind the scenes? Why can't the cell recalculate its size without being reloaded?
If you want to contract/expand cells based on their intrinsic content size, you need to do the following 3 steps:
Configure the table view:
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 44
For static table view controller, you will need to implement heightForRowAtIndexPath and return UITableViewAutomaticDimension.
Update cell constraints to reflect contracted/expanded state
Ask the table view to reflect changes:
tableView.beginUpdates()
tableView.endUpdates()

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)
}