Animation of UI View in table view cell is not consistent - ios

For some reason, when the cell that's being animated goes off-screen and comes back, the animation speed changes. Upon tapping the cell, a new view controller is opened. After I returned from the view controller to the initial view, the animation stopped altogether.
So far, I've tried to start the animation in cellForRowAt, but that didn't seem to work either.
Link to video for the problem: https://drive.google.com/file/d/1jt5IM1Ya4gIfzb1ok-NTmS2QTnrSTNoG/view?usp=sharing
Below is the code for willDisplay cell and the functions for animating my ui view inside my table view cell.
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if let cell = cell as? DirectionSummaryTableViewCell {
if let isLive = cell.isLive {
if isLive {
cell.animateBusStatusBackground()
} else {
cell.removeAnimation()
}
}
}
}
func animateBusStatusBackground() {
UIView.animate(withDuration: 1.0, delay: 0.0, options: [.repeat, .autoreverse, .curveEaseInOut], animations: { [weak self] in
if self?.busStatusView.backgroundColor == .red {
self?.busStatusView.backgroundColor = .grey6
} else {
self?.busStatusView.backgroundColor = .red
}
}, completion: nil)
}
func removeAnimation() {
self.busStatusView.layer.removeAllAnimations()
self.layer.removeAllAnimations()
self.layoutIfNeeded()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: DirectionSummaryTableViewCell.identifier, for: indexPath) as? DirectionSummaryTableViewCell else { return UITableViewCell() }
cell.configure(with: viewModel.placeToPlacePossibleDirections[indexPath.section][indexPath.row].directions,
tripDuration: viewModel.placeToPlacePossibleDirections[indexPath.section][indexPath.row].tripTime,
destinationArrivalTime:viewModel.placeToPlacePossibleDirections[indexPath.section][indexPath.row].reachBy,
busDepartureTime: viewModel.placeToPlacePossibleDirections[indexPath.section][indexPath.row].directions.routes[0].departureTime,
startLocation: viewModel.placeToPlacePossibleDirections[indexPath.section][indexPath.row].directions.routes[0].stops[0].name, addFullLabel: true,
isLive: viewModel.placeToPlacePossibleDirections[indexPath.section][indexPath.row].responseType == "realtime")
return cell
}
func configure(with directions: PlaceToPlaceBusDirections, tripDuration: Double, destinationArrivalTime: String, busDepartureTime: String, startLocation: String, addFullLabel: Bool, isLive: Bool) {
self.directions = directions
self.isLive = isLive
self.collectionView.reloadData()
self.collectionView.layoutIfNeeded()
var formattedTripDuration = ""
if tripDuration > 60 {
let hrs = Int(tripDuration / 60)
formattedTripDuration += String(hrs) + " hr"
if hrs > 1 { formattedTripDuration += "s " } else { formattedTripDuration += " " }
}
formattedTripDuration += String(Int(tripDuration) % 60)
self.tripDurationLabel.text = formattedTripDuration + " mins"
self.destinationArrivalTimeLabel.text = Date.dateStringFromString(dateString: destinationArrivalTime)
if addFullLabel {
self.busDeparturePlaceAndTimeLabel.text = ("Leaves at " + Date.dateStringFromString(dateString: busDepartureTime) + " from " + startLocation).maxLength(length: 40)
} else {
self.busDeparturePlaceAndTimeLabel.text = ("Leaves at " + Date.dateStringFromString(dateString: busDepartureTime)).maxLength(length: 40)
}
if !isLive {
busStatusText.text = "Live"
busStatusText.textColor = .white
} else {
busStatusText.text = "Scheduled"
busStatusText.textColor = .grey2
busStatusView.backgroundColor = .grey6
}
removeAnimation()
}
How do I fix this so that the animation is the same all the time?

To animate cells when you return from a different screen you can use something like below:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let visibleCells = tableView.visibleCells as? [DirectionSummaryTableViewCell] {
visibleCells.forEach {
if let isLive = $0.isLive {
if isLive {
$0.animateBusStatusBackground()
} else {
$0.removeAnimation()
}
}
}
}
}

