Collection View Cell subview frame not available until cell is reused - swift

I have collection view cells that contain a subview of which I am trying to get the frame of in relation to the superview.
So far, I've managed to successfully get the frame of the subview, however for the first x amount of visible cells, the returned frame is always (0,0,0,0).
After I scroll them out of view and scroll back to those x cells, the returned frame is fine and the frames for the subviews are computed properly.
Any thoughts?
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let window = UIApplication.shared.keyWindow
let selectedCell = self.collectionView(self.cellCollectionView, cellForItemAt: indexPath) as! postCell
let photoItem = selectedCell.photo
let photoItemPosition = photoItem.frame
let convertedFrame = selectedCell.convert(photoItemPosition, to: window)
print(photoItemPosition)
print(convertedFrame)
}

You have to update the frame of photo instance in layoutSubviews.
Override the layoutSubviews in your collectionView cell class
override func layoutSubviews ()
{
super.layoutSubviews()
//Set the self.photo frame
}

Related

Current coordinates of a tableViewCell swift

fellows!
I'm trying to make an animation of an image in my tableViewCell. My intent is to get an imageView that is in a tableViewCell, add it to exactly the same place in a view, and then use scale animation. Currently, my code looks as below, however, I need to find a cell center point in every period of time (after scrolling too), that I can add image view exactly in the same place. The value of cellRect.origin.y constantly increases, whereas I need it to be exactly center.y point of my tableView cell.
Could you please tell me where my mistake is?
Thank you in advanse!
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath) as! TableViewCell
cell.animateImageView = { [weak self]
guard let self = self else { return }
tableView.layoutSubviews()
var cellRect = self.view.convert(tableView.rectForRow(at: indexPath), to: self.tableView)
cellRect.origin.x = UIScreen.main.bounds.midX - (cell.photoImageView.frame.width / 2)
cellRect.origin.y = cellRect.origin.y + (cell.photoImageView.frame.height / 2)
UIView.animate(withDuration: 3) {
} completion: { isFinished in
}
}
}
If I understand you correctly, this is your goal
you tap on a UITableViewCell which has an imageView
You want to create a new image view with the same image in the same position as the image in the cell you tapped
Then from this position you will make this image full screen with animation
You want to do something after the animation has completed
If yes, there here are my thoughts
I feel this line has the first issue:
var cellRect
= self.view.convert(tableView.rectForRow(at: indexPath),
to: self.tableView)
Let's take a look at the convert function in the docs
Converts a rectangle from the receiver’s coordinate system to that of another view.
Parameters
rect
A rectangle specified in the local coordinate system
(bounds) of the receiver.
view
The view that is the target of the conversion operation. If view
is nil, this method instead converts to window base coordinates.
Otherwise, both view and the receiver must belong to the same UIWindow
object.
The receiver here is your cell's image view which you want to recreate so you should not call self.view.convert, it should be cell.photoImageView.convert
The rect parameter is the bounds of your tapped cell's image view and the to parameter is self.view as you want to convert the image view's coordinates in the cell to the coordinates in the main view of the view controller.
Beyond that I am not sure of the purpose of these as you will get the frame from the above function so I removed them but you can add it back if it makes sense to your application.
cellRect.origin.x
= UIScreen.main.bounds.midX - (cell.photoImageView.frame.width / 2)
cellRect.origin.y
= cellRect.origin.y + (cell.photoImageView.frame.height / 2)
So this is how I changed the function:
func tableView(_ tableView: UITableView,
didSelectRowAt indexPath: IndexPath)
{
// Retrieved the actual cell that was tapped
// same as what you did
let cell
= tableView.cellForRow(at: indexPath) as! TableViewCell
// Here I made a change based on my explanation above
let cellRect =
cell.photoImageView.convert(cell.photoImageView.bounds,
to: view)
// I created a blue view just for demo
let aView = UIView(frame: cellRect)
aView.backgroundColor = .blue
view.addSubview(aView)
}
If you end it here, you will get this result
The blue view added perfectly where we want it
Now to complete the rest with animation is quite simple after we accomplished the tricky part and here is the full function
func tableView(_ tableView: UITableView,
didSelectRowAt indexPath: IndexPath)
{
// Retrieved the actual cell that was tapped
let cell
= tableView.cellForRow(at: indexPath) as! TableViewCell
// Here I made a change based on my explanation above
let cellRect =
cell.photoImageView.convert(cell.photoImageView.bounds,
to: view)
let animateImageView = UIImageView(frame: cellRect)
animateImageView.image = cell.photoImageView.image
animateImageView.clipsToBounds = true
view.addSubview(animateImageView)
UIView.animate(withDuration: 2.0)
{ [weak self] in
if let strongSelf = self
{
animateImageView.frame = strongSelf.view.bounds
}
} completion: { (success) in
if success
{
// animation completed, do what you want
// like push your VC
}
}
}
The end result I believe is what you want
I hope this helps you get you closer to your desired result

