1

So I've been at this for a few days and no luck. I have a a custom UIView that's made from a xib file and I am adding it to my footerView. The view is displayed but it is not responding to the gesture recognizer that I added to a label in the ViewController class. In the VC I create an instance of the UIView and added it to my tableView.

What's odd to is that when using the accessibility inspector it does read the labels

Here is the containerView

class AdviceCardWidgetContainerCurvedView: UIView {
    
    struct Constants {
        static let nibName = "ClariContainerView"
        static let containerHeight: CGFloat =  169.0
        static let curvedPercent: CGFloat = 0.2
    }
    
    let nibName = "ClariContainerView"
    var clariView: UIView?
    var clariTitle: String?
    var clariQuestion: String?

    @IBOutlet weak var clariTitleLabel: UILabel!
    @IBOutlet weak var clariQuestionLabel: UILabel!
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
    }


    private func applyCurve(givenView view: UIView, curvedPercent: CGFloat) {
        let shapeLayer = CAShapeLayer(layer: view.layer)
        shapeLayer.path = self.pathForCurvedView(givenView: view, curvedPercent: curvedPercent).cgPath
        shapeLayer.frame = view.bounds
        view.layer.mask = shapeLayer
        addSubview(view)
    }
    
    private func pathForCurvedView(givenView view: UIView, curvedPercent: CGFloat) -> UIBezierPath {
        let path = UIBezierPath()
        path.move(to: CGPoint(x: 0.0, y: 0.0))

        path.addLine(to: CGPoint(x: 0.0, y: view.bounds.size.height))
        path.addLine(to: CGPoint(x: UIScreen.main.bounds.width, y: view.bounds.size.height))
        path.addLine(to: CGPoint(x: UIScreen.main.bounds.width, y: 0.0))
        path.addQuadCurve(to: CGPoint(x: 0, y: 0), controlPoint: CGPoint(x: UIScreen.main.bounds.width / 2, y: view.bounds.size.height * curvedPercent))
        path.close()

        return path
    }
    
    private func commonInit() {
        guard let clariView = loadViewFromNib() else { return }
        clariView.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: containerHeight)
        clariView.backgroundColor = .lightGray
        applyCurve(givenView: clariView, curvedPercent: 0.1)
        addSubview(clariView)
        clariTitleLabel.text = "Ask TD Clari"
        clariQuestionLabel.text = "What is my balance"
    }
    
    func loadViewFromNib() -> UIView? {
        let bundle = Bundle(for: type(of: self))
        let nib = UINib(nibName: Constants.nibName, bundle: bundle)
        return nib.instantiate(withOwner: self, options: nil).first as? UIView
    }

}

And this is what's inside my view controller

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

    @IBOutlet weak var tableView: UITableView!
    let curvedView = AdviceCardWidgetContainerCurvedView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    
    override func viewDidLayoutSubviews() {
        curvedView.clariTitleLabel.accessibilityTraits = .button
        curvedView.clariTitleLabel.isUserInteractionEnabled = true
        curvedView.clariTitleLabel.backgroundColor = .red
        curvedView.clariTitleLabel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(footerLableTapped)))

        tableView.tableFooterView = UIView(frame: CGRect(x: 0, y: 0, width: tableView.frame.width, height: 169))
        tableView.tableFooterView?.addSubview(curvedView)
    }
    
    @objc func footerLableTapped() {
        print("label tapped")
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 10
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell")!
        cell.textLabel?.text = "1"
        return cell
    }
}

enter image description here

Simon McNeil
  • 293
  • 1
  • 13

1 Answers1

0

Alright, so I solved this problem after a week of trying. In the end adding a custom xib file to a footerView causes some errors and conflicts with autoLayout. Everything looks like its supposed to be, but the frames are essentially non existent for your added subviews like buttons and label and that's why they can't be tapped. Make sure you have a reference to your tableView from storyboard or how ever you created it.

