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

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

Related

Animation of UI View in table view cell is not consistent

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

Why are 2 network calls using the same search string required before the UICollectionView decides there is data that needs to be refreshed?

I have a strange problem in a UIViewController that the user uses to search for a GIF.
There are essentially 2 issues:
The user has to enter the same search term twice before the UICollectionView triggers the cellForRowAt data source method after a call is made to reloadData().
After you enter the search term the first time, heightChanged() is called, but the self.GIFCollectionView.collectionViewLayout.collectionViewContentSize.height comes back as 0 even though I've confirmed that data is being received back from the server. The second time you enter the search term, the height is a non-zero value and the collection view shows the cells.
Here is an example of how I have to get the data to show up:
Launch app, go this UIViewController
Enter a search term (ie. "baseball")
Nothing shows up (even though reloadData() was called and new data is in the view model.)
Delete a character from the search term (ie. "basebal")
Type in the missing character (ie. "baseball")
The UICollectionView refreshes via a call to reloadData() and then calls cellForRowAt:.
Here is the entire View Controller:
import UIKit
protocol POGIFSelectViewControllerDelegate: AnyObject {
func collectionViewHeightDidChange(_ height: CGFloat)
func didSelectGIF(_ selectedGIFURL: POGIFURLs)
}
class POGIFSelectViewController: UIViewController {
//MARK: - Constants
private enum Constants {
static let POGIFCollectionViewCellIdentifier: String = "POGIFCollectionViewCell"
static let verticalPadding: CGFloat = 16
static let searchBarHeight: CGFloat = 40
static let searchLabelHeight: CGFloat = 24
static let activityIndicatorTopSpacing: CGFloat = 10
static let gifLoadDuration: Double = 0.2
static let gifStandardFPS: Double = 1/30
static let gifMaxDuration: Double = 5.0
}
//MARK: - Localized Strings
let localizedSearchGIFs = PALocalizedStringFromTable("RECOGNITION_IMAGE_SELECTION_GIF_BODY_TITLE", table: "Recognition-Osiris", comment: "Search GIFs") as String
//MARK: - Properties
var viewModel: POGIFSearchViewModel?
var activityIndicator = MDCActivityIndicator()
var gifLayout = PAGiphyCellLayout()
var selectedGIF: POGIFURLs?
//MARK: - IBOutlet
#IBOutlet weak var GIFCollectionView: UICollectionView!
#IBOutlet weak var searchGIFLabel: UILabel! {
didSet {
self.searchGIFLabel.text = self.localizedSearchGIFs
}
}
#IBOutlet weak var searchField: POSearchField! {
didSet {
self.searchField.delegate = self
}
}
#IBOutlet weak var activityIndicatorContainer: UIView!
//MARK: - Delegate
weak var delegate: POGIFSelectViewControllerDelegate?
//MARK: - View Lifecycle Methods
override func viewDidLoad() {
super.viewDidLoad()
self.setupActivityIndicator(activityIndicator: self.activityIndicator, activityIndicatorContainer: self.activityIndicatorContainer)
self.viewModel = POGIFSearchViewModel(data: PAData.sharedInstance())
if let viewModel = self.viewModel {
viewModel.viewDelegate = self
viewModel.viewDidBeginLoading()
}
self.gifLayout.delegate = self
self.gifLayout.isAXPGifLayout = true;
self.GIFCollectionView.collectionViewLayout = self.gifLayout
self.GIFCollectionView.backgroundColor = .orange
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// This Patch is to fix a bug where GIF contentSize was not calculated correctly on first load.
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .seconds(1)) {
self.viewModel?.viewDidBeginLoading()
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.heightChanged()
}
//MARK: - Helper Methods
func heightChanged() {
guard let delegate = self.delegate else { return }
let height = self.GIFCollectionView.collectionViewLayout.collectionViewContentSize.height + Constants.verticalPadding * 3 + Constants.searchLabelHeight + Constants.searchBarHeight + activityIndicatorContainer.frame.size.height + Constants.activityIndicatorTopSpacing
print("**** Items in Collection View -> self.viewModel?.gifModel.items.count: \(self.viewModel?.gifModel.items.count)")
print("**** self.GIFCollectionView.collectionViewLayout.collectionViewContentSize.height: \(self.GIFCollectionView.collectionViewLayout.collectionViewContentSize.height); height: \(height)")
delegate.collectionViewHeightDidChange(height)
}
func reloadCollectionView() {
self.GIFCollectionView.collectionViewLayout.invalidateLayout()
self.GIFCollectionView.reloadData()
self.GIFCollectionView.layoutIfNeeded()
self.heightChanged()
}
func imageAtIndexPath(_ indexPath: IndexPath) -> UIImage? {
guard let previewURL = self.viewModel?.gifModel.items[indexPath.row].previewGIFURL else { return nil }
var loadedImage: UIImage? = nil
let imageManager = SDWebImageManager.shared()
imageManager.loadImage(with: previewURL, options: .lowPriority, progress: nil) { (image: UIImage?, data: Data?, error: Error?, cacheType: SDImageCacheType, finished: Bool, imageURL: URL?) in
loadedImage = image
}
return loadedImage
}
func scrollViewDidScrollToBottom() {
guard let viewModel = self.viewModel else { return }
if viewModel.viewDidSearchMoreGIFs() {
self.activityIndicator.startAnimating()
} else {
self.activityIndicator.stopAnimating()
}
}
}
extension POGIFSelectViewController: POSearchFieldDelegate {
func searchFieldTextChanged(text: String?) {
guard let viewModel = self.viewModel else { return }
viewModel.viewDidSearchGIFs(withSearchTerm: text)
}
}
extension POGIFSelectViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
print("**** CELL FOR ROW AT -> self.viewModel?.gifModel.items.count: \(self.viewModel?.gifModel.items.count)")
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: Constants.POGIFCollectionViewCellIdentifier, for: indexPath) as! POGIFCollectionViewCell
guard let previewURL = self.viewModel?.gifModel.items[indexPath.row].previewGIFURL else {
return cell
}
var cellState: POGIFCollectionViewCell.CellState = .dimmedState
if self.selectedGIF == nil {
cellState = .defaultState
} else if (self.selectedGIF?.previewGIFURL?.absoluteString == previewURL.absoluteString) {
cellState = .selectedState
}
cell.setupUI(withState: cellState, URL: previewURL) { [weak self] () in
UIView.animate(withDuration: Constants.gifLoadDuration) {
guard let weakSelf = self else { return }
weakSelf.GIFCollectionView.collectionViewLayout.invalidateLayout()
}
}
if cell.GIFPreviewImageView.animationDuration > Constants.gifMaxDuration {
cell.GIFPreviewImageView.animationDuration = Constants.gifMaxDuration
}
cell.backgroundColor = .green
return cell
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
guard let viewModel = self.viewModel else { return 0 }
return viewModel.gifModel.items.count
}
}
extension POGIFSelectViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard let selectedGIF = self.viewModel?.gifModel.items[indexPath.row],
let delegate = self.delegate else {
return
}
self.selectedGIF = selectedGIF
delegate.didSelectGIF(selectedGIF)
self.reloadCollectionView()
}
}
extension POGIFSelectViewController: POGIFSearchViewModelToViewProtocol {
func didFetchGIFsWithSuccess() {
self.activityIndicator.stopAnimating()
print("**** didFetchGIFsWithSuccess() -> about to reload collection view")
self.reloadCollectionView()
}
func didFetchGIFsWithError(_ error: Error!, request: PARequest!) {
self.activityIndicator.stopAnimating()
}
}
extension POGIFSelectViewController: PAGiphyLayoutCellDelegate {
func heightForCell(givenWidth cellWidth: CGFloat, at indexPath: IndexPath!) -> CGFloat {
guard let image = self.imageAtIndexPath(indexPath) else {
return 0
}
if (image.size.height < 1 || image.size.width < 1 || self.activityIndicator.isAnimating) {
return cellWidth
}
let scaleFactor = image.size.height / image.size.width
let imageViewToHighlightedViewSpacing: CGFloat = 4 // this number comes from 2 * highlightedViewBorderWidth from POGIFCollectionViewCell
return cellWidth * scaleFactor + imageViewToHighlightedViewSpacing
}
func heightForHeaderView() -> CGFloat {
return 0
}
}
You'll see that the heightChanged() method calls a delegate method. That method is in another UIViewController:
func collectionViewHeightDidChange(_ height: CGFloat) {
self.collectionViewHeightConstraint.constant = height
}
So, I can't figure out why I need to either delete a character from the search term and re-add it in order for the data to refresh even though the very first call populated the view model with new data.
It's bizarre. Please help.

