Set rounded corners on UIimage in UICollectionViewCell in swift - ios

I have a simple problem that I cannot find a solution to on google, the docs or here.
I have a Collectionview in my view controller. I have created a custom cell, DescriptionCell, that contains a UIImage. I want this image to have rounded corners. However, I don't know where to set the cornerradius on the UIImage layer. I have tried in the cells' awakeFromNib method, in the delegate method CellForRowAtIndexPath and overriden LayoutSubview in the cell but it does not work.
Where should I put the code to set the radius for the UIImage?
To specify, I know how to create rounded corners of a UIImage. But if it is a subview of a Collectionview cell, I do not know where to set the cornerradius.
Here is code for my descriptionCell
class DescriptionCell: UICollectionViewCell {
#IBOutlet weak var mImage: UIImageView!
override func awakeFromNib() {
//mImage.layer.cornerradius = 5
//Does not work, the image is still square in the cell
}
And in the cellforRowAtIndePath
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
var cell = collectionView.dequeueReusableCellWithReuseIdentifier("descriptioncell", forIndexPath: indexPath) as! DescriptionCell
//cell.mImage.layer.cornerradius = 5
//Does not work, the image is still square in the cell
return cell
}
Thanks in advance!

Well you're using part of the code from the answer you said you were using.
the other part is imageView.clipsToBounds = true
Update your awakeFromNib like this:
override func awakeFromNib() {
mImage.layer.cornerRadius = 5
mimage.clipsToBounds = true
}
To make it a circle you need to set cornerRadius to half of the square height. In your cellForItemAtIndexPath add these lines:
cell.layoutIfNeeded()
cell.mImage.layer.cornerRadius = cell.mImage.frame.height/2
Update
To avoid layoutSubviews from being called twice, override layoutSubviews in your DescriptionCell class and put the code there:
override func layoutSubviews() {
super.layoutSubviews()
layoutIfNeeded()
mImage.layer.cornerRadius = mImage.frame.height/2
}

Have you tried placing it inside the custom UICollectionViewCell's init function?
override init(frame: CGRect) {
super.init(frame: frame)
image.layer.masksToBounds = true
image.layer.cornerRadius = 10
}

You can also create an extension like:
extension UIView {
func addBorder(color: UIColor, cornerRadius: CGFloat = 10, borderWidth: CGFloat = 1.0) {
layer.borderWidth = borderWidth;
layer.borderColor = color.cgColor
layer.cornerRadius = cornerRadius
layer.masksToBounds = true
}
}

Related

Nested UIView inside UICollectionViewCell not updating

I'm trying to display a list of recepcies for at pet proyect, which I've modeled with a UICollectionView and for each item, a custom UICollectionViewCell.
This UICollectionViewCell has inside a UIImageView and a custom UIView for layout pruposes.
The tree goes like this
HomeViewController (UIViewController)
└── UICollectionView
└── RecepieCardCollectionViewCell (UICollectionViewCell)
├── UIImageView
└── RecepieCardInfoView (UIView)
└── UILabel
I'm comforming to the UICollectionViewDelegate and UICollectionViewDataSource protocols at the HomeViewController, and then I'm configuring the cells and their nested UIViews though a configure method
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(
withReuseIdentifier: RecepieCardCollectionViewCell.identifier, for: indexPath
) as? RecepieCardCollectionViewCell else {
return UICollectionViewCell()
}
cell.configure(self.recepies[indexPath.row])
return cell
}
Then, at the RecepieCardCollectionViewCell:
func configure(_ recepie: Recepie) {
cardInfoView.configure(recepie)
thumbnail.kf.setImage(with: URL(string: recepie.getCover()))
}
And, finally, at the RecepieCardInfoView
func configure(_ recepie: Recepie) {
label.text = recepie.getName()
}
The thing is that the most deep nested UILabel at the RecepieCardInfoView, is receiving the data, but is not being updated. It always shows the same placeholder text, instead of the actual recepie name.
Things I've tried:
Placing .setNeedsDisplay() to all the element, with no results.
Using DispatchQueue.main.async but didn't work, which makes sense since I'm not using an API request to show the recepies (for now).
Move the label inside RecepieCardInfoView to the RecepieCardCollectionViewCell. This worked for some reason, but I would like to understand why.
If you need more context of the code, you can find the full repository here at the branch feat/recepies-list
I've been asked to add the full code of the cell, here it is:
import UIKit
import Kingfisher
class RecepieCardCollectionViewCell: UICollectionViewCell {
static let identifier = "RecepieCardUICollectionViewCell"
let thumbnail: UIImageView = {
let image = UIImageView()
image.translatesAutoresizingMaskIntoConstraints = false
image.kf.setImage(with: URL(string: "https://i.imgur.com/ISxVZHA.png"))
return image
}()
let content: UIView = {
let view = UIView(frame: .zero)
view.clipsToBounds = true
view.layer.cornerRadius = 40
return view
}()
let cardInfoView = RecepieCardInfoView(frame: .zero)
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .themeWhite
layer.cornerRadius = 40
addSubview(content)
content.frame = bounds
content.addSubview(thumbnail)
let cardInfoView = RecepieCardInfoView(frame: .zero)
content.addSubview(cardInfoView)
layer.shadowColor = UIColor.black.cgColor
layer.shadowOpacity = 0.17
layer.shadowOffset = .zero
layer.shadowRadius = 10
NSLayoutConstraint.activate([
thumbnail.leadingAnchor.constraint(equalTo: content.leadingAnchor),
thumbnail.trailingAnchor.constraint(equalTo: content.trailingAnchor),
thumbnail.topAnchor.constraint(equalTo: content.topAnchor),
thumbnail.heightAnchor.constraint(equalTo: thumbnail.widthAnchor),
cardInfoView.topAnchor.constraint(equalTo: content.bottomAnchor, constant: -100),
cardInfoView.bottomAnchor.constraint(equalTo: content.bottomAnchor),
cardInfoView.leadingAnchor.constraint(equalTo: content.leadingAnchor),
cardInfoView.trailingAnchor.constraint(equalTo: content.trailingAnchor),
])
}
required init(coder: NSCoder) {
fatalError()
}
func configure(_ recepie: Recepie) {
cardInfoView.configure(recepie)
thumbnail.kf.setImage(with: URL(string: recepie.getCover()))
}
}
Thanks you in advance!
You create 2 instances of cardInfoView inside RecepieCardCollectionViewCell
let cardInfoView = RecepieCardInfoView(frame: .zero) // here 1
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .themeWhite
layer.cornerRadius = 40
addSubview(content)
content.frame = bounds
content.addSubview(thumbnail)
let cardInfoView = RecepieCardInfoView(frame: .zero) // and here 2
The problem is that you add the inner view without updating it's content , and update the outer view without adding it to cell hierarchy

UIImage circle in UITableViewCell

So I'm following the the normal approach in Turing an image into circle :
image.layer.cornerRadius = image.frame.size.width/2.0f;
image.layer.borderColor = [UIColor blackColor].CGColor;
image.layer.borderWidth = 2;
image.clipsToBounds = YES ;
However when I used it in cells, first time it shows in Simi-perfect circle, but when I scroll to show new cells , all will be in perfect circle shape.
so my question is : why the first visible cells appear in a Simi-cirlce shape ?
This is how it looks like first time , but if I refresh or reload the page, everything is fine
Try to use this way
like in custom cell class
class ProfilePicCell: UITableViewCell {
#IBOutlet weak var image: UIImageView!
override func awakeFromNib() {
super.awakeFromNib()
image.layer.cornerRadius = image.frame.width / 2
image.layer.masksToBounds = true
}
}
in Objective C add this method in custom cell class
- (void)awakeFromNib
{
super.awakeFromNib()
image.layer.cornerRadius = image.frame.width / 2
}
Override layoutSubviews in the cell and after calling super.layoutSubviews update the image.layer.cornerRadius there. Then when the layout of the cell is updated after the cell gets visible, the corner radius will be updated accordingly.
So in swift, in your cell implementantion you would do something like:
override func layoutSubviews() {
super.layoutSubviews()
// now the frames were recalculated, and we can update cornerRadius
image.layer.cornerRadius = image.bounds.size.width / 2.0
}

