Using periodicObserver on AVPlayer to track playback progress - ios

Edit: I think problem is somehow connected with cell reuse ability, but still can't figure out how to fix
I'm building an application that has a tableView with a posts with an URL to a music track. When post becomes fully visible, my player starts playback of posts track. During playback I should display progress of playback on a waveform view in a post.
The problem is that observation correctly works only with 3 first cells, for the next cells waveformPlot does not get updated and seeking stops working.
I have no idea what is causing this problem. Any help is appreciated.
Here is a layout:
In my viewController's viewDidLoad I init AVPlayer observer with addPeriodicTimeObserver and assign its return type to a variable playerObserver.
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
timeLinePostsArray = User.sharedUser.requestTimelineData()
playerObserver = StreamMusicPlayer.shared.addPeriodicTimeObserver(forInterval: CMTime(value: 1, timescale: 2),
queue: DispatchQueue.main,
using: { (progress) in
if StreamMusicPlayer.shared.isPlaying {
self.cellToObserve?.updatePlot(with: progress)
}
})
}
Below is the code I use to detect fully visible cell. When fully visible cell is detected, I assign it to variable cellToObserve for observer and begin playback of cell's music track.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
for item in tableView.indexPathsForVisibleRows!{
if tableView.bounds.contains(tableView.rectForRow(at: item)){
let fullyVisibleCell = tableView.cellForRow(at: item) as! HomeControllerTableViewCell
if StreamMusicPlayer.currentItemURL != fullyVisibleCell.containedMusicTrack?.trackURL.absoluteString {
//Play music track and observe the playback progress
self.cellToObserve = fullyVisibleCell
self.numberOfCellToObserve = tableView.indexPath(for: fullyVisibleCell)?.row
fullyVisibleCell.playButton.isEnabled = true
fullyVisibleCell.plot.isUserInteractionEnabled = true
StreamMusicPlayer.playItem(musicTrack: fullyVisibleCell.containedMusicTrack!)
}
} else {
let _cell = tableView.cellForRow(at: item) as! HomeControllerTableViewCell
_cell.plot.isUserInteractionEnabled = false
_cell.playButton.isEnabled = false
}
}
}
In the viewController's deinit method I attempt to remove player's observer.
func attemptRemoveObservation() {
if self.playerObserver != nil {
StreamMusicPlayer.shared.removeTimeObserver(self.playerObserver)
self.playerObserver = nil
}
}
deinit {
self.attemptRemoveObservation()
}
This is my player's implementation:
class StreamMusicPlayer: AVPlayer {
private override init(){
super.init()
}
static var currentItemURL: String?
static var shared = AVPlayer()
static func playItem(musicTrack: MusicTrack) {
let item = AVPlayerItem(url: musicTrack.trackURL)
StreamMusicPlayer.shared.replaceCurrentItem(with: item)
StreamMusicPlayer.currentItemURL = musicTrack.trackURL.absoluteString
StreamMusicPlayer.shared.play()
}
}
extension AVPlayer {
var isPlaying: Bool {
return rate != 0 && error == nil
}
}
If needed, here is full viewController's source code:
//
// HomeViewController.swift
// CheckMyTrack
//
// Created by Alexey Savchenko on 09.03.17.
// Copyright © 2017 Alexey Savchenko. All rights reserved.
//
import UIKit
import CoreMedia
import Foundation
class HomeViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
//MARK: Vars
var playerObserver: Any!
var timeLinePostsArray: [TimeLinePost] = []
var cellToObserve: HomeControllerTableViewCell?
var numberOfCellToObserve: Int?
//MARK: Outlets
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
timeLinePostsArray = User.sharedUser.requestTimelineData()
playerObserver = StreamMusicPlayer.shared.addPeriodicTimeObserver(forInterval: CMTime(value: 1, timescale: 2),
queue: DispatchQueue.main,
using: { (progress) in
if StreamMusicPlayer.shared.isPlaying {
self.cellToObserve?.updatePlot(with: progress)
}
})
}
func attemptRemoveObservation(){
if self.playerObserver != nil{
StreamMusicPlayer.shared.removeTimeObserver(self.playerObserver)
self.playerObserver = nil
}
}
//MARK: TableView delegate methods
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return timeLinePostsArray.count
}
//TODO: Implement correct change of observing cell plot
func scrollViewDidScroll(_ scrollView: UIScrollView) {
for item in tableView.indexPathsForVisibleRows!{
if tableView.bounds.contains(tableView.rectForRow(at: item)){
let fullyVisibleCell = tableView.cellForRow(at: item) as! HomeControllerTableViewCell
if StreamMusicPlayer.currentItemURL != fullyVisibleCell.containedMusicTrack?.trackURL.absoluteString {
//Play music track and observe the playback progress
self.cellToObserve = fullyVisibleCell
self.numberOfCellToObserve = tableView.indexPath(for: fullyVisibleCell)?.row
fullyVisibleCell.playButton.isEnabled = true
fullyVisibleCell.plot.isUserInteractionEnabled = true
StreamMusicPlayer.playItem(musicTrack: fullyVisibleCell.containedMusicTrack!)
}
} else {
let _cell = tableView.cellForRow(at: item) as! HomeControllerTableViewCell
_cell.plot.isUserInteractionEnabled = false
_cell.playButton.isEnabled = false
}
}
}
func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
// if let _cell = cell as? HomeControllerTableViewCell{
// if self.playerToken != nil{
// StreamMusicPlayer.shared.removeTimeObserver(self.playerToken)
// self.playerToken = nil
// }
// }
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "HomeViewControllerCell", for: indexPath) as? HomeControllerTableViewCell
let track = timeLinePostsArray[indexPath.row].musicTrack
cell?.containedMusicTrack = track
cell?.authorNameLabel.text = timeLinePostsArray[indexPath.row].authorName
cell?.authorPicture.image = timeLinePostsArray[indexPath.row].authorPicture
cell?.trackName.text = track.trackName
cell?.plot.populateWithData(from: track.trackWaveformData)
cell?.plot.normalColor = UIColor.gray
cell?.plot.progressColor = UIColor.orange
cell?.playAction = { (cell) in
if StreamMusicPlayer.shared.isPlaying {
if StreamMusicPlayer.currentItemURL == cell.containedMusicTrack?.trackURL.absoluteString {
cell.playButton.setImage(UIImage(named: "play"), for: .normal)
StreamMusicPlayer.shared.pause()
}
} else {
StreamMusicPlayer.shared.play()
cell.playButton.setImage(UIImage(named: "pause"), for: .normal)
}
}
cell?.likeAction = { (cell) in
}
return cell!
}
deinit {
self.attemptRemoveObservation()
}
}

I have found the origin of problem.
It is necessary to implement prepareForReuse method for a custom cell class I use and perform a clean-up of a cell's waveformPlot.
WaveformPlot's waveforms property requires to be explicitly emptied.
My implementation is below:
override func prepareForReuse() {
plot.clearPlot()
plot.waveforms = []
}
func clearPlot(){
for item in self.subviews{
item.removeFromSuperview()
}
guard let layers = self.layer.sublayers else { return }
for _item in layers{
_item.removeFromSuperlayer()
}
}

Related

TableView cells becomes inactive

I am using a tableView to take some surveys.
Header I use for a question. Footer for «back» and «next» buttons. And tableView cells for answer options.
Now I started to have a problem, with some user interaction: when you simultaneously click on the “next” button and select an answer, the answer options cease to be active, nothing can be selected. Although the buttons remain active.
Tell me in what direction to look for the problem and how you can debug this problem in order to understand what's wrong.
It all started after fixing bugs, when the application crashed when simultaneously (or almost) pressing the "next" button and choosing an answer. Because the didSelectRowAt method worked after I changed the current array of answer options, and the selected index in the previous question turned out to be larger than the size of the array with the answers to the new question.
class AssessmentVC: UIViewController {
#IBOutlet weak var tableView: UITableView!
var footer: FooterTableView?
var header: UIView?
var arrayAssessmnet = [AssessmentDM]()
var assessment: AssessmentDM!
var question: QuestionDM!
var viewSeperationHeader = UIView()
var arrayOptions: [Option]?
var countAssessment = 0
var numberAssessment = 0
var numberQuestion = 0
var countQuestion = 0
var numberQusttionForLabel = 1
var arrayQuestion = [QuestionDM]()
var arrayAnswers = [AnswerDM]()
var arrayEvents = [EventDM]()
override func viewDidLoad() {
super.viewDidLoad()
settingAssessment()
}
//MARK: - settingAssessment()
private func settingAssessment() {
let id = self.assessment.serverId
arrayQuestion = QuestionDM.getQuestions(id: id)
assessmentName.text = assessment.name
countQuestion = arrayQuestion.count
let day = self.assessment.day
arrayAnswers = AnswerDM.getAnswers(idAssessment: id, day: day)
settingQuestion(eventType: .start)
}
//MARK: - settingQuestion()
private func settingQuestion(eventType: EventType? = nil) {
let prevQuestion = question
question = arrayQuestion[numberQuestion]
timeQuestion = 0
footer!.grayNextButton()
//first question
if numberQuestion == 0 && numberAssessment == 0 {
footer!.previousButton.isHidden = true
} else {
footer!.previousButton.isHidden = false
}
arrayOptions = [Option]()
let sortOption = question.options!.sorted {$0.numberOption < $1.numberOption}
for option in sortOption {
arrayOptions?.append(Option(label: option.label, value: option.value))
}
tableView.rowHeight = UITableView.automaticDimension
tableView.reloadData()
heightTableView()
tableView.setContentOffset(.zero, animated: false)
}
//MARK: - heightTableView()
func heightTableView() {
}
//MARK: - UITableViewDataSource
extension AssessmentVC: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
viewSeperationHeader.isHidden = false
footer?.viewSeperationFooter.isHidden = false
tableView.separatorStyle = .singleLine
return question.options?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(forIndexPath: indexPath as IndexPath) as AnswerAssessmentCell
cell.initCell(text: arrayOptions![indexPath.row].label, value: arrayOptions![indexPath.row].value, arrayValue: arrayAnswers[numberQuestion].response, isCheckbox: true)
return cell
}
}
//MARK: - UITableViewDelegate
extension AssessmentVC: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
isChangAnswerInAssessment = true
if question.answerType == "Radio" || question.answerType == "Checkbox"{
selectRadioOrChekbox(indexPath: indexPath)
}
}
}
//MARK: - selectRadioOrChekbox
extension AssessmentVC {
private func selectRadioOrChekbox(indexPath: IndexPath) {
if question.answerType == "Radio" {
let cells = tableView.visibleCells as! Array<AnswerAssessmentCell>
for cell in cells {
cell.select = false
cell.isSelected = false
}
let cell = tableView.cellForRow(at: indexPath) as! AnswerAssessmentCell
cell.select = true
cell.isSelected = true
if arrayOptions?.count ?? 0 > indexPath.row {
arrayAnswers[numberQuestion].response = arrayOptions![indexPath.row].value
footer?.greenNextButton()
}
}
if question.answerType == "Checkbox" {
if arrayOptions?.count ?? 0 > indexPath.row {
//если нажато что-то, что должно сбросить "None"
// question.options![0].isSelect = false
let cells = tableView.visibleCells as! Array<AnswerAssessmentCell>
if cells[0].answerLabel.text == "None" {
cells[0].select = false
cells[0].isSelected = false
}
var array = arrayAnswers[numberQuestion].response?.components(separatedBy: ";")
array?.removeAll { $0 == "0"}
if array?.count == 0 {
arrayAnswers[numberQuestion].response = nil
} else {
arrayAnswers[numberQuestion].response = array?.joined(separator: ";")
}
let cell = tableView.cellForRow(at: indexPath) as! AnswerAssessmentCell
cell.select = !cell.select
cell.isSelected = cell.select
arrayAnswers[numberQuestion].response = array.joined(separator: ";")
if array.count == 0 {
arrayAnswers[numberQuestion].response = nil
footer?.grayNextButton()
} else {
footer?.greenNextButton()
}
}
}
}
}
//MARK: - Navigation between questions
extension AssessmentVC {
func nextQuestion() {
footer!.grayNextButton()
numberQuestion += 1
numberQusttionForLabel += 1
settingQuestion(eventType: .next)
} else {
}
func previousQuestion() {
numberQusttionForLabel -= 1
settingQuestion(eventType: .previous)
}
}
Some snippets that can help you :
// Answer type : use enum . Here the Strong/Codable is if you want to
// save using JSON encoding/decoding
enum AnswerType: String, Codable {
case checkBox = "CheckBox"
case radio = "Radio"
}
Setup of your cell :
class AnswerAssessmentCell: UITableViewCell {
...
// work with Option type
func initCell(option: Option, response: String?, answerType: AnswerType) {
// setup cell contents (labels)
// check for selected status
switch answerType {
case .checkBox:
// check if option is in response
// set isSelected according
break
case .radio:
// check if option is response
// set isSelected according
break
}
}
}
In table view data source :
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! AnswerAssessmentCell
// Use the option to init the cell
// this will also set the selected state
let optionNumber = indexPath.row
cell.initCell(option: arrayOptions![optionNumber], response: arrayAnswers[numberQuestion].response, answerType: question.answerType)
return cell
}
In Table view delegate :
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
isChangAnswerInAssessment = true
let optionNumber = indexPath.row
switch question.answerType {
case .radio:
selectRadio(optionNumber: optionNumber)
case .checkBox:
selectCheckBox(optionNumber: optionNumber)
}
// Reload tableview to show changes
tableView.reloadData()
}
// Separate in 2 function for smaller functions
// in this function work only with model data, the reload data will do
// cell update
// only the footer view button cooler may need to be changed
private func selectRadio(optionNumber: Int) {
// Reset current response
// set response to optionNumber
// update footer button cooler if necessary
}
private func selectCheckBox(optionNumber: Int) {
// if option is in response
// remove option from response
// else
// add response to option
// update footer button cooler if necessary
}
Hope this can help you

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

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

crash due to `Lock.UnfairLock.lock()` when using `UIRefreshControl` whilst observing `Action.isExecuting`

I'm using a UITableView to display a paginated list and I'm using a UIRefreshControl to indicate that a network request is taking place.
However, for some reason self.refreshControl.beginRefreshing()/self.refreshControl.endRefreshing() causes a crash on os_unfair_lock_lock(_lock). If I comment out both of these lines, the code works fine.
The following code can be used to reproduce the issue:
import UIKit
import ReactiveSwift
import Result
class ViewController: UIViewController {
#IBOutlet var tableView : UITableView!
var refreshControl : UIRefreshControl!
var i : Int = 0
var action : Action<Int,[String],NoError>!
var words = [String]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
refreshControl = UIRefreshControl()
tableView.addSubview(refreshControl)
action = Action<Int,[String],NoError>{ pageNumber in
return SignalProducer{ sink,_ in
let deadlineTime = DispatchTime.now() + .seconds(2)
DispatchQueue.main.asyncAfter(deadline: deadlineTime) {
if pageNumber == 1{
sink.send(value: ["Apple","Bananas","Clementines"])
}else if pageNumber == 2{
sink.send(value: ["Dodo","Eels","French"])
}
sink.sendCompleted()
}
}
}
action.values.observeValues { [weak self](moreWords) in
if self?.words.isEmpty ?? true {
self?.words = moreWords
}else{
self?.words.append(contentsOf: moreWords)
}
self?.tableView?.reloadData()
}
action.isExecuting.signal.observe(on: UIScheduler()).observeValues { [weak self](loading) in
if loading{
self?.refreshControl.beginRefreshing()
}else{
self?.refreshControl.endRefreshing()
}
print("loading \(loading)")
}
}
}
extension ViewController : UITableViewDataSource {
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return self.words.count
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let identifier = "Whatever"
var cell = tableView.dequeueReusableCell(withIdentifier: identifier)
if cell == nil{
cell = UITableViewCell(style: .default, reuseIdentifier: identifier)
}
cell!.textLabel?.text = self.words[indexPath.row]
return cell!
}
}
extension ViewController : UITableViewDelegate{
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if (((scrollView.contentOffset.y + scrollView.frame.size.height) > scrollView.contentSize.height ) ){
if !self.action.isExecuting.value {
i += 1
self.action.apply(i).start()
}
}
}
}
Use the beginRefreshing & endRefreshing on main thread as:
QueueScheduler.main {
if loading {
self?.refreshControl.beginRefreshing()
} else {
self?.refreshControl.endRefreshing()
}
}
In my case, I got a crash in the UnfairLock too but the root cause was different. I had a typo like:
foo.bar <~ foo.bar.producer.skip(first: 1)
In fact, I assumed something like:
self.bar <~ foo.bar.producer.skip(first: 1)
Thus try to find self-bindings in your code.
This is caused by a race condition. Using QueueScheduler.main solved the issue for me:
SignalProducer
.combineLatest(...)
.observe(on: QueueScheduler.main)

