Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 2 years ago.
Improve this question
I'm trying to make expandable UITableView and make a simple rotate animation to UIImage in UITableViewCell from didSelectRowAt when I click the cell the image rotate 180° clockwise and when I click the cell again it will rotate -180° back to its normal state but the animation not working and it has a bug for the first time to click on cell the arrow not rotate I must click in twice to make it rotate as the gif.
TestModel:
struct Titles {
var opened = Bool()
var title = String()
var sectionData = [String]()
}
ViewControllerView:
class ViewControllerView: UIView {
var data = [Titles]()
override init(frame: CGRect) {
super.init(frame: frame)
layoutUI()
data = [
Titles(opened: false, title: "Title1", sectionData: ["Cell1", "Cell2", "Cell3"]),
Titles(opened: false, title: "Title2", sectionData: ["Cell1", "Cell2", "Cell3"]),
Titles(opened: false, title: "Title3", sectionData: ["Cell1", "Cell2", "Cell3"])
]
}
lazy var recipesTableView: UITableView = {
let recipesTableView = UITableView()
recipesTableView.register(TableViewCell.self, forCellReuseIdentifier: "TableViewCell")
recipesTableView.register(TableViewCellTwo.self, forCellReuseIdentifier: "TableViewCellTwo")
return recipesTableView
}()
}
extension ViewControllerView: UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return data.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if data[section].opened == true {
return data[section].sectionData.count + 1
} else {
return 1
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath) as! TableViewCell
cell.title.text = data[indexPath.section].title
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCellTwo", for: indexPath) as! TableViewCellTwo
cell.title.text = data[indexPath.section].sectionData[indexPath.row - 1]
return cell
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if data[indexPath.section].opened == true {
data[indexPath.section].opened = false
let section = IndexSet.init(integer: indexPath.section)
let cell = tableView.cellForRow(at: indexPath) as! TableViewCell
UIView.animate(withDuration: 1, animations: {
cell.arrowImage.transform = CGAffineTransform.init(rotationAngle: CGFloat.pi)
})
tableView.reloadSections(section, with: .none)
} else {
data[indexPath.section].opened = true
let section = IndexSet.init(integer: indexPath.section)
let cell = tableView.cellForRow(at: indexPath) as! TableViewCell
UIView.animate(withDuration: 1, animations: {
cell.arrowImage.transform = CGAffineTransform.identity
})
tableView.reloadSections(section, with: .none)
}
}
}
TableViewCell:
class TableViewCell: UITableViewCell {
lazy var arrowImage: UIImageView = {
var arrow = UIImageView(image: UIImage(systemName: "arrowtriangle.down.fill"))
arrow.tintColor = .black
arrow.translatesAutoresizingMaskIntoConstraints = false
return arrow
}()
}
First we declare an enum with available chevron directions.
enum ChevronDirection: Double {
case up = -180
case down = 0
}
Also we could add an extension to make working with radians much easier
extension Double {
var degreesToRadians: CGFloat {
return CGFloat(self) * (CGFloat(Double.pi) / 180.0)
}
}
Then you should create a UITableViewHeaderFooterView header
class AnyNameHeader: UITableViewHeaderFooterView {
#IBOutlet weak var imgChevron: UIImageView!
var tapDelegate: DelegateToGetNotified?
var currentSection: Int!
func configure(_ text: String, section: Int, isExpanded: Bool) {
currentSection = section
self.set(isExpanded: isExpanded, animated: false)
}
func set(isExpanded: Bool, animated: Bool) {
if isExpanded {
setChevronDirection(ChevronDirection.up, animated: animated)
} else {
setChevronDirection(ChevronDirection.down, animated: animated)
}
}
func setChevronDirection(_ direction: ChevronDirection, animated: Bool) {
if animated {
UIView.animate(withDuration: 0.4, delay: 0.0, options: UIViewAnimationOptions(), animations: { [weak self] in
self?.imgChevron.transform = CGAffineTransform(rotationAngle: direction.rawValue.degreesToRadians)
}, completion: nil)
} else {
self.imgChevron.transform = CGAffineTransform(rotationAngle: direction.rawValue.degreesToRadians)
}
}
//Note: You can add your way to detect user tap over header for me i add a button over the header
#IBAction func headerAction() {
tapDelegate?.didTapHeader(sectionIndex: currentSection)
}
} // End of header
Then we goes to the Final part in our View controller
class MyViewController: UIViewController {
var expandedSectionIndex : Int? = nil {
didSet{
tableView.beginUpdates()
// Collapse previously expanded section
if let oldValue = oldValue {
tableView.deleteRows(at: indexPathsFor(section: oldValue), with: UITableViewRowAnimation.top)
if let header = tableView.headerView(forSection: oldValue) as? AnyNameHeader {
header.set(isExpanded: false, animated: true)
}
}
// Don't continue to Expand new section if already collapsing opened section
if let oldValue = oldValue, let sectionIndex = expandedSectionIndex, sectionIndex == oldValue {
expandedSectionIndex = nil
tableView.endUpdates()
return
}
// Expand new section
if let expandedSectionIndex = expandedSectionIndex {
tableView.insertRows(at: indexPathsFor(section: expandedSectionIndex), with: UITableViewRowAnimation.top)
if let header = tableView.headerView(forSection: expandedSectionIndex) as? AnyNameHeader {
header.set(isExpanded: true, animated: true)
}
}
tableView.endUpdates()
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if isExpandedSection(section: section) {
return data[section].sectionData.count
} else {
return 0
}
}
}
// MARK: Expanded Section
extension MyViewController {
/// Returns Indexpaths to be Inserted or Removed for a given section Index
fileprivate func indexPathsFor(section: Int) -> [IndexPath] {
var newIndexPaths = [IndexPath]()
for index in 0 ..< data[section].sectionData.count {
let newIndexPath = IndexPath(row: index , section: section)
newIndexPaths.append(newIndexPath)
}
return newIndexPaths
}
fileprivate func isExpandedSection(section: Int) -> Bool {
return expandedSectionIndex == section
}
}
extension MyViewController: DelegateToGetNotified {
func didTapHeader(sectionIndex: Int) {
expandedSectionIndex = sectionIndex
}
}
Related
I am trying to show the default selected cell with a different button image. But could not show it when I show the languages page. I am trying to check if it is selected or not. If selected call setupSelected and if not selected call setup function.
But when I chose language I always see my tableview with no selection after all. setSelected works well and the image of the selected cells is changing.
Language
enum Language: String, CaseIterable {
case tr = "tr"
case eng = "en"
case az = "az"
var title: String {
switch self {
case .tr: return "settings.language.turkish".localized
case .eng: return "settings.language.english".localized
case .az: return "settings.language.azerbeijan".localized
}
}
var code: String {
switch self {
case .tr: return "tr"
case .eng: return "en"
case .az: return "az-AZ"
}
}
}
My viewModel
final class LanguageViewModel {
var locales: [Language] = [.tr, .eng, .az] //.ar
var currentLanguage = UserDefaultsUtil.getDeviceLanguage()
var languages: [LanguageModel] = []
func getLanguageTitles() {
languages = [
LanguageModel(language: .tr, isSelected: currentLanguage == locales[0].code ? true : false),
LanguageModel(language: .eng, isSelected: currentLanguage == locales[1].code ? true : false),
LanguageModel(language: .az, isSelected: currentLanguage == locales[2].code ? true : false)
]
}
}
My model
struct LanguageModel {
let language: Language
var isSelected: Bool
}
My ViewController
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return languageCell(indexPath: indexPath)
}
private func languageCell(indexPath: IndexPath) -> LanguageTableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: LanguageTableViewCell.identifier, for: indexPath) as! LanguageTableViewCell
if cell.isSelected {
cell.setupSelected(languageTitles[indexPath.row])
return cell
} else {
cell.setup(languageTitles[indexPath.row])
return cell
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.dequeueReusableCell(withIdentifier: LanguageTableViewCell.identifier, for: indexPath) as! LanguageTableViewCell
tableView.deselectRow(at: indexPath, animated: true)
let item = languageTitles[indexPath.row]
cell.setupSelected(item)
tableView.reloadData()
}
My cell
#IBOutlet weak var empty: UIImageView!
#IBOutlet weak var title: UILabel!
var currentLanguage = UserDefaultsUtil.getDeviceLanguage()
static let identifier = String(describing: LanguageTableViewCell.self)
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
func setup(_ languageTitles: LanguageModel) {
title.label(textStr: languageTitles.language.rawValue, textColor: KSColor.neutral700.getColor(), textFont: UIFont.sfProTextMedium(size: 15), fontSize: 15, lineSpacing: -0.16, paragraphStyle: NSMutableParagraphStyle())
}
func setupSelected(_ languageTitles: LanguageModel) {
setSelected(true, animated: true)
title.label(textStr: languageTitles.language.rawValue, textColor: KSColor.neutral700.getColor(), textFont: UIFont.sfProTextMedium(size: 15), fontSize: 15, lineSpacing: -0.16, paragraphStyle: NSMutableParagraphStyle())
self.empty.image = UIImage(named: "icon_done_bold")
self.empty.makeRounded()
self.empty.transform = CGAffineTransform(scaleX: 0.9, y: 0.9)
self.empty.setImageColor(color: KSColor.neutral100.getColor())
self.empty.backgroundColor = KSColor.ocean500Base.getColor()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
if selected {
self.empty.image = UIImage(named: "icon_done_bold")
self.empty.makeRounded()
self.empty.transform = CGAffineTransform(scaleX: 0.9, y: 0.9)
self.empty.setImageColor(color: KSColor.neutral100.getColor())
self.empty.backgroundColor = KSColor.ocean500Base.getColor()
} else {
self.empty.transform = CGAffineTransform(scaleX: 1, y: 1)
self.empty.image = UIImage(named: "icon_radio_unselected")
self.empty.setImageColor(color: KSColor.neutral100.getColor())
self.empty.backgroundColor = .white
}
}
}extension LanguageTableViewCell: Reusable, NibLoadable { }
this is how I see my tableview. In fact, tr is selected.
You need
1- First remove ? : it will give same result
LanguageModel(language: .tr, isSelected: currentLanguage == locales[0].code),
LanguageModel(language: .eng, isSelected: currentLanguage == locales[1].code),
LanguageModel(language: .az, isSelected: currentLanguage == locales[2].code)
2- Don't depend on cell's isSelected property as cells are dequeued when you scroll ,so using it will give you wrong states so instead of
if cell.isSelected {
cell.setupSelected(languageTitles[indexPath.row])
return cell
} else {
cell.setup(languageTitles[indexPath.row])
return cell
}
Do
let item = languageTitles[indexPath.row]
if item.isSelected {
cell.setupSelected(item)
return cell
} else {
cell.setup(item)
return cell
}
3- Remove override func setSelected( and move its implementation inside table didSelectRowAt
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let item = languageTitles[indexPath.row]
let value = item.isSelected
// Edit isSelected property and refresh table
languageTitles.forEach { $0.isSelected = false }
item.isSelected = !value
tableView.reloadData()
}
I have a dynamic collectionView in TableView and there are an bug when I add invalidateLayout (picture is below)
but if I delete invalidateLayout the first four cells are hidden and the layout is not displayed correctly, the video
please help I searched the whole stack but nothing helped thanks
if you need test project
MainViewController
class ViewController: UIViewController, UIScrollViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
setting()
}
func setting(){
getData()
tableView.dataSource = self
tableView.delegate = self
tableView.estimatedRowHeight = UITableView.automaticDimension
tableView.rowHeight = UITableView.automaticDimension
}
//load data
func pagination(_ completion: (()->())?){
SmartNetworkSevrice.getGoods(with: url) { [unowned self] (data) in
guard data.modals.count > 0 else {
self.tableView.tableFooterView = nil
return
}
self.goods.append(contentsOf: data.modals)
self.offSet += data.modals.count
DispatchQueue.main.async {
let indexPath = IndexPath(row: 0, section: 0)
self.tableView.tableFooterView = nil
if self.goods.count == data.modals.count || self.isRefresh {
self.tableView.reloadRows(at: [indexPath], with: .none)
} else {
if let cell = self.tableView.cellForRow(at: indexPath) as? TVCellGoods {
UIView.performWithoutAnimation {
self.tableView.beginUpdates()
cell.insertGoods(data.modals)
cell.layoutIfNeeded()
cell.collectionViewHeight.constant = cell.collectionView.collectionViewLayout.collectionViewContentSize.height
self.tableView.endUpdates()
}
}
}
completion?()
}
}
// define bottom of tableView
func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard scrollView == self.tableView else { return }
if (!isMoreDataLoading) {
// Вычислить позицию длины экрана до нижней части результатов
let scrollViewContentHeight = scrollView.contentSize.height
let scrollOffsetThreshold = scrollViewContentHeight - scrollView.bounds.size.height
if(scrollView.contentOffset.y > scrollOffsetThreshold && scrollView.isDragging) {
isMoreDataLoading = true
self.tableView.isScrollEnabled = false;
self.tableView.isScrollEnabled = true;
pagination(nil)
}
}
}
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "goods", for: indexPath) as? TVCellGoods else { return UITableViewCell() }
guard bestGoods.count != 0, goods.count != 0 else { return UITableViewCell() }
cell.delegate = self
cell.configure(bestGoods, goods, categories)
// автообновление высоты
self.tableView.beginUpdates()
cell.collectionViewHeight.constant = cell.collectionView.collectionViewLayout.collectionViewContentSize.height
self.tableView.endUpdates()
return cell
}
}
extension ViewController: UITableViewDelegate, CallDelegate {
func callMethod() {}
func callMethod(push vc:UIViewController) {
self.navigationController?.pushViewController(vc, animated: true)
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
}
TableViewCell with collectionView
class TVCellGoods: UITableViewCell {
#IBOutlet weak var collectionView:UICollectionView!
#IBOutlet weak var collectionViewHeight:NSLayoutConstraint!
weak var delegate:CallDelegate?
var bestGoods = [Goods]() // лучшие товары
var goods = [Goods]() // все товары
var categories = [Menu]()
override func layoutSubviews() {
super.layoutSubviews()
self.collectionView.collectionViewLayout.invalidateLayout()
}
override func awakeFromNib() {
super.awakeFromNib()
collectionView.delegate = self
collectionView.dataSource = self
collectionView.tag = 2
collectionView.isScrollEnabled = false
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
func configure(_ best:[Goods],_ goods:[Goods], _ category:[Menu]) {
self.bestGoods = best
self.goods = goods
self.categories = category
self.collectionView.reloadData()
}
func insertGoods(_ data:[Goods]) {
self.goods.append(contentsOf: data)
let count = self.bestGoods.count + self.categories.count + self.goods.count
let indexPaths = ((count - data.count) ..< count)
.map { IndexPath(row: $0, section: 0) }
self.collectionView.performBatchUpdates({
self.collectionView.insertItems(at: indexPaths)
}, completion: nil)
}
}
and CollectionViewCell
class CVCellGoods: UICollectionViewCell {
#IBOutlet weak var bgView: UIView!
#IBOutlet weak var imageView: UIImageView!
#IBOutlet weak var title: UILabel!
#IBOutlet weak var price: UILabel!
#IBOutlet weak var delivery: UIImageView!
#IBOutlet weak var premium: UIImageView!
override func prepareForReuse() {
super.prepareForReuse()
delivery.image = nil
premium.image = nil
title.text = nil
price.text = nil
imageView.image = nil
imageView.sd_cancelCurrentImageLoad()
}
override func awakeFromNib() {
super.awakeFromNib()
imageView.backgroundColor = UIColor(red: 215/255, green: 215/255, blue: 215/255, alpha: 1)
self.contentView.layer.cornerRadius = 5
self.contentView.layer.masksToBounds = true
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowOffset = CGSize(width: 0, height: 0.5)
self.layer.shadowRadius = 1
self.layer.shadowOpacity = 0.3
self.layer.masksToBounds = false
self.layer.shouldRasterize = true
self.layer.rasterizationScale = UIScreen.main.scale
}
}
Edit
if I use
self.collectionView.performBatchUpdates({
self.collectionView.insertItems(at: indexPaths)
}, completion: { _ in
self.collectionView.reloadItems(at: indexPaths)
})
with invalidateLayout then I get an empty space at the bottom of the screen
but if I delete invalidateLayout I get wrong Layout
if you don’t have it like mine, then discard the project with your improvements
SECOND Edit
I noticed that there is an indentation on the iPhone 11 Pro and everything is fine on the iPhone 11 Pro Max
UPDATE: I verified this works on the simulators for iPhone SE, 11, and 11 Max with no dead space at the end of the collection view.
My simulator is set to dark mode to show that the bottom of the collection view is just enough for the loading icon.
I had to make a few changes for this to properly size. First, I changed your insertGoods function in TVCellGoods class to reload only the newly added items after inserting and it no longer has missing items at the top of the list:
self.collectionView.performBatchUpdates({
self.collectionView.insertItems(at: indexPaths)
}, completion: { _ in
self.collectionView.reloadItems(at: indexPaths)
})
A big problem was setting the estimated row height for your tableView cells. I've removed the estimatedHeightForRowAt override and replaced your heightForRowAt with this:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.row == 0 {
return (tableView.frame.width / 2.5) + 20
} else if indexPath.row == 1 {
return 70
} else {
guard let cell = tableView.cellForRow(at: indexPath) as? TVCellGoods else { return UITableView.automaticDimension }
// print(cell.collectionViewHeight.constant)
return cell.collectionViewHeight.constant
}
}
I also had to add layoutIfNeeded to your cellForRow at in your ViewController UITableViewDataSource extension:
self.tableView.beginUpdates()
cell.collectionViewHeight.constant = cell.collectionView.collectionViewLayout.collectionViewContentSize.height
self.tableView.endUpdates()
cell.collectionView.layoutIfNeeded()
self.tableView.layoutIfNeeded()
Lastly, I had to update your layoutSubviews to assign the correct height after invalidating the layout in TVCellGoods:
override func layoutSubviews() {
super.layoutSubviews()
self.collectionView.collectionViewLayout.invalidateLayout()
self.collectionViewHeight.constant = self.collectionView.collectionViewLayout.collectionViewContentSize.height
}
Here is the updated source code: https://drive.google.com/open?id=1ldr4Ml2prZKmr1C4i1s5Sg7LvvxL7-jO
I have tried n number of solutions, but still no luck. I designed my own Custom cell for Header of a Section. So my table renders properly.
Then for each header cell in func viewForHeaderInSection(), I've added tap gesture to handle click on header. But every time I click on any header it disappears from the table. I haven't written any code of deleting sections.
func numberOfSections(in tableView: UITableView) -> Int {
return ModelFacade.sharedInstanceModelFacade.getGenericModel().faqsArray.count
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let cell = tableView.dequeueReusableCell(withIdentifier: "AccordianHeaderPrototypeCell") as! AccordianHeaderPrototypeCell
let cellinfo = ModelFacade.sharedInstanceModelFacade.getGenericModel().faqsArray[section]
cell.headingLabel.text = cellinfo.title
cell.arrowImage.tag = kHeaderSectionTag + section
cell.arrowImage.image = Constants.DOWNARROW_IMAGE
cell.tag = section
let headerTapGesture = UITapGestureRecognizer()
headerTapGesture.addTarget(self, action: #selector(self.sectionHeaderWasTouched(_:)))
cell.addGestureRecognizer(headerTapGesture)
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if (self.expandedSectionHeaderNumber == section) {
return ModelFacade.sharedInstanceModelFacade.getGenericModel().faqsArray[section].contentArray.count
} else {
return 0;
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ProductDetailingsPrototypeCell", for: indexPath) as! ProductDetailingsPrototypeCell
cell.separatorInset = .zero
let cellinfo = ModelFacade.sharedInstanceModelFacade.getGenericModel().faqsArray[indexPath.section].contentArray[indexPath.row]
cell.descriptionLabel.attributedText = cellinfo.html2Attributed
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
#objc func sectionHeaderWasTouched(_ sender: UITapGestureRecognizer) {
let headerView = sender.view as! AccordianHeaderPrototypeCell
let section = headerView.tag
let eImageView = headerView.viewWithTag(kHeaderSectionTag + section) as? UIImageView
if (self.expandedSectionHeaderNumber == -1) {
self.expandedSectionHeaderNumber = section
tableViewExpandSection(section, imageView: eImageView!)
} else {
if (self.expandedSectionHeaderNumber == section) {
tableViewCollapeSection(section, imageView: eImageView!)
} else {
let cImageView = self.view.viewWithTag(kHeaderSectionTag + self.expandedSectionHeaderNumber) as? UIImageView
tableViewCollapeSection(self.expandedSectionHeaderNumber, imageView: cImageView!)
tableViewExpandSection(section, imageView: eImageView!)
}
}
}
func tableViewCollapeSection(_ section: Int, imageView: UIImageView) {
let sectionData = ModelFacade.sharedInstanceModelFacade.getGenericModel().faqsArray[section].contentArray
self.expandedSectionHeaderNumber = -1
if (sectionData.count == 0) {
return
} else {
UIView.animate(withDuration: 0.4, animations: {
imageView.transform = CGAffineTransform(rotationAngle: (0.0 * CGFloat(Double.pi)) / 180.0)
})
var indexesPath = [IndexPath]()
for i in 0 ..< sectionData.count {
let index = IndexPath(row: i, section: section)
indexesPath.append(index)
}
self.itemsTableView!.beginUpdates()
self.itemsTableView!.deleteRows(at: indexesPath, with: UITableView.RowAnimation.fade)
self.itemsTableView!.endUpdates()
}
}
func tableViewExpandSection(_ section: Int, imageView: UIImageView) {
let sectionData = ModelFacade.sharedInstanceModelFacade.getGenericModel().faqsArray[section].contentArray
if (sectionData.count == 0) {
self.expandedSectionHeaderNumber = -1
return
} else {
UIView.animate(withDuration: 0.4, animations: {
imageView.transform = CGAffineTransform(rotationAngle: (180.0 * CGFloat(Double.pi)) / 180.0)
})
var indexesPath = [IndexPath]()
for i in 0 ..< sectionData.count {
let index = IndexPath(row: i, section: section)
indexesPath.append(index)
}
self.expandedSectionHeaderNumber = section
self.itemsTableView.beginUpdates()
self.itemsTableView.insertRows(at: indexesPath, with: UITableView.RowAnimation.fade)
self.itemsTableView.endUpdates()
}
}
Adding two images to explain what happens before and after click.
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()
}
})
I have developed something very similar to DateCell Demo from Apple. https://developer.apple.com/library/ios/samplecode/DateCell/Introduction/Intro.html
The only difference is that I have a UIPickerView instead of a UIDatePicker. And whenever the cell is touched a new UIPickerView is instantiated and added to the cell below once touched again it is removed from the cell. And the part I have problem with is to select a specific item in the UIPickerView.
Here is some code:
var selectedPlatform = "Mac"
var platformPickerDataSource = ["iOS", "Mac", "PC", "Android"]
func updatePlatformPicker()
{
if let indexPath = platformPickerIndexPath {
let associatedPlatformPickerCell = tableView.cellForRowAtIndexPath(indexPath)
if let targetedPlatformPicker = associatedPlatformPickerCell?.viewWithTag(Constants.kPlatformPickerTag) as! UIPickerView? {
targetedPlatformPicker.dataSource = self
targetedPlatformPicker.delegate = self
// I cannot do selectRow(selectedPlatform, inComponent: 0,
animated : true) here since the row since the rows in the UIPickerView are not set
}
}
}
So my questions is, if you refer to the code from Apple, where can I set?
selectRow(selectedPlatform, inComponent: 0,
animated : true)
Edit: (I added the whole code, which is basically the swift version of Apple's DateCell Example and DatePicker replaced with a UIPickerView)
import UIKit
class PickerTableViewController: UITableViewController, UIPickerViewDataSource, UIPickerViewDelegate {
var platformPickerIndexPath : NSIndexPath?
var pickerCellRowHeight: CGFloat = 140
var selectedPlatform = "Mac"
var platformPickerDataSource = ["iOS", "Mac", "PC", "Android"]
private struct Constants {
static let kPlatformCellID: String = "platformCell"
static let kPlatformPickerID: String = "platformPicker"
static let kOtherCell: String = "otherCell"
static let kPlatformRow : Int = 1
static let kPlatformPickerTag : Int = 99
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var rows = 3
if hasInlinePlatformPicker()
{
rows = 4
}
return rows
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell : UITableViewCell?
print("\(indexPath.row)")
var cellID = Constants.kOtherCell
if indexPathHasPicker(indexPath)
{
cellID = Constants.kPlatformPickerID
}
else if indexPathHasPlatform(indexPath)
{
cellID = Constants.kPlatformCellID
}
cell = tableView.dequeueReusableCellWithIdentifier(cellID)
var modelRow = indexPath.row
if (platformPickerIndexPath != nil && platformPickerIndexPath?.row <= indexPath.row) {
modelRow -= 1
}
if cellID == Constants.kPlatformCellID
{
cell?.textLabel?.text = "Select a platform"
cell?.detailTextLabel?.text = selectedPlatform
}
else if cellID == Constants.kOtherCell
{
cell?.textLabel?.text = "Other Cell"
}
return cell!
}
func indexPathHasPicker(indexPath: NSIndexPath) -> Bool
{
return hasInlinePlatformPicker() && platformPickerIndexPath?.row == indexPath.row
}
func indexPathHasPlatform(indexPath : NSIndexPath) -> Bool
{
var hasPlatform = false
if indexPath.row == Constants.kPlatformRow || (hasInlinePlatformPicker() && (indexPath.row == Constants.kPlatformRow + 1))
{
hasPlatform = true
}
return hasPlatform
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath)
if cell?.reuseIdentifier == Constants.kPlatformCellID
{
displayInlinePlatformPickerForRowAtIndexPath(indexPath)
}
else
{
tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
}
func displayInlinePlatformPickerForRowAtIndexPath(indexPath : NSIndexPath)
{
// display the platform picker inline with the table content
tableView.beginUpdates()
var before = false
if hasInlinePlatformPicker()
{
before = platformPickerIndexPath?.row < indexPath.row
}
let sameCellClicked = (platformPickerIndexPath?.row == indexPath.row + 1)
// remove any picker cell if it exists
if hasInlinePlatformPicker()
{
tableView.deleteRowsAtIndexPaths([NSIndexPath(forRow: platformPickerIndexPath!.row, inSection: 0)], withRowAnimation: .Fade)
platformPickerIndexPath = nil
}
if (!sameCellClicked)
{
// hide the old picker and display the new one
let rowToReveal = (before ? indexPath.row - 1 : indexPath.row)
let indexPathToReveal = NSIndexPath(forRow: rowToReveal, inSection: 0)
togglePlatformPickerForSelectedIndexPath(indexPathToReveal)
platformPickerIndexPath = NSIndexPath(forRow: indexPathToReveal.row + 1, inSection: 0)
}
tableView.deselectRowAtIndexPath(indexPath, animated: true)
tableView.endUpdates()
updatePlatformPicker()
}
func updatePlatformPicker()
{
if let indexPath = platformPickerIndexPath {
let associatedPlatformPickerCell = tableView.cellForRowAtIndexPath(indexPath)
if let targetedPlatformPicker = associatedPlatformPickerCell?.viewWithTag(Constants.kPlatformPickerTag) as! UIPickerView? {
targetedPlatformPicker.dataSource = self
targetedPlatformPicker.delegate = self
}
}
}
func togglePlatformPickerForSelectedIndexPath(indexPath: NSIndexPath)
{
tableView.beginUpdates()
let indexPaths = [NSIndexPath(forRow: indexPath.row + 1, inSection: 0)]
// check if 'indexPath' has an attached picker below it
if hasPickerForIndexPath(indexPath)
{
// found a picker below it, so remove it
tableView.deleteRowsAtIndexPaths(indexPaths, withRowAnimation: .Fade)
}
else
{
// didn't find a picker below it, so we should insert it
tableView.insertRowsAtIndexPaths(indexPaths, withRowAnimation: .Fade)
}
tableView.endUpdates()
}
func hasPickerForIndexPath(indexPath: NSIndexPath) -> Bool
{
var hasPlatformPicker = false
let targetedRow = indexPath.row + 1
let checkPlatformPickerCell = tableView.cellForRowAtIndexPath(NSIndexPath(forRow: targetedRow, inSection: 0))
let checkPlatformPicker = checkPlatformPickerCell?.viewWithTag(Constants.kPlatformPickerTag)
hasPlatformPicker = checkPlatformPicker != nil
return hasPlatformPicker
}
func hasInlinePlatformPicker() -> Bool
{
return platformPickerIndexPath != nil
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return (indexPathHasPicker(indexPath) ? pickerCellRowHeight : tableView.rowHeight)
}
// MARK: - Platform Picker View Data Source and Delegates
func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return platformPickerDataSource.count
}
func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return platformPickerDataSource[row]
}
func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
print("Platform Selected: \(platformPickerDataSource[row])")
var targetedCellIndexPath: NSIndexPath?
if hasInlinePlatformPicker() {
// inline picker: update the cell's above the picker cell
//
targetedCellIndexPath = NSIndexPath(forRow: platformPickerIndexPath!.row - 1, inSection: 0)
}
let cell = tableView.cellForRowAtIndexPath(targetedCellIndexPath!)
cell?.detailTextLabel?.text = platformPickerDataSource[row]
}
}