iossf-symbols

How to align UILabels with SF Symbols and custom images in UITableViewCell's imageView?


What I want to achive is something like list view in Files App. Document will display a little thumbnail if has one. Otherwise a SF Symbol with textStyle .callout will show up. All the rows’ labels should be left aligned. But currently the thumbnails are much bigger than the SF Symbols so that they push the labels away.

I emphasize textStyle because my app supports dynamic type, which means the imageView's frame calculation should base on SF Symbol.

I try to override layoutSubviews. But can't figure out how to do the calculation.

Files App

Files App

My App

My App


Solution

  • For maximum control of your cell appearance, you should create a custom cell. But, if you want use a standard Subtitle cell, you can resize your images when setting them.

    There are some excellent image scaling functions in this Stack Overflow answer: how to resize a bitmap on iOS

    Add the extensions from that answer to your project. Specifically, for your needs, we'll use scaledAspectFit().

    So, assuming you have a Subtitle cell prototype, with identifier of "cell", your cellForRowAt will look something like this:

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        
        // however you have your data stored
        let title = "IMG_0001.JPG"
        let subtitle = "Today at 10:15 AM, 2.5 MB"
        let imgName = "IMG_0001"
        
        cell.textLabel?.text = title
        cell.detailTextLabel?.text = subtitle
        
        if let img = UIImage(named: imgName) {
            // default cell image size is 56x56 (points)
            //  so we'll proportionally scale the image,
            //  using screen scale to get the best resolution
            let widthHeight = UIScreen.main.scale * 56
            let scaledImg = img.scaledAspectFit(to: CGSize(width: widthHeight, height: widthHeight))
            cell.imageView?.image = scaledImg
        }
        return cell
    
    }
    

    Edit

    Takes only a little research to implement the use fo SF Symbols...

    Add this func to your table view controller - it will return a UIImage of an SF Symbol at specified point size, centered in specified size:

    private func drawSystemImage(_ sysName: String, at pointSize: CGFloat, centeredIn size: CGSize) -> UIImage? {
        let cfg = UIImage.SymbolConfiguration(pointSize: pointSize)
        guard let img = UIImage(systemName: sysName, withConfiguration: cfg) else { return nil }
        let x = (size.width - img.size.width) * 0.5
        let y = (size.height - img.size.height) * 0.5
        let renderer = UIGraphicsImageRenderer(size: size)
        return renderer.image { context in
            img.draw(in: CGRect(origin: CGPoint(x: x, y: y), size: img.size))
        }
    }
    

    Then change your cellForRowAt func as needed:

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        
        // however you have your data stored
        let title = "IMG_0001.JPG"
        let subtitle = "Today at 10:15 AM, 2.5 MB"
        let imgName = "IMG_0001"
        
        cell.textLabel?.text = title
        cell.detailTextLabel?.text = subtitle
    
        // defalt image view size
        let wh = UIScreen.main.scale * 56
        let targetSize = CGSize(width: wh, height: wh)
    
        // for this example, if it's the first row, generate an image from SF Symbol
        if indexPath.row == 0 {
            if let img = drawSystemImage("doc.text", at: UIScreen.main.scale * 24, centeredIn: targetSize) {
                cell.imageView?.image = img
            }
        } else {
            if let img = UIImage(named: imgName) {
                // default cell image size is 56x56 (points)
                //  so we'll proportionally scale the image,
                //  using screen scale to get the best resolution
                let scaledImg = img.scaledAspectFit(to: targetSize)
                cell.imageView?.image = scaledImg
            }
        }
        return cell
        
    }
    

    I'll leave it up to you to tweak the point size as desired (and configure any other properties of your SF Symbol image such as weight, scale, color, etc).