Search code examples
iosswiftuiviewautolayout

Can't select second item in tableview cell over uiview


anyone please help me, I have a drop down but the I can't select it the second row. I can't figure out why, I try playing with bringSubviewsToFront or back. I try using layer.zPosition it still can't select the second row, but when I choose the first row it work. is it my auto layout setup incorrectly?

here is my ui and code setup

dropdownshit

// This is my custom Button

class GDropdownSchedule: UIButton {
    let headerLbl   = GTitleLabel(name: "Schedule Type".localized(), fontSize: 13, color: #colorLiteral(red: 0.4588235294, green: 0.4941176471, blue: 0.5647058824, alpha: 1))
    let bodyLbl     = GSubtitleLabel(name: "Additional Note".localized(), fontSize: 16, color: #colorLiteral(red: 0.09803921569, green: 0.09803921569, blue: 0.09803921569, alpha: 1))
    let dropDownIV  = GIconImageView(img: #imageLiteral(resourceName: "down-chevron"))
    var isOpen              = false
    let dropView    = DropDownView()
    var delegate: AddScheduleVCDelegate?
    var height: NSLayoutConstraint!
    override init(frame: CGRect) {
        super.init(frame: frame)
        configure()
    }
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    private func configure() {
        backgroundColor     = #colorLiteral(red: 0.9764705882, green: 0.9764705882, blue: 0.9764705882, alpha: 1)
        layer.cornerRadius  = 5
        layer.borderWidth   = 1
        layer.borderColor   = #colorLiteral(red: 0.9411764706, green: 0.9411764706, blue: 0.9450980392, alpha: 1)
        dropView.completion = { text in
            self.bodyLbl.text = text
            self.bodyLbl.font = UIFont(name: "NunitoSans-Regular", size: 12)
            self.delegate?.toggleHide(selected: text)
            self.dismissDropDown()
        }
    }
    override func didMoveToSuperview() {
        addSubview(headerLbl)
        headerLbl.anchor(top: topAnchor, trailing: nil, bottom: nil, leading: leadingAnchor, topPadding: 10, rightPadding: 0, bottomPadding: 0, leftPadding: 10, width: 70, height: 18)
        addSubview(bodyLbl)
        bodyLbl.anchor(top: headerLbl.bottomAnchor, trailing: nil, bottom: bottomAnchor, leading: leadingAnchor, topPadding: 2, rightPadding: 10, bottomPadding: 10, leftPadding: 10, width: 0, height: 0)
        addSubview(dropDownIV)
        dropDownIV.tintColor = #colorLiteral(red: 0.2549019608, green: 0.3019607843, blue: 0.3568627451, alpha: 1)
        dropDownIV.anchor(top: nil, trailing: trailingAnchor, bottom: bottomAnchor, leading: nil, topPadding: 0, rightPadding: 18, bottomPadding: 17, leftPadding: 0, width: 12, height: 10)
        addSubview(dropView)
        dropView.translatesAutoresizingMaskIntoConstraints = false
        dropView.layer.zPosition = 1
        height = dropView.heightAnchor.constraint(equalToConstant: 0)
        NSLayoutConstraint.activate([
            dropView.topAnchor.constraint(equalTo: headerLbl.bottomAnchor),
            dropView.leadingAnchor.constraint(equalTo: leadingAnchor),
            dropView.trailingAnchor.constraint(equalTo: trailingAnchor)
        ])
    }
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        if isOpen == false {
            isOpen = true
            NSLayoutConstraint.deactivate([height])
            if self.dropView.tableView.contentSize.height > 150 {
                height.constant = 150
            } else {
                height.constant = dropView.tableView.contentSize.height
            }
            NSLayoutConstraint.activate([height])
            UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.5, options: .curveEaseInOut, animations: {
                self.dropView.layoutIfNeeded()
                self.dropView.center.y += self.dropView.frame.height / 2
            })
        } else {
            isOpen = false
            NSLayoutConstraint.deactivate([height])
            height.constant = 0
            NSLayoutConstraint.activate([height])
            UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.5, options: .curveEaseInOut, animations: {
                self.dropView.center.y -= self.dropView.frame.height / 2
                self.dropView.layoutIfNeeded()
            })
        }
    }
    func dismissDropDown() {
        isOpen = false
        NSLayoutConstraint.deactivate([height])
        height.constant = 0
        NSLayoutConstraint.activate([height])
        UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.5, options: .curveEaseInOut, animations: {
            self.dropView.center.y -= self.dropView.frame.height / 2
            self.dropView.layoutIfNeeded()
        })
    }
}
class DropDownView: UIView, UITableViewDelegate, UITableViewDataSource {
    let tableView           = UITableView()
    var options             = [String]()
    var completion: ((String) -> Void)?
    var isHideSchedule      = false
    override init(frame: CGRect) {
        super.init(frame: frame)
        configure()
    }
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    private func configure() {
        translatesAutoresizingMaskIntoConstraints = false
        addSubview(tableView)
        tableView.backgroundColor = #colorLiteral(red: 0.9764705882, green: 0.9764705882, blue: 0.9764705882, alpha: 1)
        tableView.separatorStyle = .none
        tableView.delegate      = self
        tableView.dataSource    = self
        tableView.anchor(top: topAnchor, trailing: trailingAnchor, bottom: bottomAnchor, leading: leadingAnchor, topPadding: 0, rightPadding: 0, bottomPadding: 0, leftPadding: 0, width: 0, height: 0)
    }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return options.count
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = UITableViewCell()
        cell.textLabel?.text = options[indexPath.row]
        cell.textLabel?.font = UIFont(name: "NunitoSans-Regular", size: 12)
        cell.textLabel?.textColor = #colorLiteral(red: 0.09803921569, green: 0.09803921569, blue: 0.09803921569, alpha: 1)
        cell.backgroundColor = #colorLiteral(red: 0.9764705882, green: 0.9764705882, blue: 0.9764705882, alpha: 1)
        return cell
    }
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        completion?(options[indexPath.row])
        tableView.deselectRow(at: indexPath, animated: true)
    }
}