SWIFT: How to fix cell highlighter and make the whole collection view scroll instead of the highlighter?

I am building a TVOS app where I have this collectionView:
The current cell (The cell which has scrolled to), gets highlighted in Orange.
For example, here the user scrolls to the third cell:
WHAT I AM TRYING TO ACHIEVE:
When the user scrolls to another cell, I want the Orange square, to remain in the first cell, and the whole collection to scroll to the left(or to the right if the user is scrolling in the opposite direction.
And I honestly have no idea how to achieve this, or what should I use exactly.
Should I embed the whole collection view inside of a scrollView?
Anyway, here's how implemented that collection view:
extension MoviesViewController2: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
print("first function collectionView detected")
return items2.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier:
cellIdentifier, for: indexPath) as! movieCardCell
cell.movieImageView.sd_setImage(with: URL(string: items2[indexPath.item].imageURL))
return cell
}
// Handle collectionViewItem selection
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("didSelectItem\(indexPath)")
}
// Highlight the current cell
func collectionView(_ collectionView: UICollectionView, didUpdateFocusIn context: UICollectionViewFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
if let pindex = context.previouslyFocusedIndexPath, let cell = collectionView.cellForItem(at: pindex) {
cell.contentView.layer.borderWidth = 0.0
cell.contentView.layer.shadowRadius = 0.0
cell.contentView.layer.shadowOpacity = 0.0
}
if let index = context.nextFocusedIndexPath, let cell = collectionView.cellForItem(at: index) {
cell.contentView.layer.borderWidth = 8.0
cell.contentView.layer.borderColor = UIColor.orange.cgColor
cell.contentView.layer.shadowColor = UIColor.orange.cgColor
cell.contentView.layer.shadowRadius = 10.0
cell.contentView.layer.shadowOpacity = 0.9
cell.contentView.layer.shadowOffset = CGSize(width: 0, height: 0)
collectionView.scrollToItem(at: index, at: [.centeredHorizontally, .centeredVertically], animated: true)
}
}
}
If anyone can answer the question above, it'd be great. Also any tip or what I should use is most welcome.
As I understand, you want the orange rectangle to remain always on the same place. When scrolling you can make it disappear and re-appear once the scrolling has stopped maybe but that's a detail.
So you could put a UIView with the same dimensions as a cell (since all seem to have the same height and width) and make it appear once the user has stopped scrolling.
Anyway I'm looking again at your question and I think I didn't understand your issue correctly, specifically this part The current cell (The cell which has scrolled to), gets highlighted in Orange. in combination with this When the user scrolls to another cell, I want the Orange square, to remain in the first cell
The process might be as follows:
Set your collectionView's size to single cell size. And align it to the left
Make your collectionView's clipsToBounds property to false
Make UICollectionView paging enabled.
Place a rectangle view on top of your collectionView

Collectionview scroll cross top uiview

