systemImage Configuration Size

Hello, I have been having trouble with configuring the systemImage size. I took a number of approaches but couldn't manage to set a size to the systemImage within the collectionView cell. Because of this, the text which is supposed to be below the image, clashes with the image due to its large size within the cell. The last approach I took with the code I'm sharing, I get an empty cell. Otherwise, I can't find a way to change the systemImage size. Could you please advise me regarding the configuration of systemImage ?


Many Thanks.


func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    if collectionView == collectionViewEffects {
      let cellEffects = collectionView.dequeueReusableCell(withReuseIdentifier: "EffectsCell", for: indexPath) as! CustomEffectsCell
      collectionView.backgroundColor = .systemGray6
      if indexPath.row == 0 {
        cellEffects.effectsLabel = "A"
       
        let config = UIImage.SymbolConfiguration(pointSize: 0, weight: .ultraLight)
        let cellImage = UIImage(systemName: "slider.horizontal.3", withConfiguration: config)
        let width = ((cellImage?.size.width)!)/4
        let height = ((cellImage?.size.height)!)/4
        guard let context = UIGraphicsGetCurrentContext() else {
          print("could not get graphics context")
          return cellEffects
        }
        context.setStrokeColor(UIColor.yellow.cgColor)
        context.setLineWidth(2)
        context.stroke(cellImage?.cgImage as! CGRect)
        cellImage?.draw(in: CGRect(x: 0, y: 0, width: width, height: height))
       
        cellEffects.effectsImage = cellImage
        self.view.addSubview(cellEffects)
       
      }

Accepted Reply

I'm not sure if "return UICollectionViewCell()" was also relevant in line 22.

The line is needed just to silence the compiler. You need to return someting of type UICollectionViewCell, in case `collectionView !== collectionViewEffects`. Even if that would never happen, compiler needs it.


I'm getting are (57.0, 44.6). I'm roughly aiming at (15.0, 15.0).

You can control the size of the image by changing the pointSize passed to UIImage.SymbolConfiguration.

                let config = UIImage.SymbolConfiguration(pointSize: 15, weight: .ultraLight)


I guess the reason you suggested me to use 2 different settings for the cell is to have a different size for the backgroundColor.

Size for the background color may be one of the different settings, there may be others.

As cells are reused among the same Reuse Identifer cells, you need to consider the possibitily that the row-0-setting cell may be reused for non-row-0 cells.


Please tell me when you find something unclear in your next steps.

Replies

Have you defined a xib for the cell ?


If so, can set constraints for the UIImageView, in order to leave room below for text ?

Hello Claude, thank you for your revert.


I have created the collectionView programmatically, I'm not sure if I can define a xib for the cell in this case.

Yes you can.


Don't forget to register the cell:

collectionView.register(
     Cell.self,
     forItemWithIdentifier: NSUserInterfaceItemIdentifier(rawValue: "Cell")
)


Get details here

h ttps://github.com/onmyway133/blog/issues/131

It was conceptually helpful but considering almost all the code being deprecated and the errors I'm getting, is there a more feasible way to adjust the "cellImage" size within the indexPath.row block ? Regarding the code I have shared, I think I'm pretty close to the solution.

When you want to show UIImage in a UIImageView with a specific size, you usually set the size (`frame` and constraints, and some other settings) of the UIImageView.

Even if you successfully create a resized image, UIImageView will stretch the image to fit the size settings.


Please show your definition of `CustomEffectsCell`.

Hello OOPer, thank you for your response. I have the constraints of the UIImageView defined but I think I also need to define the size within the "override init" block. Here is the code you have requested;


class CustomEffectsCell: UICollectionViewCell {
 
  fileprivate let effectsImg: UIImageView = {
    let iv = UIImageView()
    iv.translatesAutoresizingMaskIntoConstraints = false
    iv.contentMode = .scaleAspectFit
    iv.clipsToBounds = true
    return iv
  }()
 
  var effectsImage: UIImage? {
    get {
      effectsImg.image
    }
    set {
      effectsImg.image = newValue
    }
  }
 
  fileprivate let effectsLbl: UILabel = {
    let il = UILabel(frame: CGRect(x: 0, y: 0, width: 100, height: 20))
    il.translatesAutoresizingMaskIntoConstraints = false
    il.contentMode = .scaleAspectFit
    il.clipsToBounds = true
    return il
  }()
 
  var effectsLabel: String? {
    get {
      effectsLbl.text
    }
    set {
      effectsLbl.text = newValue
    }
  }
 
  override init(frame: CGRect) {
    super.init(frame: frame)
   
    contentView.addSubview(effectsImg)
    contentView.addSubview(effectsLbl)
   
    effectsImg.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
    effectsImg.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
    effectsImg.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
    effectsImg.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
   
    effectsLbl.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
  }
 
  required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }
}