How to determine the "z-index of a shadow"

I'm sorry for the title, can not synthesize differently the issue. Here's the problem:
In my UICollectionView have some cells that want to put a shadow, they are arranged very close to each other which makes the shadow of each one reach the neighbor (first image), when what I want, is that it only reaches the background (second image).
What I've thought or tried:
I can not put a view behind the cells, adding their frames to it, and apply shadow in this view because the cells has dynamic movement (UIDynamics CollectionView Layout).
I tried, in the subclass of UICollectionViewLayout, put all these cells in the same z-index. Did not work. Find out why:
var zIndex: Int
(...) Items with the same value have an undetermined
order.
I would like some help with my problem, please. Thanks!
From the TheEye response, I decided to implement UIDecorationViews. All great now.
// MARK: At UICollectionViewCustomLayout:
public override init() {
super.init()
// Register the NIB of the view that will hold the shadows:
let nib = UINib(nibName: "Shadow", bundle: nil)
self.registerNib(nib, forDecorationViewOfKind: "shadow")
}
public override func layoutAttributesForDecorationViewOfKind(elementKind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? {
let layoutAtt: UICollectionViewLayoutAttributes = UICollectionViewLayoutAttributes(forDecorationViewOfKind: "shadow", withIndexPath: indexPath)
layoutAtt.frame = (layoutAttributesForItemAtIndexPath(indexPath)?.frame)!
layoutAtt.zIndex = -1
return layoutAtt
}
public override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var atts = [UICollectionViewLayoutAttributes]()
let numberOfItems:Int = (self.collectionView?.numberOfItemsInSection(0))!
for index in 0..<numberOfItems {
let layoutItem = layoutAttributesForItemAtIndexPath(NSIndexPath(forItem: index, inSection: 0))!
let frameItem = layoutItem.frame
if CGRectIntersectsRect(frameItem, rect) {
atts.append(layoutAttributesForDecorationViewOfKind("shadow", atIndexPath: NSIndexPath(forItem: index, inSection: 0))!)
atts.append(layoutItem)
}
}
return atts
}
// MARK: At the Nib Class File:
// Here I created a additional view to append the shadow.
// Thats because awakeFromNib() is called before the UICollectionViewCustomLayout
// have a chance to layout it, but I want to make
// use of shadowPath to gain performance, thats why
// I make use of the additional UIView with autolayout stuffs.
#IBOutlet weak var shadowView: UIView!
override func awakeFromNib() {
super.awakeFromNib()
shadowView.layer.shadowOffset = CGSizeMake(0, 0)
shadowView.layer.shadowColor = UIColor.yellowColor().CGColor
shadowView.layer.shadowRadius = 3
shadowView.layer.shadowOpacity = 1
let shadowFrame: CGRect = shadowView.layer.bounds
let shadowPath: CGPathRef = UIBezierPath(rect: shadowFrame).CGPath
shadowView.layer.shadowPath = shadowPath
shadowView.clipsToBounds = false
self.clipsToBounds = false
}
You could try to define the shadows as supplementary views, aligned with their respective cells, and give them a lower z order than the cells.

Image not showing up in my UICollectionView in Swift

My images not showing up in my UICollectionView. I'm not sure what is the mistake. Below is my code.
MyCollectionView.swift
/** -- **/
overide func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("MyCollectionViewCell", forIndexPath: indexPath) as! MyCollectionViewCell
let event = events[indexPath.row]
cell.imageView?.image = UIImage(named: "blabla_iPad#2x")
return cell
}
/** -- **/
MyCollectionViewCell.swift
import UIKit
class MyCollectionViewCell: UICollectionViewCell {
var imageView: UIImageView!
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(frame: CGRect) {
super.init(frame: frame)
imageView = UIImageView(frame: CGRect(x: 0, y: 16, width: frame.size.width, height: frame.size.height))
imageView.contentMode = UIViewContentMode.ScaleAspectFit
contentView.addSubview(imageView)
}
}
Even I'm using localPath, it's still not working.
/*
/Users/MNurdin/Library/Developer/CoreSimulator/Devices/B30917DD-1C57-430F-9ACF-6526B226CF15/data/Containers/Data/Application/35ACB17A-4C25-4A49-BE3D-04AA51F36CC0/tmp/jreGfApABajWPCnkfPBh.jpg
*/
cell.imageView?.image = UIImage(data: NSData(contentsOfURL: NSURL(fileURLWithPath:localPath)!)!)
var imageView: UIImageView!
this should be an IBOutlet.
#IBOutlet weak var imageView: UIImageView!
And connect it to your storyboard.
Its quite the head scratcher. I don't know exactly whats wrong here, but here are a few tips i try myself to find whats happening
After:
cell.imageView?.image = UIImage(named: "blabla_iPad#2x")
print(cell.imageView?.image.bounds.size)
cell.imageView?.layer.borderWidth = 1.0
cell.imageView?.layer.borderColor = UIColor.blackColor().CGColor
u will see one of these things now:
1. same as before, this means u r drawing your image outside the cell. try changing the frame.
2. black bordered empty square: means something is wrong with your image
3. the frame printed has zero size. Assign the imageView frame equal to the bounds of the image.
Are you sure iamge is in Xcassets or in the top bundle.Save your image as "ImageName~ipad.png". It is just an identifier to ios to use this image on i-Pad. Put the image in Xcassets. Everything will work fine then.
And also put some log message in your custom cell class initializer to check the control flow,whether something is wrong with your custom class.
Have you registered your custom class to be used with this cell identifier in viewDidLoad?
You mentioned that you are loading your cell from your storyboard. When loading from storyboards, the init(frame: CGRect) method in your MyCollectionViewCell class won't get called, which means your code to add your custom UIImageView to the cell's contentView never gets called too.
Hence, the problem isn't to do with the image not loading into the UIImageView, but is more to do with the entire UIImageView not getting added into the cell at all.
Try moving that to the awakeFromNib() instead
I had the same issue. Only happened on xib files I opened after switching to Swift 3. I disabled Clip To Bounds on any UIImageView in the xib and I can see the images again, but now my cornerRadius settings don't work.
Also had an issue when setting cornerRadius based on view size in awakeFromNib because xib views default with a frame size of (1000,1000).

Subview frame is incorrect when creating UICollectionViewCell

