swift 从UIView中删除被遮罩的角

idfiyjo8  于 2023-04-19  发布在  Swift
关注(0)|答案(3)|浏览(105)

我有一个可展开的单元格,其中包含顶部和底部UIView容器。当单元格展开时,底部容器可见。
顶部容器的圆角半径:topContainer.layer.cornerRadius = 12
当我展开单元格时,我想在顶部容器的左下角和右下角添加遮罩角。
我是这样做的:
topContainer.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMinXMinYCorner]
当单元格被折叠时,我想删除被遮罩的角:
我试着这样做:
topContainer.layer.maskedCorners = []
不起作用。如何从UIView中删除被遮罩的角?

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
         if let cell = tableView.cellForRow(at: indexPath) as? ExpandableProgramCell {
            cell.setExpanded(false)
            tableView.performBatchUpdates(nil)
        }

 func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
         if let cell = tableView.cellForRow(at: indexPath) as? ExpandableProgramCell {
            cell.setExpanded(true)
            tableView.performBatchUpdates(nil)
        }

func setExpanded(_ expanded: Bool) {
        if !expanded {
            topContainer.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMinXMinYCorner]
            UIView.animate(withDuration: 0.3, delay: 0.0, options: .curveEaseInOut, animations: {
                let upsideDown = CGAffineTransform(rotationAngle: .pi * -0.999)
                self.arrowImageView.transform = upsideDown
                self.bottomContainer.snp.remakeConstraints { make in
                    make.top.equalTo(self.topContainer.snp.bottom)
                    make.left.right.bottom.equalToSuperview()
                }
            })
        } else {
            topContainer.layer.maskedCorners = [] // - doesn't work
            UIView.animate(withDuration: 0.3, delay: 0.0, options: .curveEaseInOut, animations: {
                self.arrowImageView.transform = .identity
                self.bottomContainer.snp.remakeConstraints { make in
                    make.top.equalTo(self.topContainer.snp.bottom)
                    make.left.right.equalToSuperview()
                }
            })
        }
    }
}
elcex8rz

elcex8rz1#

一些建议,你可能会发现有用的-特别是在未来...
首先,取代func setExpanded(_ expanded: Bool),覆盖单元格的setSelected()函数,让表格视图管理每个单元格的选定状态:

override func setSelected(_ selected: Bool, animated: Bool) {
    super.setSelected(selected, animated: animated)
    
    if selected {
        topContainer.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMinXMinYCorner]
        UIView.animate(withDuration: 0.3, delay: 0.0, options: .curveEaseInOut, animations: {
            let upsideDown = CGAffineTransform(rotationAngle: .pi * -0.999)
            self.arrowImageView.transform = upsideDown
            self.bottomContainer.snp.remakeConstraints { make in
                make.top.equalTo(self.topContainer.snp.bottom)
                make.left.right.equalToSuperview().inset(16)
                make.bottom.equalToSuperview()
            }
        })
    } else {
        UIView.animate(withDuration: 0.3, delay: 0.0, options: .curveEaseInOut, animations: {
            self.arrowImageView.transform = .identity
            self.bottomContainer.snp.remakeConstraints { make in
                make.top.equalTo(self.topContainer.snp.bottom)
                make.left.right.equalToSuperview().inset(16)
            }
        }, completion: {_ in
            self.topContainer.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMaxYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner]
        })
    }
}

现在,didSelectdidDeselect看起来像这样:

func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
    tableView.performBatchUpdates(nil)
}

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    tableView.performBatchUpdates(nil, completion: nil)
}

接下来,将单元格的所有子视图布局和属性设置移动到init,这样它们只设置一次,而不是每次调用cellForRowAt时都设置一次。
对于你的“圆角”,我把你的mainContainer的圆角...不再来回改变.maskedCorners
并且,您创建了“expanded”和“collapsed”约束,因此在扩展/折叠单元格时使用它们。我们可以将该过程“简化”为:

override func setSelected(_ selected: Bool, animated: Bool) {
    super.setSelected(selected, animated: animated)

    if selected {
        collapsedConstraint.deactivate()
        expandedConstraint.activate()
        UIView.animate(withDuration: 0.3, delay: 0.0, options: .curveEaseInOut, animations: {
            self.arrowImageView.transform = CGAffineTransform(rotationAngle: .pi * -0.999)
        })
    } else {
        expandedConstraint.deactivate()
        collapsedConstraint.activate()
        UIView.animate(withDuration: 0.3, delay: 0.0, options: .curveEaseInOut, animations: {
            self.arrowImageView.transform = .identity
        })
    }

}

