简体   繁体   中英

Constraining UITableViewCell to right edge of UITableView

For some reason, when I try to put the shadows on my Cell using the following, it isn't going the full width of the cell:

cell.layer.shadowPath = UIBezierPath(roundedRect:cell.bounds, cornerRadius:cell.contentView.layer.cornerRadius).cgPath

Does anyone know how to fix this?

Granted, my ending intention is to give it spacing on both the left and right sides, but I feel like the first step is making it reach the edge.

Simulator and the storyboard:

模拟器和故事板

Code:

import UIKit

class SpendingCategoryViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

    //MARK: Properties

    @IBOutlet weak var spendingCategoriesTableView: UITableView!
    var spendingCategories = [SpendingCategory]()

    override func viewDidLoad() {
        super.viewDidLoad()
        spendingCategoriesTableView.delegate = self
        spendingCategoriesTableView.dataSource = self

        // Load the sample data.
        loadSampleCategories()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    // MARK: - Table view data source

    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return spendingCategories.count
    }


    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        // Table view cells are reused and should be dequeued using a cell identifier.
        let cellIdentifier = "SpendingCategoryTableViewCell"

        guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? SpendingCategoryTableViewCell  else {
            fatalError("The dequeued cell is not an instance of SpendingCategoryTableViewCell.")
        }

        // Fetches the appropriate SpendingCategory for the data source layout.
        let spendingCategory = spendingCategories[indexPath.row]

        cell.categoryLabel.text = spendingCategory.name
        cell.iconImageView.image = spendingCategory.icon
        cell.valueLabel.text = String(format: "$%.02f", spendingCategory.total)

        // This creates the shadows and modifies the cards a little bit
        // https://github.com/rileydnorris/cardLayoutSwift/blob/9b852fc8e1b7d62093be787a33a3a89d764dc9b8/cardLayout/ViewController.swift

        cell.contentView.layer.cornerRadius = 2.0
//        cell.contentView.layer.borderWidth = 1.0
        cell.contentView.layer.borderColor = UIColor.clear.cgColor
        cell.contentView.layer.masksToBounds = true;

        cell.layer.shadowColor = UIColor.lightGray.cgColor
        cell.layer.shadowOffset = CGSize(width:0,height: 2.0)
        cell.layer.shadowRadius = 2.0
        cell.layer.shadowOpacity = 1.0
        cell.layer.masksToBounds = false;
        cell.layer.shadowPath = UIBezierPath(roundedRect:cell.bounds, cornerRadius:cell.contentView.layer.cornerRadius).cgPath




        return cell
    }


    /*
     // Override to support conditional editing of the table view.
     override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
     // Return false if you do not want the specified item to be editable.
     return true
     }
     */

    /*
     // Override to support editing the table view.
     override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
     if editingStyle == .delete {
     // Delete the row from the data source
     tableView.deleteRows(at: [indexPath], with: .fade)
     } else if editingStyle == .insert {
     // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
     }
     }
     */

    /*
     // Override to support rearranging the table view.
     override func tableView(_ tableView: UITableView, moveRowAt fromIndexPath: IndexPath, to: IndexPath) {

     }
     */

    /*
     // Override to support conditional rearranging of the table view.
     override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
     // Return false if you do not want the item to be re-orderable.
     return true
     }
     */

    /*
     // MARK: - Navigation

     // In a storyboard-based application, you will often want to do a little preparation before navigation
     override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
     // Get the new view controller using segue.destinationViewController.
     // Pass the selected object to the new view controller.
     }
     */

    //MARK: Private Methods
    @IBAction func addCategoryButtonPress(_ sender: UIButton) {
        // Create an alert
        let alert = UIAlertController(
            title: "Add a new category",
            message: "",
            preferredStyle: .alert)

        // Add a text field to the alert for the new item's title
        alert.addTextField(configurationHandler: nil)

        // Add a "cancel" button to the alert. This one doesn't need a handler
        alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))

        // Add a "OK" button to the alert. The handler calls addNewCategory()
        alert.addAction(UIAlertAction(title: "OK", style: .default, handler:
            { (_) in
                // Get the title the user inserted, but only if it is not an empty string
                if let title = alert.textFields?[0].text, title.count > 0
                {
                    print("took in alert input")
                    self.addNewCategory(title: title)
                    //                    self.containerViewController?.viewDidLoad()
                }
        }))

        // Present the alert to the user
        self.present(alert, animated: true, completion: nil)
    }

    internal func addNewCategory(title: String)
    {
        // The index of the new item will be the current item count
        print("reached addNewCategory")
        let newIndex = spendingCategories.count

        // Create new item and add it to the todo items list
        spendingCategories.append(SpendingCategory(name: title, icon: UIImage(named: "Default")!, total: 0)!)

        // Tell the table view a new row has been created
        self.spendingCategoriesTableView.insertRows(at: [IndexPath(row: newIndex, section: 0)], with: .bottom)
        print(spendingCategories)
    }

    private func loadSampleCategories() {
        let photo1 = UIImage(named: "Default")

        guard let category1 = SpendingCategory(name: "Groceries", icon: photo1!, total: 4) else {
            fatalError("Unable to instantiate category1")
        }

        guard let category2 = SpendingCategory(name: "Transportation", icon: photo1!, total: 12345678) else {
            fatalError("Unable to instantiate category2")
        }

        guard let category3 = SpendingCategory(name: "Alcohol something really long", icon: photo1!, total: 12345) else {
            fatalError("Unable to instantiate category3")
        }

        spendingCategories += [category1, category2, category3]
    }
}

