web-dev-qa-db-fra.com

UITableViewCell: coins arrondis et ombre

Je change la largeur d'un UITableViewCell afin que la cellule soit plus petite mais que l'utilisateur puisse toujours faire défiler le long des bords de la table.

override func layoutSubviews() {        
    // Set the width of the cell
    self.bounds = CGRectMake(self.bounds.Origin.x, self.bounds.Origin.y, self.bounds.size.width - 40, self.bounds.size.height)
    super.layoutSubviews()
}

Puis j'arrondis les coins:

cell.layer.cornerRadius = 8
cell.layer.masksToBounds = true

Tout va bien jusqu'à présent. Le problème se produit avec l'ombre. Les limites sont masquées, de sorte que l'ombre ne sera évidemment pas visible. J'ai cherché d'autres réponses mais je n'arrive pas à comprendre comment arrondir les angles le long des limites et montrer l'ombre.

cell.layer.shadowOffset = CGSizeMake(0, 0)
cell.layer.shadowColor = UIColor.blackColor().CGColor
cell.layer.shadowOpacity = 0.23
cell.layer.shadowRadius = 4

Ma question est donc: comment puis-je réduire la largeur, arrondir les angles et ajouter une ombre à un UITableViewCell en même temps?

Mise à jour: Essayer la réponse de R Moyer

 Trying R Moyer's answer

32
Shaan Singh

Cette question arrive au bon moment! J'ai littéralement JUSTE résolu le même problème moi-même.

  1. Créez une UIView (appelons-la mainBackground) dans la vue du contenu de votre cellule. Cela contiendra tout le contenu de votre cellule. Positionnez-le et appliquez les contraintes nécessaires dans le Storyboard.
  2. Créez une autre UIView. Celui-ci sera celui avec l'ombre (appelons-le shadowLayer). Positionnez-le exactement comme vous l'avez fait mainBackground, mais derrière lui, et appliquez les mêmes contraintes.
  3. Vous devriez maintenant pouvoir définir les coins arrondis et les ombres comme suit:

    cell.mainBackground.layer.cornerRadius = 8  
    cell.mainBackground.layer.masksToBounds = true
    
    cell.shadowLayer.layer.masksToBounds = false
    cell.shadowLayer.layer.shadowOffset = CGSizeMake(0, 0)
    cell.shadowLayer.layer.shadowColor = UIColor.blackColor().CGColor
    cell.shadowLayer.layer.shadowOpacity = 0.23
    cell.shadowLayer.layer.shadowRadius = 4
    

Cependant, le problème est le suivant: le calcul de l'ombre pour chaque cellule est une tâche lente. Vous remarquerez un sérieux décalage lorsque vous faites défiler votre table. La meilleure façon de résoudre ce problème consiste à définir une UIBezierPath pour l'ombre, puis à la pixelliser. Donc, vous voudrez peut-être faire ceci:

cell.shadowLayer.layer.shadowPath = UIBezierPath(roundedRect: cell.shadowLayer.bounds, byRoundingCorners: .AllCorners, cornerRadii: CGSize(width: 8, height: 8)).CGPath
cell.shadowLayer.layer.shouldRasterize = true
cell.shadowLayer.layer.rasterizationScale = UIScreen.mainScreen().scale

Mais cela crée un nouveau problème! La forme de UIBezierPath dépend des limites de shadowLayer, mais celles-ci ne sont pas correctement définies au moment où cellForRowAtIndexPath est appelé. Vous devez donc ajuster la shadowPath en fonction des limites de shadowLayer. La meilleure façon de procéder consiste à sous-classer UIView et à ajouter un observateur de propriété à la propriété bounds. Définissez ensuite toutes les propriétés de l'ombre dans didSet. N'oubliez pas de changer la classe de votre shadowLayer dans le storyboard afin qu'elle corresponde à votre nouvelle sous-classe.

class ShadowView: UIView {
    override var bounds: CGRect {
        didSet {
            setupShadow()
        }
    }

    private func setupShadow() {
        self.layer.cornerRadius = 8
        self.layer.shadowOffset = CGSize(width: 0, height: 3)
        self.layer.shadowRadius = 3
        self.layer.shadowOpacity = 0.3
        self.layer.shadowPath = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: 8, height: 8)).cgPath
        self.layer.shouldRasterize = true
        self.layer.rasterizationScale = UIScreen.main.scale
    }
}
58
R Moyer
cell.layer.cornerRadius = 10
cell.layer.masksToBounds = true
15
Sai kumar Reddy

Pour créer une ombre et un coin pour une cellule, vous n'avez besoin que d'une seule vue arrière. Voir mon exemple ci-dessous.

 enter image description here

Vous devez ajouter backView et définir leading, trailing, top, bottom contraintes égales à Content view. Placez votre contenu sur backView avec des contraintes appropriées, mais soyez sûr que votre contenu n'est pas masqué r backView.

Après cela, dans votre code d'initialisation de cellule, ajoutez ces lignes:

override func awakeFromNib() {
    super.awakeFromNib()

    backgroundColor = Colors.colorClear

    self.backView.layer.borderWidth = 1
    self.backView.layer.cornerRadius = 3
    self.backView.layer.borderColor = Colors.colorClear.cgColor
    self.backView.layer.masksToBounds = true

    self.layer.shadowOpacity = 0.18
    self.layer.shadowOffset = CGSize(width: 0, height: 2)
    self.layer.shadowRadius = 2
    self.layer.shadowColor = Colors.colorBlack.cgColor
    self.layer.masksToBounds = false
}

N'oubliez pas de créer IBOutlet pour Back View.

Et voici le résultat:

 enter image description here

9
andrey

La réponse acceptée fonctionne, mais l'ajout d'une sous-vue supplémentaire pour obtenir cet effet n'a guère de sens. Voici la solution qui fonctionne.

1ère étape : Ajouter l'ombre et le rayon de l'angle

// do this in one of the init methods
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
    super.init(style: style, reuseIdentifier: reuseIdentifier)

    // add shadow on cell
    backgroundColor = .clear // very important
    layer.masksToBounds = false
    layer.shadowOpacity = 0.23
    layer.shadowRadius = 4
    layer.shadowOffset = CGSize(width: 0, height: 0)
    layer.shadowColor = UIColor.blackColor().CGColor

    // add corner radius on `contentView`
    contentView.backgroundColor = .white
    contentView.layer.cornerRadius = 8
}

2ème étape : Masque aux limites dans willDisplay

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    // this will turn on `masksToBounds` just before showing the cell
    cell.contentView.layer.masksToBounds = true
}

Bonus : défilement lisse

// if you do not set `shadowPath` you'll notice laggy scrolling
// add this in `willDisplay` method
let radius = cell.contentView.layer.cornerRadius
cell.layer.shadowPath = UIBezierPath(roundedRect: cell.bounds, cornerRadius: radius).cgPath
8
Borut Tomazin

J'ai obtenu le même résultat en utilisant le code suivant.Mais vous l'avez placé dans la méthode layoutSubviews () de votre sous-classe TableViewCell.

self.contentView.backgroundColor = [UIColor whiteColor];
self.contentView.layer.cornerRadius = 5;
self.contentView.layer.shadowOffset = CGSizeMake(1, 0);
self.contentView.layer.shadowColor = [[UIColor blackColor] CGColor];
self.contentView.layer.shadowRadius = 5;
self.contentView.layer.shadowOpacity = .25;
CGRect shadowFrame = self.contentView.layer.bounds;
CGPathRef shadowPath = [UIBezierPath bezierPathWithRect:shadowFrame].CGPath;
self.contentView.layer.shadowPath = shadowPath;
4
posha

Une autre approche que vous pouvez essayer consiste à prendre une UIView dans UITableViewCell. Définissez la couleur de fond de UITableViewCell pour effacer la couleur. Maintenant, vous pouvez créer des angles arrondis et ajouter des ombres sur UIVIew. Cela apparaîtra comme si la largeur de la cellule était réduite et que l'utilisateur pouvait faire défiler le long des bords du tableau.

3
Ashish Verma

N'utilisez jamais UIBezierPath, bezierPathWithRect: shadowFrame, etc., car il est très lourd et dessine une couche au-dessus des vues. Il est nécessaire de recharger à nouveau la vue tabulaire pour vous assurer que les cellules s'affichent correctement et que, même, un rechargement peut ne pas aider. Utilisez plutôt un en-tête de section et un pied de page dont les bords sont arrondis selon les besoins et qui se trouvent également à l'intérieur du storyboard, ce qui facilitera le défilement et le chargement de la vue tableau sans aucun problème de rendu (parfois appelées cellules manquantes et elles apparaissent sur le défilement).

référez-vous ici à la définition des différentes valeurs entières des angles arrondis: Définition des angles masqués dans Interface Builder

Utilisez simplement les valeurs ci-dessus pour votre en-tête et votre pied de section. 

0
Max

Essayez ceci, cela a fonctionné pour moi.

    cell.contentView.layer.cornerRadius = 5.0
    cell.contentView.layer.borderColor = UIColor.gray.withAlphaComponent(0.5).cgColor
    cell.contentView.layer.borderWidth = 0.5


    let border = CALayer()
    let width = CGFloat(2.0)
    border.borderColor = UIColor.darkGray.cgColor
    border.frame = CGRect(x: 0, y: cell.contentView.frame.size.height - width, width:  cell.contentView.frame.size.width, height: cell.contentView.frame.size.height)

    border.borderWidth = width
    cell.contentView.layer.addSublayer(border)
    cell.contentView.layer.masksToBounds = true
    cell.contentView.clipsToBounds = true

Cela fonctionne sans vues supplémentaires!

override func awakeFromNib() {
    super.awakeFromNib()

    layer.masksToBounds = false
    layer.cornerRadius = Constants.cornerRadius
}

public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    ...
    let layer = cell.layer
    layer.shadowOffset = CGSize(width: 0, height: 1)
    layer.shadowRadius = 2
    layer.shadowColor = UIColor.lightGray.cgColor
    layer.shadowOpacity = 0.2
    layer.frame = cell.frame
    cell.tagLabel.text = tagItems[indexPath.row].text

    return cell
}
0
IvanPavliuk