下面是您的ExpandableProgramCell的修改版本--我尽可能地实现了与您的代码相匹配的内容,尽管我确信其中有些内容并不完全正确(但实际上应该不会有任何影响):

其他

extension UIView {
    public func addSubviews(_ subviews: UIView...) {
        subviews.forEach(addSubview)
    }
}
extension NSAttributedString {
    func makeBulletList(from: [String], bulletCharacter: String, bulletAttributes: [NSAttributedString.Key : Any], textAttributes: [NSAttributedString.Key : Any]) -> NSAttributedString {
        let paraStyle = NSMutableParagraphStyle()
        paraStyle.tabStops = [NSTextTab(textAlignment: .left, location: 8, options: [:])]
        paraStyle.defaultTabInterval = 8
        
        let bulletList = NSMutableAttributedString()
        
        for string in from {
            let fmtStr = "\(bulletCharacter)\t\(string)\n"
            let attribStr = NSMutableAttributedString(string: fmtStr)
            attribStr.addAttributes([NSAttributedString.Key.paragraphStyle : paraStyle], range: NSMakeRange(0, attribStr.length))
            attribStr.addAttributes(textAttributes, range: NSMakeRange(0, attribStr.length))
            let string: NSString = NSString(string: fmtStr)
            let rangeForBullet:NSRange = string.range(of: bulletCharacter)
            attribStr.addAttributes(bulletAttributes, range: rangeForBullet)
            bulletList.append(attribStr)
        }
        
        return bulletList
    }
}
class PreparableTableCell: UITableViewCell {
    func prepare(withViewModel viewModel: PreparableViewModel?) {}
}
protocol PreparableViewModel {
}
struct ExpandableProgramViewModel: PreparableViewModel {
    var cellId: String = "xpc" // ExpandableProgramCell.className
    let title: String
    let description: [String]
}

单元格类

final class ExpandableProgramCell: PreparableTableCell {
    
    private var expandedConstraint: Constraint!
    private var collapsedConstraint: Constraint!
    
    // SnapKit internally sets .translatesAutoresizingMaskIntoConstraints = false
    //  so, unless you're doing something else in your "prepareForAutoLayout()"
    //  it's not needed here
    private let mainContainer = UIView()
    private let topContainer = UIView()
    private let bottomContainer = UIView()
    private let disciplineTitle = UILabel()
    private let list = UILabel()
    
    private lazy var arrowImageView: UIImageView = {
        // I don't have your "MapImages.chevronDown" so I'll just use a SF Symbol
        //let imageView = UIImageView(image: MapImages.chevronDown?.withRenderingMode(.alwaysTemplate))
        let imageView = UIImageView()
        if let img = UIImage(systemName: "chevron.down") {
            imageView.image = img
        }
        imageView.tintColor = .systemGreen // .greenMAP
        imageView.contentMode = .scaleAspectFit
        return imageView
    }()

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        if selected {
            collapsedConstraint.deactivate()
            expandedConstraint.activate()
            UIView.animate(withDuration: 0.3, delay: 0.0, options: .curveEaseInOut, animations: {
                self.arrowImageView.transform = CGAffineTransform(rotationAngle: .pi * -0.999)
            })
        } else {
            expandedConstraint.deactivate()
            collapsedConstraint.activate()
            UIView.animate(withDuration: 0.3, delay: 0.0, options: .curveEaseInOut, animations: {
                self.arrowImageView.transform = .identity
            })
        }

    }

    override func prepare(withViewModel viewModel: PreparableViewModel?) {
        guard let viewModel = viewModel as? ExpandableProgramViewModel else { return }
        
        // because we configured all the subviews in init,
        //  all we do here is "fill them in"
        
        disciplineTitle.text = viewModel.title
        
        let bFont: UIFont = .systemFont(ofSize: 22, weight: .regular)   //UIFont.fontInter(ofSize: 22)
        let tFont: UIFont = .systemFont(ofSize: 14, weight: .regular)   //UIFont.fontInter(ofSize: 14)

        list.attributedText = NSAttributedString().makeBulletList(
            from: viewModel.description,
            bulletCharacter: "\u{25AA}",
            bulletAttributes: [NSAttributedString.Key.foregroundColor : UIColor.gray as Any,  NSAttributedString.Key.font : bFont],
            textAttributes: [NSAttributedString.Key.font : tFont]
        )
    }

    // let's do all the layout and view property configuration on init
    //  instead of every time cellForRowAt is called
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        configureView()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        configureView()
    }
    
    private func configureView() {
        list.numberOfLines = .zero
        disciplineTitle.numberOfLines = .zero
        disciplineTitle.font = .systemFont(ofSize: 17, weight: .bold) //.fontInter(ofSize: 17, weight: .bold)
        
        makeConstraints()
        
        // let's use rounded corners on the "main" container view
        //  so we only need to set them once, and we don't need to
        //  set different corners for expanded/collapsed
        mainContainer.clipsToBounds = true
        mainContainer.layer.cornerRadius = 12
        mainContainer.backgroundColor = UIColor(white: 0.9, alpha: 1.0) // .mainGreyColor
        
        // we don't want the default "gray" selection highlighting
        self.selectionStyle = .none
    }
    
    private func makeConstraints() {
        contentView.addSubview(mainContainer)
        
        // constrain main container to contentView's built-in layout margins
        mainContainer.snp.makeConstraints { make in
            make.edges.equalTo(contentView.layoutMarginsGuide)
        }
        
        mainContainer.addSubviews(topContainer, bottomContainer)
        
        // constrain topContainer top/left/right to mainContainer
        //  height: 60
        //  and set the collapsedConstraint
        topContainer.snp.makeConstraints { make in
            make.top.left.right.equalToSuperview()
            make.height.equalTo(60)
            collapsedConstraint = make.bottom.equalToSuperview().priority(.low).constraint
        }
        
        topContainer.addSubviews(arrowImageView, disciplineTitle)
        
        arrowImageView.snp.makeConstraints { make in
            make.height.equalTo(20)
            make.width.equalTo(24)
            make.centerY.equalToSuperview()
            make.right.equalToSuperview().offset(-20)
        }
        
        disciplineTitle.snp.makeConstraints { make in
            make.leading.equalToSuperview().offset(15)
            make.trailing.equalTo(arrowImageView.snp.leading).inset(6)
            make.top.equalToSuperview()
            make.bottom.equalToSuperview()
        }
        
        // constrain bottomContainer top to topContainer bottom
        //  left/right to mainContainer
        //  height: 60
        //  and set the expandedConstraint
        bottomContainer.snp.makeConstraints { make in
            make.top.equalTo(topContainer.snp.bottom)
            make.left.right.equalToSuperview()
            expandedConstraint = make.bottom.equalToSuperview().priority(.low).constraint
        }
        
        bottomContainer.addSubview(list)
        
        list.snp.makeConstraints { make in
            make.left.top.equalToSuperview().offset(16)
            make.right.bottom.equalToSuperview().offset(-16)
        }
        
    }

}