How to track a CollectionView cell by time in Swift

I've been working on a feature to detect when a user sees a post and when he doesn't. When the user does see the post I turn the cell's background into green, when it doesn't then it stays red. Now after doing that I notice that I turn on all the cells into green even tho the user only scroll-down the page, so I added a timer but I couldn't understand how to use it right so I thought myself maybe you guys have a suggestion to me cause I'm kinda stuck with it for like two days :(
Edit: Forgot to mention that a cell marks as seen if it passes the minimum length which is 2 seconds.
Here's my Code:
My VC(CollectionView):
import UIKit
class ViewController: UIViewController,UIScrollViewDelegate {
var impressionEventStalker: ImpressionStalker?
var impressionTracker: ImpressionTracker?
var indexPathsOfCellsTurnedGreen = [IndexPath]() // All the read "posts"
#IBOutlet weak var collectionView: UICollectionView!{
didSet{
collectionView.contentInset = UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 0)
impressionEventStalker = ImpressionStalker(minimumPercentageOfCell: 0.70, collectionView: collectionView, delegate: self)
}
}
func registerCollectionViewCells(){
let cellNib = UINib(nibName: CustomCollectionViewCell.nibName, bundle: nil)
collectionView.register(cellNib, forCellWithReuseIdentifier: CustomCollectionViewCell.reuseIdentifier)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
collectionView.delegate = self
collectionView.dataSource = self
registerCollectionViewCells()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
impressionEventStalker?.stalkCells()
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
impressionEventStalker?.stalkCells()
}
}
// MARK: CollectionView Delegate + DataSource Methods
extension ViewController: UICollectionViewDelegateFlowLayout, UICollectionViewDataSource{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 100
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let customCell = collectionView.dequeueReusableCell(withReuseIdentifier: CustomCollectionViewCell.reuseIdentifier, for: indexPath) as? CustomCollectionViewCell else {
fatalError()
}
customCell.textLabel.text = "\(indexPath.row)"
if indexPathsOfCellsTurnedGreen.contains(indexPath){
customCell.cellBackground.backgroundColor = .green
}else{
customCell.cellBackground.backgroundColor = .red
}
return customCell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 150, height: 225)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20) // Setting up the padding
}
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
//Start The Clock:
if let trackableCell = cell as? TrackableView {
trackableCell.tracker = ImpressionTracker(delegate: trackableCell)
trackableCell.tracker?.start()
}
}
func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
//Stop The Clock:
(cell as? TrackableView)?.tracker?.stop()
}
}
// MARK: - Delegate Method:
extension ViewController:ImpressionStalkerDelegate{
func sendEventForCell(atIndexPath indexPath: IndexPath) {
guard let customCell = collectionView.cellForItem(at: indexPath) as? CustomCollectionViewCell else {
return
}
customCell.cellBackground.backgroundColor = .green
indexPathsOfCellsTurnedGreen.append(indexPath) // We append all the visable Cells into an array
}
}
my ImpressionStalker:
import Foundation
import UIKit
protocol ImpressionStalkerDelegate:NSObjectProtocol {
func sendEventForCell(atIndexPath indexPath:IndexPath)
}
protocol ImpressionItem {
func getUniqueId()->String
}
class ImpressionStalker: NSObject {
//MARK: Variables & Constants
let minimumPercentageOfCell: CGFloat
weak var collectionView: UICollectionView?
static var alreadySentIdentifiers = [String]()
weak var delegate: ImpressionStalkerDelegate?
//MARK: Initializer
init(minimumPercentageOfCell: CGFloat, collectionView: UICollectionView, delegate:ImpressionStalkerDelegate ) {
self.minimumPercentageOfCell = minimumPercentageOfCell
self.collectionView = collectionView
self.delegate = delegate
}
// Checks which cell is visible:
func stalkCells() {
for cell in collectionView!.visibleCells {
if let visibleCell = cell as? UICollectionViewCell & ImpressionItem {
let visiblePercentOfCell = percentOfVisiblePart(ofCell: visibleCell, inCollectionView: collectionView!)
if visiblePercentOfCell >= minimumPercentageOfCell,!ImpressionStalker.alreadySentIdentifiers.contains(visibleCell.getUniqueId()){ // >0.70 and not seen yet then...
guard let indexPath = collectionView!.indexPath(for: visibleCell), let delegate = delegate else {
continue
}
delegate.sendEventForCell(atIndexPath: indexPath) // send the cell's index since its visible.
ImpressionStalker.alreadySentIdentifiers.append(visibleCell.getUniqueId()) // to avoid double events to show up.
}
}
}
}
// Func Which Calculate the % Of Visible of each Cell:
private func percentOfVisiblePart(ofCell cell:UICollectionViewCell, inCollectionView collectionView:UICollectionView) -> CGFloat{
guard let indexPathForCell = collectionView.indexPath(for: cell),
let layoutAttributes = collectionView.layoutAttributesForItem(at: indexPathForCell) else {
return CGFloat.leastNonzeroMagnitude
}
let cellFrameInSuper = collectionView.convert(layoutAttributes.frame, to: collectionView.superview)
let interSectionRect = cellFrameInSuper.intersection(collectionView.frame)
let percentOfIntersection: CGFloat = interSectionRect.height/cellFrameInSuper.height
return percentOfIntersection
}
}
ImpressionTracker:
import Foundation
import UIKit
protocol ViewTracker {
init(delegate: TrackableView)
func start()
func pause()
func stop()
}
final class ImpressionTracker: ViewTracker {
private weak var viewToTrack: TrackableView?
private var timer: CADisplayLink?
private var startedTimeStamp: CFTimeInterval = 0
private var endTimeStamp: CFTimeInterval = 0
init(delegate: TrackableView) {
viewToTrack = delegate
setupTimer()
}
func setupTimer() {
timer = (viewToTrack as? UIView)?.window?.screen.displayLink(withTarget: self, selector: #selector(update))
timer?.add(to: RunLoop.main, forMode: .default)
timer?.isPaused = true
}
func start() {
guard viewToTrack != nil else { return }
timer?.isPaused = false
startedTimeStamp = CACurrentMediaTime() // Current Time in seconds.
}
func pause() {
guard viewToTrack != nil else { return }
timer?.isPaused = true
endTimeStamp = CACurrentMediaTime()
print("Im paused!")
}
func stop() {
timer?.isPaused = true
timer?.invalidate()
}
#objc func update() {
guard let viewToTrack = viewToTrack else {
stop()
return
}
guard viewToTrack.precondition() else {
startedTimeStamp = 0
endTimeStamp = 0
return
}
endTimeStamp = CACurrentMediaTime()
trackIfThresholdCrossed()
}
private func trackIfThresholdCrossed() {
guard let viewToTrack = viewToTrack else { return }
let elapsedTime = endTimeStamp - startedTimeStamp
if elapsedTime >= viewToTrack.thresholdTimeInSeconds() {
viewToTrack.viewDidStayOnViewPortForARound()
startedTimeStamp = endTimeStamp
}
}
}
my customCell:
import UIKit
protocol TrackableView: NSObject {
var tracker: ViewTracker? { get set }
func thresholdTimeInSeconds() -> Double //Takes care of the screen's time, how much "second" counts.
func viewDidStayOnViewPortForARound() // Counter for how long the "Post" stays on screen.
func precondition() -> Bool // Checks if the View is full displayed so the counter can go on fire.
}
class CustomCollectionViewCell: UICollectionViewCell {
var tracker: ViewTracker?
static let nibName = "CustomCollectionViewCell"
static let reuseIdentifier = "customCell"
#IBOutlet weak var cellBackground: UIView!
#IBOutlet weak var textLabel: UILabel!
var numberOfTimesTracked : Int = 0 {
didSet {
self.textLabel.text = "\(numberOfTimesTracked)"
}
}
override func awakeFromNib() {
super.awakeFromNib()
cellBackground.backgroundColor = .red
layer.borderWidth = 0.5
layer.borderColor = UIColor.lightGray.cgColor
}
override func prepareForReuse() {
super.prepareForReuse()
print("Hello")
tracker?.stop()
tracker = nil
}
}
extension CustomCollectionViewCell: ImpressionItem{
func getUniqueId() -> String {
return self.textLabel.text!
}
}
extension CustomCollectionViewCell: TrackableView {
func thresholdTimeInSeconds() -> Double { // every 2 seconds counts as a view.
return 2
}
func viewDidStayOnViewPortForARound() {
numberOfTimesTracked += 1 // counts for how long the view stays on screen.
}
func precondition() -> Bool {
let screenRect = UIScreen.main.bounds
let viewRect = convert(bounds, to: nil)
let intersection = screenRect.intersection(viewRect)
return intersection.height == bounds.height && intersection.width == bounds.width
}
}
The approach you probably want to use...
In you posted code, you've created an array of "read posts":
var indexPathsOfCellsTurnedGreen = [IndexPath]() // All the read "posts"
Assuming your real data will have multiple properties, such as:
struct TrackPost {
var message: String = ""
var postAuthor: String = ""
var postDate: Date = Date()
// ... other stuff
}
add another property to track whether or not it has been "seen":
struct TrackPost {
var message: String = ""
var postAuthor: String = ""
var postDate: Date = Date()
// ... other stuff
var hasBeenSeen: Bool = false
}
Move all of your "tracking" code out of the controller, and instead add a Timer to your cell class.
When the cell appears:
if hasBeenSeen for that cell's Data is false
start a 2-second timer
if the timer elapses, the cell has been visible for 2 seconds, so set hasBeenSeen to true (use a closure or protocol / delegate pattern to tell the controller to update the data source) and change the backgroundColor
if the cell is scrolled off-screen before the timer elapses, stop the timer and don't tell the controller anything
if hasBeenSeen is true to begin with, don't start the 2-second timer
Now, your cellForItemAt code will look something like this:
let p: TrackPost = myData[indexPath.row]
customCell.authorLabel.text = p.postAuthor
customCell.dateLabel.text = myDateFormat(p.postDate) // formatted as a string
customCell.textLabel.text = p.message
// setting hasBeenSeen in your cell should also set the backgroundColor
// and will be used so the cell knows whether or not to start the timer
customCell.hasBeenSeen = p.hasBeenSeen
// this will be called by the cell if the timer elapsed
customCell.wasSeenCallback = { [weak self] in
guard let self = self else { return }
self.myData[indexPath.item].hasBeenSeen = true
}
What about a much simpler approach like:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
for subview in collectionView!.visibleCells {
if /* check visible percentage */ {
if !(subview as! TrackableCollectionViewCell).timerRunning {
(subview as! TrackableCollectionViewCell).startTimer()
}
} else {
if (subview as! TrackableCollectionViewCell).timerRunning {
(subview as! TrackableCollectionViewCell).stopTimer()
}
}
}
}
With a Cell-Class extended by:
class TrackableCollectionViewCell {
static let minimumVisibleTime: TimeInterval = 2.0
var timerRunning: Bool = true
private var timer: Timer = Timer()
func startTimer() {
if timerRunning {
return
}
timerRunning = true
timer = Timer.scheduledTimer(withTimeInterval: minimumVisibleTime, repeats: false) { (_) in
// mark cell as seen
}
}
func stopTimer() {
timerRunning = false
timer.invalidate()
}
}

