0
votes

I have custom UITableViewCell done in code, but I have trouble with circular UIImageView with SfSymbol inside. Sometimes it is working well as you can see on the screenshot, but sometimes it has some strange shape. If I don't set any SfSymbol shape is good.

I think that I tried anything, which I can, but still it's not working. This is my custom cell code:

import UIKit

class ListsTableViewCell: UITableViewCell {
    
    // MARK: - Properties
    
    let configuration = UIImage.SymbolConfiguration(pointSize: 16, weight: .medium)
    
    var list: List? {
        didSet {
            guard let list = list else { return }
            
            iconView.backgroundColor = list.color
            titleLabel.text = list.name
        }
    }
    
    // MARK: - Layout properties
    
    var iconView: CircularImageView!
    var titleLabel: UILabel!
    
    // MARK: - Initialization
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        
        iconView = CircularImageView()
        iconView.translatesAutoresizingMaskIntoConstraints = false
        iconView.tintColor = .white
        iconView.contentMode = .center
        
        titleLabel = UILabel()
        titleLabel.translatesAutoresizingMaskIntoConstraints = false
        
        contentView.addSubview(iconView)
        contentView.addSubview(titleLabel)
        
        NSLayoutConstraint.activate([
            iconView.heightAnchor.constraint(equalToConstant: 34),
            iconView.widthAnchor.constraint(equalToConstant: 34),
            iconView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10),
            iconView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -10),
            iconView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
            iconView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
            
            titleLabel.leadingAnchor.constraint(equalTo: iconView.trailingAnchor, constant: 12),
            titleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
            titleLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor)
        ])
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

This is table view cell for row function:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let list = listsToDisplay![indexPath.row]
    
    let tableViewCell = tableView.dequeueReusableCell(withIdentifier: "list", for: indexPath) as! ListsTableViewCell
    tableViewCell.iconView.image = UIImage(systemName: list.icon, withConfiguration: tableViewCell.configuration)
    tableViewCell.list = list
    tableViewCell.accessoryType = .disclosureIndicator
   
    return tableViewCell
}

And this is custom subclass of UIImageView

import UIKit

class CircularImageView: UIImageView {
    override func layoutSubviews() {
        self.layer.masksToBounds = true
        self.clipsToBounds = true
        self.layer.cornerRadius = self.frame.size.width / 2
    }
}

Simulator screen

2
Are you getting any warnings regarding breaking constraints?Frankenstein
Yes, I have. Something it's wrong with cell size. It shows me some strange constraint: UIView-Encapsulated-Layout-Height' UITableViewCellContentView:0x7faa33e1d0c0.height == 54.3333sirekpiotr
I don't think you need to set the height and width constraints. Why don't you try without it.Rakesha Shastri
I tried, and it didn't worked.sirekpiotr
@sirekpiotr - show an image of how it looks when it "sometimes it has some strange shape"DonMag

2 Answers

1
votes

Ah - I've seen this before.

I don't know why, but using a SF Symbol in a UIImageView will change the image view's height!!!

You can easily confirm this:

  • add a view controller in Storyboard
  • add a UIImageView
  • set content mode to Center (doesn't really matter)
  • constrain Width: 60 Height: 60 centerX and centerY
  • Check the Size Inspector - it will show 60 x 60

Now:

  • use the drop-down to set the image to "globe"
  • Check the Size Inspector - it will show 60 x 59

Now:

  • use the drop-down to set the image to "envelope"
  • Check the Size Inspector - it will show 60 x 56.5

No apparent constraint conflicts... no apparent reason for this.

As far as I can tell (unless this changes with iOS 14), we need to embed the image view in a UIView ... constrain it centerX & centerY ... set the background color and corner-radius properties of that view to make it round.


Edit -- just as an exercise...

Two horizontal stack views:

  • Alignment: Fill
  • Distribution: FillEqually
  • Spacing: 0
  • each constrained to Width: 300 and Height: 100
  • each filled with 3 UIImageViews

Gives us 3 100 x 100 square image views. For the second stack, set each image view to a system image, with UIImage.SymbolConfiguration(pointSize: 60, weight: .regular). Should still give us 3 100 x 100 squares.

Instead, this is the result:

enter image description here

Here's the code to produce that:

class SystemImageTestViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let stack1 = UIStackView()
        stack1.axis = .horizontal
        stack1.alignment = .fill
        stack1.distribution = .fillEqually
        
        let stack2 = UIStackView()
        stack2.axis = .horizontal
        stack2.alignment = .fill
        stack2.distribution = .fillEqually
        
        // add stack views to the view
        [stack1, stack2].forEach {
            $0.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview($0)
        }
        
        let g = view.safeAreaLayoutGuide
        
        NSLayoutConstraint.activate([
            
            stack1.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
            stack1.widthAnchor.constraint(equalToConstant: 300.0),
            stack1.heightAnchor.constraint(equalToConstant: 100.0),
            stack1.centerXAnchor.constraint(equalTo: g.centerXAnchor),
            
            stack2.topAnchor.constraint(equalTo: stack1.bottomAnchor, constant: 20.0),
            stack2.widthAnchor.constraint(equalToConstant: 300.0),
            stack2.heightAnchor.constraint(equalToConstant: 100.0),
            stack2.centerXAnchor.constraint(equalTo: g.centerXAnchor),
            
        ])
        
        let colors: [UIColor] = [
            UIColor(red: 0.75, green: 0.00, blue: 0.00, alpha: 1.0),
            UIColor(red: 0.00, green: 0.75, blue: 0.00, alpha: 1.0),
            UIColor(red: 0.00, green: 0.00, blue: 1.00, alpha: 1.0),
        ]
        
        for c in colors {
            let v = UIImageView()
            v.backgroundColor = c
            stack1.addArrangedSubview(v)
        }
        
        let configuration = UIImage.SymbolConfiguration(pointSize: 60, weight: .regular)
        
        let names: [String] = [
            "globe",
            "bandage",
            "envelope",
        ]

        for (c, n) in zip(colors, names) {
            let v = UIImageView()
            v.backgroundColor = c
            v.contentMode = .center
            v.tintColor = .white
            let img = UIImage(systemName: n, withConfiguration: configuration)
            v.image = img
            stack2.addArrangedSubview(v)
        }
        
    }
}

