4

In the WWDC SF Symbols video, Apple says to prefer horizontal and vertical alignment when displaying SF Symbols. Presumably, that means that each UIImageView.center.x should contain the same value so that they line up in a column. My question is how to determine what that x value should be since they are all different widths. Here is an example screenshot:

Screenshot

This screenshot is using UITableViewCellStyleDefault with cell.imageView and cell.textLabel. However, I want to know how to achieve the same effect using a custom UITableViewCell. Is the correct way to just determine a certain amount of width based off the UIImageSymbolConfiguration.pointSize? Once you have that width, all symbols will be displayed according to that center?

Any guidance on how to layout/align SF Symbols especially without the use of auto layout would be super helpful :)

rickharrison
  • 4,797
  • 3
  • 31
  • 39
  • Give your image view your desired width, and then set the Content Mode of your image view to **Center** ... `myImageView.contentMode = .center` – DonMag Apr 09 '20 at 12:46
  • Yes, but how do you know how wide to make the image view? Or how to position the text so that the cells line up together? – rickharrison Apr 09 '20 at 19:43
  • That's generally done with auto-layout... I don't really understand your comment: *"especially without the use of auto layout"*? – DonMag Apr 09 '20 at 19:47
  • Are you planning on designing your cell as a Storyboard Prototype? Or via code only? – DonMag Apr 09 '20 at 19:51
  • I am doing it in code only. Using `layoutSubviews` for positioning – rickharrison Apr 09 '20 at 21:14
  • There has to be some amount of width given to the icons, because each table view cell is encapsulated yes? Is it possible to get the max width of a symbol at a point size/scale? – rickharrison Apr 09 '20 at 21:15

2 Answers2

1

Here is a very simple example.

It loops through the data to determine the widest symbol that will be used to set the imageView's width constraint constant.

class MySymbolCell: UITableViewCell {

    let symbolImageView: UIImageView = {
        let v = UIImageView()
        v.contentMode = .center
        return v
    }()
    let theLabel: UILabel = {
        let v = UILabel()
        return v
    }()

    var imageWidthConstraint: NSLayoutConstraint!

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        commonInit()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }

    func commonInit() -> Void {

        symbolImageView.translatesAutoresizingMaskIntoConstraints = false
        theLabel.translatesAutoresizingMaskIntoConstraints = false

        contentView.addSubview(symbolImageView)
        contentView.addSubview(theLabel)

        // this way we can change imageView width at run-time if needed
        imageWidthConstraint = symbolImageView.widthAnchor.constraint(equalToConstant: 40.0)
        imageWidthConstraint.priority = UILayoutPriority(rawValue: 999)

        let g = contentView.layoutMarginsGuide

        NSLayoutConstraint.activate([

            // constrain imageView top / bottom / leading
            symbolImageView.topAnchor.constraint(equalTo: g.topAnchor),
            symbolImageView.bottomAnchor.constraint(equalTo: g.bottomAnchor),
            symbolImageView.leadingAnchor.constraint(equalTo: g.leadingAnchor),

            // constrain width
            imageWidthConstraint,

            // constrain height equal to width
            symbolImageView.heightAnchor.constraint(equalTo: symbolImageView.widthAnchor),

            // constrain label leading 8-pts from image view
            theLabel.leadingAnchor.constraint(equalTo: symbolImageView.trailingAnchor, constant: 8.0),

            // constrain label centerY to image view centerY
            theLabel.centerYAnchor.constraint(equalTo: symbolImageView.centerYAnchor),

            // constrain label trailing to content view trailing
            theLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor),

        ])
    }
}

class MySymbolTableViewController: UITableViewController {

    var myData: [String] = [
        "arrow.up",
        "arrow.down",
        "person.badge.plus",
        "eye.slash",
        "line.horizontal.3",
        "textformat.123",
    ]

    var symbolImageViewWidth: CGFloat = 40.0

    // configure as desired
    let imageConfig = UIImage.SymbolConfiguration(pointSize: 20, weight: .light, scale: .default)

    override func viewDidLoad() {
        super.viewDidLoad()

        // calculate how wide you need your cell's symbol image view to be

        // either run a loop to get the widest symbol your data is using,
        //  or
        // just make sure you can fit the widest of the symbols ("bold.italic.underline")

        let testLoop = true

        if testLoop {

            var maxW: CGFloat = 0.0
            myData.forEach {
                if let img = UIImage(systemName: $0, withConfiguration: imageConfig) {
                    maxW = max(img.size.width, maxW)
                }
            }
            symbolImageViewWidth = maxW

        } else {

            if let img = UIImage(systemName: "bold.italic.underline", withConfiguration: imageConfig) {
                symbolImageViewWidth = img.size.width
            }

        }

        tableView.register(MySymbolCell.self, forCellReuseIdentifier: "MySymbolCell")

    }

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

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

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "MySymbolCell", for: indexPath) as! MySymbolCell

        let img = UIImage(systemName: myData[indexPath.row], withConfiguration: imageConfig)

        cell.symbolImageView.image = img
        cell.theLabel.text = myData[indexPath.row]

        cell.imageWidthConstraint.constant = symbolImageViewWidth

        return cell
    }

}

Result:

enter image description here

DonMag
  • 44,662
  • 5
  • 32
  • 56
  • Thanks for the help here! Is `bold.italic.underline` the widest symbol? – rickharrison Apr 10 '20 at 20:29
  • Yes - at least for now. I suppose it's possible that in a future update Apple could add a new symbol, but my assumption is that you know what symbols (or at least what set of symbols) you'll be using. – DonMag Apr 10 '20 at 20:36
0

It might be easier to adjust cell.separatorInset

override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        cell.separatorInset = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 0)
}
grep
  • 535
  • 5
  • 16