check / uncheck the check box by tapping the cell in table view and how to know which cell has checked or unchecked

i am new to ios swift 2.2 . I tried to create one table view with custom cell xib. and i am populating some data in my table view. And also i added one custom check box button. And i am creating separate class for that check box button. Now when i click only on my button in my customcell.xib .But when i tap on my cell, my check box are not changing. i need to have both. When i click on my button it should change to check and uncheck image. When i tap on my cell also i need to change my button to check or uncheck image
And when i scroll down and again come back to top, my checked images are automatically chnaged to normalcheck box.
i need to do some action , so for that. When i tap on any cell my check box should check and uncheck.And alos i need to know which row cell has checked image . So that i can perform some action for my checked image row cell alone.
here is my custom check box class:
import UIKit
class CheckBoxButton: UIButton {
// Images
let checkedImage = UIImage(named: "CheckBoxChecked")! as UIImage
let uncheckedImage = UIImage(named: "CheckBoxUnChecked")! as UIImage
// Bool property
var isChecked: Bool = false {
didSet{
if isChecked == true {
self.setImage(checkedImage, forState: .Normal)
} else {
self.setImage(uncheckedImage, forState: .Normal)
}
}
}
override func awakeFromNib() {
self.addTarget(self, action: #selector(CheckBoxButton.buttonClicked(_:)), forControlEvents: UIControlEvents.TouchUpInside)
self.isChecked = false
}
func buttonClicked(sender: UIButton) {
if sender == self {
if isChecked == true {
isChecked = false
} else {
isChecked = true
}
}
}
}
Cutom cell.xib class:
import UIKit
class FavCell: UITableViewCell {
#IBOutlet weak var FLabel1: UILabel!
#IBOutlet weak var FLabel2: UILabel!
#IBOutlet weak var checkbox: CheckBoxButton!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
#IBAction func checkboxpress(sender: AnyObject) {
}
}
my viewcontroller.swift
import UIKit
class FavVC: UIViewController {
#IBOutlet weak var FavTableView: UITableView!
//var FData = [FavouritesData]()
var arrDict :NSMutableArray=[]
let cellSpacingHeight: CGFloat = 5 // cell spacing from each cell in table view
override func viewDidLoad() {
super.viewDidLoad()
self.jsonParsingFromURL()
let nib = UINib(nibName:"FavCell", bundle: nil)
FavTableView.registerNib(nib, forCellReuseIdentifier: "FCell")
}
// web services method
func jsonParsingFromURL ()
{
// let token = NSUserDefaults.standardUserDefaults().valueForKey("access_token") as? String
let url = NSURL(string: "som url")
let session = NSURLSession.sharedSession()
let request = NSURLRequest(URL: url!)
let dataTask = session.dataTaskWithRequest(request) { (data:NSData?, response:NSURLResponse?, error:NSError?) -> Void in
// print("done, error: \(error)")
if error == nil
{
dispatch_async(dispatch_get_main_queue())
{
self.arrDict=(try! NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers)) as! NSMutableArray
if (self.arrDict.count>0)
{
self.FavTableView.reloadData()
}
}
}
}
dataTask.resume()
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int
{
// return self.FData.count
return self.arrDict.count
}
// number of rows
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return 1
}
// height for each cell
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat
{
return cellSpacingHeight
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell:FavCell = self.FavTableView.dequeueReusableCellWithIdentifier("FCell") as! FavCell
cell.FLabel1.text=arrDict[indexPath.section] .valueForKey("favourite_name") as? String
cell.FLabel2.text=arrDict[indexPath.section] .valueForKey("favourite_address") as? String
return cell
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
please help me out.
Thanks in advance
In order to solve your issue, as #El Capitan mentioned, you will need to use the didSelectRowAtIndexPath method to change its states. Your codes should look something along the lines of this:
// Declare a variable which stores checked rows. UITableViewCell gets dequeued and restored as you scroll up and down, so it is best to store a reference of rows which has been checked
var rowsWhichAreChecked = [NSIndexPath]()
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell:FavCell = tableView.cellForRowAtIndexPath(indexPath) as! FavCell
// cross checking for checked rows
if(rowsWhichAreChecked.contains(indexPath) == false){
cell.checkBox.isChecked = true
rowsWhichAreChecked.append(indexPath)
}else{
cell.checkBox.isChecked = false
// remove the indexPath from rowsWhichAreCheckedArray
if let checkedItemIndex = rowsWhichAreChecked.indexOf(indexPath){
rowsWhichAreChecked.removeAtIndex(checkedItemIndex)
}
}
}
To redisplay cells which have been checked before after scrolling the rows out of view, at your cellForRowAtIndexPath, perform the same checking against rowsWhichAreChecked array and set its states accordingly.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:FavCell = self.FavTableView.dequeueReusableCellWithIdentifier("FCell") as! FavCell
cell.FLabel1.text=arrDict[indexPath.section] .valueForKey("favourite_name") as? String
cell.FLabel2.text=arrDict[indexPath.section] .valueForKey("favourite_address") as? String
if(rowsWhichAreChecked.contains(indexPath) == false){
cell.checkBox.isChecked = true
}else{
cell.checkBox.isChecked = false
}
}
return cell
}
EDITED ANSWER
I have got your code to work but I had to make some modifications to your Checkbox class and ViewController
Checkbox.swift
class CheckBoxButton: UIButton {
// Images
let checkedImage = UIImage(named: "CheckBoxChecked")! as UIImage
let uncheckedImage = UIImage(named: "CheckBoxUnChecked")! as UIImage
// Bool property
var isChecked: Bool = false {
didSet{
if isChecked == true {
self.setImage(uncheckedImage, forState: .Normal)
} else {
self.setImage(checkedImage, forState: .Normal)
}
}
}
override func awakeFromNib() {
self.userInteractionEnabled = false
// self.addTarget(self, action: #selector(CheckBoxButton.buttonClicked(_:)), forControlEvents: UIControlEvents.TouchUpInside)
// self.isChecked = false
}
func buttonClicked(sender: UIButton) {
if sender == self {
if isChecked == true {
isChecked = false
} else {
isChecked = true
}
}
}
}
ViewController.swift
class FavVC: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var FavTableView: UITableView!
var rowsWhichAreChecked = [NSIndexPath]()
//var FData = [FavouritesData]()
var arrDict :NSMutableArray=[]
let cellSpacingHeight: CGFloat = 5 // cell spacing from each cell in table view
override func viewDidLoad() {
self.FavTableView.delegate = self
self.FavTableView.dataSource = self
super.viewDidLoad()
self.jsonParsingFromURL()
let nib = UINib(nibName:"FavCell", bundle: nil)
FavTableView.registerNib(nib, forCellReuseIdentifier: "FCell")
}
// web services method
func jsonParsingFromURL ()
{
// let token = NSUserDefaults.standardUserDefaults().valueForKey("access_token") as? String
let url = NSURL(string: "some url")
let session = NSURLSession.sharedSession()
let request = NSURLRequest(URL: url!)
let dataTask = session.dataTaskWithRequest(request) { (data:NSData?, response:NSURLResponse?, error:NSError?) -> Void in
// print("done, error: \(error)")
if error == nil
{
dispatch_async(dispatch_get_main_queue())
{
self.arrDict=(try! NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers)) as! NSMutableArray
if (self.arrDict.count>0)
{
self.FavTableView.reloadData()
}
}
}
}
dataTask.resume()
//
// let StringUrl = "http"+token!
// let url:NSURL = NSURL(string: StringUrl)!
// if let JSONData = NSData(contentsOfURL: url)
// {
// if let json = (try? NSJSONSerialization.JSONObjectWithData(JSONData, options: [])) as? NSDictionary
// {
// for values in json
// {
// self.FData.append()
// }
// if let reposArray = json["data"] as? [NSDictionary]
// {
//
// for item in reposArray
// {
// let itemObj = item as? Dictionary<String,AnyObject>
//
// let b_type = itemObj!["business_type"]?.valueForKey("type")
//
// //self.Resultcount.text = "\(b_type?.count) Results"
//
// if (b_type as? String == "Taxis")
// {
//
// self.FData.append(FavouritesData(json:item))
//
// }
// }
// }
// }
// }
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int
{
// return self.FData.count
return self.arrDict.count
}
// number of rows
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return 1
}
// height for each cell
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat
{
return cellSpacingHeight
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell:FavCell = self.FavTableView.dequeueReusableCellWithIdentifier("FCell") as! FavCell
cell.FLabel1.text=arrDict[indexPath.section] .valueForKey("favourite_name") as? String
cell.FLabel2.text=arrDict[indexPath.section] .valueForKey("favourite_address") as? String
let isRowChecked = rowsWhichAreChecked.contains(indexPath)
if(isRowChecked == true)
{
cell.checkbox.isChecked = true
cell.checkbox.buttonClicked(cell.checkbox)
}else{
cell.checkbox.isChecked = false
cell.checkbox.buttonClicked(cell.checkbox)
}
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell:FavCell = tableView.cellForRowAtIndexPath(indexPath) as! FavCell
// cross checking for checked rows
if(rowsWhichAreChecked.contains(indexPath) == false){
cell.checkbox.isChecked = true
cell.checkbox.buttonClicked(cell.checkbox)
rowsWhichAreChecked.append(indexPath)
}else{
cell.checkbox.isChecked = false
cell.checkbox.buttonClicked(cell.checkbox)
// remove the indexPath from rowsWhichAreCheckedArray
if let checkedItemIndex = rowsWhichAreChecked.indexOf(indexPath){
rowsWhichAreChecked.removeAtIndex(checkedItemIndex)
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
{
let cell:FilterTableViewCell = tableView.dequeueReusableCell(withIdentifier: "filtercell", for: indexPath) as! FilterTableViewCell
// Configure the cell...
cell.lblCategory?.attributedText = FontAttributes.sharedInstance.AttributesString(message: self.filterArray[indexPath.row], color: Textcolor)
cell.BtnIndex?.addTarget(self, action: #selector(checkMarkTapped(_ :)), for: .touchUpInside)
cell.BtnIndex?.tag = indexPath.row
let rowid = indexPath.row
let found = rowsWhichAreChecked.filter{$0.rowId == rowid}.count > 0
if found
{
cell.BtnIndex?.setImage(checkedImage, for: .normal)
}
else
{
cell.BtnIndex?.setImage(uncheckedImage, for: .normal)
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell:FilterTableViewCell = tableView.cellForRow(at: indexPath) as! FilterTableViewCell
let rowid = indexPath.row
let found = rowsWhichAreChecked.filter{$0.rowId == rowid}.count > 0
if found
{
tempArrayFordelete = rowsWhichAreChecked
for obj in tempArrayFordelete
{
if let index = rowsWhichAreChecked.index(where: { $0.rowId == obj.rowId }) {
// removing item
rowsWhichAreChecked.remove(at: index)
cell.BtnIndex?.setImage(uncheckedImage, for: .normal)
}
}
}
else
{
cell.BtnIndex?.setImage(checkedImage, for: .normal)
let objrowId = selectedIndex(rowId: indexPath.row)
rowsWhichAreChecked.append(objrowId)
}
}
It gives me a great pleasure to inform you all that solve above issue
Resolving Issue Is
CheckBox Functionality
RadioButton Functionality
ReuseCell(tableView.dequeueReusableCell)//Also solve selected cell position issue.
Tested Code
Swift 5
iOS 12.2
Here is my code
import UIKit
class countrySelection:UITableViewCell{
#IBOutlet weak var imgFlag: UIImageView!
#IBOutlet weak var lblCountryName: UILabel!
#IBOutlet weak var btnSelection: UIButton!
}
class ViewController: UIViewController {
var listingDict=[[String:String]]()
var radioOption:Int?// Only used :: if u are 2. RadioButton Functionality implement
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource=self
tableView.delegate=self
fillCountryData()
// Do any additional setup after loading the view.
}
func fillCountryData(){
self.fillJsonData(imgName: "india_flag", countryName: "India")
self.fillJsonData(imgName: "pakistan_flag", countryName: "Pakistan")
self.fillJsonData(imgName: "israel_flag", countryName: "Israel")
self.fillJsonData(imgName: "albania_flag", countryName: "Albania")
self.fillJsonData(imgName: "america_flag", countryName: "America")
self.fillJsonData(imgName: "belize_flag", countryName: "Belize")
self.fillJsonData(imgName: "brunei_flag", countryName: "Brunei")
self.fillJsonData(imgName: "comoros_flag", countryName: "Comoros")
self.fillJsonData(imgName: "congo_flag", countryName: "Congo")
self.fillJsonData(imgName: "ecuador_flag", countryName: "Ecuador")
self.fillJsonData(imgName: "haiti_flag", countryName: "Haiti")
self.fillJsonData(imgName: "jamaica_flag", countryName: "Jamaica")
self.fillJsonData(imgName: "kenya_flag", countryName: "Kenya")
self.fillJsonData(imgName: "mali_flag", countryName: "Mali")
self.tableView.reloadData()
}
func fillJsonData(imgName:String,countryName:String){
var dictData=[String:String]()
dictData["img"]=imgName
dictData["country"]=countryName
dictData["check"]="false"
listingDict.append(dictData)
}
}
extension ViewController:UITableViewDataSource,UITableViewDelegate{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return listingDict.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell=tableView.dequeueReusableCell(withIdentifier: "countrySelection") as! countrySelection
let dictVal=listingDict[indexPath.row]
cell.lblCountryName.text=dictVal["country"]
cell.imgFlag.image=UIImage(named:dictVal["img"]!)
/*//Check Box Functionality
if dictVal["check"] == "false"{
cell.btnSelection.setImage(UIImage(named: "checkbox_UnSelect"), for: .normal)
} else{
cell.btnSelection.setImage(UIImage(named: "checkbox_Select"), for: .normal)
}*/
//RadioButton Functionality
if radioOption==indexPath.row{
listingDict[indexPath.row]["check"]="true"
cell.btnSelection.setImage(UIImage(named: "radioButton_Select"), for: .normal)
} else{
listingDict[indexPath.row]["check"]="false"
cell.btnSelection.setImage(UIImage(named: "radioButton_UnSelect"), for: .normal)
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
/*//CheckBox Functionality
if listingDict[indexPath.row]["check"]=="true"{
listingDict[indexPath.row]["check"]="false"
} else{
listingDict[indexPath.row]["check"]="true"
}*/
//RadioButton Functionality
print("RadioButton",listingDict)
if listingDict[indexPath.row]["check"]=="true"{
radioOption=nil
} else{
radioOption=indexPath.row
}
self.tableView.reloadData()
}
}

Resources