Related

Rotate animation in UITableView cell not working [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 2 years ago.
Improve this question
I'm trying to make expandable UITableView and make a simple rotate animation to UIImage in UITableViewCell from didSelectRowAt when I click the cell the image rotate 180° clockwise and when I click the cell again it will rotate -180° back to its normal state but the animation not working and it has a bug for the first time to click on cell the arrow not rotate I must click in twice to make it rotate as the gif.
TestModel:
struct Titles {
var opened = Bool()
var title = String()
var sectionData = [String]()
}
ViewControllerView:
class ViewControllerView: UIView {
var data = [Titles]()
override init(frame: CGRect) {
super.init(frame: frame)
layoutUI()
data = [
Titles(opened: false, title: "Title1", sectionData: ["Cell1", "Cell2", "Cell3"]),
Titles(opened: false, title: "Title2", sectionData: ["Cell1", "Cell2", "Cell3"]),
Titles(opened: false, title: "Title3", sectionData: ["Cell1", "Cell2", "Cell3"])
]
}
lazy var recipesTableView: UITableView = {
let recipesTableView = UITableView()
recipesTableView.register(TableViewCell.self, forCellReuseIdentifier: "TableViewCell")
recipesTableView.register(TableViewCellTwo.self, forCellReuseIdentifier: "TableViewCellTwo")
return recipesTableView
}()
}
extension ViewControllerView: UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return data.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if data[section].opened == true {
return data[section].sectionData.count + 1
} else {
return 1
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath) as! TableViewCell
cell.title.text = data[indexPath.section].title
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCellTwo", for: indexPath) as! TableViewCellTwo
cell.title.text = data[indexPath.section].sectionData[indexPath.row - 1]
return cell
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if data[indexPath.section].opened == true {
data[indexPath.section].opened = false
let section = IndexSet.init(integer: indexPath.section)
let cell = tableView.cellForRow(at: indexPath) as! TableViewCell
UIView.animate(withDuration: 1, animations: {
cell.arrowImage.transform = CGAffineTransform.init(rotationAngle: CGFloat.pi)
})
tableView.reloadSections(section, with: .none)
} else {
data[indexPath.section].opened = true
let section = IndexSet.init(integer: indexPath.section)
let cell = tableView.cellForRow(at: indexPath) as! TableViewCell
UIView.animate(withDuration: 1, animations: {
cell.arrowImage.transform = CGAffineTransform.identity
})
tableView.reloadSections(section, with: .none)
}
}
}
TableViewCell:
class TableViewCell: UITableViewCell {
lazy var arrowImage: UIImageView = {
var arrow = UIImageView(image: UIImage(systemName: "arrowtriangle.down.fill"))
arrow.tintColor = .black
arrow.translatesAutoresizingMaskIntoConstraints = false
return arrow
}()
}
First we declare an enum with available chevron directions.
enum ChevronDirection: Double {
case up = -180
case down = 0
}
Also we could add an extension to make working with radians much easier
extension Double {
var degreesToRadians: CGFloat {
return CGFloat(self) * (CGFloat(Double.pi) / 180.0)
}
}
Then you should create a UITableViewHeaderFooterView header
class AnyNameHeader: UITableViewHeaderFooterView {
#IBOutlet weak var imgChevron: UIImageView!
var tapDelegate: DelegateToGetNotified?
var currentSection: Int!
func configure(_ text: String, section: Int, isExpanded: Bool) {
currentSection = section
self.set(isExpanded: isExpanded, animated: false)
}
func set(isExpanded: Bool, animated: Bool) {
if isExpanded {
setChevronDirection(ChevronDirection.up, animated: animated)
} else {
setChevronDirection(ChevronDirection.down, animated: animated)
}
}
func setChevronDirection(_ direction: ChevronDirection, animated: Bool) {
if animated {
UIView.animate(withDuration: 0.4, delay: 0.0, options: UIViewAnimationOptions(), animations: { [weak self] in
self?.imgChevron.transform = CGAffineTransform(rotationAngle: direction.rawValue.degreesToRadians)
}, completion: nil)
} else {
self.imgChevron.transform = CGAffineTransform(rotationAngle: direction.rawValue.degreesToRadians)
}
}
//Note: You can add your way to detect user tap over header for me i add a button over the header
#IBAction func headerAction() {
tapDelegate?.didTapHeader(sectionIndex: currentSection)
}
} // End of header
Then we goes to the Final part in our View controller
class MyViewController: UIViewController {
var expandedSectionIndex : Int? = nil {
didSet{
tableView.beginUpdates()
// Collapse previously expanded section
if let oldValue = oldValue {
tableView.deleteRows(at: indexPathsFor(section: oldValue), with: UITableViewRowAnimation.top)
if let header = tableView.headerView(forSection: oldValue) as? AnyNameHeader {
header.set(isExpanded: false, animated: true)
}
}
// Don't continue to Expand new section if already collapsing opened section
if let oldValue = oldValue, let sectionIndex = expandedSectionIndex, sectionIndex == oldValue {
expandedSectionIndex = nil
tableView.endUpdates()
return
}
// Expand new section
if let expandedSectionIndex = expandedSectionIndex {
tableView.insertRows(at: indexPathsFor(section: expandedSectionIndex), with: UITableViewRowAnimation.top)
if let header = tableView.headerView(forSection: expandedSectionIndex) as? AnyNameHeader {
header.set(isExpanded: true, animated: true)
}
}
tableView.endUpdates()
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if isExpandedSection(section: section) {
return data[section].sectionData.count
} else {
return 0
}
}
}
// MARK: Expanded Section
extension MyViewController {
/// Returns Indexpaths to be Inserted or Removed for a given section Index
fileprivate func indexPathsFor(section: Int) -> [IndexPath] {
var newIndexPaths = [IndexPath]()
for index in 0 ..< data[section].sectionData.count {
let newIndexPath = IndexPath(row: index , section: section)
newIndexPaths.append(newIndexPath)
}
return newIndexPaths
}
fileprivate func isExpandedSection(section: Int) -> Bool {
return expandedSectionIndex == section
}
}
extension MyViewController: DelegateToGetNotified {
func didTapHeader(sectionIndex: Int) {
expandedSectionIndex = sectionIndex
}
}

Gif when used as pagination loader, sometimes not displayed in image view

I am using collection view with pagination to show more data. At end of collection view I show an image view with gif file until data loads and then hide the image view. But sometimes gif doesn't load in the image view.
Please help and thanks in advance !!
I have tried changing libraries - Kingfisher, SDWebImage, ImageIO, UIImage+Gif etc. but it didn't help.
Also tried running on main thread.
class HorizontalScrollDealCollectionView: UICollectionView {
private var indicator:UIImageView!
private var offset:CGFloat = 0
private let loaderGif = UIImage.gif(name: "831")
override func awakeFromNib() {
super.awakeFromNib()
let frame = CGRect(x: 20, y: self.frame.height/2-15, width: 30, height: 30)
indicator = UIImageView(frame: frame)
indicator.image = loaderGif
indicator.backgroundColor = .blue
self.addSubview(indicator)
indicator.isHidden = true
}
override func layoutSubviews() {
super.layoutSubviews()
self.adjustIndicator()
}
func loadGifAnimatedIndicator() {
indicator.isHidden = true
indicator.image = loaderGif
}
private func adjustIndicator() {
indicator.frame.origin.x = self.contentSize.width + (offset/2)
let view = self.visibleCells.first
if let scrollViewAdjuster = view as? InfiniteScrollOffsetAdjuster {
indicator.frame.origin.y = scrollViewAdjuster.refralFrameForProgressView.height / 2 - 15
}
else {
indicator.frame.origin.y = self.contentSize.height / 2 - 15;
}
}
func showIndicator() {
if indicator.isHidden == false { return }
indicator.isHidden = false
indicator.image = loaderGif
indicator.startAnimating()
UIView.animate(withDuration: 0.2) {
self.contentInset.right = self.offset + 40
}
}
func hideIndicator() {
if indicator.isHidden { return }
self.indicator.isHidden = true
}
func resetContentInset(animate:Bool = true) {
UIView.setAnimationsEnabled(animate)
UIView.animate(withDuration: 0.1, animations: {
self.contentInset.right = 0
}) { (success) in
if !animate {
UIView.setAnimationsEnabled(true)
}
}
}
}
In ViewController
//Table View Delegate
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
guard let homeTableViewCell = cell as? HorizontalScrollHomeTableViewCell else {
return
}
homeTableViewCell.collectionView.loadGifAnimatedIndicator()
homeTableViewCell.collectionView.reloadData()
}
//Collection View Delegate
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
let horizontalCollView = collectionView as! HorizontalScrollDealCollectionView
let dataIndex = horizontalCollView.tableViewCellIndex!
let data = items[dataIndex.row]
if (indexPath.item == data.products.count - 1) && data.moreAvailable {
horizontalCollView.showIndicator()
data.loadMore(completion: {
(success) in
horizontalCollView.hideIndicator()
if success {horizontalCollView.reloadData()}
})
}
}
Screenshot 1
****Screenshot 2**
Changing
private let loaderGif = UIImage.gif(name: "831")
to
private var loaderGif: UIImage {
return UIImage.gif(name: "831")
}
solves the issue but tableview scrolling becomes laggy/jittering.
Also did this asynchronously using DispatchQueue but didn't helped. Still scrolling hangs.
func loadGifAnimatedIndicator() {
indicator.isHidden = true
DispatchQueue.main.async {
self.indicator.image = self.loaderGif
}
}