The problem
I created a UICollectionViewController with a custom UICollectionViewCell.
The custom cell contains a large and rectangular UIView (named colorView) and a UILabel (named nameLabel).
When the collection is first populated with its cells and I print colorView.frame, the printed frames have incorrect values. I know they are incorrect, because the colorView frames are larger than the cell frame themselves, even though the colorView gets drawn correctly.
However, if I scroll the collectionView enough to trigger a reuse of a previously created cell, the colorView.frame now has correct values!
I need the correct frames because I want to apply rounded corners to the colorView layer and I need the correct coloView size in order to do this.
By the way, in case you are wondering, colorView.bounds also has the same wrong size value as the colorView.frame.
The question
Why are the frames incorrect when creating the cells?
And now some code
This is my UICollectionViewCell:
class BugCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var colorView: UIView!
#IBOutlet weak var nameLabel: UILabel!
}
and this is the UICollectionViewController:
import UIKit
let reuseIdentifier = "Cell"
let colors = [UIColor.redColor(), UIColor.blueColor(),
UIColor.greenColor(), UIColor.purpleColor()]
let labels = ["red", "blue", "green", "purple"]
class BugCollectionViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return colors.count
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as BugCollectionViewCell
println("ColorView frame: \(cell.colorView.frame) Cell frame: \(cell.frame)")
cell.colorView.backgroundColor = colors[indexPath.row]
cell.nameLabel.text = labels[indexPath.row]
return cell
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
let width = self.collectionView?.frame.width
let height = self.collectionView?.frame.height
return CGSizeMake(width!, height!/2)
}
}
The collection view is setup in order to show two cells at a time, vertically, each cell containing a large rectangle painted with a color and a label with the color name.
When I just run the above code on the simulator, I get the following printed result:
ColorView frame: (0.0,0.0,320.0,568.0) Cell frame: (0.0,0.0,375.0,333.5)
ColorView frame: (0.0,0.0,320.0,568.0) Cell frame: (0.0,343.5,375.0,333.5)
It is a weird result - colorView.frame has a height of 568 points, while the cell frame is only 333.5 points tall.
If I drag the collectionView down and a cell gets reused, the following result is printed:
ColorView frame: (8.0,8.0,359.0,294.0) Cell frame: (0.0,1030.5,375.0,333.5)
ColorView frame: (8.0,8.0,359.0,294.0) Cell frame: (0.0,343.5,375.0,333.5)
Something, which I can’t understand, happened along the way that corrects the frame of colorView.
I think it has something to do with the fact that the cell is loaded from the Nib, so instead of using the init(frame: frame) initializer the controller uses the init(coder: aCoder) initializer, so as soon as the cell is created it probably comes with some default frame which I can't edit anyhow.
I’ll appreciate any help that allows me to understand what is happening!
I am using Xcode 6.1.1. with the iOS SDK 8.1.
You can get the final frames of your cell by overriding layoutIfNeeded() in your custom Cell class like this:
override func layoutIfNeeded() {
super.layoutIfNeeded()
self.subView.layer.cornerRadius = self.subView.bounds.width / 2
}
then in your UICollectionView data Source method cellForRowAtIndexPath: do this:
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! CustomCollectionViewCell
cell.setNeedsLayout()
cell.layoutIfNeeded()
I had the same issue with a UICollectionViewCell using auto layout constraints.
I had to call layoutIfNeeded before I was configuring my subview that relied on the views frame width.
Had this issue with Core Graphics drawing in iOS 10, Swift 3.0.1.
Add this method to UICollectionView subclass:
override func didMoveToSuperview() {
super.didMoveToSuperview()
setNeedsLayout()
layoutIfNeeded()
}
My problem was that Core Graphics shapes were not calculated properly, because a layoutSubviews() wasn't called.
Ok, I understand now that the cell is created before auto layout defines its frames. That is the reason why at the moment of creation the bounds are wrong. When the cells are reused the frames have been already corrected.
I was having this problem while creating a custom UIView that placed some layers and subviews in specific coordinates. When instances of this UIView were created, the placement of the subviews were all wrong (because auto layout hadn't kick off yet).
I found out that instead of configuring the view subviews on init(coder: aCoder) I had to override the method layoutSubviews(). This is called when auto layout asks each view to layout its own subviews, so at this point at least the parent view has the correct frame and I can use it for laying the subviews correctly.
Probably if I had used constraints on the custom view code instead of dealing myself with frame sizes and positioning then the layout would have been done properly and it wouldn't be necessary to override layoutSubviews().
I'd suggest making a subclass of whatever you're doing. I needed a gradient over an UIImageView in my cell and it was calculating it wrongly. I tried the suggestion with layoutSubviews but it was also causing issues where it seems like it would apply gradient twice.
I made a UIImageView subclass and it works as wanted.
class MyOwnImageView: UIImageView{
override func layoutSubviews() {
super.layoutSubviews()
let view = UIView(frame: frame)
let width = bounds.width
let height = bounds.height
let sHeight:CGFloat = 122.0
let shadow = UIColor.black.withAlphaComponent(0.9).cgColor
let topImageGradient = CAGradientLayer()
topImageGradient.frame = CGRect(x: 0, y: 0, width: width, height: sHeight)
topImageGradient.colors = [shadow, UIColor.clear.cgColor]
view.layer.insertSublayer(topImageGradient, at: 0)
let bottomImageGradient = CAGradientLayer()
bottomImageGradient.frame = CGRect(x: 0, y: height - sHeight, width: width, height: sHeight)
bottomImageGradient.colors = [UIColor.clear.cgColor, shadow]
view.layer.insertSublayer(bottomImageGradient, at: 0)
addSubview(view)
bringSubviewToFront(view)
}
}

Resources