// This is in my viewController, the chooseScheduleDropDown is my customButton
[chooseScheduleDropDown, entryView, chooseDateView, chooseClass, startTimeView, endTimeView, descriptionView, saveBtn].forEach {
            v in
            v.translatesAutoresizingMaskIntoConstraints = false
            scrollView.addSubview(v)
        }
        scrollView.insertSubview(entryView, belowSubview: chooseScheduleDropDown)

Solution

  • Because of the hit-test mechanism

    You add the dropView on the Button GDropdownSchedule

    The layout is

            addSubview(dropView)
            dropView.translatesAutoresizingMaskIntoConstraints = false
            dropView.layer.zPosition = 1
            height = dropView.heightAnchor.constraint(equalToConstant: 0)
            NSLayoutConstraint.activate([
                dropView.topAnchor.constraint(equalTo: headerLbl.bottomAnchor),
                dropView.leadingAnchor.constraint(equalTo: leadingAnchor),
                dropView.trailingAnchor.constraint(equalTo: trailingAnchor)
            ])
    

    From the looking and the existing code,

    dropView's frame is out of the Button GDropdownSchedule's bounds Partly.

    So u can see it , and your clicking does not work.


    To override the hit-test mechanism is OK

     class GDropdownSchedule: UIButton {
    
     // ...
    
       override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
            // if the button is hidden/disabled/transparent it can't be hit
            if self.isHidden || !self.isUserInteractionEnabled || self.alpha < 0.01 { return nil }
    
            let dropViewF = dropView.frame
    
    
            var index = 9
            if bounds.contains(point){
                index = 0
            }
    
            if dropViewF.contains(point){
                index = 1
            }
    
            switch index {
            case 0:
                for subV in subviews.reversed(){
                    let realPoint = subV.convert(point, from: self)
                    let hit = subV.hitTest(realPoint, with: event)
                    if let v = hit{
                        return v
                    }
                }
                return self
            case 1:
                if dropView.alpha > 0.01{
                    let realPoint = dropView.convert(point, from: self)
                    let hit = dropView.hitTest(realPoint, with: event)
                    if let v = hit{
                        return v
                    }
                }
            default:
                ()
            }
            return nil
        }
    
     }
    

    From Apple's Doc

    hitTest(_:with:)

    This method traverses the view hierarchy by calling the point(inside:with:) method of each subview to determine which subview should receive a touch event.

    If point(inside:with:) returns true, then the subview’s hierarchy is similarly traversed until the frontmost view containing the specified point is found. If a view does not contain the point, its branch of the view hierarchy is ignored.

    You rarely need to call this method yourself, but you might override it to hide touch events from subviews.