Edit 2 - just for the heck of it...

Implementing a couple classes for a SystemImageView and CircularSystemImageView:

class SystemImageView: UIView {

    override var contentMode: UIView.ContentMode {
        didSet {
            imageView.contentMode = contentMode
        }
    }
    override var tintColor: UIColor! {
        didSet {
            imageView.tintColor = tintColor
        }
    }
    var image: UIImage = UIImage() {
        didSet {
            imageView.image = image
        }
    }
    let imageView = UIImageView()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() -> Void {
        clipsToBounds = true
        addSubview(imageView)
        imageView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            imageView.topAnchor.constraint(equalTo: topAnchor),
            imageView.leadingAnchor.constraint(equalTo: leadingAnchor),
            imageView.trailingAnchor.constraint(equalTo: trailingAnchor),
            imageView.bottomAnchor.constraint(equalTo: bottomAnchor),
        ])
    }
}

class CircularSystemImageView: SystemImageView {
    override func layoutSubviews() {
        super.layoutSubviews()
        self.layer.masksToBounds = true
        self.clipsToBounds = true
        self.layer.cornerRadius = bounds.size.width * 0.5
    }
}

class SystemImageTestViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let stack1 = UIStackView()
        stack1.axis = .horizontal
        stack1.alignment = .fill
        stack1.distribution = .fillEqually
        
        let stack2 = UIStackView()
        stack2.axis = .horizontal
        stack2.alignment = .fill
        stack2.distribution = .fillEqually
        
        let stack3 = UIStackView()
        stack3.axis = .horizontal
        stack3.alignment = .fill
        stack3.distribution = .fillEqually
        
        let stack4 = UIStackView()
        stack4.axis = .horizontal
        stack4.alignment = .fill
        stack4.distribution = .fillEqually
        
        // add stack views to the view
        [stack1, stack2, stack3, stack4].forEach {
            $0.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview($0)
        }
        
        let g = view.safeAreaLayoutGuide
        
        NSLayoutConstraint.activate([
            
            stack1.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
            stack1.widthAnchor.constraint(equalToConstant: 300.0),
            stack1.heightAnchor.constraint(equalToConstant: 100.0),
            stack1.centerXAnchor.constraint(equalTo: g.centerXAnchor),
            
            stack2.topAnchor.constraint(equalTo: stack1.bottomAnchor, constant: 20.0),
            stack2.widthAnchor.constraint(equalToConstant: 300.0),
            stack2.heightAnchor.constraint(equalToConstant: 100.0),
            stack2.centerXAnchor.constraint(equalTo: g.centerXAnchor),
            
            stack3.topAnchor.constraint(equalTo: stack2.bottomAnchor, constant: 20.0),
            stack3.widthAnchor.constraint(equalToConstant: 300.0),
            stack3.heightAnchor.constraint(equalToConstant: 100.0),
            stack3.centerXAnchor.constraint(equalTo: g.centerXAnchor),
            
            stack4.topAnchor.constraint(equalTo: stack3.bottomAnchor, constant: 20.0),
            stack4.widthAnchor.constraint(equalToConstant: 300.0),
            stack4.heightAnchor.constraint(equalToConstant: 100.0),
            stack4.centerXAnchor.constraint(equalTo: g.centerXAnchor),
            
        ])
        
        let colors: [UIColor] = [
            UIColor(red: 0.75, green: 0.00, blue: 0.00, alpha: 1.0),
            UIColor(red: 0.00, green: 0.75, blue: 0.00, alpha: 1.0),
            UIColor(red: 0.00, green: 0.00, blue: 1.00, alpha: 1.0),
        ]
        
        for c in colors {
            let v = UIImageView()
            v.backgroundColor = c
            stack1.addArrangedSubview(v)
        }
        
        let configuration = UIImage.SymbolConfiguration(pointSize: 60, weight: .regular)
        
        let names: [String] = [
            "globe",
            "bandage",
            "envelope",
        ]

        for (c, n) in zip(colors, names) {
            let v = UIImageView()
            v.backgroundColor = c
            v.contentMode = .center
            v.tintColor = .white
            if let img = UIImage(systemName: n, withConfiguration: configuration) {
                v.image = img
            }
            stack2.addArrangedSubview(v)
        }
        
        for (c, n) in zip(colors, names) {
            let v = SystemImageView()
            v.backgroundColor = c
            v.contentMode = .center
            v.tintColor = .white
            if let img = UIImage(systemName: n, withConfiguration: configuration) {
                v.image = img
            }
            stack3.addArrangedSubview(v)
        }
        
        for (c, n) in zip(colors, names) {
            let v = CircularSystemImageView()
            v.backgroundColor = c
            v.contentMode = .center
            v.tintColor = .white
            if let img = UIImage(systemName: n, withConfiguration: configuration) {
                v.image = img
            }
            stack4.addArrangedSubview(v)
        }
        
    }
}

Results:

enter image description here

or, with a little more practical let configuration = UIImage.SymbolConfiguration(pointSize: 40, weight: .regular) to show the sizes are not being determined by the SF Symbol size:

enter image description here

0
votes

Remove height constraints for an image view. Instead of that use aspect ratio that would help to take position based on height of cell.

enter image description here