how check table view cell focus and player set pause

my project
I have several videos in the table in a row.
The problem is that they are played synchronously.
On screen 1video and 2video play synchronously
How do i can track focus cell on table view and cell.player?.play(). And other cells cell.player?.pause()
my code:
class MyViewController
//don't work
func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) as? ListViewCell {
cell.player?.pause()
}
}
//don't call
func tableView(_ tableView: UITableView, didUpdateFocusIn context: UITableViewFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
print("table view cell didUpdateFocusIn")
}
class MyTableviewCell
override func prepareForReuse() {
super.prepareForReuse()
player?.pause()
}
I'm making some changes to your project. Check this
https://yadi.sk/d/bQ-eIyMz3VF28V
Decide which video to play or pause when scrolling:
func handleScroll() {
if let indexPathsForVisibleRows = myTableView.indexPathsForVisibleRows, indexPathsForVisibleRows.count > 0 {
var focusCell: ListViewCell?
for indexPath in indexPathsForVisibleRows {
if let cell = myTableView.cellForRow(at: indexPath) as? ListViewCell {
if focusCell == nil {
let rect = myTableView.rectForRow(at: indexPath)
if myTableView.bounds.contains(rect) {
cell.player?.play()
focusCell = cell
} else {
cell.player?.pause()
}
} else {
cell.player?.pause()
}
}
}
}
}
Hope this will help you.
Use this in Your ViewController
extension ViewController {
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == #keyPath(UITableView.contentOffset) {
if let playIndexPath = currentPlayIndexPath {
if let cell = tblInstaFeed.cellForRow(at: playIndexPath) {
if player.displayView.isFullScreen { return }
let visibleCells = tblInstaFeed.visibleCells
if visibleCells.contains(cell) {
cell.contentView.addSubview(player.displayView)
player.displayView.snp.remakeConstraints{
$0.edges.equalTo(cell)
}
self.player.play()
} else {
player.displayView.removeFromSuperview()
self.player.pause()
}
}
}
}
}
}
And Call that Like this:
var tableViewContext = 0
func addTableViewObservers() {
let options = NSKeyValueObservingOptions([.new, .initial])
tblInstaFeed?.addObserver(self, forKeyPath: #keyPath(UITableView.contentOffset), options: options, context: &tableViewContext)
}
And Call addTableViewObservers function in viewDidLoad
Hope this will help.
You can use indexPathForRow(at: CGpoint)
Here is Extension to tableView
import Foundation
import UIKit
extension UITableView {
// center point of content size
var centerPoint : CGPoint {
get {
return CGPoint(x: self.center.x + self.contentOffset.x, y: self.center.y + self.contentOffset.y);
}
}
// center indexPath
var centerCellIndexPath: IndexPath? {
if let centerIndexPath: IndexPath = self.indexPathForRow(at: self.centerPoint) {
return centerIndexPath
}
return nil
}
// visible or not
func checkWhichVideoToEnableAtIndexPath() -> IndexPath? {
guard let middleIndexPath = self.centerCellIndexPath else {return nil}
guard let visibleIndexPaths = self.indexPathsForVisibleRows else {return nil}
if visibleIndexPaths.contains(middleIndexPath) {
return middleIndexPath
}
return nil
}
}
Then Use at func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
if let visibleIndex = tableView.checkWhichVideoToEnableAtIndexPath() ,
let cellMiddle = tableView.cellForRow(at: visibleIndex) as? ListViewCell
{
cellMiddle.player?.play()
}
else
{
cell.player?.pause()
}
I have implemented similar functionality like facebook video player.
-- > Auto play video
--> if video pause by user don't auto play it
--> Pause video as soon as it is removed from the screen
This is tested and working in every scenarios
You need to track video status in your datasource array. And I suggest you to create datasource array with class not with struct as it we need reference
var videos:[VideoAlbum] = []
var lastContentOffset:CGFloat = 0
func scrollViewDidScroll(_ scrollView: UIScrollView) {
// print("scrollViewDidScroll")
let visibleCell = self.collView.indexPathsForVisibleItems
if visibleCell.count > 0 {
for indexPath in visibleCell {
if videos[indexPath.row].isPlaying || videos[indexPath.row].pausedByUser {
continue
}
if let cell = self.collView.cellForItem(at: indexPath) as? CustomCell,//cell.video?.mediaType == MediaType.video,
let rect = self.collView.layoutAttributesForItem(at: indexPath)?.center {
//If cell's center is top of bottom of view OR cell's center is on Bottom of top of cell then play
let cellCenterToView = self.collView.convert(rect, to: self.view)
let cellFrameToView = self.collView.convert((self.collView.layoutAttributesForItem(at: indexPath)?.frame)!, to: self.view)
// scrolling up
if scrollView.contentOffset.y > lastContentOffset {
if cellCenterToView.y < self.view.frame.height && (cellFrameToView.height - abs(cellFrameToView.origin.y)) > self.view.frame.size.height / 2 {
self.pauseVideo(notFor: indexPath)
self.playVideoForCell(cell: cell,at: indexPath)
} else {
self.pauseVideoFor(indexPath: indexPath)
print("ELSE on SCROLL UP")
}
} else {
if cellCenterToView.y > 0 && (cellFrameToView.height - abs(cellFrameToView.origin.y)) > self.view.frame.size.height / 2 {
print(self.view.frame.intersection(cellFrameToView).size.height)
self.pauseVideo(notFor: indexPath)
self.playVideoForCell(cell: cell,at: indexPath)
} else {
self.pauseVideoFor(indexPath: indexPath)
print("ELSE on SCROLL DOwn \((self.view.frame.intersection(cellFrameToView).size.height + 64))")
}
}
}
}
}
lastContentOffset = scrollView.contentOffset.y
}
And Here is function for playing and pausing video
//--------------------------------------------------------------------------------
private func pauseVideo(notFor autoPlayIndexPath:IndexPath) {
let visibleCell = self.collView.indexPathsForVisibleItems
if visibleCell.count > 0 {
for indexPath in visibleCell {
if videos[indexPath.row].isPlaying && indexPath.row != autoPlayIndexPath.row {
guard let cellToHide = self.collView.cellForItem(at: indexPath) as? CustomCell/*,cellToHide.video?.mediaType == MediaType.video */ else {continue}
cellToHide.player?.pause()
cellToHide.player?.removeTimeObserver(cellToHide.video.timeObserver)
cellToHide.video.currentTime = cellToHide.player?.currentTime() ?? kCMTimeZero
cellToHide.video.isPlaying = false
NotificationCenter.default.removeObserver(cellToHide.player?.currentItem, name: Notification.Name.AVPlayerItemDidPlayToEndTime, object: nil)
// if cellToHide.video.timeObserver != nil {
// cellToHide.player?.removeTimeObserver(cellToHide.video.timeObserver)
// }
}
}
}
}
//--------------------------------------------------------------------------------
private func pauseVideoFor(indexPath:IndexPath) {
if videos[indexPath.row].isPlaying {
guard let cellToHide = self.collView.cellForItem(at: indexPath) as? CustomCell/*,cellToHide.video?.mediaType == MediaType.video */ else {return}
cellToHide.player?.pause()
cellToHide.player?.removeTimeObserver(cellToHide.video.timeObserver)
cellToHide.video.currentTime = cellToHide.player?.currentTime() ?? kCMTimeZero
cellToHide.video.isPlaying = false
NotificationCenter.default.removeObserver(cellToHide.player?.currentItem, name: Notification.Name.AVPlayerItemDidPlayToEndTime, object: nil)
// if cellToHide.video.timeObserver != nil {
// cellToHide.player?.removeTimeObserver(cellToHide.video.timeObserver)
// }
}
}
And here is model that is loaded in collection view
class VideoAlbum:Codable {
let id, image,video: String?
let mediaType: JSONNull?
let type, deleted, createdOn: String?
let modifiedOn: JSONNull?
var isPlaying:Bool = false
var currentTime:CMTime = kCMTimeZero
var timeObserver:Any? = nil
var pausedByUser:Bool = false
var hidePlayingControls = false
enum CodingKeys: String, CodingKey {
case id, image, video
case mediaType = "media_type"
case type, deleted
case createdOn = "created_on"
case modifiedOn = "modified_on"
}
}
Hope it is helpful to you

UIRefresh control endRefreshing doesn't work

When refresh control is triggered by swiping the tableview down, if there is no internet connection, a alert is shown and the refresh control is expected to end refreshing but it doesn't end refreshing even added in main thread
class JobsForCategoryVC: UIViewController {
//MARK:-Outlets
#IBOutlet weak var jobTableView: UITableView!
#IBOutlet weak var activityIndicator: UIActivityIndicatorView!
//MARK:-Properties
var refreshControl:UIRefreshControl!
var jobCategory:JobCategoryDB!
var pageNumber:Int = 1
var downloadMore:Bool = true
var jobs = [JobModel]()
//MARK:-LifeCycle
override func viewDidLoad() {
super.viewDidLoad()
setupView()
freshDownload()
}
func setupView(){
refreshControl = UIRefreshControl()
refreshControl.attributedTitle = NSAttributedString(string: "Loading fresh Jobs")
refreshControl.addTarget(self, action: #selector(self.freshDownload), for: .valueChanged)
jobTableView.addSubview(refreshControl)
}
func freshDownload(){
pageNumber = 1
downloadMore = true
downloadJobsFrom(top: true)
}
func downloadJobsFrom(top:Bool){
if !refreshControl.isRefreshing && top{
activityIndicator.startAnimating()
}
let url = URLStringList.getSearchCategoryJobString(pageNumber: pageNumber, categoryId: jobCategory.id!)
if let url = URL(string: url){
Alamofire.request(url, method: .get).responseJSON { (response) in
if response.result.isSuccess{
let json = response.result.value
let model = Mapper<JobModel>().mapArray(JSONArray: json as! [[String : Any]])
if model?.count == 0{
self.downloadMore = false
}
if let jobs = model{
if top{
self.jobs = jobs
}else{
self.jobs += jobs
}
self.jobTableView.reloadData()
self.pageNumber += 1
}
self.refreshControl.endRefreshing()
self.activityIndicator.stopAnimating()
}else{
self.activityIndicator.stopAnimating()
DispatchQueue.main.async(execute: {
self.refreshControl.endRefreshing()
self.jobTableView.reloadData()
})
if top{
showInternetConnectionAlert(viewController: self, activityIndicator: self.activityIndicator, completion: nil)
}
}
}
}
}
}
extension JobsForCategoryVC:UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if jobs.count > 0 {
jobTableView.backgroundView = nil
let cellCount = jobs.count + ((jobs.count-1)/(AdForNumberOfCells-1)) + 1
return cellCount
}
jobTableView.backgroundView = Bundle.main.loadNibNamed("PullToRefreshView", owner: nil, options: nil)?.first as? PullToRefreshView
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row % AdForNumberOfCells == 0{
if let cell = tableView.dequeueReusableCell(withIdentifier: "JobsAdTableViewCell", for: indexPath) as? JobsAdTableViewCell{
cell.controller = self
return cell
}
}else{
if let cell = tableView.dequeueReusableCell(withIdentifier: "JobsTableViewCell", for: indexPath) as? JobsTableViewCell{
let index = NSIndexPath(item: indexPath.row-(indexPath.row/AdForNumberOfCells)-1, section: 0)
cell.configure(job: jobs[index.row])
return cell
}
}
return UITableViewCell()
}
}
extension JobsForCategoryVC:UITableViewDelegate{
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let webView = storyboard?.instantiateViewController(withIdentifier: "WKWebVC") as? WKWebVC{
if indexPath.row % AdForNumberOfCells == 0 {
return
}
let index = NSIndexPath(item: indexPath.row-(indexPath.row/AdForNumberOfCells)-1, section: 0)
if let urlString = jobs[index.row].url{
webView.url = urlString
webView.titleString = jobs[index.row].title
present(webView, animated: true, completion: nil)
}
}
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if indexPath.row == self.jobs.count - 1 && downloadMore{
downloadJobsFrom(top: false)
}
}
}
Simple way of doing this is
DispatchQueue.main.async(execute: {
self.jobTableView.reloadData()
self.refreshControl.endRefreshing()
self.activityIndicator.stopAnimating()
self.refreshControl.setContentOffset(CGPoint.zero, animated: true)
})
Set UITableView contentOffset to zero
In Swift 3.0
refreshControl.endRefreshing()
self.yourTableView.contentOffset = CGPoint.zero
Please add all UI procedures in the main thread.
The Success block => Turn your code into :
if let jobs = model{
if top{
self.jobs = jobs
}else{
self.jobs += jobs
}
self.pageNumber += 1
}
DispatchQueue.main.async(execute: {
self.refreshControl.endRefreshing()
self.activityIndicator.stopAnimating()
self.jobTableView.reloadData()
})
And the Fail Block :
DispatchQueue.main.async(execute: {
self.refreshControl.endRefreshing()
self.activityIndicator.stopAnimating()
self.jobTableView.reloadData()
})
No matter success or failure,all your UI Progress must included in the main thread.I think you forgot to include the UI Changes inside main thread in Success Block.Or you can do like that,
Alamofire.request(url, method: .get).responseJSON { (response) in
if response.result.isSuccess{
...
}else{
...
}
DispatchQueue.main.async(execute: {
self.refreshControl.endRefreshing()
self.activityIndicator.stopAnimating()
self.jobTableView.reloadData()
})
}
Take A Look :
RefreshControlDemo [Swift 3 Xcode 8]
i find same problem
if another use self.refresh.endRefreshing() not work
I introduce this code -> UIRefreshControl().endRefreshing() replace in state self.refresh.endRefreshing()
DispatchQueue.main.async {
UIRefreshControl().endRefreshing()
self.yourTableView.contentOffset = CGPoint.zero
}
thank you