override func viewDidLoad() {
    super.viewDidLoad()
    
    let remainingBottomFooterSpace = remaingBottomFooterViewSpace(for: tableView)
    
    let footerView = UIView(frame: CGRect(x: 0, y: 0, width: tableView.frame.size.width, height: 169))
    footerView.backgroundColor = .clear
    applyCurve(givenView: footerView, curvedPercent: 0.04)
    
    let remainingFooterView = UIView(frame: CGRect(x: 0, y: 169, width: tableView.frame.size.width, height: remainingBottomFooterSpace))
    remainingFooterView.backgroundColor = .lightGray

    let curve = UIView()
    curve.translatesAutoresizingMaskIntoConstraints = false
    curve.backgroundColor = .lightGray
    
    let icon = UIImageView()
    icon.translatesAutoresizingMaskIntoConstraints = false
    icon.image = UIImage(named: "mytd_clari")
    icon.contentMode = .scaleAspectFill
    curved.addSubview(icon)
    
    let button = UIButton()
    button.translatesAutoresizingMaskIntoConstraints = false
    button.isUserInteractionEnabled = true
    button.setTitle("Ask", for: .normal)
    button.titleLabel?.font = .systemFont(ofSize: 16.0, weight: .medium)
    button.setTitleColor(.blue, for: .normal)
    button.addTarget(self, action: #selector(footerLableTapped), for: .touchUpInside)
    
    let questionLabel = UILabel()
    questionLabel.translatesAutoresizingMaskIntoConstraints = false
    questionLabel.text = "What is my balance"
    questionLabel.textColor = .green
    questionLabel.font = .systemFont(ofSize: 13, weight: .regular)
    questionLabel.textAlignment = .center
    curve.addSubview(clariQuestionLabel)
    
    let buttonContainer = UIView(frame: CGRect(x: 0, y: 0, width: self.tableView.frame.size.width, height: 169))
    curve.addSubview(buttonContainer)
    buttonContainer.addSubview(button)
    
    tableView.tableFooterView = footerView
    tableView.tableFooterView?.addSubview(remainingFooterView)
    tableView.tableFooterView?.addSubview(curve)
    
    NSLayoutConstraint.activate([
        footerView.trailingAnchor.constraint(equalTo: tableView.trailingAnchor),
        footerView.leadingAnchor.constraint(equalTo: tableView.leadingAnchor)
    ])
    
    NSLayoutConstraint.activate([
        curve.widthAnchor.constraint(equalToConstant: tableView.frame.size.width),
        curve.heightAnchor.constraint(equalToConstant: 169)
    ])
    
    NSLayoutConstraint.activate([
        icon.topAnchor.constraint(equalTo: curve.topAnchor, constant: 39),
        icon.widthAnchor.constraint(equalToConstant: 40),
        icon.heightAnchor.constraint(equalToConstant: 40),
        icon.centerXAnchor.constraint(equalTo: curve.centerXAnchor),
    ])
    
    NSLayoutConstraint.activate([
        button.topAnchor.constraint(equalTo: icon.bottomAnchor),
        button.centerXAnchor.constraint(equalTo: icon.centerXAnchor)
    ])
    
    NSLayoutConstraint.activate([
        label.topAnchor.constraint(equalTo: button.bottomAnchor),
        label.centerXAnchor.constraint(equalTo: icon.centerXAnchor),
    ])
}

If you want to add the curve here are the methods:

func applyCurve(givenView view: UIView, curvedPercent: CGFloat) {
        let shapeLayer = CAShapeLayer(layer: view.layer)
        shapeLayer.path = self.pathForCurvedView(curvedPercent: curvedPercent).cgPath
        shapeLayer.frame = view.bounds
        view.layer.mask = shapeLayer
    }
    
func pathForCurvedView(curvedPercent: CGFloat) -> UIBezierPath {
        let path = UIBezierPath()
        path.move(to: CGPoint(x: 0.0, y: 0.0))

        path.addLine(to: CGPoint(x: 0.0, y: UIScreen.main.bounds.height))
        path.addLine(to: CGPoint(x: UIScreen.main.bounds.width, y: UIScreen.main.bounds.height))
        path.addLine(to: CGPoint(x: UIScreen.main.bounds.width, y: 0.0))
        path.addQuadCurve(to: CGPoint(x: 0, y: 0), controlPoint: CGPoint(x: UIScreen.main.bounds.width / 2, y: UIScreen.main.bounds.height * curvedPercent))
        path.close()

        return path
    }

and if you want to add the remaining footerView space, here is the func

func remaingBottomFooterViewSpace(for tableView: UITableView) -> CGFloat {
        //Get content height & calculate new footer height
        let cells = tableView.visibleCells
        var height: CGFloat = 0
        for i in 0..<cells.count {
            height += cells[i].frame.height
        }
        height = tableView.bounds.height - ceil(height)
        
        //If theh footer new height is negative, we make it 0 since we don't need extra footer view anymore
        height = height > 0 ? height : 0
        return height
    }
Simon McNeil
  • 293
  • 1
  • 13