UITableViewCell 阴影覆盖

UITableViewCell shadows overlay

我有一个包含单元格的 table 视图。 叠加阴影已完成,但看起来不像我想要的那样。 我的阴影白色圆形矩形应该保持白色。阴影应该覆盖在白色矩形下方。关于如何实现预期行为的任何建议?

我将阴影添加为单独的子视图


class ShadowView: UIView {
    
    override var bounds: CGRect {
        didSet {
            setupShadow()
        }
    }
    
    private func setupShadow() {
        layer.shadowColor = UIColor.red.cgColor
        layer.shadowOpacity = 1
        layer.shadowRadius = 40
        layer.shadowOffset = CGSize(width: 1, height: 10)
        layer.masksToBounds = false
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        
        layer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: 5).cgPath
    }
}

然后

let shadowView = ShadowView()        
addSubview(shadowView)

我想要这样的东西。白色矩形是完全白色的。

如您所见,问题在于行(单元格)是分开的 视图。如果您允许元素延伸到单元格之外,它将与相邻视图重叠或欠重叠。

这里有一个简单的例子来说明...

每个单元格都有一个 systemYellow 视图,在顶部和底部延伸到其框架之外:

如果我们使用 Debug View Hierarchy 检查布局,它看起来像这样:

正如我们所见,由于初始 z 顺序,每个单元格都覆盖了向上延伸的 systemYellow 视图部分,而向下延伸的部分与下一个单元格重叠。

当我们稍微滚动时,单元格会在不同的 z 顺序位置重新绘制(基于 tableView 重新使用它们的方式):

现在我们看到一些 systemYellow 视图与上面的行重叠,一些与下面的行重叠,还有一些两者都重叠。

检查布局会向我们显示单元格的 z 顺序位置:

如果我们想要保持 z 顺序以便 none systemYellow 视图与其下方的单元格重叠,我们可以添加一个函数来操纵 z 顺序位置:

func updateLayout() -> Void {
    for c in tableView.visibleCells {
        tableView.bringSubviewToFront(c)
    }
}

我们需要在 tableView 滚动时(以及布局发生变化时)调用它:

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    updateLayout()
}

override func scrollViewDidScroll(_ scrollView: UIScrollView) {
    updateLayout()
}

因此,您的布局也发生了同样的事情...阴影延伸到单元格的框架之外,并且覆盖或覆盖相邻的单元格。

如果我们开始使用相同的方法来管理单元格的 z 顺序,我们可以得到:

因此,我们将白色圆角矩形视图保留在“上方阴影”的顶部。当然,现在我们有阴影与视图的 底部 重叠。

我们可以更改 .shadowPath 的矩形以避免:

override func layoutSubviews() {
    super.layoutSubviews()
    var r = bounds
    r.origin.y += 40
    layer.shadowPath = UIBezierPath(roundedRect: r, cornerRadius: 5).cgPath
}

我们得到这个输出:

还有一个问题——如果我们使用默认单元格 .selectionStyle,我们会得到:

这可能不被接受table。

因此,我们可以将 .selectionStyle 设置为 .none,并在我们的单元格 class 中实现 setSelected。在这里,我更改了圆角矩形背景和文本颜色,使其非常明显:

这是一些示例代码——不需要 @IBOutlet@IBAction 连接,因此只需将新的 table 视图控制器的 class 分配给 ShadowTableViewController :

class ShadowView: UIView {

    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() -> Void {
        layer.shadowColor = UIColor.red.cgColor
        layer.shadowOpacity = 1
        layer.shadowRadius = 40
        layer.masksToBounds = false
        layer.cornerRadius = 12
        layer.shouldRasterize = true
    }
    override func layoutSubviews() {
        super.layoutSubviews()
        var r = bounds
        r.origin.y += 40
        layer.shadowPath = UIBezierPath(roundedRect: r, cornerRadius: 5).cgPath
    }

}

class ShadowCell: UITableViewCell {
    
    let shadowView = ShadowView()
    let topLabel = UILabel()
    let bottomLabel = UILabel()
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() -> Void {
        
        shadowView.backgroundColor = .white
        
        topLabel.font = .boldSystemFont(ofSize: 24.0)
        bottomLabel.font = .italicSystemFont(ofSize: 20.0)
        bottomLabel.numberOfLines = 0
        
        let stack = UIStackView()
        stack.axis = .vertical
        stack.spacing = 8
        
        stack.addArrangedSubview(topLabel)
        stack.addArrangedSubview(bottomLabel)
        
        shadowView.translatesAutoresizingMaskIntoConstraints = false
        stack.translatesAutoresizingMaskIntoConstraints = false
        
        shadowView.addSubview(stack)
        contentView.addSubview(shadowView)
        
        let mg = contentView.layoutMarginsGuide
        
        NSLayoutConstraint.activate([
            
            shadowView.topAnchor.constraint(equalTo: mg.topAnchor),
            shadowView.leadingAnchor.constraint(equalTo: mg.leadingAnchor),
            shadowView.trailingAnchor.constraint(equalTo: mg.trailingAnchor),
            shadowView.bottomAnchor.constraint(equalTo: mg.bottomAnchor),
            
            stack.topAnchor.constraint(equalTo: shadowView.topAnchor, constant: 12.0),
            stack.leadingAnchor.constraint(equalTo: shadowView.leadingAnchor, constant: 12.0),
            stack.trailingAnchor.constraint(equalTo: shadowView.trailingAnchor, constant: -12.0),
            stack.bottomAnchor.constraint(equalTo: shadowView.bottomAnchor, constant: -12.0),
            
        ])
        
        contentView.clipsToBounds = false
        self.clipsToBounds = false
        self.backgroundColor = .clear

        selectionStyle = .none
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)
        shadowView.backgroundColor = selected ? .systemBlue : .white
        topLabel.textColor = selected ? .white : .black
        bottomLabel.textColor = selected ? .white : .black
    }

}

class ShadowTableViewController: UITableViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.separatorStyle = .none
        tableView.register(ShadowCell.self, forCellReuseIdentifier: "shadowCell")
    }

    func updateLayout() -> Void {
        for c in tableView.visibleCells {
            tableView.bringSubviewToFront(c)
        }
    }

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        updateLayout()
    }
    override func scrollViewDidScroll(_ scrollView: UIScrollView) {
        updateLayout()
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 30
    }
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let c = tableView.dequeueReusableCell(withIdentifier: "shadowCell", for: indexPath) as! ShadowCell
        c.topLabel.text = "Row: \(indexPath.row)"
        var s = "Description for row \(indexPath.row)"
        if indexPath.row % 3 == 1 {
            s += "\nSecond Line"
        }
        if indexPath.row % 3 == 2 {
            s += "\nSecond Line\nThirdLine"
        }
        c.bottomLabel.text = s
        return c
    }
    
}

注意:这只是 示例代码,不应被视为生产就绪