Friday, 1 February 2019

Content Inset change when UITableView Reload Sections

I have an expandable UITableView. When user tap on a header, related cell will be shown with an animation (RowAnimation.Fade) and then UITableView scrolls to that header (expanded header). When user taps again to that header, It collapse.

What I want to achieve: I need to have an expandable UITableView with header and cells. When user tap header, cells need to be opened with RowAnimation.Fade and then scroll to that header.

Bonus: Also If I can get the arrow animate when user taps on header will be great but I think this cause another bug, cuz we run so much animation on same thread (Main thread)

My problem is that when user taps to header, tableView content inset changes and whole headers goes on minus Y position. So a weird animation occurs. However, after animation finish, everything looks correct.

func toggleSection(header: DistrictTableViewHeader, section: Int) {
self.selectedHeaderIndex = section
self.cities[section].isCollapsed = !self.cities[section].isCollapsed
let contentOffset = self.tableView.contentOffset
self.tableView.reloadSections(IndexSet(integer: section), with: UITableView.RowAnimation.fade)
self.tableView.scrollToRow(at: IndexPath(row: NSNotFound, section: section) /* you can pass NSNotFound to scroll to the top of the section even if that section has 0 rows */, at: UITableView.ScrollPosition.top, animated: true)
}

In addition: I set height of headers and cells like in below.

func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
    return 1
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
    return 60
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    return 140
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
    let header: DistrictTableViewHeader = tableView.dequeueReusableHeaderFooterView(withIdentifier: headerId) as! DistrictTableViewHeader
    //let header = DistrictTableViewHeader()

    header.customInit(title: self.cities[section].name, section: section, delegate: self,isColapsed: self.cities[section].isCollapsed,isSelectedHeader: section == selectedHeaderIndex ? true : false)
    return header
}

My custom headerView Class:

protocol ExpandableHeaderViewDelegate {
func toggleSection(header: DistrictTableViewHeader, section: Int)
}

class DistrictTableViewHeader: UITableViewHeaderFooterView {
var delegate: ExpandableHeaderViewDelegate?
var section: Int!

let nameLabel: UILabel = {
   let l = UILabel()
    l.textColor = Color.DistrictsPage.headerTextColor
    return l
}()

private let arrowImage: UIImageView = {
  let i = UIImageView()
    let image = UIImage(named: "ileri")?.withRenderingMode(UIImage.RenderingMode.alwaysTemplate)
    i.image = image
    i.contentMode = .scaleAspectFit
    return i
}()
var willAnimate: Bool = false
var isColapsed: Bool!{
    didSet{
        expandCollapseHeader()
    }
}

private func expandCollapseHeader(){
    if(willAnimate){
        if(!self.isColapsed){
            let degrees : Double = 90 //the value in degrees
            self.nameLabel.textColor = Color.Common.garantiLightGreen
            self.arrowImage.tintColor = Color.Common.garantiLightGreen
            self.arrowImage.transform = CGAffineTransform.init(rotationAngle: CGFloat(degrees * .pi/180))
            self.contentView.backgroundColor = UIColor(red:0.97, green:0.97, blue:0.97, alpha:1.0)
        }else{
            let degrees : Double = 0 //the value in degrees
            self.nameLabel.textColor = Color.DistrictsPage.headerTextColor
            self.arrowImage.tintColor = UIColor.black
            self.arrowImage.transform = CGAffineTransform.init(rotationAngle: CGFloat(degrees * .pi/180))
            self.contentView.backgroundColor = UIColor.white
        }
    }else{
        if(!isColapsed){
            let degrees : Double = 90 //the value in degrees
            self.nameLabel.textColor = Color.Common.garantiLightGreen
            self.arrowImage.tintColor = Color.Common.garantiLightGreen
            self.arrowImage.transform = CGAffineTransform.init(rotationAngle: CGFloat(degrees * .pi/180))
            self.contentView.backgroundColor = UIColor(red:0.97, green:0.97, blue:0.97, alpha:1.0)
        }else{
            let degrees : Double = 0 //the value in degrees
            self.nameLabel.textColor = Color.DistrictsPage.headerTextColor
            self.arrowImage.tintColor = UIColor.black
            self.arrowImage.transform = CGAffineTransform.init(rotationAngle: CGFloat(degrees * .pi/180))
            self.contentView.backgroundColor = UIColor.white
        }
        layoutSubviews()
    }
}

override init(reuseIdentifier: String?) {

    super.init(reuseIdentifier: reuseIdentifier)
    self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(selectHeaderAction)))
    nameLabel.translatesAutoresizingMaskIntoConstraints = false
    nameLabel.font = UIFont.systemFont(ofSize: 22)
    nameLabel.textColor = Color.DistrictsPage.headerTextColor
    contentView.addSubview(nameLabel)
    nameLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
    nameLabel.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 15).isActive = true

    arrowImage.tintColor =  UIColor(red:0.32, green:0.36, blue:0.36, alpha:1.0)
    arrowImage.translatesAutoresizingMaskIntoConstraints = false
    contentView.addSubview(arrowImage)
    arrowImage.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
    arrowImage.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -20).isActive = true
    arrowImage.widthAnchor.constraint(equalToConstant: 20).isActive = true

}

required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}
func rotate(_ toValue: CGFloat) {
    self.transform = CGAffineTransform.init(rotationAngle: toValue)
}
@objc func selectHeaderAction(gestureRecognizer: UITapGestureRecognizer) {
    let cell = gestureRecognizer.view as! DistrictTableViewHeader
    delegate?.toggleSection(header: self, section: cell.section)
}


func customInit(title: String, section: Int, delegate: ExpandableHeaderViewDelegate,isColapsed: Bool, isSelectedHeader: Bool) {
    self.nameLabel.text = title
    self.nameLabel.accessibilityIdentifier = title
    self.section = section
    self.delegate = delegate
    self.willAnimate = isSelectedHeader
    self.isColapsed = isColapsed
}

override func layoutSubviews() {
    super.layoutSubviews()
    self.contentView.backgroundColor = UIColor.white

}
}

If you play below gif frame by frame, you can see that "İstanbul" and "A City" downward slip and then goes up.

Example Gif

Example Project: https://github.com/emreond/tableViewLayoutIssue



from Content Inset change when UITableView Reload Sections

No comments:

Post a Comment