0
votes

I have a custom UIView and a table view that are all created programmatically. My custom UIView involves a UIBezierPath to create it's shape.

I add this UIView to the table view tableHeaderView and it has a dynamic height. When I rotate my app from portrait to landscape the curve doesn't update. Any help would be much appreciated.

Here is all the code for the curved header view:

class ExplanationCurvedHeaderView: UIView {

public lazy var headerView: UIView = {
    let view = UIView()
    view.translatesAutoresizingMaskIntoConstraints = false
    view.autoresizesSubviews = true
    view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    //view.backgroundColor = .gray
    return view
}()

private lazy var headerLabel: UILabel = {
    let headerLabel = UILabel()
    headerLabel.translatesAutoresizingMaskIntoConstraints = false
    headerLabel.numberOfLines = 0
    headerLabel.lineBreakMode = .byWordWrapping
    headerLabel.textAlignment = .center
    headerLabel.font = .systemFont(ofSize: 18, weight: .semibold)
    headerLabel.textColor = .black
    headerLabel.text = "Hello this is a test Hello this is a testHello this is a testHello this is a test Hello this is a test Hello this is a test Hello this is a test Hello this is a test Hello this is a test"
    return headerLabel
}()

public lazy var imageIcon: UIImageView = {
    let imageView = UIImageView()
    imageView.translatesAutoresizingMaskIntoConstraints = false
    imageView.contentMode = .scaleAspectFit
    imageView.image = UIImage(systemName: "exclamationmark.triangle.fill")
    return imageView
}()


override init(frame: CGRect) {
    super.init(frame: frame)
    configure()
}

required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

override func layoutSubviews() {
    super.layoutSubviews()
    createCurve()
}

func configure() {
    addSubview(headerView)
    headerView.addSubview(headerLabel)
    headerView.addSubview(imageIcon)
        
    NSLayoutConstraint.activate([
        headerView.topAnchor.constraint(equalTo: topAnchor, constant: -44),
        headerView.leadingAnchor.constraint(equalTo: leadingAnchor),
        headerView.trailingAnchor.constraint(equalTo: trailingAnchor),
        headerView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -80),
        
        headerLabel.topAnchor.constraint(equalTo: headerView.topAnchor, constant: 75),
        headerLabel.leadingAnchor.constraint(equalTo: headerView.leadingAnchor, constant: 20),
        headerLabel.trailingAnchor.constraint(equalTo: headerView.trailingAnchor, constant: -20),
        headerLabel.bottomAnchor.constraint(equalTo: headerView.bottomAnchor, constant: -20),
        
        imageIcon.centerXAnchor.constraint(equalTo: centerXAnchor),
        imageIcon.topAnchor.constraint(equalTo: headerView.bottomAnchor, constant: 10),
        imageIcon.heightAnchor.constraint(equalToConstant: 64),
        imageIcon.widthAnchor.constraint(equalToConstant: 64)

    ])
}

func createCurve() {        
    let shapeLayer = CAShapeLayer(layer: headerView.layer)
    shapeLayer.path = pathForCurvedView().cgPath
    //shapeLayer.frame = self.bounds
    shapeLayer.frame = headerView.bounds
    shapeLayer.fillColor = UIColor.lightGray.cgColor
    headerView.layer.mask = shapeLayer.mask
    headerView.layer.insertSublayer(shapeLayer, at: 0)
}

func pathForCurvedView() -> UIBezierPath {
    let path = UIBezierPath()
    path.move(to: CGPoint(x: 0, y: 0))
    path.addLine(to: CGPoint(x: UIScreen.main.bounds.maxX, y: 0))
    path.addLine(to: CGPoint(x: UIScreen.main.bounds.maxX, y: headerView.frame.height + 30))
    path.addQuadCurve(to: CGPoint(x: UIScreen.main.bounds.minX, y:  headerView.frame.height + 30),
                      controlPoint: CGPoint(x: UIScreen.main.bounds.midX, y: headerView.frame.height + 70))
    
    
    print("header view height:", headerView.frame.height)
    print("header label systemLayou:", headerLabel.systemLayoutSizeFitting(UIView.layoutFittingExpandedSize))

    path.close()
    return path
}

}

Here is view controller code:

import UIKit

class ViewController: UIViewController {
    
    private lazy var headerView: ExplanationCurvedHeaderView = {
        let curvedView = ExplanationCurvedHeaderView(frame: .zero)
        return curvedView
    }()
    
    var arrayValues: [String] = []
        
    private lazy var tableView: UITableView = {
        let tableView = UITableView()
        tableView.translatesAutoresizingMaskIntoConstraints = false
        tableView.delegate = self
        tableView.dataSource = self
        tableView.frame = view.safeAreaLayoutGuide.layoutFrame
        return tableView
    }()
    
    /*
    
    Tried few things in this override but no correct results
    
    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        super.viewWillTransition(to: size, with: coordinator)
        coordinator.animate(alongsideTransition: { (UIViewControllerTransitionCoordinatorContext) -> Void in
            self.headerView.headerView.setNeedsLayout()
            self.headerView.createCurve()

            self.tableView.setNeedsLayout()
            self.tableView.setNeedsDisplay()
        })
    } */
    
