When using UIListContentConfiguration
, if you provide an SF Symbol image
, the system decides how to size it based on your dynamic type text size. reservedLayoutSize
documentation states: "Symbol images will be centered inside a predefined reservedLayoutSize
that is scaled with the content size category." Also note with Mac Catalyst the image size is determined by your setting in System Preferences > General > Sidebar icon size.
In my app I want to create a sidebar with cells like the Photos app - an album cell has a photo for the image rather than a symbol. To do this, I need to know what size to make the image to ensure it's the same size as symbols in other cells, since the size of the cell's image view is determined by the size of the image you provide. "Non-symbol images will use a reservedLayoutSize
equal to the actual size of the displayed image."
How do you get the predefined system reserved layout size?
UIListContentConfiguration.ImageProperties.standardDimension
sounded promising but this is just a constant that informs the system to use its default size, rather than exposing that size - its value is -1.7976931348623157e+308.
contentConfiguration.imageProperties.reservedLayoutSize
is CGSize.zero
by default which means the default size is used. So how do we get that size?
Normal usage as per docs:
You cannot get the dimension, but you can use it to set the reservedLayoutSize height OR width. You then have to size your image based on the current text size. This takes into account the text size options and accessibility options. This is the normal way to use it:
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
let font = UIFont.preferredFont(forTextStyle: .body)
let currentFontHeight = font.lineHeight
let originalImage = UIImage(named: "ImageNameInAsset")!
let scaleFactor = currentFontHeight / originalImage.size.height
let scaledImage = originalImage.resized(scaleFactor: scaleFactor)
var config = cell.defaultContentConfiguration()
config.image = scaledImage
config.text = "Text"
config.imageProperties.reservedLayoutSize.width = UIListContentConfiguration.ImageProperties.standardDimension
config.imageProperties.reservedLayoutSize.height = UIListContentConfiguration.ImageProperties.standardDimension
config.imageProperties.cornerRadius = 3
cell.contentConfiguration = config
return cell
Retrieving the actual width and height:
If you want to retrieve the actual width and height of an image and work from there, you have to work-around the strange behaviour of the UIListContentConfiguration.ImageProperties.standardDimension, I created this class to layout a custom icon image in the same way as an SF Symbol is layed out.
The trick is to set a "clear" SF Symbol as the image in the contentConfiguration and then setting a custom image. The custom image then conforms to the constraints provided by the imageLayoutGuide inside the UIListContentView to match the layout. It centers the custom image.
Maybe it's also possible to create an own contentConfiguration class, but this works for me now. I hope it's elegant enough to work around your problem. It even scales with different text sizes or accessibility options.
Here's how to use it:
// In the tableview setup:
tableView.register(CustomImageViewCell.self, forCellReuseIdentifier: "customImageViewCell")
// When dequeuing a cell
let cell = tableView.dequeueReusableCell(withIdentifier: "customImageViewCell", for: indexPath) as! CustomImageViewCell
var config = cell.defaultContentConfiguration()
let imageConfig = UIImage.SymbolConfiguration.init(hierarchicalColor: .clear)
let image = UIImage(systemName: "calendar")?.withConfiguration(imageConfig)
config.image = image
config.text = "Label"
cell.contentConfiguration = config
// This image will get overlayed in the custom class later on
cell.setCustomImage(UIImage(named: "YourAssetName"))
return cell
Here's the custom class:
class CustomImageViewCell: UITableViewCell {
private let customImageView: UIImageView = {
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.layer.cornerRadius = 3
imageView.clipsToBounds = true
return imageView
}()
private var didSetupConstraints = false
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func willMove(toSuperview newSuperview: UIView?) {
super.willMove(toSuperview: newSuperview)
if !didSetupConstraints {
if let contentView = contentView as? UIListContentView,
let imageLayoutGuide = contentView.imageLayoutGuide {
contentView.addSubview(customImageView)
NSLayoutConstraint.activate([
customImageView.topAnchor.constraint(equalTo: imageLayoutGuide.topAnchor),
customImageView.bottomAnchor.constraint(equalTo: imageLayoutGuide.bottomAnchor),
customImageView.centerXAnchor.constraint(equalTo: imageLayoutGuide.centerXAnchor),
customImageView.widthAnchor.constraint(equalTo: imageLayoutGuide.heightAnchor)
])
didSetupConstraints = true
}
}
}
func setCustomImage(_ image: UIImage?) {
customImageView.image = image
}
}