Hello I was using collection-view since year everything was fine , but after update xcode 11 i am facing problem , when i scroll collection-view it cross the boundary of collection-view and cell shows after crossing the top just like in the image please guide me
Code
extension BusinessHomeViewController : UICollectionViewDataSource,UICollectionViewDelegate{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 50
//AppStrings.DashBoardMenu.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "home", for: indexPath) as! BusinessCollectionViewCell
// let model = AppStrings.DashBoardMenu[indexPath.row]
// cell.lblTitle.text = model
return cell
}
Can you check to make sure that your collection view's clipsToBounds property is set to true? You can find this in the interface builder, or set it programmatically via yourCollectionView.clipsToBounds = true.
What clipsToBounds does is determines whether a view's subviews will still be visible, even if they are moved beyond the parent view's bounds. Setting it to false will let the subviews still be visible, even when scrolled beyond the collection view's bounds, so you'll want this property to be set to true.

how to size cell in gridview

I have a gridlist that looks like this
class ListViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout{
private var collection: ACollection?
private var collectionViewLayout:UICollectionViewFlowLayout?
override func loadView() {
super.loadView()
collectionViewLayout = UICollectionViewFlowLayout()
collectionViewLayout?.itemSize.width = 128
collectionViewLayout?.itemSize.height = 227
view = UICollectionView(frame: CGRect.zero, collectionViewLayout: collectionViewLayout!)
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let someObj: SomeObj = collection!.getObject(index: indexPath.item)
let cell: UICollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: NSStringFromClass(UICollectionViewCell.self), for: indexPath as IndexPath)
let cellView: UIImageView = UIImageView(image: object.image)
cellView.frame = cell.bounds
cell.contentView.addSubview(cellView)
return cell
}
I would like my cells to have different aspect ratios though depending on a value in someObj. How can I resize the cells without them overlapping? I have tried to make
cellView.frame = CGRect(x:0,y:0,width:100,height:300)
cellView.frame =cell.bounds
but the cells overlap.
Maybe I'll elaborate a little more about the problem.
First, if you implement collectionView(_:layout:sizeForItemAt:) method, you don't need to specify the following:
collectionViewLayout?.itemSize.width = 128
collectionViewLayout?.itemSize.height = 227
which is a default size for every cell.
Second, overlaps you see on top of every default cell are probably the cellViews (UIImageViews) that you are adding on reused cells. The problem is, you're trying to add UIImageView on the current cell every single time delegate asks for the cell. You'll need to check if the UIImageView is already there, since the entire cell may have been reused still having the image view which added previously.

UICollectionViewCell's subview absolute frame

Have been having this issue that I considered to be an easy solve, but spent a lot of time cracking it with no result.
So, I have a UIViewController with UICollectionView in it. This collection is padded 75 px from the top and has cells with pretty simple setup - just 1 UIImageView set to fit the size of the container. Everything is made with AutoLayout.
My task is to make UIImageView go full screen on tap. What I am doing is making a temporary UIImageView and add to the hierarchy of UIViewController view.
The problem I am facing is I can't get the absolute frame of UIImageView in the cell I tap.
I tried various ways to calculate the frame, my most recent try is
var rect = cell.imageView.superview!.convertRect(cell.imageView.frame, toView: self.view)
However, it returns result that seems to be ignoring the collection view top constraint.
Update:
It turns out my problem was in the wrong way I was acquiring the cell from collection view. Instead of:
let cell = collectionView.cellForItemAtIndexPath(indexPath) as! UIImageCollectionViewCell
I was getting cell with
let cell = self.collectionView(collectionView, cellForItemAtIndexPath: indexPath) as! UIImageCollectionViewCell
Which obviously resulted in creating a new cell with the wrong frame. My bad, late night coding is not the best thing sometimes. Thanks to Arun Ammannaya for the quick example
This what i tried, works as expected.
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
#IBOutlet var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
return collectionView.dequeueReusableCellWithReuseIdentifier("aaa", forIndexPath: indexPath)
}
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 10
}
#IBAction func frame(sender: AnyObject) {
print(String(sender.frame))
let rect = sender.superview??.convertRect(sender.frame, toView: self.view)
print(String(rect))
let addedView = UIView()
view.addSubview(addedView)
addedView.frame = rect!
addedView.backgroundColor = UIColor.yellowColor()
}
}
Story board looks like:
Output is looks like: