UIRefresh control endRefreshing doesn't work - ios

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

Related

cellHeight: Thread 1: Fatal error: Index out of range when pulling up data in UITableView

I am creating an Event App wherein thousands of participants are listed inside UITableView. The process should be to checkIn(by tapping a button) 2 or more participants and pull to refresh. Pull to refresh executes successfully, but when I tried pulling/scrolling up the data, it crashes and appears Thread 1: Fatal error: Index out of range . Hope you could help because I tried to use the solutions in SO but still not working. Thank you.
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return cellHeights[indexPath.row]
}
UIRefreshControl code
var refresher: UIRefreshControl!
override func viewDidLoad() {
super.viewDidLoad()
createCellHeightsArray()
refresher = UIRefreshControl()
refresher.attributedTitle = NSAttributedString(string: "Pull to refresh")
refresher.addTarget(self, action: #selector(ParticipantsViewController.refresh), for: UIControlEvents.valueChanged)
ParticipantTableView.addSubview(refresher)
countNotif()
getData()
}
#objc func refresh() {
countNotif()
getParticipants()
refresher.endRefreshing()
ParticipantTableView.reloadData()
}
func getData() {
getParticipants()
if let posts = participants {
self.participants = posts
} else {
self.participants.removeAll()
}
self.refresh()
}
cellHeightArray
func createCellHeightsArray() {
cellHeights.removeAll()
if searchController.isActive && searchController.searchBar.text != "" {
for _ in 0...filteredParticipants.count {
cellHeights.append(kCloseCellHeight)
}
}else {
for _ in 0...participants.count {
cellHeights.append(kCloseCellHeight)
}
}
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
guard case let cell as ParticipantCell = cell else {
return
}
cell.backgroundColor = UIColor.clear
if searchController.isActive && searchController.searchBar.text != "" {
cell.participant = filteredParticipants[indexPath.row]
}else {
cell.participant = participants[indexPath.row]
}
if cellHeights[(indexPath as NSIndexPath).row] == kCloseCellHeight {
cell.unfold(false, animated: false, completion: nil)
} else {
cell.unfold(true, animated: false, completion: nil)
}
}
This should help
filteredParticipants.count-1 OR 0..<unsortedStrings.count
0...participants.count-1 OR 0..<participants.count
func createCellHeightsArray() {
cellHeights.removeAll()
if searchController.isActive && searchController.searchBar.text != "" {
for _ in 0..<filteredParticipants.count {
cellHeights.append(kCloseCellHeight)
}
}else {
for _ in 0..<participants.count {
cellHeights.append(kCloseCellHeight)
}
}
}

UITableView pagination, willDisplay loading infinitely

I'm trying to implement a chat feature in my app which will mimic iMessages' pull to load more messages. My API sends 20 messages in each call along with pageIndex and other values to keep track of pages and messages.
I'm implementing pagination using TableView willDisplay and pull to refresh features.
I'm not able to add correct logic to load more messages in willDisplay and it's going into infinite loop. Can anyone point me to right direction by looking at below code?
import UIKit
class ChatViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UITextViewDelegate {
#IBOutlet weak var messagesTable: UITableView!
#IBOutlet weak var activityIndicator: UIActivityIndicatorView!
var messages: Messages!
var messageArray = [Message]()
// Pagination
var isLoading = false
var pageSize = 10
var totalPages: Int!
var currentPage: Int!
// Pull To Refresh
let refreshControl = UIRefreshControl()
override func viewWillAppear(_ animated: Bool){
super.viewWillAppear(animated)
activityIndicator.startAnimating()
fetchMessages(page: 1, completed: {
self.totalPages = self.messages.pageCount
self.currentPage = self.messages.currentPage
// Sort message by ID so that latest message appear at the bottom.
self.messageArray = self.messages.messages!.sorted(by: {$0.id! < $1.id!})
self.messagesTable.reloadData()
// Scroll to the bottom of table
self.messagesTable.scrollToBottom(animated: false)
self.activityIndicator.stopAnimating()
})
}
// MARK: - Table view data source
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return messageArray.count
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if !self.isLoading && indexPath.row == 0 {
self.isLoading = true
fetchMessages(page: self.currentPage, completed: {
if self.currentPage == 0 {
self.messageArray.removeAll()
}
self.messageArray.append(contentsOf: self.messages!.messages!)
self.messageArray = self.messageArray.sorted(by: {$0.id! < $1.id!})
self.messagesTable.reloadData()
// Scroll to the top
self.messagesTable.scrollToRow(at: indexPath, at: UITableViewScrollPosition.top, animated: true)
self.currentPage = self.currentPage + 1
})
self.isLoading = false
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "MessageCell", for: indexPath) as? MessageCell else {
fatalError("Can't find cell")
}
return cell
}
private func fetchMessages(page: Int, completed: #escaping () -> ()){
guard let url = URL(string: "http://example.com/....") else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print("Error in fetching data..........")
print(error!.localizedDescription)
}
guard let data = data else { return }
if let str = String(data: data, encoding: .utf8) { print(str) }
do {
let resultData = try JSONDecoder().decode(messagesStruct.self, from: data)
DispatchQueue.main.async {
print("DispatchQueue.main.async")
self.messages = resultData.data!
completed()
}
} catch let jsonError {
print(jsonError)
}
}.resume()
}
//Pull to refresh
#objc func refresh(_ refreshControl: UIRefreshControl) {
fetchMessages(completed: {
self.messagesTable.reloadData()
})
refreshControl.endRefreshing()
}
}
willDisplayCell is not the safe place to check if tableView is actually scrolled to bottom rather use scrollViewDelegate
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if !self.isLoading && (messagesTable.contentOffset.y >= (messagesTable.contentSize.height - messagesTable.frame.size.height)) {
self.isLoading = true
fetchMessages(page: self.currentPage, completed: {[weak self]
guard let strongSelf = self else {
return
}
strongSelf.isLoading = false
if strongSelf.currentPage == 0 {
strongSelf.messageArray.removeAll()
}
strongSelf.messageArray.append(contentsOf: strongSelf.messages!.messages!)
strongSelf.messageArray = strongSelf.messageArray.sorted(by: {$0.id! < $1.id!})
strongSelf.messagesTable.reloadData()
strongSelf.currentPage = strongSelf.currentPage + 1
})
}
}
Hope it helps

Random grey bar appears on top of ViewController in iOS (swift)

I am making an app currently and on the main screen where I load multiple items from Parse, I see a weird grey bar. I don't know where this bar came from as it does not show up on the storyboard and I don't know how to fix it. It never was a problem until it randomly recently showed up. Here is the code for that ViewController.
import UIKit
import Parse
import ParseUI
import Kingfisher
class HomeTableViewController: PFQueryTableViewController
{
override func viewDidLoad ()
{
super.viewDidLoad()
setTitle()
self.navigationController?.isNavigationBarHidden = true
self.navigationController?.navigationBar.setHeight(0.0)
// self.tableView.scrollsToTop = true
// self.tableView.scrollToNearestSelectedRow(at: UITableViewScrollPosition.bottom, animated: false)
// self.tableView.scrollToNearestSelectedRow(at: UITableViewScrollPosition.top, animated: true)
}
func setTitle()
{
var parentView = self.parent
while parent != nil
{
if let menu = parentView as? CucuMenuController
{
// menu.setTitleForLabel("Cucus")
menu.setTitleForLabel("")
break
}
parentView = parentView?.parent
}
}
var firstTime = true
var totalArticles = 0
let appDel = UIApplication.shared.delegate as! AppDelegate
required init!(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
setupHomefeed()
}
func setupHomefeed()
{
// This runs before didFinishLoadingWithOptions
self.parseClassName = "Article"
self.pullToRefreshEnabled = true
self.paginationEnabled = true
self.objectsPerPage = 10
self.loadingViewEnabled = true
}
override func objectsDidLoad(_ error: Error?)
{
super.objectsDidLoad(error)
if firstTime
{
firstTime = false
self.loadObjects()
self.tableView.reloadData()
}
else
{
PFObject.pinAll(inBackground: self.objects)
}
}
override func numberOfSections(in tableView: UITableView) -> Int
{
return 1
}
override func viewDidAppear(_ animated: Bool)
{
navigationController?.isNavigationBarHidden = false
if PFUser.current() == nil || PFUser.current()!["name"] == nil
{
let viewController = storyboard!.instantiateViewController(withIdentifier: "LoginController")
UIApplication.shared.keyWindow?.rootViewController = viewController
}
else if UserDefaults.standard.bool(forKey: "showDemo")
{
self.performSegue(withIdentifier: "detailSegue", sender: self)
}
else
{
self.loadObjects()
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
if self.objects!.count < totalArticles && self.objects!.count > 0
{
return self.objects!.count + 1
}
return self.objects!.count
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
{
if indexPath.row == self.objects?.count
{
return 70.0
}
else
{
return 221.5
}
}
override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 0.0
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 0.0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as UITableViewCell!
if indexPath.row == self.objects!.count
{
return tableView.dequeueReusableCell(withIdentifier: "loadCell") as UITableViewCell!
}
if (indexPath.row > self.objects!.count)
{
return cell!
}
let imageView = cell?.viewWithTag(200) as! UIImageView
let titleText = cell?.viewWithTag(101) as! UILabel
let timeLabel = cell?.viewWithTag(102) as! UILabel
let newImage = cell?.viewWithTag(100) as! UIImageView
let diffImage = cell?.viewWithTag(300) as! UIImageView
let scoreLabel = cell?.viewWithTag(301) as! UILabel
let catLabel = cell?.viewWithTag(103) as! UILabel
let article = Article(parseData: self.objects![indexPath.row])
titleText.text = article.title
timeLabel.text = article.duration
catLabel.text = article.category.uppercased()
let hasRead = UserController.hasReadArticle(article.objectId)
newImage.isHidden = hasRead
if hasRead
{
let score = UserController.getScoreForArticle(article.objectId)
scoreLabel.text = NSString(format: "YOUR SCORE: %.0f", score) as String
var performance = "hard"
if score >= article.idealScore { performance = "easy" }
else if score * 2 >= article.idealScore { performance = "medium" }
diffImage.image = UIImage(named: performance + "ScoreTag")
if UserController.getPlayableForArticle(article.objectId) {
cell?.isUserInteractionEnabled = true
}
}
else
{
scoreLabel.text = NSString(format: "AVG. SCORE: %.0f", article.idealScore) as String
diffImage.image = UIImage(named: article.difficulty.lowercased() + "ScoreTag")
}
//Image
let resource = ImageResource(downloadURL: article.getImageURL(), cacheKey: article.objectId)
imageView.kf.setImage(with: resource)
UIHelper.addShadowToHomeCell((cell?.viewWithTag(1)!)!)
return cell!
}
var selectedIndex = -1
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
if indexPath.row == self.objects!.count
{
self.loadNextPage()
}
else
{
selectedIndex = indexPath.row
self.performSegue(withIdentifier: "detailSegue", sender: self)
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
if segue.identifier == "detailSegue"
{
let dest = segue.destination as? ArticlePreviewViewController
if UserDefaults.standard.bool(forKey: "showDemo")
{
dest?.article = DemoArticle().article
}
else
{
dest?.article = Article(parseData: self.objects![selectedIndex])
}
}
NotificationCenter.default.post(name: Notification.Name(rawValue: "CUCU_START"), object: nil)
}
override func queryForTable() -> PFQuery<PFObject>
{
let q = PFQuery(className: "Article").whereKey("releaseDate", lessThanOrEqualTo: Date()).whereKey("validated", equalTo: true)
if firstTime
{
q.fromLocalDatastore()
}
else
{
let mods = PFQuery(className: "Question").whereKey("article", matchesQuery: q)
mods.findObjectsInBackground
{
(objects, err) -> Void in
PFObject.pinAll(inBackground: objects)
}
}
print(firstTime)
totalArticles = q.countObjects(nil)
q.order(byDescending: "createdAt")
return q
}
}
Here are a few images of what the problem looks like:
Edit 1:
As suggested in the comments, I even tried to debug view hierarchy but I don't know which element to delete to make the grey bar go away.
It looks like it might be your navigation bar. Have you tried using this:
self.navigationController?.setNavigationBarHidden(true, animated: true)
instead of the statement you have of:
self.navigationController?.isNavigationBarHidden = true
Update #2:
Actually, I just realized you have that in the viewDidLoad. Move that to viewWillAppear and I bet it will work.
Update #3:
Just to reflect the true fix here, the offending code was in the viewDidAppear:
navigationController?.isNavigationBarHidden = false

Proper Placement of dispatchGroup to reloadData

I have a tableview function that is pulling data from a database to render cells. I want to accomplish the goal of not reloading my tableview so much. I learned that dispatch groups would be the way to go beause I don't want to return to the completion block that reloads the tableView until all the data has been pulled however when I use the dispatchGroup it never reaches the completion it just stops. The placement of my variables may be in the wrong place but i just can't really see where I should put it. I have been moving it to different places and still nothing.
import UIKit
import Firebase
class FriendsEventsView: UITableViewController{
var cellID = "cellID"
var friends = [Friend]()
var attendingEvents = [Event]()
//label that will be displayed if there are no events
var currentUserName: String?
var currentUserPic: String?
var currentEventKey: String?
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Friends Events"
view.backgroundColor = .white
// Auto resizing the height of the cell
tableView.estimatedRowHeight = 44.0
tableView.rowHeight = UITableViewAutomaticDimension
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: #imageLiteral(resourceName: "close_black").withRenderingMode(.alwaysOriginal), style: .done, target: self, action: #selector(self.goBack))
tableView.register(EventDetailsCell.self, forCellReuseIdentifier: cellID)
self.tableView.tableFooterView = UIView(frame: CGRect.zero)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
DispatchQueue.global(qos: .background).async {
print("This is run on the background queue")
self.fetchEventsFromServer { (error) in
if error != nil {
print(error)
return
} else {
DispatchQueue.main.async {
self.tableView.reloadData()
print("This is run on the main queue, after the previous code in outer block")
}
}
}
}
}
#objc func goBack(){
dismiss(animated: true)
}
override func numberOfSections(in tableView: UITableView) -> Int {
// print(friends.count)
return friends.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// print(friends[section].events.count)
return friends[section].collapsed ? 0 : friends[section].events.count
}
func tableView(_ tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellID) as! EventDetailsCell? ?? EventDetailsCell(style: .default, reuseIdentifier: cellID)
// print(indexPath.row)
cell.details = friends[indexPath.section].events[indexPath.row]
return cell
}
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: "header") as? CollapsibleTableViewHeader ?? CollapsibleTableViewHeader(reuseIdentifier: "header")
// print(section)
header.arrowLabel.text = ">"
header.setCollapsed(friends[section].collapsed)
print(friends[section].collapsed)
header.section = section
// header.delegate = self
header.friendDetails = friends[section]
return header
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 50
}
func fetchEventsFromServer(_ completion: #escaping (_ error: Error?) -> Void ){
//will grab the uid of the current user
guard let myUserId = Auth.auth().currentUser?.uid else {
return
}
let ref = Database.database().reference()
//checking database for users that the current user is following
ref.child("following").child(myUserId).observeSingleEvent(of: .value, with: { (followingSnapshot) in
//handling potentail nil or error cases
guard let following = followingSnapshot.children.allObjects as? [DataSnapshot]
else {return}
//validating if proper data was pulled
let group = DispatchGroup()
for followingId in following {
group.enter()
UserService.show(forUID: followingId.key, completion: { (user) in
PostService.showFollowingEvent(for: followingId.key, completion: { (event) in
self.attendingEvents = event
var friend = Friend(friendName: (user?.username)!, events: self.attendingEvents, imageUrl: (user?.profilePic)!)
self.friends.append(friend)
})
})
}
this loop should return to the completon block in viewWillAppear following the execution of this if statement
if self.friends.count == following.count{
group.leave()
let result = group.wait(timeout: .now() + 0.01)
//will return this when done
completion(nil)
}
}) { (err) in
completion(err)
print("Couldn't grab people that you are currently following: \(err)")
}
}
Any help is greatly appreciated
You want to place the group.leave() inside of the PostService.showFollowingEvent callback.
Now you call enter following.count-times, but you call leave only once. For the group to continue you have to leave the group as many times as you entered it:
for followingId in following {
group.enter()
UserService.show(forUID: followingId.key, completion: { (user) in
PostService.showFollowingEvent(for: followingId.key, completion: { (event) in
self.attendingEvents = event
var friend = Friend(friendName: (user?.username)!, events: self.attendingEvents, imageUrl: (user?.profilePic)!)
self.friends.append(friend)
// leave here
group.leave()
})
})
}
Moreover, I would not recommend using group.wait since you are facing a possible deadlock. If any of the callbacks that are supposed to call group.leave are happening on the same thread as group.wait was called, they will never get called and you will end up with the frozen thread. Instead, use group.notify:
group.notify(queue: DispatchQueue.main) {
if self.friends.count == following.count {
completion(nil)
}
}
This will allow the execution on the main thread, but once all the tasks are finished, it will execute the provided callback closure.