import UIKit

class SpendingCategoryTableViewCell: UITableViewCell {

    //MARK: Properties
    @IBOutlet weak var categoryLabel: UILabel!
    @IBOutlet weak var iconImageView: UIImageView!
    @IBOutlet weak var valueLabel: UILabel!


    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }

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

        // Configure the view for the selected state
    }

}

In my project I ended up adding my own background view to the cell contentView . Forget about customising contentView directly. Then I applied cornering to my background view - actualCellView in awakeFromNib() and added constraints to cell edges in storyboard (also you can programmatically set them in layoutSubview() .

Warning:

Shadow + corners is a massive pain in iOS.

If you want to add corners AND shadows to a view you should use either additional view for corners/shadow or add additional CALayer in draw(_ rect: CGRect) . So I ended up with first approach. Also better move your view-specific code to a separate cell class. Check out my CommentCell class for example:

import UIKit

class CommentCell: UITableViewCell {

    @IBOutlet weak var authorLabel: UILabel!
    @IBOutlet weak var commentLabel: UILabel!
    @IBOutlet weak var actualCellView: UIView!

    var cornerView: UIView = UIView() // visible corner view
    var preferredCornerRadius: CGFloat = 10

    override func awakeFromNib() {
        super.awakeFromNib()
        setup()
    }

    override func setup() {
        super.setup()

        addCorners(radius: preferredCornerRadius, to: actualCellView, cornerView: cornerView)
        dropActualShadow()
    }

    override func dropActualShadow() {
        actualCellView.dropShadow(color: .gray,
                                  opacity: 0.25,
                                  offSet: CGSize(width: 0, height: 2),
                                  blurRadius: 1, scale: true,
                                  rounded: true,
                                  cornerRadius: preferredCornerRadius)
    }

    override func draw(_ rect: CGRect) {
        super.draw(rect)
        dropActualShadow()
    }

}

Extension to enable corners and shadow for a view:

public extension UIView {    
    // Careful with this method - adding multiple cornered subviews is not checked 
    func addCorners(radius: CGFloat = 10, to subviewView: UIView, with backgroundColor: UIColor = .white, cornerView: UIView = UIView()) {

        subviewView.insertSubview(cornerView, at: 0)

        cornerView.backgroundColor = backgroundColor
        subviewView.backgroundColor = .clear

        cornerView.cornerRadius = radius
        cornerView.layer.masksToBounds = true

        cornerView.translatesAutoresizingMaskIntoConstraints = false
        cornerView.leadingAnchor.constraint(equalTo: subviewView.leadingAnchor).isActive = true
        cornerView.trailingAnchor.constraint(equalTo: subviewView.trailingAnchor).isActive = true
        cornerView.topAnchor.constraint(equalTo: subviewView.topAnchor).isActive = true
        cornerView.bottomAnchor.constraint(equalTo: subviewView.bottomAnchor).isActive = true
    }

    func dropShadow(color: UIColor = .black,
                    opacity: Float = 0.5,
                    offSet: CGSize = .zero,
                    blurRadius: CGFloat = 1,
                    scale: Bool = true,
                    rounded: Bool = false,
                    dx: CGFloat = 0,
                    dy: CGFloat = 0,
                    cornerRadius: CGFloat = 10,
                    roundCorners: UIRectCorner = .allCorners) {

        self.layer.masksToBounds = false
        self.layer.shadowColor = color.cgColor
        self.layer.shadowOpacity = opacity
        self.layer.shadowOffset = offSet
        self.layer.shadowRadius = blurRadius
        let finalRect = self.bounds.insetBy(dx: dx, dy: dy)
        self.layer.shadowPath = rounded ?
            UIBezierPath(roundedRect: finalRect,
                         byRoundingCorners: roundCorners,
                         cornerRadii: CGSize(width: cornerRadius,
                                             height: cornerRadius)).cgPath :
            UIBezierPath(rect: finalRect).cgPath
        self.layer.shouldRasterize = true
        self.layer.rasterizationScale = scale ? UIScreen.main.scale : 1
    }
}

Hope this saves you a couple of days.

You can do this much easier...

Add a UIView and constrain it to the cell's Content View - either use its default margins or set your own.

Add your image view and labels to that view.

In your cell class, set corner radius, shadow, shadow radius, etc. as desired:

class SpendingCategoryTableViewCell: UITableViewCell {

    @IBOutlet var shadowedView: UIView!
    @IBOutlet var theImageView: UIImageView!
    @IBOutlet var catLabel: UILabel!
    @IBOutlet var moneyLabel: UILabel!

    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    override func awakeFromNib() {
        super.awakeFromNib()
        commonInit()
    }
    func commonInit() -> Void {
        guard let cv = shadowedView else {
            return
        }
        cv.backgroundColor = .white
        cv.layer.cornerRadius = 0.0
        cv.layer.shadowColor = UIColor.black.cgColor
        cv.layer.shadowOffset = CGSize(width: 0, height: 2)
        cv.layer.shadowRadius = 2.0
        cv.layer.shadowOpacity = 0.5
    }

}

Result:

在此处输入图像描述

在此处输入图像描述

And, an example with cv.layer.cornerRadius = 8.0 :

在此处输入图像描述

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM