0

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

sirekpiotr
  • 17
  • 4

2 Answers2

1

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

DonMag
  • 69,424
  • 5
  • 50
  • 86
0

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

Prakash
  • 812
  • 6
  • 16
  • Can you explain, how to use aspectRatio in this scenario? – sirekpiotr Jul 31 '20 at 10:15
  • check updated answer. If you set height constraint for 100 pixel and if your table row height is 80 pixel, then this would happen like this. So use aspect ratio instead of that. – Prakash Jul 31 '20 at 10:21