I am making a weather up which consists of two main views.
The first one is the main view, it has a scrollable UICollectionView which has cell that represent small weather cards. The second one, is a different view which has all the weather details shown in a more detail.
I want to create a UIView on top of the collection view and grow it to fill the screen. Sadly I keep failing and cannot find a proper way to do it.
When the cell is pressed, I want to create a UIView on top of the cell (getting it's position on the view) and then growing it to fill the screen.
Where I intend to put the code:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("Cell [\(indexPath.row)] selected.")
// Get cell position on screen:
let attributes = collectionView.layoutAttributesForItem(at: indexPath)
let cellRect = attributes?.frame
let frameCellInSuperview = collectionView.convert(cellRect!, to: collectionView.superview)
print("x: \(Double(frameCellInSuperview.origin.x)), y: \(Double(frameCellInSuperview.origin.y))")
// Create a small UIView in the exact same position
// Grow the UIView to fill the screen
}
you can get current index frame against superview using this code :
let theAttributes:UICollectionViewLayoutAttributes! = collectionView.layoutAttributesForItem(at: indexPath)
let cellFrameInSuperview:CGRect! = collectionView.convert(theAttributes.frame, to: collectionView.superview)
Now create a UIView of same frame . Using this code :
var popView = UIView(frame: cellFrameInSuperview)
self.view.addSubview(popView)
Now animate view by updating frame :
UIView.animateWithDuration(3.0, animations: {
popView.frame = UIScreen.main.bounds
})
Related
I'm pretty new so apologies if my title doesn't phrase things correctly. I've been hacking away and haven't been able to find an answer to this problem.
I have a horizontally scrolling collection. I'm trying to visually show poll results programatically adding CGRect to the relevant item in the collection based on voting data held in a Firestore array.
This is working, but the problem is when you scroll away (so the item in the collection is off screen) and then back, the code to draw the CGRects gets triggered again and more graphics get added to the view. Is there a way to delete these CGRects when the user scrolls an item collection off screen so when the user scrolls the item back into view, code is triggered again it doesn't create duplicates?
Here are a couple of screenshots showing first and second load
Here is my code (cell b is where the CGrect gets triggered)
//COLLECTION VIEW CODE
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if collectionView == self.sentCollectionView {
let cellA = collectionView.dequeueReusableCell(withReuseIdentifier: "sCell", for: indexPath) as! SentCollectionViewCell
cellA.sentQuestionLabel.text = sentMessages[indexPath.row].shoutText
// Set up cell
return cellA
}
else {
let cellB = receivedCollectionView.dequeueReusableCell(withReuseIdentifier: "pCell", for: indexPath) as! ReceivedCollectionViewCell
receivedMessages[indexPath.row].pollTotal = receivedMessages[indexPath.row].pollResults.reduce(0, +)
print("Sum of Array is : ", receivedMessages[indexPath.row].pollTotal!)
cellB.receivedShoutLabel.text = receivedMessages[indexPath.row].shoutText
print(receivedMessages[indexPath.row].pollResults.count)
if receivedMessages[indexPath.row].pollResults != [] {
for i in 0...receivedMessages[indexPath.row].pollResults.count - 1 {
cellB.resultsView.addSubview(sq(pollSum: receivedMessages[indexPath.row].pollTotal!, pollResult: receivedMessages[indexPath.row].pollResults[i]))
}
}
return cellB
}
}
//THIS DRAWS THE CGRECT
func sq(pollSum: Int, pollResult: Int) -> UIView {
// divide the width by total responses
let screenDivisions = Int(view.frame.size.width) / pollSum
// the rectangle top left point x axis position.
let xPos = 0
// the rectangle width.
let rectWidth = pollResult * screenDivisions
// the rectangle height.
let rectHeight = 10
// Create a CGRect object which is used to render a rectangle.
let rectFrame: CGRect = CGRect(x:CGFloat(xPos), y:CGFloat(yPos), width:CGFloat(rectWidth), height:CGFloat(rectHeight))
// Create a UIView object which use above CGRect object.
let greenView = UIView(frame: rectFrame)
// Set UIView background color.
greenView.backgroundColor = UIColor.green
//increment y position
yPos = yPos + 25
return greenView
}
Cells of collection are dequeued dequeueReusableCell , you need to override prepareForReuse
Or set a tag
greenView.tag = 333
And inside cellForItemAt do this
cellB.resultsView.subviews.forEach {
if $0.tag == 333 {
$0.removeFromSuperview()
}
}
if receivedMessages[indexPath.row].pollResults != [] {
for i in 0...receivedMessages[indexPath.row].pollResults.count - 1 {
cellB.resultsView.addSubview(sq(pollSum: receivedMessages[indexPath.row].pollTotal!, pollResult: receivedMessages[indexPath.row].pollResults[i]))
}
}
I have a horizontal collection view, which cells don't cover the whole screen. I want to insert a UIView to a view controller's hierarchy and place it right on top of the selected cell.
In order to do that I need the frame of the selected cell. To get it I've tried the following inside the didSelectItem method:
1)
let frame = collectionViewLayout.layoutAttributesForItem(at: indexPath)?.frame
2)
let selectedCell = collectionView.cellForItem(at: indexPath)
In both cases I get the same result and that's the right one. For inserting a UIView into the hierarchy I use the following code:
self.view.insertSubview(mimicView, aboveSubview: collectionView)
mimicView.frame = frame
print ("mimicView frame:", mimicView.frame)
The print statement prints the right frame (it is the same as the one of the selected cell). However, mimicView position isn't exactly on top of the cell when it is drawn. For the first cell the view's y has a small negative value. For the subsequent cells the view is drawn further and further to the left. So, for example, for the second cell, the view is drawn almost on top of the third one. And for the third one, it is drawn almost on top of the fifth one. In Interface Debugger I see that its x value is way too big and y value is a small negative number (like -20 - -40 points), however, the print statement still shows the same values as for the selected cell.
If someone knows why this is happening, I would really appreciate your help.
Make sure that you convert the cell's bounds to the view's coordinate system (the view to which you want to add the new subview) and use the resulting rectangle as your covering view's frame:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard let cell = collectionView.cellForItem(at: indexPath) else { return }
let cellFrame = cell.convert(cell.bounds, to: view)
let coverView = UIView(frame: cellFrame)
coverView.backgroundColor = .darkGray
view.addSubview(coverView)
}
Hi I am trying to make a home feed like facebook using UICollectionView But in each cell i want to put another collectionView that have 3 cells.
you can clone the project here
I have two bugs the first is when i scroll on the inner collection View the bounce do not bring back the cell to center. when i created the collection view i enabled the paging and set the minimumLineSpacing to 0
i could not understand why this is happening. when i tried to debug I noticed that this bug stops when i remove this line
layout.estimatedItemSize = CGSize(width: cv.frame.width, height: 1)
but removing that line brings me this error
The behavior of the UICollectionViewFlowLayout is not defined because: the item height must be less than the height of the UICollectionView minus the section insets top and bottom values, minus the content insets top and bottom values
because my cell have a dynamic Height
here is an example
my second problem is the text on each inner cell dosent display the good text i have to scroll until the last cell of the inner collection view to see the good text displayed here is an example
You first issue will be solved by setting the minimumInteritemSpacing for the innerCollectionView in the OuterCell. So the definition for innerCollectionView becomes this:
let innerCollectionView : UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
layout.minimumInteritemSpacing = 0
let cv = UICollectionView(frame :.zero , collectionViewLayout: layout)
cv.translatesAutoresizingMaskIntoConstraints = false
cv.backgroundColor = .orange
layout.estimatedItemSize = CGSize(width: cv.frame.width, height: 1)
cv.isPagingEnabled = true
cv.showsHorizontalScrollIndicator = false
return cv
}()
The second issue is solved by adding calls to reloadData and layoutIfNeeded in the didSet of the post property of OuterCell like this:
var post: Post? {
didSet {
if let numLikes = post?.numLikes {
likesLabel.text = "\(numLikes) Likes"
}
if let numComments = post?.numComments {
commentsLabel.text = "\(numComments) Comments"
}
innerCollectionView.reloadData()
self.layoutIfNeeded()
}
}
What you are seeing is related to cell reuse. You can see this in effect if you scroll to the yellow bordered text on the first item and then scroll down. You will see others are also on the yellow bordered text (although at least with the correct text now).
EDIT
As a bonus here is one method to remember the state of the cells.
First you need to track when the position changes so in OuterCell.swft add a new protocol like this:
protocol OuterCellProtocol: class {
func changed(toPosition position: Int, cell: OutterCell)
}
then add an instance variable for a delegate of that protocol to the OuterCell class like this:
public weak var delegate: OuterCellProtocol?
then finally you need to add the following method which is called when the scrolling finishes, calculates the new position and calls the delegate method to let it know. Like this:
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
if let index = self.innerCollectionView.indexPathForItem(at: CGPoint(x: self.innerCollectionView.contentOffset.x + 1, y: self.innerCollectionView.contentOffset.y + 1)) {
self.delegate?.changed(toPosition: index.row, cell: self)
}
}
So that's each cell detecting when the collection view cell changes and informing a delegate. Let's see how to use that information.
The OutterCellCollectionViewController is going to need to keep track the position for each cell in it's collection view and update them when they become visible.
So first make the OutterCellCollectionViewController conform to the OuterCellProtocol so it is informed when one of its
class OutterCellCollectionViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout, OuterCellProtocol {
then add a class instance variable to record the cell positions to OuterCellCollectionViewController like this:
var positionForCell: [Int: Int] = [:]
then add the required OuterCellProtocol method to record the cell position changes like this:
func changed(toPosition position: Int, cell: OutterCell) {
if let index = self.collectionView?.indexPath(for: cell) {
self.positionForCell[index.row] = position
}
}
and finally update the cellForItemAt method to set the delegate for a cell and to use the new cell positions like this:
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "OutterCardCell", for: indexPath) as! OutterCell
cell.post = posts[indexPath.row]
cell.delegate = self
let cellPosition = self.positionForCell[indexPath.row] ?? 0
cell.innerCollectionView.scrollToItem(at: IndexPath(row: cellPosition, section: 0), at: .left, animated: false)
print (cellPosition)
return cell
}
If you managed to get that all setup correctly it should track the positions when you scroll up and down the list.
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)
}
}
I have a UICollectionView that holds a bunch of a photos.
However, if I scroll to the bottom the scrollview does not let me scroll to the bottom of the last few rows (it snaps back). I have tried override the collectionView.contentSize and just adding 1000 to the height but it doesn't fix the problem.
collectionView.contentSize = CGSizeMake(collectionView.contentSize.width, collectionView.contentSize.height + 1000)
Here is a video of the problem:
https://www.youtube.com/watch?v=fH57_pL0OjQ&list=UUIctdpq1Pzujc0u0ixMSeVw
Here is my code to create cells:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
{
var cell = collectionView.dequeueReusableCellWithReuseIdentifier("selectPhotoCell", forIndexPath: indexPath) as B_SelectPhotoControllerViewCell
let reuseCount = ++cell.reuseCount
let asset = currentAssetAtIndex(indexPath.item)
PHImageManager.defaultManager().requestImageForAsset(asset, targetSize:_cellSize, contentMode: .AspectFit, options: nil)//the target size here can be set to CGZero for a super blurry preview
{
result, info in
if reuseCount == cell.reuseCount
{
cell.imageView.image = result
cell.imageView.frame = CGRectMake(0, 0,self._cellSize.width,self._cellSize.height)
}
}
return cell
}
private func currentAssetAtIndex(index:NSInteger)->PHAsset
{
if let fetchResult = _assetsFetchResults
{
return fetchResult[index] as PHAsset
}else
{
return _selectedAssets[index]
}
}
Update:
Because I am adding this as a child view controller, there seems to be some problems with the offsetting of the scrollview. I haven't fixed it yet but when open this view without adding it as a child view to another view controller, the scrollview is the correct size
The problem was I was adding this as a child view controller.
As a result, after doing some animations, the UICollectionView bounds were sizing to the view it was attached to. As a result its height was wrong and hence why it was getting cut off.
I just came across this question from a quick Google and felt I could add something useful.
I am running a segmentedControl with a UIView that changes to different UICollectionViews on the segment change and I couldn't get the collectionView to scroll fully down.
This may not be the solution for all, but I found that if I went to the XIB I was loading in the view and set size to freeform and decrease it by the size of a cell I had removed the problem.
suspect your CollectionView's bottomAchor was not set correctly to the parent uiview's safeAreaLayoutGuide bottomAnchor