从UIView中移除遮罩角

  • 本文关键字:UIView swift uikit
  • 更新时间 :
  • 英文 :


我有一个可扩展的单元格,其中包含顶部和底部的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()
}
})
}
}
}

一些建议,你可能会发现有用的-特别是在未来…

首先,不是您的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

并且,您创建了"expand "one_answers";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的修改版本——我实现了我可以匹配你的代码,虽然我确信其中一些是不太正确的(但不应该真的有任何影响):

misc

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]
}
<<p>细胞类/strong>
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)
}

}

我写了这个扩展一段时间前。也许来回设置值可以工作。

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
}
}

最后我找到了一个解决方案: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
}
}
}

将角设置为空数组,或尝试将图层蒙版设置为nil对我没有影响。最简单的解决方案是设置一个新的蒙版,所有四个角的角半径为0。

// existing masked corners
view.layer.cornerRadius = 12.0
view.layer.maskedCorners = [.layerMinXMinYCorner,
.layerMinXMaxYCorner]

// "clear" the masked corners by masking ALL corners with 0 radius
view.layer.cornerRadius = 0.0
view.layer.maskedCorners = [.layerMinXMinYCorner, 
.layerMinXMaxYCorner, 
.layerMaxXMinYCorner, 
.layerMaxXMaxYCorner]

相关内容

  • 没有找到相关文章

最新更新