和一个快速的视图控制器示例

class ExpandTestVC: UIViewController, UITableViewDataSource, UITableViewDelegate {
    
    var myData: [ExpandableProgramViewModel] = []
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // let's create 20 view models
        for j in 0..<20 {
            var desc: [String] = []
            for i in 0...(j % 5) {
                desc.append("Bullet \(i + 1)")
            }
            let m = ExpandableProgramViewModel(cellId: "", title: "Title Label: \(j)", description: desc)
            myData.append(m)
        }
        
        let tableView = UITableView()
        view.addSubview(tableView)
        
        tableView.snp.makeConstraints { make in
            make.edges.equalTo(view.safeAreaLayoutGuide)
        }
        
        tableView.register(ExpandableProgramCell.self, forCellReuseIdentifier: "c")
        tableView.dataSource = self
        tableView.delegate = self
        
        tableView.allowsMultipleSelection = true
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return myData.count
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let c = tableView.dequeueReusableCell(withIdentifier: "c", for: indexPath) as! ExpandableProgramCell
        c.prepare(withViewModel: myData[indexPath.row])
        return c
    }
    
    func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
        tableView.performBatchUpdates(nil)
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.performBatchUpdates(nil, completion: nil)
    }
    
}
llycmphe

llycmphe2#

我不久前写了这个扩展。也许来回设置值会起作用。

extension UIView {
    func roundCorners(corners: UIRectCorner, radius: CGFloat) {
        let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
        let mask = CAShapeLayer()
        mask.path = path.cgPath
        layer.maskedCorners = CACornerMask(rawValue: corners.rawValue)
        layer.mask = mask
    }
}
ef1yzkbh

ef1yzkbh3#

最后,我找到了一个解决方案:https://developer.apple.com/forums/thread/727795
所以我们需要指定所有的角。

import UIKit
import SnapKit

struct ExpandableProgramViewModel: PreparableViewModel {
    var cellId: String = ExpandableProgramCell.className
    let title: String
    let description: [String]
}

final class ExpandableProgramCell: PreparableTableCell {
        
    private var expandedConstarint: Constraint!
    private var collapsedConstraint: Constraint!
    