Getting a UITableView to refresh users after one begins a live stream

The landing page for an app I am working on has a place holder avatar named after the application that is present when no user is actively live streaming (using the Red5 Pro streaming framework for this).
However when someone does begin to stream, I want it to automatically refresh the tableview and display the new user's avatar. What I've written so far kind of works, but not entirely. When someone begins livestreaming the placeholder avatar does disappear, but the user that's streaming's avatar doesn't appear.
If I close the app and reopen it, then it is displayed correctly. Here's my code in Swift 3, what am I doing wrong? Should the call to refresh be moved out of ViewDidLoad? Am I using Dispatch Queue incorrectly? Thanks
import UIKit
import Firebase
class HomeController: UIViewController, UITableViewDataSource, UITableViewDelegate, cellDelegate {
#IBOutlet var tableView: UITableView!
var stream: String!
var top: [SSStream] = []
var recent: [SSStream] = [SSStream()]
var trending: [SSStream] = [SSStream()]
var ref: FIRDatabaseReference!
override func viewDidLoad() {
navigationItem.title = "Swiffshot"
navigationController?.navigationBar.isTranslucent = false
let settings = UIButton(frame: CGRect(x: 0, y: 0, width: 20, height: 20))
settings.setImage(#imageLiteral(resourceName: "Settings"), for: .normal)
settings.addTarget(self, action: #selector(settingsPressed), for: .touchUpInside)
navigationItem.leftBarButtonItem = UIBarButtonItem(customView: settings)
let friends = UIButton(frame: CGRect(x: 0, y: 0, width: 23, height: 20))
friends.setImage(#imageLiteral(resourceName: "AllFriends"), for: .normal)
friends.addTarget(self, action: #selector(friendsPressed), for: .touchUpInside)
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: friends)
let nib = UINib(nibName: "MainHeader", bundle: Bundle.main)
tableView.register(nib, forHeaderFooterViewReuseIdentifier: "MainHeader")
ref = FIRDatabase.database().reference()
if !SSContact.shared.active {
performSegue(withIdentifier: "fromMainToAuth", sender: self)
}
SSContact.shared.load() { SSContact.shared.propertyCheck(self) { } }
// SSContact.shared.subscribeToTop(pulse: { (streams) in
// self.top.removeAll()
// self.top.append(contentsOf: streams)
// self.tableView.reloadSections(IndexSet(integer: 0), with: .automatic)
// })
ref.child("streams").observe(.value, with: { (snapshot) in
print("I ran")
self.top.removeAll()
if let userData = snapshot.value as? NSDictionary {
for stream in userData {
let newStream = SSStream()
newStream.username = stream.key as! String
print("Found stream \(stream.key as! String)")
newStream.isPrivate = !((stream.value as! NSDictionary)["public"] as! Bool)
newStream.views = (stream.value as! NSDictionary)["views"] as! Int
newStream.isEnabled = true
self.top.append(newStream)
}
}
if self.top.isEmpty {
print("No Streams Found")
self.top.append(SSStream())
}
DispatchQueue.main.async {
self.tableView.reloadSections(IndexSet(integer: 0), with: .automatic)
self.tableView.reloadData()
}
})
}
func cellGotPressed(_ stream: String) {
self.stream = stream
performSegue(withIdentifier: "toPlayer", sender: self)
}
func settingsPressed() {
performSegue(withIdentifier: "toSettings", sender: self)
}
func friendsPressed() {
performSegue(withIdentifier: "fromMainToExpandable", sender: self)
}
func cameraTapped() {
performSegue(withIdentifier: "toRed", sender: self)
}
func cellTapped() {
print("Cell Tapped")
}
// MARK: Segue
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toPlayer" {
let player = segue.destination as! VideoPlayerViewController
player.isSubscribing = true
player.stream = stream
}
}
// MARK: Table View Functions
func numberOfSections(in tableView: UITableView) -> Int {
return 3
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "big") as! CategoryRow
cell.section = indexPath.section
cell.top = top
cell.delegate = self
cell.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(cameraTapped)))
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "small") as! CategoryRow
cell.section = indexPath.section
cell.recent = recent
cell.trending = trending
cell.delegate = self
return cell
}
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
if section == 0 {
return nil
} else {
let cell = self.tableView.dequeueReusableHeaderFooterView(withIdentifier: "MainHeader")
let header = cell as! MainHeader
if section == 1 {
header.fillHeader("RECENT")
} else if section == 2 {
header.fillHeader("Trending + Now")
} else {
print("Unknown Section")
}
return header
}
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.section == 0 {
return 300
} else {
return 100
}
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
if section == 0 {
return 0
} else {
return 50
}
}
func tableView(_ tableView: UITableView, estimatedHeightForHeaderInSection section: Int) -> CGFloat {
return 50
}
}
I realized what I needed to do. I needed to set the
ref.child("streams").observe
in the call to Dispatch.Queue. By setting the reference before dispatch was called, the program wasn't syncing properly. It should be like this:
DispatchQueue.main.async {
ref.child("streams").observe(.value, with: { (snapshot) in
self.tableView.reloadSections(IndexSet(integer: 0), with: .automatic)
self.tableView.reloadData()
}
})

Resources