Thanks for showing your code, and it explains everything you experienced with your current code.

With these constrains:


    effectsImg.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true  
    effectsImg.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true  
    effectsImg.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true  
    effectsImg.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true 

all the four edges of your imageView (`effectsImg`, which is not a good name for imageView) are bound to the edges of `contentView`.

which means the imageView occupies whole region of the content of the cell.


And another setting for the imageView is so important:

    il.contentMode = .scaleAspectFit 

that, even if you create a very small image, the imageView displays it with scaling to fill the size of the imageView itself.


Maybe, the first step (there may be a few more) to make your cell display the image as you expect, would be changing the `contentMode`.

(Rather thant setting size constraint.)


class CustomEffectsCell: UICollectionViewCell {
    
    let effectsImageView: UIImageView = { //<- Remove `fileprivate`, and give it a better name
        let iv = UIImageView()
        iv.translatesAutoresizingMaskIntoConstraints = false
        iv.contentMode = .scaleAspectFit
        iv.clipsToBounds = true
        return iv
    }()
    
    var effectsImage: UIImage? {
        get {
            effectsImageView.image
        }
        set {
            effectsImageView.image = newValue
        }
    }
    
    //...

    override init(frame: CGRect) {
        super.init(frame: frame)
        
        contentView.addSubview(effectsImageView)
        contentView.addSubview(effectsLbl)
        
        effectsImageView.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
        effectsImageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
        effectsImageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
        effectsImageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
        
        effectsLbl.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
    }
    

    //...

}


And your `collectionView(_:cellForItemAt:)` would be simplified as:

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        if collectionView === collectionViewEffects {
            if indexPath.row == 0 {
                let cellEffects = collectionView.dequeueReusableCell(withReuseIdentifier: "EffectsHeaderCell", for: indexPath) as! CustomEffectsCell
                cellEffects.effectsLabel = "A"
                
                let config = UIImage.SymbolConfiguration(pointSize: 48, weight: .ultraLight)
                guard let cellImage = UIImage(systemName: "slider.horizontal.3", withConfiguration: config) else {
                    print("SystemImage not available")
                    return cellEffects
                }
                print(cellImage.size)
                cellEffects.effectsImageView.contentMode = .topLeft
                cellEffects.effectsImage = cellImage
                return cellEffects
            } else {
                let cellEffects = collectionView.dequeueReusableCell(withReuseIdentifier: "EffectsCell", for: indexPath) as! CustomEffectsCell
                cellEffects.backgroundColor = .systemGray6
                return cellEffects
            }
        }
        return UICollectionViewCell()
    }

You should better use two different Reuse Identifier when you have two different settings for the cell.

Please do not forget to add another `register` for the new Reuse Identifier.

        collectionViewEffects.register(CustomEffectsCell.self, forCellWithReuseIdentifier: "EffectsHeaderCell")

Please try and tell me what you get.

What is the problem here ? Once you've created the xib, set the constraints for image and its drawing mode, registered the xib, it should work pretty straight, isn't it the case ?

Thank you for your comprehensive guidance. I've followed your steps, also cancelled out the constraints of the "effectsImageView" as they aren't relevant anymore. I'm not sure if "return UICollectionViewCell()" was also relevant in line 22. The values for "cellImage.size" I'm getting are (57.0, 44.6). I'm roughly aiming at (15.0, 15.0). I guess the reason you suggested me to use 2 different settings for the cell is to have a different size for the backgroundColor.

I'm not sure if "return UICollectionViewCell()" was also relevant in line 22.

The line is needed just to silence the compiler. You need to return someting of type UICollectionViewCell, in case `collectionView !== collectionViewEffects`. Even if that would never happen, compiler needs it.


I'm getting are (57.0, 44.6). I'm roughly aiming at (15.0, 15.0).

You can control the size of the image by changing the pointSize passed to UIImage.SymbolConfiguration.

                let config = UIImage.SymbolConfiguration(pointSize: 15, weight: .ultraLight)


I guess the reason you suggested me to use 2 different settings for the cell is to have a different size for the backgroundColor.

Size for the background color may be one of the different settings, there may be others.

As cells are reused among the same Reuse Identifer cells, you need to consider the possibitily that the row-0-setting cell may be reused for non-row-0 cells.


Please tell me when you find something unclear in your next steps.

I tried with 1 setting for the cell and the app crashed 🙂 Thank you so much for your guidance, it works perfectly now.