    private lazy var mainContainer = UIView().prepareForAutoLayout()
    private lazy var topContainer = UIView().prepareForAutoLayout()
    private lazy var bottomContainer = UIView().prepareForAutoLayout()
    private let disciplineTitle = UILabel().prepareForAutoLayout()
    private let list = UILabel().prepareForAutoLayout()
    
    private lazy var arrowImageView: UIImageView = {
        let imageView = UIImageView(image: MapImages.chevronDown?.withRenderingMode(.alwaysTemplate))
        imageView.tintColor = .greenMAP
        imageView.contentMode = .scaleAspectFit
        
        return imageView
    }()
    
    func setExpanded(_ expanded: Bool) {
            if expanded {
                topContainer.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMinXMinYCorner]
                UIView.animate(withDuration: 0.3, delay: 0.0, options: .curveEaseInOut, animations: {
                    let upsideDown = CGAffineTransform(rotationAngle: .pi * -0.999)
                    self.arrowImageView.transform = upsideDown
                    self.bottomContainer.snp.remakeConstraints { make in
                        make.top.equalTo(self.topContainer.snp.bottom)
                        make.left.right.equalToSuperview().inset(16)
                        make.bottom.equalToSuperview()
                    }
                })
            } else {
                UIView.animate(withDuration: 0.3, delay: 0.0, options: .curveEaseInOut, animations: {
                    self.arrowImageView.transform = .identity
                    self.bottomContainer.snp.remakeConstraints { make in
                        make.top.equalTo(self.topContainer.snp.bottom)
                        make.left.right.equalToSuperview().inset(16)
                    }
                }, completion: {_ in
                    self.topContainer.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMaxYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner]
                })
            }
        }
    
    override func prepare(withViewModel viewModel: PreparableViewModel?) {
        guard let viewModel = viewModel as? ExpandableProgramViewModel else { return }
        disciplineTitle.text = viewModel.title
        
        list.attributedText = NSAttributedString().makeBulletList(
            from: viewModel.description,
            bulletCharacter: "\u{25AA}",
            bulletAttributes: [NSAttributedString.Key.foregroundColor : UIColor.middleGrayColor as Any,  NSAttributedString.Key.font : UIFont.fontInter(ofSize: 22)],
            textAttributes: [NSAttributedString.Key.font : UIFont.fontInter(ofSize: 14)])
        
        configureView()
    }
    
    private func configureView() {
        list.numberOfLines = .zero
        disciplineTitle.numberOfLines = .zero
        disciplineTitle.font = .fontInter(ofSize: 17, weight: .bold)
        mainContainer.clipsToBounds = true
        topContainer.backgroundColor = .mainGreyColor
        bottomContainer.backgroundColor = .mainGreyColor

        makeConstraints()
        collapsedConstraint.isActive = !isSelected
        expandedConstarint.isActive = isSelected
    }
    
    private func makeConstraints() {
        contentView.addSubview(mainContainer)
        
        mainContainer.snp.makeConstraints { make in
            make.edges.equalToSuperview()
        }
        
        mainContainer.addSubviews(topContainer, bottomContainer)
        
        topContainer.layer.cornerRadius = 12
        
        topContainer.snp.makeConstraints { make in
            make.top.equalToSuperview().offset(16)
            make.left.right.equalToSuperview().inset(16)
            make.height.equalTo(60)
        }
        
        topContainer.snp.prepareConstraints { make in
            collapsedConstraint = make.bottom.equalToSuperview().constraint
            collapsedConstraint.layoutConstraints.first?.priority = .defaultLow
        }
        topContainer.addSubviews(arrowImageView, disciplineTitle)
        
        arrowImageView.snp.makeConstraints { make in
            make.height.equalTo(16)
            make.width.equalTo(24)
            make.centerY.equalToSuperview()
            make.right.equalToSuperview().offset(-20)
        }
        
        disciplineTitle.snp.makeConstraints { make in
            make.leading.equalToSuperview().offset(15)
            make.trailing.equalTo(arrowImageView.snp.leading).inset(6)
            make.top.equalToSuperview()
            make.bottom.equalToSuperview()
        }
        
        bottomContainer.addSubview(list)
        
        list.snp.makeConstraints { make in
            make.left.top.equalToSuperview().offset(16)
            make.right.bottom.equalToSuperview().offset(-16)
        }
        
        bottomContainer.layer.cornerRadius = 12
        bottomContainer.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
        bottomContainer.snp.makeConstraints { make in
            make.top.equalTo(topContainer.snp.bottom)
            make.left.right.equalToSuperview().inset(16)
        }
   
        bottomContainer.snp.prepareConstraints { make in
            expandedConstarint = make.bottom.equalToSuperview().constraint
            expandedConstarint.layoutConstraints.first?.priority = .defaultLow
        }
    }
}

相关问题