Display multiple images on scrollview by using DKImagePickerController

I would like to display multiple images on scrollview when user selected images with DKImagePickerController github.
Here is my code but images don't appear. Anyone can tell what's wrong?
Thank you in advance!
var picker = DKImagePickerController()
var imagesArray = [Any]()
#IBAction func pickPhoto(_ sender: Any) {
picker.maxSelectableCount = 10
picker.showsCancelButton = true
picker.allowsLandscape = false
picker.assetType = .allPhotos
self.present(picker, animated: true)
picker.didSelectAssets = { (assets: [DKAsset]) in
self.imagesArray.append(assets)
for i in 0..<self.imagesArray.count {
let imageView = UIImageView()
imageView.image = self.imagesArray[i] as? UIImage
imageView.contentMode = .scaleAspectFit
let xposition = self.view.frame.width * CGFloat(i)
imageView.frame = CGRect(x: xposition, y: 330, width: self.scrollView.frame.width, height: 170)
self.scrollView.contentSize.width = self.scrollView.frame.width * CGFloat(i * 1)
self.scrollView.addSubview(imageView)
}
}
}
Instead of displaying images to scrollView, you can add that images in array and reload the collectionView/TableView..
as I did it here in collectionView
func showImagePicker() {
let pickerController = DKImagePickerController()
self.previewView?.isHidden = false
pickerController.assetType = .allPhotos
pickerController.defaultAssetGroup = .smartAlbumUserLibrary
pickerController.didSelectAssets = { (assets: [DKAsset]) in
if assets.count>0
{
for var i in 0 ... assets.count-1 {
assets[i].fetchOriginalImage(true, completeBlock: { (image, info) in
self.abc.append(image)
})
}
}
self.previewView?.reloadData()
}
self.present(pickerController, animated: true) {}
}
Here abc :[UIImage] and previewView? : UICollectionView
and in collection delegate methods:
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
return self.abc.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
var cell: UICollectionViewCell?
var imageView: UIImageView?
cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CellImage", for: indexPath)
imageView = cell?.contentView.viewWithTag(1) as? UIImageView
if let cell = cell, let imageView = imageView
{
let tag = indexPath.row + 1
cell.tag = tag
imageView.image = self.abc[indexPath.row]
}
return cell!
}