Cell height not being calculated properly because of Completion Handler

I have a cell which has an image in it that is retrieved from Amazon S3. The height of this image will affect the height of the cell but unfortunately the image is retrieved in a completion handler after the cell height is determined. If I scroll down and up the cell reloads properly.
Before scrolling (incorrect cell height):
After scrolling (correct cell height):
Cell configuration (some unnecessary stuff redacted for readability):
func setCompletionCell(completion: CompletionPublic, contentType: String, classType: String){
self.completionPub = completion
self.selectionStyle = UITableViewCellSelectionStyle.None //Disables clicking
//Setting image or video
if (contentType == "image"){
nwa.fetchSignedUrl("image/accepted/" + completion.mediaId + ".png") { (result, err) in
self.nwa.fetchImage(result) { (image, err) in
if image != nil{
let screenSize: CGRect = UIScreen.mainScreen().bounds
var multiplyNum = screenSize.width / image.size.width
//if image height is going to be more than 60% of the screen, resize width and height to ensure that it isn't greater than 60% while keeping the aspect ratio correct
if ((image.size.height*multiplyNum) > (screenSize.height*0.6)){
multiplyNum = screenSize.height*0.6 / image.size.height
self.imageViewWidthConstraint.constant = (multiplyNum*image.size.width)
self.imageViewHeightConstraint.constant = screenSize.height*0.6
}
else{
self.imageViewWidthConstraint.constant = screenSize.width
self.imageViewHeightConstraint.constant = (multiplyNum*image.size.height)
}
self.imgView.image = image
}
else{
//no image returned
}
}
}
}
else if (contentType == "video"){
ytplayer.loadWithVideoId(completion.mediaId)
}
}
TableView delegate methods:
func callNWT(tableView: UITableView, completionHandler: () -> ()) {
switch trendingToggle {
case 0:
nwt.getTrendingBounties(0) { (bountyArr, err) in
//#TODO: change pos
if bountyArr == nil {
self.bountyArr = []
}
else {
self.bountyArr = bountyArr as [BountyPublic]
}
if self.bountyArr.count == 0 {
completionHandler()
}
self.reloadTableViewContent(tableView)
}
case 1:
nwt.getTrendingCompletions(0) { (compArr, err) in
if compArr == nil {
self.compArr = []
}
else {
self.compArr = compArr as [CompletionPublic]
}
if self.compArr.count == 0 {
completionHandler()
}
self.reloadTableViewContent(tableView)
}
case 2:
nwt.getTrendingPeople(0) { (peopleArr, err) in
if peopleArr == nil {
self.peopleArr = []
}
else {
self.peopleArr = peopleArr as [Person]
}
if self.peopleArr.count == 0 {
completionHandler()
}
self.reloadTableViewContent(tableView)
}
default:
break
}
}
func configureTableView(tableView: UITableView){
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 500.0
tableView.allowsSelection = false; //disables selection highlighting of cells
tableView.tableFooterView = UIView()
tableView.dataSource = self
}
func reloadTableViewContent(tableView: UITableView) {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
tableView.reloadData()
print("reloading table view content")
tableView.scrollRectToVisible(CGRectMake(0, 0, 1, 1), animated: false)
})
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if trendingToggle == 0 {
return bountyArr.count
}
else if trendingToggle == 1 {
return compArr.count
}
else {
return peopleArr.count
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if trendingToggle == 0 {
return bountyCellAtIndexPath(tableView, indexPath: indexPath)
}
else if trendingToggle == 1 {
return completedCellAtIndexPath(tableView, indexPath:indexPath)
}
else {
return personCellAtIndexPath(tableView, indexPath: indexPath)
}
}
func completedCellAtIndexPath(tableView: UITableView, indexPath:NSIndexPath) -> CompletedCell{
var cell: CompletedCell
if compArr[indexPath.row].contentType == "image" {
cell = tableView.dequeueReusableCellWithIdentifier(completedImgCellIdentifier) as! CompletedCell
let comp = compArr[indexPath.row]
cell.setCompletionCell(comp, contentType: "image", classType: "trending")
}
else { //video
cell = tableView.dequeueReusableCellWithIdentifier(completedVidCellIdentifier) as! CompletedCell
let comp = compArr[indexPath.row]
cell.setCompletionCell(comp, contentType: "video", classType: "trending")
}
return cell
}
How do I ensure the cell height is correctly calculated the first time? Is there a way I can delay the code executing until the image is retrieved? Or is that not a good idea?
Reload the tableview in completion handler.
tableView.reloadData()
In the heightForRowAtIndex method for the datasource/delegate protocols recalculate the height using the image and return the appropriate value for each individual cell.
When you call reloadData() all the datasource/delegate methods are called again, so return the correct height will allow you to resize the cell as needed.

Resources