    override func viewDidLoad() {
        for x in 1...6 {
            if x.isMultiple(of: 2) {
                arrayValues.append("Get To Invesin InvestinGet To Kn Get e o Kn Get eo Kn Get eo")
            } else {
                arrayValues.append("Get To Invesin InvestinGet To Kn Get e o Kn Get eo Kn Get eo Kn Get eo Kn Get eo Kn Get eo Kn Get eo Kn Get eo Kn Get e Get To Invesin InvestinGetGet To Invesin InvestinGetGet To Invesin InvestinGetGet To Invesin InvestinGetGet To Invesin InvestinGet  nvestinGetGenvestinGetGenvestinGetGenvestinGetGenvestinGetGe")
            }
        }
    
        tableView.register(CustomTableViewCell.self, forCellReuseIdentifier: "cell")
        //tableView.tableHeaderView = headerView
        view.addSubview(tableView)
        NSLayoutConstraint.activate([
            tableView.topAnchor.constraint(equalTo: view.topAnchor),
            tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
        ])
        tableView.tableHeaderView = headerView

    }

    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        if let headerView = self.tableView.tableHeaderView {
            let headerViewFrame = headerView.frame
            
            let height = headerView.systemLayoutSizeFitting(headerViewFrame.size, withHorizontalFittingPriority: UILayoutPriority.defaultHigh, verticalFittingPriority: UILayoutPriority.defaultLow).height
            var headerFrame = headerView.frame
            if height != headerFrame.size.height {
                headerFrame.size.height = height
                headerView.frame = headerFrame
                self.tableView.tableHeaderView = headerView
            }
        }
        headerView.layoutIfNeeded()
        tableView.beginUpdates()
        tableView.endUpdates()
    }
}



extension ViewController: UITableViewDataSource, UITableViewDelegate {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return arrayValues.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? CustomTableViewCell else {
            return UITableViewCell()
        }
        cell.setValue(value: arrayValues[indexPath.row])
        return cell
    }
    
    func tableView(_ tableView: UITableView, estimatedHeightForHeaderInSection section: Int) -> CGFloat {
        return CGFloat.leastNormalMagnitude
    }
}

enter image description here

enter image description here

This also comes up in the console:

2021-04-19 22:35:19.292199-0400 HeaderViewCurve[62191:1769385] [LayoutConstraints] Unable to simultaneously satisfy constraints. Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. ( "<NSLayoutConstraint:0x6000022fac60 V:|-(75)-[UILabel:0x7fdce741ce10] (active, names: '|':UIView:0x7fdce741cca0 )>", "<NSLayoutConstraint:0x6000022fad50 UILabel:0x7fdce741ce10.bottom == UIView:0x7fdce741cca0.bottom - 20 (active)>", "<NSLayoutConstraint:0x6000022fab20 V:|-(-44)-[UIView:0x7fdce741cca0] (active, names: '|':HeaderViewCurve.ExplanationCurvedHeaderView:0x7fdce740cf60 )>", "<NSLayoutConstraint:0x6000022fac10 UIView:0x7fdce741cca0.bottom == HeaderViewCurve.ExplanationCurvedHeaderView:0x7fdce740cf60.bottom - 80 (active)>", "<NSLayoutConstraint:0x6000022f0460 'UIView-Encapsulated-Layout-Height' HeaderViewCurve.ExplanationCurvedHeaderView:0x7fdce740cf60.height == 0 (active)>" )

Will attempt to recover by breaking constraint <NSLayoutConstraint:0x6000022fad50 UILabel:0x7fdce741ce10.bottom == UIView:0x7fdce741cca0.bottom - 20 (active)>

Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger. The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKitCore/UIView.h> may also be helpful. 2021-04-19 22:35:19.293129-0400 HeaderViewCurve[62191:1769385] [LayoutConstraints] Unable to simultaneously satisfy constraints. Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. ( "<NSLayoutConstraint:0x6000022fab20 V:|-(-44)-[UIView:0x7fdce741cca0] (active, names: '|':HeaderViewCurve.ExplanationCurvedHeaderView:0x7fdce740cf60 )>", "<NSLayoutConstraint:0x6000022fac10 UIView:0x7fdce741cca0.bottom == HeaderViewCurve.ExplanationCurvedHeaderView:0x7fdce740cf60.bottom - 80 (active)>", "<NSLayoutConstraint:0x6000022f0460 'UIView-Encapsulated-Layout-Height' HeaderViewCurve.ExplanationCurvedHeaderView:0x7fdce740cf60.height == 0 (active)>" )

Will attempt to recover by breaking constraint <NSLayoutConstraint:0x6000022fac10 UIView:0x7fdce741cca0.bottom == HeaderViewCurve.ExplanationCurvedHeaderView:0x7fdce740cf60.bottom - 80 (active)>

Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger. The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKitCore/UIView.h> may also be helpful.

1
It looks to me from your screen shots like the update is happening just fine, but the older drawing from portrait orientation is not being removed.matt
I agree with matt, with calling createCurve() you add another curve layer but at some point you should remove the old one since your header view is lazy initialised it will persist with the old shape. Also recreating the whole header on rotation might help. At some point you need to dump the old UI element.Daniel Marx
just for a quick test try adding headerView.layer.sublayers?.first?.removeFromSuperlayer() as first statement in your createCurve() method.Daniel Marx
@DanielMarx good suggestion with the headerView.layer.sublayers?.first?.removeFromSuperlayer() will try out later today and let you know if it works but this does help me figure out new things to try out!Simon McNeil
@DanielMarx thanks for your suggestion. I was able to solve it.Simon McNeil

1 Answers

0
votes

Per DanielMarx suggestion I was able to solve my question by adding this in my view controller

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransition(to: size, with: coordinator)
    coordinator.animate(alongsideTransition: { (UIViewControllerTransitionCoordinatorContext) -> Void in
        self.headerView.layer.sublayers?.first?.removeFromSuperlayer()
        self.headerView = ExplanationCurvedHeaderView()
        self.tableView.tableHeaderView = self.headerView
    })
}