uiimageview animation stops when user touches screen

I have a UImageview with animated image.
i am adding the uiimageview in code and its a part of a CollectionViewCell
When the user touches the cell the animation stops, why does this happen?
code:
var images: [UIImage] = []
for i in 0...10 {
images.append(UIImage(named: "image\(i)"))
}
let i = UIImageView(frame: CGRect(x: xPos, y: yPos, width: 200, height: 200))
i.animationImages = images
i.animationDuration = 0.5
i.startAnimating()
i.contentMode = UIViewContentMode.Center
i.userInteractionEnabled = false
self.addSubview(i)
If you don't want any interaction then following will be the fastest way to resolve this issue:
collectionView.allowsSelection = false
In your custom collection view cell class, write following methods to fix issue
func setSelected(selected:Bool) {
}
func setHighlighted(higlighted:Bool) {
}
Swift 4.0 Version:
override open var isSelected: Bool
{
set {
}
get {
return super.isSelected
}
}
override open var isHighlighted: Bool
{
set {
}
get {
return super.isHighlighted
}
}
Overriding isSelected, isHighlighted with empty setter will solve this issue, but it will lose those two properties to be set. I was able to solve this issue by calling imageView.startAnimating() at didSelectItemAt in UICollectionViewDelegate.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let item = items[indexPath.item]
if item.hasGIF {
let cell = collectionView.cellForItem(at: indexPath) as! ItemCell
cell.imageView.startAnimating()
}
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
let item = items[indexPath.item]
if item.hasGIF {
let cell = collectionView.cellForItem(at: indexPath) as! ItemCell
cell.imageView.startAnimating()
}
}
In my case, I can't disable selection or use any of the solutions posted before here. I do not need to highlight the cell so I disabled it through the delegate method below to return false which prevented the stopAnimating() method from being called. This was an issue I encountered when using the AnimatedImageView of KingFisher used in a UICollectionViewCell.
func collectionView(_ collectionView: UICollectionView, shouldHighlightItemAt indexPath: IndexPath) -> Bool {
return false
}
An array of UIImage objects to use for an animation. var highlightedAnimationImages: [UIImage]? An array of UIImage objects to use for an animation when the view is highlighted. The amount of time it takes to go through one cycle of the images.
I created a custom UIImageView class and only stopped animation if I want it to stop.
class GifImageView: UIImageView {
private var stop = false
static func getAudioGif(_ size: CGFloat) -> GifImageView {
return fromGif(frame: CGRect(x: 0, y: 0, width: size, height: size), resourceName: "audio")!
}
override func stopAnimating() {
if stop {
stop = false
super.stopAnimating()
}
}
func stopForReal() {
stop = true
stopAnimating()
}
func fromGif(frame: CGRect, resourceName: String) -> GifImageView? {
guard let path = Bundle.main.path(forResource: resourceName, ofType: "gif") else {
print("Gif does not exist at that path")
return nil
}
let url = URL(fileURLWithPath: path)
guard let gifData = try? Data(contentsOf: url),
let source = CGImageSourceCreateWithData(gifData as CFData, nil) else { return nil }
var images = [UIImage]()
let imageCount = CGImageSourceGetCount(source)
for i in 0 ..< imageCount {
if let image = CGImageSourceCreateImageAtIndex(source, i, nil) {
images.append(UIImage(cgImage: image))
}
}
let gifImageView = GifImageView(frame: CGRect(x: 0, y: 0, width: frame.width, height: frame.height))
gifImageView.animationImages = images
return gifImageView
}
}
In TableView Use code below can solve touche cancel, touche moved and so on
- (void)tableView:(UITableView *)tableView didUnhighlightRowAtIndexPath:(NSIndexPath *)indexPath {
[cell startAnimation];
}

Resources